From 96e44c0062d399baddec6de52a367d23c808febd Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 7 Jun 2008 00:55:01 +0000 Subject: [PATCH 0001/1195] Added pgtap, with full documentation and tests, w00t! --- Changes | 4 + README.pgtap | 436 +++++++++++++++++++++++++++++++++++++++++++++++++ drop_pgtap.sql | 40 +++++ pgtap.sql | 331 +++++++++++++++++++++++++++++++++++++ test.sql | 215 ++++++++++++++++++++++++ 5 files changed, 1026 insertions(+) create mode 100644 Changes create mode 100644 README.pgtap create mode 100644 drop_pgtap.sql create mode 100644 pgtap.sql create mode 100644 test.sql diff --git a/Changes b/Changes new file mode 100644 index 000000000000..4024a8efb73a --- /dev/null +++ b/Changes @@ -0,0 +1,4 @@ +Revision history for PostgreSQL data type LCTEXT. + +0.01 + - Initial public release. diff --git a/README.pgtap b/README.pgtap new file mode 100644 index 000000000000..b918a6378904 --- /dev/null +++ b/README.pgtap @@ -0,0 +1,436 @@ +pgTAP 0.01 +========== + +pgTAP is a collection of TAP-emitting unit testing functions written in +PL/pgSQL and PL/SQL. It is based on the Test::More module distributed with +Perl 5. You could even consider it a port of sorts. + +Installation +============ + +To install pgTAP into a PostgreSQL database, just do this: + + psql dbname -f pgtap.sql + +If you want pgTAP to be available to all new databases, install it into the +template1 dtabase: + + psql template1 -f pgtap.sql + +If you want to remove pgTAP from a database, run the drop_pgtap.sql script: + + psql dbname -f drop_pgtap.sql + +Running pgTAP Tests +=================== + +You can also distribute these two files with any PostgreSQL distribution, such +as a custom data type. For such a case, if your users want to run your test +suite, just load the functions in your test.sql file and then drop them at the +end. Here's an example: + + -- Load the TAP functions. + \i pgtap.sql + + -- Plan the tests. + SELECT plan(1); + + -- Run the tests. + SELECT pass( 'My test passed, w00t!' ); + + -- Finish the tests and clean up. + SELECT * FROM finish(); + \i drop_pgtap.sql + +Note that pgtap.sql sets up some variables to make things output from psql in +a that a TAP parser can parse. If you're not using pgtap.sql, perhaps because +you've installed pgTAP and can just use its functions, you'll still need to +set up your test script to properly output TAP. Don't worry, this is easy. +Just write your test script like so: + + \set ON_ERROR_STOP 1 + \pset format unaligned + \pset tuples_only + \pset pager + \pset null '[NULL]' + + -- Plan the tests. + SELECT plan(1); + + -- Run the tests. + SELECT pass( 'My test passed, w00t!' ); + + -- Finish the tests. + SELECT * FROM finish(); + +In either case, now you're ready to run your test script! + + % psql try < test.sql + 1..1 + ok 1 - My test passed, w00t! + +Yep, that's all there is to it. + +Description +=========== + +The purpose of pgTAP is to provide a wide range of testing utilities that +output TAP. TAP, or the "Test Anything Protocol", is an emerging standard for +representing the output from unit tests. It owes its success to its format as +a simple text-based interface that allows for practical machine parsing and +high legibility for humans. TAP started life as part of the test harness for +Perl but now has implementations in C/C++, Python, PHP, JavaScript, Perl, and +now PostgreSQL. + +I love it when a plan comes together +------------------------------------ + +Before anything else, you need a testing plan. This basically declares how +many tests your script is going to run to protect against premature failure. + +The preferred way to do this is to declare a plan by calling the plan() function: + + SELECT plan( 42 ); + +There are rare cases when you will not know beforehand how many tests your +script is going to run. In this case, you can declare that you have no plan. +(Try to avoid using this as it weakens your test.) + + SELECT no_plan(); + +Often, though, you'll be able to calculate the number of tests, like so: + + SELECT plan( COUNT(*) ) + FROM foo; + +At the end of your script, you should always tell pgTAP that the tests have +completed, so that it can output any diagnostics about failures or a +discrepancy between the planned number of tests and the number actually run: + + SELECT * FROM finish ( ); + +Test names +---------- + +By convention, each test is assigned a number in order. This is largely done +automatically for you. However, it's often very useful to assign a name to +each test. Would you rather see this? + + ok 4 + not ok 5 + ok 6 + +Or this? + + ok 4 - basic multi-variable + not ok 5 - simple exponential + ok 6 - force == mass * acceleration + +The later gives you some idea of what failed. It also makes it easier to find +the test in your script, simply search for "simple exponential". + +All test functions take a name argument. It's optional, but highly suggested +that you use it. + +I'm ok, you're not ok +--------------------- + +The basic purpose of pgTAP -- and of any TAP-emitting test framework, for that +matter -- is to print out either "ok #" or "not ok #", depending on whether a +given test succeeded or failed. Everything else is just gravy. + +All of the following functions return "ok" or "not ok" depending on whether +the test succeeded or failed. + +* ok ( boolean, text ) +* ok ( boolean ) + + SELECT ok( :this = :that, :test_name ); + + This function simply evaluates any expression `:this = :that` is just a + simple example) and uses that to determine if the test succeeded or + failed. A true expression passes, a false one fails. Very simple. + + For example: + + SELECT ok( 9 ^ 2 = 81, 'simple exponential' ); + SELECT ok( 9 < 10, 'simple comparison' ); + SELECT ok( 'foo' ~ '^f', 'simple regex' ); + SELECT ok( active = 1, 'widget active' ) + FROM widgets; + + (Mnemonic: "This is ok.") + + :test_name is a very short description of the test that will be printed + out. It makes it very easy to find a test in your script when it fails and + gives others an idea of your intentions. :test_name is optional, but we + *very* strongly encourage its use. + + Should an ok() fail, it will produce some diagnostics: + + not ok 18 - sufficient mucus + # Failed test 18: "sufficient mucus" + +* is (anyelement, anyelement, text) +* is (anyelement, anyelement ) +* isnt (anyelement, anyelement, text) +* isnt (anyelement, anyelement ) + + SELECT is ( :this, :that, $test_name ); + SELECT isnt( :this, :that, $test_name ); + + Similar to ok(), is() and isnt() compare their two arguments with `=` and + `<>` respectively and use the result of that to determine if the test + succeeded or failed. So these: + + -- Is the ultimate answer 42? + SELECT is( ultimate_answer(), 42, 'Meaning of Life' ); + + -- foo() doesn't return empty + SELECT isnt( foo(), '', 'Got some foo' ); + + are similar to these: + + SELECT ok( ultimate_answer() = 42, 'Meaning of Life' ); + SELECT isnt( foo() <> '', 'Got some foo' ); + + (Mnemonic: "This is that." "This isn't that.") + + So why use these? They produce better diagnostics on failure. ok() cannot + know what you are testing for (beyond the name), but is() and isnt() know + what the test was and why it failed. For example this test: + + \set foo '\'' waffle '\'' + \set bar '\'' yarblokos '\'' + SELECT is( :foo::text, :bar::text, 'Is foo the same as bar?' ); + + Will produce something like this: + + # Failed test 17: "Is foo the same as bar?" + # got: waffle + # expected: yarblokos + + So you can figure out what went wrong without re-running the test. + + You are encouraged to use is() and isnt() over ok() where possible, + however do not be tempted to use them to find out if something is true or + false! + + -- XXX BAD! + SELECT is( is_valid(9), TRUE, 'A tree grows in Brooklyn' ); + + This does not check if `is_valid(9)` is true, it checks if it *returns* + TRUE. Very different. Similar caveats exist for FALSE. In these cases, use + ok(). + + SELECT ok( is_valid(9), 'A tree grows in Brooklyn' ); + +* matches ( anyelement, text, text ) +* matches ( anyelement, text ) + + SELECT matches( :this, '^that', :test_name ); + + Similar to ok(), matches() matches :this against the regex `/^that/`. + + So this: + + SELECT matches( :this, '^that', 'this is like that' ); + + is similar to: + + SELECT ok( :this ~ '^that', 'this is like that' ); + + (Mnemonic "This matches that".) + + Its advantages over ok() are similar to that of is() and isnt(): Better + diagnostics on failure. + +* imatches ( anyelement, text, text ) +* imatches ( anyelement, text ) + + SELECT imatches( :this, '^that', :test_name ); + + These are just like matches() except that the regular expression is + case-insensitive. + +* doesnt_match ( anyelement, text, text ) +* doesnt_match ( anyelement, text ) +* doesnt_imatch ( anyelement, text, text ) +* doesnt_imatch ( anyelement, text ) + + SELECT doesnt_match( :this, '^that', :test_name ); + + These functions work exactly as matches() and imataches() do, only they + check if :this *does not* match the given pattern. + +* alike ( anyelement, text, text ) +* alike ( anyelement, text ) +* ialike ( anyelement, text, text ) +* ialike ( anyelement, text ) + + SELECT alike( :this, 'that%', :test_name ); + + Similar to ok(), alike() matches :this against the SQL LIKE pattern + 'that%'. ialike() matches case-insensitively. + + So this: + + SELECT ialike( :this, 'that%', 'this is alike that' ); + + is similar to: + + SELECT ok( :this ILIKE 'that%', 'this is like that' ); + + (Mnemonic "This is like that".) + + Its advantages over ok() are similar to that of is() and isnt(): Better + diagnostics on failure. + +* unalike ( anyelement, text, text ) +* unalike ( anyelement, text ) +* unialike ( anyelement, text, text ) +* unialike ( anyelement, text ) + + SELECT unalike( :this, 'that%', :test_name ); + + Works exactly as alike(), only it checks if :this *does not* match the + given pattern. + +* pass ( text ) +* pass ( ) +* fail ( text ) +* fail ( ) + + SELECT pass( :test_name ); + SELECT fail( :test_name ); + + Sometimes you just want to say that the tests have passed. Usually the + case is you've got some complicated condition that is difficult to wedge + into an ok(). In this case, you can simply use pass() (to declare the test + ok) or fail() (for not ok). They are synonyms for ok(1) and ok(0). + + Use these functions very, very, very sparingly. + +* throws_ok ( text, char(5), text ) +* throws_ok ( text, char(5) ) +* throws_ok ( text ) + + SELECT throws_ok( + 'INSERT INTO try (id) VALUES (1)', + '23505', + 'We should get a unique violation for a duplicate PK' + ); + + When you want to make sure that an exception is thrown by PostgreSQL under + certain circumstances, use throws_ok() to test for those circumstances. + + The first argument should be a string representing the query to be + executed. throws_ok() will use the PL/pgSQL `EXECUTE` statement to execute + it and catch any exception. + + The second argument should be an exception error code. If this value is + not NULL, throws_ok() will check the thrown exception to ensure that it is + the expected exception. For a complete list of error codes, see: + + http://www.postgresql.org/docs/current/static/errcodes-appendix.html + + The third argument is of course a brief test name. + + Should a throws_ok() test fail it produces appropriate diagnostic + messages. For example: + + not ok 81 - simple error + # Failed test "simple error" + # caught: 23505: duplicate key value violates unique constraint "try_pkey" + # expected: 23506 + + Idea borrowed from the Test::Exception Perl module. + +* lives_ok ( text, text ) +* lives_ok ( text ) + + SELECT lives_ok( + 'INSERT INTO try (id) VALUES (1)', + 'We should not get a unique violation for a new PK' + ); + + The inverse of throws_ok(), these functions test to ensure that a SQL + statement does *not* throw an exception. Should a lives_ok() test faile, + it produces appropriate diagnostic messages. For example: + + not ok 85 - simple success + # Failed test "simple success" + # died: 23505: duplicate key value violates unique constraint "try_pkey" + + Idea borrowed from the Test::Exception Perl module. + +Diagnostics +----------- + +If you pick the right test function, you'll usually get a good idea of what +went wrong when it failed. But sometimes it doesn't work out that way. So here +we have ways for you to write your own diagnostic messages which are safer +than just \echo. + +* diag ( text ) + + Returns a diagnostic message which is guaranteed not to interfere with + test output. Handy for this sort of thing: + + -- Output a diagnostic message if the collation is not en_US.UTF-8. + SELECT diag( + E'These tests expect LC_COLLATE to be en_US.UTF-8,\n' + || 'but yours is set to ' || setting || E'.\n' + || 'As a result, some tests may fail. YMMV.' + ) + FROM pg_settings + WHERE name = 'lc_collate' + AND setting <> 'en_US.UTF-8'; + + Which would produce: + + # These tests expect LC_COLLATE to be en_US.UTF-8, + # but yours is set to en_US.UTF-8. + # As a result, some tests may fail. YMMV. + +Suported Versions +----------------- + +pgTAP has been tested on the following builds of PostgreSQL: + +* PostgreSQL 8.3.1 on i386-apple-darwin9.2.2 + +If you know of others, please submit them! Use +psql template1 -c 'SELECT VERSION()' to get the formal build version and OS. + +Author +------ +David E. Wheeler + +Credits +------- + +* Michael Schwern and chromatic for Test::More. +* Adrian Howard for Test::Exception. + +Copyright and License +--------------------- + +Copyright (c) 2008 David Wheeler. Some rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose, without fee, and without a written agreement is +hereby granted, provided that the above copyright notice and this paragraph +and the following two paragraphs appear in all copies. + +IN NO EVENT SHALL KINETICODE BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING +OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF KINETICODE HAS +BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +KINETICODE SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND +KINETICODE HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, +ENHANCEMENTS, OR MODIFICATIONS. + diff --git a/drop_pgtap.sql b/drop_pgtap.sql new file mode 100644 index 000000000000..74f83ff209e6 --- /dev/null +++ b/drop_pgtap.sql @@ -0,0 +1,40 @@ +DROP FUNCTION lives_ok ( TEXT ); +DROP FUNCTION lives_ok ( TEXT, TEXT ); +DROP FUNCTION throws_ok ( TEXT, CHAR(5) ); +DROP FUNCTION throws_ok ( TEXT, CHAR(5), TEXT ); +DROP FUNCTION fail ( ); +DROP FUNCTION fail ( text ); +DROP FUNCTION pass ( ); +DROP FUNCTION pass ( text ); +DROP FUNCTION unialike ( anyelement, text ); +DROP FUNCTION unialike ( anyelement, text, text ); +DROP FUNCTION unalike ( anyelement, text ); +DROP FUNCTION unalike ( anyelement, text, text ); +DROP FUNCTION doesnt_imatch ( anyelement, text ); +DROP FUNCTION doesnt_imatch ( anyelement, text, text ); +DROP FUNCTION doesnt_match ( anyelement, text ); +DROP FUNCTION doesnt_match ( anyelement, text, text ); +DROP FUNCTION _unalike ( boolean, anyelement, text, text ); +DROP FUNCTION ialike ( anyelement, text ); +DROP FUNCTION ialike ( anyelement, text, text ); +DROP FUNCTION alike ( anyelement, text ); +DROP FUNCTION alike ( anyelement, text, text ); +DROP FUNCTION imatches ( anyelement, text ); +DROP FUNCTION imatches ( anyelement, text, text ); +DROP FUNCTION matches ( anyelement, text ); +DROP FUNCTION matches ( anyelement, text, text ); +DROP FUNCTION _alike ( boolean, anyelement, text, text ); +DROP FUNCTION isnt (anyelement, anyelement ); +DROP FUNCTION isnt (anyelement, anyelement, text); +DROP FUNCTION is (anyelement, anyelement ); +DROP FUNCTION is (anyelement, anyelement, text); +DROP FUNCTION ok ( boolean ); +DROP FUNCTION ok ( boolean, text ); +DROP FUNCTION diag ( text ); +DROP FUNCTION finish ( ); +DROP FUNCTION plan ( integer ); +DROP FUNCTION no_plan ( ); +DROP FUNCTION num_failed (); +DROP FUNCTION add_result ( boolean, bool, text, text, text ); +DROP FUNCTION _set ( text, integer ); +DROP FUNCTION _get ( text ); diff --git a/pgtap.sql b/pgtap.sql new file mode 100644 index 000000000000..78518d13b3a0 --- /dev/null +++ b/pgtap.sql @@ -0,0 +1,331 @@ +SET client_min_messages = warning; +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT off +\pset format unaligned +\pset tuples_only +\pset pager +\pset null '[NULL]' + +CREATE TEMP TABLE __tcache__ ( + label TEXT NOT NULL, + value integer NOT NULL +); + +CREATE TEMP TABLE __tresults__ ( + numb SERIAL PRIMARY KEY, + ok BOOLEAN NOT NULL DEFAULT TRUE, + aok BOOLEAN NOT NULL DEFAULT TRUE, + descr TEXT NOT NULL DEFAULT '', + type TEXT NOT NULL DEFAULT '', + reason TEXT NOT NULL DEFAULT '' +); + +CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ + SELECT value FROM __tcache__ WHERE label = $1 LIMIT 1; +$$ LANGUAGE SQL strict; + +CREATE OR REPLACE FUNCTION _set ( text, integer ) RETURNS integer AS $$ +BEGIN + UPDATE __tcache__ SET value = $2 WHERE label = $1; + IF NOT FOUND THEN + INSERT INTO __tcache__ values ($1, $2); + END IF; + RETURN $2; +END; +$$ LANGUAGE plpgsql strict; + +CREATE OR REPLACE FUNCTION add_result ( boolean, bool, text, text, text ) +RETURNS integer AS $$ +BEGIN + INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) + VALUES( $1, $2, COALESCE($3, ''), $4, $5 ); + RETURN lastval(); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ + SELECT COUNT(*)::INTEGER FROM __tresults__ WHERE ok = FALSE; +$$ LANGUAGE SQL strict; + +CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ +BEGIN + UPDATE __tcache__ SET value = $1 WHERE label = 'plan'; + IF NOT FOUND THEN + INSERT INTO __tcache__ VALUES ( 'plan', $1 ); + END IF; + RETURN '1..' || $1; +END; +$$ LANGUAGE plpgsql strict; + +CREATE OR REPLACE FUNCTION no_plan( ) RETURNS TEXT AS $$ + SELECT plan(0); +$$ LANGUAGE SQL strict; + +CREATE OR REPLACE FUNCTION finish () RETURNS SETOF TEXT AS $$ +DECLARE + curr_test integer = _get('curr_test'); + exp_tests integer = _get('plan'); + num_failed integer = num_failed(); + plural char = CASE exp_tests WHEN 1 THEN 's' ELSE '' END; +BEGIN + + IF curr_test IS NULL THEN + RAISE EXCEPTION '%', diag( 'No tests run!' ); + END IF; + + IF exp_tests = 0 THEN + -- No plan. + exp_tests = curr_test; + END IF; + + IF curr_test < exp_tests THEN + RETURN QUERY SELECT diag( + 'Looks like you planned ' || exp_tests || ' test' || + plural || ' but only ran ' || curr_test + ); + ELSIF curr_test > exp_tests THEN + RETURN QUERY SELECT diag( + 'Looks like you planned ' || exp_tests || ' test' || + plural || ' but ran ' || curr_test - exp_tests || ' extra' + ); + ELSIF num_failed > 0 THEN + RETURN QUERY SELECT diag( + 'Looks like you failed ' || num_failed || ' test' || + plural || ' of ' || exp_tests + ); + ELSE + + END IF; + RETURN; +END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION diag ( msg text ) RETURNS TEXT AS $$ +BEGIN + RETURN regexp_replace( msg, '^', '# ', 'gn' ); +END; +$$ LANGUAGE plpgsql strict; + +CREATE OR REPLACE FUNCTION ok ( ok boolean, descr text ) RETURNS TEXT AS $$ +DECLARE + test_num integer; +BEGIN + 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, ok, descr, '', '' ); + + RETURN (CASE ok WHEN TRUE THEN '' ELSE 'not ' END) + || 'ok ' || _set( 'curr_test', test_num ) + || CASE descr WHEN '' THEN '' ELSE COALESCE( ' - ' || descr, '' ) END + || CASE ok WHEN TRUE THEN '' ELSE E'\n' || + diag('Failed test ' || test_num || + CASE descr WHEN '' THEN '' ELSE COALESCE(': "' || descr || '"', '') 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 := $1 = $2; + output text := ok( result, $3); +BEGIN + RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( + ' got: ' || COALESCE( $1::text, 'NULL' ) || + E'\n expected: ' || COALESCE( $2::text, 'NULL' ) + ) 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 := $1 <> $2; + output text := ok( result, $3 ); +BEGIN + RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( + ' ' || COALESCE( $1::text, 'NULL' ) || + E'\n <>' || + E'\n ' || COALESCE( $2::text, 'NULL' ) + ) 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 ( + result boolean, + got anyelement, + rx text, + descr text +) RETURNS TEXT AS $$ +DECLARE + output text := ok( result, descr); +BEGIN + 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 ( + result boolean, + got anyelement, + rx text, + descr text +) RETURNS TEXT AS $$ +DECLARE + output text := ok( result, descr); +BEGIN + 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 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 throws_ok ( + code TEXT, + err CHAR(5), + msg TEXT +) RETURNS TEXT AS $$ +DECLARE + descr TEXT := COALESCE( msg, 'threw ' || COALESCE( err, 'an exception' ) ); +BEGIN + EXECUTE code; + RETURN ok( FALSE, descr ) || E'\n' || diag( + ' caught: no exception' || + E'\n expected: ' || COALESCE( err, 'an exception' ) + ); +EXCEPTION WHEN OTHERS THEN + IF err IS NULL OR SQLSTATE = err THEN + -- The expected error was thrown. + RETURN ok( TRUE, descr ); + ELSE + -- This was not the expected error. + RETURN ok( FALSE, descr ) || E'\n' || diag( + ' caught: ' || SQLSTATE || ': ' || SQLERRM || + E'\n expected: ' || COALESCE( err, 'an exception') + ); + END IF; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION throws_ok ( TEXT, CHAR(5) ) RETURNS TEXT AS $$ + SELECT throws_ok( $1, $2, NULL ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION throws_ok ( TEXT ) RETURNS TEXT AS $$ + SELECT throws_ok( $1, NULL, NULL ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION lives_ok ( + code TEXT, + descr TEXT +) RETURNS TEXT AS $$ +BEGIN + EXECUTE code; + RETURN ok( TRUE, descr ); +EXCEPTION WHEN OTHERS THEN + -- There should have been no exception. + RETURN ok( FALSE, descr ) || E'\n' || diag( + ' died: ' || SQLSTATE || ': ' || SQLERRM + ); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION lives_ok ( TEXT ) RETURNS TEXT AS $$ + SELECT lives_ok( $1, NULL ); +$$ LANGUAGE SQL; diff --git a/test.sql b/test.sql new file mode 100644 index 000000000000..c4a6aee8714a --- /dev/null +++ b/test.sql @@ -0,0 +1,215 @@ +-- +-- Tests for pgTAP. +-- +-- + +-- Load the TAP functions. +\i pgtap.sql +\set numb_tests 0 + +-- Plan the tests. +SELECT plan(:numb_tests); + +/****************************************************************************/ +-- Test pass(). +SELECT pass( 'My pass() passed, w00t!' ); + +-- Test fail(). +\set fail_numb 2 +\echo ok :fail_numb - Testing fail() +SELECT is( + fail('oops'), + E'not ok 2 - oops\n# Failed test 2: "oops"', 'We should get the proper output from fail()'); + +-- Check the finish() output. +SELECT is( + (SELECT * FROM finish() LIMIT 1), + '# Looks like you failed 1 test of 3', + 'The output of finish() should reflect the test failure' +); + +/****************************************************************************/ +-- Check num_failed +SELECT is( num_failed(), 1, 'We should have one failure' ); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb = :fail_numb; +SELECT is( num_failed(), 0, 'We should now have no failures' ); + +/****************************************************************************/ +-- Check diag. +SELECT is( diag('foo'), '# foo', 'diag() should work properly' ); +SELECT is( diag(E'foo\nbar'), E'# foo\n# bar', 'multiline diag() should work properly' ); + +/****************************************************************************/ +-- Check no_plan. +SELECT is ( no_plan(), '1..0', 'no_plan() should plan 0 tests' ); +SELECT is( value, 0, 'no_plan() should have stored a plan of 0' ) + FROM __tcache__ + WHERE label = 'plan'; + +-- Set the plan to a high number. +SELECT is( plan(4000), '1..4000', 'Set the plan to 4000' ); +SELECT is( + (SELECT * FROM finish() LIMIT 1), + '# Looks like you planned 4000 test but only ran 11', + 'The output of finish() should reflect a high test plan' +); + +-- Set the plan to a low number. +SELECT is( plan(4), '1..4', 'Set the plan to 4' ); +SELECT is( + (SELECT * FROM finish() LIMIT 1), + '# Looks like you planned 4 test but ran 9 extra', + 'The output of finish() should reflect a low test plan' +); + +-- Reset the original plan. +SELECT is( plan(:numb_tests), '1..' || :numb_tests, 'Reset the plan' ); +SELECT is( value, :numb_tests, 'plan() should have stored the test count' ) + FROM __tcache__ + WHERE label = 'plan'; + +/****************************************************************************/ +-- Test ok() +\echo ok 17 - ok() success +SELECT is( ok(true), 'ok 17', 'ok(true) should work' ); +\echo ok 19 - ok() success 2 +SELECT is( ok(true, ''), 'ok 19', 'ok(true, '''') should work' ); +\echo ok 21 - ok() success 3 +SELECT is( ok(true, 'foo'), 'ok 21 - foo', 'ok(true, ''foo'') should work' ); + +\echo ok 23 - ok() failure +SELECT is( ok(false), E'not ok 23\n# Failed test 23', 'ok(false) should work' ); +\echo ok 25 - ok() failure 2 +SELECT is( ok(false, ''), E'not ok 25\n# Failed test 25', 'ok(false, '''') should work' ); +\echo ok 27 - ok() failure 3 +SELECT is( ok(false, 'foo'), E'not ok 27 - foo\n# Failed test 27: "foo"', 'ok(false, ''foo'') should work' ); + +-- Clean up the failed test results. +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 23, 25, 27); + +/****************************************************************************/ +-- Test is(). +\echo ok 29 - is() success +SELECT is( is(1, 1), 'ok 29', 'isa(1, 1) should work' ); +\echo ok 31 - is() success 2 +SELECT is( is('x'::text, 'x'::text), 'ok 31', 'is(''x'', ''x'') should work' ); +\echo ok 33 - is() success 3 +SELECT is( is(1.1, 1.10), 'ok 33', 'is(1.1, 1.10) should work' ); +\echo ok 35 - is() success 4 +SELECT is( is(1.1, 1.10), 'ok 35', 'is(1.1, 1.10) should work' ); +\echo ok 37 - is() success 5 +SELECT is( is(true, true), 'ok 37', 'is(true, true) should work' ); +\echo ok 39 - is() success 6 +SELECT is( is(false, false), 'ok 39', 'is(false, false) should work' ); +--SELECT is( '12:45'::time, '12:45'::time, 'ok 41', 'is(time, time) should work' ); +\echo ok 41 - is() success 7 +SELECT is( is(1, 1, 'foo'), 'ok 41 - foo', 'is(1, 1, ''foo'') should work' ); +\echo ok 43 - is() failure +SELECT is( is( 1, 2 ), E'not ok 43\n# Failed test 43\n# got: 1\n# expected: 2', 'is(1, 2) should work' ); + +/****************************************************************************/ +-- Test isnt(). +\echo ok 45 - isnt() success +SELECT is( isnt(1, 2), 'ok 45', 'isnt(1, 2) should work' ); +\echo ok 47 - isnt() failure +SELECT is( isnt( 1, 1 ), E'not ok 47\n# Failed test 47\n# 1\n# <>\n# 1', 'is(1, 2) should work' ); + +-- Clean up the failed test results. +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 43, 47 ); + +/****************************************************************************/ +-- Try using variables. +\set foo '\'' waffle '\'' +\set bar '\'' waffle '\'' +SELECT is( :foo::text, :bar::text, 'is() should work with psql variables' ); + +/****************************************************************************/ +-- Test matches(). +SELECT matches( 'foo'::text, 'o', 'matches() should work' ); +SELECT matches( 'foo'::text, '^fo', 'matches() should work with a regex' ); +SELECT imatches( 'FOO'::text, '^fo', 'imatches() should work with a regex' ); + +-- Check matches() diagnostics. +\echo ok 53 - matches() failure +SELECT is( matches( 'foo'::text, '^a' ), E'not ok 53\n# Failed test 53\n# ''foo''\n# doesn''t match: ''^a''', 'Check matches diagnostics' ); + +-- Check doesnt_match. +SELECT doesnt_match( 'foo'::text, 'a', 'doesnt_match() should work' ); +SELECT doesnt_match( 'foo'::text, '^o', 'doesnt_match() should work with a regex' ); +SELECT doesnt_imatch( 'foo'::text, '^o', 'doesnt_imatch() should work with a regex' ); + +-- Check doesnt_match diagnostics. +\echo ok 58 - doesnt_match() failure +SELECT is( + doesnt_match( 'foo'::text, 'o' ), + E'not ok 58\n# Failed test 58\n# ''foo''\n# matches: ''o''', + 'doesnt_match() should work' +); + +-- Clean up the failed test results. +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 53, 58 ); + +/****************************************************************************/ +-- Test alike(). +SELECT alike( 'foo'::text, 'foo', 'alike() should work' ); +SELECT alike( 'foo'::text, 'fo%', 'alike() should work with a regex' ); +SELECT ialike( 'FOO'::text, 'fo%', 'ialike() should work with a regex' ); + +-- Check alike() diagnostics. +\echo ok 63 - alike() failure +SELECT is( alike( 'foo'::text, 'a%'::text ), E'not ok 63\n# Failed test 63\n# ''foo''\n# doesn''t match: ''a%''', 'Check alike diagnostics' ); + +-- Test unalike(). +SELECT unalike( 'foo'::text, 'f', 'unalike() should work' ); +SELECT unalike( 'foo'::text, 'f%i', 'unalike() should work with a regex' ); +SELECT unialike( 'FOO'::text, 'f%i', 'iunalike() should work with a regex' ); + +-- Check unalike() diagnostics. +\echo ok 68 - unalike() failure +SELECT is( unalike( 'foo'::text, 'f%'::text ), E'not ok 68\n# Failed test 68\n# ''foo''\n# matches: ''f%''', 'Check unalike diagnostics' ); + +-- Clean up the failed test results. +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 63, 68 ); + +/****************************************************************************/ +-- test throws_ok(). +SELECT throws_ok( 'SELECT 1 / 0', '22012', 'throws_ok(1/0) should work' ); + +-- Check its diagnostics for an invalid error code. +\echo ok 71 - throws_ok failure diagnostics +SELECT is( + throws_ok( 'SELECT 1 / 0', '97212' ), + E'not ok 71 - threw 97212\n# Failed test 71: "threw 97212"\n# caught: 22012: division by zero\n# expected: 97212', + 'We should get the proper diagnostics from throws_ok()' +); + +SELECT throws_ok( 'SELECT 1 / 0', NULL, 'throws_ok(1/0, NULL) should work' ); + +-- Check its diagnostics no error. +\echo ok 74 - throws_ok failure diagnostics +SELECT is( + throws_ok( 'SELECT 1', NULL ), + E'not ok 74 - threw an exception\n# Failed test 74: "threw an exception"\n# caught: no exception\n# expected: an exception', + 'We should get the proper diagnostics from throws_ok() with a NULL error code' +); + +-- Clean up the failed test results. +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 71, 74 ); + +/****************************************************************************/ +-- test lives_ok(). +SELECT lives_ok( 'SELECT 1', 'lives_ok() should work' ); + +-- Check its diagnostics when there is an exception. +\echo ok 77 - lives_ok failure diagnostics +SELECT is( + lives_ok( 'SELECT 1 / 0' ), + E'not ok 77\n# Failed test 77\n# died: 22012: division by zero', + 'We should get the proper diagnostics for a lives_ok() failure' +); + +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 77 ); + +-- Finish the tests and clean up. +SELECT * FROM finish(); +\i drop_pgtap.sql From a854b5440ce78c03125f007b6055503f5b9e0204 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 7 Jun 2008 00:55:37 +0000 Subject: [PATCH 0002/1195] Copy-and-paste-o. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 4024a8efb73a..65c6f4ba9722 100644 --- a/Changes +++ b/Changes @@ -1,4 +1,4 @@ -Revision history for PostgreSQL data type LCTEXT. +Revision history for pgTAP 0.01 - Initial public release. From 72cebbfaff617834c7aede11f6a1b44c50e1216f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 7 Jun 2008 03:35:40 +0000 Subject: [PATCH 0003/1195] Converted the documentation to Markdown. --- README.pgtap | 409 +++++++++++++++++++++++++-------------------------- 1 file changed, 203 insertions(+), 206 deletions(-) diff --git a/README.pgtap b/README.pgtap index b918a6378904..740f61cc46ff 100644 --- a/README.pgtap +++ b/README.pgtap @@ -10,16 +10,16 @@ Installation To install pgTAP into a PostgreSQL database, just do this: - psql dbname -f pgtap.sql + psql dbname -f pgtap.sql If you want pgTAP to be available to all new databases, install it into the template1 dtabase: - psql template1 -f pgtap.sql + psql template1 -f pgtap.sql If you want to remove pgTAP from a database, run the drop_pgtap.sql script: - psql dbname -f drop_pgtap.sql + psql dbname -f drop_pgtap.sql Running pgTAP Tests =================== @@ -29,18 +29,18 @@ as a custom data type. For such a case, if your users want to run your test suite, just load the functions in your test.sql file and then drop them at the end. Here's an example: - -- Load the TAP functions. - \i pgtap.sql + -- Load the TAP functions. + \i pgtap.sql - -- Plan the tests. - SELECT plan(1); + -- Plan the tests. + SELECT plan(1); - -- Run the tests. - SELECT pass( 'My test passed, w00t!' ); + -- Run the tests. + SELECT pass( 'My test passed, w00t!' ); - -- Finish the tests and clean up. - SELECT * FROM finish(); - \i drop_pgtap.sql + -- Finish the tests and clean up. + SELECT * FROM finish(); + \i drop_pgtap.sql Note that pgtap.sql sets up some variables to make things output from psql in a that a TAP parser can parse. If you're not using pgtap.sql, perhaps because @@ -48,26 +48,26 @@ you've installed pgTAP and can just use its functions, you'll still need to set up your test script to properly output TAP. Don't worry, this is easy. Just write your test script like so: - \set ON_ERROR_STOP 1 - \pset format unaligned - \pset tuples_only - \pset pager - \pset null '[NULL]' + \set ON_ERROR_STOP 1 + \pset format unaligned + \pset tuples_only + \pset pager + \pset null '[NULL]' - -- Plan the tests. - SELECT plan(1); + -- Plan the tests. + SELECT plan(1); - -- Run the tests. - SELECT pass( 'My test passed, w00t!' ); + -- Run the tests. + SELECT pass( 'My test passed, w00t!' ); - -- Finish the tests. - SELECT * FROM finish(); + -- Finish the tests. + SELECT * FROM finish(); In either case, now you're ready to run your test script! - % psql try < test.sql - 1..1 - ok 1 - My test passed, w00t! + % psql try < test.sql + 1..1 + ok 1 - My test passed, w00t! Yep, that's all there is to it. @@ -90,7 +90,7 @@ many tests your script is going to run to protect against premature failure. The preferred way to do this is to declare a plan by calling the plan() function: - SELECT plan( 42 ); + SELECT plan( 42 ); There are rare cases when you will not know beforehand how many tests your script is going to run. In this case, you can declare that you have no plan. @@ -100,14 +100,14 @@ script is going to run. In this case, you can declare that you have no plan. Often, though, you'll be able to calculate the number of tests, like so: - SELECT plan( COUNT(*) ) - FROM foo; + SELECT plan( COUNT(*) ) + FROM foo; At the end of your script, you should always tell pgTAP that the tests have completed, so that it can output any diagnostics about failures or a discrepancy between the planned number of tests and the number actually run: - SELECT * FROM finish ( ); + SELECT * FROM finish ( ); Test names ---------- @@ -116,15 +116,15 @@ By convention, each test is assigned a number in order. This is largely done automatically for you. However, it's often very useful to assign a name to each test. Would you rather see this? - ok 4 - not ok 5 - ok 6 + ok 4 + not ok 5 + ok 6 Or this? - ok 4 - basic multi-variable - not ok 5 - simple exponential - ok 6 - force == mass * acceleration + ok 4 - basic multi-variable + not ok 5 - simple exponential + ok 6 - force == mass * acceleration The later gives you some idea of what failed. It also makes it easier to find the test in your script, simply search for "simple exponential". @@ -142,227 +142,225 @@ given test succeeded or failed. Everything else is just gravy. All of the following functions return "ok" or "not ok" depending on whether the test succeeded or failed. -* ok ( boolean, text ) -* ok ( boolean ) +### ok ( boolean, text ) ### +### ok ( boolean ) ### - SELECT ok( :this = :that, :test_name ); + SELECT ok( :this = :that, :test_name ); - This function simply evaluates any expression `:this = :that` is just a - simple example) and uses that to determine if the test succeeded or - failed. A true expression passes, a false one fails. Very simple. +This function simply evaluates any expression `:this = :that` is just a +simple example) and uses that to determine if the test succeeded or +failed. A true expression passes, a false one fails. Very simple. - For example: +For example: - SELECT ok( 9 ^ 2 = 81, 'simple exponential' ); - SELECT ok( 9 < 10, 'simple comparison' ); - SELECT ok( 'foo' ~ '^f', 'simple regex' ); - SELECT ok( active = 1, 'widget active' ) - FROM widgets; + SELECT ok( 9 ^ 2 = 81, 'simple exponential' ); + SELECT ok( 9 < 10, 'simple comparison' ); + SELECT ok( 'foo' ~ '^f', 'simple regex' ); + SELECT ok( active = 1, 'widget active' ) + FROM widgets; - (Mnemonic: "This is ok.") +(Mnemonic: "This is ok.") - :test_name is a very short description of the test that will be printed - out. It makes it very easy to find a test in your script when it fails and - gives others an idea of your intentions. :test_name is optional, but we - *very* strongly encourage its use. +:test_name is a very short description of the test that will be printed +out. It makes it very easy to find a test in your script when it fails and +gives others an idea of your intentions. :test_name is optional, but we +*very* strongly encourage its use. - Should an ok() fail, it will produce some diagnostics: +Should an ok() fail, it will produce some diagnostics: - not ok 18 - sufficient mucus - # Failed test 18: "sufficient mucus" + not ok 18 - sufficient mucus + # Failed test 18: "sufficient mucus" -* is (anyelement, anyelement, text) -* is (anyelement, anyelement ) -* isnt (anyelement, anyelement, text) -* isnt (anyelement, anyelement ) +### is (anyelement, anyelement, text) ### +### is (anyelement, anyelement ) ### +### isnt (anyelement, anyelement, text) ### +### isnt (anyelement, anyelement ) ### - SELECT is ( :this, :that, $test_name ); - SELECT isnt( :this, :that, $test_name ); + SELECT is ( :this, :that, $test_name ); + SELECT isnt( :this, :that, $test_name ); - Similar to ok(), is() and isnt() compare their two arguments with `=` and - `<>` respectively and use the result of that to determine if the test - succeeded or failed. So these: +Similar to ok(), is() and isnt() compare their two arguments with `=` and +`<>` respectively and use the result of that to determine if the test +succeeded or failed. So these: - -- Is the ultimate answer 42? - SELECT is( ultimate_answer(), 42, 'Meaning of Life' ); + -- Is the ultimate answer 42? + SELECT is( ultimate_answer(), 42, 'Meaning of Life' ); - -- foo() doesn't return empty - SELECT isnt( foo(), '', 'Got some foo' ); + -- foo() doesn't return empty + SELECT isnt( foo(), '', 'Got some foo' ); - are similar to these: +are similar to these: - SELECT ok( ultimate_answer() = 42, 'Meaning of Life' ); - SELECT isnt( foo() <> '', 'Got some foo' ); + SELECT ok( ultimate_answer() = 42, 'Meaning of Life' ); + SELECT isnt( foo() <> '', 'Got some foo' ); - (Mnemonic: "This is that." "This isn't that.") +(Mnemonic: "This is that." "This isn't that.") - So why use these? They produce better diagnostics on failure. ok() cannot - know what you are testing for (beyond the name), but is() and isnt() know - what the test was and why it failed. For example this test: +So why use these? They produce better diagnostics on failure. ok() cannot +know what you are testing for (beyond the name), but is() and isnt() know +what the test was and why it failed. For example this test: - \set foo '\'' waffle '\'' - \set bar '\'' yarblokos '\'' - SELECT is( :foo::text, :bar::text, 'Is foo the same as bar?' ); + \set foo '\'' waffle '\'' + \set bar '\'' yarblokos '\'' + SELECT is( :foo::text, :bar::text, 'Is foo the same as bar?' ); - Will produce something like this: +Will produce something like this: - # Failed test 17: "Is foo the same as bar?" - # got: waffle - # expected: yarblokos + # Failed test 17: "Is foo the same as bar?" + # got: waffle + # expected: yarblokos - So you can figure out what went wrong without re-running the test. +So you can figure out what went wrong without re-running the test. - You are encouraged to use is() and isnt() over ok() where possible, - however do not be tempted to use them to find out if something is true or - false! +You are encouraged to use is() and isnt() over ok() where possible, +however do not be tempted to use them to find out if something is true or +false! - -- XXX BAD! - SELECT is( is_valid(9), TRUE, 'A tree grows in Brooklyn' ); + -- XXX BAD! + SELECT is( is_valid(9), TRUE, 'A tree grows in Brooklyn' ); - This does not check if `is_valid(9)` is true, it checks if it *returns* - TRUE. Very different. Similar caveats exist for FALSE. In these cases, use - ok(). +This does not check if `is_valid(9)` is true, it checks if it *returns* +TRUE. Very different. Similar caveats exist for FALSE. In these cases, use +ok(). - SELECT ok( is_valid(9), 'A tree grows in Brooklyn' ); + SELECT ok( is_valid(9), 'A tree grows in Brooklyn' ); -* matches ( anyelement, text, text ) -* matches ( anyelement, text ) +### matches ( anyelement, text, text ) ### +### matches ( anyelement, text ) ### - SELECT matches( :this, '^that', :test_name ); + SELECT matches( :this, '^that', :test_name ); - Similar to ok(), matches() matches :this against the regex `/^that/`. +Similar to ok(), matches() matches :this against the regex `/^that/`. - So this: +So this: - SELECT matches( :this, '^that', 'this is like that' ); + SELECT matches( :this, '^that', 'this is like that' ); - is similar to: +is similar to: - SELECT ok( :this ~ '^that', 'this is like that' ); + SELECT ok( :this ~ '^that', 'this is like that' ); - (Mnemonic "This matches that".) +(Mnemonic "This matches that".) - Its advantages over ok() are similar to that of is() and isnt(): Better - diagnostics on failure. +Its advantages over ok() are similar to that of is() and isnt(): Better +diagnostics on failure. -* imatches ( anyelement, text, text ) -* imatches ( anyelement, text ) +### imatches ( anyelement, text, text ) ### +### imatches ( anyelement, text ) ### - SELECT imatches( :this, '^that', :test_name ); + SELECT imatches( :this, '^that', :test_name ); - These are just like matches() except that the regular expression is - case-insensitive. +These are just like matches() except that the regular expression is +case-insensitive. -* doesnt_match ( anyelement, text, text ) -* doesnt_match ( anyelement, text ) -* doesnt_imatch ( anyelement, text, text ) -* doesnt_imatch ( anyelement, text ) +### doesnt_match ( anyelement, text, text ) ### +### doesnt_match ( anyelement, text ) ### +### doesnt_imatch ( anyelement, text, text ) ### +### doesnt_imatch ( anyelement, text ) ### - SELECT doesnt_match( :this, '^that', :test_name ); + SELECT doesnt_match( :this, '^that', :test_name ); - These functions work exactly as matches() and imataches() do, only they - check if :this *does not* match the given pattern. +These functions work exactly as matches() and imataches() do, only they +check if :this *does not* match the given pattern. -* alike ( anyelement, text, text ) -* alike ( anyelement, text ) -* ialike ( anyelement, text, text ) -* ialike ( anyelement, text ) +### alike ( anyelement, text, text ) ### +### alike ( anyelement, text ) ### +### ialike ( anyelement, text, text ) ### +### ialike ( anyelement, text ) ### - SELECT alike( :this, 'that%', :test_name ); + SELECT alike( :this, 'that%', :test_name ); - Similar to ok(), alike() matches :this against the SQL LIKE pattern - 'that%'. ialike() matches case-insensitively. +Similar to ok(), alike() matches :this against the SQL LIKE pattern +'that%'. ialike() matches case-insensitively. - So this: +So this: - SELECT ialike( :this, 'that%', 'this is alike that' ); + SELECT ialike( :this, 'that%', 'this is alike that' ); - is similar to: +is similar to: - SELECT ok( :this ILIKE 'that%', 'this is like that' ); + SELECT ok( :this ILIKE 'that%', 'this is like that' ); - (Mnemonic "This is like that".) +(Mnemonic "This is like that".) - Its advantages over ok() are similar to that of is() and isnt(): Better - diagnostics on failure. +Its advantages over ok() are similar to that of is() and isnt(): Better +diagnostics on failure. -* unalike ( anyelement, text, text ) -* unalike ( anyelement, text ) -* unialike ( anyelement, text, text ) -* unialike ( anyelement, text ) +### unalike ( anyelement, text, text ) ### +### unalike ( anyelement, text ) ### +### unialike ( anyelement, text, text ) ### +### unialike ( anyelement, text ) ### - SELECT unalike( :this, 'that%', :test_name ); + SELECT unalike( :this, 'that%', :test_name ); - Works exactly as alike(), only it checks if :this *does not* match the - given pattern. +Works exactly as alike(), only it checks if :this *does not* match the +given pattern. -* pass ( text ) -* pass ( ) -* fail ( text ) -* fail ( ) +### pass ( text ) ### +### pass ( ) ### +### fail ( text ) ### +### fail ( ) ### - SELECT pass( :test_name ); - SELECT fail( :test_name ); + SELECT pass( :test_name ); + SELECT fail( :test_name ); - Sometimes you just want to say that the tests have passed. Usually the - case is you've got some complicated condition that is difficult to wedge - into an ok(). In this case, you can simply use pass() (to declare the test - ok) or fail() (for not ok). They are synonyms for ok(1) and ok(0). +Sometimes you just want to say that the tests have passed. Usually the +case is you've got some complicated condition that is difficult to wedge +into an ok(). In this case, you can simply use pass() (to declare the test +ok) or fail() (for not ok). They are synonyms for ok(1) and ok(0). - Use these functions very, very, very sparingly. +Use these functions very, very, very sparingly. -* throws_ok ( text, char(5), text ) -* throws_ok ( text, char(5) ) -* throws_ok ( text ) +### throws_ok ( text, char(5), text ) ### +### throws_ok ( text, char(5) ) ### +### throws_ok ( text ) ### - SELECT throws_ok( - 'INSERT INTO try (id) VALUES (1)', - '23505', - 'We should get a unique violation for a duplicate PK' - ); + SELECT throws_ok( + 'INSERT INTO try (id) VALUES (1)', + '23505', + 'We should get a unique violation for a duplicate PK' + ); - When you want to make sure that an exception is thrown by PostgreSQL under - certain circumstances, use throws_ok() to test for those circumstances. +When you want to make sure that an exception is thrown by PostgreSQL under +certain circumstances, use throws_ok() to test for those circumstances. - The first argument should be a string representing the query to be - executed. throws_ok() will use the PL/pgSQL `EXECUTE` statement to execute - it and catch any exception. +The first argument should be a string representing the query to be +executed. throws_ok() will use the PL/pgSQL `EXECUTE` statement to execute +it and catch any exception. - The second argument should be an exception error code. If this value is - not NULL, throws_ok() will check the thrown exception to ensure that it is - the expected exception. For a complete list of error codes, see: +The second argument should be an exception error code. If this value is +not NULL, throws_ok() will check the thrown exception to ensure that it is +the expected exception. For a complete list of error codes, see [Appendix A.](http://www.postgresql.org/docs/current/static/errcodes-appendix.html) in the [PostgreSQL documentation](http://www.postgresql.org/docs/current/static/). - http://www.postgresql.org/docs/current/static/errcodes-appendix.html +The third argument is of course a brief test name. - The third argument is of course a brief test name. +Should a throws_ok() test fail it produces appropriate diagnostic +messages. For example: - Should a throws_ok() test fail it produces appropriate diagnostic - messages. For example: + not ok 81 - simple error + # Failed test "simple error" + # caught: 23505: duplicate key value violates unique constraint "try_pkey" + # expected: 23506 - not ok 81 - simple error - # Failed test "simple error" - # caught: 23505: duplicate key value violates unique constraint "try_pkey" - # expected: 23506 +Idea borrowed from the Test::Exception Perl module. - Idea borrowed from the Test::Exception Perl module. +### lives_ok ( text, text ) ### +### lives_ok ( text ) ### -* lives_ok ( text, text ) -* lives_ok ( text ) + SELECT lives_ok( + 'INSERT INTO try (id) VALUES (1)', + 'We should not get a unique violation for a new PK' + ); - SELECT lives_ok( - 'INSERT INTO try (id) VALUES (1)', - 'We should not get a unique violation for a new PK' - ); +The inverse of throws_ok(), these functions test to ensure that a SQL +statement does *not* throw an exception. Should a lives_ok() test faile, +it produces appropriate diagnostic messages. For example: - The inverse of throws_ok(), these functions test to ensure that a SQL - statement does *not* throw an exception. Should a lives_ok() test faile, - it produces appropriate diagnostic messages. For example: + not ok 85 - simple success + # Failed test "simple success" + # died: 23505: duplicate key value violates unique constraint "try_pkey" - not ok 85 - simple success - # Failed test "simple success" - # died: 23505: duplicate key value violates unique constraint "try_pkey" - - Idea borrowed from the Test::Exception Perl module. +Idea borrowed from the Test::Exception Perl module. Diagnostics ----------- @@ -372,26 +370,26 @@ went wrong when it failed. But sometimes it doesn't work out that way. So here we have ways for you to write your own diagnostic messages which are safer than just \echo. -* diag ( text ) +### diag ( text ) ### - Returns a diagnostic message which is guaranteed not to interfere with - test output. Handy for this sort of thing: +Returns a diagnostic message which is guaranteed not to interfere with +test output. Handy for this sort of thing: - -- Output a diagnostic message if the collation is not en_US.UTF-8. - SELECT diag( - E'These tests expect LC_COLLATE to be en_US.UTF-8,\n' - || 'but yours is set to ' || setting || E'.\n' - || 'As a result, some tests may fail. YMMV.' - ) - FROM pg_settings - WHERE name = 'lc_collate' - AND setting <> 'en_US.UTF-8'; + -- Output a diagnostic message if the collation is not en_US.UTF-8. + SELECT diag( + E'These tests expect LC_COLLATE to be en_US.UTF-8,\n' + || 'but yours is set to ' || setting || E'.\n' + || 'As a result, some tests may fail. YMMV.' + ) + FROM pg_settings + WHERE name = 'lc_collate' + AND setting <> 'en_US.UTF-8'; - Which would produce: +Which would produce: - # These tests expect LC_COLLATE to be en_US.UTF-8, - # but yours is set to en_US.UTF-8. - # As a result, some tests may fail. YMMV. + # These tests expect LC_COLLATE to be en_US.UTF-8, + # but yours is set to en_US.UTF-8. + # As a result, some tests may fail. YMMV. Suported Versions ----------------- @@ -401,7 +399,7 @@ pgTAP has been tested on the following builds of PostgreSQL: * PostgreSQL 8.3.1 on i386-apple-darwin9.2.2 If you know of others, please submit them! Use -psql template1 -c 'SELECT VERSION()' to get the formal build version and OS. +`psql template1 -c 'SELECT VERSION()'` to get the formal build version and OS. Author ------ @@ -423,7 +421,7 @@ documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. -IN NO EVENT SHALL KINETICODE BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +IN NO EVENT SHALL DAVID WHEELER BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF KINETICODE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. @@ -433,4 +431,3 @@ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND KINETICODE HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. - From 3e2459d4474896c24d42633a53c28f9d6f8380ba Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 7 Jun 2008 03:45:49 +0000 Subject: [PATCH 0004/1195] License. --- README.pgtap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.pgtap b/README.pgtap index 740f61cc46ff..42566bc4c0ad 100644 --- a/README.pgtap +++ b/README.pgtap @@ -414,14 +414,14 @@ Credits Copyright and License --------------------- -Copyright (c) 2008 David Wheeler. Some rights reserved. +Copyright (c) 2008 Kineticode, Inc. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. -IN NO EVENT SHALL DAVID WHEELER BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +IN NO EVENT SHALL KINETICODE BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF KINETICODE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 9ee2819f374154e9ec2b784231c7bfec23b2a879 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 7 Jun 2008 06:31:27 +0000 Subject: [PATCH 0005/1195] * Added `pg_prove`. W00t! * Fixed `no_plan()` so that it no longer emits a plan. Note that it now has to be called as a table query. Ah well! * Changed `plan()` and `no_plan` so that they throw an exception if a user tries to plan twice. * Fixed `test.sql` so that it now emits a proper test plan. * Removed all of the configuration settings from pgtap.sql, as they're now handled by pg_prove. I've mengioned them in the README for reference. --- README.pgtap | 68 +++++++++--------- pg_prove | 197 +++++++++++++++++++++++++++++++++++++++++++++++++++ pgtap.sql | 28 ++++---- test.sql | 17 ++++- 4 files changed, 259 insertions(+), 51 deletions(-) create mode 100755 pg_prove diff --git a/README.pgtap b/README.pgtap index 42566bc4c0ad..4ba8d34ec023 100644 --- a/README.pgtap +++ b/README.pgtap @@ -32,42 +32,42 @@ end. Here's an example: -- Load the TAP functions. \i pgtap.sql - -- Plan the tests. - SELECT plan(1); + -- Plan the tests. + SELECT plan(1); - -- Run the tests. - SELECT pass( 'My test passed, w00t!' ); + -- Run the tests. + SELECT pass( 'My test passed, w00t!' ); - -- Finish the tests and clean up. - SELECT * FROM finish(); - \i drop_pgtap.sql + -- Finish the tests and clean up. + SELECT * FROM finish(); + \i drop_pgtap.sql -Note that pgtap.sql sets up some variables to make things output from psql in -a that a TAP parser can parse. If you're not using pgtap.sql, perhaps because -you've installed pgTAP and can just use its functions, you'll still need to -set up your test script to properly output TAP. Don't worry, this is easy. -Just write your test script like so: +Of course, if you already have the pgTAP functions in your testing database, +you should skip running `pgtap.sql` at the beginning and `drop_pgtap.sql` at +the end of the script. - \set ON_ERROR_STOP 1 - \pset format unaligned - \pset tuples_only - \pset pager - \pset null '[NULL]' +Now you're ready to run your test script! - -- Plan the tests. - SELECT plan(1); + % psql -d try -AtP pager= -v ON_ERROR_STOP=1 -f test.sql + 1..1 + ok 1 - My test passed, w00t! - -- Run the tests. - SELECT pass( 'My test passed, w00t!' ); +You'll need to have all of those switches there to ensure that the output is +proper TAP. Or you can specify them in the test file, like so: - -- Finish the tests. - SELECT * FROM finish(); + \set ON_ERROR_STOP 1 + \pset format unaligned + \pset tuples_only + \pset pager -In either case, now you're ready to run your test script! +Or save yourself some effort -- and run a batch of tests scripts at once -- by +useing pg_prove: - % psql try < test.sql - 1..1 - ok 1 - My test passed, w00t! + % ./pg_prove -d try test_*.sql + test........ok + All tests successful. + Files=1, Tests=1, 0 wallclock secs ( 0.03 usr 0.00 sys + 0.02 cusr 0.01 csys = 0.06 CPU) + Result: PASS Yep, that's all there is to it. @@ -96,7 +96,7 @@ There are rare cases when you will not know beforehand how many tests your script is going to run. In this case, you can declare that you have no plan. (Try to avoid using this as it weakens your test.) - SELECT no_plan(); + SELECT * FROM no_plan(); Often, though, you'll be able to calculate the number of tests, like so: @@ -116,15 +116,15 @@ By convention, each test is assigned a number in order. This is largely done automatically for you. However, it's often very useful to assign a name to each test. Would you rather see this? - ok 4 - not ok 5 - ok 6 + ok 4 + not ok 5 + ok 6 Or this? - ok 4 - basic multi-variable - not ok 5 - simple exponential - ok 6 - force == mass * acceleration + ok 4 - basic multi-variable + not ok 5 - simple exponential + ok 6 - force == mass * acceleration The later gives you some idea of what failed. It also makes it easier to find the test in your script, simply search for "simple exponential". diff --git a/pg_prove b/pg_prove new file mode 100755 index 000000000000..fbd3806b8018 --- /dev/null +++ b/pg_prove @@ -0,0 +1,197 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; +use TAP::Harness; +use Getopt::Long; +our $VERSION = '0.01'; + +Getopt::Long::Configure (qw(bundling)); + +my $opts = { psql => 'psql' }; + +Getopt::Long::GetOptions( + 'psql-bin|b=s' => \$opts->{psql}, + 'dbname|d=s' => \$opts->{dbname}, + 'username|U=s' => \$opts->{username}, + 'host|h=s' => \$opts->{host}, + 'port|p=s' => \$opts->{port}, + 'timer|t' => \$opts->{timer}, + 'verbose|v+' => \$opts->{verbose}, + 'help|H' => \$opts->{help}, + 'man|m' => \$opts->{man}, + 'version|V' => \$opts->{version}, +) or require Pod::Usage && Pod::Usage::pod2usage(2); + +if ( $opts->{help} or $opts->{man} ) { + require Pod::Usage; + Pod::Usage::pod2usage( + '-sections' => $opts->{man} ? '.+' : '(?i:(Usage|Options))', + '-verbose' => 99, + '-exitval' => 0, + ) +} + +if ($opts->{version}) { + print 'pg_prove ', main->VERSION, $/; + exit; +} + +my @command = ($opts->{psql}); + +for (qw(username host port dbname)) { + push @command, "--$_" => $opts->{$_} if defined $opts->{$_} +} + +push @command, qw( + --no-align + --tuples-only + --pset pager= + --pset null=[NULL] + --set ON_ERROR_STOP=1 + --file +); + +print join ' ', @command, $/; + +TAP::Harness->new({ + verbosity => $opts->{verbose}, + timer => $opts->{timer}, + exec => \@command, +})->runtests( @ARGV ); + +__END__ + +=head1 Name + +pg_prove - A command-line tool for running pgTAP tests against TAP::Harness + +=head1 Usage + + pg_prove -d template1 test.sql + +=head1 Description + +C is a command-line interface to the test-running functionality of +L. Shell metacharacters may be used with command +lines options and will be exanded via "glob". + +The test scripts should be a series of SQL statements + +=head1 Options + + -b --psql-bin PSQL Location of the psql program. + -d --dbname DBNAME Database to which to connect. + -U --username USERNAME Username with which to connect. + -h --host HOST Host to which to connect. + -p --port PORT Port to which to connect. + -t --timer Print elapsed time after each test file. + -v --verbose Display output of test scripts while running them. + -h --help Print a usage statement and exit. + -m --man Print the complete documentation and exit. + -v --version Print the version number and exit. + +=head1 Options Details + +=over + +=item C<-b> + +=item C<--psql-bin> + + pg_prove --psql-bin /usr/local/pgsql/bin/psql + pg_prove -b /usr/local/bin/psql + +Path to the C program, which will be used to actually run the tests. +Defaults to "psql", which should work fine it if happens to be in your path. + +=item C<-d> + +=item C<--dbname> + + pg_prove --dbname try + pg_prove -d postgres + +The name of database to which to connect. Defaults to be the same as the user +name. + +=item C<-U> + +=item C<--username> + + pg_prove --username foo + pg_prove -U postgres + +PostgreSQL user name to connect as. Defaults to be the same as the operating +system name of the user running the application. + +=item C<-h> + +=item C<--host> + + pg_prove --host pg.example.com + pg_prove -h dev.local + +Specifies the host name of the machine on which the server is running. If the +value begins with a slash, it is used as the directory for the Unix-domain +socket. + +=item C<-p> + +=item C<--port> + + pg_prove --port 1234 + pg_prove -p 666 + +Specifies the TCP port or the local Unix-domain socket file extension on which +the server is listening for connections. Defaults to the value of the +C<$PGPORT> environment variable or, if not set, to the port specified at +compile time, usually 5432. + +=item C<-t> + +=item C<--timer> + + pg_prove --timer + pg_prove -t + +Print elapsed time after each test file. + +=item C<-H> + +=item C<--help> + + pg_prove --help + pg_prove -h + +Outputs a brief description of the options supported by C and exits. + +=item C<-m> + +=item C<--man> + + pg_prove --man + pg_prove -m + +Outputs this documentation and exits. + +=item C<-v> + +=item C<--version> + + pg_prove --version + pg_prove -v + +Outputs the program name and version and exits. + +=back + +=head1 Author + +David E. Wheeler + +=head1 Copyright + +Copyright (c) 2008 Kineticode, Inc. Some Rights Reserved. + +=cut diff --git a/pgtap.sql b/pgtap.sql index 78518d13b3a0..8bae71b14078 100644 --- a/pgtap.sql +++ b/pgtap.sql @@ -1,11 +1,3 @@ -SET client_min_messages = warning; -\set ON_ERROR_STOP 1 -\set AUTOCOMMIT off -\pset format unaligned -\pset tuples_only -\pset pager -\pset null '[NULL]' - CREATE TEMP TABLE __tcache__ ( label TEXT NOT NULL, value integer NOT NULL @@ -49,17 +41,25 @@ $$ LANGUAGE SQL strict; CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ BEGIN - UPDATE __tcache__ SET value = $1 WHERE label = 'plan'; - IF NOT FOUND THEN - INSERT INTO __tcache__ VALUES ( 'plan', $1 ); + PERFORM TRUE FROM __tcache__ WHERE label = 'plan'; + IF FOUND THEN + RAISE EXCEPTION 'You tried to plan twice!'; END IF; + INSERT INTO __tcache__ VALUES ( 'plan', $1 ); RETURN '1..' || $1; END; $$ LANGUAGE plpgsql strict; -CREATE OR REPLACE FUNCTION no_plan( ) RETURNS TEXT AS $$ - SELECT plan(0); -$$ LANGUAGE SQL strict; +CREATE OR REPLACE FUNCTION no_plan( ) RETURNS SETOF boolean AS $$ +BEGIN + PERFORM TRUE FROM __tcache__ WHERE label = 'plan'; + IF FOUND THEN + RAISE EXCEPTION 'You tried to plan twice!'; + END IF; + INSERT INTO __tcache__ VALUES ( 'plan', 0 ); + RETURN; +END; +$$ LANGUAGE plpgsql strict; CREATE OR REPLACE FUNCTION finish () RETURNS SETOF TEXT AS $$ DECLARE diff --git a/test.sql b/test.sql index c4a6aee8714a..089eb035c07c 100644 --- a/test.sql +++ b/test.sql @@ -3,13 +3,19 @@ -- -- +-- Keep things quiet. +SET client_min_messages = warning; + -- Load the TAP functions. \i pgtap.sql -\set numb_tests 0 +\set numb_tests 78 --- Plan the tests. +-- Set the test plan. SELECT plan(:numb_tests); +-- Replace the internal record of the plan for a few tests. +UPDATE __tcache__ SET value = 3 WHERE label = 'plan'; + /****************************************************************************/ -- Test pass(). SELECT pass( 'My pass() passed, w00t!' ); @@ -38,15 +44,18 @@ SELECT is( num_failed(), 0, 'We should now have no failures' ); -- Check diag. SELECT is( diag('foo'), '# foo', 'diag() should work properly' ); SELECT is( diag(E'foo\nbar'), E'# foo\n# bar', 'multiline diag() should work properly' ); +SELECT is( diag(E'foo\n# bar'), E'# foo\n# # bar', 'multiline diag() should work properly with existing comments' ); /****************************************************************************/ -- Check no_plan. -SELECT is ( no_plan(), '1..0', 'no_plan() should plan 0 tests' ); +DELETE FROM __tcache__ WHERE label = 'plan'; +SELECT * FROM no_plan(); SELECT is( value, 0, 'no_plan() should have stored a plan of 0' ) FROM __tcache__ WHERE label = 'plan'; -- Set the plan to a high number. +DELETE FROM __tcache__ WHERE label = 'plan'; SELECT is( plan(4000), '1..4000', 'Set the plan to 4000' ); SELECT is( (SELECT * FROM finish() LIMIT 1), @@ -55,6 +64,7 @@ SELECT is( ); -- Set the plan to a low number. +DELETE FROM __tcache__ WHERE label = 'plan'; SELECT is( plan(4), '1..4', 'Set the plan to 4' ); SELECT is( (SELECT * FROM finish() LIMIT 1), @@ -63,6 +73,7 @@ SELECT is( ); -- Reset the original plan. +DELETE FROM __tcache__ WHERE label = 'plan'; SELECT is( plan(:numb_tests), '1..' || :numb_tests, 'Reset the plan' ); SELECT is( value, :numb_tests, 'plan() should have stored the test count' ) FROM __tcache__ From 1675c5bdc4099b19471e78722edf4f84083419b0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 8 Jun 2008 02:08:03 +0000 Subject: [PATCH 0006/1195] Moved the creation of the TMP tables into `plan()`, so that everything is properly self-contained. --- pgtap.sql | 95 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/pgtap.sql b/pgtap.sql index 8bae71b14078..bba77e48bbb1 100644 --- a/pgtap.sql +++ b/pgtap.sql @@ -1,26 +1,57 @@ -CREATE TEMP TABLE __tcache__ ( - label TEXT NOT NULL, - value integer NOT NULL -); - -CREATE TEMP TABLE __tresults__ ( - numb SERIAL PRIMARY KEY, - ok BOOLEAN NOT NULL DEFAULT TRUE, - aok BOOLEAN NOT NULL DEFAULT TRUE, - descr TEXT NOT NULL DEFAULT '', - type TEXT NOT NULL DEFAULT '', - reason TEXT NOT NULL DEFAULT '' -); +CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ +BEGIN + BEGIN + EXECUTE ' + CREATE TEMP TABLE __tcache__ ( + label TEXT NOT NULL, + value integer NOT NULL + ); + + CREATE TEMP TABLE __tresults__ ( + numb SERIAL PRIMARY KEY, + ok BOOLEAN NOT NULL DEFAULT TRUE, + aok BOOLEAN NOT NULL DEFAULT TRUE, + descr TEXT NOT NULL DEFAULT '''', + type TEXT NOT NULL DEFAULT '''', + reason TEXT NOT NULL DEFAULT '''' + ); + '; + + EXCEPTION WHEN duplicate_table THEN + -- Raise an exception if there's already a plan. + EXECUTE 'SELECT TRUE FROM __tcache__ WHERE label = ''plan'''; + IF FOUND THEN + RAISE EXCEPTION 'You tried to plan twice!'; + END IF; + END; + + -- Save the plan and return. + EXECUTE 'INSERT INTO __tcache__ VALUES ( ''plan'', ' || $1 || ' )'; + 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 $$ - SELECT value FROM __tcache__ WHERE label = $1 LIMIT 1; -$$ LANGUAGE SQL strict; +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 _set ( text, integer ) RETURNS integer AS $$ BEGIN - UPDATE __tcache__ SET value = $2 WHERE label = $1; + EXECUTE 'UPDATE __tcache__ SET value = ' || $2 || ' WHERE label = ' || quote_literal($1); IF NOT FOUND THEN - INSERT INTO __tcache__ values ($1, $2); + EXECUTE 'INSERT INTO __tcache__ values (' || quote_literal($1) || ', ' || $2 || ')'; END IF; RETURN $2; END; @@ -29,35 +60,19 @@ $$ LANGUAGE plpgsql strict; CREATE OR REPLACE FUNCTION add_result ( boolean, bool, text, text, text ) RETURNS integer AS $$ BEGIN - INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) - VALUES( $1, $2, COALESCE($3, ''), $4, $5 ); + EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) + VALUES( ' || $1 || ', ' || $2 || ', ' || COALESCE(quote_literal($3), '''''') || ', ' + || quote_literal($4) || ', ' || quote_literal($5) || ' )'; RETURN lastval(); END; $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ - SELECT COUNT(*)::INTEGER FROM __tresults__ WHERE ok = FALSE; -$$ LANGUAGE SQL strict; - -CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ -BEGIN - PERFORM TRUE FROM __tcache__ WHERE label = 'plan'; - IF FOUND THEN - RAISE EXCEPTION 'You tried to plan twice!'; - END IF; - INSERT INTO __tcache__ VALUES ( 'plan', $1 ); - RETURN '1..' || $1; -END; -$$ LANGUAGE plpgsql strict; - -CREATE OR REPLACE FUNCTION no_plan( ) RETURNS SETOF boolean AS $$ +DECLARE + ret integer; BEGIN - PERFORM TRUE FROM __tcache__ WHERE label = 'plan'; - IF FOUND THEN - RAISE EXCEPTION 'You tried to plan twice!'; - END IF; - INSERT INTO __tcache__ VALUES ( 'plan', 0 ); - RETURN; + EXECUTE 'SELECT COUNT(*)::INTEGER FROM __tresults__ WHERE ok = FALSE' INTO ret; + RETURN ret; END; $$ LANGUAGE plpgsql strict; From 1588d4c8318d7c6c78fca7cd8a4cf4b38065da65 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 8 Jun 2008 04:00:31 +0000 Subject: [PATCH 0007/1195] Better handling of transactions. We do not even need `drop_tap.sql` in our test script anymore. W00t! --- README.pgtap | 19 +++++++++++-------- pg_prove | 2 +- test.sql | 4 +++- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/README.pgtap b/README.pgtap index 4ba8d34ec023..81bdfe324ef7 100644 --- a/README.pgtap +++ b/README.pgtap @@ -24,12 +24,13 @@ If you want to remove pgTAP from a database, run the drop_pgtap.sql script: Running pgTAP Tests =================== -You can also distribute these two files with any PostgreSQL distribution, such -as a custom data type. For such a case, if your users want to run your test -suite, just load the functions in your test.sql file and then drop them at the -end. Here's an example: +You can also distribute pgtap.sql with any PostgreSQL distribution, such as a +custom data type. For such a case, if your users want to run your test suite, +just be sure to start a transaction, load the functions in your test.sql file, +and then rollback the transaction at the end of the script. Here's an example: -- Load the TAP functions. + BEGIN; \i pgtap.sql -- Plan the tests. @@ -40,7 +41,7 @@ end. Here's an example: -- Finish the tests and clean up. SELECT * FROM finish(); - \i drop_pgtap.sql + ROLLBACK; Of course, if you already have the pgTAP functions in your testing database, you should skip running `pgtap.sql` at the beginning and `drop_pgtap.sql` at @@ -48,14 +49,16 @@ the end of the script. Now you're ready to run your test script! - % psql -d try -AtP pager= -v ON_ERROR_STOP=1 -f test.sql + % psql -d try -AtP pager= -v ON_ERROR_ROLLBACK=1 -f test.sql 1..1 ok 1 - My test passed, w00t! You'll need to have all of those switches there to ensure that the output is -proper TAP. Or you can specify them in the test file, like so: +proper TAP and that all changed are reversed -- including the loading of the +test functions -- in the event of an uncaught exception. Or you can specify +them in the test file, like so: - \set ON_ERROR_STOP 1 + \set ON_ERROR_ROLBACK 1 \pset format unaligned \pset tuples_only \pset pager diff --git a/pg_prove b/pg_prove index fbd3806b8018..4d78aee07e47 100755 --- a/pg_prove +++ b/pg_prove @@ -48,7 +48,7 @@ push @command, qw( --tuples-only --pset pager= --pset null=[NULL] - --set ON_ERROR_STOP=1 + --set ON_ERROR_ROLLBACK=1 --file ); diff --git a/test.sql b/test.sql index 089eb035c07c..4ed09e87a612 100644 --- a/test.sql +++ b/test.sql @@ -7,6 +7,7 @@ SET client_min_messages = warning; -- Load the TAP functions. +BEGIN; \i pgtap.sql \set numb_tests 78 @@ -223,4 +224,5 @@ UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 77 ); -- Finish the tests and clean up. SELECT * FROM finish(); -\i drop_pgtap.sql +ROLLBACK; + From 7b98373db8f99391c796c5e14369ae6e3376b4e1 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 8 Jun 2008 17:16:05 +0000 Subject: [PATCH 0008/1195] Documented --verbose and added support for TEST_VERBOSE. --- pg_prove | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pg_prove b/pg_prove index 4d78aee07e47..72da2e5d2887 100755 --- a/pg_prove +++ b/pg_prove @@ -55,7 +55,7 @@ push @command, qw( print join ' ', @command, $/; TAP::Harness->new({ - verbosity => $opts->{verbose}, + verbosity => $opts->{verbose} || $ENV{TEST_VERBOSE}, timer => $opts->{timer}, exec => \@command, })->runtests( @ARGV ); @@ -148,6 +148,14 @@ the server is listening for connections. Defaults to the value of the C<$PGPORT> environment variable or, if not set, to the port specified at compile time, usually 5432. +=item C<-v> + +=item C<--verbose> + +Display standard output of test scripts while running them. This behavior can +also be triggered by setting the C<$TEST_VERBOSE> environment variable to a +true value. + =item C<-t> =item C<--timer> From 4b76291747f85fd1af1f1e48316b51f620905ac4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 8 Jun 2008 17:23:01 +0000 Subject: [PATCH 0009/1195] Require test scripts. --- pg_prove | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pg_prove b/pg_prove index 72da2e5d2887..dc42d32386cf 100755 --- a/pg_prove +++ b/pg_prove @@ -37,6 +37,16 @@ if ($opts->{version}) { exit; } +unless (@ARGV) { + require Pod::Usage; + Pod::Usage::pod2usage( + '-message' => "\nOops! You didn't say what test scripts to run.\n", + '-sections' => '(?i:(Usage|Options))', + '-verbose' => 99, + '-exitval' => 1, + ) +} + my @command = ($opts->{psql}); for (qw(username host port dbname)) { @@ -68,7 +78,7 @@ pg_prove - A command-line tool for running pgTAP tests against TAP::Harness =head1 Usage - pg_prove -d template1 test.sql + pg_prove -d template1 test*.sql =head1 Description From 2f5f4c3255e59280a313534997e237f0f566e754 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 8 Jun 2008 17:25:12 +0000 Subject: [PATCH 0010/1195] * Started adding a build system. Need to tweak the schema stuff further. --- Makefile | 23 +++++++++++++++++++++++ drop_pgtap.sql => drop_pgtap.sql.in | 0 pgtap.sql => pgtap.sql.in | 3 +++ 3 files changed, 26 insertions(+) create mode 100644 Makefile rename drop_pgtap.sql => drop_pgtap.sql.in (100%) rename pgtap.sql => pgtap.sql.in (99%) diff --git a/Makefile b/Makefile new file mode 100644 index 000000000000..b16997321e83 --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +DATA_built = pgtap.sql drop_pgtap.sql +DOCS = README.pgtap +SCRIPTS = pg_prove + +top_builddir = ../.. +in_contrib = $(wildcard $(top_builddir)/src/Makefile.global); + +ifdef $(in_contrib) + # Just include the local makefiles + subdir = contrib/pgtap + include $(top_builddir)/src/Makefile.global + include $(top_srcdir)/contrib/contrib-global.mk +else + # Use pg_config to find PGXS and include it. + PGXS := $(shell pg_config --pgxs) + include $(PGXS) +endif + +%.sql: %.sql.in + sed 's,MODULE_PATHNAME,$$libdir/$*,g' $< >$@ + +test: + ./pg_prove *test.sql \ No newline at end of file diff --git a/drop_pgtap.sql b/drop_pgtap.sql.in similarity index 100% rename from drop_pgtap.sql rename to drop_pgtap.sql.in diff --git a/pgtap.sql b/pgtap.sql.in similarity index 99% rename from pgtap.sql rename to pgtap.sql.in index bba77e48bbb1..cc32f8f1e89c 100644 --- a/pgtap.sql +++ b/pgtap.sql.in @@ -1,3 +1,6 @@ +-- CREATE SCHEMA tap; +-- SET search_path TO tap,public; + CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ BEGIN BEGIN From 52a8bb1da0d13c63736f53fab3742d239454995b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 9 Jun 2008 04:37:48 +0000 Subject: [PATCH 0011/1195] Updated things to allow TAP to be built inside a schema. I'd like to make the Makefile's :all target simpler, but my make-foo is for shiznit. --- Makefile | 13 +++++++++++-- drop_pgtap.sql.in | 1 + pgtap.sql.in | 4 ++-- test.sql => test.sql.in | 2 ++ 4 files changed, 16 insertions(+), 4 deletions(-) rename test.sql => test.sql.in (99%) diff --git a/Makefile b/Makefile index b16997321e83..3d3a782a5831 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ DATA_built = pgtap.sql drop_pgtap.sql DOCS = README.pgtap SCRIPTS = pg_prove +TAPTEST = test.sql +EXTRA_CLEAN = $(TAPTEST) top_builddir = ../.. in_contrib = $(wildcard $(top_builddir)/src/Makefile.global); @@ -16,8 +18,15 @@ else include $(PGXS) endif +# I would really prefer to just add TAPTEST to the default... +all: $(PROGRAM) $(DATA_built) $(TAPTEST) $(SCRIPTS_built) $(addsuffix $(DLSUFFIX), $(MODULES)) + %.sql: %.sql.in - sed 's,MODULE_PATHNAME,$$libdir/$*,g' $< >$@ +ifdef TAPSCHEMA + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' $< >$@ +else + cp $< $@ +endif test: - ./pg_prove *test.sql \ No newline at end of file + ./pg_prove $(TAPTEST) \ No newline at end of file diff --git a/drop_pgtap.sql.in b/drop_pgtap.sql.in index 74f83ff209e6..931706cfa3be 100644 --- a/drop_pgtap.sql.in +++ b/drop_pgtap.sql.in @@ -38,3 +38,4 @@ DROP FUNCTION num_failed (); DROP FUNCTION add_result ( boolean, bool, text, text, text ); DROP FUNCTION _set ( text, integer ); DROP FUNCTION _get ( text ); +-- ## DROP SCHEMA TAPSCHEMA; diff --git a/pgtap.sql.in b/pgtap.sql.in index cc32f8f1e89c..d7eecce3719a 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1,5 +1,5 @@ --- CREATE SCHEMA tap; --- SET search_path TO tap,public; +-- ## CREATE SCHEMA TAPSCHEMA; +-- ## SET search_path TO TAPSCHEMA,public; CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ BEGIN diff --git a/test.sql b/test.sql.in similarity index 99% rename from test.sql rename to test.sql.in index 4ed09e87a612..e2e35da585b3 100644 --- a/test.sql +++ b/test.sql.in @@ -11,6 +11,8 @@ BEGIN; \i pgtap.sql \set numb_tests 78 +-- ## SET search_path TO TAPSCHEMA,public; + -- Set the test plan. SELECT plan(:numb_tests); From 83502b953fdc23a76f6d9a8d9b1c1931448f49b1 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 9 Jun 2008 04:39:39 +0000 Subject: [PATCH 0012/1195] comment. --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 3d3a782a5831..470f91cb5daf 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,8 @@ endif # I would really prefer to just add TAPTEST to the default... all: $(PROGRAM) $(DATA_built) $(TAPTEST) $(SCRIPTS_built) $(addsuffix $(DLSUFFIX), $(MODULES)) +# Override how .sql targets are processed to add the schema info, if +# necessary. Otherwise just copy the files. %.sql: %.sql.in ifdef TAPSCHEMA sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' $< >$@ From d63a501089770a680a25f2cc6a42223bc220a634 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 9 Jun 2008 16:36:48 +0000 Subject: [PATCH 0013/1195] Updated docs for the new build system. --- README.pgtap | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index 81bdfe324ef7..4ac3b5419b57 100644 --- a/README.pgtap +++ b/README.pgtap @@ -8,7 +8,28 @@ Perl 5. You could even consider it a port of sorts. Installation ============ -To install pgTAP into a PostgreSQL database, just do this: +For the impatient, to install pgTAP into a PostgreSQL database, just do this: + + make + make test + make install + +If you want to schema-qualify pgTAP (that is, install all of its functions +into their own schema), set the $TAPSCHEMA environment variable to the name +of the schema you'd like, for example: + + make TAPSCHEMA=tap + +The test target uses the included pg_prove script to do its testing. It +supports a number of environment variables that you might need to use, +including all the usual PostgreSQL client environment varibles: + +* PGDATABASE +* PGHOST +* PGPORT +* PGUSER + +Once pgTAP habe been built and tested, you can install it into a database: psql dbname -f pgtap.sql From 441233505142a1269ce9fa0df56ecdc881957fca Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 9 Jun 2008 17:11:17 +0000 Subject: [PATCH 0014/1195] Simplified the `all:` target. I didn't need all that other crap. --- Makefile | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 470f91cb5daf..8f0b672dd815 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ DATA_built = pgtap.sql drop_pgtap.sql DOCS = README.pgtap SCRIPTS = pg_prove TAPTEST = test.sql -EXTRA_CLEAN = $(TAPTEST) +EXTRA_CLEAN = $(TAPTEST) sql top_builddir = ../.. in_contrib = $(wildcard $(top_builddir)/src/Makefile.global); @@ -18,8 +18,7 @@ else include $(PGXS) endif -# I would really prefer to just add TAPTEST to the default... -all: $(PROGRAM) $(DATA_built) $(TAPTEST) $(SCRIPTS_built) $(addsuffix $(DLSUFFIX), $(MODULES)) +all: $(DATA_built) $(TAPTEST) $(SCRIPTS) # Override how .sql targets are processed to add the schema info, if # necessary. Otherwise just copy the files. @@ -31,4 +30,4 @@ else endif test: - ./pg_prove $(TAPTEST) \ No newline at end of file + ./pg_prove $(TAPTEST) From c8f107c8279ddc3e9477b60eb0371f0128d305d3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 9 Jun 2008 18:20:15 +0000 Subject: [PATCH 0015/1195] Added support for standard PostgreSQL-type regression testing by just copying the test script and setting some variables for it and providing an expected/ directory. The test now lives in the sql/ directory. --- Makefile | 10 +- README.pgtap | 27 ++++++ expected/pgtap.out | 81 ++++++++++++++++ sql/taptest.sql | 237 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 352 insertions(+), 3 deletions(-) create mode 100644 expected/pgtap.out create mode 100644 sql/taptest.sql diff --git a/Makefile b/Makefile index 8f0b672dd815..3e612d16e9ce 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ DATA_built = pgtap.sql drop_pgtap.sql DOCS = README.pgtap SCRIPTS = pg_prove -TAPTEST = test.sql -EXTRA_CLEAN = $(TAPTEST) sql +TAPTEST = sql/taptest.sql +REGRESS = pgtap +EXTRA_CLEAN = sql/$(REGRESS).sql top_builddir = ../.. in_contrib = $(wildcard $(top_builddir)/src/Makefile.global); @@ -18,7 +19,7 @@ else include $(PGXS) endif -all: $(DATA_built) $(TAPTEST) $(SCRIPTS) +all: $(DATA_built) $(TAPTEST) $(SCRIPTS) cptest # Override how .sql targets are processed to add the schema info, if # necessary. Otherwise just copy the files. @@ -29,5 +30,8 @@ else cp $< $@ endif +cptest: + sed -e 's/^-- !! //g' $(TAPTEST) > sql/$(REGRESS).sql + test: ./pg_prove $(TAPTEST) diff --git a/README.pgtap b/README.pgtap index 4ac3b5419b57..09e6f0cd65f0 100644 --- a/README.pgtap +++ b/README.pgtap @@ -13,6 +13,33 @@ For the impatient, to install pgTAP into a PostgreSQL database, just do this: make make test make install + make installcheck + +If you encounter an error such as: + + "Makefile", line 8: Need an operator + +You need to use GNU make, which may well be installed on your system as +'gmake': + + gmake + gmake test + gmake install + gmake installcheck + +If you encounter an error such as: + + make: pg_config: Command not found + +Be sure that you have pg_config installed and in your path. If you used a +package management system such as RPM to install PostgreSQL, be sure that the +-devel package is also installed. If necessary, add the path to pg_config to +your $PATH environment variable: + + env PATH=$PATH:/path/to/pgsql/bin make && make test && make install + +And finally, if all that fails, copy the entire distribution directory to the +'contrib' subdirectory of the PostgreSQL source code and try it there. If you want to schema-qualify pgTAP (that is, install all of its functions into their own schema), set the $TAPSCHEMA environment variable to the name diff --git a/expected/pgtap.out b/expected/pgtap.out new file mode 100644 index 000000000000..4389f5805315 --- /dev/null +++ b/expected/pgtap.out @@ -0,0 +1,81 @@ +-- These are here for the usual PostgreSQL regression tests to use. +\set ECHO +1..78 +ok 1 - My pass() passed, w00t! +ok 2 - Testing fail() +ok 3 - We should get the proper output from fail() +ok 4 - The output of finish() should reflect the test failure +ok 5 - We should have one failure +ok 6 - We should now have no failures +ok 7 - diag() should work properly +ok 8 - multiline diag() should work properly +ok 9 - multiline diag() should work properly with existing comments +ok 10 - no_plan() should have stored a plan of 0 +ok 11 - Set the plan to 4000 +ok 12 - The output of finish() should reflect a high test plan +ok 13 - Set the plan to 4 +ok 14 - The output of finish() should reflect a low test plan +ok 15 - Reset the plan +ok 16 - plan() should have stored the test count +ok 17 - ok() success +ok 18 - ok(true) should work +ok 19 - ok() success 2 +ok 20 - ok(true, '') should work +ok 21 - ok() success 3 +ok 22 - ok(true, 'foo') should work +ok 23 - ok() failure +ok 24 - ok(false) should work +ok 25 - ok() failure 2 +ok 26 - ok(false, '') should work +ok 27 - ok() failure 3 +ok 28 - ok(false, 'foo') should work +ok 29 - is() success +ok 30 - isa(1, 1) should work +ok 31 - is() success 2 +ok 32 - is('x', 'x') should work +ok 33 - is() success 3 +ok 34 - is(1.1, 1.10) should work +ok 35 - is() success 4 +ok 36 - is(1.1, 1.10) should work +ok 37 - is() success 5 +ok 38 - is(true, true) should work +ok 39 - is() success 6 +ok 40 - is(false, false) should work +ok 41 - is() success 7 +ok 42 - is(1, 1, 'foo') should work +ok 43 - is() failure +ok 44 - is(1, 2) should work +ok 45 - isnt() success +ok 46 - isnt(1, 2) should work +ok 47 - isnt() failure +ok 48 - is(1, 2) should work +ok 49 - is() should work with psql variables +ok 50 - matches() should work +ok 51 - matches() should work with a regex +ok 52 - imatches() should work with a regex +ok 53 - matches() failure +ok 54 - Check matches diagnostics +ok 55 - doesnt_match() should work +ok 56 - doesnt_match() should work with a regex +ok 57 - doesnt_imatch() should work with a regex +ok 58 - doesnt_match() failure +ok 59 - doesnt_match() should work +ok 60 - alike() should work +ok 61 - alike() should work with a regex +ok 62 - ialike() should work with a regex +ok 63 - alike() failure +ok 64 - Check alike diagnostics +ok 65 - unalike() should work +ok 66 - unalike() should work with a regex +ok 67 - iunalike() should work with a regex +ok 68 - unalike() failure +ok 69 - Check unalike diagnostics +ok 70 - throws_ok(1/0) should work +ok 71 - throws_ok failure diagnostics +ok 72 - We should get the proper diagnostics from throws_ok() +ok 73 - throws_ok(1/0, NULL) should work +ok 74 - throws_ok failure diagnostics +ok 75 - We should get the proper diagnostics from throws_ok() with a NULL error code +ok 76 - lives_ok() should work +ok 77 - lives_ok failure diagnostics +ok 78 - We should get the proper diagnostics for a lives_ok() failure diff --git a/sql/taptest.sql b/sql/taptest.sql new file mode 100644 index 000000000000..d88ef2db756b --- /dev/null +++ b/sql/taptest.sql @@ -0,0 +1,237 @@ +-- These are here for the usual PostgreSQL regression tests to use. +-- !! \set ECHO +-- !! \set ON_ERROR_ROLBACK 1 +-- !! \pset format unaligned +-- !! \pset tuples_only +-- !! \pset pager +-- !! CREATE LANGUAGE plpgsql; +-- +-- Tests for pgTAP. +-- +-- + +-- Keep things quiet. +SET client_min_messages = warning; + +-- Load the TAP functions. +BEGIN; +\i pgtap.sql +\set numb_tests 78 + +-- ## SET search_path TO TAPSCHEMA,public; + +-- Set the test plan. +SELECT plan(:numb_tests); + +-- Replace the internal record of the plan for a few tests. +UPDATE __tcache__ SET value = 3 WHERE label = 'plan'; + +/****************************************************************************/ +-- Test pass(). +SELECT pass( 'My pass() passed, w00t!' ); + +-- Test fail(). +\set fail_numb 2 +\echo ok :fail_numb - Testing fail() +SELECT is( + fail('oops'), + E'not ok 2 - oops\n# Failed test 2: "oops"', 'We should get the proper output from fail()'); + +-- Check the finish() output. +SELECT is( + (SELECT * FROM finish() LIMIT 1), + '# Looks like you failed 1 test of 3', + 'The output of finish() should reflect the test failure' +); + +/****************************************************************************/ +-- Check num_failed +SELECT is( num_failed(), 1, 'We should have one failure' ); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb = :fail_numb; +SELECT is( num_failed(), 0, 'We should now have no failures' ); + +/****************************************************************************/ +-- Check diag. +SELECT is( diag('foo'), '# foo', 'diag() should work properly' ); +SELECT is( diag(E'foo\nbar'), E'# foo\n# bar', 'multiline diag() should work properly' ); +SELECT is( diag(E'foo\n# bar'), E'# foo\n# # bar', 'multiline diag() should work properly with existing comments' ); + +/****************************************************************************/ +-- Check no_plan. +DELETE FROM __tcache__ WHERE label = 'plan'; +SELECT * FROM no_plan(); +SELECT is( value, 0, 'no_plan() should have stored a plan of 0' ) + FROM __tcache__ + WHERE label = 'plan'; + +-- Set the plan to a high number. +DELETE FROM __tcache__ WHERE label = 'plan'; +SELECT is( plan(4000), '1..4000', 'Set the plan to 4000' ); +SELECT is( + (SELECT * FROM finish() LIMIT 1), + '# Looks like you planned 4000 test but only ran 11', + 'The output of finish() should reflect a high test plan' +); + +-- Set the plan to a low number. +DELETE FROM __tcache__ WHERE label = 'plan'; +SELECT is( plan(4), '1..4', 'Set the plan to 4' ); +SELECT is( + (SELECT * FROM finish() LIMIT 1), + '# Looks like you planned 4 test but ran 9 extra', + 'The output of finish() should reflect a low test plan' +); + +-- Reset the original plan. +DELETE FROM __tcache__ WHERE label = 'plan'; +SELECT is( plan(:numb_tests), '1..' || :numb_tests, 'Reset the plan' ); +SELECT is( value, :numb_tests, 'plan() should have stored the test count' ) + FROM __tcache__ + WHERE label = 'plan'; + +/****************************************************************************/ +-- Test ok() +\echo ok 17 - ok() success +SELECT is( ok(true), 'ok 17', 'ok(true) should work' ); +\echo ok 19 - ok() success 2 +SELECT is( ok(true, ''), 'ok 19', 'ok(true, '''') should work' ); +\echo ok 21 - ok() success 3 +SELECT is( ok(true, 'foo'), 'ok 21 - foo', 'ok(true, ''foo'') should work' ); + +\echo ok 23 - ok() failure +SELECT is( ok(false), E'not ok 23\n# Failed test 23', 'ok(false) should work' ); +\echo ok 25 - ok() failure 2 +SELECT is( ok(false, ''), E'not ok 25\n# Failed test 25', 'ok(false, '''') should work' ); +\echo ok 27 - ok() failure 3 +SELECT is( ok(false, 'foo'), E'not ok 27 - foo\n# Failed test 27: "foo"', 'ok(false, ''foo'') should work' ); + +-- Clean up the failed test results. +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 23, 25, 27); + +/****************************************************************************/ +-- Test is(). +\echo ok 29 - is() success +SELECT is( is(1, 1), 'ok 29', 'isa(1, 1) should work' ); +\echo ok 31 - is() success 2 +SELECT is( is('x'::text, 'x'::text), 'ok 31', 'is(''x'', ''x'') should work' ); +\echo ok 33 - is() success 3 +SELECT is( is(1.1, 1.10), 'ok 33', 'is(1.1, 1.10) should work' ); +\echo ok 35 - is() success 4 +SELECT is( is(1.1, 1.10), 'ok 35', 'is(1.1, 1.10) should work' ); +\echo ok 37 - is() success 5 +SELECT is( is(true, true), 'ok 37', 'is(true, true) should work' ); +\echo ok 39 - is() success 6 +SELECT is( is(false, false), 'ok 39', 'is(false, false) should work' ); +--SELECT is( '12:45'::time, '12:45'::time, 'ok 41', 'is(time, time) should work' ); +\echo ok 41 - is() success 7 +SELECT is( is(1, 1, 'foo'), 'ok 41 - foo', 'is(1, 1, ''foo'') should work' ); +\echo ok 43 - is() failure +SELECT is( is( 1, 2 ), E'not ok 43\n# Failed test 43\n# got: 1\n# expected: 2', 'is(1, 2) should work' ); + +/****************************************************************************/ +-- Test isnt(). +\echo ok 45 - isnt() success +SELECT is( isnt(1, 2), 'ok 45', 'isnt(1, 2) should work' ); +\echo ok 47 - isnt() failure +SELECT is( isnt( 1, 1 ), E'not ok 47\n# Failed test 47\n# 1\n# <>\n# 1', 'is(1, 2) should work' ); + +-- Clean up the failed test results. +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 43, 47 ); + +/****************************************************************************/ +-- Try using variables. +\set foo '\'' waffle '\'' +\set bar '\'' waffle '\'' +SELECT is( :foo::text, :bar::text, 'is() should work with psql variables' ); + +/****************************************************************************/ +-- Test matches(). +SELECT matches( 'foo'::text, 'o', 'matches() should work' ); +SELECT matches( 'foo'::text, '^fo', 'matches() should work with a regex' ); +SELECT imatches( 'FOO'::text, '^fo', 'imatches() should work with a regex' ); + +-- Check matches() diagnostics. +\echo ok 53 - matches() failure +SELECT is( matches( 'foo'::text, '^a' ), E'not ok 53\n# Failed test 53\n# ''foo''\n# doesn''t match: ''^a''', 'Check matches diagnostics' ); + +-- Check doesnt_match. +SELECT doesnt_match( 'foo'::text, 'a', 'doesnt_match() should work' ); +SELECT doesnt_match( 'foo'::text, '^o', 'doesnt_match() should work with a regex' ); +SELECT doesnt_imatch( 'foo'::text, '^o', 'doesnt_imatch() should work with a regex' ); + +-- Check doesnt_match diagnostics. +\echo ok 58 - doesnt_match() failure +SELECT is( + doesnt_match( 'foo'::text, 'o' ), + E'not ok 58\n# Failed test 58\n# ''foo''\n# matches: ''o''', + 'doesnt_match() should work' +); + +-- Clean up the failed test results. +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 53, 58 ); + +/****************************************************************************/ +-- Test alike(). +SELECT alike( 'foo'::text, 'foo', 'alike() should work' ); +SELECT alike( 'foo'::text, 'fo%', 'alike() should work with a regex' ); +SELECT ialike( 'FOO'::text, 'fo%', 'ialike() should work with a regex' ); + +-- Check alike() diagnostics. +\echo ok 63 - alike() failure +SELECT is( alike( 'foo'::text, 'a%'::text ), E'not ok 63\n# Failed test 63\n# ''foo''\n# doesn''t match: ''a%''', 'Check alike diagnostics' ); + +-- Test unalike(). +SELECT unalike( 'foo'::text, 'f', 'unalike() should work' ); +SELECT unalike( 'foo'::text, 'f%i', 'unalike() should work with a regex' ); +SELECT unialike( 'FOO'::text, 'f%i', 'iunalike() should work with a regex' ); + +-- Check unalike() diagnostics. +\echo ok 68 - unalike() failure +SELECT is( unalike( 'foo'::text, 'f%'::text ), E'not ok 68\n# Failed test 68\n# ''foo''\n# matches: ''f%''', 'Check unalike diagnostics' ); + +-- Clean up the failed test results. +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 63, 68 ); + +/****************************************************************************/ +-- test throws_ok(). +SELECT throws_ok( 'SELECT 1 / 0', '22012', 'throws_ok(1/0) should work' ); + +-- Check its diagnostics for an invalid error code. +\echo ok 71 - throws_ok failure diagnostics +SELECT is( + throws_ok( 'SELECT 1 / 0', '97212' ), + E'not ok 71 - threw 97212\n# Failed test 71: "threw 97212"\n# caught: 22012: division by zero\n# expected: 97212', + 'We should get the proper diagnostics from throws_ok()' +); + +SELECT throws_ok( 'SELECT 1 / 0', NULL, 'throws_ok(1/0, NULL) should work' ); + +-- Check its diagnostics no error. +\echo ok 74 - throws_ok failure diagnostics +SELECT is( + throws_ok( 'SELECT 1', NULL ), + E'not ok 74 - threw an exception\n# Failed test 74: "threw an exception"\n# caught: no exception\n# expected: an exception', + 'We should get the proper diagnostics from throws_ok() with a NULL error code' +); + +-- Clean up the failed test results. +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 71, 74 ); + +/****************************************************************************/ +-- test lives_ok(). +SELECT lives_ok( 'SELECT 1', 'lives_ok() should work' ); + +-- Check its diagnostics when there is an exception. +\echo ok 77 - lives_ok failure diagnostics +SELECT is( + lives_ok( 'SELECT 1 / 0' ), + E'not ok 77\n# Failed test 77\n# died: 22012: division by zero', + 'We should get the proper diagnostics for a lives_ok() failure' +); + +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 77 ); + +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; + From 2368e4138fa6c5c4a13225f5dc9f9403cea5f751 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 9 Jun 2008 18:24:23 +0000 Subject: [PATCH 0016/1195] Forgot to delete that. --- README.pgtap | 7 +- test.sql.in | 230 --------------------------------------------------- 2 files changed, 3 insertions(+), 234 deletions(-) delete mode 100644 test.sql.in diff --git a/README.pgtap b/README.pgtap index 09e6f0cd65f0..85f594aeb975 100644 --- a/README.pgtap +++ b/README.pgtap @@ -92,8 +92,7 @@ and then rollback the transaction at the end of the script. Here's an example: ROLLBACK; Of course, if you already have the pgTAP functions in your testing database, -you should skip running `pgtap.sql` at the beginning and `drop_pgtap.sql` at -the end of the script. +you should skip `\i pgtap.sql` at the beginning of the script. Now you're ready to run your test script! @@ -102,7 +101,7 @@ Now you're ready to run your test script! ok 1 - My test passed, w00t! You'll need to have all of those switches there to ensure that the output is -proper TAP and that all changed are reversed -- including the loading of the +proper TAP and that all changes are reversed -- including the loading of the test functions -- in the event of an uncaught exception. Or you can specify them in the test file, like so: @@ -112,7 +111,7 @@ them in the test file, like so: \pset pager Or save yourself some effort -- and run a batch of tests scripts at once -- by -useing pg_prove: +using pg_prove: % ./pg_prove -d try test_*.sql test........ok diff --git a/test.sql.in b/test.sql.in deleted file mode 100644 index e2e35da585b3..000000000000 --- a/test.sql.in +++ /dev/null @@ -1,230 +0,0 @@ --- --- Tests for pgTAP. --- --- - --- Keep things quiet. -SET client_min_messages = warning; - --- Load the TAP functions. -BEGIN; -\i pgtap.sql -\set numb_tests 78 - --- ## SET search_path TO TAPSCHEMA,public; - --- Set the test plan. -SELECT plan(:numb_tests); - --- Replace the internal record of the plan for a few tests. -UPDATE __tcache__ SET value = 3 WHERE label = 'plan'; - -/****************************************************************************/ --- Test pass(). -SELECT pass( 'My pass() passed, w00t!' ); - --- Test fail(). -\set fail_numb 2 -\echo ok :fail_numb - Testing fail() -SELECT is( - fail('oops'), - E'not ok 2 - oops\n# Failed test 2: "oops"', 'We should get the proper output from fail()'); - --- Check the finish() output. -SELECT is( - (SELECT * FROM finish() LIMIT 1), - '# Looks like you failed 1 test of 3', - 'The output of finish() should reflect the test failure' -); - -/****************************************************************************/ --- Check num_failed -SELECT is( num_failed(), 1, 'We should have one failure' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb = :fail_numb; -SELECT is( num_failed(), 0, 'We should now have no failures' ); - -/****************************************************************************/ --- Check diag. -SELECT is( diag('foo'), '# foo', 'diag() should work properly' ); -SELECT is( diag(E'foo\nbar'), E'# foo\n# bar', 'multiline diag() should work properly' ); -SELECT is( diag(E'foo\n# bar'), E'# foo\n# # bar', 'multiline diag() should work properly with existing comments' ); - -/****************************************************************************/ --- Check no_plan. -DELETE FROM __tcache__ WHERE label = 'plan'; -SELECT * FROM no_plan(); -SELECT is( value, 0, 'no_plan() should have stored a plan of 0' ) - FROM __tcache__ - WHERE label = 'plan'; - --- Set the plan to a high number. -DELETE FROM __tcache__ WHERE label = 'plan'; -SELECT is( plan(4000), '1..4000', 'Set the plan to 4000' ); -SELECT is( - (SELECT * FROM finish() LIMIT 1), - '# Looks like you planned 4000 test but only ran 11', - 'The output of finish() should reflect a high test plan' -); - --- Set the plan to a low number. -DELETE FROM __tcache__ WHERE label = 'plan'; -SELECT is( plan(4), '1..4', 'Set the plan to 4' ); -SELECT is( - (SELECT * FROM finish() LIMIT 1), - '# Looks like you planned 4 test but ran 9 extra', - 'The output of finish() should reflect a low test plan' -); - --- Reset the original plan. -DELETE FROM __tcache__ WHERE label = 'plan'; -SELECT is( plan(:numb_tests), '1..' || :numb_tests, 'Reset the plan' ); -SELECT is( value, :numb_tests, 'plan() should have stored the test count' ) - FROM __tcache__ - WHERE label = 'plan'; - -/****************************************************************************/ --- Test ok() -\echo ok 17 - ok() success -SELECT is( ok(true), 'ok 17', 'ok(true) should work' ); -\echo ok 19 - ok() success 2 -SELECT is( ok(true, ''), 'ok 19', 'ok(true, '''') should work' ); -\echo ok 21 - ok() success 3 -SELECT is( ok(true, 'foo'), 'ok 21 - foo', 'ok(true, ''foo'') should work' ); - -\echo ok 23 - ok() failure -SELECT is( ok(false), E'not ok 23\n# Failed test 23', 'ok(false) should work' ); -\echo ok 25 - ok() failure 2 -SELECT is( ok(false, ''), E'not ok 25\n# Failed test 25', 'ok(false, '''') should work' ); -\echo ok 27 - ok() failure 3 -SELECT is( ok(false, 'foo'), E'not ok 27 - foo\n# Failed test 27: "foo"', 'ok(false, ''foo'') should work' ); - --- Clean up the failed test results. -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 23, 25, 27); - -/****************************************************************************/ --- Test is(). -\echo ok 29 - is() success -SELECT is( is(1, 1), 'ok 29', 'isa(1, 1) should work' ); -\echo ok 31 - is() success 2 -SELECT is( is('x'::text, 'x'::text), 'ok 31', 'is(''x'', ''x'') should work' ); -\echo ok 33 - is() success 3 -SELECT is( is(1.1, 1.10), 'ok 33', 'is(1.1, 1.10) should work' ); -\echo ok 35 - is() success 4 -SELECT is( is(1.1, 1.10), 'ok 35', 'is(1.1, 1.10) should work' ); -\echo ok 37 - is() success 5 -SELECT is( is(true, true), 'ok 37', 'is(true, true) should work' ); -\echo ok 39 - is() success 6 -SELECT is( is(false, false), 'ok 39', 'is(false, false) should work' ); ---SELECT is( '12:45'::time, '12:45'::time, 'ok 41', 'is(time, time) should work' ); -\echo ok 41 - is() success 7 -SELECT is( is(1, 1, 'foo'), 'ok 41 - foo', 'is(1, 1, ''foo'') should work' ); -\echo ok 43 - is() failure -SELECT is( is( 1, 2 ), E'not ok 43\n# Failed test 43\n# got: 1\n# expected: 2', 'is(1, 2) should work' ); - -/****************************************************************************/ --- Test isnt(). -\echo ok 45 - isnt() success -SELECT is( isnt(1, 2), 'ok 45', 'isnt(1, 2) should work' ); -\echo ok 47 - isnt() failure -SELECT is( isnt( 1, 1 ), E'not ok 47\n# Failed test 47\n# 1\n# <>\n# 1', 'is(1, 2) should work' ); - --- Clean up the failed test results. -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 43, 47 ); - -/****************************************************************************/ --- Try using variables. -\set foo '\'' waffle '\'' -\set bar '\'' waffle '\'' -SELECT is( :foo::text, :bar::text, 'is() should work with psql variables' ); - -/****************************************************************************/ --- Test matches(). -SELECT matches( 'foo'::text, 'o', 'matches() should work' ); -SELECT matches( 'foo'::text, '^fo', 'matches() should work with a regex' ); -SELECT imatches( 'FOO'::text, '^fo', 'imatches() should work with a regex' ); - --- Check matches() diagnostics. -\echo ok 53 - matches() failure -SELECT is( matches( 'foo'::text, '^a' ), E'not ok 53\n# Failed test 53\n# ''foo''\n# doesn''t match: ''^a''', 'Check matches diagnostics' ); - --- Check doesnt_match. -SELECT doesnt_match( 'foo'::text, 'a', 'doesnt_match() should work' ); -SELECT doesnt_match( 'foo'::text, '^o', 'doesnt_match() should work with a regex' ); -SELECT doesnt_imatch( 'foo'::text, '^o', 'doesnt_imatch() should work with a regex' ); - --- Check doesnt_match diagnostics. -\echo ok 58 - doesnt_match() failure -SELECT is( - doesnt_match( 'foo'::text, 'o' ), - E'not ok 58\n# Failed test 58\n# ''foo''\n# matches: ''o''', - 'doesnt_match() should work' -); - --- Clean up the failed test results. -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 53, 58 ); - -/****************************************************************************/ --- Test alike(). -SELECT alike( 'foo'::text, 'foo', 'alike() should work' ); -SELECT alike( 'foo'::text, 'fo%', 'alike() should work with a regex' ); -SELECT ialike( 'FOO'::text, 'fo%', 'ialike() should work with a regex' ); - --- Check alike() diagnostics. -\echo ok 63 - alike() failure -SELECT is( alike( 'foo'::text, 'a%'::text ), E'not ok 63\n# Failed test 63\n# ''foo''\n# doesn''t match: ''a%''', 'Check alike diagnostics' ); - --- Test unalike(). -SELECT unalike( 'foo'::text, 'f', 'unalike() should work' ); -SELECT unalike( 'foo'::text, 'f%i', 'unalike() should work with a regex' ); -SELECT unialike( 'FOO'::text, 'f%i', 'iunalike() should work with a regex' ); - --- Check unalike() diagnostics. -\echo ok 68 - unalike() failure -SELECT is( unalike( 'foo'::text, 'f%'::text ), E'not ok 68\n# Failed test 68\n# ''foo''\n# matches: ''f%''', 'Check unalike diagnostics' ); - --- Clean up the failed test results. -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 63, 68 ); - -/****************************************************************************/ --- test throws_ok(). -SELECT throws_ok( 'SELECT 1 / 0', '22012', 'throws_ok(1/0) should work' ); - --- Check its diagnostics for an invalid error code. -\echo ok 71 - throws_ok failure diagnostics -SELECT is( - throws_ok( 'SELECT 1 / 0', '97212' ), - E'not ok 71 - threw 97212\n# Failed test 71: "threw 97212"\n# caught: 22012: division by zero\n# expected: 97212', - 'We should get the proper diagnostics from throws_ok()' -); - -SELECT throws_ok( 'SELECT 1 / 0', NULL, 'throws_ok(1/0, NULL) should work' ); - --- Check its diagnostics no error. -\echo ok 74 - throws_ok failure diagnostics -SELECT is( - throws_ok( 'SELECT 1', NULL ), - E'not ok 74 - threw an exception\n# Failed test 74: "threw an exception"\n# caught: no exception\n# expected: an exception', - 'We should get the proper diagnostics from throws_ok() with a NULL error code' -); - --- Clean up the failed test results. -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 71, 74 ); - -/****************************************************************************/ --- test lives_ok(). -SELECT lives_ok( 'SELECT 1', 'lives_ok() should work' ); - --- Check its diagnostics when there is an exception. -\echo ok 77 - lives_ok failure diagnostics -SELECT is( - lives_ok( 'SELECT 1 / 0' ), - E'not ok 77\n# Failed test 77\n# died: 22012: division by zero', - 'We should get the proper diagnostics for a lives_ok() failure' -); - -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 77 ); - --- Finish the tests and clean up. -SELECT * FROM finish(); -ROLLBACK; - From b3ed0fb0cc98b729a4e7ec8759e53648a9a5d518 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 9 Jun 2008 18:29:16 +0000 Subject: [PATCH 0017/1195] More on installation. --- README.pgtap | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.pgtap b/README.pgtap index 85f594aeb975..d593354f38c9 100644 --- a/README.pgtap +++ b/README.pgtap @@ -69,6 +69,12 @@ If you want to remove pgTAP from a database, run the drop_pgtap.sql script: psql dbname -f drop_pgtap.sql +Both scripts will also be installed in the "contrib" directory under the +directory output by `pg_config --sharedir`. So you can always do this, +as well: + + psql template1 -f `pg_config --sharedir`/contrib/pgtap.sql + Running pgTAP Tests =================== From 0e9efc0eca231b0d82246938127b72c948d6c392 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 9 Jun 2008 18:38:08 +0000 Subject: [PATCH 0018/1195] * Bumped version number. * Added some more files to svn:ignore. * Added To Do section to docs. --- README.pgtap | 18 ++++++++++++++---- pg_prove | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/README.pgtap b/README.pgtap index d593354f38c9..9f9e8898cbc0 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.01 +pgTAP 0.02 ========== pgTAP is a collection of TAP-emitting unit testing functions written in @@ -34,15 +34,15 @@ If you encounter an error such as: Be sure that you have pg_config installed and in your path. If you used a package management system such as RPM to install PostgreSQL, be sure that the -devel package is also installed. If necessary, add the path to pg_config to -your $PATH environment variable: +your `$PATH` environment variable: env PATH=$PATH:/path/to/pgsql/bin make && make test && make install And finally, if all that fails, copy the entire distribution directory to the -'contrib' subdirectory of the PostgreSQL source code and try it there. +`contrib/` subdirectory of the PostgreSQL source code and try it there. If you want to schema-qualify pgTAP (that is, install all of its functions -into their own schema), set the $TAPSCHEMA environment variable to the name +into their own schema), set the `$TAPSCHEMA` environment variable to the name of the schema you'd like, for example: make TAPSCHEMA=tap @@ -75,6 +75,9 @@ as well: psql template1 -f `pg_config --sharedir`/contrib/pgtap.sql +But do be aware that, if you've specified a schema using `$TAPSCHEMA`, it will +always be created and the functions placed in it. + Running pgTAP Tests =================== @@ -447,6 +450,13 @@ Which would produce: # but yours is set to en_US.UTF-8. # As a result, some tests may fail. YMMV. +To Do +----- + +* Update the Makefile to process pg_prove and set the proper path to Perl on + the shebang line. Will likely require a `$(PERL)` environment variable. +* Add a test function to test test functions. + Suported Versions ----------------- diff --git a/pg_prove b/pg_prove index dc42d32386cf..f94d63202b03 100755 --- a/pg_prove +++ b/pg_prove @@ -4,7 +4,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.01'; +our $VERSION = '0.02'; Getopt::Long::Configure (qw(bundling)); From a987ee9b7162904b42563a61a2395fd97d419558 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 9 Jun 2008 18:59:51 +0000 Subject: [PATCH 0019/1195] * Added a couple more To Dos. * Documented the changes in 0.02. Waiting on pgFoundry to release. --- Changes | 37 +++++++++++++++++++++++++++++++++++-- README.pgtap | 4 +++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 65c6f4ba9722..15ee3a0992ef 100644 --- a/Changes +++ b/Changes @@ -1,4 +1,37 @@ Revision history for pgTAP -0.01 - - Initial public release. +0.02 + - Converted the documentation to Markdown. + - Added pg_prove, a Perl script that use TAP::Harness to run tests and + report the results, juse like the Perl program `prove`. + - Fixed `no_plan()` so that it no longer emits a plan, which apparently + was wrong. + - Fixed the test script so that it now emits a proper plan. + - Removed all of the configuration settings from `pgtap.sql`, as they're + now handled by `pg_prove`. I've mengioned them in the README for + reference. + - Added `lives_ok()`. + - Moved the creation of temporary tables into `plan()`, so that + everything is properly self-contained. + - Improved the handling of transactions. Test scripts are now assumed to + be single transactions with a ROLLBACK at the end. This makes it so + that test scripts don't have to include `drop_pgtap.sql`. + - Updated `pg_prove` to rollback on an error, rather than just stop. + This allows all test functions to be properly rolled back, too, in a + test script that includes them but then encounters an unhandled + exception. + - Updated `pg_prove` to emit an appropriate error message if no test + scripts are specified. + - Added a Makefile. It uses the typical PostgreSQL installation + procedures to install pgTAP. The SQL files have now been turned into + `.in` templates that are processed by `make`. + - Added support for schema qualification of test functions. Just set the + `$TAPSCHEMA` environment variable when running `make`. + - Added support for standard PostgreSQL-type regression testing by just + copying the test script, setting some variables inside it, and + providing an `expected/` directory. The test now lives in the `sql/` + directory. + +0.01 2008-06-07T05:24:27 + - Initial public release. Announcement at + http://justatheory.com/computers/databases/postgresql/introducing_pgtap.html diff --git a/README.pgtap b/README.pgtap index 9f9e8898cbc0..d7ef362bfa56 100644 --- a/README.pgtap +++ b/README.pgtap @@ -455,7 +455,9 @@ To Do * Update the Makefile to process pg_prove and set the proper path to Perl on the shebang line. Will likely require a `$(PERL)` environment variable. -* Add a test function to test test functions. +* Update Makefile to require TAP::Harness. +* Add a test function to test test functions and update the test script to + use it. Suported Versions ----------------- From bead4c3e8ac8d2089133d24ad43a54b302f282a3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 10 Jun 2008 18:09:55 +0000 Subject: [PATCH 0020/1195] Fewer commented-out lines in taptest.sql. If I can figure out how to get rid of the `CREATE LANGUAGE` line, I will not need to process that file at all, which will save some Makefile hacking. --- expected/pgtap.out | 1 - sql/taptest.sql | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/expected/pgtap.out b/expected/pgtap.out index 4389f5805315..29923016445c 100644 --- a/expected/pgtap.out +++ b/expected/pgtap.out @@ -1,4 +1,3 @@ --- These are here for the usual PostgreSQL regression tests to use. \set ECHO 1..78 ok 1 - My pass() passed, w00t! diff --git a/sql/taptest.sql b/sql/taptest.sql index d88ef2db756b..557f38d86a8a 100644 --- a/sql/taptest.sql +++ b/sql/taptest.sql @@ -1,10 +1,10 @@ --- These are here for the usual PostgreSQL regression tests to use. --- !! \set ECHO --- !! \set ON_ERROR_ROLBACK 1 --- !! \pset format unaligned --- !! \pset tuples_only --- !! \pset pager +\set ECHO +\set ON_ERROR_ROLBACK 1 +\pset format unaligned +\pset tuples_only true +\pset pager -- !! CREATE LANGUAGE plpgsql; + -- -- Tests for pgTAP. -- From 3479053485ac4388015b05ec9af28f15b05da038 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 10 Jun 2008 18:25:55 +0000 Subject: [PATCH 0021/1195] No more processing of the test script in the makefile. W00t! --- Makefile | 9 +-------- sql/{taptest.sql => pgtap.sql} | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 13 deletions(-) rename sql/{taptest.sql => pgtap.sql} (98%) diff --git a/Makefile b/Makefile index 3e612d16e9ce..a7074bc47b98 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,7 @@ DATA_built = pgtap.sql drop_pgtap.sql DOCS = README.pgtap SCRIPTS = pg_prove -TAPTEST = sql/taptest.sql REGRESS = pgtap -EXTRA_CLEAN = sql/$(REGRESS).sql top_builddir = ../.. in_contrib = $(wildcard $(top_builddir)/src/Makefile.global); @@ -19,8 +17,6 @@ else include $(PGXS) endif -all: $(DATA_built) $(TAPTEST) $(SCRIPTS) cptest - # Override how .sql targets are processed to add the schema info, if # necessary. Otherwise just copy the files. %.sql: %.sql.in @@ -30,8 +26,5 @@ else cp $< $@ endif -cptest: - sed -e 's/^-- !! //g' $(TAPTEST) > sql/$(REGRESS).sql - test: - ./pg_prove $(TAPTEST) + ./pg_prove sql/$(REGRESS).sql diff --git a/sql/taptest.sql b/sql/pgtap.sql similarity index 98% rename from sql/taptest.sql rename to sql/pgtap.sql index 557f38d86a8a..0230cf531e3e 100644 --- a/sql/taptest.sql +++ b/sql/pgtap.sql @@ -1,18 +1,25 @@ \set ECHO -\set ON_ERROR_ROLBACK 1 -\pset format unaligned -\pset tuples_only true -\pset pager --- !! CREATE LANGUAGE plpgsql; -- -- Tests for pgTAP. -- -- +-- Format the output for nice TAP. +\pset format unaligned +\pset tuples_only true +\pset pager + +-- Create plpgsql if it's not already there. +SET client_min_messages = fatal; +CREATE LANGUAGE plpgsql; + -- Keep things quiet. SET client_min_messages = warning; +-- Revert all changes on failure. +\set ON_ERROR_ROLBACK 1 + -- Load the TAP functions. BEGIN; \i pgtap.sql From bb116fe5d112586024a7c759ae307548fe9714af Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 10 Jun 2008 18:59:50 +0000 Subject: [PATCH 0022/1195] RETURN NEXT, dude. --- Changes | 3 +++ pgtap.sql.in | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 15ee3a0992ef..32c896f0306f 100644 --- a/Changes +++ b/Changes @@ -31,6 +31,9 @@ Revision history for pgTAP copying the test script, setting some variables inside it, and providing an `expected/` directory. The test now lives in the `sql/` directory. + - Changed all instances of `RETURN QUERY SELECT` to `RETURN NEXT`, which + should allow pgtap to run on versions of PostgreSQL earlier than 8.3. + Thanks to Niel Conway for the suggestion. 0.01 2008-06-07T05:24:27 - Initial public release. Announcement at diff --git a/pgtap.sql.in b/pgtap.sql.in index d7eecce3719a..a9e0ce059c6d 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -97,17 +97,17 @@ BEGIN END IF; IF curr_test < exp_tests THEN - RETURN QUERY SELECT diag( + RETURN NEXT diag( 'Looks like you planned ' || exp_tests || ' test' || plural || ' but only ran ' || curr_test ); ELSIF curr_test > exp_tests THEN - RETURN QUERY SELECT diag( + RETURN NEXT diag( 'Looks like you planned ' || exp_tests || ' test' || plural || ' but ran ' || curr_test - exp_tests || ' extra' ); ELSIF num_failed > 0 THEN - RETURN QUERY SELECT diag( + RETURN NEXT diag( 'Looks like you failed ' || num_failed || ' test' || plural || ' of ' || exp_tests ); From a092563c6308b242e57ccb3b73af4e07af6cb03b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 11 Jun 2008 19:31:37 +0000 Subject: [PATCH 0023/1195] Fixed `finish()` so that it properly outputs a plan when `no_plan()` was called. --- Changes | 2 +- pgtap.sql.in | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 32c896f0306f..d61d19053cb2 100644 --- a/Changes +++ b/Changes @@ -5,7 +5,7 @@ Revision history for pgTAP - Added pg_prove, a Perl script that use TAP::Harness to run tests and report the results, juse like the Perl program `prove`. - Fixed `no_plan()` so that it no longer emits a plan, which apparently - was wrong. + was wrong. Now `finish()` outputs it when there's no plan. - Fixed the test script so that it now emits a proper plan. - Removed all of the configuration settings from `pgtap.sql`, as they're now handled by `pg_prove`. I've mengioned them in the README for diff --git a/pgtap.sql.in b/pgtap.sql.in index a9e0ce059c6d..2aff7d122cc7 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -92,8 +92,9 @@ BEGIN END IF; IF exp_tests = 0 THEN - -- No plan. + -- No plan. Output one now. exp_tests = curr_test; + RETURN NEXT '1..' || exp_tests; END IF; IF curr_test < exp_tests THEN From 9fbdb21d5696fb6ecf6de8bc7d808e724a973d6e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 11 Jun 2008 20:28:33 +0000 Subject: [PATCH 0024/1195] Die on any errors. --- sql/pgtap.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/sql/pgtap.sql b/sql/pgtap.sql index 0230cf531e3e..f67aea6894cd 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -19,6 +19,7 @@ SET client_min_messages = warning; -- Revert all changes on failure. \set ON_ERROR_ROLBACK 1 +\set ON_ERROR_STOP true -- Load the TAP functions. BEGIN; From 70ac30b756f8e006ac6c26e2f51f487baf4132da Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 11 Jun 2008 21:01:10 +0000 Subject: [PATCH 0025/1195] Added `--color` (on by default) and `--nocolor`. --- pg_prove | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/pg_prove b/pg_prove index f94d63202b03..227b6f9a1cfa 100755 --- a/pg_prove +++ b/pg_prove @@ -8,7 +8,7 @@ our $VERSION = '0.02'; Getopt::Long::Configure (qw(bundling)); -my $opts = { psql => 'psql' }; +my $opts = { psql => 'psql', color => 1 }; Getopt::Long::GetOptions( 'psql-bin|b=s' => \$opts->{psql}, @@ -17,6 +17,7 @@ Getopt::Long::GetOptions( 'host|h=s' => \$opts->{host}, 'port|p=s' => \$opts->{port}, 'timer|t' => \$opts->{timer}, + 'color|c!' => \$opts->{color}, 'verbose|v+' => \$opts->{verbose}, 'help|H' => \$opts->{help}, 'man|m' => \$opts->{man}, @@ -67,6 +68,7 @@ print join ' ', @command, $/; TAP::Harness->new({ verbosity => $opts->{verbose} || $ENV{TEST_VERBOSE}, timer => $opts->{timer}, + color => $opts->{color}, exec => \@command, })->runtests( @ARGV ); @@ -96,6 +98,8 @@ The test scripts should be a series of SQL statements -h --host HOST Host to which to connect. -p --port PORT Port to which to connect. -t --timer Print elapsed time after each test file. + -c --color Display colored test ouput. + --nocolor Do not display colored test ouput. -v --verbose Display output of test scripts while running them. -h --help Print a usage statement and exit. -m --man Print the complete documentation and exit. @@ -175,6 +179,24 @@ true value. Print elapsed time after each test file. +=item C<-t> + +=item C<--color> + + pg_prove --color + pg_prove -c + +Display test results in color. Colored test output is the default, but if +output is not to a terminal, color is disabled. + +Requires L on Unix‐like platforms and +L on Windows. If the necessary module is not +installed colored output will not be available. + +=item C<--nocolor> + +Do not display test results in color. + =item C<-H> =item C<--help> From ee106d45a331c0506d80faf60d1e301f78563102 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 12 Jun 2008 04:34:36 +0000 Subject: [PATCH 0026/1195] No psqlrc! --- pg_prove | 1 + 1 file changed, 1 insertion(+) diff --git a/pg_prove b/pg_prove index 227b6f9a1cfa..f7744ca95d6c 100755 --- a/pg_prove +++ b/pg_prove @@ -55,6 +55,7 @@ for (qw(username host port dbname)) { } push @command, qw( + --no-psqlrc --no-align --tuples-only --pset pager= From 616fdeff184840287d3bf7fdabf16a479c8ce592 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 17 Jun 2008 16:18:42 +0000 Subject: [PATCH 0027/1195] Timestampd for 0.02 release. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index d61d19053cb2..bb362def144c 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,6 @@ Revision history for pgTAP -0.02 +0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. - Added pg_prove, a Perl script that use TAP::Harness to run tests and report the results, juse like the Perl program `prove`. From c97a8b5d8d9a2ab126733d52ed5328a9fe3488a2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 17 Jun 2008 16:34:12 +0000 Subject: [PATCH 0028/1195] Incremented version number to 0.02. --- Changes | 2 ++ README.pgtap | 2 +- pg_prove | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index bb362def144c..10cb6580e90e 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Revision history for pgTAP +0.03 + 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. - Added pg_prove, a Perl script that use TAP::Harness to run tests and diff --git a/README.pgtap b/README.pgtap index d7ef362bfa56..60acb94be7f2 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.02 +pgTAP 0.03 ========== pgTAP is a collection of TAP-emitting unit testing functions written in diff --git a/pg_prove b/pg_prove index f7744ca95d6c..58163035bb8b 100755 --- a/pg_prove +++ b/pg_prove @@ -4,7 +4,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.02'; +our $VERSION = '0.03'; Getopt::Long::Configure (qw(bundling)); From 2457e8539ff1df663f1e876d4f626d656439a1cc Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 17 Jun 2008 16:45:20 +0000 Subject: [PATCH 0029/1195] Make things quieter. --- Changes | 2 ++ pg_prove | 1 + 2 files changed, 3 insertions(+) diff --git a/Changes b/Changes index 10cb6580e90e..524796c6591a 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,8 @@ Revision history for pgTAP 0.03 + - Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the + output of extraneous stuff. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/pg_prove b/pg_prove index 58163035bb8b..f59ff4569023 100755 --- a/pg_prove +++ b/pg_prove @@ -61,6 +61,7 @@ push @command, qw( --pset pager= --pset null=[NULL] --set ON_ERROR_ROLLBACK=1 + --set QUIET=1 --file ); From dc408965f61a54abc074554d4ef2edcf45469dfb Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Jun 2008 18:35:37 +0000 Subject: [PATCH 0030/1195] Added `GRANT`s to `plan()` to allow test functions to be run by all roles. --- Changes | 3 +++ pgtap.sql.in | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 524796c6591a..9034e8e82367 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,9 @@ Revision history for pgTAP 0.03 - Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the output of extraneous stuff. + - Added some `GRANT` statements to `plan()` in order to make it easier + to run tests using roles other than the one that called `plan()`. + Patch from Rod Taylor. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/pgtap.sql.in b/pgtap.sql.in index 2aff7d122cc7..564714330a62 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1,7 +1,7 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA,public; -CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION pgtap.plan( integer ) RETURNS TEXT AS $$ BEGIN BEGIN EXECUTE ' @@ -9,6 +9,7 @@ BEGIN label TEXT NOT NULL, value integer NOT NULL ); + GRANT ALL ON TABLE __tcache__ TO PUBLIC; CREATE TEMP TABLE __tresults__ ( numb SERIAL PRIMARY KEY, @@ -18,6 +19,8 @@ BEGIN type TEXT NOT NULL DEFAULT '''', reason TEXT NOT NULL DEFAULT '''' ); + GRANT ALL ON TABLE __tresults__ TO PUBLIC; + GRANT ALL ON SEQUENCE __tresults___numb_seq TO PUBLIC; '; EXCEPTION WHEN duplicate_table THEN From 9b2360b6c1355ebca7572faec508224721ef2e3b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Jun 2008 18:40:41 +0000 Subject: [PATCH 0031/1195] Didn't mean to check it in with the schema name. --- pgtap.sql.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 564714330a62..4b564d577cbf 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1,7 +1,7 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA,public; -CREATE OR REPLACE FUNCTION pgtap.plan( integer ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ BEGIN BEGIN EXECUTE ' From b5c9273a3e99e343d9abb412a17df5f9df942f85 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Jun 2008 19:09:35 +0000 Subject: [PATCH 0032/1195] Whitespace tweaking in the documentation. --- README.pgtap | 68 ++++++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/README.pgtap b/README.pgtap index 60acb94be7f2..fe476ab5820a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -166,7 +166,7 @@ At the end of your script, you should always tell pgTAP that the tests have completed, so that it can output any diagnostics about failures or a discrepancy between the planned number of tests and the number actually run: - SELECT * FROM finish ( ); + SELECT * FROM finish(); Test names ---------- @@ -201,8 +201,8 @@ given test succeeded or failed. Everything else is just gravy. All of the following functions return "ok" or "not ok" depending on whether the test succeeded or failed. -### ok ( boolean, text ) ### -### ok ( boolean ) ### +### ok( boolean, text ) ### +### ok( boolean ) ### SELECT ok( :this = :that, :test_name ); @@ -230,12 +230,12 @@ Should an ok() fail, it will produce some diagnostics: not ok 18 - sufficient mucus # Failed test 18: "sufficient mucus" -### is (anyelement, anyelement, text) ### -### is (anyelement, anyelement ) ### -### isnt (anyelement, anyelement, text) ### -### isnt (anyelement, anyelement ) ### +### is( anyelement, anyelement, text ) ### +### is( anyelement, anyelement ) ### +### isnt( anyelement, anyelement, text ) ### +### isnt( anyelement, anyelement ) ### - SELECT is ( :this, :that, $test_name ); + SELECT is( :this, :that, $test_name ); SELECT isnt( :this, :that, $test_name ); Similar to ok(), is() and isnt() compare their two arguments with `=` and @@ -284,8 +284,8 @@ ok(). SELECT ok( is_valid(9), 'A tree grows in Brooklyn' ); -### matches ( anyelement, text, text ) ### -### matches ( anyelement, text ) ### +### matches( anyelement, text, text ) ### +### matches( anyelement, text ) ### SELECT matches( :this, '^that', :test_name ); @@ -304,28 +304,28 @@ is similar to: Its advantages over ok() are similar to that of is() and isnt(): Better diagnostics on failure. -### imatches ( anyelement, text, text ) ### -### imatches ( anyelement, text ) ### +### imatches( anyelement, text, text ) ### +### imatches( anyelement, text ) ### SELECT imatches( :this, '^that', :test_name ); These are just like matches() except that the regular expression is case-insensitive. -### doesnt_match ( anyelement, text, text ) ### -### doesnt_match ( anyelement, text ) ### -### doesnt_imatch ( anyelement, text, text ) ### -### doesnt_imatch ( anyelement, text ) ### +### doesnt_match( anyelement, text, text ) ### +### doesnt_match( anyelement, text ) ### +### doesnt_imatch( anyelement, text, text ) ### +### doesnt_imatch( anyelement, text ) ### SELECT doesnt_match( :this, '^that', :test_name ); These functions work exactly as matches() and imataches() do, only they check if :this *does not* match the given pattern. -### alike ( anyelement, text, text ) ### -### alike ( anyelement, text ) ### -### ialike ( anyelement, text, text ) ### -### ialike ( anyelement, text ) ### +### alike( anyelement, text, text ) ### +### alike( anyelement, text ) ### +### ialike( anyelement, text, text ) ### +### ialike( anyelement, text ) ### SELECT alike( :this, 'that%', :test_name ); @@ -345,20 +345,20 @@ is similar to: Its advantages over ok() are similar to that of is() and isnt(): Better diagnostics on failure. -### unalike ( anyelement, text, text ) ### -### unalike ( anyelement, text ) ### -### unialike ( anyelement, text, text ) ### -### unialike ( anyelement, text ) ### +### unalike( anyelement, text, text ) ### +### unalike( anyelement, text ) ### +### unialike( anyelement, text, text ) ### +### unialike( anyelement, text ) ### SELECT unalike( :this, 'that%', :test_name ); Works exactly as alike(), only it checks if :this *does not* match the given pattern. -### pass ( text ) ### -### pass ( ) ### -### fail ( text ) ### -### fail ( ) ### +### pass( text ) ### +### pass() ### +### fail( text ) ### +### fail() ### SELECT pass( :test_name ); SELECT fail( :test_name ); @@ -370,9 +370,9 @@ ok) or fail() (for not ok). They are synonyms for ok(1) and ok(0). Use these functions very, very, very sparingly. -### throws_ok ( text, char(5), text ) ### -### throws_ok ( text, char(5) ) ### -### throws_ok ( text ) ### +### throws_ok( text, char(5), text ) ### +### throws_ok( text, char(5) ) ### +### throws_ok( text ) ### SELECT throws_ok( 'INSERT INTO try (id) VALUES (1)', @@ -403,8 +403,8 @@ messages. For example: Idea borrowed from the Test::Exception Perl module. -### lives_ok ( text, text ) ### -### lives_ok ( text ) ### +### lives_ok( text, text ) ### +### lives_ok( text ) ### SELECT lives_ok( 'INSERT INTO try (id) VALUES (1)', @@ -429,7 +429,7 @@ went wrong when it failed. But sometimes it doesn't work out that way. So here we have ways for you to write your own diagnostic messages which are safer than just \echo. -### diag ( text ) ### +### diag( text ) ### Returns a diagnostic message which is guaranteed not to interfere with test output. Handy for this sort of thing: From e882021083fd1414da516c69e4d8e293ec6b6bd2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 21 Jun 2008 22:00:17 +0000 Subject: [PATCH 0033/1195] Use currval instead of lastval. --- Changes | 2 ++ pgtap.sql.in | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 9034e8e82367..83f2b33f85d2 100644 --- a/Changes +++ b/Changes @@ -6,6 +6,8 @@ Revision history for pgTAP - Added some `GRANT` statements to `plan()` in order to make it easier to run tests using roles other than the one that called `plan()`. Patch from Rod Taylor. + - Replaced a call to `lastval()` with a call to `currvall()` in order to + improve compatibility with PostgreSQL 8.0. Reported by David Westbrook. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/pgtap.sql.in b/pgtap.sql.in index 4b564d577cbf..b6d3c4bc3640 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -69,7 +69,7 @@ BEGIN EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) VALUES( ' || $1 || ', ' || $2 || ', ' || COALESCE(quote_literal($3), '''''') || ', ' || quote_literal($4) || ', ' || quote_literal($5) || ' )'; - RETURN lastval(); + RETURN currval('__tresults___numb_seq'); END; $$ LANGUAGE plpgsql; From a7b501bcace0bd9db1456fde4c8fe131c5a18bcd Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 24 Jun 2008 02:16:28 +0000 Subject: [PATCH 0034/1195] Added support for TODO tests. :-) --- Changes | 1 + README.pgtap | 33 +++++++++++++++++++++++ pgtap.sql.in | 73 ++++++++++++++++++++++++++++++++++++++++++--------- sql/pgtap.sql | 26 ++++++++++++++++-- 4 files changed, 119 insertions(+), 14 deletions(-) diff --git a/Changes b/Changes index 83f2b33f85d2..a1effd715e23 100644 --- a/Changes +++ b/Changes @@ -8,6 +8,7 @@ Revision history for pgTAP Patch from Rod Taylor. - Replaced a call to `lastval()` with a call to `currvall()` in order to improve compatibility with PostgreSQL 8.0. Reported by David Westbrook. + - Added support for TODO tests. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/README.pgtap b/README.pgtap index fe476ab5820a..ae62d0c311a8 100644 --- a/README.pgtap +++ b/README.pgtap @@ -450,6 +450,39 @@ Which would produce: # but yours is set to en_US.UTF-8. # As a result, some tests may fail. YMMV. +Conditional Tests +----------------- + +Sometimes you might have tests that you want to pass, but you haven't gotten +'round to implementing the logic required to make them pass. pgTAP allows you +to declare that such tests are supposed to fail but will work in the future. +This is known as a "todo test." + +### todo ### + + todo(:why, :howMany); + -- ...normal testing code goes here... + +Declares a series of tests that you expect to fail and why. Perhaps it's +because you haven't fixed a bug or haven't finished a new feature: + + todo('URIGeller not finished', 2); + + \set card '\'' Eight of clubs '\'' + SELECT is( URIGeller.yourCard(), :card, 'Is THIS your card?' ); + SELECT is( URIGeller.bendSpoon(), 'bent', 'Spoon bending, how original' ); + +With `todo()`, `:howMany` specifies how many tests are expected to fail. pgTAP +will run the tests normally, but print out special flags indicating they are +"todo" tests. The test harness will interpret these failures as ok. Should any +todo test pass, the harness will report it as an unexpected success. You then +know the thing you had todo is done and can remove the call to `todo()`. + +The nice part about todo tests, as opposed to simply commenting out a block of +tests, is that they're like a programmatic todo list. You know how much work +is left to be done, you're aware of what bugs there are, and you'll know +immediately when they're fixed. + To Do ----- diff --git a/pgtap.sql.in b/pgtap.sql.in index b6d3c4bc3640..97daeaf36fe1 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -7,7 +7,8 @@ BEGIN EXECUTE ' CREATE TEMP TABLE __tcache__ ( label TEXT NOT NULL, - value integer NOT NULL + value INTEGER NOT NULL, + note TEXT NOT NULL DEFAULT '''' ); GRANT ALL ON TABLE __tcache__ TO PUBLIC; @@ -32,7 +33,7 @@ BEGIN END; -- Save the plan and return. - EXECUTE 'INSERT INTO __tcache__ VALUES ( ''plan'', ' || $1 || ' )'; + PERFORM _set('plan', $1 ); RETURN '1..' || $1; END; $$ LANGUAGE plpgsql strict; @@ -53,17 +54,33 @@ BEGIN END; $$ LANGUAGE plpgsql strict; -CREATE OR REPLACE FUNCTION _set ( text, integer ) RETURNS integer AS $$ +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 _set ( text, integer, text ) RETURNS integer AS $$ +DECLARE BEGIN - EXECUTE 'UPDATE __tcache__ SET value = ' || $2 || ' WHERE label = ' || quote_literal($1); + EXECUTE 'UPDATE __tcache__ SET value = ' || $2 + || CASE $3 WHEN '' THEN '' ELSE ', note = ' || quote_literal($3) END + || ' WHERE label = ' || quote_literal($1); IF NOT FOUND THEN - EXECUTE 'INSERT INTO __tcache__ values (' || quote_literal($1) || ', ' || $2 || ')'; + EXECUTE 'INSERT INTO __tcache__ values (' || quote_literal($1) || ', ' || $2 || ', ' || quote_literal(COALESCE($3, '')) || ')'; END IF; RETURN $2; END; $$ LANGUAGE plpgsql strict; -CREATE OR REPLACE FUNCTION add_result ( boolean, bool, text, text, text ) +CREATE OR REPLACE FUNCTION _set ( text, integer ) RETURNS integer AS $$ + SELECT _set($1, $2, '') +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION add_result ( bool, bool, text, text, text ) RETURNS integer AS $$ BEGIN EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) @@ -129,21 +146,32 @@ BEGIN END; $$ LANGUAGE plpgsql strict; -CREATE OR REPLACE FUNCTION ok ( ok boolean, descr text ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION ok ( aok boolean, descr text ) RETURNS TEXT AS $$ DECLARE test_num integer; + todo_why TEXT := _todo(); + ok bool := CASE WHEN aok = TRUE THEN aok WHEN todo_why IS NULL THEN aok ELSE TRUE END; BEGIN 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, ok, descr, '', '' ); + test_num := add_result( + ok, + aok, + descr, + CASE WHEN todo_why IS NULL THEN '' ELSE 'todo' END, + COALESCE(todo_why, '') + ); - RETURN (CASE ok WHEN TRUE THEN '' ELSE 'not ' END) + RETURN (CASE aok WHEN TRUE THEN '' ELSE 'not ' END) || 'ok ' || _set( 'curr_test', test_num ) - || CASE descr WHEN '' THEN '' ELSE COALESCE( ' - ' || descr, '' ) END - || CASE ok WHEN TRUE THEN '' ELSE E'\n' || - diag('Failed test ' || test_num || + || CASE descr WHEN '' THEN '' ELSE COALESCE( ' - ' || substr(regexp_replace( descr, '^', '# ', 'gn' ), 3), '' ) END + || COALESCE( ' ' || regexp_replace( 'TODO ' || todo_why, '^', '# ', 'gn' ), '') + || 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 ) END; END; @@ -298,6 +326,27 @@ 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 _set('todo', COALESCE( _get('todo'), 0) + how_many, why ); + RETURN; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _todo() RETURNS TEXT AS $$ +DECLARE + todos int := _get('todo'); +BEGIN + IF todos IS NULL OR todos = 0 THEN + RETURN NULL; + END IF; + -- Decrement the count of todos and return the reason. + PERFORM _set('todo', todos - 1); + RETURN _get_note('todo'); +END; +$$ LANGUAGE plpgsql; + CREATE OR REPLACE FUNCTION throws_ok ( code TEXT, err CHAR(5), diff --git a/sql/pgtap.sql b/sql/pgtap.sql index f67aea6894cd..7d2899d67184 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -24,7 +24,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 78 +\set numb_tests 83 -- ## SET search_path TO TAPSCHEMA,public; @@ -238,8 +238,30 @@ SELECT is( ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 77 ); +\echo ok 79 - lives_ok is ok + +/****************************************************************************/ +-- test multiline description. +SELECT is( + ok( true, E'foo\nbar' ), + E'ok 79 - foo\n# bar', + 'multiline desriptions should have subsequent lines escaped' +); + +/****************************************************************************/ +-- Test todo tests. +\echo ok 81 todo fail +\echo ok 82 todo pass +SELECT * FROM todo('just because', 2 ); +SELECT is( + fail('This is a todo test' ) + || pass('This is a todo test that unexpectedly passes' ), + 'not ok 81 - This is a todo test # TODO just because +# Failed (TODO) test 81: "This is a todo test"ok 82 - This is a todo test that unexpectedly passes # TODO just because', + 'TODO tests should display properly' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 81 ); -- Finish the tests and clean up. SELECT * FROM finish(); ROLLBACK; - From 1334bf4f44bc88ac07fdc653a0f9a49e704cca46 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 24 Jun 2008 02:19:29 +0000 Subject: [PATCH 0035/1195] * Added `svn:keywords` for the $Id keyword. * Updated the expected output for the regression tests. --- Makefile | 1 + drop_pgtap.sql.in | 1 + expected/pgtap.out | 7 ++++++- pg_prove | 2 ++ pgtap.sql.in | 1 + sql/pgtap.sql | 1 + 6 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a7074bc47b98..7655237415e8 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,4 @@ +# $Id$ DATA_built = pgtap.sql drop_pgtap.sql DOCS = README.pgtap SCRIPTS = pg_prove diff --git a/drop_pgtap.sql.in b/drop_pgtap.sql.in index 931706cfa3be..58b87d68a232 100644 --- a/drop_pgtap.sql.in +++ b/drop_pgtap.sql.in @@ -1,3 +1,4 @@ +-- $Id$ DROP FUNCTION lives_ok ( TEXT ); DROP FUNCTION lives_ok ( TEXT, TEXT ); DROP FUNCTION throws_ok ( TEXT, CHAR(5) ); diff --git a/expected/pgtap.out b/expected/pgtap.out index 29923016445c..6fcb01959de5 100644 --- a/expected/pgtap.out +++ b/expected/pgtap.out @@ -1,5 +1,5 @@ \set ECHO -1..78 +1..83 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -78,3 +78,8 @@ ok 75 - We should get the proper diagnostics from throws_ok() with a NULL error ok 76 - lives_ok() should work ok 77 - lives_ok failure diagnostics ok 78 - We should get the proper diagnostics for a lives_ok() failure +ok 79 - lives_ok is ok +ok 80 - multiline desriptions should have subsequent lines escaped +ok 81 todo fail +ok 82 todo pass +ok 83 - TODO tests should display properly diff --git a/pg_prove b/pg_prove index f59ff4569023..62ab7dce04aa 100755 --- a/pg_prove +++ b/pg_prove @@ -1,5 +1,7 @@ #!/usr/bin/perl -w +# $Id$ + use strict; use warnings; use TAP::Harness; diff --git a/pgtap.sql.in b/pgtap.sql.in index 97daeaf36fe1..62ab424aa8e1 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1,3 +1,4 @@ +-- $Id$ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA,public; diff --git a/sql/pgtap.sql b/sql/pgtap.sql index 7d2899d67184..d8c42ccae828 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -4,6 +4,7 @@ -- Tests for pgTAP. -- -- +-- $Id$ -- Format the output for nice TAP. \pset format unaligned From b9a0fc2266c3a67e6727e4bfcbece530e04e544b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 24 Jun 2008 02:21:50 +0000 Subject: [PATCH 0036/1195] Adding $HeadURL$ keyword to the main SQL file. This is so that those bundling the file in other distributions will have a link back to the original. --- pgtap.sql.in | 1 + 1 file changed, 1 insertion(+) diff --git a/pgtap.sql.in b/pgtap.sql.in index 62ab424aa8e1..9544877137dc 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1,3 +1,4 @@ +-- $HeadURL$ -- $Id$ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA,public; From 0f71ee9e36c6cd5e34fc0da8437c6e1399e486c0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 24 Jun 2008 02:24:23 +0000 Subject: [PATCH 0037/1195] More notes on the location of the pgTAP file and distribution (and license). --- pgtap.sql.in | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pgtap.sql.in b/pgtap.sql.in index 9544877137dc..91de13122326 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1,4 +1,13 @@ +-- This file defines pgTAP, a collection of functions for TAP-based unit +-- testing. It is distributed under the revised FreeBSD license. You can +-- find the original here: +-- -- $HeadURL$ +-- +-- The home page for the pgTAP project is: +-- +-- http://pgfoundry.org/projects/pgtap/ + -- $Id$ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA,public; From 5689741320b7842f8ffcb15ccfe6447e32f72e4d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 24 Jun 2008 16:29:00 +0000 Subject: [PATCH 0038/1195] Switch from named parameters to aliases. --- Changes | 3 +++ pgtap.sql.in | 54 ++++++++++++++++++++++++++++++++++------------------ 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/Changes b/Changes index a1effd715e23..4fc1964cbea8 100644 --- a/Changes +++ b/Changes @@ -9,6 +9,9 @@ Revision history for pgTAP - Replaced a call to `lastval()` with a call to `currvall()` in order to improve compatibility with PostgreSQL 8.0. Reported by David Westbrook. - Added support for TODO tests. + - Removed the few uses of named parameters and added alias names instead. + This improves compatibility for versions of PostgreSQL prior to 8.0. + Reported by David Westbrook. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/pgtap.sql.in b/pgtap.sql.in index 91de13122326..8d9ab9d3d138 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -157,11 +157,13 @@ BEGIN END; $$ LANGUAGE plpgsql strict; -CREATE OR REPLACE FUNCTION ok ( aok boolean, descr text ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION ok ( boolean, text ) RETURNS TEXT AS $$ DECLARE - test_num integer; + aok ALIAS FOR $1; + descr ALIAS FOR $2; + test_num INTEGER; todo_why TEXT := _todo(); - ok bool := CASE WHEN aok = TRUE THEN aok WHEN todo_why IS NULL THEN aok ELSE TRUE END; + ok bOOL := CASE WHEN aok = TRUE THEN aok WHEN todo_why IS NULL THEN aok ELSE TRUE END; BEGIN IF _get('plan') IS NULL THEN RAISE EXCEPTION 'You tried to run a test without a plan! Gotta have a plan'; @@ -226,13 +228,17 @@ CREATE OR REPLACE FUNCTION isnt (anyelement, anyelement) RETURNS TEXT AS $$ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _alike ( - result boolean, - got anyelement, - rx text, - descr text + BOOLEAN, + ANYELEMENT, + TEXT, + TEXT ) RETURNS TEXT AS $$ DECLARE - output text := ok( result, descr); + result ALIAS FOR $1; + got ALIAS FOR $2; + rx ALIAS FOR $3; + descr ALIAS FOR $4; + output TEXT := ok( result, descr); BEGIN RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( ' ' || COALESCE( quote_literal(got), 'NULL' ) || @@ -274,13 +280,17 @@ CREATE OR REPLACE FUNCTION ialike ( anyelement, text ) RETURNS TEXT AS $$ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _unalike ( - result boolean, - got anyelement, - rx text, - descr text + BOOLEAN, + ANYELEMENT, + TEXT, + TEXT ) RETURNS TEXT AS $$ DECLARE - output text := ok( result, descr); + result ALIAS FOR $1; + got ALIAS FOR $2; + rx ALIAS FOR $3; + descr ALIAS FOR $4; + output TEXT := ok( result, descr); BEGIN RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( ' ' || COALESCE( quote_literal(got), 'NULL' ) || @@ -359,12 +369,15 @@ END; $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION throws_ok ( - code TEXT, - err CHAR(5), - msg TEXT + TEXT, + CHAR(5), + TEXT ) RETURNS TEXT AS $$ DECLARE - descr TEXT := COALESCE( msg, 'threw ' || COALESCE( err, 'an exception' ) ); + code ALIAS FOR $1; + err ALIAS FOR $2; + msg ALIAS FOR $3; + descr TEXT := COALESCE( msg, 'threw ' || COALESCE( err, 'an exception' ) ); BEGIN EXECUTE code; RETURN ok( FALSE, descr ) || E'\n' || diag( @@ -394,9 +407,12 @@ CREATE OR REPLACE FUNCTION throws_ok ( TEXT ) RETURNS TEXT AS $$ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION lives_ok ( - code TEXT, - descr TEXT + TEXT, + TEXT ) RETURNS TEXT AS $$ +DECLARE + code ALIAS FOR $1; + descr ALIAS FOR $2; BEGIN EXECUTE code; RETURN ok( TRUE, descr ); From 7f89307a7b59b00e24bbab1f95af3d6891b67877 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 24 Jun 2008 17:00:17 +0000 Subject: [PATCH 0039/1195] * Moved assignment to declared variables inside function bodies, for better compatibility with PostgreSQL 7.x. * Fixed plural of "test" in the `finish()` function. --- Changes | 6 +++ pgtap.sql.in | 108 ++++++++++++++++++++++++++++---------------------- sql/pgtap.sql | 4 +- 3 files changed, 69 insertions(+), 49 deletions(-) diff --git a/Changes b/Changes index 4fc1964cbea8..108ba2f15034 100644 --- a/Changes +++ b/Changes @@ -12,6 +12,12 @@ Revision history for pgTAP - Removed the few uses of named parameters and added alias names instead. This improves compatibility for versions of PostgreSQL prior to 8.0. Reported by David Westbrook. + - Fixed the plural of "test" in the output from `finish()`. It was + plural when there was one test and singular otherwise. So I reversed + that. + - Moved assignment to declared variables from the `DECLARE` block to the + body of the functions. Improves compatibility with versions of + PostgreSQL prior to 8.0. Reported by David Westbrook. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/pgtap.sql.in b/pgtap.sql.in index 8d9ab9d3d138..71056f7618ea 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -75,7 +75,6 @@ END; $$ LANGUAGE plpgsql strict; CREATE OR REPLACE FUNCTION _set ( text, integer, text ) RETURNS integer AS $$ -DECLARE BEGIN EXECUTE 'UPDATE __tcache__ SET value = ' || $2 || CASE $3 WHEN '' THEN '' ELSE ', note = ' || quote_literal($3) END @@ -112,41 +111,46 @@ $$ LANGUAGE plpgsql strict; CREATE OR REPLACE FUNCTION finish () RETURNS SETOF TEXT AS $$ DECLARE - curr_test integer = _get('curr_test'); - exp_tests integer = _get('plan'); - num_failed integer = num_failed(); - plural char = CASE exp_tests WHEN 1 THEN 's' ELSE '' END; + curr_test INTEGER; + exp_tests INTEGER; + num_faild INTEGER; + plural CHAR; BEGIN + curr_test := _get('curr_test'); + exp_tests := _get('plan'); + num_faild := num_failed(); + plural := CASE exp_tests WHEN 1 THEN '' ELSE 's' END; + + IF curr_test IS NULL THEN + RAISE EXCEPTION '%', diag( '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 IS NULL THEN - RAISE EXCEPTION '%', diag( 'No tests run!' ); - END IF; - - IF exp_tests = 0 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 only ran ' || curr_test - ); - ELSIF curr_test > exp_tests THEN - RETURN NEXT diag( - 'Looks like you planned ' || exp_tests || ' test' || - plural || ' but ran ' || curr_test - exp_tests || ' extra' - ); - ELSIF num_failed > 0 THEN - RETURN NEXT diag( - 'Looks like you failed ' || num_failed || ' test' || - plural || ' of ' || exp_tests - ); - ELSE + IF curr_test < exp_tests THEN + RETURN NEXT diag( + 'Looks like you planned ' || exp_tests || ' test' || + plural || ' but only ran ' || curr_test + ); + ELSIF curr_test > exp_tests THEN + RETURN NEXT diag( + 'Looks like you planned ' || exp_tests || ' test' || + plural || ' but ran ' || curr_test - exp_tests || ' extra' + ); + ELSIF num_faild > 0 THEN + 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 IF; + RETURN; END; $$ LANGUAGE plpgsql; @@ -162,9 +166,11 @@ DECLARE aok ALIAS FOR $1; descr ALIAS FOR $2; test_num INTEGER; - todo_why TEXT := _todo(); - ok bOOL := CASE WHEN aok = TRUE THEN aok WHEN todo_why IS NULL THEN aok ELSE TRUE END; + todo_why TEXT; + ok BOOL; BEGIN + todo_why := _todo(); + ok := CASE WHEN aok = TRUE THEN aok WHEN todo_why IS NULL THEN aok 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; @@ -196,9 +202,11 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION is (anyelement, anyelement, text) RETURNS TEXT AS $$ DECLARE - result boolean := $1 = $2; - output text := ok( result, $3); + result BOOLEAN; + output TEXT; BEGIN + result := $1 = $2; + output := ok( result, $3 ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( ' got: ' || COALESCE( $1::text, 'NULL' ) || E'\n expected: ' || COALESCE( $2::text, 'NULL' ) @@ -212,9 +220,11 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION isnt (anyelement, anyelement, text) RETURNS TEXT AS $$ DECLARE - result boolean := $1 <> $2; - output text := ok( result, $3 ); + result BOOLEAN; + output TEXT; BEGIN + result := $1 <> $2; + output := ok( result, $3 ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( ' ' || COALESCE( $1::text, 'NULL' ) || E'\n <>' || @@ -234,12 +244,13 @@ CREATE OR REPLACE FUNCTION _alike ( TEXT ) RETURNS TEXT AS $$ DECLARE - result ALIAS FOR $1; - got ALIAS FOR $2; - rx ALIAS FOR $3; - descr ALIAS FOR $4; - output TEXT := ok( result, descr); + 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' ) @@ -290,8 +301,9 @@ DECLARE got ALIAS FOR $2; rx ALIAS FOR $3; descr ALIAS FOR $4; - output TEXT := ok( result, descr); + 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' ) @@ -357,8 +369,9 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION _todo() RETURNS TEXT AS $$ DECLARE - todos int := _get('todo'); + todos INT; BEGIN + todos := _get('todo'); IF todos IS NULL OR todos = 0 THEN RETURN NULL; END IF; @@ -377,8 +390,9 @@ DECLARE code ALIAS FOR $1; err ALIAS FOR $2; msg ALIAS FOR $3; - descr TEXT := COALESCE( msg, 'threw ' || COALESCE( err, 'an exception' ) ); + descr TEXT; BEGIN + descr := COALESCE( msg, 'threw ' || COALESCE( err, 'an exception' ) ); EXECUTE code; RETURN ok( FALSE, descr ) || E'\n' || diag( ' caught: no exception' || diff --git a/sql/pgtap.sql b/sql/pgtap.sql index d8c42ccae828..9c3ca984ed96 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -78,7 +78,7 @@ DELETE FROM __tcache__ WHERE label = 'plan'; SELECT is( plan(4000), '1..4000', 'Set the plan to 4000' ); SELECT is( (SELECT * FROM finish() LIMIT 1), - '# Looks like you planned 4000 test but only ran 11', + '# Looks like you planned 4000 tests but only ran 11', 'The output of finish() should reflect a high test plan' ); @@ -87,7 +87,7 @@ DELETE FROM __tcache__ WHERE label = 'plan'; SELECT is( plan(4), '1..4', 'Set the plan to 4' ); SELECT is( (SELECT * FROM finish() LIMIT 1), - '# Looks like you planned 4 test but ran 9 extra', + '# Looks like you planned 4 tests but ran 9 extra', 'The output of finish() should reflect a low test plan' ); From 5565f671f486277ad923151fc4db5a3aecdc7d90 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 24 Jun 2008 17:10:15 +0000 Subject: [PATCH 0040/1195] Eliminated passing function call result to `RAISE`. --- Changes | 3 +++ pgtap.sql.in | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 108ba2f15034..8e0d86324b16 100644 --- a/Changes +++ b/Changes @@ -18,6 +18,9 @@ Revision history for pgTAP - Moved assignment to declared variables from the `DECLARE` block to the body of the functions. Improves compatibility with versions of PostgreSQL prior to 8.0. Reported by David Westbrook. + - Eliminated passing the result of a function call to `RAISE` in order + to better support older versions of PostgreSQL. Reported by David + Westbrook. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/pgtap.sql.in b/pgtap.sql.in index 71056f7618ea..f657af84f375 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -122,7 +122,7 @@ BEGIN plural := CASE exp_tests WHEN 1 THEN '' ELSE 's' END; IF curr_test IS NULL THEN - RAISE EXCEPTION '%', diag( 'No tests run!' ); + RAISE EXCEPTION '%', '# No tests run!'; END IF; IF exp_tests = 0 OR exp_tests IS NULL THEN From 261e2a5513c85d7004d94a65b5f296bd98f9de97 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 24 Jun 2008 17:27:02 +0000 Subject: [PATCH 0041/1195] Switched from `FOUND` to `GET DIAGNOSTICS ROW_COUNT`. --- Changes | 3 +++ pgtap.sql.in | 10 ++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 8e0d86324b16..0d8074fe2e74 100644 --- a/Changes +++ b/Changes @@ -21,6 +21,9 @@ Revision history for pgTAP - Eliminated passing the result of a function call to `RAISE` in order to better support older versions of PostgreSQL. Reported by David Westbrook. + - Switched from using `FOUND` to `GET DIAGNOSTICS ROW_COUNT` because the + former does not seem to b set for `EXECUTE`d SQL in older versions of + PostgreSQL. Reported by David Westbrook. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/pgtap.sql.in b/pgtap.sql.in index f657af84f375..ca1c29a64fac 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -13,6 +13,8 @@ -- ## SET search_path TO TAPSCHEMA,public; CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ +DECLARE + rcount INTEGER; BEGIN BEGIN EXECUTE ' @@ -38,7 +40,8 @@ BEGIN EXCEPTION WHEN duplicate_table THEN -- Raise an exception if there's already a plan. EXECUTE 'SELECT TRUE FROM __tcache__ WHERE label = ''plan'''; - IF FOUND THEN + GET DIAGNOSTICS rcount = ROW_COUNT; + IF rcount > 0 THEN RAISE EXCEPTION 'You tried to plan twice!'; END IF; END; @@ -75,11 +78,14 @@ 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 $3 WHEN '' THEN '' ELSE ', note = ' || quote_literal($3) END || ' WHERE label = ' || quote_literal($1); - IF NOT FOUND THEN + GET DIAGNOSTICS rcount = ROW_COUNT; + IF rcount = 0 THEN EXECUTE 'INSERT INTO __tcache__ values (' || quote_literal($1) || ', ' || $2 || ', ' || quote_literal(COALESCE($3, '')) || ')'; END IF; RETURN $2; From ab6adb4c59fdb251153e00ee72e7c1e12f00552f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jul 2008 00:27:40 +0000 Subject: [PATCH 0042/1195] Added pg 7.3 tests. --- Changes | 2 ++ Makefile | 21 ++++++------- expected/pg73.out | 41 ++++++++++++++++++++++++++ sql/pg73.sql | 75 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 12 deletions(-) create mode 100644 expected/pg73.out create mode 100644 sql/pg73.sql diff --git a/Changes b/Changes index 0d8074fe2e74..2f6940e7d25b 100644 --- a/Changes +++ b/Changes @@ -24,6 +24,8 @@ Revision history for pgTAP - Switched from using `FOUND` to `GET DIAGNOSTICS ROW_COUNT` because the former does not seem to b set for `EXECUTE`d SQL in older versions of PostgreSQL. Reported by David Westbrook. + - Added tests specifically targeting PostgreSQL 7.3. From David + Westbrook. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/Makefile b/Makefile index 7655237415e8..3c37a2056e23 100644 --- a/Makefile +++ b/Makefile @@ -2,20 +2,17 @@ DATA_built = pgtap.sql drop_pgtap.sql DOCS = README.pgtap SCRIPTS = pg_prove -REGRESS = pgtap +REGRESS = pgtap pg73 -top_builddir = ../.. -in_contrib = $(wildcard $(top_builddir)/src/Makefile.global); - -ifdef $(in_contrib) - # Just include the local makefiles - subdir = contrib/pgtap - include $(top_builddir)/src/Makefile.global - include $(top_srcdir)/contrib/contrib-global.mk +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) else - # Use pg_config to find PGXS and include it. - PGXS := $(shell pg_config --pgxs) - include $(PGXS) +subdir = contrib/citext +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk endif # Override how .sql targets are processed to add the schema info, if diff --git a/expected/pg73.out b/expected/pg73.out new file mode 100644 index 000000000000..6487dd36c293 --- /dev/null +++ b/expected/pg73.out @@ -0,0 +1,41 @@ +\set ECHO +1..39 +ok 1 +ok 2 - true +ok 3 +ok 4 - NOT false +ok 5 +ok 6 - three +ok 7 +ok 8 +ok 9 +ok 10 - 1=2 +ok 11 - now()=now() +ok 12 - '1 hour'::interval, '1 hour'::interval +ok 13 +ok 14 - now=now date +ok 15 - now!=now+1 +ok 16 - now()=now() timestamp +ok 17 - now()=now() date +ok 18 - TRUE=TRUE +ok 19 - TRUE!=FALSE +ok 20 - a=a char +ok 21 - a!=b char +ok 22 - a=a text +ok 23 - a!=b text +ok 24 - 3=3 int +ok 25 - 3!=4 int +ok 26 - 3=3 integer +ok 27 - 3!=4 integer +ok 28 - 3=3 int2 +ok 29 - 3!=4 int2 +ok 30 - 3=3 int4 +ok 31 - 3!=4 int4 +ok 32 - 3=3 int8 +ok 33 - 3!=4 int8 +ok 34 - 3.2=3.2 float +ok 35 - 3.2!=4.5 float +ok 36 - 3.2=3.2 float4 +ok 37 - 3.2!=4.5 float4 +ok 38 - 3.2=3.2 float8 +ok 39 - 3.2!=4.5 float8 diff --git a/sql/pg73.sql b/sql/pg73.sql new file mode 100644 index 000000000000..c9d1cf2a6229 --- /dev/null +++ b/sql/pg73.sql @@ -0,0 +1,75 @@ +\set ECHO + +-- +-- Tests for pgTAP on PostgreSQL 7.3. +-- +-- +-- $Id: pgtap.sql 4057 2008-06-24 17:00:17Z david $ + +-- Format the output for nice TAP. +\pset format unaligned +\pset tuples_only true +\pset pager +-- Create plpgsql if it's not already there. +SET client_min_messages = fatal; +CREATE LANGUAGE plpgsql; + +-- Keep things quiet. +SET client_min_messages = warning; + +-- Revert all changes on failure. +\set ON_ERROR_ROLBACK 1 +\set ON_ERROR_STOP true + +-- Load the TAP functions. +BEGIN; +\i pgtap.sql + +select plan(39); + +select ok(true); +select ok(true, 'true'); +select ok(NOT false); +select ok(NOT false, 'NOT false'); +select ok(3 = 3); +select ok(3 = 3, 'three'); + +select ok(1 != 2); +select ok(1 <> 2); +select isnt(1,2); +select isnt(1,2,'1=2'); + +select is( now(), now(), 'now()=now()'); +select is( '1 hour'::interval, '1 hour'::interval, '''1 hour''::interval, ''1 hour''::interval'); +select is( now()::date, now()::date); +select is( now()::date, now()::date, 'now=now date' ); +select isnt( now()::date, now()::date + 1, 'now!=now+1' ); + +select is( now()::timestamp, now()::timestamp, 'now()=now() timestamp'); +select is( now()::date, now()::date, 'now()=now() date'); +select is( TRUE, TRUE, 'TRUE=TRUE' ); +select isnt( TRUE, FALSE, 'TRUE!=FALSE' ); +select is('a'::char, 'a'::char, 'a=a char'); +select isnt('a'::char, 'b'::char, 'a!=b char'); + +select is('a'::text, 'a'::text, 'a=a text'); +select isnt('a'::text, 'b'::text, 'a!=b text'); +select is(3::int, 3::int, '3=3 int'); +select isnt(3::int, 4::int, '3!=4 int'); +select is(3::integer, 3::integer, '3=3 integer'); +select isnt(3::integer, 4::integer, '3!=4 integer'); +select is(3::int2, 3::int2, '3=3 int2'); +select isnt(3::int2, 4::int2, '3!=4 int2'); +select is(3::int4, 3::int4, '3=3 int4'); +select isnt(3::int4, 4::int4, '3!=4 int4'); +select is(3::int8, 3::int8, '3=3 int8'); +select isnt(3::int8, 4::int8, '3!=4 int8'); +select is(3.2::float, 3.2::float, '3.2=3.2 float'); +select isnt(3.2::float, 4.5::float, '3.2!=4.5 float'); +select is(3.2::float4, 3.2::float4, '3.2=3.2 float4'); +select isnt(3.2::float4, 4.5::float4, '3.2!=4.5 float4'); +select is(3.2::float8, 3.2::float8, '3.2=3.2 float8'); +select isnt(3.2::float8, 4.5::float8, '3.2!=4.5 float8'); + +select * from finish(); + From 8445f08ec94fbca8fe5af07d49f938e21e23a7ed Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jul 2008 00:51:15 +0000 Subject: [PATCH 0043/1195] Removed test target. --- Makefile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Makefile b/Makefile index 3c37a2056e23..089e3343d1dd 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,3 @@ ifdef TAPSCHEMA else cp $< $@ endif - -test: - ./pg_prove sql/$(REGRESS).sql From 154511105bd1587e321f70e3a301a0475060769f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jul 2008 00:59:46 +0000 Subject: [PATCH 0044/1195] keywords. --- pgtap.sql.in | 2 +- sql/pg73.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index ca1c29a64fac..9f6b1bf51c80 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2,7 +2,7 @@ -- testing. It is distributed under the revised FreeBSD license. You can -- find the original here: -- --- $HeadURL$ +-- $HeadURL: https://svn.kineticode.com/pgtap/trunk/pgtap.sql.in $ -- -- The home page for the pgTAP project is: -- diff --git a/sql/pg73.sql b/sql/pg73.sql index c9d1cf2a6229..2f1da0d03329 100644 --- a/sql/pg73.sql +++ b/sql/pg73.sql @@ -4,7 +4,7 @@ -- Tests for pgTAP on PostgreSQL 7.3. -- -- --- $Id: pgtap.sql 4057 2008-06-24 17:00:17Z david $ +-- $Id$ -- Format the output for nice TAP. \pset format unaligned From 9bb285d7b30b429786ceaa57655dbfc878496660 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jul 2008 01:01:58 +0000 Subject: [PATCH 0045/1195] Got to keep that. --- pgtap.sql.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 9f6b1bf51c80..ca1c29a64fac 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2,7 +2,7 @@ -- testing. It is distributed under the revised FreeBSD license. You can -- find the original here: -- --- $HeadURL: https://svn.kineticode.com/pgtap/trunk/pgtap.sql.in $ +-- $HeadURL$ -- -- The home page for the pgTAP project is: -- From abd05c11207083e5cac0fb4035c2dd5fe8162ef2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 7 Aug 2008 04:18:17 +0000 Subject: [PATCH 0046/1195] Changed "got/expected" to "have/want". --- Changes | 3 +++ pgtap.sql.in | 8 ++++---- sql/pgtap.sql | 6 +++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Changes b/Changes index 2f6940e7d25b..02e943951f1b 100644 --- a/Changes +++ b/Changes @@ -26,6 +26,9 @@ Revision history for pgTAP PostgreSQL. Reported by David Westbrook. - Added tests specifically targeting PostgreSQL 7.3. From David Westbrook. + - Changed "got/expected" to "have/want", following Schwern's plans for + Test::Builder 2. Also changed "caught/expected" to "caught/wanted" for + nice parity when outputting diagnostics for exception testing. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/pgtap.sql.in b/pgtap.sql.in index ca1c29a64fac..aae64770101d 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -214,8 +214,8 @@ BEGIN result := $1 = $2; output := ok( result, $3 ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( - ' got: ' || COALESCE( $1::text, 'NULL' ) || - E'\n expected: ' || COALESCE( $2::text, 'NULL' ) + ' have: ' || COALESCE( $1::text, 'NULL' ) || + E'\n want: ' || COALESCE( $2::text, 'NULL' ) ) END; END; $$ LANGUAGE plpgsql; @@ -402,7 +402,7 @@ BEGIN EXECUTE code; RETURN ok( FALSE, descr ) || E'\n' || diag( ' caught: no exception' || - E'\n expected: ' || COALESCE( err, 'an exception' ) + E'\n wanted: ' || COALESCE( err, 'an exception' ) ); EXCEPTION WHEN OTHERS THEN IF err IS NULL OR SQLSTATE = err THEN @@ -412,7 +412,7 @@ EXCEPTION WHEN OTHERS THEN -- This was not the expected error. RETURN ok( FALSE, descr ) || E'\n' || diag( ' caught: ' || SQLSTATE || ': ' || SQLERRM || - E'\n expected: ' || COALESCE( err, 'an exception') + E'\n wanted: ' || COALESCE( err, 'an exception') ); END IF; END; diff --git a/sql/pgtap.sql b/sql/pgtap.sql index 9c3ca984ed96..1656d129d145 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -135,7 +135,7 @@ SELECT is( is(false, false), 'ok 39', 'is(false, false) should work' ); \echo ok 41 - is() success 7 SELECT is( is(1, 1, 'foo'), 'ok 41 - foo', 'is(1, 1, ''foo'') should work' ); \echo ok 43 - is() failure -SELECT is( is( 1, 2 ), E'not ok 43\n# Failed test 43\n# got: 1\n# expected: 2', 'is(1, 2) should work' ); +SELECT is( is( 1, 2 ), E'not ok 43\n# Failed test 43\n# have: 1\n# want: 2', 'is(1, 2) should work' ); /****************************************************************************/ -- Test isnt(). @@ -209,7 +209,7 @@ SELECT throws_ok( 'SELECT 1 / 0', '22012', 'throws_ok(1/0) should work' ); \echo ok 71 - throws_ok failure diagnostics SELECT is( throws_ok( 'SELECT 1 / 0', '97212' ), - E'not ok 71 - threw 97212\n# Failed test 71: "threw 97212"\n# caught: 22012: division by zero\n# expected: 97212', + E'not ok 71 - threw 97212\n# Failed test 71: "threw 97212"\n# caught: 22012: division by zero\n# wanted: 97212', 'We should get the proper diagnostics from throws_ok()' ); @@ -219,7 +219,7 @@ SELECT throws_ok( 'SELECT 1 / 0', NULL, 'throws_ok(1/0, NULL) should work' ); \echo ok 74 - throws_ok failure diagnostics SELECT is( throws_ok( 'SELECT 1', NULL ), - E'not ok 74 - threw an exception\n# Failed test 74: "threw an exception"\n# caught: no exception\n# expected: an exception', + E'not ok 74 - threw an exception\n# Failed test 74: "threw an exception"\n# caught: no exception\n# wanted: an exception', 'We should get the proper diagnostics from throws_ok() with a NULL error code' ); From 44c4500cedeedd7b657429432154afce7bef9c05 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 15 Aug 2008 20:55:22 +0000 Subject: [PATCH 0047/1195] Added `table_exists()`. It uses information_schema; we will have to change it if we really want to support 7.3, though I am not really interested in that, personally. --- Changes | 1 + README.pgtap | 30 ++++++++++++++++++++++++++++++ expected/pgtap.out | 10 +++++++++- sql/pgtap.sql | 35 ++++++++++++++++++++++++++++++++++- 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 02e943951f1b..c2bba2a388b1 100644 --- a/Changes +++ b/Changes @@ -29,6 +29,7 @@ Revision history for pgTAP - Changed "got/expected" to "have/want", following Schwern's plans for Test::Builder 2. Also changed "caught/expected" to "caught/wanted" for nice parity when outputting diagnostics for exception testing. + - Added `table_exists()` test function. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/README.pgtap b/README.pgtap index ae62d0c311a8..1d50f30efb20 100644 --- a/README.pgtap +++ b/README.pgtap @@ -370,6 +370,14 @@ ok) or fail() (for not ok). They are synonyms for ok(1) and ok(0). Use these functions very, very, very sparingly. +To Error is Human +----------------- + +Sometimes you just want to know that a particular query will trigger an error. +Or maybe you want to make sure a query *does not* trigger an error. For such +cases, we provide a couple of test functions to make sure your queries are as +error-prone as you think they should be. + ### throws_ok( text, char(5), text ) ### ### throws_ok( text, char(5) ) ### ### throws_ok( text ) ### @@ -421,6 +429,28 @@ it produces appropriate diagnostic messages. For example: Idea borrowed from the Test::Exception Perl module. +A Wicked Schema +--------------- + +Need to make sure that your database is designed just the way you think it +should be? Use these test functions and rest easy. + +### table_exists( text, text, text ) ### +### table_exists( text, text ) ### +### table_exists( text ) ### + + SELECT table_exists( + 'myschema', + 'sometable', + 'I got myschema.sometable' + ); + +This function tests whether or not a table exist in the database. The first +argument is a schema name, the second is a table name, and the third is the +test description. If you omit the test description, it will be set to "Table +$schema.$table should exist". If you pass just a single argument, it is +assumed to be the table name and the schema is assumed to be "public". + Diagnostics ----------- diff --git a/expected/pgtap.out b/expected/pgtap.out index 6fcb01959de5..db52c90eb42a 100644 --- a/expected/pgtap.out +++ b/expected/pgtap.out @@ -1,5 +1,5 @@ \set ECHO -1..83 +1..91 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -83,3 +83,11 @@ ok 80 - multiline desriptions should have subsequent lines escaped ok 81 todo fail ok 82 todo pass ok 83 - TODO tests should display properly +ok 84 table_exists(table) fail +ok 85 - table_exists(table) should fail for non-existent table +ok 86 table_exists(schema, table) fail +ok 87 - table_exists(schema, table) should fail for non-existent table +ok 88 table_exists(schema, table, desc) fail +ok 89 - table_exists(schema, table, desc) should fail for non-existent table +ok 90 table_exists(schema, table) pass +ok 91 - table_exists(schema, type, desc) should pass for an existing table diff --git a/sql/pgtap.sql b/sql/pgtap.sql index 1656d129d145..58794279b909 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -25,7 +25,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 83 +\set numb_tests 91 -- ## SET search_path TO TAPSCHEMA,public; @@ -263,6 +263,39 @@ SELECT is( ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 81 ); + +/****************************************************************************/ +-- Test table_exists(). + +\echo ok 84 table_exists(table) fail + +SELECT is( + table_exists( '__SDFSDFD__' ), + E'not ok 84 - Table public.__SDFSDFD__ should exist\n# Failed test 84: "Table public.__SDFSDFD__ should exist"', + 'table_exists(table) should fail for non-existent table' +); +\echo ok 86 table_exists(schema, table) fail +SELECT is( + table_exists( 'foo', '__SDFSDFD__' ), + E'not ok 86 - Table foo.__SDFSDFD__ should exist\n# Failed test 86: "Table foo.__SDFSDFD__ should exist"', + 'table_exists(schema, table) should fail for non-existent table' +); + +\echo ok 88 table_exists(schema, table, desc) fail +SELECT is( + table_exists( 'foo', '__SDFSDFD__', 'desc' ), + E'not ok 88 - desc\n# Failed test 88: "desc"', + 'table_exists(schema, table, desc) should fail for non-existent table' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 84, 86, 88 ); + +\echo ok 90 table_exists(schema, table) pass +SELECT is( + table_exists( 'pg_catalog', 'pg_type', 'desc' ), + 'ok 90 - desc', + 'table_exists(schema, type, desc) should pass for an existing table' +); + -- Finish the tests and clean up. SELECT * FROM finish(); ROLLBACK; From ac7ded279cf111f13bf280c81d54048450474ea7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 15 Aug 2008 23:10:15 +0000 Subject: [PATCH 0048/1195] One must add code to pgtap.sql.in, not pgtap.sql. The latter is generated and not in SVN. --- pgtap.sql.in | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pgtap.sql.in b/pgtap.sql.in index aae64770101d..f54cb72e7f03 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -447,3 +447,20 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION lives_ok ( TEXT ) RETURNS TEXT AS $$ SELECT lives_ok( $1, NULL ); $$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION table_exists ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM information_schema.tables + WHERE table_schema = $1 AND table_name = $2 + ), $3 + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION table_exists ( TEXT ) RETURNS TEXT AS $$ + SELECT table_exists( 'public', $1, 'Table public.' || $1 || ' should exist' ); +$$ LANGUAGE SQL; +CREATE OR REPLACE FUNCTION table_exists ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT table_exists( 'public', $1, 'Table ' || $1 || '.' || $2 || ' should exist' ); +$$ LANGUAGE SQL; From e1d93c3055b4776a2d8a98457df1fb04d9372d79 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 15 Aug 2008 23:27:56 +0000 Subject: [PATCH 0049/1195] Added `view_exists()`. --- Changes | 2 +- README.pgtap | 82 ++++++++++++++++++++++++++++++--------------------- pgtap.sql.in | 17 +++++++++++ sql/pgtap.sql | 34 ++++++++++++++++++++- 4 files changed, 100 insertions(+), 35 deletions(-) diff --git a/Changes b/Changes index c2bba2a388b1..cf8e5ddc3db4 100644 --- a/Changes +++ b/Changes @@ -29,7 +29,7 @@ Revision history for pgTAP - Changed "got/expected" to "have/want", following Schwern's plans for Test::Builder 2. Also changed "caught/expected" to "caught/wanted" for nice parity when outputting diagnostics for exception testing. - - Added `table_exists()` test function. + - Added the `table_exists()` and `view_exists()` test functions. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/README.pgtap b/README.pgtap index 1d50f30efb20..da9930f0c265 100644 --- a/README.pgtap +++ b/README.pgtap @@ -147,7 +147,8 @@ I love it when a plan comes together Before anything else, you need a testing plan. This basically declares how many tests your script is going to run to protect against premature failure. -The preferred way to do this is to declare a plan by calling the plan() function: +The preferred way to do this is to declare a plan by calling the `plan()` +function: SELECT plan( 42 ); @@ -225,7 +226,7 @@ out. It makes it very easy to find a test in your script when it fails and gives others an idea of your intentions. :test_name is optional, but we *very* strongly encourage its use. -Should an ok() fail, it will produce some diagnostics: +Should an `ok()` fail, it will produce some diagnostics: not ok 18 - sufficient mucus # Failed test 18: "sufficient mucus" @@ -238,8 +239,8 @@ Should an ok() fail, it will produce some diagnostics: SELECT is( :this, :that, $test_name ); SELECT isnt( :this, :that, $test_name ); -Similar to ok(), is() and isnt() compare their two arguments with `=` and -`<>` respectively and use the result of that to determine if the test +Similar to o`k()`, `is()` and `isnt()` compare their two arguments with `=` +and `<>` respectively and use the result of that to determine if the test succeeded or failed. So these: -- Is the ultimate answer 42? @@ -255,8 +256,8 @@ are similar to these: (Mnemonic: "This is that." "This isn't that.") -So why use these? They produce better diagnostics on failure. ok() cannot -know what you are testing for (beyond the name), but is() and isnt() know +So why use these? They produce better diagnostics on failure. `ok()` cannot +know what you are testing for (beyond the name), but `is()` and `isnt()` know what the test was and why it failed. For example this test: \set foo '\'' waffle '\'' @@ -271,7 +272,7 @@ Will produce something like this: So you can figure out what went wrong without re-running the test. -You are encouraged to use is() and isnt() over ok() where possible, +You are encouraged to use `is()` and `isnt()` over `ok()` where possible, however do not be tempted to use them to find out if something is true or false! @@ -280,7 +281,7 @@ false! This does not check if `is_valid(9)` is true, it checks if it *returns* TRUE. Very different. Similar caveats exist for FALSE. In these cases, use -ok(). +`ok()`. SELECT ok( is_valid(9), 'A tree grows in Brooklyn' ); @@ -289,7 +290,7 @@ ok(). SELECT matches( :this, '^that', :test_name ); -Similar to ok(), matches() matches :this against the regex `/^that/`. +Similar to `ok()`, `matches()` matches :this against the regex `/^that/`. So this: @@ -301,7 +302,7 @@ is similar to: (Mnemonic "This matches that".) -Its advantages over ok() are similar to that of is() and isnt(): Better +Its advantages over `ok()` are similar to that of `is()` and `isnt()`: Better diagnostics on failure. ### imatches( anyelement, text, text ) ### @@ -309,7 +310,7 @@ diagnostics on failure. SELECT imatches( :this, '^that', :test_name ); -These are just like matches() except that the regular expression is +These are just like `matches()` except that the regular expression is case-insensitive. ### doesnt_match( anyelement, text, text ) ### @@ -319,7 +320,7 @@ case-insensitive. SELECT doesnt_match( :this, '^that', :test_name ); -These functions work exactly as matches() and imataches() do, only they +These functions work exactly as `matches()` and `imataches()` do, only they check if :this *does not* match the given pattern. ### alike( anyelement, text, text ) ### @@ -329,8 +330,8 @@ check if :this *does not* match the given pattern. SELECT alike( :this, 'that%', :test_name ); -Similar to ok(), alike() matches :this against the SQL LIKE pattern -'that%'. ialike() matches case-insensitively. +Similar to `ok()`, `alike()` matches `:this` against the SQL LIKE pattern +'that%'. `ialike()` matches case-insensitively. So this: @@ -342,7 +343,7 @@ is similar to: (Mnemonic "This is like that".) -Its advantages over ok() are similar to that of is() and isnt(): Better +Its advantages over `ok()` are similar to that of `is()` and `isnt()`: Better diagnostics on failure. ### unalike( anyelement, text, text ) ### @@ -352,8 +353,8 @@ diagnostics on failure. SELECT unalike( :this, 'that%', :test_name ); -Works exactly as alike(), only it checks if :this *does not* match the -given pattern. +Works exactly as `alike()`, only it checks if :this *does not* match the given +pattern. ### pass( text ) ### ### pass() ### @@ -363,10 +364,10 @@ given pattern. SELECT pass( :test_name ); SELECT fail( :test_name ); -Sometimes you just want to say that the tests have passed. Usually the -case is you've got some complicated condition that is difficult to wedge -into an ok(). In this case, you can simply use pass() (to declare the test -ok) or fail() (for not ok). They are synonyms for ok(1) and ok(0). +Sometimes you just want to say that the tests have passed. Usually the case is +you've got some complicated condition that is difficult to wedge into an +`ok()`. In this case, you can simply use `pass()` (to declare the test ok) or +`fail()` (for not ok). They are synonyms for `ok(1)` and `ok(0)`. Use these functions very, very, very sparingly. @@ -389,20 +390,23 @@ error-prone as you think they should be. ); When you want to make sure that an exception is thrown by PostgreSQL under -certain circumstances, use throws_ok() to test for those circumstances. +certain circumstances, use `throws_ok()` to test for those circumstances. -The first argument should be a string representing the query to be -executed. throws_ok() will use the PL/pgSQL `EXECUTE` statement to execute -it and catch any exception. +The first argument should be a string representing the query to be executed. +`throws_ok()` will use the PL/pgSQL `EXECUTE` statement to execute it and +catch any exception. -The second argument should be an exception error code. If this value is -not NULL, throws_ok() will check the thrown exception to ensure that it is -the expected exception. For a complete list of error codes, see [Appendix A.](http://www.postgresql.org/docs/current/static/errcodes-appendix.html) in the [PostgreSQL documentation](http://www.postgresql.org/docs/current/static/). +The second argument should be an exception error code. If this value is not +NULL, `throws_ok()` will check the thrown exception to ensure that it is the +expected exception. For a complete list of error codes, see [Appendix +A.](http://www.postgresql.org/docs/current/static/errcodes-appendix.html) in +the [PostgreSQL +documentation](http://www.postgresql.org/docs/current/static/). The third argument is of course a brief test name. -Should a throws_ok() test fail it produces appropriate diagnostic -messages. For example: +Should a `throws_ok()` test fail it produces appropriate diagnostic messages. +For example: not ok 81 - simple error # Failed test "simple error" @@ -419,9 +423,9 @@ Idea borrowed from the Test::Exception Perl module. 'We should not get a unique violation for a new PK' ); -The inverse of throws_ok(), these functions test to ensure that a SQL -statement does *not* throw an exception. Should a lives_ok() test faile, -it produces appropriate diagnostic messages. For example: +The inverse of `throws_ok()`, these functions test to ensure that a SQL +statement does *not* throw an exception. Should a `lives_ok()` test faile, it +produces appropriate diagnostic messages. For example: not ok 85 - simple success # Failed test "simple success" @@ -451,6 +455,18 @@ test description. If you omit the test description, it will be set to "Table $schema.$table should exist". If you pass just a single argument, it is assumed to be the table name and the schema is assumed to be "public". +### view_exists( text, text, text ) ### +### view_exists( text, text ) ### +### view_exists( text ) ### + + SELECT view_exists( + 'myschema', + 'someview', + 'I got myschema.someview' + ); + +Just like `table_exists()`, only it tests for the existence of a view. + Diagnostics ----------- diff --git a/pgtap.sql.in b/pgtap.sql.in index f54cb72e7f03..4c46f3af6f8b 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -464,3 +464,20 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION table_exists ( TEXT, TEXT ) RETURNS TEXT AS $$ SELECT table_exists( 'public', $1, 'Table ' || $1 || '.' || $2 || ' should exist' ); $$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION view_exists ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM information_schema.views + WHERE table_schema = $1 AND table_name = $2 + ), $3 + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION view_exists ( TEXT ) RETURNS TEXT AS $$ + SELECT view_exists( 'public', $1, 'View public.' || $1 || ' should exist' ); +$$ LANGUAGE SQL; +CREATE OR REPLACE FUNCTION view_exists ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT view_exists( 'public', $1, 'View ' || $1 || '.' || $2 || ' should exist' ); +$$ LANGUAGE SQL; diff --git a/sql/pgtap.sql b/sql/pgtap.sql index 58794279b909..b680b9464521 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -25,7 +25,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 91 +\set numb_tests 99 -- ## SET search_path TO TAPSCHEMA,public; @@ -296,6 +296,38 @@ SELECT is( 'table_exists(schema, type, desc) should pass for an existing table' ); +/****************************************************************************/ +-- Test view_exists(). + +\echo ok 92 view_exists(view) fail + +SELECT is( + view_exists( '__SDFSDFD__' ), + E'not ok 92 - View public.__SDFSDFD__ should exist\n# Failed test 92: "View public.__SDFSDFD__ should exist"', + 'view_exists(view) should fail for non-existent view' +); +\echo ok 94 view_exists(schema, view) fail +SELECT is( + view_exists( 'foo', '__SDFSDFD__' ), + E'not ok 94 - View foo.__SDFSDFD__ should exist\n# Failed test 94: "View foo.__SDFSDFD__ should exist"', + 'view_exists(schema, view) should fail for non-existent view' +); + +\echo ok 96 view_exists(schema, view, desc) fail +SELECT is( + view_exists( 'foo', '__SDFSDFD__', 'desc' ), + E'not ok 96 - desc\n# Failed test 96: "desc"', + 'view_exists(schema, view, desc) should fail for non-existent view' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 92, 94, 96 ); + +\echo ok 98 view_exists(schema, view) pass +SELECT is( + view_exists( 'information_schema', 'tables', 'desc' ), + 'ok 98 - desc', + 'view_exists(schema, type, desc) should pass for an existing view' +); + -- Finish the tests and clean up. SELECT * FROM finish(); ROLLBACK; From 159f211f2ccce8765594b3c1285417c5aedbf786 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 15 Aug 2008 23:58:50 +0000 Subject: [PATCH 0050/1195] * Fixed `table_exists(schema, table)` and `view_exists(schema, view)` to properly pass the schema name instead of "public". * Added comments above the `_exists()` function definitions with the names of the arguments being passed. * Added `column_exists()`. * Fixed the `\echo`s in the test to properly include a hyphen after the test numbers. * Fixed misspelling of "Neil" in Changes. --- Changes | 5 ++- README.pgtap | 20 ++++++++++ expected/pgtap.out | 38 ++++++++++++++---- pgtap.sql.in | 34 +++++++++++++++- sql/pgtap.sql | 96 +++++++++++++++++++++++++++++++++++++--------- 5 files changed, 162 insertions(+), 31 deletions(-) diff --git a/Changes b/Changes index cf8e5ddc3db4..ac0f9f065e57 100644 --- a/Changes +++ b/Changes @@ -29,7 +29,8 @@ Revision history for pgTAP - Changed "got/expected" to "have/want", following Schwern's plans for Test::Builder 2. Also changed "caught/expected" to "caught/wanted" for nice parity when outputting diagnostics for exception testing. - - Added the `table_exists()` and `view_exists()` test functions. + - Added the `table_exists()`, `view_exists()`, and `column_exists()` + test functions. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. @@ -64,7 +65,7 @@ Revision history for pgTAP directory. - Changed all instances of `RETURN QUERY SELECT` to `RETURN NEXT`, which should allow pgtap to run on versions of PostgreSQL earlier than 8.3. - Thanks to Niel Conway for the suggestion. + Thanks to Neil Conway for the suggestion. 0.01 2008-06-07T05:24:27 - Initial public release. Announcement at diff --git a/README.pgtap b/README.pgtap index da9930f0c265..825b0060a698 100644 --- a/README.pgtap +++ b/README.pgtap @@ -467,6 +467,26 @@ assumed to be the table name and the schema is assumed to be "public". Just like `table_exists()`, only it tests for the existence of a view. +### column_exists( text, text, text, text ) ### +### column_exists( text, text, text ) ### +### column_exists( text, text ) ### + + SELECT column_exists( + 'myschema', + 'somecolumn', + 'somecolumn', + 'I got myschema.sometable.somecolumn' + ); + +Tests whether or not a column exists in a given table or view. The first +argument is the schema name, the second the table name, the third the column +name, and the fourth is the test description. If the test description is +omitted, it will be set to "Column $schema.$table.$column should exist". If +only two arguments are passed, they are assumed to be the table and column +names, and the schema will default to "public". + +Just like `table_exists()`, only it tests for the existence of a column. + Diagnostics ----------- diff --git a/expected/pgtap.out b/expected/pgtap.out index db52c90eb42a..855648fc440e 100644 --- a/expected/pgtap.out +++ b/expected/pgtap.out @@ -1,5 +1,5 @@ \set ECHO -1..91 +1..113 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -80,14 +80,36 @@ ok 77 - lives_ok failure diagnostics ok 78 - We should get the proper diagnostics for a lives_ok() failure ok 79 - lives_ok is ok ok 80 - multiline desriptions should have subsequent lines escaped -ok 81 todo fail -ok 82 todo pass +ok 81 - todo fail +ok 82 - todo pass ok 83 - TODO tests should display properly -ok 84 table_exists(table) fail +ok 84 - table_exists(table) fail ok 85 - table_exists(table) should fail for non-existent table -ok 86 table_exists(schema, table) fail +ok 86 - table_exists(schema, table) fail ok 87 - table_exists(schema, table) should fail for non-existent table -ok 88 table_exists(schema, table, desc) fail +ok 88 - table_exists(schema, table, desc) fail ok 89 - table_exists(schema, table, desc) should fail for non-existent table -ok 90 table_exists(schema, table) pass -ok 91 - table_exists(schema, type, desc) should pass for an existing table +ok 90 - table_exists(schema, table) pass +ok 91 - table_exists(schema, table) should pass for an existing table +ok 92 - table_exists(schema, table, desc) pass +ok 93 - table_exists(schema, table, desc) should pass for an existing table +ok 94 - view_exists(view) fail +ok 95 - view_exists(view) should fail for non-existent view +ok 96 - view_exists(schema, view) fail +ok 97 - view_exists(schema, view) should fail for non-existent view +ok 98 - view_exists(schema, view, desc) fail +ok 99 - view_exists(schema, view, desc) should fail for non-existent view +ok 100 - view_exists(schema, view) pass +ok 101 - view_exists(schema, view) should pass for an existing view +ok 102 - view_exists(schema, view, desc) pass +ok 103 - view_exists(schema, view, desc) should pass for an existing view +ok 104 - column_exists(table, column) fail +ok 105 - column_exists(table, column) should fail for non-existent table +ok 106 - column_exists(schema, table, column) fail +ok 107 - column_exists(schema, table, column) should fail for non-existent table +ok 108 - column_exists(schema, table, column, desc) fail +ok 109 - column_exists(schema, table, column, desc) should fail for non-existent table +ok 110 - column_exists(table, column) pass +ok 111 - column_exists(table, column) should pass for an existing column +ok 112 - column_exists(schema, column, desc) pass +ok 113 - column_exists(schema, table, column, desc) should pass for an existing view column diff --git a/pgtap.sql.in b/pgtap.sql.in index 4c46f3af6f8b..c50f13d5ac66 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -448,6 +448,7 @@ CREATE OR REPLACE FUNCTION lives_ok ( TEXT ) RETURNS TEXT AS $$ SELECT lives_ok( $1, NULL ); $$ LANGUAGE SQL; +-- table_exists( schema, table, description ) CREATE OR REPLACE FUNCTION table_exists ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ SELECT ok( EXISTS( @@ -458,13 +459,17 @@ CREATE OR REPLACE FUNCTION table_exists ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; +-- table_exists( table ) CREATE OR REPLACE FUNCTION table_exists ( TEXT ) RETURNS TEXT AS $$ SELECT table_exists( 'public', $1, 'Table public.' || $1 || ' should exist' ); $$ LANGUAGE SQL; + +-- table_exists( schema, table ) CREATE OR REPLACE FUNCTION table_exists ( TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT table_exists( 'public', $1, 'Table ' || $1 || '.' || $2 || ' should exist' ); + SELECT table_exists( $1, $2, 'Table ' || $1 || '.' || $2 || ' should exist' ); $$ LANGUAGE SQL; +-- view_exists( schema, table, description ) CREATE OR REPLACE FUNCTION view_exists ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ SELECT ok( EXISTS( @@ -475,9 +480,34 @@ CREATE OR REPLACE FUNCTION view_exists ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; +-- view_exists( table ) CREATE OR REPLACE FUNCTION view_exists ( TEXT ) RETURNS TEXT AS $$ SELECT view_exists( 'public', $1, 'View public.' || $1 || ' should exist' ); $$ LANGUAGE SQL; + +-- view_exists( schema, table ) CREATE OR REPLACE FUNCTION view_exists ( TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT view_exists( 'public', $1, 'View ' || $1 || '.' || $2 || ' should exist' ); + SELECT view_exists( $1, $2, 'View ' || $1 || '.' || $2 || ' should exist' ); $$ LANGUAGE SQL; + +-- column_exists( schema, table, column, description ) +CREATE OR REPLACE FUNCTION column_exists ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM information_schema.columns + WHERE table_schema = $1 AND table_name = $2 AND column_name = $3 + ), $4 + ); +$$ LANGUAGE SQL; + +-- column_exists( table, column ) +CREATE OR REPLACE FUNCTION column_exists ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT column_exists( 'public', $1, $2, 'Column ' || 'public.' || $1 || '.' || $2 || ' should exist' ); +$$ LANGUAGE SQL; + +-- column_exists( schema, table, column ) +CREATE OR REPLACE FUNCTION column_exists ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT column_exists( $1, $2, $3, 'Column ' || $1 || '.' || $2 || '.' || $3 || ' should exist' ); +$$ LANGUAGE SQL; + diff --git a/sql/pgtap.sql b/sql/pgtap.sql index b680b9464521..4206b2e785d1 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -25,7 +25,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 99 +\set numb_tests 113 -- ## SET search_path TO TAPSCHEMA,public; @@ -251,8 +251,8 @@ SELECT is( /****************************************************************************/ -- Test todo tests. -\echo ok 81 todo fail -\echo ok 82 todo pass +\echo ok 81 - todo fail +\echo ok 82 - todo pass SELECT * FROM todo('just because', 2 ); SELECT is( fail('This is a todo test' ) @@ -267,21 +267,22 @@ UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 81 ); /****************************************************************************/ -- Test table_exists(). -\echo ok 84 table_exists(table) fail +\echo ok 84 - table_exists(table) fail SELECT is( table_exists( '__SDFSDFD__' ), E'not ok 84 - Table public.__SDFSDFD__ should exist\n# Failed test 84: "Table public.__SDFSDFD__ should exist"', 'table_exists(table) should fail for non-existent table' ); -\echo ok 86 table_exists(schema, table) fail + +\echo ok 86 - table_exists(schema, table) fail SELECT is( table_exists( 'foo', '__SDFSDFD__' ), E'not ok 86 - Table foo.__SDFSDFD__ should exist\n# Failed test 86: "Table foo.__SDFSDFD__ should exist"', 'table_exists(schema, table) should fail for non-existent table' ); -\echo ok 88 table_exists(schema, table, desc) fail +\echo ok 88 - table_exists(schema, table, desc) fail SELECT is( table_exists( 'foo', '__SDFSDFD__', 'desc' ), E'not ok 88 - desc\n# Failed test 88: "desc"', @@ -289,43 +290,100 @@ SELECT is( ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 84, 86, 88 ); -\echo ok 90 table_exists(schema, table) pass +\echo ok 90 - table_exists(schema, table) pass +SELECT is( + table_exists( 'pg_catalog', 'pg_type' ), + 'ok 90 - Table pg_catalog.pg_type should exist', + 'table_exists(schema, table) should pass for an existing table' +); + +\echo ok 92 - table_exists(schema, table, desc) pass SELECT is( table_exists( 'pg_catalog', 'pg_type', 'desc' ), - 'ok 90 - desc', - 'table_exists(schema, type, desc) should pass for an existing table' + 'ok 92 - desc', + 'table_exists(schema, table, desc) should pass for an existing table' ); /****************************************************************************/ -- Test view_exists(). -\echo ok 92 view_exists(view) fail +\echo ok 94 - view_exists(view) fail SELECT is( view_exists( '__SDFSDFD__' ), - E'not ok 92 - View public.__SDFSDFD__ should exist\n# Failed test 92: "View public.__SDFSDFD__ should exist"', + E'not ok 94 - View public.__SDFSDFD__ should exist\n# Failed test 94: "View public.__SDFSDFD__ should exist"', 'view_exists(view) should fail for non-existent view' ); -\echo ok 94 view_exists(schema, view) fail + +\echo ok 96 - view_exists(schema, view) fail SELECT is( view_exists( 'foo', '__SDFSDFD__' ), - E'not ok 94 - View foo.__SDFSDFD__ should exist\n# Failed test 94: "View foo.__SDFSDFD__ should exist"', + E'not ok 96 - View foo.__SDFSDFD__ should exist\n# Failed test 96: "View foo.__SDFSDFD__ should exist"', 'view_exists(schema, view) should fail for non-existent view' ); -\echo ok 96 view_exists(schema, view, desc) fail +\echo ok 98 - view_exists(schema, view, desc) fail SELECT is( view_exists( 'foo', '__SDFSDFD__', 'desc' ), - E'not ok 96 - desc\n# Failed test 96: "desc"', + E'not ok 98 - desc\n# Failed test 98: "desc"', 'view_exists(schema, view, desc) should fail for non-existent view' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 92, 94, 96 ); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 94, 96, 98 ); -\echo ok 98 view_exists(schema, view) pass +\echo ok 100 - view_exists(schema, view) pass +SELECT is( + view_exists( 'information_schema', 'tables' ), + 'ok 100 - View information_schema.tables should exist', + 'view_exists(schema, view) should pass for an existing view' +); + +\echo ok 102 - view_exists(schema, view, desc) pass SELECT is( view_exists( 'information_schema', 'tables', 'desc' ), - 'ok 98 - desc', - 'view_exists(schema, type, desc) should pass for an existing view' + 'ok 102 - desc', + 'view_exists(schema, view, desc) should pass for an existing view' +); + +/****************************************************************************/ +-- Test column_exists(). + +\echo ok 104 - column_exists(table, column) fail +SELECT is( + column_exists( '__SDFSDFD__', 'foo' ), + E'not ok 104 - Column public.__SDFSDFD__.foo should exist\n# Failed test 104: "Column public.__SDFSDFD__.foo should exist"', + 'column_exists(table, column) should fail for non-existent table' +); + +\echo ok 106 - column_exists(schema, table, column) fail +SELECT is( + column_exists( 'foo', '__SDFSDFD__', 'bar' ), + E'not ok 106 - Column foo.__SDFSDFD__.bar should exist\n# Failed test 106: "Column foo.__SDFSDFD__.bar should exist"', + 'column_exists(schema, table, column) should fail for non-existent table' +); + +\echo ok 108 - column_exists(schema, table, column, desc) fail +SELECT is( + column_exists( 'foo', '__SDFSDFD__', 'bar', 'desc' ), + E'not ok 108 - desc\n# Failed test 108: "desc"', + 'column_exists(schema, table, column, desc) should fail for non-existent table' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 104, 106, 108 ); + +-- This will be rolled back. :-) +CREATE TABLE sometab (id int); + +\echo ok 110 - column_exists(table, column) pass +SELECT is( + column_exists( 'sometab', 'id' ), + 'ok 110 - Column public.sometab.id should exist', + 'column_exists(table, column) should pass for an existing column' +); + +\echo ok 112 - column_exists(schema, column, desc) pass +SELECT is( + column_exists( 'information_schema', 'tables', 'table_name', 'desc' ), + 'ok 112 - desc', + 'column_exists(schema, table, column, desc) should pass for an existing view column' ); -- Finish the tests and clean up. From c0888aa9716d8874f34e00a2021c0e35cb0358c7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 18 Aug 2008 19:09:47 +0000 Subject: [PATCH 0051/1195] Changed `*_exists` to `has_*`. --- Changes | 4 +- expected/pgtap.out | 60 ++++++++++++++--------------- pgtap.sql.in | 49 ++++++++++++----------- sql/pgtap.sql | 96 +++++++++++++++++++++++----------------------- 4 files changed, 104 insertions(+), 105 deletions(-) diff --git a/Changes b/Changes index ac0f9f065e57..f12d4be912df 100644 --- a/Changes +++ b/Changes @@ -29,8 +29,8 @@ Revision history for pgTAP - Changed "got/expected" to "have/want", following Schwern's plans for Test::Builder 2. Also changed "caught/expected" to "caught/wanted" for nice parity when outputting diagnostics for exception testing. - - Added the `table_exists()`, `view_exists()`, and `column_exists()` - test functions. + - Added the `has_table()`, `has_view()`, and `has_column()` test + functions. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/expected/pgtap.out b/expected/pgtap.out index 855648fc440e..7a2d26c3f887 100644 --- a/expected/pgtap.out +++ b/expected/pgtap.out @@ -83,33 +83,33 @@ ok 80 - multiline desriptions should have subsequent lines escaped ok 81 - todo fail ok 82 - todo pass ok 83 - TODO tests should display properly -ok 84 - table_exists(table) fail -ok 85 - table_exists(table) should fail for non-existent table -ok 86 - table_exists(schema, table) fail -ok 87 - table_exists(schema, table) should fail for non-existent table -ok 88 - table_exists(schema, table, desc) fail -ok 89 - table_exists(schema, table, desc) should fail for non-existent table -ok 90 - table_exists(schema, table) pass -ok 91 - table_exists(schema, table) should pass for an existing table -ok 92 - table_exists(schema, table, desc) pass -ok 93 - table_exists(schema, table, desc) should pass for an existing table -ok 94 - view_exists(view) fail -ok 95 - view_exists(view) should fail for non-existent view -ok 96 - view_exists(schema, view) fail -ok 97 - view_exists(schema, view) should fail for non-existent view -ok 98 - view_exists(schema, view, desc) fail -ok 99 - view_exists(schema, view, desc) should fail for non-existent view -ok 100 - view_exists(schema, view) pass -ok 101 - view_exists(schema, view) should pass for an existing view -ok 102 - view_exists(schema, view, desc) pass -ok 103 - view_exists(schema, view, desc) should pass for an existing view -ok 104 - column_exists(table, column) fail -ok 105 - column_exists(table, column) should fail for non-existent table -ok 106 - column_exists(schema, table, column) fail -ok 107 - column_exists(schema, table, column) should fail for non-existent table -ok 108 - column_exists(schema, table, column, desc) fail -ok 109 - column_exists(schema, table, column, desc) should fail for non-existent table -ok 110 - column_exists(table, column) pass -ok 111 - column_exists(table, column) should pass for an existing column -ok 112 - column_exists(schema, column, desc) pass -ok 113 - column_exists(schema, table, column, desc) should pass for an existing view column +ok 84 - has_table(table) fail +ok 85 - has_table(table) should fail for non-existent table +ok 86 - has_table(schema, table) fail +ok 87 - has_table(schema, table) should fail for non-existent table +ok 88 - has_table(schema, table, desc) fail +ok 89 - has_table(schema, table, desc) should fail for non-existent table +ok 90 - has_table(schema, table) pass +ok 91 - has_table(schema, table) should pass for an existing table +ok 92 - has_table(schema, table, desc) pass +ok 93 - has_table(schema, table, desc) should pass for an existing table +ok 94 - has_view(view) fail +ok 95 - has_view(view) should fail for non-existent view +ok 96 - has_view(schema, view) fail +ok 97 - has_view(schema, view) should fail for non-existent view +ok 98 - has_view(schema, view, desc) fail +ok 99 - has_view(schema, view, desc) should fail for non-existent view +ok 100 - has_view(schema, view) pass +ok 101 - has_view(schema, view) should pass for an existing view +ok 102 - has_view(schema, view, desc) pass +ok 103 - has_view(schema, view, desc) should pass for an existing view +ok 104 - has_column(table, column) fail +ok 105 - has_column(table, column) should fail for non-existent table +ok 106 - has_column(schema, table, column) fail +ok 107 - has_column(schema, table, column) should fail for non-existent table +ok 108 - has_column(schema, table, column, desc) fail +ok 109 - has_column(schema, table, column, desc) should fail for non-existent table +ok 110 - has_column(table, column) pass +ok 111 - has_column(table, column) should pass for an existing column +ok 112 - has_column(schema, column, desc) pass +ok 113 - has_column(schema, table, column, desc) should pass for an existing view column diff --git a/pgtap.sql.in b/pgtap.sql.in index c50f13d5ac66..826d0ff62556 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -448,8 +448,8 @@ CREATE OR REPLACE FUNCTION lives_ok ( TEXT ) RETURNS TEXT AS $$ SELECT lives_ok( $1, NULL ); $$ LANGUAGE SQL; --- table_exists( schema, table, description ) -CREATE OR REPLACE FUNCTION table_exists ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +-- has_table( schema, table, description ) +CREATE OR REPLACE FUNCTION has_table ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ SELECT ok( EXISTS( SELECT true @@ -459,18 +459,18 @@ CREATE OR REPLACE FUNCTION table_exists ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; --- table_exists( table ) -CREATE OR REPLACE FUNCTION table_exists ( TEXT ) RETURNS TEXT AS $$ - SELECT table_exists( 'public', $1, 'Table public.' || $1 || ' should exist' ); +-- has_table( table ) +CREATE OR REPLACE FUNCTION has_table ( TEXT ) RETURNS TEXT AS $$ + SELECT has_table( 'public', $1, 'Table public.' || $1 || ' should exist' ); $$ LANGUAGE SQL; --- table_exists( schema, table ) -CREATE OR REPLACE FUNCTION table_exists ( TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT table_exists( $1, $2, 'Table ' || $1 || '.' || $2 || ' should exist' ); +-- has_table( schema, table ) +CREATE OR REPLACE FUNCTION has_table ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT has_table( $1, $2, 'Table ' || $1 || '.' || $2 || ' should exist' ); $$ LANGUAGE SQL; --- view_exists( schema, table, description ) -CREATE OR REPLACE FUNCTION view_exists ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +-- has_view( schema, table, description ) +CREATE OR REPLACE FUNCTION has_view ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ SELECT ok( EXISTS( SELECT true @@ -480,18 +480,18 @@ CREATE OR REPLACE FUNCTION view_exists ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; --- view_exists( table ) -CREATE OR REPLACE FUNCTION view_exists ( TEXT ) RETURNS TEXT AS $$ - SELECT view_exists( 'public', $1, 'View public.' || $1 || ' should exist' ); +-- has_view( table ) +CREATE OR REPLACE FUNCTION has_view ( TEXT ) RETURNS TEXT AS $$ + SELECT has_view( 'public', $1, 'View public.' || $1 || ' should exist' ); $$ LANGUAGE SQL; --- view_exists( schema, table ) -CREATE OR REPLACE FUNCTION view_exists ( TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT view_exists( $1, $2, 'View ' || $1 || '.' || $2 || ' should exist' ); +-- has_view( schema, table ) +CREATE OR REPLACE FUNCTION has_view ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT has_view( $1, $2, 'View ' || $1 || '.' || $2 || ' should exist' ); $$ LANGUAGE SQL; --- column_exists( schema, table, column, description ) -CREATE OR REPLACE FUNCTION column_exists ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +-- has_column( schema, table, column, description ) +CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ SELECT ok( EXISTS( SELECT true @@ -501,13 +501,12 @@ CREATE OR REPLACE FUNCTION column_exists ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT ); $$ LANGUAGE SQL; --- column_exists( table, column ) -CREATE OR REPLACE FUNCTION column_exists ( TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT column_exists( 'public', $1, $2, 'Column ' || 'public.' || $1 || '.' || $2 || ' should exist' ); +-- has_column( table, column ) +CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT has_column( 'public', $1, $2, 'Column ' || 'public.' || $1 || '.' || $2 || ' should exist' ); $$ LANGUAGE SQL; --- column_exists( schema, table, column ) -CREATE OR REPLACE FUNCTION column_exists ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT column_exists( $1, $2, $3, 'Column ' || $1 || '.' || $2 || '.' || $3 || ' should exist' ); +-- has_column( schema, table, column ) +CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT has_column( $1, $2, $3, 'Column ' || $1 || '.' || $2 || '.' || $3 || ' should exist' ); $$ LANGUAGE SQL; - diff --git a/sql/pgtap.sql b/sql/pgtap.sql index 4206b2e785d1..e0a89661279b 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -265,125 +265,125 @@ UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 81 ); /****************************************************************************/ --- Test table_exists(). +-- Test has_table(). -\echo ok 84 - table_exists(table) fail +\echo ok 84 - has_table(table) fail SELECT is( - table_exists( '__SDFSDFD__' ), + has_table( '__SDFSDFD__' ), E'not ok 84 - Table public.__SDFSDFD__ should exist\n# Failed test 84: "Table public.__SDFSDFD__ should exist"', - 'table_exists(table) should fail for non-existent table' + 'has_table(table) should fail for non-existent table' ); -\echo ok 86 - table_exists(schema, table) fail +\echo ok 86 - has_table(schema, table) fail SELECT is( - table_exists( 'foo', '__SDFSDFD__' ), + has_table( 'foo', '__SDFSDFD__' ), E'not ok 86 - Table foo.__SDFSDFD__ should exist\n# Failed test 86: "Table foo.__SDFSDFD__ should exist"', - 'table_exists(schema, table) should fail for non-existent table' + 'has_table(schema, table) should fail for non-existent table' ); -\echo ok 88 - table_exists(schema, table, desc) fail +\echo ok 88 - has_table(schema, table, desc) fail SELECT is( - table_exists( 'foo', '__SDFSDFD__', 'desc' ), + has_table( 'foo', '__SDFSDFD__', 'desc' ), E'not ok 88 - desc\n# Failed test 88: "desc"', - 'table_exists(schema, table, desc) should fail for non-existent table' + 'has_table(schema, table, desc) should fail for non-existent table' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 84, 86, 88 ); -\echo ok 90 - table_exists(schema, table) pass +\echo ok 90 - has_table(schema, table) pass SELECT is( - table_exists( 'pg_catalog', 'pg_type' ), + has_table( 'pg_catalog', 'pg_type' ), 'ok 90 - Table pg_catalog.pg_type should exist', - 'table_exists(schema, table) should pass for an existing table' + 'has_table(schema, table) should pass for an existing table' ); -\echo ok 92 - table_exists(schema, table, desc) pass +\echo ok 92 - has_table(schema, table, desc) pass SELECT is( - table_exists( 'pg_catalog', 'pg_type', 'desc' ), + has_table( 'pg_catalog', 'pg_type', 'desc' ), 'ok 92 - desc', - 'table_exists(schema, table, desc) should pass for an existing table' + 'has_table(schema, table, desc) should pass for an existing table' ); /****************************************************************************/ --- Test view_exists(). +-- Test has_view(). -\echo ok 94 - view_exists(view) fail +\echo ok 94 - has_view(view) fail SELECT is( - view_exists( '__SDFSDFD__' ), + has_view( '__SDFSDFD__' ), E'not ok 94 - View public.__SDFSDFD__ should exist\n# Failed test 94: "View public.__SDFSDFD__ should exist"', - 'view_exists(view) should fail for non-existent view' + 'has_view(view) should fail for non-existent view' ); -\echo ok 96 - view_exists(schema, view) fail +\echo ok 96 - has_view(schema, view) fail SELECT is( - view_exists( 'foo', '__SDFSDFD__' ), + has_view( 'foo', '__SDFSDFD__' ), E'not ok 96 - View foo.__SDFSDFD__ should exist\n# Failed test 96: "View foo.__SDFSDFD__ should exist"', - 'view_exists(schema, view) should fail for non-existent view' + 'has_view(schema, view) should fail for non-existent view' ); -\echo ok 98 - view_exists(schema, view, desc) fail +\echo ok 98 - has_view(schema, view, desc) fail SELECT is( - view_exists( 'foo', '__SDFSDFD__', 'desc' ), + has_view( 'foo', '__SDFSDFD__', 'desc' ), E'not ok 98 - desc\n# Failed test 98: "desc"', - 'view_exists(schema, view, desc) should fail for non-existent view' + 'has_view(schema, view, desc) should fail for non-existent view' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 94, 96, 98 ); -\echo ok 100 - view_exists(schema, view) pass +\echo ok 100 - has_view(schema, view) pass SELECT is( - view_exists( 'information_schema', 'tables' ), + has_view( 'information_schema', 'tables' ), 'ok 100 - View information_schema.tables should exist', - 'view_exists(schema, view) should pass for an existing view' + 'has_view(schema, view) should pass for an existing view' ); -\echo ok 102 - view_exists(schema, view, desc) pass +\echo ok 102 - has_view(schema, view, desc) pass SELECT is( - view_exists( 'information_schema', 'tables', 'desc' ), + has_view( 'information_schema', 'tables', 'desc' ), 'ok 102 - desc', - 'view_exists(schema, view, desc) should pass for an existing view' + 'has_view(schema, view, desc) should pass for an existing view' ); /****************************************************************************/ --- Test column_exists(). +-- Test has_column(). -\echo ok 104 - column_exists(table, column) fail +\echo ok 104 - has_column(table, column) fail SELECT is( - column_exists( '__SDFSDFD__', 'foo' ), + has_column( '__SDFSDFD__', 'foo' ), E'not ok 104 - Column public.__SDFSDFD__.foo should exist\n# Failed test 104: "Column public.__SDFSDFD__.foo should exist"', - 'column_exists(table, column) should fail for non-existent table' + 'has_column(table, column) should fail for non-existent table' ); -\echo ok 106 - column_exists(schema, table, column) fail +\echo ok 106 - has_column(schema, table, column) fail SELECT is( - column_exists( 'foo', '__SDFSDFD__', 'bar' ), + has_column( 'foo', '__SDFSDFD__', 'bar' ), E'not ok 106 - Column foo.__SDFSDFD__.bar should exist\n# Failed test 106: "Column foo.__SDFSDFD__.bar should exist"', - 'column_exists(schema, table, column) should fail for non-existent table' + 'has_column(schema, table, column) should fail for non-existent table' ); -\echo ok 108 - column_exists(schema, table, column, desc) fail +\echo ok 108 - has_column(schema, table, column, desc) fail SELECT is( - column_exists( 'foo', '__SDFSDFD__', 'bar', 'desc' ), + has_column( 'foo', '__SDFSDFD__', 'bar', 'desc' ), E'not ok 108 - desc\n# Failed test 108: "desc"', - 'column_exists(schema, table, column, desc) should fail for non-existent table' + 'has_column(schema, table, column, desc) should fail for non-existent table' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 104, 106, 108 ); -- This will be rolled back. :-) CREATE TABLE sometab (id int); -\echo ok 110 - column_exists(table, column) pass +\echo ok 110 - has_column(table, column) pass SELECT is( - column_exists( 'sometab', 'id' ), + has_column( 'sometab', 'id' ), 'ok 110 - Column public.sometab.id should exist', - 'column_exists(table, column) should pass for an existing column' + 'has_column(table, column) should pass for an existing column' ); -\echo ok 112 - column_exists(schema, column, desc) pass +\echo ok 112 - has_column(schema, column, desc) pass SELECT is( - column_exists( 'information_schema', 'tables', 'table_name', 'desc' ), + has_column( 'information_schema', 'tables', 'table_name', 'desc' ), 'ok 112 - desc', - 'column_exists(schema, table, column, desc) should pass for an existing view column' + 'has_column(schema, table, column, desc) should pass for an existing view column' ); -- Finish the tests and clean up. From 7a7945f7a0ee07df035ddb47979e54a5300858c6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 18 Aug 2008 19:38:25 +0000 Subject: [PATCH 0052/1195] Added the `col_not_null()` and `col_is_null()` test functions. --- Changes | 1 + expected/pgtap.out | 18 ++++++++++++- pgtap.sql.in | 44 +++++++++++++++++++++++++++++++ sql/pgtap.sql | 64 ++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 124 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index f12d4be912df..4fb236cbd323 100644 --- a/Changes +++ b/Changes @@ -31,6 +31,7 @@ Revision history for pgTAP nice parity when outputting diagnostics for exception testing. - Added the `has_table()`, `has_view()`, and `has_column()` test functions. + - Added the `col_not_null()` and `col_is_null()` test functions. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/expected/pgtap.out b/expected/pgtap.out index 7a2d26c3f887..ad33b6de8f26 100644 --- a/expected/pgtap.out +++ b/expected/pgtap.out @@ -1,5 +1,5 @@ \set ECHO -1..113 +1..129 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -113,3 +113,19 @@ ok 110 - has_column(table, column) pass ok 111 - has_column(table, column) should pass for an existing column ok 112 - has_column(schema, column, desc) pass ok 113 - has_column(schema, table, column, desc) should pass for an existing view column +ok 114 - testing col_not_null( schema, table, column, desc ) +ok 115 - col_not_null( schema, table, column, desc ) should work +ok 116 - testing col_not_null( schema, table, column, desc ) +ok 117 - col_not_null( schema, table, column ) should work +ok 118 - testing col_not_null( schema, table, column, desc ) +ok 119 - col_not_null( table, column ) should work +ok 120 - testing col_not_null( schema, table, column, desc ) +ok 121 - col_not_null( table, column ) should properly fail +ok 122 - testing col_is_null( schema, table, column, desc ) +ok 123 - col_is_null( schema, table, column, desc ) should work +ok 124 - testing col_is_null( schema, table, column, desc ) +ok 125 - col_is_null( schema, table, column ) should work +ok 126 - testing col_is_null( schema, table, column, desc ) +ok 127 - col_is_null( table, column ) should work +ok 128 - testing col_is_null( schema, table, column, desc ) +ok 129 - col_is_null( table, column ) should properly fail diff --git a/pgtap.sql.in b/pgtap.sql.in index 826d0ff62556..b0c04affe289 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -510,3 +510,47 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ SELECT has_column( $1, $2, $3, 'Column ' || $1 || '.' || $2 || '.' || $3 || ' should exist' ); $$ LANGUAGE SQL; + +-- _col_is_null( schema, table, column, desc, null ) +CREATE OR REPLACE FUNCTION _col_is_null ( TEXT, TEXT, TEXT, TEXT, bool ) RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM information_schema.columns + WHERE table_schema = $1 + AND table_name = $2 + AND column_name = $3 + AND is_nullable = CASE $5 WHEN TRUE THEN 'YES' ELSE 'NO' END + ), $4 + ); +$$ LANGUAGE SQL; + +-- col_not_null( schema, table, column, desc ) +CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, $3, $4, false ); +$$ LANGUAGE SQL; + +-- col_not_null( schema, table, column ) +CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, $3, 'Column ' || $1 || '.' || $2 || '.' || $3 || ' should be NOT NULL', false ); +$$ LANGUAGE SQL; + +-- col_not_null( table, column ) +CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT _col_is_null( 'public', $1, $2, 'Column public.' || $1 || '.' || $2 || ' should be NOT NULL', false ); +$$ LANGUAGE SQL; + +-- col_is_null( schema, table, column, desc ) +CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, $3, $4, true ); +$$ LANGUAGE SQL; + +-- col_is_null( schema, table, column ) +CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, $3, 'Column ' || $1 || '.' || $2 || '.' || $3 || ' should allow NULL', true ); +$$ LANGUAGE SQL; + +-- col_is_null( table, column ) +CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT _col_is_null( 'public', $1, $2, 'Column public.' || $1 || '.' || $2 || ' should allow NULL', true ); +$$ LANGUAGE SQL; diff --git a/sql/pgtap.sql b/sql/pgtap.sql index e0a89661279b..e002c6a95572 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -25,7 +25,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 113 +\set numb_tests 129 -- ## SET search_path TO TAPSCHEMA,public; @@ -370,7 +370,7 @@ SELECT is( UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 104, 106, 108 ); -- This will be rolled back. :-) -CREATE TABLE sometab (id int); +CREATE TABLE sometab (id int NOT NULL, name text); \echo ok 110 - has_column(table, column) pass SELECT is( @@ -386,6 +386,66 @@ SELECT is( 'has_column(schema, table, column, desc) should pass for an existing view column' ); +/****************************************************************************/ +-- Test col_not_null(). +\echo ok 114 - testing col_not_null( schema, table, column, desc ) +SELECT is( + col_not_null( 'pg_catalog', 'pg_type', 'typname', 'typname not null' ), + 'ok 114 - typname not null', + 'col_not_null( schema, table, column, desc ) should work' +); +\echo ok 116 - testing col_not_null( schema, table, column, desc ) +SELECT is( + col_not_null( 'pg_catalog', 'pg_type', 'typname' ), + 'ok 116 - Column pg_catalog.pg_type.typname should be NOT NULL', + 'col_not_null( schema, table, column ) should work' +); + +\echo ok 118 - testing col_not_null( schema, table, column, desc ) +SELECT is( + col_not_null( 'sometab', 'id' ), + 'ok 118 - Column public.sometab.id should be NOT NULL', + 'col_not_null( table, column ) should work' +); +-- Make sure failure is correct. +\echo ok 120 - testing col_not_null( schema, table, column, desc ) +SELECT is( + col_not_null( 'sometab', 'name' ), + E'not ok 120 - Column public.sometab.name should be NOT NULL\n# Failed test 120: "Column public.sometab.name should be NOT NULL"', + 'col_not_null( table, column ) should properly fail' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 120 ); + +/****************************************************************************/ +-- Test col_is_null(). +\echo ok 122 - testing col_is_null( schema, table, column, desc ) +SELECT is( + col_is_null( 'public', 'sometab', 'name', 'name is null' ), + 'ok 122 - name is null', + 'col_is_null( schema, table, column, desc ) should work' +); +\echo ok 124 - testing col_is_null( schema, table, column, desc ) +SELECT is( + col_is_null( 'public', 'sometab', 'name' ), + 'ok 124 - Column public.sometab.name should allow NULL', + 'col_is_null( schema, table, column ) should work' +); + +\echo ok 126 - testing col_is_null( schema, table, column, desc ) +SELECT is( + col_is_null( 'sometab', 'name' ), + 'ok 126 - Column public.sometab.name should allow NULL', + 'col_is_null( table, column ) should work' +); +-- Make sure failure is correct. +\echo ok 128 - testing col_is_null( schema, table, column, desc ) +SELECT is( + col_is_null( 'sometab', 'id' ), + E'not ok 128 - Column public.sometab.id should allow NULL\n# Failed test 128: "Column public.sometab.id should allow NULL"', + 'col_is_null( table, column ) should properly fail' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 128 ); + -- Finish the tests and clean up. SELECT * FROM finish(); ROLLBACK; From 62313de8df1a8b6c444d9f58f5d6350982f6f25e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 18 Aug 2008 19:47:22 +0000 Subject: [PATCH 0053/1195] Drop all functions. --- drop_pgtap.sql.in | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drop_pgtap.sql.in b/drop_pgtap.sql.in index 58b87d68a232..2469547a8e9e 100644 --- a/drop_pgtap.sql.in +++ b/drop_pgtap.sql.in @@ -1,4 +1,20 @@ -- $Id$ +DROP FUNCTION col_is_null ( TEXT, TEXT ); +DROP FUNCTION col_is_null ( TEXT, TEXT, TEXT ); +DROP FUNCTION col_is_null ( TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION col_not_null ( TEXT, TEXT ); +DROP FUNCTION col_not_null ( TEXT, TEXT, TEXT ); +DROP FUNCTION col_not_null ( TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION _col_is_null ( TEXT, TEXT, TEXT, TEXT, bool ); +DROP FUNCTION has_column ( TEXT, TEXT, TEXT ); +DROP FUNCTION has_column ( TEXT, TEXT ); +DROP FUNCTION has_column ( TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION has_view ( TEXT, TEXT ); +DROP FUNCTION has_view ( TEXT ); +DROP FUNCTION has_view ( TEXT, TEXT, TEXT ); +DROP FUNCTION has_table ( TEXT, TEXT ); +DROP FUNCTION has_table ( TEXT ); +DROP FUNCTION has_table ( TEXT, TEXT, TEXT ); DROP FUNCTION lives_ok ( TEXT ); DROP FUNCTION lives_ok ( TEXT, TEXT ); DROP FUNCTION throws_ok ( TEXT, CHAR(5) ); From 07fdcd43b790e6442848db8b72e0b5c3713e68cc Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 18 Aug 2008 20:59:42 +0000 Subject: [PATCH 0054/1195] Added the `col_type_is()` test function. --- Changes | 1 + expected/pgtap.out | 8 ++++++- pgtap.sql.in | 56 ++++++++++++++++++++++++++++++++++++++++++++++ sql/pgtap.sql | 26 ++++++++++++++++++++- 4 files changed, 89 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 4fb236cbd323..666f443c8a8a 100644 --- a/Changes +++ b/Changes @@ -32,6 +32,7 @@ Revision history for pgTAP - Added the `has_table()`, `has_view()`, and `has_column()` test functions. - Added the `col_not_null()` and `col_is_null()` test functions. + - Added the `col_type_is()` test function. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/expected/pgtap.out b/expected/pgtap.out index ad33b6de8f26..df64434c9583 100644 --- a/expected/pgtap.out +++ b/expected/pgtap.out @@ -1,5 +1,5 @@ \set ECHO -1..129 +1..135 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -129,3 +129,9 @@ ok 126 - testing col_is_null( schema, table, column, desc ) ok 127 - col_is_null( table, column ) should work ok 128 - testing col_is_null( schema, table, column, desc ) ok 129 - col_is_null( table, column ) should properly fail +ok 130 - testing col_type_is( schema, table, column, type, desc ) +ok 131 - col_type_is( schema, table, column, type, desc ) should work +ok 132 - testing col_type_is( schema, table, column, type ) +ok 133 - col_type_is( schema, table, column, type ) should work +ok 134 - testing col_type_is( table, column, type ) +ok 135 - col_type_is( table, column, type ) should work diff --git a/pgtap.sql.in b/pgtap.sql.in index b0c04affe289..7e154724392b 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -554,3 +554,59 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT ) RETURNS TEXT AS $$ SELECT _col_is_null( 'public', $1, $2, 'Column public.' || $1 || '.' || $2 || ' should allow NULL', true ); $$ LANGUAGE SQL; + +-- col_type_is( schema, table, column, type, desc ) +CREATE OR REPLACE FUNCTION col_type_is ( TEXT, TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +DECLARE + actual_type TEXT; +BEGIN + -- Get the data type. + SELECT data_type into actual_type + FROM information_schema.columns + WHERE table_schema = $1 + AND table_name = $2 + AND column_name = $3; + + IF actual_type = $4 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: ' || COALESCE( actual_type::text, 'NULL' ) || + E'\n want: ' || $4::text + ); +END; +$$ LANGUAGE plpgsql; + +-- col_type_is( schema, table, column, type ) +CREATE OR REPLACE FUNCTION col_type_is ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT col_type_is( $1, $2, $3, $4, 'Column ' || $1 || '.' || $2 || '.' || $3 || ' should be type ' || $4 ); +$$ LANGUAGE SQL; + +-- col_type_is( table, column, type ) +CREATE OR REPLACE FUNCTION col_type_is ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT col_type_is( 'public', $1, $2, $3, 'Column public.' || $1 || '.' || $2 || ' should be type ' || $3 ); +$$ LANGUAGE SQL; + +/* + +CREATE OR REPLACE FUNCTION column_is ( TEXT, TEXT, TEXT, TEXT, bool, TEXT, bool, bool, TEXT ) RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM information_schema.columns + WHERE table_schema = $1 + AND table_name = $2 + AND column_name = $3 + AND data_type = LOWER($4) + AND is_nullable = CASE $5 WHEN TRUE THEN 'YES' ELSE 'NO' END, + AND COALESCE(column_default, '[_NULL_]') = COALESCE($6, '[_NULL_]'), + -- PK? + AND EXISTS( select + ), $4 + ); +$$ LANGUAGE SQL; + +*/ diff --git a/sql/pgtap.sql b/sql/pgtap.sql index e002c6a95572..30258ec44b28 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -25,7 +25,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 129 +\set numb_tests 135 -- ## SET search_path TO TAPSCHEMA,public; @@ -446,6 +446,30 @@ SELECT is( ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 128 ); +/****************************************************************************/ +-- Test col_type_is(). +\echo ok 130 - testing col_type_is( schema, table, column, type, desc ) +SELECT is( + col_type_is( 'public', 'sometab', 'name', 'text', 'name is text' ), + 'ok 130 - name is text', + 'col_type_is( schema, table, column, type, desc ) should work' +); + +\echo ok 132 - testing col_type_is( schema, table, column, type ) +SELECT is( + col_type_is( 'public', 'sometab', 'name', 'text' ), + 'ok 132 - Column public.sometab.name should be type text', + 'col_type_is( schema, table, column, type ) should work' +); + +\echo ok 134 - testing col_type_is( table, column, type ) +SELECT is( + col_type_is( 'sometab', 'name', 'text' ), + 'ok 134 - Column public.sometab.name should be type text', + 'col_type_is( table, column, type ) should work' +); + +/****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); ROLLBACK; From d4cdbcebd9747c6de40f785f0706d47b777e5d2f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 18 Aug 2008 21:15:05 +0000 Subject: [PATCH 0055/1195] * Added missing documentation. * Added missing uninstall stuff. * Made `col_type_is()` compare types case-insensitively. * Added a test for the diagnostics from `col_type_is()`. --- README.pgtap | 88 +++++++++++++++++++++++++++++++++++++++-------- drop_pgtap.sql.in | 3 ++ pgtap.sql.in | 3 +- sql/pgtap.sql | 18 +++++++++- 4 files changed, 96 insertions(+), 16 deletions(-) diff --git a/README.pgtap b/README.pgtap index 825b0060a698..90aedc06679e 100644 --- a/README.pgtap +++ b/README.pgtap @@ -439,11 +439,11 @@ A Wicked Schema Need to make sure that your database is designed just the way you think it should be? Use these test functions and rest easy. -### table_exists( text, text, text ) ### -### table_exists( text, text ) ### -### table_exists( text ) ### +### has_table( text, text, text ) ### +### has_table( text, text ) ### +### has_table( text ) ### - SELECT table_exists( + SELECT has_table( 'myschema', 'sometable', 'I got myschema.sometable' @@ -455,23 +455,23 @@ test description. If you omit the test description, it will be set to "Table $schema.$table should exist". If you pass just a single argument, it is assumed to be the table name and the schema is assumed to be "public". -### view_exists( text, text, text ) ### -### view_exists( text, text ) ### -### view_exists( text ) ### +### has_view( text, text, text ) ### +### has_view( text, text ) ### +### has_view( text ) ### - SELECT view_exists( + SELECT has_view( 'myschema', 'someview', 'I got myschema.someview' ); -Just like `table_exists()`, only it tests for the existence of a view. +Just like `has_table()`, only it tests for the existence of a view. -### column_exists( text, text, text, text ) ### -### column_exists( text, text, text ) ### -### column_exists( text, text ) ### +### has_column( text, text, text, text ) ### +### has_column( text, text, text ) ### +### has_column( text, text ) ### - SELECT column_exists( + SELECT has_column( 'myschema', 'somecolumn', 'somecolumn', @@ -485,7 +485,67 @@ omitted, it will be set to "Column $schema.$table.$column should exist". If only two arguments are passed, they are assumed to be the table and column names, and the schema will default to "public". -Just like `table_exists()`, only it tests for the existence of a column. +Just like `has_table()`, only it tests for the existence of a column. + +### col_not_null ( text, text, text, text ) ### +### col_not_null ( text, text, text ) ### +### col_not_null ( text, text ) ### + + SELECT col_not_null( + 'myschema', + 'somecolumn', + 'somecolumn', + 'Column myschema.sometable.somecolumn should be NOT NULL' + ); + +Tests whether the specified column has a `NOT NULL` constraint. The first +argument is the schema name, the second the table name, the third the column +name, and the fourth is the test description. If the test description is +omitted, it will be set to "Column $schema.$table.$column should be NOT NULL". +If only two arguments are passed, they are assumed to be the table and column +names, and the schema will default to "public". Note that this test will fail +if the column in question does not exist. + +### col_is_null ( text, text, text, text ) ### +### col_is_null ( text, text, text ) ### +### col_is_null ( text, text ) ### + + SELECT col_is_null( + 'myschema', + 'somecolumn', + 'somecolumn', + 'Column myschema.sometable.somecolumn should allow NULL' + ); + +This function is the inverse of `col_not_null()`: the test passes if the +column does not have a `NOT NULL` constraint. The first argument is the schema +name, the second the table name, the third the column name, and the fourth is +the test description. If the test description is omitted, it will be set to +"Column $schema.$table.$column should allow NULL". If only two arguments are +passed, they are assumed to be the table and column names, and the schema will +default to "public". Note that this test will fail if the column in question +does not exist. + +### col_type_is ( text, text, text, text, text ) ### +### col_type_is ( text, text, text, text ) ### +### col_type_is ( text, text, text ) ### + + SELECT col_is_null( + 'myschema', + 'somecolumn', + 'somecolumn', + 'text', + 'Column myschema.sometable.somecolumn be type text' + ); + +This function tests that the specified column is of a particular type. If it +fails, it will emit diagnostics naming the actual type. The first argument is +the schema name, the second the table name, the third the column name, the +fourth the type, and the fifth is the test description. If the test +description is omitted, it will be set to "Column $schema.$table.$column +should be type $type". If only three arguments are passed, they are assumed to +be the table, column names, and type, and the schema will default to "public". +Note that this test will fail if the column in question does not exist. Diagnostics ----------- diff --git a/drop_pgtap.sql.in b/drop_pgtap.sql.in index 2469547a8e9e..5ab8a196585b 100644 --- a/drop_pgtap.sql.in +++ b/drop_pgtap.sql.in @@ -1,4 +1,7 @@ -- $Id$ +DROP FUNCTION col_type_is ( TEXT, TEXT, TEXT ); +DROP FUNCTION col_type_is ( TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION col_type_is ( TEXT, TEXT, TEXT, TEXT, TEXT ); DROP FUNCTION col_is_null ( TEXT, TEXT ); DROP FUNCTION col_is_null ( TEXT, TEXT, TEXT ); DROP FUNCTION col_is_null ( TEXT, TEXT, TEXT, TEXT ); diff --git a/pgtap.sql.in b/pgtap.sql.in index 7e154724392b..caafaa03583b 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -567,7 +567,7 @@ BEGIN AND table_name = $2 AND column_name = $3; - IF actual_type = $4 THEN + IF actual_type = LOWER($4) THEN -- We're good to go. RETURN ok( true, $5 ); END IF; @@ -610,3 +610,4 @@ CREATE OR REPLACE FUNCTION column_is ( TEXT, TEXT, TEXT, TEXT, bool, TEXT, bool, $$ LANGUAGE SQL; */ + \ No newline at end of file diff --git a/sql/pgtap.sql b/sql/pgtap.sql index 30258ec44b28..6e1173848b1a 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -25,7 +25,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 135 +\set numb_tests 139 -- ## SET search_path TO TAPSCHEMA,public; @@ -469,6 +469,22 @@ SELECT is( 'col_type_is( table, column, type ) should work' ); +\echo ok 136 - testing col_type_is( table, column, type ) case-insensitively +SELECT is( + col_type_is( 'sometab', 'name', 'TEXT' ), + 'ok 136 - Column public.sometab.name should be type TEXT', + 'col_type_is( table, column, type ) should work case-insensitively' +); + +-- Make sure failure is correct. +\echo ok 138 - testing col_type_is( table, column, type ) failure +SELECT is( + col_type_is( 'sometab', 'name', 'int4' ), + E'not ok 138 - Column public.sometab.name should be type int4\n# Failed test 138: "Column public.sometab.name should be type int4"\n# have: text\n# want: int4', + 'col_type_is( table, column, type ) should fail with proper diagnostics' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 138 ); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 500561275270e6646148467a90a99ec70f92db66 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 18 Aug 2008 21:48:03 +0000 Subject: [PATCH 0056/1195] * Added an alias for `throws_ok()` that allows the error code to be passed as an integer. The integer will be case to `char(5)` and passed to the real `throws_ok()` function. * Changed the function signatures in the documentation to have descriptive names for the arguments, rather than just types. This is different than the typical PostgreSQL documentation, but much more useful to testers. * Fixed a few other minor issues with the README. * Added an example of the diagnostics from `col_type_is()`. --- README.pgtap | 179 ++++++++++++++++++++++++--------------------- expected/pgtap.out | 6 +- pgtap.sql.in | 9 +++ sql/pgtap.sql | 2 +- 4 files changed, 109 insertions(+), 87 deletions(-) diff --git a/README.pgtap b/README.pgtap index 90aedc06679e..c7cf8d3f6951 100644 --- a/README.pgtap +++ b/README.pgtap @@ -202,14 +202,14 @@ given test succeeded or failed. Everything else is just gravy. All of the following functions return "ok" or "not ok" depending on whether the test succeeded or failed. -### ok( boolean, text ) ### +### ok( boolean, description ) ### ### ok( boolean ) ### - SELECT ok( :this = :that, :test_name ); + SELECT ok( :this = :that, :description ); -This function simply evaluates any expression `:this = :that` is just a -simple example) and uses that to determine if the test succeeded or -failed. A true expression passes, a false one fails. Very simple. +This function simply evaluates any expression (`:this = :that` is just a +simple example) and uses that to determine if the test succeeded or failed. A +true expression passes, a false one fails. Very simple. For example: @@ -221,9 +221,9 @@ For example: (Mnemonic: "This is ok.") -:test_name is a very short description of the test that will be printed +The `description` is a very short description of the test that will be printed out. It makes it very easy to find a test in your script when it fails and -gives others an idea of your intentions. :test_name is optional, but we +gives others an idea of your intentions. The description is optional, but we *very* strongly encourage its use. Should an `ok()` fail, it will produce some diagnostics: @@ -231,15 +231,15 @@ Should an `ok()` fail, it will produce some diagnostics: not ok 18 - sufficient mucus # Failed test 18: "sufficient mucus" -### is( anyelement, anyelement, text ) ### +### is( anyelement, anyelement, description ) ### ### is( anyelement, anyelement ) ### -### isnt( anyelement, anyelement, text ) ### +### isnt( anyelement, anyelement, description ) ### ### isnt( anyelement, anyelement ) ### - SELECT is( :this, :that, $test_name ); - SELECT isnt( :this, :that, $test_name ); + SELECT is( :this, :that, :description ); + SELECT isnt( :this, :that, :description ); -Similar to o`k()`, `is()` and `isnt()` compare their two arguments with `=` +Similar to `ok()`, `is()` and `isnt()` compare their two arguments with `=` and `<>` respectively and use the result of that to determine if the test succeeded or failed. So these: @@ -257,18 +257,18 @@ are similar to these: (Mnemonic: "This is that." "This isn't that.") So why use these? They produce better diagnostics on failure. `ok()` cannot -know what you are testing for (beyond the name), but `is()` and `isnt()` know -what the test was and why it failed. For example this test: +know what you are testing for (beyond the description), but `is()` and +`isnt()` know what the test was and why it failed. For example this test: \set foo '\'' waffle '\'' \set bar '\'' yarblokos '\'' - SELECT is( :foo::text, :bar::text, 'Is foo the same as bar?' ); + SELECT is( :foo::text, :bar::text, 'Is foo the same as bar?' ); Will produce something like this: # Failed test 17: "Is foo the same as bar?" - # got: waffle - # expected: yarblokos + # have: waffle + # want: yarblokos So you can figure out what went wrong without re-running the test. @@ -280,17 +280,17 @@ false! SELECT is( is_valid(9), TRUE, 'A tree grows in Brooklyn' ); This does not check if `is_valid(9)` is true, it checks if it *returns* -TRUE. Very different. Similar caveats exist for FALSE. In these cases, use +`TRUE`. Very different. Similar caveats exist for `FALSE`. In these cases, use `ok()`. - SELECT ok( is_valid(9), 'A tree grows in Brooklyn' ); + SELECT ok( is_valid(9), 'A tree grows in Brooklyn' ); -### matches( anyelement, text, text ) ### -### matches( anyelement, text ) ### +### matches( anyelement, regex, description ) ### +### matches( anyelement, regex ) ### - SELECT matches( :this, '^that', :test_name ); + SELECT matches( :this, '^that', :description ); -Similar to `ok()`, `matches()` matches :this against the regex `/^that/`. +Similar to `ok()`, `matches()` matches `:this` against the regex `/^that/`. So this: @@ -305,32 +305,32 @@ is similar to: Its advantages over `ok()` are similar to that of `is()` and `isnt()`: Better diagnostics on failure. -### imatches( anyelement, text, text ) ### -### imatches( anyelement, text ) ### +### imatches( anyelement, regex, description ) ### +### imatches( anyelement, regex ) ### - SELECT imatches( :this, '^that', :test_name ); + SELECT imatches( :this, '^that', :description ); These are just like `matches()` except that the regular expression is case-insensitive. -### doesnt_match( anyelement, text, text ) ### -### doesnt_match( anyelement, text ) ### -### doesnt_imatch( anyelement, text, text ) ### -### doesnt_imatch( anyelement, text ) ### +### doesnt_match( anyelement, regex, description ) ### +### doesnt_match( anyelement, regex ) ### +### doesnt_imatch( anyelement, regex, description ) ### +### doesnt_imatch( anyelement, regex ) ### - SELECT doesnt_match( :this, '^that', :test_name ); + SELECT doesnt_match( :this, '^that', :description ); These functions work exactly as `matches()` and `imataches()` do, only they -check if :this *does not* match the given pattern. +check if `:this` *does not* match the given pattern. -### alike( anyelement, text, text ) ### -### alike( anyelement, text ) ### -### ialike( anyelement, text, text ) ### -### ialike( anyelement, text ) ### +### alike( anyelement, pattern, description ) ### +### alike( anyelement, pattern ) ### +### ialike( anyelement, pattern, description ) ### +### ialike( anyelement, pattern ) ### - SELECT alike( :this, 'that%', :test_name ); + SELECT alike( :this, 'that%', :description ); -Similar to `ok()`, `alike()` matches `:this` against the SQL LIKE pattern +Similar to `ok()`, `alike()` matches `:this` against the SQL `LIKE` pattern 'that%'. `ialike()` matches case-insensitively. So this: @@ -346,23 +346,23 @@ is similar to: Its advantages over `ok()` are similar to that of `is()` and `isnt()`: Better diagnostics on failure. -### unalike( anyelement, text, text ) ### -### unalike( anyelement, text ) ### -### unialike( anyelement, text, text ) ### -### unialike( anyelement, text ) ### +### unalike( anyelement, pattern, description ) ### +### unalike( anyelement, pattern ) ### +### unialike( anyelement, pattern, description ) ### +### unialike( anyelement, pattern ) ### - SELECT unalike( :this, 'that%', :test_name ); + SELECT unalike( :this, 'that%', :description ); -Works exactly as `alike()`, only it checks if :this *does not* match the given -pattern. +Works exactly as `alike()`, only it checks if `:this` *does not* match the +given pattern. -### pass( text ) ### +### pass( description ) ### ### pass() ### -### fail( text ) ### +### fail( description ) ### ### fail() ### - SELECT pass( :test_name ); - SELECT fail( :test_name ); + SELECT pass( :description ); + SELECT fail( :description ); Sometimes you just want to say that the tests have passed. Usually the case is you've got some complicated condition that is difficult to wedge into an @@ -379,8 +379,8 @@ Or maybe you want to make sure a query *does not* trigger an error. For such cases, we provide a couple of test functions to make sure your queries are as error-prone as you think they should be. -### throws_ok( text, char(5), text ) ### -### throws_ok( text, char(5) ) ### +### throws_ok( text, err_code, description ) ### +### throws_ok( text, err_code ) ### ### throws_ok( text ) ### SELECT throws_ok( @@ -396,26 +396,28 @@ The first argument should be a string representing the query to be executed. `throws_ok()` will use the PL/pgSQL `EXECUTE` statement to execute it and catch any exception. -The second argument should be an exception error code. If this value is not -NULL, `throws_ok()` will check the thrown exception to ensure that it is the -expected exception. For a complete list of error codes, see [Appendix +The second argument should be an exception error code, which is a +five-character string (it happens to consist only of numbers and you pass it +as an integer, it will still work). If this value is not `NULL`, `throws_ok()` +will check the thrown exception to ensure that it is the expected exception. +For a complete list of error codes, see [Appendix A.](http://www.postgresql.org/docs/current/static/errcodes-appendix.html) in the [PostgreSQL documentation](http://www.postgresql.org/docs/current/static/). -The third argument is of course a brief test name. +The third argument is of course a brief test description. Should a `throws_ok()` test fail it produces appropriate diagnostic messages. For example: not ok 81 - simple error - # Failed test "simple error" + # Failed test "This should not die" # caught: 23505: duplicate key value violates unique constraint "try_pkey" - # expected: 23506 + # wanted: 23506 Idea borrowed from the Test::Exception Perl module. -### lives_ok( text, text ) ### +### lives_ok( text, err_code ) ### ### lives_ok( text ) ### SELECT lives_ok( @@ -424,7 +426,7 @@ Idea borrowed from the Test::Exception Perl module. ); The inverse of `throws_ok()`, these functions test to ensure that a SQL -statement does *not* throw an exception. Should a `lives_ok()` test faile, it +statement does *not* throw an exception. Should a `lives_ok()` test fail, it produces appropriate diagnostic messages. For example: not ok 85 - simple success @@ -439,9 +441,9 @@ A Wicked Schema Need to make sure that your database is designed just the way you think it should be? Use these test functions and rest easy. -### has_table( text, text, text ) ### -### has_table( text, text ) ### -### has_table( text ) ### +### has_table( schema, table, description ) ### +### has_table( schema, table ) ### +### has_table( table ) ### SELECT has_table( 'myschema', @@ -455,9 +457,9 @@ test description. If you omit the test description, it will be set to "Table $schema.$table should exist". If you pass just a single argument, it is assumed to be the table name and the schema is assumed to be "public". -### has_view( text, text, text ) ### -### has_view( text, text ) ### -### has_view( text ) ### +### has_view( schema, view, description ) ### +### has_view( schema, view ) ### +### has_view( view ) ### SELECT has_view( 'myschema', @@ -467,9 +469,9 @@ assumed to be the table name and the schema is assumed to be "public". Just like `has_table()`, only it tests for the existence of a view. -### has_column( text, text, text, text ) ### -### has_column( text, text, text ) ### -### has_column( text, text ) ### +### has_column( schema, table, column, description ) ### +### has_column( schema, table, column ) ### +### has_column( table, column ) ### SELECT has_column( 'myschema', @@ -487,9 +489,9 @@ names, and the schema will default to "public". Just like `has_table()`, only it tests for the existence of a column. -### col_not_null ( text, text, text, text ) ### -### col_not_null ( text, text, text ) ### -### col_not_null ( text, text ) ### +### col_not_null( schema, table, column, description ) ### +### col_not_null( schema, table, column ) ### +### col_not_null( table, column ) ### SELECT col_not_null( 'myschema', @@ -506,9 +508,9 @@ If only two arguments are passed, they are assumed to be the table and column names, and the schema will default to "public". Note that this test will fail if the column in question does not exist. -### col_is_null ( text, text, text, text ) ### -### col_is_null ( text, text, text ) ### -### col_is_null ( text, text ) ### +### col_is_null( schema, table, column, description ) ### +### col_is_null( schema, table, column ) ### +### col_is_null( table, column ) ### SELECT col_is_null( 'myschema', @@ -526,16 +528,16 @@ passed, they are assumed to be the table and column names, and the schema will default to "public". Note that this test will fail if the column in question does not exist. -### col_type_is ( text, text, text, text, text ) ### -### col_type_is ( text, text, text, text ) ### -### col_type_is ( text, text, text ) ### +### col_type_is( schema, table, column, type, description ) ### +### col_type_is( schema, table, column, type ) ### +### col_type_is( table, column, type ) ### - SELECT col_is_null( + SELECT col_type_is( 'myschema', 'somecolumn', 'somecolumn', 'text', - 'Column myschema.sometable.somecolumn be type text' + 'Column myschema.sometable.somecolumn should be type text' ); This function tests that the specified column is of a particular type. If it @@ -547,6 +549,16 @@ should be type $type". If only three arguments are passed, they are assumed to be the table, column names, and type, and the schema will default to "public". Note that this test will fail if the column in question does not exist. +If the test fails, it will output useful diagnostics. For example this test: + + SELECT col_type_is( 'pg_catalog', 'pg_type', 'typname', 'text' ); + +Will produce something like this: + + # Failed test 138: "Column pg_catalog.pg_type.typname should be type text + # have: name + # want: text + Diagnostics ----------- @@ -584,10 +596,7 @@ Sometimes you might have tests that you want to pass, but you haven't gotten to declare that such tests are supposed to fail but will work in the future. This is known as a "todo test." -### todo ### - - todo(:why, :howMany); - -- ...normal testing code goes here... +### todo( why, how_many ) ### Declares a series of tests that you expect to fail and why. Perhaps it's because you haven't fixed a bug or haven't finished a new feature: @@ -598,11 +607,11 @@ because you haven't fixed a bug or haven't finished a new feature: SELECT is( URIGeller.yourCard(), :card, 'Is THIS your card?' ); SELECT is( URIGeller.bendSpoon(), 'bent', 'Spoon bending, how original' ); -With `todo()`, `:howMany` specifies how many tests are expected to fail. pgTAP +With `todo()`, `how_any` specifies how many tests are expected to fail. pgTAP will run the tests normally, but print out special flags indicating they are "todo" tests. The test harness will interpret these failures as ok. Should any todo test pass, the harness will report it as an unexpected success. You then -know the thing you had todo is done and can remove the call to `todo()`. +know that the thing you had todo is done and can remove the call to `todo()`. The nice part about todo tests, as opposed to simply commenting out a block of tests, is that they're like a programmatic todo list. You know how much work diff --git a/expected/pgtap.out b/expected/pgtap.out index df64434c9583..65a4aff9b0e4 100644 --- a/expected/pgtap.out +++ b/expected/pgtap.out @@ -1,5 +1,5 @@ \set ECHO -1..135 +1..139 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -135,3 +135,7 @@ ok 132 - testing col_type_is( schema, table, column, type ) ok 133 - col_type_is( schema, table, column, type ) should work ok 134 - testing col_type_is( table, column, type ) ok 135 - col_type_is( table, column, type ) should work +ok 136 - testing col_type_is( table, column, type ) case-insensitively +ok 137 - col_type_is( table, column, type ) should work case-insensitively +ok 138 - testing col_type_is( table, column, type ) failure +ok 139 - col_type_is( table, column, type ) should fail with proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index caafaa03583b..27c08757a9ab 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -426,6 +426,15 @@ CREATE OR REPLACE FUNCTION throws_ok ( TEXT ) RETURNS TEXT AS $$ SELECT throws_ok( $1, NULL, NULL ); $$ LANGUAGE SQL; +-- Magically cast integer error codes. +CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4, TEXT ) RETURNS TEXT AS $$ + SELECT throws_ok( $1, $2::char(5), $3 ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4 ) RETURNS TEXT AS $$ + SELECT throws_ok( $1, $2::char(5), NULL ); +$$ LANGUAGE SQL; + CREATE OR REPLACE FUNCTION lives_ok ( TEXT, TEXT diff --git a/sql/pgtap.sql b/sql/pgtap.sql index 6e1173848b1a..dda4253d2c76 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -208,7 +208,7 @@ SELECT throws_ok( 'SELECT 1 / 0', '22012', 'throws_ok(1/0) should work' ); -- Check its diagnostics for an invalid error code. \echo ok 71 - throws_ok failure diagnostics SELECT is( - throws_ok( 'SELECT 1 / 0', '97212' ), + throws_ok( 'SELECT 1 / 0', 97212 ), E'not ok 71 - threw 97212\n# Failed test 71: "threw 97212"\n# caught: 22012: division by zero\n# wanted: 97212', 'We should get the proper diagnostics from throws_ok()' ); From 20a0f62a8a5dfbb3a138b06761c4ac0a287ef8e4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 18 Aug 2008 23:40:04 +0000 Subject: [PATCH 0057/1195] * Changed `col_type_is()` to use the catalog tables and functions, for nicer formatting, and to take advantage of the formatting's inclusion of character lengths, numeric precisions, etc. * Fixed some pastos in the documentation. * Removed a commented-out function that I had been using for reference. It's replaced with two short comments for other things I want to implement. --- Changes | 2 +- README.pgtap | 16 +++++++++++----- expected/pgtap.out | 6 +++++- pgtap.sql.in | 38 ++++++++++++-------------------------- sql/pgtap.sql | 23 +++++++++++++++++++++-- 5 files changed, 50 insertions(+), 35 deletions(-) diff --git a/Changes b/Changes index 666f443c8a8a..e587165546ff 100644 --- a/Changes +++ b/Changes @@ -32,7 +32,7 @@ Revision history for pgTAP - Added the `has_table()`, `has_view()`, and `has_column()` test functions. - Added the `col_not_null()` and `col_is_null()` test functions. - - Added the `col_type_is()` test function. + - Added the `col_type_is()` test functions. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/README.pgtap b/README.pgtap index c7cf8d3f6951..c6d460520822 100644 --- a/README.pgtap +++ b/README.pgtap @@ -475,7 +475,7 @@ Just like `has_table()`, only it tests for the existence of a view. SELECT has_column( 'myschema', - 'somecolumn', + 'sometable', 'somecolumn', 'I got myschema.sometable.somecolumn' ); @@ -495,7 +495,7 @@ Just like `has_table()`, only it tests for the existence of a column. SELECT col_not_null( 'myschema', - 'somecolumn', + 'sometable', 'somecolumn', 'Column myschema.sometable.somecolumn should be NOT NULL' ); @@ -514,7 +514,7 @@ if the column in question does not exist. SELECT col_is_null( 'myschema', - 'somecolumn', + 'sometable', 'somecolumn', 'Column myschema.sometable.somecolumn should allow NULL' ); @@ -534,9 +534,9 @@ does not exist. SELECT col_type_is( 'myschema', + 'sometable', 'somecolumn', - 'somecolumn', - 'text', + 'numeric(10,2)', 'Column myschema.sometable.somecolumn should be type text' ); @@ -549,6 +549,12 @@ should be type $type". If only three arguments are passed, they are assumed to be the table, column names, and type, and the schema will default to "public". Note that this test will fail if the column in question does not exist. +The type argument should be formatted as it would be displayed in the view of +a table usin the `\d` command in `psql`. For example, if you have a numeric +column with a precision of 8, you should specify "numeric(8,0)". If you +created a `varchar(64)` column, you should pass the type as "character +varying(64)". + If the test fails, it will output useful diagnostics. For example this test: SELECT col_type_is( 'pg_catalog', 'pg_type', 'typname', 'text' ); diff --git a/expected/pgtap.out b/expected/pgtap.out index 65a4aff9b0e4..30b0fde3b8c2 100644 --- a/expected/pgtap.out +++ b/expected/pgtap.out @@ -1,5 +1,5 @@ \set ECHO -1..139 +1..143 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -139,3 +139,7 @@ ok 136 - testing col_type_is( table, column, type ) case-insensitively ok 137 - col_type_is( table, column, type ) should work case-insensitively ok 138 - testing col_type_is( table, column, type ) failure ok 139 - col_type_is( table, column, type ) should fail with proper diagnostics +ok 140 - testing col_type_is( schema, table, column, type(precision,scale), description ) +ok 141 - col_type_is( schema, table, column, type, precision(scale,description) should work +ok 142 - col_type_is( schema, table, column, type, precision ) fail +ok 143 - col_type_is with precision should have nice diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 27c08757a9ab..9d52e43b88dc 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -570,11 +570,16 @@ DECLARE actual_type TEXT; BEGIN -- Get the data type. - SELECT data_type into actual_type - FROM information_schema.columns - WHERE table_schema = $1 - AND table_name = $2 - AND column_name = $3; + SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) into actual_type + FROM pg_attribute a, pg_class c, pg_tables t + WHERE a.attrelid = c.oid + AND pg_table_is_visible(c.oid) + AND c.relname = $2 + AND t.tablename = $2 + AND t.schemaname = $1 + AND attnum > 0 + AND NOT attisdropped + AND a.attname = $3; IF actual_type = LOWER($4) THEN -- We're good to go. @@ -599,24 +604,5 @@ CREATE OR REPLACE FUNCTION col_type_is ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ SELECT col_type_is( 'public', $1, $2, $3, 'Column public.' || $1 || '.' || $2 || ' should be type ' || $3 ); $$ LANGUAGE SQL; -/* - -CREATE OR REPLACE FUNCTION column_is ( TEXT, TEXT, TEXT, TEXT, bool, TEXT, bool, bool, TEXT ) RETURNS TEXT AS $$ - SELECT ok( - EXISTS( - SELECT true - FROM information_schema.columns - WHERE table_schema = $1 - AND table_name = $2 - AND column_name = $3 - AND data_type = LOWER($4) - AND is_nullable = CASE $5 WHEN TRUE THEN 'YES' ELSE 'NO' END, - AND COALESCE(column_default, '[_NULL_]') = COALESCE($6, '[_NULL_]'), - -- PK? - AND EXISTS( select - ), $4 - ); -$$ LANGUAGE SQL; - -*/ - \ No newline at end of file +-- Test PK? +-- Test unique? diff --git a/sql/pgtap.sql b/sql/pgtap.sql index dda4253d2c76..e28e80b9bb85 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -25,7 +25,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 139 +\set numb_tests 143 -- ## SET search_path TO TAPSCHEMA,public; @@ -370,7 +370,7 @@ SELECT is( UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 104, 106, 108 ); -- This will be rolled back. :-) -CREATE TABLE sometab (id int NOT NULL, name text); +CREATE TABLE sometab (id int NOT NULL, name text, numb numeric(10, 2), myint numeric(8)); \echo ok 110 - has_column(table, column) pass SELECT is( @@ -485,6 +485,25 @@ SELECT is( ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 138 ); +/****************************************************************************/ +-- Try col_type_is() with precision. +\echo ok 140 - testing col_type_is( schema, table, column, type(precision,scale), description ) +SELECT is( + col_type_is( 'public', 'sometab', 'numb', 'numeric(10,2)', 'lol' ), + 'ok 140 - lol', + 'col_type_is( schema, table, column, type, precision(scale,description) should work' +); + +-- Check its diagnostics. +\echo ok 142 - col_type_is( schema, table, column, type, precision ) fail +SELECT is( + col_type_is( 'public', 'sometab', 'myint', 'numeric(7)' ), + E'not ok 142 - Column public.sometab.myint should be type numeric(7)\n# Failed test 142: "Column public.sometab.myint should be type numeric(7)"\n# have: numeric(8,0)\n# want: numeric(7)', + 'col_type_is with precision should have nice diagnostics' +); + +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 142, 158 ); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From e4f9991ab112d314480973d381ffadc7d80b9c53 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 19 Aug 2008 00:15:52 +0000 Subject: [PATCH 0058/1195] Converted all schema queries to use the cataglog instead of information_schema. --- pgtap.sql.in | 58 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 9d52e43b88dc..aa5e7c6d2148 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -462,8 +462,11 @@ CREATE OR REPLACE FUNCTION has_table ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ SELECT ok( EXISTS( SELECT true - FROM information_schema.tables - WHERE table_schema = $1 AND table_name = $2 + FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c + WHERE n.oid = c.relnamespace + AND c.relkind = 'r' + AND n.nspname = $1 + AND c.relname = $2 ), $3 ); $$ LANGUAGE SQL; @@ -478,23 +481,26 @@ CREATE OR REPLACE FUNCTION has_table ( TEXT, TEXT ) RETURNS TEXT AS $$ SELECT has_table( $1, $2, 'Table ' || $1 || '.' || $2 || ' should exist' ); $$ LANGUAGE SQL; --- has_view( schema, table, description ) +-- has_view( schema, view, description ) CREATE OR REPLACE FUNCTION has_view ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ SELECT ok( EXISTS( SELECT true - FROM information_schema.views - WHERE table_schema = $1 AND table_name = $2 + FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c + WHERE n.oid = c.relnamespace + AND c.relkind = 'v' + AND n.nspname = $1 + AND c.relname = $2 ), $3 ); $$ LANGUAGE SQL; --- has_view( table ) +-- has_view( view ) CREATE OR REPLACE FUNCTION has_view ( TEXT ) RETURNS TEXT AS $$ SELECT has_view( 'public', $1, 'View public.' || $1 || ' should exist' ); $$ LANGUAGE SQL; --- has_view( schema, table ) +-- has_view( schema, view ) CREATE OR REPLACE FUNCTION has_view ( TEXT, TEXT ) RETURNS TEXT AS $$ SELECT has_view( $1, $2, 'View ' || $1 || '.' || $2 || ' should exist' ); $$ LANGUAGE SQL; @@ -504,8 +510,14 @@ CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS SELECT ok( EXISTS( SELECT true - FROM information_schema.columns - WHERE table_schema = $1 AND table_name = $2 AND column_name = $3 + FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a + WHERE n.oid = c.relnamespace + AND c.oid = a.attrelid + AND n.nspname = $1 + AND c.relname = $2 + AND a.attnum > 0 + AND NOT a.attisdropped + AND a.attname = LOWER($3) ), $4 ); $$ LANGUAGE SQL; @@ -525,43 +537,47 @@ CREATE OR REPLACE FUNCTION _col_is_null ( TEXT, TEXT, TEXT, TEXT, bool ) RETURNS SELECT ok( EXISTS( SELECT true - FROM information_schema.columns - WHERE table_schema = $1 - AND table_name = $2 - AND column_name = $3 - AND is_nullable = CASE $5 WHEN TRUE THEN 'YES' ELSE 'NO' END + FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a + WHERE n.oid = c.relnamespace + AND c.oid = a.attrelid + AND n.nspname = $1 + AND c.relname = $2 + AND a.attnum > 0 + AND NOT a.attisdropped + AND a.attname = LOWER($3) + AND a.attnotnull = $5 ), $4 ); $$ LANGUAGE SQL; -- col_not_null( schema, table, column, desc ) CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, $4, false ); + SELECT _col_is_null( $1, $2, $3, $4, true ); $$ LANGUAGE SQL; -- col_not_null( schema, table, column ) CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, 'Column ' || $1 || '.' || $2 || '.' || $3 || ' should be NOT NULL', false ); + SELECT _col_is_null( $1, $2, $3, 'Column ' || $1 || '.' || $2 || '.' || $3 || ' should be NOT NULL', true ); $$ LANGUAGE SQL; -- col_not_null( table, column ) CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT _col_is_null( 'public', $1, $2, 'Column public.' || $1 || '.' || $2 || ' should be NOT NULL', false ); + SELECT _col_is_null( 'public', $1, $2, 'Column public.' || $1 || '.' || $2 || ' should be NOT NULL', true ); $$ LANGUAGE SQL; -- col_is_null( schema, table, column, desc ) CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, $4, true ); + SELECT _col_is_null( $1, $2, $3, $4, false ); $$ LANGUAGE SQL; -- col_is_null( schema, table, column ) CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, 'Column ' || $1 || '.' || $2 || '.' || $3 || ' should allow NULL', true ); + SELECT _col_is_null( $1, $2, $3, 'Column ' || $1 || '.' || $2 || '.' || $3 || ' should allow NULL', false ); $$ LANGUAGE SQL; -- col_is_null( table, column ) CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT _col_is_null( 'public', $1, $2, 'Column public.' || $1 || '.' || $2 || ' should allow NULL', true ); + SELECT _col_is_null( 'public', $1, $2, 'Column public.' || $1 || '.' || $2 || ' should allow NULL', false ); $$ LANGUAGE SQL; -- col_type_is( schema, table, column, type, desc ) @@ -571,7 +587,7 @@ DECLARE BEGIN -- Get the data type. SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) into actual_type - FROM pg_attribute a, pg_class c, pg_tables t + FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c, pg_catalog.pg_tables t WHERE a.attrelid = c.oid AND pg_table_is_visible(c.oid) AND c.relname = $2 From df115e97de3affa3c334b2ccf89404ba0759b9ba Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 19 Aug 2008 03:38:06 +0000 Subject: [PATCH 0059/1195] * When the schema argument is missing in calls to schema testing functions, they now look for tables and views in the search path rather than assume "public". * Changed the schema testing functions to assume that the schema argument will be dropped rather than the description. I think that this will be a more common usage, and it makes a lot of sense given the above change. * Ran a spell-checker over `README.pgtap` --- README.pgtap | 58 ++++++++++---------- expected/pgtap.out | 34 ++++++------ pgtap.sql.in | 132 +++++++++++++++++++++++++++++++-------------- sql/pgtap.sql | 92 +++++++++++++++---------------- 4 files changed, 183 insertions(+), 133 deletions(-) diff --git a/README.pgtap b/README.pgtap index c6d460520822..9b3c970d64b8 100644 --- a/README.pgtap +++ b/README.pgtap @@ -49,19 +49,19 @@ of the schema you'd like, for example: The test target uses the included pg_prove script to do its testing. It supports a number of environment variables that you might need to use, -including all the usual PostgreSQL client environment varibles: +including all the usual PostgreSQL client environment variables: * PGDATABASE * PGHOST * PGPORT * PGUSER -Once pgTAP habe been built and tested, you can install it into a database: +Once pgTAP has been built and tested, you can install it into a database: psql dbname -f pgtap.sql If you want pgTAP to be available to all new databases, install it into the -template1 dtabase: +template1 database: psql template1 -f pgtap.sql @@ -69,7 +69,7 @@ If you want to remove pgTAP from a database, run the drop_pgtap.sql script: psql dbname -f drop_pgtap.sql -Both scripts will also be installed in the "contrib" directory under the +Both scripts will also be installed in the `contrib` directory under the directory output by `pg_config --sharedir`. So you can always do this, as well: @@ -442,7 +442,7 @@ Need to make sure that your database is designed just the way you think it should be? Use these test functions and rest easy. ### has_table( schema, table, description ) ### -### has_table( schema, table ) ### +### has_table( table, description ) ### ### has_table( table ) ### SELECT has_table( @@ -453,12 +453,12 @@ should be? Use these test functions and rest easy. This function tests whether or not a table exist in the database. The first argument is a schema name, the second is a table name, and the third is the -test description. If you omit the test description, it will be set to "Table -$schema.$table should exist". If you pass just a single argument, it is -assumed to be the table name and the schema is assumed to be "public". +test description. If you omit the schema, the table must be visible in the +search path. If you omit the test description, it will be set to "Table :table +should exist". ### has_view( schema, view, description ) ### -### has_view( schema, view ) ### +### has_view( view, description ) ### ### has_view( view ) ### SELECT has_view( @@ -470,7 +470,7 @@ assumed to be the table name and the schema is assumed to be "public". Just like `has_table()`, only it tests for the existence of a view. ### has_column( schema, table, column, description ) ### -### has_column( schema, table, column ) ### +### has_column( table, column, description ) ### ### has_column( table, column ) ### SELECT has_column( @@ -482,15 +482,14 @@ Just like `has_table()`, only it tests for the existence of a view. Tests whether or not a column exists in a given table or view. The first argument is the schema name, the second the table name, the third the column -name, and the fourth is the test description. If the test description is -omitted, it will be set to "Column $schema.$table.$column should exist". If -only two arguments are passed, they are assumed to be the table and column -names, and the schema will default to "public". +name, and the fourth is the test description. If the schema is omitted, the +table must be visible in the search path. If the test description is omitted, +it will be set to "Column :table.:column should exist". Just like `has_table()`, only it tests for the existence of a column. ### col_not_null( schema, table, column, description ) ### -### col_not_null( schema, table, column ) ### +### col_not_null( table, column, description ) ### ### col_not_null( table, column ) ### SELECT col_not_null( @@ -502,14 +501,13 @@ Just like `has_table()`, only it tests for the existence of a column. Tests whether the specified column has a `NOT NULL` constraint. The first argument is the schema name, the second the table name, the third the column -name, and the fourth is the test description. If the test description is -omitted, it will be set to "Column $schema.$table.$column should be NOT NULL". -If only two arguments are passed, they are assumed to be the table and column -names, and the schema will default to "public". Note that this test will fail -if the column in question does not exist. +name, and the fourth is the test description. If the schema is omitted, the +table must be visible in the search path. If the test description is omitted, +it will be set to "Column :table.:column should be NOT NULL". Note that this +test will fail if the column in question does not exist. ### col_is_null( schema, table, column, description ) ### -### col_is_null( schema, table, column ) ### +### col_is_null( table, column, description ) ### ### col_is_null( table, column ) ### SELECT col_is_null( @@ -522,14 +520,13 @@ if the column in question does not exist. This function is the inverse of `col_not_null()`: the test passes if the column does not have a `NOT NULL` constraint. The first argument is the schema name, the second the table name, the third the column name, and the fourth is -the test description. If the test description is omitted, it will be set to -"Column $schema.$table.$column should allow NULL". If only two arguments are -passed, they are assumed to be the table and column names, and the schema will -default to "public". Note that this test will fail if the column in question -does not exist. +the test description. If the schema is omitted, the table must be visible in +the search path. If the test description is omitted, it will be set to "Column +:table.:column should allow NULL". Note that this test will fail if the column +in question does not exist. ### col_type_is( schema, table, column, type, description ) ### -### col_type_is( schema, table, column, type ) ### +### col_type_is( table, column, type, description ) ### ### col_type_is( table, column, type ) ### SELECT col_type_is( @@ -543,10 +540,9 @@ does not exist. This function tests that the specified column is of a particular type. If it fails, it will emit diagnostics naming the actual type. The first argument is the schema name, the second the table name, the third the column name, the -fourth the type, and the fifth is the test description. If the test -description is omitted, it will be set to "Column $schema.$table.$column -should be type $type". If only three arguments are passed, they are assumed to -be the table, column names, and type, and the schema will default to "public". +fourth the type, and the fifth is the test description. If the schema is +omitted, the table must be visible in the search path. If the test description +is omitted, it will be set to "Column :table.:column should be type $type". Note that this test will fail if the column in question does not exist. The type argument should be formatted as it would be displayed in the view of diff --git a/expected/pgtap.out b/expected/pgtap.out index 30b0fde3b8c2..4b6b39ebc173 100644 --- a/expected/pgtap.out +++ b/expected/pgtap.out @@ -85,28 +85,28 @@ ok 82 - todo pass ok 83 - TODO tests should display properly ok 84 - has_table(table) fail ok 85 - has_table(table) should fail for non-existent table -ok 86 - has_table(schema, table) fail -ok 87 - has_table(schema, table) should fail for non-existent table +ok 86 - has_table(table, desc) fail +ok 87 - has_table(table, dessc) should fail for non-existent table ok 88 - has_table(schema, table, desc) fail ok 89 - has_table(schema, table, desc) should fail for non-existent table -ok 90 - has_table(schema, table) pass -ok 91 - has_table(schema, table) should pass for an existing table +ok 90 - has_table(table, desc) pass +ok 91 - has_table(table, desc) should pass for an existing table ok 92 - has_table(schema, table, desc) pass ok 93 - has_table(schema, table, desc) should pass for an existing table ok 94 - has_view(view) fail ok 95 - has_view(view) should fail for non-existent view -ok 96 - has_view(schema, view) fail -ok 97 - has_view(schema, view) should fail for non-existent view +ok 96 - has_view(view, desc) fail +ok 97 - has_view(view, desc) should fail for non-existent view ok 98 - has_view(schema, view, desc) fail ok 99 - has_view(schema, view, desc) should fail for non-existent view -ok 100 - has_view(schema, view) pass -ok 101 - has_view(schema, view) should pass for an existing view +ok 100 - has_view(view, desc) pass +ok 101 - has_view(view, desc) should pass for an existing view ok 102 - has_view(schema, view, desc) pass ok 103 - has_view(schema, view, desc) should pass for an existing view ok 104 - has_column(table, column) fail ok 105 - has_column(table, column) should fail for non-existent table -ok 106 - has_column(schema, table, column) fail -ok 107 - has_column(schema, table, column) should fail for non-existent table +ok 106 - has_column(table, column, desc) fail +ok 107 - has_column(table, column, desc) should fail for non-existent table ok 108 - has_column(schema, table, column, desc) fail ok 109 - has_column(schema, table, column, desc) should fail for non-existent table ok 110 - has_column(table, column) pass @@ -115,24 +115,24 @@ ok 112 - has_column(schema, column, desc) pass ok 113 - has_column(schema, table, column, desc) should pass for an existing view column ok 114 - testing col_not_null( schema, table, column, desc ) ok 115 - col_not_null( schema, table, column, desc ) should work -ok 116 - testing col_not_null( schema, table, column, desc ) -ok 117 - col_not_null( schema, table, column ) should work +ok 116 - testing col_not_null( table, column, desc ) +ok 117 - col_not_null( table, column, desc ) should work ok 118 - testing col_not_null( schema, table, column, desc ) ok 119 - col_not_null( table, column ) should work ok 120 - testing col_not_null( schema, table, column, desc ) ok 121 - col_not_null( table, column ) should properly fail ok 122 - testing col_is_null( schema, table, column, desc ) ok 123 - col_is_null( schema, table, column, desc ) should work -ok 124 - testing col_is_null( schema, table, column, desc ) -ok 125 - col_is_null( schema, table, column ) should work +ok 124 - testing col_is_null( table, column, desc ) +ok 125 - col_is_null( table, column, desc ) should work ok 126 - testing col_is_null( schema, table, column, desc ) ok 127 - col_is_null( table, column ) should work ok 128 - testing col_is_null( schema, table, column, desc ) ok 129 - col_is_null( table, column ) should properly fail ok 130 - testing col_type_is( schema, table, column, type, desc ) ok 131 - col_type_is( schema, table, column, type, desc ) should work -ok 132 - testing col_type_is( schema, table, column, type ) -ok 133 - col_type_is( schema, table, column, type ) should work +ok 132 - testing col_type_is( table, column, type, desc ) +ok 133 - col_type_is( table, column, type, desc ) should work ok 134 - testing col_type_is( table, column, type ) ok 135 - col_type_is( table, column, type ) should work ok 136 - testing col_type_is( table, column, type ) case-insensitively @@ -141,5 +141,5 @@ ok 138 - testing col_type_is( table, column, type ) failure ok 139 - col_type_is( table, column, type ) should fail with proper diagnostics ok 140 - testing col_type_is( schema, table, column, type(precision,scale), description ) ok 141 - col_type_is( schema, table, column, type, precision(scale,description) should work -ok 142 - col_type_is( schema, table, column, type, precision ) fail +ok 142 - col_type_is( table, column, type, precision, desc ) fail ok 143 - col_type_is with precision should have nice diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index aa5e7c6d2148..bbfc40012c9a 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -471,14 +471,22 @@ CREATE OR REPLACE FUNCTION has_table ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; --- has_table( table ) -CREATE OR REPLACE FUNCTION has_table ( TEXT ) RETURNS TEXT AS $$ - SELECT has_table( 'public', $1, 'Table public.' || $1 || ' should exist' ); +-- has_table( table, description ) +CREATE OR REPLACE FUNCTION has_table ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT ok( + 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 + ), $2 + ); $$ LANGUAGE SQL; --- has_table( schema, table ) -CREATE OR REPLACE FUNCTION has_table ( TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT has_table( $1, $2, 'Table ' || $1 || '.' || $2 || ' should exist' ); +-- has_table( table ) +CREATE OR REPLACE FUNCTION has_table ( TEXT ) RETURNS TEXT AS $$ + SELECT has_table( $1, 'Table ' || $1 || ' should exist' ); $$ LANGUAGE SQL; -- has_view( schema, view, description ) @@ -495,14 +503,22 @@ CREATE OR REPLACE FUNCTION has_view ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; --- has_view( view ) -CREATE OR REPLACE FUNCTION has_view ( TEXT ) RETURNS TEXT AS $$ - SELECT has_view( 'public', $1, 'View public.' || $1 || ' should exist' ); +-- has_view( view, description ) +CREATE OR REPLACE FUNCTION has_view ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_class c + WHERE c.relkind = 'v' + AND pg_catalog.pg_table_is_visible(c.oid) + AND c.relname = $1 + ), $2 + ); $$ LANGUAGE SQL; --- has_view( schema, view ) -CREATE OR REPLACE FUNCTION has_view ( TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT has_view( $1, $2, 'View ' || $1 || '.' || $2 || ' should exist' ); +-- has_view( view ) +CREATE OR REPLACE FUNCTION has_view ( TEXT ) RETURNS TEXT AS $$ + SELECT has_view( $1, 'View ' || $1 || ' should exist' ); $$ LANGUAGE SQL; -- has_column( schema, table, column, description ) @@ -522,14 +538,25 @@ CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS ); $$ LANGUAGE SQL; --- has_column( table, column ) -CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT has_column( 'public', $1, $2, 'Column ' || 'public.' || $1 || '.' || $2 || ' should exist' ); +-- has_column( table, column, description ) +CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a + WHERE c.oid = a.attrelid + AND pg_catalog.pg_table_is_visible(c.oid) + AND c.relname = $1 + AND a.attnum > 0 + AND NOT a.attisdropped + AND a.attname = LOWER($2) + ), $3 + ); $$ LANGUAGE SQL; --- has_column( schema, table, column ) -CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT has_column( $1, $2, $3, 'Column ' || $1 || '.' || $2 || '.' || $3 || ' should exist' ); +-- has_column( table, column ) +CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT has_column( $1, $2, 'Column ' || $1 || '.' || $2 || ' should exist' ); $$ LANGUAGE SQL; -- _col_is_null( schema, table, column, desc, null ) @@ -550,52 +577,79 @@ CREATE OR REPLACE FUNCTION _col_is_null ( TEXT, TEXT, TEXT, TEXT, bool ) RETURNS ); $$ LANGUAGE SQL; --- col_not_null( schema, table, column, desc ) +-- _col_is_null( table, column, desc, null ) +CREATE OR REPLACE FUNCTION _col_is_null ( TEXT, TEXT, TEXT, bool ) RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a + WHERE c.oid = a.attrelid + AND pg_catalog.pg_table_is_visible(c.oid) + AND c.relname = $1 + AND a.attnum > 0 + AND NOT a.attisdropped + AND a.attname = LOWER($2) + AND a.attnotnull = $4 + ), $3 + ); +$$ LANGUAGE SQL; + +-- col_not_null( schema, table, column, description ) CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ SELECT _col_is_null( $1, $2, $3, $4, true ); $$ LANGUAGE SQL; --- col_not_null( schema, table, column ) +-- col_not_null( table, column, description ) CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, 'Column ' || $1 || '.' || $2 || '.' || $3 || ' should be NOT NULL', true ); + SELECT _col_is_null( $1, $2, $3, true ); $$ LANGUAGE SQL; -- col_not_null( table, column ) CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT _col_is_null( 'public', $1, $2, 'Column public.' || $1 || '.' || $2 || ' should be NOT NULL', true ); + SELECT _col_is_null( $1, $2, 'Column ' || $1 || '.' || $2 || ' should be NOT NULL', true ); $$ LANGUAGE SQL; --- col_is_null( schema, table, column, desc ) +-- col_is_null( schema, table, column, description ) CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ SELECT _col_is_null( $1, $2, $3, $4, false ); $$ LANGUAGE SQL; -- col_is_null( schema, table, column ) CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, 'Column ' || $1 || '.' || $2 || '.' || $3 || ' should allow NULL', false ); + SELECT _col_is_null( $1, $2, $3, false ); $$ LANGUAGE SQL; -- col_is_null( table, column ) CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT _col_is_null( 'public', $1, $2, 'Column public.' || $1 || '.' || $2 || ' should allow NULL', false ); + SELECT _col_is_null( $1, $2, 'Column ' || $1 || '.' || $2 || ' should allow NULL', false ); $$ LANGUAGE SQL; --- col_type_is( schema, table, column, type, desc ) +-- col_type_is( schema, table, column, type, description ) CREATE OR REPLACE FUNCTION col_type_is ( TEXT, TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE actual_type TEXT; BEGIN -- Get the data type. - SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) into actual_type - FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c, pg_catalog.pg_tables t - WHERE a.attrelid = c.oid - AND pg_table_is_visible(c.oid) - AND c.relname = $2 - AND t.tablename = $2 - AND t.schemaname = $1 - AND attnum > 0 - AND NOT attisdropped - AND a.attname = $3; + IF $1 IS NULL THEN + SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) into actual_type + FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c + WHERE a.attrelid = c.oid + AND pg_table_is_visible(c.oid) + AND c.relname = $2 + AND attnum > 0 + AND NOT attisdropped + AND a.attname = $3; + ELSE + SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) into actual_type + FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a + WHERE n.oid = c.relnamespace + AND c.oid = a.attrelid + AND n.nspname = $1 + AND c.relname = $2 + AND attnum > 0 + AND NOT attisdropped + AND a.attname = $3; + END IF; IF actual_type = LOWER($4) THEN -- We're good to go. @@ -610,14 +664,14 @@ BEGIN END; $$ LANGUAGE plpgsql; --- col_type_is( schema, table, column, type ) +-- col_type_is( table, column, type, description ) CREATE OR REPLACE FUNCTION col_type_is ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT col_type_is( $1, $2, $3, $4, 'Column ' || $1 || '.' || $2 || '.' || $3 || ' should be type ' || $4 ); + SELECT col_type_is( NULL, $1, $2, $3, $4 ); $$ LANGUAGE SQL; -- col_type_is( table, column, type ) CREATE OR REPLACE FUNCTION col_type_is ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT col_type_is( 'public', $1, $2, $3, 'Column public.' || $1 || '.' || $2 || ' should be type ' || $3 ); + SELECT col_type_is( $1, $2, $3, 'Column ' || $1 || '.' || $2 || ' should be type ' || $3 ); $$ LANGUAGE SQL; -- Test PK? diff --git a/sql/pgtap.sql b/sql/pgtap.sql index e28e80b9bb85..45fe0901be7f 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -271,15 +271,15 @@ UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 81 ); SELECT is( has_table( '__SDFSDFD__' ), - E'not ok 84 - Table public.__SDFSDFD__ should exist\n# Failed test 84: "Table public.__SDFSDFD__ should exist"', + E'not ok 84 - Table __SDFSDFD__ should exist\n# Failed test 84: "Table __SDFSDFD__ should exist"', 'has_table(table) should fail for non-existent table' ); -\echo ok 86 - has_table(schema, table) fail +\echo ok 86 - has_table(table, desc) fail SELECT is( - has_table( 'foo', '__SDFSDFD__' ), - E'not ok 86 - Table foo.__SDFSDFD__ should exist\n# Failed test 86: "Table foo.__SDFSDFD__ should exist"', - 'has_table(schema, table) should fail for non-existent table' + has_table( '__SDFSDFD__', 'lol' ), + E'not ok 86 - lol\n# Failed test 86: "lol"', + 'has_table(table, dessc) should fail for non-existent table' ); \echo ok 88 - has_table(schema, table, desc) fail @@ -290,11 +290,11 @@ SELECT is( ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 84, 86, 88 ); -\echo ok 90 - has_table(schema, table) pass +\echo ok 90 - has_table(table, desc) pass SELECT is( - has_table( 'pg_catalog', 'pg_type' ), - 'ok 90 - Table pg_catalog.pg_type should exist', - 'has_table(schema, table) should pass for an existing table' + has_table( 'pg_type', 'lol' ), + 'ok 90 - lol', + 'has_table(table, desc) should pass for an existing table' ); \echo ok 92 - has_table(schema, table, desc) pass @@ -311,15 +311,15 @@ SELECT is( SELECT is( has_view( '__SDFSDFD__' ), - E'not ok 94 - View public.__SDFSDFD__ should exist\n# Failed test 94: "View public.__SDFSDFD__ should exist"', + E'not ok 94 - View __SDFSDFD__ should exist\n# Failed test 94: "View __SDFSDFD__ should exist"', 'has_view(view) should fail for non-existent view' ); -\echo ok 96 - has_view(schema, view) fail +\echo ok 96 - has_view(view, desc) fail SELECT is( - has_view( 'foo', '__SDFSDFD__' ), - E'not ok 96 - View foo.__SDFSDFD__ should exist\n# Failed test 96: "View foo.__SDFSDFD__ should exist"', - 'has_view(schema, view) should fail for non-existent view' + has_view( '__SDFSDFD__', 'howdy' ), + E'not ok 96 - howdy\n# Failed test 96: "howdy"', + 'has_view(view, desc) should fail for non-existent view' ); \echo ok 98 - has_view(schema, view, desc) fail @@ -330,11 +330,11 @@ SELECT is( ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 94, 96, 98 ); -\echo ok 100 - has_view(schema, view) pass +\echo ok 100 - has_view(view, desc) pass SELECT is( - has_view( 'information_schema', 'tables' ), - 'ok 100 - View information_schema.tables should exist', - 'has_view(schema, view) should pass for an existing view' + has_view( 'pg_tables', 'yowza' ), + 'ok 100 - yowza', + 'has_view(view, desc) should pass for an existing view' ); \echo ok 102 - has_view(schema, view, desc) pass @@ -350,15 +350,15 @@ SELECT is( \echo ok 104 - has_column(table, column) fail SELECT is( has_column( '__SDFSDFD__', 'foo' ), - E'not ok 104 - Column public.__SDFSDFD__.foo should exist\n# Failed test 104: "Column public.__SDFSDFD__.foo should exist"', + E'not ok 104 - Column __SDFSDFD__.foo should exist\n# Failed test 104: "Column __SDFSDFD__.foo should exist"', 'has_column(table, column) should fail for non-existent table' ); -\echo ok 106 - has_column(schema, table, column) fail +\echo ok 106 - has_column(table, column, desc) fail SELECT is( - has_column( 'foo', '__SDFSDFD__', 'bar' ), - E'not ok 106 - Column foo.__SDFSDFD__.bar should exist\n# Failed test 106: "Column foo.__SDFSDFD__.bar should exist"', - 'has_column(schema, table, column) should fail for non-existent table' + has_column( '__SDFSDFD__', 'bar', 'whatever' ), + E'not ok 106 - whatever\n# Failed test 106: "whatever"', + 'has_column(table, column, desc) should fail for non-existent table' ); \echo ok 108 - has_column(schema, table, column, desc) fail @@ -375,7 +375,7 @@ CREATE TABLE sometab (id int NOT NULL, name text, numb numeric(10, 2), myint num \echo ok 110 - has_column(table, column) pass SELECT is( has_column( 'sometab', 'id' ), - 'ok 110 - Column public.sometab.id should exist', + 'ok 110 - Column sometab.id should exist', 'has_column(table, column) should pass for an existing column' ); @@ -394,24 +394,24 @@ SELECT is( 'ok 114 - typname not null', 'col_not_null( schema, table, column, desc ) should work' ); -\echo ok 116 - testing col_not_null( schema, table, column, desc ) +\echo ok 116 - testing col_not_null( table, column, desc ) SELECT is( - col_not_null( 'pg_catalog', 'pg_type', 'typname' ), - 'ok 116 - Column pg_catalog.pg_type.typname should be NOT NULL', - 'col_not_null( schema, table, column ) should work' + col_not_null( 'sometab', 'id', 'blah blah blah' ), + 'ok 116 - blah blah blah', + 'col_not_null( table, column, desc ) should work' ); \echo ok 118 - testing col_not_null( schema, table, column, desc ) SELECT is( col_not_null( 'sometab', 'id' ), - 'ok 118 - Column public.sometab.id should be NOT NULL', + 'ok 118 - Column sometab.id should be NOT NULL', 'col_not_null( table, column ) should work' ); -- Make sure failure is correct. \echo ok 120 - testing col_not_null( schema, table, column, desc ) SELECT is( col_not_null( 'sometab', 'name' ), - E'not ok 120 - Column public.sometab.name should be NOT NULL\n# Failed test 120: "Column public.sometab.name should be NOT NULL"', + E'not ok 120 - Column sometab.name should be NOT NULL\n# Failed test 120: "Column sometab.name should be NOT NULL"', 'col_not_null( table, column ) should properly fail' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 120 ); @@ -424,24 +424,24 @@ SELECT is( 'ok 122 - name is null', 'col_is_null( schema, table, column, desc ) should work' ); -\echo ok 124 - testing col_is_null( schema, table, column, desc ) +\echo ok 124 - testing col_is_null( table, column, desc ) SELECT is( - col_is_null( 'public', 'sometab', 'name' ), - 'ok 124 - Column public.sometab.name should allow NULL', - 'col_is_null( schema, table, column ) should work' + col_is_null( 'sometab', 'name', 'my desc' ), + 'ok 124 - my desc', + 'col_is_null( table, column, desc ) should work' ); \echo ok 126 - testing col_is_null( schema, table, column, desc ) SELECT is( col_is_null( 'sometab', 'name' ), - 'ok 126 - Column public.sometab.name should allow NULL', + 'ok 126 - Column sometab.name should allow NULL', 'col_is_null( table, column ) should work' ); -- Make sure failure is correct. \echo ok 128 - testing col_is_null( schema, table, column, desc ) SELECT is( col_is_null( 'sometab', 'id' ), - E'not ok 128 - Column public.sometab.id should allow NULL\n# Failed test 128: "Column public.sometab.id should allow NULL"', + E'not ok 128 - Column sometab.id should allow NULL\n# Failed test 128: "Column sometab.id should allow NULL"', 'col_is_null( table, column ) should properly fail' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 128 ); @@ -455,24 +455,24 @@ SELECT is( 'col_type_is( schema, table, column, type, desc ) should work' ); -\echo ok 132 - testing col_type_is( schema, table, column, type ) +\echo ok 132 - testing col_type_is( table, column, type, desc ) SELECT is( - col_type_is( 'public', 'sometab', 'name', 'text' ), - 'ok 132 - Column public.sometab.name should be type text', - 'col_type_is( schema, table, column, type ) should work' + col_type_is( 'sometab', 'name', 'text', 'yadda yadda yadda' ), + 'ok 132 - yadda yadda yadda', + 'col_type_is( table, column, type, desc ) should work' ); \echo ok 134 - testing col_type_is( table, column, type ) SELECT is( col_type_is( 'sometab', 'name', 'text' ), - 'ok 134 - Column public.sometab.name should be type text', + 'ok 134 - Column sometab.name should be type text', 'col_type_is( table, column, type ) should work' ); \echo ok 136 - testing col_type_is( table, column, type ) case-insensitively SELECT is( col_type_is( 'sometab', 'name', 'TEXT' ), - 'ok 136 - Column public.sometab.name should be type TEXT', + 'ok 136 - Column sometab.name should be type TEXT', 'col_type_is( table, column, type ) should work case-insensitively' ); @@ -480,7 +480,7 @@ SELECT is( \echo ok 138 - testing col_type_is( table, column, type ) failure SELECT is( col_type_is( 'sometab', 'name', 'int4' ), - E'not ok 138 - Column public.sometab.name should be type int4\n# Failed test 138: "Column public.sometab.name should be type int4"\n# have: text\n# want: int4', + E'not ok 138 - Column sometab.name should be type int4\n# Failed test 138: "Column sometab.name should be type int4"\n# have: text\n# want: int4', 'col_type_is( table, column, type ) should fail with proper diagnostics' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 138 ); @@ -495,10 +495,10 @@ SELECT is( ); -- Check its diagnostics. -\echo ok 142 - col_type_is( schema, table, column, type, precision ) fail +\echo ok 142 - col_type_is( table, column, type, precision, desc ) fail SELECT is( - col_type_is( 'public', 'sometab', 'myint', 'numeric(7)' ), - E'not ok 142 - Column public.sometab.myint should be type numeric(7)\n# Failed test 142: "Column public.sometab.myint should be type numeric(7)"\n# have: numeric(8,0)\n# want: numeric(7)', + col_type_is( 'sometab', 'myint', 'numeric(7)', 'should be numeric(7)' ), + E'not ok 142 - should be numeric(7)\n# Failed test 142: "should be numeric(7)"\n# have: numeric(8,0)\n# want: numeric(7)', 'col_type_is with precision should have nice diagnostics' ); From f28c4c7c6466d5523a3872d07e682122aed228ea Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 19 Aug 2008 03:38:22 +0000 Subject: [PATCH 0060/1195] Rebuilt the uninstall script. --- drop_pgtap.sql.in | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/drop_pgtap.sql.in b/drop_pgtap.sql.in index 5ab8a196585b..2aadba322504 100644 --- a/drop_pgtap.sql.in +++ b/drop_pgtap.sql.in @@ -8,20 +8,26 @@ DROP FUNCTION col_is_null ( TEXT, TEXT, TEXT, TEXT ); DROP FUNCTION col_not_null ( TEXT, TEXT ); DROP FUNCTION col_not_null ( TEXT, TEXT, TEXT ); DROP FUNCTION col_not_null ( TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION _col_is_null ( TEXT, TEXT, TEXT, bool ); DROP FUNCTION _col_is_null ( TEXT, TEXT, TEXT, TEXT, bool ); -DROP FUNCTION has_column ( TEXT, TEXT, TEXT ); DROP FUNCTION has_column ( TEXT, TEXT ); +DROP FUNCTION has_column ( TEXT, TEXT, TEXT ); DROP FUNCTION has_column ( TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION has_view ( TEXT, TEXT ); DROP FUNCTION has_view ( TEXT ); +DROP FUNCTION has_view ( TEXT, TEXT ); DROP FUNCTION has_view ( TEXT, TEXT, TEXT ); -DROP FUNCTION has_table ( TEXT, TEXT ); DROP FUNCTION has_table ( TEXT ); +DROP FUNCTION has_table ( TEXT, TEXT ); DROP FUNCTION has_table ( TEXT, TEXT, TEXT ); DROP FUNCTION lives_ok ( TEXT ); -DROP FUNCTION lives_ok ( TEXT, TEXT ); +DROP FUNCTION lives_ok ( +DROP FUNCTION throws_ok ( TEXT, int4 ); +DROP FUNCTION throws_ok ( TEXT, int4, TEXT ); +DROP FUNCTION throws_ok ( TEXT ); DROP FUNCTION throws_ok ( TEXT, CHAR(5) ); -DROP FUNCTION throws_ok ( TEXT, CHAR(5), TEXT ); +DROP FUNCTION throws_ok ( +DROP FUNCTION _todo(); +DROP FUNCTION todo ( why text, how_many int ) DROP FUNCTION fail ( ); DROP FUNCTION fail ( text ); DROP FUNCTION pass ( ); @@ -34,7 +40,7 @@ DROP FUNCTION doesnt_imatch ( anyelement, text ); DROP FUNCTION doesnt_imatch ( anyelement, text, text ); DROP FUNCTION doesnt_match ( anyelement, text ); DROP FUNCTION doesnt_match ( anyelement, text, text ); -DROP FUNCTION _unalike ( boolean, anyelement, text, text ); +DROP FUNCTION _unalike ( DROP FUNCTION ialike ( anyelement, text ); DROP FUNCTION ialike ( anyelement, text, text ); DROP FUNCTION alike ( anyelement, text ); @@ -43,19 +49,21 @@ DROP FUNCTION imatches ( anyelement, text ); DROP FUNCTION imatches ( anyelement, text, text ); DROP FUNCTION matches ( anyelement, text ); DROP FUNCTION matches ( anyelement, text, text ); -DROP FUNCTION _alike ( boolean, anyelement, text, text ); -DROP FUNCTION isnt (anyelement, anyelement ); +DROP FUNCTION _alike ( +DROP FUNCTION isnt (anyelement, anyelement); DROP FUNCTION isnt (anyelement, anyelement, text); -DROP FUNCTION is (anyelement, anyelement ); +DROP FUNCTION is (anyelement, anyelement); DROP FUNCTION is (anyelement, anyelement, text); -DROP FUNCTION ok ( boolean ); +DROP FUNCTION ok ( boolean ); DROP FUNCTION ok ( boolean, text ); -DROP FUNCTION diag ( text ); -DROP FUNCTION finish ( ); -DROP FUNCTION plan ( integer ); -DROP FUNCTION no_plan ( ); +DROP FUNCTION diag ( msg text ); +DROP FUNCTION finish (); DROP FUNCTION num_failed (); -DROP FUNCTION add_result ( boolean, bool, text, text, text ); +DROP FUNCTION add_result ( bool, bool, text, text, text ) DROP FUNCTION _set ( text, integer ); +DROP FUNCTION _set ( text, integer, text ); +DROP FUNCTION _get_note ( text ); DROP FUNCTION _get ( text ); +DROP FUNCTION no_plan( ); +DROP FUNCTION plan( integer ); -- ## DROP SCHEMA TAPSCHEMA; From 893eaca68ccb8044a1cbc372b94bfb68dbe35c63 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 19 Aug 2008 03:43:55 +0000 Subject: [PATCH 0061/1195] Fixed bad signatures in the drop file by putting them all on one line in the pgtap.sql.in file. --- drop_pgtap.sql.in | 10 +++++----- pgtap.sql.in | 17 +++++------------ 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/drop_pgtap.sql.in b/drop_pgtap.sql.in index 2aadba322504..44eeb253bc72 100644 --- a/drop_pgtap.sql.in +++ b/drop_pgtap.sql.in @@ -20,17 +20,17 @@ DROP FUNCTION has_table ( TEXT ); DROP FUNCTION has_table ( TEXT, TEXT ); DROP FUNCTION has_table ( TEXT, TEXT, TEXT ); DROP FUNCTION lives_ok ( TEXT ); -DROP FUNCTION lives_ok ( +DROP FUNCTION lives_ok ( TEXT, TEXT ); DROP FUNCTION throws_ok ( TEXT, int4 ); DROP FUNCTION throws_ok ( TEXT, int4, TEXT ); DROP FUNCTION throws_ok ( TEXT ); DROP FUNCTION throws_ok ( TEXT, CHAR(5) ); -DROP FUNCTION throws_ok ( +DROP FUNCTION throws_ok ( TEXT, CHAR(5), TEXT ); DROP FUNCTION _todo(); DROP FUNCTION todo ( why text, how_many int ) -DROP FUNCTION fail ( ); +DROP FUNCTION fail (); DROP FUNCTION fail ( text ); -DROP FUNCTION pass ( ); +DROP FUNCTION pass (); DROP FUNCTION pass ( text ); DROP FUNCTION unialike ( anyelement, text ); DROP FUNCTION unialike ( anyelement, text, text ); @@ -64,6 +64,6 @@ DROP FUNCTION _set ( text, integer ); DROP FUNCTION _set ( text, integer, text ); DROP FUNCTION _get_note ( text ); DROP FUNCTION _get ( text ); -DROP FUNCTION no_plan( ); +DROP FUNCTION no_plan(); DROP FUNCTION plan( integer ); -- ## DROP SCHEMA TAPSCHEMA; diff --git a/pgtap.sql.in b/pgtap.sql.in index bbfc40012c9a..a76ed7793a91 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -52,7 +52,7 @@ BEGIN END; $$ LANGUAGE plpgsql strict; -CREATE OR REPLACE FUNCTION no_plan( ) RETURNS SETOF boolean AS $$ +CREATE OR REPLACE FUNCTION no_plan() RETURNS SETOF boolean AS $$ BEGIN PERFORM plan(0); RETURN; @@ -353,7 +353,7 @@ CREATE OR REPLACE FUNCTION pass ( text ) RETURNS TEXT AS $$ SELECT ok( TRUE, $1 ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION pass ( ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION pass () RETURNS TEXT AS $$ SELECT ok( TRUE, NULL ); $$ LANGUAGE SQL; @@ -361,7 +361,7 @@ CREATE OR REPLACE FUNCTION fail ( text ) RETURNS TEXT AS $$ SELECT ok( FALSE, $1 ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION fail ( ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION fail () RETURNS TEXT AS $$ SELECT ok( FALSE, NULL ); $$ LANGUAGE SQL; @@ -387,11 +387,7 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION throws_ok ( - TEXT, - CHAR(5), - TEXT -) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION throws_ok ( TEXT, CHAR(5), TEXT ) RETURNS TEXT AS $$ DECLARE code ALIAS FOR $1; err ALIAS FOR $2; @@ -435,10 +431,7 @@ CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4 ) RETURNS TEXT AS $$ SELECT throws_ok( $1, $2::char(5), NULL ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION lives_ok ( - TEXT, - TEXT -) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION lives_ok ( TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE code ALIAS FOR $1; descr ALIAS FOR $2; From 100c6966030761bc87b172935383529c767b9697 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 19 Aug 2008 20:17:17 +0000 Subject: [PATCH 0062/1195] * Added `col_has_default()`. * Fixed the handling of `NULL`s by `is()` and `isnt()`. --- Changes | 3 +- README.pgtap | 51 ++++++++++++++++++++++++++++++++ expected/pgtap.out | 10 ++++++- pgtap.sql.in | 72 ++++++++++++++++++++++++++++++++++++++++++---- sql/pgtap.sql | 41 ++++++++++++++++++++++++-- 5 files changed, 168 insertions(+), 9 deletions(-) diff --git a/Changes b/Changes index e587165546ff..a2de190bf0dd 100644 --- a/Changes +++ b/Changes @@ -32,7 +32,8 @@ Revision history for pgTAP - Added the `has_table()`, `has_view()`, and `has_column()` test functions. - Added the `col_not_null()` and `col_is_null()` test functions. - - Added the `col_type_is()` test functions. + - Added the `col_type_is()` and `col_default_is()` test functions. + - Fixed `is()` and `isnt()` to better handle NULLs. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/README.pgtap b/README.pgtap index 9b3c970d64b8..de2777c0a49c 100644 --- a/README.pgtap +++ b/README.pgtap @@ -256,6 +256,10 @@ are similar to these: (Mnemonic: "This is that." "This isn't that.") +`NULL`s are not treated as unknowns by `is()` or `isnt()`. That is, if `:this` +and `:that` are both `NULL`, the test will pass, and if only one of them is +`NULL`, the test will fail. + So why use these? They produce better diagnostics on failure. `ok()` cannot know what you are testing for (beyond the description), but `is()` and `isnt()` know what the test was and why it failed. For example this test: @@ -561,6 +565,53 @@ Will produce something like this: # have: name # want: text +### col_default_is( schema, table, column, default, description ) ### +### col_default_is( table, column, default, description ) ### +### col_default_is( table, column, type ) ### + + SELECT col_default_is( + 'myschema', + 'sometable', + 'somecolumn', + 'howdy'::text, + 'Column myschema.sometable.somecolumn should default to ''howdy''' + ); + +Tests the default value of a column. If it fails, it will emit diagnostics +showing the actual default value. The first argument is the schema name, the +second the table name, the third the column name, the fourth the default +value, and the fifth is the test description. If the schema is omitted, the +table must be visible in the search path. If the test description is omitted, +it will be set to "Column :table.:column should default to :default". Note +that this test will fail if the column in question does not exist. + +The default argument must have an unambiguous type in order for the call to +succeed. If you see an error such as 'ERROR: could not determine polymorphic +type because input has type "unknown"', it's because you forgot to cast the +expected value to its proper type. IOW, this will fail: + + SELECT col_is_default( 'tab', 'name', 'foo' ); + +But this will not: + + SELECT col_is_default( 'tab', 'name', 'foo'::text ); + +If the test fails, it will output useful diagnostics. For example, this test: + + SELECT col_has_default( + 'pg_catalog', + 'pg_type', + 'typname', + 'foo'::text, + 'check typname' + ); + +Will produce something like this: + + # Failed test 152: "check typname" + # have: NULL + # want: foo + Diagnostics ----------- diff --git a/expected/pgtap.out b/expected/pgtap.out index 4b6b39ebc173..e9b35ebc0e25 100644 --- a/expected/pgtap.out +++ b/expected/pgtap.out @@ -1,5 +1,5 @@ \set ECHO -1..143 +1..151 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -143,3 +143,11 @@ ok 140 - testing col_type_is( schema, table, column, type(precision,scale), desc ok 141 - col_type_is( schema, table, column, type, precision(scale,description) should work ok 142 - col_type_is( table, column, type, precision, desc ) fail ok 143 - col_type_is with precision should have nice diagnostics +ok 144 - col_has_default( schema, table, column, default, description ) +ok 145 - col_has_default( schema, table, column, default, description ) should work +ok 146 - col_has_default( schema, table, column, default, description ) fail +ok 147 - ok 146 - Should get proper diagnostics for a default failure +ok 148 - col_has_default( table, column, default, description ) +ok 149 - col_has_default( table, column, default, description ) should work +ok 150 - col_has_default( table, column, default ) +ok 151 - col_has_default( table, column, default ) should work diff --git a/pgtap.sql.in b/pgtap.sql.in index a76ed7793a91..ff089093dcbf 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -100,7 +100,7 @@ CREATE OR REPLACE FUNCTION add_result ( bool, bool, text, text, text ) RETURNS integer AS $$ BEGIN EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) - VALUES( ' || $1 || ', ' || $2 || ', ' || COALESCE(quote_literal($3), '''''') || ', ' + VALUES( ' || $1 || ', ' || $2 || ', ' || quote_literal(COALESCE($3, '')) || ', ' || quote_literal($4) || ', ' || quote_literal($5) || ' )'; RETURN currval('__tresults___numb_seq'); END; @@ -211,7 +211,9 @@ DECLARE result BOOLEAN; output TEXT; BEGIN - result := $1 = $2; + result := CASE WHEN $2 IS NULL AND $1 IS NULL THEN TRUE + WHEN $2 IS NULL OR $1 IS NULL THEN FALSE + ELSE $1 = $2 END; output := ok( result, $3 ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( ' have: ' || COALESCE( $1::text, 'NULL' ) || @@ -229,7 +231,9 @@ DECLARE result BOOLEAN; output TEXT; BEGIN - result := $1 <> $2; + result := CASE WHEN $2 IS NULL AND $1 IS NULL THEN FALSE + WHEN $2 IS NULL OR $1 IS NULL THEN TRUE + ELSE $1 <> $2 END; output := ok( result, $3 ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( ' ' || COALESCE( $1::text, 'NULL' ) || @@ -667,5 +671,63 @@ CREATE OR REPLACE FUNCTION col_type_is ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ SELECT col_type_is( $1, $2, $3, 'Column ' || $1 || '.' || $2 || ' should be type ' || $3 ); $$ LANGUAGE SQL; --- Test PK? --- Test unique? +CREATE OR REPLACE FUNCTION _def_is( TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ +DECLARE + thing text; +BEGIN + IF $1 ~ '^[^'']+[(]' THEN + -- It's a functional default. + RETURN is( $1, $2, $3 ); + END IF; + EXECUTE 'SELECT is(' || COALESCE($1, 'NULL::text') || ', ' || quote_literal($2) || ', ' || quote_literal($3) || ')' + INTO thing; + RETURN thing; +END; +$$ LANGUAGE plpgsql; + +-- col_has_default( schema, table, column, default, description ) +CREATE OR REPLACE FUNCTION col_has_default ( TEXT, TEXT, TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ + SELECT _def_is(( + SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid) + 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 + ), $4, $5 ); +$$ LANGUAGE sql; + +-- col_has_default( table, column, default, description ) +CREATE OR REPLACE FUNCTION col_has_default ( TEXT, TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ + SELECT _def_is(( + SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid) + 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 + ), $3, $4 ); +$$ LANGUAGE sql; + +-- col_has_default( table, column, default ) +CREATE OR REPLACE FUNCTION col_has_default ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT col_has_default( + $1, $2, $3, + 'Column ' || $1 || '.' || $2 || ' should default to ' || quote_literal($3) + ); +$$ LANGUAGE sql; + +-- col_is_pk() +-- col_is_unique() diff --git a/sql/pgtap.sql b/sql/pgtap.sql index 45fe0901be7f..a2594c5a45c4 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -25,7 +25,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 143 +\set numb_tests 151 -- ## SET search_path TO TAPSCHEMA,public; @@ -370,7 +370,12 @@ SELECT is( UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 104, 106, 108 ); -- This will be rolled back. :-) -CREATE TABLE sometab (id int NOT NULL, name text, numb numeric(10, 2), myint numeric(8)); +CREATE TABLE sometab( + id INT NOT NULL, + name TEXT DEFAULT '', + numb NUMERIC(10, 2), + myint NUMERIC(8) +); \echo ok 110 - has_column(table, column) pass SELECT is( @@ -504,6 +509,38 @@ SELECT is( UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 142, 158 ); +/****************************************************************************/ +-- Test col_has_default(). + +\echo ok 144 - col_has_default( schema, table, column, default, description ) +SELECT is( + col_has_default( 'public', 'sometab', 'name', ''::text, 'name should default to empty string' ), + 'ok 144 - name should default to empty string', + 'col_has_default( schema, table, column, default, description ) should work' +); + +\echo ok 146 - col_has_default( schema, table, column, default, description ) fail +SELECT is( + col_has_default( 'public', 'sometab', 'name', 'foo'::text, 'name should default to ''foo''' ), + E'not ok 146 - name should default to ''foo''\n# Failed test 146: "name should default to ''foo''"\n# have: \n# want: foo', + 'ok 146 - Should get proper diagnostics for a default failure' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 146 ); + +\echo ok 148 - col_has_default( table, column, default, description ) +SELECT is( + col_has_default( 'sometab', 'name', ''::text, 'name should default to empty string' ), + 'ok 148 - name should default to empty string', + 'col_has_default( table, column, default, description ) should work' +); + +\echo ok 150 - col_has_default( table, column, default ) +SELECT is( + col_has_default( 'sometab', 'name', '' ), + 'ok 150 - Column sometab.name should default to ''''', + 'col_has_default( table, column, default ) should work' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From c744728f8445b6377aefe02472d77176b2d6ae0e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 20 Aug 2008 22:57:30 +0000 Subject: [PATCH 0063/1195] Renamed `col_has_default` to `col_default_is()`. --- README.pgtap | 2 +- expected/pgtap.out | 14 +++++++------- pgtap.sql.in | 14 +++++++------- sql/pgtap.sql | 24 ++++++++++++------------ 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/README.pgtap b/README.pgtap index de2777c0a49c..035590d91393 100644 --- a/README.pgtap +++ b/README.pgtap @@ -598,7 +598,7 @@ But this will not: If the test fails, it will output useful diagnostics. For example, this test: - SELECT col_has_default( + SELECT col_default_is( 'pg_catalog', 'pg_type', 'typname', diff --git a/expected/pgtap.out b/expected/pgtap.out index e9b35ebc0e25..9b957319d49c 100644 --- a/expected/pgtap.out +++ b/expected/pgtap.out @@ -143,11 +143,11 @@ ok 140 - testing col_type_is( schema, table, column, type(precision,scale), desc ok 141 - col_type_is( schema, table, column, type, precision(scale,description) should work ok 142 - col_type_is( table, column, type, precision, desc ) fail ok 143 - col_type_is with precision should have nice diagnostics -ok 144 - col_has_default( schema, table, column, default, description ) -ok 145 - col_has_default( schema, table, column, default, description ) should work -ok 146 - col_has_default( schema, table, column, default, description ) fail +ok 144 - col_default_is( schema, table, column, default, description ) +ok 145 - col_default_is( schema, table, column, default, description ) should work +ok 146 - col_default_is( schema, table, column, default, description ) fail ok 147 - ok 146 - Should get proper diagnostics for a default failure -ok 148 - col_has_default( table, column, default, description ) -ok 149 - col_has_default( table, column, default, description ) should work -ok 150 - col_has_default( table, column, default ) -ok 151 - col_has_default( table, column, default ) should work +ok 148 - col_default_is( table, column, default, description ) +ok 149 - col_default_is( table, column, default, description ) should work +ok 150 - col_default_is( table, column, default ) +ok 151 - col_default_is( table, column, default ) should work diff --git a/pgtap.sql.in b/pgtap.sql.in index ff089093dcbf..67320b681f91 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -685,8 +685,8 @@ BEGIN END; $$ LANGUAGE plpgsql; --- col_has_default( schema, table, column, default, description ) -CREATE OR REPLACE FUNCTION col_has_default ( TEXT, TEXT, TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ +-- col_default_is( schema, table, column, default, description ) +CREATE OR REPLACE FUNCTION col_default_is ( TEXT, TEXT, TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ SELECT _def_is(( SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid) FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a, @@ -704,8 +704,8 @@ CREATE OR REPLACE FUNCTION col_has_default ( TEXT, TEXT, TEXT, anyelement, TEXT ), $4, $5 ); $$ LANGUAGE sql; --- col_has_default( table, column, default, description ) -CREATE OR REPLACE FUNCTION col_has_default ( TEXT, TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ +-- col_default_is( table, column, default, description ) +CREATE OR REPLACE FUNCTION col_default_is ( TEXT, TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ SELECT _def_is(( SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid) FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, pg_catalog.pg_attrdef d @@ -721,9 +721,9 @@ CREATE OR REPLACE FUNCTION col_has_default ( TEXT, TEXT, anyelement, TEXT ) RETU ), $3, $4 ); $$ LANGUAGE sql; --- col_has_default( table, column, default ) -CREATE OR REPLACE FUNCTION col_has_default ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT col_has_default( +-- col_default_is( table, column, default ) +CREATE OR REPLACE FUNCTION col_default_is ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT col_default_is( $1, $2, $3, 'Column ' || $1 || '.' || $2 || ' should default to ' || quote_literal($3) ); diff --git a/sql/pgtap.sql b/sql/pgtap.sql index a2594c5a45c4..c1cfeaa3a6aa 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -510,35 +510,35 @@ SELECT is( UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 142, 158 ); /****************************************************************************/ --- Test col_has_default(). +-- Test col_default_is(). -\echo ok 144 - col_has_default( schema, table, column, default, description ) +\echo ok 144 - col_default_is( schema, table, column, default, description ) SELECT is( - col_has_default( 'public', 'sometab', 'name', ''::text, 'name should default to empty string' ), + col_default_is( 'public', 'sometab', 'name', ''::text, 'name should default to empty string' ), 'ok 144 - name should default to empty string', - 'col_has_default( schema, table, column, default, description ) should work' + 'col_default_is( schema, table, column, default, description ) should work' ); -\echo ok 146 - col_has_default( schema, table, column, default, description ) fail +\echo ok 146 - col_default_is( schema, table, column, default, description ) fail SELECT is( - col_has_default( 'public', 'sometab', 'name', 'foo'::text, 'name should default to ''foo''' ), + col_default_is( 'public', 'sometab', 'name', 'foo'::text, 'name should default to ''foo''' ), E'not ok 146 - name should default to ''foo''\n# Failed test 146: "name should default to ''foo''"\n# have: \n# want: foo', 'ok 146 - Should get proper diagnostics for a default failure' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 146 ); -\echo ok 148 - col_has_default( table, column, default, description ) +\echo ok 148 - col_default_is( table, column, default, description ) SELECT is( - col_has_default( 'sometab', 'name', ''::text, 'name should default to empty string' ), + col_default_is( 'sometab', 'name', ''::text, 'name should default to empty string' ), 'ok 148 - name should default to empty string', - 'col_has_default( table, column, default, description ) should work' + 'col_default_is( table, column, default, description ) should work' ); -\echo ok 150 - col_has_default( table, column, default ) +\echo ok 150 - col_default_is( table, column, default ) SELECT is( - col_has_default( 'sometab', 'name', '' ), + col_default_is( 'sometab', 'name', '' ), 'ok 150 - Column sometab.name should default to ''''', - 'col_has_default( table, column, default ) should work' + 'col_default_is( table, column, default ) should work' ); /****************************************************************************/ From 10aacd28a7a069363b1c8f74fb23f277792c1260 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 20 Aug 2008 23:25:59 +0000 Subject: [PATCH 0064/1195] Added `has_pk()` test function. --- Changes | 4 ++-- README.pgtap | 17 +++++++++++++++++ expected/pgtap.out | 12 +++++++++++- pgtap.sql.in | 40 ++++++++++++++++++++++++++++++++++++++++ sql/pgtap.sql | 43 +++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 111 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index a2de190bf0dd..7d91f3ae1017 100644 --- a/Changes +++ b/Changes @@ -29,8 +29,8 @@ Revision history for pgTAP - Changed "got/expected" to "have/want", following Schwern's plans for Test::Builder 2. Also changed "caught/expected" to "caught/wanted" for nice parity when outputting diagnostics for exception testing. - - Added the `has_table()`, `has_view()`, and `has_column()` test - functions. + - Added the `has_table()`, `has_view()`, `has_column()` and `has_pk()` + test functions. - Added the `col_not_null()` and `col_is_null()` test functions. - Added the `col_type_is()` and `col_default_is()` test functions. - Fixed `is()` and `isnt()` to better handle NULLs. diff --git a/README.pgtap b/README.pgtap index 035590d91393..bfffc07ce920 100644 --- a/README.pgtap +++ b/README.pgtap @@ -612,6 +612,23 @@ Will produce something like this: # have: NULL # want: foo +### has_pk( schema, table, description ) ### +### has_pk( table, description ) ### +### has_pk( table ) ### + + SELECT has_pk( + 'myschema', + 'sometable', + 'Table myschema.sometable.somecolumn should have a primary key' + ); + +Tests whether or not a table has a primary key. The first argument is the +schema name, the second the table name, the the third is the test description. +If the schema is omitted, the table must be visible in the search path. If the +test description is omitted, it will be set to "Table :table should have a +primary key". Note that this test will fail if the table in question does not +exist. + Diagnostics ----------- diff --git a/expected/pgtap.out b/expected/pgtap.out index 9b957319d49c..e80d2523d023 100644 --- a/expected/pgtap.out +++ b/expected/pgtap.out @@ -1,5 +1,5 @@ \set ECHO -1..151 +1..161 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -151,3 +151,13 @@ ok 148 - col_default_is( table, column, default, description ) ok 149 - col_default_is( table, column, default, description ) should work ok 150 - col_default_is( table, column, default ) ok 151 - col_default_is( table, column, default ) should work +ok 152 - test has_pk( schema, table, description ) +ok 153 - has_pk( schema, table, description ) should work +ok 154 - test has_pk( table, description ) +ok 155 - has_pk( table, description ) should work +ok 156 - test has_pk( table ) +ok 157 - has_pk( table ) should work +ok 158 - test has_pk( schema, table, description ) fail +ok 159 - has_pk( schema, table, description ) should fail properly +ok 160 - test has_pk( table, description ) fail +ok 161 - has_pk( table, description ) should fail properly diff --git a/pgtap.sql.in b/pgtap.sql.in index 67320b681f91..6917fc1fdd8b 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -729,5 +729,45 @@ CREATE OR REPLACE FUNCTION col_default_is ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $ ); $$ LANGUAGE sql; +-- has_pk( schema, table, description ) +CREATE OR REPLACE FUNCTION has_pk ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, + pg_catalog.pg_constraint x + WHERE n.oid = c.relnamespace + AND c.oid = x.conrelid + AND c.relkind = 'r' + AND n.nspname = $1 + AND c.relname = $2 + AND c.relhaspkey = true + AND x.contype = 'p' + ), $3 + ); +$$ LANGUAGE sql; + + +-- has_pk( table, description ) +CREATE OR REPLACE FUNCTION has_pk ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_class c, pg_catalog.pg_constraint x + WHERE c.oid = x.conrelid + AND c.relkind = 'r' + AND pg_catalog.pg_table_is_visible(c.oid) + AND c.relname = $1 + AND c.relhaspkey = true + AND x.contype = 'p' + ), $2 + ); +$$ LANGUAGE sql; + +-- has_pk( table ) +CREATE OR REPLACE FUNCTION has_pk ( TEXT ) RETURNS TEXT AS $$ + SELECT has_pk( $1, 'Table ' || $1 || ' should have a primary key' ); +$$ LANGUAGE sql; + -- col_is_pk() -- col_is_unique() diff --git a/sql/pgtap.sql b/sql/pgtap.sql index c1cfeaa3a6aa..b74f35b4f3f0 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -25,7 +25,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 151 +\set numb_tests 161 -- ## SET search_path TO TAPSCHEMA,public; @@ -371,7 +371,7 @@ UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 104, 106, 108 ); -- This will be rolled back. :-) CREATE TABLE sometab( - id INT NOT NULL, + id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '', numb NUMERIC(10, 2), myint NUMERIC(8) @@ -541,6 +541,45 @@ SELECT is( 'col_default_is( table, column, default ) should work' ); +/****************************************************************************/ +-- Test has_pk(). + +\echo ok 152 - test has_pk( schema, table, description ) +SELECT is( + has_pk( 'public', 'sometab', 'public.sometab should have a pk' ), + 'ok 152 - public.sometab should have a pk', + 'has_pk( schema, table, description ) should work' +); + +\echo ok 154 - test has_pk( table, description ) +SELECT is( + has_pk( 'sometab', 'sometab should have a pk' ), + 'ok 154 - sometab should have a pk', + 'has_pk( table, description ) should work' +); + +\echo ok 156 - test has_pk( table ) +SELECT is( + has_pk( 'sometab' ), + 'ok 156 - Table sometab should have a primary key', + 'has_pk( table ) should work' +); + +\echo ok 158 - test has_pk( schema, table, description ) fail +SELECT is( + has_pk( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should have a pk' ), + E'not ok 158 - pg_catalog.pg_class should have a pk\n# Failed test 158: "pg_catalog.pg_class should have a pk"', + 'has_pk( schema, table, description ) should fail properly' +); + +\echo ok 160 - test has_pk( table, description ) fail +SELECT is( + has_pk( 'pg_class', 'pg_class should have a pk' ), + E'not ok 160 - pg_class should have a pk\n# Failed test 160: "pg_class should have a pk"', + 'has_pk( table, description ) should fail properly' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 158, 160 ); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 4dee6ab0e1df4f312cc1795b498c1c1a96f76570 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 00:22:52 +0000 Subject: [PATCH 0065/1195] Added `col_is_pk()`. --- Changes | 3 ++- README.pgtap | 40 ++++++++++++++++++++++++++- expected/pgtap.out | 18 ++++++++++++- pgtap.sql.in | 54 ++++++++++++++++++++++++++++++++++++- sql/pgtap.sql | 67 +++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 177 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 7d91f3ae1017..571091166fd0 100644 --- a/Changes +++ b/Changes @@ -32,7 +32,8 @@ Revision history for pgTAP - Added the `has_table()`, `has_view()`, `has_column()` and `has_pk()` test functions. - Added the `col_not_null()` and `col_is_null()` test functions. - - Added the `col_type_is()` and `col_default_is()` test functions. + - Added the `col_type_is()`, `col_default_is()`, and `col_is_pk()` test + functions. - Fixed `is()` and `isnt()` to better handle NULLs. 0.02 2008-06-17T16:26:41 diff --git a/README.pgtap b/README.pgtap index bfffc07ce920..d498d0a1ea3a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -619,7 +619,7 @@ Will produce something like this: SELECT has_pk( 'myschema', 'sometable', - 'Table myschema.sometable.somecolumn should have a primary key' + 'Table myschema.sometable should have a primary key' ); Tests whether or not a table has a primary key. The first argument is the @@ -629,6 +629,44 @@ test description is omitted, it will be set to "Table :table should have a primary key". Note that this test will fail if the table in question does not exist. +### col_is_pk( schema, table, column, description ) ### +### col_is_pk( schema, table, column[], description ) ### +### col_is_pk( table, column, description ) ### +### col_is_pk( table, column[], description ) ### +### col_is_pk( table, column ) ### +### col_is_pk( table, column[] ) ### + + SELECT col_is_pk( + 'myschema', + 'sometable', + 'id', + 'Column myschema.sometable.id should be a primary key' + ); + + SELECT col_is_pk( + 'persons', + ARRAY['first', 'last'], + ); + +Tests whether the specified column or columns in a table is/are the primary +key for that table. If it fails, it will emit diagnostics showing the actual +primary key columns, if any. The first argument is the schema name, the second +the table name, the third the column name or an array of column names, and the +fourth is the test description. If the schema is omitted, the table must be +visible in the search path. If the test description is omitted, it will be set +to "Column :table.:column should be a primary key". Note that this test will +fail if the column in question does not exist. + +If the test fails, it will output useful diagnostics. For example this test: + + SELECT col_is_pk( 'pg_type', 'id' ); + +Will produce something like this: + + # Failed test 178: "Column pg_type.id should be a primary key" + # have: {} + # want: {id} + Diagnostics ----------- diff --git a/expected/pgtap.out b/expected/pgtap.out index e80d2523d023..f03752347a4e 100644 --- a/expected/pgtap.out +++ b/expected/pgtap.out @@ -1,5 +1,5 @@ \set ECHO -1..161 +1..177 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -161,3 +161,19 @@ ok 158 - test has_pk( schema, table, description ) fail ok 159 - has_pk( schema, table, description ) should fail properly ok 160 - test has_pk( table, description ) fail ok 161 - has_pk( table, description ) should fail properly +ok 162 - test col_is_pk( schema, table, column, description ) +ok 163 - col_is_pk( schema, table, column, description ) should work +ok 164 - test col_is_pk( table, column, description ) +ok 165 - col_is_pk( table, column, description ) should work +ok 166 - test col_is_pk( table, column ) +ok 167 - col_is_pk( table, column ) should work +ok 168 - test col_is_pk( schema, table, column, description ) fail +ok 169 - col_is_pk( schema, table, column, description ) should fail properly +ok 170 - test col_is_pk( table, column, description ) fail +ok 171 - col_is_pk( table, column, description ) should fail properly +ok 172 - test col_is_pk( schema, table, column[], description ) +ok 173 - col_is_pk( schema, table, column[], description ) should work +ok 174 - test col_is_pk( table, column[], description ) +ok 175 - col_is_pk( table, column[], description ) should work +ok 176 - test col_is_pk( table, column[], description ) +ok 177 - col_is_pk( table, column[] ) should work diff --git a/pgtap.sql.in b/pgtap.sql.in index 6917fc1fdd8b..1bf1c08544e5 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -769,5 +769,57 @@ CREATE OR REPLACE FUNCTION has_pk ( TEXT ) RETURNS TEXT AS $$ SELECT has_pk( $1, 'Table ' || $1 || ' should have a primary key' ); $$ LANGUAGE sql; --- col_is_pk() +-- col_is_pk( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ + SELECT is(ARRAY (SELECT a.attname::text + FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a, + pg_catalog.pg_constraint x + WHERE n.oid = c.relnamespace + AND c.oid = a.attrelid + AND c.oid = x.conrelid + AND a.attnum = ANY(x.conkey) + AND n.nspname = $1 + AND c.relname = $2 + AND a.attnum > 0 + AND NOT a.attisdropped + ORDER BY a.attnum + ), $3, $4 ); +$$ LANGUAGE sql; + +-- col_is_pk( table, column, description ) +CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ + SELECT is(ARRAY (SELECT a.attname::text + FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, + pg_catalog.pg_constraint x + WHERE c.oid = a.attrelid + AND c.oid = x.conrelid + AND pg_table_is_visible(c.oid) + AND a.attnum = ANY(x.conkey) + AND c.relname = $1 + AND a.attnum > 0 + AND NOT a.attisdropped + ORDER BY a.attnum + ), $2, $3 ); +$$ LANGUAGE sql; + +-- col_is_pk( table, column[] ) +CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT[] ) RETURNS TEXT AS $$ + SELECT col_is_pk( $1, $2, 'Column ' || $1 || '.' || $2::text || ' should be a primary key' ); +$$ LANGUAGE sql; + +-- col_is_pk( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT col_is_pk( $1, $2, ARRAY[$3], $4 ); +$$ LANGUAGE sql; + +-- col_is_pk( table, column, description ) +CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT, 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 ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT col_is_pk( $1, $2, 'Column ' || $1 || '.' || $2 || ' should be a primary key' ); +$$ LANGUAGE sql; + -- col_is_unique() diff --git a/sql/pgtap.sql b/sql/pgtap.sql index b74f35b4f3f0..971cd4359a90 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -25,7 +25,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 161 +\set numb_tests 177 -- ## SET search_path TO TAPSCHEMA,public; @@ -580,6 +580,71 @@ SELECT is( ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 158, 160 ); +/****************************************************************************/ +-- Test col_is_pk(). + +\echo ok 162 - test col_is_pk( schema, table, column, description ) +SELECT is( + col_is_pk( 'public', 'sometab', 'id', 'public.sometab.id should be a pk' ), + 'ok 162 - public.sometab.id should be a pk', + 'col_is_pk( schema, table, column, description ) should work' +); + +\echo ok 164 - test col_is_pk( table, column, description ) +SELECT is( + col_is_pk( 'sometab', 'id', 'sometab.id should be a pk' ), + 'ok 164 - sometab.id should be a pk', + 'col_is_pk( table, column, description ) should work' +); + +\echo ok 166 - test col_is_pk( table, column ) +SELECT is( + col_is_pk( 'sometab', 'id' ), + 'ok 166 - Column sometab.id should be a primary key', + 'col_is_pk( table, column ) should work' +); + +\echo ok 168 - test col_is_pk( schema, table, column, description ) fail +SELECT is( + col_is_pk( 'public', 'sometab', 'name', 'public.sometab.name should be a pk' ), + E'not ok 168 - public.sometab.name should be a pk\n# Failed test 168: "public.sometab.name should be a pk"\n# have: {id}\n# want: {name}', + 'col_is_pk( schema, table, column, description ) should fail properly' +); + +\echo ok 170 - test col_is_pk( table, column, description ) fail +SELECT is( + col_is_pk( 'sometab', 'name', 'sometab.name should be a pk' ), + E'not ok 170 - sometab.name should be a pk\n# Failed test 170: "sometab.name should be a pk"\n# have: {id}\n# want: {name}', + 'col_is_pk( table, column, description ) should fail properly' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 168, 170 ); + +/****************************************************************************/ +-- Test col_is_pk() with an array of columns. + +CREATE TABLE argh (id int not null, name text not null, primary key (id, name)); + +\echo ok 172 - test col_is_pk( schema, table, column[], description ) +SELECT is( + col_is_pk( 'public', 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), + 'ok 172 - id + name should be a pk', + 'col_is_pk( schema, table, column[], description ) should work' +); + +\echo ok 174 - test col_is_pk( table, column[], description ) +SELECT is( + col_is_pk( 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), + 'ok 174 - id + name should be a pk', + 'col_is_pk( table, column[], description ) should work' +); + +\echo ok 176 - test col_is_pk( table, column[], description ) +SELECT is( + col_is_pk( 'argh', ARRAY['id', 'name'] ), + 'ok 176 - Column argh.{id,name} should be a primary key', + 'col_is_pk( table, column[] ) should work' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From e3cb4673a8acbf9eba9641fbe59da2da183993fa Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 00:29:12 +0000 Subject: [PATCH 0066/1195] Note final schema test methods I want to add. Should be pretty simple, though a fair bit of copy and paste. --- pgtap.sql.in | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 1bf1c08544e5..6e6ebbc77aa8 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -822,4 +822,9 @@ CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT ) RETURNS TEXT AS $$ SELECT col_is_pk( $1, $2, 'Column ' || $1 || '.' || $2 || ' should be a primary key' ); $$ LANGUAGE sql; --- col_is_unique() +-- has_fk( schema, table, description ) +-- has_unique( schema, table, description ) +-- has_check( schema, table, constraint, description ) + +-- col_has_fk( schema, table, column[], description ) +-- col_is_unique( schema, table, column[], description ) From 66c8d1330eaed24c5858b2df21b8377c732b1abf Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 02:07:54 +0000 Subject: [PATCH 0067/1195] Moved pk test function testing to its own test script. More will be moved shortly. --- Makefile | 2 +- expected/pgtap.out | 28 +-------- expected/pktap.out | 28 +++++++++ sql/pgtap.sql | 105 +------------------------------- sql/pktap.sql | 149 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 180 insertions(+), 132 deletions(-) create mode 100644 expected/pktap.out create mode 100644 sql/pktap.sql diff --git a/Makefile b/Makefile index 089e3343d1dd..2907d1024ce1 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ DATA_built = pgtap.sql drop_pgtap.sql DOCS = README.pgtap SCRIPTS = pg_prove -REGRESS = pgtap pg73 +REGRESS = pgtap pg73 pktap ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/expected/pgtap.out b/expected/pgtap.out index f03752347a4e..9b957319d49c 100644 --- a/expected/pgtap.out +++ b/expected/pgtap.out @@ -1,5 +1,5 @@ \set ECHO -1..177 +1..151 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -151,29 +151,3 @@ ok 148 - col_default_is( table, column, default, description ) ok 149 - col_default_is( table, column, default, description ) should work ok 150 - col_default_is( table, column, default ) ok 151 - col_default_is( table, column, default ) should work -ok 152 - test has_pk( schema, table, description ) -ok 153 - has_pk( schema, table, description ) should work -ok 154 - test has_pk( table, description ) -ok 155 - has_pk( table, description ) should work -ok 156 - test has_pk( table ) -ok 157 - has_pk( table ) should work -ok 158 - test has_pk( schema, table, description ) fail -ok 159 - has_pk( schema, table, description ) should fail properly -ok 160 - test has_pk( table, description ) fail -ok 161 - has_pk( table, description ) should fail properly -ok 162 - test col_is_pk( schema, table, column, description ) -ok 163 - col_is_pk( schema, table, column, description ) should work -ok 164 - test col_is_pk( table, column, description ) -ok 165 - col_is_pk( table, column, description ) should work -ok 166 - test col_is_pk( table, column ) -ok 167 - col_is_pk( table, column ) should work -ok 168 - test col_is_pk( schema, table, column, description ) fail -ok 169 - col_is_pk( schema, table, column, description ) should fail properly -ok 170 - test col_is_pk( table, column, description ) fail -ok 171 - col_is_pk( table, column, description ) should fail properly -ok 172 - test col_is_pk( schema, table, column[], description ) -ok 173 - col_is_pk( schema, table, column[], description ) should work -ok 174 - test col_is_pk( table, column[], description ) -ok 175 - col_is_pk( table, column[], description ) should work -ok 176 - test col_is_pk( table, column[], description ) -ok 177 - col_is_pk( table, column[] ) should work diff --git a/expected/pktap.out b/expected/pktap.out new file mode 100644 index 000000000000..da0e9472fad9 --- /dev/null +++ b/expected/pktap.out @@ -0,0 +1,28 @@ +\set ECHO +1..26 +ok 1 - test has_pk( schema, table, description ) +ok 2 - has_pk( schema, table, description ) should work +ok 3 - test has_pk( table, description ) +ok 4 - has_pk( table, description ) should work +ok 5 - test has_pk( table ) +ok 6 - has_pk( table ) should work +ok 7 - test has_pk( schema, table, description ) fail +ok 8 - has_pk( schema, table, description ) should fail properly +ok 9 - test has_pk( table, description ) fail +ok 10 - has_pk( table, description ) should fail properly +ok 11 - test col_is_pk( schema, table, column, description ) +ok 12 - col_is_pk( schema, table, column, description ) should work +ok 13 - test col_is_pk( table, column, description ) +ok 14 - col_is_pk( table, column, description ) should work +ok 15 - test col_is_pk( table, column ) +ok 16 - col_is_pk( table, column ) should work +ok 17 - test col_is_pk( schema, table, column, description ) fail +ok 18 - col_is_pk( schema, table, column, description ) should fail properly +ok 19 - test col_is_pk( table, column, description ) fail +ok 20 - col_is_pk( table, column, description ) should fail properly +ok 21 - test col_is_pk( schema, table, column[], description ) +ok 22 - col_is_pk( schema, table, column[], description ) should work +ok 23 - test col_is_pk( table, column[], description ) +ok 24 - col_is_pk( table, column[], description ) should work +ok 25 - test col_is_pk( table, column[], description ) +ok 26 - col_is_pk( table, column[] ) should work diff --git a/sql/pgtap.sql b/sql/pgtap.sql index 971cd4359a90..39536f92b168 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -25,7 +25,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 177 +\set numb_tests 151 -- ## SET search_path TO TAPSCHEMA,public; @@ -541,109 +541,6 @@ SELECT is( 'col_default_is( table, column, default ) should work' ); -/****************************************************************************/ --- Test has_pk(). - -\echo ok 152 - test has_pk( schema, table, description ) -SELECT is( - has_pk( 'public', 'sometab', 'public.sometab should have a pk' ), - 'ok 152 - public.sometab should have a pk', - 'has_pk( schema, table, description ) should work' -); - -\echo ok 154 - test has_pk( table, description ) -SELECT is( - has_pk( 'sometab', 'sometab should have a pk' ), - 'ok 154 - sometab should have a pk', - 'has_pk( table, description ) should work' -); - -\echo ok 156 - test has_pk( table ) -SELECT is( - has_pk( 'sometab' ), - 'ok 156 - Table sometab should have a primary key', - 'has_pk( table ) should work' -); - -\echo ok 158 - test has_pk( schema, table, description ) fail -SELECT is( - has_pk( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should have a pk' ), - E'not ok 158 - pg_catalog.pg_class should have a pk\n# Failed test 158: "pg_catalog.pg_class should have a pk"', - 'has_pk( schema, table, description ) should fail properly' -); - -\echo ok 160 - test has_pk( table, description ) fail -SELECT is( - has_pk( 'pg_class', 'pg_class should have a pk' ), - E'not ok 160 - pg_class should have a pk\n# Failed test 160: "pg_class should have a pk"', - 'has_pk( table, description ) should fail properly' -); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 158, 160 ); - -/****************************************************************************/ --- Test col_is_pk(). - -\echo ok 162 - test col_is_pk( schema, table, column, description ) -SELECT is( - col_is_pk( 'public', 'sometab', 'id', 'public.sometab.id should be a pk' ), - 'ok 162 - public.sometab.id should be a pk', - 'col_is_pk( schema, table, column, description ) should work' -); - -\echo ok 164 - test col_is_pk( table, column, description ) -SELECT is( - col_is_pk( 'sometab', 'id', 'sometab.id should be a pk' ), - 'ok 164 - sometab.id should be a pk', - 'col_is_pk( table, column, description ) should work' -); - -\echo ok 166 - test col_is_pk( table, column ) -SELECT is( - col_is_pk( 'sometab', 'id' ), - 'ok 166 - Column sometab.id should be a primary key', - 'col_is_pk( table, column ) should work' -); - -\echo ok 168 - test col_is_pk( schema, table, column, description ) fail -SELECT is( - col_is_pk( 'public', 'sometab', 'name', 'public.sometab.name should be a pk' ), - E'not ok 168 - public.sometab.name should be a pk\n# Failed test 168: "public.sometab.name should be a pk"\n# have: {id}\n# want: {name}', - 'col_is_pk( schema, table, column, description ) should fail properly' -); - -\echo ok 170 - test col_is_pk( table, column, description ) fail -SELECT is( - col_is_pk( 'sometab', 'name', 'sometab.name should be a pk' ), - E'not ok 170 - sometab.name should be a pk\n# Failed test 170: "sometab.name should be a pk"\n# have: {id}\n# want: {name}', - 'col_is_pk( table, column, description ) should fail properly' -); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 168, 170 ); - -/****************************************************************************/ --- Test col_is_pk() with an array of columns. - -CREATE TABLE argh (id int not null, name text not null, primary key (id, name)); - -\echo ok 172 - test col_is_pk( schema, table, column[], description ) -SELECT is( - col_is_pk( 'public', 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), - 'ok 172 - id + name should be a pk', - 'col_is_pk( schema, table, column[], description ) should work' -); - -\echo ok 174 - test col_is_pk( table, column[], description ) -SELECT is( - col_is_pk( 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), - 'ok 174 - id + name should be a pk', - 'col_is_pk( table, column[], description ) should work' -); - -\echo ok 176 - test col_is_pk( table, column[], description ) -SELECT is( - col_is_pk( 'argh', ARRAY['id', 'name'] ), - 'ok 176 - Column argh.{id,name} should be a primary key', - 'col_is_pk( table, column[] ) should work' -); /****************************************************************************/ -- Finish the tests and clean up. diff --git a/sql/pktap.sql b/sql/pktap.sql new file mode 100644 index 000000000000..abb77a198377 --- /dev/null +++ b/sql/pktap.sql @@ -0,0 +1,149 @@ +\set ECHO + +-- +-- Tests for pgTAP. +-- +-- +-- $Id: pgtap.sql 4199 2008-08-21 00:22:52Z david $ + +-- Format the output for nice TAP. +\pset format unaligned +\pset tuples_only true +\pset pager + +-- Create plpgsql if it's not already there. +SET client_min_messages = fatal; +CREATE LANGUAGE plpgsql; + +-- Keep things quiet. +SET client_min_messages = warning; + +-- Revert all changes on failure. +\set ON_ERROR_ROLBACK 1 +\set ON_ERROR_STOP true + +-- Load the TAP functions. +BEGIN; +\i pgtap.sql + +-- ## SET search_path TO TAPSCHEMA,public; + +-- Set the test plan. +SELECT plan(26); + +-- This will be rolled back. :-) +CREATE TABLE sometab( + id INT NOT NULL PRIMARY KEY, + name TEXT DEFAULT '', + numb NUMERIC(10, 2), + myint NUMERIC(8) +); + +/****************************************************************************/ +-- Test has_pk(). + +\echo ok 1 - test has_pk( schema, table, description ) +SELECT is( + has_pk( 'public', 'sometab', 'public.sometab should have a pk' ), + 'ok 1 - public.sometab should have a pk', + 'has_pk( schema, table, description ) should work' +); + +\echo ok 3 - test has_pk( table, description ) +SELECT is( + has_pk( 'sometab', 'sometab should have a pk' ), + 'ok 3 - sometab should have a pk', + 'has_pk( table, description ) should work' +); + +\echo ok 5 - test has_pk( table ) +SELECT is( + has_pk( 'sometab' ), + 'ok 5 - Table sometab should have a primary key', + 'has_pk( table ) should work' +); + +\echo ok 7 - test has_pk( schema, table, description ) fail +SELECT is( + has_pk( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should have a pk' ), + E'not ok 7 - pg_catalog.pg_class should have a pk\n# Failed test 7: "pg_catalog.pg_class should have a pk"', + 'has_pk( schema, table, description ) should fail properly' +); + +\echo ok 9 - test has_pk( table, description ) fail +SELECT is( + has_pk( 'pg_class', 'pg_class should have a pk' ), + E'not ok 9 - pg_class should have a pk\n# Failed test 9: "pg_class should have a pk"', + 'has_pk( table, description ) should fail properly' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 7, 9 ); + +/****************************************************************************/ +-- Test col_is_pk(). + +\echo ok 11 - test col_is_pk( schema, table, column, description ) +SELECT is( + col_is_pk( 'public', 'sometab', 'id', 'public.sometab.id should be a pk' ), + 'ok 11 - public.sometab.id should be a pk', + 'col_is_pk( schema, table, column, description ) should work' +); + +\echo ok 13 - test col_is_pk( table, column, description ) +SELECT is( + col_is_pk( 'sometab', 'id', 'sometab.id should be a pk' ), + 'ok 13 - sometab.id should be a pk', + 'col_is_pk( table, column, description ) should work' +); + +\echo ok 15 - test col_is_pk( table, column ) +SELECT is( + col_is_pk( 'sometab', 'id' ), + 'ok 15 - Column sometab.id should be a primary key', + 'col_is_pk( table, column ) should work' +); + +\echo ok 17 - test col_is_pk( schema, table, column, description ) fail +SELECT is( + col_is_pk( 'public', 'sometab', 'name', 'public.sometab.name should be a pk' ), + E'not ok 17 - public.sometab.name should be a pk\n# Failed test 17: "public.sometab.name should be a pk"\n# have: {id}\n# want: {name}', + 'col_is_pk( schema, table, column, description ) should fail properly' +); + +\echo ok 19 - test col_is_pk( table, column, description ) fail +SELECT is( + col_is_pk( 'sometab', 'name', 'sometab.name should be a pk' ), + E'not ok 19 - sometab.name should be a pk\n# Failed test 19: "sometab.name should be a pk"\n# have: {id}\n# want: {name}', + 'col_is_pk( table, column, description ) should fail properly' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 17, 19 ); + +/****************************************************************************/ +-- Test col_is_pk() with an array of columns. + +CREATE TABLE argh (id int not null, name text not null, primary key (id, name)); + +\echo ok 21 - test col_is_pk( schema, table, column[], description ) +SELECT is( + col_is_pk( 'public', 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), + 'ok 21 - id + name should be a pk', + 'col_is_pk( schema, table, column[], description ) should work' +); + +\echo ok 23 - test col_is_pk( table, column[], description ) +SELECT is( + col_is_pk( 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), + 'ok 23 - id + name should be a pk', + 'col_is_pk( table, column[], description ) should work' +); + +\echo ok 25 - test col_is_pk( table, column[], description ) +SELECT is( + col_is_pk( 'argh', ARRAY['id', 'name'] ), + 'ok 25 - Column argh.{id,name} should be a primary key', + 'col_is_pk( table, column[] ) should work' +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From 27452d26e02748f20d24e43d6ac19a8d9dd76847 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 02:25:45 +0000 Subject: [PATCH 0068/1195] Set missing svn:keywords. --- pgtap.sql.in | 2 +- sql/pktap.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 6e6ebbc77aa8..d7705cbb95e0 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2,7 +2,7 @@ -- testing. It is distributed under the revised FreeBSD license. You can -- find the original here: -- --- $HeadURL$ +-- $HeadURL: https://svn.kineticode.com/pgtap/trunk/pgtap.sql.in $ -- -- The home page for the pgTAP project is: -- diff --git a/sql/pktap.sql b/sql/pktap.sql index abb77a198377..4ab044fb4adb 100644 --- a/sql/pktap.sql +++ b/sql/pktap.sql @@ -4,7 +4,7 @@ -- Tests for pgTAP. -- -- --- $Id: pgtap.sql 4199 2008-08-21 00:22:52Z david $ +-- $Id$ -- Format the output for nice TAP. \pset format unaligned From 62b859201aa3de0a2c49b0e50c9d4f3cea889fcd Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 04:12:39 +0000 Subject: [PATCH 0069/1195] Set missing svn:keywords. --- Makefile | 2 +- expected/coltap.out | 40 +++++++++ expected/pgtap.out | 40 +-------- sql/coltap.sql | 195 ++++++++++++++++++++++++++++++++++++++++++++ sql/pgtap.sql | 153 +--------------------------------- 5 files changed, 238 insertions(+), 192 deletions(-) create mode 100644 expected/coltap.out create mode 100644 sql/coltap.sql diff --git a/Makefile b/Makefile index 2907d1024ce1..c2f7ce6113f0 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ DATA_built = pgtap.sql drop_pgtap.sql DOCS = README.pgtap SCRIPTS = pg_prove -REGRESS = pgtap pg73 pktap +REGRESS = pgtap pg73 pktap coltap ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/expected/coltap.out b/expected/coltap.out new file mode 100644 index 000000000000..97bb1885460f --- /dev/null +++ b/expected/coltap.out @@ -0,0 +1,40 @@ +\set ECHO +1..38 +ok 1 - testing col_not_null( schema, table, column, desc ) +ok 2 - col_not_null( schema, table, column, desc ) should work +ok 3 - testing col_not_null( table, column, desc ) +ok 4 - col_not_null( table, column, desc ) should work +ok 5 - testing col_not_null( schema, table, column, desc ) +ok 6 - col_not_null( table, column ) should work +ok 7 - testing col_not_null( schema, table, column, desc ) +ok 8 - col_not_null( table, column ) should properly fail +ok 9 - testing col_is_null( schema, table, column, desc ) +ok 10 - col_is_null( schema, table, column, desc ) should work +ok 11 - testing col_is_null( table, column, desc ) +ok 12 - col_is_null( table, column, desc ) should work +ok 13 - testing col_is_null( schema, table, column, desc ) +ok 14 - col_is_null( table, column ) should work +ok 15 - testing col_is_null( schema, table, column, desc ) +ok 16 - col_is_null( table, column ) should properly fail +ok 17 - testing col_type_is( schema, table, column, type, desc ) +ok 18 - col_type_is( schema, table, column, type, desc ) should work +ok 19 - testing col_type_is( table, column, type, desc ) +ok 20 - col_type_is( table, column, type, desc ) should work +ok 21 - testing col_type_is( table, column, type ) +ok 22 - col_type_is( table, column, type ) should work +ok 23 - testing col_type_is( table, column, type ) case-insensitively +ok 24 - col_type_is( table, column, type ) should work case-insensitively +ok 25 - testing col_type_is( table, column, type ) failure +ok 26 - col_type_is( table, column, type ) should fail with proper diagnostics +ok 27 - testing col_type_is( schema, table, column, type(precision,scale), description ) +ok 28 - col_type_is( schema, table, column, type, precision(scale,description) should work +ok 29 - col_type_is( table, column, type, precision, desc ) fail +ok 30 - col_type_is with precision should have nice diagnostics +ok 31 - col_default_is( schema, table, column, default, description ) +ok 32 - col_default_is( schema, table, column, default, description ) should work +ok 33 - col_default_is( schema, table, column, default, description ) fail +ok 34 - ok 33 - Should get proper diagnostics for a default failure +ok 35 - col_default_is( table, column, default, description ) +ok 36 - col_default_is( table, column, default, description ) should work +ok 37 - col_default_is( table, column, default ) +ok 38 - col_default_is( table, column, default ) should work diff --git a/expected/pgtap.out b/expected/pgtap.out index 9b957319d49c..4274e0e5198c 100644 --- a/expected/pgtap.out +++ b/expected/pgtap.out @@ -1,5 +1,5 @@ \set ECHO -1..151 +1..113 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -113,41 +113,3 @@ ok 110 - has_column(table, column) pass ok 111 - has_column(table, column) should pass for an existing column ok 112 - has_column(schema, column, desc) pass ok 113 - has_column(schema, table, column, desc) should pass for an existing view column -ok 114 - testing col_not_null( schema, table, column, desc ) -ok 115 - col_not_null( schema, table, column, desc ) should work -ok 116 - testing col_not_null( table, column, desc ) -ok 117 - col_not_null( table, column, desc ) should work -ok 118 - testing col_not_null( schema, table, column, desc ) -ok 119 - col_not_null( table, column ) should work -ok 120 - testing col_not_null( schema, table, column, desc ) -ok 121 - col_not_null( table, column ) should properly fail -ok 122 - testing col_is_null( schema, table, column, desc ) -ok 123 - col_is_null( schema, table, column, desc ) should work -ok 124 - testing col_is_null( table, column, desc ) -ok 125 - col_is_null( table, column, desc ) should work -ok 126 - testing col_is_null( schema, table, column, desc ) -ok 127 - col_is_null( table, column ) should work -ok 128 - testing col_is_null( schema, table, column, desc ) -ok 129 - col_is_null( table, column ) should properly fail -ok 130 - testing col_type_is( schema, table, column, type, desc ) -ok 131 - col_type_is( schema, table, column, type, desc ) should work -ok 132 - testing col_type_is( table, column, type, desc ) -ok 133 - col_type_is( table, column, type, desc ) should work -ok 134 - testing col_type_is( table, column, type ) -ok 135 - col_type_is( table, column, type ) should work -ok 136 - testing col_type_is( table, column, type ) case-insensitively -ok 137 - col_type_is( table, column, type ) should work case-insensitively -ok 138 - testing col_type_is( table, column, type ) failure -ok 139 - col_type_is( table, column, type ) should fail with proper diagnostics -ok 140 - testing col_type_is( schema, table, column, type(precision,scale), description ) -ok 141 - col_type_is( schema, table, column, type, precision(scale,description) should work -ok 142 - col_type_is( table, column, type, precision, desc ) fail -ok 143 - col_type_is with precision should have nice diagnostics -ok 144 - col_default_is( schema, table, column, default, description ) -ok 145 - col_default_is( schema, table, column, default, description ) should work -ok 146 - col_default_is( schema, table, column, default, description ) fail -ok 147 - ok 146 - Should get proper diagnostics for a default failure -ok 148 - col_default_is( table, column, default, description ) -ok 149 - col_default_is( table, column, default, description ) should work -ok 150 - col_default_is( table, column, default ) -ok 151 - col_default_is( table, column, default ) should work diff --git a/sql/coltap.sql b/sql/coltap.sql new file mode 100644 index 000000000000..a9a2a6aab6ad --- /dev/null +++ b/sql/coltap.sql @@ -0,0 +1,195 @@ +\set ECHO + +-- +-- Tests for pgTAP. +-- +-- +-- $Id: pgtap.sql 4201 2008-08-21 02:07:54Z david $ + +-- Format the output for nice TAP. +\pset format unaligned +\pset tuples_only true +\pset pager + +-- Create plpgsql if it's not already there. +SET client_min_messages = fatal; +CREATE LANGUAGE plpgsql; + +-- Keep things quiet. +SET client_min_messages = warning; + +-- Revert all changes on failure. +\set ON_ERROR_ROLBACK 1 +\set ON_ERROR_STOP true + +-- Load the TAP functions. +BEGIN; +\i pgtap.sql + +-- ## SET search_path TO TAPSCHEMA,public; + +-- Set the test plan. +SELECT plan(38); + +-- This will be rolled back. :-) +CREATE TABLE sometab( + id INT NOT NULL PRIMARY KEY, + name TEXT DEFAULT '', + numb NUMERIC(10, 2), + myint NUMERIC(8) +); + +/****************************************************************************/ +-- Test col_not_null(). +\echo ok 1 - testing col_not_null( schema, table, column, desc ) +SELECT is( + col_not_null( 'pg_catalog', 'pg_type', 'typname', 'typname not null' ), + 'ok 1 - typname not null', + 'col_not_null( schema, table, column, desc ) should work' +); +\echo ok 3 - testing col_not_null( table, column, desc ) +SELECT is( + col_not_null( 'sometab', 'id', 'blah blah blah' ), + 'ok 3 - blah blah blah', + 'col_not_null( table, column, desc ) should work' +); + +\echo ok 5 - testing col_not_null( schema, table, column, desc ) +SELECT is( + col_not_null( 'sometab', 'id' ), + 'ok 5 - Column sometab.id should be NOT NULL', + 'col_not_null( table, column ) should work' +); +-- Make sure failure is correct. +\echo ok 7 - testing col_not_null( schema, table, column, desc ) +SELECT is( + col_not_null( 'sometab', 'name' ), + E'not ok 7 - Column sometab.name should be NOT NULL\n# Failed test 7: "Column sometab.name should be NOT NULL"', + 'col_not_null( table, column ) should properly fail' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 7 ); + +/****************************************************************************/ +-- Test col_is_null(). +\echo ok 9 - testing col_is_null( schema, table, column, desc ) +SELECT is( + col_is_null( 'public', 'sometab', 'name', 'name is null' ), + 'ok 9 - name is null', + 'col_is_null( schema, table, column, desc ) should work' +); +\echo ok 11 - testing col_is_null( table, column, desc ) +SELECT is( + col_is_null( 'sometab', 'name', 'my desc' ), + 'ok 11 - my desc', + 'col_is_null( table, column, desc ) should work' +); + +\echo ok 13 - testing col_is_null( schema, table, column, desc ) +SELECT is( + col_is_null( 'sometab', 'name' ), + 'ok 13 - Column sometab.name should allow NULL', + 'col_is_null( table, column ) should work' +); +-- Make sure failure is correct. +\echo ok 15 - testing col_is_null( schema, table, column, desc ) +SELECT is( + col_is_null( 'sometab', 'id' ), + E'not ok 15 - Column sometab.id should allow NULL\n# Failed test 15: "Column sometab.id should allow NULL"', + 'col_is_null( table, column ) should properly fail' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 15 ); + +/****************************************************************************/ +-- Test col_type_is(). +\echo ok 17 - testing col_type_is( schema, table, column, type, desc ) +SELECT is( + col_type_is( 'public', 'sometab', 'name', 'text', 'name is text' ), + 'ok 17 - name is text', + 'col_type_is( schema, table, column, type, desc ) should work' +); + +\echo ok 19 - testing col_type_is( table, column, type, desc ) +SELECT is( + col_type_is( 'sometab', 'name', 'text', 'yadda yadda yadda' ), + 'ok 19 - yadda yadda yadda', + 'col_type_is( table, column, type, desc ) should work' +); + +\echo ok 21 - testing col_type_is( table, column, type ) +SELECT is( + col_type_is( 'sometab', 'name', 'text' ), + 'ok 21 - Column sometab.name should be type text', + 'col_type_is( table, column, type ) should work' +); + +\echo ok 23 - testing col_type_is( table, column, type ) case-insensitively +SELECT is( + col_type_is( 'sometab', 'name', 'TEXT' ), + 'ok 23 - Column sometab.name should be type TEXT', + 'col_type_is( table, column, type ) should work case-insensitively' +); + +-- Make sure failure is correct. +\echo ok 25 - testing col_type_is( table, column, type ) failure +SELECT is( + col_type_is( 'sometab', 'name', 'int4' ), + E'not ok 25 - Column sometab.name should be type int4\n# Failed test 25: "Column sometab.name should be type int4"\n# have: text\n# want: int4', + 'col_type_is( table, column, type ) should fail with proper diagnostics' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 25 ); + +/****************************************************************************/ +-- Try col_type_is() with precision. +\echo ok 27 - testing col_type_is( schema, table, column, type(precision,scale), description ) +SELECT is( + col_type_is( 'public', 'sometab', 'numb', 'numeric(10,2)', 'lol' ), + 'ok 27 - lol', + 'col_type_is( schema, table, column, type, precision(scale,description) should work' +); + +-- Check its diagnostics. +\echo ok 29 - col_type_is( table, column, type, precision, desc ) fail +SELECT is( + col_type_is( 'sometab', 'myint', 'numeric(7)', 'should be numeric(7)' ), + E'not ok 29 - should be numeric(7)\n# Failed test 29: "should be numeric(7)"\n# have: numeric(8,0)\n# want: numeric(7)', + 'col_type_is with precision should have nice diagnostics' +); + +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 29 ); + +/****************************************************************************/ +-- Test col_default_is(). + +\echo ok 31 - col_default_is( schema, table, column, default, description ) +SELECT is( + col_default_is( 'public', 'sometab', 'name', ''::text, 'name should default to empty string' ), + 'ok 31 - name should default to empty string', + 'col_default_is( schema, table, column, default, description ) should work' +); + +\echo ok 33 - col_default_is( schema, table, column, default, description ) fail +SELECT is( + col_default_is( 'public', 'sometab', 'name', 'foo'::text, 'name should default to ''foo''' ), + E'not ok 33 - name should default to ''foo''\n# Failed test 33: "name should default to ''foo''"\n# have: \n# want: foo', + 'ok 33 - Should get proper diagnostics for a default failure' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 33 ); + +\echo ok 35 - col_default_is( table, column, default, description ) +SELECT is( + col_default_is( 'sometab', 'name', ''::text, 'name should default to empty string' ), + 'ok 35 - name should default to empty string', + 'col_default_is( table, column, default, description ) should work' +); + +\echo ok 37 - col_default_is( table, column, default ) +SELECT is( + col_default_is( 'sometab', 'name', '' ), + 'ok 37 - Column sometab.name should default to ''''', + 'col_default_is( table, column, default ) should work' +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; diff --git a/sql/pgtap.sql b/sql/pgtap.sql index 39536f92b168..8d35ef2aff0e 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -25,7 +25,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 151 +\set numb_tests 113 -- ## SET search_path TO TAPSCHEMA,public; @@ -391,157 +391,6 @@ SELECT is( 'has_column(schema, table, column, desc) should pass for an existing view column' ); -/****************************************************************************/ --- Test col_not_null(). -\echo ok 114 - testing col_not_null( schema, table, column, desc ) -SELECT is( - col_not_null( 'pg_catalog', 'pg_type', 'typname', 'typname not null' ), - 'ok 114 - typname not null', - 'col_not_null( schema, table, column, desc ) should work' -); -\echo ok 116 - testing col_not_null( table, column, desc ) -SELECT is( - col_not_null( 'sometab', 'id', 'blah blah blah' ), - 'ok 116 - blah blah blah', - 'col_not_null( table, column, desc ) should work' -); - -\echo ok 118 - testing col_not_null( schema, table, column, desc ) -SELECT is( - col_not_null( 'sometab', 'id' ), - 'ok 118 - Column sometab.id should be NOT NULL', - 'col_not_null( table, column ) should work' -); --- Make sure failure is correct. -\echo ok 120 - testing col_not_null( schema, table, column, desc ) -SELECT is( - col_not_null( 'sometab', 'name' ), - E'not ok 120 - Column sometab.name should be NOT NULL\n# Failed test 120: "Column sometab.name should be NOT NULL"', - 'col_not_null( table, column ) should properly fail' -); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 120 ); - -/****************************************************************************/ --- Test col_is_null(). -\echo ok 122 - testing col_is_null( schema, table, column, desc ) -SELECT is( - col_is_null( 'public', 'sometab', 'name', 'name is null' ), - 'ok 122 - name is null', - 'col_is_null( schema, table, column, desc ) should work' -); -\echo ok 124 - testing col_is_null( table, column, desc ) -SELECT is( - col_is_null( 'sometab', 'name', 'my desc' ), - 'ok 124 - my desc', - 'col_is_null( table, column, desc ) should work' -); - -\echo ok 126 - testing col_is_null( schema, table, column, desc ) -SELECT is( - col_is_null( 'sometab', 'name' ), - 'ok 126 - Column sometab.name should allow NULL', - 'col_is_null( table, column ) should work' -); --- Make sure failure is correct. -\echo ok 128 - testing col_is_null( schema, table, column, desc ) -SELECT is( - col_is_null( 'sometab', 'id' ), - E'not ok 128 - Column sometab.id should allow NULL\n# Failed test 128: "Column sometab.id should allow NULL"', - 'col_is_null( table, column ) should properly fail' -); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 128 ); - -/****************************************************************************/ --- Test col_type_is(). -\echo ok 130 - testing col_type_is( schema, table, column, type, desc ) -SELECT is( - col_type_is( 'public', 'sometab', 'name', 'text', 'name is text' ), - 'ok 130 - name is text', - 'col_type_is( schema, table, column, type, desc ) should work' -); - -\echo ok 132 - testing col_type_is( table, column, type, desc ) -SELECT is( - col_type_is( 'sometab', 'name', 'text', 'yadda yadda yadda' ), - 'ok 132 - yadda yadda yadda', - 'col_type_is( table, column, type, desc ) should work' -); - -\echo ok 134 - testing col_type_is( table, column, type ) -SELECT is( - col_type_is( 'sometab', 'name', 'text' ), - 'ok 134 - Column sometab.name should be type text', - 'col_type_is( table, column, type ) should work' -); - -\echo ok 136 - testing col_type_is( table, column, type ) case-insensitively -SELECT is( - col_type_is( 'sometab', 'name', 'TEXT' ), - 'ok 136 - Column sometab.name should be type TEXT', - 'col_type_is( table, column, type ) should work case-insensitively' -); - --- Make sure failure is correct. -\echo ok 138 - testing col_type_is( table, column, type ) failure -SELECT is( - col_type_is( 'sometab', 'name', 'int4' ), - E'not ok 138 - Column sometab.name should be type int4\n# Failed test 138: "Column sometab.name should be type int4"\n# have: text\n# want: int4', - 'col_type_is( table, column, type ) should fail with proper diagnostics' -); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 138 ); - -/****************************************************************************/ --- Try col_type_is() with precision. -\echo ok 140 - testing col_type_is( schema, table, column, type(precision,scale), description ) -SELECT is( - col_type_is( 'public', 'sometab', 'numb', 'numeric(10,2)', 'lol' ), - 'ok 140 - lol', - 'col_type_is( schema, table, column, type, precision(scale,description) should work' -); - --- Check its diagnostics. -\echo ok 142 - col_type_is( table, column, type, precision, desc ) fail -SELECT is( - col_type_is( 'sometab', 'myint', 'numeric(7)', 'should be numeric(7)' ), - E'not ok 142 - should be numeric(7)\n# Failed test 142: "should be numeric(7)"\n# have: numeric(8,0)\n# want: numeric(7)', - 'col_type_is with precision should have nice diagnostics' -); - -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 142, 158 ); - -/****************************************************************************/ --- Test col_default_is(). - -\echo ok 144 - col_default_is( schema, table, column, default, description ) -SELECT is( - col_default_is( 'public', 'sometab', 'name', ''::text, 'name should default to empty string' ), - 'ok 144 - name should default to empty string', - 'col_default_is( schema, table, column, default, description ) should work' -); - -\echo ok 146 - col_default_is( schema, table, column, default, description ) fail -SELECT is( - col_default_is( 'public', 'sometab', 'name', 'foo'::text, 'name should default to ''foo''' ), - E'not ok 146 - name should default to ''foo''\n# Failed test 146: "name should default to ''foo''"\n# have: \n# want: foo', - 'ok 146 - Should get proper diagnostics for a default failure' -); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 146 ); - -\echo ok 148 - col_default_is( table, column, default, description ) -SELECT is( - col_default_is( 'sometab', 'name', ''::text, 'name should default to empty string' ), - 'ok 148 - name should default to empty string', - 'col_default_is( table, column, default, description ) should work' -); - -\echo ok 150 - col_default_is( table, column, default ) -SELECT is( - col_default_is( 'sometab', 'name', '' ), - 'ok 150 - Column sometab.name should default to ''''', - 'col_default_is( table, column, default ) should work' -); - - /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From feb22d86e8322bb08cf2661f6449b6dbf837a663 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 04:13:33 +0000 Subject: [PATCH 0070/1195] Well, this commit really sets svn:keywords; the previous commit actually moved a bunch of column testing function tests into `sql/coltap.sql`. But that was obvious, was it not? --- sql/coltap.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/coltap.sql b/sql/coltap.sql index a9a2a6aab6ad..438195f70a4e 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -4,7 +4,7 @@ -- Tests for pgTAP. -- -- --- $Id: pgtap.sql 4201 2008-08-21 02:07:54Z david $ +-- $Id$ -- Format the output for nice TAP. \pset format unaligned From caa89e1dca257afebb8a58d19a91e1cca1e07187 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 04:24:50 +0000 Subject: [PATCH 0071/1195] * Moved `pg_prove` to a new `bin` subdirectory. * Added `\set ON_ERROR_STOP=1` to `pg_prove`. I hadn't done this before because I needed to continue on errors in the test scripts here when they installed plpgsql when it was missing. But then I realized that I could just turn off `ON_ERROR_STOP` while I did that. And now it all works nicely. --- Changes | 1 + Makefile | 2 +- pg_prove => bin/pg_prove | 1 + sql/coltap.sql | 1 + sql/pg73.sql | 1 + sql/pgtap.sql | 1 + sql/pktap.sql | 1 + 7 files changed, 7 insertions(+), 1 deletion(-) rename pg_prove => bin/pg_prove (99%) diff --git a/Changes b/Changes index 571091166fd0..85af8ab247fc 100644 --- a/Changes +++ b/Changes @@ -35,6 +35,7 @@ Revision history for pgTAP - Added the `col_type_is()`, `col_default_is()`, and `col_is_pk()` test functions. - Fixed `is()` and `isnt()` to better handle NULLs. + - Added `--set ON_ERROR_STOP=1` to `pg_prove`. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/Makefile b/Makefile index c2f7ce6113f0..516b6b6d4c4f 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # $Id$ DATA_built = pgtap.sql drop_pgtap.sql DOCS = README.pgtap -SCRIPTS = pg_prove +SCRIPTS = bin/pg_prove REGRESS = pgtap pg73 pktap coltap ifdef USE_PGXS diff --git a/pg_prove b/bin/pg_prove similarity index 99% rename from pg_prove rename to bin/pg_prove index 62ab7dce04aa..37b193956d86 100755 --- a/pg_prove +++ b/bin/pg_prove @@ -63,6 +63,7 @@ push @command, qw( --pset pager= --pset null=[NULL] --set ON_ERROR_ROLLBACK=1 + --set ON_ERROR_STOP=1 --set QUIET=1 --file ); diff --git a/sql/coltap.sql b/sql/coltap.sql index 438195f70a4e..035a94e42afb 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -13,6 +13,7 @@ -- Create plpgsql if it's not already there. SET client_min_messages = fatal; +\set ON_ERROR_STOP off CREATE LANGUAGE plpgsql; -- Keep things quiet. diff --git a/sql/pg73.sql b/sql/pg73.sql index 2f1da0d03329..cad96def3f15 100644 --- a/sql/pg73.sql +++ b/sql/pg73.sql @@ -12,6 +12,7 @@ \pset pager -- Create plpgsql if it's not already there. SET client_min_messages = fatal; +\set ON_ERROR_STOP off CREATE LANGUAGE plpgsql; -- Keep things quiet. diff --git a/sql/pgtap.sql b/sql/pgtap.sql index 8d35ef2aff0e..bd21e913b93d 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -13,6 +13,7 @@ -- Create plpgsql if it's not already there. SET client_min_messages = fatal; +\set ON_ERROR_STOP off CREATE LANGUAGE plpgsql; -- Keep things quiet. diff --git a/sql/pktap.sql b/sql/pktap.sql index 4ab044fb4adb..89b5d9d5f05f 100644 --- a/sql/pktap.sql +++ b/sql/pktap.sql @@ -13,6 +13,7 @@ -- Create plpgsql if it's not already there. SET client_min_messages = fatal; +\set ON_ERROR_STOP off CREATE LANGUAGE plpgsql; -- Keep things quiet. From 450838f29bfb12184a97a580102f31f0239db57c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 04:26:25 +0000 Subject: [PATCH 0072/1195] A bit more accurate. --- Changes | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 85af8ab247fc..4bfa7ad7d03e 100644 --- a/Changes +++ b/Changes @@ -35,7 +35,8 @@ Revision history for pgTAP - Added the `col_type_is()`, `col_default_is()`, and `col_is_pk()` test functions. - Fixed `is()` and `isnt()` to better handle NULLs. - - Added `--set ON_ERROR_STOP=1` to `pg_prove`. + - Added the `--set ON_ERROR_STOP=1` option to the call to `psql` in + `pg_prove`. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. From 5c00fd51b10f2142e7a910fe6f7ee57f8b306646 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 04:59:53 +0000 Subject: [PATCH 0073/1195] Moved `has_*` tests to `sql/hastap.sql`. --- Makefile | 2 +- expected/hastap.out | 32 +++++++++ expected/pgtap.out | 32 +-------- sql/hastap.sql | 164 ++++++++++++++++++++++++++++++++++++++++++++ sql/pgtap.sql | 130 +---------------------------------- 5 files changed, 199 insertions(+), 161 deletions(-) create mode 100644 expected/hastap.out create mode 100644 sql/hastap.sql diff --git a/Makefile b/Makefile index 516b6b6d4c4f..d74bd70fac2a 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ DATA_built = pgtap.sql drop_pgtap.sql DOCS = README.pgtap SCRIPTS = bin/pg_prove -REGRESS = pgtap pg73 pktap coltap +REGRESS = pgtap pg73 hastap coltap pktap ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/expected/hastap.out b/expected/hastap.out new file mode 100644 index 000000000000..c257cd5b4fd2 --- /dev/null +++ b/expected/hastap.out @@ -0,0 +1,32 @@ +\set ECHO +1..30 +ok 1 - has_table(table) fail +ok 2 - has_table(table) should fail for non-existent table +ok 3 - has_table(table, desc) fail +ok 4 - has_table(table, dessc) should fail for non-existent table +ok 5 - has_table(schema, table, desc) fail +ok 6 - has_table(schema, table, desc) should fail for non-existent table +ok 7 - has_table(table, desc) pass +ok 8 - has_table(table, desc) should pass for an existing table +ok 9 - has_table(schema, table, desc) pass +ok 10 - has_table(schema, table, desc) should pass for an existing table +ok 11 - has_view(view) fail +ok 12 - has_view(view) should fail for non-existent view +ok 13 - has_view(view, desc) fail +ok 14 - has_view(view, desc) should fail for non-existent view +ok 15 - has_view(schema, view, desc) fail +ok 16 - has_view(schema, view, desc) should fail for non-existent view +ok 17 - has_view(view, desc) pass +ok 18 - has_view(view, desc) should pass for an existing view +ok 19 - has_view(schema, view, desc) pass +ok 20 - has_view(schema, view, desc) should pass for an existing view +ok 21 - has_column(table, column) fail +ok 22 - has_column(table, column) should fail for non-existent table +ok 23 - has_column(table, column, desc) fail +ok 24 - has_column(table, column, desc) should fail for non-existent table +ok 25 - has_column(schema, table, column, desc) fail +ok 26 - has_column(schema, table, column, desc) should fail for non-existent table +ok 27 - has_column(table, column) pass +ok 28 - has_column(table, column) should pass for an existing column +ok 29 - has_column(schema, column, desc) pass +ok 30 - has_column(schema, table, column, desc) should pass for an existing view column diff --git a/expected/pgtap.out b/expected/pgtap.out index 4274e0e5198c..ba20f9e67d76 100644 --- a/expected/pgtap.out +++ b/expected/pgtap.out @@ -1,5 +1,5 @@ \set ECHO -1..113 +1..83 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -83,33 +83,3 @@ ok 80 - multiline desriptions should have subsequent lines escaped ok 81 - todo fail ok 82 - todo pass ok 83 - TODO tests should display properly -ok 84 - has_table(table) fail -ok 85 - has_table(table) should fail for non-existent table -ok 86 - has_table(table, desc) fail -ok 87 - has_table(table, dessc) should fail for non-existent table -ok 88 - has_table(schema, table, desc) fail -ok 89 - has_table(schema, table, desc) should fail for non-existent table -ok 90 - has_table(table, desc) pass -ok 91 - has_table(table, desc) should pass for an existing table -ok 92 - has_table(schema, table, desc) pass -ok 93 - has_table(schema, table, desc) should pass for an existing table -ok 94 - has_view(view) fail -ok 95 - has_view(view) should fail for non-existent view -ok 96 - has_view(view, desc) fail -ok 97 - has_view(view, desc) should fail for non-existent view -ok 98 - has_view(schema, view, desc) fail -ok 99 - has_view(schema, view, desc) should fail for non-existent view -ok 100 - has_view(view, desc) pass -ok 101 - has_view(view, desc) should pass for an existing view -ok 102 - has_view(schema, view, desc) pass -ok 103 - has_view(schema, view, desc) should pass for an existing view -ok 104 - has_column(table, column) fail -ok 105 - has_column(table, column) should fail for non-existent table -ok 106 - has_column(table, column, desc) fail -ok 107 - has_column(table, column, desc) should fail for non-existent table -ok 108 - has_column(schema, table, column, desc) fail -ok 109 - has_column(schema, table, column, desc) should fail for non-existent table -ok 110 - has_column(table, column) pass -ok 111 - has_column(table, column) should pass for an existing column -ok 112 - has_column(schema, column, desc) pass -ok 113 - has_column(schema, table, column, desc) should pass for an existing view column diff --git a/sql/hastap.sql b/sql/hastap.sql new file mode 100644 index 000000000000..c56a38be69b8 --- /dev/null +++ b/sql/hastap.sql @@ -0,0 +1,164 @@ +\set ECHO + +-- +-- Tests for pgTAP. +-- +-- +-- $Id$ + +-- Format the output for nice TAP. +\pset format unaligned +\pset tuples_only true +\pset pager + +-- Create plpgsql if it's not already there. +SET client_min_messages = fatal; +\set ON_ERROR_STOP off +CREATE LANGUAGE plpgsql; + +-- Keep things quiet. +SET client_min_messages = warning; + +-- Revert all changes on failure. +\set ON_ERROR_ROLBACK 1 +\set ON_ERROR_STOP true + +-- Load the TAP functions. +BEGIN; +\i pgtap.sql + +-- ## SET search_path TO TAPSCHEMA,public; + +-- Set the test plan. +SELECT plan(30); + +-- This will be rolled back. :-) +CREATE TABLE sometab( + id INT NOT NULL PRIMARY KEY, + name TEXT DEFAULT '', + numb NUMERIC(10, 2), + myint NUMERIC(8) +); + +/****************************************************************************/ +-- Test has_table(). + +\echo ok 1 - has_table(table) fail + +SELECT is( + has_table( '__SDFSDFD__' ), + E'not ok 1 - Table __SDFSDFD__ should exist\n# Failed test 1: "Table __SDFSDFD__ should exist"', + 'has_table(table) should fail for non-existent table' +); + +\echo ok 3 - has_table(table, desc) fail +SELECT is( + has_table( '__SDFSDFD__', 'lol' ), + E'not ok 3 - lol\n# Failed test 3: "lol"', + 'has_table(table, dessc) should fail for non-existent table' +); + +\echo ok 5 - has_table(schema, table, desc) fail +SELECT is( + has_table( 'foo', '__SDFSDFD__', 'desc' ), + E'not ok 5 - desc\n# Failed test 5: "desc"', + 'has_table(schema, table, desc) should fail for non-existent table' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 1, 3, 5 ); + +\echo ok 7 - has_table(table, desc) pass +SELECT is( + has_table( 'pg_type', 'lol' ), + 'ok 7 - lol', + 'has_table(table, desc) should pass for an existing table' +); + +\echo ok 9 - has_table(schema, table, desc) pass +SELECT is( + has_table( 'pg_catalog', 'pg_type', 'desc' ), + 'ok 9 - desc', + 'has_table(schema, table, desc) should pass for an existing table' +); + +/****************************************************************************/ +-- Test has_view(). + +\echo ok 11 - has_view(view) fail +SELECT is( + has_view( '__SDFSDFD__' ), + E'not ok 11 - View __SDFSDFD__ should exist\n# Failed test 11: "View __SDFSDFD__ should exist"', + 'has_view(view) should fail for non-existent view' +); + +\echo ok 13 - has_view(view, desc) fail +SELECT is( + has_view( '__SDFSDFD__', 'howdy' ), + E'not ok 13 - howdy\n# Failed test 13: "howdy"', + 'has_view(view, desc) should fail for non-existent view' +); + +\echo ok 15 - has_view(schema, view, desc) fail +SELECT is( + has_view( 'foo', '__SDFSDFD__', 'desc' ), + E'not ok 15 - desc\n# Failed test 15: "desc"', + 'has_view(schema, view, desc) should fail for non-existent view' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 11, 13, 15 ); + +\echo ok 17 - has_view(view, desc) pass +SELECT is( + has_view( 'pg_tables', 'yowza' ), + 'ok 17 - yowza', + 'has_view(view, desc) should pass for an existing view' +); + +\echo ok 19 - has_view(schema, view, desc) pass +SELECT is( + has_view( 'information_schema', 'tables', 'desc' ), + 'ok 19 - desc', + 'has_view(schema, view, desc) should pass for an existing view' +); + +/****************************************************************************/ +-- Test has_column(). + +\echo ok 21 - has_column(table, column) fail +SELECT is( + has_column( '__SDFSDFD__', 'foo' ), + E'not ok 21 - Column __SDFSDFD__.foo should exist\n# Failed test 21: "Column __SDFSDFD__.foo should exist"', + 'has_column(table, column) should fail for non-existent table' +); + +\echo ok 23 - has_column(table, column, desc) fail +SELECT is( + has_column( '__SDFSDFD__', 'bar', 'whatever' ), + E'not ok 23 - whatever\n# Failed test 23: "whatever"', + 'has_column(table, column, desc) should fail for non-existent table' +); + +\echo ok 25 - has_column(schema, table, column, desc) fail +SELECT is( + has_column( 'foo', '__SDFSDFD__', 'bar', 'desc' ), + E'not ok 25 - desc\n# Failed test 25: "desc"', + 'has_column(schema, table, column, desc) should fail for non-existent table' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 21, 23, 25 ); + +\echo ok 27 - has_column(table, column) pass +SELECT is( + has_column( 'sometab', 'id' ), + 'ok 27 - Column sometab.id should exist', + 'has_column(table, column) should pass for an existing column' +); + +\echo ok 29 - has_column(schema, column, desc) pass +SELECT is( + has_column( 'information_schema', 'tables', 'table_name', 'desc' ), + 'ok 29 - desc', + 'has_column(schema, table, column, desc) should pass for an existing view column' +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; diff --git a/sql/pgtap.sql b/sql/pgtap.sql index bd21e913b93d..0b9f5cab4f37 100644 --- a/sql/pgtap.sql +++ b/sql/pgtap.sql @@ -26,7 +26,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 113 +\set numb_tests 83 -- ## SET search_path TO TAPSCHEMA,public; @@ -264,134 +264,6 @@ SELECT is( ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 81 ); - -/****************************************************************************/ --- Test has_table(). - -\echo ok 84 - has_table(table) fail - -SELECT is( - has_table( '__SDFSDFD__' ), - E'not ok 84 - Table __SDFSDFD__ should exist\n# Failed test 84: "Table __SDFSDFD__ should exist"', - 'has_table(table) should fail for non-existent table' -); - -\echo ok 86 - has_table(table, desc) fail -SELECT is( - has_table( '__SDFSDFD__', 'lol' ), - E'not ok 86 - lol\n# Failed test 86: "lol"', - 'has_table(table, dessc) should fail for non-existent table' -); - -\echo ok 88 - has_table(schema, table, desc) fail -SELECT is( - has_table( 'foo', '__SDFSDFD__', 'desc' ), - E'not ok 88 - desc\n# Failed test 88: "desc"', - 'has_table(schema, table, desc) should fail for non-existent table' -); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 84, 86, 88 ); - -\echo ok 90 - has_table(table, desc) pass -SELECT is( - has_table( 'pg_type', 'lol' ), - 'ok 90 - lol', - 'has_table(table, desc) should pass for an existing table' -); - -\echo ok 92 - has_table(schema, table, desc) pass -SELECT is( - has_table( 'pg_catalog', 'pg_type', 'desc' ), - 'ok 92 - desc', - 'has_table(schema, table, desc) should pass for an existing table' -); - -/****************************************************************************/ --- Test has_view(). - -\echo ok 94 - has_view(view) fail - -SELECT is( - has_view( '__SDFSDFD__' ), - E'not ok 94 - View __SDFSDFD__ should exist\n# Failed test 94: "View __SDFSDFD__ should exist"', - 'has_view(view) should fail for non-existent view' -); - -\echo ok 96 - has_view(view, desc) fail -SELECT is( - has_view( '__SDFSDFD__', 'howdy' ), - E'not ok 96 - howdy\n# Failed test 96: "howdy"', - 'has_view(view, desc) should fail for non-existent view' -); - -\echo ok 98 - has_view(schema, view, desc) fail -SELECT is( - has_view( 'foo', '__SDFSDFD__', 'desc' ), - E'not ok 98 - desc\n# Failed test 98: "desc"', - 'has_view(schema, view, desc) should fail for non-existent view' -); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 94, 96, 98 ); - -\echo ok 100 - has_view(view, desc) pass -SELECT is( - has_view( 'pg_tables', 'yowza' ), - 'ok 100 - yowza', - 'has_view(view, desc) should pass for an existing view' -); - -\echo ok 102 - has_view(schema, view, desc) pass -SELECT is( - has_view( 'information_schema', 'tables', 'desc' ), - 'ok 102 - desc', - 'has_view(schema, view, desc) should pass for an existing view' -); - -/****************************************************************************/ --- Test has_column(). - -\echo ok 104 - has_column(table, column) fail -SELECT is( - has_column( '__SDFSDFD__', 'foo' ), - E'not ok 104 - Column __SDFSDFD__.foo should exist\n# Failed test 104: "Column __SDFSDFD__.foo should exist"', - 'has_column(table, column) should fail for non-existent table' -); - -\echo ok 106 - has_column(table, column, desc) fail -SELECT is( - has_column( '__SDFSDFD__', 'bar', 'whatever' ), - E'not ok 106 - whatever\n# Failed test 106: "whatever"', - 'has_column(table, column, desc) should fail for non-existent table' -); - -\echo ok 108 - has_column(schema, table, column, desc) fail -SELECT is( - has_column( 'foo', '__SDFSDFD__', 'bar', 'desc' ), - E'not ok 108 - desc\n# Failed test 108: "desc"', - 'has_column(schema, table, column, desc) should fail for non-existent table' -); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 104, 106, 108 ); - --- This will be rolled back. :-) -CREATE TABLE sometab( - id INT NOT NULL PRIMARY KEY, - name TEXT DEFAULT '', - numb NUMERIC(10, 2), - myint NUMERIC(8) -); - -\echo ok 110 - has_column(table, column) pass -SELECT is( - has_column( 'sometab', 'id' ), - 'ok 110 - Column sometab.id should exist', - 'has_column(table, column) should pass for an existing column' -); - -\echo ok 112 - has_column(schema, column, desc) pass -SELECT is( - has_column( 'information_schema', 'tables', 'table_name', 'desc' ), - 'ok 112 - desc', - 'has_column(schema, table, column, desc) should pass for an existing view column' -); - /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 47db91c1f90c578a573d14345489c4327237b718 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 05:01:55 +0000 Subject: [PATCH 0074/1195] Changed the test script name from `pgtap.sql` to `moretap.sql`. Better. I will likely add a `simpletap.sql`. :-) --- Makefile | 2 +- expected/{pgtap.out => moretap.out} | 0 sql/{pgtap.sql => moretap.sql} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename expected/{pgtap.out => moretap.out} (100%) rename sql/{pgtap.sql => moretap.sql} (100%) diff --git a/Makefile b/Makefile index d74bd70fac2a..edf2cb13319b 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ DATA_built = pgtap.sql drop_pgtap.sql DOCS = README.pgtap SCRIPTS = bin/pg_prove -REGRESS = pgtap pg73 hastap coltap pktap +REGRESS = moretap pg73 hastap coltap pktap ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/expected/pgtap.out b/expected/moretap.out similarity index 100% rename from expected/pgtap.out rename to expected/moretap.out diff --git a/sql/pgtap.sql b/sql/moretap.sql similarity index 100% rename from sql/pgtap.sql rename to sql/moretap.sql From 12672648cf73281b87982173185a5a3b970e2591 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 05:03:08 +0000 Subject: [PATCH 0075/1195] Rollback! --- sql/pg73.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/pg73.sql b/sql/pg73.sql index cad96def3f15..44b8cc8087ad 100644 --- a/sql/pg73.sql +++ b/sql/pg73.sql @@ -73,4 +73,4 @@ select is(3.2::float8, 3.2::float8, '3.2=3.2 float8'); select isnt(3.2::float8, 4.5::float8, '3.2!=4.5 float8'); select * from finish(); - +ROLLBACK; From cb2b7a846c7b187edd3096fe27ee52ae29e93ab5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 19:27:05 +0000 Subject: [PATCH 0076/1195] * Thoroughly proof-read and revised the documentation, correcting content and formatting errors. It now outputs a nice html using the "discount" markdown utility. * Added the `QUIET` setting to the test scripts. * Renamed `drop_pgtap.sql.in` to `uninstall_pgtap.sql.in`. * Restored the `test` make target. * Removed the requirement that `has_pk()` be checking against a table. Maybe it will work for views, too? --- Changes | 2 + Makefile | 6 +- README.pgtap | 396 +++++++++++--------- pgtap.sql.in | 2 - sql/coltap.sql | 1 + sql/hastap.sql | 1 + sql/moretap.sql | 1 + sql/pg73.sql | 1 + sql/pktap.sql | 1 + drop_pgtap.sql.in => uninstall_pgtap.sql.in | 0 10 files changed, 230 insertions(+), 181 deletions(-) rename drop_pgtap.sql.in => uninstall_pgtap.sql.in (100%) diff --git a/Changes b/Changes index 4bfa7ad7d03e..d677d640e504 100644 --- a/Changes +++ b/Changes @@ -37,6 +37,8 @@ Revision history for pgTAP - Fixed `is()` and `isnt()` to better handle NULLs. - Added the `--set ON_ERROR_STOP=1` option to the call to `psql` in `pg_prove`. + - Renamed `drop_pgtap.sql.in` to `uninstall_pgtap.sql.in`, which is more + in line with typical PostgreSQL contrib modules. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/Makefile b/Makefile index edf2cb13319b..30192713f8b2 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ # $Id$ -DATA_built = pgtap.sql drop_pgtap.sql +DATA_built = pgtap.sql uninstall_pgtap.sql DOCS = README.pgtap SCRIPTS = bin/pg_prove REGRESS = moretap pg73 hastap coltap pktap @@ -23,3 +23,7 @@ ifdef TAPSCHEMA else cp $< $@ endif + +# In addition to installcheck, one can also run the tests through pg_prove. +test: + ./bin/pg_prove sql/*.sql diff --git a/README.pgtap b/README.pgtap index d498d0a1ea3a..7aa6a4e5314f 100644 --- a/README.pgtap +++ b/README.pgtap @@ -11,32 +11,32 @@ Installation For the impatient, to install pgTAP into a PostgreSQL database, just do this: make - make test + make test PGDATABASE=template1 make install make installcheck If you encounter an error such as: - "Makefile", line 8: Need an operator + "Makefile", line 8: Need an operator You need to use GNU make, which may well be installed on your system as 'gmake': gmake - gmake test + gmake test PGDATABASE=template1 gmake install gmake installcheck If you encounter an error such as: - make: pg_config: Command not found + make: pg_config: Command not found -Be sure that you have pg_config installed and in your path. If you used a +Be sure that you have `pg_config` installed and in your path. If you used a package management system such as RPM to install PostgreSQL, be sure that the --devel package is also installed. If necessary, add the path to pg_config to -your `$PATH` environment variable: +`-devel` package is also installed. If necessary, add the path to `pg_config` +to your `$PATH` environment variable: - env PATH=$PATH:/path/to/pgsql/bin make && make test && make install + env PATH=$PATH:/path/to/pgsql/bin make && make test && make install And finally, if all that fails, copy the entire distribution directory to the `contrib/` subdirectory of the PostgreSQL source code and try it there. @@ -47,85 +47,128 @@ of the schema you'd like, for example: make TAPSCHEMA=tap -The test target uses the included pg_prove script to do its testing. It -supports a number of environment variables that you might need to use, -including all the usual PostgreSQL client environment variables: +The `test` target uses the included `pg_prove` Perl program to do its testing, +which requires that TAP::Harness, included in +[Test::Harness](http://search.cpan.org/dist/Test-Harness/ "Test::Harness on CPAN") 3.x. `pg_prove` supports a number of environment variables that you +might need to use, including all the usual PostgreSQL client environment +variables: -* PGDATABASE -* PGHOST -* PGPORT -* PGUSER +* `$PGDATABASE` +* `$PGHOST` +* `$PGPORT` +* `$PGUSER` Once pgTAP has been built and tested, you can install it into a database: - psql dbname -f pgtap.sql + psql -d dbname -f pgtap.sql If you want pgTAP to be available to all new databases, install it into the -template1 database: +"template1" database: - psql template1 -f pgtap.sql + psql -d template1 -f pgtap.sql -If you want to remove pgTAP from a database, run the drop_pgtap.sql script: +If you want to remove pgTAP from a database, run the `uninstall_pgtap.sql` +script: - psql dbname -f drop_pgtap.sql + psql -d dbname -f uninstall_pgtap.sql Both scripts will also be installed in the `contrib` directory under the -directory output by `pg_config --sharedir`. So you can always do this, -as well: +directory output by `pg_config --sharedir`. So you can always do this: - psql template1 -f `pg_config --sharedir`/contrib/pgtap.sql + psql -d template1 -f `pg_config --sharedir`/contrib/pgtap.sql But do be aware that, if you've specified a schema using `$TAPSCHEMA`, it will -always be created and the functions placed in it. +always be created and the pgTAP functions placed in it. Running pgTAP Tests =================== -You can also distribute pgtap.sql with any PostgreSQL distribution, such as a -custom data type. For such a case, if your users want to run your test suite, -just be sure to start a transaction, load the functions in your test.sql file, -and then rollback the transaction at the end of the script. Here's an example: +You can also distribute `pgtap.sql` with any PostgreSQL distribution, such as +a custom data type. For such a case, if your users want to run your test suite +using PostgreSQL's standard `installcheck` make target, just be sure to set +varibles to keep the tests quiet, start a transaction, load the functions in +your test script, and then rollback the transaction at the end of the script. +Here's an example: + + \set ECHO + \set QUIET 1 + -- Turn off echo and keep things quiet. + + -- Format the output for nice TAP. + \pset format unaligned + \pset tuples_only true + \pset pager + + -- Revert all changes on failure. + \set ON_ERROR_ROLBACK 1 + \set ON_ERROR_STOP true + \set QUIET 1 -- Load the TAP functions. BEGIN; + SET client_min_messages = warning; \i pgtap.sql - -- Plan the tests. - SELECT plan(1); + -- Plan the tests. + SELECT plan(1); - -- Run the tests. - SELECT pass( 'My test passed, w00t!' ); + -- Run the tests. + SELECT pass( 'My test passed, w00t!' ); - -- Finish the tests and clean up. - SELECT * FROM finish(); - ROLLBACK; + -- Finish the tests and clean up. + SELECT * FROM finish(); + ROLLBACK; Of course, if you already have the pgTAP functions in your testing database, -you should skip `\i pgtap.sql` at the beginning of the script. +you should skip `\i pgtap.sql` at the beginning of the script. If your +database does not already have PL/pgSQL installed, you'll need to add it to +the database before you load pgTAP. Just add these lines after the `\pset` +section and before the `\set` section: + + -- Create plpgsql if it's not already there. + SET client_min_messages = fatal; + \set ON_ERROR_STOP off + CREATE LANGUAGE plpgsql; Now you're ready to run your test script! - % psql -d try -AtP pager= -v ON_ERROR_ROLLBACK=1 -f test.sql + % psql -d try -Xf test.sql 1..1 ok 1 - My test passed, w00t! -You'll need to have all of those switches there to ensure that the output is -proper TAP and that all changes are reversed -- including the loading of the -test functions -- in the event of an uncaught exception. Or you can specify -them in the test file, like so: - - \set ON_ERROR_ROLBACK 1 - \pset format unaligned - \pset tuples_only - \pset pager +You'll need to have all of those variables in the script to ensure that the +output is proper TAP and that all changes are rolled back -- including the +loading of the test functions -- in the event of an uncaught exception. Or save yourself some effort -- and run a batch of tests scripts at once -- by -using pg_prove: +using `pg_prove`. If you're not relying on `installcheck`, your test scripts +can be a lot less verbose; you don't need to set all the extra variables, +because `pg_prove` takes care of that for you: + + BEGIN; + SET client_min_messages = warning; + + -- Plan the tests. + SELECT plan(1); - % ./pg_prove -d try test_*.sql - test........ok + -- Run the tests. + SELECT pass( 'My test passed, w00t!' ); + + -- Finish the tests and clean up. + SELECT * FROM finish(); + ROLLBACK; + +Now run the tests. Here's what it looks like when the pgTAP tests are run with +`pg_prove`: + + % pg_prove -d try sql/*.sql + sql/coltap.....ok + sql/hastap.....ok + sql/moretap....ok + sql/pg73.......ok + sql/pktap......ok All tests successful. - Files=1, Tests=1, 0 wallclock secs ( 0.03 usr 0.00 sys + 0.02 cusr 0.01 csys = 0.06 CPU) + Files=5, Tests=216, 1 wallclock secs ( 0.06 usr 0.02 sys + 0.08 cusr 0.07 csys = 0.23 CPU) Result: PASS Yep, that's all there is to it. @@ -156,7 +199,7 @@ There are rare cases when you will not know beforehand how many tests your script is going to run. In this case, you can declare that you have no plan. (Try to avoid using this as it weakens your test.) - SELECT * FROM no_plan(); + SELECT * FROM no_plan(); Often, though, you'll be able to calculate the number of tests, like so: @@ -195,15 +238,15 @@ that you use it. I'm ok, you're not ok --------------------- -The basic purpose of pgTAP -- and of any TAP-emitting test framework, for that -matter -- is to print out either "ok #" or "not ok #", depending on whether a +The basic purpose of pgTAP--and of any TAP-emitting test framework, for that +matter--is to print out either "ok #" or "not ok #", depending on whether a given test succeeded or failed. Everything else is just gravy. All of the following functions return "ok" or "not ok" depending on whether the test succeeded or failed. -### ok( boolean, description ) ### -### ok( boolean ) ### +### `ok( boolean, description )` ### +### `ok( boolean )` ### SELECT ok( :this = :that, :description ); @@ -213,10 +256,10 @@ true expression passes, a false one fails. Very simple. For example: - SELECT ok( 9 ^ 2 = 81, 'simple exponential' ); - SELECT ok( 9 < 10, 'simple comparison' ); - SELECT ok( 'foo' ~ '^f', 'simple regex' ); - SELECT ok( active = 1, 'widget active' ) + SELECT ok( 9 ^ 2 = 81, 'simple exponential' ); + SELECT ok( 9 < 10, 'simple comparison' ); + SELECT ok( 'foo' ~ '^f', 'simple regex' ); + SELECT ok( active = true, name || widget active' ) FROM widgets; (Mnemonic: "This is ok.") @@ -231,10 +274,10 @@ Should an `ok()` fail, it will produce some diagnostics: not ok 18 - sufficient mucus # Failed test 18: "sufficient mucus" -### is( anyelement, anyelement, description ) ### -### is( anyelement, anyelement ) ### -### isnt( anyelement, anyelement, description ) ### -### isnt( anyelement, anyelement ) ### +### `is( anyelement, anyelement, description )` ### +### `is( anyelement, anyelement )` ### +### `isnt( anyelement, anyelement, description )` ### +### `isnt( anyelement, anyelement )` ### SELECT is( :this, :that, :description ); SELECT isnt( :this, :that, :description ); @@ -256,9 +299,9 @@ are similar to these: (Mnemonic: "This is that." "This isn't that.") -`NULL`s are not treated as unknowns by `is()` or `isnt()`. That is, if `:this` -and `:that` are both `NULL`, the test will pass, and if only one of them is -`NULL`, the test will fail. +*Note:* `NULL`s are not treated as unknowns by `is()` or `isnt()`. That is, if +`:this` and `:that` are both `NULL`, the test will pass, and if only one of +them is `NULL`, the test will fail. So why use these? They produce better diagnostics on failure. `ok()` cannot know what you are testing for (beyond the description), but `is()` and @@ -276,21 +319,10 @@ Will produce something like this: So you can figure out what went wrong without re-running the test. -You are encouraged to use `is()` and `isnt()` over `ok()` where possible, -however do not be tempted to use them to find out if something is true or -false! +You are encouraged to use `is()` and `isnt()` over `ok()` where possible. - -- XXX BAD! - SELECT is( is_valid(9), TRUE, 'A tree grows in Brooklyn' ); - -This does not check if `is_valid(9)` is true, it checks if it *returns* -`TRUE`. Very different. Similar caveats exist for `FALSE`. In these cases, use -`ok()`. - - SELECT ok( is_valid(9), 'A tree grows in Brooklyn' ); - -### matches( anyelement, regex, description ) ### -### matches( anyelement, regex ) ### +### `matches( anyelement, regex, description )` ### +### `matches( anyelement, regex )` ### SELECT matches( :this, '^that', :description ); @@ -309,33 +341,33 @@ is similar to: Its advantages over `ok()` are similar to that of `is()` and `isnt()`: Better diagnostics on failure. -### imatches( anyelement, regex, description ) ### -### imatches( anyelement, regex ) ### +### `imatches( anyelement, regex, description )` ### +### `imatches( anyelement, regex )` ### SELECT imatches( :this, '^that', :description ); -These are just like `matches()` except that the regular expression is -case-insensitive. +These are just like `matches()` except that the regular expression is compared +to `:this` case-insensitively. -### doesnt_match( anyelement, regex, description ) ### -### doesnt_match( anyelement, regex ) ### -### doesnt_imatch( anyelement, regex, description ) ### -### doesnt_imatch( anyelement, regex ) ### +### `doesnt_match( anyelement, regex, description )` ### +### `doesnt_match( anyelement, regex )` ### +### `doesnt_imatch( anyelement, regex, description )` ### +### `doesnt_imatch( anyelement, regex )` ### SELECT doesnt_match( :this, '^that', :description ); -These functions work exactly as `matches()` and `imataches()` do, only they +These functions work exactly as `matches()` and `imatches()` do, only they check if `:this` *does not* match the given pattern. -### alike( anyelement, pattern, description ) ### -### alike( anyelement, pattern ) ### -### ialike( anyelement, pattern, description ) ### -### ialike( anyelement, pattern ) ### +### `alike( anyelement, pattern, description )` ### +### `alike( anyelement, pattern )` ### +### `ialike( anyelement, pattern, description )` ### +### `ialike( anyelement, pattern )` ### SELECT alike( :this, 'that%', :description ); -Similar to `ok()`, `alike()` matches `:this` against the SQL `LIKE` pattern -'that%'. `ialike()` matches case-insensitively. +Similar to `matches()`, `alike()` matches `:this` against the SQL `LIKE` +pattern 'that%'. `ialike()` matches case-insensitively. So this: @@ -350,20 +382,20 @@ is similar to: Its advantages over `ok()` are similar to that of `is()` and `isnt()`: Better diagnostics on failure. -### unalike( anyelement, pattern, description ) ### -### unalike( anyelement, pattern ) ### -### unialike( anyelement, pattern, description ) ### -### unialike( anyelement, pattern ) ### +### `unalike( anyelement, pattern, description )` ### +### `unalike( anyelement, pattern )` ### +### `unialike( anyelement, pattern, description )` ### +### `unialike( anyelement, pattern )` ### SELECT unalike( :this, 'that%', :description ); Works exactly as `alike()`, only it checks if `:this` *does not* match the given pattern. -### pass( description ) ### -### pass() ### -### fail( description ) ### -### fail() ### +### `pass( description )` ### +### `pass()` ### +### `fail( description )` ### +### `fail()` ### SELECT pass( :description ); SELECT fail( :description ); @@ -383,9 +415,9 @@ Or maybe you want to make sure a query *does not* trigger an error. For such cases, we provide a couple of test functions to make sure your queries are as error-prone as you think they should be. -### throws_ok( text, err_code, description ) ### -### throws_ok( text, err_code ) ### -### throws_ok( text ) ### +### `throws_ok( text, errcode, description )` ### +### `throws_ok( text, errcode )` ### +### `throws_ok( text )` ### SELECT throws_ok( 'INSERT INTO try (id) VALUES (1)', @@ -401,12 +433,12 @@ The first argument should be a string representing the query to be executed. catch any exception. The second argument should be an exception error code, which is a -five-character string (it happens to consist only of numbers and you pass it -as an integer, it will still work). If this value is not `NULL`, `throws_ok()` -will check the thrown exception to ensure that it is the expected exception. -For a complete list of error codes, see [Appendix -A.](http://www.postgresql.org/docs/current/static/errcodes-appendix.html) in -the [PostgreSQL +five-character string (if it happens to consist only of numbers and you pass +it as an integer, it will still work). If this value is not `NULL`, +`throws_ok()` will check the thrown exception to ensure that it is the +expected exception. For a complete list of error codes, see [Appendix +A.](http://www.postgresql.org/docs/current/static/errcodes-appendix.html +"Appendix A. PostgreSQL Error Codes") in the [PostgreSQL documentation](http://www.postgresql.org/docs/current/static/). The third argument is of course a brief test description. @@ -421,8 +453,8 @@ For example: Idea borrowed from the Test::Exception Perl module. -### lives_ok( text, err_code ) ### -### lives_ok( text ) ### +### `lives_ok( text, description )` ### +### `lives_ok( text )` ### SELECT lives_ok( 'INSERT INTO try (id) VALUES (1)', @@ -445,9 +477,9 @@ A Wicked Schema Need to make sure that your database is designed just the way you think it should be? Use these test functions and rest easy. -### has_table( schema, table, description ) ### -### has_table( table, description ) ### -### has_table( table ) ### +### `has_table( schema, table, description )` ### +### `has_table( table, description )` ### +### `has_table( table )` ### SELECT has_table( 'myschema', @@ -455,15 +487,15 @@ should be? Use these test functions and rest easy. 'I got myschema.sometable' ); -This function tests whether or not a table exist in the database. The first +This function tests whether or not a table exists in the database. The first argument is a schema name, the second is a table name, and the third is the test description. If you omit the schema, the table must be visible in the -search path. If you omit the test description, it will be set to "Table :table -should exist". +search path. If you omit the test description, it will be set to "Table +`:table` should exist". -### has_view( schema, view, description ) ### -### has_view( view, description ) ### -### has_view( view ) ### +### `has_view( schema, view, description )` ### +### `has_view( view, description )` ### +### `has_view( view )` ### SELECT has_view( 'myschema', @@ -473,9 +505,9 @@ should exist". Just like `has_table()`, only it tests for the existence of a view. -### has_column( schema, table, column, description ) ### -### has_column( table, column, description ) ### -### has_column( table, column ) ### +### `has_column( schema, table, column, description )` ### +### `has_column( table, column, description )` ### +### `has_column( table, column )` ### SELECT has_column( 'myschema', @@ -488,13 +520,11 @@ Tests whether or not a column exists in a given table or view. The first argument is the schema name, the second the table name, the third the column name, and the fourth is the test description. If the schema is omitted, the table must be visible in the search path. If the test description is omitted, -it will be set to "Column :table.:column should exist". - -Just like `has_table()`, only it tests for the existence of a column. +it will be set to "Column `:table`.`:column` should exist". -### col_not_null( schema, table, column, description ) ### -### col_not_null( table, column, description ) ### -### col_not_null( table, column ) ### +### `col_not_null( schema, table, column, description )` ### +### `col_not_null( table, column, description )` ### +### `col_not_null( table, column )` ### SELECT col_not_null( 'myschema', @@ -507,12 +537,12 @@ Tests whether the specified column has a `NOT NULL` constraint. The first argument is the schema name, the second the table name, the third the column name, and the fourth is the test description. If the schema is omitted, the table must be visible in the search path. If the test description is omitted, -it will be set to "Column :table.:column should be NOT NULL". Note that this -test will fail if the column in question does not exist. +it will be set to "Column `:table`.`:column` should be NOT NULL". Note that +this test will fail if the table or column in question does not exist. -### col_is_null( schema, table, column, description ) ### -### col_is_null( table, column, description ) ### -### col_is_null( table, column ) ### +### `col_is_null( schema, table, column, description )` ### +### `col_is_null( table, column, description )` ### +### `col_is_null( table, column )` ### SELECT col_is_null( 'myschema', @@ -526,12 +556,12 @@ column does not have a `NOT NULL` constraint. The first argument is the schema name, the second the table name, the third the column name, and the fourth is the test description. If the schema is omitted, the table must be visible in the search path. If the test description is omitted, it will be set to "Column -:table.:column should allow NULL". Note that this test will fail if the column -in question does not exist. +`:table`.`:column` should allow NULL". Note that this test will fail if the +table or column in question does not exist. -### col_type_is( schema, table, column, type, description ) ### -### col_type_is( table, column, type, description ) ### -### col_type_is( table, column, type ) ### +### `col_type_is( schema, table, column, type, description )` ### +### `col_type_is( table, column, type, description )` ### +### `col_type_is( table, column, type )` ### SELECT col_type_is( 'myschema', @@ -546,8 +576,9 @@ fails, it will emit diagnostics naming the actual type. The first argument is the schema name, the second the table name, the third the column name, the fourth the type, and the fifth is the test description. If the schema is omitted, the table must be visible in the search path. If the test description -is omitted, it will be set to "Column :table.:column should be type $type". -Note that this test will fail if the column in question does not exist. +is omitted, it will be set to "Column `:table`.`:column` should be type +`:type`". Note that this test will fail if the table or column in question +does not exist. The type argument should be formatted as it would be displayed in the view of a table usin the `\d` command in `psql`. For example, if you have a numeric @@ -565,9 +596,9 @@ Will produce something like this: # have: name # want: text -### col_default_is( schema, table, column, default, description ) ### -### col_default_is( table, column, default, description ) ### -### col_default_is( table, column, type ) ### +### `col_default_is( schema, table, column, default, description )` ### +### `col_default_is( table, column, default, description )` ### +### `col_default_is( table, column, type )` ### SELECT col_default_is( 'myschema', @@ -582,19 +613,25 @@ showing the actual default value. The first argument is the schema name, the second the table name, the third the column name, the fourth the default value, and the fifth is the test description. If the schema is omitted, the table must be visible in the search path. If the test description is omitted, -it will be set to "Column :table.:column should default to :default". Note -that this test will fail if the column in question does not exist. +it will be set to "Column `:table`.`:column` should default to `:default`". +Note that this test will fail if the table or column in question does not +exist. The default argument must have an unambiguous type in order for the call to succeed. If you see an error such as 'ERROR: could not determine polymorphic type because input has type "unknown"', it's because you forgot to cast the expected value to its proper type. IOW, this will fail: - SELECT col_is_default( 'tab', 'name', 'foo' ); + SELECT col_default_is( 'tab', 'name', 'foo' ); But this will not: - SELECT col_is_default( 'tab', 'name', 'foo'::text ); + SELECT col_default_is( 'tab', 'name', 'foo'::text ); + +You can also test for functional defaults. Just specify the function call as a +string: + + SELECT col_default_is( 'user', 'created_at', 'now()' ); If the test fails, it will output useful diagnostics. For example, this test: @@ -612,9 +649,9 @@ Will produce something like this: # have: NULL # want: foo -### has_pk( schema, table, description ) ### -### has_pk( table, description ) ### -### has_pk( table ) ### +### `has_pk( schema, table, description )` ### +### `has_pk( table, description )` ### +### `has_pk( table )` ### SELECT has_pk( 'myschema', @@ -625,16 +662,16 @@ Will produce something like this: Tests whether or not a table has a primary key. The first argument is the schema name, the second the table name, the the third is the test description. If the schema is omitted, the table must be visible in the search path. If the -test description is omitted, it will be set to "Table :table should have a +test description is omitted, it will be set to "Table `:table` should have a primary key". Note that this test will fail if the table in question does not exist. -### col_is_pk( schema, table, column, description ) ### -### col_is_pk( schema, table, column[], description ) ### -### col_is_pk( table, column, description ) ### -### col_is_pk( table, column[], description ) ### -### col_is_pk( table, column ) ### -### col_is_pk( table, column[] ) ### +### `col_is_pk( schema, table, column, description )` ### +### `col_is_pk( schema, table, column[], description )` ### +### `col_is_pk( table, column, description )` ### +### `col_is_pk( table, column[], description )` ### +### `col_is_pk( table, column )` ### +### `col_is_pk( table, column[] )` ### SELECT col_is_pk( 'myschema', @@ -654,8 +691,8 @@ primary key columns, if any. The first argument is the schema name, the second the table name, the third the column name or an array of column names, and the fourth is the test description. If the schema is omitted, the table must be visible in the search path. If the test description is omitted, it will be set -to "Column :table.:column should be a primary key". Note that this test will -fail if the column in question does not exist. +to "Column `:table`.`:column` should be a primary key". Note that this test +will fail if the table or column in question does not exist. If the test fails, it will output useful diagnostics. For example this test: @@ -673,9 +710,9 @@ Diagnostics If you pick the right test function, you'll usually get a good idea of what went wrong when it failed. But sometimes it doesn't work out that way. So here we have ways for you to write your own diagnostic messages which are safer -than just \echo. +than just `\echo` or `SELECT foo`. -### diag( text ) ### +### `diag( text )` ### Returns a diagnostic message which is guaranteed not to interfere with test output. Handy for this sort of thing: @@ -693,18 +730,18 @@ test output. Handy for this sort of thing: Which would produce: # These tests expect LC_COLLATE to be en_US.UTF-8, - # but yours is set to en_US.UTF-8. + # but yours is set to en_US.ISO8859-1. # As a result, some tests may fail. YMMV. Conditional Tests ----------------- Sometimes you might have tests that you want to pass, but you haven't gotten -'round to implementing the logic required to make them pass. pgTAP allows you +around to implementing the logic required to make them pass. pgTAP allows you to declare that such tests are supposed to fail but will work in the future. This is known as a "todo test." -### todo( why, how_many ) ### +### `todo( why, how_many )` ### Declares a series of tests that you expect to fail and why. Perhaps it's because you haven't fixed a bug or haven't finished a new feature: @@ -715,11 +752,12 @@ because you haven't fixed a bug or haven't finished a new feature: SELECT is( URIGeller.yourCard(), :card, 'Is THIS your card?' ); SELECT is( URIGeller.bendSpoon(), 'bent', 'Spoon bending, how original' ); -With `todo()`, `how_any` specifies how many tests are expected to fail. pgTAP -will run the tests normally, but print out special flags indicating they are -"todo" tests. The test harness will interpret these failures as ok. Should any -todo test pass, the harness will report it as an unexpected success. You then -know that the thing you had todo is done and can remove the call to `todo()`. +With `todo()`, `:how_many` specifies how many tests are expected to fail. +pgTAP will run the tests normally, but print out special flags indicating they +are "todo" tests. The test harness will interpret these failures as ok. Should +any todo test pass, the harness will report it as an unexpected success. You +then know that the thing you had todo is done and can remove the call to +`todo()`. The nice part about todo tests, as opposed to simply commenting out a block of tests, is that they're like a programmatic todo list. You know how much work @@ -729,11 +767,13 @@ immediately when they're fixed. To Do ----- -* Update the Makefile to process pg_prove and set the proper path to Perl on - the shebang line. Will likely require a `$(PERL)` environment variable. -* Update Makefile to require TAP::Harness. * Add a test function to test test functions and update the test script to use it. +* Update the Makefile to process pg_prove and set the proper path to Perl on + the shebang line. Will likely require a `$(PERL)` environment variable. +* Update the Makefile to require TAP::Harness. +* Update the Makefile to alter the source as appropriate for older versions + of PostgreSQL. Suported Versions ----------------- @@ -743,11 +783,11 @@ pgTAP has been tested on the following builds of PostgreSQL: * PostgreSQL 8.3.1 on i386-apple-darwin9.2.2 If you know of others, please submit them! Use -`psql template1 -c 'SELECT VERSION()'` to get the formal build version and OS. +`psql -d template1 -c 'SELECT VERSION()'` to get the formal build version and OS. Author ------ -David E. Wheeler +[David E. Wheeler](http://justatheory.com/) Credits ------- diff --git a/pgtap.sql.in b/pgtap.sql.in index d7705cbb95e0..ec646362bbcd 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -738,7 +738,6 @@ CREATE OR REPLACE FUNCTION has_pk ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ pg_catalog.pg_constraint x WHERE n.oid = c.relnamespace AND c.oid = x.conrelid - AND c.relkind = 'r' AND n.nspname = $1 AND c.relname = $2 AND c.relhaspkey = true @@ -755,7 +754,6 @@ CREATE OR REPLACE FUNCTION has_pk ( TEXT, TEXT ) RETURNS TEXT AS $$ SELECT true FROM pg_catalog.pg_class c, pg_catalog.pg_constraint x WHERE c.oid = x.conrelid - AND c.relkind = 'r' AND pg_catalog.pg_table_is_visible(c.oid) AND c.relname = $1 AND c.relhaspkey = true diff --git a/sql/coltap.sql b/sql/coltap.sql index 035a94e42afb..f10433277257 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -1,4 +1,5 @@ \set ECHO +\set QUIET 1 -- -- Tests for pgTAP. diff --git a/sql/hastap.sql b/sql/hastap.sql index c56a38be69b8..287adee1505f 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -1,4 +1,5 @@ \set ECHO +\set QUIET 1 -- -- Tests for pgTAP. diff --git a/sql/moretap.sql b/sql/moretap.sql index 0b9f5cab4f37..f98bd1e4b535 100644 --- a/sql/moretap.sql +++ b/sql/moretap.sql @@ -1,4 +1,5 @@ \set ECHO +\set QUIET 1 -- -- Tests for pgTAP. diff --git a/sql/pg73.sql b/sql/pg73.sql index 44b8cc8087ad..ae610401ebae 100644 --- a/sql/pg73.sql +++ b/sql/pg73.sql @@ -1,4 +1,5 @@ \set ECHO +\set QUIET 1 -- -- Tests for pgTAP on PostgreSQL 7.3. diff --git a/sql/pktap.sql b/sql/pktap.sql index 89b5d9d5f05f..8b83e0d45ac2 100644 --- a/sql/pktap.sql +++ b/sql/pktap.sql @@ -1,4 +1,5 @@ \set ECHO +\set QUIET 1 -- -- Tests for pgTAP. diff --git a/drop_pgtap.sql.in b/uninstall_pgtap.sql.in similarity index 100% rename from drop_pgtap.sql.in rename to uninstall_pgtap.sql.in From d1f34502932a424c87f0ac069e68d16e94581f15 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 21:09:34 +0000 Subject: [PATCH 0077/1195] Duh. Thanks Andy. --- Changes | 2 ++ bin/pg_prove | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index d677d640e504..0e30bdc12b77 100644 --- a/Changes +++ b/Changes @@ -39,6 +39,8 @@ Revision history for pgTAP `pg_prove`. - Renamed `drop_pgtap.sql.in` to `uninstall_pgtap.sql.in`, which is more in line with typical PostgreSQL contrib modules. + - Removed verbose output of the `psql` command from `pg_prove`. Thanks + to Andy Armstrong for the spot. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/bin/pg_prove b/bin/pg_prove index 37b193956d86..fbacb2142d28 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -68,8 +68,6 @@ push @command, qw( --file ); -print join ' ', @command, $/; - TAP::Harness->new({ verbosity => $opts->{verbose} || $ENV{TEST_VERBOSE}, timer => $opts->{timer}, From 3882d414fa210e033c1e220101792639b2f48017 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 21:29:49 +0000 Subject: [PATCH 0078/1195] Moved `throws_ok()` and `lives_ok()` tests into their own test script. --- Makefile | 2 +- expected/throwtap.out | 13 +++++++ sql/moretap.sql | 7 ++-- sql/throwtap.sql | 87 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 expected/throwtap.out create mode 100644 sql/throwtap.sql diff --git a/Makefile b/Makefile index 30192713f8b2..ad104b1f73dc 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ DATA_built = pgtap.sql uninstall_pgtap.sql DOCS = README.pgtap SCRIPTS = bin/pg_prove -REGRESS = moretap pg73 hastap coltap pktap +REGRESS = moretap pg73 throwtap hastap coltap pktap ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/expected/throwtap.out b/expected/throwtap.out new file mode 100644 index 000000000000..73240e1bf1f7 --- /dev/null +++ b/expected/throwtap.out @@ -0,0 +1,13 @@ +\set ECHO +1..11 +ok 1 - throws_ok(1/0) should work +ok 2 - throws_ok failure diagnostics +ok 3 - We should get the proper diagnostics from throws_ok() +ok 4 - throws_ok(1/0, NULL) should work +ok 5 - throws_ok failure diagnostics +ok 6 - We should get the proper diagnostics from throws_ok() with a NULL error code +ok 7 - lives_ok() should work +ok 8 - lives_ok failure diagnostics +ok 9 - We should get the proper diagnostics for a lives_ok() failure +ok 10 - lives_ok is ok +ok 11 - multiline desriptions should have subsequent lines escaped diff --git a/sql/moretap.sql b/sql/moretap.sql index f98bd1e4b535..843ec6e6d379 100644 --- a/sql/moretap.sql +++ b/sql/moretap.sql @@ -257,10 +257,9 @@ SELECT is( \echo ok 82 - todo pass SELECT * FROM todo('just because', 2 ); SELECT is( - fail('This is a todo test' ) - || pass('This is a todo test that unexpectedly passes' ), - 'not ok 81 - This is a todo test # TODO just because -# Failed (TODO) test 81: "This is a todo test"ok 82 - This is a todo test that unexpectedly passes # TODO just because', + fail('This is a todo test' ) || E'\n' + || pass('This is a todo test that unexpectedly passes' ), + E'not ok 81 - This is a todo test # TODO just because\n# Failed (TODO) test 81: "This is a todo test"\nok 82 - This is a todo test that unexpectedly passes # TODO just because', 'TODO tests should display properly' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 81 ); diff --git a/sql/throwtap.sql b/sql/throwtap.sql new file mode 100644 index 000000000000..acd480f2c70f --- /dev/null +++ b/sql/throwtap.sql @@ -0,0 +1,87 @@ +\set ECHO +\set QUIET 1 + +-- +-- Tests for pgTAP. +-- +-- +-- $Id$ + +-- Format the output for nice TAP. +\pset format unaligned +\pset tuples_only true +\pset pager + +-- Create plpgsql if it's not already there. +SET client_min_messages = fatal; +\set ON_ERROR_STOP off +CREATE LANGUAGE plpgsql; + +-- Keep things quiet. +SET client_min_messages = warning; + +-- Revert all changes on failure. +\set ON_ERROR_ROLBACK 1 +\set ON_ERROR_STOP true + +-- Load the TAP functions. +BEGIN; +\i pgtap.sql + +-- ## SET search_path TO TAPSCHEMA,public; + +-- Set the test plan. +SELECT plan(11); + +/****************************************************************************/ +-- test throws_ok(). +SELECT throws_ok( 'SELECT 1 / 0', '22012', 'throws_ok(1/0) should work' ); + +-- Check its diagnostics for an invalid error code. +\echo ok 2 - throws_ok failure diagnostics +SELECT is( + throws_ok( 'SELECT 1 / 0', 97212 ), + E'not ok 2 - threw 97212\n# Failed test 2: "threw 97212"\n# caught: 22012: division by zero\n# wanted: 97212', + 'We should get the proper diagnostics from throws_ok()' +); + +SELECT throws_ok( 'SELECT 1 / 0', NULL, 'throws_ok(1/0, NULL) should work' ); + +-- Check its diagnostics no error. +\echo ok 5 - throws_ok failure diagnostics +SELECT is( + throws_ok( 'SELECT 1', NULL ), + E'not ok 5 - threw an exception\n# Failed test 5: "threw an exception"\n# caught: no exception\n# wanted: an exception', + 'We should get the proper diagnostics from throws_ok() with a NULL error code' +); + +-- Clean up the failed test results. +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 2, 5 ); + +/****************************************************************************/ +-- test lives_ok(). +SELECT lives_ok( 'SELECT 1', 'lives_ok() should work' ); + +-- Check its diagnostics when there is an exception. +\echo ok 8 - lives_ok failure diagnostics +SELECT is( + lives_ok( 'SELECT 1 / 0' ), + E'not ok 8\n# Failed test 8\n# died: 22012: division by zero', + 'We should get the proper diagnostics for a lives_ok() failure' +); + +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 8 ); +\echo ok 10 - lives_ok is ok + +/****************************************************************************/ +-- test multiline description. +SELECT is( + ok( true, E'foo\nbar' ), + E'ok 10 - foo\n# bar', + 'multiline desriptions should have subsequent lines escaped' +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From f73c70da621c7a1a18e064a5fb117c102c3dbe63 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 21:32:54 +0000 Subject: [PATCH 0079/1195] Moved todo tests to their own test script. --- Makefile | 2 +- expected/moretap.out | 5 +---- expected/todotap.out | 5 +++++ sql/moretap.sql | 15 +------------ sql/todotap.sql | 50 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 58 insertions(+), 19 deletions(-) create mode 100644 expected/todotap.out create mode 100644 sql/todotap.sql diff --git a/Makefile b/Makefile index ad104b1f73dc..052ebda02b09 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ DATA_built = pgtap.sql uninstall_pgtap.sql DOCS = README.pgtap SCRIPTS = bin/pg_prove -REGRESS = moretap pg73 throwtap hastap coltap pktap +REGRESS = moretap pg73 todotap throwtap hastap coltap pktap ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/expected/moretap.out b/expected/moretap.out index ba20f9e67d76..4ed56073995b 100644 --- a/expected/moretap.out +++ b/expected/moretap.out @@ -1,5 +1,5 @@ \set ECHO -1..83 +1..80 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -80,6 +80,3 @@ ok 77 - lives_ok failure diagnostics ok 78 - We should get the proper diagnostics for a lives_ok() failure ok 79 - lives_ok is ok ok 80 - multiline desriptions should have subsequent lines escaped -ok 81 - todo fail -ok 82 - todo pass -ok 83 - TODO tests should display properly diff --git a/expected/todotap.out b/expected/todotap.out new file mode 100644 index 000000000000..25130a8579f2 --- /dev/null +++ b/expected/todotap.out @@ -0,0 +1,5 @@ +\set ECHO +1..3 +ok 1 - todo fail +ok 2 - todo pass +ok 3 - TODO tests should display properly diff --git a/sql/moretap.sql b/sql/moretap.sql index 843ec6e6d379..022a374ee317 100644 --- a/sql/moretap.sql +++ b/sql/moretap.sql @@ -27,7 +27,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 83 +\set numb_tests 80 -- ## SET search_path TO TAPSCHEMA,public; @@ -251,19 +251,6 @@ SELECT is( 'multiline desriptions should have subsequent lines escaped' ); -/****************************************************************************/ --- Test todo tests. -\echo ok 81 - todo fail -\echo ok 82 - todo pass -SELECT * FROM todo('just because', 2 ); -SELECT is( - fail('This is a todo test' ) || E'\n' - || pass('This is a todo test that unexpectedly passes' ), - E'not ok 81 - This is a todo test # TODO just because\n# Failed (TODO) test 81: "This is a todo test"\nok 82 - This is a todo test that unexpectedly passes # TODO just because', - 'TODO tests should display properly' -); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 81 ); - /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/sql/todotap.sql b/sql/todotap.sql new file mode 100644 index 000000000000..dca47a7bf972 --- /dev/null +++ b/sql/todotap.sql @@ -0,0 +1,50 @@ +\set ECHO +\set QUIET 1 + +-- +-- Tests for pgTAP. +-- +-- +-- $Id$ + +-- Format the output for nice TAP. +\pset format unaligned +\pset tuples_only true +\pset pager + +-- Create plpgsql if it's not already there. +SET client_min_messages = fatal; +\set ON_ERROR_STOP off +CREATE LANGUAGE plpgsql; + +-- Keep things quiet. +SET client_min_messages = warning; + +-- Revert all changes on failure. +\set ON_ERROR_ROLBACK 1 +\set ON_ERROR_STOP true + +-- Load the TAP functions. +BEGIN; +\i pgtap.sql + +-- Set the test plan. +SELECT plan(3); + +/****************************************************************************/ +-- Test todo tests. +\echo ok 1 - todo fail +\echo ok 2 - todo pass +SELECT * FROM todo('just because', 2 ); +SELECT is( + fail('This is a todo test' ) || E'\n' + || pass('This is a todo test that unexpectedly passes' ), + E'not ok 1 - This is a todo test # TODO just because\n# Failed (TODO) test 1: "This is a todo test"\nok 2 - This is a todo test that unexpectedly passes # TODO just because', + 'TODO tests should display properly' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 2 ); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From 4ad133f0e80eb7aac89658b983383d47b1e708f2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 21:36:02 +0000 Subject: [PATCH 0080/1195] Forgot to remove the throws and lives tests from moretap. --- expected/moretap.out | 15 +++------------ sql/moretap.sql | 45 +++----------------------------------------- 2 files changed, 6 insertions(+), 54 deletions(-) diff --git a/expected/moretap.out b/expected/moretap.out index 4ed56073995b..fb6ee5b4a290 100644 --- a/expected/moretap.out +++ b/expected/moretap.out @@ -1,5 +1,5 @@ \set ECHO -1..80 +1..71 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -69,14 +69,5 @@ ok 66 - unalike() should work with a regex ok 67 - iunalike() should work with a regex ok 68 - unalike() failure ok 69 - Check unalike diagnostics -ok 70 - throws_ok(1/0) should work -ok 71 - throws_ok failure diagnostics -ok 72 - We should get the proper diagnostics from throws_ok() -ok 73 - throws_ok(1/0, NULL) should work -ok 74 - throws_ok failure diagnostics -ok 75 - We should get the proper diagnostics from throws_ok() with a NULL error code -ok 76 - lives_ok() should work -ok 77 - lives_ok failure diagnostics -ok 78 - We should get the proper diagnostics for a lives_ok() failure -ok 79 - lives_ok is ok -ok 80 - multiline desriptions should have subsequent lines escaped +ok 70 - Multline diagnostics +ok 71 - multiline desriptions should have subsequent lines escaped diff --git a/sql/moretap.sql b/sql/moretap.sql index 022a374ee317..26dd45410d10 100644 --- a/sql/moretap.sql +++ b/sql/moretap.sql @@ -27,7 +27,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 80 +\set numb_tests 71 -- ## SET search_path TO TAPSCHEMA,public; @@ -203,51 +203,12 @@ SELECT is( unalike( 'foo'::text, 'f%'::text ), E'not ok 68\n# Failed test 68\n# -- Clean up the failed test results. UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 63, 68 ); -/****************************************************************************/ --- test throws_ok(). -SELECT throws_ok( 'SELECT 1 / 0', '22012', 'throws_ok(1/0) should work' ); - --- Check its diagnostics for an invalid error code. -\echo ok 71 - throws_ok failure diagnostics -SELECT is( - throws_ok( 'SELECT 1 / 0', 97212 ), - E'not ok 71 - threw 97212\n# Failed test 71: "threw 97212"\n# caught: 22012: division by zero\n# wanted: 97212', - 'We should get the proper diagnostics from throws_ok()' -); - -SELECT throws_ok( 'SELECT 1 / 0', NULL, 'throws_ok(1/0, NULL) should work' ); - --- Check its diagnostics no error. -\echo ok 74 - throws_ok failure diagnostics -SELECT is( - throws_ok( 'SELECT 1', NULL ), - E'not ok 74 - threw an exception\n# Failed test 74: "threw an exception"\n# caught: no exception\n# wanted: an exception', - 'We should get the proper diagnostics from throws_ok() with a NULL error code' -); - --- Clean up the failed test results. -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 71, 74 ); - -/****************************************************************************/ --- test lives_ok(). -SELECT lives_ok( 'SELECT 1', 'lives_ok() should work' ); - --- Check its diagnostics when there is an exception. -\echo ok 77 - lives_ok failure diagnostics -SELECT is( - lives_ok( 'SELECT 1 / 0' ), - E'not ok 77\n# Failed test 77\n# died: 22012: division by zero', - 'We should get the proper diagnostics for a lives_ok() failure' -); - -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 77 ); -\echo ok 79 - lives_ok is ok - /****************************************************************************/ -- test multiline description. +\echo ok 70 - Multline diagnostics SELECT is( ok( true, E'foo\nbar' ), - E'ok 79 - foo\n# bar', + E'ok 70 - foo\n# bar', 'multiline desriptions should have subsequent lines escaped' ); From 0b355095202ca8d7b05e4af2f90dd8ba6ae441ec Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 21:41:18 +0000 Subject: [PATCH 0081/1195] Moved the matching tests to their own script. --- Makefile | 2 +- expected/matching.out | 22 +++++++++++ expected/moretap.out | 26 ++----------- sql/matching.sql | 85 +++++++++++++++++++++++++++++++++++++++++++ sql/moretap.sql | 54 ++------------------------- 5 files changed, 114 insertions(+), 75 deletions(-) create mode 100644 expected/matching.out create mode 100644 sql/matching.sql diff --git a/Makefile b/Makefile index 052ebda02b09..8f1506bf6b1b 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ DATA_built = pgtap.sql uninstall_pgtap.sql DOCS = README.pgtap SCRIPTS = bin/pg_prove -REGRESS = moretap pg73 todotap throwtap hastap coltap pktap +REGRESS = moretap pg73 todotap matching throwtap hastap coltap pktap ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/expected/matching.out b/expected/matching.out new file mode 100644 index 000000000000..7e50dd6b4594 --- /dev/null +++ b/expected/matching.out @@ -0,0 +1,22 @@ +\set ECHO +1..20 +ok 1 - matches() should work +ok 2 - matches() should work with a regex +ok 3 - imatches() should work with a regex +ok 4 - matches() failure +ok 5 - Check matches diagnostics +ok 6 - doesnt_match() should work +ok 7 - doesnt_match() should work with a regex +ok 8 - doesnt_imatch() should work with a regex +ok 9 - doesnt_match() failure +ok 10 - doesnt_match() should work +ok 11 - alike() should work +ok 12 - alike() should work with a regex +ok 13 - ialike() should work with a regex +ok 14 - alike() failure +ok 15 - Check alike diagnostics +ok 16 - unalike() should work +ok 17 - unalike() should work with a regex +ok 18 - iunalike() should work with a regex +ok 19 - unalike() failure +ok 20 - Check unalike diagnostics diff --git a/expected/moretap.out b/expected/moretap.out index fb6ee5b4a290..4cbd83a12813 100644 --- a/expected/moretap.out +++ b/expected/moretap.out @@ -1,5 +1,5 @@ \set ECHO -1..71 +1..51 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -49,25 +49,5 @@ ok 46 - isnt(1, 2) should work ok 47 - isnt() failure ok 48 - is(1, 2) should work ok 49 - is() should work with psql variables -ok 50 - matches() should work -ok 51 - matches() should work with a regex -ok 52 - imatches() should work with a regex -ok 53 - matches() failure -ok 54 - Check matches diagnostics -ok 55 - doesnt_match() should work -ok 56 - doesnt_match() should work with a regex -ok 57 - doesnt_imatch() should work with a regex -ok 58 - doesnt_match() failure -ok 59 - doesnt_match() should work -ok 60 - alike() should work -ok 61 - alike() should work with a regex -ok 62 - ialike() should work with a regex -ok 63 - alike() failure -ok 64 - Check alike diagnostics -ok 65 - unalike() should work -ok 66 - unalike() should work with a regex -ok 67 - iunalike() should work with a regex -ok 68 - unalike() failure -ok 69 - Check unalike diagnostics -ok 70 - Multline diagnostics -ok 71 - multiline desriptions should have subsequent lines escaped +ok 50 - Multline diagnostics +ok 51 - multiline desriptions should have subsequent lines escaped diff --git a/sql/matching.sql b/sql/matching.sql new file mode 100644 index 000000000000..0c87937515b9 --- /dev/null +++ b/sql/matching.sql @@ -0,0 +1,85 @@ +\set ECHO +\set QUIET 1 + +-- +-- Tests for pgTAP. +-- +-- +-- $Id$ + +-- Format the output for nice TAP. +\pset format unaligned +\pset tuples_only true +\pset pager + +-- Create plpgsql if it's not already there. +SET client_min_messages = fatal; +\set ON_ERROR_STOP off +CREATE LANGUAGE plpgsql; + +-- Keep things quiet. +SET client_min_messages = warning; + +-- Revert all changes on failure. +\set ON_ERROR_ROLBACK 1 +\set ON_ERROR_STOP true + +-- Load the TAP functions. +BEGIN; +\i pgtap.sql + +-- Set the test plan. +SELECT plan(20); + +/****************************************************************************/ +-- Test matches(). +SELECT matches( 'foo'::text, 'o', 'matches() should work' ); +SELECT matches( 'foo'::text, '^fo', 'matches() should work with a regex' ); +SELECT imatches( 'FOO'::text, '^fo', 'imatches() should work with a regex' ); + +-- Check matches() diagnostics. +\echo ok 4 - matches() failure +SELECT is( matches( 'foo'::text, '^a' ), E'not ok 4\n# Failed test 4\n# ''foo''\n# doesn''t match: ''^a''', 'Check matches diagnostics' ); + +-- Check doesnt_match. +SELECT doesnt_match( 'foo'::text, 'a', 'doesnt_match() should work' ); +SELECT doesnt_match( 'foo'::text, '^o', 'doesnt_match() should work with a regex' ); +SELECT doesnt_imatch( 'foo'::text, '^o', 'doesnt_imatch() should work with a regex' ); + +-- Check doesnt_match diagnostics. +\echo ok 9 - doesnt_match() failure +SELECT is( + doesnt_match( 'foo'::text, 'o' ), + E'not ok 9\n# Failed test 9\n# ''foo''\n# matches: ''o''', + 'doesnt_match() should work' +); + +-- Clean up the failed test results. +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 4, 9 ); + +/****************************************************************************/ +-- Test alike(). +SELECT alike( 'foo'::text, 'foo', 'alike() should work' ); +SELECT alike( 'foo'::text, 'fo%', 'alike() should work with a regex' ); +SELECT ialike( 'FOO'::text, 'fo%', 'ialike() should work with a regex' ); + +-- Check alike() diagnostics. +\echo ok 14 - alike() failure +SELECT is( alike( 'foo'::text, 'a%'::text ), E'not ok 14\n# Failed test 14\n# ''foo''\n# doesn''t match: ''a%''', 'Check alike diagnostics' ); + +-- Test unalike(). +SELECT unalike( 'foo'::text, 'f', 'unalike() should work' ); +SELECT unalike( 'foo'::text, 'f%i', 'unalike() should work with a regex' ); +SELECT unialike( 'FOO'::text, 'f%i', 'iunalike() should work with a regex' ); + +-- Check unalike() diagnostics. +\echo ok 19 - unalike() failure +SELECT is( unalike( 'foo'::text, 'f%'::text ), E'not ok 19\n# Failed test 19\n# ''foo''\n# matches: ''f%''', 'Check unalike diagnostics' ); + +-- Clean up the failed test results. +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 14, 19 ); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; diff --git a/sql/moretap.sql b/sql/moretap.sql index 26dd45410d10..36a7bf822a7d 100644 --- a/sql/moretap.sql +++ b/sql/moretap.sql @@ -27,7 +27,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 71 +\set numb_tests 51 -- ## SET search_path TO TAPSCHEMA,public; @@ -155,60 +155,12 @@ UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 43, 47 ); \set bar '\'' waffle '\'' SELECT is( :foo::text, :bar::text, 'is() should work with psql variables' ); -/****************************************************************************/ --- Test matches(). -SELECT matches( 'foo'::text, 'o', 'matches() should work' ); -SELECT matches( 'foo'::text, '^fo', 'matches() should work with a regex' ); -SELECT imatches( 'FOO'::text, '^fo', 'imatches() should work with a regex' ); - --- Check matches() diagnostics. -\echo ok 53 - matches() failure -SELECT is( matches( 'foo'::text, '^a' ), E'not ok 53\n# Failed test 53\n# ''foo''\n# doesn''t match: ''^a''', 'Check matches diagnostics' ); - --- Check doesnt_match. -SELECT doesnt_match( 'foo'::text, 'a', 'doesnt_match() should work' ); -SELECT doesnt_match( 'foo'::text, '^o', 'doesnt_match() should work with a regex' ); -SELECT doesnt_imatch( 'foo'::text, '^o', 'doesnt_imatch() should work with a regex' ); - --- Check doesnt_match diagnostics. -\echo ok 58 - doesnt_match() failure -SELECT is( - doesnt_match( 'foo'::text, 'o' ), - E'not ok 58\n# Failed test 58\n# ''foo''\n# matches: ''o''', - 'doesnt_match() should work' -); - --- Clean up the failed test results. -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 53, 58 ); - -/****************************************************************************/ --- Test alike(). -SELECT alike( 'foo'::text, 'foo', 'alike() should work' ); -SELECT alike( 'foo'::text, 'fo%', 'alike() should work with a regex' ); -SELECT ialike( 'FOO'::text, 'fo%', 'ialike() should work with a regex' ); - --- Check alike() diagnostics. -\echo ok 63 - alike() failure -SELECT is( alike( 'foo'::text, 'a%'::text ), E'not ok 63\n# Failed test 63\n# ''foo''\n# doesn''t match: ''a%''', 'Check alike diagnostics' ); - --- Test unalike(). -SELECT unalike( 'foo'::text, 'f', 'unalike() should work' ); -SELECT unalike( 'foo'::text, 'f%i', 'unalike() should work with a regex' ); -SELECT unialike( 'FOO'::text, 'f%i', 'iunalike() should work with a regex' ); - --- Check unalike() diagnostics. -\echo ok 68 - unalike() failure -SELECT is( unalike( 'foo'::text, 'f%'::text ), E'not ok 68\n# Failed test 68\n# ''foo''\n# matches: ''f%''', 'Check unalike diagnostics' ); - --- Clean up the failed test results. -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 63, 68 ); - /****************************************************************************/ -- test multiline description. -\echo ok 70 - Multline diagnostics +\echo ok 50 - Multline diagnostics SELECT is( ok( true, E'foo\nbar' ), - E'ok 70 - foo\n# bar', + E'ok 50 - foo\n# bar', 'multiline desriptions should have subsequent lines escaped' ); From 73215f307159cf853717845948bbebc12dc34549 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 21:42:31 +0000 Subject: [PATCH 0082/1195] Might need those at some point. --- sql/matching.sql | 2 ++ sql/pg73.sql | 2 ++ sql/todotap.sql | 2 ++ 3 files changed, 6 insertions(+) diff --git a/sql/matching.sql b/sql/matching.sql index 0c87937515b9..6cba0707328d 100644 --- a/sql/matching.sql +++ b/sql/matching.sql @@ -28,6 +28,8 @@ SET client_min_messages = warning; BEGIN; \i pgtap.sql +-- ## SET search_path TO TAPSCHEMA,public; + -- Set the test plan. SELECT plan(20); diff --git a/sql/pg73.sql b/sql/pg73.sql index ae610401ebae..3b5ca7f8443c 100644 --- a/sql/pg73.sql +++ b/sql/pg73.sql @@ -27,6 +27,8 @@ SET client_min_messages = warning; BEGIN; \i pgtap.sql +-- ## SET search_path TO TAPSCHEMA,public; + select plan(39); select ok(true); diff --git a/sql/todotap.sql b/sql/todotap.sql index dca47a7bf972..b12f975e8213 100644 --- a/sql/todotap.sql +++ b/sql/todotap.sql @@ -28,6 +28,8 @@ SET client_min_messages = warning; BEGIN; \i pgtap.sql +-- ## SET search_path TO TAPSCHEMA,public; + -- Set the test plan. SELECT plan(3); From dd027d3c46bd869a01b118f240277336d83d0962 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 21:47:24 +0000 Subject: [PATCH 0083/1195] Moved the `is()` and `isnt()` tests to their own script. --- Makefile | 2 +- expected/istap.out | 23 ++++++++++++++ expected/moretap.out | 27 ++-------------- sql/istap.sql | 74 ++++++++++++++++++++++++++++++++++++++++++++ sql/moretap.sql | 42 ++----------------------- 5 files changed, 104 insertions(+), 64 deletions(-) create mode 100644 expected/istap.out create mode 100644 sql/istap.sql diff --git a/Makefile b/Makefile index 8f1506bf6b1b..10af9d9333da 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ DATA_built = pgtap.sql uninstall_pgtap.sql DOCS = README.pgtap SCRIPTS = bin/pg_prove -REGRESS = moretap pg73 todotap matching throwtap hastap coltap pktap +REGRESS = moretap istap pg73 todotap matching throwtap hastap coltap pktap ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/expected/istap.out b/expected/istap.out new file mode 100644 index 000000000000..9d2719e47cee --- /dev/null +++ b/expected/istap.out @@ -0,0 +1,23 @@ +\set ECHO +1..21 +ok 1 - is() success +ok 2 - isa(1, 1) should work +ok 3 - is() success 2 +ok 4 - is('x', 'x') should work +ok 5 - is() success 3 +ok 6 - is(1.1, 1.10) should work +ok 7 - is() success 4 +ok 8 - is(1.1, 1.10) should work +ok 9 - is() success 5 +ok 10 - is(true, true) should work +ok 11 - is() success 6 +ok 12 - is(false, false) should work +ok 13 - is() success 7 +ok 14 - is(1, 1, 'foo') should work +ok 15 - is() failure +ok 16 - is(1, 2) should work +ok 17 - isnt() success +ok 18 - isnt(1, 2) should work +ok 19 - isnt() failure +ok 20 - is(1, 2) should work +ok 21 - is() should work with psql variables diff --git a/expected/moretap.out b/expected/moretap.out index 4cbd83a12813..7abedc08f093 100644 --- a/expected/moretap.out +++ b/expected/moretap.out @@ -1,5 +1,5 @@ \set ECHO -1..51 +1..30 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -28,26 +28,5 @@ ok 25 - ok() failure 2 ok 26 - ok(false, '') should work ok 27 - ok() failure 3 ok 28 - ok(false, 'foo') should work -ok 29 - is() success -ok 30 - isa(1, 1) should work -ok 31 - is() success 2 -ok 32 - is('x', 'x') should work -ok 33 - is() success 3 -ok 34 - is(1.1, 1.10) should work -ok 35 - is() success 4 -ok 36 - is(1.1, 1.10) should work -ok 37 - is() success 5 -ok 38 - is(true, true) should work -ok 39 - is() success 6 -ok 40 - is(false, false) should work -ok 41 - is() success 7 -ok 42 - is(1, 1, 'foo') should work -ok 43 - is() failure -ok 44 - is(1, 2) should work -ok 45 - isnt() success -ok 46 - isnt(1, 2) should work -ok 47 - isnt() failure -ok 48 - is(1, 2) should work -ok 49 - is() should work with psql variables -ok 50 - Multline diagnostics -ok 51 - multiline desriptions should have subsequent lines escaped +ok 29 - Multline diagnostics +ok 30 - multiline desriptions should have subsequent lines escaped diff --git a/sql/istap.sql b/sql/istap.sql new file mode 100644 index 000000000000..843b1750c2c3 --- /dev/null +++ b/sql/istap.sql @@ -0,0 +1,74 @@ +\set ECHO +\set QUIET 1 + +-- +-- Tests for pgTAP. +-- +-- +-- $Id$ + +-- Format the output for nice TAP. +\pset format unaligned +\pset tuples_only true +\pset pager + +-- Create plpgsql if it's not already there. +SET client_min_messages = fatal; +\set ON_ERROR_STOP off +CREATE LANGUAGE plpgsql; + +-- Keep things quiet. +SET client_min_messages = warning; + +-- Revert all changes on failure. +\set ON_ERROR_ROLBACK 1 +\set ON_ERROR_STOP true + +-- Load the TAP functions. +BEGIN; +\i pgtap.sql + +-- ## SET search_path TO TAPSCHEMA,public; + +-- Set the test plan. +SELECT plan(21); + +/****************************************************************************/ +-- Test is(). +\echo ok 1 - is() success +SELECT is( is(1, 1), 'ok 1', 'isa(1, 1) should work' ); +\echo ok 3 - is() success 2 +SELECT is( is('x'::text, 'x'::text), 'ok 3', 'is(''x'', ''x'') should work' ); +\echo ok 5 - is() success 3 +SELECT is( is(1.1, 1.10), 'ok 5', 'is(1.1, 1.10) should work' ); +\echo ok 7 - is() success 4 +SELECT is( is(1.1, 1.10), 'ok 7', 'is(1.1, 1.10) should work' ); +\echo ok 9 - is() success 5 +SELECT is( is(true, true), 'ok 9', 'is(true, true) should work' ); +\echo ok 11 - is() success 6 +SELECT is( is(false, false), 'ok 11', 'is(false, false) should work' ); +\echo ok 13 - is() success 7 +SELECT is( is(1, 1, 'foo'), 'ok 13 - foo', 'is(1, 1, ''foo'') should work' ); +\echo ok 15 - is() failure +SELECT is( is( 1, 2 ), E'not ok 15\n# Failed test 15\n# have: 1\n# want: 2', 'is(1, 2) should work' ); + +/****************************************************************************/ +-- Test isnt(). +\echo ok 17 - isnt() success +SELECT is( isnt(1, 2), 'ok 17', 'isnt(1, 2) should work' ); +\echo ok 19 - isnt() failure +SELECT is( isnt( 1, 1 ), E'not ok 19\n# Failed test 19\n# 1\n# <>\n# 1', 'is(1, 2) should work' ); + +-- Clean up the failed test results. +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 15, 19 ); + +/****************************************************************************/ +-- Try using variables. +\set foo '\'' waffle '\'' +\set bar '\'' waffle '\'' +SELECT is( :foo::text, :bar::text, 'is() should work with psql variables' ); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; diff --git a/sql/moretap.sql b/sql/moretap.sql index 36a7bf822a7d..d2a45f6840e0 100644 --- a/sql/moretap.sql +++ b/sql/moretap.sql @@ -27,7 +27,7 @@ SET client_min_messages = warning; -- Load the TAP functions. BEGIN; \i pgtap.sql -\set numb_tests 51 +\set numb_tests 30 -- ## SET search_path TO TAPSCHEMA,public; @@ -119,48 +119,12 @@ SELECT is( ok(false, 'foo'), E'not ok 27 - foo\n# Failed test 27: "foo"', 'ok(fa -- Clean up the failed test results. UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 23, 25, 27); -/****************************************************************************/ --- Test is(). -\echo ok 29 - is() success -SELECT is( is(1, 1), 'ok 29', 'isa(1, 1) should work' ); -\echo ok 31 - is() success 2 -SELECT is( is('x'::text, 'x'::text), 'ok 31', 'is(''x'', ''x'') should work' ); -\echo ok 33 - is() success 3 -SELECT is( is(1.1, 1.10), 'ok 33', 'is(1.1, 1.10) should work' ); -\echo ok 35 - is() success 4 -SELECT is( is(1.1, 1.10), 'ok 35', 'is(1.1, 1.10) should work' ); -\echo ok 37 - is() success 5 -SELECT is( is(true, true), 'ok 37', 'is(true, true) should work' ); -\echo ok 39 - is() success 6 -SELECT is( is(false, false), 'ok 39', 'is(false, false) should work' ); ---SELECT is( '12:45'::time, '12:45'::time, 'ok 41', 'is(time, time) should work' ); -\echo ok 41 - is() success 7 -SELECT is( is(1, 1, 'foo'), 'ok 41 - foo', 'is(1, 1, ''foo'') should work' ); -\echo ok 43 - is() failure -SELECT is( is( 1, 2 ), E'not ok 43\n# Failed test 43\n# have: 1\n# want: 2', 'is(1, 2) should work' ); - -/****************************************************************************/ --- Test isnt(). -\echo ok 45 - isnt() success -SELECT is( isnt(1, 2), 'ok 45', 'isnt(1, 2) should work' ); -\echo ok 47 - isnt() failure -SELECT is( isnt( 1, 1 ), E'not ok 47\n# Failed test 47\n# 1\n# <>\n# 1', 'is(1, 2) should work' ); - --- Clean up the failed test results. -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 43, 47 ); - -/****************************************************************************/ --- Try using variables. -\set foo '\'' waffle '\'' -\set bar '\'' waffle '\'' -SELECT is( :foo::text, :bar::text, 'is() should work with psql variables' ); - /****************************************************************************/ -- test multiline description. -\echo ok 50 - Multline diagnostics +\echo ok 29 - Multline diagnostics SELECT is( ok( true, E'foo\nbar' ), - E'ok 50 - foo\n# bar', + E'ok 29 - foo\n# bar', 'multiline desriptions should have subsequent lines escaped' ); From 72fee428afd81ddb2d7f007b970171a78932a08d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 21:48:15 +0000 Subject: [PATCH 0084/1195] Note test suite changes. --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 0e30bdc12b77..8a4d2939ebb0 100644 --- a/Changes +++ b/Changes @@ -41,6 +41,8 @@ Revision history for pgTAP in line with typical PostgreSQL contrib modules. - Removed verbose output of the `psql` command from `pg_prove`. Thanks to Andy Armstrong for the spot. + - Divided the tests up into many separate test script files, so that + things are a bit better organized and easier to maintain. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. From 32752fbb9b4f8c0ec94468c331b8b9cd4cdec6aa Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 22:03:09 +0000 Subject: [PATCH 0085/1195] Added tests for NULL values. --- sql/istap.sql | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/sql/istap.sql b/sql/istap.sql index 843b1750c2c3..6a9147dc1a1a 100644 --- a/sql/istap.sql +++ b/sql/istap.sql @@ -31,7 +31,7 @@ BEGIN; -- ## SET search_path TO TAPSCHEMA,public; -- Set the test plan. -SELECT plan(21); +SELECT plan(27); /****************************************************************************/ -- Test is(). @@ -68,6 +68,32 @@ UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 15, 19 ); \set bar '\'' waffle '\'' SELECT is( :foo::text, :bar::text, 'is() should work with psql variables' ); +/****************************************************************************/ +-- Try using NULLs. +\echo ok 22 - is(NULL, NULL) success +SELECT is( + is( NULL::text, NULL::text, 'NULLs' ), + 'ok 22 - NULLs', + 'is(NULL, NULL) should pass' +); + +\echo ok 24 - is(NULL, foo) failure +SELECT is( + is( NULL::text, 'foo' ), + E'not ok 24\n# Failed test 24\n# have: NULL\n# want: foo', + 'is(NULL, foo) should fail' +); + +\echo ok 26 - is(foo, NULL) failure +SELECT is( + is( 'foo', NULL::text ), + E'not ok 26\n# Failed test 26\n# have: foo\n# want: NULL', + 'is(foo, NULL) should fail' +); + +-- Clean up the failed test results. +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 24, 26 ); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 13f5e6112fcdeed33e65e8976b83cd13d1c30d51 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 22:04:12 +0000 Subject: [PATCH 0086/1195] Updated exected. --- expected/istap.out | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/expected/istap.out b/expected/istap.out index 9d2719e47cee..29b9298f7d5c 100644 --- a/expected/istap.out +++ b/expected/istap.out @@ -1,5 +1,5 @@ \set ECHO -1..21 +1..27 ok 1 - is() success ok 2 - isa(1, 1) should work ok 3 - is() success 2 @@ -21,3 +21,9 @@ ok 18 - isnt(1, 2) should work ok 19 - isnt() failure ok 20 - is(1, 2) should work ok 21 - is() should work with psql variables +ok 22 - is(NULL, NULL) success +ok 23 - is(NULL, NULL) should pass +ok 24 - is(NULL, foo) failure +ok 25 - is(NULL, foo) should fail +ok 26 - is(foo, NULL) failure +ok 27 - is(foo, NULL) should fail From 866f340bdaffe273a3b200ff942ebcf938952f69 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Aug 2008 23:52:49 +0000 Subject: [PATCH 0087/1195] * Added `has_fk()` and `col_is_fk()`, plus some notes on yet another test function, `fk_ok()`, which will check that an FK actually points to the appropriate table and PK columns. * Updated the `svn:ignore` property to properly ignore `uninstall_pgtap.sql` instead of the old `drop_pgtap.sql` file. --- Changes | 8 +-- Makefile | 2 +- README.pgtap | 39 +++++++++++ expected/fktap.out | 28 ++++++++ pgtap.sql.in | 106 +++++++++++++++++++++++++++-- sql/fktap.sql | 164 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 338 insertions(+), 9 deletions(-) create mode 100644 expected/fktap.out create mode 100644 sql/fktap.sql diff --git a/Changes b/Changes index 8a4d2939ebb0..564ef6ad9f10 100644 --- a/Changes +++ b/Changes @@ -29,11 +29,11 @@ Revision history for pgTAP - Changed "got/expected" to "have/want", following Schwern's plans for Test::Builder 2. Also changed "caught/expected" to "caught/wanted" for nice parity when outputting diagnostics for exception testing. - - Added the `has_table()`, `has_view()`, `has_column()` and `has_pk()` - test functions. + - Added the `has_table()`, `has_view()`, `has_column()`, `has_pk()`, + and `has_fk()` test functions. - Added the `col_not_null()` and `col_is_null()` test functions. - - Added the `col_type_is()`, `col_default_is()`, and `col_is_pk()` test - functions. + - Added the `col_type_is()`, `col_default_is()`, `col_is_pk()` and + `col_is_fk()` test functions. - Fixed `is()` and `isnt()` to better handle NULLs. - Added the `--set ON_ERROR_STOP=1` option to the call to `psql` in `pg_prove`. diff --git a/Makefile b/Makefile index 10af9d9333da..615b26a6a2e6 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ DATA_built = pgtap.sql uninstall_pgtap.sql DOCS = README.pgtap SCRIPTS = bin/pg_prove -REGRESS = moretap istap pg73 todotap matching throwtap hastap coltap pktap +REGRESS = moretap istap pg73 todotap matching throwtap hastap coltap pktap fktap ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/README.pgtap b/README.pgtap index 7aa6a4e5314f..1005ab98c3a0 100644 --- a/README.pgtap +++ b/README.pgtap @@ -666,6 +666,23 @@ test description is omitted, it will be set to "Table `:table` should have a primary key". Note that this test will fail if the table in question does not exist. +### `has_fk( schema, table, description )` ### +### `has_fk( table, description )` ### +### `has_fk( table )` ### + + SELECT has_fk( + 'myschema', + 'sometable', + 'Table myschema.sometable should have a foreign key constraint' + ); + +Tests whether or not a table has a foreign key constraint. The first argument +is the schema name, the second the table name, the the third is the test +description. If the schema is omitted, the table must be visible in the search +path. If the test description is omitted, it will be set to "Table `:table` +should have a foreign key constraint". Note that this test will fail if the +table in question does not exist. + ### `col_is_pk( schema, table, column, description )` ### ### `col_is_pk( schema, table, column[], description )` ### ### `col_is_pk( table, column, description )` ### @@ -704,6 +721,28 @@ Will produce something like this: # have: {} # want: {id} +### `col_is_fk( schema, table, column, description )` ### +### `col_is_fk( schema, table, column[], description )` ### +### `col_is_fk( table, column, description )` ### +### `col_is_fk( table, column[], description )` ### +### `col_is_fk( table, column )` ### +### `col_is_fk( table, column[] )` ### + + SELECT col_is_fk( + 'myschema', + 'sometable', + 'other_id', + 'Column myschema.sometable.other_id should be a foreign key' + ); + + SELECT col_is_fk( + 'contacts', + ARRAY['first', 'last'], + ); + +Just like `col_is_fk()`, except that it test that the column or array of +columns are a primary key. + Diagnostics ----------- diff --git a/expected/fktap.out b/expected/fktap.out new file mode 100644 index 000000000000..d5d9e3d091bc --- /dev/null +++ b/expected/fktap.out @@ -0,0 +1,28 @@ +\set ECHO +1..26 +ok 1 - test has_fk( schema, table, description ) +ok 2 - has_fk( schema, table, description ) should work +ok 3 - test has_fk( table, description ) +ok 4 - has_fk( table, description ) should work +ok 5 - test has_fk( table ) +ok 6 - has_fk( table ) should work +ok 7 - test has_fk( schema, table, description ) fail +ok 8 - has_fk( schema, table, description ) should fail properly +ok 9 - test has_fk( table, description ) fail +ok 10 - has_fk( table, description ) should fail properly +ok 11 - test col_is_fk( schema, table, column, description ) +ok 12 - col_is_fk( schema, table, column, description ) should work +ok 13 - test col_is_fk( table, column, description ) +ok 14 - col_is_fk( table, column, description ) should work +ok 15 - test col_is_fk( table, column ) +ok 16 - col_is_fk( table, column ) should work +ok 17 - test col_is_fk( schema, table, column, description ) fail +ok 18 - col_is_fk( schema, table, column, description ) should fail properly +ok 19 - test col_is_fk( table, column, description ) fail +ok 20 - col_is_fk( table, column, description ) should fail properly +ok 21 - test col_is_fk( schema, table, column[], description ) +ok 22 - col_is_fk( schema, table, column[], description ) should work +ok 23 - test col_is_fk( table, column[], description ) +ok 24 - col_is_fk( table, column[], description ) should work +ok 25 - test col_is_fk( table, column[], description ) +ok 26 - col_is_fk( table, column[] ) should work diff --git a/pgtap.sql.in b/pgtap.sql.in index ec646362bbcd..f23e45754f0e 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -746,7 +746,6 @@ CREATE OR REPLACE FUNCTION has_pk ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ ); $$ LANGUAGE sql; - -- has_pk( table, description ) CREATE OR REPLACE FUNCTION has_pk ( TEXT, TEXT ) RETURNS TEXT AS $$ SELECT ok( @@ -775,6 +774,7 @@ CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT A WHERE n.oid = c.relnamespace AND c.oid = a.attrelid AND c.oid = x.conrelid + AND x.contype = 'p' AND a.attnum = ANY(x.conkey) AND n.nspname = $1 AND c.relname = $2 @@ -791,6 +791,7 @@ CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ pg_catalog.pg_constraint x WHERE c.oid = a.attrelid AND c.oid = x.conrelid + AND x.contype = 'p' AND pg_table_is_visible(c.oid) AND a.attnum = ANY(x.conkey) AND c.relname = $1 @@ -821,8 +822,105 @@ CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT ) RETURNS TEXT AS $$ $$ LANGUAGE sql; -- has_fk( schema, table, description ) --- has_unique( schema, table, description ) --- has_check( schema, table, constraint, description ) +CREATE OR REPLACE FUNCTION has_fk ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, + pg_catalog.pg_constraint x + WHERE n.oid = c.relnamespace + AND c.oid = x.conrelid + AND n.nspname = $1 + AND c.relname = $2 + AND c.relhaspkey = true + AND x.contype = 'f' + ), $3 + ); +$$ LANGUAGE sql; + +-- has_fk( table, description ) +CREATE OR REPLACE FUNCTION has_fk ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_class c, pg_catalog.pg_constraint x + WHERE c.oid = x.conrelid + AND pg_catalog.pg_table_is_visible(c.oid) + AND c.relname = $1 + AND c.relhaspkey = true + AND x.contype = 'f' + ), $2 + ); +$$ LANGUAGE sql; + +-- has_fk( table ) +CREATE OR REPLACE FUNCTION has_fk ( TEXT ) RETURNS TEXT AS $$ + SELECT has_fk( $1, 'Table ' || $1 || ' should have a foreign key constraint' ); +$$ LANGUAGE sql; --- col_has_fk( schema, table, column[], description ) +-- col_is_fk( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ + SELECT is(ARRAY (SELECT a.attname::text + FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a, + pg_catalog.pg_constraint x + WHERE n.oid = c.relnamespace + AND c.oid = a.attrelid + AND c.oid = x.conrelid + AND x.contype = 'f' + AND a.attnum = ANY(x.conkey) + AND n.nspname = $1 + AND c.relname = $2 + AND a.attnum > 0 + AND NOT a.attisdropped + ORDER BY a.attnum + ), $3, $4 ); +$$ LANGUAGE sql; + +-- col_is_fk( table, column, description ) +CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ + SELECT is(ARRAY (SELECT a.attname::text + FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, + pg_catalog.pg_constraint x + WHERE c.oid = a.attrelid + AND c.oid = x.conrelid + AND x.contype = 'f' + AND pg_table_is_visible(c.oid) + AND a.attnum = ANY(x.conkey) + AND c.relname = $1 + AND a.attnum > 0 + AND NOT a.attisdropped + ORDER BY a.attnum + ), $2, $3 ); +$$ LANGUAGE sql; + +-- col_is_fk( table, column[] ) +CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT[] ) RETURNS TEXT AS $$ + SELECT col_is_fk( $1, $2, 'Column ' || $1 || '.' || $2::text || ' should be a foreign key' ); +$$ LANGUAGE sql; + +-- col_is_fk( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT, TEXT, 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 ( TEXT, TEXT, 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 ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT col_is_fk( $1, $2, 'Column ' || $1 || '.' || $2 || ' should be a foreign key' ); +$$ LANGUAGE sql; + +-- fk_ok( schema, table, column[], schema, table, column[], description ) +-- fk_ok( table, column[], table, column[] description ) +-- fk_ok( table, column[], table, column[] ) +-- fk_ok( schema, table, column, schema, table, column, description ) +-- fk_ok( table, column, table, column description ) +-- fk_ok( table, column, table, column ) + +-- has_unique( schema, table, description ) -- col_is_unique( schema, table, column[], description ) + +-- has_check( schema, table, constraint, description ) diff --git a/sql/fktap.sql b/sql/fktap.sql new file mode 100644 index 000000000000..89b073ffae6a --- /dev/null +++ b/sql/fktap.sql @@ -0,0 +1,164 @@ +\set ECHO +\set QUIET 1 + +-- +-- Tests for pgTAP. +-- +-- +-- $Id$ + +-- Format the output for nice TAP. +\pset format unaligned +\pset tuples_only true +\pset pager + +-- Create plpgsql if it's not already there. +SET client_min_messages = fatal; +\set ON_ERROR_STOP off +CREATE LANGUAGE plpgsql; + +-- Keep things quiet. +SET client_min_messages = warning; + +-- Revert all changes on failure. +\set ON_ERROR_ROLBACK 1 +\set ON_ERROR_STOP true + +-- Load the TAP functions. +BEGIN; +\i pgtap.sql + +-- ## SET search_path TO TAPSCHEMA,public; + +-- Set the test plan. +SELECT plan(26); + +-- These will be rolled back. :-) +CREATE TABLE pk ( + id INT NOT NULL PRIMARY KEY, + name TEXT DEFAULT '' +); + +CREATE TABLE fk ( + id INT NOT NULL PRIMARY KEY, + pk_id INT NOT NULL REFERENCES pk(id) +); + +CREATE TABLE pk2 ( + num int NOT NULL, + dot int NOT NULL, + PRIMARY KEY (num, dot) +); + +CREATE TABLE fk2 ( + pk2_num int NOT NULL, + pk2_dot int NOT NULL, + FOREIGN KEY(pk2_num, pk2_dot) REFERENCES pk2( num, dot) +); + +/****************************************************************************/ +-- Test has_fk(). + +\echo ok 1 - test has_fk( schema, table, description ) +SELECT is( + has_fk( 'public', 'fk', 'public.fk should have an fk' ), + 'ok 1 - public.fk should have an fk', + 'has_fk( schema, table, description ) should work' +); + +\echo ok 3 - test has_fk( table, description ) +SELECT is( + has_fk( 'fk', 'fk should have a pk' ), + 'ok 3 - fk should have a pk', + 'has_fk( table, description ) should work' +); + +\echo ok 5 - test has_fk( table ) +SELECT is( + has_fk( 'fk' ), + 'ok 5 - Table fk should have a foreign key constraint', + 'has_fk( table ) should work' +); + +\echo ok 7 - test has_fk( schema, table, description ) fail +SELECT is( + has_fk( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should have a pk' ), + E'not ok 7 - pg_catalog.pg_class should have a pk\n# Failed test 7: "pg_catalog.pg_class should have a pk"', + 'has_fk( schema, table, description ) should fail properly' +); + +\echo ok 9 - test has_fk( table, description ) fail +SELECT is( + has_fk( 'pg_class', 'pg_class should have a pk' ), + E'not ok 9 - pg_class should have a pk\n# Failed test 9: "pg_class should have a pk"', + 'has_fk( table, description ) should fail properly' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 7, 9 ); + +/****************************************************************************/ +-- Test col_is_fk(). + +\echo ok 11 - test col_is_fk( schema, table, column, description ) +SELECT is( + col_is_fk( 'public', 'fk', 'pk_id', 'public.fk.pk_id should be an fk' ), + 'ok 11 - public.fk.pk_id should be an fk', + 'col_is_fk( schema, table, column, description ) should work' +); + +\echo ok 13 - test col_is_fk( table, column, description ) +SELECT is( + col_is_fk( 'fk', 'pk_id', 'fk.pk_id should be an fk' ), + 'ok 13 - fk.pk_id should be an fk', + 'col_is_fk( table, column, description ) should work' +); + +\echo ok 15 - test col_is_fk( table, column ) +SELECT is( + col_is_fk( 'fk', 'pk_id' ), + 'ok 15 - Column fk.pk_id should be a foreign key', + 'col_is_fk( table, column ) should work' +); + +\echo ok 17 - test col_is_fk( schema, table, column, description ) fail +SELECT is( + col_is_fk( 'public', 'fk', 'name', 'public.fk.name should be an fk' ), + E'not ok 17 - public.fk.name should be an fk\n# Failed test 17: "public.fk.name should be an fk"\n# have: {pk_id}\n# want: {name}', + 'col_is_fk( schema, table, column, description ) should fail properly' +); + +\echo ok 19 - test col_is_fk( table, column, description ) fail +SELECT is( + col_is_fk( 'fk', 'name', 'fk.name should be an fk' ), + E'not ok 19 - fk.name should be an fk\n# Failed test 19: "fk.name should be an fk"\n# have: {pk_id}\n# want: {name}', + 'col_is_fk( table, column, description ) should fail properly' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 17, 19 ); + +/****************************************************************************/ +-- Test col_is_fk() with an array of columns. + +\echo ok 21 - test col_is_fk( schema, table, column[], description ) +SELECT is( + col_is_fk( 'public', 'fk2', ARRAY['pk2_num', 'pk2_dot'], 'id + pk2_dot should be an fk' ), + 'ok 21 - id + pk2_dot should be an fk', + 'col_is_fk( schema, table, column[], description ) should work' +); + +\echo ok 23 - test col_is_fk( table, column[], description ) +SELECT is( + col_is_fk( 'fk2', ARRAY['pk2_num', 'pk2_dot'], 'id + pk2_dot should be an fk' ), + 'ok 23 - id + pk2_dot should be an fk', + 'col_is_fk( table, column[], description ) should work' +); + +\echo ok 25 - test col_is_fk( table, column[], description ) +SELECT is( + col_is_fk( 'fk2', ARRAY['pk2_num', 'pk2_dot'] ), + 'ok 25 - Column fk2.{pk2_num,pk2_dot} should be a foreign key', + 'col_is_fk( table, column[] ) should work' +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From 7814181c209bcb9dfe1ae60736fa59bd8d94ecdb Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 22 Aug 2008 01:45:01 +0000 Subject: [PATCH 0088/1195] Refactored the FK and PK test functions to just direct their calls to four core functions that do most of the work. These will also be used in the forthcoming `fk_ok()` functions. --- pgtap.sql.in | 128 +++++++++++++++++++-------------------------------- 1 file changed, 48 insertions(+), 80 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index f23e45754f0e..dd19bca87653 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -729,76 +729,91 @@ CREATE OR REPLACE FUNCTION col_default_is ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $ ); $$ LANGUAGE sql; --- has_pk( schema, table, description ) -CREATE OR REPLACE FUNCTION has_pk ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT ok( - EXISTS( +-- _hasc( schema, table, constraint_type ) +CREATE OR REPLACE FUNCTION _hasc ( TEXT, TEXT, CHAR ) RETURNS BOOLEAN AS $$ + SELECT EXISTS( SELECT true FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_constraint x WHERE n.oid = c.relnamespace AND c.oid = x.conrelid + AND pg_table_is_visible(c.oid) + AND c.relhaspkey = true AND n.nspname = $1 AND c.relname = $2 - AND c.relhaspkey = true - AND x.contype = 'p' - ), $3 + AND x.contype = $3 ); $$ LANGUAGE sql; --- has_pk( table, description ) -CREATE OR REPLACE FUNCTION has_pk ( TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT ok( - EXISTS( +-- _hasc( table, constraint_type ) +CREATE OR REPLACE FUNCTION _hasc ( TEXT, CHAR ) RETURNS BOOLEAN AS $$ + SELECT EXISTS( SELECT true - FROM pg_catalog.pg_class c, pg_catalog.pg_constraint x + FROM pg_catalog.pg_class c, + pg_catalog.pg_constraint x WHERE c.oid = x.conrelid - AND pg_catalog.pg_table_is_visible(c.oid) - AND c.relname = $1 AND c.relhaspkey = true - AND x.contype = 'p' - ), $2 + AND c.relname = $1 + AND x.contype = $2 ); $$ LANGUAGE sql; +-- has_pk( schema, table, description ) +CREATE OR REPLACE FUNCTION has_pk ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT ok( _hasc( $1, $2, 'p' ), $3 ); +$$ LANGUAGE sql; + +-- has_pk( table, description ) +CREATE OR REPLACE FUNCTION has_pk ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT ok( _hasc( $1, 'p' ), $2 ); +$$ LANGUAGE sql; + -- has_pk( table ) CREATE OR REPLACE FUNCTION has_pk ( TEXT ) RETURNS TEXT AS $$ SELECT has_pk( $1, 'Table ' || $1 || ' should have a primary key' ); $$ LANGUAGE sql; --- col_is_pk( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ - SELECT is(ARRAY (SELECT a.attname::text +-- _ckeys( schema, table, constraint_type ) +CREATE OR REPLACE FUNCTION _ckeys ( TEXT, TEXT, CHAR ) RETURNS TEXT[] AS $$ + SELECT ARRAY (SELECT a.attname::text FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a, pg_catalog.pg_constraint x WHERE n.oid = c.relnamespace AND c.oid = a.attrelid AND c.oid = x.conrelid - AND x.contype = 'p' AND a.attnum = ANY(x.conkey) AND n.nspname = $1 AND c.relname = $2 + AND x.contype = $3 AND a.attnum > 0 AND NOT a.attisdropped - ORDER BY a.attnum - ), $3, $4 ); + ); $$ LANGUAGE sql; --- col_is_pk( table, column, description ) -CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ - SELECT is(ARRAY (SELECT a.attname::text +-- _ckeys( table, constraint_type ) +CREATE OR REPLACE FUNCTION _ckeys ( TEXT, CHAR ) RETURNS TEXT[] AS $$ + SELECT ARRAY (SELECT a.attname::text FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, pg_catalog.pg_constraint x WHERE c.oid = a.attrelid AND c.oid = x.conrelid - AND x.contype = 'p' AND pg_table_is_visible(c.oid) - AND a.attnum = ANY(x.conkey) AND c.relname = $1 + AND x.contype = $2 + AND a.attnum = ANY(x.conkey) AND a.attnum > 0 AND NOT a.attisdropped - ORDER BY a.attnum - ), $2, $3 ); + ); +$$ LANGUAGE sql; + +-- col_is_pk( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ + SELECT is( _ckeys( $1, $2, 'p' ), $3, $4 ); +$$ LANGUAGE sql; + +-- col_is_pk( table, column, description ) +CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ + SELECT is( _ckeys( $1, 'p' ), $2, $3 ); $$ LANGUAGE sql; -- col_is_pk( table, column[] ) @@ -823,34 +838,12 @@ $$ LANGUAGE sql; -- has_fk( schema, table, description ) CREATE OR REPLACE FUNCTION has_fk ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT ok( - EXISTS( - SELECT true - FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, - pg_catalog.pg_constraint x - WHERE n.oid = c.relnamespace - AND c.oid = x.conrelid - AND n.nspname = $1 - AND c.relname = $2 - AND c.relhaspkey = true - AND x.contype = 'f' - ), $3 - ); + SELECT ok( _hasc( $1, $2, 'f' ), $3 ); $$ LANGUAGE sql; -- has_fk( table, description ) CREATE OR REPLACE FUNCTION has_fk ( TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT ok( - EXISTS( - SELECT true - FROM pg_catalog.pg_class c, pg_catalog.pg_constraint x - WHERE c.oid = x.conrelid - AND pg_catalog.pg_table_is_visible(c.oid) - AND c.relname = $1 - AND c.relhaspkey = true - AND x.contype = 'f' - ), $2 - ); + SELECT ok( _hasc( $1, 'f' ), $2 ); $$ LANGUAGE sql; -- has_fk( table ) @@ -860,37 +853,12 @@ $$ LANGUAGE sql; -- col_is_fk( schema, table, column, description ) CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ - SELECT is(ARRAY (SELECT a.attname::text - FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a, - pg_catalog.pg_constraint x - WHERE n.oid = c.relnamespace - AND c.oid = a.attrelid - AND c.oid = x.conrelid - AND x.contype = 'f' - AND a.attnum = ANY(x.conkey) - AND n.nspname = $1 - AND c.relname = $2 - AND a.attnum > 0 - AND NOT a.attisdropped - ORDER BY a.attnum - ), $3, $4 ); + SELECT is( _ckeys( $1, $2, 'f' ), $3, $4 ); $$ LANGUAGE sql; -- col_is_fk( table, column, description ) CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ - SELECT is(ARRAY (SELECT a.attname::text - FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, - pg_catalog.pg_constraint x - WHERE c.oid = a.attrelid - AND c.oid = x.conrelid - AND x.contype = 'f' - AND pg_table_is_visible(c.oid) - AND a.attnum = ANY(x.conkey) - AND c.relname = $1 - AND a.attnum > 0 - AND NOT a.attisdropped - ORDER BY a.attnum - ), $2, $3 ); + SELECT is( _ckeys( $1, 'f' ), $2, $3 ); $$ LANGUAGE sql; -- col_is_fk( table, column[] ) From 4bd16b6bb847373dd385e3f4c6266869c411c887 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 23 Aug 2008 00:07:06 +0000 Subject: [PATCH 0089/1195] Added `has_unique()` and `col_is_unique()`. --- Changes | 6 +- Makefile | 2 +- README.pgtap | 39 ++++++++++++ expected/unique.out | 28 ++++++++ pgtap.sql.in | 53 ++++++++++++++-- sql/fktap.sql | 2 +- sql/pktap.sql | 2 +- sql/unique.sql | 151 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 272 insertions(+), 11 deletions(-) create mode 100644 expected/unique.out create mode 100644 sql/unique.sql diff --git a/Changes b/Changes index 564ef6ad9f10..015638aee29b 100644 --- a/Changes +++ b/Changes @@ -30,10 +30,10 @@ Revision history for pgTAP Test::Builder 2. Also changed "caught/expected" to "caught/wanted" for nice parity when outputting diagnostics for exception testing. - Added the `has_table()`, `has_view()`, `has_column()`, `has_pk()`, - and `has_fk()` test functions. + `has_fk()`, and `has_unique()` test functions. - Added the `col_not_null()` and `col_is_null()` test functions. - - Added the `col_type_is()`, `col_default_is()`, `col_is_pk()` and - `col_is_fk()` test functions. + - Added the `col_type_is()`, `col_default_is()`, `col_is_pk()`, + `col_is_fk()`, and `col_is_unique()` test functions. - Fixed `is()` and `isnt()` to better handle NULLs. - Added the `--set ON_ERROR_STOP=1` option to the call to `psql` in `pg_prove`. diff --git a/Makefile b/Makefile index 615b26a6a2e6..662c801f523e 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ DATA_built = pgtap.sql uninstall_pgtap.sql DOCS = README.pgtap SCRIPTS = bin/pg_prove -REGRESS = moretap istap pg73 todotap matching throwtap hastap coltap pktap fktap +REGRESS = moretap istap pg73 todotap matching throwtap hastap coltap pktap fktap unique ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/README.pgtap b/README.pgtap index 1005ab98c3a0..258ac82a4b29 100644 --- a/README.pgtap +++ b/README.pgtap @@ -743,6 +743,45 @@ Will produce something like this: Just like `col_is_fk()`, except that it test that the column or array of columns are a primary key. +### `has_unique( schema, table, description )` ### +### `has_unique( table, description )` ### +### `has_unique( table )` ### + + SELECT has_unique( + 'myschema', + 'sometable', + 'Table myschema.sometable should have a unique constraint' + ); + +Tests whether or not a table has a unique key constraint. The first argument +is the schema name, the second the table name, the the third is the test +description. If the schema is omitted, the table must be visible in the search +path. If the test description is omitted, it will be set to "Table `:table` +should have a unique constraint". Note that this test will fail if the table +in question does not exist. + +### `col_is_uqique( schema, table, column, description )` ### +### `col_is_uqique( schema, table, column[], description )` ### +### `col_is_uqique( table, column, description )` ### +### `col_is_uqique( table, column[], description )` ### +### `col_is_uqique( table, column )` ### +### `col_is_uqique( table, column[] )` ### + + SELECT col_is_uqique( + 'myschema', + 'sometable', + 'other_id', + 'Column myschema.sometable.other_id should have a unique constraint' + ); + + SELECT col_is_uqique( + 'contacts', + ARRAY['first', 'last'], + ); + +Just like `col_is_pk()`, except that it test that the column or array of +columns have a unique constraint on them. + Diagnostics ----------- diff --git a/expected/unique.out b/expected/unique.out new file mode 100644 index 000000000000..9c898e264438 --- /dev/null +++ b/expected/unique.out @@ -0,0 +1,28 @@ +\set ECHO +1..26 +ok 1 - test has_unique( schema, table, description ) +ok 2 - has_unique( schema, table, description ) should work +ok 3 - test has_unique( table, description ) +ok 4 - has_unique( table, description ) should work +ok 5 - test has_unique( table ) +ok 6 - has_unique( table ) should work +ok 7 - test has_unique( schema, table, description ) fail +ok 8 - has_unique( schema, table, description ) should fail properly +ok 9 - test has_unique( table, description ) fail +ok 10 - has_unique( table, description ) should fail properly +ok 11 - test col_is_unique( schema, table, column, description ) +ok 12 - col_is_unique( schema, table, column, description ) should work +ok 13 - test col_is_unique( table, column, description ) +ok 14 - col_is_unique( table, column, description ) should work +ok 15 - test col_is_unique( table, column ) +ok 16 - col_is_unique( table, column ) should work +ok 17 - test col_is_unique( schema, table, column, description ) fail +ok 18 - col_is_unique( schema, table, column, description ) should fail properly +ok 19 - test col_is_unique( table, column, description ) fail +ok 20 - col_is_unique( table, column, description ) should fail properly +ok 21 - test col_is_unique( schema, table, column[], description ) +ok 22 - col_is_unique( schema, table, column[], description ) should work +ok 23 - test col_is_unique( table, column[], description ) +ok 24 - col_is_unique( table, column[], description ) should work +ok 25 - test col_is_unique( table, column[], description ) +ok 26 - col_is_unique( table, column[] ) should work diff --git a/pgtap.sql.in b/pgtap.sql.in index dd19bca87653..8bfea8ff6f5a 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -818,7 +818,7 @@ $$ LANGUAGE sql; -- col_is_pk( table, column[] ) CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT[] ) RETURNS TEXT AS $$ - SELECT col_is_pk( $1, $2, 'Column ' || $1 || '.' || $2::text || ' should be a primary key' ); + SELECT col_is_pk( $1, $2, 'Columns ' || $1 || '.' || $2::text || ' should be a primary key' ); $$ LANGUAGE sql; -- col_is_pk( schema, table, column, description ) @@ -863,7 +863,7 @@ $$ LANGUAGE sql; -- col_is_fk( table, column[] ) CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT[] ) RETURNS TEXT AS $$ - SELECT col_is_fk( $1, $2, 'Column ' || $1 || '.' || $2::text || ' should be a foreign key' ); + SELECT col_is_fk( $1, $2, 'Columns ' || $1 || '.' || $2::text || ' should be a foreign key' ); $$ LANGUAGE sql; -- col_is_fk( schema, table, column, description ) @@ -881,6 +881,51 @@ CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT ) RETURNS TEXT AS $$ SELECT col_is_fk( $1, $2, 'Column ' || $1 || '.' || $2 || ' should 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 ' || $1 || ' should have a unique constraint' ); +$$ LANGUAGE sql; + +-- col_is_unique( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ + SELECT is( _ckeys( $1, $2, 'u' ), $3, $4 ); +$$ LANGUAGE sql; + +-- col_is_unique( table, column, description ) +CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ + SELECT is( _ckeys( $1, 'u' ), $2, $3 ); +$$ LANGUAGE sql; + +-- col_is_unique( table, column[] ) +CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT[] ) RETURNS TEXT AS $$ + SELECT col_is_unique( $1, $2, 'Columns ' || $1 || '.' || $2::text || ' should have a unique constraint' ); +$$ LANGUAGE sql; + +-- col_is_unique( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT, TEXT, 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 ( TEXT, TEXT, 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 ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT col_is_unique( $1, $2, 'Column ' || $1 || '.' || $2 || ' should have a unique constraint' ); +$$ LANGUAGE sql; + -- fk_ok( schema, table, column[], schema, table, column[], description ) -- fk_ok( table, column[], table, column[] description ) -- fk_ok( table, column[], table, column[] ) @@ -888,7 +933,5 @@ $$ LANGUAGE sql; -- fk_ok( table, column, table, column description ) -- fk_ok( table, column, table, column ) --- has_unique( schema, table, description ) --- col_is_unique( schema, table, column[], description ) - -- has_check( schema, table, constraint, description ) +-- cmp_ok( $val, $op, $val, $description ) \ No newline at end of file diff --git a/sql/fktap.sql b/sql/fktap.sql index 89b073ffae6a..b2941bd9b6fe 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -154,7 +154,7 @@ SELECT is( \echo ok 25 - test col_is_fk( table, column[], description ) SELECT is( col_is_fk( 'fk2', ARRAY['pk2_num', 'pk2_dot'] ), - 'ok 25 - Column fk2.{pk2_num,pk2_dot} should be a foreign key', + 'ok 25 - Columns fk2.{pk2_num,pk2_dot} should be a foreign key', 'col_is_fk( table, column[] ) should work' ); diff --git a/sql/pktap.sql b/sql/pktap.sql index 8b83e0d45ac2..e46e1d34a264 100644 --- a/sql/pktap.sql +++ b/sql/pktap.sql @@ -141,7 +141,7 @@ SELECT is( \echo ok 25 - test col_is_pk( table, column[], description ) SELECT is( col_is_pk( 'argh', ARRAY['id', 'name'] ), - 'ok 25 - Column argh.{id,name} should be a primary key', + 'ok 25 - Columns argh.{id,name} should be a primary key', 'col_is_pk( table, column[] ) should work' ); diff --git a/sql/unique.sql b/sql/unique.sql new file mode 100644 index 000000000000..229652cc95a2 --- /dev/null +++ b/sql/unique.sql @@ -0,0 +1,151 @@ +\set ECHO +\set QUIET 1 + +-- +-- Tests for pgTAP. +-- +-- +-- $Id: pktap.sql 4210 2008-08-21 19:27:05Z david $ + +-- Format the output for nice TAP. +\pset format unaligned +\pset tuples_only true +\pset pager + +-- Create plpgsql if it's not already there. +SET client_min_messages = fatal; +\set ON_ERROR_STOP off +CREATE LANGUAGE plpgsql; + +-- Keep things quiet. +SET client_min_messages = warning; + +-- Revert all changes on failure. +\set ON_ERROR_ROLBACK 1 +\set ON_ERROR_STOP true + +-- Load the TAP functions. +BEGIN; +\i pgtap.sql + +-- ## SET search_path TO TAPSCHEMA,public; + +-- Set the test plan. +SELECT plan(26); + +-- This will be rolled back. :-) +CREATE TABLE sometab( + id INT NOT NULL PRIMARY KEY, + name TEXT DEFAULT '' UNIQUE, + numb NUMERIC(10, 2), + myint NUMERIC(8) +); + +/****************************************************************************/ +-- Test has_unique(). + +\echo ok 1 - test has_unique( schema, table, description ) +SELECT is( + has_unique( 'public', 'sometab', 'public.sometab should have a unique constraint' ), + 'ok 1 - public.sometab should have a unique constraint', + 'has_unique( schema, table, description ) should work' +); + +\echo ok 3 - test has_unique( table, description ) +SELECT is( + has_unique( 'sometab', 'sometab should have a unique constraint' ), + 'ok 3 - sometab should have a unique constraint', + 'has_unique( table, description ) should work' +); + +\echo ok 5 - test has_unique( table ) +SELECT is( + has_unique( 'sometab' ), + 'ok 5 - Table sometab should have a unique constraint', + 'has_unique( table ) should work' +); + +\echo ok 7 - test has_unique( schema, table, description ) fail +SELECT is( + has_unique( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should have a unique constraint' ), + E'not ok 7 - pg_catalog.pg_class should have a unique constraint\n# Failed test 7: "pg_catalog.pg_class should have a unique constraint"', + 'has_unique( schema, table, description ) should fail properly' +); + +\echo ok 9 - test has_unique( table, description ) fail +SELECT is( + has_unique( 'pg_class', 'pg_class should have a unique constraint' ), + E'not ok 9 - pg_class should have a unique constraint\n# Failed test 9: "pg_class should have a unique constraint"', + 'has_unique( table, description ) should fail properly' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 7, 9 ); + +/****************************************************************************/ +-- Test col_is_unique(). + +\echo ok 11 - test col_is_unique( schema, table, column, description ) +SELECT is( + col_is_unique( 'public', 'sometab', 'name', 'public.sometab.name should be a pk' ), + 'ok 11 - public.sometab.name should be a pk', + 'col_is_unique( schema, table, column, description ) should work' +); + +\echo ok 13 - test col_is_unique( table, column, description ) +SELECT is( + col_is_unique( 'sometab', 'name', 'sometab.name should be a pk' ), + 'ok 13 - sometab.name should be a pk', + 'col_is_unique( table, column, description ) should work' +); + +\echo ok 15 - test col_is_unique( table, column ) +SELECT is( + col_is_unique( 'sometab', 'name' ), + 'ok 15 - Column sometab.name should have a unique constraint', + 'col_is_unique( table, column ) should work' +); + +\echo ok 17 - test col_is_unique( schema, table, column, description ) fail +SELECT is( + col_is_unique( 'public', 'sometab', 'id', 'public.sometab.id should be a pk' ), + E'not ok 17 - public.sometab.id should be a pk\n# Failed test 17: "public.sometab.id should be a pk"\n# have: {name}\n# want: {id}', + 'col_is_unique( schema, table, column, description ) should fail properly' +); + +\echo ok 19 - test col_is_unique( table, column, description ) fail +SELECT is( + col_is_unique( 'sometab', 'id', 'sometab.id should be a pk' ), + E'not ok 19 - sometab.id should be a pk\n# Failed test 19: "sometab.id should be a pk"\n# have: {name}\n# want: {id}', + 'col_is_unique( table, column, description ) should fail properly' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 17, 19 ); + +/****************************************************************************/ +-- Test col_is_unique() with an array of columns. + +CREATE TABLE argh (id int not null, name text not null, unique (id, name)); + +\echo ok 21 - test col_is_unique( schema, table, column[], description ) +SELECT is( + col_is_unique( 'public', 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), + 'ok 21 - id + name should be a pk', + 'col_is_unique( schema, table, column[], description ) should work' +); + +\echo ok 23 - test col_is_unique( table, column[], description ) +SELECT is( + col_is_unique( 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), + 'ok 23 - id + name should be a pk', + 'col_is_unique( table, column[], description ) should work' +); + +\echo ok 25 - test col_is_unique( table, column[], description ) +SELECT is( + col_is_unique( 'argh', ARRAY['id', 'name'] ), + 'ok 25 - Columns argh.{id,name} should have a unique constraint', + 'col_is_unique( table, column[] ) should work' +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From 0c2ef6fe6a9999254a9c2fd19905fa8fc92e1874 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 23 Aug 2008 00:07:38 +0000 Subject: [PATCH 0090/1195] Keywords --- sql/unique.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/unique.sql b/sql/unique.sql index 229652cc95a2..a7878d345650 100644 --- a/sql/unique.sql +++ b/sql/unique.sql @@ -5,7 +5,7 @@ -- Tests for pgTAP. -- -- --- $Id: pktap.sql 4210 2008-08-21 19:27:05Z david $ +-- $Id$ -- Format the output for nice TAP. \pset format unaligned From d42619ff589362d49bb82d3756d0596e01572c3f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 23 Aug 2008 00:21:03 +0000 Subject: [PATCH 0091/1195] Added `has_check()` and `col_has_check()`. --- Changes | 5 +- Makefile | 2 +- README.pgtap | 59 +++++++++++++++--- expected/check.out | 28 +++++++++ pgtap.sql.in | 46 +++++++++++++- sql/check.sql | 151 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 277 insertions(+), 14 deletions(-) create mode 100644 expected/check.out create mode 100644 sql/check.sql diff --git a/Changes b/Changes index 015638aee29b..3ca93aa9bdf4 100644 --- a/Changes +++ b/Changes @@ -30,10 +30,11 @@ Revision history for pgTAP Test::Builder 2. Also changed "caught/expected" to "caught/wanted" for nice parity when outputting diagnostics for exception testing. - Added the `has_table()`, `has_view()`, `has_column()`, `has_pk()`, - `has_fk()`, and `has_unique()` test functions. + `has_fk()`, `has_unique()` and `has_check()` test functions. - Added the `col_not_null()` and `col_is_null()` test functions. - Added the `col_type_is()`, `col_default_is()`, `col_is_pk()`, - `col_is_fk()`, and `col_is_unique()` test functions. + `col_is_fk()`, `col_is_unique()`, and `col_has_check()` test + functions. - Fixed `is()` and `isnt()` to better handle NULLs. - Added the `--set ON_ERROR_STOP=1` option to the call to `psql` in `pg_prove`. diff --git a/Makefile b/Makefile index 662c801f523e..0ccae3329d5c 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ DATA_built = pgtap.sql uninstall_pgtap.sql DOCS = README.pgtap SCRIPTS = bin/pg_prove -REGRESS = moretap istap pg73 todotap matching throwtap hastap coltap pktap fktap unique +REGRESS = moretap istap pg73 todotap matching throwtap hastap coltap pktap fktap unique check ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/README.pgtap b/README.pgtap index 258ac82a4b29..22c9c134cb8a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -753,28 +753,28 @@ columns are a primary key. 'Table myschema.sometable should have a unique constraint' ); -Tests whether or not a table has a unique key constraint. The first argument -is the schema name, the second the table name, the the third is the test +Tests whether or not a table has a unique constraint. The first argument is +the schema name, the second the table name, the the third is the test description. If the schema is omitted, the table must be visible in the search path. If the test description is omitted, it will be set to "Table `:table` should have a unique constraint". Note that this test will fail if the table in question does not exist. -### `col_is_uqique( schema, table, column, description )` ### -### `col_is_uqique( schema, table, column[], description )` ### -### `col_is_uqique( table, column, description )` ### -### `col_is_uqique( table, column[], description )` ### -### `col_is_uqique( table, column )` ### -### `col_is_uqique( table, column[] )` ### +### `col_is_unique( schema, table, column, description )` ### +### `col_is_unique( schema, table, column[], description )` ### +### `col_is_unique( table, column, description )` ### +### `col_is_unique( table, column[], description )` ### +### `col_is_unique( table, column )` ### +### `col_is_unique( table, column[] )` ### - SELECT col_is_uqique( + SELECT col_is_unique( 'myschema', 'sometable', 'other_id', 'Column myschema.sometable.other_id should have a unique constraint' ); - SELECT col_is_uqique( + SELECT col_is_unique( 'contacts', ARRAY['first', 'last'], ); @@ -782,6 +782,45 @@ in question does not exist. Just like `col_is_pk()`, except that it test that the column or array of columns have a unique constraint on them. +### `has_check( schema, table, description )` ### +### `has_check( table, description )` ### +### `has_check( table )` ### + + SELECT has_check( + 'myschema', + 'sometable', + 'Table myschema.sometable should have a check constraint' + ); + +Tests whether or not a table has a check constraint. The first argument is the +schema name, the second the table name, the the third is the test description. +If the schema is omitted, the table must be visible in the search path. If the +test description is omitted, it will be set to "Table `:table` should have a +check constraint". Note that this test will fail if the table in question does +not exist. + +### `col_has_check( schema, table, column, description )` ### +### `col_has_check( schema, table, column[], description )` ### +### `col_has_check( table, column, description )` ### +### `col_has_check( table, column[], description )` ### +### `col_has_check( table, column )` ### +### `col_has_check( table, column[] )` ### + + SELECT col_has_check( + 'myschema', + 'sometable', + 'other_id', + 'Column myschema.sometable.other_id should have a check constraint' + ); + + SELECT col_has_check( + 'contacts', + ARRAY['first', 'last'], + ); + +Just like `col_is_pk()`, except that it test that the column or array of +columns have a check constraint on them. + Diagnostics ----------- diff --git a/expected/check.out b/expected/check.out new file mode 100644 index 000000000000..ececb6aef454 --- /dev/null +++ b/expected/check.out @@ -0,0 +1,28 @@ +\set ECHO +1..26 +ok 1 - test has_check( schema, table, description ) +ok 2 - has_check( schema, table, description ) should work +ok 3 - test has_check( table, description ) +ok 4 - has_check( table, description ) should work +ok 5 - test has_check( table ) +ok 6 - has_check( table ) should work +ok 7 - test has_check( schema, table, description ) fail +ok 8 - has_check( schema, table, description ) should fail properly +ok 9 - test has_check( table, description ) fail +ok 10 - has_check( table, description ) should fail properly +ok 11 - test col_has_check( schema, table, column, description ) +ok 12 - col_has_check( schema, table, column, description ) should work +ok 13 - test col_has_check( table, column, description ) +ok 14 - col_has_check( table, column, description ) should work +ok 15 - test col_has_check( table, column ) +ok 16 - col_has_check( table, column ) should work +ok 17 - test col_has_check( schema, table, column, description ) fail +ok 18 - col_has_check( schema, table, column, description ) should fail properly +ok 19 - test col_has_check( table, column, description ) fail +ok 20 - col_has_check( table, column, description ) should fail properly +ok 21 - test col_has_check( schema, table, column[], description ) +ok 22 - col_has_check( schema, table, column[], description ) should work +ok 23 - test col_has_check( table, column[], description ) +ok 24 - col_has_check( table, column[], description ) should work +ok 25 - test col_has_check( table, column[], description ) +ok 26 - col_has_check( table, column[] ) should work diff --git a/pgtap.sql.in b/pgtap.sql.in index 8bfea8ff6f5a..f723c2530cf5 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -926,6 +926,51 @@ CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT ) RETURNS TEXT AS $$ SELECT col_is_unique( $1, $2, 'Column ' || $1 || '.' || $2 || ' should have a unique constraint' ); $$ LANGUAGE sql; +-- has_check( schema, table, description ) +CREATE OR REPLACE FUNCTION has_check ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT ok( _hasc( $1, $2, 'c' ), $3 ); +$$ LANGUAGE sql; + +-- has_check( table, description ) +CREATE OR REPLACE FUNCTION has_check ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT ok( _hasc( $1, 'c' ), $2 ); +$$ LANGUAGE sql; + +-- has_check( table ) +CREATE OR REPLACE FUNCTION has_check ( TEXT ) RETURNS TEXT AS $$ + SELECT has_check( $1, 'Table ' || $1 || ' should have a check constraint' ); +$$ LANGUAGE sql; + +-- col_has_check( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ + SELECT is( _ckeys( $1, $2, 'c' ), $3, $4 ); +$$ LANGUAGE sql; + +-- col_has_check( table, column, description ) +CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ + SELECT is( _ckeys( $1, 'c' ), $2, $3 ); +$$ LANGUAGE sql; + +-- col_has_check( table, column[] ) +CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT[] ) RETURNS TEXT AS $$ + SELECT col_has_check( $1, $2, 'Columns ' || $1 || '.' || $2::text || ' should have a check constraint' ); +$$ LANGUAGE sql; + +-- col_has_check( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT, TEXT, 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 ( TEXT, TEXT, 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 ( TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT col_has_check( $1, $2, 'Column ' || $1 || '.' || $2 || ' should have a check constraint' ); +$$ LANGUAGE sql; + -- fk_ok( schema, table, column[], schema, table, column[], description ) -- fk_ok( table, column[], table, column[] description ) -- fk_ok( table, column[], table, column[] ) @@ -933,5 +978,4 @@ $$ LANGUAGE sql; -- fk_ok( table, column, table, column description ) -- fk_ok( table, column, table, column ) --- has_check( schema, table, constraint, description ) -- cmp_ok( $val, $op, $val, $description ) \ No newline at end of file diff --git a/sql/check.sql b/sql/check.sql new file mode 100644 index 000000000000..bdd8ff83749a --- /dev/null +++ b/sql/check.sql @@ -0,0 +1,151 @@ +\set ECHO +\set QUIET 1 + +-- +-- Tests for pgTAP. +-- +-- +-- $Id$ + +-- Format the output for nice TAP. +\pset format unaligned +\pset tuples_only true +\pset pager + +-- Create plpgsql if it's not already there. +SET client_min_messages = fatal; +\set ON_ERROR_STOP off +CREATE LANGUAGE plpgsql; + +-- Keep things quiet. +SET client_min_messages = warning; + +-- Revert all changes on failure. +\set ON_ERROR_ROLBACK 1 +\set ON_ERROR_STOP true + +-- Load the TAP functions. +BEGIN; +\i pgtap.sql + +-- ## SET search_path TO TAPSCHEMA,public; + +-- Set the test plan. +SELECT plan(26); + +-- This will be rolled back. :-) +CREATE TABLE sometab( + id INT NOT NULL PRIMARY KEY, + name TEXT DEFAULT '' CHECK ( name IN ('foo', 'bar', 'baz') ), + numb NUMERIC(10, 2), + myint NUMERIC(8) +); + +/****************************************************************************/ +-- Test has_check(). + +\echo ok 1 - test has_check( schema, table, description ) +SELECT is( + has_check( 'public', 'sometab', 'public.sometab should have a check constraint' ), + 'ok 1 - public.sometab should have a check constraint', + 'has_check( schema, table, description ) should work' +); + +\echo ok 3 - test has_check( table, description ) +SELECT is( + has_check( 'sometab', 'sometab should have a check constraint' ), + 'ok 3 - sometab should have a check constraint', + 'has_check( table, description ) should work' +); + +\echo ok 5 - test has_check( table ) +SELECT is( + has_check( 'sometab' ), + 'ok 5 - Table sometab should have a check constraint', + 'has_check( table ) should work' +); + +\echo ok 7 - test has_check( schema, table, description ) fail +SELECT is( + has_check( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should have a check constraint' ), + E'not ok 7 - pg_catalog.pg_class should have a check constraint\n# Failed test 7: "pg_catalog.pg_class should have a check constraint"', + 'has_check( schema, table, description ) should fail properly' +); + +\echo ok 9 - test has_check( table, description ) fail +SELECT is( + has_check( 'pg_class', 'pg_class should have a check constraint' ), + E'not ok 9 - pg_class should have a check constraint\n# Failed test 9: "pg_class should have a check constraint"', + 'has_check( table, description ) should fail properly' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 7, 9 ); + +/****************************************************************************/ +-- Test col_has_check(). + +\echo ok 11 - test col_has_check( schema, table, column, description ) +SELECT is( + col_has_check( 'public', 'sometab', 'name', 'public.sometab.name should be a pk' ), + 'ok 11 - public.sometab.name should be a pk', + 'col_has_check( schema, table, column, description ) should work' +); + +\echo ok 13 - test col_has_check( table, column, description ) +SELECT is( + col_has_check( 'sometab', 'name', 'sometab.name should be a pk' ), + 'ok 13 - sometab.name should be a pk', + 'col_has_check( table, column, description ) should work' +); + +\echo ok 15 - test col_has_check( table, column ) +SELECT is( + col_has_check( 'sometab', 'name' ), + 'ok 15 - Column sometab.name should have a check constraint', + 'col_has_check( table, column ) should work' +); + +\echo ok 17 - test col_has_check( schema, table, column, description ) fail +SELECT is( + col_has_check( 'public', 'sometab', 'id', 'public.sometab.id should be a pk' ), + E'not ok 17 - public.sometab.id should be a pk\n# Failed test 17: "public.sometab.id should be a pk"\n# have: {name}\n# want: {id}', + 'col_has_check( schema, table, column, description ) should fail properly' +); + +\echo ok 19 - test col_has_check( table, column, description ) fail +SELECT is( + col_has_check( 'sometab', 'id', 'sometab.id should be a pk' ), + E'not ok 19 - sometab.id should be a pk\n# Failed test 19: "sometab.id should be a pk"\n# have: {name}\n# want: {id}', + 'col_has_check( table, column, description ) should fail properly' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 17, 19 ); + +/****************************************************************************/ +-- Test col_has_check() with an array of columns. + +CREATE TABLE argh (id int not null, name text not null, check ( id IN (1, 2) AND name IN ('foo', 'bar'))); + +\echo ok 21 - test col_has_check( schema, table, column[], description ) +SELECT is( + col_has_check( 'public', 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), + 'ok 21 - id + name should be a pk', + 'col_has_check( schema, table, column[], description ) should work' +); + +\echo ok 23 - test col_has_check( table, column[], description ) +SELECT is( + col_has_check( 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), + 'ok 23 - id + name should be a pk', + 'col_has_check( table, column[], description ) should work' +); + +\echo ok 25 - test col_has_check( table, column[], description ) +SELECT is( + col_has_check( 'argh', ARRAY['id', 'name'] ), + 'ok 25 - Columns argh.{id,name} should have a check constraint', + 'col_has_check( table, column[] ) should work' +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From 1f2d0582237e68b44970486ad5a5d88db52aaa42 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 2 Sep 2008 04:16:42 +0000 Subject: [PATCH 0092/1195] * Added `pgtap.c`, with a first function, `type_of()`, which returns the data type of its argument. * Added `cmp_ok()`. This required `type_of()`. I have a few first tests for it, and will soon add more, as well as documentation. --- Makefile | 7 +++--- expected/cmpok.out | 8 ++++++ pgtap.c | 42 +++++++++++++++++++++++++++++++ pgtap.sql.in | 30 +++++++++++++++++++++- sql/cmpok.sql | 63 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 expected/cmpok.out create mode 100644 pgtap.c create mode 100644 sql/cmpok.sql diff --git a/Makefile b/Makefile index 0ccae3329d5c..4ba68566c617 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ # $Id$ DATA_built = pgtap.sql uninstall_pgtap.sql +MODULES = pgtap DOCS = README.pgtap SCRIPTS = bin/pg_prove -REGRESS = moretap istap pg73 todotap matching throwtap hastap coltap pktap fktap unique check +REGRESS = moretap istap pg73 todotap matching throwtap hastap coltap pktap fktap unique check cmpok ifdef USE_PGXS PG_CONFIG = pg_config @@ -19,9 +20,9 @@ endif # necessary. Otherwise just copy the files. %.sql: %.sql.in ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' $< >$@ + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/$*,g' $< >$@ else - cp $< $@ + sed -e 's,MODULE_PATHNAME,$$libdir/$*,g' $< >$@ endif # In addition to installcheck, one can also run the tests through pg_prove. diff --git a/expected/cmpok.out b/expected/cmpok.out new file mode 100644 index 000000000000..631acb363dbd --- /dev/null +++ b/expected/cmpok.out @@ -0,0 +1,8 @@ +\set ECHO +1..6 +ok 1 - test cmp_ok( int, =, int, description ) +ok 2 - test cmp_ok( int, =, int, description ) should work +ok 3 - test cmp_ok( int, <>, int, description ) +ok 4 - test cmp_ok( int, <>, int ) should work +ok 5 - test cmp_ok( polygon, ~=, polygon ) +ok 6 - test test cmp_ok( polygon, ~=, polygon ) should work diff --git a/pgtap.c b/pgtap.c new file mode 100644 index 000000000000..86559e10e680 --- /dev/null +++ b/pgtap.c @@ -0,0 +1,42 @@ +/* + * PostgreSQL utility functions for pgTAP. + */ + +#include "postgres.h" +#include "fmgr.h" +#include "utils/builtins.h" + +#ifdef PG_MODULE_MAGIC +PG_MODULE_MAGIC; +#endif + +extern Datum type_of (PG_FUNCTION_ARGS); + +/* + * type_of() + * Returns a string for the data type of an anyelement argument. + */ + +PG_FUNCTION_INFO_V1(type_of); + +Datum +type_of(PG_FUNCTION_ARGS) +{ + Oid typeoid; + Datum result; + char *typename; + + typeoid = get_fn_expr_argtype(fcinfo->flinfo, 0); + if (typeoid == InvalidOid) { + ereport( + ERROR, ( + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine data type of argument to type_of()") + ) + ); + } + + typename = format_type_be(typeoid); + result = DirectFunctionCall1(textin, CStringGetDatum(typename)); + PG_RETURN_DATUM(result); +} diff --git a/pgtap.sql.in b/pgtap.sql.in index f723c2530cf5..e678cdf728c5 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -12,6 +12,11 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA,public; +CREATE OR REPLACE FUNCTION type_of(anyelement) +RETURNS text +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ DECLARE rcount INTEGER; @@ -978,4 +983,27 @@ $$ LANGUAGE sql; -- fk_ok( table, column, table, column description ) -- fk_ok( table, column, table, column ) --- cmp_ok( $val, $op, $val, $description ) \ No newline at end of file +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 ' || quote_literal( have ) || '::' || type_of(have) || ' ' + || op || ' ' || quote_literal( want ) || '::' || type_of(want) INTO result; + output := ok( result, descr ); + RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( + ' ' || COALESCE( quote_literal(have::text), 'NULL' ) || + E'\n ' || op || + E'\n ' || COALESCE( quote_literal(want::text), '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; + diff --git a/sql/cmpok.sql b/sql/cmpok.sql new file mode 100644 index 000000000000..ae24ac9af068 --- /dev/null +++ b/sql/cmpok.sql @@ -0,0 +1,63 @@ +\set ECHO +\set QUIET 1 + +-- +-- Tests for pgTAP. +-- +-- +-- $Id: check.sql 4226 2008-08-23 00:21:03Z david $ + +-- Format the output for nice TAP. +\pset format unaligned +\pset tuples_only true +\pset pager + +-- Create plpgsql if it's not already there. +SET client_min_messages = fatal; +\set ON_ERROR_STOP off +CREATE LANGUAGE plpgsql; + +-- Keep things quiet. +SET client_min_messages = warning; + +-- Revert all changes on failure. +\set ON_ERROR_ROLBACK 1 +\set ON_ERROR_STOP true + +-- Load the TAP functions. +BEGIN; +\i pgtap.sql + +-- ## SET search_path TO TAPSCHEMA,public; + +-- Set the test plan. +SELECT plan(6); + +/****************************************************************************/ +-- Test cmp_ok(). +\echo ok 1 - test cmp_ok( int, =, int, description ) +SELECT is( + cmp_ok( 1, '=', 1, '1 should = 1' ), + 'ok 1 - 1 should = 1', + 'test cmp_ok( int, =, int, description ) should work' +); + +\echo ok 3 - test cmp_ok( int, <>, int, description ) +SELECT is( + cmp_ok( 1, '<>', 2, '1 should <> 2' ), + 'ok 3 - 1 should <> 2', + 'test cmp_ok( int, <>, int ) should work' +); + +\echo ok 5 - test cmp_ok( polygon, ~=, polygon ) +SELECT is( + cmp_ok( '((0,0),(1,1))'::polygon, '~=', '((1,1),(0,0))'::polygon ), + 'ok 5', + 'test test cmp_ok( polygon, ~=, polygon ) should work' +); + + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From 0986fe2523f83c03833a8a21bbebc8a70e039f1e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 2 Sep 2008 19:39:12 +0000 Subject: [PATCH 0093/1195] * Added tests for comparing arrays with `cmp_ok()`. * Added a test for the failure output from `cmp_ok()`. Tweaked that format a bit to get it right. * Added documentation for `cmp_ok()` to `README.pgtap`. * Changed the name of `type_of()` to `pg_typeof()` to reflect what will likely soon be committed to PostgreSQL HEAD. * Changed the argument to `pg_typeof()` from "anyelement" to "any". --- Changes | 1 + README.pgtap | 26 ++++++++++++++++++++++++++ expected/cmpok.out | 14 ++++++++++---- pgtap.c | 18 ++++++++---------- pgtap.sql.in | 12 ++++++------ sql/cmpok.sql | 38 ++++++++++++++++++++++++++++++++++---- 6 files changed, 85 insertions(+), 24 deletions(-) diff --git a/Changes b/Changes index 3ca93aa9bdf4..62de2d0ac8e5 100644 --- a/Changes +++ b/Changes @@ -36,6 +36,7 @@ Revision history for pgTAP `col_is_fk()`, `col_is_unique()`, and `col_has_check()` test functions. - Fixed `is()` and `isnt()` to better handle NULLs. + - Added `cmp_ok()`. - Added the `--set ON_ERROR_STOP=1` option to the call to `psql` in `pg_prove`. - Renamed `drop_pgtap.sql.in` to `uninstall_pgtap.sql.in`, which is more diff --git a/README.pgtap b/README.pgtap index 22c9c134cb8a..401a61c86528 100644 --- a/README.pgtap +++ b/README.pgtap @@ -392,6 +392,32 @@ diagnostics on failure. Works exactly as `alike()`, only it checks if `:this` *does not* match the given pattern. +### `cmp_ok( anyelement, operator, anyelement, description )` ### +### `cmp_ok( anyelement, operator, anyelement )` ### + + SELECT cmp_ok( :this, :op, :that, :description ); + +Halfway between `ok()` and `is()` lies `cmp_ok()`. This function allows you to +compare two arguments using any binary operator. + + -- ok( :this = :that ); + cmp_ok( :this, '=', :that, 'this = that' ); + + -- ok( :this >= :that ); + cmp_ok( :this, '>=, 'this >= that' ); + + -- ok( :this && :that ); + cmp_ok( :this, '&&', :that, 'this && that' ); + +Its advantage over `ok()` is that when the test fails you'll know what `:this` +and `:that` were: + + not ok 1 + # Failed test + # '23' + # && + # NULL + ### `pass( description )` ### ### `pass()` ### ### `fail( description )` ### diff --git a/expected/cmpok.out b/expected/cmpok.out index 631acb363dbd..2dbb16e0451f 100644 --- a/expected/cmpok.out +++ b/expected/cmpok.out @@ -1,8 +1,14 @@ \set ECHO -1..6 +1..12 ok 1 - test cmp_ok( int, =, int, description ) -ok 2 - test cmp_ok( int, =, int, description ) should work +ok 2 - cmp_ok( int, =, int, description ) should work ok 3 - test cmp_ok( int, <>, int, description ) -ok 4 - test cmp_ok( int, <>, int ) should work +ok 4 - cmp_ok( int, <>, int ) should work ok 5 - test cmp_ok( polygon, ~=, polygon ) -ok 6 - test test cmp_ok( polygon, ~=, polygon ) should work +ok 6 - cmp_ok( polygon, ~=, polygon ) should work +ok 7 - test cmp_ok( int[], =, int[] ) +ok 8 - cmp_ok( int[], =, int[] ) should work +ok 9 - test cmp_ok( inet[], =, inet[] ) +ok 10 - cmp_ok( inet[], =, inet[] ) should work +ok 11 - Test cmp_ok() failure output +ok 12 - cmp_ok() failure output should be correct diff --git a/pgtap.c b/pgtap.c index 86559e10e680..fdbd0ff69b0b 100644 --- a/pgtap.c +++ b/pgtap.c @@ -10,33 +10,31 @@ PG_MODULE_MAGIC; #endif -extern Datum type_of (PG_FUNCTION_ARGS); +extern Datum pg_typeof (PG_FUNCTION_ARGS); /* - * type_of() + * pg_typeof() * Returns a string for the data type of an anyelement argument. */ -PG_FUNCTION_INFO_V1(type_of); +PG_FUNCTION_INFO_V1(pg_regtypeof); Datum -type_of(PG_FUNCTION_ARGS) +pg_typeof(PG_FUNCTION_ARGS) { Oid typeoid; - Datum result; - char *typename; typeoid = get_fn_expr_argtype(fcinfo->flinfo, 0); if (typeoid == InvalidOid) { ereport( ERROR, ( errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("could not determine data type of argument to type_of()") + errmsg("could not determine data type of argument to pg_typeof()") ) ); } - typename = format_type_be(typeoid); - result = DirectFunctionCall1(textin, CStringGetDatum(typename)); - PG_RETURN_DATUM(result); + PG_RETURN_DATUM( + DirectFunctionCall1(textin, CStringGetDatum(format_type_be(typeoid))) + ); } diff --git a/pgtap.sql.in b/pgtap.sql.in index e678cdf728c5..b76cf0400dd7 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -12,10 +12,10 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA,public; -CREATE OR REPLACE FUNCTION type_of(anyelement) +CREATE OR REPLACE FUNCTION pg_typeof("any") RETURNS text AS 'MODULE_PATHNAME' -LANGUAGE C STRICT IMMUTABLE; +LANGUAGE C IMMUTABLE; CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ DECLARE @@ -992,18 +992,18 @@ DECLARE result BOOLEAN; output TEXT; BEGIN - EXECUTE 'SELECT ' || quote_literal( have ) || '::' || type_of(have) || ' ' - || op || ' ' || quote_literal( want ) || '::' || type_of(want) INTO result; + EXECUTE 'SELECT ' || quote_literal( have ) || '::' || pg_typeof(have) || ' ' + || op || ' ' || quote_literal( want ) || '::' || pg_typeof(want) INTO result; output := ok( result, descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( ' ' || COALESCE( quote_literal(have::text), 'NULL' ) || - E'\n ' || op || + E'\n ' || op || E'\n ' || COALESCE( quote_literal(want::text), 'NULL' ) ) END; END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION cmp_ok (anyelement, text, anyelement ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION cmp_ok (anyelement, text, anyelement) RETURNS TEXT AS $$ SELECT cmp_ok( $1, $2, $3, NULL ); $$ LANGUAGE sql; diff --git a/sql/cmpok.sql b/sql/cmpok.sql index ae24ac9af068..d6d934f446a2 100644 --- a/sql/cmpok.sql +++ b/sql/cmpok.sql @@ -31,7 +31,7 @@ BEGIN; -- ## SET search_path TO TAPSCHEMA,public; -- Set the test plan. -SELECT plan(6); +SELECT plan(12); /****************************************************************************/ -- Test cmp_ok(). @@ -39,25 +39,55 @@ SELECT plan(6); SELECT is( cmp_ok( 1, '=', 1, '1 should = 1' ), 'ok 1 - 1 should = 1', - 'test cmp_ok( int, =, int, description ) should work' + 'cmp_ok( int, =, int, description ) should work' ); \echo ok 3 - test cmp_ok( int, <>, int, description ) SELECT is( cmp_ok( 1, '<>', 2, '1 should <> 2' ), 'ok 3 - 1 should <> 2', - 'test cmp_ok( int, <>, int ) should work' + 'cmp_ok( int, <>, int ) should work' ); \echo ok 5 - test cmp_ok( polygon, ~=, polygon ) SELECT is( cmp_ok( '((0,0),(1,1))'::polygon, '~=', '((1,1),(0,0))'::polygon ), 'ok 5', - 'test test cmp_ok( polygon, ~=, polygon ) should work' + 'cmp_ok( polygon, ~=, polygon ) should work' ); +\echo ok 7 - test cmp_ok( int[], =, int[] ) +SELECT is( + cmp_ok( ARRAY[1, 2], '=', ARRAY[1, 2]), + 'ok 7', + 'cmp_ok( int[], =, int[] ) should work' +); + +\echo ok 9 - test cmp_ok( inet[], =, inet[] ) +SELECT is( + cmp_ok( ARRAY['192.168.1.2'::inet], '=', ARRAY['192.168.1.2'::inet] ), + 'ok 9', + 'cmp_ok( inet[], =, inet[] ) should work' +); + +\echo ok 11 - Test cmp_ok() failure output +SELECT is( + cmp_ok( 1, '=', 2, '1 should = 2' ), + 'not ok 11 - 1 should = 2 +# Failed test 11: "1 should = 2" +# ''1'' +# = +# ''2''', + 'cmp_ok() failure output should be correct' +); + +-- Clean up the failed test results. +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 11); /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); ROLLBACK; + +-- Spam fingerprints: Contains an exact font color, and the words in the title are the same as in the body. +-- rule that extracts the existing google ad ID, a string, get from original special features script. From 39ebe5f56dfcde640f126caee5fd102368782821 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 2 Sep 2008 20:00:37 +0000 Subject: [PATCH 0094/1195] Added a test for a `NULL` passed to `cmp_ok()`, and then fixed it to actually work properly. The upshot is that `cmp_ok()` now assmes that a return value of `NULL` means that the test failed, which may not be exactly what you want. Nothing we can do to work around the trinary value of `NULL` here, I'm afraid. --- README.pgtap | 14 +++++++++++--- expected/cmpok.out | 4 +++- pgtap.sql.in | 9 ++++++--- sql/cmpok.sql | 15 +++++++++++++-- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/README.pgtap b/README.pgtap index 401a61c86528..8dcc943cb289 100644 --- a/README.pgtap +++ b/README.pgtap @@ -401,13 +401,13 @@ Halfway between `ok()` and `is()` lies `cmp_ok()`. This function allows you to compare two arguments using any binary operator. -- ok( :this = :that ); - cmp_ok( :this, '=', :that, 'this = that' ); + SELECT cmp_ok( :this, '=', :that, 'this = that' ); -- ok( :this >= :that ); - cmp_ok( :this, '>=, 'this >= that' ); + SELECT cmp_ok( :this, '>=, 'this >= that' ); -- ok( :this && :that ); - cmp_ok( :this, '&&', :that, 'this && that' ); + SELECT cmp_ok( :this, '&&', :that, 'this && that' ); Its advantage over `ok()` is that when the test fails you'll know what `:this` and `:that` were: @@ -418,6 +418,14 @@ and `:that` were: # && # NULL +Note that if the value returned by the operation is `NULL`, the test will +be considered to have failed. This may not be what you expect if your test +was, for example: + + SELECT cmp_ok( NULL, '=', NULL ); + +But in that case, you should probably use `is()`, instead. + ### `pass( description )` ### ### `pass()` ### ### `fail( description )` ### diff --git a/expected/cmpok.out b/expected/cmpok.out index 2dbb16e0451f..24165c30db1a 100644 --- a/expected/cmpok.out +++ b/expected/cmpok.out @@ -1,5 +1,5 @@ \set ECHO -1..12 +1..14 ok 1 - test cmp_ok( int, =, int, description ) ok 2 - cmp_ok( int, =, int, description ) should work ok 3 - test cmp_ok( int, <>, int, description ) @@ -12,3 +12,5 @@ ok 9 - test cmp_ok( inet[], =, inet[] ) ok 10 - cmp_ok( inet[], =, inet[] ) should work ok 11 - Test cmp_ok() failure output ok 12 - cmp_ok() failure output should be correct +ok 13 - Test cmp_ok() failure output +ok 14 - cmp_ok() failure output should be correct diff --git a/pgtap.sql.in b/pgtap.sql.in index b76cf0400dd7..437431e47de9 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -992,9 +992,12 @@ DECLARE result BOOLEAN; output TEXT; BEGIN - EXECUTE 'SELECT ' || quote_literal( have ) || '::' || pg_typeof(have) || ' ' - || op || ' ' || quote_literal( want ) || '::' || pg_typeof(want) INTO result; - output := ok( result, descr ); + 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::text), 'NULL' ) || E'\n ' || op || diff --git a/sql/cmpok.sql b/sql/cmpok.sql index d6d934f446a2..7c7f1016fc57 100644 --- a/sql/cmpok.sql +++ b/sql/cmpok.sql @@ -31,7 +31,7 @@ BEGIN; -- ## SET search_path TO TAPSCHEMA,public; -- Set the test plan. -SELECT plan(12); +SELECT plan(14); /****************************************************************************/ -- Test cmp_ok(). @@ -81,8 +81,19 @@ SELECT is( 'cmp_ok() failure output should be correct' ); +\echo ok 13 - Test cmp_ok() failure output +SELECT is( + cmp_ok( 1, '=', NULL, '1 should = NULL' ), + 'not ok 13 - 1 should = NULL +# Failed test 13: "1 should = NULL" +# ''1'' +# = +# NULL', + 'cmp_ok() failure output should be correct' +); + -- Clean up the failed test results. -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 11); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 11, 13 ); /****************************************************************************/ -- Finish the tests and clean up. From 21d2c12a621f2010e0c0b8cd1cb22f7b81387df2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 2 Sep 2008 23:03:43 +0000 Subject: [PATCH 0095/1195] Nasty typo. --- pgtap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgtap.c b/pgtap.c index fdbd0ff69b0b..8ae77229049b 100644 --- a/pgtap.c +++ b/pgtap.c @@ -17,7 +17,7 @@ extern Datum pg_typeof (PG_FUNCTION_ARGS); * Returns a string for the data type of an anyelement argument. */ -PG_FUNCTION_INFO_V1(pg_regtypeof); +PG_FUNCTION_INFO_V1(pg_typeof); Datum pg_typeof(PG_FUNCTION_ARGS) From cfb0d1cb4f37ae46ab1155f704a2a7c3eaa7c9fa Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 2 Sep 2008 23:06:11 +0000 Subject: [PATCH 0096/1195] Changed `pg_typeof()` to be just like what will likely be submitted to core. By having it simply return `regtype` for an OID, it magically gives me just what I need. --- pgtap.c | 16 +--------------- pgtap.sql.in | 2 +- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/pgtap.c b/pgtap.c index 8ae77229049b..0984f0495ff9 100644 --- a/pgtap.c +++ b/pgtap.c @@ -22,19 +22,5 @@ PG_FUNCTION_INFO_V1(pg_typeof); Datum pg_typeof(PG_FUNCTION_ARGS) { - Oid typeoid; - - typeoid = get_fn_expr_argtype(fcinfo->flinfo, 0); - if (typeoid == InvalidOid) { - ereport( - ERROR, ( - errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("could not determine data type of argument to pg_typeof()") - ) - ); - } - - PG_RETURN_DATUM( - DirectFunctionCall1(textin, CStringGetDatum(format_type_be(typeoid))) - ); + PG_RETURN_OID( get_fn_expr_argtype(fcinfo->flinfo, 0) ); } diff --git a/pgtap.sql.in b/pgtap.sql.in index 437431e47de9..7889e83af4e2 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -13,7 +13,7 @@ -- ## SET search_path TO TAPSCHEMA,public; CREATE OR REPLACE FUNCTION pg_typeof("any") -RETURNS text +RETURNS regtype AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; From b2b72069269af45fe291cd75028468df8b4c208c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 2 Sep 2008 23:07:53 +0000 Subject: [PATCH 0097/1195] Comment. --- pgtap.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pgtap.c b/pgtap.c index 0984f0495ff9..9d238d29fb7c 100644 --- a/pgtap.c +++ b/pgtap.c @@ -14,7 +14,8 @@ extern Datum pg_typeof (PG_FUNCTION_ARGS); /* * pg_typeof() - * Returns a string for the data type of an anyelement argument. + * Returns the OID for the data type of its first argument. The SQL function + * returns regtype, which magically makes it return text. */ PG_FUNCTION_INFO_V1(pg_typeof); From 0327c156b9e37ca981a4bc58b7907cecb0fdda94 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 2 Sep 2008 23:12:43 +0000 Subject: [PATCH 0098/1195] Don't need that. --- pgtap.c | 1 - 1 file changed, 1 deletion(-) diff --git a/pgtap.c b/pgtap.c index 9d238d29fb7c..9b898e15a6bd 100644 --- a/pgtap.c +++ b/pgtap.c @@ -3,7 +3,6 @@ */ #include "postgres.h" -#include "fmgr.h" #include "utils/builtins.h" #ifdef PG_MODULE_MAGIC From 523fc55d4b80deba4cfa93dd6dca226bfbc028ba Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 3 Sep 2008 00:28:24 +0000 Subject: [PATCH 0099/1195] Just moving `cmp_ok()`. --- pgtap.sql.in | 55 ++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 7889e83af4e2..91a38a584dba 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -358,6 +358,33 @@ 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::text), 'NULL' ) || + E'\n ' || op || + E'\n ' || COALESCE( quote_literal(want::text), '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; @@ -982,31 +1009,3 @@ $$ LANGUAGE sql; -- fk_ok( schema, table, column, schema, table, column, description ) -- fk_ok( table, column, table, column description ) -- fk_ok( table, column, table, column ) - -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::text), 'NULL' ) || - E'\n ' || op || - E'\n ' || COALESCE( quote_literal(want::text), '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; - From c30780957d9bec15e52b19b38cc1abd49ebc1b25 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 3 Sep 2008 23:52:27 +0000 Subject: [PATCH 0100/1195] * Implemented `test_ok()`. Need to add documentation for it and convert all tests. * Implemented `fk_ok()`. Need to add documentation for it. --- pgtap.sql.in | 171 +++++++++++++++++++++++++++++++++++++++++++++++--- sql/fktap.sql | 69 +++++++++++++++++++- 2 files changed, 229 insertions(+), 11 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 91a38a584dba..85296e77f26e 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -159,13 +159,12 @@ BEGIN || ' of ' || exp_tests ); ELSE - + END IF; RETURN; END; $$ LANGUAGE plpgsql; - CREATE OR REPLACE FUNCTION diag ( msg text ) RETURNS TEXT AS $$ BEGIN RETURN regexp_replace( msg, '^', '# ', 'gn' ); @@ -199,7 +198,7 @@ BEGIN || CASE descr WHEN '' THEN '' ELSE COALESCE( ' - ' || substr(regexp_replace( descr, '^', '# ', 'gn' ), 3), '' ) END || COALESCE( ' ' || regexp_replace( 'TODO ' || todo_why, '^', '# ', 'gn' ), '') || CASE aok WHEN TRUE THEN '' ELSE E'\n' || - diag('Failed ' || + diag('Failed ' || CASE WHEN todo_why IS NULL THEN '' ELSE '(TODO) ' END || 'test ' || test_num || CASE descr WHEN '' THEN '' ELSE COALESCE(': "' || descr || '"', '') END ) @@ -207,7 +206,7 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION ok ( boolean ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION ok ( boolean ) RETURNS TEXT AS $$ SELECT ok( $1, NULL ); $$ LANGUAGE SQL; @@ -1003,9 +1002,161 @@ CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT ) RETURNS TEXT AS $$ SELECT col_has_check( $1, $2, 'Column ' || $1 || '.' || $2 || ' should have a check constraint' ); $$ LANGUAGE sql; --- fk_ok( schema, table, column[], schema, table, column[], description ) --- fk_ok( table, column[], table, column[] description ) --- fk_ok( table, column[], table, column[] ) --- fk_ok( schema, table, column, schema, table, column, description ) --- fk_ok( table, column, table, column description ) --- fk_ok( table, column, table, column ) +-- fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description ) +CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ + SELECT is( + $1 || '.' || $2 || '(' || array_to_string( _ckeys( $1, $2, 'f' ), ', ' ) + || ') REFERENCES ' || + $4 || '.' || $5 || '(' || array_to_string( _ckeys( $4, $5, 'p' ), ', ' ) || ')', + $1 || '.' || $2 || '(' || array_to_string( $3, ', ' ) + || ') REFERENCES ' || + $4 || '.' || $5 || '(' || array_to_string( $6, ', ' ) || ')', + $7 + ); +$$ LANGUAGE sql; + +-- fk_ok( fk_table, fk_column[], pk_table, pk_column[], description ) +CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT[], TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ + SELECT is( + $1 || '(' || array_to_string( _ckeys( $1, 'f' ), ', ' ) + || ') REFERENCES ' || + $3 || '(' || array_to_string( _ckeys( $3, 'p' ), ', ' ) || ')', + $1 || '(' || array_to_string( $2, ', ' ) + || ') REFERENCES ' || + $3 || '(' || array_to_string( $4, ', ' ) || ')', + $5 + ); +$$ LANGUAGE sql; + +-- fk_ok( fk_schema, fk_table, fk_column[], fk_schema, pk_table, pk_column[] ) +CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[] ) RETURNS TEXT AS $$ + SELECT fk_ok( $1, $2, $3, $4, $5, $6, + $1 || '.' || $2 || '(' || array_to_string( $3, ', ' ) + || ') should reference ' || + $4 || '.' || $5 || '(' || array_to_string( $6, ', ' ) || ')' + ); +$$ LANGUAGE sql; + +-- fk_ok( fk_table, fk_column[], pk_table, pk_column[] ) +CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT[], TEXT, TEXT[] ) RETURNS TEXT AS $$ + SELECT fk_ok( $1, $2, $3, $4, + $1 || '(' || array_to_string( $2, ', ' ) + || ') should reference ' || + $3 || '(' || 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 ( TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, 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 ( TEXT, TEXT, TEXT, TEXT, TEXT, 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 ( TEXT, TEXT, TEXT, TEXT, 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 ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ + SELECT fk_ok( $1, ARRAY[$2], $3, ARRAY[$4] ); +$$ LANGUAGE sql; + +-- test_ok( test_output, pass, name, description, diag ) +CREATE OR REPLACE FUNCTION test_ok( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) 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; +BEGIN + -- What test was it that just ran? + tnumb := currval('__tresults___numb_seq'); + + -- Fetch the results. + EXECUTE 'SELECT aok, descr FROM __tresults__ WHERE numb = ' || tnumb + INTO aok, adescr; + + -- Now delete those results. + EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb; + + -- Set up the description. + descr := coalesce( name || ' ', '' ) || '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 8 END + char_length(tnumb::text) + ); + + -- Remove the description, if there is one. + adiag := substring( adiag FROM 3 + char_length( diag( adescr ) ) ); + + -- Remove failure message from ok(). + IF NOT aok THEN + adiag := substring( + adiag + FROM 14 + char_length(tnumb::text) + 4 + char_length( diag( adescr ) ) + ); + END IF; + + -- Remove the #s. + adiag := regexp_replace( adiag, '^# ', '', 'gn' ); + + -- Now compare the diagnostics. + RETURN NEXT is( + adiag, + ediag, + descr || 'have the proper diagnostics' + ); + END IF; + + -- And we're done + RETURN; +END; +$$ LANGUAGE plpgsql; + +-- test_ok( test_output, pass, name, description ) +CREATE OR REPLACE FUNCTION test_ok( TEXT, BOOLEAN, TEXT, TEXT ) RETURNS SETOF TEXT AS $$ + SELECT * FROM test_ok( $1, $2, $3, $4, NULL ); +$$ LANGUAGE sql; + +-- test_ok( test_output, pass, name ) +CREATE OR REPLACE FUNCTION test_ok( TEXT, BOOLEAN, TEXT ) RETURNS SETOF TEXT AS $$ + SELECT * FROM test_ok( $1, $2, $3, NULL, NULL ); +$$ LANGUAGE sql; + +-- test_ok( test_output, pass ) +CREATE OR REPLACE FUNCTION test_ok( TEXT, BOOLEAN ) RETURNS SETOF TEXT AS $$ + SELECT * FROM test_ok( $1, $2, NULL, NULL, NULL ); +$$ LANGUAGE sql; diff --git a/sql/fktap.sql b/sql/fktap.sql index b2941bd9b6fe..efc2e65d5e8c 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -31,7 +31,7 @@ BEGIN; -- ## SET search_path TO TAPSCHEMA,public; -- Set the test plan. -SELECT plan(26); +SELECT plan(45); -- These will be rolled back. :-) CREATE TABLE pk ( @@ -158,6 +158,73 @@ SELECT is( 'col_is_fk( table, column[] ) should work' ); +/****************************************************************************/ +-- Test fk_ok(). +SELECT * FROM test_ok( + fk_ok( 'public', 'fk', ARRAY['pk_id'], 'public', 'pk', ARRAY['id'], 'WHATEVER' ), + true, + 'full fk_ok array', + 'WHATEVER' +); + +SELECT * FROM test_ok( + fk_ok( 'public', 'fk', ARRAY['pk_id'], 'public', 'pk', ARRAY['id'] ), + true, + 'fk_ok array desc', + 'public.fk(pk_id) should reference public.pk(id)' +); + +SELECT * FROM test_ok( + fk_ok( 'fk', ARRAY['pk_id'], 'pk', ARRAY['id'] ), + true, + 'fk_ok array noschema desc', + 'fk(pk_id) should reference pk(id)' +); + +SELECT * FROM test_ok( + fk_ok( 'fk', ARRAY['pk_id'], 'pk', ARRAY['id'], 'WHATEVER' ), + true, + 'fk_ok array noschema', + 'WHATEVER' +); + +SELECT * FROM test_ok( + fk_ok( 'public', 'fk', 'pk_id', 'public', 'pk', 'id', 'WHATEVER' ), + true, + 'basic fk_ok', + 'WHATEVER' +); + +SELECT * FROM test_ok( + fk_ok( 'public', 'fk', 'pk_id', 'public', 'pk', 'id' ), + true, + 'basic fk_ok desc', + 'public.fk(pk_id) should reference public.pk(id)' +); + +SELECT * FROM test_ok( + fk_ok( 'fk', 'pk_id', 'pk', 'id', 'WHATEVER' ), + true, + 'basic fk_ok noschema', + 'WHATEVER' +); + +SELECT * FROM test_ok( + fk_ok( 'fk', 'pk_id', 'pk', 'id' ), + true, + 'basic fk_ok noschema desc', + 'fk(pk_id) should reference pk(id)' +); + +SELECT * FROM test_ok( + fk_ok( 'public', 'fk', ARRAY['pk_id'], 'public', 'pk', ARRAY['fid'], 'WHATEVER' ), + false, + 'fk_ok fail', + 'WHATEVER', + ' have: public.fk(pk_id) REFERENCES public.pk(id) + want: public.fk(pk_id) REFERENCES public.pk(fid)' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 1af62902d0dc2f0a11a3e0f242d71f4ec8a1d233 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 3 Sep 2008 23:53:09 +0000 Subject: [PATCH 0101/1195] Get all tests passing in `installcheck`. --- expected/fktap.out | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/expected/fktap.out b/expected/fktap.out index d5d9e3d091bc..c722a1f8e243 100644 --- a/expected/fktap.out +++ b/expected/fktap.out @@ -1,5 +1,5 @@ \set ECHO -1..26 +1..45 ok 1 - test has_fk( schema, table, description ) ok 2 - has_fk( schema, table, description ) should work ok 3 - test has_fk( table, description ) @@ -26,3 +26,22 @@ ok 23 - test col_is_fk( table, column[], description ) ok 24 - col_is_fk( table, column[], description ) should work ok 25 - test col_is_fk( table, column[], description ) ok 26 - col_is_fk( table, column[] ) should work +ok 27 - full fk_ok array should pass +ok 28 - full fk_ok array should have the proper description +ok 29 - fk_ok array desc should pass +ok 30 - fk_ok array desc should have the proper description +ok 31 - fk_ok array noschema desc should pass +ok 32 - fk_ok array noschema desc should have the proper description +ok 33 - fk_ok array noschema should pass +ok 34 - fk_ok array noschema should have the proper description +ok 35 - basic fk_ok should pass +ok 36 - basic fk_ok should have the proper description +ok 37 - basic fk_ok desc should pass +ok 38 - basic fk_ok desc should have the proper description +ok 39 - basic fk_ok noschema should pass +ok 40 - basic fk_ok noschema should have the proper description +ok 41 - basic fk_ok noschema desc should pass +ok 42 - basic fk_ok noschema desc should have the proper description +ok 43 - fk_ok fail should fail +ok 44 - fk_ok fail should have the proper description +ok 45 - fk_ok fail should have the proper diagnostics From eddac36ada02baecbc90385446028a37e2fa7e7e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 4 Sep 2008 19:07:17 +0000 Subject: [PATCH 0102/1195] More tests for `fk_ok()`. --- expected/fktap.out | 49 +++++++++++++++++++++++++++++----------------- sql/fktap.sql | 43 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 19 deletions(-) diff --git a/expected/fktap.out b/expected/fktap.out index c722a1f8e243..65baba11e88a 100644 --- a/expected/fktap.out +++ b/expected/fktap.out @@ -1,5 +1,5 @@ \set ECHO -1..45 +1..58 ok 1 - test has_fk( schema, table, description ) ok 2 - has_fk( schema, table, description ) should work ok 3 - test has_fk( table, description ) @@ -28,20 +28,33 @@ ok 25 - test col_is_fk( table, column[], description ) ok 26 - col_is_fk( table, column[] ) should work ok 27 - full fk_ok array should pass ok 28 - full fk_ok array should have the proper description -ok 29 - fk_ok array desc should pass -ok 30 - fk_ok array desc should have the proper description -ok 31 - fk_ok array noschema desc should pass -ok 32 - fk_ok array noschema desc should have the proper description -ok 33 - fk_ok array noschema should pass -ok 34 - fk_ok array noschema should have the proper description -ok 35 - basic fk_ok should pass -ok 36 - basic fk_ok should have the proper description -ok 37 - basic fk_ok desc should pass -ok 38 - basic fk_ok desc should have the proper description -ok 39 - basic fk_ok noschema should pass -ok 40 - basic fk_ok noschema should have the proper description -ok 41 - basic fk_ok noschema desc should pass -ok 42 - basic fk_ok noschema desc should have the proper description -ok 43 - fk_ok fail should fail -ok 44 - fk_ok fail should have the proper description -ok 45 - fk_ok fail should have the proper diagnostics +ok 29 - multiple fk fk_ok desc should pass +ok 30 - multiple fk fk_ok desc should have the proper description +ok 31 - fk_ok array desc should pass +ok 32 - fk_ok array desc should have the proper description +ok 33 - fk_ok array noschema desc should pass +ok 34 - fk_ok array noschema desc should have the proper description +ok 35 - multiple fk fk_ok noschema desc should pass +ok 36 - multiple fk fk_ok noschema desc should have the proper description +ok 37 - fk_ok array noschema should pass +ok 38 - fk_ok array noschema should have the proper description +ok 39 - basic fk_ok should pass +ok 40 - basic fk_ok should have the proper description +ok 41 - basic fk_ok desc should pass +ok 42 - basic fk_ok desc should have the proper description +ok 43 - basic fk_ok noschema should pass +ok 44 - basic fk_ok noschema should have the proper description +ok 45 - basic fk_ok noschema desc should pass +ok 46 - basic fk_ok noschema desc should have the proper description +ok 47 - fk_ok fail should fail +ok 48 - fk_ok fail should have the proper description +ok 49 - fk_ok fail should have the proper diagnostics +ok 50 - fk_ok fail desc should fail +ok 51 - fk_ok fail desc should have the proper description +ok 52 - fk_ok fail desc should have the proper diagnostics +ok 53 - fk_ok fail no schema should fail +ok 54 - fk_ok fail no schema should have the proper description +ok 55 - fk_ok fail no schema should have the proper diagnostics +ok 56 - fk_ok fail no schema desc should fail +ok 57 - fk_ok fail no schema desc should have the proper description +ok 58 - fk_ok fail no schema desc should have the proper diagnostics diff --git a/sql/fktap.sql b/sql/fktap.sql index efc2e65d5e8c..e4b853644f2f 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -31,7 +31,7 @@ BEGIN; -- ## SET search_path TO TAPSCHEMA,public; -- Set the test plan. -SELECT plan(45); +SELECT plan(58); -- These will be rolled back. :-) CREATE TABLE pk ( @@ -167,6 +167,13 @@ SELECT * FROM test_ok( 'WHATEVER' ); +SELECT * FROM test_ok( + fk_ok( 'public', 'fk2', ARRAY['pk2_num', 'pk2_dot'], 'public', 'pk2', ARRAY['num', 'dot'] ), + true, + 'multiple fk fk_ok desc', + 'public.fk2(pk2_num, pk2_dot) should reference public.pk2(num, dot)' +); + SELECT * FROM test_ok( fk_ok( 'public', 'fk', ARRAY['pk_id'], 'public', 'pk', ARRAY['id'] ), true, @@ -181,6 +188,13 @@ SELECT * FROM test_ok( 'fk(pk_id) should reference pk(id)' ); +SELECT * FROM test_ok( + fk_ok( 'fk2', ARRAY['pk2_num', 'pk2_dot'], 'pk2', ARRAY['num', 'dot'] ), + true, + 'multiple fk fk_ok noschema desc', + 'fk2(pk2_num, pk2_dot) should reference pk2(num, dot)' +); + SELECT * FROM test_ok( fk_ok( 'fk', ARRAY['pk_id'], 'pk', ARRAY['id'], 'WHATEVER' ), true, @@ -225,6 +239,33 @@ SELECT * FROM test_ok( want: public.fk(pk_id) REFERENCES public.pk(fid)' ); +SELECT * FROM test_ok( + fk_ok( 'public', 'fk', ARRAY['pk_id'], 'public', 'pk', ARRAY['fid'] ), + false, + 'fk_ok fail desc', + 'public.fk(pk_id) should reference public.pk(fid)', + ' have: public.fk(pk_id) REFERENCES public.pk(id) + want: public.fk(pk_id) REFERENCES public.pk(fid)' +); + +SELECT * FROM test_ok( + fk_ok( 'fk', ARRAY['pk_id'], 'pk', ARRAY['fid'], 'WHATEVER' ), + false, + 'fk_ok fail no schema', + 'WHATEVER', + ' have: fk(pk_id) REFERENCES pk(id) + want: fk(pk_id) REFERENCES pk(fid)' +); + +SELECT * FROM test_ok( + fk_ok( 'fk', ARRAY['pk_id'], 'pk', ARRAY['fid'] ), + false, + 'fk_ok fail no schema desc', + 'fk(pk_id) should reference pk(fid)', + ' have: fk(pk_id) REFERENCES pk(id) + want: fk(pk_id) REFERENCES pk(fid)' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From d34d1763a23dc76ffdeedb84bb1481c487ef8790 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 4 Sep 2008 19:15:06 +0000 Subject: [PATCH 0103/1195] Changed `test_ok()` to `check_test()`, since it is not really a simple `ok()` test, but a collection of tests. This matches up with a similar function in Test::Tester. --- pgtap.sql.in | 22 +++++++++++----------- sql/fktap.sql | 28 ++++++++++++++-------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 85296e77f26e..87747e79c66b 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1066,8 +1066,8 @@ CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ SELECT fk_ok( $1, ARRAY[$2], $3, ARRAY[$4] ); $$ LANGUAGE sql; --- test_ok( test_output, pass, name, description, diag ) -CREATE OR REPLACE FUNCTION test_ok( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) RETURNS SETOF TEXT AS $$ +-- check_test( test_output, pass, name, description, diag ) +CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) RETURNS SETOF TEXT AS $$ DECLARE tnumb INTEGER; aok BOOLEAN; @@ -1146,17 +1146,17 @@ BEGIN END; $$ LANGUAGE plpgsql; --- test_ok( test_output, pass, name, description ) -CREATE OR REPLACE FUNCTION test_ok( TEXT, BOOLEAN, TEXT, TEXT ) RETURNS SETOF TEXT AS $$ - SELECT * FROM test_ok( $1, $2, $3, $4, NULL ); +-- 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 ); $$ LANGUAGE sql; --- test_ok( test_output, pass, name ) -CREATE OR REPLACE FUNCTION test_ok( TEXT, BOOLEAN, TEXT ) RETURNS SETOF TEXT AS $$ - SELECT * FROM test_ok( $1, $2, $3, NULL, NULL ); +-- 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 ); $$ LANGUAGE sql; --- test_ok( test_output, pass ) -CREATE OR REPLACE FUNCTION test_ok( TEXT, BOOLEAN ) RETURNS SETOF TEXT AS $$ - SELECT * FROM test_ok( $1, $2, NULL, NULL, NULL ); +-- 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 ); $$ LANGUAGE sql; diff --git a/sql/fktap.sql b/sql/fktap.sql index e4b853644f2f..91497a8811e6 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -160,77 +160,77 @@ SELECT is( /****************************************************************************/ -- Test fk_ok(). -SELECT * FROM test_ok( +SELECT * FROM check_test( fk_ok( 'public', 'fk', ARRAY['pk_id'], 'public', 'pk', ARRAY['id'], 'WHATEVER' ), true, 'full fk_ok array', 'WHATEVER' ); -SELECT * FROM test_ok( +SELECT * FROM check_test( fk_ok( 'public', 'fk2', ARRAY['pk2_num', 'pk2_dot'], 'public', 'pk2', ARRAY['num', 'dot'] ), true, 'multiple fk fk_ok desc', 'public.fk2(pk2_num, pk2_dot) should reference public.pk2(num, dot)' ); -SELECT * FROM test_ok( +SELECT * FROM check_test( fk_ok( 'public', 'fk', ARRAY['pk_id'], 'public', 'pk', ARRAY['id'] ), true, 'fk_ok array desc', 'public.fk(pk_id) should reference public.pk(id)' ); -SELECT * FROM test_ok( +SELECT * FROM check_test( fk_ok( 'fk', ARRAY['pk_id'], 'pk', ARRAY['id'] ), true, 'fk_ok array noschema desc', 'fk(pk_id) should reference pk(id)' ); -SELECT * FROM test_ok( +SELECT * FROM check_test( fk_ok( 'fk2', ARRAY['pk2_num', 'pk2_dot'], 'pk2', ARRAY['num', 'dot'] ), true, 'multiple fk fk_ok noschema desc', 'fk2(pk2_num, pk2_dot) should reference pk2(num, dot)' ); -SELECT * FROM test_ok( +SELECT * FROM check_test( fk_ok( 'fk', ARRAY['pk_id'], 'pk', ARRAY['id'], 'WHATEVER' ), true, 'fk_ok array noschema', 'WHATEVER' ); -SELECT * FROM test_ok( +SELECT * FROM check_test( fk_ok( 'public', 'fk', 'pk_id', 'public', 'pk', 'id', 'WHATEVER' ), true, 'basic fk_ok', 'WHATEVER' ); -SELECT * FROM test_ok( +SELECT * FROM check_test( fk_ok( 'public', 'fk', 'pk_id', 'public', 'pk', 'id' ), true, 'basic fk_ok desc', 'public.fk(pk_id) should reference public.pk(id)' ); -SELECT * FROM test_ok( +SELECT * FROM check_test( fk_ok( 'fk', 'pk_id', 'pk', 'id', 'WHATEVER' ), true, 'basic fk_ok noschema', 'WHATEVER' ); -SELECT * FROM test_ok( +SELECT * FROM check_test( fk_ok( 'fk', 'pk_id', 'pk', 'id' ), true, 'basic fk_ok noschema desc', 'fk(pk_id) should reference pk(id)' ); -SELECT * FROM test_ok( +SELECT * FROM check_test( fk_ok( 'public', 'fk', ARRAY['pk_id'], 'public', 'pk', ARRAY['fid'], 'WHATEVER' ), false, 'fk_ok fail', @@ -239,7 +239,7 @@ SELECT * FROM test_ok( want: public.fk(pk_id) REFERENCES public.pk(fid)' ); -SELECT * FROM test_ok( +SELECT * FROM check_test( fk_ok( 'public', 'fk', ARRAY['pk_id'], 'public', 'pk', ARRAY['fid'] ), false, 'fk_ok fail desc', @@ -248,7 +248,7 @@ SELECT * FROM test_ok( want: public.fk(pk_id) REFERENCES public.pk(fid)' ); -SELECT * FROM test_ok( +SELECT * FROM check_test( fk_ok( 'fk', ARRAY['pk_id'], 'pk', ARRAY['fid'], 'WHATEVER' ), false, 'fk_ok fail no schema', @@ -257,7 +257,7 @@ SELECT * FROM test_ok( want: fk(pk_id) REFERENCES pk(fid)' ); -SELECT * FROM test_ok( +SELECT * FROM check_test( fk_ok( 'fk', ARRAY['pk_id'], 'pk', ARRAY['fid'] ), false, 'fk_ok fail no schema desc', From 37d6056398dc8c312e0e4eacfb362760fca95ec5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 4 Sep 2008 19:27:11 +0000 Subject: [PATCH 0104/1195] Converted pktap tests to use `check_test()`. It took about 15 minutes. It will be busy work to update the other test scripts, but not too bad, and then they will be much easier to maintain. --- expected/fktap.out | 120 +++++++++++++++++++++++---------------------- sql/fktap.sql | 111 +++++++++++++++++++++-------------------- 2 files changed, 116 insertions(+), 115 deletions(-) diff --git a/expected/fktap.out b/expected/fktap.out index 65baba11e88a..d81c0c12d6c1 100644 --- a/expected/fktap.out +++ b/expected/fktap.out @@ -1,60 +1,62 @@ \set ECHO -1..58 -ok 1 - test has_fk( schema, table, description ) -ok 2 - has_fk( schema, table, description ) should work -ok 3 - test has_fk( table, description ) -ok 4 - has_fk( table, description ) should work -ok 5 - test has_fk( table ) -ok 6 - has_fk( table ) should work -ok 7 - test has_fk( schema, table, description ) fail -ok 8 - has_fk( schema, table, description ) should fail properly -ok 9 - test has_fk( table, description ) fail -ok 10 - has_fk( table, description ) should fail properly -ok 11 - test col_is_fk( schema, table, column, description ) -ok 12 - col_is_fk( schema, table, column, description ) should work -ok 13 - test col_is_fk( table, column, description ) -ok 14 - col_is_fk( table, column, description ) should work -ok 15 - test col_is_fk( table, column ) -ok 16 - col_is_fk( table, column ) should work -ok 17 - test col_is_fk( schema, table, column, description ) fail -ok 18 - col_is_fk( schema, table, column, description ) should fail properly -ok 19 - test col_is_fk( table, column, description ) fail -ok 20 - col_is_fk( table, column, description ) should fail properly -ok 21 - test col_is_fk( schema, table, column[], description ) -ok 22 - col_is_fk( schema, table, column[], description ) should work -ok 23 - test col_is_fk( table, column[], description ) -ok 24 - col_is_fk( table, column[], description ) should work -ok 25 - test col_is_fk( table, column[], description ) -ok 26 - col_is_fk( table, column[] ) should work -ok 27 - full fk_ok array should pass -ok 28 - full fk_ok array should have the proper description -ok 29 - multiple fk fk_ok desc should pass -ok 30 - multiple fk fk_ok desc should have the proper description -ok 31 - fk_ok array desc should pass -ok 32 - fk_ok array desc should have the proper description -ok 33 - fk_ok array noschema desc should pass -ok 34 - fk_ok array noschema desc should have the proper description -ok 35 - multiple fk fk_ok noschema desc should pass -ok 36 - multiple fk fk_ok noschema desc should have the proper description -ok 37 - fk_ok array noschema should pass -ok 38 - fk_ok array noschema should have the proper description -ok 39 - basic fk_ok should pass -ok 40 - basic fk_ok should have the proper description -ok 41 - basic fk_ok desc should pass -ok 42 - basic fk_ok desc should have the proper description -ok 43 - basic fk_ok noschema should pass -ok 44 - basic fk_ok noschema should have the proper description -ok 45 - basic fk_ok noschema desc should pass -ok 46 - basic fk_ok noschema desc should have the proper description -ok 47 - fk_ok fail should fail -ok 48 - fk_ok fail should have the proper description -ok 49 - fk_ok fail should have the proper diagnostics -ok 50 - fk_ok fail desc should fail -ok 51 - fk_ok fail desc should have the proper description -ok 52 - fk_ok fail desc should have the proper diagnostics -ok 53 - fk_ok fail no schema should fail -ok 54 - fk_ok fail no schema should have the proper description -ok 55 - fk_ok fail no schema should have the proper diagnostics -ok 56 - fk_ok fail no schema desc should fail -ok 57 - fk_ok fail no schema desc should have the proper description -ok 58 - fk_ok fail no schema desc should have the proper diagnostics +1..60 +ok 1 - has_fk( schema, table, description ) should pass +ok 2 - has_fk( schema, table, description ) should have the proper description +ok 3 - has_fk( table, description ) should pass +ok 4 - has_fk( table, description ) should have the proper description +ok 5 - has_fk( table ) should pass +ok 6 - has_fk( table ) should have the proper description +ok 7 - has_fk( schema, table, description ) fail should fail +ok 8 - has_fk( schema, table, description ) fail should have the proper description +ok 9 - has_fk( table, description ) fail should fail +ok 10 - has_fk( table, description ) fail should have the proper description +ok 11 - col_is_fk( schema, table, column, description ) should pass +ok 12 - col_is_fk( schema, table, column, description ) should have the proper description +ok 13 - col_is_fk( table, column, description ) should pass +ok 14 - col_is_fk( table, column, description ) should have the proper description +ok 15 - col_is_fk( table, column ) should pass +ok 16 - col_is_fk( table, column ) should have the proper description +ok 17 - col_is_fk( schema, table, column, description ) should fail +ok 18 - col_is_fk( schema, table, column, description ) should have the proper description +ok 19 - col_is_fk( schema, table, column, description ) should have the proper diagnostics +ok 20 - col_is_fk( table, column, description ) should fail +ok 21 - col_is_fk( table, column, description ) should have the proper description +ok 22 - col_is_fk( table, column, description ) should have the proper diagnostics +ok 23 - col_is_fk( schema, table, column[], description ) should pass +ok 24 - col_is_fk( schema, table, column[], description ) should have the proper description +ok 25 - col_is_fk( table, column[], description ) should pass +ok 26 - col_is_fk( table, column[], description ) should have the proper description +ok 27 - col_is_fk( table, column[] ) should pass +ok 28 - col_is_fk( table, column[] ) should have the proper description +ok 29 - full fk_ok array should pass +ok 30 - full fk_ok array should have the proper description +ok 31 - multiple fk fk_ok desc should pass +ok 32 - multiple fk fk_ok desc should have the proper description +ok 33 - fk_ok array desc should pass +ok 34 - fk_ok array desc should have the proper description +ok 35 - fk_ok array noschema desc should pass +ok 36 - fk_ok array noschema desc should have the proper description +ok 37 - multiple fk fk_ok noschema desc should pass +ok 38 - multiple fk fk_ok noschema desc should have the proper description +ok 39 - fk_ok array noschema should pass +ok 40 - fk_ok array noschema should have the proper description +ok 41 - basic fk_ok should pass +ok 42 - basic fk_ok should have the proper description +ok 43 - basic fk_ok desc should pass +ok 44 - basic fk_ok desc should have the proper description +ok 45 - basic fk_ok noschema should pass +ok 46 - basic fk_ok noschema should have the proper description +ok 47 - basic fk_ok noschema desc should pass +ok 48 - basic fk_ok noschema desc should have the proper description +ok 49 - fk_ok fail should fail +ok 50 - fk_ok fail should have the proper description +ok 51 - fk_ok fail should have the proper diagnostics +ok 52 - fk_ok fail desc should fail +ok 53 - fk_ok fail desc should have the proper description +ok 54 - fk_ok fail desc should have the proper diagnostics +ok 55 - fk_ok fail no schema should fail +ok 56 - fk_ok fail no schema should have the proper description +ok 57 - fk_ok fail no schema should have the proper diagnostics +ok 58 - fk_ok fail no schema desc should fail +ok 59 - fk_ok fail no schema desc should have the proper description +ok 60 - fk_ok fail no schema desc should have the proper diagnostics diff --git a/sql/fktap.sql b/sql/fktap.sql index 91497a8811e6..c569844d5a98 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -31,7 +31,7 @@ BEGIN; -- ## SET search_path TO TAPSCHEMA,public; -- Set the test plan. -SELECT plan(58); +SELECT plan(60); -- These will be rolled back. :-) CREATE TABLE pk ( @@ -58,104 +58,103 @@ CREATE TABLE fk2 ( /****************************************************************************/ -- Test has_fk(). - -\echo ok 1 - test has_fk( schema, table, description ) -SELECT is( +SELECT * FROM check_test( has_fk( 'public', 'fk', 'public.fk should have an fk' ), - 'ok 1 - public.fk should have an fk', - 'has_fk( schema, table, description ) should work' + true, + 'has_fk( schema, table, description )', + 'public.fk should have an fk' ); -\echo ok 3 - test has_fk( table, description ) -SELECT is( +SELECT * FROM check_test( has_fk( 'fk', 'fk should have a pk' ), - 'ok 3 - fk should have a pk', - 'has_fk( table, description ) should work' + 'true', + 'has_fk( table, description )', + 'fk should have a pk' ); -\echo ok 5 - test has_fk( table ) -SELECT is( +SELECT * FROM check_test( has_fk( 'fk' ), - 'ok 5 - Table fk should have a foreign key constraint', - 'has_fk( table ) should work' + true, + 'has_fk( table )', + 'Table fk should have a foreign key constraint' ); -\echo ok 7 - test has_fk( schema, table, description ) fail -SELECT is( +SELECT * FROM check_test( has_fk( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should have a pk' ), - E'not ok 7 - pg_catalog.pg_class should have a pk\n# Failed test 7: "pg_catalog.pg_class should have a pk"', - 'has_fk( schema, table, description ) should fail properly' + false, + 'has_fk( schema, table, description ) fail', + 'pg_catalog.pg_class should have a pk' ); -\echo ok 9 - test has_fk( table, description ) fail -SELECT is( +SELECT * FROM check_test( has_fk( 'pg_class', 'pg_class should have a pk' ), - E'not ok 9 - pg_class should have a pk\n# Failed test 9: "pg_class should have a pk"', - 'has_fk( table, description ) should fail properly' + false, + 'has_fk( table, description ) fail', + 'pg_class should have a pk' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 7, 9 ); /****************************************************************************/ -- Test col_is_fk(). -\echo ok 11 - test col_is_fk( schema, table, column, description ) -SELECT is( +SELECT * FROM check_test( col_is_fk( 'public', 'fk', 'pk_id', 'public.fk.pk_id should be an fk' ), - 'ok 11 - public.fk.pk_id should be an fk', - 'col_is_fk( schema, table, column, description ) should work' + true, + 'col_is_fk( schema, table, column, description )', + 'public.fk.pk_id should be an fk' ); -\echo ok 13 - test col_is_fk( table, column, description ) -SELECT is( +SELECT * FROM check_test( col_is_fk( 'fk', 'pk_id', 'fk.pk_id should be an fk' ), - 'ok 13 - fk.pk_id should be an fk', - 'col_is_fk( table, column, description ) should work' + true, + 'col_is_fk( table, column, description )', + 'fk.pk_id should be an fk' ); -\echo ok 15 - test col_is_fk( table, column ) -SELECT is( +SELECT * FROM check_test( col_is_fk( 'fk', 'pk_id' ), - 'ok 15 - Column fk.pk_id should be a foreign key', - 'col_is_fk( table, column ) should work' + true, + 'col_is_fk( table, column )', + 'Column fk.pk_id should be a foreign key' ); -\echo ok 17 - test col_is_fk( schema, table, column, description ) fail -SELECT is( +SELECT * FROM check_test( col_is_fk( 'public', 'fk', 'name', 'public.fk.name should be an fk' ), - E'not ok 17 - public.fk.name should be an fk\n# Failed test 17: "public.fk.name should be an fk"\n# have: {pk_id}\n# want: {name}', - 'col_is_fk( schema, table, column, description ) should fail properly' + false, + 'col_is_fk( schema, table, column, description )', + 'public.fk.name should be an fk', + E' have: {pk_id}\n want: {name}' ); -\echo ok 19 - test col_is_fk( table, column, description ) fail -SELECT is( +SELECT * FROM check_test( col_is_fk( 'fk', 'name', 'fk.name should be an fk' ), - E'not ok 19 - fk.name should be an fk\n# Failed test 19: "fk.name should be an fk"\n# have: {pk_id}\n# want: {name}', - 'col_is_fk( table, column, description ) should fail properly' + false, + 'col_is_fk( table, column, description )', + 'fk.name should be an fk', + E' have: {pk_id}\n want: {name}' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 17, 19 ); /****************************************************************************/ -- Test col_is_fk() with an array of columns. -\echo ok 21 - test col_is_fk( schema, table, column[], description ) -SELECT is( +SELECT * FROM check_test( col_is_fk( 'public', 'fk2', ARRAY['pk2_num', 'pk2_dot'], 'id + pk2_dot should be an fk' ), - 'ok 21 - id + pk2_dot should be an fk', - 'col_is_fk( schema, table, column[], description ) should work' + true, + 'col_is_fk( schema, table, column[], description )', + 'id + pk2_dot should be an fk' ); -\echo ok 23 - test col_is_fk( table, column[], description ) -SELECT is( +SELECT * FROM check_test( col_is_fk( 'fk2', ARRAY['pk2_num', 'pk2_dot'], 'id + pk2_dot should be an fk' ), - 'ok 23 - id + pk2_dot should be an fk', - 'col_is_fk( table, column[], description ) should work' + true, + 'col_is_fk( table, column[], description )', + 'id + pk2_dot should be an fk' ); -\echo ok 25 - test col_is_fk( table, column[], description ) -SELECT is( +SELECT * FROM check_test( col_is_fk( 'fk2', ARRAY['pk2_num', 'pk2_dot'] ), - 'ok 25 - Columns fk2.{pk2_num,pk2_dot} should be a foreign key', - 'col_is_fk( table, column[] ) should work' + true, + 'col_is_fk( table, column[] )', + 'Columns fk2.{pk2_num,pk2_dot} should be a foreign key' ); /****************************************************************************/ From f46662ee432ae35b24035d7b77a8b09511dd7b71 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 4 Sep 2008 19:44:40 +0000 Subject: [PATCH 0105/1195] * Documented `fk_ok()`. * Updated `uninstall_pgtap.sql.in`. * Changed function declarations in `pgtap.sql.in` so that the `RETURNS` clause appears on a separate line. This makes it easier to generate the uninstall script.' --- README.pgtap | 52 ++++++ pgtap.sql.in | 369 +++++++++++++++++++++++++++-------------- uninstall_pgtap.sql.in | 63 ++++++- 3 files changed, 359 insertions(+), 125 deletions(-) diff --git a/README.pgtap b/README.pgtap index 8dcc943cb289..a47d5dd9c73d 100644 --- a/README.pgtap +++ b/README.pgtap @@ -777,6 +777,58 @@ Will produce something like this: Just like `col_is_fk()`, except that it test that the column or array of columns are a primary key. +### fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description ) ### +### fk_ok( fk_table, fk_column[], pk_table, pk_column[], description ) ### +### fk_ok( fk_schema, fk_table, fk_column[], fk_schema, pk_table, pk_column[] ) ### +### fk_ok( fk_table, fk_column[], pk_table, pk_column[] ) ### +### fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column, description ) ### +### fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column ) ### +### fk_ok( fk_table, fk_column, pk_table, pk_column, description ) ### +### fk_ok( fk_table, fk_column, pk_table, pk_column ) ### + + SELECT fk_ok( + 'myschema', + 'sometable', + 'big_id', + 'myschema', + 'bigtable', + 'id', + 'myschema.sometable(big_id) should reference myschema.bigtable(id)' + ); + + SELECT fk_ok( + 'contacts', + ARRAY['person_first', 'person_last'], + 'persons', + ARRAY['first', 'last'], + ); + +This function combines `col_is_fk()` and `col_is_pk()` into a single test that +also happens to determine that there is in fact a foreign key relationship +between the foreign and primary key tables. To properly test your +relationships, this should be your main test function of choice. + +The first three arguments are the schema, table, and column or array of +columns that constitue the foreign key constraint. The schema name is +optional, and the columns can be specified as a string for a single column or +an array of strings for multiple columns. The next three arguments are the +schema, table, and column or columns that constitute the corresponding primary +key. Again, the schema is optional and the columns may be a string or array of +strings (though of course it should have the same number of elements as the +foreign key column argument). The seventh argument is an optional description +If it's not included, it will be set to ":fk_schema.:fk_table(:fk_column) +should reference :pk_column.pk_table(:pk_column). + +If the test fails, it will output useful diagnostics. For example this test: + + SELECT fk_ok( 'contacts', 'person_id', 'persons', 'id' ); + +Will produce something like this: + + # Failed test 178: "Column contacts(person_id) should reference persons(id)" + # have: contacts(person_id) REFERENCES persons(id)" + # want: contacts(person_nick) REFERENCES persons(nick)" + ### `has_unique( schema, table, description )` ### ### `has_unique( table, description )` ### ### `has_unique( table )` ### diff --git a/pgtap.sql.in b/pgtap.sql.in index 87747e79c66b..1587fb96fb57 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -17,7 +17,8 @@ RETURNS regtype AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; -CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION plan( integer ) +RETURNS TEXT AS $$ DECLARE rcount INTEGER; BEGIN @@ -57,14 +58,16 @@ BEGIN END; $$ LANGUAGE plpgsql strict; -CREATE OR REPLACE FUNCTION no_plan() RETURNS SETOF boolean AS $$ +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 $$ +CREATE OR REPLACE FUNCTION _get ( text ) +RETURNS integer AS $$ DECLARE ret integer; BEGIN @@ -73,7 +76,8 @@ BEGIN END; $$ LANGUAGE plpgsql strict; -CREATE OR REPLACE FUNCTION _get_note ( text ) RETURNS text AS $$ +CREATE OR REPLACE FUNCTION _get_note ( text ) +RETURNS text AS $$ DECLARE ret text; BEGIN @@ -82,7 +86,8 @@ BEGIN END; $$ LANGUAGE plpgsql strict; -CREATE OR REPLACE FUNCTION _set ( text, integer, text ) RETURNS integer AS $$ +CREATE OR REPLACE FUNCTION _set ( text, integer, text ) +RETURNS integer AS $$ DECLARE rcount integer; BEGIN @@ -97,7 +102,8 @@ BEGIN END; $$ LANGUAGE plpgsql strict; -CREATE OR REPLACE FUNCTION _set ( text, integer ) RETURNS integer AS $$ +CREATE OR REPLACE FUNCTION _set ( text, integer ) +RETURNS integer AS $$ SELECT _set($1, $2, '') $$ LANGUAGE SQL; @@ -111,7 +117,8 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ +CREATE OR REPLACE FUNCTION num_failed () +RETURNS INTEGER AS $$ DECLARE ret integer; BEGIN @@ -120,7 +127,8 @@ BEGIN END; $$ LANGUAGE plpgsql strict; -CREATE OR REPLACE FUNCTION finish () RETURNS SETOF TEXT AS $$ +CREATE OR REPLACE FUNCTION finish () +RETURNS SETOF TEXT AS $$ DECLARE curr_test INTEGER; exp_tests INTEGER; @@ -165,13 +173,15 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION diag ( msg text ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION diag ( msg text ) +RETURNS TEXT AS $$ BEGIN RETURN regexp_replace( msg, '^', '# ', 'gn' ); END; $$ LANGUAGE plpgsql strict; -CREATE OR REPLACE FUNCTION ok ( boolean, text ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION ok ( boolean, text ) +RETURNS TEXT AS $$ DECLARE aok ALIAS FOR $1; descr ALIAS FOR $2; @@ -206,11 +216,13 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION ok ( boolean ) RETURNS TEXT AS $$ +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 $$ +CREATE OR REPLACE FUNCTION is (anyelement, anyelement, text) +RETURNS TEXT AS $$ DECLARE result BOOLEAN; output TEXT; @@ -226,11 +238,13 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION is (anyelement, anyelement) RETURNS TEXT AS $$ +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 $$ +CREATE OR REPLACE FUNCTION isnt (anyelement, anyelement, text) +RETURNS TEXT AS $$ DECLARE result BOOLEAN; output TEXT; @@ -247,7 +261,8 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION isnt (anyelement, anyelement) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION isnt (anyelement, anyelement) +RETURNS TEXT AS $$ SELECT isnt( $1, $2, NULL); $$ LANGUAGE SQL; @@ -256,7 +271,8 @@ CREATE OR REPLACE FUNCTION _alike ( ANYELEMENT, TEXT, TEXT -) RETURNS TEXT AS $$ +) +RETURNS TEXT AS $$ DECLARE result ALIAS FOR $1; got ALIAS FOR $2; @@ -272,35 +288,43 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION matches ( anyelement, text, text ) RETURNS TEXT AS $$ +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 $$ +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 $$ +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 $$ +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 $$ +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 $$ +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 $$ +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 $$ +CREATE OR REPLACE FUNCTION ialike ( anyelement, text ) +RETURNS TEXT AS $$ SELECT _alike( $1 ~~* $2, $1, $2, NULL ); $$ LANGUAGE SQL; @@ -309,7 +333,8 @@ CREATE OR REPLACE FUNCTION _unalike ( ANYELEMENT, TEXT, TEXT -) RETURNS TEXT AS $$ +) +RETURNS TEXT AS $$ DECLARE result ALIAS FOR $1; got ALIAS FOR $2; @@ -325,39 +350,48 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION doesnt_match ( anyelement, text, text ) RETURNS TEXT AS $$ +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 $$ +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 $$ +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 $$ +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 $$ +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 $$ +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 $$ +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 $$ +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 $$ +CREATE OR REPLACE FUNCTION cmp_ok (anyelement, text, anyelement, text) +RETURNS TEXT AS $$ DECLARE have ALIAS FOR $1; op ALIAS FOR $2; @@ -380,23 +414,28 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION cmp_ok (anyelement, text, anyelement) RETURNS TEXT AS $$ +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 $$ +CREATE OR REPLACE FUNCTION pass ( text ) +RETURNS TEXT AS $$ SELECT ok( TRUE, $1 ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION pass () RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION pass () +RETURNS TEXT AS $$ SELECT ok( TRUE, NULL ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION fail ( text ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION fail ( text ) +RETURNS TEXT AS $$ SELECT ok( FALSE, $1 ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION fail () RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION fail () +RETURNS TEXT AS $$ SELECT ok( FALSE, NULL ); $$ LANGUAGE SQL; @@ -408,7 +447,8 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION _todo() RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION _todo() +RETURNS TEXT AS $$ DECLARE todos INT; BEGIN @@ -422,7 +462,8 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION throws_ok ( TEXT, CHAR(5), TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION throws_ok ( TEXT, CHAR(5), TEXT ) +RETURNS TEXT AS $$ DECLARE code ALIAS FOR $1; err ALIAS FOR $2; @@ -449,24 +490,29 @@ EXCEPTION WHEN OTHERS THEN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION throws_ok ( TEXT, CHAR(5) ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION throws_ok ( TEXT, CHAR(5) ) +RETURNS TEXT AS $$ SELECT throws_ok( $1, $2, NULL ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION throws_ok ( TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION throws_ok ( TEXT ) +RETURNS TEXT AS $$ SELECT throws_ok( $1, NULL, NULL ); $$ LANGUAGE SQL; -- Magically cast integer error codes. -CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4, TEXT ) +RETURNS TEXT AS $$ SELECT throws_ok( $1, $2::char(5), $3 ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4 ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4 ) +RETURNS TEXT AS $$ SELECT throws_ok( $1, $2::char(5), NULL ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION lives_ok ( TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION lives_ok ( TEXT, TEXT ) +RETURNS TEXT AS $$ DECLARE code ALIAS FOR $1; descr ALIAS FOR $2; @@ -481,12 +527,14 @@ EXCEPTION WHEN OTHERS THEN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION lives_ok ( TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION lives_ok ( TEXT ) +RETURNS TEXT AS $$ SELECT lives_ok( $1, NULL ); $$ LANGUAGE SQL; -- has_table( schema, table, description ) -CREATE OR REPLACE FUNCTION has_table ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_table ( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT ok( EXISTS( SELECT true @@ -500,7 +548,8 @@ CREATE OR REPLACE FUNCTION has_table ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ $$ LANGUAGE SQL; -- has_table( table, description ) -CREATE OR REPLACE FUNCTION has_table ( TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_table ( TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT ok( EXISTS( SELECT true @@ -513,12 +562,14 @@ CREATE OR REPLACE FUNCTION has_table ( TEXT, TEXT ) RETURNS TEXT AS $$ $$ LANGUAGE SQL; -- has_table( table ) -CREATE OR REPLACE FUNCTION has_table ( TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_table ( TEXT ) +RETURNS TEXT AS $$ SELECT has_table( $1, 'Table ' || $1 || ' should exist' ); $$ LANGUAGE SQL; -- has_view( schema, view, description ) -CREATE OR REPLACE FUNCTION has_view ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_view ( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT ok( EXISTS( SELECT true @@ -532,7 +583,8 @@ CREATE OR REPLACE FUNCTION has_view ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ $$ LANGUAGE SQL; -- has_view( view, description ) -CREATE OR REPLACE FUNCTION has_view ( TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_view ( TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT ok( EXISTS( SELECT true @@ -545,12 +597,14 @@ CREATE OR REPLACE FUNCTION has_view ( TEXT, TEXT ) RETURNS TEXT AS $$ $$ LANGUAGE SQL; -- has_view( view ) -CREATE OR REPLACE FUNCTION has_view ( TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_view ( TEXT ) +RETURNS TEXT AS $$ SELECT has_view( $1, 'View ' || $1 || ' should exist' ); $$ LANGUAGE SQL; -- has_column( schema, table, column, description ) -CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT ok( EXISTS( SELECT true @@ -567,7 +621,8 @@ CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ LANGUAGE SQL; -- has_column( table, column, description ) -CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT ok( EXISTS( SELECT true @@ -583,12 +638,14 @@ CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ $$ LANGUAGE SQL; -- has_column( table, column ) -CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT has_column( $1, $2, 'Column ' || $1 || '.' || $2 || ' should exist' ); $$ LANGUAGE SQL; -- _col_is_null( schema, table, column, desc, null ) -CREATE OR REPLACE FUNCTION _col_is_null ( TEXT, TEXT, TEXT, TEXT, bool ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION _col_is_null ( TEXT, TEXT, TEXT, TEXT, bool ) +RETURNS TEXT AS $$ SELECT ok( EXISTS( SELECT true @@ -606,7 +663,8 @@ CREATE OR REPLACE FUNCTION _col_is_null ( TEXT, TEXT, TEXT, TEXT, bool ) RETURNS $$ LANGUAGE SQL; -- _col_is_null( table, column, desc, null ) -CREATE OR REPLACE FUNCTION _col_is_null ( TEXT, TEXT, TEXT, bool ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION _col_is_null ( TEXT, TEXT, TEXT, bool ) +RETURNS TEXT AS $$ SELECT ok( EXISTS( SELECT true @@ -623,37 +681,44 @@ CREATE OR REPLACE FUNCTION _col_is_null ( TEXT, TEXT, TEXT, bool ) RETURNS TEXT $$ LANGUAGE SQL; -- col_not_null( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT _col_is_null( $1, $2, $3, $4, true ); $$ LANGUAGE SQL; -- col_not_null( table, column, description ) -CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT _col_is_null( $1, $2, $3, true ); $$ LANGUAGE SQL; -- col_not_null( table, column ) -CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT _col_is_null( $1, $2, 'Column ' || $1 || '.' || $2 || ' should be NOT NULL', true ); $$ LANGUAGE SQL; -- col_is_null( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT _col_is_null( $1, $2, $3, $4, false ); $$ LANGUAGE SQL; -- col_is_null( schema, table, column ) -CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT _col_is_null( $1, $2, $3, false ); $$ LANGUAGE SQL; -- col_is_null( table, column ) -CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT _col_is_null( $1, $2, 'Column ' || $1 || '.' || $2 || ' should allow NULL', false ); $$ LANGUAGE SQL; -- col_type_is( schema, table, column, type, description ) -CREATE OR REPLACE FUNCTION col_type_is ( TEXT, TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_type_is ( TEXT, TEXT, TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ DECLARE actual_type TEXT; BEGIN @@ -693,16 +758,19 @@ END; $$ LANGUAGE plpgsql; -- col_type_is( table, column, type, description ) -CREATE OR REPLACE FUNCTION col_type_is ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_type_is ( TEXT, TEXT, 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 ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_type_is ( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT col_type_is( $1, $2, $3, 'Column ' || $1 || '.' || $2 || ' should be type ' || $3 ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _def_is( TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION _def_is( TEXT, anyelement, TEXT ) +RETURNS TEXT AS $$ DECLARE thing text; BEGIN @@ -717,7 +785,8 @@ END; $$ LANGUAGE plpgsql; -- col_default_is( schema, table, column, default, description ) -CREATE OR REPLACE FUNCTION col_default_is ( TEXT, TEXT, TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_default_is ( TEXT, TEXT, TEXT, anyelement, TEXT ) +RETURNS TEXT AS $$ SELECT _def_is(( SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid) FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a, @@ -736,7 +805,8 @@ CREATE OR REPLACE FUNCTION col_default_is ( TEXT, TEXT, TEXT, anyelement, TEXT ) $$ LANGUAGE sql; -- col_default_is( table, column, default, description ) -CREATE OR REPLACE FUNCTION col_default_is ( TEXT, TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_default_is ( TEXT, TEXT, anyelement, TEXT ) +RETURNS TEXT AS $$ SELECT _def_is(( SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid) FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, pg_catalog.pg_attrdef d @@ -753,7 +823,8 @@ CREATE OR REPLACE FUNCTION col_default_is ( TEXT, TEXT, anyelement, TEXT ) RETUR $$ LANGUAGE sql; -- col_default_is( table, column, default ) -CREATE OR REPLACE FUNCTION col_default_is ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_default_is ( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT col_default_is( $1, $2, $3, 'Column ' || $1 || '.' || $2 || ' should default to ' || quote_literal($3) @@ -761,7 +832,8 @@ CREATE OR REPLACE FUNCTION col_default_is ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $ $$ LANGUAGE sql; -- _hasc( schema, table, constraint_type ) -CREATE OR REPLACE FUNCTION _hasc ( TEXT, TEXT, CHAR ) RETURNS BOOLEAN AS $$ +CREATE OR REPLACE FUNCTION _hasc ( TEXT, TEXT, CHAR ) +RETURNS BOOLEAN AS $$ SELECT EXISTS( SELECT true FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, @@ -777,7 +849,8 @@ CREATE OR REPLACE FUNCTION _hasc ( TEXT, TEXT, CHAR ) RETURNS BOOLEAN AS $$ $$ LANGUAGE sql; -- _hasc( table, constraint_type ) -CREATE OR REPLACE FUNCTION _hasc ( TEXT, CHAR ) RETURNS BOOLEAN AS $$ +CREATE OR REPLACE FUNCTION _hasc ( TEXT, CHAR ) +RETURNS BOOLEAN AS $$ SELECT EXISTS( SELECT true FROM pg_catalog.pg_class c, @@ -790,22 +863,26 @@ CREATE OR REPLACE FUNCTION _hasc ( TEXT, CHAR ) RETURNS BOOLEAN AS $$ $$ LANGUAGE sql; -- has_pk( schema, table, description ) -CREATE OR REPLACE FUNCTION has_pk ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_pk ( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT ok( _hasc( $1, $2, 'p' ), $3 ); $$ LANGUAGE sql; -- has_pk( table, description ) -CREATE OR REPLACE FUNCTION has_pk ( TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_pk ( TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT ok( _hasc( $1, 'p' ), $2 ); $$ LANGUAGE sql; -- has_pk( table ) -CREATE OR REPLACE FUNCTION has_pk ( TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_pk ( TEXT ) +RETURNS TEXT AS $$ SELECT has_pk( $1, 'Table ' || $1 || ' should have a primary key' ); $$ LANGUAGE sql; -- _ckeys( schema, table, constraint_type ) -CREATE OR REPLACE FUNCTION _ckeys ( TEXT, TEXT, CHAR ) RETURNS TEXT[] AS $$ +CREATE OR REPLACE FUNCTION _ckeys ( TEXT, TEXT, CHAR ) +RETURNS TEXT[] AS $$ SELECT ARRAY (SELECT a.attname::text FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a, pg_catalog.pg_constraint x @@ -822,7 +899,8 @@ CREATE OR REPLACE FUNCTION _ckeys ( TEXT, TEXT, CHAR ) RETURNS TEXT[] AS $$ $$ LANGUAGE sql; -- _ckeys( table, constraint_type ) -CREATE OR REPLACE FUNCTION _ckeys ( TEXT, CHAR ) RETURNS TEXT[] AS $$ +CREATE OR REPLACE FUNCTION _ckeys ( TEXT, CHAR ) +RETURNS TEXT[] AS $$ SELECT ARRAY (SELECT a.attname::text FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, pg_catalog.pg_constraint x @@ -838,172 +916,206 @@ CREATE OR REPLACE FUNCTION _ckeys ( TEXT, CHAR ) RETURNS TEXT[] AS $$ $$ LANGUAGE sql; -- col_is_pk( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT, TEXT[], TEXT ) +RETURNS TEXT AS $$ SELECT is( _ckeys( $1, $2, 'p' ), $3, $4 ); $$ LANGUAGE sql; -- col_is_pk( table, column, description ) -CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT[], 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 ( TEXT, TEXT[] ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT[] ) +RETURNS TEXT AS $$ SELECT col_is_pk( $1, $2, 'Columns ' || $1 || '.' || $2::text || ' should be a primary key' ); $$ LANGUAGE sql; -- col_is_pk( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT col_is_pk( $1, $2, ARRAY[$3], $4 ); $$ LANGUAGE sql; -- col_is_pk( table, column, description ) -CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT, 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 ( TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT col_is_pk( $1, $2, 'Column ' || $1 || '.' || $2 || ' should be a primary key' ); $$ LANGUAGE sql; -- has_fk( schema, table, description ) -CREATE OR REPLACE FUNCTION has_fk ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_fk ( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT ok( _hasc( $1, $2, 'f' ), $3 ); $$ LANGUAGE sql; -- has_fk( table, description ) -CREATE OR REPLACE FUNCTION has_fk ( TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_fk ( TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT ok( _hasc( $1, 'f' ), $2 ); $$ LANGUAGE sql; -- has_fk( table ) -CREATE OR REPLACE FUNCTION has_fk ( TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_fk ( TEXT ) +RETURNS TEXT AS $$ SELECT has_fk( $1, 'Table ' || $1 || ' should have a foreign key constraint' ); $$ LANGUAGE sql; -- col_is_fk( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT, TEXT[], TEXT ) +RETURNS TEXT AS $$ SELECT is( _ckeys( $1, $2, 'f' ), $3, $4 ); $$ LANGUAGE sql; -- col_is_fk( table, column, description ) -CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT[], TEXT ) +RETURNS TEXT AS $$ SELECT is( _ckeys( $1, 'f' ), $2, $3 ); $$ LANGUAGE sql; -- col_is_fk( table, column[] ) -CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT[] ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT[] ) +RETURNS TEXT AS $$ SELECT col_is_fk( $1, $2, 'Columns ' || $1 || '.' || $2::text || ' should be a foreign key' ); $$ LANGUAGE sql; -- col_is_fk( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT, TEXT, 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 ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT, 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 ( TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT col_is_fk( $1, $2, 'Column ' || $1 || '.' || $2 || ' should be a foreign key' ); $$ LANGUAGE sql; -- has_unique( schema, table, description ) -CREATE OR REPLACE FUNCTION has_unique ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +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 $$ +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 $$ +CREATE OR REPLACE FUNCTION has_unique ( TEXT ) +RETURNS TEXT AS $$ SELECT has_unique( $1, 'Table ' || $1 || ' should have a unique constraint' ); $$ LANGUAGE sql; -- col_is_unique( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT, TEXT[], TEXT ) +RETURNS TEXT AS $$ SELECT is( _ckeys( $1, $2, 'u' ), $3, $4 ); $$ LANGUAGE sql; -- col_is_unique( table, column, description ) -CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT[], TEXT ) +RETURNS TEXT AS $$ SELECT is( _ckeys( $1, 'u' ), $2, $3 ); $$ LANGUAGE sql; -- col_is_unique( table, column[] ) -CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT[] ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT[] ) +RETURNS TEXT AS $$ SELECT col_is_unique( $1, $2, 'Columns ' || $1 || '.' || $2::text || ' should have a unique constraint' ); $$ LANGUAGE sql; -- col_is_unique( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT, TEXT, 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 ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT, 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 ( TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT col_is_unique( $1, $2, 'Column ' || $1 || '.' || $2 || ' should have a unique constraint' ); $$ LANGUAGE sql; -- has_check( schema, table, description ) -CREATE OR REPLACE FUNCTION has_check ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_check ( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT ok( _hasc( $1, $2, 'c' ), $3 ); $$ LANGUAGE sql; -- has_check( table, description ) -CREATE OR REPLACE FUNCTION has_check ( TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_check ( TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT ok( _hasc( $1, 'c' ), $2 ); $$ LANGUAGE sql; -- has_check( table ) -CREATE OR REPLACE FUNCTION has_check ( TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION has_check ( TEXT ) +RETURNS TEXT AS $$ SELECT has_check( $1, 'Table ' || $1 || ' should have a check constraint' ); $$ LANGUAGE sql; -- col_has_check( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT, TEXT[], TEXT ) +RETURNS TEXT AS $$ SELECT is( _ckeys( $1, $2, 'c' ), $3, $4 ); $$ LANGUAGE sql; -- col_has_check( table, column, description ) -CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT[], TEXT ) +RETURNS TEXT AS $$ SELECT is( _ckeys( $1, 'c' ), $2, $3 ); $$ LANGUAGE sql; -- col_has_check( table, column[] ) -CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT[] ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT[] ) +RETURNS TEXT AS $$ SELECT col_has_check( $1, $2, 'Columns ' || $1 || '.' || $2::text || ' should have a check constraint' ); $$ LANGUAGE sql; -- col_has_check( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT, TEXT, 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 ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT, 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 ( TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT col_has_check( $1, $2, 'Column ' || $1 || '.' || $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 ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[], TEXT ) +RETURNS TEXT AS $$ SELECT is( $1 || '.' || $2 || '(' || array_to_string( _ckeys( $1, $2, 'f' ), ', ' ) || ') REFERENCES ' || @@ -1016,7 +1128,8 @@ CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[], TEXT $$ LANGUAGE sql; -- fk_ok( fk_table, fk_column[], pk_table, pk_column[], description ) -CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT[], TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT[], TEXT, TEXT[], TEXT ) +RETURNS TEXT AS $$ SELECT is( $1 || '(' || array_to_string( _ckeys( $1, 'f' ), ', ' ) || ') REFERENCES ' || @@ -1029,7 +1142,8 @@ CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT[], TEXT, TEXT[], TEXT ) RETURNS TE $$ LANGUAGE sql; -- fk_ok( fk_schema, fk_table, fk_column[], fk_schema, pk_table, pk_column[] ) -CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[] ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[] ) +RETURNS TEXT AS $$ SELECT fk_ok( $1, $2, $3, $4, $5, $6, $1 || '.' || $2 || '(' || array_to_string( $3, ', ' ) || ') should reference ' || @@ -1038,7 +1152,8 @@ CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[] ) RETU $$ LANGUAGE sql; -- fk_ok( fk_table, fk_column[], pk_table, pk_column[] ) -CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT[], TEXT, TEXT[] ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT[], TEXT, TEXT[] ) +RETURNS TEXT AS $$ SELECT fk_ok( $1, $2, $3, $4, $1 || '(' || array_to_string( $2, ', ' ) || ') should reference ' || @@ -1047,27 +1162,32 @@ CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT[], TEXT, TEXT[] ) RETURNS TEXT AS $$ LANGUAGE sql; -- fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column, description ) -CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, 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 ( TEXT, TEXT, TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT, TEXT, TEXT, 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 ( TEXT, TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT, TEXT, 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 ( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ SELECT fk_ok( $1, ARRAY[$2], $3, ARRAY[$4] ); $$ LANGUAGE sql; -- check_test( test_output, pass, name, description, diag ) -CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) RETURNS SETOF TEXT AS $$ +CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) +RETURNS SETOF TEXT AS $$ DECLARE tnumb INTEGER; aok BOOLEAN; @@ -1147,16 +1267,19 @@ END; $$ LANGUAGE plpgsql; -- check_test( test_output, pass, name, description ) -CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ) RETURNS SETOF TEXT AS $$ +CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ) +RETURNS SETOF TEXT AS $$ SELECT * FROM check_test( $1, $2, $3, $4, NULL ); $$ LANGUAGE sql; -- check_test( test_output, pass, name ) -CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT ) RETURNS SETOF TEXT AS $$ +CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT ) +RETURNS SETOF TEXT AS $$ SELECT * FROM check_test( $1, $2, $3, NULL, NULL ); $$ LANGUAGE sql; -- check_test( test_output, pass ) -CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN ) RETURNS SETOF TEXT AS $$ +CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN ) +RETURNS SETOF TEXT AS $$ SELECT * FROM check_test( $1, $2, NULL, NULL, NULL ); $$ LANGUAGE sql; diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 44eeb253bc72..8e60e14585cc 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,4 +1,60 @@ -- $Id$ +DROP FUNCTION check_test( TEXT, BOOLEAN ); +DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); +DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); +DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); +DROP FUNCTION fk_ok ( TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION fk_ok ( TEXT, TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION fk_ok ( TEXT, TEXT, TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION fk_ok ( TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION fk_ok ( TEXT, TEXT[], TEXT, TEXT[] ); +DROP FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[] ); +DROP FUNCTION fk_ok ( TEXT, TEXT[], TEXT, TEXT[], TEXT ); +DROP FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[], TEXT ); +DROP FUNCTION col_has_check ( TEXT, TEXT ); +DROP FUNCTION col_has_check ( TEXT, TEXT, TEXT ); +DROP FUNCTION col_has_check ( TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION col_has_check ( TEXT, TEXT[] ); +DROP FUNCTION col_has_check ( TEXT, TEXT[], TEXT ); +DROP FUNCTION col_has_check ( TEXT, TEXT, TEXT[], TEXT ); +DROP FUNCTION has_check ( TEXT ); +DROP FUNCTION has_check ( TEXT, TEXT ); +DROP FUNCTION has_check ( TEXT, TEXT, TEXT ); +DROP FUNCTION col_is_unique ( TEXT, TEXT ); +DROP FUNCTION col_is_unique ( TEXT, TEXT, TEXT ); +DROP FUNCTION col_is_unique ( TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION col_is_unique ( TEXT, TEXT[] ); +DROP FUNCTION col_is_unique ( TEXT, TEXT[], TEXT ); +DROP FUNCTION col_is_unique ( TEXT, TEXT, TEXT[], TEXT ); +DROP FUNCTION has_unique ( TEXT ); +DROP FUNCTION has_unique ( TEXT, TEXT ); +DROP FUNCTION has_unique ( TEXT, TEXT, TEXT ); +DROP FUNCTION col_is_fk ( TEXT, TEXT ); +DROP FUNCTION col_is_fk ( TEXT, TEXT, TEXT ); +DROP FUNCTION col_is_fk ( TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION col_is_fk ( TEXT, TEXT[] ); +DROP FUNCTION col_is_fk ( TEXT, TEXT[], TEXT ); +DROP FUNCTION col_is_fk ( TEXT, TEXT, TEXT[], TEXT ); +DROP FUNCTION has_fk ( TEXT ); +DROP FUNCTION has_fk ( TEXT, TEXT ); +DROP FUNCTION has_fk ( TEXT, TEXT, TEXT ); +DROP FUNCTION col_is_pk ( TEXT, TEXT ); +DROP FUNCTION col_is_pk ( TEXT, TEXT, TEXT ); +DROP FUNCTION col_is_pk ( TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION col_is_pk ( TEXT, TEXT[] ); +DROP FUNCTION col_is_pk ( TEXT, TEXT[], TEXT ); +DROP FUNCTION col_is_pk ( TEXT, TEXT, TEXT[], TEXT ); +DROP FUNCTION _ckeys ( TEXT, CHAR ); +DROP FUNCTION _ckeys ( TEXT, TEXT, CHAR ); +DROP FUNCTION has_pk ( TEXT ); +DROP FUNCTION has_pk ( TEXT, TEXT ); +DROP FUNCTION has_pk ( TEXT, TEXT, TEXT ); +DROP FUNCTION _hasc ( TEXT, CHAR ); +DROP FUNCTION _hasc ( TEXT, TEXT, CHAR ); +DROP FUNCTION col_default_is ( TEXT, TEXT, TEXT ); +DROP FUNCTION col_default_is ( TEXT, TEXT, anyelement, TEXT ); +DROP FUNCTION col_default_is ( TEXT, TEXT, TEXT, anyelement, TEXT ); +DROP FUNCTION _def_is( TEXT, anyelement, TEXT ); DROP FUNCTION col_type_is ( TEXT, TEXT, TEXT ); DROP FUNCTION col_type_is ( TEXT, TEXT, TEXT, TEXT ); DROP FUNCTION col_type_is ( TEXT, TEXT, TEXT, TEXT, TEXT ); @@ -32,6 +88,8 @@ DROP FUNCTION fail (); DROP FUNCTION fail ( text ); DROP FUNCTION pass (); DROP FUNCTION pass ( text ); +DROP FUNCTION cmp_ok (anyelement, text, anyelement); +DROP FUNCTION cmp_ok (anyelement, text, anyelement, text); DROP FUNCTION unialike ( anyelement, text ); DROP FUNCTION unialike ( anyelement, text, text ); DROP FUNCTION unalike ( anyelement, text ); @@ -54,16 +112,17 @@ DROP FUNCTION isnt (anyelement, anyelement); DROP FUNCTION isnt (anyelement, anyelement, text); DROP FUNCTION is (anyelement, anyelement); DROP FUNCTION is (anyelement, anyelement, text); -DROP FUNCTION ok ( boolean ); +DROP FUNCTION ok ( boolean ); DROP FUNCTION ok ( boolean, text ); DROP FUNCTION diag ( msg text ); DROP FUNCTION finish (); DROP FUNCTION num_failed (); -DROP FUNCTION add_result ( bool, bool, text, text, text ) +DROP FUNCTION add_result ( bool, bool, text, text, text ); DROP FUNCTION _set ( text, integer ); DROP FUNCTION _set ( text, integer, text ); DROP FUNCTION _get_note ( text ); DROP FUNCTION _get ( text ); DROP FUNCTION no_plan(); DROP FUNCTION plan( integer ); +DROP FUNCTION pg_typeof("any") -- ## DROP SCHEMA TAPSCHEMA; From 5264993b813c168df7107160fb5fb669d84eefd8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 5 Sep 2008 01:27:46 +0000 Subject: [PATCH 0106/1195] Better ordering. --- README.pgtap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index a47d5dd9c73d..3f814dec5576 100644 --- a/README.pgtap +++ b/README.pgtap @@ -778,8 +778,8 @@ Just like `col_is_fk()`, except that it test that the column or array of columns are a primary key. ### fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description ) ### -### fk_ok( fk_table, fk_column[], pk_table, pk_column[], description ) ### ### fk_ok( fk_schema, fk_table, fk_column[], fk_schema, pk_table, pk_column[] ) ### +### fk_ok( fk_table, fk_column[], pk_table, pk_column[], description ) ### ### fk_ok( fk_table, fk_column[], pk_table, pk_column[] ) ### ### fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column, description ) ### ### fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column ) ### From 4aab3db712c49eddcfb81fbc3644508fafcb7183 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 5 Sep 2008 04:12:08 +0000 Subject: [PATCH 0107/1195] * Documented `check_test()` and how to write custom test functions. * Updated `Changes` with notes about `fk_ok()` and `check_test()`. * Modified `check_test()` so that the `:name` argument defaults to "Test" instead of an empty string. * Added a test that calls `check_test()` with no name argument, to ensure that it does indeed generate the default name.' --- Changes | 5 +- README.pgtap | 189 ++++++++++++++++++++++++++++++++++++++++++++- expected/fktap.out | 27 +++---- pgtap.sql.in | 2 +- sql/fktap.sql | 8 +- 5 files changed, 213 insertions(+), 18 deletions(-) diff --git a/Changes b/Changes index 62de2d0ac8e5..83f211cdd3d3 100644 --- a/Changes +++ b/Changes @@ -30,7 +30,8 @@ Revision history for pgTAP Test::Builder 2. Also changed "caught/expected" to "caught/wanted" for nice parity when outputting diagnostics for exception testing. - Added the `has_table()`, `has_view()`, `has_column()`, `has_pk()`, - `has_fk()`, `has_unique()` and `has_check()` test functions. + `has_fk()`, `fk_ok()`, `has_unique()` and `has_check()` test + functions. - Added the `col_not_null()` and `col_is_null()` test functions. - Added the `col_type_is()`, `col_default_is()`, `col_is_pk()`, `col_is_fk()`, `col_is_unique()`, and `col_has_check()` test @@ -45,6 +46,8 @@ Revision history for pgTAP to Andy Armstrong for the spot. - Divided the tests up into many separate test script files, so that things are a bit better organized and easier to maintain. + - Added the `check_test()` function and started converting internal + tests to use it instead of the hacked stuff they were doing before. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/README.pgtap b/README.pgtap index 3f814dec5576..8fb46705c3df 100644 --- a/README.pgtap +++ b/README.pgtap @@ -967,11 +967,196 @@ tests, is that they're like a programmatic todo list. You know how much work is left to be done, you're aware of what bugs there are, and you'll know immediately when they're fixed. +Writing Test Functions +====================== + +So, you've been using pgTAP for a while, and now you want to write your own +test functions. Go ahead; I don't mind. In fact, I encourage it. How? Why, +by providing a function you can use to test your tests, of course! + +But first, a brief primer on writing your own test functions. There isn't much +to it, really. Just write your function to do whatever comparison you want. As +long as you have a boolean value indicating whether or not the test passed, +you're golden. Just then use `ok()` to ensure that everything is tracked +appropriately by a test script. + +For example, say that you wanted to create a function to ensure that two text +values always compare case-insensitively. Sure you could do this with `is()` +and the `LOWER()` function, but if you're doing this all the time, you might +want to simplify things. Here's how to go about it: + + CREATE OR REPLACE FUNCTION is (text, text, text) + RETURNS TEXT AS $$ + DECLARE + result BOOLEAN; + BEGIN + result := LOWER($1) = LOWER($2); + RETURN ok( result, $3 ) || CASE WHEN result THEN '' ELSE E'\n' || diag( + ' Have: ' || $1 || + E'\n Want: ' || $2; + ) END; +END; +$$ LANGUAGE plpgsql; + +Yep, that's it. The key is to always use pgTAP's `ok()` function to guarantee +that the output is properly formatted, uses the next number in the sequence, +and the results are properly recorded in the database for summarization at +the end of the test script. You can also provide diagnostics as appropriate; +just append them to the output of `ok()` as we've done here. + +Of course, you don't have to directly use `ok()`; you can also use another +pgTAP function that ultimately calls `ok()`. IOW, while the above example +is instructive, this version is easier on the eyes: + + CREATE OR REPLACE FUNCTION lc_is ( TEXT, TEXT, TEXT ) + RETURNS TEXT AS $$ + SELECT is( LOWER($1), LOWER(2), $3); + $$ LANGUAGE sql; + +But either way, let pgTAP handle recording the test results and formatting the +output. + +Testing Test Functions +---------------------- + +Now you've written your test function. So how do you test it? Why, with this +handy-dandy test function! + +### check_test( test_output, is_ok, name, want_description, want_diag ) ### +### check_test( test_output, is_ok, name, want_description ) ### +### check_test( test_output, is_ok, name ) ### +### check_test( test_output, is_ok ) ### + + SELECT * FROM check_test( + lc_eq('This', 'THAT', 'not eq'), + false, + 'lc_eq fail', + 'not eq', + E' Want: this\n Have: that' + ); + + SELECT * FROM check_test( + lc_eq('This', 'THIS', 'eq'), + true + ); + +This function runs anywhere between one and three tests against a test +function. For the impatient, the arguments are: + +* `:test_output` - The output from your test. Usually it's just returned by a + call to the test function itself. Required. +* `:is_ok` - Boolean indicating whether or not the test is expected to pass. + Required. +* `:name` - A brief name for your test, to make it easier to find failures in + your test script. Optional. +* `:want_description` - Expected test description to be output by the test. + Optional. +* `:want_diag` - Expected diagnostic message output during the execution of + a test. Must always follow whatever is output by the call to `ok()`. + +Now, on with the detailed documentation. At its simplest, you just pass it the +output of your test (and it must be one and **only one** test function's +output, or you'll screw up the count, so don't do that!) and a boolean value +indicating whether or not you expect the test to have passed. That looks +something like the second example above. + +All other arguments are optional, but I recommend that you *always* include a +short test name to make it easier to track down failures in your test script. +`check_test()` uses this name to construct descriptions of all of the tests it +runs. For example, without a short name, the above example will yield output +like so: + + not ok 14 - Test should pass + +Yeah, but which test? So give it a very succinct name and you'll know what +test. If you have a lot of these, it won't be much help. So give each call +to `check_test()` a name: + + SELECT * FROM check_test( + lc_eq('This', 'THIS', 'eq'), + true, + 'Simple lc_eq test', + ); + +Then you'll get output more like this: + + not ok 14 - Simple lc_test should pass + +Which will make it much easier to find the failing test in your test script. + +The optional fourth argument is the description you expect to be output. This +is especially important if your test function generates a description when +none is passed to it. You want to make sure that your function generates the +test description you think it should! This will cause a second test to be run +on your test function. So for something like this: + + SELECT * FROM check_test( + lc_eq( ''this'', ''THIS'' ), + true, + 'lc_eq() test', + 'this is THIS' + ); + +The output then would look something like this, assuming that the `lc_eq()` +function generated the proper description (the above example does not): + + ok 42 - lc_eq() test should pass + ok 43 - lc_eq() test should have the proper description + +See how there are two tests run for a single call to `check_test()`? Be sure +to adjust your plan accordingly. Also note how the test name was used in the +descriptions for both tests. + +If the test had failed, it would output a nice diagnostics (internally it just +uses `is()` to compare the strings): + + not ok 43 - lc_eq() test should have the proper description + # Failed test 43: "lc_eq() test should have the proper description" + # have: 'this is this' + # want: 'this is THIS' + +The fifth argument, also optional, of course, compares the diagnostics +generated during the test to an expected string. Such diagnostics **must** +follow whatever is output by the call to `ok()` in your test. Your test +fuction should not call `diag()` until after it calls `ok()` or things will +get truly funky. + +Assumign you've followed that rule in your `lc_eq()` tset function, to see +what happens when a `lc_eq()` fails. Write your test to test the diagnostics +like so: + + SELECT * FROM check_test( + lc_eq( ''this'', ''THat'' ), + false, + 'lc_eq() failing test', + 'this is THat', + E' Want: this\n Have: THat + ); + +This of course triggers a third test to run. The output will look like so: + + ok 44 - lc_eq() failing test should fail + ok 45 - lc_eq() failing test should have the proper description + ok 46 - lc_eq() failing test should have the proper diagnostics + +And of course, it the diagnostic test fails, it will output diagnostics just +like a description failure would, something like this: + + not ok 46 - lc_eq() failing test should have the proper diagnostics + # Failed test 46: "lc_eq() failing test should have the proper diagnostics" + # have: Have: this + # Want: that + # want: Have: this + # Want: THat + +I realize that can be a bit confusing, given the various haves and wants, but +it gets the job done. Of course, if your diagnostics use something other than +indented "have" and "want", such failures will be easier to read. But either +way, do test your diagnostics! + To Do ----- -* Add a test function to test test functions and update the test script to - use it. * Update the Makefile to process pg_prove and set the proper path to Perl on the shebang line. Will likely require a `$(PERL)` environment variable. * Update the Makefile to require TAP::Harness. diff --git a/expected/fktap.out b/expected/fktap.out index d81c0c12d6c1..90dc9d1a88ac 100644 --- a/expected/fktap.out +++ b/expected/fktap.out @@ -1,5 +1,5 @@ \set ECHO -1..60 +1..61 ok 1 - has_fk( schema, table, description ) should pass ok 2 - has_fk( schema, table, description ) should have the proper description ok 3 - has_fk( table, description ) should pass @@ -48,15 +48,16 @@ ok 45 - basic fk_ok noschema should pass ok 46 - basic fk_ok noschema should have the proper description ok 47 - basic fk_ok noschema desc should pass ok 48 - basic fk_ok noschema desc should have the proper description -ok 49 - fk_ok fail should fail -ok 50 - fk_ok fail should have the proper description -ok 51 - fk_ok fail should have the proper diagnostics -ok 52 - fk_ok fail desc should fail -ok 53 - fk_ok fail desc should have the proper description -ok 54 - fk_ok fail desc should have the proper diagnostics -ok 55 - fk_ok fail no schema should fail -ok 56 - fk_ok fail no schema should have the proper description -ok 57 - fk_ok fail no schema should have the proper diagnostics -ok 58 - fk_ok fail no schema desc should fail -ok 59 - fk_ok fail no schema desc should have the proper description -ok 60 - fk_ok fail no schema desc should have the proper diagnostics +ok 49 - Test should pass +ok 50 - fk_ok fail should fail +ok 51 - fk_ok fail should have the proper description +ok 52 - fk_ok fail should have the proper diagnostics +ok 53 - fk_ok fail desc should fail +ok 54 - fk_ok fail desc should have the proper description +ok 55 - fk_ok fail desc should have the proper diagnostics +ok 56 - fk_ok fail no schema should fail +ok 57 - fk_ok fail no schema should have the proper description +ok 58 - fk_ok fail no schema should have the proper diagnostics +ok 59 - fk_ok fail no schema desc should fail +ok 60 - fk_ok fail no schema desc should have the proper description +ok 61 - fk_ok fail no schema desc should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 1587fb96fb57..f14871a2ac22 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1213,7 +1213,7 @@ BEGIN EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb; -- Set up the description. - descr := coalesce( name || ' ', '' ) || 'should '; + descr := coalesce( name || ' ', 'Test ' ) || 'should '; -- So, did the test pass? RETURN NEXT is( diff --git a/sql/fktap.sql b/sql/fktap.sql index c569844d5a98..28ce7c31c7f1 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -31,7 +31,7 @@ BEGIN; -- ## SET search_path TO TAPSCHEMA,public; -- Set the test plan. -SELECT plan(60); +SELECT plan(61); -- These will be rolled back. :-) CREATE TABLE pk ( @@ -229,6 +229,12 @@ SELECT * FROM check_test( 'fk(pk_id) should reference pk(id)' ); +-- Make sure check_test() works properly with no name argument. +SELECT * FROM check_test( + fk_ok( 'fk', 'pk_id', 'pk', 'id' ), + true +); + SELECT * FROM check_test( fk_ok( 'public', 'fk', ARRAY['pk_id'], 'public', 'pk', ARRAY['fid'], 'WHATEVER' ), false, From e30a70c140c15abb4cfa154e57e49370b491f1a6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 5 Sep 2008 04:15:00 +0000 Subject: [PATCH 0108/1195] Note bug that needs fixing. --- pgtap.sql.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pgtap.sql.in b/pgtap.sql.in index f14871a2ac22..024cf918608a 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1116,6 +1116,8 @@ $$ LANGUAGE sql; -- fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description ) CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ +-- XXX Need to fix the check for the fk to make sure that it actually +-- references the pk table. SELECT is( $1 || '.' || $2 || '(' || array_to_string( _ckeys( $1, $2, 'f' ), ', ' ) || ') REFERENCES ' || From b8a1af2c9acf536051fd3355407540987fd2196d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 5 Sep 2008 04:26:54 +0000 Subject: [PATCH 0109/1195] A couple of things I need to fix before release. --- README.pgtap | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.pgtap b/README.pgtap index 8fb46705c3df..6ad710877cce 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1157,6 +1157,9 @@ way, do test your diagnostics! To Do ----- +* Fix the check for the fk in `fk_ok() to make sure that it actually + references the pk table. +* Handle multiple FK constraints in `fk_ok()` and the other FK test functions. * Update the Makefile to process pg_prove and set the proper path to Perl on the shebang line. Will likely require a `$(PERL)` environment variable. * Update the Makefile to require TAP::Harness. From 773d6c54ca831ce59fa6d29e9e2ff4e613f3bf6e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 5 Sep 2008 05:33:08 +0000 Subject: [PATCH 0110/1195] * Fixed `fk_ok()` so that it checks for the actual primary key table for a given foreign key table. There's probably more to do here, though. Needs some thought. --- README.pgtap | 2 -- expected/fktap.out | 5 +++- pgtap.sql.in | 62 ++++++++++++++++++++++++++++++++++++++++------ sql/fktap.sql | 11 +++++++- 4 files changed, 68 insertions(+), 12 deletions(-) diff --git a/README.pgtap b/README.pgtap index 6ad710877cce..12a2d4ce3afb 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1157,8 +1157,6 @@ way, do test your diagnostics! To Do ----- -* Fix the check for the fk in `fk_ok() to make sure that it actually - references the pk table. * Handle multiple FK constraints in `fk_ok()` and the other FK test functions. * Update the Makefile to process pg_prove and set the proper path to Perl on the shebang line. Will likely require a `$(PERL)` environment variable. diff --git a/expected/fktap.out b/expected/fktap.out index 90dc9d1a88ac..bc4f6f89d990 100644 --- a/expected/fktap.out +++ b/expected/fktap.out @@ -1,5 +1,5 @@ \set ECHO -1..61 +1..64 ok 1 - has_fk( schema, table, description ) should pass ok 2 - has_fk( schema, table, description ) should have the proper description ok 3 - has_fk( table, description ) should pass @@ -61,3 +61,6 @@ ok 58 - fk_ok fail no schema should have the proper diagnostics ok 59 - fk_ok fail no schema desc should fail ok 60 - fk_ok fail no schema desc should have the proper description ok 61 - fk_ok fail no schema desc should have the proper diagnostics +ok 62 - fk_ok bad PK test should fail +ok 63 - fk_ok bad PK test should have the proper description +ok 64 - fk_ok bad PK test should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 024cf918608a..35d176e8b25d 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1113,35 +1113,81 @@ RETURNS TEXT AS $$ SELECT col_has_check( $1, $2, 'Column ' || $1 || '.' || $2 || ' should have a check constraint' ); $$ LANGUAGE sql; +-- _pk_table_for( fk_schema, fk_table ) +CREATE OR REPLACE FUNCTION _pk_table_for ( TEXT, TEXT ) +RETURNS NAME[] AS $$ + SELECT ARRAY [n2.nspname, c2.relname] + FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, + pg_catalog.pg_attribute a, pg_catalog.pg_constraint x, + pg_catalog.pg_namespace n2, pg_catalog.pg_class c2 + WHERE n.oid = c.relnamespace + AND c.oid = a.attrelid + AND c.oid = x.conrelid + AND x.confrelid = c2.oid + AND c2.relnamespace = n2.oid + AND a.attnum = ANY(x.conkey) + AND n.nspname = $1 + AND c.relname = $2 + AND x.contype = 'f' + AND a.attnum > 0 + AND NOT a.attisdropped + LIMIT 1; +$$ LANGUAGE sql; + +-- _pk_table_for( fk_table ) +CREATE OR REPLACE FUNCTION _pk_table_for ( TEXT ) +RETURNS NAME AS $$ + SELECT c2.relname + FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, + pg_catalog.pg_constraint x, pg_catalog.pg_class c2 + WHERE c.oid = a.attrelid + AND c.oid = x.conrelid + AND x.confrelid = c2.oid + AND a.attnum = ANY(x.conkey) + AND c.relname = $1 + AND x.contype = 'f' + AND a.attnum > 0 + AND NOT a.attisdropped + LIMIT 1; +$$ LANGUAGE sql; + -- fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description ) CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ --- XXX Need to fix the check for the fk to make sure that it actually --- references the pk table. - SELECT is( +DECLARE + pk_table NAME[]; +BEGIN + pk_table = COALESCE(_pk_table_for( $1, $2 ), '{}'::name[]); + RETURN is( $1 || '.' || $2 || '(' || array_to_string( _ckeys( $1, $2, 'f' ), ', ' ) || ') REFERENCES ' || - $4 || '.' || $5 || '(' || array_to_string( _ckeys( $4, $5, 'p' ), ', ' ) || ')', + array_to_string(pk_table, '.') || '(' || array_to_string( _ckeys( pk_table[1], pk_table[2], 'p' ), ', ' ) || ')', $1 || '.' || $2 || '(' || array_to_string( $3, ', ' ) || ') REFERENCES ' || $4 || '.' || $5 || '(' || array_to_string( $6, ', ' ) || ')', $7 ); -$$ LANGUAGE sql; +END; +$$ LANGUAGE plpgsql; -- fk_ok( fk_table, fk_column[], pk_table, pk_column[], description ) CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT[], TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ - SELECT is( +DECLARE + pk_table NAME; +BEGIN + pk_table = COALESCE(_pk_table_for( $1 ), ''::name); + RETURN is( $1 || '(' || array_to_string( _ckeys( $1, 'f' ), ', ' ) || ') REFERENCES ' || - $3 || '(' || array_to_string( _ckeys( $3, 'p' ), ', ' ) || ')', + pk_table || '(' || array_to_string( _ckeys( pk_table, 'p' ), ', ' ) || ')', $1 || '(' || array_to_string( $2, ', ' ) || ') REFERENCES ' || $3 || '(' || array_to_string( $4, ', ' ) || ')', $5 ); -$$ LANGUAGE sql; +END; +$$ LANGUAGE plpgsql; -- fk_ok( fk_schema, fk_table, fk_column[], fk_schema, pk_table, pk_column[] ) CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[] ) diff --git a/sql/fktap.sql b/sql/fktap.sql index 28ce7c31c7f1..a848a26b3888 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -31,7 +31,7 @@ BEGIN; -- ## SET search_path TO TAPSCHEMA,public; -- Set the test plan. -SELECT plan(61); +SELECT plan(64); -- These will be rolled back. :-) CREATE TABLE pk ( @@ -271,6 +271,15 @@ SELECT * FROM check_test( want: fk(pk_id) REFERENCES pk(fid)' ); +SELECT * FROM check_test( + fk_ok( 'fk', ARRAY['pk_id'], 'ok', ARRAY['fid'], 'WHATEVER' ), + false, + 'fk_ok bad PK test', + 'WHATEVER', + ' have: fk(pk_id) REFERENCES pk(id) + want: fk(pk_id) REFERENCES ok(fid)' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 90a48e99a788e7b9b8d0801038de4a9a1ef1f474 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 5 Sep 2008 21:28:18 +0000 Subject: [PATCH 0111/1195] Added functions to find a primary key table and columns based on a foreign key table and columns. This maks `fk_ok()` a bit better. --- pgtap.sql.in | 137 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 93 insertions(+), 44 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 35d176e8b25d..149f650ddc88 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -895,6 +895,7 @@ RETURNS TEXT[] AS $$ AND x.contype = $3 AND a.attnum > 0 AND NOT a.attisdropped + ORDER BY a.attnum ); $$ LANGUAGE sql; @@ -912,9 +913,91 @@ RETURNS TEXT[] AS $$ AND a.attnum = ANY(x.conkey) AND a.attnum > 0 AND NOT a.attisdropped + ORDER BY a.attnum ); $$ LANGUAGE sql; +-- _pk_cols_for_fk( schema, table, cols[], constraint_type ) +CREATE OR REPLACE FUNCTION _pk_cols_for_fk ( TEXT, TEXT, TEXT[], CHAR ) +RETURNS TEXT[] AS $$ + SELECT ARRAY( SELECT col::text FROM ( + SELECT DISTINCT a2.attnum AS num, a2.attname AS col + FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, + pg_catalog.pg_attribute a, pg_catalog.pg_constraint x, + pg_catalog.pg_attribute a2 + WHERE n.oid = c.relnamespace + AND c.oid = a.attrelid + AND c.oid = x.conrelid + AND x.confrelid = a2.attrelid + AND a2.attnum = ANY(x.confkey) + AND n.nspname = $1 + AND c.relname = $2 + AND a.attname = ANY($3) + AND x.contype = $4 + AND a.attnum > 0 + AND NOT a.attisdropped + ORDER BY a2.attnum + ) AS foo); +$$ LANGUAGE sql; + +-- _pk_cols_for_fk( table, cols[], constraint_type ) +CREATE OR REPLACE FUNCTION _pk_cols_for_fk ( TEXT, TEXT[], CHAR ) +RETURNS TEXT[] AS $$ + SELECT ARRAY( SELECT col::text FROM ( + SELECT DISTINCT a2.attnum AS num, a2.attname AS col + FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, + pg_catalog.pg_constraint x, pg_catalog.pg_attribute a2 + WHERE c.oid = a.attrelid + AND c.oid = x.conrelid + AND x.confrelid = a2.attrelid + AND a2.attnum = ANY(x.confkey) + AND c.relname = $1 + AND a.attname = ANY($2) + AND x.contype = $3 + AND a.attnum > 0 + AND NOT a.attisdropped + ORDER BY a2.attnum + ) AS foo); +$$ LANGUAGE sql; + +-- _pk_tab_for_fk( schema, table, cols[], constraint_type ) +CREATE OR REPLACE FUNCTION _pk_tab_for_fk ( TEXT, TEXT, TEXT[], CHAR ) +RETURNS TEXT AS $$ + SELECT n2.nspname || '.' || c2.relname + FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, + pg_catalog.pg_attribute a, pg_catalog.pg_constraint x, + pg_catalog.pg_namespace n2, pg_catalog.pg_class c2 + WHERE n.oid = c.relnamespace + AND c.oid = a.attrelid + AND c.oid = x.conrelid + AND x.confrelid = c2.oid + AND c2.relnamespace = n2.oid + AND n.nspname = $1 + AND c.relname = $2 + AND a.attname = ANY($3) + AND x.contype = $4 + AND a.attnum > 0 + AND NOT a.attisdropped + LIMIT 1 +$$ LANGUAGE sql; + +-- _pk_tab_for_fk( table, cols[], constraint_type ) +CREATE OR REPLACE FUNCTION _pk_tab_for_fk ( TEXT, TEXT[], CHAR ) +RETURNS TEXT AS $$ + SELECT c2.relname::text + FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, + pg_catalog.pg_constraint x, pg_catalog.pg_class c2 + WHERE c.oid = a.attrelid + AND c.oid = x.conrelid + AND x.confrelid = c2.oid + AND c.relname = $1 + AND a.attname = ANY($2) + AND x.contype = $3 + AND a.attnum > 0 + AND NOT a.attisdropped + LIMIT 1 +$$ LANGUAGE sql; + -- col_is_pk( schema, table, column, description ) CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ @@ -1113,55 +1196,19 @@ RETURNS TEXT AS $$ SELECT col_has_check( $1, $2, 'Column ' || $1 || '.' || $2 || ' should have a check constraint' ); $$ LANGUAGE sql; --- _pk_table_for( fk_schema, fk_table ) -CREATE OR REPLACE FUNCTION _pk_table_for ( TEXT, TEXT ) -RETURNS NAME[] AS $$ - SELECT ARRAY [n2.nspname, c2.relname] - FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, - pg_catalog.pg_attribute a, pg_catalog.pg_constraint x, - pg_catalog.pg_namespace n2, pg_catalog.pg_class c2 - WHERE n.oid = c.relnamespace - AND c.oid = a.attrelid - AND c.oid = x.conrelid - AND x.confrelid = c2.oid - AND c2.relnamespace = n2.oid - AND a.attnum = ANY(x.conkey) - AND n.nspname = $1 - AND c.relname = $2 - AND x.contype = 'f' - AND a.attnum > 0 - AND NOT a.attisdropped - LIMIT 1; -$$ LANGUAGE sql; - --- _pk_table_for( fk_table ) -CREATE OR REPLACE FUNCTION _pk_table_for ( TEXT ) -RETURNS NAME AS $$ - SELECT c2.relname - FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, - pg_catalog.pg_constraint x, pg_catalog.pg_class c2 - WHERE c.oid = a.attrelid - AND c.oid = x.conrelid - AND x.confrelid = c2.oid - AND a.attnum = ANY(x.conkey) - AND c.relname = $1 - AND x.contype = 'f' - AND a.attnum > 0 - AND NOT a.attisdropped - LIMIT 1; -$$ LANGUAGE sql; - -- fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description ) CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ DECLARE - pk_table NAME[]; + pk_table TEXT; + pk_cols TEXT[]; BEGIN - pk_table = COALESCE(_pk_table_for( $1, $2 ), '{}'::name[]); + pk_table = _pk_tab_for_fk( $1, $2, $3, 'f' ); + pk_cols = _pk_cols_for_fk( $1, $2, $3, 'f' ); RETURN is( $1 || '.' || $2 || '(' || array_to_string( _ckeys( $1, $2, 'f' ), ', ' ) || ') REFERENCES ' || - array_to_string(pk_table, '.') || '(' || array_to_string( _ckeys( pk_table[1], pk_table[2], 'p' ), ', ' ) || ')', + pk_table || '(' || array_to_string( pk_cols, ', ' ) || ')', $1 || '.' || $2 || '(' || array_to_string( $3, ', ' ) || ') REFERENCES ' || $4 || '.' || $5 || '(' || array_to_string( $6, ', ' ) || ')', @@ -1174,13 +1221,15 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT[], TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ DECLARE - pk_table NAME; + pk_table TEXT; + pk_cols TEXT[]; BEGIN - pk_table = COALESCE(_pk_table_for( $1 ), ''::name); + pk_table = _pk_tab_for_fk( $1, $2, 'f' ); + pk_cols = _pk_cols_for_fk( $1, $2, 'f' ); RETURN is( $1 || '(' || array_to_string( _ckeys( $1, 'f' ), ', ' ) || ') REFERENCES ' || - pk_table || '(' || array_to_string( _ckeys( pk_table, 'p' ), ', ' ) || ')', + pk_table || '(' || array_to_string( pk_cols, ', ' ) || ')', $1 || '(' || array_to_string( $2, ', ' ) || ') REFERENCES ' || $3 || '(' || array_to_string( $4, ', ' ) || ')', From c953373aedb3598f3eafd9badf6e1963f4544fd4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 5 Sep 2008 21:29:52 +0000 Subject: [PATCH 0112/1195] Go back to SQL functions. --- pgtap.sql.in | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 149f650ddc88..41701cc23de3 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1199,44 +1199,30 @@ $$ LANGUAGE sql; -- fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description ) CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ -DECLARE - pk_table TEXT; - pk_cols TEXT[]; -BEGIN - pk_table = _pk_tab_for_fk( $1, $2, $3, 'f' ); - pk_cols = _pk_cols_for_fk( $1, $2, $3, 'f' ); - RETURN is( + SELECT is( $1 || '.' || $2 || '(' || array_to_string( _ckeys( $1, $2, 'f' ), ', ' ) || ') REFERENCES ' || - pk_table || '(' || array_to_string( pk_cols, ', ' ) || ')', + _pk_tab_for_fk( $1, $2, $3, 'f' ) || '(' || array_to_string( _pk_cols_for_fk( $1, $2, $3, 'f' ), ', ' ) || ')', $1 || '.' || $2 || '(' || array_to_string( $3, ', ' ) || ') REFERENCES ' || $4 || '.' || $5 || '(' || array_to_string( $6, ', ' ) || ')', $7 ); -END; -$$ LANGUAGE plpgsql; +$$ LANGUAGE sql; -- fk_ok( fk_table, fk_column[], pk_table, pk_column[], description ) CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT[], TEXT, TEXT[], TEXT ) RETURNS TEXT AS $$ -DECLARE - pk_table TEXT; - pk_cols TEXT[]; -BEGIN - pk_table = _pk_tab_for_fk( $1, $2, 'f' ); - pk_cols = _pk_cols_for_fk( $1, $2, 'f' ); - RETURN is( + SELECT is( $1 || '(' || array_to_string( _ckeys( $1, 'f' ), ', ' ) || ') REFERENCES ' || - pk_table || '(' || array_to_string( pk_cols, ', ' ) || ')', + _pk_tab_for_fk( $1, $2, 'f' ) || '(' || array_to_string( _pk_cols_for_fk( $1, $2, 'f' ), ', ' ) || ')', $1 || '(' || array_to_string( $2, ', ' ) || ') REFERENCES ' || $3 || '(' || array_to_string( $4, ', ' ) || ')', $5 ); -END; -$$ LANGUAGE plpgsql; +$$ LANGUAGE sql; -- fk_ok( fk_schema, fk_table, fk_column[], fk_schema, pk_table, pk_column[] ) CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[] ) From 5c5493e6769e6f8bcbbd742a902866f32b72a9c9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 5 Sep 2008 21:40:43 +0000 Subject: [PATCH 0113/1195] Updated uninstall script and fixed formatting in main script to make uninstall script generation easier in the future. --- pgtap.sql.in | 14 ++------------ uninstall_pgtap.sql.in | 12 ++++++++---- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 41701cc23de3..615dc8b8cef2 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -266,12 +266,7 @@ RETURNS TEXT AS $$ SELECT isnt( $1, $2, NULL); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _alike ( - BOOLEAN, - ANYELEMENT, - TEXT, - TEXT -) +CREATE OR REPLACE FUNCTION _alike ( BOOLEAN, ANYELEMENT, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE result ALIAS FOR $1; @@ -328,12 +323,7 @@ RETURNS TEXT AS $$ SELECT _alike( $1 ~~* $2, $1, $2, NULL ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _unalike ( - BOOLEAN, - ANYELEMENT, - TEXT, - TEXT -) +CREATE OR REPLACE FUNCTION _unalike ( BOOLEAN, ANYELEMENT, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE result ALIAS FOR $1; diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 8e60e14585cc..01ab537b3ba5 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -44,6 +44,10 @@ DROP FUNCTION col_is_pk ( TEXT, TEXT, TEXT, TEXT ); DROP FUNCTION col_is_pk ( TEXT, TEXT[] ); DROP FUNCTION col_is_pk ( TEXT, TEXT[], TEXT ); DROP FUNCTION col_is_pk ( TEXT, TEXT, TEXT[], TEXT ); +DROP FUNCTION _pk_tab_for_fk ( TEXT, TEXT[], CHAR ); +DROP FUNCTION _pk_tab_for_fk ( TEXT, TEXT, TEXT[], CHAR ); +DROP FUNCTION _pk_cols_for_fk ( TEXT, TEXT[], CHAR ); +DROP FUNCTION _pk_cols_for_fk ( TEXT, TEXT, TEXT[], CHAR ); DROP FUNCTION _ckeys ( TEXT, CHAR ); DROP FUNCTION _ckeys ( TEXT, TEXT, CHAR ); DROP FUNCTION has_pk ( TEXT ); @@ -83,7 +87,7 @@ DROP FUNCTION throws_ok ( TEXT ); DROP FUNCTION throws_ok ( TEXT, CHAR(5) ); DROP FUNCTION throws_ok ( TEXT, CHAR(5), TEXT ); DROP FUNCTION _todo(); -DROP FUNCTION todo ( why text, how_many int ) +DROP FUNCTION todo ( why text, how_many int ); DROP FUNCTION fail (); DROP FUNCTION fail ( text ); DROP FUNCTION pass (); @@ -98,7 +102,7 @@ DROP FUNCTION doesnt_imatch ( anyelement, text ); DROP FUNCTION doesnt_imatch ( anyelement, text, text ); DROP FUNCTION doesnt_match ( anyelement, text ); DROP FUNCTION doesnt_match ( anyelement, text, text ); -DROP FUNCTION _unalike ( +DROP FUNCTION _unalike ( BOOLEAN, ANYELEMENT, TEXT, TEXT ); DROP FUNCTION ialike ( anyelement, text ); DROP FUNCTION ialike ( anyelement, text, text ); DROP FUNCTION alike ( anyelement, text ); @@ -107,7 +111,7 @@ DROP FUNCTION imatches ( anyelement, text ); DROP FUNCTION imatches ( anyelement, text, text ); DROP FUNCTION matches ( anyelement, text ); DROP FUNCTION matches ( anyelement, text, text ); -DROP FUNCTION _alike ( +DROP FUNCTION _alike ( BOOLEAN, ANYELEMENT, TEXT, TEXT ); DROP FUNCTION isnt (anyelement, anyelement); DROP FUNCTION isnt (anyelement, anyelement, text); DROP FUNCTION is (anyelement, anyelement); @@ -124,5 +128,5 @@ DROP FUNCTION _get_note ( text ); DROP FUNCTION _get ( text ); DROP FUNCTION no_plan(); DROP FUNCTION plan( integer ); -DROP FUNCTION pg_typeof("any") +DROP FUNCTION pg_typeof("any"); -- ## DROP SCHEMA TAPSCHEMA; From f23be74acca4e42b66a409bcde4ae0f409e0016c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 7 Sep 2008 20:43:26 +0000 Subject: [PATCH 0114/1195] * Changed output of extra tests so user does not have to do any extra math. * Added some functions to the To Do list, so I remember to implement them. --- Changes | 3 +++ README.pgtap | 4 ++++ pgtap.sql.in | 9 ++------- sql/moretap.sql | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Changes b/Changes index 83f211cdd3d3..961adec4d8ef 100644 --- a/Changes +++ b/Changes @@ -48,6 +48,9 @@ Revision history for pgTAP things are a bit better organized and easier to maintain. - Added the `check_test()` function and started converting internal tests to use it instead of the hacked stuff they were doing before. + - As in Test::Builder 0.81_01, changed the message for extra tests run + to show the number of tests run rather than the number extra to avoid + the user having to do mental math. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/README.pgtap b/README.pgtap index 12a2d4ce3afb..0c6816089d06 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1163,6 +1163,10 @@ To Do * Update the Makefile to require TAP::Harness. * Update the Makefile to alter the source as appropriate for older versions of PostgreSQL. +* Add `todo_start()` and `todo_end()` (and `in_todo()`). +* Add `note()`? +* Add `can_ok()`. + Suported Versions ----------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index 615dc8b8cef2..c499563ae35c 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -150,15 +150,10 @@ BEGIN RETURN NEXT '1..' || exp_tests; END IF; - IF curr_test < exp_tests THEN + IF curr_test <> exp_tests THEN RETURN NEXT diag( 'Looks like you planned ' || exp_tests || ' test' || - plural || ' but only ran ' || curr_test - ); - ELSIF curr_test > exp_tests THEN - RETURN NEXT diag( - 'Looks like you planned ' || exp_tests || ' test' || - plural || ' but ran ' || curr_test - exp_tests || ' extra' + plural || ' but ran ' || curr_test ); ELSIF num_faild > 0 THEN RETURN NEXT diag( diff --git a/sql/moretap.sql b/sql/moretap.sql index d2a45f6840e0..dcbb4ba955bc 100644 --- a/sql/moretap.sql +++ b/sql/moretap.sql @@ -80,7 +80,7 @@ DELETE FROM __tcache__ WHERE label = 'plan'; SELECT is( plan(4000), '1..4000', 'Set the plan to 4000' ); SELECT is( (SELECT * FROM finish() LIMIT 1), - '# Looks like you planned 4000 tests but only ran 11', + '# Looks like you planned 4000 tests but ran 11', 'The output of finish() should reflect a high test plan' ); @@ -89,7 +89,7 @@ DELETE FROM __tcache__ WHERE label = 'plan'; SELECT is( plan(4), '1..4', 'Set the plan to 4' ); SELECT is( (SELECT * FROM finish() LIMIT 1), - '# Looks like you planned 4 tests but ran 9 extra', + '# Looks like you planned 4 tests but ran 13', 'The output of finish() should reflect a low test plan' ); From 78f8754df318112ff45a4f0f1b522c60d6de1803 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 12 Sep 2008 19:08:14 +0000 Subject: [PATCH 0115/1195] Fixed function for fetching PKs for FKs. Still have new failing tests, though, as I need to correct for the opposite. --- pgtap.sql.in | 2 ++ sql/fktap.sql | 30 ++++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index c499563ae35c..02a7a0b75191 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -915,6 +915,7 @@ RETURNS TEXT[] AS $$ AND c.oid = x.conrelid AND x.confrelid = a2.attrelid AND a2.attnum = ANY(x.confkey) + AND a.attnum = ANY(x.conkey) AND n.nspname = $1 AND c.relname = $2 AND a.attname = ANY($3) @@ -936,6 +937,7 @@ RETURNS TEXT[] AS $$ AND c.oid = x.conrelid AND x.confrelid = a2.attrelid AND a2.attnum = ANY(x.confkey) + AND a.attnum = ANY(x.conkey) AND c.relname = $1 AND a.attname = ANY($2) AND x.contype = $3 diff --git a/sql/fktap.sql b/sql/fktap.sql index a848a26b3888..5bea135c3bc2 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -31,7 +31,8 @@ BEGIN; -- ## SET search_path TO TAPSCHEMA,public; -- Set the test plan. -SELECT plan(64); +--SELECT plan(64); +select * from no_plan(); -- These will be rolled back. :-) CREATE TABLE pk ( @@ -56,6 +57,14 @@ CREATE TABLE fk2 ( FOREIGN KEY(pk2_num, pk2_dot) REFERENCES pk2( num, dot) ); +CREATE TABLE fk3( + id INT NOT NULL PRIMARY KEY, + pk_id INT NOT NULL REFERENCES pk(id), + pk2_num int NOT NULL, + pk2_dot int NOT NULL, + FOREIGN KEY(pk2_num, pk2_dot) REFERENCES pk2( num, dot) +); + /****************************************************************************/ -- Test has_fk(). SELECT * FROM check_test( @@ -226,7 +235,8 @@ SELECT * FROM check_test( fk_ok( 'fk', 'pk_id', 'pk', 'id' ), true, 'basic fk_ok noschema desc', - 'fk(pk_id) should reference pk(id)' + 'fk(pk_id) should reference pk(id)', + '' ); -- Make sure check_test() works properly with no name argument. @@ -280,6 +290,22 @@ SELECT * FROM check_test( want: fk(pk_id) REFERENCES ok(fid)' ); +SELECT * FROM check_test( + fk_ok( 'public', 'fk3', 'pk_id', 'public', 'pk', 'id' ), + true, + 'double fk schema test', + 'public.fk3(pk_id) should reference public.pk(id)', + '' +); + +SELECT * FROM check_test( + fk_ok( 'fk3', 'pk_id', 'pk', 'id' ), + true, + 'double fk test', + 'fk3(pk_id) should reference pk(id)', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From c0550000b2bb6e27e4044bd5b86750299274dfd4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 15 Sep 2008 22:28:56 +0000 Subject: [PATCH 0116/1195] Added a view from newsysviews in order to make querying for foreign keys easier. And it's a lot better now, with better support for multiple FKs and the like. Still need to update the `col_is_fk()` functions to use it. That's up next. --- expected/fktap.out | 50 +++++--- pgtap.sql.in | 276 +++++++++++++++++++++++++-------------------- sql/fktap.sql | 45 +++++++- 3 files changed, 225 insertions(+), 146 deletions(-) diff --git a/expected/fktap.out b/expected/fktap.out index bc4f6f89d990..ea50e48dd6c5 100644 --- a/expected/fktap.out +++ b/expected/fktap.out @@ -1,5 +1,5 @@ \set ECHO -1..64 +1..80 ok 1 - has_fk( schema, table, description ) should pass ok 2 - has_fk( schema, table, description ) should have the proper description ok 3 - has_fk( table, description ) should pass @@ -48,19 +48,35 @@ ok 45 - basic fk_ok noschema should pass ok 46 - basic fk_ok noschema should have the proper description ok 47 - basic fk_ok noschema desc should pass ok 48 - basic fk_ok noschema desc should have the proper description -ok 49 - Test should pass -ok 50 - fk_ok fail should fail -ok 51 - fk_ok fail should have the proper description -ok 52 - fk_ok fail should have the proper diagnostics -ok 53 - fk_ok fail desc should fail -ok 54 - fk_ok fail desc should have the proper description -ok 55 - fk_ok fail desc should have the proper diagnostics -ok 56 - fk_ok fail no schema should fail -ok 57 - fk_ok fail no schema should have the proper description -ok 58 - fk_ok fail no schema should have the proper diagnostics -ok 59 - fk_ok fail no schema desc should fail -ok 60 - fk_ok fail no schema desc should have the proper description -ok 61 - fk_ok fail no schema desc should have the proper diagnostics -ok 62 - fk_ok bad PK test should fail -ok 63 - fk_ok bad PK test should have the proper description -ok 64 - fk_ok bad PK test should have the proper diagnostics +ok 49 - basic fk_ok noschema desc should have the proper diagnostics +ok 50 - Test should pass +ok 51 - fk_ok fail should fail +ok 52 - fk_ok fail should have the proper description +ok 53 - fk_ok fail should have the proper diagnostics +ok 54 - fk_ok fail desc should fail +ok 55 - fk_ok fail desc should have the proper description +ok 56 - fk_ok fail desc should have the proper diagnostics +ok 57 - fk_ok fail no schema should fail +ok 58 - fk_ok fail no schema should have the proper description +ok 59 - fk_ok fail no schema should have the proper diagnostics +ok 60 - fk_ok fail no schema desc should fail +ok 61 - fk_ok fail no schema desc should have the proper description +ok 62 - fk_ok fail no schema desc should have the proper diagnostics +ok 63 - fk_ok bad PK test should fail +ok 64 - fk_ok bad PK test should have the proper description +ok 65 - fk_ok bad PK test should have the proper diagnostics +ok 66 - double fk schema test should pass +ok 67 - double fk schema test should have the proper description +ok 68 - double fk schema test should have the proper diagnostics +ok 69 - double fk test should pass +ok 70 - double fk test should have the proper description +ok 71 - double fk test should have the proper diagnostics +ok 72 - double fk and col schema test should pass +ok 73 - double fk and col schema test should have the proper description +ok 74 - double fk and col schema test should have the proper diagnostics +ok 75 - missing fk test should fail +ok 76 - missing fk test should have the proper description +ok 77 - missing fk test should have the proper diagnostics +ok 78 - bad FK column test should fail +ok 79 - bad FK column test should have the proper description +ok 80 - bad FK column test should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 02a7a0b75191..ebd3b98ee1aa 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -868,122 +868,122 @@ $$ LANGUAGE sql; -- _ckeys( schema, table, constraint_type ) CREATE OR REPLACE FUNCTION _ckeys ( TEXT, TEXT, CHAR ) RETURNS TEXT[] AS $$ - SELECT ARRAY (SELECT a.attname::text - FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a, - pg_catalog.pg_constraint x - WHERE n.oid = c.relnamespace - AND c.oid = a.attrelid - AND c.oid = x.conrelid - AND a.attnum = ANY(x.conkey) - AND n.nspname = $1 - AND c.relname = $2 - AND x.contype = $3 - AND a.attnum > 0 - AND NOT a.attisdropped - ORDER BY a.attnum + SELECT ARRAY ( + SELECT a.attname::text + 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_constraint x ON c.oid = x.conrelid AND a.attnum = ANY( x.conkey ) + WHERE n.nspname = $1 + AND c.relname = $2 + AND x.contype = $3 ); $$ LANGUAGE sql; -- _ckeys( table, constraint_type ) CREATE OR REPLACE FUNCTION _ckeys ( TEXT, CHAR ) RETURNS TEXT[] AS $$ - SELECT ARRAY (SELECT a.attname::text - FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, - pg_catalog.pg_constraint x - WHERE c.oid = a.attrelid - AND c.oid = x.conrelid - AND pg_table_is_visible(c.oid) - AND c.relname = $1 - AND x.contype = $2 - AND a.attnum = ANY(x.conkey) - AND a.attnum > 0 - AND NOT a.attisdropped - ORDER BY a.attnum + SELECT ARRAY ( + SELECT a.attname::text + FROM pg_catalog.pg_class c + JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid + JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid AND a.attnum = ANY( x.conkey ) + AND c.relname = $1 + AND x.contype = $2 ); $$ LANGUAGE sql; --- _pk_cols_for_fk( schema, table, cols[], constraint_type ) -CREATE OR REPLACE FUNCTION _pk_cols_for_fk ( TEXT, TEXT, TEXT[], CHAR ) -RETURNS TEXT[] AS $$ - SELECT ARRAY( SELECT col::text FROM ( - SELECT DISTINCT a2.attnum AS num, a2.attname AS col - FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, - pg_catalog.pg_attribute a, pg_catalog.pg_constraint x, - pg_catalog.pg_attribute a2 - WHERE n.oid = c.relnamespace - AND c.oid = a.attrelid - AND c.oid = x.conrelid - AND x.confrelid = a2.attrelid - AND a2.attnum = ANY(x.confkey) - AND a.attnum = ANY(x.conkey) - AND n.nspname = $1 - AND c.relname = $2 - AND a.attname = ANY($3) - AND x.contype = $4 - AND a.attnum > 0 - AND NOT a.attisdropped - ORDER BY a2.attnum - ) AS foo); -$$ LANGUAGE sql; - --- _pk_cols_for_fk( table, cols[], constraint_type ) -CREATE OR REPLACE FUNCTION _pk_cols_for_fk ( TEXT, TEXT[], CHAR ) -RETURNS TEXT[] AS $$ - SELECT ARRAY( SELECT col::text FROM ( - SELECT DISTINCT a2.attnum AS num, a2.attname AS col - FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, - pg_catalog.pg_constraint x, pg_catalog.pg_attribute a2 - WHERE c.oid = a.attrelid - AND c.oid = x.conrelid - AND x.confrelid = a2.attrelid - AND a2.attnum = ANY(x.confkey) - AND a.attnum = ANY(x.conkey) - AND c.relname = $1 - AND a.attname = ANY($2) - AND x.contype = $3 - AND a.attnum > 0 - AND NOT a.attisdropped - ORDER BY a2.attnum - ) AS foo); -$$ LANGUAGE sql; - --- _pk_tab_for_fk( schema, table, cols[], constraint_type ) -CREATE OR REPLACE FUNCTION _pk_tab_for_fk ( TEXT, TEXT, TEXT[], CHAR ) -RETURNS TEXT AS $$ - SELECT n2.nspname || '.' || c2.relname - FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, - pg_catalog.pg_attribute a, pg_catalog.pg_constraint x, - pg_catalog.pg_namespace n2, pg_catalog.pg_class c2 - WHERE n.oid = c.relnamespace - AND c.oid = a.attrelid - AND c.oid = x.conrelid - AND x.confrelid = c2.oid - AND c2.relnamespace = n2.oid - AND n.nspname = $1 - AND c.relname = $2 - AND a.attname = ANY($3) - AND x.contype = $4 - AND a.attnum > 0 - AND NOT a.attisdropped - LIMIT 1 -$$ LANGUAGE sql; - --- _pk_tab_for_fk( table, cols[], constraint_type ) -CREATE OR REPLACE FUNCTION _pk_tab_for_fk ( TEXT, TEXT[], CHAR ) -RETURNS TEXT AS $$ - SELECT c2.relname::text - FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, - pg_catalog.pg_constraint x, pg_catalog.pg_class c2 - WHERE c.oid = a.attrelid - AND c.oid = x.conrelid - AND x.confrelid = c2.oid - AND c.relname = $1 - AND a.attname = ANY($2) - AND x.contype = $3 - AND a.attnum > 0 - AND NOT a.attisdropped - LIMIT 1 -$$ LANGUAGE sql; +-- Borrowed from newsysviews: http://pgfoundry.org/projects/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, 32) s(i) ON (a.attnum = $2[i]) + WHERE attrelid = $1 + ORDER BY i + ) +$$ LANGUAGE SQL stable; + +-- Borrowed from newsysviews: http://pgfoundry.org/projects/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: http://pgfoundry.org/projects/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); -- col_is_pk( schema, table, column, description ) CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT, TEXT[], TEXT ) @@ -1184,35 +1184,61 @@ RETURNS TEXT AS $$ $$ LANGUAGE sql; -- fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description ) -CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[], TEXT ) +CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME[], NAME, NAME, NAME[], TEXT ) RETURNS TEXT AS $$ - SELECT is( - $1 || '.' || $2 || '(' || array_to_string( _ckeys( $1, $2, 'f' ), ', ' ) - || ') REFERENCES ' || - _pk_tab_for_fk( $1, $2, $3, 'f' ) || '(' || array_to_string( _pk_cols_for_fk( $1, $2, $3, 'f' ), ', ' ) || ')', +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 + $1 || '.' || $2 || '(' || array_to_string( $3, ', ' ) + || ') REFERENCES ' || COALESCE ( sch || '.' || tab || '(' || array_to_string( cols, ', ' ) || ')', 'NOTHING' ), + -- want $1 || '.' || $2 || '(' || array_to_string( $3, ', ' ) || ') REFERENCES ' || $4 || '.' || $5 || '(' || array_to_string( $6, ', ' ) || ')', $7 ); -$$ LANGUAGE sql; +END; +$$ LANGUAGE plpgsql; -- fk_ok( fk_table, fk_column[], pk_table, pk_column[], description ) -CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT[], TEXT, TEXT[], TEXT ) +CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME[], NAME, NAME[], TEXT ) RETURNS TEXT AS $$ - SELECT is( - $1 || '(' || array_to_string( _ckeys( $1, 'f' ), ', ' ) - || ') REFERENCES ' || - _pk_tab_for_fk( $1, $2, 'f' ) || '(' || array_to_string( _pk_cols_for_fk( $1, $2, 'f' ), ', ' ) || ')', +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 + INTO tab, cols; + + RETURN is( + -- have + $1 || '(' || array_to_string( $2, ', ' ) + || ') REFERENCES ' || COALESCE( tab || '(' || array_to_string( cols, ', ' ) || ')', 'NOTHING'), + -- want $1 || '(' || array_to_string( $2, ', ' ) || ') REFERENCES ' || $3 || '(' || array_to_string( $4, ', ' ) || ')', $5 ); -$$ LANGUAGE sql; +END; +$$ LANGUAGE plpgsql; -- fk_ok( fk_schema, fk_table, fk_column[], fk_schema, pk_table, pk_column[] ) -CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[] ) +CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME[], NAME, NAME, NAME[] ) RETURNS TEXT AS $$ SELECT fk_ok( $1, $2, $3, $4, $5, $6, $1 || '.' || $2 || '(' || array_to_string( $3, ', ' ) @@ -1222,7 +1248,7 @@ RETURNS TEXT AS $$ $$ LANGUAGE sql; -- fk_ok( fk_table, fk_column[], pk_table, pk_column[] ) -CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT[], TEXT, TEXT[] ) +CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME[], NAME, NAME[] ) RETURNS TEXT AS $$ SELECT fk_ok( $1, $2, $3, $4, $1 || '(' || array_to_string( $2, ', ' ) @@ -1232,25 +1258,25 @@ RETURNS TEXT AS $$ $$ LANGUAGE sql; -- fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column, description ) -CREATE OR REPLACE FUNCTION fk_ok ( TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT ) +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 ( TEXT, TEXT, TEXT, TEXT, TEXT, TEXT ) +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 ( TEXT, TEXT, TEXT, TEXT, TEXT ) +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 ( TEXT, TEXT, TEXT, TEXT ) +CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME, NAME ) RETURNS TEXT AS $$ SELECT fk_ok( $1, ARRAY[$2], $3, ARRAY[$4] ); $$ LANGUAGE sql; diff --git a/sql/fktap.sql b/sql/fktap.sql index 5bea135c3bc2..f5106abd26ff 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -31,8 +31,8 @@ BEGIN; -- ## SET search_path TO TAPSCHEMA,public; -- Set the test plan. ---SELECT plan(64); -select * from no_plan(); +SELECT plan(80); +--select * from no_plan(); -- These will be rolled back. :-) CREATE TABLE pk ( @@ -62,6 +62,7 @@ CREATE TABLE fk3( pk_id INT NOT NULL REFERENCES pk(id), pk2_num int NOT NULL, pk2_dot int NOT NULL, + foo_id INT NOT NULL, FOREIGN KEY(pk2_num, pk2_dot) REFERENCES pk2( num, dot) ); @@ -290,12 +291,13 @@ SELECT * FROM check_test( want: fk(pk_id) REFERENCES ok(fid)' ); +-- Try a table with multiple FKs. SELECT * FROM check_test( fk_ok( 'public', 'fk3', 'pk_id', 'public', 'pk', 'id' ), true, 'double fk schema test', 'public.fk3(pk_id) should reference public.pk(id)', - '' + '' ); SELECT * FROM check_test( @@ -303,7 +305,42 @@ SELECT * FROM check_test( true, 'double fk test', 'fk3(pk_id) should reference pk(id)', - '' + '' +); + +-- Try the second FK on that table, which happens to be a multicolumn FK. +SELECT * FROM check_test( + fk_ok( + 'public', 'fk3', ARRAY['pk2_num', 'pk2_dot'], + 'public', 'pk2', ARRAY['num', 'dot'] + ), + true, + 'double fk and col schema test', + 'public.fk3(pk2_num, pk2_dot) should reference public.pk2(num, dot)', + '' +); + +-- Try FK columns that reference nothing. +SELECT * FROM check_test( + fk_ok( 'public', 'fk3', 'id', 'public', 'foo', 'id' ), + false, + 'missing fk test', + 'public.fk3(id) should reference public.foo(id)', + ' have: public.fk3(id) REFERENCES NOTHING + want: public.fk3(id) REFERENCES public.foo(id)' +); + +-- Try non-existent FK colums. +SELECT * FROM check_test( + fk_ok( + 'fk3', ARRAY['pk2_blah', 'pk2_dot'], + 'pk2', ARRAY['num', 'dot'] + ), + false, + 'bad FK column test', + 'fk3(pk2_blah, pk2_dot) should reference pk2(num, dot)', + ' have: fk3(pk2_blah, pk2_dot) REFERENCES NOTHING + want: fk3(pk2_blah, pk2_dot) REFERENCES pk2(num, dot)' ); /****************************************************************************/ From ae3787f149a770c063f60c7aeffb6d92b46c0e0d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 15 Sep 2008 23:01:35 +0000 Subject: [PATCH 0117/1195] Fixed `col_is_fk()` to use the new view. Also improved the diagnostics on failure to just list the available FK columns, if there are any. --- expected/fktap.out | 120 +++++++++++++++++++++++---------------------- pgtap.sql.in | 80 ++++++++++++++++++++++++++---- sql/fktap.sql | 17 +++++-- 3 files changed, 145 insertions(+), 72 deletions(-) diff --git a/expected/fktap.out b/expected/fktap.out index ea50e48dd6c5..0ee2d171561c 100644 --- a/expected/fktap.out +++ b/expected/fktap.out @@ -1,5 +1,5 @@ \set ECHO -1..80 +1..82 ok 1 - has_fk( schema, table, description ) should pass ok 2 - has_fk( schema, table, description ) should have the proper description ok 3 - has_fk( table, description ) should pass @@ -22,61 +22,63 @@ ok 19 - col_is_fk( schema, table, column, description ) should have the proper d ok 20 - col_is_fk( table, column, description ) should fail ok 21 - col_is_fk( table, column, description ) should have the proper description ok 22 - col_is_fk( table, column, description ) should have the proper diagnostics -ok 23 - col_is_fk( schema, table, column[], description ) should pass -ok 24 - col_is_fk( schema, table, column[], description ) should have the proper description -ok 25 - col_is_fk( table, column[], description ) should pass -ok 26 - col_is_fk( table, column[], description ) should have the proper description -ok 27 - col_is_fk( table, column[] ) should pass -ok 28 - col_is_fk( table, column[] ) should have the proper description -ok 29 - full fk_ok array should pass -ok 30 - full fk_ok array should have the proper description -ok 31 - multiple fk fk_ok desc should pass -ok 32 - multiple fk fk_ok desc should have the proper description -ok 33 - fk_ok array desc should pass -ok 34 - fk_ok array desc should have the proper description -ok 35 - fk_ok array noschema desc should pass -ok 36 - fk_ok array noschema desc should have the proper description -ok 37 - multiple fk fk_ok noschema desc should pass -ok 38 - multiple fk fk_ok noschema desc should have the proper description -ok 39 - fk_ok array noschema should pass -ok 40 - fk_ok array noschema should have the proper description -ok 41 - basic fk_ok should pass -ok 42 - basic fk_ok should have the proper description -ok 43 - basic fk_ok desc should pass -ok 44 - basic fk_ok desc should have the proper description -ok 45 - basic fk_ok noschema should pass -ok 46 - basic fk_ok noschema should have the proper description -ok 47 - basic fk_ok noschema desc should pass -ok 48 - basic fk_ok noschema desc should have the proper description -ok 49 - basic fk_ok noschema desc should have the proper diagnostics -ok 50 - Test should pass -ok 51 - fk_ok fail should fail -ok 52 - fk_ok fail should have the proper description -ok 53 - fk_ok fail should have the proper diagnostics -ok 54 - fk_ok fail desc should fail -ok 55 - fk_ok fail desc should have the proper description -ok 56 - fk_ok fail desc should have the proper diagnostics -ok 57 - fk_ok fail no schema should fail -ok 58 - fk_ok fail no schema should have the proper description -ok 59 - fk_ok fail no schema should have the proper diagnostics -ok 60 - fk_ok fail no schema desc should fail -ok 61 - fk_ok fail no schema desc should have the proper description -ok 62 - fk_ok fail no schema desc should have the proper diagnostics -ok 63 - fk_ok bad PK test should fail -ok 64 - fk_ok bad PK test should have the proper description -ok 65 - fk_ok bad PK test should have the proper diagnostics -ok 66 - double fk schema test should pass -ok 67 - double fk schema test should have the proper description -ok 68 - double fk schema test should have the proper diagnostics -ok 69 - double fk test should pass -ok 70 - double fk test should have the proper description -ok 71 - double fk test should have the proper diagnostics -ok 72 - double fk and col schema test should pass -ok 73 - double fk and col schema test should have the proper description -ok 74 - double fk and col schema test should have the proper diagnostics -ok 75 - missing fk test should fail -ok 76 - missing fk test should have the proper description -ok 77 - missing fk test should have the proper diagnostics -ok 78 - bad FK column test should fail -ok 79 - bad FK column test should have the proper description -ok 80 - bad FK column test should have the proper diagnostics +ok 23 - multi-fk col_is_fk test should pass +ok 24 - multi-fk col_is_fk test should have the proper description +ok 25 - col_is_fk( schema, table, column[], description ) should pass +ok 26 - col_is_fk( schema, table, column[], description ) should have the proper description +ok 27 - col_is_fk( table, column[], description ) should pass +ok 28 - col_is_fk( table, column[], description ) should have the proper description +ok 29 - col_is_fk( table, column[] ) should pass +ok 30 - col_is_fk( table, column[] ) should have the proper description +ok 31 - full fk_ok array should pass +ok 32 - full fk_ok array should have the proper description +ok 33 - multiple fk fk_ok desc should pass +ok 34 - multiple fk fk_ok desc should have the proper description +ok 35 - fk_ok array desc should pass +ok 36 - fk_ok array desc should have the proper description +ok 37 - fk_ok array noschema desc should pass +ok 38 - fk_ok array noschema desc should have the proper description +ok 39 - multiple fk fk_ok noschema desc should pass +ok 40 - multiple fk fk_ok noschema desc should have the proper description +ok 41 - fk_ok array noschema should pass +ok 42 - fk_ok array noschema should have the proper description +ok 43 - basic fk_ok should pass +ok 44 - basic fk_ok should have the proper description +ok 45 - basic fk_ok desc should pass +ok 46 - basic fk_ok desc should have the proper description +ok 47 - basic fk_ok noschema should pass +ok 48 - basic fk_ok noschema should have the proper description +ok 49 - basic fk_ok noschema desc should pass +ok 50 - basic fk_ok noschema desc should have the proper description +ok 51 - basic fk_ok noschema desc should have the proper diagnostics +ok 52 - Test should pass +ok 53 - fk_ok fail should fail +ok 54 - fk_ok fail should have the proper description +ok 55 - fk_ok fail should have the proper diagnostics +ok 56 - fk_ok fail desc should fail +ok 57 - fk_ok fail desc should have the proper description +ok 58 - fk_ok fail desc should have the proper diagnostics +ok 59 - fk_ok fail no schema should fail +ok 60 - fk_ok fail no schema should have the proper description +ok 61 - fk_ok fail no schema should have the proper diagnostics +ok 62 - fk_ok fail no schema desc should fail +ok 63 - fk_ok fail no schema desc should have the proper description +ok 64 - fk_ok fail no schema desc should have the proper diagnostics +ok 65 - fk_ok bad PK test should fail +ok 66 - fk_ok bad PK test should have the proper description +ok 67 - fk_ok bad PK test should have the proper diagnostics +ok 68 - double fk schema test should pass +ok 69 - double fk schema test should have the proper description +ok 70 - double fk schema test should have the proper diagnostics +ok 71 - double fk test should pass +ok 72 - double fk test should have the proper description +ok 73 - double fk test should have the proper diagnostics +ok 74 - double fk and col schema test should pass +ok 75 - double fk and col schema test should have the proper description +ok 76 - double fk and col schema test should have the proper diagnostics +ok 77 - missing fk test should fail +ok 78 - missing fk test should have the proper description +ok 79 - missing fk test should have the proper diagnostics +ok 80 - bad FK column test should fail +ok 81 - bad FK column test should have the proper description +ok 82 - bad FK column test should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index ebd3b98ee1aa..0fe8c1ac5757 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1040,37 +1040,97 @@ RETURNS TEXT AS $$ $$ LANGUAGE sql; -- col_is_fk( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT, TEXT[], TEXT ) +CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME, NAME[], TEXT ) RETURNS TEXT AS $$ - SELECT is( _ckeys( $1, $2, 'f' ), $3, $4 ); -$$ LANGUAGE sql; +DECLARE + names text[][]; +BEGIN + PERFORM TRUE + FROM pg_all_foreign_keys + WHERE fk_schema_name = $1 + AND fk_table_name = $2 + AND fk_columns = $3; + IF FOUND THEN + RETURN pass( $4 ); + END IF; + + -- Try to show the columns. + SELECT ARRAY( + SELECT fk_columns::text + FROM pg_all_foreign_keys + WHERE fk_schema_name = $1 + AND fk_table_name = $2 + ) INTO names; + + IF FOUND THEN + RETURN fail($4) || E'\n' || diag( + ' Table ' || $1 || '.' || $2 || E' has these foreign key columns:\n ' + || array_to_string( names, E'\n ' ) + ); + END IF; + + -- No FKs in this table. + RETURN fail($4) || E'\n' || diag( + ' No foreign key columns exist in table ' || $1 || '.' || $2 + ); +END; +$$ LANGUAGE plpgsql; -- col_is_fk( table, column, description ) -CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT[], TEXT ) +CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ - SELECT is( _ckeys( $1, 'f' ), $2, $3 ); -$$ LANGUAGE sql; +DECLARE + names text[][]; +BEGIN + PERFORM TRUE + FROM pg_all_foreign_keys + WHERE fk_table_name = $1 + AND fk_columns = $2; + IF FOUND THEN + RETURN pass( $3 ); + END IF; + + -- Try to show the columns. + SELECT ARRAY( + SELECT fk_columns::text + FROM pg_all_foreign_keys + WHERE fk_table_name = $1 + ) INTO names; + + IF FOUND THEN + RETURN fail($3) || E'\n' || diag( + ' Table ' || $1 || E' has these foreign key columns:\n ' + || array_to_string( names, E'\n ' ) + ); + END IF; + + -- No FKs in this table. + RETURN fail($3) || E'\n' || diag( + ' No foreign key columns exist in table ' || $1 || + ); +END; +$$ LANGUAGE plpgsql; -- col_is_fk( table, column[] ) -CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT[] ) +CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME[] ) RETURNS TEXT AS $$ SELECT col_is_fk( $1, $2, 'Columns ' || $1 || '.' || $2::text || ' should be a foreign key' ); $$ LANGUAGE sql; -- col_is_fk( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_fk ( TEXT, TEXT, TEXT, TEXT ) +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 ( TEXT, TEXT, TEXT ) +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 ( TEXT, TEXT ) +CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME ) RETURNS TEXT AS $$ SELECT col_is_fk( $1, $2, 'Column ' || $1 || '.' || $2 || ' should be a foreign key' ); $$ LANGUAGE sql; diff --git a/sql/fktap.sql b/sql/fktap.sql index f5106abd26ff..2f8ebb91aa88 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -31,7 +31,7 @@ BEGIN; -- ## SET search_path TO TAPSCHEMA,public; -- Set the test plan. -SELECT plan(80); +SELECT plan(82); --select * from no_plan(); -- These will be rolled back. :-) @@ -132,7 +132,8 @@ SELECT * FROM check_test( false, 'col_is_fk( schema, table, column, description )', 'public.fk.name should be an fk', - E' have: {pk_id}\n want: {name}' + ' Table public.fk has these foreign key columns: + {pk_id}' ); SELECT * FROM check_test( @@ -140,9 +141,19 @@ SELECT * FROM check_test( false, 'col_is_fk( table, column, description )', 'fk.name should be an fk', - E' have: {pk_id}\n want: {name}' + ' Table fk has these foreign key columns: + {pk_id}' ); +-- Check table with multiple FKs. +SELECT * FROM check_test( + col_is_fk( 'fk3', 'pk_id' ), + true, + 'multi-fk col_is_fk test', + 'Column fk3.pk_id should be a foreign key' +); + + /****************************************************************************/ -- Test col_is_fk() with an array of columns. From b3074f1ba15b12563eac5f7c7eabc02b3c932288 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 15 Sep 2008 23:09:48 +0000 Subject: [PATCH 0118/1195] Added tests for `col_is_fk()` when there are no FK columns in the target table. Tweaked the diagnostics there, too. --- expected/fktap.out | 124 ++++++++++++++++++++++++--------------------- pgtap.sql.in | 8 +-- sql/fktap.sql | 21 +++++++- 3 files changed, 88 insertions(+), 65 deletions(-) diff --git a/expected/fktap.out b/expected/fktap.out index 0ee2d171561c..aeecbea54fc1 100644 --- a/expected/fktap.out +++ b/expected/fktap.out @@ -1,5 +1,5 @@ \set ECHO -1..82 +1..88 ok 1 - has_fk( schema, table, description ) should pass ok 2 - has_fk( schema, table, description ) should have the proper description ok 3 - has_fk( table, description ) should pass @@ -24,61 +24,67 @@ ok 21 - col_is_fk( table, column, description ) should have the proper descripti ok 22 - col_is_fk( table, column, description ) should have the proper diagnostics ok 23 - multi-fk col_is_fk test should pass ok 24 - multi-fk col_is_fk test should have the proper description -ok 25 - col_is_fk( schema, table, column[], description ) should pass -ok 26 - col_is_fk( schema, table, column[], description ) should have the proper description -ok 27 - col_is_fk( table, column[], description ) should pass -ok 28 - col_is_fk( table, column[], description ) should have the proper description -ok 29 - col_is_fk( table, column[] ) should pass -ok 30 - col_is_fk( table, column[] ) should have the proper description -ok 31 - full fk_ok array should pass -ok 32 - full fk_ok array should have the proper description -ok 33 - multiple fk fk_ok desc should pass -ok 34 - multiple fk fk_ok desc should have the proper description -ok 35 - fk_ok array desc should pass -ok 36 - fk_ok array desc should have the proper description -ok 37 - fk_ok array noschema desc should pass -ok 38 - fk_ok array noschema desc should have the proper description -ok 39 - multiple fk fk_ok noschema desc should pass -ok 40 - multiple fk fk_ok noschema desc should have the proper description -ok 41 - fk_ok array noschema should pass -ok 42 - fk_ok array noschema should have the proper description -ok 43 - basic fk_ok should pass -ok 44 - basic fk_ok should have the proper description -ok 45 - basic fk_ok desc should pass -ok 46 - basic fk_ok desc should have the proper description -ok 47 - basic fk_ok noschema should pass -ok 48 - basic fk_ok noschema should have the proper description -ok 49 - basic fk_ok noschema desc should pass -ok 50 - basic fk_ok noschema desc should have the proper description -ok 51 - basic fk_ok noschema desc should have the proper diagnostics -ok 52 - Test should pass -ok 53 - fk_ok fail should fail -ok 54 - fk_ok fail should have the proper description -ok 55 - fk_ok fail should have the proper diagnostics -ok 56 - fk_ok fail desc should fail -ok 57 - fk_ok fail desc should have the proper description -ok 58 - fk_ok fail desc should have the proper diagnostics -ok 59 - fk_ok fail no schema should fail -ok 60 - fk_ok fail no schema should have the proper description -ok 61 - fk_ok fail no schema should have the proper diagnostics -ok 62 - fk_ok fail no schema desc should fail -ok 63 - fk_ok fail no schema desc should have the proper description -ok 64 - fk_ok fail no schema desc should have the proper diagnostics -ok 65 - fk_ok bad PK test should fail -ok 66 - fk_ok bad PK test should have the proper description -ok 67 - fk_ok bad PK test should have the proper diagnostics -ok 68 - double fk schema test should pass -ok 69 - double fk schema test should have the proper description -ok 70 - double fk schema test should have the proper diagnostics -ok 71 - double fk test should pass -ok 72 - double fk test should have the proper description -ok 73 - double fk test should have the proper diagnostics -ok 74 - double fk and col schema test should pass -ok 75 - double fk and col schema test should have the proper description -ok 76 - double fk and col schema test should have the proper diagnostics -ok 77 - missing fk test should fail -ok 78 - missing fk test should have the proper description -ok 79 - missing fk test should have the proper diagnostics -ok 80 - bad FK column test should fail -ok 81 - bad FK column test should have the proper description -ok 82 - bad FK column test should have the proper diagnostics +ok 25 - col_is_fk with no FKs should fail +ok 26 - col_is_fk with no FKs should have the proper description +ok 27 - col_is_fk with no FKs should have the proper diagnostics +ok 28 - col_is_fk with no FKs should fail +ok 29 - col_is_fk with no FKs should have the proper description +ok 30 - col_is_fk with no FKs should have the proper diagnostics +ok 31 - col_is_fk( schema, table, column[], description ) should pass +ok 32 - col_is_fk( schema, table, column[], description ) should have the proper description +ok 33 - col_is_fk( table, column[], description ) should pass +ok 34 - col_is_fk( table, column[], description ) should have the proper description +ok 35 - col_is_fk( table, column[] ) should pass +ok 36 - col_is_fk( table, column[] ) should have the proper description +ok 37 - full fk_ok array should pass +ok 38 - full fk_ok array should have the proper description +ok 39 - multiple fk fk_ok desc should pass +ok 40 - multiple fk fk_ok desc should have the proper description +ok 41 - fk_ok array desc should pass +ok 42 - fk_ok array desc should have the proper description +ok 43 - fk_ok array noschema desc should pass +ok 44 - fk_ok array noschema desc should have the proper description +ok 45 - multiple fk fk_ok noschema desc should pass +ok 46 - multiple fk fk_ok noschema desc should have the proper description +ok 47 - fk_ok array noschema should pass +ok 48 - fk_ok array noschema should have the proper description +ok 49 - basic fk_ok should pass +ok 50 - basic fk_ok should have the proper description +ok 51 - basic fk_ok desc should pass +ok 52 - basic fk_ok desc should have the proper description +ok 53 - basic fk_ok noschema should pass +ok 54 - basic fk_ok noschema should have the proper description +ok 55 - basic fk_ok noschema desc should pass +ok 56 - basic fk_ok noschema desc should have the proper description +ok 57 - basic fk_ok noschema desc should have the proper diagnostics +ok 58 - Test should pass +ok 59 - fk_ok fail should fail +ok 60 - fk_ok fail should have the proper description +ok 61 - fk_ok fail should have the proper diagnostics +ok 62 - fk_ok fail desc should fail +ok 63 - fk_ok fail desc should have the proper description +ok 64 - fk_ok fail desc should have the proper diagnostics +ok 65 - fk_ok fail no schema should fail +ok 66 - fk_ok fail no schema should have the proper description +ok 67 - fk_ok fail no schema should have the proper diagnostics +ok 68 - fk_ok fail no schema desc should fail +ok 69 - fk_ok fail no schema desc should have the proper description +ok 70 - fk_ok fail no schema desc should have the proper diagnostics +ok 71 - fk_ok bad PK test should fail +ok 72 - fk_ok bad PK test should have the proper description +ok 73 - fk_ok bad PK test should have the proper diagnostics +ok 74 - double fk schema test should pass +ok 75 - double fk schema test should have the proper description +ok 76 - double fk schema test should have the proper diagnostics +ok 77 - double fk test should pass +ok 78 - double fk test should have the proper description +ok 79 - double fk test should have the proper diagnostics +ok 80 - double fk and col schema test should pass +ok 81 - double fk and col schema test should have the proper description +ok 82 - double fk and col schema test should have the proper diagnostics +ok 83 - missing fk test should fail +ok 84 - missing fk test should have the proper description +ok 85 - missing fk test should have the proper diagnostics +ok 86 - bad FK column test should fail +ok 87 - bad FK column test should have the proper description +ok 88 - bad FK column test should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 0fe8c1ac5757..0767539eba9f 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1062,7 +1062,7 @@ BEGIN AND fk_table_name = $2 ) INTO names; - IF FOUND THEN + IF NAMES[1] IS NOT NULL THEN RETURN fail($4) || E'\n' || diag( ' Table ' || $1 || '.' || $2 || E' has these foreign key columns:\n ' || array_to_string( names, E'\n ' ) @@ -1071,7 +1071,7 @@ BEGIN -- No FKs in this table. RETURN fail($4) || E'\n' || diag( - ' No foreign key columns exist in table ' || $1 || '.' || $2 + ' Table ' || $1 || '.' || $2 || ' has no foreign key columns' ); END; $$ LANGUAGE plpgsql; @@ -1097,7 +1097,7 @@ BEGIN WHERE fk_table_name = $1 ) INTO names; - IF FOUND THEN + IF NAMES[1] IS NOT NULL THEN RETURN fail($3) || E'\n' || diag( ' Table ' || $1 || E' has these foreign key columns:\n ' || array_to_string( names, E'\n ' ) @@ -1106,7 +1106,7 @@ BEGIN -- No FKs in this table. RETURN fail($3) || E'\n' || diag( - ' No foreign key columns exist in table ' || $1 || + ' Table ' || $1 || ' has no foreign key columns' ); END; $$ LANGUAGE plpgsql; diff --git a/sql/fktap.sql b/sql/fktap.sql index 2f8ebb91aa88..0cd11c3c0623 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -31,8 +31,8 @@ BEGIN; -- ## SET search_path TO TAPSCHEMA,public; -- Set the test plan. -SELECT plan(82); ---select * from no_plan(); +SELECT plan(88); +--SELECT * from no_plan(); -- These will be rolled back. :-) CREATE TABLE pk ( @@ -153,6 +153,23 @@ SELECT * FROM check_test( 'Column fk3.pk_id should be a foreign key' ); +-- Check failure for table with no FKs. +SELECT * FROM check_test( + col_is_fk( 'public', 'pk', 'name', 'pk.name should be an fk' ), + false, + 'col_is_fk with no FKs', + 'pk.name should be an fk', + ' Table public.pk has no foreign key columns' +); + +SELECT * FROM check_test( + col_is_fk( 'pk', 'name' ), + false, + 'col_is_fk with no FKs', + 'Column pk.name should be a foreign key', + ' Table pk has no foreign key columns' +); + /****************************************************************************/ -- Test col_is_fk() with an array of columns. From 45d425d5f30df614997feedc0e90403d59fc291c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 15 Sep 2008 23:21:28 +0000 Subject: [PATCH 0119/1195] Switched schema testing functions to use `name` parameters instead of `text` where appropriate. --- pgtap.sql.in | 116 +++++++++++++++++++++++++-------------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 0767539eba9f..c87fd7e86735 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -518,7 +518,7 @@ RETURNS TEXT AS $$ $$ LANGUAGE SQL; -- has_table( schema, table, description ) -CREATE OR REPLACE FUNCTION has_table ( TEXT, TEXT, TEXT ) +CREATE OR REPLACE FUNCTION has_table ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ SELECT ok( EXISTS( @@ -533,7 +533,7 @@ RETURNS TEXT AS $$ $$ LANGUAGE SQL; -- has_table( table, description ) -CREATE OR REPLACE FUNCTION has_table ( TEXT, TEXT ) +CREATE OR REPLACE FUNCTION has_table ( NAME, TEXT ) RETURNS TEXT AS $$ SELECT ok( EXISTS( @@ -547,13 +547,13 @@ RETURNS TEXT AS $$ $$ LANGUAGE SQL; -- has_table( table ) -CREATE OR REPLACE FUNCTION has_table ( TEXT ) +CREATE OR REPLACE FUNCTION has_table ( NAME ) RETURNS TEXT AS $$ SELECT has_table( $1, 'Table ' || $1 || ' should exist' ); $$ LANGUAGE SQL; -- has_view( schema, view, description ) -CREATE OR REPLACE FUNCTION has_view ( TEXT, TEXT, TEXT ) +CREATE OR REPLACE FUNCTION has_view ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ SELECT ok( EXISTS( @@ -568,7 +568,7 @@ RETURNS TEXT AS $$ $$ LANGUAGE SQL; -- has_view( view, description ) -CREATE OR REPLACE FUNCTION has_view ( TEXT, TEXT ) +CREATE OR REPLACE FUNCTION has_view ( NAME, TEXT ) RETURNS TEXT AS $$ SELECT ok( EXISTS( @@ -582,13 +582,13 @@ RETURNS TEXT AS $$ $$ LANGUAGE SQL; -- has_view( view ) -CREATE OR REPLACE FUNCTION has_view ( TEXT ) +CREATE OR REPLACE FUNCTION has_view ( NAME ) RETURNS TEXT AS $$ SELECT has_view( $1, 'View ' || $1 || ' should exist' ); $$ LANGUAGE SQL; -- has_column( schema, table, column, description ) -CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT, TEXT, TEXT ) +CREATE OR REPLACE FUNCTION has_column ( NAME, NAME, NAME, TEXT ) RETURNS TEXT AS $$ SELECT ok( EXISTS( @@ -606,7 +606,7 @@ RETURNS TEXT AS $$ $$ LANGUAGE SQL; -- has_column( table, column, description ) -CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT, TEXT ) +CREATE OR REPLACE FUNCTION has_column ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ SELECT ok( EXISTS( @@ -623,13 +623,13 @@ RETURNS TEXT AS $$ $$ LANGUAGE SQL; -- has_column( table, column ) -CREATE OR REPLACE FUNCTION has_column ( TEXT, TEXT ) +CREATE OR REPLACE FUNCTION has_column ( NAME, NAME ) RETURNS TEXT AS $$ SELECT has_column( $1, $2, 'Column ' || $1 || '.' || $2 || ' should exist' ); $$ LANGUAGE SQL; -- _col_is_null( schema, table, column, desc, null ) -CREATE OR REPLACE FUNCTION _col_is_null ( TEXT, TEXT, TEXT, TEXT, bool ) +CREATE OR REPLACE FUNCTION _col_is_null ( NAME, NAME, NAME, TEXT, bool ) RETURNS TEXT AS $$ SELECT ok( EXISTS( @@ -648,7 +648,7 @@ RETURNS TEXT AS $$ $$ LANGUAGE SQL; -- _col_is_null( table, column, desc, null ) -CREATE OR REPLACE FUNCTION _col_is_null ( TEXT, TEXT, TEXT, bool ) +CREATE OR REPLACE FUNCTION _col_is_null ( NAME, NAME, TEXT, bool ) RETURNS TEXT AS $$ SELECT ok( EXISTS( @@ -666,43 +666,43 @@ RETURNS TEXT AS $$ $$ LANGUAGE SQL; -- col_not_null( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT, TEXT, TEXT ) +CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, NAME, TEXT ) RETURNS TEXT AS $$ SELECT _col_is_null( $1, $2, $3, $4, true ); $$ LANGUAGE SQL; -- col_not_null( table, column, description ) -CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT, TEXT ) +CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ SELECT _col_is_null( $1, $2, $3, true ); $$ LANGUAGE SQL; -- col_not_null( table, column ) -CREATE OR REPLACE FUNCTION col_not_null ( TEXT, TEXT ) +CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME ) RETURNS TEXT AS $$ SELECT _col_is_null( $1, $2, 'Column ' || $1 || '.' || $2 || ' should be NOT NULL', true ); $$ LANGUAGE SQL; -- col_is_null( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT, TEXT, TEXT ) +CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME, TEXT ) RETURNS TEXT AS $$ SELECT _col_is_null( $1, $2, $3, $4, false ); $$ LANGUAGE SQL; -- col_is_null( schema, table, column ) -CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT, TEXT ) +CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME ) RETURNS TEXT AS $$ SELECT _col_is_null( $1, $2, $3, false ); $$ LANGUAGE SQL; -- col_is_null( table, column ) -CREATE OR REPLACE FUNCTION col_is_null ( TEXT, TEXT ) +CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME ) RETURNS TEXT AS $$ SELECT _col_is_null( $1, $2, 'Column ' || $1 || '.' || $2 || ' should allow NULL', false ); $$ LANGUAGE SQL; -- col_type_is( schema, table, column, type, description ) -CREATE OR REPLACE FUNCTION col_type_is ( TEXT, TEXT, TEXT, TEXT, TEXT ) +CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, NAME, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE actual_type TEXT; @@ -743,13 +743,13 @@ END; $$ LANGUAGE plpgsql; -- col_type_is( table, column, type, description ) -CREATE OR REPLACE FUNCTION col_type_is ( TEXT, TEXT, TEXT, TEXT ) +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 ( TEXT, TEXT, TEXT ) +CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ SELECT col_type_is( $1, $2, $3, 'Column ' || $1 || '.' || $2 || ' should be type ' || $3 ); $$ LANGUAGE SQL; @@ -770,7 +770,7 @@ END; $$ LANGUAGE plpgsql; -- col_default_is( schema, table, column, default, description ) -CREATE OR REPLACE FUNCTION col_default_is ( TEXT, TEXT, TEXT, anyelement, TEXT ) +CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, NAME, anyelement, TEXT ) RETURNS TEXT AS $$ SELECT _def_is(( SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid) @@ -790,7 +790,7 @@ RETURNS TEXT AS $$ $$ LANGUAGE sql; -- col_default_is( table, column, default, description ) -CREATE OR REPLACE FUNCTION col_default_is ( TEXT, TEXT, anyelement, TEXT ) +CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, anyelement, TEXT ) RETURNS TEXT AS $$ SELECT _def_is(( SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid) @@ -808,7 +808,7 @@ RETURNS TEXT AS $$ $$ LANGUAGE sql; -- col_default_is( table, column, default ) -CREATE OR REPLACE FUNCTION col_default_is ( TEXT, TEXT, TEXT ) +CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ SELECT col_default_is( $1, $2, $3, @@ -817,7 +817,7 @@ RETURNS TEXT AS $$ $$ LANGUAGE sql; -- _hasc( schema, table, constraint_type ) -CREATE OR REPLACE FUNCTION _hasc ( TEXT, TEXT, CHAR ) +CREATE OR REPLACE FUNCTION _hasc ( NAME, NAME, CHAR ) RETURNS BOOLEAN AS $$ SELECT EXISTS( SELECT true @@ -834,7 +834,7 @@ RETURNS BOOLEAN AS $$ $$ LANGUAGE sql; -- _hasc( table, constraint_type ) -CREATE OR REPLACE FUNCTION _hasc ( TEXT, CHAR ) +CREATE OR REPLACE FUNCTION _hasc ( NAME, CHAR ) RETURNS BOOLEAN AS $$ SELECT EXISTS( SELECT true @@ -848,28 +848,28 @@ RETURNS BOOLEAN AS $$ $$ LANGUAGE sql; -- has_pk( schema, table, description ) -CREATE OR REPLACE FUNCTION has_pk ( TEXT, TEXT, TEXT ) +CREATE OR REPLACE FUNCTION has_pk ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ SELECT ok( _hasc( $1, $2, 'p' ), $3 ); $$ LANGUAGE sql; -- has_pk( table, description ) -CREATE OR REPLACE FUNCTION has_pk ( TEXT, TEXT ) +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 ( TEXT ) +CREATE OR REPLACE FUNCTION has_pk ( NAME ) RETURNS TEXT AS $$ SELECT has_pk( $1, 'Table ' || $1 || ' should have a primary key' ); $$ LANGUAGE sql; -- _ckeys( schema, table, constraint_type ) -CREATE OR REPLACE FUNCTION _ckeys ( TEXT, TEXT, CHAR ) -RETURNS TEXT[] AS $$ +CREATE OR REPLACE FUNCTION _ckeys ( NAME, NAME, CHAR ) +RETURNS NAME[] AS $$ SELECT ARRAY ( - SELECT a.attname::text + 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 @@ -881,10 +881,10 @@ RETURNS TEXT[] AS $$ $$ LANGUAGE sql; -- _ckeys( table, constraint_type ) -CREATE OR REPLACE FUNCTION _ckeys ( TEXT, CHAR ) -RETURNS TEXT[] AS $$ +CREATE OR REPLACE FUNCTION _ckeys ( NAME, CHAR ) +RETURNS NAME[] AS $$ SELECT ARRAY ( - SELECT a.attname::text + SELECT a.attname FROM pg_catalog.pg_class c JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid AND a.attnum = ANY( x.conkey ) @@ -986,55 +986,55 @@ CREATE OR REPLACE VIEW pg_all_foreign_keys AS AND _pg_sv_table_accessible(n1.oid, c1.oid); -- col_is_pk( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT, TEXT[], TEXT ) +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( table, column, description ) -CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT[], TEXT ) +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 ( TEXT, TEXT[] ) +CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME[] ) RETURNS TEXT AS $$ SELECT col_is_pk( $1, $2, 'Columns ' || $1 || '.' || $2::text || ' should be a primary key' ); $$ LANGUAGE sql; -- col_is_pk( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT, TEXT, TEXT ) +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( table, column, description ) -CREATE OR REPLACE FUNCTION col_is_pk ( TEXT, TEXT, TEXT ) +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 ( TEXT, TEXT ) +CREATE OR REPLACE FUNCTION col_is_pk ( NAME, NAME ) RETURNS TEXT AS $$ SELECT col_is_pk( $1, $2, 'Column ' || $1 || '.' || $2 || ' should be a primary key' ); $$ LANGUAGE sql; -- has_fk( schema, table, description ) -CREATE OR REPLACE FUNCTION has_fk ( TEXT, TEXT, TEXT ) +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 ( TEXT, TEXT ) +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 ( TEXT ) +CREATE OR REPLACE FUNCTION has_fk ( NAME ) RETURNS TEXT AS $$ SELECT has_fk( $1, 'Table ' || $1 || ' should have a foreign key constraint' ); $$ LANGUAGE sql; @@ -1154,91 +1154,91 @@ RETURNS TEXT AS $$ $$ LANGUAGE sql; -- col_is_unique( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT, TEXT[], TEXT ) +CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME, NAME[], TEXT ) RETURNS TEXT AS $$ SELECT is( _ckeys( $1, $2, 'u' ), $3, $4 ); $$ LANGUAGE sql; -- col_is_unique( table, column, description ) -CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT[], TEXT ) +CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ SELECT is( _ckeys( $1, 'u' ), $2, $3 ); $$ LANGUAGE sql; -- col_is_unique( table, column[] ) -CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT[] ) +CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME[] ) RETURNS TEXT AS $$ SELECT col_is_unique( $1, $2, 'Columns ' || $1 || '.' || $2::text || ' should have a unique constraint' ); $$ LANGUAGE sql; -- col_is_unique( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_unique ( TEXT, TEXT, TEXT, TEXT ) +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 ( TEXT, TEXT, TEXT ) +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 ( TEXT, TEXT ) +CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME ) RETURNS TEXT AS $$ SELECT col_is_unique( $1, $2, 'Column ' || $1 || '.' || $2 || ' should have a unique constraint' ); $$ LANGUAGE sql; -- has_check( schema, table, description ) -CREATE OR REPLACE FUNCTION has_check ( TEXT, TEXT, TEXT ) +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 ( TEXT, TEXT ) +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 ( TEXT ) +CREATE OR REPLACE FUNCTION has_check ( NAME ) RETURNS TEXT AS $$ SELECT has_check( $1, 'Table ' || $1 || ' should have a check constraint' ); $$ LANGUAGE sql; -- col_has_check( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT, TEXT[], TEXT ) +CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME, NAME[], TEXT ) RETURNS TEXT AS $$ SELECT is( _ckeys( $1, $2, 'c' ), $3, $4 ); $$ LANGUAGE sql; -- col_has_check( table, column, description ) -CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT[], TEXT ) +CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ SELECT is( _ckeys( $1, 'c' ), $2, $3 ); $$ LANGUAGE sql; -- col_has_check( table, column[] ) -CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT[] ) +CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME[] ) RETURNS TEXT AS $$ SELECT col_has_check( $1, $2, 'Columns ' || $1 || '.' || $2::text || ' should have a check constraint' ); $$ LANGUAGE sql; -- col_has_check( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_has_check ( TEXT, TEXT, TEXT, TEXT ) +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 ( TEXT, TEXT, TEXT ) +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 ( TEXT, TEXT ) +CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME ) RETURNS TEXT AS $$ SELECT col_has_check( $1, $2, 'Column ' || $1 || '.' || $2 || ' should have a check constraint' ); $$ LANGUAGE sql; From c58694fdfbb9b45be7bc856277bff010730b5dd6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 15 Sep 2008 23:29:19 +0000 Subject: [PATCH 0120/1195] * Updated documentation on the diagnostics from `col_is_fk()`. * Tweaked said diagnostic message to be a bit more accurate. --- README.pgtap | 8 +++++++- pgtap.sql.in | 4 ++-- sql/fktap.sql | 11 ++++++----- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/README.pgtap b/README.pgtap index 0c6816089d06..9b04271b0177 100644 --- a/README.pgtap +++ b/README.pgtap @@ -775,7 +775,13 @@ Will produce something like this: ); Just like `col_is_fk()`, except that it test that the column or array of -columns are a primary key. +columns are a primary key. The diagnostics on failure are a bit different, +too. Since the table might have more than one foreign key, the diagnostics +simply list all of the foreign key constraint columns, like so: + + # Table widget has foreign key constraints on these columns: + # {pk_id} + # {pk2_num,pk2_dot} ### fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description ) ### ### fk_ok( fk_schema, fk_table, fk_column[], fk_schema, pk_table, pk_column[] ) ### diff --git a/pgtap.sql.in b/pgtap.sql.in index c87fd7e86735..59509894c641 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1064,7 +1064,7 @@ BEGIN IF NAMES[1] IS NOT NULL THEN RETURN fail($4) || E'\n' || diag( - ' Table ' || $1 || '.' || $2 || E' has these foreign key columns:\n ' + ' Table ' || $1 || '.' || $2 || E' has foreign key constraints on these columns:\n ' || array_to_string( names, E'\n ' ) ); END IF; @@ -1099,7 +1099,7 @@ BEGIN IF NAMES[1] IS NOT NULL THEN RETURN fail($3) || E'\n' || diag( - ' Table ' || $1 || E' has these foreign key columns:\n ' + ' Table ' || $1 || E' has foreign key constraints on these columns:\n ' || array_to_string( names, E'\n ' ) ); END IF; diff --git a/sql/fktap.sql b/sql/fktap.sql index 0cd11c3c0623..04ba4c99d82e 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -132,17 +132,18 @@ SELECT * FROM check_test( false, 'col_is_fk( schema, table, column, description )', 'public.fk.name should be an fk', - ' Table public.fk has these foreign key columns: + ' Table public.fk has foreign key constraints on these columns: {pk_id}' ); SELECT * FROM check_test( - col_is_fk( 'fk', 'name', 'fk.name should be an fk' ), + col_is_fk( 'fk3', 'name', 'fk3.name should be an fk' ), false, 'col_is_fk( table, column, description )', - 'fk.name should be an fk', - ' Table fk has these foreign key columns: - {pk_id}' + 'fk3.name should be an fk', + ' Table fk3 has foreign key constraints on these columns: + {pk_id} + {pk2_num,pk2_dot}' ); -- Check table with multiple FKs. From 2f857d252a6df0dd7a4d3eccff081917b4cd8cff Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 15 Sep 2008 23:32:13 +0000 Subject: [PATCH 0121/1195] Better example. --- README.pgtap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.pgtap b/README.pgtap index 9b04271b0177..29faeb191bea 100644 --- a/README.pgtap +++ b/README.pgtap @@ -780,8 +780,8 @@ too. Since the table might have more than one foreign key, the diagnostics simply list all of the foreign key constraint columns, like so: # Table widget has foreign key constraints on these columns: - # {pk_id} - # {pk2_num,pk2_dot} + # {thingy_id} + # {surname,given_name} ### fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description ) ### ### fk_ok( fk_schema, fk_table, fk_column[], fk_schema, pk_table, pk_column[] ) ### From 1afcf1a8da35ca1007a8e9ddb4e934f891ffa665 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 15 Sep 2008 23:33:24 +0000 Subject: [PATCH 0122/1195] Let's be consistent, shall we? --- README.pgtap | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.pgtap b/README.pgtap index 29faeb191bea..765e08ecd408 100644 --- a/README.pgtap +++ b/README.pgtap @@ -733,7 +733,7 @@ table in question does not exist. SELECT col_is_pk( 'persons', - ARRAY['first', 'last'], + ARRAY['given_name', 'surname'], ); Tests whether the specified column or columns in a table is/are the primary @@ -771,7 +771,7 @@ Will produce something like this: SELECT col_is_fk( 'contacts', - ARRAY['first', 'last'], + ARRAY['given_name', 'surname'], ); Just like `col_is_fk()`, except that it test that the column or array of @@ -804,9 +804,9 @@ simply list all of the foreign key constraint columns, like so: SELECT fk_ok( 'contacts', - ARRAY['person_first', 'person_last'], + ARRAY['person_given_name', 'person_surname'], 'persons', - ARRAY['first', 'last'], + ARRAY['given_name', 'surname'], ); This function combines `col_is_fk()` and `col_is_pk()` into a single test that @@ -868,7 +868,7 @@ in question does not exist. SELECT col_is_unique( 'contacts', - ARRAY['first', 'last'], + ARRAY['given_name', 'surname'], ); Just like `col_is_pk()`, except that it test that the column or array of @@ -907,7 +907,7 @@ not exist. SELECT col_has_check( 'contacts', - ARRAY['first', 'last'], + ARRAY['given_name', 'surname'], ); Just like `col_is_pk()`, except that it test that the column or array of From 4eab0fc780d8d62b41202b15c17dc255add0a54e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 16 Sep 2008 04:06:20 +0000 Subject: [PATCH 0123/1195] Started making changes to supports PostgreSQL 8.0: * Older versions of PostgreSQL cannot cast `name[]` to text. So we pass such variables through `array_to_string()`, instead. I think that the output is probably more appropriate, anyway. * Replaced all but one usage of `regexp_replace()`, since it's not available in older versions of PostgreSQL. That last one will be replaced before long, too. * Switched to using `FOR record IN EXECUTE` to fetch a value. It's an ugly hack, but older versions of PostgreSQL require it. I'll probably have to do the same thing in a few other places. * Booleans don't case to text very well in 8.0. So use a CASE statement, instead. Thanks to David Westerbrook for the patch. * Fixed the raising of an exception so that its variable argument is not dynamically generated, which 8.0 doesn't seem to like. --- pgtap.sql.in | 50 ++++++++++++++++++++++++++++---------------------- sql/check.sql | 4 ++-- sql/coltap.sql | 16 ++++++++-------- sql/fktap.sql | 8 ++++---- sql/hastap.sql | 4 ++-- sql/pktap.sql | 4 ++-- sql/unique.sql | 4 ++-- 7 files changed, 48 insertions(+), 42 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 59509894c641..d53751730ffe 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -69,10 +69,12 @@ $$ LANGUAGE plpgsql strict; CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ DECLARE - ret integer; + rec RECORD; BEGIN - EXECUTE 'SELECT value FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' INTO ret; - RETURN ret; + FOR rec IN EXECUTE 'SELECT value FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' LOOP + RETURN rec.value; + END LOOP; + RETURN NULL; END; $$ LANGUAGE plpgsql strict; @@ -111,8 +113,11 @@ CREATE OR REPLACE FUNCTION add_result ( bool, bool, text, text, text ) RETURNS integer AS $$ BEGIN EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) - VALUES( ' || $1 || ', ' || $2 || ', ' || quote_literal(COALESCE($3, '')) || ', ' - || quote_literal($4) || ', ' || quote_literal($5) || ' )'; + VALUES( ' || quote_literal(CASE WHEN $1 THEN 't' ELSE 'f' END) || ', ' + || quote_literal(CASE WHEN $2 THEN 't' ELSE 'f' END) || ', ' + || quote_literal(COALESCE($3, '')) || ', ' + || quote_literal($4) || ', ' + || quote_literal($5) || ' )'; RETURN currval('__tresults___numb_seq'); END; $$ LANGUAGE plpgsql; @@ -141,7 +146,7 @@ BEGIN plural := CASE exp_tests WHEN 1 THEN '' ELSE 's' END; IF curr_test IS NULL THEN - RAISE EXCEPTION '%', '# No tests run!'; + RAISE EXCEPTION '# No tests run!'; END IF; IF exp_tests = 0 OR exp_tests IS NULL THEN @@ -171,7 +176,8 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION diag ( msg text ) RETURNS TEXT AS $$ BEGIN - RETURN regexp_replace( msg, '^', '# ', 'gn' ); +-- RETURN regexp_replace( msg, '^', '# ', 'gn' ); + RETURN '# ' || replace( replace( replace( msg, E'\r\n', E'\n# ' ), E'\n', E'\n# ' ), E'\r', E'\n# '); END; $$ LANGUAGE plpgsql strict; @@ -200,8 +206,8 @@ BEGIN RETURN (CASE aok WHEN TRUE THEN '' ELSE 'not ' END) || 'ok ' || _set( 'curr_test', test_num ) - || CASE descr WHEN '' THEN '' ELSE COALESCE( ' - ' || substr(regexp_replace( descr, '^', '# ', 'gn' ), 3), '' ) END - || COALESCE( ' ' || regexp_replace( 'TODO ' || todo_why, '^', '# ', 'gn' ), '') + || 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 || @@ -625,7 +631,7 @@ $$ LANGUAGE SQL; -- has_column( table, column ) CREATE OR REPLACE FUNCTION has_column ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT has_column( $1, $2, 'Column ' || $1 || '.' || $2 || ' should exist' ); + SELECT has_column( $1, $2, 'Column ' || $1 || '(' || $2 || ') should exist' ); $$ LANGUAGE SQL; -- _col_is_null( schema, table, column, desc, null ) @@ -680,7 +686,7 @@ $$ LANGUAGE SQL; -- col_not_null( table, column ) CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, 'Column ' || $1 || '.' || $2 || ' should be NOT NULL', true ); + SELECT _col_is_null( $1, $2, 'Column ' || $1 || '(' || $2 || ') should be NOT NULL', true ); $$ LANGUAGE SQL; -- col_is_null( schema, table, column, description ) @@ -698,7 +704,7 @@ $$ LANGUAGE SQL; -- col_is_null( table, column ) CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, 'Column ' || $1 || '.' || $2 || ' should allow NULL', false ); + SELECT _col_is_null( $1, $2, 'Column ' || $1 || '(' || $2 || ') should allow NULL', false ); $$ LANGUAGE SQL; -- col_type_is( schema, table, column, type, description ) @@ -751,7 +757,7 @@ $$ 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 ' || $1 || '.' || $2 || ' should be type ' || $3 ); + SELECT col_type_is( $1, $2, $3, 'Column ' || $1 || '(' || $2 || ') should be type ' || $3 ); $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _def_is( TEXT, anyelement, TEXT ) @@ -812,7 +818,7 @@ CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ SELECT col_default_is( $1, $2, $3, - 'Column ' || $1 || '.' || $2 || ' should default to ' || quote_literal($3) + 'Column ' || $1 || '(' || $2 || ') should default to ' || quote_literal($3) ); $$ LANGUAGE sql; @@ -1000,7 +1006,7 @@ $$ 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 ' || $1 || '.' || $2::text || ' should be a primary key' ); + SELECT col_is_pk( $1, $2, 'Columns ' || $1 || '(' || array_to_string($2, ', ') || ') should be a primary key' ); $$ LANGUAGE sql; -- col_is_pk( schema, table, column, description ) @@ -1018,7 +1024,7 @@ $$ 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 ' || $1 || '.' || $2 || ' should be a primary key' ); + SELECT col_is_pk( $1, $2, 'Column ' || $1 || '(' || $2 || ') should be a primary key' ); $$ LANGUAGE sql; -- has_fk( schema, table, description ) @@ -1114,7 +1120,7 @@ $$ 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 ' || $1 || '.' || $2::text || ' should be a foreign key' ); + SELECT col_is_fk( $1, $2, 'Columns ' || $1 || '(' || array_to_string($2, ', ') || ') should be a foreign key' ); $$ LANGUAGE sql; -- col_is_fk( schema, table, column, description ) @@ -1132,7 +1138,7 @@ $$ 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 ' || $1 || '.' || $2 || ' should be a foreign key' ); + SELECT col_is_fk( $1, $2, 'Column ' || $1 || '(' || $2 || ') should be a foreign key' ); $$ LANGUAGE sql; -- has_unique( schema, table, description ) @@ -1168,7 +1174,7 @@ $$ 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 ' || $1 || '.' || $2::text || ' should have a unique constraint' ); + SELECT col_is_unique( $1, $2, 'Columns ' || $1 || '(' || array_to_string($2, ', ') || ') should have a unique constraint' ); $$ LANGUAGE sql; -- col_is_unique( schema, table, column, description ) @@ -1186,7 +1192,7 @@ $$ 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 ' || $1 || '.' || $2 || ' should have a unique constraint' ); + SELECT col_is_unique( $1, $2, 'Column ' || $1 || '(' || $2 || ') should have a unique constraint' ); $$ LANGUAGE sql; -- has_check( schema, table, description ) @@ -1222,7 +1228,7 @@ $$ 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 ' || $1 || '.' || $2::text || ' should have a check constraint' ); + SELECT col_has_check( $1, $2, 'Columns ' || $1 || '(' || array_to_string($2, ', ') || ') should have a check constraint' ); $$ LANGUAGE sql; -- col_has_check( schema, table, column, description ) @@ -1240,7 +1246,7 @@ $$ 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 ' || $1 || '.' || $2 || ' should have a check constraint' ); + SELECT col_has_check( $1, $2, 'Column ' || $1 || '(' || $2 || ') should have a check constraint' ); $$ LANGUAGE sql; -- fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description ) diff --git a/sql/check.sql b/sql/check.sql index bdd8ff83749a..8b3a40645236 100644 --- a/sql/check.sql +++ b/sql/check.sql @@ -100,7 +100,7 @@ SELECT is( \echo ok 15 - test col_has_check( table, column ) SELECT is( col_has_check( 'sometab', 'name' ), - 'ok 15 - Column sometab.name should have a check constraint', + 'ok 15 - Column sometab(name) should have a check constraint', 'col_has_check( table, column ) should work' ); @@ -141,7 +141,7 @@ SELECT is( \echo ok 25 - test col_has_check( table, column[], description ) SELECT is( col_has_check( 'argh', ARRAY['id', 'name'] ), - 'ok 25 - Columns argh.{id,name} should have a check constraint', + 'ok 25 - Columns argh(id, name) should have a check constraint', 'col_has_check( table, column[] ) should work' ); diff --git a/sql/coltap.sql b/sql/coltap.sql index f10433277257..8f10db2fa360 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -59,14 +59,14 @@ SELECT is( \echo ok 5 - testing col_not_null( schema, table, column, desc ) SELECT is( col_not_null( 'sometab', 'id' ), - 'ok 5 - Column sometab.id should be NOT NULL', + 'ok 5 - Column sometab(id) should be NOT NULL', 'col_not_null( table, column ) should work' ); -- Make sure failure is correct. \echo ok 7 - testing col_not_null( schema, table, column, desc ) SELECT is( col_not_null( 'sometab', 'name' ), - E'not ok 7 - Column sometab.name should be NOT NULL\n# Failed test 7: "Column sometab.name should be NOT NULL"', + E'not ok 7 - Column sometab(name) should be NOT NULL\n# Failed test 7: "Column sometab(name) should be NOT NULL"', 'col_not_null( table, column ) should properly fail' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 7 ); @@ -89,14 +89,14 @@ SELECT is( \echo ok 13 - testing col_is_null( schema, table, column, desc ) SELECT is( col_is_null( 'sometab', 'name' ), - 'ok 13 - Column sometab.name should allow NULL', + 'ok 13 - Column sometab(name) should allow NULL', 'col_is_null( table, column ) should work' ); -- Make sure failure is correct. \echo ok 15 - testing col_is_null( schema, table, column, desc ) SELECT is( col_is_null( 'sometab', 'id' ), - E'not ok 15 - Column sometab.id should allow NULL\n# Failed test 15: "Column sometab.id should allow NULL"', + E'not ok 15 - Column sometab(id) should allow NULL\n# Failed test 15: "Column sometab(id) should allow NULL"', 'col_is_null( table, column ) should properly fail' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 15 ); @@ -120,14 +120,14 @@ SELECT is( \echo ok 21 - testing col_type_is( table, column, type ) SELECT is( col_type_is( 'sometab', 'name', 'text' ), - 'ok 21 - Column sometab.name should be type text', + 'ok 21 - Column sometab(name) should be type text', 'col_type_is( table, column, type ) should work' ); \echo ok 23 - testing col_type_is( table, column, type ) case-insensitively SELECT is( col_type_is( 'sometab', 'name', 'TEXT' ), - 'ok 23 - Column sometab.name should be type TEXT', + 'ok 23 - Column sometab(name) should be type TEXT', 'col_type_is( table, column, type ) should work case-insensitively' ); @@ -135,7 +135,7 @@ SELECT is( \echo ok 25 - testing col_type_is( table, column, type ) failure SELECT is( col_type_is( 'sometab', 'name', 'int4' ), - E'not ok 25 - Column sometab.name should be type int4\n# Failed test 25: "Column sometab.name should be type int4"\n# have: text\n# want: int4', + E'not ok 25 - Column sometab(name) should be type int4\n# Failed test 25: "Column sometab(name) should be type int4"\n# have: text\n# want: int4', 'col_type_is( table, column, type ) should fail with proper diagnostics' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 25 ); @@ -187,7 +187,7 @@ SELECT is( \echo ok 37 - col_default_is( table, column, default ) SELECT is( col_default_is( 'sometab', 'name', '' ), - 'ok 37 - Column sometab.name should default to ''''', + 'ok 37 - Column sometab(name) should default to ''''', 'col_default_is( table, column, default ) should work' ); diff --git a/sql/fktap.sql b/sql/fktap.sql index 04ba4c99d82e..dc6f41fe7c5c 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -124,7 +124,7 @@ SELECT * FROM check_test( col_is_fk( 'fk', 'pk_id' ), true, 'col_is_fk( table, column )', - 'Column fk.pk_id should be a foreign key' + 'Column fk(pk_id) should be a foreign key' ); SELECT * FROM check_test( @@ -151,7 +151,7 @@ SELECT * FROM check_test( col_is_fk( 'fk3', 'pk_id' ), true, 'multi-fk col_is_fk test', - 'Column fk3.pk_id should be a foreign key' + 'Column fk3(pk_id) should be a foreign key' ); -- Check failure for table with no FKs. @@ -167,7 +167,7 @@ SELECT * FROM check_test( col_is_fk( 'pk', 'name' ), false, 'col_is_fk with no FKs', - 'Column pk.name should be a foreign key', + 'Column pk(name) should be a foreign key', ' Table pk has no foreign key columns' ); @@ -193,7 +193,7 @@ SELECT * FROM check_test( col_is_fk( 'fk2', ARRAY['pk2_num', 'pk2_dot'] ), true, 'col_is_fk( table, column[] )', - 'Columns fk2.{pk2_num,pk2_dot} should be a foreign key' + 'Columns fk2(pk2_num, pk2_dot) should be a foreign key' ); /****************************************************************************/ diff --git a/sql/hastap.sql b/sql/hastap.sql index 287adee1505f..f2ee154f8353 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -126,7 +126,7 @@ SELECT is( \echo ok 21 - has_column(table, column) fail SELECT is( has_column( '__SDFSDFD__', 'foo' ), - E'not ok 21 - Column __SDFSDFD__.foo should exist\n# Failed test 21: "Column __SDFSDFD__.foo should exist"', + E'not ok 21 - Column __SDFSDFD__(foo) should exist\n# Failed test 21: "Column __SDFSDFD__(foo) should exist"', 'has_column(table, column) should fail for non-existent table' ); @@ -148,7 +148,7 @@ UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 21, 23, 25 ); \echo ok 27 - has_column(table, column) pass SELECT is( has_column( 'sometab', 'id' ), - 'ok 27 - Column sometab.id should exist', + 'ok 27 - Column sometab(id) should exist', 'has_column(table, column) should pass for an existing column' ); diff --git a/sql/pktap.sql b/sql/pktap.sql index e46e1d34a264..274a590b8f17 100644 --- a/sql/pktap.sql +++ b/sql/pktap.sql @@ -100,7 +100,7 @@ SELECT is( \echo ok 15 - test col_is_pk( table, column ) SELECT is( col_is_pk( 'sometab', 'id' ), - 'ok 15 - Column sometab.id should be a primary key', + 'ok 15 - Column sometab(id) should be a primary key', 'col_is_pk( table, column ) should work' ); @@ -141,7 +141,7 @@ SELECT is( \echo ok 25 - test col_is_pk( table, column[], description ) SELECT is( col_is_pk( 'argh', ARRAY['id', 'name'] ), - 'ok 25 - Columns argh.{id,name} should be a primary key', + 'ok 25 - Columns argh(id, name) should be a primary key', 'col_is_pk( table, column[] ) should work' ); diff --git a/sql/unique.sql b/sql/unique.sql index a7878d345650..6bd02a2e8536 100644 --- a/sql/unique.sql +++ b/sql/unique.sql @@ -100,7 +100,7 @@ SELECT is( \echo ok 15 - test col_is_unique( table, column ) SELECT is( col_is_unique( 'sometab', 'name' ), - 'ok 15 - Column sometab.name should have a unique constraint', + 'ok 15 - Column sometab(name) should have a unique constraint', 'col_is_unique( table, column ) should work' ); @@ -141,7 +141,7 @@ SELECT is( \echo ok 25 - test col_is_unique( table, column[], description ) SELECT is( col_is_unique( 'argh', ARRAY['id', 'name'] ), - 'ok 25 - Columns argh.{id,name} should have a unique constraint', + 'ok 25 - Columns argh(id, name) should have a unique constraint', 'col_is_unique( table, column[] ) should work' ); From 7bf6cf1d3314840259aba54fd4d90c5a78f7495c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 02:42:17 +0000 Subject: [PATCH 0124/1195] Reverted some of the changes for 8.0. I'm going to go back to 8.2 first, then 8.1, and then 8.0 if I really feel like it. --- pgtap.sql.in | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index d53751730ffe..3d0a935317fc 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -69,12 +69,10 @@ $$ LANGUAGE plpgsql strict; CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ DECLARE - rec RECORD; + ret integer; BEGIN - FOR rec IN EXECUTE 'SELECT value FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' LOOP - RETURN rec.value; - END LOOP; - RETURN NULL; + EXECUTE 'SELECT value FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' INTO ret; + RETURN ret; END; $$ LANGUAGE plpgsql strict; @@ -113,8 +111,8 @@ CREATE OR REPLACE FUNCTION add_result ( bool, bool, text, text, text ) RETURNS integer AS $$ BEGIN EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) - VALUES( ' || quote_literal(CASE WHEN $1 THEN 't' ELSE 'f' END) || ', ' - || quote_literal(CASE WHEN $2 THEN 't' ELSE 'f' END) || ', ' + VALUES( ' || $1 || ', ' + || $2 || ', ' || quote_literal(COALESCE($3, '')) || ', ' || quote_literal($4) || ', ' || quote_literal($5) || ' )'; @@ -175,11 +173,16 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION diag ( msg text ) RETURNS TEXT AS $$ -BEGIN --- RETURN regexp_replace( msg, '^', '# ', 'gn' ); - RETURN '# ' || replace( replace( replace( msg, E'\r\n', E'\n# ' ), E'\n', E'\n# ' ), E'\r', E'\n# '); -END; -$$ LANGUAGE plpgsql strict; + 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 ok ( boolean, text ) RETURNS TEXT AS $$ @@ -1413,7 +1416,7 @@ BEGIN END IF; -- Remove the #s. - adiag := regexp_replace( adiag, '^# ', '', 'gn' ); + adiag := replace( substring(adiag from 3), E'\n# ', E'\n' ); -- Now compare the diagnostics. RETURN NEXT is( From 33a5516917000cf42f3356ebdaebc8613a3259d7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 18:05:59 +0000 Subject: [PATCH 0125/1195] Ported pgTAP to PostgreSQL 8.2. Mostly this involved adding the appropriate casts. Here's how I did it: * Modified `Makefile` to concatenate extra compatibility files into `pgtap.sql` and `uninstall_pgtap.sql` depending on version numbers. It's ugly, but it works well enough. The files live in `compat` and are named for the appropriate version. Right now that means that it adds the contents of extra files for 8.2. I'll be adding more for 8.1 later, if necessary. And more for 8.0 even later. Maybe. * Added some temporary functions to `sql/cmp_ok.sql`. These allow the tests to properly run in 8.2 and older, where those functions are not available. They're not required for the test suite itself, though, just for these particular tests. * Added compatibility files for PostgreSQL 8.2. It includes a few casts and one operator. * While working on this stuff, added the `skip()` function. I need to write some tests for it, though. * Eliminated unnecessary casts to `::text` in calls to `quote_literal()`. I had them in one place but not another. --- Changes | 2 +- Makefile | 41 +++++++++++++++++++++-- README.pgtap | 71 +++++++++++++++++++++++++++++++++++++--- compat/install-8.2.sql | 34 +++++++++++++++++++ compat/uninstall-8.2.sql | 8 +++++ pgtap.sql.in | 16 +++++++-- sql/cmpok.sql | 13 ++++++++ 7 files changed, 176 insertions(+), 9 deletions(-) create mode 100644 compat/install-8.2.sql create mode 100644 compat/uninstall-8.2.sql diff --git a/Changes b/Changes index 961adec4d8ef..65673427553b 100644 --- a/Changes +++ b/Changes @@ -8,7 +8,7 @@ Revision history for pgTAP Patch from Rod Taylor. - Replaced a call to `lastval()` with a call to `currvall()` in order to improve compatibility with PostgreSQL 8.0. Reported by David Westbrook. - - Added support for TODO tests. + - Added support for TODO and SKIP tests. - Removed the few uses of named parameters and added alias names instead. This improves compatibility for versions of PostgreSQL prior to 8.0. Reported by David Westbrook. diff --git a/Makefile b/Makefile index 4ba68566c617..67b96e2e9401 100644 --- a/Makefile +++ b/Makefile @@ -16,15 +16,52 @@ include $(top_builddir)/src/Makefile.global include $(top_srcdir)/contrib/contrib-global.mk endif +# We need to do various things with various versions of PostgreSQL. +PGVER_MAJOR = $(shell echo $(VERSION) | awk -F. '{ print ($$1 + 0) }') +PGVER_MINOR = $(shell echo $(VERSION) | awk -F. '{ print ($$2 + 0) }') +PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') + +# We support 8.1 and later. +ifneq ($(PGVER_MAJOR), 8) +$(error pgTAP requires PostgreSQL 8.1 or later. This is $(VERSION)) +endif + # Override how .sql targets are processed to add the schema info, if # necessary. Otherwise just copy the files. %.sql: %.sql.in ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/$*,g' $< >$@ + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' $< >$@ +else + cp $< $@ +endif + +pgtap.sql: +ifdef TAPSCHEMA + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' pgtap.sql.in > pgtap.sql +else + sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' pgtap.sql.in > pgtap.sql +endif +ifeq ($(PGVER_MAJOR), 8) +ifneq ($(PGVER_MINOR), 3) + cat compat/install-8.2.sql >> pgtap.sql +endif +endif + +uninstall_pgtap.sql: +ifdef TAPSCHEMA + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' uninstall_pgtap.sql.in > uninstall_pgtap.sql else - sed -e 's,MODULE_PATHNAME,$$libdir/$*,g' $< >$@ + cp uninstall_pgtap.sql.in uninstall_pgtap.sql +endif +ifeq ($(PGVER_MAJOR), 8) +ifneq ($(PGVER_MINOR), 3) + mv uninstall_pgtap.sql uninstall_pgtap.tmp + cat compat/uninstall-8.2.sql uninstall_pgtap.tmp >> uninstall_pgtap.sql + rm uninstall_pgtap.tmp +endif endif # In addition to installcheck, one can also run the tests through pg_prove. test: ./bin/pg_prove sql/*.sql + diff --git a/README.pgtap b/README.pgtap index 765e08ecd408..d71458ad309e 100644 --- a/README.pgtap +++ b/README.pgtap @@ -973,6 +973,44 @@ tests, is that they're like a programmatic todo list. You know how much work is left to be done, you're aware of what bugs there are, and you'll know immediately when they're fixed. +### `skip( why, how_many )` ### +### `skip( why )` ### + +Outputs SKIP test results. Use it in a conditional expression within a +`SELECT` statement to replace the output of a test that you otherwise would +have run. + + SELECT CASE current_schema() + WHEN 'public' THEN is( :this, :that ) || E'\n' || is( :foo, :bar ) + ELSE skip( 'Tests not running in the "public" schema', 2 ) + END; + +Note how use of the conditional `CASE` statement has been used to determine +whether or not to run a couple of tests. If they are to be run, they are +concatenated with newlines, so that we can run a few tests in the same query. +If we don't want to run them, we call `skip()` and tell it how many tests +we're skipping. + +If you don't specify how many tests to skip, `skip()` will assume that you're +skipping only one. This is useful for the simple case, of course: + + SELECT CASE current_schema() + WHEN 'public' THEN is( :this, :that ) + ELSE skip( 'Tests not running in the "public" schema' ) + END; + +But you can also use it in a `SELECT` statement that would otherwise return +multiple rows: + + SELECT CASE current_schema() + WHEN 'public' THEN is( nspname, 'public' ) + ELSE skip( 'Cannot see the public schema' ) + END + FROM pg_namespace; + +This will cause it to skip the same number of rows as would have been tested +had the `WHEN` condition been true. + Writing Test Functions ====================== @@ -1160,20 +1198,45 @@ it gets the job done. Of course, if your diagnostics use something other than indented "have" and "want", such failures will be easier to read. But either way, do test your diagnostics! +Compatibility +============= + +Here are some notes on how pgTAP is built for particular versions of +PostgreSQL. This helps you to understand any side-effects. If you'd rather not +have these changes in your schema, build `pgTAP` with a schema just for it, +instead: + + make TAPSCHEMA=tap + +To see the specifics for each version of PostgreSQL, consult the files in the +`compat/` directory in the pgTAP distribution. + +8.3 and Higher +-------------- + +No changes. Everything should just work. + +8.2 and Lower +------------- + +A number of casts are added to increase compatibility. The casts are: + +* `boolean` to `text` +* `name[]` to `text` +* `regtype` to `text` + +An `=` operator is also added that compares `name[]` values. + To Do ----- -* Handle multiple FK constraints in `fk_ok()` and the other FK test functions. * Update the Makefile to process pg_prove and set the proper path to Perl on the shebang line. Will likely require a `$(PERL)` environment variable. * Update the Makefile to require TAP::Harness. -* Update the Makefile to alter the source as appropriate for older versions - of PostgreSQL. * Add `todo_start()` and `todo_end()` (and `in_todo()`). * Add `note()`? * Add `can_ok()`. - Suported Versions ----------------- diff --git a/compat/install-8.2.sql b/compat/install-8.2.sql new file mode 100644 index 000000000000..a0d5b69defb7 --- /dev/null +++ b/compat/install-8.2.sql @@ -0,0 +1,34 @@ + +-- 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 IMPLICIT; + +-- 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 IMPLICIT; + +-- 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; + +CREATE OPERATOR = ( + LEFTARG = name[], + RIGHTARG = name[], + COMMUTATOR = <>, + PROCEDURE = namearray_eq +); + +-- 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 IMPLICIT; diff --git a/compat/uninstall-8.2.sql b/compat/uninstall-8.2.sql new file mode 100644 index 000000000000..4c99841c698e --- /dev/null +++ b/compat/uninstall-8.2.sql @@ -0,0 +1,8 @@ +DROP CAST (regtype AS text); +DROP FUNCTION regtypetext(regtype); +DROP OPERATOR = ( name[], name[] ); +DROP FUNCTION namearray_eq( name[], name[] ); +DROP CAST (name[] AS text); +DROP FUNCTION namearray_text(name[]); +DROP CAST (boolean AS char(1); +DROP FUNCTION booltext(boolean); diff --git a/pgtap.sql.in b/pgtap.sql.in index 3d0a935317fc..fa62b2940a90 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -401,9 +401,9 @@ BEGIN INTO result; output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( - ' ' || COALESCE( quote_literal(have::text), 'NULL' ) || + ' ' || COALESCE( quote_literal(have), 'NULL' ) || E'\n ' || op || - E'\n ' || COALESCE( quote_literal(want::text), 'NULL' ) + E'\n ' || COALESCE( quote_literal(want), 'NULL' ) ) END; END; $$ LANGUAGE plpgsql; @@ -456,6 +456,18 @@ BEGIN END; $$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION skip ( why text, how_many int ) +RETURNS TEXT AS $$ +DECLARE + output TEXT[]; +BEGIN + FOR i IN 1..how_many LOOP + output = array_append(output, pass( 'SKIP: ' || why ) ); + END LOOP; + RETURN array_to_string(output, E'\n'); +END; +$$ LANGUAGE plpgsql; + CREATE OR REPLACE FUNCTION throws_ok ( TEXT, CHAR(5), TEXT ) RETURNS TEXT AS $$ DECLARE diff --git a/sql/cmpok.sql b/sql/cmpok.sql index 7c7f1016fc57..dc17ed19db95 100644 --- a/sql/cmpok.sql +++ b/sql/cmpok.sql @@ -33,6 +33,19 @@ BEGIN; -- Set the test plan. SELECT plan(14); +/****************************************************************************/ + +-- Set up some functions that are used only by this test, and aren't available +-- in PostgreSQL 8.2 or older + +CREATE OR REPLACE FUNCTION quote_literal(polygon) +RETURNS TEXT AS 'SELECT '''''''' || textin(poly_out($1)) || ''''''''' +LANGUAGE SQL IMMUTABLE STRICT; + +CREATE OR REPLACE FUNCTION quote_literal(anyarray) +RETURNS TEXT AS 'SELECT '''''''' || textin(array_out($1)) || ''''''''' +LANGUAGE SQL IMMUTABLE STRICT; + /****************************************************************************/ -- Test cmp_ok(). \echo ok 1 - test cmp_ok( int, =, int, description ) From 5902bbc2d31f845d0c190fad5097fd6e4bd4b96e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 18:09:03 +0000 Subject: [PATCH 0126/1195] Restore comaptibility with 8.3. --- sql/cmpok.sql | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sql/cmpok.sql b/sql/cmpok.sql index dc17ed19db95..69b1450aa19d 100644 --- a/sql/cmpok.sql +++ b/sql/cmpok.sql @@ -42,7 +42,11 @@ CREATE OR REPLACE FUNCTION quote_literal(polygon) RETURNS TEXT AS 'SELECT '''''''' || textin(poly_out($1)) || ''''''''' LANGUAGE SQL IMMUTABLE STRICT; -CREATE OR REPLACE FUNCTION quote_literal(anyarray) +CREATE OR REPLACE FUNCTION quote_literal(integer[]) +RETURNS TEXT AS 'SELECT '''''''' || textin(array_out($1)) || ''''''''' +LANGUAGE SQL IMMUTABLE STRICT; + +CREATE OR REPLACE FUNCTION quote_literal(inet[]) RETURNS TEXT AS 'SELECT '''''''' || textin(array_out($1)) || ''''''''' LANGUAGE SQL IMMUTABLE STRICT; From 0b188f608baa57a9393dc9fec2bc75e6b65eac8d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 18:15:11 +0000 Subject: [PATCH 0127/1195] Now processing all of the TEST files to properly set the TAPSCHEMA. --- Makefile | 2 +- sql/{check.sql => check.sql.in} | 0 sql/{cmpok.sql => cmpok.sql.in} | 0 sql/{coltap.sql => coltap.sql.in} | 0 sql/{fktap.sql => fktap.sql.in} | 0 sql/{hastap.sql => hastap.sql.in} | 0 sql/{istap.sql => istap.sql.in} | 0 sql/{matching.sql => matching.sql.in} | 0 sql/{moretap.sql => moretap.sql.in} | 0 sql/{pg73.sql => pg73.sql.in} | 0 sql/{pktap.sql => pktap.sql.in} | 0 sql/{throwtap.sql => throwtap.sql.in} | 0 sql/{todotap.sql => todotap.sql.in} | 0 sql/{unique.sql => unique.sql.in} | 0 14 files changed, 1 insertion(+), 1 deletion(-) rename sql/{check.sql => check.sql.in} (100%) rename sql/{cmpok.sql => cmpok.sql.in} (100%) rename sql/{coltap.sql => coltap.sql.in} (100%) rename sql/{fktap.sql => fktap.sql.in} (100%) rename sql/{hastap.sql => hastap.sql.in} (100%) rename sql/{istap.sql => istap.sql.in} (100%) rename sql/{matching.sql => matching.sql.in} (100%) rename sql/{moretap.sql => moretap.sql.in} (100%) rename sql/{pg73.sql => pg73.sql.in} (100%) rename sql/{pktap.sql => pktap.sql.in} (100%) rename sql/{throwtap.sql => throwtap.sql.in} (100%) rename sql/{todotap.sql => todotap.sql.in} (100%) rename sql/{unique.sql => unique.sql.in} (100%) diff --git a/Makefile b/Makefile index 67b96e2e9401..386af8ddedac 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ # $Id$ -DATA_built = pgtap.sql uninstall_pgtap.sql +DATA_built = pgtap.sql uninstall_pgtap.sql $(patsubst %.sql.in,%.sql,$(wildcard sql/*.sql.in)) MODULES = pgtap DOCS = README.pgtap SCRIPTS = bin/pg_prove diff --git a/sql/check.sql b/sql/check.sql.in similarity index 100% rename from sql/check.sql rename to sql/check.sql.in diff --git a/sql/cmpok.sql b/sql/cmpok.sql.in similarity index 100% rename from sql/cmpok.sql rename to sql/cmpok.sql.in diff --git a/sql/coltap.sql b/sql/coltap.sql.in similarity index 100% rename from sql/coltap.sql rename to sql/coltap.sql.in diff --git a/sql/fktap.sql b/sql/fktap.sql.in similarity index 100% rename from sql/fktap.sql rename to sql/fktap.sql.in diff --git a/sql/hastap.sql b/sql/hastap.sql.in similarity index 100% rename from sql/hastap.sql rename to sql/hastap.sql.in diff --git a/sql/istap.sql b/sql/istap.sql.in similarity index 100% rename from sql/istap.sql rename to sql/istap.sql.in diff --git a/sql/matching.sql b/sql/matching.sql.in similarity index 100% rename from sql/matching.sql rename to sql/matching.sql.in diff --git a/sql/moretap.sql b/sql/moretap.sql.in similarity index 100% rename from sql/moretap.sql rename to sql/moretap.sql.in diff --git a/sql/pg73.sql b/sql/pg73.sql.in similarity index 100% rename from sql/pg73.sql rename to sql/pg73.sql.in diff --git a/sql/pktap.sql b/sql/pktap.sql.in similarity index 100% rename from sql/pktap.sql rename to sql/pktap.sql.in diff --git a/sql/throwtap.sql b/sql/throwtap.sql.in similarity index 100% rename from sql/throwtap.sql rename to sql/throwtap.sql.in diff --git a/sql/todotap.sql b/sql/todotap.sql.in similarity index 100% rename from sql/todotap.sql rename to sql/todotap.sql.in diff --git a/sql/unique.sql b/sql/unique.sql.in similarity index 100% rename from sql/unique.sql rename to sql/unique.sql.in From 498585b4e1f80adb240448efe864b305d81b5f19 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 18:21:51 +0000 Subject: [PATCH 0128/1195] Compatibility in tests for the TAPSCHEMA. Just be sure to use "public" when we are going to be testing "public". --- sql/check.sql.in | 4 ++-- sql/coltap.sql.in | 2 +- sql/fktap.sql.in | 10 +++++----- sql/pktap.sql.in | 4 ++-- sql/unique.sql.in | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/sql/check.sql.in b/sql/check.sql.in index 8b3a40645236..33800ccb9f4e 100644 --- a/sql/check.sql.in +++ b/sql/check.sql.in @@ -34,7 +34,7 @@ BEGIN; SELECT plan(26); -- This will be rolled back. :-) -CREATE TABLE sometab( +CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '' CHECK ( name IN ('foo', 'bar', 'baz') ), numb NUMERIC(10, 2), @@ -122,7 +122,7 @@ UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 17, 19 ); /****************************************************************************/ -- Test col_has_check() with an array of columns. -CREATE TABLE argh (id int not null, name text not null, check ( id IN (1, 2) AND name IN ('foo', 'bar'))); +CREATE TABLE public.argh (id int not null, name text not null, check ( id IN (1, 2) AND name IN ('foo', 'bar'))); \echo ok 21 - test col_has_check( schema, table, column[], description ) SELECT is( diff --git a/sql/coltap.sql.in b/sql/coltap.sql.in index 8f10db2fa360..851a80e0c5f2 100644 --- a/sql/coltap.sql.in +++ b/sql/coltap.sql.in @@ -34,7 +34,7 @@ BEGIN; SELECT plan(38); -- This will be rolled back. :-) -CREATE TABLE sometab( +CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '', numb NUMERIC(10, 2), diff --git a/sql/fktap.sql.in b/sql/fktap.sql.in index dc6f41fe7c5c..f7de99410a0b 100644 --- a/sql/fktap.sql.in +++ b/sql/fktap.sql.in @@ -35,29 +35,29 @@ SELECT plan(88); --SELECT * from no_plan(); -- These will be rolled back. :-) -CREATE TABLE pk ( +CREATE TABLE public.pk ( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '' ); -CREATE TABLE fk ( +CREATE TABLE public.fk ( id INT NOT NULL PRIMARY KEY, pk_id INT NOT NULL REFERENCES pk(id) ); -CREATE TABLE pk2 ( +CREATE TABLE public.pk2 ( num int NOT NULL, dot int NOT NULL, PRIMARY KEY (num, dot) ); -CREATE TABLE fk2 ( +CREATE TABLE public.fk2 ( pk2_num int NOT NULL, pk2_dot int NOT NULL, FOREIGN KEY(pk2_num, pk2_dot) REFERENCES pk2( num, dot) ); -CREATE TABLE fk3( +CREATE TABLE public.fk3( id INT NOT NULL PRIMARY KEY, pk_id INT NOT NULL REFERENCES pk(id), pk2_num int NOT NULL, diff --git a/sql/pktap.sql.in b/sql/pktap.sql.in index 274a590b8f17..6d0fba349e6c 100644 --- a/sql/pktap.sql.in +++ b/sql/pktap.sql.in @@ -34,7 +34,7 @@ BEGIN; SELECT plan(26); -- This will be rolled back. :-) -CREATE TABLE sometab( +CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '', numb NUMERIC(10, 2), @@ -122,7 +122,7 @@ UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 17, 19 ); /****************************************************************************/ -- Test col_is_pk() with an array of columns. -CREATE TABLE argh (id int not null, name text not null, primary key (id, name)); +CREATE TABLE public.argh (id int not null, name text not null, primary key (id, name)); \echo ok 21 - test col_is_pk( schema, table, column[], description ) SELECT is( diff --git a/sql/unique.sql.in b/sql/unique.sql.in index 6bd02a2e8536..7c365a3812fb 100644 --- a/sql/unique.sql.in +++ b/sql/unique.sql.in @@ -34,7 +34,7 @@ BEGIN; SELECT plan(26); -- This will be rolled back. :-) -CREATE TABLE sometab( +CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '' UNIQUE, numb NUMERIC(10, 2), @@ -122,7 +122,7 @@ UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 17, 19 ); /****************************************************************************/ -- Test col_is_unique() with an array of columns. -CREATE TABLE argh (id int not null, name text not null, unique (id, name)); +CREATE TABLE public.argh (id int not null, name text not null, unique (id, name)); \echo ok 21 - test col_is_unique( schema, table, column[], description ) SELECT is( From 8c576c3d4322199f94576bfd3e6b2745f595d9c9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 18:23:23 +0000 Subject: [PATCH 0129/1195] Ignore `*.sql` files in the `sql/` directory. From a64c946fe19ed8656ef3fdad0c3015700f12d203 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 18:36:43 +0000 Subject: [PATCH 0130/1195] Added tests for `skip()`. And added missing implementation while I was at it. --- expected/todotap.out | 12 +++++++++++- pgtap.sql.in | 7 ++++++- sql/todotap.sql.in | 31 ++++++++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/expected/todotap.out b/expected/todotap.out index 25130a8579f2..9651d9d32042 100644 --- a/expected/todotap.out +++ b/expected/todotap.out @@ -1,5 +1,15 @@ \set ECHO -1..3 +1..13 ok 1 - todo fail ok 2 - todo pass ok 3 - TODO tests should display properly +ok 4 - simple skip should pass +ok 5 - simple skip should have the proper description +ok 6 - simple skip should have the proper diagnostics +ok 7 - skip with num should pass +ok 8 - skip with num should have the proper description +ok 9 - skip with num should have the proper diagnostics +ok 10 Skip multiple +ok 11 Skip multiple +ok 12 Skip multiple +ok 13 - We should get the proper output for multiple skips diff --git a/pgtap.sql.in b/pgtap.sql.in index fa62b2940a90..b015db3c3534 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -462,12 +462,17 @@ DECLARE output TEXT[]; BEGIN FOR i IN 1..how_many LOOP - output = array_append(output, pass( 'SKIP: ' || why ) ); + output = array_append(output, ok( TRUE, 'SKIP: ' || 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, 'SKIP: ' || $1 ); +$$ LANGUAGE sql; + CREATE OR REPLACE FUNCTION throws_ok ( TEXT, CHAR(5), TEXT ) RETURNS TEXT AS $$ DECLARE diff --git a/sql/todotap.sql.in b/sql/todotap.sql.in index b12f975e8213..67ccb2ef1681 100644 --- a/sql/todotap.sql.in +++ b/sql/todotap.sql.in @@ -31,7 +31,7 @@ BEGIN; -- ## SET search_path TO TAPSCHEMA,public; -- Set the test plan. -SELECT plan(3); +SELECT plan(13); /****************************************************************************/ -- Test todo tests. @@ -46,6 +46,35 @@ SELECT is( ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 2 ); +/****************************************************************************/ +-- Test skipping tests. +SELECT * FROM check_test( + skip('Just because'), + true, + 'simple skip', + 'SKIP: Just because', + '' +); + +SELECT * FROM check_test( + skip('Just because', 1), + true, + 'skip with num', + 'SKIP: Just because', + '' +); + +\echo ok 10 Skip multiple +\echo ok 11 Skip multiple +\echo ok 12 Skip multiple +SELECT is( + skip( 'Whatever', 3 ), + 'ok 10 - SKIP: Whatever +ok 11 - SKIP: Whatever +ok 12 - SKIP: Whatever', + 'We should get the proper output for multiple skips' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From deb9e6814a2ab22b85e1d2ace4e0eaed17ce3086 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 18:46:28 +0000 Subject: [PATCH 0131/1195] Make sure that any changes to files trigger the targets. --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 386af8ddedac..b35d08a97ed5 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ else cp $< $@ endif -pgtap.sql: +pgtap.sql: pgtap.sql.in ifdef TAPSCHEMA sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' pgtap.sql.in > pgtap.sql else @@ -47,7 +47,7 @@ ifneq ($(PGVER_MINOR), 3) endif endif -uninstall_pgtap.sql: +uninstall_pgtap.sql: uninstall_pgtap.sql.in ifdef TAPSCHEMA sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' uninstall_pgtap.sql.in > uninstall_pgtap.sql else From 4bf9c98ef8adaf5fee2bcfb50f0de1e85c54c564 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 19:03:30 +0000 Subject: [PATCH 0132/1195] Ported to PostgreSQL 8.1. That was easy. Yay! --- pgtap.sql.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index b015db3c3534..7d405fcf9839 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -40,7 +40,7 @@ BEGIN reason TEXT NOT NULL DEFAULT '''' ); GRANT ALL ON TABLE __tresults__ TO PUBLIC; - GRANT ALL ON SEQUENCE __tresults___numb_seq TO PUBLIC; + GRANT ALL ON TABLE __tresults___numb_seq TO PUBLIC; '; EXCEPTION WHEN duplicate_table THEN @@ -461,6 +461,7 @@ RETURNS TEXT AS $$ DECLARE output TEXT[]; BEGIN + output := '{}'; FOR i IN 1..how_many LOOP output = array_append(output, ok( TRUE, 'SKIP: ' || why ) ); END LOOP; From 6a8c410e8a3d67575eded1539c149a74f3a6b2fa Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 20:05:56 +0000 Subject: [PATCH 0133/1195] Ported to PostgreSQL 8.0. I did most of the hard work the other day, and realized that I could apply it all using a patch. So that's what I've done. Details: * Added an `ORDER BY` clause to the `SELECT` statement that fetches the list of foreign key column combinations in `col_is_fk()`. This ensures that the output is always the same, which is at least important for my test. * Updated `Makefile` to replace all instances of `E''` when using 8.0. * Updated `Makefile` to apply a patch to get things working on 8.0. * Changed a test in `sql/moretap.sql.in` so that there is a space before a `E'`. This allows the substitution to work without needlessly killing off strings that end in "E'". * Updated `Makefile` so that the list of test scripts is generated. It also strips out the `throwtap` test in 8.0, since `throws_ok()` and `lives_ok()` are not supported in 8.0. --- Makefile | 44 +++++++++++---- bin/pg_prove | 1 - compat/install-8.0.patch | 113 +++++++++++++++++++++++++++++++++++++++ pgtap.sql.in | 2 + sql/fktap.sql.in | 4 +- sql/moretap.sql.in | 4 +- 6 files changed, 153 insertions(+), 15 deletions(-) create mode 100644 compat/install-8.0.patch diff --git a/Makefile b/Makefile index b35d08a97ed5..0111d2a2a9cd 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,10 @@ # $Id$ -DATA_built = pgtap.sql uninstall_pgtap.sql $(patsubst %.sql.in,%.sql,$(wildcard sql/*.sql.in)) +TESTS = $(patsubst %.sql.in,%.sql,$(wildcard sql/*.sql.in)) +DATA_built = pgtap.sql uninstall_pgtap.sql $(TESTS) MODULES = pgtap DOCS = README.pgtap SCRIPTS = bin/pg_prove -REGRESS = moretap istap pg73 todotap matching throwtap hastap coltap pktap fktap unique check cmpok +REGRESS = $(patsubst sql/%.sql,%,$(TESTS)) ifdef USE_PGXS PG_CONFIG = pg_config @@ -21,38 +22,62 @@ PGVER_MAJOR = $(shell echo $(VERSION) | awk -F. '{ print ($$1 + 0) }') PGVER_MINOR = $(shell echo $(VERSION) | awk -F. '{ print ($$2 + 0) }') PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') -# We support 8.1 and later. +# We support 8.0 and later. ifneq ($(PGVER_MAJOR), 8) -$(error pgTAP requires PostgreSQL 8.1 or later. This is $(VERSION)) +$(error pgTAP requires PostgreSQL 8.0 or later. This is $(VERSION)) +endif + +# Set up extra substitutions based on version numbers. +ifeq ($(PGVER_MAJOR), 8) +ifeq ($(PGVER_MINOR), 0) +# Hack for E'' syntax (<= PG8.0) +EXTRAS := -e "s/ E'/ '/g" +# Throw isn't supported in 8.0. +TESTS := $(filter-out sql/throwtap.sql,$(TESTS)) +REGRESS := $(filter-out throwtap,$(REGRESS)) +endif endif # Override how .sql targets are processed to add the schema info, if # necessary. Otherwise just copy the files. %.sql: %.sql.in ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' $< >$@ + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' $(EXTRAS) $< >$@ +else +ifdef EXTRAS + sed $(EXTRAS) $< >$@ else cp $< $@ endif +endif pgtap.sql: pgtap.sql.in ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' pgtap.sql.in > pgtap.sql + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' $(EXTRAS) pgtap.sql.in > pgtap.sql else - sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' pgtap.sql.in > pgtap.sql + sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' $(EXTRAS) pgtap.sql.in > pgtap.sql endif ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 3) cat compat/install-8.2.sql >> pgtap.sql +ifneq ($(PGVER_MINOR), 2) +ifneq ($(PGVER_MINOR), 1) + patch -p0 < compat/install-8.0.patch +endif +endif endif endif uninstall_pgtap.sql: uninstall_pgtap.sql.in ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' uninstall_pgtap.sql.in > uninstall_pgtap.sql + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' $(EXTRAS) uninstall_pgtap.sql.in > uninstall_pgtap.sql +else +ifdef EXTRAS + sed $(EXTRAS) uninstall_pgtap.sql.in > uninstall_pgtap.sql else cp uninstall_pgtap.sql.in uninstall_pgtap.sql endif +endif ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 3) mv uninstall_pgtap.sql uninstall_pgtap.tmp @@ -63,5 +88,4 @@ endif # In addition to installcheck, one can also run the tests through pg_prove. test: - ./bin/pg_prove sql/*.sql - + ./bin/pg_prove $(TESTS) diff --git a/bin/pg_prove b/bin/pg_prove index fbacb2142d28..2e3833aae1e9 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -59,7 +59,6 @@ for (qw(username host port dbname)) { push @command, qw( --no-psqlrc --no-align - --tuples-only --pset pager= --pset null=[NULL] --set ON_ERROR_ROLLBACK=1 diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch new file mode 100644 index 000000000000..1fd5ed33bcea --- /dev/null +++ b/compat/install-8.0.patch @@ -0,0 +1,113 @@ +Index: pgtap.sql +=================================================================== +--- pgtap.sql (revision 4280) ++++ pgtap.sql (working copy) +@@ -69,20 +69,24 @@ + CREATE OR REPLACE FUNCTION _get ( text ) + RETURNS integer AS $$ + DECLARE +- ret integer; ++ rec RECORD; + BEGIN +- EXECUTE 'SELECT value FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' INTO ret; +- RETURN ret; ++ FOR rec IN EXECUTE 'SELECT value FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' LOOP ++ RETURN rec.value; ++ END LOOP; ++ RETURN NULL; + END; + $$ LANGUAGE plpgsql strict; + + CREATE OR REPLACE FUNCTION _get_note ( text ) + RETURNS text AS $$ + DECLARE +- ret text; ++ rec RECORD; + BEGIN +- EXECUTE 'SELECT note FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' INTO ret; +- RETURN ret; ++ FOR rec IN EXECUTE 'SELECT note FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' LOOP ++ RETURN rec.NOTE; ++ END LOOP; ++ RETURN NULL; + END; + $$ LANGUAGE plpgsql strict; + +@@ -123,10 +127,12 @@ + CREATE OR REPLACE FUNCTION num_failed () + RETURNS INTEGER AS $$ + DECLARE +- ret integer; ++ rec RECORD; + BEGIN +- EXECUTE 'SELECT COUNT(*)::INTEGER FROM __tresults__ WHERE ok = FALSE' INTO ret; +- RETURN ret; ++ FOR rec IN EXECUTE 'SELECT COUNT(*)::INTEGER AS cnt FROM __tresults__ WHERE ok = FALSE' LOOP ++ RETURN rec.cnt; ++ END LOOP; ++ RETURN NULL; + END; + $$ LANGUAGE plpgsql strict; + +@@ -392,13 +398,16 @@ + want ALIAS FOR $3; + descr ALIAS FOR $4; + result BOOLEAN; ++ rec RECORD; + output TEXT; + BEGIN +- EXECUTE 'SELECT ' || ++ FOR rec IN EXECUTE 'SELECT ' || + COALESCE(quote_literal( have ), 'NULL') || '::' || pg_typeof(have) || ' ' + || op || ' ' || +- COALESCE(quote_literal( want ), 'NULL') || '::' || pg_typeof(want) +- INTO result; ++ COALESCE(quote_literal( want ), 'NULL') || '::' || pg_typeof(want) || ' AS res' ++ LOOP ++ result := rec.res; ++ END LOOP; + output := ok( COALESCE(result, FALSE), descr ); + RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( + ' ' || COALESCE( quote_literal(have), 'NULL' ) || +@@ -784,15 +793,16 @@ + CREATE OR REPLACE FUNCTION _def_is( TEXT, anyelement, TEXT ) + RETURNS TEXT AS $$ + DECLARE +- thing text; ++ rec RECORD; + BEGIN + IF $1 ~ '^[^'']+[(]' THEN + -- It's a functional default. + RETURN is( $1, $2, $3 ); + END IF; +- EXECUTE 'SELECT is(' || COALESCE($1, 'NULL::text') || ', ' || quote_literal($2) || ', ' || quote_literal($3) || ')' +- INTO thing; +- RETURN thing; ++ FOR rec IN EXECUTE 'SELECT is(' || COALESCE($1, 'NULL::text') || ', ' || quote_literal($2) || ', ' || quote_literal($3) || ')' || 'AS is' LOOP ++ RETURN rec.is; ++ END LOOP; ++ RETURN NULL; + END; + $$ LANGUAGE plpgsql; + +@@ -1378,6 +1390,7 @@ + res BOOLEAN; + descr TEXT; + adiag TEXT; ++ rec RECORD; + have ALIAS FOR $1; + eok ALIAS FOR $2; + name ALIAS FOR $3; +@@ -1388,8 +1401,10 @@ + tnumb := currval('__tresults___numb_seq'); + + -- Fetch the results. +- EXECUTE 'SELECT aok, descr FROM __tresults__ WHERE numb = ' || tnumb +- INTO aok, adescr; ++ FOR rec IN EXECUTE 'SELECT aok, descr FROM __tresults__ WHERE numb = ' || tnumb LOOP ++ aok := rec.aok; ++ adescr := rec.descr; ++ END LOOP; + + -- Now delete those results. + EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; diff --git a/pgtap.sql.in b/pgtap.sql.in index 7d405fcf9839..3d80d4bc93f7 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1087,6 +1087,7 @@ BEGIN 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 @@ -1122,6 +1123,7 @@ BEGIN SELECT fk_columns::text FROM pg_all_foreign_keys WHERE fk_table_name = $1 + ORDER BY fk_columns ) INTO names; IF NAMES[1] IS NOT NULL THEN diff --git a/sql/fktap.sql.in b/sql/fktap.sql.in index f7de99410a0b..60fd17614493 100644 --- a/sql/fktap.sql.in +++ b/sql/fktap.sql.in @@ -142,8 +142,8 @@ SELECT * FROM check_test( 'col_is_fk( table, column, description )', 'fk3.name should be an fk', ' Table fk3 has foreign key constraints on these columns: - {pk_id} - {pk2_num,pk2_dot}' + {pk2_num,pk2_dot} + {pk_id}' ); -- Check table with multiple FKs. diff --git a/sql/moretap.sql.in b/sql/moretap.sql.in index dcbb4ba955bc..c9d2aed4e8bf 100644 --- a/sql/moretap.sql.in +++ b/sql/moretap.sql.in @@ -64,8 +64,8 @@ SELECT is( num_failed(), 0, 'We should now have no failures' ); /****************************************************************************/ -- Check diag. SELECT is( diag('foo'), '# foo', 'diag() should work properly' ); -SELECT is( diag(E'foo\nbar'), E'# foo\n# bar', 'multiline diag() should work properly' ); -SELECT is( diag(E'foo\n# bar'), E'# foo\n# # bar', 'multiline diag() should work properly with existing comments' ); +SELECT is( diag( E'foo\nbar'), E'# foo\n# bar', 'multiline diag() should work properly' ); +SELECT is( diag( E'foo\n# bar'), E'# foo\n# # bar', 'multiline diag() should work properly with existing comments' ); /****************************************************************************/ -- Check no_plan. From 28d46017e18f10acf4dcccc96555724d4828fee4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 20:07:53 +0000 Subject: [PATCH 0134/1195] Didn't mean to delete that. --- bin/pg_prove | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/pg_prove b/bin/pg_prove index 2e3833aae1e9..fbacb2142d28 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -59,6 +59,7 @@ for (qw(username host port dbname)) { push @command, qw( --no-psqlrc --no-align + --tuples-only --pset pager= --pset null=[NULL] --set ON_ERROR_ROLLBACK=1 From c0ec52069e4710a3157ab01972a9552b20ca6625 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 20:13:01 +0000 Subject: [PATCH 0135/1195] Use the command-line parameter. --- bin/pg_prove | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/pg_prove b/bin/pg_prove index fbacb2142d28..cf0900730aa1 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -60,11 +60,11 @@ push @command, qw( --no-psqlrc --no-align --tuples-only + --quiet --pset pager= --pset null=[NULL] --set ON_ERROR_ROLLBACK=1 --set ON_ERROR_STOP=1 - --set QUIET=1 --file ); From dd35998eb910ba4fbc46bbf7dbb0487dad701ff5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 21:35:42 +0000 Subject: [PATCH 0136/1195] * Added the `--pset` option to `pg_prove`, and made use of it in the `test` target so that it is always properly toggled. * Removed the `--pset null=[NULL]` call in `pg_prove`, since users can now use `--pset` to do it themselves. --- Makefile | 2 +- README.pgtap | 1 + bin/pg_prove | 27 ++++++++++++++++++++++++--- sql/pg73.sql.in | 3 ++- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 0111d2a2a9cd..d1eda04f1723 100644 --- a/Makefile +++ b/Makefile @@ -88,4 +88,4 @@ endif # In addition to installcheck, one can also run the tests through pg_prove. test: - ./bin/pg_prove $(TESTS) + ./bin/pg_prove --pset tuples_only=1 $(TESTS) diff --git a/README.pgtap b/README.pgtap index d71458ad309e..1db26de03570 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1236,6 +1236,7 @@ To Do * Add `todo_start()` and `todo_end()` (and `in_todo()`). * Add `note()`? * Add `can_ok()`. +* Remove creation of plpgsql in the tests. Require it in template 1, instead. Suported Versions ----------------- diff --git a/bin/pg_prove b/bin/pg_prove index cf0900730aa1..f56662f2148a 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -18,6 +18,7 @@ Getopt::Long::GetOptions( 'username|U=s' => \$opts->{username}, 'host|h=s' => \$opts->{host}, 'port|p=s' => \$opts->{port}, + 'pset|P=s%' => \$opts->{pset}, 'timer|t' => \$opts->{timer}, 'color|c!' => \$opts->{color}, 'verbose|v+' => \$opts->{verbose}, @@ -56,18 +57,26 @@ for (qw(username host port dbname)) { push @command, "--$_" => $opts->{$_} if defined $opts->{$_} } +# We can't use --tuples-only because then it doesn't allow --pset to toggle it +# properly. :-( push @command, qw( --no-psqlrc --no-align - --tuples-only --quiet --pset pager= - --pset null=[NULL] + --pset tuples_only=true --set ON_ERROR_ROLLBACK=1 --set ON_ERROR_STOP=1 - --file ); +if (my $pset = $opts->{pset}) { + while (my ($k, $v) = each %{ $pset }) { + push @command, '--pset', "$k=$v"; + } +} + +push @command, '--file'; + TAP::Harness->new({ verbosity => $opts->{verbose} || $ENV{TEST_VERBOSE}, timer => $opts->{timer}, @@ -107,6 +116,7 @@ The test scripts should be a series of SQL statements -h --help Print a usage statement and exit. -m --man Print the complete documentation and exit. -v --version Print the version number and exit. + -P --pset OPTION=VALUE Set psql printing option. =head1 Options Details @@ -165,6 +175,17 @@ the server is listening for connections. Defaults to the value of the C<$PGPORT> environment variable or, if not set, to the port specified at compile time, usually 5432. +=item C<-P> + +=item C<--pset> + + pg_prove --pset tuples_only=0 + pg_prove -P null=[NULL] + +Specifies printing options in the style of C<\pset> in the C program. +See L for details +on the supported options. + =item C<-v> =item C<--verbose> diff --git a/sql/pg73.sql.in b/sql/pg73.sql.in index 3b5ca7f8443c..5906a84b8a2e 100644 --- a/sql/pg73.sql.in +++ b/sql/pg73.sql.in @@ -2,7 +2,7 @@ \set QUIET 1 -- --- Tests for pgTAP on PostgreSQL 7.3. +-- Tests for pgTAP. -- -- -- $Id$ @@ -11,6 +11,7 @@ \pset format unaligned \pset tuples_only true \pset pager + -- Create plpgsql if it's not already there. SET client_min_messages = fatal; \set ON_ERROR_STOP off From 62d350bdde33501d81557cbb57fffefeea4d78c0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 21:47:16 +0000 Subject: [PATCH 0137/1195] Now properly having `pg_regress` load plpgsql before running the tests. --- Makefile | 1 + README.pgtap | 1 - sql/check.sql.in | 5 ----- sql/cmpok.sql.in | 5 ----- sql/coltap.sql.in | 5 ----- sql/fktap.sql.in | 5 ----- sql/hastap.sql.in | 5 ----- sql/istap.sql.in | 5 ----- sql/matching.sql.in | 5 ----- sql/moretap.sql.in | 5 ----- sql/pg73.sql.in | 5 ----- sql/pktap.sql.in | 5 ----- sql/throwtap.sql.in | 5 ----- sql/todotap.sql.in | 5 ----- sql/unique.sql.in | 5 ----- 15 files changed, 1 insertion(+), 66 deletions(-) diff --git a/Makefile b/Makefile index d1eda04f1723..17e71b168494 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ MODULES = pgtap DOCS = README.pgtap SCRIPTS = bin/pg_prove REGRESS = $(patsubst sql/%.sql,%,$(TESTS)) +REGRESS_OPTS = --load-language plpgsql ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/README.pgtap b/README.pgtap index 1db26de03570..d71458ad309e 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1236,7 +1236,6 @@ To Do * Add `todo_start()` and `todo_end()` (and `in_todo()`). * Add `note()`? * Add `can_ok()`. -* Remove creation of plpgsql in the tests. Require it in template 1, instead. Suported Versions ----------------- diff --git a/sql/check.sql.in b/sql/check.sql.in index 33800ccb9f4e..d57b5e8f30e6 100644 --- a/sql/check.sql.in +++ b/sql/check.sql.in @@ -12,11 +12,6 @@ \pset tuples_only true \pset pager --- Create plpgsql if it's not already there. -SET client_min_messages = fatal; -\set ON_ERROR_STOP off -CREATE LANGUAGE plpgsql; - -- Keep things quiet. SET client_min_messages = warning; diff --git a/sql/cmpok.sql.in b/sql/cmpok.sql.in index 69b1450aa19d..c0108a0a2b15 100644 --- a/sql/cmpok.sql.in +++ b/sql/cmpok.sql.in @@ -12,11 +12,6 @@ \pset tuples_only true \pset pager --- Create plpgsql if it's not already there. -SET client_min_messages = fatal; -\set ON_ERROR_STOP off -CREATE LANGUAGE plpgsql; - -- Keep things quiet. SET client_min_messages = warning; diff --git a/sql/coltap.sql.in b/sql/coltap.sql.in index 851a80e0c5f2..8dac8d88136c 100644 --- a/sql/coltap.sql.in +++ b/sql/coltap.sql.in @@ -12,11 +12,6 @@ \pset tuples_only true \pset pager --- Create plpgsql if it's not already there. -SET client_min_messages = fatal; -\set ON_ERROR_STOP off -CREATE LANGUAGE plpgsql; - -- Keep things quiet. SET client_min_messages = warning; diff --git a/sql/fktap.sql.in b/sql/fktap.sql.in index 60fd17614493..bba2519af9fb 100644 --- a/sql/fktap.sql.in +++ b/sql/fktap.sql.in @@ -12,11 +12,6 @@ \pset tuples_only true \pset pager --- Create plpgsql if it's not already there. -SET client_min_messages = fatal; -\set ON_ERROR_STOP off -CREATE LANGUAGE plpgsql; - -- Keep things quiet. SET client_min_messages = warning; diff --git a/sql/hastap.sql.in b/sql/hastap.sql.in index f2ee154f8353..1a27cd2ecc82 100644 --- a/sql/hastap.sql.in +++ b/sql/hastap.sql.in @@ -12,11 +12,6 @@ \pset tuples_only true \pset pager --- Create plpgsql if it's not already there. -SET client_min_messages = fatal; -\set ON_ERROR_STOP off -CREATE LANGUAGE plpgsql; - -- Keep things quiet. SET client_min_messages = warning; diff --git a/sql/istap.sql.in b/sql/istap.sql.in index 6a9147dc1a1a..c88ae9652213 100644 --- a/sql/istap.sql.in +++ b/sql/istap.sql.in @@ -12,11 +12,6 @@ \pset tuples_only true \pset pager --- Create plpgsql if it's not already there. -SET client_min_messages = fatal; -\set ON_ERROR_STOP off -CREATE LANGUAGE plpgsql; - -- Keep things quiet. SET client_min_messages = warning; diff --git a/sql/matching.sql.in b/sql/matching.sql.in index 6cba0707328d..144505fc239f 100644 --- a/sql/matching.sql.in +++ b/sql/matching.sql.in @@ -12,11 +12,6 @@ \pset tuples_only true \pset pager --- Create plpgsql if it's not already there. -SET client_min_messages = fatal; -\set ON_ERROR_STOP off -CREATE LANGUAGE plpgsql; - -- Keep things quiet. SET client_min_messages = warning; diff --git a/sql/moretap.sql.in b/sql/moretap.sql.in index c9d2aed4e8bf..c31a1b31eccc 100644 --- a/sql/moretap.sql.in +++ b/sql/moretap.sql.in @@ -12,11 +12,6 @@ \pset tuples_only true \pset pager --- Create plpgsql if it's not already there. -SET client_min_messages = fatal; -\set ON_ERROR_STOP off -CREATE LANGUAGE plpgsql; - -- Keep things quiet. SET client_min_messages = warning; diff --git a/sql/pg73.sql.in b/sql/pg73.sql.in index 5906a84b8a2e..1e370591cafd 100644 --- a/sql/pg73.sql.in +++ b/sql/pg73.sql.in @@ -12,11 +12,6 @@ \pset tuples_only true \pset pager --- Create plpgsql if it's not already there. -SET client_min_messages = fatal; -\set ON_ERROR_STOP off -CREATE LANGUAGE plpgsql; - -- Keep things quiet. SET client_min_messages = warning; diff --git a/sql/pktap.sql.in b/sql/pktap.sql.in index 6d0fba349e6c..ec67a1095d8c 100644 --- a/sql/pktap.sql.in +++ b/sql/pktap.sql.in @@ -12,11 +12,6 @@ \pset tuples_only true \pset pager --- Create plpgsql if it's not already there. -SET client_min_messages = fatal; -\set ON_ERROR_STOP off -CREATE LANGUAGE plpgsql; - -- Keep things quiet. SET client_min_messages = warning; diff --git a/sql/throwtap.sql.in b/sql/throwtap.sql.in index acd480f2c70f..456bc35e1179 100644 --- a/sql/throwtap.sql.in +++ b/sql/throwtap.sql.in @@ -12,11 +12,6 @@ \pset tuples_only true \pset pager --- Create plpgsql if it's not already there. -SET client_min_messages = fatal; -\set ON_ERROR_STOP off -CREATE LANGUAGE plpgsql; - -- Keep things quiet. SET client_min_messages = warning; diff --git a/sql/todotap.sql.in b/sql/todotap.sql.in index 67ccb2ef1681..a842202b12f7 100644 --- a/sql/todotap.sql.in +++ b/sql/todotap.sql.in @@ -12,11 +12,6 @@ \pset tuples_only true \pset pager --- Create plpgsql if it's not already there. -SET client_min_messages = fatal; -\set ON_ERROR_STOP off -CREATE LANGUAGE plpgsql; - -- Keep things quiet. SET client_min_messages = warning; diff --git a/sql/unique.sql.in b/sql/unique.sql.in index 7c365a3812fb..c8aa8b7a8c97 100644 --- a/sql/unique.sql.in +++ b/sql/unique.sql.in @@ -12,11 +12,6 @@ \pset tuples_only true \pset pager --- Create plpgsql if it's not already there. -SET client_min_messages = fatal; -\set ON_ERROR_STOP off -CREATE LANGUAGE plpgsql; - -- Keep things quiet. SET client_min_messages = warning; From 556bbd9a55a3c5ad0868569e5da37eb1f518686e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 23:25:25 +0000 Subject: [PATCH 0138/1195] * Updated installation docs. * Updated the uninstall script. --- README.pgtap | 59 +++++++++-------- uninstall_pgtap.sql.in | 145 +++++++++++++++++++++-------------------- 2 files changed, 105 insertions(+), 99 deletions(-) diff --git a/README.pgtap b/README.pgtap index d71458ad309e..298f2f52bfa3 100644 --- a/README.pgtap +++ b/README.pgtap @@ -10,10 +10,9 @@ Installation For the impatient, to install pgTAP into a PostgreSQL database, just do this: - make - make test PGDATABASE=template1 - make install - make installcheck + env USE_PGXS=1 make + env USE_PGXS=1 make install + env USE_PGXS=1 make installcheck If you encounter an error such as: @@ -22,10 +21,9 @@ If you encounter an error such as: You need to use GNU make, which may well be installed on your system as 'gmake': - gmake - gmake test PGDATABASE=template1 - gmake install - gmake installcheck + env USE_PGXS=1 gmake + env USE_PGXS=1 gmake install + env USE_PGXS=1 gmake installcheck If you encounter an error such as: @@ -36,10 +34,15 @@ package management system such as RPM to install PostgreSQL, be sure that the `-devel` package is also installed. If necessary, add the path to `pg_config` to your `$PATH` environment variable: - env PATH=$PATH:/path/to/pgsql/bin make && make test && make install + env PATH=$PATH:/path/to/pgsql/bin USE_PGXS=1 make && make test && make install And finally, if all that fails, copy the entire distribution directory to the -`contrib/` subdirectory of the PostgreSQL source code and try it there. +`contrib/` subdirectory of the PostgreSQL source code and try it there without +the `USE_PGXS` environment variable: + + make + make install + make installcheck If you want to schema-qualify pgTAP (that is, install all of its functions into their own schema), set the `$TAPSCHEMA` environment variable to the name @@ -47,18 +50,22 @@ of the schema you'd like, for example: make TAPSCHEMA=tap -The `test` target uses the included `pg_prove` Perl program to do its testing, -which requires that TAP::Harness, included in -[Test::Harness](http://search.cpan.org/dist/Test-Harness/ "Test::Harness on CPAN") 3.x. `pg_prove` supports a number of environment variables that you -might need to use, including all the usual PostgreSQL client environment -variables: +In addition to the PostgreSQL-standared `installcheck` target, the `test` +target uses the included `pg_prove` Perl program to do its testing, which +requires that TAP::Harness, included in +[Test::Harness](http://search.cpan.org/dist/Test-Harness/ "Test::Harness on +CPAN") 3.x. You'll need to make sure that you use a database with PL/pgSQL +loaded, or else the tests wont't work. `pg_prove` supports a number of +environment variables that you might need to use, including all the usual +PostgreSQL client environment variables: * `$PGDATABASE` * `$PGHOST` * `$PGPORT` * `$PGUSER` -Once pgTAP has been built and tested, you can install it into a database: +Once pgTAP has been built and tested, you can install it into a +PL/pgSQL-enabled database: psql -d dbname -f pgtap.sql @@ -120,15 +127,7 @@ Here's an example: ROLLBACK; Of course, if you already have the pgTAP functions in your testing database, -you should skip `\i pgtap.sql` at the beginning of the script. If your -database does not already have PL/pgSQL installed, you'll need to add it to -the database before you load pgTAP. Just add these lines after the `\pset` -section and before the `\set` section: - - -- Create plpgsql if it's not already there. - SET client_min_messages = fatal; - \set ON_ERROR_STOP off - CREATE LANGUAGE plpgsql; +you should skip `\i pgtap.sql` at the beginning of the script. Now you're ready to run your test script! @@ -140,6 +139,9 @@ You'll need to have all of those variables in the script to ensure that the output is proper TAP and that all changes are rolled back -- including the loading of the test functions -- in the event of an uncaught exception. +Using `pg_prove` +---------------- + Or save yourself some effort -- and run a batch of tests scripts at once -- by using `pg_prove`. If you're not relying on `installcheck`, your test scripts can be a lot less verbose; you don't need to set all the extra variables, @@ -161,7 +163,7 @@ because `pg_prove` takes care of that for you: Now run the tests. Here's what it looks like when the pgTAP tests are run with `pg_prove`: - % pg_prove -d try sql/*.sql + % pg_prove -U postgres sql/*.sql sql/coltap.....ok sql/hastap.....ok sql/moretap....ok @@ -171,7 +173,8 @@ Now run the tests. Here's what it looks like when the pgTAP tests are run with Files=5, Tests=216, 1 wallclock secs ( 0.06 usr 0.02 sys + 0.08 cusr 0.07 csys = 0.23 CPU) Result: PASS -Yep, that's all there is to it. +Yep, that's all there is to it. Call `pg_prove --help` to see other supported +options, and `pg_prove --man` to see its entire documentation. Description =========== @@ -1236,6 +1239,7 @@ To Do * Add `todo_start()` and `todo_end()` (and `in_todo()`). * Add `note()`? * Add `can_ok()`. +* Don't install test scripts. Suported Versions ----------------- @@ -1243,6 +1247,7 @@ Suported Versions pgTAP has been tested on the following builds of PostgreSQL: * PostgreSQL 8.3.1 on i386-apple-darwin9.2.2 +* PostgreSQL 8.0.17 on i686-apple-darwin9.4.0 If you know of others, please submit them! Use `psql -d template1 -c 'SELECT VERSION()'` to get the formal build version and OS. diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 01ab537b3ba5..d8ea6dbe9d5c 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,84 +1,83 @@ -- $Id$ +DROP VIEW pg_all_foreign_keys; DROP FUNCTION check_test( TEXT, BOOLEAN ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); -DROP FUNCTION fk_ok ( TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION fk_ok ( TEXT, TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION fk_ok ( TEXT, TEXT, TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION fk_ok ( TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION fk_ok ( TEXT, TEXT[], TEXT, TEXT[] ); -DROP FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[] ); -DROP FUNCTION fk_ok ( TEXT, TEXT[], TEXT, TEXT[], TEXT ); -DROP FUNCTION fk_ok ( TEXT, TEXT, TEXT[], TEXT, TEXT, TEXT[], TEXT ); -DROP FUNCTION col_has_check ( TEXT, TEXT ); -DROP FUNCTION col_has_check ( TEXT, TEXT, TEXT ); -DROP FUNCTION col_has_check ( TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION col_has_check ( TEXT, TEXT[] ); -DROP FUNCTION col_has_check ( TEXT, TEXT[], TEXT ); -DROP FUNCTION col_has_check ( TEXT, TEXT, TEXT[], TEXT ); -DROP FUNCTION has_check ( TEXT ); -DROP FUNCTION has_check ( TEXT, TEXT ); -DROP FUNCTION has_check ( TEXT, TEXT, TEXT ); -DROP FUNCTION col_is_unique ( TEXT, TEXT ); -DROP FUNCTION col_is_unique ( TEXT, TEXT, TEXT ); -DROP FUNCTION col_is_unique ( TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION col_is_unique ( TEXT, TEXT[] ); -DROP FUNCTION col_is_unique ( TEXT, TEXT[], TEXT ); -DROP FUNCTION col_is_unique ( TEXT, TEXT, TEXT[], TEXT ); +DROP FUNCTION fk_ok ( NAME, NAME, NAME, NAME ); +DROP FUNCTION fk_ok ( NAME, NAME, NAME, NAME, TEXT ); +DROP FUNCTION fk_ok ( NAME, NAME, NAME, NAME, NAME, TEXT ); +DROP FUNCTION fk_ok ( NAME, NAME, NAME, NAME, NAME, NAME, TEXT ); +DROP FUNCTION fk_ok ( NAME, NAME[], NAME, NAME[] ); +DROP FUNCTION fk_ok ( NAME, NAME, NAME[], NAME, NAME, NAME[] ); +DROP FUNCTION fk_ok ( NAME, NAME[], NAME, NAME[], TEXT ); +DROP FUNCTION fk_ok ( NAME, NAME, NAME[], NAME, NAME, NAME[], TEXT ); +DROP FUNCTION col_has_check ( NAME, NAME ); +DROP FUNCTION col_has_check ( NAME, NAME, TEXT ); +DROP FUNCTION col_has_check ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION col_has_check ( NAME, NAME[] ); +DROP FUNCTION col_has_check ( NAME, NAME[], TEXT ); +DROP FUNCTION col_has_check ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION has_check ( NAME ); +DROP FUNCTION has_check ( NAME, TEXT ); +DROP FUNCTION has_check ( NAME, NAME, TEXT ); +DROP FUNCTION col_is_unique ( NAME, NAME ); +DROP FUNCTION col_is_unique ( NAME, NAME, TEXT ); +DROP FUNCTION col_is_unique ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION col_is_unique ( NAME, NAME[] ); +DROP FUNCTION col_is_unique ( NAME, NAME[], TEXT ); +DROP FUNCTION col_is_unique ( NAME, NAME, NAME[], TEXT ); DROP FUNCTION has_unique ( TEXT ); DROP FUNCTION has_unique ( TEXT, TEXT ); DROP FUNCTION has_unique ( TEXT, TEXT, TEXT ); -DROP FUNCTION col_is_fk ( TEXT, TEXT ); -DROP FUNCTION col_is_fk ( TEXT, TEXT, TEXT ); -DROP FUNCTION col_is_fk ( TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION col_is_fk ( TEXT, TEXT[] ); -DROP FUNCTION col_is_fk ( TEXT, TEXT[], TEXT ); -DROP FUNCTION col_is_fk ( TEXT, TEXT, TEXT[], TEXT ); -DROP FUNCTION has_fk ( TEXT ); -DROP FUNCTION has_fk ( TEXT, TEXT ); -DROP FUNCTION has_fk ( TEXT, TEXT, TEXT ); -DROP FUNCTION col_is_pk ( TEXT, TEXT ); -DROP FUNCTION col_is_pk ( TEXT, TEXT, TEXT ); -DROP FUNCTION col_is_pk ( TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION col_is_pk ( TEXT, TEXT[] ); -DROP FUNCTION col_is_pk ( TEXT, TEXT[], TEXT ); -DROP FUNCTION col_is_pk ( TEXT, TEXT, TEXT[], TEXT ); -DROP FUNCTION _pk_tab_for_fk ( TEXT, TEXT[], CHAR ); -DROP FUNCTION _pk_tab_for_fk ( TEXT, TEXT, TEXT[], CHAR ); -DROP FUNCTION _pk_cols_for_fk ( TEXT, TEXT[], CHAR ); -DROP FUNCTION _pk_cols_for_fk ( TEXT, TEXT, TEXT[], CHAR ); -DROP FUNCTION _ckeys ( TEXT, CHAR ); -DROP FUNCTION _ckeys ( TEXT, TEXT, CHAR ); -DROP FUNCTION has_pk ( TEXT ); -DROP FUNCTION has_pk ( TEXT, TEXT ); -DROP FUNCTION has_pk ( TEXT, TEXT, TEXT ); -DROP FUNCTION _hasc ( TEXT, CHAR ); -DROP FUNCTION _hasc ( TEXT, TEXT, CHAR ); -DROP FUNCTION col_default_is ( TEXT, TEXT, TEXT ); -DROP FUNCTION col_default_is ( TEXT, TEXT, anyelement, TEXT ); -DROP FUNCTION col_default_is ( TEXT, TEXT, TEXT, anyelement, TEXT ); +DROP FUNCTION col_is_fk ( NAME, NAME ); +DROP FUNCTION col_is_fk ( NAME, NAME, TEXT ); +DROP FUNCTION col_is_fk ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION col_is_fk ( NAME, NAME[] ); +DROP FUNCTION col_is_fk ( NAME, NAME[], TEXT ); +DROP FUNCTION col_is_fk ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION has_fk ( NAME ); +DROP FUNCTION has_fk ( NAME, TEXT ); +DROP FUNCTION has_fk ( NAME, NAME, TEXT ); +DROP FUNCTION col_is_pk ( NAME, NAME ); +DROP FUNCTION col_is_pk ( NAME, NAME, TEXT ); +DROP FUNCTION col_is_pk ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION col_is_pk ( NAME, NAME[] ); +DROP FUNCTION col_is_pk ( NAME, NAME[], TEXT ); +DROP FUNCTION col_is_pk ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _pg_sv_table_accessible(oid,oid) RETURNS BOOLEAN; +DROP FUNCTION _pg_sv_column_array(oid, smallint[]) RETURNS NAME[]; +DROP FUNCTION _ckeys ( NAME, CHAR ); +DROP FUNCTION _ckeys ( NAME, NAME, CHAR ); +DROP FUNCTION has_pk ( NAME ); +DROP FUNCTION has_pk ( NAME, TEXT ); +DROP FUNCTION has_pk ( NAME, NAME, TEXT ); +DROP FUNCTION _hasc ( NAME, CHAR ); +DROP FUNCTION _hasc ( NAME, NAME, CHAR ); +DROP FUNCTION col_default_is ( NAME, NAME, TEXT ); +DROP FUNCTION col_default_is ( NAME, NAME, anyelement, TEXT ); +DROP FUNCTION col_default_is ( NAME, NAME, NAME, anyelement, TEXT ); DROP FUNCTION _def_is( TEXT, anyelement, TEXT ); -DROP FUNCTION col_type_is ( TEXT, TEXT, TEXT ); -DROP FUNCTION col_type_is ( TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION col_type_is ( TEXT, TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION col_is_null ( TEXT, TEXT ); -DROP FUNCTION col_is_null ( TEXT, TEXT, TEXT ); -DROP FUNCTION col_is_null ( TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION col_not_null ( TEXT, TEXT ); -DROP FUNCTION col_not_null ( TEXT, TEXT, TEXT ); -DROP FUNCTION col_not_null ( TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION _col_is_null ( TEXT, TEXT, TEXT, bool ); -DROP FUNCTION _col_is_null ( TEXT, TEXT, TEXT, TEXT, bool ); -DROP FUNCTION has_column ( TEXT, TEXT ); -DROP FUNCTION has_column ( TEXT, TEXT, TEXT ); -DROP FUNCTION has_column ( TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION has_view ( TEXT ); -DROP FUNCTION has_view ( TEXT, TEXT ); -DROP FUNCTION has_view ( TEXT, TEXT, TEXT ); -DROP FUNCTION has_table ( TEXT ); -DROP FUNCTION has_table ( TEXT, TEXT ); -DROP FUNCTION has_table ( TEXT, TEXT, TEXT ); +DROP FUNCTION col_type_is ( NAME, NAME, TEXT ); +DROP FUNCTION col_type_is ( NAME, NAME, TEXT, TEXT ); +DROP FUNCTION col_type_is ( NAME, NAME, NAME, TEXT, TEXT ); +DROP FUNCTION col_is_null ( NAME, NAME ); +DROP FUNCTION col_is_null ( NAME, NAME, NAME ); +DROP FUNCTION col_is_null ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION col_not_null ( NAME, NAME ); +DROP FUNCTION col_not_null ( NAME, NAME, TEXT ); +DROP FUNCTION col_not_null ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION _col_is_null ( NAME, NAME, TEXT, bool ); +DROP FUNCTION _col_is_null ( NAME, NAME, NAME, TEXT, bool ); +DROP FUNCTION has_column ( NAME, NAME ); +DROP FUNCTION has_column ( NAME, NAME, TEXT ); +DROP FUNCTION has_column ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION has_view ( NAME ); +DROP FUNCTION has_view ( NAME, TEXT ); +DROP FUNCTION has_view ( NAME, NAME, TEXT ); +DROP FUNCTION has_table ( NAME ); +DROP FUNCTION has_table ( NAME, TEXT ); +DROP FUNCTION has_table ( NAME, NAME, TEXT ); DROP FUNCTION lives_ok ( TEXT ); DROP FUNCTION lives_ok ( TEXT, TEXT ); DROP FUNCTION throws_ok ( TEXT, int4 ); @@ -86,6 +85,8 @@ DROP FUNCTION throws_ok ( TEXT, int4, TEXT ); DROP FUNCTION throws_ok ( TEXT ); DROP FUNCTION throws_ok ( TEXT, CHAR(5) ); DROP FUNCTION throws_ok ( TEXT, CHAR(5), TEXT ); +DROP FUNCTION skip ( text ); +DROP FUNCTION skip ( why text, how_many int ); DROP FUNCTION _todo(); DROP FUNCTION todo ( why text, how_many int ); DROP FUNCTION fail (); From 3c1e406beda39117f7ed7707088bef678cbbe646 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 23:30:20 +0000 Subject: [PATCH 0139/1195] Updated docs to reflect that `throws_ok()` and `lives_ok()` are not supported under 8.0. --- README.pgtap | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/README.pgtap b/README.pgtap index 298f2f52bfa3..a1d5e6ae56cc 100644 --- a/README.pgtap +++ b/README.pgtap @@ -464,6 +464,7 @@ error-prone as you think they should be. When you want to make sure that an exception is thrown by PostgreSQL under certain circumstances, use `throws_ok()` to test for those circumstances. +Supported by 8.1 or higher. The first argument should be a string representing the query to be executed. `throws_ok()` will use the PL/pgSQL `EXECUTE` statement to execute it and @@ -499,8 +500,11 @@ Idea borrowed from the Test::Exception Perl module. ); The inverse of `throws_ok()`, these functions test to ensure that a SQL -statement does *not* throw an exception. Should a `lives_ok()` test fail, it -produces appropriate diagnostic messages. For example: +statement does *not* throw an exception. +Supported by 8.1 or higher. + +Should a `lives_ok()` test fail, it produces appropriate diagnostic messages. +For example: not ok 85 - simple success # Failed test "simple success" @@ -1230,6 +1234,13 @@ A number of casts are added to increase compatibility. The casts are: An `=` operator is also added that compares `name[]` values. +8.0 and Lower +------------- + +A patch is applied that changes how some of the test functions are written; +otherwise, all is the same as for 8.2 Do note, however, that the `throws_ok()` +and `lives_ok()` functions do not work under 8.0. Don't even use them there. + To Do ----- From b69864358653c0c3b92d03a2e117891b99abb993 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 23:31:02 +0000 Subject: [PATCH 0140/1195] Keyword. --- sql/cmpok.sql.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/cmpok.sql.in b/sql/cmpok.sql.in index c0108a0a2b15..a7258eeedcc8 100644 --- a/sql/cmpok.sql.in +++ b/sql/cmpok.sql.in @@ -5,7 +5,7 @@ -- Tests for pgTAP. -- -- --- $Id: check.sql 4226 2008-08-23 00:21:03Z david $ +-- $Id$ -- Format the output for nice TAP. \pset format unaligned From 2c70beee0b46ced26ad426fa4c8fc0ec726a075a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 23:51:13 +0000 Subject: [PATCH 0141/1195] * No longer installing the test scripts. * Updated the docs a bit more. Now have to specify `$TAPSCHEMA` for the `installcheck` and `test` targets, too. --- Makefile | 8 ++++++-- README.pgtap | 50 ++++++++++++++++++++++++++++++++------------------ 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 17e71b168494..dd172413be16 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ # $Id$ TESTS = $(patsubst %.sql.in,%.sql,$(wildcard sql/*.sql.in)) -DATA_built = pgtap.sql uninstall_pgtap.sql $(TESTS) +EXTRA_CLEAN = $(TESTS) +DATA_built = pgtap.sql uninstall_pgtap.sql MODULES = pgtap DOCS = README.pgtap SCRIPTS = bin/pg_prove @@ -87,6 +88,9 @@ ifneq ($(PGVER_MINOR), 3) endif endif +# Make sure that we build the regression tests. +installcheck: $(TESTS) + # In addition to installcheck, one can also run the tests through pg_prove. -test: +test: $(TESTS) ./bin/pg_prove --pset tuples_only=1 $(TESTS) diff --git a/README.pgtap b/README.pgtap index a1d5e6ae56cc..d0e7247e9355 100644 --- a/README.pgtap +++ b/README.pgtap @@ -10,9 +10,9 @@ Installation For the impatient, to install pgTAP into a PostgreSQL database, just do this: - env USE_PGXS=1 make - env USE_PGXS=1 make install - env USE_PGXS=1 make installcheck + make USE_PGXS=1 + make install USE_PGXS=1 + make installcheck USE_PGXS=1 If you encounter an error such as: @@ -21,9 +21,9 @@ If you encounter an error such as: You need to use GNU make, which may well be installed on your system as 'gmake': - env USE_PGXS=1 gmake - env USE_PGXS=1 gmake install - env USE_PGXS=1 gmake installcheck + gmake USE_PGXS=1 + gmake install USE_PGXS=1 + gmake installcheck USE_PGXS=1 If you encounter an error such as: @@ -38,17 +38,22 @@ to your `$PATH` environment variable: And finally, if all that fails, copy the entire distribution directory to the `contrib/` subdirectory of the PostgreSQL source code and try it there without -the `USE_PGXS` environment variable: +the `$USE_PGXS` variable: make make install make installcheck If you want to schema-qualify pgTAP (that is, install all of its functions -into their own schema), set the `$TAPSCHEMA` environment variable to the name -of the schema you'd like, for example: +into their own schema), set the `$TAPSCHEMA` variable to the name of the +schema you'd like, for example: make TAPSCHEMA=tap + make install + make installcheck TAPSCHEMA=tap + +Testing pgTAP with pgTAP +------------------------ In addition to the PostgreSQL-standared `installcheck` target, the `test` target uses the included `pg_prove` Perl program to do its testing, which @@ -64,6 +69,16 @@ PostgreSQL client environment variables: * `$PGPORT` * `$PGUSER` +You can use it to run the test suite like so: + + make test USE_PGXS=1 TAPSCHEMA=tap PGUSER=postgres + +Of course, if you're running the tests from the `contrib/` directory, you +should omit the `USE_PGXS` variable. + +Adding pgTAP to a Database +-------------------------- + Once pgTAP has been built and tested, you can install it into a PL/pgSQL-enabled database: @@ -84,14 +99,14 @@ directory output by `pg_config --sharedir`. So you can always do this: psql -d template1 -f `pg_config --sharedir`/contrib/pgtap.sql -But do be aware that, if you've specified a schema using `$TAPSCHEMA`, it will -always be created and the pgTAP functions placed in it. +But do be aware that, if you've specified a schema using `$TAPSCHEMA`, that +schema will always be created and the pgTAP functions placed in it. -Running pgTAP Tests -=================== +pgTAP Test Scripts +================== -You can also distribute `pgtap.sql` with any PostgreSQL distribution, such as -a custom data type. For such a case, if your users want to run your test suite +You can distribute `pgtap.sql` with any PostgreSQL distribution, such as a +custom data type. For such a case, if your users want to run your test suite using PostgreSQL's standard `installcheck` make target, just be sure to set varibles to keep the tests quiet, start a transaction, load the functions in your test script, and then rollback the transaction at the end of the script. @@ -176,7 +191,7 @@ Now run the tests. Here's what it looks like when the pgTAP tests are run with Yep, that's all there is to it. Call `pg_prove --help` to see other supported options, and `pg_prove --man` to see its entire documentation. -Description +Using pgTAP =========== The purpose of pgTAP is to provide a wide range of testing utilities that @@ -1250,14 +1265,13 @@ To Do * Add `todo_start()` and `todo_end()` (and `in_todo()`). * Add `note()`? * Add `can_ok()`. -* Don't install test scripts. Suported Versions ----------------- pgTAP has been tested on the following builds of PostgreSQL: -* PostgreSQL 8.3.1 on i386-apple-darwin9.2.2 +* PostgreSQL 8.3.3 on i386-apple-darwin9.4.0 * PostgreSQL 8.0.17 on i686-apple-darwin9.4.0 If you know of others, please submit them! Use From 0ab3f34c542565431f10dc02a184b5fb37bb182b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2008 23:51:49 +0000 Subject: [PATCH 0142/1195] Versions tested. --- README.pgtap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.pgtap b/README.pgtap index d0e7247e9355..f2fd2e4cad4f 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1272,6 +1272,8 @@ Suported Versions pgTAP has been tested on the following builds of PostgreSQL: * PostgreSQL 8.3.3 on i386-apple-darwin9.4.0 +* PostgreSQL 8.2.9 on i386-apple-darwin9.4.0 +* PostgreSQL 8.1.13 on i386-apple-darwin9.4.0 * PostgreSQL 8.0.17 on i686-apple-darwin9.4.0 If you know of others, please submit them! Use From bfb63e5e3927de872bf7e95cd417d1eaea61a2e2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 18 Sep 2008 00:03:08 +0000 Subject: [PATCH 0143/1195] * Incremented the version number to 0.10. * Edited `Changes`. --- Changes | 15 ++++++++++++--- README.pgtap | 3 ++- bin/pg_prove | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 65673427553b..60760345f4ad 100644 --- a/Changes +++ b/Changes @@ -1,12 +1,12 @@ Revision history for pgTAP -0.03 +0.10 - Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the output of extraneous stuff. - Added some `GRANT` statements to `plan()` in order to make it easier to run tests using roles other than the one that called `plan()`. Patch from Rod Taylor. - - Replaced a call to `lastval()` with a call to `currvall()` in order to + - Replaced a call to `lastval()` with a call to `currval()` in order to improve compatibility with PostgreSQL 8.0. Reported by David Westbrook. - Added support for TODO and SKIP tests. - Removed the few uses of named parameters and added alias names instead. @@ -22,10 +22,16 @@ Revision history for pgTAP to better support older versions of PostgreSQL. Reported by David Westbrook. - Switched from using `FOUND` to `GET DIAGNOSTICS ROW_COUNT` because the - former does not seem to b set for `EXECUTE`d SQL in older versions of + former does not seem to be set for `EXECUTE` SQL in older versions of PostgreSQL. Reported by David Westbrook. - Added tests specifically targeting PostgreSQL 7.3. From David Westbrook. + - Ported all the way back to PostgreSQL 8.0, thanks to some Makefile + hackery and a patch file. All tests pass on 8.0, 8.1, 8.2, and 8.3. + The only exception is `throws_ok()` and `lives_ok()`, which are not + supported on 8.0. Versions of PostgreSQL lower than 8.0 are not yet + supported, even though we have made some changes to simplify getting + things to work in earlier versions. - Changed "got/expected" to "have/want", following Schwern's plans for Test::Builder 2. Also changed "caught/expected" to "caught/wanted" for nice parity when outputting diagnostics for exception testing. @@ -51,6 +57,9 @@ Revision history for pgTAP - As in Test::Builder 0.81_01, changed the message for extra tests run to show the number of tests run rather than the number extra to avoid the user having to do mental math. + - The regression test files are now processed by `make installcheck` and + `make test` so that the schema can be properly set, if pgTAP is built + with a schema. 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. diff --git a/README.pgtap b/README.pgtap index f2fd2e4cad4f..b898e40bfc2d 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.03 +pgTAP 0.10 ========== pgTAP is a collection of TAP-emitting unit testing functions written in @@ -1265,6 +1265,7 @@ To Do * Add `todo_start()` and `todo_end()` (and `in_todo()`). * Add `note()`? * Add `can_ok()`. +* Finish porting tests to use `check_test()`. Suported Versions ----------------- diff --git a/bin/pg_prove b/bin/pg_prove index f56662f2148a..8e634fa9f760 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -6,7 +6,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.03'; +our $VERSION = '0.10'; Getopt::Long::Configure (qw(bundling)); From 74db83b7107500769f8def8718854a910b9bbe29 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 18 Sep 2008 00:05:34 +0000 Subject: [PATCH 0144/1195] markdown errors. --- README.pgtap | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.pgtap b/README.pgtap index b898e40bfc2d..e85841db185c 100644 --- a/README.pgtap +++ b/README.pgtap @@ -805,14 +805,14 @@ simply list all of the foreign key constraint columns, like so: # {thingy_id} # {surname,given_name} -### fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description ) ### -### fk_ok( fk_schema, fk_table, fk_column[], fk_schema, pk_table, pk_column[] ) ### -### fk_ok( fk_table, fk_column[], pk_table, pk_column[], description ) ### -### fk_ok( fk_table, fk_column[], pk_table, pk_column[] ) ### -### fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column, description ) ### -### fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column ) ### -### fk_ok( fk_table, fk_column, pk_table, pk_column, description ) ### -### fk_ok( fk_table, fk_column, pk_table, pk_column ) ### +### `fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description )` ### +### `fk_ok( fk_schema, fk_table, fk_column[], fk_schema, pk_table, pk_column[] )` ### +### `fk_ok( fk_table, fk_column[], pk_table, pk_column[], description )` ### +### `fk_ok( fk_table, fk_column[], pk_table, pk_column[] )` ### +### `fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column, description )` ### +### `fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column )` ### +### `fk_ok( fk_table, fk_column, pk_table, pk_column, description )` ### +### `fk_ok( fk_table, fk_column, pk_table, pk_column )` ### SELECT fk_ok( 'myschema', @@ -1088,10 +1088,10 @@ Testing Test Functions Now you've written your test function. So how do you test it? Why, with this handy-dandy test function! -### check_test( test_output, is_ok, name, want_description, want_diag ) ### -### check_test( test_output, is_ok, name, want_description ) ### -### check_test( test_output, is_ok, name ) ### -### check_test( test_output, is_ok ) ### +### `check_test( test_output, is_ok, name, want_description, want_diag )` ### +### `check_test( test_output, is_ok, name, want_description )` ### +### `check_test( test_output, is_ok, name )` ### +### `check_test( test_output, is_ok )` ### SELECT * FROM check_test( lc_eq('This', 'THAT', 'not eq'), From 7c1f145e8b78595ae7f813e8ad7e70f5284d261d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 18 Sep 2008 22:34:30 +0000 Subject: [PATCH 0145/1195] Typo. --- README.pgtap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index e85841db185c..146d6eec626d 100644 --- a/README.pgtap +++ b/README.pgtap @@ -648,7 +648,7 @@ If the test fails, it will output useful diagnostics. For example this test: Will produce something like this: - # Failed test 138: "Column pg_catalog.pg_type.typname should be type text + # Failed test 138: "Column pg_catalog.pg_type.typname should be type text" # have: name # want: text From 693bb33efe8d844b72d51896b42f8a58f5400162 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 18 Sep 2008 22:51:14 +0000 Subject: [PATCH 0146/1195] Timestamped for 0.10 release. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 60760345f4ad..d22f058535de 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,6 @@ Revision history for pgTAP -0.10 +0.10 2008-09-18T22:59:31 - Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the output of extraneous stuff. - Added some `GRANT` statements to `plan()` in order to make it easier From 3cb08b84513a84843ff7ebd5d8f1f72c6a86c67f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 18 Sep 2008 22:59:14 +0000 Subject: [PATCH 0147/1195] Incremented version number to 0.11. --- Changes | 3 ++- README.pgtap | 2 +- bin/pg_prove | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index d22f058535de..549eccbdf95e 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,7 @@ Revision history for pgTAP -0.10 2008-09-18T22:59:31 +0.11 +g0.10 2008-09-18T22:59:31 - Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the output of extraneous stuff. - Added some `GRANT` statements to `plan()` in order to make it easier diff --git a/README.pgtap b/README.pgtap index 146d6eec626d..b3706323bb51 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.10 +pgTAP 0.11 ========== pgTAP is a collection of TAP-emitting unit testing functions written in diff --git a/bin/pg_prove b/bin/pg_prove index 8e634fa9f760..cbf63b1a262c 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -6,7 +6,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.10'; +our $VERSION = '0.11'; Getopt::Long::Configure (qw(bundling)); From 5ea4e87b75ec806a7e416be48d9067e15fe8ff27 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 18 Sep 2008 22:59:34 +0000 Subject: [PATCH 0148/1195] Incremented version number to 0.11. --- Changes | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 549eccbdf95e..58d63db53fed 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,8 @@ Revision history for pgTAP 0.11 -g0.10 2008-09-18T22:59:31 + +0.10 2008-09-18T22:59:31 - Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the output of extraneous stuff. - Added some `GRANT` statements to `plan()` in order to make it easier From bf0d5a439b5718e886f9870c6a5607be41392444 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 19 Sep 2008 16:03:36 +0000 Subject: [PATCH 0149/1195] Typo spoted by Ovid. --- README.pgtap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index b3706323bb51..521098dc2288 100644 --- a/README.pgtap +++ b/README.pgtap @@ -122,7 +122,7 @@ Here's an example: \pset pager -- Revert all changes on failure. - \set ON_ERROR_ROLBACK 1 + \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true \set QUIET 1 From 0148bc4bd7ca2d3417afd9bf2ca5c9e22c4c557c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 19 Sep 2008 22:41:35 +0000 Subject: [PATCH 0150/1195] * Better testing. Just load the common stuff from a `test_setup.sql` file, and now only that file has to be processed. * Eliminated all uses of `E''` in the tests, so that they pass on 8.0 without preprocessing. --- Changes | 7 ++++ Makefile | 19 ++++------ README.pgtap | 6 ++-- sql/{check.sql.in => check.sql} | 43 ++++++++-------------- sql/{cmpok.sql.in => cmpok.sql} | 25 +------------ sql/{coltap.sql.in => coltap.sql} | 46 +++++++++--------------- sql/{fktap.sql.in => fktap.sql} | 25 +------------ sql/{hastap.sql.in => hastap.sql} | 52 ++++++++++----------------- sql/{istap.sql.in => istap.sql} | 46 ++++++++++-------------- sql/{matching.sql.in => matching.sql} | 45 +++++++++-------------- sql/{moretap.sql.in => moretap.sql} | 51 ++++++++++---------------- sql/{pg73.sql.in => pg73.sql} | 24 +------------ sql/{pktap.sql.in => pktap.sql} | 41 +++++++-------------- sql/{throwtap.sql.in => throwtap.sql} | 45 +++++++++-------------- sql/{todotap.sql.in => todotap.sql} | 32 ++++------------- sql/{unique.sql.in => unique.sql} | 41 +++++++-------------- test_setup.sql.in | 25 +++++++++++++ 17 files changed, 197 insertions(+), 376 deletions(-) rename sql/{check.sql.in => check.sql} (81%) rename sql/{cmpok.sql.in => cmpok.sql} (87%) rename sql/{coltap.sql.in => coltap.sql} (84%) rename sql/{fktap.sql.in => fktap.sql} (95%) rename sql/{hastap.sql.in => hastap.sql} (78%) rename sql/{istap.sql.in => istap.sql} (73%) rename sql/{matching.sql.in => matching.sql} (65%) rename sql/{moretap.sql.in => moretap.sql} (75%) rename sql/{pg73.sql.in => pg73.sql} (82%) rename sql/{pktap.sql.in => pktap.sql} (81%) rename sql/{throwtap.sql.in => throwtap.sql} (67%) rename sql/{todotap.sql.in => todotap.sql} (65%) rename sql/{unique.sql.in => unique.sql} (81%) create mode 100644 test_setup.sql.in diff --git a/Changes b/Changes index 58d63db53fed..920dbdd2794b 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,13 @@ Revision history for pgTAP 0.11 + - Simplified the tests so that they now load `test_setup.sql` instead of + setting a bunch of stuff themselves. Now only `test_setup.sql` needs + to be created from `test_setup.sql.in`, and the other `.sql` files + depend on it, meaning that one no longer has to specify `TAPSCHEMA` + for any `make` target other than the default. + - Eliminated all uses of `E''` in the tests, so that we don't have to + process them for testing on 8.0. 0.10 2008-09-18T22:59:31 - Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the diff --git a/Makefile b/Makefile index dd172413be16..f8e3afbf6170 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # $Id$ -TESTS = $(patsubst %.sql.in,%.sql,$(wildcard sql/*.sql.in)) -EXTRA_CLEAN = $(TESTS) +TESTS = $(wildcard sql/*.sql) +EXTRA_CLEAN = test_setup.sql DATA_built = pgtap.sql uninstall_pgtap.sql MODULES = pgtap DOCS = README.pgtap @@ -42,18 +42,13 @@ endif # Override how .sql targets are processed to add the schema info, if # necessary. Otherwise just copy the files. -%.sql: %.sql.in +test_setup.sql: test_setup.sql.in ifdef TAPSCHEMA sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' $(EXTRAS) $< >$@ -else -ifdef EXTRAS - sed $(EXTRAS) $< >$@ -else cp $< $@ endif -endif -pgtap.sql: pgtap.sql.in +pgtap.sql: pgtap.sql.in test_setup.sql ifdef TAPSCHEMA sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' $(EXTRAS) pgtap.sql.in > pgtap.sql else @@ -70,7 +65,7 @@ endif endif endif -uninstall_pgtap.sql: uninstall_pgtap.sql.in +uninstall_pgtap.sql: uninstall_pgtap.sql.in test_setup.sql ifdef TAPSCHEMA sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' $(EXTRAS) uninstall_pgtap.sql.in > uninstall_pgtap.sql else @@ -89,8 +84,8 @@ endif endif # Make sure that we build the regression tests. -installcheck: $(TESTS) +installcheck: test_setup.sql # In addition to installcheck, one can also run the tests through pg_prove. -test: $(TESTS) +test: test_setup.sql ./bin/pg_prove --pset tuples_only=1 $(TESTS) diff --git a/README.pgtap b/README.pgtap index 521098dc2288..4717f53eab18 100644 --- a/README.pgtap +++ b/README.pgtap @@ -46,11 +46,11 @@ the `$USE_PGXS` variable: If you want to schema-qualify pgTAP (that is, install all of its functions into their own schema), set the `$TAPSCHEMA` variable to the name of the -schema you'd like, for example: +schema you'd like to be created, for example: make TAPSCHEMA=tap make install - make installcheck TAPSCHEMA=tap + make installcheck Testing pgTAP with pgTAP ------------------------ @@ -71,7 +71,7 @@ PostgreSQL client environment variables: You can use it to run the test suite like so: - make test USE_PGXS=1 TAPSCHEMA=tap PGUSER=postgres + make test USE_PGXS=1 PGUSER=postgres Of course, if you're running the tests from the `contrib/` directory, you should omit the `USE_PGXS` variable. diff --git a/sql/check.sql.in b/sql/check.sql similarity index 81% rename from sql/check.sql.in rename to sql/check.sql index d57b5e8f30e6..e52c657fe2f3 100644 --- a/sql/check.sql.in +++ b/sql/check.sql @@ -1,31 +1,8 @@ \set ECHO -\set QUIET 1 +\i test_setup.sql --- --- Tests for pgTAP. --- --- --- $Id$ +-- $Id: pg73.sql.in 4285 2008-09-17 21:47:16Z david $ --- Format the output for nice TAP. -\pset format unaligned -\pset tuples_only true -\pset pager - --- Keep things quiet. -SET client_min_messages = warning; - --- Revert all changes on failure. -\set ON_ERROR_ROLBACK 1 -\set ON_ERROR_STOP true - --- Load the TAP functions. -BEGIN; -\i pgtap.sql - --- ## SET search_path TO TAPSCHEMA,public; - --- Set the test plan. SELECT plan(26); -- This will be rolled back. :-) @@ -63,14 +40,16 @@ SELECT is( \echo ok 7 - test has_check( schema, table, description ) fail SELECT is( has_check( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should have a check constraint' ), - E'not ok 7 - pg_catalog.pg_class should have a check constraint\n# Failed test 7: "pg_catalog.pg_class should have a check constraint"', + 'not ok 7 - pg_catalog.pg_class should have a check constraint +# Failed test 7: "pg_catalog.pg_class should have a check constraint"', 'has_check( schema, table, description ) should fail properly' ); \echo ok 9 - test has_check( table, description ) fail SELECT is( has_check( 'pg_class', 'pg_class should have a check constraint' ), - E'not ok 9 - pg_class should have a check constraint\n# Failed test 9: "pg_class should have a check constraint"', + 'not ok 9 - pg_class should have a check constraint +# Failed test 9: "pg_class should have a check constraint"', 'has_check( table, description ) should fail properly' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 7, 9 ); @@ -102,14 +81,20 @@ SELECT is( \echo ok 17 - test col_has_check( schema, table, column, description ) fail SELECT is( col_has_check( 'public', 'sometab', 'id', 'public.sometab.id should be a pk' ), - E'not ok 17 - public.sometab.id should be a pk\n# Failed test 17: "public.sometab.id should be a pk"\n# have: {name}\n# want: {id}', + 'not ok 17 - public.sometab.id should be a pk +# Failed test 17: "public.sometab.id should be a pk" +# have: {name} +# want: {id}', 'col_has_check( schema, table, column, description ) should fail properly' ); \echo ok 19 - test col_has_check( table, column, description ) fail SELECT is( col_has_check( 'sometab', 'id', 'sometab.id should be a pk' ), - E'not ok 19 - sometab.id should be a pk\n# Failed test 19: "sometab.id should be a pk"\n# have: {name}\n# want: {id}', + 'not ok 19 - sometab.id should be a pk +# Failed test 19: "sometab.id should be a pk" +# have: {name} +# want: {id}', 'col_has_check( table, column, description ) should fail properly' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 17, 19 ); diff --git a/sql/cmpok.sql.in b/sql/cmpok.sql similarity index 87% rename from sql/cmpok.sql.in rename to sql/cmpok.sql index a7258eeedcc8..1dc2a2dc4511 100644 --- a/sql/cmpok.sql.in +++ b/sql/cmpok.sql @@ -1,31 +1,8 @@ \set ECHO -\set QUIET 1 +\i test_setup.sql --- --- Tests for pgTAP. --- --- -- $Id$ --- Format the output for nice TAP. -\pset format unaligned -\pset tuples_only true -\pset pager - --- Keep things quiet. -SET client_min_messages = warning; - --- Revert all changes on failure. -\set ON_ERROR_ROLBACK 1 -\set ON_ERROR_STOP true - --- Load the TAP functions. -BEGIN; -\i pgtap.sql - --- ## SET search_path TO TAPSCHEMA,public; - --- Set the test plan. SELECT plan(14); /****************************************************************************/ diff --git a/sql/coltap.sql.in b/sql/coltap.sql similarity index 84% rename from sql/coltap.sql.in rename to sql/coltap.sql index 8dac8d88136c..dc29eb439d6b 100644 --- a/sql/coltap.sql.in +++ b/sql/coltap.sql @@ -1,31 +1,8 @@ \set ECHO -\set QUIET 1 +\i test_setup.sql --- --- Tests for pgTAP. --- --- -- $Id$ --- Format the output for nice TAP. -\pset format unaligned -\pset tuples_only true -\pset pager - --- Keep things quiet. -SET client_min_messages = warning; - --- Revert all changes on failure. -\set ON_ERROR_ROLBACK 1 -\set ON_ERROR_STOP true - --- Load the TAP functions. -BEGIN; -\i pgtap.sql - --- ## SET search_path TO TAPSCHEMA,public; - --- Set the test plan. SELECT plan(38); -- This will be rolled back. :-) @@ -61,7 +38,8 @@ SELECT is( \echo ok 7 - testing col_not_null( schema, table, column, desc ) SELECT is( col_not_null( 'sometab', 'name' ), - E'not ok 7 - Column sometab(name) should be NOT NULL\n# Failed test 7: "Column sometab(name) should be NOT NULL"', + 'not ok 7 - Column sometab(name) should be NOT NULL +# Failed test 7: "Column sometab(name) should be NOT NULL"', 'col_not_null( table, column ) should properly fail' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 7 ); @@ -91,7 +69,8 @@ SELECT is( \echo ok 15 - testing col_is_null( schema, table, column, desc ) SELECT is( col_is_null( 'sometab', 'id' ), - E'not ok 15 - Column sometab(id) should allow NULL\n# Failed test 15: "Column sometab(id) should allow NULL"', + 'not ok 15 - Column sometab(id) should allow NULL +# Failed test 15: "Column sometab(id) should allow NULL"', 'col_is_null( table, column ) should properly fail' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 15 ); @@ -130,7 +109,10 @@ SELECT is( \echo ok 25 - testing col_type_is( table, column, type ) failure SELECT is( col_type_is( 'sometab', 'name', 'int4' ), - E'not ok 25 - Column sometab(name) should be type int4\n# Failed test 25: "Column sometab(name) should be type int4"\n# have: text\n# want: int4', + 'not ok 25 - Column sometab(name) should be type int4 +# Failed test 25: "Column sometab(name) should be type int4" +# have: text +# want: int4', 'col_type_is( table, column, type ) should fail with proper diagnostics' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 25 ); @@ -148,7 +130,10 @@ SELECT is( \echo ok 29 - col_type_is( table, column, type, precision, desc ) fail SELECT is( col_type_is( 'sometab', 'myint', 'numeric(7)', 'should be numeric(7)' ), - E'not ok 29 - should be numeric(7)\n# Failed test 29: "should be numeric(7)"\n# have: numeric(8,0)\n# want: numeric(7)', + 'not ok 29 - should be numeric(7) +# Failed test 29: "should be numeric(7)" +# have: numeric(8,0) +# want: numeric(7)', 'col_type_is with precision should have nice diagnostics' ); @@ -167,7 +152,10 @@ SELECT is( \echo ok 33 - col_default_is( schema, table, column, default, description ) fail SELECT is( col_default_is( 'public', 'sometab', 'name', 'foo'::text, 'name should default to ''foo''' ), - E'not ok 33 - name should default to ''foo''\n# Failed test 33: "name should default to ''foo''"\n# have: \n# want: foo', + 'not ok 33 - name should default to ''foo'' +# Failed test 33: "name should default to ''foo''" +# have: +# want: foo', 'ok 33 - Should get proper diagnostics for a default failure' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 33 ); diff --git a/sql/fktap.sql.in b/sql/fktap.sql similarity index 95% rename from sql/fktap.sql.in rename to sql/fktap.sql index bba2519af9fb..ecd2e3581b52 100644 --- a/sql/fktap.sql.in +++ b/sql/fktap.sql @@ -1,31 +1,8 @@ \set ECHO -\set QUIET 1 +\i test_setup.sql --- --- Tests for pgTAP. --- --- -- $Id$ --- Format the output for nice TAP. -\pset format unaligned -\pset tuples_only true -\pset pager - --- Keep things quiet. -SET client_min_messages = warning; - --- Revert all changes on failure. -\set ON_ERROR_ROLBACK 1 -\set ON_ERROR_STOP true - --- Load the TAP functions. -BEGIN; -\i pgtap.sql - --- ## SET search_path TO TAPSCHEMA,public; - --- Set the test plan. SELECT plan(88); --SELECT * from no_plan(); diff --git a/sql/hastap.sql.in b/sql/hastap.sql similarity index 78% rename from sql/hastap.sql.in rename to sql/hastap.sql index 1a27cd2ecc82..b6697aefc7c1 100644 --- a/sql/hastap.sql.in +++ b/sql/hastap.sql @@ -1,31 +1,8 @@ \set ECHO -\set QUIET 1 +\i test_setup.sql --- --- Tests for pgTAP. --- --- -- $Id$ --- Format the output for nice TAP. -\pset format unaligned -\pset tuples_only true -\pset pager - --- Keep things quiet. -SET client_min_messages = warning; - --- Revert all changes on failure. -\set ON_ERROR_ROLBACK 1 -\set ON_ERROR_STOP true - --- Load the TAP functions. -BEGIN; -\i pgtap.sql - --- ## SET search_path TO TAPSCHEMA,public; - --- Set the test plan. SELECT plan(30); -- This will be rolled back. :-) @@ -43,21 +20,24 @@ CREATE TABLE sometab( SELECT is( has_table( '__SDFSDFD__' ), - E'not ok 1 - Table __SDFSDFD__ should exist\n# Failed test 1: "Table __SDFSDFD__ should exist"', + 'not ok 1 - Table __SDFSDFD__ should exist +# Failed test 1: "Table __SDFSDFD__ should exist"', 'has_table(table) should fail for non-existent table' ); \echo ok 3 - has_table(table, desc) fail SELECT is( has_table( '__SDFSDFD__', 'lol' ), - E'not ok 3 - lol\n# Failed test 3: "lol"', + 'not ok 3 - lol +# Failed test 3: "lol"', 'has_table(table, dessc) should fail for non-existent table' ); \echo ok 5 - has_table(schema, table, desc) fail SELECT is( has_table( 'foo', '__SDFSDFD__', 'desc' ), - E'not ok 5 - desc\n# Failed test 5: "desc"', + 'not ok 5 - desc +# Failed test 5: "desc"', 'has_table(schema, table, desc) should fail for non-existent table' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 1, 3, 5 ); @@ -82,21 +62,24 @@ SELECT is( \echo ok 11 - has_view(view) fail SELECT is( has_view( '__SDFSDFD__' ), - E'not ok 11 - View __SDFSDFD__ should exist\n# Failed test 11: "View __SDFSDFD__ should exist"', + 'not ok 11 - View __SDFSDFD__ should exist +# Failed test 11: "View __SDFSDFD__ should exist"', 'has_view(view) should fail for non-existent view' ); \echo ok 13 - has_view(view, desc) fail SELECT is( has_view( '__SDFSDFD__', 'howdy' ), - E'not ok 13 - howdy\n# Failed test 13: "howdy"', + 'not ok 13 - howdy +# Failed test 13: "howdy"', 'has_view(view, desc) should fail for non-existent view' ); \echo ok 15 - has_view(schema, view, desc) fail SELECT is( has_view( 'foo', '__SDFSDFD__', 'desc' ), - E'not ok 15 - desc\n# Failed test 15: "desc"', + 'not ok 15 - desc +# Failed test 15: "desc"', 'has_view(schema, view, desc) should fail for non-existent view' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 11, 13, 15 ); @@ -121,21 +104,24 @@ SELECT is( \echo ok 21 - has_column(table, column) fail SELECT is( has_column( '__SDFSDFD__', 'foo' ), - E'not ok 21 - Column __SDFSDFD__(foo) should exist\n# Failed test 21: "Column __SDFSDFD__(foo) should exist"', + 'not ok 21 - Column __SDFSDFD__(foo) should exist +# Failed test 21: "Column __SDFSDFD__(foo) should exist"', 'has_column(table, column) should fail for non-existent table' ); \echo ok 23 - has_column(table, column, desc) fail SELECT is( has_column( '__SDFSDFD__', 'bar', 'whatever' ), - E'not ok 23 - whatever\n# Failed test 23: "whatever"', + 'not ok 23 - whatever +# Failed test 23: "whatever"', 'has_column(table, column, desc) should fail for non-existent table' ); \echo ok 25 - has_column(schema, table, column, desc) fail SELECT is( has_column( 'foo', '__SDFSDFD__', 'bar', 'desc' ), - E'not ok 25 - desc\n# Failed test 25: "desc"', + 'not ok 25 - desc +# Failed test 25: "desc"', 'has_column(schema, table, column, desc) should fail for non-existent table' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 21, 23, 25 ); diff --git a/sql/istap.sql.in b/sql/istap.sql similarity index 73% rename from sql/istap.sql.in rename to sql/istap.sql index c88ae9652213..3bbcdd23293a 100644 --- a/sql/istap.sql.in +++ b/sql/istap.sql @@ -1,31 +1,8 @@ \set ECHO -\set QUIET 1 +\i test_setup.sql --- --- Tests for pgTAP. --- --- -- $Id$ --- Format the output for nice TAP. -\pset format unaligned -\pset tuples_only true -\pset pager - --- Keep things quiet. -SET client_min_messages = warning; - --- Revert all changes on failure. -\set ON_ERROR_ROLBACK 1 -\set ON_ERROR_STOP true - --- Load the TAP functions. -BEGIN; -\i pgtap.sql - --- ## SET search_path TO TAPSCHEMA,public; - --- Set the test plan. SELECT plan(27); /****************************************************************************/ @@ -45,14 +22,21 @@ SELECT is( is(false, false), 'ok 11', 'is(false, false) should work' ); \echo ok 13 - is() success 7 SELECT is( is(1, 1, 'foo'), 'ok 13 - foo', 'is(1, 1, ''foo'') should work' ); \echo ok 15 - is() failure -SELECT is( is( 1, 2 ), E'not ok 15\n# Failed test 15\n# have: 1\n# want: 2', 'is(1, 2) should work' ); +SELECT is( is( 1, 2 ), 'not ok 15 +# Failed test 15 +# have: 1 +# want: 2', 'is(1, 2) should work' ); /****************************************************************************/ -- Test isnt(). \echo ok 17 - isnt() success SELECT is( isnt(1, 2), 'ok 17', 'isnt(1, 2) should work' ); \echo ok 19 - isnt() failure -SELECT is( isnt( 1, 1 ), E'not ok 19\n# Failed test 19\n# 1\n# <>\n# 1', 'is(1, 2) should work' ); +SELECT is( isnt( 1, 1 ), 'not ok 19 +# Failed test 19 +# 1 +# <> +# 1', 'is(1, 2) should work' ); -- Clean up the failed test results. UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 15, 19 ); @@ -75,14 +59,20 @@ SELECT is( \echo ok 24 - is(NULL, foo) failure SELECT is( is( NULL::text, 'foo' ), - E'not ok 24\n# Failed test 24\n# have: NULL\n# want: foo', + 'not ok 24 +# Failed test 24 +# have: NULL +# want: foo', 'is(NULL, foo) should fail' ); \echo ok 26 - is(foo, NULL) failure SELECT is( is( 'foo', NULL::text ), - E'not ok 26\n# Failed test 26\n# have: foo\n# want: NULL', + 'not ok 26 +# Failed test 26 +# have: foo +# want: NULL', 'is(foo, NULL) should fail' ); diff --git a/sql/matching.sql.in b/sql/matching.sql similarity index 65% rename from sql/matching.sql.in rename to sql/matching.sql index 144505fc239f..63f9072c9118 100644 --- a/sql/matching.sql.in +++ b/sql/matching.sql @@ -1,31 +1,8 @@ \set ECHO -\set QUIET 1 +\i test_setup.sql --- --- Tests for pgTAP. --- --- -- $Id$ --- Format the output for nice TAP. -\pset format unaligned -\pset tuples_only true -\pset pager - --- Keep things quiet. -SET client_min_messages = warning; - --- Revert all changes on failure. -\set ON_ERROR_ROLBACK 1 -\set ON_ERROR_STOP true - --- Load the TAP functions. -BEGIN; -\i pgtap.sql - --- ## SET search_path TO TAPSCHEMA,public; - --- Set the test plan. SELECT plan(20); /****************************************************************************/ @@ -36,7 +13,10 @@ SELECT imatches( 'FOO'::text, '^fo', 'imatches() should work with a regex' ); -- Check matches() diagnostics. \echo ok 4 - matches() failure -SELECT is( matches( 'foo'::text, '^a' ), E'not ok 4\n# Failed test 4\n# ''foo''\n# doesn''t match: ''^a''', 'Check matches diagnostics' ); +SELECT is( matches( 'foo'::text, '^a' ), 'not ok 4 +# Failed test 4 +# ''foo'' +# doesn''t match: ''^a''', 'Check matches diagnostics' ); -- Check doesnt_match. SELECT doesnt_match( 'foo'::text, 'a', 'doesnt_match() should work' ); @@ -47,7 +27,10 @@ SELECT doesnt_imatch( 'foo'::text, '^o', 'doesnt_imatch() should work with a reg \echo ok 9 - doesnt_match() failure SELECT is( doesnt_match( 'foo'::text, 'o' ), - E'not ok 9\n# Failed test 9\n# ''foo''\n# matches: ''o''', + 'not ok 9 +# Failed test 9 +# ''foo'' +# matches: ''o''', 'doesnt_match() should work' ); @@ -62,7 +45,10 @@ SELECT ialike( 'FOO'::text, 'fo%', 'ialike() should work with a regex' ); -- Check alike() diagnostics. \echo ok 14 - alike() failure -SELECT is( alike( 'foo'::text, 'a%'::text ), E'not ok 14\n# Failed test 14\n# ''foo''\n# doesn''t match: ''a%''', 'Check alike diagnostics' ); +SELECT is( alike( 'foo'::text, 'a%'::text ), 'not ok 14 +# Failed test 14 +# ''foo'' +# doesn''t match: ''a%''', 'Check alike diagnostics' ); -- Test unalike(). SELECT unalike( 'foo'::text, 'f', 'unalike() should work' ); @@ -71,7 +57,10 @@ SELECT unialike( 'FOO'::text, 'f%i', 'iunalike() should work with a regex' ); -- Check unalike() diagnostics. \echo ok 19 - unalike() failure -SELECT is( unalike( 'foo'::text, 'f%'::text ), E'not ok 19\n# Failed test 19\n# ''foo''\n# matches: ''f%''', 'Check unalike diagnostics' ); +SELECT is( unalike( 'foo'::text, 'f%'::text ), 'not ok 19 +# Failed test 19 +# ''foo'' +# matches: ''f%''', 'Check unalike diagnostics' ); -- Clean up the failed test results. UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 14, 19 ); diff --git a/sql/moretap.sql.in b/sql/moretap.sql similarity index 75% rename from sql/moretap.sql.in rename to sql/moretap.sql index c31a1b31eccc..a0ead3400cd0 100644 --- a/sql/moretap.sql.in +++ b/sql/moretap.sql @@ -1,32 +1,9 @@ \set ECHO -\set QUIET 1 +\i test_setup.sql --- --- Tests for pgTAP. --- --- -- $Id$ --- Format the output for nice TAP. -\pset format unaligned -\pset tuples_only true -\pset pager - --- Keep things quiet. -SET client_min_messages = warning; - --- Revert all changes on failure. -\set ON_ERROR_ROLBACK 1 -\set ON_ERROR_STOP true - --- Load the TAP functions. -BEGIN; -\i pgtap.sql \set numb_tests 30 - --- ## SET search_path TO TAPSCHEMA,public; - --- Set the test plan. SELECT plan(:numb_tests); -- Replace the internal record of the plan for a few tests. @@ -41,7 +18,8 @@ SELECT pass( 'My pass() passed, w00t!' ); \echo ok :fail_numb - Testing fail() SELECT is( fail('oops'), - E'not ok 2 - oops\n# Failed test 2: "oops"', 'We should get the proper output from fail()'); + 'not ok 2 - oops +# Failed test 2: "oops"', 'We should get the proper output from fail()'); -- Check the finish() output. SELECT is( @@ -59,8 +37,12 @@ SELECT is( num_failed(), 0, 'We should now have no failures' ); /****************************************************************************/ -- Check diag. SELECT is( diag('foo'), '# foo', 'diag() should work properly' ); -SELECT is( diag( E'foo\nbar'), E'# foo\n# bar', 'multiline diag() should work properly' ); -SELECT is( diag( E'foo\n# bar'), E'# foo\n# # bar', 'multiline diag() should work properly with existing comments' ); +SELECT is( diag( 'foo +bar'), '# foo +# bar', 'multiline diag() should work properly' ); +SELECT is( diag( 'foo +# bar'), '# foo +# # bar', 'multiline diag() should work properly with existing comments' ); /****************************************************************************/ -- Check no_plan. @@ -105,11 +87,14 @@ SELECT is( ok(true, ''), 'ok 19', 'ok(true, '''') should work' ); SELECT is( ok(true, 'foo'), 'ok 21 - foo', 'ok(true, ''foo'') should work' ); \echo ok 23 - ok() failure -SELECT is( ok(false), E'not ok 23\n# Failed test 23', 'ok(false) should work' ); +SELECT is( ok(false), 'not ok 23 +# Failed test 23', 'ok(false) should work' ); \echo ok 25 - ok() failure 2 -SELECT is( ok(false, ''), E'not ok 25\n# Failed test 25', 'ok(false, '''') should work' ); +SELECT is( ok(false, ''), 'not ok 25 +# Failed test 25', 'ok(false, '''') should work' ); \echo ok 27 - ok() failure 3 -SELECT is( ok(false, 'foo'), E'not ok 27 - foo\n# Failed test 27: "foo"', 'ok(false, ''foo'') should work' ); +SELECT is( ok(false, 'foo'), 'not ok 27 - foo +# Failed test 27: "foo"', 'ok(false, ''foo'') should work' ); -- Clean up the failed test results. UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 23, 25, 27); @@ -118,8 +103,10 @@ UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 23, 25, 27); -- test multiline description. \echo ok 29 - Multline diagnostics SELECT is( - ok( true, E'foo\nbar' ), - E'ok 29 - foo\n# bar', + ok( true, 'foo +bar' ), + 'ok 29 - foo +# bar', 'multiline desriptions should have subsequent lines escaped' ); diff --git a/sql/pg73.sql.in b/sql/pg73.sql similarity index 82% rename from sql/pg73.sql.in rename to sql/pg73.sql index 1e370591cafd..07f2a9dbd9e4 100644 --- a/sql/pg73.sql.in +++ b/sql/pg73.sql @@ -1,30 +1,8 @@ \set ECHO -\set QUIET 1 +\i test_setup.sql --- --- Tests for pgTAP. --- --- -- $Id$ --- Format the output for nice TAP. -\pset format unaligned -\pset tuples_only true -\pset pager - --- Keep things quiet. -SET client_min_messages = warning; - --- Revert all changes on failure. -\set ON_ERROR_ROLBACK 1 -\set ON_ERROR_STOP true - --- Load the TAP functions. -BEGIN; -\i pgtap.sql - --- ## SET search_path TO TAPSCHEMA,public; - select plan(39); select ok(true); diff --git a/sql/pktap.sql.in b/sql/pktap.sql similarity index 81% rename from sql/pktap.sql.in rename to sql/pktap.sql index ec67a1095d8c..ede608c904ac 100644 --- a/sql/pktap.sql.in +++ b/sql/pktap.sql @@ -1,31 +1,8 @@ \set ECHO -\set QUIET 1 +\i test_setup.sql --- --- Tests for pgTAP. --- --- -- $Id$ --- Format the output for nice TAP. -\pset format unaligned -\pset tuples_only true -\pset pager - --- Keep things quiet. -SET client_min_messages = warning; - --- Revert all changes on failure. -\set ON_ERROR_ROLBACK 1 -\set ON_ERROR_STOP true - --- Load the TAP functions. -BEGIN; -\i pgtap.sql - --- ## SET search_path TO TAPSCHEMA,public; - --- Set the test plan. SELECT plan(26); -- This will be rolled back. :-) @@ -63,14 +40,16 @@ SELECT is( \echo ok 7 - test has_pk( schema, table, description ) fail SELECT is( has_pk( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should have a pk' ), - E'not ok 7 - pg_catalog.pg_class should have a pk\n# Failed test 7: "pg_catalog.pg_class should have a pk"', + 'not ok 7 - pg_catalog.pg_class should have a pk +# Failed test 7: "pg_catalog.pg_class should have a pk"', 'has_pk( schema, table, description ) should fail properly' ); \echo ok 9 - test has_pk( table, description ) fail SELECT is( has_pk( 'pg_class', 'pg_class should have a pk' ), - E'not ok 9 - pg_class should have a pk\n# Failed test 9: "pg_class should have a pk"', + 'not ok 9 - pg_class should have a pk +# Failed test 9: "pg_class should have a pk"', 'has_pk( table, description ) should fail properly' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 7, 9 ); @@ -102,14 +81,20 @@ SELECT is( \echo ok 17 - test col_is_pk( schema, table, column, description ) fail SELECT is( col_is_pk( 'public', 'sometab', 'name', 'public.sometab.name should be a pk' ), - E'not ok 17 - public.sometab.name should be a pk\n# Failed test 17: "public.sometab.name should be a pk"\n# have: {id}\n# want: {name}', + 'not ok 17 - public.sometab.name should be a pk +# Failed test 17: "public.sometab.name should be a pk" +# have: {id} +# want: {name}', 'col_is_pk( schema, table, column, description ) should fail properly' ); \echo ok 19 - test col_is_pk( table, column, description ) fail SELECT is( col_is_pk( 'sometab', 'name', 'sometab.name should be a pk' ), - E'not ok 19 - sometab.name should be a pk\n# Failed test 19: "sometab.name should be a pk"\n# have: {id}\n# want: {name}', + 'not ok 19 - sometab.name should be a pk +# Failed test 19: "sometab.name should be a pk" +# have: {id} +# want: {name}', 'col_is_pk( table, column, description ) should fail properly' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 17, 19 ); diff --git a/sql/throwtap.sql.in b/sql/throwtap.sql similarity index 67% rename from sql/throwtap.sql.in rename to sql/throwtap.sql index 456bc35e1179..f7c5c12ac3d5 100644 --- a/sql/throwtap.sql.in +++ b/sql/throwtap.sql @@ -1,31 +1,8 @@ \set ECHO -\set QUIET 1 +\i test_setup.sql --- --- Tests for pgTAP. --- --- -- $Id$ --- Format the output for nice TAP. -\pset format unaligned -\pset tuples_only true -\pset pager - --- Keep things quiet. -SET client_min_messages = warning; - --- Revert all changes on failure. -\set ON_ERROR_ROLBACK 1 -\set ON_ERROR_STOP true - --- Load the TAP functions. -BEGIN; -\i pgtap.sql - --- ## SET search_path TO TAPSCHEMA,public; - --- Set the test plan. SELECT plan(11); /****************************************************************************/ @@ -36,7 +13,10 @@ SELECT throws_ok( 'SELECT 1 / 0', '22012', 'throws_ok(1/0) should work' ); \echo ok 2 - throws_ok failure diagnostics SELECT is( throws_ok( 'SELECT 1 / 0', 97212 ), - E'not ok 2 - threw 97212\n# Failed test 2: "threw 97212"\n# caught: 22012: division by zero\n# wanted: 97212', + 'not ok 2 - threw 97212 +# Failed test 2: "threw 97212" +# caught: 22012: division by zero +# wanted: 97212', 'We should get the proper diagnostics from throws_ok()' ); @@ -46,7 +26,10 @@ SELECT throws_ok( 'SELECT 1 / 0', NULL, 'throws_ok(1/0, NULL) should work' ); \echo ok 5 - throws_ok failure diagnostics SELECT is( throws_ok( 'SELECT 1', NULL ), - E'not ok 5 - threw an exception\n# Failed test 5: "threw an exception"\n# caught: no exception\n# wanted: an exception', + 'not ok 5 - threw an exception +# Failed test 5: "threw an exception" +# caught: no exception +# wanted: an exception', 'We should get the proper diagnostics from throws_ok() with a NULL error code' ); @@ -61,7 +44,9 @@ SELECT lives_ok( 'SELECT 1', 'lives_ok() should work' ); \echo ok 8 - lives_ok failure diagnostics SELECT is( lives_ok( 'SELECT 1 / 0' ), - E'not ok 8\n# Failed test 8\n# died: 22012: division by zero', + 'not ok 8 +# Failed test 8 +# died: 22012: division by zero', 'We should get the proper diagnostics for a lives_ok() failure' ); @@ -71,8 +56,10 @@ UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 8 ); /****************************************************************************/ -- test multiline description. SELECT is( - ok( true, E'foo\nbar' ), - E'ok 10 - foo\n# bar', + ok( true, 'foo +bar' ), + 'ok 10 - foo +# bar', 'multiline desriptions should have subsequent lines escaped' ); diff --git a/sql/todotap.sql.in b/sql/todotap.sql similarity index 65% rename from sql/todotap.sql.in rename to sql/todotap.sql index a842202b12f7..3877dcb0bc8b 100644 --- a/sql/todotap.sql.in +++ b/sql/todotap.sql @@ -1,31 +1,8 @@ \set ECHO -\set QUIET 1 +\i test_setup.sql --- --- Tests for pgTAP. --- --- -- $Id$ --- Format the output for nice TAP. -\pset format unaligned -\pset tuples_only true -\pset pager - --- Keep things quiet. -SET client_min_messages = warning; - --- Revert all changes on failure. -\set ON_ERROR_ROLBACK 1 -\set ON_ERROR_STOP true - --- Load the TAP functions. -BEGIN; -\i pgtap.sql - --- ## SET search_path TO TAPSCHEMA,public; - --- Set the test plan. SELECT plan(13); /****************************************************************************/ @@ -34,9 +11,12 @@ SELECT plan(13); \echo ok 2 - todo pass SELECT * FROM todo('just because', 2 ); SELECT is( - fail('This is a todo test' ) || E'\n' + fail('This is a todo test' ) || ' +' || pass('This is a todo test that unexpectedly passes' ), - E'not ok 1 - This is a todo test # TODO just because\n# Failed (TODO) test 1: "This is a todo test"\nok 2 - This is a todo test that unexpectedly passes # TODO just because', + 'not ok 1 - This is a todo test # TODO just because +# Failed (TODO) test 1: "This is a todo test" +ok 2 - This is a todo test that unexpectedly passes # TODO just because', 'TODO tests should display properly' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 2 ); diff --git a/sql/unique.sql.in b/sql/unique.sql similarity index 81% rename from sql/unique.sql.in rename to sql/unique.sql index c8aa8b7a8c97..93d7179b2d02 100644 --- a/sql/unique.sql.in +++ b/sql/unique.sql @@ -1,31 +1,8 @@ \set ECHO -\set QUIET 1 +\i test_setup.sql --- --- Tests for pgTAP. --- --- -- $Id$ --- Format the output for nice TAP. -\pset format unaligned -\pset tuples_only true -\pset pager - --- Keep things quiet. -SET client_min_messages = warning; - --- Revert all changes on failure. -\set ON_ERROR_ROLBACK 1 -\set ON_ERROR_STOP true - --- Load the TAP functions. -BEGIN; -\i pgtap.sql - --- ## SET search_path TO TAPSCHEMA,public; - --- Set the test plan. SELECT plan(26); -- This will be rolled back. :-) @@ -63,14 +40,16 @@ SELECT is( \echo ok 7 - test has_unique( schema, table, description ) fail SELECT is( has_unique( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should have a unique constraint' ), - E'not ok 7 - pg_catalog.pg_class should have a unique constraint\n# Failed test 7: "pg_catalog.pg_class should have a unique constraint"', + 'not ok 7 - pg_catalog.pg_class should have a unique constraint +# Failed test 7: "pg_catalog.pg_class should have a unique constraint"', 'has_unique( schema, table, description ) should fail properly' ); \echo ok 9 - test has_unique( table, description ) fail SELECT is( has_unique( 'pg_class', 'pg_class should have a unique constraint' ), - E'not ok 9 - pg_class should have a unique constraint\n# Failed test 9: "pg_class should have a unique constraint"', + 'not ok 9 - pg_class should have a unique constraint +# Failed test 9: "pg_class should have a unique constraint"', 'has_unique( table, description ) should fail properly' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 7, 9 ); @@ -102,14 +81,20 @@ SELECT is( \echo ok 17 - test col_is_unique( schema, table, column, description ) fail SELECT is( col_is_unique( 'public', 'sometab', 'id', 'public.sometab.id should be a pk' ), - E'not ok 17 - public.sometab.id should be a pk\n# Failed test 17: "public.sometab.id should be a pk"\n# have: {name}\n# want: {id}', + 'not ok 17 - public.sometab.id should be a pk +# Failed test 17: "public.sometab.id should be a pk" +# have: {name} +# want: {id}', 'col_is_unique( schema, table, column, description ) should fail properly' ); \echo ok 19 - test col_is_unique( table, column, description ) fail SELECT is( col_is_unique( 'sometab', 'id', 'sometab.id should be a pk' ), - E'not ok 19 - sometab.id should be a pk\n# Failed test 19: "sometab.id should be a pk"\n# have: {name}\n# want: {id}', + 'not ok 19 - sometab.id should be a pk +# Failed test 19: "sometab.id should be a pk" +# have: {name} +# want: {id}', 'col_is_unique( table, column, description ) should fail properly' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 17, 19 ); diff --git a/test_setup.sql.in b/test_setup.sql.in new file mode 100644 index 000000000000..e454a3338a63 --- /dev/null +++ b/test_setup.sql.in @@ -0,0 +1,25 @@ +\set QUIET 1 + +-- +-- Tests for pgTAP. +-- +-- +-- $Id: check.sql.in 4285 2008-09-17 21:47:16Z david $ + +-- Format the output for nice TAP. +\pset format unaligned +\pset tuples_only true +\pset pager + +-- Keep things quiet. +SET client_min_messages = warning; + +-- Revert all changes on failure. +\set ON_ERROR_ROLBACK 1 +\set ON_ERROR_STOP true + +-- Load the TAP functions. +BEGIN; +\i pgtap.sql + +-- ## SET search_path TO TAPSCHEMA,public; From fe4f9d7a6543fd26f961c342338f87d06be33ac0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 19 Sep 2008 23:03:00 +0000 Subject: [PATCH 0151/1195] Unset. --- Makefile | 14 +++++--------- expected/check.out | 2 +- expected/cmpok.out | 2 +- expected/coltap.out | 2 +- expected/fktap.out | 2 +- expected/hastap.out | 2 +- expected/istap.out | 2 +- expected/matching.out | 2 +- expected/moretap.out | 2 +- expected/pg73.out | 2 +- expected/pktap.out | 2 +- expected/throwtap.out | 2 +- expected/todotap.out | 2 +- expected/unique.out | 2 +- sql/check.sql | 2 +- sql/cmpok.sql | 2 +- sql/coltap.sql | 2 +- sql/fktap.sql | 2 +- sql/hastap.sql | 2 +- sql/istap.sql | 2 +- sql/matching.sql | 2 +- sql/moretap.sql | 2 +- sql/pg73.sql | 2 +- sql/pktap.sql | 2 +- sql/throwtap.sql | 2 +- sql/todotap.sql | 2 +- sql/unique.sql | 2 +- 27 files changed, 31 insertions(+), 35 deletions(-) diff --git a/Makefile b/Makefile index f8e3afbf6170..d0eb3df3ebc0 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ endif ifeq ($(PGVER_MAJOR), 8) ifeq ($(PGVER_MINOR), 0) # Hack for E'' syntax (<= PG8.0) -EXTRAS := -e "s/ E'/ '/g" +REMOVE_E := -e "s/ E'/ '/g" # Throw isn't supported in 8.0. TESTS := $(filter-out sql/throwtap.sql,$(TESTS)) REGRESS := $(filter-out throwtap,$(REGRESS)) @@ -44,15 +44,15 @@ endif # necessary. Otherwise just copy the files. test_setup.sql: test_setup.sql.in ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' $(EXTRAS) $< >$@ + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' $< >$@ cp $< $@ endif pgtap.sql: pgtap.sql.in test_setup.sql ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' $(EXTRAS) pgtap.sql.in > pgtap.sql + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' $(REMOVE_E) pgtap.sql.in > pgtap.sql else - sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' $(EXTRAS) pgtap.sql.in > pgtap.sql + sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' $(REMOVE_E) pgtap.sql.in > pgtap.sql endif ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 3) @@ -67,14 +67,10 @@ endif uninstall_pgtap.sql: uninstall_pgtap.sql.in test_setup.sql ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' $(EXTRAS) uninstall_pgtap.sql.in > uninstall_pgtap.sql -else -ifdef EXTRAS - sed $(EXTRAS) uninstall_pgtap.sql.in > uninstall_pgtap.sql + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' uninstall_pgtap.sql.in > uninstall_pgtap.sql else cp uninstall_pgtap.sql.in uninstall_pgtap.sql endif -endif ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 3) mv uninstall_pgtap.sql uninstall_pgtap.tmp diff --git a/expected/check.out b/expected/check.out index ececb6aef454..66e6105e5c7a 100644 --- a/expected/check.out +++ b/expected/check.out @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO 1..26 ok 1 - test has_check( schema, table, description ) ok 2 - has_check( schema, table, description ) should work diff --git a/expected/cmpok.out b/expected/cmpok.out index 24165c30db1a..06a02ad96ce5 100644 --- a/expected/cmpok.out +++ b/expected/cmpok.out @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO 1..14 ok 1 - test cmp_ok( int, =, int, description ) ok 2 - cmp_ok( int, =, int, description ) should work diff --git a/expected/coltap.out b/expected/coltap.out index 97bb1885460f..753454889332 100644 --- a/expected/coltap.out +++ b/expected/coltap.out @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO 1..38 ok 1 - testing col_not_null( schema, table, column, desc ) ok 2 - col_not_null( schema, table, column, desc ) should work diff --git a/expected/fktap.out b/expected/fktap.out index aeecbea54fc1..3159f05fbcac 100644 --- a/expected/fktap.out +++ b/expected/fktap.out @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO 1..88 ok 1 - has_fk( schema, table, description ) should pass ok 2 - has_fk( schema, table, description ) should have the proper description diff --git a/expected/hastap.out b/expected/hastap.out index c257cd5b4fd2..acf0d2f8f364 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO 1..30 ok 1 - has_table(table) fail ok 2 - has_table(table) should fail for non-existent table diff --git a/expected/istap.out b/expected/istap.out index 29b9298f7d5c..070ab1db66b0 100644 --- a/expected/istap.out +++ b/expected/istap.out @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO 1..27 ok 1 - is() success ok 2 - isa(1, 1) should work diff --git a/expected/matching.out b/expected/matching.out index 7e50dd6b4594..cbbc0e9ac634 100644 --- a/expected/matching.out +++ b/expected/matching.out @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO 1..20 ok 1 - matches() should work ok 2 - matches() should work with a regex diff --git a/expected/moretap.out b/expected/moretap.out index 7abedc08f093..0dd6d393c3b2 100644 --- a/expected/moretap.out +++ b/expected/moretap.out @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO 1..30 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() diff --git a/expected/pg73.out b/expected/pg73.out index 6487dd36c293..d9472703abb4 100644 --- a/expected/pg73.out +++ b/expected/pg73.out @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO 1..39 ok 1 ok 2 - true diff --git a/expected/pktap.out b/expected/pktap.out index da0e9472fad9..9b3a7d6410cd 100644 --- a/expected/pktap.out +++ b/expected/pktap.out @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO 1..26 ok 1 - test has_pk( schema, table, description ) ok 2 - has_pk( schema, table, description ) should work diff --git a/expected/throwtap.out b/expected/throwtap.out index 73240e1bf1f7..15d635d51395 100644 --- a/expected/throwtap.out +++ b/expected/throwtap.out @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO 1..11 ok 1 - throws_ok(1/0) should work ok 2 - throws_ok failure diagnostics diff --git a/expected/todotap.out b/expected/todotap.out index 9651d9d32042..1088bd9449ed 100644 --- a/expected/todotap.out +++ b/expected/todotap.out @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO 1..13 ok 1 - todo fail ok 2 - todo pass diff --git a/expected/unique.out b/expected/unique.out index 9c898e264438..273229c087a6 100644 --- a/expected/unique.out +++ b/expected/unique.out @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO 1..26 ok 1 - test has_unique( schema, table, description ) ok 2 - has_unique( schema, table, description ) should work diff --git a/sql/check.sql b/sql/check.sql index e52c657fe2f3..d0e93616be5a 100644 --- a/sql/check.sql +++ b/sql/check.sql @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO \i test_setup.sql -- $Id: pg73.sql.in 4285 2008-09-17 21:47:16Z david $ diff --git a/sql/cmpok.sql b/sql/cmpok.sql index 1dc2a2dc4511..ccd344fa34c3 100644 --- a/sql/cmpok.sql +++ b/sql/cmpok.sql @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO \i test_setup.sql -- $Id$ diff --git a/sql/coltap.sql b/sql/coltap.sql index dc29eb439d6b..d940e027d6cb 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO \i test_setup.sql -- $Id$ diff --git a/sql/fktap.sql b/sql/fktap.sql index ecd2e3581b52..79cf980c08e6 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO \i test_setup.sql -- $Id$ diff --git a/sql/hastap.sql b/sql/hastap.sql index b6697aefc7c1..70c1fc6131c4 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO \i test_setup.sql -- $Id$ diff --git a/sql/istap.sql b/sql/istap.sql index 3bbcdd23293a..d0a9eac4db8e 100644 --- a/sql/istap.sql +++ b/sql/istap.sql @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO \i test_setup.sql -- $Id$ diff --git a/sql/matching.sql b/sql/matching.sql index 63f9072c9118..ad6fecc1e144 100644 --- a/sql/matching.sql +++ b/sql/matching.sql @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO \i test_setup.sql -- $Id$ diff --git a/sql/moretap.sql b/sql/moretap.sql index a0ead3400cd0..ec63211cb21a 100644 --- a/sql/moretap.sql +++ b/sql/moretap.sql @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO \i test_setup.sql -- $Id$ diff --git a/sql/pg73.sql b/sql/pg73.sql index 07f2a9dbd9e4..555eb90e8d16 100644 --- a/sql/pg73.sql +++ b/sql/pg73.sql @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO \i test_setup.sql -- $Id$ diff --git a/sql/pktap.sql b/sql/pktap.sql index ede608c904ac..82d565f6a75f 100644 --- a/sql/pktap.sql +++ b/sql/pktap.sql @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO \i test_setup.sql -- $Id$ diff --git a/sql/throwtap.sql b/sql/throwtap.sql index f7c5c12ac3d5..7629ec8a91a4 100644 --- a/sql/throwtap.sql +++ b/sql/throwtap.sql @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO \i test_setup.sql -- $Id$ diff --git a/sql/todotap.sql b/sql/todotap.sql index 3877dcb0bc8b..cf8c83f34c89 100644 --- a/sql/todotap.sql +++ b/sql/todotap.sql @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO \i test_setup.sql -- $Id$ diff --git a/sql/unique.sql b/sql/unique.sql index 93d7179b2d02..cb7f6815fac8 100644 --- a/sql/unique.sql +++ b/sql/unique.sql @@ -1,4 +1,4 @@ -\set ECHO +\unset ECHO \i test_setup.sql -- $Id$ From cb2c07d3218763f1c471485bf887c86d1d744351 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 19 Sep 2008 23:23:52 +0000 Subject: [PATCH 0152/1195] I kant spel. --- test_setup.sql.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_setup.sql.in b/test_setup.sql.in index e454a3338a63..b31b41c9bac8 100644 --- a/test_setup.sql.in +++ b/test_setup.sql.in @@ -15,7 +15,7 @@ SET client_min_messages = warning; -- Revert all changes on failure. -\set ON_ERROR_ROLBACK 1 +\set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true -- Load the TAP functions. From a538c95af1da195305ffefa5734c7309e30228f8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 19 Sep 2008 23:25:09 +0000 Subject: [PATCH 0153/1195] Credit. --- Changes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changes b/Changes index 920dbdd2794b..c6f69eed7865 100644 --- a/Changes +++ b/Changes @@ -8,6 +8,9 @@ Revision history for pgTAP for any `make` target other than the default. - Eliminated all uses of `E''` in the tests, so that we don't have to process them for testing on 8.0. + - Fixed the spelling of `ON_ROLLBACK` in the test setup. Can't believe I + had it with one L in all of the test files before! Thanks to Curtis + "Ovid" Poe for the spot. 0.10 2008-09-18T22:59:31 - Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the From 05e0fa923cd0c5e121aa4afb8d16115923846618 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 21 Sep 2008 16:24:05 +0000 Subject: [PATCH 0154/1195] Added some variants of `todo()` and `skip()`. Yay polymorphism! --- Changes | 4 +++ README.pgtap | 16 +++++++----- expected/todotap.out | 33 ++++++++++++++++-------- pgtap.sql.in | 34 +++++++++++++++++++++---- sql/todotap.sql | 60 ++++++++++++++++++++++++++++++++++++++------ 5 files changed, 117 insertions(+), 30 deletions(-) diff --git a/Changes b/Changes index c6f69eed7865..16214cb34f54 100644 --- a/Changes +++ b/Changes @@ -11,6 +11,10 @@ Revision history for pgTAP - Fixed the spelling of `ON_ROLLBACK` in the test setup. Can't believe I had it with one L in all of the test files before! Thanks to Curtis "Ovid" Poe for the spot. + - Added a couple of variants of `todo()` and `skip()`, since I can never + remember whether the numeric argument comes first or second. Thanks to + PostgreSQL's functional polymorphism, I don't have to. Also, there are + variants where the numeric value, if not passed, defaults to 1. 0.10 2008-09-18T22:59:31 - Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the diff --git a/README.pgtap b/README.pgtap index 4717f53eab18..2076c32e4b99 100644 --- a/README.pgtap +++ b/README.pgtap @@ -973,6 +973,8 @@ to declare that such tests are supposed to fail but will work in the future. This is known as a "todo test." ### `todo( why, how_many )` ### +### `todo( how_many )` ### +### `todo( why )` ### Declares a series of tests that you expect to fail and why. Perhaps it's because you haven't fixed a bug or haven't finished a new feature: @@ -983,12 +985,12 @@ because you haven't fixed a bug or haven't finished a new feature: SELECT is( URIGeller.yourCard(), :card, 'Is THIS your card?' ); SELECT is( URIGeller.bendSpoon(), 'bent', 'Spoon bending, how original' ); -With `todo()`, `:how_many` specifies how many tests are expected to fail. -pgTAP will run the tests normally, but print out special flags indicating they -are "todo" tests. The test harness will interpret these failures as ok. Should -any todo test pass, the harness will report it as an unexpected success. You -then know that the thing you had todo is done and can remove the call to -`todo()`. +With `todo()`, `:how_many` specifies how many tests are expected to fail. If +`:how_many` is omitted, it defaults to 1. pgTAP will run the tests normally, +but print out special flags indicating they are "todo" tests. The test harness +will interpret these failures as ok. Should any todo test pass, the harness +will report it as an unexpected success. You then know that the thing you had +todo is done and can remove the call to `todo()`. The nice part about todo tests, as opposed to simply commenting out a block of tests, is that they're like a programmatic todo list. You know how much work @@ -996,7 +998,9 @@ is left to be done, you're aware of what bugs there are, and you'll know immediately when they're fixed. ### `skip( why, how_many )` ### +### `skip( how_many, why )` ### ### `skip( why )` ### +### `skip( how_many )` ### Outputs SKIP test results. Use it in a conditional expression within a `SELECT` statement to replace the output of a test that you otherwise would diff --git a/expected/todotap.out b/expected/todotap.out index 1088bd9449ed..30c066422106 100644 --- a/expected/todotap.out +++ b/expected/todotap.out @@ -1,15 +1,26 @@ \unset ECHO -1..13 +1..24 ok 1 - todo fail ok 2 - todo pass ok 3 - TODO tests should display properly -ok 4 - simple skip should pass -ok 5 - simple skip should have the proper description -ok 6 - simple skip should have the proper diagnostics -ok 7 - skip with num should pass -ok 8 - skip with num should have the proper description -ok 9 - skip with num should have the proper diagnostics -ok 10 Skip multiple -ok 11 Skip multiple -ok 12 Skip multiple -ok 13 - We should get the proper output for multiple skips +ok 4 - todo fail +ok 5 - Single default todo test should display properly +ok 6 - todo fail +ok 7 - todo pass +ok 8 - TODO tests should display properly +ok 9 - simple skip should pass +ok 10 - simple skip should have the proper description +ok 11 - simple skip should have the proper diagnostics +ok 12 - skip with num should pass +ok 13 - skip with num should have the proper description +ok 14 - skip with num should have the proper diagnostics +ok 15 - Skip multiple +ok 16 - Skip multiple +ok 17 - Skip multiple +ok 18 - We should get the proper output for multiple skips +ok 19 - inverted skip should pass +ok 20 - inverted skip should have the proper description +ok 21 - inverted skip should have the proper diagnostics +ok 22 - num only should pass +ok 23 - num only should have the proper description +ok 24 - num only should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 3d80d4bc93f7..64175e9b9198 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -92,7 +92,7 @@ DECLARE rcount integer; BEGIN EXECUTE 'UPDATE __tcache__ SET value = ' || $2 - || CASE $3 WHEN '' THEN '' ELSE ', note = ' || quote_literal($3) END + || 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 @@ -100,7 +100,7 @@ BEGIN END IF; RETURN $2; END; -$$ LANGUAGE plpgsql strict; +$$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION _set ( text, integer ) RETURNS integer AS $$ @@ -436,7 +436,23 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION todo ( why text, how_many int ) RETURNS SETOF BOOLEAN AS $$ BEGIN - PERFORM _set('todo', COALESCE( _get('todo'), 0) + how_many, why ); + PERFORM _set('todo', COALESCE( _get('todo'), 0) + how_many, COALESCE(why, '') ); + RETURN; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION todo ( why text ) +RETURNS SETOF BOOLEAN AS $$ +BEGIN + PERFORM _set('todo', COALESCE( _get('todo'), 0) + 1, COALESCE(why, '') ); + RETURN; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION todo ( how_many int ) +RETURNS SETOF BOOLEAN AS $$ +BEGIN + PERFORM _set('todo', COALESCE( _get('todo'), 0) + how_many, '' ); RETURN; END; $$ LANGUAGE plpgsql; @@ -451,7 +467,7 @@ BEGIN RETURN NULL; END IF; -- Decrement the count of todos and return the reason. - PERFORM _set('todo', todos - 1); + PERFORM _set('todo', todos - 1, NULL); RETURN _get_note('todo'); END; $$ LANGUAGE plpgsql; @@ -463,7 +479,7 @@ DECLARE BEGIN output := '{}'; FOR i IN 1..how_many LOOP - output = array_append(output, ok( TRUE, 'SKIP: ' || why ) ); + output = array_append(output, ok( TRUE, 'SKIP: ' || COALESCE( why, '') ) ); END LOOP; RETURN array_to_string(output, E'\n'); END; @@ -474,6 +490,14 @@ RETURNS TEXT AS $$ SELECT ok( TRUE, 'SKIP: ' || $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 throws_ok ( TEXT, CHAR(5), TEXT ) RETURNS TEXT AS $$ DECLARE diff --git a/sql/todotap.sql b/sql/todotap.sql index cf8c83f34c89..b68c69690db8 100644 --- a/sql/todotap.sql +++ b/sql/todotap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(13); +SELECT plan(24); /****************************************************************************/ -- Test todo tests. @@ -19,7 +19,32 @@ SELECT is( ok 2 - This is a todo test that unexpectedly passes # TODO just because', 'TODO tests should display properly' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 2 ); + +-- Try just a reason. +\echo ok 4 - todo fail +SELECT * FROM todo( 'for whatever reason' ); +SELECT is( + fail('Another todo test'), + 'not ok 4 - Another todo test # TODO for whatever reason +# Failed (TODO) test 4: "Another todo test"', + 'Single default todo test should display properly' +); +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 2, 4 ); + +-- Try just a number. +\echo ok 6 - todo fail +\echo ok 7 - todo pass +SELECT * FROM todo( 2 ); +SELECT is( + fail('This is a todo test' ) || ' +' + || pass('This is a todo test that unexpectedly passes' ), + 'not ok 6 - This is a todo test # TODO +# Failed (TODO) test 6: "This is a todo test" +ok 7 - This is a todo test that unexpectedly passes # TODO ', + 'TODO tests should display properly' +); + /****************************************************************************/ -- Test skipping tests. @@ -39,17 +64,36 @@ SELECT * FROM check_test( '' ); -\echo ok 10 Skip multiple -\echo ok 11 Skip multiple -\echo ok 12 Skip multiple +\echo ok 15 - Skip multiple +\echo ok 16 - Skip multiple +\echo ok 17 - Skip multiple SELECT is( skip( 'Whatever', 3 ), - 'ok 10 - SKIP: Whatever -ok 11 - SKIP: Whatever -ok 12 - SKIP: Whatever', + 'ok 15 - SKIP: Whatever +ok 16 - SKIP: Whatever +ok 17 - SKIP: Whatever', 'We should get the proper output for multiple skips' ); +-- Test inversion. +SELECT * FROM check_test( + skip(1, 'Just because'), + true, + 'inverted skip', + 'SKIP: Just because', + '' +); + +-- Test num only. +SELECT * FROM check_test( + skip(1), + true, + 'num only', + 'SKIP: ', + '' +); + + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 19de672ffb53c3f8806c5b2fd53b5e1ed28fc283 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 21 Sep 2008 21:36:27 +0000 Subject: [PATCH 0155/1195] TODO tests now properly nest. --- Changes | 2 ++ README.pgtap | 64 ++++++++++++++++++--------------- expected/todotap.out | 6 +++- pgtap.sql.in | 86 +++++++++++++++++++++++++++++++++++++------- sql/todotap.sql | 32 +++++++++++++++-- 5 files changed, 147 insertions(+), 43 deletions(-) diff --git a/Changes b/Changes index 16214cb34f54..82b41d6de934 100644 --- a/Changes +++ b/Changes @@ -15,6 +15,8 @@ Revision history for pgTAP remember whether the numeric argument comes first or second. Thanks to PostgreSQL's functional polymorphism, I don't have to. Also, there are variants where the numeric value, if not passed, defaults to 1. + - Updated the link to the pgTAP home page in `pgtap.sql.in`. + - TODO tests can now nest. 0.10 2008-09-18T22:59:31 - Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the diff --git a/README.pgtap b/README.pgtap index 2076c32e4b99..b3ec4aa5f342 100644 --- a/README.pgtap +++ b/README.pgtap @@ -967,35 +967,18 @@ Which would produce: Conditional Tests ----------------- -Sometimes you might have tests that you want to pass, but you haven't gotten -around to implementing the logic required to make them pass. pgTAP allows you -to declare that such tests are supposed to fail but will work in the future. -This is known as a "todo test." - -### `todo( why, how_many )` ### -### `todo( how_many )` ### -### `todo( why )` ### - -Declares a series of tests that you expect to fail and why. Perhaps it's -because you haven't fixed a bug or haven't finished a new feature: - - todo('URIGeller not finished', 2); - - \set card '\'' Eight of clubs '\'' - SELECT is( URIGeller.yourCard(), :card, 'Is THIS your card?' ); - SELECT is( URIGeller.bendSpoon(), 'bent', 'Spoon bending, how original' ); +Sometimes running a test under certain conditions will cause the test script +to die. A certain function or method isn't implemented (such as fork() on +MacOS), some resource isn't available (like a net connection) or a module +isn't available. In these cases it's necessary to skip tests, or declare that +they are supposed to fail but will work in the future (a todo test). -With `todo()`, `:how_many` specifies how many tests are expected to fail. If -`:how_many` is omitted, it defaults to 1. pgTAP will run the tests normally, -but print out special flags indicating they are "todo" tests. The test harness -will interpret these failures as ok. Should any todo test pass, the harness -will report it as an unexpected success. You then know that the thing you had -todo is done and can remove the call to `todo()`. - -The nice part about todo tests, as opposed to simply commenting out a block of -tests, is that they're like a programmatic todo list. You know how much work -is left to be done, you're aware of what bugs there are, and you'll know -immediately when they're fixed. +Sometimes you might have tests that you want to pass, but you haven't gotten +around to implementing the logic required to make them pass Other times, you +might have tests that pass only under certain circumstances, such as with +particular versions of PostgreSQL. In these cases it's necessary to skip +tests, or to declare that they are supposed to fail but will work in the +future (a todo test). ### `skip( why, how_many )` ### ### `skip( how_many, why )` ### @@ -1037,6 +1020,31 @@ multiple rows: This will cause it to skip the same number of rows as would have been tested had the `WHEN` condition been true. +### `todo( why, how_many )` ### +### `todo( how_many )` ### +### `todo( why )` ### + +Declares a series of tests that you expect to fail and why. Perhaps it's +because you haven't fixed a bug or haven't finished a new feature: + + todo('URIGeller not finished', 2); + + \set card '\'' Eight of clubs '\'' + SELECT is( URIGeller.yourCard(), :card, 'Is THIS your card?' ); + SELECT is( URIGeller.bendSpoon(), 'bent', 'Spoon bending, how original' ); + +With `todo()`, `:how_many` specifies how many tests are expected to fail. If +`:how_many` is omitted, it defaults to 1. pgTAP will run the tests normally, +but print out special flags indicating they are "todo" tests. The test harness +will interpret these failures as ok. Should any todo test pass, the harness +will report it as an unexpected success. You then know that the thing you had +todo is done and can remove the call to `todo()`. + +The nice part about todo tests, as opposed to simply commenting out a block of +tests, is that they're like a programmatic todo list. You know how much work +is left to be done, you're aware of what bugs there are, and you'll know +immediately when they're fixed. + Writing Test Functions ====================== diff --git a/expected/todotap.out b/expected/todotap.out index 30c066422106..52d176670c56 100644 --- a/expected/todotap.out +++ b/expected/todotap.out @@ -1,5 +1,5 @@ \unset ECHO -1..24 +1..28 ok 1 - todo fail ok 2 - todo pass ok 3 - TODO tests should display properly @@ -24,3 +24,7 @@ ok 21 - inverted skip should have the proper diagnostics ok 22 - num only should pass ok 23 - num only should have the proper description ok 24 - num only should have the proper diagnostics +ok 25 - todo fail +ok 26 - todo fail +ok 27 - todo fail +ok 28 - Nested todos should work properly diff --git a/pgtap.sql.in b/pgtap.sql.in index 64175e9b9198..743bb69943cb 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -6,7 +6,7 @@ -- -- The home page for the pgTAP project is: -- --- http://pgfoundry.org/projects/pgtap/ +-- http://pgtap.projects.postgresql.org/ -- $Id$ -- ## CREATE SCHEMA TAPSCHEMA; @@ -25,6 +25,7 @@ BEGIN BEGIN EXECUTE ' CREATE TEMP TABLE __tcache__ ( + id SERIAL PRIMARY KEY, label TEXT NOT NULL, value INTEGER NOT NULL, note TEXT NOT NULL DEFAULT '''' @@ -40,8 +41,7 @@ BEGIN reason TEXT NOT NULL DEFAULT '''' ); GRANT ALL ON TABLE __tresults__ TO PUBLIC; - GRANT ALL ON TABLE __tresults___numb_seq TO PUBLIC; - '; + GRANT ALL ON TABLE __tresults___numb_seq TO PUBLIC;'; EXCEPTION WHEN duplicate_table THEN -- Raise an exception if there's already a plan. @@ -76,6 +76,18 @@ BEGIN 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; +END; +$$ LANGUAGE plpgsql strict; + CREATE OR REPLACE FUNCTION _get_note ( text ) RETURNS text AS $$ DECLARE @@ -86,6 +98,16 @@ BEGIN 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 @@ -96,7 +118,7 @@ BEGIN || ' WHERE label = ' || quote_literal($1); GET DIAGNOSTICS rcount = ROW_COUNT; IF rcount = 0 THEN - EXECUTE 'INSERT INTO __tcache__ values (' || quote_literal($1) || ', ' || $2 || ', ' || quote_literal(COALESCE($3, '')) || ')'; + RETURN _add( $1, $2, $3 ); END IF; RETURN $2; END; @@ -107,6 +129,29 @@ 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 @@ -436,7 +481,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION todo ( why text, how_many int ) RETURNS SETOF BOOLEAN AS $$ BEGIN - PERFORM _set('todo', COALESCE( _get('todo'), 0) + how_many, COALESCE(why, '') ); + PERFORM _add('todo', COALESCE(how_many, 1), COALESCE(why, '')); RETURN; END; $$ LANGUAGE plpgsql; @@ -444,7 +489,7 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION todo ( why text ) RETURNS SETOF BOOLEAN AS $$ BEGIN - PERFORM _set('todo', COALESCE( _get('todo'), 0) + 1, COALESCE(why, '') ); + PERFORM _add('todo', 1, COALESCE(why, '')); RETURN; END; $$ LANGUAGE plpgsql; @@ -452,7 +497,7 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION todo ( how_many int ) RETURNS SETOF BOOLEAN AS $$ BEGIN - PERFORM _set('todo', COALESCE( _get('todo'), 0) + how_many, '' ); + PERFORM _add('todo', COALESCE(how_many, 1), ''); RETURN; END; $$ LANGUAGE plpgsql; @@ -460,15 +505,32 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION _todo() RETURNS TEXT AS $$ DECLARE - todos INT; + todos INT[]; + note text; BEGIN - todos := _get('todo'); - IF todos IS NULL OR todos = 0 THEN + -- 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 todos and return the reason. - PERFORM _set('todo', todos - 1, NULL); - RETURN _get_note('todo'); + PERFORM _set(todos[1], todos[2] - 1); + 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; diff --git a/sql/todotap.sql b/sql/todotap.sql index b68c69690db8..0f9aff990cec 100644 --- a/sql/todotap.sql +++ b/sql/todotap.sql @@ -3,7 +3,8 @@ -- $Id$ -SELECT plan(24); +SELECT plan(28); +--SELECT * FROM no_plan(); /****************************************************************************/ -- Test todo tests. @@ -45,7 +46,6 @@ ok 7 - This is a todo test that unexpectedly passes # TODO ', 'TODO tests should display properly' ); - /****************************************************************************/ -- Test skipping tests. SELECT * FROM check_test( @@ -93,6 +93,34 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Try nesting todo tests. +\echo ok 25 - todo fail +\echo ok 26 - todo fail +\echo ok 27 - todo fail +SELECT * FROM todo('just because', 2 ); +SELECT is( + ARRAY( + SELECT fail('This is a todo test 1') + UNION + SELECT todo::text FROM todo('inside') + UNION + SELECT fail('This is a todo test 2') + UNION + SELECT fail('This is a todo test 3') + ), + ARRAY[ + 'not ok 25 - This is a todo test 1 # TODO just because +# Failed (TODO) test 25: "This is a todo test 1"', + 'not ok 26 - This is a todo test 2 # TODO inside +# Failed (TODO) test 26: "This is a todo test 2"', + 'not ok 27 - This is a todo test 3 # TODO just because +# Failed (TODO) test 27: "This is a todo test 3"' + ], + 'Nested todos should work properly' +); + +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 25, 26, 27 ); /****************************************************************************/ -- Finish the tests and clean up. From 2470d7f566bd05d332ac188f5af7fdc66f46804c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 21 Sep 2008 22:22:35 +0000 Subject: [PATCH 0156/1195] Added `todo_start()`, `todo_end()` and `in_todo()`. --- Changes | 1 + README.pgtap | 50 +++++++++++++++++++++++++++++++- expected/todotap.out | 7 ++++- pgtap.sql.in | 68 +++++++++++++++++++++++++++++++++++++++----- sql/todotap.sql | 46 +++++++++++++++++++++++++++++- 5 files changed, 162 insertions(+), 10 deletions(-) diff --git a/Changes b/Changes index 82b41d6de934..6ed919b43a88 100644 --- a/Changes +++ b/Changes @@ -17,6 +17,7 @@ Revision history for pgTAP variants where the numeric value, if not passed, defaults to 1. - Updated the link to the pgTAP home page in `pgtap.sql.in`. - TODO tests can now nest. + - Added `todo_start()`, `todo_end()`, and `in_todo()`. 0.10 2008-09-18T22:59:31 - Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the diff --git a/README.pgtap b/README.pgtap index b3ec4aa5f342..1a02e84bea88 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1045,6 +1045,54 @@ tests, is that they're like a programmatic todo list. You know how much work is left to be done, you're aware of what bugs there are, and you'll know immediately when they're fixed. +### `todo_start( :why ) ### +### `todo_start( ) ### + +This function allows you declare all subsequent tests as TODO tests, up until +the `todo_end()` function is called. + +The `todo()` syntax is generally pretty good about figuring out whether or not +we're in a TODO test. However, often we find it difficult to specify the +*number* of tests that are TODO tests. Thus, you can instead use +`todo_start()` and `todo_end()` to more easily define the scope of your TODO +tests. + +Note that you can nest TODO tests, too: + + SELECT todo_start('working on this'); + -- lots of code + SELECT todo_start('working on that'); + -- more code + SELECT todo_end(); + SELECT todo_end(); + +This is generally not recommended, but large testing systems often have weird +internal needs. + +The `todo_start()` and `todo_end()` function should also work with the +`todo()` function, although it's not guaranteed and its use is also +discouraged: + + + SELECT todo_start('working on this'); + -- lots of code + SELECT todo('working on that', 2); + -- Two tests for which the above line applies + -- Followed by more tests scoped till the following line. + SELECT todo_end(); + +We recommend that you pick one style or another of TODO to be on the safe +side. + +### todo_end() ### + +Stops running tests as TODO tests. This function is fatal if called without a +preceding `todo_start()` method call. + +### in_todo() ### + +Returns true if the test is currently inside a TODO block. + Writing Test Functions ====================== @@ -1274,10 +1322,10 @@ To Do * Update the Makefile to process pg_prove and set the proper path to Perl on the shebang line. Will likely require a `$(PERL)` environment variable. * Update the Makefile to require TAP::Harness. -* Add `todo_start()` and `todo_end()` (and `in_todo()`). * Add `note()`? * Add `can_ok()`. * Finish porting tests to use `check_test()`. +* Add throws_ok() version that tests the message. Suported Versions ----------------- diff --git a/expected/todotap.out b/expected/todotap.out index 52d176670c56..e59349eab891 100644 --- a/expected/todotap.out +++ b/expected/todotap.out @@ -1,5 +1,5 @@ \unset ECHO -1..28 +1..33 ok 1 - todo fail ok 2 - todo pass ok 3 - TODO tests should display properly @@ -28,3 +28,8 @@ ok 25 - todo fail ok 26 - todo fail ok 27 - todo fail ok 28 - Nested todos should work properly +ok 29 - todo fail +ok 30 - todo fail +ok 31 - todo fail +ok 32 - todo_start() and todo_end() should work properly with in_todo() +ok 33 - Should get an exception when todo_end() is called without todo_start() diff --git a/pgtap.sql.in b/pgtap.sql.in index 743bb69943cb..c8ce8f2f6f00 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -88,6 +88,17 @@ BEGIN 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 @@ -502,6 +513,46 @@ BEGIN 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 @@ -521,8 +572,10 @@ BEGIN EXECUTE 'DELETE FROM __tcache__ WHERE id = ' || todos[1]; RETURN NULL; END IF; - -- Decrement the count of todos and return the reason. - PERFORM _set(todos[1], todos[2] - 1); + -- 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 @@ -1007,8 +1060,8 @@ RETURNS NAME[] AS $$ $$ LANGUAGE sql; -- Borrowed from newsysviews: http://pgfoundry.org/projects/newsysviews/ -CREATE OR REPLACE FUNCTION _pg_sv_column_array(oid, smallint[]) RETURNS NAME[] -AS $$ +CREATE OR REPLACE FUNCTION _pg_sv_column_array(oid, smallint[]) +RETURNS NAME[] AS $$ SELECT ARRAY( SELECT a.attname FROM pg_catalog.pg_attribute a @@ -1019,8 +1072,8 @@ AS $$ $$ LANGUAGE SQL stable; -- Borrowed from newsysviews: http://pgfoundry.org/projects/newsysviews/ -CREATE OR REPLACE FUNCTION _pg_sv_table_accessible(oid,oid) RETURNS BOOLEAN -as $$ +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') @@ -1034,7 +1087,8 @@ as $$ $$ LANGUAGE SQL immutable strict; -- Borrowed from newsysviews: http://pgfoundry.org/projects/newsysviews/ -CREATE OR REPLACE VIEW pg_all_foreign_keys AS +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, diff --git a/sql/todotap.sql b/sql/todotap.sql index 0f9aff990cec..beb759d628e8 100644 --- a/sql/todotap.sql +++ b/sql/todotap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(28); +SELECT plan(33); --SELECT * FROM no_plan(); /****************************************************************************/ @@ -122,6 +122,50 @@ SELECT is( UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 25, 26, 27 ); +/****************************************************************************/ +-- Test todo_start() and todo_end(). +\echo ok 29 - todo fail +\echo ok 30 - todo fail +\echo ok 31 - todo fail +SELECT * FROM todo_start('some todos'); +SELECT is( + ARRAY( + SELECT fail('This is a todo test 1') AS stuff + UNION + SELECT in_todo()::text + UNION + SELECT todo::text FROM todo('inside') + UNION + SELECT fail('This is a todo test 2') + UNION + SELECT fail('This is a todo test 3') + UNION + SELECT todo_end::text FROM todo_end() + UNION + SELECT in_todo()::text + ORDER BY stuff + ), + ARRAY[ + 'false', + 'not ok 29 - This is a todo test 1 # TODO some todos +# Failed (TODO) test 29: "This is a todo test 1"', + 'not ok 30 - This is a todo test 2 # TODO inside +# Failed (TODO) test 30: "This is a todo test 2"', + 'not ok 31 - This is a todo test 3 # TODO some todos +# Failed (TODO) test 31: "This is a todo test 3"', + 'true' + ], + 'todo_start() and todo_end() should work properly with in_todo()' +); + +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 29, 30, 31 ); + +SELECT throws_ok( + 'SELECT todo_end()', + 'P0001', + 'Should get an exception when todo_end() is called without todo_start()' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From aac215d090bf48e0b39ffa5c4fe0d8eebb1cfa46 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 21 Sep 2008 22:24:56 +0000 Subject: [PATCH 0157/1195] Updated `uninstall_pgtap.sql.in`. --- uninstall_pgtap.sql.in | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index d8ea6dbe9d5c..63b0569b6ac1 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,5 +1,4 @@ -- $Id$ -DROP VIEW pg_all_foreign_keys; DROP FUNCTION check_test( TEXT, BOOLEAN ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); @@ -45,8 +44,9 @@ DROP FUNCTION col_is_pk ( NAME, NAME, NAME, TEXT ); DROP FUNCTION col_is_pk ( NAME, NAME[] ); DROP FUNCTION col_is_pk ( NAME, NAME[], TEXT ); DROP FUNCTION col_is_pk ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _pg_sv_table_accessible(oid,oid) RETURNS BOOLEAN; -DROP FUNCTION _pg_sv_column_array(oid, smallint[]) RETURNS NAME[]; +DROP VIEW pg_all_foreign_keys ; +DROP FUNCTION _pg_sv_table_accessible(oid,oid); +DROP FUNCTION _pg_sv_column_array(oid, smallint[]); DROP FUNCTION _ckeys ( NAME, CHAR ); DROP FUNCTION _ckeys ( NAME, NAME, CHAR ); DROP FUNCTION has_pk ( NAME ); @@ -85,9 +85,17 @@ DROP FUNCTION throws_ok ( TEXT, int4, TEXT ); DROP FUNCTION throws_ok ( TEXT ); DROP FUNCTION throws_ok ( TEXT, CHAR(5) ); DROP FUNCTION throws_ok ( TEXT, CHAR(5), TEXT ); +DROP FUNCTION skip( int ); +DROP FUNCTION skip( int, text ); DROP FUNCTION skip ( text ); DROP FUNCTION skip ( why text, how_many int ); DROP FUNCTION _todo(); +DROP FUNCTION todo_end (); +DROP FUNCTION in_todo (); +DROP FUNCTION todo_start (); +DROP FUNCTION todo_start (text); +DROP FUNCTION todo ( how_many int ); +DROP FUNCTION todo ( why text ); DROP FUNCTION todo ( why text, how_many int ); DROP FUNCTION fail (); DROP FUNCTION fail ( text ); @@ -123,9 +131,15 @@ DROP FUNCTION diag ( msg text ); DROP FUNCTION finish (); DROP FUNCTION num_failed (); DROP FUNCTION add_result ( bool, bool, text, text, text ); +DROP FUNCTION _add ( text, integer ); +DROP FUNCTION _add ( text, integer, text ); +DROP FUNCTION _set ( integer, integer ); DROP FUNCTION _set ( text, integer ); DROP FUNCTION _set ( text, integer, text ); +DROP FUNCTION _get_note ( integer ); DROP FUNCTION _get_note ( text ); +DROP FUNCTION _get_latest ( text, integer ); +DROP FUNCTION _get_latest ( text ); DROP FUNCTION _get ( text ); DROP FUNCTION no_plan(); DROP FUNCTION plan( integer ); From 555f3bb472e710e5b6f7fadcdc567d9132273a3c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 21 Sep 2008 22:26:09 +0000 Subject: [PATCH 0158/1195] Formatting. --- pgtap.sql.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index c8ce8f2f6f00..e889dc773afa 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1060,7 +1060,7 @@ RETURNS NAME[] AS $$ $$ LANGUAGE sql; -- Borrowed from newsysviews: http://pgfoundry.org/projects/newsysviews/ -CREATE OR REPLACE FUNCTION _pg_sv_column_array(oid, smallint[]) +CREATE OR REPLACE FUNCTION _pg_sv_column_array( OID, SMALLINT[] ) RETURNS NAME[] AS $$ SELECT ARRAY( SELECT a.attname @@ -1072,7 +1072,7 @@ RETURNS NAME[] AS $$ $$ LANGUAGE SQL stable; -- Borrowed from newsysviews: http://pgfoundry.org/projects/newsysviews/ -CREATE OR REPLACE FUNCTION _pg_sv_table_accessible(oid,oid) +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') From 996036250c0eabfc3bbebb2e73fe941307a6bb1d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 21 Sep 2008 22:50:19 +0000 Subject: [PATCH 0159/1195] Backported to 8.0 again. --- compat/install-8.0.patch | 72 +++++++++++++++++++++++++++++++++------- sql/todotap.sql | 32 ++++++++++-------- 2 files changed, 79 insertions(+), 25 deletions(-) diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index 1fd5ed33bcea..0c8b4dd8d84f 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,8 +1,6 @@ -Index: pgtap.sql -=================================================================== ---- pgtap.sql (revision 4280) -+++ pgtap.sql (working copy) -@@ -69,20 +69,24 @@ +--- pgtap.sql.saf 2008-09-21 15:38:31.000000000 -0700 ++++ pgtap.sql 2008-09-21 15:44:50.000000000 -0700 +@@ -69,53 +69,63 @@ CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ DECLARE @@ -18,6 +16,41 @@ Index: pgtap.sql END; $$ LANGUAGE plpgsql strict; + CREATE OR REPLACE FUNCTION _get_latest ( text ) + RETURNS integer[] AS $$ + DECLARE +- ret integer[]; ++ rec RECORD; + BEGIN +- EXECUTE 'SELECT ARRAY[ id, value] FROM __tcache__ WHERE label = ' || ++ FOR rec IN EXECUTE 'SELECT ARRAY[ id, value] AS a 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; ++ quote_literal($1) || ') LIMIT 1' LOOP ++ RETURN rec.a; ++ END LOOP; ++ RETURN NULL; + END; + $$ LANGUAGE plpgsql strict; + + CREATE OR REPLACE FUNCTION _get_latest ( text, integer ) + RETURNS integer AS $$ + DECLARE +- ret integer; ++ rec RECORD; + BEGIN +- EXECUTE 'SELECT MAX(id) FROM __tcache__ WHERE label = ' || +- quote_literal($1) || ' AND value = ' || $2 INTO ret; +- RETURN ret; ++ FOR rec IN EXECUTE 'SELECT MAX(id) AS id FROM __tcache__ WHERE label = ' || ++ quote_literal($1) || ' AND value = ' || $2 LOOP ++ RETURN rec.id; ++ END LOOP; ++ RETURN NULL; + END; + $$ LANGUAGE plpgsql strict; + CREATE OR REPLACE FUNCTION _get_note ( text ) RETURNS text AS $$ DECLARE @@ -27,13 +60,28 @@ Index: pgtap.sql - EXECUTE 'SELECT note FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' INTO ret; - RETURN ret; + FOR rec IN EXECUTE 'SELECT note FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' LOOP -+ RETURN rec.NOTE; ++ RETURN rec.note; ++ END LOOP; ++ RETURN NULL; + END; + $$ LANGUAGE plpgsql strict; + + CREATE OR REPLACE FUNCTION _get_note ( integer ) + RETURNS text AS $$ + DECLARE +- ret text; ++ rec RECORD; + BEGIN +- EXECUTE 'SELECT note FROM __tcache__ WHERE id = ' || $1 || ' LIMIT 1' INTO ret; +- RETURN ret; ++ FOR rec IN EXECUTE 'SELECT note FROM __tcache__ WHERE id = ' || $1 || ' LIMIT 1' LOOP ++ RETURN rec.note; + END LOOP; + RETURN NULL; END; $$ LANGUAGE plpgsql strict; -@@ -123,10 +127,12 @@ +@@ -179,10 +189,12 @@ CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ DECLARE @@ -49,7 +97,7 @@ Index: pgtap.sql END; $$ LANGUAGE plpgsql strict; -@@ -392,13 +398,16 @@ +@@ -448,13 +460,16 @@ want ALIAS FOR $3; descr ALIAS FOR $4; result BOOLEAN; @@ -67,9 +115,9 @@ Index: pgtap.sql + result := rec.res; + END LOOP; output := ok( COALESCE(result, FALSE), descr ); - RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( + RETURN output || CASE result WHEN TRUE THEN '' ELSE '\n' || diag( ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -784,15 +793,16 @@ +@@ -923,15 +938,16 @@ CREATE OR REPLACE FUNCTION _def_is( TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -90,7 +138,7 @@ Index: pgtap.sql END; $$ LANGUAGE plpgsql; -@@ -1378,6 +1390,7 @@ +@@ -1520,6 +1536,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -98,7 +146,7 @@ Index: pgtap.sql have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -1388,8 +1401,10 @@ +@@ -1530,8 +1547,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. diff --git a/sql/todotap.sql b/sql/todotap.sql index beb759d628e8..ba06391ebb00 100644 --- a/sql/todotap.sql +++ b/sql/todotap.sql @@ -99,8 +99,9 @@ SELECT * FROM check_test( \echo ok 26 - todo fail \echo ok 27 - todo fail SELECT * FROM todo('just because', 2 ); +-- We have to use textin(array_out()) to get around a missing cast to text in 8.0. SELECT is( - ARRAY( + textin(array_out(ARRAY( SELECT fail('This is a todo test 1') UNION SELECT todo::text FROM todo('inside') @@ -108,15 +109,15 @@ SELECT is( SELECT fail('This is a todo test 2') UNION SELECT fail('This is a todo test 3') - ), - ARRAY[ + ))), + textin(array_out(ARRAY[ 'not ok 25 - This is a todo test 1 # TODO just because # Failed (TODO) test 25: "This is a todo test 1"', 'not ok 26 - This is a todo test 2 # TODO inside # Failed (TODO) test 26: "This is a todo test 2"', 'not ok 27 - This is a todo test 3 # TODO just because # Failed (TODO) test 27: "This is a todo test 3"' - ], + ])), 'Nested todos should work properly' ); @@ -128,8 +129,9 @@ UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 25, 26, 27 ); \echo ok 30 - todo fail \echo ok 31 - todo fail SELECT * FROM todo_start('some todos'); +-- We have to use textin(array_out()) to get around a missing cast to text in 8.0. SELECT is( - ARRAY( + textin(array_out(ARRAY( SELECT fail('This is a todo test 1') AS stuff UNION SELECT in_todo()::text @@ -144,8 +146,8 @@ SELECT is( UNION SELECT in_todo()::text ORDER BY stuff - ), - ARRAY[ + ))), + textin(array_out(ARRAY[ 'false', 'not ok 29 - This is a todo test 1 # TODO some todos # Failed (TODO) test 29: "This is a todo test 1"', @@ -154,17 +156,21 @@ SELECT is( 'not ok 31 - This is a todo test 3 # TODO some todos # Failed (TODO) test 31: "This is a todo test 3"', 'true' - ], + ])), 'todo_start() and todo_end() should work properly with in_todo()' ); UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 29, 30, 31 ); -SELECT throws_ok( - 'SELECT todo_end()', - 'P0001', - 'Should get an exception when todo_end() is called without todo_start()' -); +-- Test the exception when throws_ok() is available. +SELECT CASE WHEN substring(version() from '[[:digit:]]+[.][[:digit:]]')::numeric < 8.1 + then 'ok 33 - Should get an exception when todo_end() is called without todo_start()' + ELSE throws_ok( + 'SELECT todo_end()', + 'P0001', + 'Should get an exception when todo_end() is called without todo_start()' + ) + END; /****************************************************************************/ -- Finish the tests and clean up. From 2cc295c56f4eb774286ef1584881c118636b8599 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 22 Sep 2008 18:41:40 +0000 Subject: [PATCH 0160/1195] * Added variants of `throws_ok()` that will test the error message as well as the error code. * Moved testing of multiline description from `sql/throwtap.sql` to `sql/moretap.sql`, where it belongs. * Switched to `check_test()` in `sql/throwtap.sql`. * Now testing for the exception message for an invalid call to `todo_end()` in `t/todotap.sql`. * Changed the documentation and comments for `throws_ok()` and `lives_ok()` to use the word "query" for their first augments, rather than "text" or "sql". --- Changes | 3 ++ README.pgtap | 31 ++++++++---- expected/moretap.out | 4 +- expected/throwtap.out | 41 +++++++++++----- pgtap.sql.in | 77 +++++++++++++++++++++++------- sql/moretap.sql | 13 ++++- sql/throwtap.sql | 107 +++++++++++++++++++++++++++--------------- sql/todotap.sql | 1 + 8 files changed, 198 insertions(+), 79 deletions(-) diff --git a/Changes b/Changes index 6ed919b43a88..526dd3c61e0d 100644 --- a/Changes +++ b/Changes @@ -18,6 +18,9 @@ Revision history for pgTAP - Updated the link to the pgTAP home page in `pgtap.sql.in`. - TODO tests can now nest. - Added `todo_start()`, `todo_end()`, and `in_todo()`. + - Added variants of `throws_ok()` that test error messages as well as + error codes. + - Converted some more tests to use `check_test()`. 0.10 2008-09-18T22:59:31 - Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the diff --git a/README.pgtap b/README.pgtap index 1a02e84bea88..555a10e7d3bb 100644 --- a/README.pgtap +++ b/README.pgtap @@ -467,13 +467,17 @@ Or maybe you want to make sure a query *does not* trigger an error. For such cases, we provide a couple of test functions to make sure your queries are as error-prone as you think they should be. -### `throws_ok( text, errcode, description )` ### -### `throws_ok( text, errcode )` ### -### `throws_ok( text )` ### +### `throws_ok( query, errcode, errmsg, description )` ### +### `throws_ok( query, errcode, errmsg )` ### +### `throws_ok( query, errmsg, description )` ### +### `throws_ok( query, errcode )` ### +### `throws_ok( query, errmsg )` ### +### `throws_ok( query )` ### SELECT throws_ok( 'INSERT INTO try (id) VALUES (1)', '23505', + 'duplicate key value violates unique constraint "try_pkey"', 'We should get a unique violation for a duplicate PK' ); @@ -494,7 +498,19 @@ A.](http://www.postgresql.org/docs/current/static/errcodes-appendix.html "Appendix A. PostgreSQL Error Codes") in the [PostgreSQL documentation](http://www.postgresql.org/docs/current/static/). -The third argument is of course a brief test description. +The third argument is an error message. This will be most useful for functions +you've written that raise exceptions, so that you can test the excption +message that you've thrown. Otherwise, for core errors, you'll need to be +careful of localized error messages. + +The fourth argument is of course a brief test description. + +For the three- and two-argument forms of `throws_ok()`, if the second argument +is exactly five bytes long, it is assumed to be an error code and the optional +third argument is the error message. Otherwise, the second argument is assumed +to be an error message and the third argument is a description. If for some +reason you need to test an error message that is five bytes long, use the +four-argument form. Should a `throws_ok()` test fail it produces appropriate diagnostic messages. For example: @@ -502,12 +518,12 @@ For example: not ok 81 - simple error # Failed test "This should not die" # caught: 23505: duplicate key value violates unique constraint "try_pkey" - # wanted: 23506 + # wanted: 23502: null value in column "id" violates not-null constraint Idea borrowed from the Test::Exception Perl module. -### `lives_ok( text, description )` ### -### `lives_ok( text )` ### +### `lives_ok( query, description )` ### +### `lives_ok( query )` ### SELECT lives_ok( 'INSERT INTO try (id) VALUES (1)', @@ -1325,7 +1341,6 @@ To Do * Add `note()`? * Add `can_ok()`. * Finish porting tests to use `check_test()`. -* Add throws_ok() version that tests the message. Suported Versions ----------------- diff --git a/expected/moretap.out b/expected/moretap.out index 0dd6d393c3b2..04fbb1946d08 100644 --- a/expected/moretap.out +++ b/expected/moretap.out @@ -1,5 +1,5 @@ \unset ECHO -1..30 +1..32 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -30,3 +30,5 @@ ok 27 - ok() failure 3 ok 28 - ok(false, 'foo') should work ok 29 - Multline diagnostics ok 30 - multiline desriptions should have subsequent lines escaped +ok 31 - Multiline description +ok 32 - multiline desriptions should have subsequent lines escaped diff --git a/expected/throwtap.out b/expected/throwtap.out index 15d635d51395..f9aab366f45c 100644 --- a/expected/throwtap.out +++ b/expected/throwtap.out @@ -1,13 +1,30 @@ \unset ECHO -1..11 -ok 1 - throws_ok(1/0) should work -ok 2 - throws_ok failure diagnostics -ok 3 - We should get the proper diagnostics from throws_ok() -ok 4 - throws_ok(1/0, NULL) should work -ok 5 - throws_ok failure diagnostics -ok 6 - We should get the proper diagnostics from throws_ok() with a NULL error code -ok 7 - lives_ok() should work -ok 8 - lives_ok failure diagnostics -ok 9 - We should get the proper diagnostics for a lives_ok() failure -ok 10 - lives_ok is ok -ok 11 - multiline desriptions should have subsequent lines escaped +1..28 +ok 1 - four-argument form should pass +ok 2 - four-argument form should have the proper description +ok 3 - four-argument form should have the proper diagnostics +ok 4 - three-argument errcode should pass +ok 5 - three-argument errcode should have the proper description +ok 6 - three-argument errcode should have the proper diagnostics +ok 7 - two-argument errcode should pass +ok 8 - two-argument errcode should have the proper description +ok 9 - three argument errmsg should pass +ok 10 - three argument errmsg should have the proper description +ok 11 - three argument errmsg should have the proper diagnostics +ok 12 - two-argument errmsg should pass +ok 13 - two-argument errmsg should have the proper description +ok 14 - two-argument errmsg should have the proper diagnostics +ok 15 - single-argument form should pass +ok 16 - single-argument form should have the proper description +ok 17 - single-argument form should have the proper diagnostics +ok 18 - invalid errcode should fail +ok 19 - invalid errcode should have the proper description +ok 20 - invalid errcode should have the proper diagnostics +ok 21 - throws_ok(1/0, NULL) should work +ok 22 - throws_ok diagnostics should fail +ok 23 - throws_ok diagnostics should have the proper description +ok 24 - throws_ok diagnostics should have the proper diagnostics +ok 25 - lives_ok() should work +ok 26 - lives_ok failure diagnostics should fail +ok 27 - lives_ok failure diagnostics should have the proper description +ok 28 - lives_ok failure diagnostics should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index e889dc773afa..00266b9d2bb9 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -613,53 +613,94 @@ CREATE OR REPLACE FUNCTION skip( int ) RETURNS TEXT AS 'SELECT skip(NULL, $1)' LANGUAGE sql; -CREATE OR REPLACE FUNCTION throws_ok ( TEXT, CHAR(5), TEXT ) +--CREATE OR REPLACE FUNCTION throws_ok ( sql, errcode, errmsg, description ) +CREATE OR REPLACE FUNCTION throws_ok ( TEXT, CHAR(5), TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE - code ALIAS FOR $1; - err ALIAS FOR $2; - msg ALIAS FOR $3; - descr TEXT; + query ALIAS FOR $1; + errcode ALIAS FOR $2; + errmsg ALIAS FOR $3; + desctext ALIAS FOR $4; + descr TEXT; BEGIN - descr := COALESCE( msg, 'threw ' || COALESCE( err, 'an exception' ) ); - EXECUTE code; + 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( err, 'an exception' ) + E'\n wanted: ' || COALESCE( errcode, 'an exception' ) ); EXCEPTION WHEN OTHERS THEN - IF err IS NULL OR SQLSTATE = err THEN - -- The expected error was thrown. + 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 error. + -- This was not the expected errcodeor. RETURN ok( FALSE, descr ) || E'\n' || diag( ' caught: ' || SQLSTATE || ': ' || SQLERRM || - E'\n wanted: ' || COALESCE( err, 'an exception') + E'\n wanted: ' || COALESCE( errcode, 'an exception' ) || + COALESCE( ': ' || errmsg, '') ); END IF; END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION throws_ok ( TEXT, CHAR(5) ) +--CREATE OR REPLACE FUNCTION throws_ok ( query, errcode, errmsg ) +--CREATE OR REPLACE FUNCTION throws_ok ( query, errmsg, description ) +CREATE OR REPLACE FUNCTION throws_ok ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT throws_ok( $1, $2, NULL ); -$$ LANGUAGE SQL; +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; +--CREATE OR REPLACE FUNCTION throws_ok ( query, errcode ) +--CREATE OR REPLACE FUNCTION 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; + +--CREATE OR REPLACE FUNCTION throws_ok ( query ) CREATE OR REPLACE FUNCTION throws_ok ( TEXT ) RETURNS TEXT AS $$ - SELECT throws_ok( $1, NULL, NULL ); + SELECT throws_ok( $1, NULL, NULL, NULL ); $$ LANGUAGE SQL; -- Magically cast integer error codes. +--CREATE OR REPLACE FUNCTION throws_ok ( query, 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; + +--CREATE OR REPLACE FUNCTION throws_ok ( query, errcode, errmsg ) CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4, TEXT ) RETURNS TEXT AS $$ - SELECT throws_ok( $1, $2::char(5), $3 ); + SELECT throws_ok( $1, $2::char(5), $3, NULL ); $$ LANGUAGE SQL; +--CREATE OR REPLACE FUNCTION throws_ok ( query, errcode ) CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4 ) RETURNS TEXT AS $$ - SELECT throws_ok( $1, $2::char(5), NULL ); + SELECT throws_ok( $1, $2::char(5), NULL, NULL ); $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION lives_ok ( TEXT, TEXT ) diff --git a/sql/moretap.sql b/sql/moretap.sql index ec63211cb21a..b72a46f3d089 100644 --- a/sql/moretap.sql +++ b/sql/moretap.sql @@ -3,7 +3,7 @@ -- $Id$ -\set numb_tests 30 +\set numb_tests 32 SELECT plan(:numb_tests); -- Replace the internal record of the plan for a few tests. @@ -110,6 +110,17 @@ bar' ), 'multiline desriptions should have subsequent lines escaped' ); +/****************************************************************************/ +-- test multiline description. +\echo ok 31 - Multiline description +SELECT is( + ok( true, 'foo +bar' ), + 'ok 31 - foo +# bar', + 'multiline desriptions should have subsequent lines escaped' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/sql/throwtap.sql b/sql/throwtap.sql index 7629ec8a91a4..331c6c66bffe 100644 --- a/sql/throwtap.sql +++ b/sql/throwtap.sql @@ -3,64 +3,93 @@ -- $Id$ -SELECT plan(11); +SELECT plan(28); +--SELECT * FROM no_plan(); /****************************************************************************/ -- test throws_ok(). -SELECT throws_ok( 'SELECT 1 / 0', '22012', 'throws_ok(1/0) should work' ); +SELECT * FROM check_test( + throws_ok( 'SELECT 1 / 0', '22012', 'division by zero', 'whatever' ), + true, + 'four-argument form', + 'whatever', + '' +); + +SELECT * FROM check_test( + throws_ok( 'SELECT 1 / 0', '22012', 'division by zero'), + true, + 'three-argument errcode', + 'threw 22012: division by zero', + '' +); + +SELECT * FROM check_test( + throws_ok( 'SELECT 1 / 0', '22012' ), + true, + 'two-argument errcode', + 'threw 22012' + '' +); + +SELECT * FROM check_test( + throws_ok( 'SELECT 1 / 0', 'division by zero', 'whatever' ), + true, + 'three argument errmsg', + 'whatever', + '' +); + +SELECT * FROM check_test( + throws_ok( 'SELECT 1 / 0', 'division by zero' ), + true, + 'two-argument errmsg', + 'threw division by zero', + '' +); + +SELECT * FROM check_test( + throws_ok( 'SELECT 1 / 0' ), + true, + 'single-argument form', + 'threw an exception', + '' +); -- Check its diagnostics for an invalid error code. -\echo ok 2 - throws_ok failure diagnostics -SELECT is( +SELECT * FROM check_test( throws_ok( 'SELECT 1 / 0', 97212 ), - 'not ok 2 - threw 97212 -# Failed test 2: "threw 97212" -# caught: 22012: division by zero -# wanted: 97212', - 'We should get the proper diagnostics from throws_ok()' + false, + 'invalid errcode', + 'threw 97212', + ' caught: 22012: division by zero + wanted: 97212' ); -SELECT throws_ok( 'SELECT 1 / 0', NULL, 'throws_ok(1/0, NULL) should work' ); +SELECT throws_ok( 'SELECT 1 / 0', NULL, NULL, 'throws_ok(1/0, NULL) should work' ); -- Check its diagnostics no error. -\echo ok 5 - throws_ok failure diagnostics -SELECT is( + +SELECT * FROM check_test( throws_ok( 'SELECT 1', NULL ), - 'not ok 5 - threw an exception -# Failed test 5: "threw an exception" -# caught: no exception -# wanted: an exception', - 'We should get the proper diagnostics from throws_ok() with a NULL error code' + false, + 'throws_ok diagnostics', + 'threw an exception', + ' caught: no exception + wanted: an exception' ); --- Clean up the failed test results. -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 2, 5 ); - /****************************************************************************/ -- test lives_ok(). SELECT lives_ok( 'SELECT 1', 'lives_ok() should work' ); -- Check its diagnostics when there is an exception. -\echo ok 8 - lives_ok failure diagnostics -SELECT is( +SELECT * FROM check_test( lives_ok( 'SELECT 1 / 0' ), - 'not ok 8 -# Failed test 8 -# died: 22012: division by zero', - 'We should get the proper diagnostics for a lives_ok() failure' -); - -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 8 ); -\echo ok 10 - lives_ok is ok - -/****************************************************************************/ --- test multiline description. -SELECT is( - ok( true, 'foo -bar' ), - 'ok 10 - foo -# bar', - 'multiline desriptions should have subsequent lines escaped' + false, + 'lives_ok failure diagnostics', + '', + ' died: 22012: division by zero' ); /****************************************************************************/ diff --git a/sql/todotap.sql b/sql/todotap.sql index ba06391ebb00..67bd946ded36 100644 --- a/sql/todotap.sql +++ b/sql/todotap.sql @@ -168,6 +168,7 @@ SELECT CASE WHEN substring(version() from '[[:digit:]]+[.][[:digit:]]')::numeric ELSE throws_ok( 'SELECT todo_end()', 'P0001', + 'todo_end() called without todo_start()', 'Should get an exception when todo_end() is called without todo_start()' ) END; From 39cdfe9633f0738c51221a214ffae53cf2a2b770 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 22 Sep 2008 21:06:00 +0000 Subject: [PATCH 0161/1195] Don't test an internal error message, as it is subject to localization. So use one we wrote ourselves. --- sql/throwtap.sql | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/sql/throwtap.sql b/sql/throwtap.sql index 331c6c66bffe..332325c849e6 100644 --- a/sql/throwtap.sql +++ b/sql/throwtap.sql @@ -9,7 +9,7 @@ SELECT plan(28); /****************************************************************************/ -- test throws_ok(). SELECT * FROM check_test( - throws_ok( 'SELECT 1 / 0', '22012', 'division by zero', 'whatever' ), + throws_ok( 'SELECT * FROM todo_end()', 'P0001', 'todo_end() called without todo_start()', 'whatever' ), true, 'four-argument form', 'whatever', @@ -17,10 +17,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - throws_ok( 'SELECT 1 / 0', '22012', 'division by zero'), + throws_ok( 'SELECT * FROM todo_end()', 'P0001', 'todo_end() called without todo_start()'), true, 'three-argument errcode', - 'threw 22012: division by zero', + 'threw P0001: todo_end() called without todo_start()', '' ); @@ -33,7 +33,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - throws_ok( 'SELECT 1 / 0', 'division by zero', 'whatever' ), + throws_ok( 'SELECT * FROM todo_end()', 'todo_end() called without todo_start()', 'whatever'), true, 'three argument errmsg', 'whatever', @@ -41,10 +41,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - throws_ok( 'SELECT 1 / 0', 'division by zero' ), + throws_ok( 'SELECT * FROM todo_end()', 'todo_end() called without todo_start()'), true, 'two-argument errmsg', - 'threw division by zero', + 'threw todo_end() called without todo_start()', '' ); @@ -58,11 +58,11 @@ SELECT * FROM check_test( -- Check its diagnostics for an invalid error code. SELECT * FROM check_test( - throws_ok( 'SELECT 1 / 0', 97212 ), + throws_ok( 'SELECT * FROM todo_end()', 97212 ), false, 'invalid errcode', 'threw 97212', - ' caught: 22012: division by zero + ' caught: P0001: todo_end() called without todo_start() wanted: 97212' ); @@ -85,11 +85,11 @@ SELECT lives_ok( 'SELECT 1', 'lives_ok() should work' ); -- Check its diagnostics when there is an exception. SELECT * FROM check_test( - lives_ok( 'SELECT 1 / 0' ), + lives_ok( 'SELECT * FROM todo_end()' ), false, 'lives_ok failure diagnostics', '', - ' died: 22012: division by zero' + ' died: P0001: todo_end() called without todo_start()' ); /****************************************************************************/ From ec5df5230020b237183231f9fb2b6b56cd908756 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 23 Sep 2008 18:28:50 +0000 Subject: [PATCH 0162/1195] * Added `can()` and `can_ok()`. * Fixed a couple of `$Id` keywords. * Fixed a typo in the README. --- Changes | 1 + README.pgtap | 66 ++++++++++++++- expected/cantap.out | 62 +++++++++++++++ pgtap.sql.in | 154 +++++++++++++++++++++++++++++++++++ sql/cantap.sql | 190 ++++++++++++++++++++++++++++++++++++++++++++ sql/check.sql | 2 +- test_setup.sql.in | 2 +- 7 files changed, 473 insertions(+), 4 deletions(-) create mode 100644 expected/cantap.out create mode 100644 sql/cantap.sql diff --git a/Changes b/Changes index 526dd3c61e0d..c307a3c8aa78 100644 --- a/Changes +++ b/Changes @@ -21,6 +21,7 @@ Revision history for pgTAP - Added variants of `throws_ok()` that test error messages as well as error codes. - Converted some more tests to use `check_test()`. + - Added `can()` and `can_ok()`. 0.10 2008-09-18T22:59:31 - Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the diff --git a/README.pgtap b/README.pgtap index 555a10e7d3bb..623a6e316c68 100644 --- a/README.pgtap +++ b/README.pgtap @@ -653,7 +653,7 @@ is omitted, it will be set to "Column `:table`.`:column` should be type does not exist. The type argument should be formatted as it would be displayed in the view of -a table usin the `\d` command in `psql`. For example, if you have a numeric +a table using the `\d` command in `psql`. For example, if you have a numeric column with a precision of 8, you should specify "numeric(8,0)". If you created a `varchar(64)` column, you should pass the type as "character varying(64)". @@ -951,6 +951,69 @@ not exist. Just like `col_is_pk()`, except that it test that the column or array of columns have a check constraint on them. +### `can( schema, functions[], description )` ### +### `can( schema, functions[] )` ### +### `can( functions[], description )` ### +### `can( functions[] )` ### + + SELECT can( 'pg_catalog', ARRAY['upper', 'lower'] ); + +Checks to be sure that `:schema` has `:functions[]` defined. If `:schema` is +omitted, then `can()` will look for functions defined in schemas defined in +the search path. No matter how many functions are listed in `:functions[]`, a +single call to `can()` counts as one test. If you want otherwise, call `can()` +once for each function -- or better yet, use `can_ok()`. + +If any of the functions are not defined, the test will fail and the +diagnostics will output a list of the functions that are missing, like so: + + # Failed test 52: "Schema pg_catalog can" + # pg_catalog.foo() missing + # pg_catalog.bar() missing + +### `can_ok( schema, function, args[], description )` ### +### `can_ok( schema, function, args[] )` ### +### `can_ok( schema, function, description )` ### +### `can_ok( schema, function )` ### +### `can_ok( function, args[], description )` ### +### `can_ok( function, args[] )` ### +### `can_ok( function, description )` ### +### `can_ok( function )` ### + + SELECT can_ok( + 'pg_catalog', + 'decode', + ARRAY[ 'text', 'text' ], + 'Function decode(text, text) should exist' + ); + + SELECT can_ok( 'do_something' ); + SELECT can_ok( 'do_something', ARRAY['integer'] ); + SELECT can_ok( 'do_something', ARRAY['numeric'] ); + +Checks to be sure that the given function exists in the named schema and with +the specified argument data types. If `:schema` is omitted, `can_ok()` will +search for the function in the schemas defined in the search path. If +`:args[]` is omitted, `can_ok()` will see if the function exists without +regard to its arguments. + +The `:args[]` argument should be formatted as it would be displayed in the +view of a function using the `\df` command in `psql`. For example, even if you +have a numeric column with a precision of 8, you should specify +`ARRAY['numeric']`". If you created a `varchar(64)` column, you should pass +the `:args[]` argument as `ARRAY['character varying']`. + +If you wish to use the two-argument form of `can_ok()`, specifying only the +schema and the function name, you must cast the `:function` argument to +`:name` in order to disambiguate it from from the +`can_ok(`:function`, `:description)` form. If you neglect to do so, your +results will be unexpected. + +Also, if you use the string form to specify the `:args[]` array, be sure to +cast it to `name[]` to disambiguate it from a text string: + + SELECT can_ok( 'lower', '{text}'::name[] ); + Diagnostics ----------- @@ -1339,7 +1402,6 @@ To Do the shebang line. Will likely require a `$(PERL)` environment variable. * Update the Makefile to require TAP::Harness. * Add `note()`? -* Add `can_ok()`. * Finish porting tests to use `check_test()`. Suported Versions diff --git a/expected/cantap.out b/expected/cantap.out new file mode 100644 index 000000000000..ee0235e8313d --- /dev/null +++ b/expected/cantap.out @@ -0,0 +1,62 @@ +\unset ECHO +1..60 +ok 1 - simple function should pass +ok 2 - simple function should have the proper description +ok 3 - simple function should have the proper diagnostics +ok 4 - simple schema.function should pass +ok 5 - simple schema.function should have the proper description +ok 6 - simple schema.function should have the proper diagnostics +ok 7 - simple function desc should pass +ok 8 - simple function desc should have the proper description +ok 9 - simple function desc should have the proper diagnostics +ok 10 - simple with 0 args should pass +ok 11 - simple with 0 args should have the proper description +ok 12 - simple with 0 args should have the proper diagnostics +ok 13 - simple with 0 args desc should pass +ok 14 - simple with 0 args desc should have the proper description +ok 15 - simple with 0 args desc should have the proper diagnostics +ok 16 - simple schema.func with 0 args should pass +ok 17 - simple schema.func with 0 args should have the proper description +ok 18 - simple schema.func with 0 args should have the proper diagnostics +ok 19 - simple schema.func with desc should pass +ok 20 - simple schema.func with desc should have the proper description +ok 21 - simple schema.func with desc should have the proper diagnostics +ok 22 - simple scchma.func with 0 args, desc should pass +ok 23 - simple scchma.func with 0 args, desc should have the proper description +ok 24 - simple scchma.func with 0 args, desc should have the proper diagnostics +ok 25 - simple function with 1 arg should pass +ok 26 - simple function with 1 arg should have the proper description +ok 27 - simple function with 1 arg should have the proper diagnostics +ok 28 - simple function with 2 args should pass +ok 29 - simple function with 2 args should have the proper description +ok 30 - simple function with 2 args should have the proper diagnostics +ok 31 - simple array function should pass +ok 32 - simple array function should have the proper description +ok 33 - simple array function should have the proper diagnostics +ok 34 - custom array function should pass +ok 35 - custom array function should have the proper description +ok 36 - custom array function should have the proper diagnostics +ok 37 - custom numeric function should pass +ok 38 - custom numeric function should have the proper description +ok 39 - custom numeric function should have the proper diagnostics +ok 40 - failure output should fail +ok 41 - failure output should have the proper description +ok 42 - failure output should have the proper diagnostics +ok 43 - can(schema) with desc should pass +ok 44 - can(schema) with desc should have the proper description +ok 45 - can(schema) with desc should have the proper diagnostics +ok 46 - can(schema) should pass +ok 47 - can(schema) should have the proper description +ok 48 - can(schema) should have the proper diagnostics +ok 49 - fail can(schema) with desc should fail +ok 50 - fail can(schema) with desc should have the proper description +ok 51 - fail can(schema) with desc should have the proper diagnostics +ok 52 - can() with desc should pass +ok 53 - can() with desc should have the proper description +ok 54 - can() with desc should have the proper diagnostics +ok 55 - can(schema) should pass +ok 56 - can(schema) should have the proper description +ok 57 - can(schema) should have the proper diagnostics +ok 58 - fail can() with desc should fail +ok 59 - fail can() with desc should have the proper description +ok 60 - fail can() with desc should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 00266b9d2bb9..138e9d9fa081 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1551,6 +1551,160 @@ RETURNS TEXT AS $$ SELECT fk_ok( $1, ARRAY[$2], $3, ARRAY[$4] ); $$ LANGUAGE sql; +-- can_ok( schema, func_name, args[], description ) +CREATE OR REPLACE FUNCTION can_ok ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + 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 array_to_string(p.proargtypes::regtype[], ',') = array_to_string($3, ',') + ), $4 + ); +$$ LANGUAGE SQL; + +-- can_ok( schema, func_name, args[] ) +CREATE OR REPLACE FUNCTION can_ok( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT can_ok( $1, $2, $3, 'Function ' || $1 || '.' || $2 || '(' || + array_to_string($3, ', ') || ') should exist' ); +$$ LANGUAGE sql; + +-- can_ok( schema, func_name, description ) +CREATE OR REPLACE FUNCTION can_ok ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + 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 + ), $3 + ); +$$ LANGUAGE SQL; + +-- can_ok( schema, func_name ) +CREATE OR REPLACE FUNCTION can_ok( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT can_ok( $1, $2, 'Function ' || $1 || '.' || $2 || '() should exist' ); +$$ LANGUAGE sql; + +-- can_ok( func_name, args[], description ) +CREATE OR REPLACE FUNCTION can_ok ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_proc p + WHERE p.proname = $1 + AND pg_catalog.pg_function_is_visible(p.oid) + AND array_to_string(p.proargtypes::regtype[], ',') = array_to_string($2, ',') + ), $3 + ); +$$ LANGUAGE SQL; + +-- can_ok( func_name, args[] ) +CREATE OR REPLACE FUNCTION can_ok( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT can_ok( $1, $2, 'Function ' || $1 || '(' || + array_to_string($2, ', ') || ') should exist' ); +$$ LANGUAGE sql; + +-- can_ok( func_name, description ) +CREATE OR REPLACE FUNCTION can_ok( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_proc p + WHERE p.proname = $1 + AND pg_catalog.pg_function_is_visible(p.oid) + ), $2 + ); +$$ LANGUAGE sql; + +-- can_ok( func_name ) +CREATE OR REPLACE FUNCTION can_ok( NAME ) +RETURNS TEXT AS $$ + SELECT can_ok( $1, 'Function ' || $1 || '() should 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, func_names[], description ) +CREATE OR REPLACE FUNCTION can ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + missing name[]; +BEGIN + SELECT ARRAY( + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + LEFT JOIN pg_catalog.pg_proc p + ON ($2[i] = p.proname) + LEFT JOIN pg_catalog.pg_namespace n + ON p.pronamespace = n.oid AND n.nspname = 'pg_catalog' + WHERE p.oid IS NULL + ) INTO missing; + IF missing[1] IS NULL THEN + RETURN ok( true, $3 ); + END IF; + RETURN ok( false, $3 ) || E'\n' || diag( + ' ' || $1 || '.' || + array_to_string( missing, E'() missing\n ' || $1 || '.') || + '() missing' + ); +END; +$$ LANGUAGE plpgsql; + +-- can( schema, func_names[] ) +CREATE OR REPLACE FUNCTION can ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT can( $1, $2, 'Schema ' || $1 || ' can' ); +$$ LANGUAGE sql; + +-- can( func_names[], description ) +CREATE OR REPLACE FUNCTION can ( NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + missing name[]; +BEGIN + SELECT ARRAY( + SELECT $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 + ) 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( func_names[] ) +CREATE OR REPLACE FUNCTION can ( NAME[] ) +RETURNS TEXT AS $$ + SELECT can( $1, 'Schema ' || array_to_string(current_schemas(true), ' or ') || ' can' ); +$$ LANGUAGE sql; + -- check_test( test_output, pass, name, description, diag ) CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) RETURNS SETOF TEXT AS $$ diff --git a/sql/cantap.sql b/sql/cantap.sql new file mode 100644 index 000000000000..a586f1e4924a --- /dev/null +++ b/sql/cantap.sql @@ -0,0 +1,190 @@ +\unset ECHO +\i test_setup.sql + +-- $Id$ + +SELECT plan(60); +--SELECT * FROM no_plan(); + +/****************************************************************************/ +-- Test can_ok(). +SELECT * FROM check_test( + can_ok( 'now' ), + true, + 'simple function', + 'Function now() should exist', + '' +); + +SELECT * FROM check_test( + can_ok( 'pg_catalog', 'now'::name ), + true, + 'simple schema.function', + 'Function pg_catalog.now() should exist', + '' +); + +SELECT * FROM check_test( + can_ok( 'now', 'whatever' ), + true, + 'simple function desc', + 'whatever', + '' +); + +SELECT * FROM check_test( + can_ok( 'now', '{}'::name[] ), + true, + 'simple with 0 args', + 'Function now() should exist', + '' +); + +SELECT * FROM check_test( + can_ok( 'now', '{}'::name[], 'whatever' ), + true, + 'simple with 0 args desc', + 'whatever', + '' +); + +SELECT * FROM check_test( + can_ok( 'pg_catalog', 'now', '{}'::name[] ), + true, + 'simple schema.func with 0 args', + 'Function pg_catalog.now() should exist', + '' +); + +SELECT * FROM check_test( + can_ok( 'pg_catalog', 'now', 'whatever' ), + true, + 'simple schema.func with desc', + 'whatever', + '' +); + +SELECT * FROM check_test( + can_ok( 'pg_catalog', 'now', '{}'::name[], 'whatever' ), + true, + 'simple scchma.func with 0 args, desc', + 'whatever', + '' +); + +SELECT * FROM check_test( + can_ok( 'lower', '{text}'::name[] ), + true, + 'simple function with 1 arg', + 'Function lower(text) should exist', + '' +); + +SELECT * FROM check_test( + can_ok( 'decode', '{text,text}'::name[] ), + true, + 'simple function with 2 args', + 'Function decode(text, text) should exist', + '' +); + +SELECT * FROM check_test( + can_ok( 'array_cat', ARRAY['anyarray','anyarray'] ), + true, + 'simple array function', + 'Function array_cat(anyarray, anyarray) should exist', + '' +); + +-- Check a custom function with an array argument. +CREATE FUNCTION __cat__ (text[]) RETURNS BOOLEAN +AS 'SELECT TRUE' +LANGUAGE SQL; + +SELECT * FROM check_test( + can_ok( '__cat__', '{text[]}'::name[] ), + true, + 'custom array function', + 'Function __cat__(text[]) should exist', + '' +); + +-- Check a custom function with a numeric argument. +CREATE FUNCTION __cat__ (numeric(10,2)) RETURNS BOOLEAN +AS 'SELECT TRUE' +LANGUAGE SQL; + +SELECT * FROM check_test( + can_ok( '__cat__', '{numeric}'::name[] ), + true, + 'custom numeric function', + 'Function __cat__(numeric) should exist', + '' +); + +-- Check failure output. +SELECT * FROM check_test( + can_ok( '__cat__', '{varchar[]}'::name[] ), + false, + 'failure output', + 'Function __cat__(varchar[]) should exist', + '' -- No diagnostics. +); + +/****************************************************************************/ +-- Try can() function names. +SELECT * FROM check_test( + can( 'pg_catalog', ARRAY['lower', 'upper'], 'whatever' ), + true, + 'can(schema) with desc', + 'whatever', + '' +); + +SELECT * FROM check_test( + can( 'pg_catalog', ARRAY['lower', 'upper'] ), + true, + 'can(schema)', + 'Schema pg_catalog can', + '' +); + +SELECT * FROM check_test( + can( 'pg_catalog', ARRAY['lower', 'foo', 'bar'], 'whatever' ), + false, + 'fail can(schema) with desc', + 'whatever', + ' pg_catalog.foo() missing + pg_catalog.bar() missing' +); + +SELECT * FROM check_test( + can( ARRAY['__cat__', 'lower', 'upper'], 'whatever' ), + true, + 'can() with desc', + 'whatever', + '' +); + +SELECT * FROM check_test( + can( ARRAY['lower', 'upper'] ), + true, + 'can(schema)', + 'Schema ' || array_to_string(current_schemas(true), ' or ') || ' can', + '' +); + +SELECT * FROM check_test( + can( ARRAY['__cat__', 'foo', 'bar'], 'whatever' ), + false, + 'fail can() with desc', + 'whatever', + ' foo() missing + bar() missing' +); + + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; diff --git a/sql/check.sql b/sql/check.sql index d0e93616be5a..7c10d676db3f 100644 --- a/sql/check.sql +++ b/sql/check.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql --- $Id: pg73.sql.in 4285 2008-09-17 21:47:16Z david $ +-- $Id$ SELECT plan(26); diff --git a/test_setup.sql.in b/test_setup.sql.in index b31b41c9bac8..c5fdbaf1bbd2 100644 --- a/test_setup.sql.in +++ b/test_setup.sql.in @@ -4,7 +4,7 @@ -- Tests for pgTAP. -- -- --- $Id: check.sql.in 4285 2008-09-17 21:47:16Z david $ +-- $Id$ -- Format the output for nice TAP. \pset format unaligned From 052242f915c32aa0fa1c791eb0eebc4e5834d0b2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 23 Sep 2008 18:32:33 +0000 Subject: [PATCH 0163/1195] Won't be adding that. --- README.pgtap | 1 - 1 file changed, 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index 623a6e316c68..f236790848dd 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1401,7 +1401,6 @@ To Do * Update the Makefile to process pg_prove and set the proper path to Perl on the shebang line. Will likely require a `$(PERL)` environment variable. * Update the Makefile to require TAP::Harness. -* Add `note()`? * Finish porting tests to use `check_test()`. Suported Versions From 33a62677c3b4945e3c800cbb799f155fe2dcbfa7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 23 Sep 2008 18:45:59 +0000 Subject: [PATCH 0164/1195] Converted `sql/check.sql` to use `check_test()`. --- README.pgtap | 3 +- expected/check.out | 67 +++++++++++++--------- sql/check.sql | 137 ++++++++++++++++++++++++--------------------- 3 files changed, 115 insertions(+), 92 deletions(-) diff --git a/README.pgtap b/README.pgtap index f236790848dd..7a7e186c4b14 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1400,7 +1400,8 @@ To Do * Update the Makefile to process pg_prove and set the proper path to Perl on the shebang line. Will likely require a `$(PERL)` environment variable. -* Update the Makefile to require TAP::Harness. +* Update the Makefile to check for TAP::Harness and warn if it's not + installed. * Finish porting tests to use `check_test()`. Suported Versions diff --git a/expected/check.out b/expected/check.out index 66e6105e5c7a..063411781e61 100644 --- a/expected/check.out +++ b/expected/check.out @@ -1,28 +1,41 @@ \unset ECHO -1..26 -ok 1 - test has_check( schema, table, description ) -ok 2 - has_check( schema, table, description ) should work -ok 3 - test has_check( table, description ) -ok 4 - has_check( table, description ) should work -ok 5 - test has_check( table ) -ok 6 - has_check( table ) should work -ok 7 - test has_check( schema, table, description ) fail -ok 8 - has_check( schema, table, description ) should fail properly -ok 9 - test has_check( table, description ) fail -ok 10 - has_check( table, description ) should fail properly -ok 11 - test col_has_check( schema, table, column, description ) -ok 12 - col_has_check( schema, table, column, description ) should work -ok 13 - test col_has_check( table, column, description ) -ok 14 - col_has_check( table, column, description ) should work -ok 15 - test col_has_check( table, column ) -ok 16 - col_has_check( table, column ) should work -ok 17 - test col_has_check( schema, table, column, description ) fail -ok 18 - col_has_check( schema, table, column, description ) should fail properly -ok 19 - test col_has_check( table, column, description ) fail -ok 20 - col_has_check( table, column, description ) should fail properly -ok 21 - test col_has_check( schema, table, column[], description ) -ok 22 - col_has_check( schema, table, column[], description ) should work -ok 23 - test col_has_check( table, column[], description ) -ok 24 - col_has_check( table, column[], description ) should work -ok 25 - test col_has_check( table, column[], description ) -ok 26 - col_has_check( table, column[] ) should work +1..39 +ok 1 - has_check( schema, table, desc ) should pass +ok 2 - has_check( schema, table, desc ) should have the proper description +ok 3 - has_check( schema, table, desc ) should have the proper diagnostics +ok 4 - has_check( table, desc ) should pass +ok 5 - has_check( table, desc ) should have the proper description +ok 6 - has_check( table, desc ) should have the proper diagnostics +ok 7 - has_check( table ) should pass +ok 8 - has_check( table ) should have the proper description +ok 9 - has_check( table ) should have the proper diagnostics +ok 10 - has_check( schema, table, descr ) fail should fail +ok 11 - has_check( schema, table, descr ) fail should have the proper description +ok 12 - has_check( schema, table, descr ) fail should have the proper diagnostics +ok 13 - has_check( table, desc ) fail should fail +ok 14 - has_check( table, desc ) fail should have the proper description +ok 15 - has_check( table, desc ) fail should have the proper diagnostics +ok 16 - col_has_check( sch, tab, col, desc ) should pass +ok 17 - col_has_check( sch, tab, col, desc ) should have the proper description +ok 18 - col_has_check( sch, tab, col, desc ) should have the proper diagnostics +ok 19 - col_has_check( tab, col, desc ) should pass +ok 20 - col_has_check( tab, col, desc ) should have the proper description +ok 21 - col_has_check( tab, col, desc ) should have the proper diagnostics +ok 22 - col_has_check( table, column ) should pass +ok 23 - col_has_check( table, column ) should have the proper description +ok 24 - col_has_check( table, column ) should have the proper diagnostics +ok 25 - col_has_check( sch, tab, col, desc ) fail should fail +ok 26 - col_has_check( sch, tab, col, desc ) fail should have the proper description +ok 27 - col_has_check( sch, tab, col, desc ) fail should have the proper diagnostics +ok 28 - col_has_check( tab, col, desc ) fail should fail +ok 29 - col_has_check( tab, col, desc ) fail should have the proper description +ok 30 - col_has_check( tab, col, desc ) fail should have the proper diagnostics +ok 31 - col_has_check( sch, tab, col[], desc ) should pass +ok 32 - col_has_check( sch, tab, col[], desc ) should have the proper description +ok 33 - col_has_check( sch, tab, col[], desc ) should have the proper diagnostics +ok 34 - col_has_check( tab, col[], desc ) should pass +ok 35 - col_has_check( tab, col[], desc ) should have the proper description +ok 36 - col_has_check( tab, col[], desc ) should have the proper diagnostics +ok 37 - col_has_check( tab, col[] ) should pass +ok 38 - col_has_check( tab, col[] ) should have the proper description +ok 39 - col_has_check( tab, col[] ) should have the proper diagnostics diff --git a/sql/check.sql b/sql/check.sql index 7c10d676db3f..20f8cd0291d2 100644 --- a/sql/check.sql +++ b/sql/check.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(26); +SELECT plan(39); -- This will be rolled back. :-) CREATE TABLE public.sometab( @@ -16,113 +16,122 @@ CREATE TABLE public.sometab( /****************************************************************************/ -- Test has_check(). -\echo ok 1 - test has_check( schema, table, description ) -SELECT is( +SELECT * FROM check_test( has_check( 'public', 'sometab', 'public.sometab should have a check constraint' ), - 'ok 1 - public.sometab should have a check constraint', - 'has_check( schema, table, description ) should work' + true, + 'has_check( schema, table, desc )', + 'public.sometab should have a check constraint', + '' ); -\echo ok 3 - test has_check( table, description ) -SELECT is( +SELECT * FROM check_test( has_check( 'sometab', 'sometab should have a check constraint' ), - 'ok 3 - sometab should have a check constraint', - 'has_check( table, description ) should work' + true, + 'has_check( table, desc )', + 'sometab should have a check constraint', + '' ); -\echo ok 5 - test has_check( table ) -SELECT is( +SELECT * FROM check_test( has_check( 'sometab' ), - 'ok 5 - Table sometab should have a check constraint', - 'has_check( table ) should work' + true, + 'has_check( table )', + 'Table sometab should have a check constraint', + '' ); -\echo ok 7 - test has_check( schema, table, description ) fail -SELECT is( +SELECT * FROM check_test( has_check( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should have a check constraint' ), - 'not ok 7 - pg_catalog.pg_class should have a check constraint -# Failed test 7: "pg_catalog.pg_class should have a check constraint"', - 'has_check( schema, table, description ) should fail properly' + false, + 'has_check( schema, table, descr ) fail', + 'pg_catalog.pg_class should have a check constraint', + '' ); -\echo ok 9 - test has_check( table, description ) fail -SELECT is( +SELECT * FROM check_test( has_check( 'pg_class', 'pg_class should have a check constraint' ), - 'not ok 9 - pg_class should have a check constraint -# Failed test 9: "pg_class should have a check constraint"', - 'has_check( table, description ) should fail properly' + false, + 'has_check( table, desc ) fail', + 'pg_class should have a check constraint', + '' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 7, 9 ); /****************************************************************************/ -- Test col_has_check(). -\echo ok 11 - test col_has_check( schema, table, column, description ) -SELECT is( +SELECT * FROM check_test( col_has_check( 'public', 'sometab', 'name', 'public.sometab.name should be a pk' ), - 'ok 11 - public.sometab.name should be a pk', - 'col_has_check( schema, table, column, description ) should work' + true, + 'col_has_check( sch, tab, col, desc )', + 'public.sometab.name should be a pk', + '' ); -\echo ok 13 - test col_has_check( table, column, description ) -SELECT is( +SELECT * FROM check_test( col_has_check( 'sometab', 'name', 'sometab.name should be a pk' ), - 'ok 13 - sometab.name should be a pk', - 'col_has_check( table, column, description ) should work' + true, + 'col_has_check( tab, col, desc )', + 'sometab.name should be a pk', + '' ); -\echo ok 15 - test col_has_check( table, column ) -SELECT is( +SELECT * FROM check_test( col_has_check( 'sometab', 'name' ), - 'ok 15 - Column sometab(name) should have a check constraint', - 'col_has_check( table, column ) should work' + true, + 'col_has_check( table, column )', + 'Column sometab(name) should have a check constraint', + '' ); -\echo ok 17 - test col_has_check( schema, table, column, description ) fail -SELECT is( +SELECT * FROM check_test( col_has_check( 'public', 'sometab', 'id', 'public.sometab.id should be a pk' ), - 'not ok 17 - public.sometab.id should be a pk -# Failed test 17: "public.sometab.id should be a pk" -# have: {name} -# want: {id}', - 'col_has_check( schema, table, column, description ) should fail properly' + false, + 'col_has_check( sch, tab, col, desc ) fail', + 'public.sometab.id should be a pk', + ' have: {name} + want: {id}' ); -\echo ok 19 - test col_has_check( table, column, description ) fail -SELECT is( +SELECT * FROM check_test( col_has_check( 'sometab', 'id', 'sometab.id should be a pk' ), - 'not ok 19 - sometab.id should be a pk -# Failed test 19: "sometab.id should be a pk" -# have: {name} -# want: {id}', - 'col_has_check( table, column, description ) should fail properly' + false, + 'col_has_check( tab, col, desc ) fail', + 'sometab.id should be a pk', + ' have: {name} + want: {id}' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 17, 19 ); /****************************************************************************/ -- Test col_has_check() with an array of columns. -CREATE TABLE public.argh (id int not null, name text not null, check ( id IN (1, 2) AND name IN ('foo', 'bar'))); +CREATE TABLE public.argh ( + id INT NOT NULL, + name TEXT NOT NULL, + CHECK ( id IN (1, 2) AND name IN ('foo', 'bar')) +); -\echo ok 21 - test col_has_check( schema, table, column[], description ) -SELECT is( +SELECT * FROM check_test( col_has_check( 'public', 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), - 'ok 21 - id + name should be a pk', - 'col_has_check( schema, table, column[], description ) should work' + true, + 'col_has_check( sch, tab, col[], desc )', + 'id + name should be a pk', + '' ); -\echo ok 23 - test col_has_check( table, column[], description ) -SELECT is( +SELECT * FROM check_test( col_has_check( 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), - 'ok 23 - id + name should be a pk', - 'col_has_check( table, column[], description ) should work' + true, + 'col_has_check( tab, col[], desc )', + 'id + name should be a pk', + '' ); -\echo ok 25 - test col_has_check( table, column[], description ) -SELECT is( +SELECT * FROM check_test( col_has_check( 'argh', ARRAY['id', 'name'] ), - 'ok 25 - Columns argh(id, name) should have a check constraint', - 'col_has_check( table, column[] ) should work' + true, + 'col_has_check( tab, col[] )', + 'Columns argh(id, name) should have a check constraint', + '' ); /****************************************************************************/ From 9d6a6c3a711e3a10108be69782d2a6802297ef98 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 23 Sep 2008 18:54:39 +0000 Subject: [PATCH 0165/1195] Convertd `sql/cmpok.sql` to use `check_test()`. --- expected/cmpok.out | 36 ++++++++++++--------- sql/cmpok.sql | 80 +++++++++++++++++++++++----------------------- 2 files changed, 61 insertions(+), 55 deletions(-) diff --git a/expected/cmpok.out b/expected/cmpok.out index 06a02ad96ce5..5aeff495b898 100644 --- a/expected/cmpok.out +++ b/expected/cmpok.out @@ -1,16 +1,22 @@ \unset ECHO -1..14 -ok 1 - test cmp_ok( int, =, int, description ) -ok 2 - cmp_ok( int, =, int, description ) should work -ok 3 - test cmp_ok( int, <>, int, description ) -ok 4 - cmp_ok( int, <>, int ) should work -ok 5 - test cmp_ok( polygon, ~=, polygon ) -ok 6 - cmp_ok( polygon, ~=, polygon ) should work -ok 7 - test cmp_ok( int[], =, int[] ) -ok 8 - cmp_ok( int[], =, int[] ) should work -ok 9 - test cmp_ok( inet[], =, inet[] ) -ok 10 - cmp_ok( inet[], =, inet[] ) should work -ok 11 - Test cmp_ok() failure output -ok 12 - cmp_ok() failure output should be correct -ok 13 - Test cmp_ok() failure output -ok 14 - cmp_ok() failure output should be correct +1..20 +ok 1 - cmp_ok( int, =, int ) should pass +ok 2 - cmp_ok( int, =, int ) should have the proper description +ok 3 - cmp_ok( int, =, int ) should have the proper diagnostics +ok 4 - cmp_ok( int, <>, int ) should pass +ok 5 - cmp_ok( int, <>, int ) should have the proper description +ok 6 - cmp_ok( int, <>, int ) should have the proper diagnostics +ok 7 - cmp_ok( polygon, ~=, polygon ) should pass +ok 8 - cmp_ok( polygon, ~=, polygon ) should have the proper description +ok 9 - cmp_ok( int[], =, int[] ) should pass +ok 10 - cmp_ok( int[], =, int[] ) should have the proper description +ok 11 - cmp_ok( int[], =, int[] ) should have the proper diagnostics +ok 12 - cmp_ok( inet[], =, inet[] ) should pass +ok 13 - cmp_ok( inet[], =, inet[] ) should have the proper description +ok 14 - cmp_ok( inet[], =, inet[] ) should have the proper diagnostics +ok 15 - cmp_ok() fail should fail +ok 16 - cmp_ok() fail should have the proper description +ok 17 - cmp_ok() fail should have the proper diagnostics +ok 18 - cmp_ok() NULL fail should fail +ok 19 - cmp_ok() NULL fail should have the proper description +ok 20 - cmp_ok() NULL fail should have the proper diagnostics diff --git a/sql/cmpok.sql b/sql/cmpok.sql index ccd344fa34c3..f77a7af8a3ac 100644 --- a/sql/cmpok.sql +++ b/sql/cmpok.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(14); +SELECT plan(20); /****************************************************************************/ @@ -24,66 +24,66 @@ LANGUAGE SQL IMMUTABLE STRICT; /****************************************************************************/ -- Test cmp_ok(). -\echo ok 1 - test cmp_ok( int, =, int, description ) -SELECT is( +SELECT * FROM check_test( cmp_ok( 1, '=', 1, '1 should = 1' ), - 'ok 1 - 1 should = 1', - 'cmp_ok( int, =, int, description ) should work' + true, + 'cmp_ok( int, =, int )', + '1 should = 1', + '' ); -\echo ok 3 - test cmp_ok( int, <>, int, description ) -SELECT is( +SELECT * FROM check_test( cmp_ok( 1, '<>', 2, '1 should <> 2' ), - 'ok 3 - 1 should <> 2', - 'cmp_ok( int, <>, int ) should work' + true, + 'cmp_ok( int, <>, int )', + '1 should <> 2', + '' ); -\echo ok 5 - test cmp_ok( polygon, ~=, polygon ) -SELECT is( +SELECT * FROM check_test( cmp_ok( '((0,0),(1,1))'::polygon, '~=', '((1,1),(0,0))'::polygon ), - 'ok 5', - 'cmp_ok( polygon, ~=, polygon ) should work' + true, + 'cmp_ok( polygon, ~=, polygon )' + '', + '' ); -\echo ok 7 - test cmp_ok( int[], =, int[] ) -SELECT is( +SELECT * FROM check_test( cmp_ok( ARRAY[1, 2], '=', ARRAY[1, 2]), - 'ok 7', - 'cmp_ok( int[], =, int[] ) should work' + true, + 'cmp_ok( int[], =, int[] )', + '', + '' ); -\echo ok 9 - test cmp_ok( inet[], =, inet[] ) -SELECT is( +SELECT * FROM check_test( cmp_ok( ARRAY['192.168.1.2'::inet], '=', ARRAY['192.168.1.2'::inet] ), - 'ok 9', - 'cmp_ok( inet[], =, inet[] ) should work' + true, + 'cmp_ok( inet[], =, inet[] )', + '', + '' ); -\echo ok 11 - Test cmp_ok() failure output -SELECT is( +SELECT * FROM check_test( cmp_ok( 1, '=', 2, '1 should = 2' ), - 'not ok 11 - 1 should = 2 -# Failed test 11: "1 should = 2" -# ''1'' -# = -# ''2''', - 'cmp_ok() failure output should be correct' + false, + 'cmp_ok() fail', + '1 should = 2', + ' ''1'' + = + ''2''' ); -\echo ok 13 - Test cmp_ok() failure output -SELECT is( +SELECT * FROM check_test( cmp_ok( 1, '=', NULL, '1 should = NULL' ), - 'not ok 13 - 1 should = NULL -# Failed test 13: "1 should = NULL" -# ''1'' -# = -# NULL', - 'cmp_ok() failure output should be correct' + false, + 'cmp_ok() NULL fail', + '1 should = NULL', + ' ''1'' + = + NULL' ); --- Clean up the failed test results. -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 11, 13 ); - /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From ca6fa97d3a2fc5a3d33c25edee088403e575b93d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 23 Sep 2008 19:10:38 +0000 Subject: [PATCH 0166/1195] Convertd `sql/coltap.sql` to use `check_test()`. --- expected/coltap.out | 97 +++++++++++++--------- sql/coltap.sql | 196 +++++++++++++++++++++++--------------------- 2 files changed, 160 insertions(+), 133 deletions(-) diff --git a/expected/coltap.out b/expected/coltap.out index 753454889332..947c71cf7633 100644 --- a/expected/coltap.out +++ b/expected/coltap.out @@ -1,40 +1,59 @@ \unset ECHO -1..38 -ok 1 - testing col_not_null( schema, table, column, desc ) -ok 2 - col_not_null( schema, table, column, desc ) should work -ok 3 - testing col_not_null( table, column, desc ) -ok 4 - col_not_null( table, column, desc ) should work -ok 5 - testing col_not_null( schema, table, column, desc ) -ok 6 - col_not_null( table, column ) should work -ok 7 - testing col_not_null( schema, table, column, desc ) -ok 8 - col_not_null( table, column ) should properly fail -ok 9 - testing col_is_null( schema, table, column, desc ) -ok 10 - col_is_null( schema, table, column, desc ) should work -ok 11 - testing col_is_null( table, column, desc ) -ok 12 - col_is_null( table, column, desc ) should work -ok 13 - testing col_is_null( schema, table, column, desc ) -ok 14 - col_is_null( table, column ) should work -ok 15 - testing col_is_null( schema, table, column, desc ) -ok 16 - col_is_null( table, column ) should properly fail -ok 17 - testing col_type_is( schema, table, column, type, desc ) -ok 18 - col_type_is( schema, table, column, type, desc ) should work -ok 19 - testing col_type_is( table, column, type, desc ) -ok 20 - col_type_is( table, column, type, desc ) should work -ok 21 - testing col_type_is( table, column, type ) -ok 22 - col_type_is( table, column, type ) should work -ok 23 - testing col_type_is( table, column, type ) case-insensitively -ok 24 - col_type_is( table, column, type ) should work case-insensitively -ok 25 - testing col_type_is( table, column, type ) failure -ok 26 - col_type_is( table, column, type ) should fail with proper diagnostics -ok 27 - testing col_type_is( schema, table, column, type(precision,scale), description ) -ok 28 - col_type_is( schema, table, column, type, precision(scale,description) should work -ok 29 - col_type_is( table, column, type, precision, desc ) fail -ok 30 - col_type_is with precision should have nice diagnostics -ok 31 - col_default_is( schema, table, column, default, description ) -ok 32 - col_default_is( schema, table, column, default, description ) should work -ok 33 - col_default_is( schema, table, column, default, description ) fail -ok 34 - ok 33 - Should get proper diagnostics for a default failure -ok 35 - col_default_is( table, column, default, description ) -ok 36 - col_default_is( table, column, default, description ) should work -ok 37 - col_default_is( table, column, default ) -ok 38 - col_default_is( table, column, default ) should work +1..57 +ok 1 - col_not_null( sch, tab, col, desc ) should pass +ok 2 - col_not_null( sch, tab, col, desc ) should have the proper description +ok 3 - col_not_null( sch, tab, col, desc ) should have the proper diagnostics +ok 4 - col_not_null( tab, col, desc ) should pass +ok 5 - col_not_null( tab, col, desc ) should have the proper description +ok 6 - col_not_null( tab, col, desc ) should have the proper diagnostics +ok 7 - col_not_null( table, column ) should pass +ok 8 - col_not_null( table, column ) should have the proper description +ok 9 - col_not_null( table, column ) should have the proper diagnostics +ok 10 - col_not_null( table, column ) fail should fail +ok 11 - col_not_null( table, column ) fail should have the proper description +ok 12 - col_not_null( table, column ) fail should have the proper diagnostics +ok 13 - col_is_null( sch, tab, col, desc ) should pass +ok 14 - col_is_null( sch, tab, col, desc ) should have the proper description +ok 15 - col_is_null( sch, tab, col, desc ) should have the proper diagnostics +ok 16 - col_is_null( tab, col, desc ) should pass +ok 17 - col_is_null( tab, col, desc ) should have the proper description +ok 18 - col_is_null( tab, col, desc ) should have the proper diagnostics +ok 19 - col_is_null( tab, col ) should pass +ok 20 - col_is_null( tab, col ) should have the proper description +ok 21 - col_is_null( tab, col ) should have the proper diagnostics +ok 22 - col_is_null( tab, col ) fail should fail +ok 23 - col_is_null( tab, col ) fail should have the proper description +ok 24 - col_is_null( tab, col ) fail should have the proper diagnostics +ok 25 - col_type_is( sch, tab, col, type, desc ) should pass +ok 26 - col_type_is( sch, tab, col, type, desc ) should have the proper description +ok 27 - col_type_is( sch, tab, col, type, desc ) should have the proper diagnostics +ok 28 - col_type_is( tab, col, type, desc ) should pass +ok 29 - col_type_is( tab, col, type, desc ) should have the proper description +ok 30 - col_type_is( tab, col, type, desc ) should have the proper diagnostics +ok 31 - col_type_is( tab, col, type ) should pass +ok 32 - col_type_is( tab, col, type ) should have the proper description +ok 33 - col_type_is( tab, col, type ) should have the proper diagnostics +ok 34 - col_type_is( tab, col, type ) insensitive should pass +ok 35 - col_type_is( tab, col, type ) insensitive should have the proper description +ok 36 - col_type_is( tab, col, type ) insensitive should have the proper diagnostics +ok 37 - col_type_is( tab, col, type ) fail should fail +ok 38 - col_type_is( tab, col, type ) fail should have the proper description +ok 39 - col_type_is( tab, col, type ) fail should have the proper diagnostics +ok 40 - col_type_is with precision should pass +ok 41 - col_type_is with precision should have the proper description +ok 42 - col_type_is with precision should have the proper diagnostics +ok 43 - col_type_is precision fail should fail +ok 44 - col_type_is precision fail should have the proper description +ok 45 - col_type_is precision fail should have the proper diagnostics +ok 46 - col_default_is( sch, tab, col, def, desc ) should pass +ok 47 - col_default_is( sch, tab, col, def, desc ) should have the proper description +ok 48 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics +ok 49 - col_default_is() fail should fail +ok 50 - col_default_is() fail should have the proper description +ok 51 - col_default_is() fail should have the proper diagnostics +ok 52 - col_default_is( tab, col, def, desc ) should pass +ok 53 - col_default_is( tab, col, def, desc ) should have the proper description +ok 54 - col_default_is( tab, col, def, desc ) should have the proper diagnostics +ok 55 - col_default_is( tab, col, def ) should pass +ok 56 - col_default_is( tab, col, def ) should have the proper description +ok 57 - col_default_is( tab, col, def ) should have the proper diagnostics diff --git a/sql/coltap.sql b/sql/coltap.sql index d940e027d6cb..49a80a47a999 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(38); +SELECT plan(57); -- This will be rolled back. :-) CREATE TABLE public.sometab( @@ -15,163 +15,171 @@ CREATE TABLE public.sometab( /****************************************************************************/ -- Test col_not_null(). -\echo ok 1 - testing col_not_null( schema, table, column, desc ) -SELECT is( +SELECT * FROM check_test( col_not_null( 'pg_catalog', 'pg_type', 'typname', 'typname not null' ), - 'ok 1 - typname not null', - 'col_not_null( schema, table, column, desc ) should work' + true, + 'col_not_null( sch, tab, col, desc )', + 'typname not null', + '' ); -\echo ok 3 - testing col_not_null( table, column, desc ) -SELECT is( + +SELECT * FROM check_test( col_not_null( 'sometab', 'id', 'blah blah blah' ), - 'ok 3 - blah blah blah', - 'col_not_null( table, column, desc ) should work' + true, + 'col_not_null( tab, col, desc )', + 'blah blah blah', + '' ); -\echo ok 5 - testing col_not_null( schema, table, column, desc ) -SELECT is( +SELECT * FROM check_test( col_not_null( 'sometab', 'id' ), - 'ok 5 - Column sometab(id) should be NOT NULL', - 'col_not_null( table, column ) should work' + true, + 'col_not_null( table, column )', + 'Column sometab(id) should be NOT NULL', + '' ); + -- Make sure failure is correct. -\echo ok 7 - testing col_not_null( schema, table, column, desc ) -SELECT is( +SELECT * FROM check_test( col_not_null( 'sometab', 'name' ), - 'not ok 7 - Column sometab(name) should be NOT NULL -# Failed test 7: "Column sometab(name) should be NOT NULL"', - 'col_not_null( table, column ) should properly fail' + false, + 'col_not_null( table, column ) fail', + 'Column sometab(name) should be NOT NULL', + '' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 7 ); /****************************************************************************/ -- Test col_is_null(). -\echo ok 9 - testing col_is_null( schema, table, column, desc ) -SELECT is( +SELECT * FROM check_test( col_is_null( 'public', 'sometab', 'name', 'name is null' ), - 'ok 9 - name is null', - 'col_is_null( schema, table, column, desc ) should work' + true, + 'col_is_null( sch, tab, col, desc )', + 'name is null', + '' ); -\echo ok 11 - testing col_is_null( table, column, desc ) -SELECT is( + +SELECT * FROM check_test( col_is_null( 'sometab', 'name', 'my desc' ), - 'ok 11 - my desc', - 'col_is_null( table, column, desc ) should work' + true, + 'col_is_null( tab, col, desc )', + 'my desc', + '' ); -\echo ok 13 - testing col_is_null( schema, table, column, desc ) -SELECT is( +SELECT * FROM check_test( col_is_null( 'sometab', 'name' ), - 'ok 13 - Column sometab(name) should allow NULL', - 'col_is_null( table, column ) should work' + true, + 'col_is_null( tab, col )', + 'Column sometab(name) should allow NULL', + '' ); -- Make sure failure is correct. -\echo ok 15 - testing col_is_null( schema, table, column, desc ) -SELECT is( +SELECT * FROM check_test( col_is_null( 'sometab', 'id' ), - 'not ok 15 - Column sometab(id) should allow NULL -# Failed test 15: "Column sometab(id) should allow NULL"', - 'col_is_null( table, column ) should properly fail' + false, + 'col_is_null( tab, col ) fail', + 'Column sometab(id) should allow NULL', + '' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 15 ); /****************************************************************************/ -- Test col_type_is(). -\echo ok 17 - testing col_type_is( schema, table, column, type, desc ) -SELECT is( +SELECT * FROM check_test( col_type_is( 'public', 'sometab', 'name', 'text', 'name is text' ), - 'ok 17 - name is text', - 'col_type_is( schema, table, column, type, desc ) should work' + true, + 'col_type_is( sch, tab, col, type, desc )', + 'name is text', + '' ); -\echo ok 19 - testing col_type_is( table, column, type, desc ) -SELECT is( +SELECT * FROM check_test( col_type_is( 'sometab', 'name', 'text', 'yadda yadda yadda' ), - 'ok 19 - yadda yadda yadda', - 'col_type_is( table, column, type, desc ) should work' + true, + 'col_type_is( tab, col, type, desc )', + 'yadda yadda yadda', + '' ); -\echo ok 21 - testing col_type_is( table, column, type ) -SELECT is( +SELECT * FROM check_test( col_type_is( 'sometab', 'name', 'text' ), - 'ok 21 - Column sometab(name) should be type text', - 'col_type_is( table, column, type ) should work' + true, + 'col_type_is( tab, col, type )', + 'Column sometab(name) should be type text', + '' ); -\echo ok 23 - testing col_type_is( table, column, type ) case-insensitively -SELECT is( +SELECT * FROM check_test( col_type_is( 'sometab', 'name', 'TEXT' ), - 'ok 23 - Column sometab(name) should be type TEXT', - 'col_type_is( table, column, type ) should work case-insensitively' + true, + 'col_type_is( tab, col, type ) insensitive', + 'Column sometab(name) should be type TEXT', + '' ); -- Make sure failure is correct. -\echo ok 25 - testing col_type_is( table, column, type ) failure -SELECT is( +SELECT * FROM check_test( col_type_is( 'sometab', 'name', 'int4' ), - 'not ok 25 - Column sometab(name) should be type int4 -# Failed test 25: "Column sometab(name) should be type int4" -# have: text -# want: int4', - 'col_type_is( table, column, type ) should fail with proper diagnostics' + false, + 'col_type_is( tab, col, type ) fail', + 'Column sometab(name) should be type int4', + ' have: text + want: int4' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 25 ); /****************************************************************************/ -- Try col_type_is() with precision. -\echo ok 27 - testing col_type_is( schema, table, column, type(precision,scale), description ) -SELECT is( +SELECT * FROM check_test( col_type_is( 'public', 'sometab', 'numb', 'numeric(10,2)', 'lol' ), - 'ok 27 - lol', - 'col_type_is( schema, table, column, type, precision(scale,description) should work' + true, + 'col_type_is with precision', + 'lol', + '' ); -- Check its diagnostics. -\echo ok 29 - col_type_is( table, column, type, precision, desc ) fail -SELECT is( +SELECT * FROM check_test( col_type_is( 'sometab', 'myint', 'numeric(7)', 'should be numeric(7)' ), - 'not ok 29 - should be numeric(7) -# Failed test 29: "should be numeric(7)" -# have: numeric(8,0) -# want: numeric(7)', - 'col_type_is with precision should have nice diagnostics' + false, + 'col_type_is precision fail', + 'should be numeric(7)', + ' have: numeric(8,0) + want: numeric(7)' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 29 ); - /****************************************************************************/ -- Test col_default_is(). -\echo ok 31 - col_default_is( schema, table, column, default, description ) -SELECT is( +SELECT * FROM check_test( col_default_is( 'public', 'sometab', 'name', ''::text, 'name should default to empty string' ), - 'ok 31 - name should default to empty string', - 'col_default_is( schema, table, column, default, description ) should work' + true, + 'col_default_is( sch, tab, col, def, desc )', + 'name should default to empty string', + '' ); -\echo ok 33 - col_default_is( schema, table, column, default, description ) fail -SELECT is( +SELECT * FROM check_test( col_default_is( 'public', 'sometab', 'name', 'foo'::text, 'name should default to ''foo''' ), - 'not ok 33 - name should default to ''foo'' -# Failed test 33: "name should default to ''foo''" -# have: -# want: foo', - 'ok 33 - Should get proper diagnostics for a default failure' + false, + 'col_default_is() fail', + 'name should default to ''foo''', + ' have: + want: foo' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 33 ); -\echo ok 35 - col_default_is( table, column, default, description ) -SELECT is( +SELECT * FROM check_test( col_default_is( 'sometab', 'name', ''::text, 'name should default to empty string' ), - 'ok 35 - name should default to empty string', - 'col_default_is( table, column, default, description ) should work' + true, + 'col_default_is( tab, col, def, desc )', + 'name should default to empty string', + '' ); -\echo ok 37 - col_default_is( table, column, default ) -SELECT is( +SELECT * FROM check_test( col_default_is( 'sometab', 'name', '' ), - 'ok 37 - Column sometab(name) should default to ''''', - 'col_default_is( table, column, default ) should work' + true, + 'col_default_is( tab, col, def )', + 'Column sometab(name) should default to ''''', + '' ); /****************************************************************************/ From 2b8c3c1e3786087293f843dc179d8d11ce8691cf Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 23 Sep 2008 19:23:17 +0000 Subject: [PATCH 0167/1195] Convertd `sql/hastap.sql` to use `check_test()`. --- expected/hastap.out | 77 ++++++++++++++--------- sql/hastap.sql | 150 ++++++++++++++++++++++---------------------- 2 files changed, 122 insertions(+), 105 deletions(-) diff --git a/expected/hastap.out b/expected/hastap.out index acf0d2f8f364..14b70150fd33 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,32 +1,47 @@ \unset ECHO -1..30 -ok 1 - has_table(table) fail -ok 2 - has_table(table) should fail for non-existent table -ok 3 - has_table(table, desc) fail -ok 4 - has_table(table, dessc) should fail for non-existent table -ok 5 - has_table(schema, table, desc) fail -ok 6 - has_table(schema, table, desc) should fail for non-existent table -ok 7 - has_table(table, desc) pass -ok 8 - has_table(table, desc) should pass for an existing table -ok 9 - has_table(schema, table, desc) pass -ok 10 - has_table(schema, table, desc) should pass for an existing table -ok 11 - has_view(view) fail -ok 12 - has_view(view) should fail for non-existent view -ok 13 - has_view(view, desc) fail -ok 14 - has_view(view, desc) should fail for non-existent view -ok 15 - has_view(schema, view, desc) fail -ok 16 - has_view(schema, view, desc) should fail for non-existent view -ok 17 - has_view(view, desc) pass -ok 18 - has_view(view, desc) should pass for an existing view -ok 19 - has_view(schema, view, desc) pass -ok 20 - has_view(schema, view, desc) should pass for an existing view -ok 21 - has_column(table, column) fail -ok 22 - has_column(table, column) should fail for non-existent table -ok 23 - has_column(table, column, desc) fail -ok 24 - has_column(table, column, desc) should fail for non-existent table -ok 25 - has_column(schema, table, column, desc) fail -ok 26 - has_column(schema, table, column, desc) should fail for non-existent table -ok 27 - has_column(table, column) pass -ok 28 - has_column(table, column) should pass for an existing column -ok 29 - has_column(schema, column, desc) pass -ok 30 - has_column(schema, table, column, desc) should pass for an existing view column +1..45 +ok 1 - has_table(non-existent table) should fail +ok 2 - has_table(non-existent table) should have the proper description +ok 3 - has_table(non-existent table) should have the proper diagnostics +ok 4 - has_table(non-existent schema, tab) should fail +ok 5 - has_table(non-existent schema, tab) should have the proper description +ok 6 - has_table(non-existent schema, tab) should have the proper diagnostics +ok 7 - has_table(sch, non-existent tab, desc) should fail +ok 8 - has_table(sch, non-existent tab, desc) should have the proper description +ok 9 - has_table(sch, non-existent tab, desc) should have the proper diagnostics +ok 10 - has_table(tab, desc) should pass +ok 11 - has_table(tab, desc) should have the proper description +ok 12 - has_table(tab, desc) should have the proper diagnostics +ok 13 - has_table(sch, tab, desc) should pass +ok 14 - has_table(sch, tab, desc) should have the proper description +ok 15 - has_table(sch, tab, desc) should have the proper diagnostics +ok 16 - has_view(non-existent view) should fail +ok 17 - has_view(non-existent view) should have the proper description +ok 18 - has_view(non-existent view) should have the proper diagnostics +ok 19 - has_view(non-existent view, desc) should fail +ok 20 - has_view(non-existent view, desc) should have the proper description +ok 21 - has_view(non-existent view, desc) should have the proper diagnostics +ok 22 - has_view(sch, non-existtent view, desc) should fail +ok 23 - has_view(sch, non-existtent view, desc) should have the proper description +ok 24 - has_view(sch, non-existtent view, desc) should have the proper diagnostics +ok 25 - has_view(view, desc) should pass +ok 26 - has_view(view, desc) should have the proper description +ok 27 - has_view(view, desc) should have the proper diagnostics +ok 28 - has_view(sch, view, desc) should pass +ok 29 - has_view(sch, view, desc) should have the proper description +ok 30 - has_view(sch, view, desc) should have the proper diagnostics +ok 31 - has_column(non-existent tab, col) should fail +ok 32 - has_column(non-existent tab, col) should have the proper description +ok 33 - has_column(non-existent tab, col) should have the proper diagnostics +ok 34 - has_column(non-existent tab, col, desc) should fail +ok 35 - has_column(non-existent tab, col, desc) should have the proper description +ok 36 - has_column(non-existent tab, col, desc) should have the proper diagnostics +ok 37 - has_column(non-existent sch, tab, col, desc) should fail +ok 38 - has_column(non-existent sch, tab, col, desc) should have the proper description +ok 39 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 40 - has_column(table, column) should pass +ok 41 - has_column(table, column) should have the proper description +ok 42 - has_column(table, column) should have the proper diagnostics +ok 43 - has_column(sch, tab, col, desc) should pass +ok 44 - has_column(sch, tab, col, desc) should have the proper description +ok 45 - has_column(sch, tab, col, desc) should have the proper diagnostics diff --git a/sql/hastap.sql b/sql/hastap.sql index 70c1fc6131c4..611027e8e88a 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(30); +SELECT plan(45); -- This will be rolled back. :-) CREATE TABLE sometab( @@ -16,128 +16,130 @@ CREATE TABLE sometab( /****************************************************************************/ -- Test has_table(). -\echo ok 1 - has_table(table) fail - -SELECT is( +SELECT * FROM check_test( has_table( '__SDFSDFD__' ), - 'not ok 1 - Table __SDFSDFD__ should exist -# Failed test 1: "Table __SDFSDFD__ should exist"', - 'has_table(table) should fail for non-existent table' + false, + 'has_table(non-existent table)', + 'Table __SDFSDFD__ should exist', + '' ); -\echo ok 3 - has_table(table, desc) fail -SELECT is( +SELECT * FROM check_test( has_table( '__SDFSDFD__', 'lol' ), - 'not ok 3 - lol -# Failed test 3: "lol"', - 'has_table(table, dessc) should fail for non-existent table' + false, + 'has_table(non-existent schema, tab)', + 'lol', + '' ); -\echo ok 5 - has_table(schema, table, desc) fail -SELECT is( +SELECT * FROM check_test( has_table( 'foo', '__SDFSDFD__', 'desc' ), - 'not ok 5 - desc -# Failed test 5: "desc"', - 'has_table(schema, table, desc) should fail for non-existent table' + false, + 'has_table(sch, non-existent tab, desc)', + 'desc', + '' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 1, 3, 5 ); -\echo ok 7 - has_table(table, desc) pass -SELECT is( +SELECT * FROM check_test( has_table( 'pg_type', 'lol' ), - 'ok 7 - lol', - 'has_table(table, desc) should pass for an existing table' + true, + 'has_table(tab, desc)', + 'lol', + '' ); -\echo ok 9 - has_table(schema, table, desc) pass -SELECT is( +SELECT * FROM check_test( has_table( 'pg_catalog', 'pg_type', 'desc' ), - 'ok 9 - desc', - 'has_table(schema, table, desc) should pass for an existing table' + true, + 'has_table(sch, tab, desc)', + 'desc', + '' ); /****************************************************************************/ -- Test has_view(). -\echo ok 11 - has_view(view) fail -SELECT is( +SELECT * FROM check_test( has_view( '__SDFSDFD__' ), - 'not ok 11 - View __SDFSDFD__ should exist -# Failed test 11: "View __SDFSDFD__ should exist"', - 'has_view(view) should fail for non-existent view' + false, + 'has_view(non-existent view)', + 'View __SDFSDFD__ should exist', + '' ); -\echo ok 13 - has_view(view, desc) fail -SELECT is( +SELECT * FROM check_test( has_view( '__SDFSDFD__', 'howdy' ), - 'not ok 13 - howdy -# Failed test 13: "howdy"', - 'has_view(view, desc) should fail for non-existent view' + false, + 'has_view(non-existent view, desc)', + 'howdy', + '' ); -\echo ok 15 - has_view(schema, view, desc) fail -SELECT is( +SELECT * FROM check_test( has_view( 'foo', '__SDFSDFD__', 'desc' ), - 'not ok 15 - desc -# Failed test 15: "desc"', - 'has_view(schema, view, desc) should fail for non-existent view' + false, + 'has_view(sch, non-existtent view, desc)', + 'desc', + '' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 11, 13, 15 ); -\echo ok 17 - has_view(view, desc) pass -SELECT is( +SELECT * FROM check_test( has_view( 'pg_tables', 'yowza' ), - 'ok 17 - yowza', - 'has_view(view, desc) should pass for an existing view' + true, + 'has_view(view, desc)', + 'yowza', + '' ); -\echo ok 19 - has_view(schema, view, desc) pass -SELECT is( +SELECT * FROM check_test( has_view( 'information_schema', 'tables', 'desc' ), - 'ok 19 - desc', - 'has_view(schema, view, desc) should pass for an existing view' + true, + 'has_view(sch, view, desc)', + 'desc', + '' ); /****************************************************************************/ -- Test has_column(). -\echo ok 21 - has_column(table, column) fail -SELECT is( +SELECT * FROM check_test( has_column( '__SDFSDFD__', 'foo' ), - 'not ok 21 - Column __SDFSDFD__(foo) should exist -# Failed test 21: "Column __SDFSDFD__(foo) should exist"', - 'has_column(table, column) should fail for non-existent table' + false, + 'has_column(non-existent tab, col)', + 'Column __SDFSDFD__(foo) should exist', + '' ); -\echo ok 23 - has_column(table, column, desc) fail -SELECT is( +SELECT * FROM check_test( has_column( '__SDFSDFD__', 'bar', 'whatever' ), - 'not ok 23 - whatever -# Failed test 23: "whatever"', - 'has_column(table, column, desc) should fail for non-existent table' + false, + 'has_column(non-existent tab, col, desc)', + 'whatever', + '' ); -\echo ok 25 - has_column(schema, table, column, desc) fail -SELECT is( +SELECT * FROM check_test( has_column( 'foo', '__SDFSDFD__', 'bar', 'desc' ), - 'not ok 25 - desc -# Failed test 25: "desc"', - 'has_column(schema, table, column, desc) should fail for non-existent table' + false, + 'has_column(non-existent sch, tab, col, desc)', + 'desc', + '' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 21, 23, 25 ); -\echo ok 27 - has_column(table, column) pass -SELECT is( +SELECT * FROM check_test( has_column( 'sometab', 'id' ), - 'ok 27 - Column sometab(id) should exist', - 'has_column(table, column) should pass for an existing column' + true, + 'has_column(table, column)', + 'Column sometab(id) should exist', + '' ); -\echo ok 29 - has_column(schema, column, desc) pass -SELECT is( +SELECT * FROM check_test( has_column( 'information_schema', 'tables', 'table_name', 'desc' ), - 'ok 29 - desc', - 'has_column(schema, table, column, desc) should pass for an existing view column' + true, + 'has_column(sch, tab, col, desc)', + 'desc', + '' ); /****************************************************************************/ From ac93cc6f7fb8bbdff438854d4e4a0ea000fc5921 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 23 Sep 2008 21:07:21 +0000 Subject: [PATCH 0168/1195] * Fixed whitespace issues when testing diagnostics in `check_test()`. * Converted `sql/istap.sql` to use `check_test()`. --- Changes | 2 ++ expected/istap.out | 66 ++++++++++++++++++++---------------- pgtap.sql.in | 9 +++-- sql/cantap.sql | 4 +-- sql/check.sql | 4 +-- sql/cmpok.sql | 4 +-- sql/coltap.sql | 6 ++-- sql/fktap.sql | 22 ++++++------ sql/istap.sql | 83 +++++++++++++++++----------------------------- sql/throwtap.sql | 6 ++-- 10 files changed, 100 insertions(+), 106 deletions(-) diff --git a/Changes b/Changes index c307a3c8aa78..c7d5e82c315f 100644 --- a/Changes +++ b/Changes @@ -22,6 +22,8 @@ Revision history for pgTAP error codes. - Converted some more tests to use `check_test()`. - Added `can()` and `can_ok()`. + - Fixed a bug in `check_test()` where the leading whitespace for + diagnostic messages could be off by 1 or more characters. 0.10 2008-09-18T22:59:31 - Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the diff --git a/expected/istap.out b/expected/istap.out index 070ab1db66b0..2001b9edeb47 100644 --- a/expected/istap.out +++ b/expected/istap.out @@ -1,29 +1,39 @@ \unset ECHO -1..27 -ok 1 - is() success -ok 2 - isa(1, 1) should work -ok 3 - is() success 2 -ok 4 - is('x', 'x') should work -ok 5 - is() success 3 -ok 6 - is(1.1, 1.10) should work -ok 7 - is() success 4 -ok 8 - is(1.1, 1.10) should work -ok 9 - is() success 5 -ok 10 - is(true, true) should work -ok 11 - is() success 6 -ok 12 - is(false, false) should work -ok 13 - is() success 7 -ok 14 - is(1, 1, 'foo') should work -ok 15 - is() failure -ok 16 - is(1, 2) should work -ok 17 - isnt() success -ok 18 - isnt(1, 2) should work -ok 19 - isnt() failure -ok 20 - is(1, 2) should work -ok 21 - is() should work with psql variables -ok 22 - is(NULL, NULL) success -ok 23 - is(NULL, NULL) should pass -ok 24 - is(NULL, foo) failure -ok 25 - is(NULL, foo) should fail -ok 26 - is(foo, NULL) failure -ok 27 - is(foo, NULL) should fail +1..37 +ok 1 - is(1, 1) should pass +ok 2 - is(1, 1) should have the proper description +ok 3 - is(1, 1) should have the proper diagnostics +ok 4 - is('x', 'x') should pass +ok 5 - is('x', 'x') should have the proper description +ok 6 - is('x', 'x') should have the proper diagnostics +ok 7 - is(1.1, 1.10) should pass +ok 8 - is(1.1, 1.10) should have the proper description +ok 9 - is(1.1, 1.10) should have the proper diagnostics +ok 10 - is(true, true) should pass +ok 11 - is(true, true) should have the proper description +ok 12 - is(true, true) should have the proper diagnostics +ok 13 - is(false, false) should pass +ok 14 - is(false, false) should have the proper description +ok 15 - is(false, false) should have the proper diagnostics +ok 16 - is(1, 1, desc) should pass +ok 17 - is(1, 1, desc) should have the proper description +ok 18 - is(1, 1, desc) should have the proper diagnostics +ok 19 - is(1, 2) should fail +ok 20 - is(1, 2) should have the proper description +ok 21 - is(1, 2) should have the proper diagnostics +ok 22 - isnt(1, 2) should pass +ok 23 - isnt(1, 2) should have the proper description +ok 24 - isnt(1, 2) should have the proper diagnostics +ok 25 - isnt(1, 1) should fail +ok 26 - isnt(1, 1) should have the proper description +ok 27 - isnt(1, 1) should have the proper diagnostics +ok 28 - is() should work with psql variables +ok 29 - is(NULL, NULL) should pass +ok 30 - is(NULL, NULL) should have the proper description +ok 31 - is(NULL, NULL) should have the proper diagnostics +ok 32 - is(NULL, foo) should fail +ok 33 - is(NULL, foo) should have the proper description +ok 34 - is(NULL, foo) should have the proper diagnostics +ok 35 - is(foo, NULL) should fail +ok 36 - is(foo, NULL) should have the proper description +ok 37 - is(foo, NULL) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 138e9d9fa081..3952fd05c860 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1756,17 +1756,20 @@ BEGIN -- Remove ok and the test number. adiag := substring( have - FROM CASE WHEN aok THEN 4 ELSE 8 END + char_length(tnumb::text) + FROM CASE WHEN aok THEN 4 ELSE 9 END + char_length(tnumb::text) ); -- Remove the description, if there is one. - adiag := substring( adiag FROM 3 + char_length( diag( adescr ) ) ); + IF adescr <> '' THEN + adiag := substring( adiag FROM 3 + char_length( diag( adescr ) ) ); + END IF; -- Remove failure message from ok(). IF NOT aok THEN adiag := substring( adiag - FROM 14 + char_length(tnumb::text) + 4 + char_length( diag( adescr ) ) + FROM 14 + char_length(tnumb::text) + + CASE adescr WHEN '' THEN 3 ELSE 4 + char_length( diag( adescr ) ) END ); END IF; diff --git a/sql/cantap.sql b/sql/cantap.sql index a586f1e4924a..411327c6eef3 100644 --- a/sql/cantap.sql +++ b/sql/cantap.sql @@ -154,7 +154,7 @@ SELECT * FROM check_test( false, 'fail can(schema) with desc', 'whatever', - ' pg_catalog.foo() missing + ' pg_catalog.foo() missing pg_catalog.bar() missing' ); @@ -179,7 +179,7 @@ SELECT * FROM check_test( false, 'fail can() with desc', 'whatever', - ' foo() missing + ' foo() missing bar() missing' ); diff --git a/sql/check.sql b/sql/check.sql index 20f8cd0291d2..8e7627fb1163 100644 --- a/sql/check.sql +++ b/sql/check.sql @@ -88,7 +88,7 @@ SELECT * FROM check_test( false, 'col_has_check( sch, tab, col, desc ) fail', 'public.sometab.id should be a pk', - ' have: {name} + ' have: {name} want: {id}' ); @@ -97,7 +97,7 @@ SELECT * FROM check_test( false, 'col_has_check( tab, col, desc ) fail', 'sometab.id should be a pk', - ' have: {name} + ' have: {name} want: {id}' ); diff --git a/sql/cmpok.sql b/sql/cmpok.sql index f77a7af8a3ac..e65c5f347b13 100644 --- a/sql/cmpok.sql +++ b/sql/cmpok.sql @@ -69,7 +69,7 @@ SELECT * FROM check_test( false, 'cmp_ok() fail', '1 should = 2', - ' ''1'' + ' ''1'' = ''2''' ); @@ -79,7 +79,7 @@ SELECT * FROM check_test( false, 'cmp_ok() NULL fail', '1 should = NULL', - ' ''1'' + ' ''1'' = NULL' ); diff --git a/sql/coltap.sql b/sql/coltap.sql index 49a80a47a999..7225047d041b 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -122,7 +122,7 @@ SELECT * FROM check_test( false, 'col_type_is( tab, col, type ) fail', 'Column sometab(name) should be type int4', - ' have: text + ' have: text want: int4' ); @@ -142,7 +142,7 @@ SELECT * FROM check_test( false, 'col_type_is precision fail', 'should be numeric(7)', - ' have: numeric(8,0) + ' have: numeric(8,0) want: numeric(7)' ); @@ -162,7 +162,7 @@ SELECT * FROM check_test( false, 'col_default_is() fail', 'name should default to ''foo''', - ' have: + ' have: want: foo' ); diff --git a/sql/fktap.sql b/sql/fktap.sql index 79cf980c08e6..cef012c3fc3c 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -104,7 +104,7 @@ SELECT * FROM check_test( false, 'col_is_fk( schema, table, column, description )', 'public.fk.name should be an fk', - ' Table public.fk has foreign key constraints on these columns: + ' Table public.fk has foreign key constraints on these columns: {pk_id}' ); @@ -113,7 +113,7 @@ SELECT * FROM check_test( false, 'col_is_fk( table, column, description )', 'fk3.name should be an fk', - ' Table fk3 has foreign key constraints on these columns: + ' Table fk3 has foreign key constraints on these columns: {pk2_num,pk2_dot} {pk_id}' ); @@ -132,7 +132,7 @@ SELECT * FROM check_test( false, 'col_is_fk with no FKs', 'pk.name should be an fk', - ' Table public.pk has no foreign key columns' + ' Table public.pk has no foreign key columns' ); SELECT * FROM check_test( @@ -140,7 +140,7 @@ SELECT * FROM check_test( false, 'col_is_fk with no FKs', 'Column pk(name) should be a foreign key', - ' Table pk has no foreign key columns' + ' Table pk has no foreign key columns' ); @@ -252,7 +252,7 @@ SELECT * FROM check_test( false, 'fk_ok fail', 'WHATEVER', - ' have: public.fk(pk_id) REFERENCES public.pk(id) + ' have: public.fk(pk_id) REFERENCES public.pk(id) want: public.fk(pk_id) REFERENCES public.pk(fid)' ); @@ -261,7 +261,7 @@ SELECT * FROM check_test( false, 'fk_ok fail desc', 'public.fk(pk_id) should reference public.pk(fid)', - ' have: public.fk(pk_id) REFERENCES public.pk(id) + ' have: public.fk(pk_id) REFERENCES public.pk(id) want: public.fk(pk_id) REFERENCES public.pk(fid)' ); @@ -270,7 +270,7 @@ SELECT * FROM check_test( false, 'fk_ok fail no schema', 'WHATEVER', - ' have: fk(pk_id) REFERENCES pk(id) + ' have: fk(pk_id) REFERENCES pk(id) want: fk(pk_id) REFERENCES pk(fid)' ); @@ -279,7 +279,7 @@ SELECT * FROM check_test( false, 'fk_ok fail no schema desc', 'fk(pk_id) should reference pk(fid)', - ' have: fk(pk_id) REFERENCES pk(id) + ' have: fk(pk_id) REFERENCES pk(id) want: fk(pk_id) REFERENCES pk(fid)' ); @@ -288,7 +288,7 @@ SELECT * FROM check_test( false, 'fk_ok bad PK test', 'WHATEVER', - ' have: fk(pk_id) REFERENCES pk(id) + ' have: fk(pk_id) REFERENCES pk(id) want: fk(pk_id) REFERENCES ok(fid)' ); @@ -327,7 +327,7 @@ SELECT * FROM check_test( false, 'missing fk test', 'public.fk3(id) should reference public.foo(id)', - ' have: public.fk3(id) REFERENCES NOTHING + ' have: public.fk3(id) REFERENCES NOTHING want: public.fk3(id) REFERENCES public.foo(id)' ); @@ -340,7 +340,7 @@ SELECT * FROM check_test( false, 'bad FK column test', 'fk3(pk2_blah, pk2_dot) should reference pk2(num, dot)', - ' have: fk3(pk2_blah, pk2_dot) REFERENCES NOTHING + ' have: fk3(pk2_blah, pk2_dot) REFERENCES NOTHING want: fk3(pk2_blah, pk2_dot) REFERENCES pk2(num, dot)' ); diff --git a/sql/istap.sql b/sql/istap.sql index d0a9eac4db8e..9a1e357f08d9 100644 --- a/sql/istap.sql +++ b/sql/istap.sql @@ -3,43 +3,25 @@ -- $Id$ -SELECT plan(27); +SELECT plan(37); /****************************************************************************/ -- Test is(). -\echo ok 1 - is() success -SELECT is( is(1, 1), 'ok 1', 'isa(1, 1) should work' ); -\echo ok 3 - is() success 2 -SELECT is( is('x'::text, 'x'::text), 'ok 3', 'is(''x'', ''x'') should work' ); -\echo ok 5 - is() success 3 -SELECT is( is(1.1, 1.10), 'ok 5', 'is(1.1, 1.10) should work' ); -\echo ok 7 - is() success 4 -SELECT is( is(1.1, 1.10), 'ok 7', 'is(1.1, 1.10) should work' ); -\echo ok 9 - is() success 5 -SELECT is( is(true, true), 'ok 9', 'is(true, true) should work' ); -\echo ok 11 - is() success 6 -SELECT is( is(false, false), 'ok 11', 'is(false, false) should work' ); -\echo ok 13 - is() success 7 -SELECT is( is(1, 1, 'foo'), 'ok 13 - foo', 'is(1, 1, ''foo'') should work' ); -\echo ok 15 - is() failure -SELECT is( is( 1, 2 ), 'not ok 15 -# Failed test 15 -# have: 1 -# want: 2', 'is(1, 2) should work' ); +SELECT * FROM check_test( is(1, 1), true, 'is(1, 1)', '', '' ); +SELECT * FROM check_test( is('x'::text, 'x'::text), true, 'is(''x'', ''x'')', '', '' ); +SELECT * FROM check_test( is(1.1, 1.10), true, 'is(1.1, 1.10)', '', '' ); +SELECT * FROM check_test( is(true, true), true, 'is(true, true)', '', '' ); +SELECT * FROM check_test( is(false, false), true, 'is(false, false)', '', '' ); +SELECT * FROM check_test( is(1, 1, 'foo'), true, 'is(1, 1, desc)', 'foo', '' ); +SELECT * FROM check_test( is( 1, 2 ), false, 'is(1, 2)', '', ' have: 1 + want: 2'); /****************************************************************************/ -- Test isnt(). -\echo ok 17 - isnt() success -SELECT is( isnt(1, 2), 'ok 17', 'isnt(1, 2) should work' ); -\echo ok 19 - isnt() failure -SELECT is( isnt( 1, 1 ), 'not ok 19 -# Failed test 19 -# 1 -# <> -# 1', 'is(1, 2) should work' ); - --- Clean up the failed test results. -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 15, 19 ); +SELECT * FROM check_test( isnt(1, 2), true, 'isnt(1, 2)', '', '' ); +SELECT * FROM check_test( isnt( 1, 1 ), false, 'isnt(1, 1)', '', ' 1 + <> + 1' ); /****************************************************************************/ -- Try using variables. @@ -49,36 +31,33 @@ SELECT is( :foo::text, :bar::text, 'is() should work with psql variables' ); /****************************************************************************/ -- Try using NULLs. -\echo ok 22 - is(NULL, NULL) success -SELECT is( + +SELECT * FROM check_test( is( NULL::text, NULL::text, 'NULLs' ), - 'ok 22 - NULLs', - 'is(NULL, NULL) should pass' + true, + 'is(NULL, NULL)', + 'NULLs', + '' ); -\echo ok 24 - is(NULL, foo) failure -SELECT is( +SELECT * FROM check_test( is( NULL::text, 'foo' ), - 'not ok 24 -# Failed test 24 -# have: NULL -# want: foo', - 'is(NULL, foo) should fail' + false, + 'is(NULL, foo)', + '', + ' have: NULL + want: foo' ); -\echo ok 26 - is(foo, NULL) failure -SELECT is( +SELECT * FROM check_test( is( 'foo', NULL::text ), - 'not ok 26 -# Failed test 26 -# have: foo -# want: NULL', - 'is(foo, NULL) should fail' + false, + 'is(foo, NULL)', + '', + ' have: foo + want: NULL' ); --- Clean up the failed test results. -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 24, 26 ); - /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/sql/throwtap.sql b/sql/throwtap.sql index 332325c849e6..8cf98c0da10d 100644 --- a/sql/throwtap.sql +++ b/sql/throwtap.sql @@ -62,7 +62,7 @@ SELECT * FROM check_test( false, 'invalid errcode', 'threw 97212', - ' caught: P0001: todo_end() called without todo_start() + ' caught: P0001: todo_end() called without todo_start() wanted: 97212' ); @@ -75,7 +75,7 @@ SELECT * FROM check_test( false, 'throws_ok diagnostics', 'threw an exception', - ' caught: no exception + ' caught: no exception wanted: an exception' ); @@ -89,7 +89,7 @@ SELECT * FROM check_test( false, 'lives_ok failure diagnostics', '', - ' died: P0001: todo_end() called without todo_start()' + ' died: P0001: todo_end() called without todo_start()' ); /****************************************************************************/ From 7dcc41946c3442d28741020026eea4f2cd1c3172 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 23 Sep 2008 21:12:21 +0000 Subject: [PATCH 0169/1195] Converted `sql/matching.sql` to use `check_test()`. --- expected/matching.out | 40 ++++++++++++++++------------- sql/matching.sql | 60 ++++++++++++++++++++++--------------------- 2 files changed, 53 insertions(+), 47 deletions(-) diff --git a/expected/matching.out b/expected/matching.out index cbbc0e9ac634..83049e757c52 100644 --- a/expected/matching.out +++ b/expected/matching.out @@ -1,22 +1,26 @@ \unset ECHO -1..20 +1..24 ok 1 - matches() should work ok 2 - matches() should work with a regex ok 3 - imatches() should work with a regex -ok 4 - matches() failure -ok 5 - Check matches diagnostics -ok 6 - doesnt_match() should work -ok 7 - doesnt_match() should work with a regex -ok 8 - doesnt_imatch() should work with a regex -ok 9 - doesnt_match() failure -ok 10 - doesnt_match() should work -ok 11 - alike() should work -ok 12 - alike() should work with a regex -ok 13 - ialike() should work with a regex -ok 14 - alike() failure -ok 15 - Check alike diagnostics -ok 16 - unalike() should work -ok 17 - unalike() should work with a regex -ok 18 - iunalike() should work with a regex -ok 19 - unalike() failure -ok 20 - Check unalike diagnostics +ok 4 - matches() fail should fail +ok 5 - matches() fail should have the proper description +ok 6 - matches() fail should have the proper diagnostics +ok 7 - doesnt_match() should work +ok 8 - doesnt_match() should work with a regex +ok 9 - doesnt_imatch() should work with a regex +ok 10 - doesnt_match() fail should fail +ok 11 - doesnt_match() fail should have the proper description +ok 12 - doesnt_match() fail should have the proper diagnostics +ok 13 - alike() should work +ok 14 - alike() should work with a regex +ok 15 - ialike() should work with a regex +ok 16 - alike() fail should fail +ok 17 - alike() fail should have the proper description +ok 18 - alike() fail should have the proper diagnostics +ok 19 - unalike() should work +ok 20 - unalike() should work with a regex +ok 21 - iunalike() should work with a regex +ok 22 - unalike() fail should fail +ok 23 - unalike() fail should have the proper description +ok 24 - unalike() fail should have the proper diagnostics diff --git a/sql/matching.sql b/sql/matching.sql index ad6fecc1e144..5b39b48d7eae 100644 --- a/sql/matching.sql +++ b/sql/matching.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(20); +SELECT plan(24); /****************************************************************************/ -- Test matches(). @@ -12,11 +12,14 @@ SELECT matches( 'foo'::text, '^fo', 'matches() should work with a regex' ); SELECT imatches( 'FOO'::text, '^fo', 'imatches() should work with a regex' ); -- Check matches() diagnostics. -\echo ok 4 - matches() failure -SELECT is( matches( 'foo'::text, '^a' ), 'not ok 4 -# Failed test 4 -# ''foo'' -# doesn''t match: ''^a''', 'Check matches diagnostics' ); +SELECT * FROM check_test( + matches( 'foo'::text, '^a' ), + false, + 'matches() fail', + '', + ' ''foo'' + doesn''t match: ''^a''' +); -- Check doesnt_match. SELECT doesnt_match( 'foo'::text, 'a', 'doesnt_match() should work' ); @@ -24,19 +27,15 @@ SELECT doesnt_match( 'foo'::text, '^o', 'doesnt_match() should work with a regex SELECT doesnt_imatch( 'foo'::text, '^o', 'doesnt_imatch() should work with a regex' ); -- Check doesnt_match diagnostics. -\echo ok 9 - doesnt_match() failure -SELECT is( +SELECT * FROM check_test( doesnt_match( 'foo'::text, 'o' ), - 'not ok 9 -# Failed test 9 -# ''foo'' -# matches: ''o''', - 'doesnt_match() should work' + false, + 'doesnt_match() fail', + '', + ' ''foo'' + matches: ''o''' ); --- Clean up the failed test results. -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 4, 9 ); - /****************************************************************************/ -- Test alike(). SELECT alike( 'foo'::text, 'foo', 'alike() should work' ); @@ -44,11 +43,14 @@ SELECT alike( 'foo'::text, 'fo%', 'alike() should work with a regex' ); SELECT ialike( 'FOO'::text, 'fo%', 'ialike() should work with a regex' ); -- Check alike() diagnostics. -\echo ok 14 - alike() failure -SELECT is( alike( 'foo'::text, 'a%'::text ), 'not ok 14 -# Failed test 14 -# ''foo'' -# doesn''t match: ''a%''', 'Check alike diagnostics' ); +SELECT * FROM check_test( + alike( 'foo'::text, 'a%'::text ), + false, + 'alike() fail', + '', + ' ''foo'' + doesn''t match: ''a%''' +); -- Test unalike(). SELECT unalike( 'foo'::text, 'f', 'unalike() should work' ); @@ -56,14 +58,14 @@ SELECT unalike( 'foo'::text, 'f%i', 'unalike() should work with a regex' ); SELECT unialike( 'FOO'::text, 'f%i', 'iunalike() should work with a regex' ); -- Check unalike() diagnostics. -\echo ok 19 - unalike() failure -SELECT is( unalike( 'foo'::text, 'f%'::text ), 'not ok 19 -# Failed test 19 -# ''foo'' -# matches: ''f%''', 'Check unalike diagnostics' ); - --- Clean up the failed test results. -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 14, 19 ); +SELECT * FROM check_test( + unalike( 'foo'::text, 'f%'::text ), + false, + 'unalike() fail', + '', + ' ''foo'' + matches: ''f%''' +); /****************************************************************************/ -- Finish the tests and clean up. From 039ce5c8e223e03c965dfe811a8af9aaae46973a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 23 Sep 2008 21:22:28 +0000 Subject: [PATCH 0170/1195] Converted `sql/moretap.out` to use `check_test()`, where it can. --- expected/moretap.out | 39 ++++++++++++++++++++---------------- sql/moretap.sql | 47 +++++++++++--------------------------------- 2 files changed, 34 insertions(+), 52 deletions(-) diff --git a/expected/moretap.out b/expected/moretap.out index 04fbb1946d08..753cb1010ec8 100644 --- a/expected/moretap.out +++ b/expected/moretap.out @@ -1,5 +1,5 @@ \unset ECHO -1..32 +1..37 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -16,19 +16,24 @@ ok 13 - Set the plan to 4 ok 14 - The output of finish() should reflect a low test plan ok 15 - Reset the plan ok 16 - plan() should have stored the test count -ok 17 - ok() success -ok 18 - ok(true) should work -ok 19 - ok() success 2 -ok 20 - ok(true, '') should work -ok 21 - ok() success 3 -ok 22 - ok(true, 'foo') should work -ok 23 - ok() failure -ok 24 - ok(false) should work -ok 25 - ok() failure 2 -ok 26 - ok(false, '') should work -ok 27 - ok() failure 3 -ok 28 - ok(false, 'foo') should work -ok 29 - Multline diagnostics -ok 30 - multiline desriptions should have subsequent lines escaped -ok 31 - Multiline description -ok 32 - multiline desriptions should have subsequent lines escaped +ok 17 - ok(true) should pass +ok 18 - ok(true) should have the proper description +ok 19 - ok(true) should have the proper diagnostics +ok 20 - ok(true, '') should pass +ok 21 - ok(true, '') should have the proper description +ok 22 - ok(true, '') should have the proper diagnostics +ok 23 - ok(true, 'foo') should pass +ok 24 - ok(true, 'foo') should have the proper description +ok 25 - ok(true, 'foo') should have the proper diagnostics +ok 26 - ok(false) should fail +ok 27 - ok(false) should have the proper description +ok 28 - ok(false) should have the proper diagnostics +ok 29 - ok(false, '') should fail +ok 30 - ok(false, '') should have the proper description +ok 31 - ok(false, '') should have the proper diagnostics +ok 32 - ok(false, 'foo') should fail +ok 33 - ok(false, 'foo') should have the proper description +ok 34 - ok(false, 'foo') should have the proper diagnostics +ok 35 - multiline desc should pass +ok 36 - multiline desc should have the proper description +ok 37 - multiline desc should have the proper diagnostics diff --git a/sql/moretap.sql b/sql/moretap.sql index b72a46f3d089..81e67c7f2bff 100644 --- a/sql/moretap.sql +++ b/sql/moretap.sql @@ -3,7 +3,7 @@ -- $Id$ -\set numb_tests 32 +\set numb_tests 37 SELECT plan(:numb_tests); -- Replace the internal record of the plan for a few tests. @@ -79,46 +79,23 @@ SELECT is( value, :numb_tests, 'plan() should have stored the test count' ) /****************************************************************************/ -- Test ok() -\echo ok 17 - ok() success -SELECT is( ok(true), 'ok 17', 'ok(true) should work' ); -\echo ok 19 - ok() success 2 -SELECT is( ok(true, ''), 'ok 19', 'ok(true, '''') should work' ); -\echo ok 21 - ok() success 3 -SELECT is( ok(true, 'foo'), 'ok 21 - foo', 'ok(true, ''foo'') should work' ); - -\echo ok 23 - ok() failure -SELECT is( ok(false), 'not ok 23 -# Failed test 23', 'ok(false) should work' ); -\echo ok 25 - ok() failure 2 -SELECT is( ok(false, ''), 'not ok 25 -# Failed test 25', 'ok(false, '''') should work' ); -\echo ok 27 - ok() failure 3 -SELECT is( ok(false, 'foo'), 'not ok 27 - foo -# Failed test 27: "foo"', 'ok(false, ''foo'') should work' ); - --- Clean up the failed test results. -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 23, 25, 27); +SELECT * FROM check_test( ok(true), true, 'ok(true)', '', ''); +SELECT * FROM check_test( ok(true, ''), true, 'ok(true, '''')', '', '' ); +SELECT * FROM check_test( ok(true, 'foo'), true, 'ok(true, ''foo'')', 'foo', '' ); -/****************************************************************************/ --- test multiline description. -\echo ok 29 - Multline diagnostics -SELECT is( - ok( true, 'foo -bar' ), - 'ok 29 - foo -# bar', - 'multiline desriptions should have subsequent lines escaped' -); +SELECT * FROM check_test( ok(false), false, 'ok(false)', '', '' ); +SELECT * FROM check_test( ok(false, ''), false, 'ok(false, '''')', '', '' ); +SELECT * FROM check_test( ok(false, 'foo'), false, 'ok(false, ''foo'')', 'foo', '' ); /****************************************************************************/ -- test multiline description. -\echo ok 31 - Multiline description -SELECT is( +SELECT * FROM check_test( ok( true, 'foo bar' ), - 'ok 31 - foo -# bar', - 'multiline desriptions should have subsequent lines escaped' + true, + 'multiline desc', 'foo +bar', + '' ); /****************************************************************************/ From cd138f5aa1aaeb492d79b69abf0536ab6aa311f7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 24 Sep 2008 04:42:57 +0000 Subject: [PATCH 0171/1195] * Converted `sql/pktap.sql` to use `check_test()`. * Converted `sql/unique.sql` to use `check_test()`. * Changed `sql/todotap.sql` to use `ok()` to compare arrays, instead of using `is()` with the undocumented `textin()` and `array_out()` functions. We lose the better diagnostics, but as long as the test pass, it should be fine, and we can switch back if the test ever starts to fail again. --- expected/pktap.out | 67 +++++++++++++--------- expected/unique.out | 67 +++++++++++++--------- sql/pktap.sql | 137 +++++++++++++++++++++++--------------------- sql/todotap.sql | 24 ++++---- sql/unique.sql | 137 +++++++++++++++++++++++--------------------- 5 files changed, 238 insertions(+), 194 deletions(-) diff --git a/expected/pktap.out b/expected/pktap.out index 9b3a7d6410cd..5e5886199eb7 100644 --- a/expected/pktap.out +++ b/expected/pktap.out @@ -1,28 +1,41 @@ \unset ECHO -1..26 -ok 1 - test has_pk( schema, table, description ) -ok 2 - has_pk( schema, table, description ) should work -ok 3 - test has_pk( table, description ) -ok 4 - has_pk( table, description ) should work -ok 5 - test has_pk( table ) -ok 6 - has_pk( table ) should work -ok 7 - test has_pk( schema, table, description ) fail -ok 8 - has_pk( schema, table, description ) should fail properly -ok 9 - test has_pk( table, description ) fail -ok 10 - has_pk( table, description ) should fail properly -ok 11 - test col_is_pk( schema, table, column, description ) -ok 12 - col_is_pk( schema, table, column, description ) should work -ok 13 - test col_is_pk( table, column, description ) -ok 14 - col_is_pk( table, column, description ) should work -ok 15 - test col_is_pk( table, column ) -ok 16 - col_is_pk( table, column ) should work -ok 17 - test col_is_pk( schema, table, column, description ) fail -ok 18 - col_is_pk( schema, table, column, description ) should fail properly -ok 19 - test col_is_pk( table, column, description ) fail -ok 20 - col_is_pk( table, column, description ) should fail properly -ok 21 - test col_is_pk( schema, table, column[], description ) -ok 22 - col_is_pk( schema, table, column[], description ) should work -ok 23 - test col_is_pk( table, column[], description ) -ok 24 - col_is_pk( table, column[], description ) should work -ok 25 - test col_is_pk( table, column[], description ) -ok 26 - col_is_pk( table, column[] ) should work +1..39 +ok 1 - has_pk( schema, table, description ) should pass +ok 2 - has_pk( schema, table, description ) should have the proper description +ok 3 - has_pk( schema, table, description ) should have the proper diagnostics +ok 4 - has_pk( table, description ) should pass +ok 5 - has_pk( table, description ) should have the proper description +ok 6 - has_pk( table, description ) should have the proper diagnostics +ok 7 - has_pk( table ) should pass +ok 8 - has_pk( table ) should have the proper description +ok 9 - has_pk( table ) should have the proper diagnostics +ok 10 - has_pk( schema, table, description ) fail should fail +ok 11 - has_pk( schema, table, description ) fail should have the proper description +ok 12 - has_pk( schema, table, description ) fail should have the proper diagnostics +ok 13 - has_pk( table, description ) fail should fail +ok 14 - has_pk( table, description ) fail should have the proper description +ok 15 - has_pk( table, description ) fail should have the proper diagnostics +ok 16 - col_is_pk( schema, table, column, description ) should pass +ok 17 - col_is_pk( schema, table, column, description ) should have the proper description +ok 18 - col_is_pk( schema, table, column, description ) should have the proper diagnostics +ok 19 - col_is_pk( table, column, description ) should pass +ok 20 - col_is_pk( table, column, description ) should have the proper description +ok 21 - col_is_pk( table, column, description ) should have the proper diagnostics +ok 22 - col_is_pk( table, column ) should pass +ok 23 - col_is_pk( table, column ) should have the proper description +ok 24 - col_is_pk( table, column ) should have the proper diagnostics +ok 25 - col_is_pk( schema, table, column, description ) fail should fail +ok 26 - col_is_pk( schema, table, column, description ) fail should have the proper description +ok 27 - col_is_pk( schema, table, column, description ) fail should have the proper diagnostics +ok 28 - col_is_pk( table, column, description ) fail should fail +ok 29 - col_is_pk( table, column, description ) fail should have the proper description +ok 30 - col_is_pk( table, column, description ) fail should have the proper diagnostics +ok 31 - col_is_pk( schema, table, column[], description ) should pass +ok 32 - col_is_pk( schema, table, column[], description ) should have the proper description +ok 33 - col_is_pk( schema, table, column[], description ) should have the proper diagnostics +ok 34 - col_is_pk( table, column[], description ) should pass +ok 35 - col_is_pk( table, column[], description ) should have the proper description +ok 36 - col_is_pk( table, column[], description ) should have the proper diagnostics +ok 37 - col_is_pk( table, column[] ) should pass +ok 38 - col_is_pk( table, column[] ) should have the proper description +ok 39 - col_is_pk( table, column[] ) should have the proper diagnostics diff --git a/expected/unique.out b/expected/unique.out index 273229c087a6..9c3dd3f86fac 100644 --- a/expected/unique.out +++ b/expected/unique.out @@ -1,28 +1,41 @@ \unset ECHO -1..26 -ok 1 - test has_unique( schema, table, description ) -ok 2 - has_unique( schema, table, description ) should work -ok 3 - test has_unique( table, description ) -ok 4 - has_unique( table, description ) should work -ok 5 - test has_unique( table ) -ok 6 - has_unique( table ) should work -ok 7 - test has_unique( schema, table, description ) fail -ok 8 - has_unique( schema, table, description ) should fail properly -ok 9 - test has_unique( table, description ) fail -ok 10 - has_unique( table, description ) should fail properly -ok 11 - test col_is_unique( schema, table, column, description ) -ok 12 - col_is_unique( schema, table, column, description ) should work -ok 13 - test col_is_unique( table, column, description ) -ok 14 - col_is_unique( table, column, description ) should work -ok 15 - test col_is_unique( table, column ) -ok 16 - col_is_unique( table, column ) should work -ok 17 - test col_is_unique( schema, table, column, description ) fail -ok 18 - col_is_unique( schema, table, column, description ) should fail properly -ok 19 - test col_is_unique( table, column, description ) fail -ok 20 - col_is_unique( table, column, description ) should fail properly -ok 21 - test col_is_unique( schema, table, column[], description ) -ok 22 - col_is_unique( schema, table, column[], description ) should work -ok 23 - test col_is_unique( table, column[], description ) -ok 24 - col_is_unique( table, column[], description ) should work -ok 25 - test col_is_unique( table, column[], description ) -ok 26 - col_is_unique( table, column[] ) should work +1..39 +ok 1 - has_unique( schema, table, description ) should pass +ok 2 - has_unique( schema, table, description ) should have the proper description +ok 3 - has_unique( schema, table, description ) should have the proper diagnostics +ok 4 - has_unique( table, description ) should pass +ok 5 - has_unique( table, description ) should have the proper description +ok 6 - has_unique( table, description ) should have the proper diagnostics +ok 7 - has_unique( table ) should pass +ok 8 - has_unique( table ) should have the proper description +ok 9 - has_unique( table ) should have the proper diagnostics +ok 10 - has_unique( schema, table, description ) fail should fail +ok 11 - has_unique( schema, table, description ) fail should have the proper description +ok 12 - has_unique( schema, table, description ) fail should have the proper diagnostics +ok 13 - has_unique( table, description ) fail should fail +ok 14 - has_unique( table, description ) fail should have the proper description +ok 15 - has_unique( table, description ) fail should have the proper diagnostics +ok 16 - col_is_unique( schema, table, column, description ) should pass +ok 17 - col_is_unique( schema, table, column, description ) should have the proper description +ok 18 - col_is_unique( schema, table, column, description ) should have the proper diagnostics +ok 19 - col_is_unique( table, column, description ) should pass +ok 20 - col_is_unique( table, column, description ) should have the proper description +ok 21 - col_is_unique( table, column, description ) should have the proper diagnostics +ok 22 - col_is_unique( table, column ) should pass +ok 23 - col_is_unique( table, column ) should have the proper description +ok 24 - col_is_unique( table, column ) should have the proper diagnostics +ok 25 - col_is_unique( schema, table, column, description ) fail should fail +ok 26 - col_is_unique( schema, table, column, description ) fail should have the proper description +ok 27 - col_is_unique( schema, table, column, description ) fail should have the proper diagnostics +ok 28 - col_is_unique( table, column, description ) fail should fail +ok 29 - col_is_unique( table, column, description ) fail should have the proper description +ok 30 - col_is_unique( table, column, description ) fail should have the proper diagnostics +ok 31 - col_is_unique( schema, table, column[], description ) should pass +ok 32 - col_is_unique( schema, table, column[], description ) should have the proper description +ok 33 - col_is_unique( schema, table, column[], description ) should have the proper diagnostics +ok 34 - col_is_unique( table, column[], description ) should pass +ok 35 - col_is_unique( table, column[], description ) should have the proper description +ok 36 - col_is_unique( table, column[], description ) should have the proper diagnostics +ok 37 - col_is_unique( table, column[] ) should pass +ok 38 - col_is_unique( table, column[] ) should have the proper description +ok 39 - col_is_unique( table, column[] ) should have the proper diagnostics diff --git a/sql/pktap.sql b/sql/pktap.sql index 82d565f6a75f..53001a701724 100644 --- a/sql/pktap.sql +++ b/sql/pktap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(26); +SELECT plan(39); -- This will be rolled back. :-) CREATE TABLE public.sometab( @@ -16,113 +16,122 @@ CREATE TABLE public.sometab( /****************************************************************************/ -- Test has_pk(). -\echo ok 1 - test has_pk( schema, table, description ) -SELECT is( +SELECT * FROM check_test( has_pk( 'public', 'sometab', 'public.sometab should have a pk' ), - 'ok 1 - public.sometab should have a pk', - 'has_pk( schema, table, description ) should work' + true, + 'has_pk( schema, table, description )', + 'public.sometab should have a pk', + '' ); -\echo ok 3 - test has_pk( table, description ) -SELECT is( +SELECT * FROM check_test( has_pk( 'sometab', 'sometab should have a pk' ), - 'ok 3 - sometab should have a pk', - 'has_pk( table, description ) should work' + true, + 'has_pk( table, description )', + 'sometab should have a pk', + '' ); -\echo ok 5 - test has_pk( table ) -SELECT is( +SELECT * FROM check_test( has_pk( 'sometab' ), - 'ok 5 - Table sometab should have a primary key', - 'has_pk( table ) should work' + true, + 'has_pk( table )', + 'Table sometab should have a primary key', + '' ); -\echo ok 7 - test has_pk( schema, table, description ) fail -SELECT is( +SELECT * FROM check_test( has_pk( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should have a pk' ), - 'not ok 7 - pg_catalog.pg_class should have a pk -# Failed test 7: "pg_catalog.pg_class should have a pk"', - 'has_pk( schema, table, description ) should fail properly' + false, + 'has_pk( schema, table, description ) fail', + 'pg_catalog.pg_class should have a pk', + '' ); -\echo ok 9 - test has_pk( table, description ) fail -SELECT is( +SELECT * FROM check_test( has_pk( 'pg_class', 'pg_class should have a pk' ), - 'not ok 9 - pg_class should have a pk -# Failed test 9: "pg_class should have a pk"', - 'has_pk( table, description ) should fail properly' + false, + 'has_pk( table, description ) fail', + 'pg_class should have a pk', + '' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 7, 9 ); /****************************************************************************/ -- Test col_is_pk(). -\echo ok 11 - test col_is_pk( schema, table, column, description ) -SELECT is( +SELECT * FROM check_test( col_is_pk( 'public', 'sometab', 'id', 'public.sometab.id should be a pk' ), - 'ok 11 - public.sometab.id should be a pk', - 'col_is_pk( schema, table, column, description ) should work' + true, + 'col_is_pk( schema, table, column, description )', + 'public.sometab.id should be a pk', + '' ); -\echo ok 13 - test col_is_pk( table, column, description ) -SELECT is( +SELECT * FROM check_test( col_is_pk( 'sometab', 'id', 'sometab.id should be a pk' ), - 'ok 13 - sometab.id should be a pk', - 'col_is_pk( table, column, description ) should work' + true, + 'col_is_pk( table, column, description )', + 'sometab.id should be a pk', + '' ); -\echo ok 15 - test col_is_pk( table, column ) -SELECT is( +SELECT * FROM check_test( col_is_pk( 'sometab', 'id' ), - 'ok 15 - Column sometab(id) should be a primary key', - 'col_is_pk( table, column ) should work' + true, + 'col_is_pk( table, column )', + 'Column sometab(id) should be a primary key', + '' ); -\echo ok 17 - test col_is_pk( schema, table, column, description ) fail -SELECT is( +SELECT * FROM check_test( col_is_pk( 'public', 'sometab', 'name', 'public.sometab.name should be a pk' ), - 'not ok 17 - public.sometab.name should be a pk -# Failed test 17: "public.sometab.name should be a pk" -# have: {id} -# want: {name}', - 'col_is_pk( schema, table, column, description ) should fail properly' + false, + 'col_is_pk( schema, table, column, description ) fail', + 'public.sometab.name should be a pk', + ' have: {id} + want: {name}' ); -\echo ok 19 - test col_is_pk( table, column, description ) fail -SELECT is( +SELECT * FROM check_test( col_is_pk( 'sometab', 'name', 'sometab.name should be a pk' ), - 'not ok 19 - sometab.name should be a pk -# Failed test 19: "sometab.name should be a pk" -# have: {id} -# want: {name}', - 'col_is_pk( table, column, description ) should fail properly' + false, + 'col_is_pk( table, column, description ) fail', + 'sometab.name should be a pk', + ' have: {id} + want: {name}' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 17, 19 ); /****************************************************************************/ -- Test col_is_pk() with an array of columns. -CREATE TABLE public.argh (id int not null, name text not null, primary key (id, name)); +CREATE TABLE public.argh ( + id INT NOT NULL, + name TEXT NOT NULL, + PRIMARY KEY (id, name) +); -\echo ok 21 - test col_is_pk( schema, table, column[], description ) -SELECT is( +SELECT * FROM check_test( col_is_pk( 'public', 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), - 'ok 21 - id + name should be a pk', - 'col_is_pk( schema, table, column[], description ) should work' + true, + 'col_is_pk( schema, table, column[], description )', + 'id + name should be a pk', + '' ); -\echo ok 23 - test col_is_pk( table, column[], description ) -SELECT is( +SELECT * FROM check_test( col_is_pk( 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), - 'ok 23 - id + name should be a pk', - 'col_is_pk( table, column[], description ) should work' + true, + 'col_is_pk( table, column[], description )', + 'id + name should be a pk', + '' ); -\echo ok 25 - test col_is_pk( table, column[], description ) -SELECT is( +SELECT * FROM check_test( col_is_pk( 'argh', ARRAY['id', 'name'] ), - 'ok 25 - Columns argh(id, name) should be a primary key', - 'col_is_pk( table, column[] ) should work' + true, + 'col_is_pk( table, column[] )', + 'Columns argh(id, name) should be a primary key', + '' ); /****************************************************************************/ diff --git a/sql/todotap.sql b/sql/todotap.sql index 67bd946ded36..4ccc950eac60 100644 --- a/sql/todotap.sql +++ b/sql/todotap.sql @@ -99,9 +99,9 @@ SELECT * FROM check_test( \echo ok 26 - todo fail \echo ok 27 - todo fail SELECT * FROM todo('just because', 2 ); --- We have to use textin(array_out()) to get around a missing cast to text in 8.0. -SELECT is( - textin(array_out(ARRAY( +-- We have to use ok() instead of is() to get around lack of cast to text in 8.0. +SELECT ok( + ARRAY( SELECT fail('This is a todo test 1') UNION SELECT todo::text FROM todo('inside') @@ -109,15 +109,15 @@ SELECT is( SELECT fail('This is a todo test 2') UNION SELECT fail('This is a todo test 3') - ))), - textin(array_out(ARRAY[ + ) + = ARRAY[ 'not ok 25 - This is a todo test 1 # TODO just because # Failed (TODO) test 25: "This is a todo test 1"', 'not ok 26 - This is a todo test 2 # TODO inside # Failed (TODO) test 26: "This is a todo test 2"', 'not ok 27 - This is a todo test 3 # TODO just because # Failed (TODO) test 27: "This is a todo test 3"' - ])), + ], 'Nested todos should work properly' ); @@ -129,9 +129,9 @@ UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 25, 26, 27 ); \echo ok 30 - todo fail \echo ok 31 - todo fail SELECT * FROM todo_start('some todos'); --- We have to use textin(array_out()) to get around a missing cast to text in 8.0. -SELECT is( - textin(array_out(ARRAY( +-- We have to use ok() instead of is() to get around lack of cast to text in 8.0. +SELECT ok( + ARRAY( SELECT fail('This is a todo test 1') AS stuff UNION SELECT in_todo()::text @@ -146,8 +146,8 @@ SELECT is( UNION SELECT in_todo()::text ORDER BY stuff - ))), - textin(array_out(ARRAY[ + ) + = ARRAY[ 'false', 'not ok 29 - This is a todo test 1 # TODO some todos # Failed (TODO) test 29: "This is a todo test 1"', @@ -156,7 +156,7 @@ SELECT is( 'not ok 31 - This is a todo test 3 # TODO some todos # Failed (TODO) test 31: "This is a todo test 3"', 'true' - ])), + ], 'todo_start() and todo_end() should work properly with in_todo()' ); diff --git a/sql/unique.sql b/sql/unique.sql index cb7f6815fac8..48a812a15abc 100644 --- a/sql/unique.sql +++ b/sql/unique.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(26); +SELECT plan(39); -- This will be rolled back. :-) CREATE TABLE public.sometab( @@ -16,113 +16,122 @@ CREATE TABLE public.sometab( /****************************************************************************/ -- Test has_unique(). -\echo ok 1 - test has_unique( schema, table, description ) -SELECT is( +SELECT * FROM check_test( has_unique( 'public', 'sometab', 'public.sometab should have a unique constraint' ), - 'ok 1 - public.sometab should have a unique constraint', - 'has_unique( schema, table, description ) should work' + true, + 'has_unique( schema, table, description )', + 'public.sometab should have a unique constraint', + '' ); -\echo ok 3 - test has_unique( table, description ) -SELECT is( +SELECT * FROM check_test( has_unique( 'sometab', 'sometab should have a unique constraint' ), - 'ok 3 - sometab should have a unique constraint', - 'has_unique( table, description ) should work' + true, + 'has_unique( table, description )', + 'sometab should have a unique constraint', + '' ); -\echo ok 5 - test has_unique( table ) -SELECT is( +SELECT * FROM check_test( has_unique( 'sometab' ), - 'ok 5 - Table sometab should have a unique constraint', - 'has_unique( table ) should work' + true, + 'has_unique( table )', + 'Table sometab should have a unique constraint', + '' ); -\echo ok 7 - test has_unique( schema, table, description ) fail -SELECT is( +SELECT * FROM check_test( has_unique( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should have a unique constraint' ), - 'not ok 7 - pg_catalog.pg_class should have a unique constraint -# Failed test 7: "pg_catalog.pg_class should have a unique constraint"', - 'has_unique( schema, table, description ) should fail properly' + false, + 'has_unique( schema, table, description ) fail', + 'pg_catalog.pg_class should have a unique constraint', + '' ); -\echo ok 9 - test has_unique( table, description ) fail -SELECT is( +SELECT * FROM check_test( has_unique( 'pg_class', 'pg_class should have a unique constraint' ), - 'not ok 9 - pg_class should have a unique constraint -# Failed test 9: "pg_class should have a unique constraint"', - 'has_unique( table, description ) should fail properly' + false, + 'has_unique( table, description ) fail', + 'pg_class should have a unique constraint', + '' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 7, 9 ); /****************************************************************************/ -- Test col_is_unique(). -\echo ok 11 - test col_is_unique( schema, table, column, description ) -SELECT is( +SELECT * FROM check_test( col_is_unique( 'public', 'sometab', 'name', 'public.sometab.name should be a pk' ), - 'ok 11 - public.sometab.name should be a pk', - 'col_is_unique( schema, table, column, description ) should work' + true, + 'col_is_unique( schema, table, column, description )', + 'public.sometab.name should be a pk', + '' ); -\echo ok 13 - test col_is_unique( table, column, description ) -SELECT is( +SELECT * FROM check_test( col_is_unique( 'sometab', 'name', 'sometab.name should be a pk' ), - 'ok 13 - sometab.name should be a pk', - 'col_is_unique( table, column, description ) should work' + true, + 'col_is_unique( table, column, description )', + 'sometab.name should be a pk', + '' ); -\echo ok 15 - test col_is_unique( table, column ) -SELECT is( +SELECT * FROM check_test( col_is_unique( 'sometab', 'name' ), - 'ok 15 - Column sometab(name) should have a unique constraint', - 'col_is_unique( table, column ) should work' + true, + 'col_is_unique( table, column )', + 'Column sometab(name) should have a unique constraint', + '' ); -\echo ok 17 - test col_is_unique( schema, table, column, description ) fail -SELECT is( +SELECT * FROM check_test( col_is_unique( 'public', 'sometab', 'id', 'public.sometab.id should be a pk' ), - 'not ok 17 - public.sometab.id should be a pk -# Failed test 17: "public.sometab.id should be a pk" -# have: {name} -# want: {id}', - 'col_is_unique( schema, table, column, description ) should fail properly' + false, + 'col_is_unique( schema, table, column, description ) fail', + 'public.sometab.id should be a pk', + ' have: {name} + want: {id}' ); -\echo ok 19 - test col_is_unique( table, column, description ) fail -SELECT is( +SELECT * FROM check_test( col_is_unique( 'sometab', 'id', 'sometab.id should be a pk' ), - 'not ok 19 - sometab.id should be a pk -# Failed test 19: "sometab.id should be a pk" -# have: {name} -# want: {id}', - 'col_is_unique( table, column, description ) should fail properly' + false, + 'col_is_unique( table, column, description ) fail', + 'sometab.id should be a pk', + ' have: {name} + want: {id}' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 17, 19 ); /****************************************************************************/ -- Test col_is_unique() with an array of columns. -CREATE TABLE public.argh (id int not null, name text not null, unique (id, name)); +CREATE TABLE public.argh ( + id INT NOT NULL, + name TEXT NOT NULL, + UNIQUE (id, name) +); -\echo ok 21 - test col_is_unique( schema, table, column[], description ) -SELECT is( +SELECT * FROM check_test( col_is_unique( 'public', 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), - 'ok 21 - id + name should be a pk', - 'col_is_unique( schema, table, column[], description ) should work' + true, + 'col_is_unique( schema, table, column[], description )', + 'id + name should be a pk', + '' ); -\echo ok 23 - test col_is_unique( table, column[], description ) -SELECT is( +SELECT * FROM check_test( col_is_unique( 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), - 'ok 23 - id + name should be a pk', - 'col_is_unique( table, column[], description ) should work' + true, + 'col_is_unique( table, column[], description )', + 'id + name should be a pk', + '' ); -\echo ok 25 - test col_is_unique( table, column[], description ) -SELECT is( +SELECT * FROM check_test( col_is_unique( 'argh', ARRAY['id', 'name'] ), - 'ok 25 - Columns argh(id, name) should have a unique constraint', - 'col_is_unique( table, column[] ) should work' + true, + 'col_is_unique( table, column[] )', + 'Columns argh(id, name) should have a unique constraint', + '' ); /****************************************************************************/ From e640bc2f271a54fda08e356e322306cb8121d156 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 24 Sep 2008 18:13:58 +0000 Subject: [PATCH 0172/1195] Updated 8.0 patch. --- compat/install-8.0.patch | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index 0c8b4dd8d84f..9bce79118d5e 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.saf 2008-09-21 15:38:31.000000000 -0700 -+++ pgtap.sql 2008-09-21 15:44:50.000000000 -0700 +--- pgtap.sql.saf 2008-09-24 11:22:15.000000000 -0700 ++++ pgtap.sql 2008-09-24 11:22:22.000000000 -0700 @@ -69,53 +69,63 @@ CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ @@ -117,7 +117,7 @@ output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE '\n' || diag( ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -923,15 +938,16 @@ +@@ -964,15 +979,16 @@ CREATE OR REPLACE FUNCTION _def_is( TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -138,7 +138,7 @@ END; $$ LANGUAGE plpgsql; -@@ -1520,6 +1536,7 @@ +@@ -1715,6 +1731,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -146,7 +146,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -1530,8 +1547,10 @@ +@@ -1725,8 +1742,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. From 7fc9b1ef7e4e2bd53b4b2870eb9e55609dd505c8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 24 Sep 2008 18:26:27 +0000 Subject: [PATCH 0173/1195] Make sure that the order of the functions we're checking is preserved. --- pgtap.sql.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pgtap.sql.in b/pgtap.sql.in index 3952fd05c860..07e78d8a7973 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1657,6 +1657,7 @@ BEGIN LEFT JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid AND n.nspname = 'pg_catalog' WHERE p.oid IS NULL + ORDER BY s.i ) INTO missing; IF missing[1] IS NULL THEN RETURN ok( true, $3 ); @@ -1687,6 +1688,7 @@ BEGIN 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 ); From d413e4c030026ea1c3b76c7ca0fefdd9e556ffb8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 24 Sep 2008 18:28:41 +0000 Subject: [PATCH 0174/1195] Updated patch offsets again. --- compat/install-8.0.patch | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index 9bce79118d5e..9de9e0eda9a1 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.saf 2008-09-24 11:22:15.000000000 -0700 -+++ pgtap.sql 2008-09-24 11:22:22.000000000 -0700 +--- pgtap.sql.saf 2008-09-24 11:37:05.000000000 -0700 ++++ pgtap.sql 2008-09-24 11:37:14.000000000 -0700 @@ -69,53 +69,63 @@ CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ @@ -138,7 +138,7 @@ END; $$ LANGUAGE plpgsql; -@@ -1715,6 +1731,7 @@ +@@ -1717,6 +1733,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -146,7 +146,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -1725,8 +1742,10 @@ +@@ -1727,8 +1744,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. From cec235c568b88f4b923ec84a7233a11070f7f693 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 24 Sep 2008 20:26:01 +0000 Subject: [PATCH 0175/1195] Restored 8.0 compatibility. * Fixed an error in the test count on 8.0 in `sql/todotap.sql`. * Changed the `Makefile` so that options to `pg_regress` are passed with an equals sign, as required by pg_regress 8.1 and earlier. * Added a cast from `oidvector` to `regtype[]` on 8.0. --- Makefile | 5 +++++ README.pgtap | 8 ++++++-- compat/install-8.0.patch | 31 +++++++++++++++++++++++-------- compat/uninstall-8.0.sql | 2 ++ sql/todotap.sql | 2 +- 5 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 compat/uninstall-8.0.sql diff --git a/Makefile b/Makefile index d0eb3df3ebc0..80f8ef44b6fa 100644 --- a/Makefile +++ b/Makefile @@ -77,6 +77,11 @@ ifneq ($(PGVER_MINOR), 3) cat compat/uninstall-8.2.sql uninstall_pgtap.tmp >> uninstall_pgtap.sql rm uninstall_pgtap.tmp endif +ifeq ($(PGVER_MINOR), 0) + mv uninstall_pgtap.sql uninstall_pgtap.tmp + cat compat/uninstall-8.0.sql uninstall_pgtap.tmp >> uninstall_pgtap.sql + rm uninstall_pgtap.tmp +endif endif # Make sure that we build the regression tests. diff --git a/README.pgtap b/README.pgtap index 7a7e186c4b14..a095b1ba3e36 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1391,8 +1391,12 @@ An `=` operator is also added that compares `name[]` values. 8.0 and Lower ------------- -A patch is applied that changes how some of the test functions are written; -otherwise, all is the same as for 8.2 Do note, however, that the `throws_ok()` +A patch is applied that changes how some of the test functions are written. +Also, a single cast is added for compatibility: + +* `oidvector` to `regtypep[]`. + +Otherwise, all is the same as for 8.2 Do note, however, that the `throws_ok()` and `lives_ok()` functions do not work under 8.0. Don't even use them there. To Do diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index 9de9e0eda9a1..bcbbc58ab50e 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,6 +1,21 @@ ---- pgtap.sql.saf 2008-09-24 11:37:05.000000000 -0700 -+++ pgtap.sql 2008-09-24 11:37:14.000000000 -0700 -@@ -69,53 +69,63 @@ +--- pgtap.sql.saf 2008-09-24 13:13:13.000000000 -0700 ++++ pgtap.sql 2008-09-24 13:13:38.000000000 -0700 +@@ -12,6 +12,14 @@ + -- ## CREATE SCHEMA TAPSCHEMA; + -- ## SET search_path TO TAPSCHEMA,public; + ++-- Cast oidvector to regtype[] like 8.1 does. ++CREATE OR REPLACE FUNCTION oidvregtype(oidvector) ++RETURNS regtype[] AS ++'SELECT COALESCE(string_to_array(textin(oidvectorout($1::oidvector)), '' '')::oid[]::regtype[], ''{}''::regtype[]);' ++LANGUAGE sql IMMUTABLE STRICT; ++ ++CREATE CAST (oidvector AS regtype[]) WITH FUNCTION oidvregtype(oidvector) AS ASSIGNMENT; ++ + CREATE OR REPLACE FUNCTION pg_typeof("any") + RETURNS regtype + AS '$libdir/pgtap' +@@ -69,53 +77,63 @@ CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ DECLARE @@ -81,7 +96,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -179,10 +189,12 @@ +@@ -179,10 +197,12 @@ CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ DECLARE @@ -97,7 +112,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -448,13 +460,16 @@ +@@ -448,13 +468,16 @@ want ALIAS FOR $3; descr ALIAS FOR $4; result BOOLEAN; @@ -117,7 +132,7 @@ output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE '\n' || diag( ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -964,15 +979,16 @@ +@@ -964,15 +987,16 @@ CREATE OR REPLACE FUNCTION _def_is( TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -138,7 +153,7 @@ END; $$ LANGUAGE plpgsql; -@@ -1717,6 +1733,7 @@ +@@ -1717,6 +1741,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -146,7 +161,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -1727,8 +1744,10 @@ +@@ -1727,8 +1752,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. diff --git a/compat/uninstall-8.0.sql b/compat/uninstall-8.0.sql new file mode 100644 index 000000000000..18590c5b800d --- /dev/null +++ b/compat/uninstall-8.0.sql @@ -0,0 +1,2 @@ +DROP CAST (oidvector AS regtype[]); +DROP FUNCTION oidvregtype(oidvector); diff --git a/sql/todotap.sql b/sql/todotap.sql index 4ccc950eac60..36df57734fb2 100644 --- a/sql/todotap.sql +++ b/sql/todotap.sql @@ -164,7 +164,7 @@ UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 29, 30, 31 ); -- Test the exception when throws_ok() is available. SELECT CASE WHEN substring(version() from '[[:digit:]]+[.][[:digit:]]')::numeric < 8.1 - then 'ok 33 - Should get an exception when todo_end() is called without todo_start()' + then pass('Should get an exception when todo_end() is called without todo_start()') ELSE throws_ok( 'SELECT todo_end()', 'P0001', From c263b8d2f0518477a6b8277a1a11ef334a27fe65 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 24 Sep 2008 20:28:29 +0000 Subject: [PATCH 0176/1195] Use an `=` for `pg_regress` options. I know I said I did this in the last commit, but I didn't. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 80f8ef44b6fa..ce2e60ff44b5 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ MODULES = pgtap DOCS = README.pgtap SCRIPTS = bin/pg_prove REGRESS = $(patsubst sql/%.sql,%,$(TESTS)) -REGRESS_OPTS = --load-language plpgsql +REGRESS_OPTS = --load-language=plpgsql ifdef USE_PGXS PG_CONFIG = pg_config From 70930a235ddc5ab2dbbc351a0f4a04795c7ddd59 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 24 Sep 2008 20:32:00 +0000 Subject: [PATCH 0177/1195] Timestamped for 0.11 release. --- Changes | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index c7d5e82c315f..73f610324ecf 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,6 @@ Revision history for pgTAP -0.11 +0.11 2008-09-24T20:41:42 - Simplified the tests so that they now load `test_setup.sql` instead of setting a bunch of stuff themselves. Now only `test_setup.sql` needs to be created from `test_setup.sql.in`, and the other `.sql` files @@ -24,6 +24,8 @@ Revision history for pgTAP - Added `can()` and `can_ok()`. - Fixed a bug in `check_test()` where the leading whitespace for diagnostic messages could be off by 1 or more characters. + - Fixed the `installcheck` target so that it properly installs PL/pgSQL + into the target database before the tests run. 0.10 2008-09-18T22:59:31 - Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the From 911255364749f865164bd64c2ac9572694fac3bd Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 24 Sep 2008 20:51:34 +0000 Subject: [PATCH 0178/1195] Incremented version number to 0.12. --- Changes | 2 ++ README.pgtap | 2 +- bin/pg_prove | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 73f610324ecf..bfab16bb4ad5 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Revision history for pgTAP +0.12 + 0.11 2008-09-24T20:41:42 - Simplified the tests so that they now load `test_setup.sql` instead of setting a bunch of stuff themselves. Now only `test_setup.sql` needs diff --git a/README.pgtap b/README.pgtap index a095b1ba3e36..e327618c7d82 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.11 +pgTAP 0.12 ========== pgTAP is a collection of TAP-emitting unit testing functions written in diff --git a/bin/pg_prove b/bin/pg_prove index cbf63b1a262c..eb2e6425fa66 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -6,7 +6,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.11'; +our $VERSION = '0.12; Getopt::Long::Configure (qw(bundling)); From ab443c2aed8f32ae3d1fa5d7db64d8710d6cb9cf Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 25 Sep 2008 16:38:50 +0000 Subject: [PATCH 0179/1195] Handle table creation noisiness ourselves. Spotted this hack in EpicTest. --- Changes | 4 ++++ README.pgtap | 7 ++----- pgtap.sql.in | 43 ++++++++++++++++++++++++------------------- sql/check.sql | 4 ++++ sql/coltap.sql | 2 ++ sql/fktap.sql | 2 ++ sql/hastap.sql | 2 ++ sql/pktap.sql | 4 ++++ sql/unique.sql | 4 ++++ 9 files changed, 48 insertions(+), 24 deletions(-) diff --git a/Changes b/Changes index bfab16bb4ad5..92362baf63f0 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,10 @@ Revision history for pgTAP 0.12 + - Updated `plan()` to disable warnings while it creates its tables. + This means that `plan()` no longer send NOTICE messages when they run, + although tests still might, depending on the setting of + `client_min_messages`. 0.11 2008-09-24T20:41:42 - Simplified the tests so that they now load `test_setup.sql` instead of diff --git a/README.pgtap b/README.pgtap index e327618c7d82..b28b96b9f410 100644 --- a/README.pgtap +++ b/README.pgtap @@ -128,8 +128,7 @@ Here's an example: -- Load the TAP functions. BEGIN; - SET client_min_messages = warning; - \i pgtap.sql + \i pgtap.sql -- Plan the tests. SELECT plan(1); @@ -162,10 +161,8 @@ using `pg_prove`. If you're not relying on `installcheck`, your test scripts can be a lot less verbose; you don't need to set all the extra variables, because `pg_prove` takes care of that for you: + -- Start transaction and plan the tests. BEGIN; - SET client_min_messages = warning; - - -- Plan the tests. SELECT plan(1); -- Run the tests. diff --git a/pgtap.sql.in b/pgtap.sql.in index 07e78d8a7973..6e2cd2ede3c0 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -23,27 +23,30 @@ DECLARE rcount INTEGER; BEGIN BEGIN - EXECUTE ' - CREATE TEMP TABLE __tcache__ ( - id SERIAL PRIMARY KEY, - label TEXT NOT NULL, - value INTEGER NOT NULL, - note TEXT NOT NULL DEFAULT '''' - ); - GRANT ALL ON TABLE __tcache__ TO PUBLIC; - - CREATE TEMP TABLE __tresults__ ( - numb SERIAL PRIMARY KEY, - ok BOOLEAN NOT NULL DEFAULT TRUE, - aok BOOLEAN NOT NULL DEFAULT TRUE, - descr TEXT NOT NULL DEFAULT '''', - type TEXT NOT NULL DEFAULT '''', - reason TEXT NOT NULL DEFAULT '''' - ); - GRANT ALL ON TABLE __tresults__ TO PUBLIC; - GRANT ALL ON TABLE __tresults___numb_seq TO PUBLIC;'; + SET LOCAL client_min_messages = warning; + EXECUTE ' + CREATE TEMP TABLE __tcache__ ( + id SERIAL PRIMARY KEY, + label TEXT NOT NULL, + value INTEGER NOT NULL, + note TEXT NOT NULL DEFAULT '''' + ); + GRANT ALL ON TABLE __tcache__ TO PUBLIC; + + CREATE TEMP TABLE __tresults__ ( + numb SERIAL PRIMARY KEY, + ok BOOLEAN NOT NULL DEFAULT TRUE, + aok BOOLEAN NOT NULL DEFAULT TRUE, + descr TEXT NOT NULL DEFAULT '''', + type TEXT NOT NULL DEFAULT '''', + reason TEXT NOT NULL DEFAULT '''' + ); + GRANT ALL ON TABLE __tresults__ TO PUBLIC; + GRANT ALL ON TABLE __tresults___numb_seq TO PUBLIC; + '; EXCEPTION WHEN duplicate_table THEN + RESET client_min_messages; -- Raise an exception if there's already a plan. EXECUTE 'SELECT TRUE FROM __tcache__ WHERE label = ''plan'''; GET DIAGNOSTICS rcount = ROW_COUNT; @@ -52,6 +55,8 @@ BEGIN END IF; END; + RESET client_min_messages; + -- Save the plan and return. PERFORM _set('plan', $1 ); RETURN '1..' || $1; diff --git a/sql/check.sql b/sql/check.sql index 8e7627fb1163..a815bc2c2c36 100644 --- a/sql/check.sql +++ b/sql/check.sql @@ -6,12 +6,14 @@ SELECT plan(39); -- This will be rolled back. :-) +SET LOCAL client_min_messages = warning; CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '' CHECK ( name IN ('foo', 'bar', 'baz') ), numb NUMERIC(10, 2), myint NUMERIC(8) ); +RESET client_min_messages; /****************************************************************************/ -- Test has_check(). @@ -104,11 +106,13 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test col_has_check() with an array of columns. +SET LOCAL client_min_messages = warning; CREATE TABLE public.argh ( id INT NOT NULL, name TEXT NOT NULL, CHECK ( id IN (1, 2) AND name IN ('foo', 'bar')) ); +RESET client_min_messages; SELECT * FROM check_test( col_has_check( 'public', 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), diff --git a/sql/coltap.sql b/sql/coltap.sql index 7225047d041b..0c3fed805f0f 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -6,12 +6,14 @@ SELECT plan(57); -- This will be rolled back. :-) +SET LOCAL client_min_messages = warning; CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '', numb NUMERIC(10, 2), myint NUMERIC(8) ); +RESET client_min_messages; /****************************************************************************/ -- Test col_not_null(). diff --git a/sql/fktap.sql b/sql/fktap.sql index cef012c3fc3c..d6433a16491d 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -7,6 +7,7 @@ SELECT plan(88); --SELECT * from no_plan(); -- These will be rolled back. :-) +SET LOCAL client_min_messages = warning; CREATE TABLE public.pk ( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '' @@ -37,6 +38,7 @@ CREATE TABLE public.fk3( foo_id INT NOT NULL, FOREIGN KEY(pk2_num, pk2_dot) REFERENCES pk2( num, dot) ); +RESET client_min_messages; /****************************************************************************/ -- Test has_fk(). diff --git a/sql/hastap.sql b/sql/hastap.sql index 611027e8e88a..58bf8a78d7d0 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -6,12 +6,14 @@ SELECT plan(45); -- This will be rolled back. :-) +SET LOCAL client_min_messages = warning; CREATE TABLE sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '', numb NUMERIC(10, 2), myint NUMERIC(8) ); +RESET client_min_messages; /****************************************************************************/ -- Test has_table(). diff --git a/sql/pktap.sql b/sql/pktap.sql index 53001a701724..3b26b265fbcc 100644 --- a/sql/pktap.sql +++ b/sql/pktap.sql @@ -6,12 +6,14 @@ SELECT plan(39); -- This will be rolled back. :-) +SET LOCAL client_min_messages = warning; CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '', numb NUMERIC(10, 2), myint NUMERIC(8) ); +RESET client_min_messages; /****************************************************************************/ -- Test has_pk(). @@ -104,11 +106,13 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test col_is_pk() with an array of columns. +SET LOCAL client_min_messages = warning; CREATE TABLE public.argh ( id INT NOT NULL, name TEXT NOT NULL, PRIMARY KEY (id, name) ); +RESET client_min_messages; SELECT * FROM check_test( col_is_pk( 'public', 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), diff --git a/sql/unique.sql b/sql/unique.sql index 48a812a15abc..3b4400f48810 100644 --- a/sql/unique.sql +++ b/sql/unique.sql @@ -6,12 +6,14 @@ SELECT plan(39); -- This will be rolled back. :-) +SET LOCAL client_min_messages = warning; CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '' UNIQUE, numb NUMERIC(10, 2), myint NUMERIC(8) ); +RESET client_min_messages; /****************************************************************************/ -- Test has_unique(). @@ -104,11 +106,13 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test col_is_unique() with an array of columns. +SET LOCAL client_min_messages = warning; CREATE TABLE public.argh ( id INT NOT NULL, name TEXT NOT NULL, UNIQUE (id, name) ); +RESET client_min_messages; SELECT * FROM check_test( col_is_unique( 'public', 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), From 2d45d79fa7b83db5423bc5110d58f42a5dcdc683 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 25 Sep 2008 23:37:18 +0000 Subject: [PATCH 0180/1195] Forgot to delete that. --- test_setup.sql.in | 3 --- 1 file changed, 3 deletions(-) diff --git a/test_setup.sql.in b/test_setup.sql.in index c5fdbaf1bbd2..1aeb5b3068bd 100644 --- a/test_setup.sql.in +++ b/test_setup.sql.in @@ -11,9 +11,6 @@ \pset tuples_only true \pset pager --- Keep things quiet. -SET client_min_messages = warning; - -- Revert all changes on failure. \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true From 88e0c599362ec3ea03a88aedff5797bec2a36183 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 10 Oct 2008 21:43:17 +0000 Subject: [PATCH 0181/1195] * Added `hasnt_table()`, `hasnt_view()`, and `hasnt_column()`. * Added `hasnt_pk()` and `hasnt_fk()`. * Added missing `DROP` statements to `uninstall_pgtap.sql.in`. * Refactored the code for checking for the presence or absence of tables and columns so that it's much simpler. --- Changes | 3 + README.pgtap | 66 ++++++++++++ bin/pg_prove | 2 +- expected/fktap.out | 168 +++++++++++++++-------------- expected/hastap.out | 107 +++++++++++++------ expected/pktap.out | 65 +++++++----- pgtap.sql.in | 236 ++++++++++++++++++++++++++++------------- sql/fktap.sql | 51 +++++++-- sql/hastap.sql | 131 ++++++++++++++++++++++- sql/pktap.sql | 45 +++++++- uninstall_pgtap.sql.in | 44 +++++++- 11 files changed, 692 insertions(+), 226 deletions(-) diff --git a/Changes b/Changes index 92362baf63f0..cebf455e7655 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,9 @@ Revision history for pgTAP This means that `plan()` no longer send NOTICE messages when they run, although tests still might, depending on the setting of `client_min_messages`. + - Added `hasnt_table()`, `hasnt_view()`, and `hasnt_column()`. + - Added `hasnt_pk()` and `hasnt_fk()`. + - Added missing `DROP` statements to `uninstall_pgtap.sql.in`. 0.11 2008-09-24T20:41:42 - Simplified the tests so that they now load `test_setup.sql` instead of diff --git a/README.pgtap b/README.pgtap index b28b96b9f410..a5dad03aae84 100644 --- a/README.pgtap +++ b/README.pgtap @@ -562,6 +562,19 @@ test description. If you omit the schema, the table must be visible in the search path. If you omit the test description, it will be set to "Table `:table` should exist". +### `hasnt_table( schema, table, description )` ### +### `hasnt_table( table, description )` ### +### `hasnt_table( table )` ### + + SELECT hasnt_table( + 'myschema', + 'sometable', + 'There should be no table myschema.sometable' + ); + +This function is the inversion of `has_table()`. The test passes if the +specified table does *not* exist. + ### `has_view( schema, view, description )` ### ### `has_view( view, description )` ### ### `has_view( view )` ### @@ -574,6 +587,19 @@ search path. If you omit the test description, it will be set to "Table Just like `has_table()`, only it tests for the existence of a view. +### `hasnt_view( schema, view, description )` ### +### `hasnt_view( view, description )` ### +### `hasnt_view( view )` ### + + SELECT hasnt_view( + 'myschema', + 'someview', + 'There should be no myschema.someview' + ); + +This function is the inversion of `has_view()`. The test passes if the +specified view does *not* exist. + ### `has_column( schema, table, column, description )` ### ### `has_column( table, column, description )` ### ### `has_column( table, column )` ### @@ -591,6 +617,20 @@ name, and the fourth is the test description. If the schema is omitted, the table must be visible in the search path. If the test description is omitted, it will be set to "Column `:table`.`:column` should exist". +### `hasnt_column( schema, table, column, description )` ### +### `hasnt_column( table, column, description )` ### +### `hasnt_column( table, column )` ### + + SELECT hasnt_column( + 'myschema', + 'sometable', + 'somecolumn', + 'There should be no myschema.sometable.somecolumn column' + ); + +This function is the inversion of `has_column()`. The test passes if the +specified column does *not* exist. + ### `col_not_null( schema, table, column, description )` ### ### `col_not_null( table, column, description )` ### ### `col_not_null( table, column )` ### @@ -735,6 +775,19 @@ test description is omitted, it will be set to "Table `:table` should have a primary key". Note that this test will fail if the table in question does not exist. +### `hasnt_pk( schema, table, description )` ### +### `hasnt_pk( table, description )` ### +### `hasnt_pk( table )` ### + + SELECT hasnt_pk( + 'myschema', + 'sometable', + 'Table myschema.sometable should not have a primary key' + ); + +This function is the inversion of `has_pk()`. The test passes if the specified +primary key does *not* exist. + ### `has_fk( schema, table, description )` ### ### `has_fk( table, description )` ### ### `has_fk( table )` ### @@ -752,6 +805,19 @@ path. If the test description is omitted, it will be set to "Table `:table` should have a foreign key constraint". Note that this test will fail if the table in question does not exist. +### `hasnt_fk( schema, table, description )` ### +### `hasnt_fk( table, description )` ### +### `hasnt_fk( table )` ### + + SELECT hasnt_fk( + 'myschema', + 'sometable', + 'Table myschema.sometable should not have a foreign key constraint' + ); + +This function is the inversion of `has_fk()`. The test passes if the specified +foreign key does *not* exist. + ### `col_is_pk( schema, table, column, description )` ### ### `col_is_pk( schema, table, column[], description )` ### ### `col_is_pk( table, column, description )` ### diff --git a/bin/pg_prove b/bin/pg_prove index eb2e6425fa66..d6693de08792 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -6,7 +6,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.12; +our $VERSION = '0.12'; Getopt::Long::Configure (qw(bundling)); diff --git a/expected/fktap.out b/expected/fktap.out index 3159f05fbcac..daea09a69e64 100644 --- a/expected/fktap.out +++ b/expected/fktap.out @@ -1,5 +1,5 @@ \unset ECHO -1..88 +1..98 ok 1 - has_fk( schema, table, description ) should pass ok 2 - has_fk( schema, table, description ) should have the proper description ok 3 - has_fk( table, description ) should pass @@ -10,81 +10,91 @@ ok 7 - has_fk( schema, table, description ) fail should fail ok 8 - has_fk( schema, table, description ) fail should have the proper description ok 9 - has_fk( table, description ) fail should fail ok 10 - has_fk( table, description ) fail should have the proper description -ok 11 - col_is_fk( schema, table, column, description ) should pass -ok 12 - col_is_fk( schema, table, column, description ) should have the proper description -ok 13 - col_is_fk( table, column, description ) should pass -ok 14 - col_is_fk( table, column, description ) should have the proper description -ok 15 - col_is_fk( table, column ) should pass -ok 16 - col_is_fk( table, column ) should have the proper description -ok 17 - col_is_fk( schema, table, column, description ) should fail -ok 18 - col_is_fk( schema, table, column, description ) should have the proper description -ok 19 - col_is_fk( schema, table, column, description ) should have the proper diagnostics -ok 20 - col_is_fk( table, column, description ) should fail -ok 21 - col_is_fk( table, column, description ) should have the proper description -ok 22 - col_is_fk( table, column, description ) should have the proper diagnostics -ok 23 - multi-fk col_is_fk test should pass -ok 24 - multi-fk col_is_fk test should have the proper description -ok 25 - col_is_fk with no FKs should fail -ok 26 - col_is_fk with no FKs should have the proper description -ok 27 - col_is_fk with no FKs should have the proper diagnostics -ok 28 - col_is_fk with no FKs should fail -ok 29 - col_is_fk with no FKs should have the proper description -ok 30 - col_is_fk with no FKs should have the proper diagnostics -ok 31 - col_is_fk( schema, table, column[], description ) should pass -ok 32 - col_is_fk( schema, table, column[], description ) should have the proper description -ok 33 - col_is_fk( table, column[], description ) should pass -ok 34 - col_is_fk( table, column[], description ) should have the proper description -ok 35 - col_is_fk( table, column[] ) should pass -ok 36 - col_is_fk( table, column[] ) should have the proper description -ok 37 - full fk_ok array should pass -ok 38 - full fk_ok array should have the proper description -ok 39 - multiple fk fk_ok desc should pass -ok 40 - multiple fk fk_ok desc should have the proper description -ok 41 - fk_ok array desc should pass -ok 42 - fk_ok array desc should have the proper description -ok 43 - fk_ok array noschema desc should pass -ok 44 - fk_ok array noschema desc should have the proper description -ok 45 - multiple fk fk_ok noschema desc should pass -ok 46 - multiple fk fk_ok noschema desc should have the proper description -ok 47 - fk_ok array noschema should pass -ok 48 - fk_ok array noschema should have the proper description -ok 49 - basic fk_ok should pass -ok 50 - basic fk_ok should have the proper description -ok 51 - basic fk_ok desc should pass -ok 52 - basic fk_ok desc should have the proper description -ok 53 - basic fk_ok noschema should pass -ok 54 - basic fk_ok noschema should have the proper description -ok 55 - basic fk_ok noschema desc should pass -ok 56 - basic fk_ok noschema desc should have the proper description -ok 57 - basic fk_ok noschema desc should have the proper diagnostics -ok 58 - Test should pass -ok 59 - fk_ok fail should fail -ok 60 - fk_ok fail should have the proper description -ok 61 - fk_ok fail should have the proper diagnostics -ok 62 - fk_ok fail desc should fail -ok 63 - fk_ok fail desc should have the proper description -ok 64 - fk_ok fail desc should have the proper diagnostics -ok 65 - fk_ok fail no schema should fail -ok 66 - fk_ok fail no schema should have the proper description -ok 67 - fk_ok fail no schema should have the proper diagnostics -ok 68 - fk_ok fail no schema desc should fail -ok 69 - fk_ok fail no schema desc should have the proper description -ok 70 - fk_ok fail no schema desc should have the proper diagnostics -ok 71 - fk_ok bad PK test should fail -ok 72 - fk_ok bad PK test should have the proper description -ok 73 - fk_ok bad PK test should have the proper diagnostics -ok 74 - double fk schema test should pass -ok 75 - double fk schema test should have the proper description -ok 76 - double fk schema test should have the proper diagnostics -ok 77 - double fk test should pass -ok 78 - double fk test should have the proper description -ok 79 - double fk test should have the proper diagnostics -ok 80 - double fk and col schema test should pass -ok 81 - double fk and col schema test should have the proper description -ok 82 - double fk and col schema test should have the proper diagnostics -ok 83 - missing fk test should fail -ok 84 - missing fk test should have the proper description -ok 85 - missing fk test should have the proper diagnostics -ok 86 - bad FK column test should fail -ok 87 - bad FK column test should have the proper description -ok 88 - bad FK column test should have the proper diagnostics +ok 11 - hasnt_fk( schema, table, description ) should fail +ok 12 - hasnt_fk( schema, table, description ) should have the proper description +ok 13 - hasnt_fk( table, description ) should fail +ok 14 - hasnt_fk( table, description ) should have the proper description +ok 15 - hasnt_fk( table ) should fail +ok 16 - hasnt_fk( table ) should have the proper description +ok 17 - hasnt_fk( schema, table, description ) pass should pass +ok 18 - hasnt_fk( schema, table, description ) pass should have the proper description +ok 19 - hasnt_fk( table, description ) pass should pass +ok 20 - hasnt_fk( table, description ) pass should have the proper description +ok 21 - col_is_fk( schema, table, column, description ) should pass +ok 22 - col_is_fk( schema, table, column, description ) should have the proper description +ok 23 - col_is_fk( table, column, description ) should pass +ok 24 - col_is_fk( table, column, description ) should have the proper description +ok 25 - col_is_fk( table, column ) should pass +ok 26 - col_is_fk( table, column ) should have the proper description +ok 27 - col_is_fk( schema, table, column, description ) should fail +ok 28 - col_is_fk( schema, table, column, description ) should have the proper description +ok 29 - col_is_fk( schema, table, column, description ) should have the proper diagnostics +ok 30 - col_is_fk( table, column, description ) should fail +ok 31 - col_is_fk( table, column, description ) should have the proper description +ok 32 - col_is_fk( table, column, description ) should have the proper diagnostics +ok 33 - multi-fk col_is_fk test should pass +ok 34 - multi-fk col_is_fk test should have the proper description +ok 35 - col_is_fk with no FKs should fail +ok 36 - col_is_fk with no FKs should have the proper description +ok 37 - col_is_fk with no FKs should have the proper diagnostics +ok 38 - col_is_fk with no FKs should fail +ok 39 - col_is_fk with no FKs should have the proper description +ok 40 - col_is_fk with no FKs should have the proper diagnostics +ok 41 - col_is_fk( schema, table, column[], description ) should pass +ok 42 - col_is_fk( schema, table, column[], description ) should have the proper description +ok 43 - col_is_fk( table, column[], description ) should pass +ok 44 - col_is_fk( table, column[], description ) should have the proper description +ok 45 - col_is_fk( table, column[] ) should pass +ok 46 - col_is_fk( table, column[] ) should have the proper description +ok 47 - full fk_ok array should pass +ok 48 - full fk_ok array should have the proper description +ok 49 - multiple fk fk_ok desc should pass +ok 50 - multiple fk fk_ok desc should have the proper description +ok 51 - fk_ok array desc should pass +ok 52 - fk_ok array desc should have the proper description +ok 53 - fk_ok array noschema desc should pass +ok 54 - fk_ok array noschema desc should have the proper description +ok 55 - multiple fk fk_ok noschema desc should pass +ok 56 - multiple fk fk_ok noschema desc should have the proper description +ok 57 - fk_ok array noschema should pass +ok 58 - fk_ok array noschema should have the proper description +ok 59 - basic fk_ok should pass +ok 60 - basic fk_ok should have the proper description +ok 61 - basic fk_ok desc should pass +ok 62 - basic fk_ok desc should have the proper description +ok 63 - basic fk_ok noschema should pass +ok 64 - basic fk_ok noschema should have the proper description +ok 65 - basic fk_ok noschema desc should pass +ok 66 - basic fk_ok noschema desc should have the proper description +ok 67 - basic fk_ok noschema desc should have the proper diagnostics +ok 68 - Test should pass +ok 69 - fk_ok fail should fail +ok 70 - fk_ok fail should have the proper description +ok 71 - fk_ok fail should have the proper diagnostics +ok 72 - fk_ok fail desc should fail +ok 73 - fk_ok fail desc should have the proper description +ok 74 - fk_ok fail desc should have the proper diagnostics +ok 75 - fk_ok fail no schema should fail +ok 76 - fk_ok fail no schema should have the proper description +ok 77 - fk_ok fail no schema should have the proper diagnostics +ok 78 - fk_ok fail no schema desc should fail +ok 79 - fk_ok fail no schema desc should have the proper description +ok 80 - fk_ok fail no schema desc should have the proper diagnostics +ok 81 - fk_ok bad PK test should fail +ok 82 - fk_ok bad PK test should have the proper description +ok 83 - fk_ok bad PK test should have the proper diagnostics +ok 84 - double fk schema test should pass +ok 85 - double fk schema test should have the proper description +ok 86 - double fk schema test should have the proper diagnostics +ok 87 - double fk test should pass +ok 88 - double fk test should have the proper description +ok 89 - double fk test should have the proper diagnostics +ok 90 - double fk and col schema test should pass +ok 91 - double fk and col schema test should have the proper description +ok 92 - double fk and col schema test should have the proper diagnostics +ok 93 - missing fk test should fail +ok 94 - missing fk test should have the proper description +ok 95 - missing fk test should have the proper diagnostics +ok 96 - bad FK column test should fail +ok 97 - bad FK column test should have the proper description +ok 98 - bad FK column test should have the proper diagnostics diff --git a/expected/hastap.out b/expected/hastap.out index 14b70150fd33..e211dcaaa94f 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..45 +1..90 ok 1 - has_table(non-existent table) should fail ok 2 - has_table(non-existent table) should have the proper description ok 3 - has_table(non-existent table) should have the proper diagnostics @@ -15,33 +15,78 @@ ok 12 - has_table(tab, desc) should have the proper diagnostics ok 13 - has_table(sch, tab, desc) should pass ok 14 - has_table(sch, tab, desc) should have the proper description ok 15 - has_table(sch, tab, desc) should have the proper diagnostics -ok 16 - has_view(non-existent view) should fail -ok 17 - has_view(non-existent view) should have the proper description -ok 18 - has_view(non-existent view) should have the proper diagnostics -ok 19 - has_view(non-existent view, desc) should fail -ok 20 - has_view(non-existent view, desc) should have the proper description -ok 21 - has_view(non-existent view, desc) should have the proper diagnostics -ok 22 - has_view(sch, non-existtent view, desc) should fail -ok 23 - has_view(sch, non-existtent view, desc) should have the proper description -ok 24 - has_view(sch, non-existtent view, desc) should have the proper diagnostics -ok 25 - has_view(view, desc) should pass -ok 26 - has_view(view, desc) should have the proper description -ok 27 - has_view(view, desc) should have the proper diagnostics -ok 28 - has_view(sch, view, desc) should pass -ok 29 - has_view(sch, view, desc) should have the proper description -ok 30 - has_view(sch, view, desc) should have the proper diagnostics -ok 31 - has_column(non-existent tab, col) should fail -ok 32 - has_column(non-existent tab, col) should have the proper description -ok 33 - has_column(non-existent tab, col) should have the proper diagnostics -ok 34 - has_column(non-existent tab, col, desc) should fail -ok 35 - has_column(non-existent tab, col, desc) should have the proper description -ok 36 - has_column(non-existent tab, col, desc) should have the proper diagnostics -ok 37 - has_column(non-existent sch, tab, col, desc) should fail -ok 38 - has_column(non-existent sch, tab, col, desc) should have the proper description -ok 39 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 40 - has_column(table, column) should pass -ok 41 - has_column(table, column) should have the proper description -ok 42 - has_column(table, column) should have the proper diagnostics -ok 43 - has_column(sch, tab, col, desc) should pass -ok 44 - has_column(sch, tab, col, desc) should have the proper description -ok 45 - has_column(sch, tab, col, desc) should have the proper diagnostics +ok 16 - hasnt_table(non-existent table) should pass +ok 17 - hasnt_table(non-existent table) should have the proper description +ok 18 - hasnt_table(non-existent table) should have the proper diagnostics +ok 19 - hasnt_table(non-existent schema, tab) should pass +ok 20 - hasnt_table(non-existent schema, tab) should have the proper description +ok 21 - hasnt_table(non-existent schema, tab) should have the proper diagnostics +ok 22 - hasnt_table(sch, non-existent tab, desc) should pass +ok 23 - hasnt_table(sch, non-existent tab, desc) should have the proper description +ok 24 - hasnt_table(sch, non-existent tab, desc) should have the proper diagnostics +ok 25 - hasnt_table(tab, desc) should fail +ok 26 - hasnt_table(tab, desc) should have the proper description +ok 27 - hasnt_table(tab, desc) should have the proper diagnostics +ok 28 - hasnt_table(sch, tab, desc) should fail +ok 29 - hasnt_table(sch, tab, desc) should have the proper description +ok 30 - hasnt_table(sch, tab, desc) should have the proper diagnostics +ok 31 - has_view(non-existent view) should fail +ok 32 - has_view(non-existent view) should have the proper description +ok 33 - has_view(non-existent view) should have the proper diagnostics +ok 34 - has_view(non-existent view, desc) should fail +ok 35 - has_view(non-existent view, desc) should have the proper description +ok 36 - has_view(non-existent view, desc) should have the proper diagnostics +ok 37 - has_view(sch, non-existtent view, desc) should fail +ok 38 - has_view(sch, non-existtent view, desc) should have the proper description +ok 39 - has_view(sch, non-existtent view, desc) should have the proper diagnostics +ok 40 - has_view(view, desc) should pass +ok 41 - has_view(view, desc) should have the proper description +ok 42 - has_view(view, desc) should have the proper diagnostics +ok 43 - has_view(sch, view, desc) should pass +ok 44 - has_view(sch, view, desc) should have the proper description +ok 45 - has_view(sch, view, desc) should have the proper diagnostics +ok 46 - hasnt_view(non-existent view) should pass +ok 47 - hasnt_view(non-existent view) should have the proper description +ok 48 - hasnt_view(non-existent view) should have the proper diagnostics +ok 49 - hasnt_view(non-existent view, desc) should pass +ok 50 - hasnt_view(non-existent view, desc) should have the proper description +ok 51 - hasnt_view(non-existent view, desc) should have the proper diagnostics +ok 52 - hasnt_view(sch, non-existtent view, desc) should pass +ok 53 - hasnt_view(sch, non-existtent view, desc) should have the proper description +ok 54 - hasnt_view(sch, non-existtent view, desc) should have the proper diagnostics +ok 55 - hasnt_view(view, desc) should fail +ok 56 - hasnt_view(view, desc) should have the proper description +ok 57 - hasnt_view(view, desc) should have the proper diagnostics +ok 58 - hasnt_view(sch, view, desc) should fail +ok 59 - hasnt_view(sch, view, desc) should have the proper description +ok 60 - hasnt_view(sch, view, desc) should have the proper diagnostics +ok 61 - has_column(non-existent tab, col) should fail +ok 62 - has_column(non-existent tab, col) should have the proper description +ok 63 - has_column(non-existent tab, col) should have the proper diagnostics +ok 64 - has_column(non-existent tab, col, desc) should fail +ok 65 - has_column(non-existent tab, col, desc) should have the proper description +ok 66 - has_column(non-existent tab, col, desc) should have the proper diagnostics +ok 67 - has_column(non-existent sch, tab, col, desc) should fail +ok 68 - has_column(non-existent sch, tab, col, desc) should have the proper description +ok 69 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 70 - has_column(table, column) should pass +ok 71 - has_column(table, column) should have the proper description +ok 72 - has_column(table, column) should have the proper diagnostics +ok 73 - has_column(sch, tab, col, desc) should pass +ok 74 - has_column(sch, tab, col, desc) should have the proper description +ok 75 - has_column(sch, tab, col, desc) should have the proper diagnostics +ok 76 - hasnt_column(non-existent tab, col) should pass +ok 77 - hasnt_column(non-existent tab, col) should have the proper description +ok 78 - hasnt_column(non-existent tab, col) should have the proper diagnostics +ok 79 - hasnt_column(non-existent tab, col, desc) should pass +ok 80 - hasnt_column(non-existent tab, col, desc) should have the proper description +ok 81 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics +ok 82 - hasnt_column(non-existent sch, tab, col, desc) should pass +ok 83 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description +ok 84 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 85 - hasnt_column(table, column) should fail +ok 86 - hasnt_column(table, column) should have the proper description +ok 87 - hasnt_column(table, column) should have the proper diagnostics +ok 88 - hasnt_column(sch, tab, col, desc) should fail +ok 89 - hasnt_column(sch, tab, col, desc) should have the proper description +ok 90 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics diff --git a/expected/pktap.out b/expected/pktap.out index 5e5886199eb7..ce04136b2ed2 100644 --- a/expected/pktap.out +++ b/expected/pktap.out @@ -1,5 +1,5 @@ \unset ECHO -1..39 +1..54 ok 1 - has_pk( schema, table, description ) should pass ok 2 - has_pk( schema, table, description ) should have the proper description ok 3 - has_pk( schema, table, description ) should have the proper diagnostics @@ -15,27 +15,42 @@ ok 12 - has_pk( schema, table, description ) fail should have the proper diagnos ok 13 - has_pk( table, description ) fail should fail ok 14 - has_pk( table, description ) fail should have the proper description ok 15 - has_pk( table, description ) fail should have the proper diagnostics -ok 16 - col_is_pk( schema, table, column, description ) should pass -ok 17 - col_is_pk( schema, table, column, description ) should have the proper description -ok 18 - col_is_pk( schema, table, column, description ) should have the proper diagnostics -ok 19 - col_is_pk( table, column, description ) should pass -ok 20 - col_is_pk( table, column, description ) should have the proper description -ok 21 - col_is_pk( table, column, description ) should have the proper diagnostics -ok 22 - col_is_pk( table, column ) should pass -ok 23 - col_is_pk( table, column ) should have the proper description -ok 24 - col_is_pk( table, column ) should have the proper diagnostics -ok 25 - col_is_pk( schema, table, column, description ) fail should fail -ok 26 - col_is_pk( schema, table, column, description ) fail should have the proper description -ok 27 - col_is_pk( schema, table, column, description ) fail should have the proper diagnostics -ok 28 - col_is_pk( table, column, description ) fail should fail -ok 29 - col_is_pk( table, column, description ) fail should have the proper description -ok 30 - col_is_pk( table, column, description ) fail should have the proper diagnostics -ok 31 - col_is_pk( schema, table, column[], description ) should pass -ok 32 - col_is_pk( schema, table, column[], description ) should have the proper description -ok 33 - col_is_pk( schema, table, column[], description ) should have the proper diagnostics -ok 34 - col_is_pk( table, column[], description ) should pass -ok 35 - col_is_pk( table, column[], description ) should have the proper description -ok 36 - col_is_pk( table, column[], description ) should have the proper diagnostics -ok 37 - col_is_pk( table, column[] ) should pass -ok 38 - col_is_pk( table, column[] ) should have the proper description -ok 39 - col_is_pk( table, column[] ) should have the proper diagnostics +ok 16 - hasnt_pk( schema, table, description ) should fail +ok 17 - hasnt_pk( schema, table, description ) should have the proper description +ok 18 - hasnt_pk( schema, table, description ) should have the proper diagnostics +ok 19 - hasnt_pk( table, description ) should fail +ok 20 - hasnt_pk( table, description ) should have the proper description +ok 21 - hasnt_pk( table, description ) should have the proper diagnostics +ok 22 - hasnt_pk( table ) should fail +ok 23 - hasnt_pk( table ) should have the proper description +ok 24 - hasnt_pk( table ) should have the proper diagnostics +ok 25 - hasnt_pk( schema, table, description ) pass should pass +ok 26 - hasnt_pk( schema, table, description ) pass should have the proper description +ok 27 - hasnt_pk( schema, table, description ) pass should have the proper diagnostics +ok 28 - hasnt_pk( table, description ) pass should pass +ok 29 - hasnt_pk( table, description ) pass should have the proper description +ok 30 - hasnt_pk( table, description ) pass should have the proper diagnostics +ok 31 - col_is_pk( schema, table, column, description ) should pass +ok 32 - col_is_pk( schema, table, column, description ) should have the proper description +ok 33 - col_is_pk( schema, table, column, description ) should have the proper diagnostics +ok 34 - col_is_pk( table, column, description ) should pass +ok 35 - col_is_pk( table, column, description ) should have the proper description +ok 36 - col_is_pk( table, column, description ) should have the proper diagnostics +ok 37 - col_is_pk( table, column ) should pass +ok 38 - col_is_pk( table, column ) should have the proper description +ok 39 - col_is_pk( table, column ) should have the proper diagnostics +ok 40 - col_is_pk( schema, table, column, description ) fail should fail +ok 41 - col_is_pk( schema, table, column, description ) fail should have the proper description +ok 42 - col_is_pk( schema, table, column, description ) fail should have the proper diagnostics +ok 43 - col_is_pk( table, column, description ) fail should fail +ok 44 - col_is_pk( table, column, description ) fail should have the proper description +ok 45 - col_is_pk( table, column, description ) fail should have the proper diagnostics +ok 46 - col_is_pk( schema, table, column[], description ) should pass +ok 47 - col_is_pk( schema, table, column[], description ) should have the proper description +ok 48 - col_is_pk( schema, table, column[], description ) should have the proper diagnostics +ok 49 - col_is_pk( table, column[], description ) should pass +ok 50 - col_is_pk( table, column[], description ) should have the proper description +ok 51 - col_is_pk( table, column[], description ) should have the proper diagnostics +ok 52 - col_is_pk( table, column[] ) should pass +ok 53 - col_is_pk( table, column[] ) should have the proper description +ok 54 - col_is_pk( table, column[] ) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 6e2cd2ede3c0..543a512b16ab 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -618,7 +618,7 @@ CREATE OR REPLACE FUNCTION skip( int ) RETURNS TEXT AS 'SELECT skip(NULL, $1)' LANGUAGE sql; ---CREATE OR REPLACE FUNCTION throws_ok ( sql, errcode, errmsg, description ) +-- throws_ok ( sql, errcode, errmsg, description ) CREATE OR REPLACE FUNCTION throws_ok ( TEXT, CHAR(5), TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -657,8 +657,8 @@ EXCEPTION WHEN OTHERS THEN END; $$ LANGUAGE plpgsql; ---CREATE OR REPLACE FUNCTION throws_ok ( query, errcode, errmsg ) ---CREATE OR REPLACE FUNCTION throws_ok ( query, errmsg, description ) +-- throws_ok ( query, errcode, errmsg ) +-- throws_ok ( query, errmsg, description ) CREATE OR REPLACE FUNCTION throws_ok ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ BEGIN @@ -670,8 +670,8 @@ BEGIN END; $$ LANGUAGE plpgsql; ---CREATE OR REPLACE FUNCTION throws_ok ( query, errcode ) ---CREATE OR REPLACE FUNCTION throws_ok ( query, errmsg ) +-- throws_ok ( query, errcode ) +-- throws_ok ( query, errmsg ) CREATE OR REPLACE FUNCTION throws_ok ( TEXT, TEXT ) RETURNS TEXT AS $$ BEGIN @@ -683,26 +683,26 @@ BEGIN END; $$ LANGUAGE plpgsql; ---CREATE OR REPLACE FUNCTION throws_ok ( query ) +-- throws_ok ( query ) CREATE OR REPLACE FUNCTION throws_ok ( TEXT ) RETURNS TEXT AS $$ SELECT throws_ok( $1, NULL, NULL, NULL ); $$ LANGUAGE SQL; -- Magically cast integer error codes. ---CREATE OR REPLACE FUNCTION throws_ok ( query, errcode, errmsg, description ) +-- throws_ok ( query, 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; ---CREATE OR REPLACE FUNCTION throws_ok ( query, errcode, errmsg ) +-- throws_ok ( query, 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; ---CREATE OR REPLACE FUNCTION throws_ok ( query, errcode ) +-- throws_ok ( query, errcode ) CREATE OR REPLACE FUNCTION throws_ok ( TEXT, int4 ) RETURNS TEXT AS $$ SELECT throws_ok( $1, $2::char(5), NULL, NULL ); @@ -729,33 +729,39 @@ RETURNS TEXT AS $$ SELECT lives_ok( $1, NULL ); $$ 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 = $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 = $1 + AND pg_catalog.pg_table_is_visible(c.oid) + AND c.relname = $2 + ); +$$ LANGUAGE SQL; + -- has_table( schema, table, description ) CREATE OR REPLACE FUNCTION has_table ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ - SELECT ok( - EXISTS( - SELECT true - FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c - WHERE n.oid = c.relnamespace - AND c.relkind = 'r' - AND n.nspname = $1 - AND c.relname = $2 - ), $3 - ); + SELECT ok( _rexists( 'r', $1, $2 ), $3 ); $$ LANGUAGE SQL; -- has_table( table, description ) CREATE OR REPLACE FUNCTION has_table ( NAME, TEXT ) RETURNS TEXT AS $$ - SELECT ok( - 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 - ), $2 - ); + SELECT ok( _rexists( 'r', $1 ), $2 ); $$ LANGUAGE SQL; -- has_table( table ) @@ -764,33 +770,34 @@ RETURNS TEXT AS $$ SELECT has_table( $1, 'Table ' || $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', $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_table( table, description ) +CREATE OR REPLACE FUNCTION hasnt_table ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _rexists( 'r', $1 ), $2 ); +$$ LANGUAGE SQL; + +-- hasnt_table( table ) +CREATE OR REPLACE FUNCTION hasnt_table ( NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_table( $1, 'Table ' || $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( - EXISTS( - SELECT true - FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c - WHERE n.oid = c.relnamespace - AND c.relkind = 'v' - AND n.nspname = $1 - AND c.relname = $2 - ), $3 - ); + SELECT ok( _rexists( 'v', $1, $2 ), $3 ); $$ LANGUAGE SQL; -- has_view( view, description ) CREATE OR REPLACE FUNCTION has_view ( NAME, TEXT ) RETURNS TEXT AS $$ - SELECT ok( - EXISTS( - SELECT true - FROM pg_catalog.pg_class c - WHERE c.relkind = 'v' - AND pg_catalog.pg_table_is_visible(c.oid) - AND c.relname = $1 - ), $2 - ); + SELECT ok( _rexists( 'v', $1 ), $2 ); $$ LANGUAGE SQL; -- has_view( view ) @@ -799,39 +806,63 @@ RETURNS TEXT AS $$ SELECT has_view( $1, 'View ' || $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( 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 ' || $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 = LOWER($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 = LOWER($2) + ); +$$ LANGUAGE SQL; + -- has_column( schema, table, column, description ) CREATE OR REPLACE FUNCTION has_column ( NAME, NAME, NAME, TEXT ) RETURNS TEXT AS $$ - SELECT ok( - EXISTS( - SELECT true - FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a - WHERE n.oid = c.relnamespace - AND c.oid = a.attrelid - AND n.nspname = $1 - AND c.relname = $2 - AND a.attnum > 0 - AND NOT a.attisdropped - AND a.attname = LOWER($3) - ), $4 - ); + 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( - EXISTS( - SELECT true - FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a - WHERE c.oid = a.attrelid - AND pg_catalog.pg_table_is_visible(c.oid) - AND c.relname = $1 - AND a.attnum > 0 - AND NOT a.attisdropped - AND a.attname = LOWER($2) - ), $3 - ); + SELECT ok( _cexists( $1, $2 ), $3 ); $$ LANGUAGE SQL; -- has_column( table, column ) @@ -840,6 +871,24 @@ RETURNS TEXT AS $$ SELECT has_column( $1, $2, 'Column ' || $1 || '(' || $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 ' || $1 || '(' || $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 $$ @@ -1050,10 +1099,9 @@ CREATE OR REPLACE FUNCTION _hasc ( NAME, CHAR ) RETURNS BOOLEAN AS $$ SELECT EXISTS( SELECT true - FROM pg_catalog.pg_class c, - pg_catalog.pg_constraint x - WHERE c.oid = x.conrelid - AND c.relhaspkey = true + FROM pg_catalog.pg_class c + JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid + WHERE c.relhaspkey = true AND c.relname = $1 AND x.contype = $2 ); @@ -1077,6 +1125,24 @@ RETURNS TEXT AS $$ SELECT has_pk( $1, 'Table ' || $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 ' || $1 || ' should not have a primary key' ); +$$ LANGUAGE sql; + -- _ckeys( schema, table, constraint_type ) CREATE OR REPLACE FUNCTION _ckeys ( NAME, NAME, CHAR ) RETURNS NAME[] AS $$ @@ -1252,6 +1318,24 @@ RETURNS TEXT AS $$ SELECT has_fk( $1, 'Table ' || $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 ' || $1 || ' should not have a foreign key constraint' ); +$$ LANGUAGE sql; + -- col_is_fk( schema, table, column, description ) CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME, NAME[], TEXT ) RETURNS TEXT AS $$ diff --git a/sql/fktap.sql b/sql/fktap.sql index d6433a16491d..1c664c88517a 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(88); +SELECT plan(98); --SELECT * from no_plan(); -- These will be rolled back. :-) @@ -50,10 +50,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_fk( 'fk', 'fk should have a pk' ), + has_fk( 'fk', 'fk should have an fk' ), 'true', 'has_fk( table, description )', - 'fk should have a pk' + 'fk should have an fk' ); SELECT * FROM check_test( @@ -64,17 +64,54 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_fk( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should have a pk' ), + has_fk( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should have an fk' ), false, 'has_fk( schema, table, description ) fail', - 'pg_catalog.pg_class should have a pk' + 'pg_catalog.pg_class should have an fk' ); SELECT * FROM check_test( - has_fk( 'pg_class', 'pg_class should have a pk' ), + has_fk( 'pg_class', 'pg_class should have an fk' ), false, 'has_fk( table, description ) fail', - 'pg_class should have a pk' + 'pg_class should have an fk' +); + +/****************************************************************************/ +-- Test hasnt_fk(). +SELECT * FROM check_test( + hasnt_fk( 'public', 'fk', 'public.fk should not have an fk' ), + false, + 'hasnt_fk( schema, table, description )', + 'public.fk should not have an fk' +); + +SELECT * FROM check_test( + hasnt_fk( 'fk', 'fk should not have an fk' ), + 'false', + 'hasnt_fk( table, description )', + 'fk should not have an fk' +); + +SELECT * FROM check_test( + hasnt_fk( 'fk' ), + false, + 'hasnt_fk( table )', + 'Table fk should not have a foreign key constraint' +); + +SELECT * FROM check_test( + hasnt_fk( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should not have an fk' ), + true, + 'hasnt_fk( schema, table, description ) pass', + 'pg_catalog.pg_class should not have an fk' +); + +SELECT * FROM check_test( + hasnt_fk( 'pg_class', 'pg_class should not have an fk' ), + true, + 'hasnt_fk( table, description ) pass', + 'pg_class should not have an fk' ); /****************************************************************************/ diff --git a/sql/hastap.sql b/sql/hastap.sql index 58bf8a78d7d0..52873d659f77 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(45); +SELECT plan(90); -- This will be rolled back. :-) SET LOCAL client_min_messages = warning; @@ -58,6 +58,49 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test hasnt_table(). + +SELECT * FROM check_test( + hasnt_table( '__SDFSDFD__' ), + true, + 'hasnt_table(non-existent table)', + 'Table __SDFSDFD__ should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_table( '__SDFSDFD__', 'lol' ), + true, + 'hasnt_table(non-existent schema, tab)', + 'lol', + '' +); + +SELECT * FROM check_test( + hasnt_table( 'foo', '__SDFSDFD__', 'desc' ), + true, + 'hasnt_table(sch, non-existent tab, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_table( 'pg_type', 'lol' ), + false, + 'hasnt_table(tab, desc)', + 'lol', + '' +); + +SELECT * FROM check_test( + hasnt_table( 'pg_catalog', 'pg_type', 'desc' ), + false, + 'hasnt_table(sch, tab, desc)', + 'desc', + '' +); + /****************************************************************************/ -- Test has_view(). @@ -101,6 +144,49 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test hasnt_view(). + +SELECT * FROM check_test( + hasnt_view( '__SDFSDFD__' ), + true, + 'hasnt_view(non-existent view)', + 'View __SDFSDFD__ should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_view( '__SDFSDFD__', 'howdy' ), + true, + 'hasnt_view(non-existent view, desc)', + 'howdy', + '' +); + +SELECT * FROM check_test( + hasnt_view( 'foo', '__SDFSDFD__', 'desc' ), + true, + 'hasnt_view(sch, non-existtent view, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_view( 'pg_tables', 'yowza' ), + false, + 'hasnt_view(view, desc)', + 'yowza', + '' +); + +SELECT * FROM check_test( + hasnt_view( 'information_schema', 'tables', 'desc' ), + false, + 'hasnt_view(sch, view, desc)', + 'desc', + '' +); + /****************************************************************************/ -- Test has_column(). @@ -144,6 +230,49 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test hasnt_column(). + +SELECT * FROM check_test( + hasnt_column( '__SDFSDFD__', 'foo' ), + true, + 'hasnt_column(non-existent tab, col)', + 'Column __SDFSDFD__(foo) should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_column( '__SDFSDFD__', 'bar', 'whatever' ), + true, + 'hasnt_column(non-existent tab, col, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_column( 'foo', '__SDFSDFD__', 'bar', 'desc' ), + true, + 'hasnt_column(non-existent sch, tab, col, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_column( 'sometab', 'id' ), + false, + 'hasnt_column(table, column)', + 'Column sometab(id) should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_column( 'information_schema', 'tables', 'table_name', 'desc' ), + false, + 'hasnt_column(sch, tab, col, desc)', + 'desc', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/sql/pktap.sql b/sql/pktap.sql index 3b26b265fbcc..cf3a78ec79f9 100644 --- a/sql/pktap.sql +++ b/sql/pktap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(39); +SELECT plan(54); -- This will be rolled back. :-) SET LOCAL client_min_messages = warning; @@ -58,6 +58,49 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test hasnt_pk(). + +SELECT * FROM check_test( + hasnt_pk( 'public', 'sometab', 'public.sometab should not have a pk' ), + false, + 'hasnt_pk( schema, table, description )', + 'public.sometab should not have a pk', + '' +); + +SELECT * FROM check_test( + hasnt_pk( 'sometab', 'sometab should not have a pk' ), + false, + 'hasnt_pk( table, description )', + 'sometab should not have a pk', + '' +); + +SELECT * FROM check_test( + hasnt_pk( 'sometab' ), + false, + 'hasnt_pk( table )', + 'Table sometab should not have a primary key', + '' +); + +SELECT * FROM check_test( + hasnt_pk( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should not have a pk' ), + true, + 'hasnt_pk( schema, table, description ) pass', + 'pg_catalog.pg_class should not have a pk', + '' +); + +SELECT * FROM check_test( + hasnt_pk( 'pg_class', 'pg_class should not have a pk' ), + true, + 'hasnt_pk( table, description ) pass', + 'pg_class should not have a pk', + '' +); + /****************************************************************************/ -- Test col_is_pk(). diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 63b0569b6ac1..ebbaad3d9660 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -3,6 +3,19 @@ DROP FUNCTION check_test( TEXT, BOOLEAN ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); +DROP FUNCTION can ( NAME[] ); +DROP FUNCTION can ( NAME[], TEXT ); +DROP FUNCTION can ( NAME, NAME[] ); +DROP FUNCTION can ( NAME, NAME[], TEXT ); +DROP FUNCTION _pg_sv_type_array( OID[] ); +DROP FUNCTION can_ok( NAME ); +DROP FUNCTION can_ok( NAME, TEXT ); +DROP FUNCTION can_ok( NAME, NAME[] ); +DROP FUNCTION can_ok ( NAME, NAME[], TEXT ); +DROP FUNCTION can_ok( NAME, NAME ); +DROP FUNCTION can_ok ( NAME, NAME, TEXT ); +DROP FUNCTION can_ok( NAME, NAME, NAME[] ); +DROP FUNCTION can_ok ( NAME, NAME, NAME[], TEXT ); DROP FUNCTION fk_ok ( NAME, NAME, NAME, NAME ); DROP FUNCTION fk_ok ( NAME, NAME, NAME, NAME, TEXT ); DROP FUNCTION fk_ok ( NAME, NAME, NAME, NAME, NAME, TEXT ); @@ -35,6 +48,9 @@ DROP FUNCTION col_is_fk ( NAME, NAME, NAME, TEXT ); DROP FUNCTION col_is_fk ( NAME, NAME[] ); DROP FUNCTION col_is_fk ( NAME, NAME[], TEXT ); DROP FUNCTION col_is_fk ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION hasnt_fk ( NAME ); +DROP FUNCTION hasnt_fk ( NAME, TEXT ); +DROP FUNCTION hasnt_fk ( NAME, NAME, TEXT ); DROP FUNCTION has_fk ( NAME ); DROP FUNCTION has_fk ( NAME, TEXT ); DROP FUNCTION has_fk ( NAME, NAME, TEXT ); @@ -44,11 +60,14 @@ DROP FUNCTION col_is_pk ( NAME, NAME, NAME, TEXT ); DROP FUNCTION col_is_pk ( NAME, NAME[] ); DROP FUNCTION col_is_pk ( NAME, NAME[], TEXT ); DROP FUNCTION col_is_pk ( NAME, NAME, NAME[], TEXT ); -DROP VIEW pg_all_foreign_keys ; -DROP FUNCTION _pg_sv_table_accessible(oid,oid); -DROP FUNCTION _pg_sv_column_array(oid, smallint[]); +DROP VIEW pg_all_foreign_keys; +DROP FUNCTION _pg_sv_table_accessible( OID, OID ); +DROP FUNCTION _pg_sv_column_array( OID, SMALLINT[] ); DROP FUNCTION _ckeys ( NAME, CHAR ); DROP FUNCTION _ckeys ( NAME, NAME, CHAR ); +DROP FUNCTION hasnt_pk ( NAME ); +DROP FUNCTION hasnt_pk ( NAME, TEXT ); +DROP FUNCTION hasnt_pk ( NAME, NAME, TEXT ); DROP FUNCTION has_pk ( NAME ); DROP FUNCTION has_pk ( NAME, TEXT ); DROP FUNCTION has_pk ( NAME, NAME, TEXT ); @@ -69,22 +88,37 @@ DROP FUNCTION col_not_null ( NAME, NAME, TEXT ); DROP FUNCTION col_not_null ( NAME, NAME, NAME, TEXT ); DROP FUNCTION _col_is_null ( NAME, NAME, TEXT, bool ); DROP FUNCTION _col_is_null ( NAME, NAME, NAME, TEXT, bool ); +DROP FUNCTION hasnt_column ( NAME, NAME ); +DROP FUNCTION hasnt_column ( NAME, NAME, TEXT ); +DROP FUNCTION hasnt_column ( NAME, NAME, NAME, TEXT ); DROP FUNCTION has_column ( NAME, NAME ); DROP FUNCTION has_column ( NAME, NAME, TEXT ); DROP FUNCTION has_column ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION _cexists ( NAME, NAME ); +DROP FUNCTION _cexists ( NAME, NAME, NAME ); +DROP FUNCTION hasnt_view ( NAME ); +DROP FUNCTION hasnt_view ( NAME, TEXT ); +DROP FUNCTION hasnt_view ( NAME, NAME, TEXT ); DROP FUNCTION has_view ( NAME ); DROP FUNCTION has_view ( NAME, TEXT ); DROP FUNCTION has_view ( NAME, NAME, TEXT ); +DROP FUNCTION hasnt_table ( NAME ); +DROP FUNCTION hasnt_table ( NAME, TEXT ); +DROP FUNCTION hasnt_table ( NAME, NAME, TEXT ); DROP FUNCTION has_table ( NAME ); DROP FUNCTION has_table ( NAME, TEXT ); DROP FUNCTION has_table ( NAME, NAME, TEXT ); +DROP FUNCTION _rexists ( CHAR, NAME ); +DROP FUNCTION _rexists ( CHAR, NAME, NAME ); DROP FUNCTION lives_ok ( TEXT ); DROP FUNCTION lives_ok ( TEXT, TEXT ); DROP FUNCTION throws_ok ( TEXT, int4 ); DROP FUNCTION throws_ok ( TEXT, int4, TEXT ); +DROP FUNCTION throws_ok ( TEXT, int4, TEXT, TEXT ); DROP FUNCTION throws_ok ( TEXT ); -DROP FUNCTION throws_ok ( TEXT, CHAR(5) ); -DROP FUNCTION throws_ok ( TEXT, CHAR(5), TEXT ); +DROP FUNCTION throws_ok ( TEXT, TEXT ); +DROP FUNCTION throws_ok ( TEXT, TEXT, TEXT ); +DROP FUNCTION throws_ok ( TEXT, CHAR(5);, TEXT, TEXT ); DROP FUNCTION skip( int ); DROP FUNCTION skip( int, text ); DROP FUNCTION skip ( text ); From d565fee97c1037e1a27c90f5b40698db65e939b7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 10 Oct 2008 22:38:11 +0000 Subject: [PATCH 0182/1195] * Added `col_isnt_pk()` and `col_isnt_fk()`. * Refactored the above to cut down on redundant code. --- Changes | 3 +- README.pgtap | 56 +++++++++++++++++-- expected/fktap.out | 136 +++++++++++++++++++++++++++------------------ expected/pktap.out | 26 ++++++++- pgtap.sql.in | 106 +++++++++++++++++++++++++++++++---- sql/fktap.sql | 95 ++++++++++++++++++++++++++++++- sql/pktap.sql | 79 +++++++++++++++++++++++++- 7 files changed, 427 insertions(+), 74 deletions(-) diff --git a/Changes b/Changes index cebf455e7655..440a5926dd4d 100644 --- a/Changes +++ b/Changes @@ -6,7 +6,8 @@ Revision history for pgTAP although tests still might, depending on the setting of `client_min_messages`. - Added `hasnt_table()`, `hasnt_view()`, and `hasnt_column()`. - - Added `hasnt_pk()` and `hasnt_fk()`. + - Added `hasnt_pk()`, `hasnt_fk()`, `col_isnt_pk()`, and + `col_isnt_fk()`. - Added missing `DROP` statements to `uninstall_pgtap.sql.in`. 0.11 2008-09-24T20:41:42 diff --git a/README.pgtap b/README.pgtap index a5dad03aae84..c306a8170bff 100644 --- a/README.pgtap +++ b/README.pgtap @@ -572,7 +572,7 @@ search path. If you omit the test description, it will be set to "Table 'There should be no table myschema.sometable' ); -This function is the inversion of `has_table()`. The test passes if the +This function is the inverse of `has_table()`. The test passes if the specified table does *not* exist. ### `has_view( schema, view, description )` ### @@ -597,8 +597,8 @@ Just like `has_table()`, only it tests for the existence of a view. 'There should be no myschema.someview' ); -This function is the inversion of `has_view()`. The test passes if the -specified view does *not* exist. +This function is the inverse of `has_view()`. The test passes if the specified +view does *not* exist. ### `has_column( schema, table, column, description )` ### ### `has_column( table, column, description )` ### @@ -628,7 +628,7 @@ it will be set to "Column `:table`.`:column` should exist". 'There should be no myschema.sometable.somecolumn column' ); -This function is the inversion of `has_column()`. The test passes if the +This function is the inverse of `has_column()`. The test passes if the specified column does *not* exist. ### `col_not_null( schema, table, column, description )` ### @@ -785,7 +785,7 @@ exist. 'Table myschema.sometable should not have a primary key' ); -This function is the inversion of `has_pk()`. The test passes if the specified +This function is the inverse of `has_pk()`. The test passes if the specified primary key does *not* exist. ### `has_fk( schema, table, description )` ### @@ -815,7 +815,7 @@ table in question does not exist. 'Table myschema.sometable should not have a foreign key constraint' ); -This function is the inversion of `has_fk()`. The test passes if the specified +This function is the inverse of `has_fk()`. The test passes if the specified foreign key does *not* exist. ### `col_is_pk( schema, table, column, description )` ### @@ -856,6 +856,28 @@ Will produce something like this: # have: {} # want: {id} +### `col_isnt_pk( schema, table, column, description )` ### +### `col_isnt_pk( schema, table, column[], description )` ### +### `col_isnt_pk( table, column, description )` ### +### `col_isnt_pk( table, column[], description )` ### +### `col_isnt_pk( table, column )` ### +### `col_isnt_pk( table, column[] )` ### + + SELECT col_isnt_pk( + 'myschema', + 'sometable', + 'id', + 'Column myschema.sometable.id should not be a primary key' + ); + + SELECT col_isnt_pk( + 'persons', + ARRAY['given_name', 'surname'], + ); + +This function is the inverse of `col_is_pk()`. The test passes if the +specified column or columns are not a primary key. + ### `col_is_fk( schema, table, column, description )` ### ### `col_is_fk( schema, table, column[], description )` ### ### `col_is_fk( table, column, description )` ### @@ -884,6 +906,28 @@ simply list all of the foreign key constraint columns, like so: # {thingy_id} # {surname,given_name} +### `col_isnt_fk( schema, table, column, description )` ### +### `col_isnt_fk( schema, table, column[], description )` ### +### `col_isnt_fk( table, column, description )` ### +### `col_isnt_fk( table, column[], description )` ### +### `col_isnt_fk( table, column )` ### +### `col_isnt_fk( table, column[] )` ### + + SELECT col_isnt_fk( + 'myschema', + 'sometable', + 'other_id', + 'Column myschema.sometable.other_id should not be a foreign key' + ); + + SELECT col_isnt_fk( + 'contacts', + ARRAY['given_name', 'surname'], + ); + +This function is the inverse of `col_is_fk()`. The test passes if the +specified column or columns are not a foreign key. + ### `fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description )` ### ### `fk_ok( fk_schema, fk_table, fk_column[], fk_schema, pk_table, pk_column[] )` ### ### `fk_ok( fk_table, fk_column[], pk_table, pk_column[], description )` ### diff --git a/expected/fktap.out b/expected/fktap.out index daea09a69e64..0844b71b04fe 100644 --- a/expected/fktap.out +++ b/expected/fktap.out @@ -1,5 +1,5 @@ \unset ECHO -1..98 +1..128 ok 1 - has_fk( schema, table, description ) should pass ok 2 - has_fk( schema, table, description ) should have the proper description ok 3 - has_fk( table, description ) should pass @@ -46,55 +46,85 @@ ok 43 - col_is_fk( table, column[], description ) should pass ok 44 - col_is_fk( table, column[], description ) should have the proper description ok 45 - col_is_fk( table, column[] ) should pass ok 46 - col_is_fk( table, column[] ) should have the proper description -ok 47 - full fk_ok array should pass -ok 48 - full fk_ok array should have the proper description -ok 49 - multiple fk fk_ok desc should pass -ok 50 - multiple fk fk_ok desc should have the proper description -ok 51 - fk_ok array desc should pass -ok 52 - fk_ok array desc should have the proper description -ok 53 - fk_ok array noschema desc should pass -ok 54 - fk_ok array noschema desc should have the proper description -ok 55 - multiple fk fk_ok noschema desc should pass -ok 56 - multiple fk fk_ok noschema desc should have the proper description -ok 57 - fk_ok array noschema should pass -ok 58 - fk_ok array noschema should have the proper description -ok 59 - basic fk_ok should pass -ok 60 - basic fk_ok should have the proper description -ok 61 - basic fk_ok desc should pass -ok 62 - basic fk_ok desc should have the proper description -ok 63 - basic fk_ok noschema should pass -ok 64 - basic fk_ok noschema should have the proper description -ok 65 - basic fk_ok noschema desc should pass -ok 66 - basic fk_ok noschema desc should have the proper description -ok 67 - basic fk_ok noschema desc should have the proper diagnostics -ok 68 - Test should pass -ok 69 - fk_ok fail should fail -ok 70 - fk_ok fail should have the proper description -ok 71 - fk_ok fail should have the proper diagnostics -ok 72 - fk_ok fail desc should fail -ok 73 - fk_ok fail desc should have the proper description -ok 74 - fk_ok fail desc should have the proper diagnostics -ok 75 - fk_ok fail no schema should fail -ok 76 - fk_ok fail no schema should have the proper description -ok 77 - fk_ok fail no schema should have the proper diagnostics -ok 78 - fk_ok fail no schema desc should fail -ok 79 - fk_ok fail no schema desc should have the proper description -ok 80 - fk_ok fail no schema desc should have the proper diagnostics -ok 81 - fk_ok bad PK test should fail -ok 82 - fk_ok bad PK test should have the proper description -ok 83 - fk_ok bad PK test should have the proper diagnostics -ok 84 - double fk schema test should pass -ok 85 - double fk schema test should have the proper description -ok 86 - double fk schema test should have the proper diagnostics -ok 87 - double fk test should pass -ok 88 - double fk test should have the proper description -ok 89 - double fk test should have the proper diagnostics -ok 90 - double fk and col schema test should pass -ok 91 - double fk and col schema test should have the proper description -ok 92 - double fk and col schema test should have the proper diagnostics -ok 93 - missing fk test should fail -ok 94 - missing fk test should have the proper description -ok 95 - missing fk test should have the proper diagnostics -ok 96 - bad FK column test should fail -ok 97 - bad FK column test should have the proper description -ok 98 - bad FK column test should have the proper diagnostics +ok 47 - col_isnt_fk( schema, table, column, description ) should fail +ok 48 - col_isnt_fk( schema, table, column, description ) should have the proper description +ok 49 - col_isnt_fk( schema, table, column, description ) should have the proper diagnostics +ok 50 - col_isnt_fk( table, column, description ) should fail +ok 51 - col_isnt_fk( table, column, description ) should have the proper description +ok 52 - col_isnt_fk( table, column, description ) should have the proper diagnostics +ok 53 - col_isnt_fk( table, column ) should fail +ok 54 - col_isnt_fk( table, column ) should have the proper description +ok 55 - col_isnt_fk( table, column ) should have the proper diagnostics +ok 56 - col_isnt_fk( schema, table, column, description ) should pass +ok 57 - col_isnt_fk( schema, table, column, description ) should have the proper description +ok 58 - col_isnt_fk( schema, table, column, description ) should have the proper diagnostics +ok 59 - col_isnt_fk( table, column, description ) should pass +ok 60 - col_isnt_fk( table, column, description ) should have the proper description +ok 61 - col_isnt_fk( table, column, description ) should have the proper diagnostics +ok 62 - multi-fk col_isnt_fk test should fail +ok 63 - multi-fk col_isnt_fk test should have the proper description +ok 64 - multi-fk col_isnt_fk test should have the proper diagnostics +ok 65 - col_isnt_fk with no FKs should pass +ok 66 - col_isnt_fk with no FKs should have the proper description +ok 67 - col_isnt_fk with no FKs should have the proper diagnostics +ok 68 - col_isnt_fk with no FKs should pass +ok 69 - col_isnt_fk with no FKs should have the proper description +ok 70 - col_isnt_fk with no FKs should have the proper diagnostics +ok 71 - col_isnt_fk( schema, table, column[], description ) should fail +ok 72 - col_isnt_fk( schema, table, column[], description ) should have the proper description +ok 73 - col_isnt_fk( table, column[], description ) should fail +ok 74 - col_isnt_fk( table, column[], description ) should have the proper description +ok 75 - col_isnt_fk( table, column[] ) should fail +ok 76 - col_isnt_fk( table, column[] ) should have the proper description +ok 77 - full fk_ok array should pass +ok 78 - full fk_ok array should have the proper description +ok 79 - multiple fk fk_ok desc should pass +ok 80 - multiple fk fk_ok desc should have the proper description +ok 81 - fk_ok array desc should pass +ok 82 - fk_ok array desc should have the proper description +ok 83 - fk_ok array noschema desc should pass +ok 84 - fk_ok array noschema desc should have the proper description +ok 85 - multiple fk fk_ok noschema desc should pass +ok 86 - multiple fk fk_ok noschema desc should have the proper description +ok 87 - fk_ok array noschema should pass +ok 88 - fk_ok array noschema should have the proper description +ok 89 - basic fk_ok should pass +ok 90 - basic fk_ok should have the proper description +ok 91 - basic fk_ok desc should pass +ok 92 - basic fk_ok desc should have the proper description +ok 93 - basic fk_ok noschema should pass +ok 94 - basic fk_ok noschema should have the proper description +ok 95 - basic fk_ok noschema desc should pass +ok 96 - basic fk_ok noschema desc should have the proper description +ok 97 - basic fk_ok noschema desc should have the proper diagnostics +ok 98 - Test should pass +ok 99 - fk_ok fail should fail +ok 100 - fk_ok fail should have the proper description +ok 101 - fk_ok fail should have the proper diagnostics +ok 102 - fk_ok fail desc should fail +ok 103 - fk_ok fail desc should have the proper description +ok 104 - fk_ok fail desc should have the proper diagnostics +ok 105 - fk_ok fail no schema should fail +ok 106 - fk_ok fail no schema should have the proper description +ok 107 - fk_ok fail no schema should have the proper diagnostics +ok 108 - fk_ok fail no schema desc should fail +ok 109 - fk_ok fail no schema desc should have the proper description +ok 110 - fk_ok fail no schema desc should have the proper diagnostics +ok 111 - fk_ok bad PK test should fail +ok 112 - fk_ok bad PK test should have the proper description +ok 113 - fk_ok bad PK test should have the proper diagnostics +ok 114 - double fk schema test should pass +ok 115 - double fk schema test should have the proper description +ok 116 - double fk schema test should have the proper diagnostics +ok 117 - double fk test should pass +ok 118 - double fk test should have the proper description +ok 119 - double fk test should have the proper diagnostics +ok 120 - double fk and col schema test should pass +ok 121 - double fk and col schema test should have the proper description +ok 122 - double fk and col schema test should have the proper diagnostics +ok 123 - missing fk test should fail +ok 124 - missing fk test should have the proper description +ok 125 - missing fk test should have the proper diagnostics +ok 126 - bad FK column test should fail +ok 127 - bad FK column test should have the proper description +ok 128 - bad FK column test should have the proper diagnostics diff --git a/expected/pktap.out b/expected/pktap.out index ce04136b2ed2..e6fcba6cd027 100644 --- a/expected/pktap.out +++ b/expected/pktap.out @@ -1,5 +1,5 @@ \unset ECHO -1..54 +1..78 ok 1 - has_pk( schema, table, description ) should pass ok 2 - has_pk( schema, table, description ) should have the proper description ok 3 - has_pk( schema, table, description ) should have the proper diagnostics @@ -54,3 +54,27 @@ ok 51 - col_is_pk( table, column[], description ) should have the proper diagnos ok 52 - col_is_pk( table, column[] ) should pass ok 53 - col_is_pk( table, column[] ) should have the proper description ok 54 - col_is_pk( table, column[] ) should have the proper diagnostics +ok 55 - col_isnt_pk( schema, table, column, description ) should fail +ok 56 - col_isnt_pk( schema, table, column, description ) should have the proper description +ok 57 - col_isnt_pk( schema, table, column, description ) should have the proper diagnostics +ok 58 - col_isnt_pk( table, column, description ) should fail +ok 59 - col_isnt_pk( table, column, description ) should have the proper description +ok 60 - col_isnt_pk( table, column, description ) should have the proper diagnostics +ok 61 - col_isnt_pk( table, column ) should fail +ok 62 - col_isnt_pk( table, column ) should have the proper description +ok 63 - col_isnt_pk( table, column ) should have the proper diagnostics +ok 64 - col_isnt_pk( schema, table, column, description ) pass should pass +ok 65 - col_isnt_pk( schema, table, column, description ) pass should have the proper description +ok 66 - col_isnt_pk( schema, table, column, description ) pass should have the proper diagnostics +ok 67 - col_isnt_pk( table, column, description ) pass should pass +ok 68 - col_isnt_pk( table, column, description ) pass should have the proper description +ok 69 - col_isnt_pk( table, column, description ) pass should have the proper diagnostics +ok 70 - col_isnt_pk( schema, table, column[], description ) should pass +ok 71 - col_isnt_pk( schema, table, column[], description ) should have the proper description +ok 72 - col_isnt_pk( schema, table, column[], description ) should have the proper diagnostics +ok 73 - col_isnt_pk( table, column[], description ) should pass +ok 74 - col_isnt_pk( table, column[], description ) should have the proper description +ok 75 - col_isnt_pk( table, column[], description ) should have the proper diagnostics +ok 76 - col_isnt_pk( table, column[] ) should pass +ok 77 - col_isnt_pk( table, column[] ) should have the proper description +ok 78 - col_isnt_pk( table, column[] ) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 543a512b16ab..9acc97573d09 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1300,6 +1300,42 @@ RETURNS TEXT AS $$ SELECT col_is_pk( $1, $2, 'Column ' || $1 || '(' || $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 ' || $1 || '(' || 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 ' || $1 || '(' || $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 $$ @@ -1336,18 +1372,34 @@ RETURNS TEXT AS $$ SELECT hasnt_fk( $1, 'Table ' || $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 fk_table_name = $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 fk_table_name = $1 + 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 - PERFORM TRUE - FROM pg_all_foreign_keys - WHERE fk_schema_name = $1 - AND fk_table_name = $2 - AND fk_columns = $3; - IF FOUND THEN + IF _fkexists($1, $2, $3) THEN RETURN pass( $4 ); END IF; @@ -1380,11 +1432,7 @@ RETURNS TEXT AS $$ DECLARE names text[][]; BEGIN - PERFORM TRUE - FROM pg_all_foreign_keys - WHERE fk_table_name = $1 - AND fk_columns = $2; - IF FOUND THEN + IF _fkexists($1, $2) THEN RETURN pass( $3 ); END IF; @@ -1434,6 +1482,42 @@ RETURNS TEXT AS $$ SELECT col_is_fk( $1, $2, 'Column ' || $1 || '(' || $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 ' || $1 || '(' || 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 ' || $1 || '(' || $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 $$ diff --git a/sql/fktap.sql b/sql/fktap.sql index 1c664c88517a..a524d1c4df43 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(98); +SELECT plan(128); --SELECT * from no_plan(); -- These will be rolled back. :-) @@ -207,6 +207,99 @@ SELECT * FROM check_test( 'Columns fk2(pk2_num, pk2_dot) should be a foreign key' ); +/****************************************************************************/ +-- Test col_isnt_fk(). + +SELECT * FROM check_test( + col_isnt_fk( 'public', 'fk', 'pk_id', 'public.fk.pk_id should not be an fk' ), + false, + 'col_isnt_fk( schema, table, column, description )', + 'public.fk.pk_id should not be an fk', + '' +); + +SELECT * FROM check_test( + col_isnt_fk( 'fk', 'pk_id', 'fk.pk_id should not be an fk' ), + false, + 'col_isnt_fk( table, column, description )', + 'fk.pk_id should not be an fk', + '' +); + +SELECT * FROM check_test( + col_isnt_fk( 'fk', 'pk_id' ), + false, + 'col_isnt_fk( table, column )', + 'Column fk(pk_id) should not be a foreign key', + '' +); + +SELECT * FROM check_test( + col_isnt_fk( 'public', 'fk', 'name', 'public.fk.name should not be an fk' ), + true, + 'col_isnt_fk( schema, table, column, description )', + 'public.fk.name should not be an fk', + '' +); + +SELECT * FROM check_test( + col_isnt_fk( 'fk3', 'name', 'fk3.name should not be an fk' ), + true, + 'col_isnt_fk( table, column, description )', + 'fk3.name should not be an fk', + '' +); + +-- Check table with multiple FKs. +SELECT * FROM check_test( + col_isnt_fk( 'fk3', 'pk_id' ), + false, + 'multi-fk col_isnt_fk test', + 'Column fk3(pk_id) should not be a foreign key', + '' +); + +-- Check failure for table with no FKs. +SELECT * FROM check_test( + col_isnt_fk( 'public', 'pk', 'name', 'pk.name should not be an fk' ), + true, + 'col_isnt_fk with no FKs', + 'pk.name should not be an fk', + '' +); + +SELECT * FROM check_test( + col_isnt_fk( 'pk', 'name' ), + true, + 'col_isnt_fk with no FKs', + 'Column pk(name) should not be a foreign key', + '' +); + +/****************************************************************************/ +-- Test col_isnt_fk() with an array of columns. + +SELECT * FROM check_test( + col_isnt_fk( 'public', 'fk2', ARRAY['pk2_num', 'pk2_dot'], 'id + pk2_dot should not be an fk' ), + false, + 'col_isnt_fk( schema, table, column[], description )', + 'id + pk2_dot should not be an fk' +); + +SELECT * FROM check_test( + col_isnt_fk( 'fk2', ARRAY['pk2_num', 'pk2_dot'], 'id + pk2_dot should not be an fk' ), + false, + 'col_isnt_fk( table, column[], description )', + 'id + pk2_dot should not be an fk' +); + +SELECT * FROM check_test( + col_isnt_fk( 'fk2', ARRAY['pk2_num', 'pk2_dot'] ), + false, + 'col_isnt_fk( table, column[] )', + 'Columns fk2(pk2_num, pk2_dot) should not be a foreign key' +); + /****************************************************************************/ -- Test fk_ok(). SELECT * FROM check_test( diff --git a/sql/pktap.sql b/sql/pktap.sql index cf3a78ec79f9..421d8ee1e065 100644 --- a/sql/pktap.sql +++ b/sql/pktap.sql @@ -3,7 +3,8 @@ -- $Id$ -SELECT plan(54); +SELECT plan(78); +--SELECT * FROM no_plan(); -- This will be rolled back. :-) SET LOCAL client_min_messages = warning; @@ -181,6 +182,82 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test col_isnt_pk(). + +SELECT * FROM check_test( + col_isnt_pk( 'public', 'sometab', 'id', 'public.sometab.id should not be a pk' ), + false, + 'col_isnt_pk( schema, table, column, description )', + 'public.sometab.id should not be a pk', + ' {id} + <> + {id}' +); + +SELECT * FROM check_test( + col_isnt_pk( 'sometab', 'id', 'sometab.id should not be a pk' ), + false, + 'col_isnt_pk( table, column, description )', + 'sometab.id should not be a pk', + ' {id} + <> + {id}' +); + +SELECT * FROM check_test( + col_isnt_pk( 'sometab', 'id' ), + false, + 'col_isnt_pk( table, column )', + 'Column sometab(id) should not be a primary key', + ' {id} + <> + {id}' +); + +SELECT * FROM check_test( + col_isnt_pk( 'public', 'sometab', 'name', 'public.sometab.name should not be a pk' ), + true, + 'col_isnt_pk( schema, table, column, description ) pass', + 'public.sometab.name should not be a pk', + '' +); + +SELECT * FROM check_test( + col_isnt_pk( 'sometab', 'name', 'sometab.name should not be a pk' ), + true, + 'col_isnt_pk( table, column, description ) pass', + 'sometab.name should not be a pk', + '' +); + +/****************************************************************************/ +-- Test col_isnt_pk() with an array of columns. + +SELECT * FROM check_test( + col_isnt_pk( 'public', 'argh', ARRAY['id', 'foo'], 'id + foo should not be a pk' ), + true, + 'col_isnt_pk( schema, table, column[], description )', + 'id + foo should not be a pk', + '' +); + +SELECT * FROM check_test( + col_isnt_pk( 'argh', ARRAY['id', 'foo'], 'id + foo should not be a pk' ), + true, + 'col_isnt_pk( table, column[], description )', + 'id + foo should not be a pk', + '' +); + +SELECT * FROM check_test( + col_isnt_pk( 'argh', ARRAY['id', 'foo'] ), + true, + 'col_isnt_pk( table, column[] )', + 'Columns argh(id, foo) should not be a primary key', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 75c66782c0748ded718c21b4e7642ac53b35c774 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 10 Oct 2008 22:41:25 +0000 Subject: [PATCH 0183/1195] * Updated uninstall script * Eliminated trailing white space. --- pgtap.sql.in | 8 ++++---- uninstall_pgtap.sql.in | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 9acc97573d09..895eff000141 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1199,7 +1199,7 @@ RETURNS BOOLEAN AS $$ $$ LANGUAGE SQL immutable strict; -- Borrowed from newsysviews: http://pgfoundry.org/projects/newsysviews/ -CREATE OR REPLACE VIEW pg_all_foreign_keys +CREATE OR REPLACE VIEW pg_all_foreign_keys AS SELECT n1.nspname AS fk_schema_name, c1.relname AS fk_table_name, @@ -1806,14 +1806,14 @@ RETURNS TEXT AS $$ SELECT can_ok( $1, 'Function ' || $1 || '() should exist' ); $$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION _pg_sv_type_array( OID[] ) +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, func_names[], description ) @@ -1943,7 +1943,7 @@ BEGIN IF NOT aok THEN adiag := substring( adiag - FROM 14 + char_length(tnumb::text) + FROM 14 + char_length(tnumb::text) + CASE adescr WHEN '' THEN 3 ELSE 4 + char_length( diag( adescr ) ) END ); END IF; diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index ebbaad3d9660..20ab95b2540f 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -7,7 +7,7 @@ DROP FUNCTION can ( NAME[] ); DROP FUNCTION can ( NAME[], TEXT ); DROP FUNCTION can ( NAME, NAME[] ); DROP FUNCTION can ( NAME, NAME[], TEXT ); -DROP FUNCTION _pg_sv_type_array( OID[] ); +DROP FUNCTION _pg_sv_type_array( OID[] ); DROP FUNCTION can_ok( NAME ); DROP FUNCTION can_ok( NAME, TEXT ); DROP FUNCTION can_ok( NAME, NAME[] ); @@ -42,18 +42,32 @@ DROP FUNCTION col_is_unique ( NAME, NAME, NAME[], TEXT ); DROP FUNCTION has_unique ( TEXT ); DROP FUNCTION has_unique ( TEXT, TEXT ); DROP FUNCTION has_unique ( TEXT, TEXT, TEXT ); +DROP FUNCTION col_isnt_fk ( NAME, NAME ); +DROP FUNCTION col_isnt_fk ( NAME, NAME, TEXT ); +DROP FUNCTION col_isnt_fk ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION col_isnt_fk ( NAME, NAME[] ); +DROP FUNCTION col_isnt_fk ( NAME, NAME[], TEXT ); +DROP FUNCTION col_isnt_fk ( NAME, NAME, NAME[], TEXT ); DROP FUNCTION col_is_fk ( NAME, NAME ); DROP FUNCTION col_is_fk ( NAME, NAME, TEXT ); DROP FUNCTION col_is_fk ( NAME, NAME, NAME, TEXT ); DROP FUNCTION col_is_fk ( NAME, NAME[] ); DROP FUNCTION col_is_fk ( NAME, NAME[], TEXT ); DROP FUNCTION col_is_fk ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _fkexists ( NAME, NAME[] ); +DROP FUNCTION _fkexists ( NAME, NAME, NAME[] ); DROP FUNCTION hasnt_fk ( NAME ); DROP FUNCTION hasnt_fk ( NAME, TEXT ); DROP FUNCTION hasnt_fk ( NAME, NAME, TEXT ); DROP FUNCTION has_fk ( NAME ); DROP FUNCTION has_fk ( NAME, TEXT ); DROP FUNCTION has_fk ( NAME, NAME, TEXT ); +DROP FUNCTION col_isnt_pk ( NAME, NAME ); +DROP FUNCTION col_isnt_pk ( NAME, NAME, TEXT ); +DROP FUNCTION col_isnt_pk ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION col_isnt_pk ( NAME, NAME[] ); +DROP FUNCTION col_isnt_pk ( NAME, NAME[], TEXT ); +DROP FUNCTION col_isnt_pk ( NAME, NAME, NAME[], TEXT ); DROP FUNCTION col_is_pk ( NAME, NAME ); DROP FUNCTION col_is_pk ( NAME, NAME, TEXT ); DROP FUNCTION col_is_pk ( NAME, NAME, NAME, TEXT ); @@ -118,7 +132,7 @@ DROP FUNCTION throws_ok ( TEXT, int4, TEXT, TEXT ); DROP FUNCTION throws_ok ( TEXT ); DROP FUNCTION throws_ok ( TEXT, TEXT ); DROP FUNCTION throws_ok ( TEXT, TEXT, TEXT ); -DROP FUNCTION throws_ok ( TEXT, CHAR(5);, TEXT, TEXT ); +DROP FUNCTION throws_ok ( TEXT, CHAR(5), TEXT, TEXT ); DROP FUNCTION skip( int ); DROP FUNCTION skip( int, text ); DROP FUNCTION skip ( text ); From 3ace1bd987093f734e3a9f1b62d26cc5734ca3ae Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 11 Oct 2008 00:21:15 +0000 Subject: [PATCH 0184/1195] Restore 8.2 compatibility. --- sql/check.sql | 2 +- sql/coltap.sql | 2 +- sql/fktap.sql | 2 +- sql/hastap.sql | 2 +- sql/pktap.sql | 4 ++-- sql/unique.sql | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sql/check.sql b/sql/check.sql index a815bc2c2c36..5f7b4bfbeef7 100644 --- a/sql/check.sql +++ b/sql/check.sql @@ -6,7 +6,7 @@ SELECT plan(39); -- This will be rolled back. :-) -SET LOCAL client_min_messages = warning; +SET client_min_messages = warning; CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '' CHECK ( name IN ('foo', 'bar', 'baz') ), diff --git a/sql/coltap.sql b/sql/coltap.sql index 0c3fed805f0f..d3fcfe22c37d 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -6,7 +6,7 @@ SELECT plan(57); -- This will be rolled back. :-) -SET LOCAL client_min_messages = warning; +SET client_min_messages = warning; CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '', diff --git a/sql/fktap.sql b/sql/fktap.sql index a524d1c4df43..30074f637403 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -7,7 +7,7 @@ SELECT plan(128); --SELECT * from no_plan(); -- These will be rolled back. :-) -SET LOCAL client_min_messages = warning; +SET client_min_messages = warning; CREATE TABLE public.pk ( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '' diff --git a/sql/hastap.sql b/sql/hastap.sql index 52873d659f77..a91d8b2c233f 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -6,7 +6,7 @@ SELECT plan(90); -- This will be rolled back. :-) -SET LOCAL client_min_messages = warning; +SET client_min_messages = warning; CREATE TABLE sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '', diff --git a/sql/pktap.sql b/sql/pktap.sql index 421d8ee1e065..3ec6b804f58e 100644 --- a/sql/pktap.sql +++ b/sql/pktap.sql @@ -7,7 +7,7 @@ SELECT plan(78); --SELECT * FROM no_plan(); -- This will be rolled back. :-) -SET LOCAL client_min_messages = warning; +SET client_min_messages = warning; CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '', @@ -150,7 +150,7 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test col_is_pk() with an array of columns. -SET LOCAL client_min_messages = warning; +SET client_min_messages = warning; CREATE TABLE public.argh ( id INT NOT NULL, name TEXT NOT NULL, diff --git a/sql/unique.sql b/sql/unique.sql index 3b4400f48810..6df79e662d63 100644 --- a/sql/unique.sql +++ b/sql/unique.sql @@ -6,7 +6,7 @@ SELECT plan(39); -- This will be rolled back. :-) -SET LOCAL client_min_messages = warning; +SET client_min_messages = warning; CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '' UNIQUE, @@ -106,7 +106,7 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test col_is_unique() with an array of columns. -SET LOCAL client_min_messages = warning; +SET client_min_messages = warning; CREATE TABLE public.argh ( id INT NOT NULL, name TEXT NOT NULL, From e891fa19e4193b53e1db22962ed946c231fe3161 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 11 Oct 2008 03:21:05 +0000 Subject: [PATCH 0185/1195] Workaround for 8.2. No bloody idea why it doesn't like `<>` in `isnt()` when that function is called by `col_isnt_pk()`. Very weird. --- pgtap.sql.in | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 895eff000141..fef3611ef174 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -292,8 +292,8 @@ DECLARE result BOOLEAN; output TEXT; BEGIN - result := CASE WHEN $2 IS NULL AND $1 IS NULL THEN TRUE - WHEN $2 IS NULL OR $1 IS NULL THEN FALSE + result := CASE WHEN $1 IS NULL AND $2 IS NULL THEN TRUE + WHEN $1 IS NULL OR $2 IS NULL THEN FALSE ELSE $1 = $2 END; output := ok( result, $3 ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( @@ -314,9 +314,9 @@ DECLARE result BOOLEAN; output TEXT; BEGIN - result := CASE WHEN $2 IS NULL AND $1 IS NULL THEN FALSE - WHEN $2 IS NULL OR $1 IS NULL THEN TRUE - ELSE $1 <> $2 END; + result := CASE WHEN $1 IS NULL AND $2 IS NULL THEN FALSE + WHEN $1 IS NULL OR $2 IS NULL THEN TRUE + ELSE NOT $1 = $2 END; -- XXX use of <> sometimes causes an error in 8.2! output := ok( result, $3 ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( ' ' || COALESCE( $1::text, 'NULL' ) || From ce3c3770e2ad92a28d359ae9c0fb8d4bc691a02b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 11 Oct 2008 03:29:03 +0000 Subject: [PATCH 0186/1195] Updatd 8.0 patch. This baby is ready to roll! --- compat/install-8.0.patch | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index bcbbc58ab50e..57477ee95a9a 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.saf 2008-09-24 13:13:13.000000000 -0700 -+++ pgtap.sql 2008-09-24 13:13:38.000000000 -0700 +--- pgtap.sql.orig 2008-10-10 20:36:09.000000000 -0700 ++++ pgtap.sql 2008-10-10 20:36:09.000000000 -0700 @@ -12,6 +12,14 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA,public; @@ -15,7 +15,7 @@ CREATE OR REPLACE FUNCTION pg_typeof("any") RETURNS regtype AS '$libdir/pgtap' -@@ -69,53 +77,63 @@ +@@ -74,53 +82,63 @@ CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ DECLARE @@ -96,7 +96,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -179,10 +197,12 @@ +@@ -184,10 +202,12 @@ CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ DECLARE @@ -112,7 +112,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -448,13 +468,16 @@ +@@ -453,13 +473,16 @@ want ALIAS FOR $3; descr ALIAS FOR $4; result BOOLEAN; @@ -132,7 +132,7 @@ output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE '\n' || diag( ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -964,15 +987,16 @@ +@@ -1018,15 +1041,16 @@ CREATE OR REPLACE FUNCTION _def_is( TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -153,7 +153,7 @@ END; $$ LANGUAGE plpgsql; -@@ -1717,6 +1741,7 @@ +@@ -1890,6 +1914,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -161,7 +161,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -1727,8 +1752,10 @@ +@@ -1900,8 +1925,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. From a022be59d681e6390d7ebe5f68f70b0768311a77 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 11 Oct 2008 03:51:40 +0000 Subject: [PATCH 0187/1195] Timestamped for 0.12 release. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 440a5926dd4d..ae5dec46eab3 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,6 @@ Revision history for pgTAP -0.12 +0.12 2008-10-11T04:02:42 - Updated `plan()` to disable warnings while it creates its tables. This means that `plan()` no longer send NOTICE messages when they run, although tests still might, depending on the setting of From dfc27354aafeec150287e8419cd5f61007dc3ee1 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 11 Oct 2008 03:57:50 +0000 Subject: [PATCH 0188/1195] Incremented version number to 0.13. --- Changes | 2 ++ README.pgtap | 2 +- bin/pg_prove | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index ae5dec46eab3..4ff132bbaae3 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Revision history for pgTAP +0.13 + 0.12 2008-10-11T04:02:42 - Updated `plan()` to disable warnings while it creates its tables. This means that `plan()` no longer send NOTICE messages when they run, diff --git a/README.pgtap b/README.pgtap index c306a8170bff..e4b40ad71979 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.12 +pgTAP 0.13 ========== pgTAP is a collection of TAP-emitting unit testing functions written in diff --git a/bin/pg_prove b/bin/pg_prove index d6693de08792..335e44562c2e 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -6,7 +6,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.12'; +our $VERSION = '0.13'; Getopt::Long::Configure (qw(bundling)); From fe400e07f010e3a875b0e5e602d6cefbec1b3f07 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 11 Oct 2008 03:58:29 +0000 Subject: [PATCH 0189/1195] To Do item. --- README.pgtap | 1 + 1 file changed, 1 insertion(+) diff --git a/README.pgtap b/README.pgtap index e4b40ad71979..e283600de235 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1514,6 +1514,7 @@ To Do * Update the Makefile to check for TAP::Harness and warn if it's not installed. * Finish porting tests to use `check_test()`. +* Add functions that return standardized version numbers, OS specs, etc. Suported Versions ----------------- From 0a0e7b790941456df7e2a028f22c7193999b9fc6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 11 Oct 2008 04:10:44 +0000 Subject: [PATCH 0190/1195] New versions. --- README.pgtap | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.pgtap b/README.pgtap index e283600de235..3a83bde7b14a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1521,10 +1521,10 @@ Suported Versions pgTAP has been tested on the following builds of PostgreSQL: -* PostgreSQL 8.3.3 on i386-apple-darwin9.4.0 -* PostgreSQL 8.2.9 on i386-apple-darwin9.4.0 -* PostgreSQL 8.1.13 on i386-apple-darwin9.4.0 -* PostgreSQL 8.0.17 on i686-apple-darwin9.4.0 +* PostgreSQL 8.3.4 on i386-apple-darwin9.5.0 +* PostgreSQL 8.2.10 on i386-apple-darwin9.5.0 +* PostgreSQL 8.1.14 on i386-apple-darwin9.5.0 +* PostgreSQL 8.0.19 on i686-apple-darwin9.5.0 If you know of others, please submit them! Use `psql -d template1 -c 'SELECT VERSION()'` to get the formal build version and OS. From 29284265ab76242905e0a3732a7ca9a46c5ee5af Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 12 Oct 2008 22:53:34 +0000 Subject: [PATCH 0191/1195] * Added `pg_version()` and `pg_version_num()`. * Documented `pg_typeof()`. --- Changes | 2 ++ README.pgtap | 50 ++++++++++++++++++++++++++++++++++++++++ compat/install-8.0.patch | 16 ++++++------- expected/util.out | 11 +++++++++ pgtap.c | 42 +++++++++++++++++++++++++++++++++ pgtap.sql.in | 10 ++++++++ sql/todotap.sql | 4 ++-- sql/util.sql | 49 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 174 insertions(+), 10 deletions(-) create mode 100644 expected/util.out create mode 100644 sql/util.sql diff --git a/Changes b/Changes index 4ff132bbaae3..30e6eae8f45e 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,8 @@ Revision history for pgTAP 0.13 + - Added `pg_version()` and `pg_version_num()`. + - Documented `pg_typeof()`. 0.12 2008-10-11T04:02:42 - Updated `plan()` to disable warnings while it creates its tables. diff --git a/README.pgtap b/README.pgtap index 3a83bde7b14a..536f9bf7efbd 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1466,6 +1466,56 @@ it gets the job done. Of course, if your diagnostics use something other than indented "have" and "want", such failures will be easier to read. But either way, do test your diagnostics! +Utility Functions +================= + +Along with the usual array of testing, planning, and diagnostic functions, +pTAP provides a few extra functions to make the work of testing more pleasant. + +### `pg_version()` ### + + SELECT pg_version(); + +Returns the server version number against which pgTAP was compiled. This is +the stringified version number displayed in the first part of the core +`version()` function and stored in the "server_version" setting: + + try=% select current_setting( 'server_version'), pg_version(); + current_setting | pg_version + -----------------+------------ + 8.3.4 | 8.3.4 + (1 row) + +### `pg_version_num()` ### + + SELECT pg_version_num(); + +Returns an integer representation of the server version number against which +pgTAP was compiled. The revision level is in the tens psotion, the minor +version in the thousands position, and the major version in the ten thousands +position and above (assuming PostgreSQL 10 is ever released, it will be in the +hundred thousands position). This value is the same as the "sever_version_num" +setting available in PostgreSQL 8.2 and higher, but supported by this function +back to PostgreSQL 8.0: + + try=% select current_setting( 'server_version_num'), pg_version_num(); + current_setting | pg_version_num + -----------------+---------------- + 80304 | 80304 + +### `pg_typeof(any)` + + SELECT pg_typeof(:value); + +Returns a `regtype` identifying the type of value passed to the function. This +function is used internally by `cmp_ok()` to properly construct types when +executing the comparison, but might be generally useful. + + try=% select pg_typeof(12), pg_typeof(100.2); + pg_typeof | pg_typeof + -----------+----------- + integer | numeric + Compatibility ============= diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index 57477ee95a9a..3cd1dbd6a450 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.orig 2008-10-10 20:36:09.000000000 -0700 -+++ pgtap.sql 2008-10-10 20:36:09.000000000 -0700 +--- pgtap.sql.orig 2008-10-12 15:50:14.000000000 -0700 ++++ pgtap.sql 2008-10-12 15:50:14.000000000 -0700 @@ -12,6 +12,14 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA,public; @@ -15,7 +15,7 @@ CREATE OR REPLACE FUNCTION pg_typeof("any") RETURNS regtype AS '$libdir/pgtap' -@@ -74,53 +82,63 @@ +@@ -84,53 +92,63 @@ CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ DECLARE @@ -96,7 +96,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -184,10 +202,12 @@ +@@ -194,10 +212,12 @@ CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ DECLARE @@ -112,7 +112,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -453,13 +473,16 @@ +@@ -463,13 +483,16 @@ want ALIAS FOR $3; descr ALIAS FOR $4; result BOOLEAN; @@ -132,7 +132,7 @@ output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE '\n' || diag( ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -1018,15 +1041,16 @@ +@@ -1028,15 +1051,16 @@ CREATE OR REPLACE FUNCTION _def_is( TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -153,7 +153,7 @@ END; $$ LANGUAGE plpgsql; -@@ -1890,6 +1914,7 @@ +@@ -1900,6 +1924,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -161,7 +161,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -1900,8 +1925,10 @@ +@@ -1910,8 +1935,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. diff --git a/expected/util.out b/expected/util.out new file mode 100644 index 000000000000..d0d05563d1ff --- /dev/null +++ b/expected/util.out @@ -0,0 +1,11 @@ +\unset ECHO +1..9 +ok 1 - pg_type(int) should work +ok 2 - pg_type(numeric) should work +ok 3 - pg_type(text) should work +ok 4 - pg_version() should return text +ok 5 - pg_version() should return same as "sever_version" setting +ok 6 - pg_version should work +ok 7 - pg_version() should return same as "sever_version" setting +ok 8 - pg_version_num() should return integer +ok 9 - pg_version_num() should be correct diff --git a/pgtap.c b/pgtap.c index 9b898e15a6bd..8eb568f1e79b 100644 --- a/pgtap.c +++ b/pgtap.c @@ -10,6 +10,8 @@ PG_MODULE_MAGIC; #endif extern Datum pg_typeof (PG_FUNCTION_ARGS); +extern Datum pg_version (PG_FUNCTION_ARGS); +extern Datum pg_version_num (PG_FUNCTION_ARGS); /* * pg_typeof() @@ -24,3 +26,43 @@ pg_typeof(PG_FUNCTION_ARGS) { PG_RETURN_OID( get_fn_expr_argtype(fcinfo->flinfo, 0) ); } + +Datum +pg_version(PG_FUNCTION_ARGS) +{ + int n = strlen(PG_VERSION); + text *ret = (text *) palloc(n + VARHDRSZ); + +#ifdef SET_VARSIZE + SET_VARSIZE(ret, n + VARHDRSZ); +#else + VARATT_SIZEP(ret) = n + VARHDRSZ; +#endif + memcpy(VARDATA(ret), PG_VERSION, n); + + PG_RETURN_TEXT_P(ret); +} + +Datum +pg_version_num(PG_FUNCTION_ARGS) +{ +#ifdef PG_VERSION_NUM + PG_RETURN_INT32(PG_VERSION_NUM); +#else + /* Code borrowed from dumputils.c. */ + int cnt; + int vmaj, + vmin, + vrev; + + cnt = sscanf(PG_VERSION, "%d.%d.%d", &vmaj, &vmin, &vrev); + + if (cnt < 2) + return -1; + + if (cnt == 2) + vrev = 0; + + PG_RETURN_INT32( (100 * vmaj + vmin) * 100 + vrev ); +#endif +} diff --git a/pgtap.sql.in b/pgtap.sql.in index fef3611ef174..e39687ce795e 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -17,6 +17,16 @@ RETURNS regtype AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; +CREATE OR REPLACE FUNCTION pg_version() +RETURNS text +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE; + +CREATE OR REPLACE FUNCTION pg_version_num() +RETURNS integer +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE; + CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ DECLARE diff --git a/sql/todotap.sql b/sql/todotap.sql index 36df57734fb2..6af980b2ccd8 100644 --- a/sql/todotap.sql +++ b/sql/todotap.sql @@ -163,8 +163,8 @@ SELECT ok( UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 29, 30, 31 ); -- Test the exception when throws_ok() is available. -SELECT CASE WHEN substring(version() from '[[:digit:]]+[.][[:digit:]]')::numeric < 8.1 - then pass('Should get an exception when todo_end() is called without todo_start()') +SELECT CASE WHEN pg_version_num() < 81000 + THEN pass('Should get an exception when todo_end() is called without todo_start()') ELSE throws_ok( 'SELECT todo_end()', 'P0001', diff --git a/sql/util.sql b/sql/util.sql new file mode 100644 index 000000000000..f289359a4fd9 --- /dev/null +++ b/sql/util.sql @@ -0,0 +1,49 @@ +\unset ECHO +\i test_setup.sql + +-- $Id$ + +SELECT plan(9); +--SELECT * FROM no_plan(); + +SELECT is( pg_typeof(42), 'integer', 'pg_type(int) should work' ); +SELECT is( pg_typeof(42.1), 'numeric', 'pg_type(numeric) should work' ); +SELECT is( pg_typeof(''::text), 'text', 'pg_type(text) should work' ); + +SELECT is( pg_typeof( pg_version() ), 'text', 'pg_version() should return text' ); +SELECT is( + pg_version(), + current_setting( 'server_version'), + 'pg_version() should return same as "sever_version" setting' +); +SELECT matches( + pg_version(), + '^8[.][[:digit:]]{1,2}[.][[:digit:]]{1,2}$', + 'pg_version should work' +); + +SELECT CASE WHEN pg_version_num() < 81000 + THEN pass( 'pg_version() should return same as "sever_version" setting' ) + ELSE is( + pg_version_num(), + current_setting( 'server_version_num')::integer, + 'pg_version() should return same as "sever_version" setting' + ) + END; + +SELECT is( + pg_typeof( pg_version_num() ), + 'integer', + 'pg_version_num() should return integer' +); +SELECT matches( + pg_version_num()::text, + '^8[[:digit:]]{4}$', + 'pg_version_num() should be correct' +); + +/****************************************************************************/ +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From dd6dc03b374d60b20732dce7818d6fd3f17e153d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 12 Oct 2008 22:54:37 +0000 Subject: [PATCH 0192/1195] Give credit where credit is due. --- pgtap.c | 1 + 1 file changed, 1 insertion(+) diff --git a/pgtap.c b/pgtap.c index 8eb568f1e79b..0fcc6dba4534 100644 --- a/pgtap.c +++ b/pgtap.c @@ -30,6 +30,7 @@ pg_typeof(PG_FUNCTION_ARGS) Datum pg_version(PG_FUNCTION_ARGS) { + /* Code borrowed from version.c. */ int n = strlen(PG_VERSION); text *ret = (text *) palloc(n + VARHDRSZ); From a8f8eb1015ff27f75710eefc4d402e61688ff76d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 12 Oct 2008 22:58:41 +0000 Subject: [PATCH 0193/1195] Cleanup. --- pgtap.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pgtap.c b/pgtap.c index 0fcc6dba4534..141c47ad1033 100644 --- a/pgtap.c +++ b/pgtap.c @@ -27,10 +27,17 @@ pg_typeof(PG_FUNCTION_ARGS) PG_RETURN_OID( get_fn_expr_argtype(fcinfo->flinfo, 0) ); } +/* + * pg_version() + * Returns the version number as output by version(), but without all the + * other crap. Code borrowed from version.c. + */ + +PG_FUNCTION_INFO_V1(pg_version); + Datum pg_version(PG_FUNCTION_ARGS) { - /* Code borrowed from version.c. */ int n = strlen(PG_VERSION); text *ret = (text *) palloc(n + VARHDRSZ); @@ -44,13 +51,20 @@ pg_version(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(ret); } +/* + * pg_version_num() + * Returns the version number as an integer. Support for pre-8.2 borrowed from + * dumputils.c. + */ + +PG_FUNCTION_INFO_V1(pg_version_num); + Datum pg_version_num(PG_FUNCTION_ARGS) { #ifdef PG_VERSION_NUM PG_RETURN_INT32(PG_VERSION_NUM); #else - /* Code borrowed from dumputils.c. */ int cnt; int vmaj, vmin, From b064e6a9445cf251d197e7edb4472bf2bb5612b2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 13 Oct 2008 18:45:17 +0000 Subject: [PATCH 0194/1195] * Updated the documentation of `skip()` to demonstrate the use of `pg_version_num()`. * Added a note about using `pg_version_num()` for skipping tests to the documentation for `pg_version_num()`. --- README.pgtap | 31 +++++++++++++++++++++---------- sql/todotap.sql | 2 +- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/README.pgtap b/README.pgtap index 536f9bf7efbd..c2c0d296296c 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1175,10 +1175,12 @@ Outputs SKIP test results. Use it in a conditional expression within a `SELECT` statement to replace the output of a test that you otherwise would have run. - SELECT CASE current_schema() - WHEN 'public' THEN is( :this, :that ) || E'\n' || is( :foo, :bar ) - ELSE skip( 'Tests not running in the "public" schema', 2 ) - END; + SELECT CASE WHEN pg_version_num() < 80100 + THEN skip('throws_ok() not supported before 8.1', 2 ) + ELSE throws_ok( 'SELECT 1/0', 22012, 'division by zero' ) + || E'\n' + || throws_ok( 'INSERT INTO try (id) VALUES (1)', '23505' ) + END; Note how use of the conditional `CASE` statement has been used to determine whether or not to run a couple of tests. If they are to be run, they are @@ -1491,12 +1493,21 @@ the stringified version number displayed in the first part of the core SELECT pg_version_num(); Returns an integer representation of the server version number against which -pgTAP was compiled. The revision level is in the tens psotion, the minor -version in the thousands position, and the major version in the ten thousands -position and above (assuming PostgreSQL 10 is ever released, it will be in the -hundred thousands position). This value is the same as the "sever_version_num" -setting available in PostgreSQL 8.2 and higher, but supported by this function -back to PostgreSQL 8.0: +pgTAP was compiled. This function is useful for determining whether or not +certain tests should be run or skipped (using `skip()`) depending on the +version of PostgreSQL. For example: + + SELECT CASE WHEN pg_version_num() < 80100 + THEN skip('throws_ok() not supported before 8.1' ) + ELSE throws_ok( 'SELECT 1/0', 22012, 'division by zero' ) + END; + +The revision level is in the tens position, the minor version in the thousands +position, and the major version in the ten thousands position and above +(assuming PostgreSQL 10 is ever released, it will be in the hundred thousands +position). This value is the same as the "sever_version_num" setting available +in PostgreSQL 8.2 and higher, but supported by this function back to +PostgreSQL 8.0: try=% select current_setting( 'server_version_num'), pg_version_num(); current_setting | pg_version_num diff --git a/sql/todotap.sql b/sql/todotap.sql index 6af980b2ccd8..ed168ccfab27 100644 --- a/sql/todotap.sql +++ b/sql/todotap.sql @@ -163,7 +163,7 @@ SELECT ok( UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 29, 30, 31 ); -- Test the exception when throws_ok() is available. -SELECT CASE WHEN pg_version_num() < 81000 +SELECT CASE WHEN pg_version_num() < 80100 THEN pass('Should get an exception when todo_end() is called without todo_start()') ELSE throws_ok( 'SELECT todo_end()', From 75c1c20417786bb91785814983bee7a06cf0587a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 13 Oct 2008 18:57:53 +0000 Subject: [PATCH 0195/1195] Timestamped for 0.13 release. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 30e6eae8f45e..34c0d1b83d34 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,6 @@ Revision history for pgTAP -0.13 +0.13 2008-10-13T19:09:46 - Added `pg_version()` and `pg_version_num()`. - Documented `pg_typeof()`. From ae36fd5ec306d5b062ae2a25988c3586152f6d99 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 13 Oct 2008 19:07:32 +0000 Subject: [PATCH 0196/1195] Incremented version number to 0.14. --- Changes | 2 ++ README.pgtap | 2 +- bin/pg_prove | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 34c0d1b83d34..fef03b226138 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Revision history for pgTAP +0.14 + 0.13 2008-10-13T19:09:46 - Added `pg_version()` and `pg_version_num()`. - Documented `pg_typeof()`. diff --git a/README.pgtap b/README.pgtap index c2c0d296296c..667ee49fc7cf 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.13 +pgTAP 0.14 ========== pgTAP is a collection of TAP-emitting unit testing functions written in diff --git a/bin/pg_prove b/bin/pg_prove index 335e44562c2e..50c227b39c5b 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -6,7 +6,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.13'; +our $VERSION = '0.14'; Getopt::Long::Configure (qw(bundling)); From ae56fa6ae35c9404520f37b44dbcad1edf0b82a0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 13 Oct 2008 21:01:14 +0000 Subject: [PATCH 0197/1195] Other changes we need to make. --- README.pgtap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index 667ee49fc7cf..7ad4daf966d6 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1574,7 +1574,7 @@ To Do the shebang line. Will likely require a `$(PERL)` environment variable. * Update the Makefile to check for TAP::Harness and warn if it's not installed. -* Finish porting tests to use `check_test()`. +* Add `has_index()` and `has_trigger()`. * Add functions that return standardized version numbers, OS specs, etc. Suported Versions From 26535958008640a501c46ae5768a45ecd613599b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 13 Oct 2008 22:59:12 +0000 Subject: [PATCH 0198/1195] Oops, forgot to properly update uninstall_pgtap.sql.in, and to set the path in that script, too! --- Changes | 5 +++++ pgtap.sql.in | 2 +- uninstall_pgtap.sql.in | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index fef03b226138..5dc0864bfdd1 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,11 @@ Revision history for pgTAP 0.14 + - Added `SET search_path` statements to `uninstall_pgtap.sql.in` so that + it will work properly when TAP is installed in its own schema. Thanks to + Ben for the catch! + - Added commands to drop `pg_version()` and `pg_version_num()` + to`uninstall_pgtap.sql.in`. 0.13 2008-10-13T19:09:46 - Added `pg_version()` and `pg_version_num()`. diff --git a/pgtap.sql.in b/pgtap.sql.in index e39687ce795e..9d6efbdd2275 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -10,7 +10,7 @@ -- $Id$ -- ## CREATE SCHEMA TAPSCHEMA; --- ## SET search_path TO TAPSCHEMA,public; +-- ## SET search_path TO TAPSCHEMA, public; CREATE OR REPLACE FUNCTION pg_typeof("any") RETURNS regtype diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 20ab95b2540f..15a98b31ad4d 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,4 +1,5 @@ -- $Id$ +-- ## SET search_path TO TAPSCHEMA, public; DROP FUNCTION check_test( TEXT, BOOLEAN ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); @@ -191,5 +192,8 @@ DROP FUNCTION _get_latest ( text ); DROP FUNCTION _get ( text ); DROP FUNCTION no_plan(); DROP FUNCTION plan( integer ); +DROP FUNCTION pg_version_num(); +DROP FUNCTION pg_version(); DROP FUNCTION pg_typeof("any"); +-- ## SET search_path TO public; -- ## DROP SCHEMA TAPSCHEMA; From d3ca738f993eda2d9d369c708e0e4591650b469f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 14 Oct 2008 19:36:08 +0000 Subject: [PATCH 0199/1195] Added has_index()`. --- Changes | 1 + README.pgtap | 45 ++++++++ compat/install-8.2.sql | 4 +- expected/index.out | 65 +++++++++++ pgtap.sql.in | 240 +++++++++++++++++++++++++++++++++++++++++ sql/index.sql | 202 ++++++++++++++++++++++++++++++++++ 6 files changed, 555 insertions(+), 2 deletions(-) create mode 100644 expected/index.out create mode 100644 sql/index.sql diff --git a/Changes b/Changes index 5dc0864bfdd1..f879b9f891a4 100644 --- a/Changes +++ b/Changes @@ -6,6 +6,7 @@ Revision history for pgTAP Ben for the catch! - Added commands to drop `pg_version()` and `pg_version_num()` to`uninstall_pgtap.sql.in`. + - Added `has_index()`. 0.13 2008-10-13T19:09:46 - Added `pg_version()` and `pg_version_num()`. diff --git a/README.pgtap b/README.pgtap index 7ad4daf966d6..758907a5f3df 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1058,6 +1058,51 @@ not exist. Just like `col_is_pk()`, except that it test that the column or array of columns have a check constraint on them. +### `has_index( schema, table, index, columns[], description )` ### +### `has_index( schema, table, index, columns[] )` ### +### `has_index( schema, table, index, column/expression, description )` ### +### `has_index( schema, table, index, columns/expression )` ### +### `has_index( table, index, columns[], description )` ### +### `has_index( table, index, columns[], description )` ### +### `has_index( table, index, column/expression, description )` ### +### `has_index( schema, table, index, column/expression )` ### +### `has_index( table, index, column/expression )` ### +### `has_index( schema, table, index )` ### +### `has_index( table, index, description )` ### +### `has_index( table, index )` ### + + SELECT has_index( + 'myschema', + 'sometable', + 'myindex', + ARRAY[ 'somecolumn', 'anothercolumn' ], + 'Index "myindex" should exist' + ); + + SELECT has_index('myschema', 'sometable', 'anidx', 'somecolumn'); + SELECT has_index('myschema', 'sometable', 'loweridx', 'LOWER(somecolumn)'); + SELECT has_index('sometable', 'someindex'); + +Checks for the existence of an index associated with the named table. The +`:schema` argument is optional, as is the column name or names or expression, +and the description. The columns argument may be a string naming one column or +an array of column names. It may also be a string representing an expression, +such as `LOWER(foo)`. If you find that the function call seems to be getting +confused, cast the index name to the `NAME` type: + + SELECT has_index( 'public', 'sometab', 'idx_foo', 'name'::name ); + +If the index does not exist, `has_column()` will output a diagnostic message +such as: + + # Index "blah" ON public.sometab not found + +If the index was found but the column specification or expression is +incorrect, the diagnostics will look more like this: + + # have: "idx_baz" ON public.sometab(lower(name)) + # want: "idx_baz" ON public.sometab(lower(lname)) + ### `can( schema, functions[], description )` ### ### `can( schema, functions[] )` ### ### `can( functions[], description )` ### diff --git a/compat/install-8.2.sql b/compat/install-8.2.sql index a0d5b69defb7..1b7da3ceab2b 100644 --- a/compat/install-8.2.sql +++ b/compat/install-8.2.sql @@ -7,11 +7,11 @@ LANGUAGE sql IMMUTABLE STRICT; CREATE CAST (boolean AS text) WITH FUNCTION booltext(boolean) AS IMPLICIT; -- Cast name[]s to text like 8.3 does. -CREATE OR REPLACE FUNCTION namearray_text(name[]) +CREATE OR REPLACE FUNCTION anyarray_text(anyarray) RETURNS TEXT AS 'SELECT textin(array_out($1));' LANGUAGE sql IMMUTABLE STRICT; -CREATE CAST (name[] AS text) WITH FUNCTION namearray_text(name[]) AS IMPLICIT; +CREATE CAST (name[] AS text) WITH FUNCTION anyrray_text(anyarray) AS IMPLICIT; -- Compare name[]s more or less like 8.3 does. CREATE OR REPLACE FUNCTION namearray_eq( name[], name[] ) diff --git a/expected/index.out b/expected/index.out new file mode 100644 index 000000000000..a6de018b705a --- /dev/null +++ b/expected/index.out @@ -0,0 +1,65 @@ +\unset ECHO +1..63 +ok 1 - has_index() single column should pass +ok 2 - has_index() single column should have the proper description +ok 3 - has_index() single column should have the proper diagnostics +ok 4 - has_index() single column no desc should pass +ok 5 - has_index() single column no desc should have the proper description +ok 6 - has_index() single column no desc should have the proper diagnostics +ok 7 - has_index() multi-column should pass +ok 8 - has_index() multi-column should have the proper description +ok 9 - has_index() multi-column should have the proper diagnostics +ok 10 - has_index() multi-column no desc should pass +ok 11 - has_index() multi-column no desc should have the proper description +ok 12 - has_index() multi-column no desc should have the proper diagnostics +ok 13 - has_index() functional should pass +ok 14 - has_index() functional should have the proper description +ok 15 - has_index() functional should have the proper diagnostics +ok 16 - has_index() no cols should pass +ok 17 - has_index() no cols should have the proper description +ok 18 - has_index() no cols should have the proper diagnostics +ok 19 - has_index() no cols no desc should pass +ok 20 - has_index() no cols no desc should have the proper description +ok 21 - has_index() no cols no desc should have the proper diagnostics +ok 22 - has_index() no schema single column should pass +ok 23 - has_index() no schema single column should have the proper description +ok 24 - has_index() no schema single column should have the proper diagnostics +ok 25 - has_index() no schema single column no desc should pass +ok 26 - has_index() no schema single column no desc should have the proper description +ok 27 - has_index() no schema single column no desc should have the proper diagnostics +ok 28 - has_index() no schema multi-column should pass +ok 29 - has_index() no schema multi-column should have the proper description +ok 30 - has_index() no schema multi-column should have the proper diagnostics +ok 31 - has_index() no schema multi-column no desc should pass +ok 32 - has_index() no schema multi-column no desc should have the proper description +ok 33 - has_index() no schema multi-column no desc should have the proper diagnostics +ok 34 - has_index() no schema functional should pass +ok 35 - has_index() no schema functional should have the proper description +ok 36 - has_index() no schema functional should have the proper diagnostics +ok 37 - has_index() no schema functional no desc should pass +ok 38 - has_index() no schema functional no desc should have the proper description +ok 39 - has_index() no schema functional no desc should have the proper diagnostics +ok 40 - has_index() no schema or cols should pass +ok 41 - has_index() no schema or cols should have the proper description +ok 42 - has_index() no schema or cols should have the proper diagnostics +ok 43 - has_index() no schema or cols or desc should pass +ok 44 - has_index() no schema or cols or desc should have the proper description +ok 45 - has_index() no schema or cols or desc should have the proper diagnostics +ok 46 - has_index() missing should fail +ok 47 - has_index() missing should have the proper description +ok 48 - has_index() missing should have the proper diagnostics +ok 49 - has_index() invalid should fail +ok 50 - has_index() invalid should have the proper description +ok 51 - has_index() invalid should have the proper diagnostics +ok 52 - has_index() missing no schema should fail +ok 53 - has_index() missing no schema should have the proper description +ok 54 - has_index() missing no schema should have the proper diagnostics +ok 55 - has_index() invalid no schema should fail +ok 56 - has_index() invalid no schema should have the proper description +ok 57 - has_index() invalid no schema should have the proper diagnostics +ok 58 - has_index() functional fail should fail +ok 59 - has_index() functional fail should have the proper description +ok 60 - has_index() functional fail should have the proper diagnostics +ok 61 - has_index() functional fail no schema should fail +ok 62 - has_index() functional fail no schema should have the proper description +ok 63 - has_index() functional fail no schema should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 9d6efbdd2275..78b8e40a8062 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1890,6 +1890,246 @@ RETURNS TEXT AS $$ SELECT can( $1, 'Schema ' || array_to_string(current_schemas(true), ' or ') || ' can' ); $$ LANGUAGE sql; +CREATE OR REPLACE FUNCTION _ikeys( NAME, NAME, NAME) +RETURNS NAME[] AS $$ + SELECT ARRAY( + SELECT a.attname + 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_attribute a ON (ct.oid = a.attrelid) + WHERE ct.relname = $2 + AND ci.relname = $3 + AND n.nspname = $1 + AND a.attnum = ANY(x.indkey::int[]) + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _ikeys( NAME, NAME) +RETURNS NAME[] AS $$ + SELECT ARRAY( + SELECT a.attname + 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_attribute a ON (ct.oid = a.attrelid) + WHERE ct.relname = $1 + AND ci.relname = $2 + AND a.attnum = ANY(x.indkey::int[]) + AND pg_catalog.pg_table_is_visible(ct.oid) + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME, NAME) +RETURNS TEXT AS $$ + SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) + 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; +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME) +RETURNS TEXT AS $$ + SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) + 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 = '{}'::name[] THEN + RETURN ok( false, $5 ) || E'\\n' + || diag( 'Index "' || $3 || '" ON ' || $1 || '.' || $2 || ' not found'); + END IF; + + RETURN is( + '"' || $3 || '" ON ' || $1 || '.' || $2 || '(' || array_to_string( index_cols, ', ' ) || ')', + '"' || $3 || '" ON ' || $1 || '.' || $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 "' || $3 || '" should exist' ); +$$ LANGUAGE sql; + +-- has_index( schema, table, index, column/expression, description ) +CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME, text ) +RETURNS TEXT AS $$ +DECLARE + expr text; +BEGIN + IF $4 NOT LIKE '%(%' THEN + -- Not a functional index. + RETURN has_index( $1, $2, $3, ARRAY[$4], $5 ); + END IF; + + -- Get the functional expression. + expr := _iexpr($1, $2, $3); + + IF expr IS NULL THEN + RETURN ok( false, $5 ) || E'\\n' + || diag( 'Index "' || $3 || '" ON ' || $1 || '.' || $2 || ' not found'); + END IF; + + RETURN is( + '"' || $3 || '" ON ' || $1 || '.' || $2 || '(' || expr || ')', + '"' || $3 || '" ON ' || $1 || '.' || $2 || '(' || LOWER($4) || ')', + $5 + ); +END; +$$ LANGUAGE plpgsql; + +-- has_index( schema, table, index, columns/expression ) +CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT has_index( $1, $2, $3, $4, 'Index "' || $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 = '{}'::name[] THEN + RETURN ok( false, $4 ) || E'\\n' + || diag( 'Index "' || $2 || '" ON ' || $1 || ' not found'); + END IF; + + RETURN is( + '"' || $2 || '" ON ' || $1 || '(' || array_to_string( index_cols, ', ' ) || ')', + '"' || $2 || '" ON ' || $1 || '(' || array_to_string( $3, ', ' ) || ')', + $4 + ); +END; +$$ LANGUAGE plpgsql; + +-- has_index( table, index, columns[], description ) +CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT has_index( $1, $2, $3, 'Index "' || $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 = LOWER($1) ); +$$ LANGUAGE sql; + +-- _has_index( schema, table, index ) +CREATE OR REPLACE FUNCTION _has_index( NAME, NAME, NAME ) +RETURNS boolean AS $$ + SELECT EXISTS ( SELECT _iexpr( $1, $2, $3 ) ); +$$ LANGUAGE sql; + +-- _has_index( table, index ) +CREATE OR REPLACE FUNCTION _has_index( NAME, NAME ) +RETURNS boolean AS $$ + SELECT EXISTS ( SELECT _iexpr( $1, $2 ) ); +$$ 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 $$ +DECLARE + want_expr text; + descr text; + have_expr text; + idx name; + tab text; +BEGIN + IF $3 NOT LIKE '%(%' THEN + -- Not a functional index. + IF _is_schema( $1 ) THEN + -- Looking for schema.table index. + RETURN ok ( _has_index( $1, $2, $3 ), $4); + END IF; + -- Looking for particular columns. + RETURN has_index( $1, $2, ARRAY[$3], $4 ); + END IF; + + -- Get the functional expression. + IF _is_schema( $1 ) THEN + -- Looking for an index within a schema. + have_expr := _iexpr($1, $2, $3); + want_expr := lower($4); + descr := 'Index "' || $3 || '" should exist'; + idx := $3; + tab := $1 || '.' || $2; + ELSE + -- Looking for an index without a schema spec. + have_expr := _iexpr($1, $2); + want_expr := lower($3); + descr := $4; + idx := $2; + tab := $1; + END IF; + + IF have_expr IS NULL THEN + RETURN ok( false, descr ) || E'\\n' + || diag( 'Index "' || idx || '" ON ' || tab || ' not found'); + END IF; + + RETURN is( + '"' || idx || '" ON ' || tab || '(' || have_expr || ')', + '"' || idx || '" ON ' || tab || '(' || want_expr || ')', + descr + ); +END; +$$ LANGUAGE plpgsql; + +-- 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( _has_index( $1, $2, $3 ), 'Index "' || $3 || '" should exist' ); + ELSE + -- ( table, index, column/expression ) + RETURN has_index( $1, $2, $3, 'Index "' || $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( _has_index( $1, $2 ), $3 ) + END; +$$ LANGUAGE sql; + +-- has_index( table, index ) +CREATE OR REPLACE FUNCTION has_index ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( _has_index( $1, $2 ), 'Index "' || $2 || '" should exist' ); +$$ LANGUAGE sql; + -- check_test( test_output, pass, name, description, diag ) CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) RETURNS SETOF TEXT AS $$ diff --git a/sql/index.sql b/sql/index.sql new file mode 100644 index 000000000000..2342b6cae821 --- /dev/null +++ b/sql/index.sql @@ -0,0 +1,202 @@ +\unset ECHO +\i test_setup.sql + +-- $Id$ + +SELECT plan(63); +--SELECT * FROM no_plan(); + +-- This will be rolled back. :-) +SET client_min_messages = warning; +CREATE TABLE public.sometab( + id INT NOT NULL PRIMARY KEY, + name TEXT DEFAULT '' UNIQUE, + numb NUMERIC(10, 2), + myint NUMERIC(8) +); +CREATE INDEX idx_foo ON public.sometab(name); +CREATE INDEX idx_bar ON public.sometab(name, numb); +CREATE INDEX idx_baz ON public.sometab(LOWER(name)); +RESET client_min_messages; + +/****************************************************************************/ +-- Test has_index(). + +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'idx_foo', 'name', 'whatever' ), + true, + 'has_index() single column', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'idx_foo', 'name'::name ), + true, + 'has_index() single column no desc', + 'Index "idx_foo" should exist', + '' +); + +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'idx_bar', ARRAY['name', 'numb'], 'whatever' ), + true, + 'has_index() multi-column', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'idx_bar', ARRAY['name', 'numb'] ), + true, + 'has_index() multi-column no desc', + 'Index "idx_bar" should exist', + '' +); + +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'idx_baz', 'LOWER(name)', 'whatever' ), + true, + 'has_index() functional', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'idx_baz', 'whatever' ), + true, + 'has_index() no cols', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'idx_baz'::name ), + true, + 'has_index() no cols no desc', + 'Index "idx_baz" should exist', + '' +); + +SELECT * FROM check_test( + has_index( 'sometab', 'idx_foo', 'name', 'whatever' ), + true, + 'has_index() no schema single column', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_index( 'sometab', 'idx_foo', 'name'::name ), + true, + 'has_index() no schema single column no desc', + 'Index "idx_foo" should exist', + '' +); + +SELECT * FROM check_test( + has_index( 'sometab', 'idx_bar', ARRAY['name', 'numb'], 'whatever' ), + true, + 'has_index() no schema multi-column', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_index( 'sometab', 'idx_bar', ARRAY['name', 'numb'] ), + true, + 'has_index() no schema multi-column no desc', + 'Index "idx_bar" should exist', + '' +); + +SELECT * FROM check_test( + has_index( 'sometab', 'idx_baz', 'LOWER(name)', 'whatever' ), + true, + 'has_index() no schema functional', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_index( 'sometab', 'idx_baz', 'LOWER(name)' ), + true, + 'has_index() no schema functional no desc', + 'Index "idx_baz" should exist', + '' +); + +SELECT * FROM check_test( + has_index( 'sometab', 'idx_baz', 'whatever' ), + true, + 'has_index() no schema or cols', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_index( 'sometab', 'idx_baz' ), + true, + 'has_index() no schema or cols or desc', + 'Index "idx_baz" should exist', + '' +); + +-- Check failure diagnostics. +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'blah', ARRAY['name', 'numb'], 'whatever' ), + false, + 'has_index() missing', + 'whatever', + 'Index "blah" ON public.sometab not found' +); + +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'idx_bar', ARRAY['name', 'id'], 'whatever' ), + false, + 'has_index() invalid', + 'whatever', + ' have: "idx_bar" ON public.sometab(name, numb) + want: "idx_bar" ON public.sometab(name, id)' +); + +SELECT * FROM check_test( + has_index( 'sometab', 'blah', ARRAY['name', 'numb'], 'whatever' ), + false, + 'has_index() missing no schema', + 'whatever', + 'Index "blah" ON sometab not found' +); + +SELECT * FROM check_test( + has_index( 'sometab', 'idx_bar', ARRAY['name', 'id'], 'whatever' ), + false, + 'has_index() invalid no schema', + 'whatever', + ' have: "idx_bar" ON sometab(name, numb) + want: "idx_bar" ON sometab(name, id)' +); + +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'idx_baz', 'LOWER(wank)', 'whatever' ), + false, + 'has_index() functional fail', + 'whatever', + ' have: "idx_baz" ON public.sometab(lower(name)) + want: "idx_baz" ON public.sometab(lower(wank))' +); + +SELECT * FROM check_test( + has_index( 'sometab', 'idx_baz', 'LOWER(wank)', 'whatever' ), + false, + 'has_index() functional fail no schema', + 'whatever', + ' have: "idx_baz" ON sometab(lower(name)) + want: "idx_baz" ON sometab(lower(wank))' +); + + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From 65bce408168a17fb4113a16bc10ed3388de0a873 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 14 Oct 2008 19:36:58 +0000 Subject: [PATCH 0200/1195] Updated todo list. --- README.pgtap | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index 758907a5f3df..edadef9b71d9 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1619,7 +1619,8 @@ To Do the shebang line. Will likely require a `$(PERL)` environment variable. * Update the Makefile to check for TAP::Harness and warn if it's not installed. -* Add `has_index()` and `has_trigger()`. +* Add `index_is_type()`, `index_is_unique()`, `index_is_pk()`, and + `has_trigger()`. * Add functions that return standardized version numbers, OS specs, etc. Suported Versions From 9b029bd77c9c38cf08e743eae1a946efadd8825e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 14 Oct 2008 19:37:36 +0000 Subject: [PATCH 0201/1195] Updated todo. --- README.pgtap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index edadef9b71d9..be8ff09434d2 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1621,7 +1621,7 @@ To Do installed. * Add `index_is_type()`, `index_is_unique()`, `index_is_pk()`, and `has_trigger()`. -* Add functions that return standardized version numbers, OS specs, etc. +* Add a function to return an OS identifier. Suported Versions ----------------- From 598f3f90dba165e926e12e2b6dda04db3b85f0b4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 14 Oct 2008 21:26:56 +0000 Subject: [PATCH 0202/1195] Addd `os_name()`. --- Changes | 4 ++++ Makefile | 56 +++++++++++++++++++++++++++++++++++++++++++++-- README.pgtap | 19 ++++++++++++++-- expected/util.out | 3 ++- pgtap.sql.in | 4 ++++ sql/util.sql | 8 ++++++- 6 files changed, 88 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index f879b9f891a4..4ec9dde84d69 100644 --- a/Changes +++ b/Changes @@ -7,6 +7,10 @@ Revision history for pgTAP - Added commands to drop `pg_version()` and `pg_version_num()` to`uninstall_pgtap.sql.in`. - Added `has_index()`. + - Added `os_name()`. This is somewhat experimental. If you have `uname`, + it's probably correct, but assistance in improving OS detection in the + `Makefile` would be greatly appreciated. Notably, it does not detect + Windows. 0.13 2008-10-13T19:09:46 - Added `pg_version()` and `pg_version_num()`. diff --git a/Makefile b/Makefile index ce2e60ff44b5..c396293cd7dc 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,58 @@ REGRESS := $(filter-out throwtap,$(REGRESS)) endif endif +# Determine the OS. Borrowed from Perl's Configure. +ifeq ($(wildcard /irix), /irix) +OSNAME=irix +endif +ifeq ($(wildcard /xynix), /xynix) +OSNAME=sco_xenix +endif +ifeq ($(wildcard /dynix), /dynix) +OSNAME=dynix +endif +ifeq ($(wildcard /dnix), /dnix) +OSNAME=dnxi +endif +ifeq ($(wildcard /lynx.os), /lynx.os) +OSNAME=lynxos +endif +ifeq ($(wildcard /unicos), /unicox) +OSNAME=unicos +endif +ifeq ($(wildcard /unicosmk), /unicosmk) +OSNAME=unicosmk +endif +ifeq ($(wildcard /unicosmk.ar), /unicosmk.ar) +OSNAME=unicosmk +endif +ifeq ($(wildcard /bin/mips), /bin/mips) +OSNAME=mips +endif +ifeq ($(wildcard /usr/apollo/bin), /usr/apollo/bin) +OSNAME=apollo +endif +ifeq ($(wildcard /etc/saf/_sactab), /etc/saf/_sactab) +OSNAME=svr4 +endif +ifeq ($(wildcard /usr/include/minix), /usr/include/minix) +OSNAME=minix +endif +ifeq ($(wildcard /system/gnu_library/bin/ar.pm), /system/gnu_library/bin/ar.pm) +OSNAME=vos +endif +ifeq ($(wildcard /MachTen), /MachTen) +OSNAME=machten +endif +ifeq ($(wildcard /sys/posix.dll), /sys/posix.dll) +OSNAME=uwin +endif + +# Fallback on uname, if it's available. +ifndef OSNAME +OSNAME = $(shell uname | awk '{print tolower($1)}') +endif + # Override how .sql targets are processed to add the schema info, if # necessary. Otherwise just copy the files. test_setup.sql: test_setup.sql.in @@ -50,9 +102,9 @@ endif pgtap.sql: pgtap.sql.in test_setup.sql ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' $(REMOVE_E) pgtap.sql.in > pgtap.sql + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' $(REMOVE_E) pgtap.sql.in > pgtap.sql else - sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' $(REMOVE_E) pgtap.sql.in > pgtap.sql + sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' $(REMOVE_E) pgtap.sql.in > pgtap.sql endif ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 3) diff --git a/README.pgtap b/README.pgtap index be8ff09434d2..8dd1c7542fe9 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1559,7 +1559,23 @@ PostgreSQL 8.0: -----------------+---------------- 80304 | 80304 -### `pg_typeof(any)` +### `os_name()` ### + + SELECT os_name(); + +Returns a string representing the name of the operating system on which pgTAP +was compiled. This can be useful for determining whether or not to skip tests +on certain operating systems. + +This is usually the same a the ouput of `uname`, but converted to lower case. +There are some semantics in the pgTAP build process to detect other operating +systems, though assistance in improving such detection would be greatly +appreciated. + +**NOTE:** The values returned by this function may change in the future, +depending on how good the pgTAP build process gets at detecting a OS. + +### `pg_typeof(any)` ### SELECT pg_typeof(:value); @@ -1621,7 +1637,6 @@ To Do installed. * Add `index_is_type()`, `index_is_unique()`, `index_is_pk()`, and `has_trigger()`. -* Add a function to return an OS identifier. Suported Versions ----------------- diff --git a/expected/util.out b/expected/util.out index d0d05563d1ff..d0b471397a65 100644 --- a/expected/util.out +++ b/expected/util.out @@ -1,5 +1,5 @@ \unset ECHO -1..9 +1..10 ok 1 - pg_type(int) should work ok 2 - pg_type(numeric) should work ok 3 - pg_type(text) should work @@ -9,3 +9,4 @@ ok 6 - pg_version should work ok 7 - pg_version() should return same as "sever_version" setting ok 8 - pg_version_num() should return integer ok 9 - pg_version_num() should be correct +ok 10 - os_name() should output something like an OS name diff --git a/pgtap.sql.in b/pgtap.sql.in index 78b8e40a8062..719895495171 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -27,6 +27,10 @@ RETURNS integer AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; +CREATE OR REPLACE FUNCTION os_name() +RETURNS TEXT AS 'SELECT ''__OS__''::text;' +LANGUAGE SQL IMMUTABLE; + CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ DECLARE diff --git a/sql/util.sql b/sql/util.sql index f289359a4fd9..195c1dbff604 100644 --- a/sql/util.sql +++ b/sql/util.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(9); +SELECT plan(10); --SELECT * FROM no_plan(); SELECT is( pg_typeof(42), 'integer', 'pg_type(int) should work' ); @@ -42,6 +42,12 @@ SELECT matches( 'pg_version_num() should be correct' ); +SELECT matches( + os_name(), + '^[[:alpha:]]+$', + 'os_name() should output something like an OS name' +); + /****************************************************************************/ /****************************************************************************/ -- Finish the tests and clean up. From 2fab29aa3c3e434ee6a6f497b728d2d74c0763e4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 17 Oct 2008 16:08:15 +0000 Subject: [PATCH 0203/1195] * Added better handling of `NULL` results in `ok()`. * Fixed an issue with `check_test()` where it was removing one character too many before testing a diagnostic message. * Fixed an issue in the new `has_index()` functions where an extra backslash was jammed into diagnostic output. --- Changes | 5 +++++ README.pgtap | 8 ++++++++ expected/moretap.out | 11 +++++++---- pgtap.sql.in | 23 ++++++++++++++--------- sql/cantap.sql | 4 ++-- sql/check.sql | 4 ++-- sql/cmpok.sql | 4 ++-- sql/coltap.sql | 6 +++--- sql/fktap.sql | 22 +++++++++++----------- sql/index.sql | 8 ++++---- sql/moretap.sql | 3 ++- sql/pktap.sql | 10 +++++----- sql/throwtap.sql | 4 ++-- sql/unique.sql | 4 ++-- 14 files changed, 69 insertions(+), 47 deletions(-) diff --git a/Changes b/Changes index 4ec9dde84d69..660c70e43894 100644 --- a/Changes +++ b/Changes @@ -11,6 +11,11 @@ Revision history for pgTAP it's probably correct, but assistance in improving OS detection in the `Makefile` would be greatly appreciated. Notably, it does not detect Windows. + - Made `ok()` smarter when the test result is passed as `NULL`. It was + dying, but now it simply fails and attaches a diagnostic message + reporting that the test result was `NULL`. Reported by Jason Gordon. + - Fixed an issue in `check_test()` where an extra character was removed + from the beginning of the diagnostic output before testing it. 0.13 2008-10-13T19:09:46 - Added `pg_version()` and `pg_version_num()`. diff --git a/README.pgtap b/README.pgtap index 8dd1c7542fe9..a4f2a0a2c229 100644 --- a/README.pgtap +++ b/README.pgtap @@ -289,6 +289,14 @@ Should an `ok()` fail, it will produce some diagnostics: not ok 18 - sufficient mucus # Failed test 18: "sufficient mucus" +Furthermore, should the boolean test result argument be passed as a `NULL` +rather than `true` or `false`, `ok()` will assume a test failure and attach an +additional diagnostic: + + not ok 18 - sufficient mucus + # Failed test 18: "sufficient mucus" + # (test result was NULL) + ### `is( anyelement, anyelement, description )` ### ### `is( anyelement, anyelement )` ### ### `isnt( anyelement, anyelement, description )` ### diff --git a/expected/moretap.out b/expected/moretap.out index 753cb1010ec8..3f32ead49203 100644 --- a/expected/moretap.out +++ b/expected/moretap.out @@ -1,5 +1,5 @@ \unset ECHO -1..37 +1..40 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -34,6 +34,9 @@ ok 31 - ok(false, '') should have the proper diagnostics ok 32 - ok(false, 'foo') should fail ok 33 - ok(false, 'foo') should have the proper description ok 34 - ok(false, 'foo') should have the proper diagnostics -ok 35 - multiline desc should pass -ok 36 - multiline desc should have the proper description -ok 37 - multiline desc should have the proper diagnostics +ok 35 - ok(NULL, 'null') should fail +ok 36 - ok(NULL, 'null') should have the proper description +ok 37 - ok(NULL, 'null') should have the proper diagnostics +ok 38 - multiline desc should pass +ok 39 - multiline desc should have the proper description +ok 40 - multiline desc should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 719895495171..47941be00fe3 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -263,20 +263,24 @@ CREATE OR REPLACE FUNCTION ok ( boolean, text ) RETURNS TEXT AS $$ DECLARE aok ALIAS FOR $1; - descr ALIAS FOR $2; + 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 aok ELSE TRUE END; + 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, - aok, + COALESCE(aok, false), descr, CASE WHEN todo_why IS NULL THEN '' ELSE 'todo' END, COALESCE(todo_why, '') @@ -290,7 +294,8 @@ BEGIN diag('Failed ' || CASE WHEN todo_why IS NULL THEN '' ELSE '(TODO) ' END || 'test ' || test_num || - CASE descr WHEN '' THEN '' ELSE COALESCE(': "' || descr || '"', '') END ) + 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; @@ -1957,7 +1962,7 @@ BEGIN index_cols := _ikeys($1, $2, $3 ); IF index_cols = '{}'::name[] THEN - RETURN ok( false, $5 ) || E'\\n' + RETURN ok( false, $5 ) || E'\n' || diag( 'Index "' || $3 || '" ON ' || $1 || '.' || $2 || ' not found'); END IF; @@ -1990,7 +1995,7 @@ BEGIN expr := _iexpr($1, $2, $3); IF expr IS NULL THEN - RETURN ok( false, $5 ) || E'\\n' + RETURN ok( false, $5 ) || E'\n' || diag( 'Index "' || $3 || '" ON ' || $1 || '.' || $2 || ' not found'); END IF; @@ -2017,7 +2022,7 @@ BEGIN index_cols := _ikeys($1, $2 ); IF index_cols = '{}'::name[] THEN - RETURN ok( false, $4 ) || E'\\n' + RETURN ok( false, $4 ) || E'\n' || diag( 'Index "' || $2 || '" ON ' || $1 || ' not found'); END IF; @@ -2092,7 +2097,7 @@ BEGIN END IF; IF have_expr IS NULL THEN - RETURN ok( false, descr ) || E'\\n' + RETURN ok( false, descr ) || E'\n' || diag( 'Index "' || idx || '" ON ' || tab || ' not found'); END IF; @@ -2198,7 +2203,7 @@ BEGIN adiag := substring( adiag FROM 14 + char_length(tnumb::text) - + CASE adescr WHEN '' THEN 3 ELSE 4 + char_length( diag( adescr ) ) END + + CASE adescr WHEN '' THEN 3 ELSE 3 + char_length( diag( adescr ) ) END ); END IF; diff --git a/sql/cantap.sql b/sql/cantap.sql index 411327c6eef3..a586f1e4924a 100644 --- a/sql/cantap.sql +++ b/sql/cantap.sql @@ -154,7 +154,7 @@ SELECT * FROM check_test( false, 'fail can(schema) with desc', 'whatever', - ' pg_catalog.foo() missing + ' pg_catalog.foo() missing pg_catalog.bar() missing' ); @@ -179,7 +179,7 @@ SELECT * FROM check_test( false, 'fail can() with desc', 'whatever', - ' foo() missing + ' foo() missing bar() missing' ); diff --git a/sql/check.sql b/sql/check.sql index 5f7b4bfbeef7..c342d80e6ba4 100644 --- a/sql/check.sql +++ b/sql/check.sql @@ -90,7 +90,7 @@ SELECT * FROM check_test( false, 'col_has_check( sch, tab, col, desc ) fail', 'public.sometab.id should be a pk', - ' have: {name} + ' have: {name} want: {id}' ); @@ -99,7 +99,7 @@ SELECT * FROM check_test( false, 'col_has_check( tab, col, desc ) fail', 'sometab.id should be a pk', - ' have: {name} + ' have: {name} want: {id}' ); diff --git a/sql/cmpok.sql b/sql/cmpok.sql index e65c5f347b13..f77a7af8a3ac 100644 --- a/sql/cmpok.sql +++ b/sql/cmpok.sql @@ -69,7 +69,7 @@ SELECT * FROM check_test( false, 'cmp_ok() fail', '1 should = 2', - ' ''1'' + ' ''1'' = ''2''' ); @@ -79,7 +79,7 @@ SELECT * FROM check_test( false, 'cmp_ok() NULL fail', '1 should = NULL', - ' ''1'' + ' ''1'' = NULL' ); diff --git a/sql/coltap.sql b/sql/coltap.sql index d3fcfe22c37d..3638acd2c5b7 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -124,7 +124,7 @@ SELECT * FROM check_test( false, 'col_type_is( tab, col, type ) fail', 'Column sometab(name) should be type int4', - ' have: text + ' have: text want: int4' ); @@ -144,7 +144,7 @@ SELECT * FROM check_test( false, 'col_type_is precision fail', 'should be numeric(7)', - ' have: numeric(8,0) + ' have: numeric(8,0) want: numeric(7)' ); @@ -164,7 +164,7 @@ SELECT * FROM check_test( false, 'col_default_is() fail', 'name should default to ''foo''', - ' have: + ' have: want: foo' ); diff --git a/sql/fktap.sql b/sql/fktap.sql index 30074f637403..d6f91d4d051d 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -143,7 +143,7 @@ SELECT * FROM check_test( false, 'col_is_fk( schema, table, column, description )', 'public.fk.name should be an fk', - ' Table public.fk has foreign key constraints on these columns: + ' Table public.fk has foreign key constraints on these columns: {pk_id}' ); @@ -152,7 +152,7 @@ SELECT * FROM check_test( false, 'col_is_fk( table, column, description )', 'fk3.name should be an fk', - ' Table fk3 has foreign key constraints on these columns: + ' Table fk3 has foreign key constraints on these columns: {pk2_num,pk2_dot} {pk_id}' ); @@ -171,7 +171,7 @@ SELECT * FROM check_test( false, 'col_is_fk with no FKs', 'pk.name should be an fk', - ' Table public.pk has no foreign key columns' + ' Table public.pk has no foreign key columns' ); SELECT * FROM check_test( @@ -179,7 +179,7 @@ SELECT * FROM check_test( false, 'col_is_fk with no FKs', 'Column pk(name) should be a foreign key', - ' Table pk has no foreign key columns' + ' Table pk has no foreign key columns' ); @@ -384,7 +384,7 @@ SELECT * FROM check_test( false, 'fk_ok fail', 'WHATEVER', - ' have: public.fk(pk_id) REFERENCES public.pk(id) + ' have: public.fk(pk_id) REFERENCES public.pk(id) want: public.fk(pk_id) REFERENCES public.pk(fid)' ); @@ -393,7 +393,7 @@ SELECT * FROM check_test( false, 'fk_ok fail desc', 'public.fk(pk_id) should reference public.pk(fid)', - ' have: public.fk(pk_id) REFERENCES public.pk(id) + ' have: public.fk(pk_id) REFERENCES public.pk(id) want: public.fk(pk_id) REFERENCES public.pk(fid)' ); @@ -402,7 +402,7 @@ SELECT * FROM check_test( false, 'fk_ok fail no schema', 'WHATEVER', - ' have: fk(pk_id) REFERENCES pk(id) + ' have: fk(pk_id) REFERENCES pk(id) want: fk(pk_id) REFERENCES pk(fid)' ); @@ -411,7 +411,7 @@ SELECT * FROM check_test( false, 'fk_ok fail no schema desc', 'fk(pk_id) should reference pk(fid)', - ' have: fk(pk_id) REFERENCES pk(id) + ' have: fk(pk_id) REFERENCES pk(id) want: fk(pk_id) REFERENCES pk(fid)' ); @@ -420,7 +420,7 @@ SELECT * FROM check_test( false, 'fk_ok bad PK test', 'WHATEVER', - ' have: fk(pk_id) REFERENCES pk(id) + ' have: fk(pk_id) REFERENCES pk(id) want: fk(pk_id) REFERENCES ok(fid)' ); @@ -459,7 +459,7 @@ SELECT * FROM check_test( false, 'missing fk test', 'public.fk3(id) should reference public.foo(id)', - ' have: public.fk3(id) REFERENCES NOTHING + ' have: public.fk3(id) REFERENCES NOTHING want: public.fk3(id) REFERENCES public.foo(id)' ); @@ -472,7 +472,7 @@ SELECT * FROM check_test( false, 'bad FK column test', 'fk3(pk2_blah, pk2_dot) should reference pk2(num, dot)', - ' have: fk3(pk2_blah, pk2_dot) REFERENCES NOTHING + ' have: fk3(pk2_blah, pk2_dot) REFERENCES NOTHING want: fk3(pk2_blah, pk2_dot) REFERENCES pk2(num, dot)' ); diff --git a/sql/index.sql b/sql/index.sql index 2342b6cae821..f806a6f4fdc4 100644 --- a/sql/index.sql +++ b/sql/index.sql @@ -156,7 +156,7 @@ SELECT * FROM check_test( false, 'has_index() invalid', 'whatever', - ' have: "idx_bar" ON public.sometab(name, numb) + ' have: "idx_bar" ON public.sometab(name, numb) want: "idx_bar" ON public.sometab(name, id)' ); @@ -173,7 +173,7 @@ SELECT * FROM check_test( false, 'has_index() invalid no schema', 'whatever', - ' have: "idx_bar" ON sometab(name, numb) + ' have: "idx_bar" ON sometab(name, numb) want: "idx_bar" ON sometab(name, id)' ); @@ -182,7 +182,7 @@ SELECT * FROM check_test( false, 'has_index() functional fail', 'whatever', - ' have: "idx_baz" ON public.sometab(lower(name)) + ' have: "idx_baz" ON public.sometab(lower(name)) want: "idx_baz" ON public.sometab(lower(wank))' ); @@ -191,7 +191,7 @@ SELECT * FROM check_test( false, 'has_index() functional fail no schema', 'whatever', - ' have: "idx_baz" ON sometab(lower(name)) + ' have: "idx_baz" ON sometab(lower(name)) want: "idx_baz" ON sometab(lower(wank))' ); diff --git a/sql/moretap.sql b/sql/moretap.sql index 81e67c7f2bff..2df92ff3f2ea 100644 --- a/sql/moretap.sql +++ b/sql/moretap.sql @@ -3,7 +3,7 @@ -- $Id$ -\set numb_tests 37 +\set numb_tests 40 SELECT plan(:numb_tests); -- Replace the internal record of the plan for a few tests. @@ -86,6 +86,7 @@ SELECT * FROM check_test( ok(true, 'foo'), true, 'ok(true, ''foo'')', 'foo', '' SELECT * FROM check_test( ok(false), false, 'ok(false)', '', '' ); SELECT * FROM check_test( ok(false, ''), false, 'ok(false, '''')', '', '' ); SELECT * FROM check_test( ok(false, 'foo'), false, 'ok(false, ''foo'')', 'foo', '' ); +SELECT * FROM check_test( ok(NULL, 'null'), false, 'ok(NULL, ''null'')', 'null', ' (test result was NULL)' ); /****************************************************************************/ -- test multiline description. diff --git a/sql/pktap.sql b/sql/pktap.sql index 3ec6b804f58e..bf6dd4fedfe4 100644 --- a/sql/pktap.sql +++ b/sql/pktap.sql @@ -134,7 +134,7 @@ SELECT * FROM check_test( false, 'col_is_pk( schema, table, column, description ) fail', 'public.sometab.name should be a pk', - ' have: {id} + ' have: {id} want: {name}' ); @@ -143,7 +143,7 @@ SELECT * FROM check_test( false, 'col_is_pk( table, column, description ) fail', 'sometab.name should be a pk', - ' have: {id} + ' have: {id} want: {name}' ); @@ -190,7 +190,7 @@ SELECT * FROM check_test( false, 'col_isnt_pk( schema, table, column, description )', 'public.sometab.id should not be a pk', - ' {id} + ' {id} <> {id}' ); @@ -200,7 +200,7 @@ SELECT * FROM check_test( false, 'col_isnt_pk( table, column, description )', 'sometab.id should not be a pk', - ' {id} + ' {id} <> {id}' ); @@ -210,7 +210,7 @@ SELECT * FROM check_test( false, 'col_isnt_pk( table, column )', 'Column sometab(id) should not be a primary key', - ' {id} + ' {id} <> {id}' ); diff --git a/sql/throwtap.sql b/sql/throwtap.sql index 8cf98c0da10d..9a869e4d0a97 100644 --- a/sql/throwtap.sql +++ b/sql/throwtap.sql @@ -62,7 +62,7 @@ SELECT * FROM check_test( false, 'invalid errcode', 'threw 97212', - ' caught: P0001: todo_end() called without todo_start() + ' caught: P0001: todo_end() called without todo_start() wanted: 97212' ); @@ -75,7 +75,7 @@ SELECT * FROM check_test( false, 'throws_ok diagnostics', 'threw an exception', - ' caught: no exception + ' caught: no exception wanted: an exception' ); diff --git a/sql/unique.sql b/sql/unique.sql index 6df79e662d63..794f7e7f2fdc 100644 --- a/sql/unique.sql +++ b/sql/unique.sql @@ -90,7 +90,7 @@ SELECT * FROM check_test( false, 'col_is_unique( schema, table, column, description ) fail', 'public.sometab.id should be a pk', - ' have: {name} + ' have: {name} want: {id}' ); @@ -99,7 +99,7 @@ SELECT * FROM check_test( false, 'col_is_unique( table, column, description ) fail', 'sometab.id should be a pk', - ' have: {name} + ' have: {name} want: {id}' ); From d359e7411165344714d6f27b9b2c45cceda8c186 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 17 Oct 2008 16:53:07 +0000 Subject: [PATCH 0204/1195] * Added `is_clustered()`. * Added missing uninstall commands. --- Changes | 2 +- README.pgtap | 19 ++++++++++++ expected/index.out | 14 ++++++++- pgtap.sql.in | 70 ++++++++++++++++++++++++++++++++++++++++++ sql/index.sql | 35 ++++++++++++++++++++- uninstall_pgtap.sql.in | 22 +++++++++++++ 6 files changed, 159 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 660c70e43894..7af4c91281eb 100644 --- a/Changes +++ b/Changes @@ -6,7 +6,7 @@ Revision history for pgTAP Ben for the catch! - Added commands to drop `pg_version()` and `pg_version_num()` to`uninstall_pgtap.sql.in`. - - Added `has_index()`. + - Added `has_index()` and `is_clustered()`. - Added `os_name()`. This is somewhat experimental. If you have `uname`, it's probably correct, but assistance in improving OS detection in the `Makefile` would be greatly appreciated. Notably, it does not detect diff --git a/README.pgtap b/README.pgtap index a4f2a0a2c229..571647bc2c16 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1111,6 +1111,25 @@ incorrect, the diagnostics will look more like this: # have: "idx_baz" ON public.sometab(lower(name)) # want: "idx_baz" ON public.sometab(lower(lname)) +### `is_clustered( schema, table, index, description )` ### +### `is_clustered( schema, table, index )` ### +### `is_clustered( table, index )` ### +### `is_clustered( index )` ### + + SELECT is_clustered( + 'myschema', + 'sometable', + 'myindex', + 'Table sometable should be clustered on "myindex"' + ); + + SELECT is_clustered( 'sometable', 'myindex' ); + +Tests whether a table is clustered on the given index. A table is clustered on +an index when the SQL command `CLUSTER TABLE INDEXNAME` has been executed. +Clustering reorganizes the table tuples so that they are stored on disk in the +order defined by the index. + ### `can( schema, functions[], description )` ### ### `can( schema, functions[] )` ### ### `can( functions[], description )` ### diff --git a/expected/index.out b/expected/index.out index a6de018b705a..cf6a7c3cecb7 100644 --- a/expected/index.out +++ b/expected/index.out @@ -1,5 +1,5 @@ \unset ECHO -1..63 +1..75 ok 1 - has_index() single column should pass ok 2 - has_index() single column should have the proper description ok 3 - has_index() single column should have the proper diagnostics @@ -63,3 +63,15 @@ ok 60 - has_index() functional fail should have the proper diagnostics ok 61 - has_index() functional fail no schema should fail ok 62 - has_index() functional fail no schema should have the proper description ok 63 - has_index() functional fail no schema should have the proper diagnostics +ok 64 - is_clustered() fail should fail +ok 65 - is_clustered() fail should have the proper description +ok 66 - is_clustered() fail should have the proper diagnostics +ok 67 - is_clustered() fail no desc should fail +ok 68 - is_clustered() fail no desc should have the proper description +ok 69 - is_clustered() fail no desc should have the proper diagnostics +ok 70 - is_clustered() fail no schema should fail +ok 71 - is_clustered() fail no schema should have the proper description +ok 72 - is_clustered() fail no schema should have the proper diagnostics +ok 73 - is_clustered() fail index only should fail +ok 74 - is_clustered() fail index only should have the proper description +ok 75 - is_clustered() fail index only should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 47941be00fe3..fca2458dc74a 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2139,6 +2139,76 @@ RETURNS TEXT AS $$ SELECT ok( _has_index( $1, $2 ), 'Index "' || $2 || '" should exist' ); $$ LANGUAGE sql; +-- 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 ' || $1 || '.' || $2 || + ' should be clustered on index "' || $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 ' || $1 || ' should be clustered on index "' || $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 "' || $1 || '"' + ); +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 $$ diff --git a/sql/index.sql b/sql/index.sql index f806a6f4fdc4..f7d403cf9e4c 100644 --- a/sql/index.sql +++ b/sql/index.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(63); +SELECT plan(75); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -195,6 +195,39 @@ SELECT * FROM check_test( want: "idx_baz" ON sometab(lower(wank))' ); +/****************************************************************************/ +-- Test is_clustered(). +SELECT * FROM check_test( + is_clustered( 'public', 'sometab', 'idx_bar', 'whatever' ), + false, + 'is_clustered() fail', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_clustered( 'public', 'sometab', 'idx_bar' ), + false, + 'is_clustered() fail no desc', + 'Table public.sometab should be clustered on index "idx_bar"', + '' +); + +SELECT * FROM check_test( + is_clustered( 'sometab', 'idx_bar' ), + false, + 'is_clustered() fail no schema', + 'Table sometab should be clustered on index "idx_bar"', + '' +); + +SELECT * FROM check_test( + is_clustered( 'idx_bar' ), + false, + 'is_clustered() fail index only', + 'Table should be clustered on index "idx_bar"', + '' +); /****************************************************************************/ -- Finish the tests and clean up. diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 15a98b31ad4d..79bd9de3877b 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -4,6 +4,27 @@ DROP FUNCTION check_test( TEXT, BOOLEAN ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); +DROP FUNCTION is_clustered ( NAME ); +DROP FUNCTION is_clustered ( NAME, NAME ); +DROP FUNCTION is_clustered ( NAME, NAME, NAME ); +DROP FUNCTION is_clustered ( NAME, NAME, NAME, text ); +DROP FUNCTION has_index ( NAME, NAME ); +DROP FUNCTION has_index ( NAME, NAME, text ); +DROP FUNCTION has_index ( NAME, NAME, NAME ); +DROP FUNCTION has_index ( NAME, NAME, NAME, text ); +DROP FUNCTION _has_index( NAME, NAME ); +DROP FUNCTION _has_index( NAME, NAME, NAME ); +DROP FUNCTION _is_schema( NAME ); +DROP FUNCTION has_index ( NAME, NAME, NAME[] ); +DROP FUNCTION has_index ( NAME, NAME, NAME[], text ); +DROP FUNCTION has_index ( NAME, NAME, NAME, NAME ); +DROP FUNCTION has_index ( NAME, NAME, NAME, NAME, text ); +DROP FUNCTION has_index ( NAME, NAME, NAME, NAME[] ); +DROP FUNCTION has_index ( NAME, NAME, NAME, NAME[], text ); +DROP FUNCTION _iexpr( NAME, NAME); +DROP FUNCTION _iexpr( NAME, NAME, NAME); +DROP FUNCTION _ikeys( NAME, NAME); +DROP FUNCTION _ikeys( NAME, NAME, NAME); DROP FUNCTION can ( NAME[] ); DROP FUNCTION can ( NAME[], TEXT ); DROP FUNCTION can ( NAME, NAME[] ); @@ -192,6 +213,7 @@ DROP FUNCTION _get_latest ( text ); DROP FUNCTION _get ( text ); DROP FUNCTION no_plan(); DROP FUNCTION plan( integer ); +DROP FUNCTION os_name(); DROP FUNCTION pg_version_num(); DROP FUNCTION pg_version(); DROP FUNCTION pg_typeof("any"); From f92b25f265a3efcc600696909e71ea93c7dbb66c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 17 Oct 2008 18:19:21 +0000 Subject: [PATCH 0205/1195] Added `index_is_unique()` and `index_is_primary()`. --- Changes | 3 +- README.pgtap | 32 +++++++ expected/index.out | 104 ++++++++++++++++++--- pgtap.sql.in | 144 +++++++++++++++++++++++++++++ sql/index.sql | 219 ++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 485 insertions(+), 17 deletions(-) diff --git a/Changes b/Changes index 7af4c91281eb..2fea0df1faf0 100644 --- a/Changes +++ b/Changes @@ -6,7 +6,8 @@ Revision history for pgTAP Ben for the catch! - Added commands to drop `pg_version()` and `pg_version_num()` to`uninstall_pgtap.sql.in`. - - Added `has_index()` and `is_clustered()`. + - Added `has_index()`, `index_is_unique()`, `index_is_primary(), and + `is_clustered()`. - Added `os_name()`. This is somewhat experimental. If you have `uname`, it's probably correct, but assistance in improving OS detection in the `Makefile` would be greatly appreciated. Notably, it does not detect diff --git a/README.pgtap b/README.pgtap index 571647bc2c16..32fac709adb8 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1111,6 +1111,38 @@ incorrect, the diagnostics will look more like this: # have: "idx_baz" ON public.sometab(lower(name)) # want: "idx_baz" ON public.sometab(lower(lname)) +### `index_is_unique( schema, table, index, description )` ### +### `index_is_unique( schema, table, index )` ### +### `index_is_unique( table, index )` ### +### `index_is_unique( index )` ### + + SELECT index_is_unique( + 'myschema', + 'sometable', + 'myindex', + 'Index "myindex" should be unique' + ); + + SELECT index_is_unique( 'sometable', 'myindex' ); + +Tests whether an index is unique. + +### `index_is_primary( schema, table, index, description )` ### +### `index_is_primary( schema, table, index )` ### +### `index_is_primary( table, index )` ### +### `index_is_primary( index )` ### + + SELECT index_is_primary( + 'myschema', + 'sometable', + 'myindex', + 'Index "myindex" should be on a primary key' + ); + + SELECT index_is_primary( 'sometable', 'myindex' ); + +Tests whether an index is on a primary key. + ### `is_clustered( schema, table, index, description )` ### ### `is_clustered( schema, table, index )` ### ### `is_clustered( table, index )` ### diff --git a/expected/index.out b/expected/index.out index cf6a7c3cecb7..593d9111a872 100644 --- a/expected/index.out +++ b/expected/index.out @@ -1,5 +1,5 @@ \unset ECHO -1..75 +1..153 ok 1 - has_index() single column should pass ok 2 - has_index() single column should have the proper description ok 3 - has_index() single column should have the proper diagnostics @@ -63,15 +63,93 @@ ok 60 - has_index() functional fail should have the proper diagnostics ok 61 - has_index() functional fail no schema should fail ok 62 - has_index() functional fail no schema should have the proper description ok 63 - has_index() functional fail no schema should have the proper diagnostics -ok 64 - is_clustered() fail should fail -ok 65 - is_clustered() fail should have the proper description -ok 66 - is_clustered() fail should have the proper diagnostics -ok 67 - is_clustered() fail no desc should fail -ok 68 - is_clustered() fail no desc should have the proper description -ok 69 - is_clustered() fail no desc should have the proper diagnostics -ok 70 - is_clustered() fail no schema should fail -ok 71 - is_clustered() fail no schema should have the proper description -ok 72 - is_clustered() fail no schema should have the proper diagnostics -ok 73 - is_clustered() fail index only should fail -ok 74 - is_clustered() fail index only should have the proper description -ok 75 - is_clustered() fail index only should have the proper diagnostics +ok 64 - index_is_unique() should pass +ok 65 - index_is_unique() should have the proper description +ok 66 - index_is_unique() should have the proper diagnostics +ok 67 - index_is_unique() no desc should pass +ok 68 - index_is_unique() no desc should have the proper description +ok 69 - index_is_unique() no desc should have the proper diagnostics +ok 70 - index_is_unique() no schema should pass +ok 71 - index_is_unique() no schema should have the proper description +ok 72 - index_is_unique() no schema should have the proper diagnostics +ok 73 - index_is_unique() index only should pass +ok 74 - index_is_unique() index only should have the proper description +ok 75 - index_is_unique() index only should have the proper diagnostics +ok 76 - index_is_unique() on pk should pass +ok 77 - index_is_unique() on pk should have the proper description +ok 78 - index_is_unique() on pk should have the proper diagnostics +ok 79 - index_is_unique() on pk no desc should pass +ok 80 - index_is_unique() on pk no desc should have the proper description +ok 81 - index_is_unique() on pk no desc should have the proper diagnostics +ok 82 - index_is_unique() on pk no schema should pass +ok 83 - index_is_unique() on pk no schema should have the proper description +ok 84 - index_is_unique() on pk no schema should have the proper diagnostics +ok 85 - index_is_unique() on pk index only should pass +ok 86 - index_is_unique() on pk index only should have the proper description +ok 87 - index_is_unique() on pk index only should have the proper diagnostics +ok 88 - index_is_unique() fail should fail +ok 89 - index_is_unique() fail should have the proper description +ok 90 - index_is_unique() fail should have the proper diagnostics +ok 91 - index_is_unique() fail no desc should fail +ok 92 - index_is_unique() fail no desc should have the proper description +ok 93 - index_is_unique() fail no desc should have the proper diagnostics +ok 94 - index_is_unique() fail no schema should fail +ok 95 - index_is_unique() fail no schema should have the proper description +ok 96 - index_is_unique() fail no schema should have the proper diagnostics +ok 97 - index_is_unique() fail index only should fail +ok 98 - index_is_unique() fail index only should have the proper description +ok 99 - index_is_unique() fail index only should have the proper diagnostics +ok 100 - index_is_unique() no such index should fail +ok 101 - index_is_unique() no such index should have the proper description +ok 102 - index_is_unique() no such index should have the proper diagnostics +ok 103 - index_is_primary() should pass +ok 104 - index_is_primary() should have the proper description +ok 105 - index_is_primary() should have the proper diagnostics +ok 106 - index_is_primary() no desc should pass +ok 107 - index_is_primary() no desc should have the proper description +ok 108 - index_is_primary() no desc should have the proper diagnostics +ok 109 - index_is_primary() no schema should pass +ok 110 - index_is_primary() no schema should have the proper description +ok 111 - index_is_primary() no schema should have the proper diagnostics +ok 112 - index_is_primary() index only should pass +ok 113 - index_is_primary() index only should have the proper description +ok 114 - index_is_primary() index only should have the proper diagnostics +ok 115 - index_is_primary() fail should fail +ok 116 - index_is_primary() fail should have the proper description +ok 117 - index_is_primary() fail should have the proper diagnostics +ok 118 - index_is_primary() fail no desc should fail +ok 119 - index_is_primary() fail no desc should have the proper description +ok 120 - index_is_primary() fail no desc should have the proper diagnostics +ok 121 - index_is_primary() fail no schema should fail +ok 122 - index_is_primary() fail no schema should have the proper description +ok 123 - index_is_primary() fail no schema should have the proper diagnostics +ok 124 - index_is_primary() fail index only should fail +ok 125 - index_is_primary() fail index only should have the proper description +ok 126 - index_is_primary() fail index only should have the proper diagnostics +ok 127 - index_is_primary() no such index should fail +ok 128 - index_is_primary() no such index should have the proper description +ok 129 - index_is_primary() no such index should have the proper diagnostics +ok 130 - is_clustered() fail should fail +ok 131 - is_clustered() fail should have the proper description +ok 132 - is_clustered() fail should have the proper diagnostics +ok 133 - is_clustered() fail no desc should fail +ok 134 - is_clustered() fail no desc should have the proper description +ok 135 - is_clustered() fail no desc should have the proper diagnostics +ok 136 - is_clustered() fail no schema should fail +ok 137 - is_clustered() fail no schema should have the proper description +ok 138 - is_clustered() fail no schema should have the proper diagnostics +ok 139 - is_clustered() fail index only should fail +ok 140 - is_clustered() fail index only should have the proper description +ok 141 - is_clustered() fail index only should have the proper diagnostics +ok 142 - is_clustered() should pass +ok 143 - is_clustered() should have the proper description +ok 144 - is_clustered() should have the proper diagnostics +ok 145 - is_clustered() no desc should pass +ok 146 - is_clustered() no desc should have the proper description +ok 147 - is_clustered() no desc should have the proper diagnostics +ok 148 - is_clustered() no schema should pass +ok 149 - is_clustered() no schema should have the proper description +ok 150 - is_clustered() no schema should have the proper diagnostics +ok 151 - is_clustered() index only should pass +ok 152 - is_clustered() index only should have the proper description +ok 153 - is_clustered() index only should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index fca2458dc74a..01801a9fa21c 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2139,6 +2139,150 @@ RETURNS TEXT AS $$ SELECT ok( _has_index( $1, $2 ), 'Index "' || $2 || '" should exist' ); $$ 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 "' || $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 "' || $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 "' || $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 "' || $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 "' || $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 "' || $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 $$ diff --git a/sql/index.sql b/sql/index.sql index f7d403cf9e4c..b2174c17e32e 100644 --- a/sql/index.sql +++ b/sql/index.sql @@ -3,20 +3,20 @@ -- $Id$ -SELECT plan(75); +SELECT plan(153); --SELECT * FROM no_plan(); -- This will be rolled back. :-) SET client_min_messages = warning; CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, - name TEXT DEFAULT '' UNIQUE, + name TEXT DEFAULT '', numb NUMERIC(10, 2), myint NUMERIC(8) ); CREATE INDEX idx_foo ON public.sometab(name); CREATE INDEX idx_bar ON public.sometab(name, numb); -CREATE INDEX idx_baz ON public.sometab(LOWER(name)); +CREATE UNIQUE INDEX idx_baz ON public.sometab(LOWER(name)); RESET client_min_messages; /****************************************************************************/ @@ -195,6 +195,186 @@ SELECT * FROM check_test( want: "idx_baz" ON sometab(lower(wank))' ); +/****************************************************************************/ +-- Test index_is_unique(). +SELECT * FROM check_test( + index_is_unique( 'public', 'sometab', 'idx_baz', 'whatever' ), + true, + 'index_is_unique()', + 'whatever', + '' +); + +SELECT * FROM check_test( + index_is_unique( 'public', 'sometab', 'idx_baz' ), + true, + 'index_is_unique() no desc', + 'Index "idx_baz" should be unique', + '' +); + +SELECT * FROM check_test( + index_is_unique( 'sometab', 'idx_baz' ), + true, + 'index_is_unique() no schema', + 'Index "idx_baz" should be unique', + '' +); + +SELECT * FROM check_test( + index_is_unique( 'idx_baz' ), + true, + 'index_is_unique() index only', + 'Index "idx_baz" should be unique', + '' +); + +SELECT * FROM check_test( + index_is_unique( 'public', 'sometab', 'sometab_pkey', 'whatever' ), + true, + 'index_is_unique() on pk', + 'whatever', + '' +); + +SELECT * FROM check_test( + index_is_unique( 'public', 'sometab', 'sometab_pkey' ), + true, + 'index_is_unique() on pk no desc', + 'Index "sometab_pkey" should be unique', + '' +); + +SELECT * FROM check_test( + index_is_unique( 'sometab', 'sometab_pkey' ), + true, + 'index_is_unique() on pk no schema', + 'Index "sometab_pkey" should be unique', + '' +); + +SELECT * FROM check_test( + index_is_unique( 'sometab_pkey' ), + true, + 'index_is_unique() on pk index only', + 'Index "sometab_pkey" should be unique', + '' +); + +SELECT * FROM check_test( + index_is_unique( 'public', 'sometab', 'idx_bar', 'whatever' ), + false, + 'index_is_unique() fail', + 'whatever', + '' +); + +SELECT * FROM check_test( + index_is_unique( 'public', 'sometab', 'idx_bar' ), + false, + 'index_is_unique() fail no desc', + 'Index "idx_bar" should be unique', + '' +); + +SELECT * FROM check_test( + index_is_unique( 'sometab', 'idx_bar' ), + false, + 'index_is_unique() fail no schema', + 'Index "idx_bar" should be unique', + '' +); + +SELECT * FROM check_test( + index_is_unique( 'idx_bar' ), + false, + 'index_is_unique() fail index only', + 'Index "idx_bar" should be unique', + '' +); + +SELECT * FROM check_test( + index_is_unique( 'blahblah' ), + false, + 'index_is_unique() no such index', + 'Index "blahblah" should be unique', + '' +); + +/****************************************************************************/ +-- Test index_is_primary(). +SELECT * FROM check_test( + index_is_primary( 'public', 'sometab', 'sometab_pkey', 'whatever' ), + true, + 'index_is_primary()', + 'whatever', + '' +); + +SELECT * FROM check_test( + index_is_primary( 'public', 'sometab', 'sometab_pkey' ), + true, + 'index_is_primary() no desc', + 'Index "sometab_pkey" should be on a primary key', + '' +); + +SELECT * FROM check_test( + index_is_primary( 'sometab', 'sometab_pkey' ), + true, + 'index_is_primary() no schema', + 'Index "sometab_pkey" should be on a primary key', + '' +); + +SELECT * FROM check_test( + index_is_primary( 'sometab_pkey' ), + true, + 'index_is_primary() index only', + 'Index "sometab_pkey" should be on a primary key', + '' +); + +SELECT * FROM check_test( + index_is_primary( 'public', 'sometab', 'idx_baz', 'whatever' ), + false, + 'index_is_primary() fail', + 'whatever', + '' +); + +SELECT * FROM check_test( + index_is_primary( 'public', 'sometab', 'idx_baz' ), + false, + 'index_is_primary() fail no desc', + 'Index "idx_baz" should be on a primary key', + '' +); + +SELECT * FROM check_test( + index_is_primary( 'sometab', 'idx_baz' ), + false, + 'index_is_primary() fail no schema', + 'Index "idx_baz" should be on a primary key', + '' +); + +SELECT * FROM check_test( + index_is_primary( 'idx_baz' ), + false, + 'index_is_primary() fail index only', + 'Index "idx_baz" should be on a primary key', + '' +); + +SELECT * FROM check_test( + index_is_primary( 'blahblah' ), + false, + 'index_is_primary() no such index', + 'Index "blahblah" should be on a primary key', + '' +); + /****************************************************************************/ -- Test is_clustered(). SELECT * FROM check_test( @@ -229,6 +409,39 @@ SELECT * FROM check_test( '' ); +CLUSTER public.sometab using idx_bar; +SELECT * FROM check_test( + is_clustered( 'public', 'sometab', 'idx_bar', 'whatever' ), + true, + 'is_clustered()', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_clustered( 'public', 'sometab', 'idx_bar' ), + true, + 'is_clustered() no desc', + 'Table public.sometab should be clustered on index "idx_bar"', + '' +); + +SELECT * FROM check_test( + is_clustered( 'sometab', 'idx_bar' ), + true, + 'is_clustered() no schema', + 'Table sometab should be clustered on index "idx_bar"', + '' +); + +SELECT * FROM check_test( + is_clustered( 'idx_bar' ), + true, + 'is_clustered() index only', + 'Table should be clustered on index "idx_bar"', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 7eee558cc8c8b3fddea0d5970d706e2659f42782 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 17 Oct 2008 18:20:28 +0000 Subject: [PATCH 0206/1195] Gotta drop those functions. --- uninstall_pgtap.sql.in | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 79bd9de3877b..0f8bea0b0154 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -8,6 +8,14 @@ DROP FUNCTION is_clustered ( NAME ); DROP FUNCTION is_clustered ( NAME, NAME ); DROP FUNCTION is_clustered ( NAME, NAME, NAME ); DROP FUNCTION is_clustered ( NAME, NAME, NAME, text ); +DROP FUNCTION index_is_primary ( NAME ); +DROP FUNCTION index_is_primary ( NAME, NAME ); +DROP FUNCTION index_is_primary ( NAME, NAME, NAME ); +DROP FUNCTION index_is_primary ( NAME, NAME, NAME, text ); +DROP FUNCTION index_is_unique ( NAME ); +DROP FUNCTION index_is_unique ( NAME, NAME ); +DROP FUNCTION index_is_unique ( NAME, NAME, NAME ); +DROP FUNCTION index_is_unique ( NAME, NAME, NAME, text ); DROP FUNCTION has_index ( NAME, NAME ); DROP FUNCTION has_index ( NAME, NAME, text ); DROP FUNCTION has_index ( NAME, NAME, NAME ); From 1b46dfda2a4f474255b9cf9ef05a34b14e84e6da Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 17 Oct 2008 18:21:18 +0000 Subject: [PATCH 0207/1195] Just a couple more functions I know I want to add now. --- README.pgtap | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.pgtap b/README.pgtap index 32fac709adb8..47b03cf86e32 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1694,8 +1694,7 @@ To Do the shebang line. Will likely require a `$(PERL)` environment variable. * Update the Makefile to check for TAP::Harness and warn if it's not installed. -* Add `index_is_type()`, `index_is_unique()`, `index_is_pk()`, and - `has_trigger()`. +* Add `index_is_type()`, and `has_trigger()`. Suported Versions ----------------- From ba0fa010e0b894efc17bcc9ab699210d6c8eb252 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 22 Oct 2008 18:22:43 +0000 Subject: [PATCH 0208/1195] Fixed syntax errors. --- compat/install-8.2.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compat/install-8.2.sql b/compat/install-8.2.sql index 1b7da3ceab2b..86b8b756f4cd 100644 --- a/compat/install-8.2.sql +++ b/compat/install-8.2.sql @@ -7,11 +7,11 @@ LANGUAGE sql IMMUTABLE STRICT; CREATE CAST (boolean AS text) WITH FUNCTION booltext(boolean) AS IMPLICIT; -- Cast name[]s to text like 8.3 does. -CREATE OR REPLACE FUNCTION anyarray_text(anyarray) +CREATE OR REPLACE FUNCTION anyarray_text(name[]) RETURNS TEXT AS 'SELECT textin(array_out($1));' LANGUAGE sql IMMUTABLE STRICT; -CREATE CAST (name[] AS text) WITH FUNCTION anyrray_text(anyarray) AS IMPLICIT; +CREATE CAST (name[] AS text) WITH FUNCTION anyarray_text(name[]) AS IMPLICIT; -- Compare name[]s more or less like 8.3 does. CREATE OR REPLACE FUNCTION namearray_eq( name[], name[] ) From c6a54b03023c6dcbfe6f07a3950dcea727a84dad Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 22 Oct 2008 18:42:49 +0000 Subject: [PATCH 0209/1195] Better name. --- compat/install-8.2.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compat/install-8.2.sql b/compat/install-8.2.sql index 86b8b756f4cd..a0d5b69defb7 100644 --- a/compat/install-8.2.sql +++ b/compat/install-8.2.sql @@ -7,11 +7,11 @@ LANGUAGE sql IMMUTABLE STRICT; CREATE CAST (boolean AS text) WITH FUNCTION booltext(boolean) AS IMPLICIT; -- Cast name[]s to text like 8.3 does. -CREATE OR REPLACE FUNCTION anyarray_text(name[]) +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 anyarray_text(name[]) AS IMPLICIT; +CREATE CAST (name[] AS text) WITH FUNCTION namearray_text(name[]) AS IMPLICIT; -- Compare name[]s more or less like 8.3 does. CREATE OR REPLACE FUNCTION namearray_eq( name[], name[] ) From 2e923cebf808a140dd5b77518fb5d214934d2cac Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 22 Oct 2008 19:12:06 +0000 Subject: [PATCH 0210/1195] Fixed hideous workaround for ugly "ERROR: cache lookup failed for function 0" error. --- Changes | 2 ++ compat/install-8.2.sql | 14 +++++++++++++- compat/uninstall-8.2.sql | 2 ++ pgtap.sql.in | 2 +- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 2fea0df1faf0..13656aae276e 100644 --- a/Changes +++ b/Changes @@ -17,6 +17,8 @@ Revision history for pgTAP reporting that the test result was `NULL`. Reported by Jason Gordon. - Fixed an issue in `check_test()` where an extra character was removed from the beginning of the diagnostic output before testing it. + - Fixed a bug comparing `name[]`s on PostgreSQL 8.2, previously hacked + around. 0.13 2008-10-13T19:09:46 - Added `pg_version()` and `pg_version_num()`. diff --git a/compat/install-8.2.sql b/compat/install-8.2.sql index a0d5b69defb7..623e7b2ba78d 100644 --- a/compat/install-8.2.sql +++ b/compat/install-8.2.sql @@ -22,10 +22,22 @@ LANGUAGE sql IMMUTABLE STRICT; CREATE OPERATOR = ( LEFTARG = name[], RIGHTARG = name[], - COMMUTATOR = <>, + 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))' diff --git a/compat/uninstall-8.2.sql b/compat/uninstall-8.2.sql index 4c99841c698e..d28cccd002f0 100644 --- a/compat/uninstall-8.2.sql +++ b/compat/uninstall-8.2.sql @@ -1,5 +1,7 @@ DROP CAST (regtype AS text); DROP FUNCTION regtypetext(regtype); +DROP OPERATOR <> ( name[], name[] ); +DROP FUNCTION namearray_ne( name[], name[] ); DROP OPERATOR = ( name[], name[] ); DROP FUNCTION namearray_eq( name[], name[] ); DROP CAST (name[] AS text); diff --git a/pgtap.sql.in b/pgtap.sql.in index 01801a9fa21c..3977747b3ddb 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -335,7 +335,7 @@ DECLARE BEGIN result := CASE WHEN $1 IS NULL AND $2 IS NULL THEN FALSE WHEN $1 IS NULL OR $2 IS NULL THEN TRUE - ELSE NOT $1 = $2 END; -- XXX use of <> sometimes causes an error in 8.2! + ELSE $1 <> $2 END; output := ok( result, $3 ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( ' ' || COALESCE( $1::text, 'NULL' ) || From 9d94fd2bcb08b058f83aae10c0c094e2e49fca0c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 22 Oct 2008 20:25:12 +0000 Subject: [PATCH 0211/1195] Fixed 8.2 compatibility of the new index testing functions. --- pgtap.sql.in | 4 ++-- sql/index.sql | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 3977747b3ddb..1ffaab4218da 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1961,7 +1961,7 @@ DECLARE BEGIN index_cols := _ikeys($1, $2, $3 ); - IF index_cols = '{}'::name[] THEN + IF index_cols IS NULL OR index_cols = '{}'::name[] THEN RETURN ok( false, $5 ) || E'\n' || diag( 'Index "' || $3 || '" ON ' || $1 || '.' || $2 || ' not found'); END IF; @@ -2021,7 +2021,7 @@ DECLARE BEGIN index_cols := _ikeys($1, $2 ); - IF index_cols = '{}'::name[] THEN + IF index_cols IS NULL OR index_cols = '{}'::name[] THEN RETURN ok( false, $4 ) || E'\n' || diag( 'Index "' || $2 || '" ON ' || $1 || ' not found'); END IF; diff --git a/sql/index.sql b/sql/index.sql index b2174c17e32e..5e39ad90144c 100644 --- a/sql/index.sql +++ b/sql/index.sql @@ -409,7 +409,7 @@ SELECT * FROM check_test( '' ); -CLUSTER public.sometab using idx_bar; +CLUSTER idx_bar ON public.sometab; SELECT * FROM check_test( is_clustered( 'public', 'sometab', 'idx_bar', 'whatever' ), true, From b3380cd70e47416a8799077200336d1eb4276684 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 24 Oct 2008 23:54:23 +0000 Subject: [PATCH 0212/1195] Added `index_is_type()`. --- Changes | 4 +-- README.pgtap | 30 +++++++++++++++++ expected/index.out | 29 ++++++++++++++++- pgtap.sql.in | 74 ++++++++++++++++++++++++++++++++++++++++++ sql/index.sql | 81 ++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 213 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 13656aae276e..ae03b859895f 100644 --- a/Changes +++ b/Changes @@ -6,8 +6,8 @@ Revision history for pgTAP Ben for the catch! - Added commands to drop `pg_version()` and `pg_version_num()` to`uninstall_pgtap.sql.in`. - - Added `has_index()`, `index_is_unique()`, `index_is_primary(), and - `is_clustered()`. + - Added `has_index()`, `index_is_unique()`, `index_is_primary(), + `is_clustered()`, and `index_is_type()`. - Added `os_name()`. This is somewhat experimental. If you have `uname`, it's probably correct, but assistance in improving OS detection in the `Makefile` would be greatly appreciated. Notably, it does not detect diff --git a/README.pgtap b/README.pgtap index 47b03cf86e32..556edabff0cc 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1162,6 +1162,36 @@ an index when the SQL command `CLUSTER TABLE INDEXNAME` has been executed. Clustering reorganizes the table tuples so that they are stored on disk in the order defined by the index. +### `index_is_type( schema, table, index, type, description )` ### +### `index_is_type( schema, table, index, type )` ### +### `index_is_type( table, index, type )` ### +### `index_is_type( index, type )` ### + + SELECT index_is_type( + 'myschema', + 'sometable', + 'myindex', + 'gist', + 'Index "myindex" should be a GIST index' + ); + + SELECT index_is_type( 'myindex', 'gin' ); + +Tests to ensure that an index is of a particular type. At the time of this +writing, the supported types are: + +* btree +* hash +* gist +* gin + +If the test fails, it will emit a diagnostic message with the actual index +type, like so: + + # Failed test 175: "Index idx_bar should be a hash index" + # have: btree + # want: hash + ### `can( schema, functions[], description )` ### ### `can( schema, functions[] )` ### ### `can( functions[], description )` ### diff --git a/expected/index.out b/expected/index.out index 593d9111a872..d32a67210e14 100644 --- a/expected/index.out +++ b/expected/index.out @@ -1,5 +1,5 @@ \unset ECHO -1..153 +1..180 ok 1 - has_index() single column should pass ok 2 - has_index() single column should have the proper description ok 3 - has_index() single column should have the proper diagnostics @@ -153,3 +153,30 @@ ok 150 - is_clustered() no schema should have the proper diagnostics ok 151 - is_clustered() index only should pass ok 152 - is_clustered() index only should have the proper description ok 153 - is_clustered() index only should have the proper diagnostics +ok 154 - index_is_type() should pass +ok 155 - index_is_type() should have the proper description +ok 156 - index_is_type() should have the proper diagnostics +ok 157 - index_is_type() ci should pass +ok 158 - index_is_type() ci should have the proper description +ok 159 - index_is_type() ci should have the proper diagnostics +ok 160 - index_is_type() no desc should pass +ok 161 - index_is_type() no desc should have the proper description +ok 162 - index_is_type() no desc should have the proper diagnostics +ok 163 - index_is_type() fail should fail +ok 164 - index_is_type() fail should have the proper description +ok 165 - index_is_type() fail should have the proper diagnostics +ok 166 - index_is_type() no schema should pass +ok 167 - index_is_type() no schema should have the proper description +ok 168 - index_is_type() no schema should have the proper diagnostics +ok 169 - index_is_type() no schema fail should fail +ok 170 - index_is_type() no schema fail should have the proper description +ok 171 - index_is_type() no schema fail should have the proper diagnostics +ok 172 - index_is_type() no table should pass +ok 173 - index_is_type() no table should have the proper description +ok 174 - index_is_type() no table should have the proper diagnostics +ok 175 - index_is_type() no table fail should fail +ok 176 - index_is_type() no table fail should have the proper description +ok 177 - index_is_type() no table fail should have the proper diagnostics +ok 178 - index_is_type() hash should pass +ok 179 - index_is_type() hash should have the proper description +ok 180 - index_is_type() hash should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 1ffaab4218da..b15fb2e1072b 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2353,6 +2353,80 @@ BEGIN 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, LOWER($4)::name, $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 ' || $3 || ' should be a ' || $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, + LOWER($3)::name, + 'Index ' || $2 || ' should be a ' || $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, + LOWER($3)::name, + 'Index ' || $1 || ' should be a ' || $2 || ' index' + ); +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 $$ diff --git a/sql/index.sql b/sql/index.sql index 5e39ad90144c..33beae3b8528 100644 --- a/sql/index.sql +++ b/sql/index.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(153); +SELECT plan(180); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -14,7 +14,7 @@ CREATE TABLE public.sometab( numb NUMERIC(10, 2), myint NUMERIC(8) ); -CREATE INDEX idx_foo ON public.sometab(name); +CREATE INDEX idx_foo ON public.sometab using hash(name); CREATE INDEX idx_bar ON public.sometab(name, numb); CREATE UNIQUE INDEX idx_baz ON public.sometab(LOWER(name)); RESET client_min_messages; @@ -442,6 +442,83 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test index_is_type(). +SELECT * FROM check_test( + index_is_type( 'public', 'sometab', 'idx_bar', 'btree', 'whatever' ), + true, + 'index_is_type()', + 'whatever', + '' +); + +SELECT * FROM check_test( + index_is_type( 'public', 'sometab', 'idx_bar', 'BTREE', 'whatever' ), + true, + 'index_is_type() ci', + 'whatever', + '' +); + +SELECT * FROM check_test( + index_is_type( 'public', 'sometab', 'idx_bar', 'btree' ), + true, + 'index_is_type() no desc', + 'Index idx_bar should be a btree index', + '' +); + +SELECT * FROM check_test( + index_is_type( 'public', 'sometab', 'idx_bar', 'hash' ), + false, + 'index_is_type() fail', + 'Index idx_bar should be a hash index', + ' have: btree + want: hash' +); + +SELECT * FROM check_test( + index_is_type( 'sometab', 'idx_bar', 'btree' ), + true, + 'index_is_type() no schema', + 'Index idx_bar should be a btree index', + '' +); + +SELECT * FROM check_test( + index_is_type( 'sometab', 'idx_bar', 'hash' ), + false, + 'index_is_type() no schema fail', + 'Index idx_bar should be a hash index', + ' have: btree + want: hash' +); + +SELECT * FROM check_test( + index_is_type( 'idx_bar', 'btree' ), + true, + 'index_is_type() no table', + 'Index idx_bar should be a btree index', + '' +); + +SELECT * FROM check_test( + index_is_type( 'idx_bar', 'hash' ), + false, + 'index_is_type() no table fail', + 'Index idx_bar should be a hash index', + ' have: btree + want: hash' +); + +SELECT * FROM check_test( + index_is_type( 'idx_foo', 'hash' ), + true, + 'index_is_type() hash', + 'Index idx_foo should be a hash index', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From e087530cac0ef6dd191892f91f882de272a4ce8c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 27 Oct 2008 19:10:44 +0000 Subject: [PATCH 0213/1195] * Added `has_trigger()` and `trigger_is()`. * Updated the uninstall functions. --- Changes | 1 + README.pgtap | 65 ++++++++++++++++----- expected/trigger.out | 35 ++++++++++++ pgtap.sql.in | 107 ++++++++++++++++++++++++++++++++++ sql/trigger.sql | 126 +++++++++++++++++++++++++++++++++++++++++ uninstall_pgtap.sql.in | 11 ++++ 6 files changed, 331 insertions(+), 14 deletions(-) create mode 100644 expected/trigger.out create mode 100644 sql/trigger.sql diff --git a/Changes b/Changes index ae03b859895f..dc0b2c3b65e5 100644 --- a/Changes +++ b/Changes @@ -19,6 +19,7 @@ Revision history for pgTAP from the beginning of the diagnostic output before testing it. - Fixed a bug comparing `name[]`s on PostgreSQL 8.2, previously hacked around. + - Added `has_trigger()` and `trigger_is()`. 0.13 2008-10-13T19:09:46 - Added `pg_version()` and `pg_version_num()`. diff --git a/README.pgtap b/README.pgtap index 556edabff0cc..5203163b0337 100644 --- a/README.pgtap +++ b/README.pgtap @@ -176,11 +176,11 @@ Now run the tests. Here's what it looks like when the pgTAP tests are run with `pg_prove`: % pg_prove -U postgres sql/*.sql - sql/coltap.....ok - sql/hastap.....ok - sql/moretap....ok - sql/pg73.......ok - sql/pktap......ok + sql/coltap.....ok + sql/hastap.....ok + sql/moretap....ok + sql/pg73.......ok + sql/pktap......ok All tests successful. Files=5, Tests=216, 1 wallclock secs ( 0.06 usr 0.02 sys + 0.08 cusr 0.07 csys = 0.23 CPU) Result: PASS @@ -264,7 +264,7 @@ the test succeeded or failed. ### `ok( boolean )` ### SELECT ok( :this = :that, :description ); - + This function simply evaluates any expression (`:this = :that` is just a simple example) and uses that to determine if the test succeeded or failed. A true expression passes, a false one fails. Very simple. @@ -1192,6 +1192,44 @@ type, like so: # have: btree # want: hash +### `has_trigger( schema, table, trigger, description )` ### +### `has_trigger( schema, table, trigger )` ### +### `has_trigger( table, trigger )` ### + + SELECT has_trigger( + 'myschema', + 'sometable', + 'sometrigger', + 'Trigger "sometrigger" should exist' + ); + + SELECT has_trigger( 'sometable', 'sometrigger' ); + +Tests to see if the specified table has the named trigger. The `:description` +is optional, and if the schema is omitted, the table with which the trigger is +associated must be visible. + +### `trigger_is( schema, table, trigger, schema, function, description )` ### +### `trigger_is( schema, table, trigger, schema, function )` ### +### `trigger_is( table, trigger, function, description )` ### +### `trigger_is( table, trigger, function )` ### + + SELECT trigger_is( + 'myschema', + 'sometable', + 'sometrigger', + 'myschema', + 'somefunction', + 'Trigger "sometrigger" should call somefunction()' + ); + +Tests that the specified trigger calls the named function. If not, it outputs +a useful diagnostic: + + # Failed test 31: "Trigger set_users_pass should call hash_password()" + # have: hash_pass + # want: hash_password + ### `can( schema, functions[], description )` ### ### `can( schema, functions[] )` ### ### `can( functions[], description )` ### @@ -1309,12 +1347,12 @@ Outputs SKIP test results. Use it in a conditional expression within a `SELECT` statement to replace the output of a test that you otherwise would have run. - SELECT CASE WHEN pg_version_num() < 80100 + SELECT CASE WHEN pg_version_num() < 80100 THEN skip('throws_ok() not supported before 8.1', 2 ) ELSE throws_ok( 'SELECT 1/0', 22012, 'division by zero' ) || E'\n' || throws_ok( 'INSERT INTO try (id) VALUES (1)', '23505' ) - END; + END; Note how use of the conditional `CASE` statement has been used to determine whether or not to run a couple of tests. If they are to be run, they are @@ -1617,7 +1655,7 @@ the stringified version number displayed in the first part of the core `version()` function and stored in the "server_version" setting: try=% select current_setting( 'server_version'), pg_version(); - current_setting | pg_version + current_setting | pg_version -----------------+------------ 8.3.4 | 8.3.4 (1 row) @@ -1631,10 +1669,10 @@ pgTAP was compiled. This function is useful for determining whether or not certain tests should be run or skipped (using `skip()`) depending on the version of PostgreSQL. For example: - SELECT CASE WHEN pg_version_num() < 80100 + SELECT CASE WHEN pg_version_num() < 80100 THEN skip('throws_ok() not supported before 8.1' ) ELSE throws_ok( 'SELECT 1/0', 22012, 'division by zero' ) - END; + END; The revision level is in the tens position, the minor version in the thousands position, and the major version in the ten thousands position and above @@ -1644,7 +1682,7 @@ in PostgreSQL 8.2 and higher, but supported by this function back to PostgreSQL 8.0: try=% select current_setting( 'server_version_num'), pg_version_num(); - current_setting | pg_version_num + current_setting | pg_version_num -----------------+---------------- 80304 | 80304 @@ -1673,7 +1711,7 @@ function is used internally by `cmp_ok()` to properly construct types when executing the comparison, but might be generally useful. try=% select pg_typeof(12), pg_typeof(100.2); - pg_typeof | pg_typeof + pg_typeof | pg_typeof -----------+----------- integer | numeric @@ -1724,7 +1762,6 @@ To Do the shebang line. Will likely require a `$(PERL)` environment variable. * Update the Makefile to check for TAP::Harness and warn if it's not installed. -* Add `index_is_type()`, and `has_trigger()`. Suported Versions ----------------- diff --git a/expected/trigger.out b/expected/trigger.out new file mode 100644 index 000000000000..df61ddfca660 --- /dev/null +++ b/expected/trigger.out @@ -0,0 +1,35 @@ +\unset ECHO +1..33 +ok 1 - has_trigger() should pass +ok 2 - has_trigger() should have the proper description +ok 3 - has_trigger() should have the proper diagnostics +ok 4 - has_trigger() no desc should pass +ok 5 - has_trigger() no desc should have the proper description +ok 6 - has_trigger() no desc should have the proper diagnostics +ok 7 - has_trigger() no schema should pass +ok 8 - has_trigger() no schema should have the proper description +ok 9 - has_trigger() no schema should have the proper diagnostics +ok 10 - has_trigger() fail should fail +ok 11 - has_trigger() fail should have the proper description +ok 12 - has_trigger() fail should have the proper diagnostics +ok 13 - has_trigger() no schema fail should fail +ok 14 - has_trigger() no schema fail should have the proper description +ok 15 - has_trigger() no schema fail should have the proper diagnostics +ok 16 - trigger_is() should pass +ok 17 - trigger_is() should have the proper description +ok 18 - trigger_is() should have the proper diagnostics +ok 19 - trigger_is() no desc should pass +ok 20 - trigger_is() no desc should have the proper description +ok 21 - trigger_is() no desc should have the proper diagnostics +ok 22 - trigger_is() no schema should pass +ok 23 - trigger_is() no schema should have the proper description +ok 24 - trigger_is() no schema should have the proper diagnostics +ok 25 - trigger_is() no schema or desc should pass +ok 26 - trigger_is() no schema or desc should have the proper description +ok 27 - trigger_is() no schema or desc should have the proper diagnostics +ok 28 - trigger_is() fail should fail +ok 29 - trigger_is() fail should have the proper description +ok 30 - trigger_is() fail should have the proper diagnostics +ok 31 - trigger_is() no schema fail should fail +ok 32 - trigger_is() no schema fail should have the proper description +ok 33 - trigger_is() no schema fail should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index b15fb2e1072b..5adf0b196cbf 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2427,6 +2427,113 @@ BEGIN END; $$ LANGUAGE plpgsql; +-- has_trigger( schema, table, trigger, description ) +CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ +DECLARE + res boolean; +BEGIN + 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 + INTO res; + + RETURN ok( COALESCE(res, false), $4 ); +END; +$$ LANGUAGE plpgsql; + +-- has_trigger( schema, table, trigger ) +CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT has_trigger( + $1, $2, $3, + 'Table ' || $1 || '.' || $2 || ' should have trigger ' || $3 + ); +$$ LANGUAGE sql; + +-- has_trigger( table, trigger ) +CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME ) +RETURNS TEXT AS $$ +DECLARE + res boolean; +BEGIN + 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) + INTO res; + + RETURN ok( + COALESCE(res, false), + 'Table ' || $1 || ' should have trigger ' || $2 + ); +END; +$$ LANGUAGE plpgsql; + +-- 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 ni.nspname || '.' || 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, $4 || '.' || $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 ' || $3 || ' should call ' || $4 || '.' || $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 ' || $2 || ' should call ' || $3 || '()' + ); +$$ LANGUAGE sql; + -- check_test( test_output, pass, name, description, diag ) CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) RETURNS SETOF TEXT AS $$ diff --git a/sql/trigger.sql b/sql/trigger.sql new file mode 100644 index 000000000000..3b458107aefa --- /dev/null +++ b/sql/trigger.sql @@ -0,0 +1,126 @@ +\unset ECHO +\i test_setup.sql + +-- $Id: unique.sql 4376 2008-10-17 16:08:15Z david $ + +SELECT plan(33); +--SELECT * FROM no_plan(); + +-- This will be rolled back. :-) +SET client_min_messages = warning; +CREATE TABLE public.users( + nick text NOT NULL PRIMARY KEY, + pass text NOT NULL +); +CREATE FUNCTION public.hash_pass() RETURNS TRIGGER AS ' +BEGIN + NEW.pass := MD5( NEW.pass ); + RETURN NEW; +END; +' LANGUAGE plpgsql; + +CREATE TRIGGER set_users_pass +BEFORE INSERT OR UPDATE ON public.users +FOR EACH ROW EXECUTE PROCEDURE hash_pass(); +RESET client_min_messages; + +/****************************************************************************/ +-- Test has_trigger(). + +SELECT * FROM check_test( + has_trigger( 'public', 'users', 'set_users_pass', 'whatever' ), + true, + 'has_trigger()', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_trigger( 'public', 'users', 'set_users_pass' ), + true, + 'has_trigger() no desc', + 'Table public.users should have trigger set_users_pass', + '' +); + +SELECT * FROM check_test( + has_trigger( 'users', 'set_users_pass' ), + true, + 'has_trigger() no schema', + 'Table users should have trigger set_users_pass', + '' +); + +SELECT * FROM check_test( + has_trigger( 'public', 'users', 'nosuch', 'whatever' ), + false, + 'has_trigger() fail', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_trigger( 'users', 'nosuch' ), + false, + 'has_trigger() no schema fail', + 'Table users should have trigger nosuch', + '' +); + +/****************************************************************************/ +-- test trigger_is() + +SELECT * FROM check_test( + trigger_is( 'public', 'users', 'set_users_pass', 'public', 'hash_pass', 'whatever' ), + true, + 'trigger_is()', + 'whatever', + '' +); + +SELECT * FROM check_test( + trigger_is( 'public', 'users', 'set_users_pass', 'public', 'hash_pass' ), + true, + 'trigger_is() no desc', + 'Trigger set_users_pass should call public.hash_pass()', + '' +); + +SELECT * FROM check_test( + trigger_is( 'users', 'set_users_pass', 'hash_pass', 'whatever' ), + true, + 'trigger_is() no schema', + 'whatever', + '' +); + +SELECT * FROM check_test( + trigger_is( 'users', 'set_users_pass', 'hash_pass' ), + true, + 'trigger_is() no schema or desc', + 'Trigger set_users_pass should call hash_pass()', + '' +); + +SELECT * FROM check_test( + trigger_is( 'public', 'users', 'set_users_pass', 'public', 'oops', 'whatever' ), + false, + 'trigger_is() fail', + 'whatever', + ' have: public.hash_pass + want: public.oops' +); + +SELECT * FROM check_test( + trigger_is( 'users', 'set_users_pass', 'oops' ), + false, + 'trigger_is() no schema fail', + 'Trigger set_users_pass should call oops()', + ' have: hash_pass + want: oops' +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 0f8bea0b0154..3c78dedfd799 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -4,6 +4,17 @@ DROP FUNCTION check_test( TEXT, BOOLEAN ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); +DROP FUNCTION trigger_is ( NAME, NAME, NAME ); +DROP FUNCTION trigger_is ( NAME, NAME, NAME, text ); +DROP FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME ); +DROP FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME, text ); +DROP FUNCTION has_trigger ( NAME, NAME ); +DROP FUNCTION has_trigger ( NAME, NAME, NAME ); +DROP FUNCTION has_trigger ( NAME, NAME, NAME, NAME ); +DROP FUNCTION index_is_type ( NAME, NAME ); +DROP FUNCTION index_is_type ( NAME, NAME, NAME ); +DROP FUNCTION index_is_type ( NAME, NAME, NAME, NAME ); +DROP FUNCTION index_is_type ( NAME, NAME, NAME, NAME, text ); DROP FUNCTION is_clustered ( NAME ); DROP FUNCTION is_clustered ( NAME, NAME ); DROP FUNCTION is_clustered ( NAME, NAME, NAME ); From c6babbf43e6ac8761a81f61aeec2f1685142e9fb Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 27 Oct 2008 20:12:09 +0000 Subject: [PATCH 0214/1195] Added a cast from `int2vector` to `int[]` for compatibility on 8.0. --- compat/install-8.0.patch | 28 ++++++++++++++++++---------- compat/uninstall-8.0.sql | 2 ++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index 3cd1dbd6a450..6b3d9a7d0c16 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,8 +1,8 @@ ---- pgtap.sql.orig 2008-10-12 15:50:14.000000000 -0700 -+++ pgtap.sql 2008-10-12 15:50:14.000000000 -0700 -@@ -12,6 +12,14 @@ +--- pgtap.sql.orig 2008-10-27 13:20:25.000000000 -0700 ++++ pgtap.sql 2008-10-27 13:20:25.000000000 -0700 +@@ -12,6 +12,22 @@ -- ## CREATE SCHEMA TAPSCHEMA; - -- ## SET search_path TO TAPSCHEMA,public; + -- ## SET search_path TO TAPSCHEMA, public; +-- Cast oidvector to regtype[] like 8.1 does. +CREATE OR REPLACE FUNCTION oidvregtype(oidvector) @@ -11,11 +11,19 @@ +LANGUAGE sql IMMUTABLE STRICT; + +CREATE CAST (oidvector AS regtype[]) WITH FUNCTION oidvregtype(oidvector) AS ASSIGNMENT; ++ ++-- Cast int2vector to int[] like 8.1 does. ++CREATE OR REPLACE FUNCTION int2vint(int2vector) ++RETURNS int[] AS ++'SELECT COALESCE(string_to_array(textin(int2vectorout($1::int2vector)), '' '')::int[], ''{}''::int[]);' ++LANGUAGE sql IMMUTABLE STRICT; ++ ++CREATE CAST (int2vector AS int[]) WITH FUNCTION int2vint(int2vector) AS ASSIGNMENT; + CREATE OR REPLACE FUNCTION pg_typeof("any") RETURNS regtype AS '$libdir/pgtap' -@@ -84,53 +92,63 @@ +@@ -88,53 +104,63 @@ CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ DECLARE @@ -96,7 +104,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -194,10 +212,12 @@ +@@ -198,10 +224,12 @@ CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ DECLARE @@ -112,7 +120,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -463,13 +483,16 @@ +@@ -472,13 +500,16 @@ want ALIAS FOR $3; descr ALIAS FOR $4; result BOOLEAN; @@ -132,7 +140,7 @@ output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE '\n' || diag( ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -1028,15 +1051,16 @@ +@@ -1037,15 +1068,16 @@ CREATE OR REPLACE FUNCTION _def_is( TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -153,7 +161,7 @@ END; $$ LANGUAGE plpgsql; -@@ -1900,6 +1924,7 @@ +@@ -2544,6 +2576,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -161,7 +169,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -1910,8 +1935,10 @@ +@@ -2554,8 +2587,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. diff --git a/compat/uninstall-8.0.sql b/compat/uninstall-8.0.sql index 18590c5b800d..a998dae519b8 100644 --- a/compat/uninstall-8.0.sql +++ b/compat/uninstall-8.0.sql @@ -1,2 +1,4 @@ +DROP CAST (int2vector AS int[]); +DROP FUNCTION int2vint(int2vector); DROP CAST (oidvector AS regtype[]); DROP FUNCTION oidvregtype(oidvector); From 5d35eaea19f8ddf36a833f117b8b329db6fc1543 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 27 Oct 2008 20:12:49 +0000 Subject: [PATCH 0215/1195] Note new cast. --- README.pgtap | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index 5203163b0337..5bd22beffb70 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1748,9 +1748,10 @@ An `=` operator is also added that compares `name[]` values. ------------- A patch is applied that changes how some of the test functions are written. -Also, a single cast is added for compatibility: +Also, a few casts are added for compatibility: * `oidvector` to `regtypep[]`. +* `int2vector` to `integer[]`. Otherwise, all is the same as for 8.2 Do note, however, that the `throws_ok()` and `lives_ok()` functions do not work under 8.0. Don't even use them there. From a850b557cbba6d1c3978f79ce93d71c957a05595 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 27 Oct 2008 22:23:22 +0000 Subject: [PATCH 0216/1195] * Switched to pure SQL implementations of the `pg_version()` and `pg_version_num()` functions, to simplify including pgTAP in module distributions. * Added a note to `README.pgtap` about the need to avoid `pg_typeof()` and `cmp_ok()` in tests run as part of a distribution. --- Changes | 5 +++++ README.pgtap | 6 ++++++ pgtap.c | 9 +++++++++ pgtap.sql.in | 14 ++++++++------ 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index dc0b2c3b65e5..3690fd894b73 100644 --- a/Changes +++ b/Changes @@ -20,6 +20,11 @@ Revision history for pgTAP - Fixed a bug comparing `name[]`s on PostgreSQL 8.2, previously hacked around. - Added `has_trigger()` and `trigger_is()`. + - Switched to pure SQL implementations of the `pg_version()` and + `pg_version_num()` functions, to simplify including pgTAP in module + distributions. + - Added a note to `README.pgtap` about the need to avoid `pg_typeof()` + and `cmp_ok()` in tests run as part of a distribution. 0.13 2008-10-13T19:09:46 - Added `pg_version()` and `pg_version_num()`. diff --git a/README.pgtap b/README.pgtap index 5bd22beffb70..6ef777dcb924 100644 --- a/README.pgtap +++ b/README.pgtap @@ -143,6 +143,12 @@ Here's an example: Of course, if you already have the pgTAP functions in your testing database, you should skip `\i pgtap.sql` at the beginning of the script. +The only other limitation is that the `pg_typoeof()` function, which is +written in C, will not be available. You'll want to comment-out its +declaration in `pgtap.sql` and then avoid using `cmp_ok()`, since that +function relies on `pg_typeof()`. Note that `pg_typeof()` might be included in +PostgreSQL 8.4, in which case you wouldn't need to avoid it. + Now you're ready to run your test script! % psql -d try -Xf test.sql diff --git a/pgtap.c b/pgtap.c index 141c47ad1033..f4d0a0513335 100644 --- a/pgtap.c +++ b/pgtap.c @@ -10,8 +10,10 @@ PG_MODULE_MAGIC; #endif extern Datum pg_typeof (PG_FUNCTION_ARGS); +/* Switched to pure SQL. extern Datum pg_version (PG_FUNCTION_ARGS); extern Datum pg_version_num (PG_FUNCTION_ARGS); +*/ /* * pg_typeof() @@ -33,6 +35,8 @@ pg_typeof(PG_FUNCTION_ARGS) * other crap. Code borrowed from version.c. */ +/* Switched to pure SQL. Kept here for posterity. + PG_FUNCTION_INFO_V1(pg_version); Datum @@ -51,12 +55,15 @@ pg_version(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(ret); } +*/ + /* * pg_version_num() * Returns the version number as an integer. Support for pre-8.2 borrowed from * dumputils.c. */ +/* Switched to pure SQL. Ketp here for posterity. PG_FUNCTION_INFO_V1(pg_version_num); Datum @@ -81,3 +88,5 @@ pg_version_num(PG_FUNCTION_ARGS) PG_RETURN_INT32( (100 * vmaj + vmin) * 100 + vrev ); #endif } + +*/ diff --git a/pgtap.sql.in b/pgtap.sql.in index 5adf0b196cbf..c323955393c7 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -18,14 +18,16 @@ AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE; CREATE OR REPLACE FUNCTION pg_version() -RETURNS text -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE; +RETURNS text AS 'SELECT current_setting(''server_version'')' +LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION pg_version_num() -RETURNS integer -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE; +RETURNS integer AS $$ + SELECT s.a[1]::int * 10000 + s.a[2]::int * 100 + s.a[3]::int + FROM ( + SELECT string_to_array(current_setting('server_version'), '.') AS a + ) AS s; +$$ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION os_name() RETURNS TEXT AS 'SELECT ''__OS__''::text;' From 07c08c862a280deb2e467491ecc3a64fc58b97d1 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 27 Oct 2008 22:30:33 +0000 Subject: [PATCH 0217/1195] 8.0 compatibility patch adjustment. --- README.pgtap | 7 ++++--- compat/install-8.0.patch | 16 ++++++++-------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/README.pgtap b/README.pgtap index 6ef777dcb924..a28c10f96718 100644 --- a/README.pgtap +++ b/README.pgtap @@ -145,9 +145,10 @@ you should skip `\i pgtap.sql` at the beginning of the script. The only other limitation is that the `pg_typoeof()` function, which is written in C, will not be available. You'll want to comment-out its -declaration in `pgtap.sql` and then avoid using `cmp_ok()`, since that -function relies on `pg_typeof()`. Note that `pg_typeof()` might be included in -PostgreSQL 8.4, in which case you wouldn't need to avoid it. +declaration in the bundled copy of `pgtap.sql` and then avoid using +`cmp_ok()`, since that function relies on `pg_typeof()`. Note that +`pg_typeof()` might be included in PostgreSQL 8.4, in which case you wouldn't +need to avoid it. Now you're ready to run your test script! diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index 6b3d9a7d0c16..f0a0d6e514ac 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.orig 2008-10-27 13:20:25.000000000 -0700 -+++ pgtap.sql 2008-10-27 13:20:25.000000000 -0700 +--- pgtap.sql.orig 2008-10-27 15:40:47.000000000 -0700 ++++ pgtap.sql 2008-10-27 15:40:47.000000000 -0700 @@ -12,6 +12,22 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA, public; @@ -23,7 +23,7 @@ CREATE OR REPLACE FUNCTION pg_typeof("any") RETURNS regtype AS '$libdir/pgtap' -@@ -88,53 +104,63 @@ +@@ -90,53 +106,63 @@ CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ DECLARE @@ -104,7 +104,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -198,10 +224,12 @@ +@@ -200,10 +226,12 @@ CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ DECLARE @@ -120,7 +120,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -472,13 +500,16 @@ +@@ -474,13 +502,16 @@ want ALIAS FOR $3; descr ALIAS FOR $4; result BOOLEAN; @@ -140,7 +140,7 @@ output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE '\n' || diag( ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -1037,15 +1068,16 @@ +@@ -1039,15 +1070,16 @@ CREATE OR REPLACE FUNCTION _def_is( TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -161,7 +161,7 @@ END; $$ LANGUAGE plpgsql; -@@ -2544,6 +2576,7 @@ +@@ -2546,6 +2578,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -169,7 +169,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -2554,8 +2587,10 @@ +@@ -2556,8 +2589,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. From 26f4dc56efaaac6f5d40d70a854329a6897d23b0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 27 Oct 2008 22:31:23 +0000 Subject: [PATCH 0218/1195] Timestamped for 0.14 release. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 3690fd894b73..aaf9da4929be 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,6 @@ Revision history for pgTAP -0.14 +0.14 2008-10-27T22:43:36 - Added `SET search_path` statements to `uninstall_pgtap.sql.in` so that it will work properly when TAP is installed in its own schema. Thanks to Ben for the catch! From 1c5992f58d03b6df4ce3e0fcd5903c6794465c41 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 27 Oct 2008 22:39:00 +0000 Subject: [PATCH 0219/1195] Incremented version number to 0.15. --- Changes | 2 ++ README.pgtap | 2 +- bin/pg_prove | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index aaf9da4929be..54016703d10f 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Revision history for pgTAP +0.15 + 0.14 2008-10-27T22:43:36 - Added `SET search_path` statements to `uninstall_pgtap.sql.in` so that it will work properly when TAP is installed in its own schema. Thanks to diff --git a/README.pgtap b/README.pgtap index a28c10f96718..9a516bda15e9 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.14 +pgTAP 0.15 ========== pgTAP is a collection of TAP-emitting unit testing functions written in diff --git a/bin/pg_prove b/bin/pg_prove index 50c227b39c5b..8313274287a9 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -6,7 +6,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.14'; +our $VERSION = '0.15'; Getopt::Long::Configure (qw(bundling)); From 6c5b59972d5cb74fb5daa974921134036345af24 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 27 Oct 2008 22:55:27 +0000 Subject: [PATCH 0220/1195] Oops. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 54016703d10f..bdea0165fb66 100644 --- a/Changes +++ b/Changes @@ -8,7 +8,7 @@ Revision history for pgTAP Ben for the catch! - Added commands to drop `pg_version()` and `pg_version_num()` to`uninstall_pgtap.sql.in`. - - Added `has_index()`, `index_is_unique()`, `index_is_primary(), + - Added `has_index()`, `index_is_unique()`, `index_is_primary()`, `is_clustered()`, and `index_is_type()`. - Added `os_name()`. This is somewhat experimental. If you have `uname`, it's probably correct, but assistance in improving OS detection in the From daaee0db22652f403d687fc519d07c1c37ada4dd Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 23 Nov 2008 05:53:42 +0000 Subject: [PATCH 0221/1195] To Do. --- README.pgtap | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index 9a516bda15e9..469dce0e51e0 100644 --- a/README.pgtap +++ b/README.pgtap @@ -147,7 +147,7 @@ The only other limitation is that the `pg_typoeof()` function, which is written in C, will not be available. You'll want to comment-out its declaration in the bundled copy of `pgtap.sql` and then avoid using `cmp_ok()`, since that function relies on `pg_typeof()`. Note that -`pg_typeof()` might be included in PostgreSQL 8.4, in which case you wouldn't +`pg_typeof()` will be included in PostgreSQL 8.4, in which case you wouldn't need to avoid it. Now you're ready to run your test script! @@ -1735,6 +1735,12 @@ instead: To see the specifics for each version of PostgreSQL, consult the files in the `compat/` directory in the pgTAP distribution. +8.4 and Higher +-------------- + +The `pg_typeof()` function will not be built, as it is included in PostgreSQL +8.4. + 8.3 and Higher -------------- @@ -1766,6 +1772,10 @@ and `lives_ok()` functions do not work under 8.0. Don't even use them there. To Do ----- +* Update build for 8.4, where `pg_typeof()` is already included. +* Add xUnit-style support by adding a function to run test functions that emit + TAP. The function should support startup and shutdown and setup and teardown + functions, too. * Update the Makefile to process pg_prove and set the proper path to Perl on the shebang line. Will likely require a `$(PERL)` environment variable. * Update the Makefile to check for TAP::Harness and warn if it's not From 0149587f0dbd940b7c85fe80b7e4eaf3bc5f202b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 23 Nov 2008 05:56:40 +0000 Subject: [PATCH 0222/1195] Changed `pg_typeof()` from `IMMUTABLE` to `STABLE`. --- Changes | 2 ++ pgtap.sql.in | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index bdea0165fb66..a68d9fe091d1 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,8 @@ Revision history for pgTAP 0.15 + - Changed `pg_typeof()` from immutable to stable, in compliance with its + configuration in the forthcoming PostgreSQL 8.4. 0.14 2008-10-27T22:43:36 - Added `SET search_path` statements to `uninstall_pgtap.sql.in` so that diff --git a/pgtap.sql.in b/pgtap.sql.in index c323955393c7..aed9b60ec568 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -15,7 +15,7 @@ CREATE OR REPLACE FUNCTION pg_typeof("any") RETURNS regtype AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE; +LANGUAGE C STABLE; CREATE OR REPLACE FUNCTION pg_version() RETURNS text AS 'SELECT current_setting(''server_version'')' From a46ff14797868d855330626e35e47c8ee9bc1d59 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 18 Jan 2009 19:02:13 +0000 Subject: [PATCH 0223/1195] * Added `do_tap()`, which finds test functions and runs them, allowing tap tests to be embedded in functions and then executed all at once. * Added `runtests()`, which introduces xUnit-style test running, for those who prefer to put their tests in a slew of test functions and then run one function to run all of the test functions (plus startup, setup, teardown, and shutdown tests). * Added `findfuncs()`, which is used by `do_tap()` to find the functions to execute. * The `col_type_is()` function now requires that the type be visible in the search path when it is called without a schema argument. * The `plan()` function no longer resets the `client_min_messages` setting to its default value, but leaves it set to whatever it was set to before the function was called. * Fixed a typo in the documentation of the short version of the `--version` option to `pg_prove`, which is `-V`, not `-v`. --- Changes | 15 +++ README.pgtap | 117 ++++++++++++++++++++- bin/pg_prove | 9 +- expected/do_tap.out | 40 +++++++ expected/runtests.out | 38 +++++++ expected/util.out | 3 +- pgtap.sql.in | 238 ++++++++++++++++++++++++++++++++++++++++-- sql/do_tap.sql | 50 +++++++++ sql/runtests.sql | 76 ++++++++++++++ sql/trigger.sql | 2 +- sql/util.sql | 8 +- 11 files changed, 576 insertions(+), 20 deletions(-) create mode 100644 expected/do_tap.out create mode 100644 expected/runtests.out create mode 100644 sql/do_tap.sql create mode 100644 sql/runtests.sql diff --git a/Changes b/Changes index a68d9fe091d1..cb2f5bca908f 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,21 @@ Revision history for pgTAP 0.15 - Changed `pg_typeof()` from immutable to stable, in compliance with its configuration in the forthcoming PostgreSQL 8.4. + - Added `do_tap()`, which finds test functions and runs them, allowing + tap tests to be embedded in functions and then executed all at once. + - Added `runtests()`, which introduces xUnit-style test running, for + those who prefer to put their tests in a slew of test functions and + then run one function to run all of the test functions (plus startup, + setup, teardown, and shutdown tests). + - Added `findfuncs()`, which is used by `do_tap()` to find the functions + to execute. + - The `col_type_is()` function now requires that the type be visible in + the search path when it is called without a schema argument. + - The `plan()` function no longer resets the `client_min_messages` + setting to its default value, but leaves it set to whatever it was set + to before the function was called. + - Fixed a typo in the documentation of the short version of the + `--version` option to `pg_prove`, which is `-V`, not `-v`. 0.14 2008-10-27T22:43:36 - Added `SET search_path` statements to `uninstall_pgtap.sql.in` so that diff --git a/README.pgtap b/README.pgtap index 469dce0e51e0..e5ae1744b50a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -699,10 +699,10 @@ This function tests that the specified column is of a particular type. If it fails, it will emit diagnostics naming the actual type. The first argument is the schema name, the second the table name, the third the column name, the fourth the type, and the fifth is the test description. If the schema is -omitted, the table must be visible in the search path. If the test description -is omitted, it will be set to "Column `:table`.`:column` should be type -`:type`". Note that this test will fail if the table or column in question -does not exist. +omitted, the table and the type must be visible in the search path. If the +test description is omitted, it will be set to "Column `:table`.`:column` +should be type `:type`". Note that this test will fail if the table or column +in question does not exist. The type argument should be formatted as it would be displayed in the view of a table using the `\d` command in `psql`. For example, if you have a numeric @@ -1647,6 +1647,98 @@ it gets the job done. Of course, if your diagnostics use something other than indented "have" and "want", such failures will be easier to read. But either way, do test your diagnostics! +Tap that Batch +-------------- + +Sometimes it can be useful to batch a lot of TAP tests into a function. The +simplest way to do so is to define a function that `RETURNS SETOF TEXT` and +then simply call `RETURN NEXT` for each TAP test. Here's a simple example: + + CREATE OR REPLACE FUNCTION my_tests( + ) RETURNS SETOF TEXT AS $$ + BEGIN + RETURN NEXT pass( 'plpgsql simple' ); + RETURN NEXT pass( 'plpgsql simple 2' ); + END; + $$ LANGUAGE plpgsql; + +Then you can just call the function to run all of your TAP tests at once: + + SELECT plan(2); + SELECT * FROM my_tests(); + SELECT * FROM finish(); + +### `do_tap( schema, pattern )` ### +### `do_tap( schema )` ### +### `do_tap( pattern )` ### +### `do_tap()` ### + + SELECT plan(32); + SELECT * FROM do_tap('testschema'); + SELECT * FROM finish(); + +If you like you can create a whole slew of these batched tap functions, and +then use the `do_tap()` function to run them all at once. If passed no +arguments, it will attempt to find all visible functions that start with +"test". If passed a schema name, it will look for and run test functions only +in that schema (be sure to cast the schema to `name` if it is the only +argument). If passed a regular expression pattern, it will look for function +names that match that pattern in the search path. If passed both, it will of +course only search for test functions that match the function in the named +schema. + +This can be very useful if you prefer to keep all of your TAP tests in +functions defined in the database. Simply call `plan()`, use `do_tap()` to +execute all of your tests, and then call `finish()`. + +As a bonus, if `client_min_messages` is set to "warning", "error", "fatal", or +"panic", the name of each function will be emitted as a diagnostic message +before it is called. For example, if `do_tap()` found and executed two TAP +testing functions an `client_min_messages` is set to "warning", output will +look something like this: + + # public.test_this() + ok 1 - simple pass + ok 2 - another simple pass + # public.test_that() + ok 3 - that simple + ok 4 - that simple 2 + +Which will make it much easier to tell what functions need to be examined for +failing tests. + +### `runtests( schema, match )` ### +### `runtests( schema )` ### +### `runtests( match )` ### +### `runtests( )` ### + + SELECT * FROM runtests('testschema'); + +If you'd like pgTAP to plan, run all of your tests functions, and finish all +in one fell swoop, use `runtests()`. This most closely emulates the xUnit +testing environment, similar to the functionality of +[PGUnit](http://en.dklab.ru/lib/dklab_pgunit/) and +[Epic](http://www.epictest.org/). + +As with `do_tap()`, you can pass in a schema argument and/or a pattern that +the names of the tests functions can match. But unlike `do_tap()`, +`runtests()` fully supports startup, shutdown, setup, and teardown functions, +as well as transactional rollbacks between tests. It also outputs the test +plan and fishes the tests, so you don't have to call `plan()` or `finish()` +yourself. + +The fixture functions run by `runtests()` are as follows: + +* `^startup` - Functions whose names start with "startup" are run in + alphabetical order before any test functions are run. +* `^setup` - Functions whose names start with "setup" are run in alphabetical + order before each test function is run. +* `^teardown` - Functions whose names start with "teardown" are run in + alphabetical order after each test function is run. They will not be run, + however, after a test that has died. +* `^shutdown` - Functions whose names start with "shutdown" are run in + alphabetical order after all test functions have been run. + Utility Functions ================= @@ -1722,6 +1814,22 @@ executing the comparison, but might be generally useful. -----------+----------- integer | numeric +### `findfuncs( schema, pattern )` ### +### `findfuncs( pattern )` ### + + SELECT findfuncs('myschema', '^test' ); + +This function searches the named schema or, if no schema is passed, the search +patch, for all functions that match the regular expression pattern. The +functions it finds are returned as an array of text values, with each value +consisting of the schema name, a dot, and the function name. For example: + + SELECT findfuncs('tests', '^test); + findfuncs + ----------------------------------- + {tests.test_foo,tests."test bar"} + (1 row) + Compatibility ============= @@ -1772,6 +1880,7 @@ and `lives_ok()` functions do not work under 8.0. Don't even use them there. To Do ----- +* Add a `pgtap_version()` function. * Update build for 8.4, where `pg_typeof()` is already included. * Add xUnit-style support by adding a function to run test functions that emit TAP. The function should support startup and shutdown and setup and teardown diff --git a/bin/pg_prove b/bin/pg_prove index 8313274287a9..e3b7534a7326 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -115,7 +115,7 @@ The test scripts should be a series of SQL statements -v --verbose Display output of test scripts while running them. -h --help Print a usage statement and exit. -m --man Print the complete documentation and exit. - -v --version Print the version number and exit. + -V --version Print the version number and exit. -P --pset OPTION=VALUE Set psql printing option. =head1 Options Details @@ -190,6 +190,9 @@ on the supported options. =item C<--verbose> + pg_prove --verbose + pg_prove -v + Display standard output of test scripts while running them. This behavior can also be triggered by setting the C<$TEST_VERBOSE> environment variable to a true value. @@ -239,12 +242,12 @@ Outputs a brief description of the options supported by C and exits. Outputs this documentation and exits. -=item C<-v> +=item C<-V> =item C<--version> pg_prove --version - pg_prove -v + pg_prove -V Outputs the program name and version and exits. diff --git a/expected/do_tap.out b/expected/do_tap.out new file mode 100644 index 000000000000..aad638d4dace --- /dev/null +++ b/expected/do_tap.out @@ -0,0 +1,40 @@ +\unset ECHO +1..26 +ok 1 - findfuncs(public, ^test) should work +ok 2 - findfuncs(^test) should work +# public."test ident"() +ok 3 - ident +ok 4 - ident 2 +# public.test_this() +ok 5 - simple pass +ok 6 - another simple pass +# public.testplpgsql() +ok 7 - plpgsql simple +ok 8 - plpgsql simple 2 +# public."test ident"() +ok 9 - ident +ok 10 - ident 2 +# public.test_this() +ok 11 - simple pass +ok 12 - another simple pass +# public.testplpgsql() +ok 13 - plpgsql simple +ok 14 - plpgsql simple 2 +# public."test ident"() +ok 15 - ident +ok 16 - ident 2 +# public.test_this() +ok 17 - simple pass +ok 18 - another simple pass +# public.testplpgsql() +ok 19 - plpgsql simple +ok 20 - plpgsql simple 2 +# public."test ident"() +ok 21 - ident +ok 22 - ident 2 +# public.test_this() +ok 23 - simple pass +ok 24 - another simple pass +# public.testplpgsql() +ok 25 - plpgsql simple +ok 26 - plpgsql simple 2 diff --git a/expected/runtests.out b/expected/runtests.out new file mode 100644 index 000000000000..4df415942a45 --- /dev/null +++ b/expected/runtests.out @@ -0,0 +1,38 @@ +\unset ECHO +ok 1 - starting up +ok 2 - starting up some more +# whatever."test ident"() +ok 3 - setup +ok 4 - Should be nothing in the test table +ok 5 - setup more +ok 6 - ident +ok 7 - ident 2 +ok 8 - teardown +ok 9 - teardown more +# whatever.test_this() +ok 10 - setup +ok 11 - Should be nothing in the test table +ok 12 - setup more +ok 13 - simple pass +ok 14 - another simple pass +ok 15 - teardown +ok 16 - teardown more +# whatever.testplpgsql() +ok 17 - setup +ok 18 - Should be nothing in the test table +ok 19 - setup more +ok 20 - plpgsql simple +ok 21 - plpgsql simple 2 +ok 22 - Should be a 1 in the test table +ok 23 - teardown +ok 24 - teardown more +# whatever.testz() +ok 25 - setup +ok 26 - Should be nothing in the test table +ok 27 - setup more +ok 28 - Late test should find nothing in the test table +ok 29 - teardown +ok 30 - teardown more +ok 31 - shutting down +ok 32 - shutting down more +1..32 diff --git a/expected/util.out b/expected/util.out index d0b471397a65..9174b1eafc07 100644 --- a/expected/util.out +++ b/expected/util.out @@ -1,5 +1,5 @@ \unset ECHO -1..10 +1..11 ok 1 - pg_type(int) should work ok 2 - pg_type(numeric) should work ok 3 - pg_type(text) should work @@ -10,3 +10,4 @@ ok 7 - pg_version() should return same as "sever_version" setting ok 8 - pg_version_num() should return integer ok 9 - pg_version_num() should be correct ok 10 - os_name() should output something like an OS name +ok 11 - findfincs() should return distinct values diff --git a/pgtap.sql.in b/pgtap.sql.in index aed9b60ec568..d1d30325eb50 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -37,9 +37,10 @@ CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ DECLARE rcount INTEGER; + cmm text := current_setting('client_min_messages'); BEGIN BEGIN - SET LOCAL client_min_messages = warning; + PERFORM set_config('client_min_messages', 'warning', true); EXECUTE ' CREATE TEMP TABLE __tcache__ ( id SERIAL PRIMARY KEY, @@ -62,7 +63,7 @@ BEGIN '; EXCEPTION WHEN duplicate_table THEN - RESET client_min_messages; + PERFORM set_config('client_min_messages', cmm, true); -- Raise an exception if there's already a plan. EXECUTE 'SELECT TRUE FROM __tcache__ WHERE label = ''plan'''; GET DIAGNOSTICS rcount = ROW_COUNT; @@ -71,7 +72,7 @@ BEGIN END IF; END; - RESET client_min_messages; + PERFORM set_config('client_min_messages', cmm, true); -- Save the plan and return. PERFORM _set('plan', $1 ); @@ -207,17 +208,14 @@ BEGIN END; $$ LANGUAGE plpgsql strict; -CREATE OR REPLACE FUNCTION finish () +CREATE OR REPLACE FUNCTION _finish ( INTEGER, INTEGER, INTEGER) RETURNS SETOF TEXT AS $$ DECLARE - curr_test INTEGER; - exp_tests INTEGER; - num_faild INTEGER; + curr_test ALIAS FOR $1; + exp_tests INTEGER := $2; + num_faild ALIAS FOR $3; plural CHAR; BEGIN - curr_test := _get('curr_test'); - exp_tests := _get('plan'); - num_faild := num_failed(); plural := CASE exp_tests WHEN 1 THEN '' ELSE 's' END; IF curr_test IS NULL THEN @@ -248,6 +246,15 @@ BEGIN END; $$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION finish () +RETURNS SETOF TEXT AS $$ + SELECT * FROM _finish( + _get('curr_test'), + _get('plan'), + num_failed() + ); +$$ LANGUAGE sql; + CREATE OR REPLACE FUNCTION diag ( msg text ) RETURNS TEXT AS $$ SELECT '# ' || replace( @@ -995,6 +1002,7 @@ BEGIN FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c WHERE a.attrelid = c.oid AND pg_table_is_visible(c.oid) + AND pg_type_is_visible(a.atttypid) AND c.relname = $2 AND attnum > 0 AND NOT attisdropped @@ -1003,6 +1011,7 @@ BEGIN SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) into actual_type FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a WHERE n.oid = c.relnamespace + AND pg_type_is_visible(a.atttypid) AND c.oid = a.attrelid AND n.nspname = $1 AND c.relname = $2 @@ -2637,3 +2646,212 @@ CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN ) RETURNS SETOF TEXT AS $$ SELECT * FROM check_test( $1, $2, NULL, NULL, NULL ); $$ LANGUAGE sql; + + +CREATE OR REPLACE FUNCTION findfuncs( NAME, TEXT ) +RETURNS TEXT[] AS $$ + SELECT ARRAY( + SELECT DISTINCT quote_ident(n.nspname) || '.' || quote_ident(p.proname) 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 + ORDER BY pname + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION findfuncs( TEXT ) +RETURNS TEXT[] AS $$ + SELECT ARRAY( + SELECT DISTINCT quote_ident(n.nspname) || '.' || quote_ident(p.proname) 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 + ORDER BY pname + ); +$$ 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 $$ +DECLARE + cmm text := current_setting('client_min_messages'); +BEGIN + PERFORM set_config('client_min_messages', 'warning', true); + DROP TABLE __tresults__; + DROP TABLE __tcache__; + PERFORM set_config('client_min_messages', cmm, true); + RETURN TRUE; +END +$$ LANGUAGE plpgsql; + +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; + verbose boolean := _is_verbose(); + num_faild INTEGER := 0; +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; + + BEGIN + FOR i IN 1..array_upper(tests, 1) LOOP + BEGIN + -- What test are we running? + IF verbose THEN RETURN NEXT diag(tests[i] || '()'); END IF; + + -- Run the setup functions. + FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; + + -- Run the actual test function. + FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP + RETURN NEXT tap; + END LOOP; + + -- Run the teardown functions. + FOR tap IN SELECT * FROM _runem(teardown, false) LOOP RETURN NEXT tap; END LOOP; + + -- Remember how many failed and then roll back. + num_faild := num_faild + num_failed(); + RAISE EXCEPTION '__TAP_ROLLBACK__'; + + EXCEPTION WHEN raise_exception THEN + IF SQLERRM <> '__TAP_ROLLBACK__' THEN + -- We didn't raise it, so propagate it. + RAISE EXCEPTION '%', SQLERRM; + END IF; + END; + END LOOP; + + -- Run the shutdown functions. + FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; + + -- Raise an exception to rollback any changes. + RAISE EXCEPTION '__TAP_ROLLBACK__'; + EXCEPTION WHEN raise_exception THEN + IF SQLERRM <> '__TAP_ROLLBACK__' THEN + -- We didn't raise it, so propagate it. + RAISE EXCEPTION '%', SQLERRM; + END IF; + END; + -- Finish up. + FOR tap IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) 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 ) + ); +$$ 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 ) + ); +$$ LANGUAGE sql; + +-- runtests( ) +CREATE OR REPLACE FUNCTION runtests( ) +RETURNS SETOF TEXT AS $$ + SELECT * FROM runtests( '^test' ); +$$ LANGUAGE sql; diff --git a/sql/do_tap.sql b/sql/do_tap.sql new file mode 100644 index 000000000000..105248b3db97 --- /dev/null +++ b/sql/do_tap.sql @@ -0,0 +1,50 @@ +\unset ECHO +\i test_setup.sql +SET client_min_messages = notice; + +-- $Id$ + +SELECT plan(26); +--SELECT * FROM no_plan(); + +CREATE OR REPLACE FUNCTION public.test_this() RETURNS SETOF TEXT AS $$ + SELECT pass('simple pass') AS foo + UNION SELECT pass('another simple pass') + ORDER BY foo ASC; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION public.testplpgsql() RETURNS SETOF TEXT AS $$ +BEGIN + RETURN NEXT pass( 'plpgsql simple' ); + RETURN NEXT pass( 'plpgsql simple 2' ); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION public."test ident"() RETURNS SETOF TEXT AS $$ +BEGIN + RETURN NEXT pass( 'ident' ); + RETURN NEXT pass( 'ident 2' ); +END; +$$ LANGUAGE plpgsql; + +SELECT is( + findfuncs('public', '^test'), + ARRAY[ 'public."test ident"', 'public.test_this', 'public.testplpgsql' ], + 'findfuncs(public, ^test) should work' +); + +SELECT is( + findfuncs('^test'), + ARRAY[ 'public."test ident"', 'public.test_this', 'public.testplpgsql' ], + 'findfuncs(^test) should work' +); + +SELECT * FROM do_tap('public', '^test'); +SELECT * FROM do_tap('public'::name); +SELECT * FROM do_tap('^test'); +SELECT * FROM do_tap(); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; diff --git a/sql/runtests.sql b/sql/runtests.sql new file mode 100644 index 000000000000..1ccc456a81e2 --- /dev/null +++ b/sql/runtests.sql @@ -0,0 +1,76 @@ +\unset ECHO +\i test_setup.sql +SET client_min_messages = warning; + +-- $Id$ + +CREATE SCHEMA whatever; +CREATE TABLE whatever.foo ( id serial primary key ); + +-- Make sure we get test function names. +SET client_min_messages = notice; + +CREATE OR REPLACE FUNCTION whatever.startup() RETURNS SETOF TEXT AS $$ + SELECT pass('starting up'); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION whatever.startup_more() RETURNS SETOF TEXT AS $$ + SELECT pass('starting up some more'); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION whatever.setup() RETURNS SETOF TEXT AS $$ + SELECT pass('setup') + UNION + SELECT is( MAX(id), NULL, 'Should be nothing in the test table') FROM whatever.foo; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION whatever.setup_more() RETURNS SETOF TEXT AS $$ + SELECT pass('setup more'); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION whatever.teardown() RETURNS SETOF TEXT AS $$ + SELECT pass('teardown'); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION whatever.teardown_more() RETURNS SETOF TEXT AS $$ + SELECT pass('teardown more'); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION whatever.shutdown() RETURNS SETOF TEXT AS $$ + SELECT pass('shutting down'); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION whatever.shutdown_more() RETURNS SETOF TEXT AS $$ + SELECT pass('shutting down more'); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION whatever.test_this() RETURNS SETOF TEXT AS $$ + SELECT pass('simple pass') AS foo + UNION SELECT pass('another simple pass') + ORDER BY foo ASC; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION whatever.testplpgsql() RETURNS SETOF TEXT AS $$ +BEGIN + RETURN NEXT pass( 'plpgsql simple' ); + RETURN NEXT pass( 'plpgsql simple 2' ); + INSERT INTO whatever.foo VALUES(1); + RETURN NEXT is( MAX(id), 1, 'Should be a 1 in the test table') FROM whatever.foo; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION whatever.testz() RETURNS SETOF TEXT AS $$ + SELECT is( MAX(id), NULL, 'Late test should find nothing in the test table') FROM whatever.foo; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION whatever."test ident"() RETURNS SETOF TEXT AS $$ +BEGIN + RETURN NEXT pass( 'ident' ); + RETURN NEXT pass( 'ident 2' ); +END; +$$ LANGUAGE plpgsql; + +-- Run the actual tests. Yes, it's a one-liner! +SELECT * FROM runtests('whatever'::name); + +ROLLBACK; diff --git a/sql/trigger.sql b/sql/trigger.sql index 3b458107aefa..023fd5fd6b92 100644 --- a/sql/trigger.sql +++ b/sql/trigger.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql --- $Id: unique.sql 4376 2008-10-17 16:08:15Z david $ +-- $Id$ SELECT plan(33); --SELECT * FROM no_plan(); diff --git a/sql/util.sql b/sql/util.sql index 195c1dbff604..3857eabf2aa0 100644 --- a/sql/util.sql +++ b/sql/util.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(10); +SELECT plan(11); --SELECT * FROM no_plan(); SELECT is( pg_typeof(42), 'integer', 'pg_type(int) should work' ); @@ -48,6 +48,12 @@ SELECT matches( 'os_name() should output something like an OS name' ); +SELECT is( + findfuncs('pg_catalog', '^abs$'), + ARRAY['pg_catalog.abs'], + 'findfincs() should return distinct values' +); + /****************************************************************************/ /****************************************************************************/ -- Finish the tests and clean up. From b226a1ae31db44fe0b47949ddc6665b421ca739f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 18 Jan 2009 19:34:05 +0000 Subject: [PATCH 0224/1195] Added `pgtap_version()`. --- Changes | 1 + Makefile | 5 +++-- README.pgtap | 16 ++++++++++++---- expected/util.out | 5 +++-- pgtap.sql.in | 4 ++++ sql/util.sql | 11 +++++++++-- 6 files changed, 32 insertions(+), 10 deletions(-) diff --git a/Changes b/Changes index cb2f5bca908f..275465f13059 100644 --- a/Changes +++ b/Changes @@ -18,6 +18,7 @@ Revision history for pgTAP to before the function was called. - Fixed a typo in the documentation of the short version of the `--version` option to `pg_prove`, which is `-V`, not `-v`. + - Added `pgtap_version()`. 0.14 2008-10-27T22:43:36 - Added `SET search_path` statements to `uninstall_pgtap.sql.in` so that diff --git a/Makefile b/Makefile index c396293cd7dc..a64cf52655f6 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ endif PGVER_MAJOR = $(shell echo $(VERSION) | awk -F. '{ print ($$1 + 0) }') PGVER_MINOR = $(shell echo $(VERSION) | awk -F. '{ print ($$2 + 0) }') PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') +PGTAP_VERSION = 0.15 # We support 8.0 and later. ifneq ($(PGVER_MAJOR), 8) @@ -102,9 +103,9 @@ endif pgtap.sql: pgtap.sql.in test_setup.sql ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' $(REMOVE_E) pgtap.sql.in > pgtap.sql + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' $(REMOVE_E) pgtap.sql.in > pgtap.sql else - sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' $(REMOVE_E) pgtap.sql.in > pgtap.sql + sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' $(REMOVE_E) pgtap.sql.in > pgtap.sql endif ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 3) diff --git a/README.pgtap b/README.pgtap index e5ae1744b50a..9c61a034ba86 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1745,6 +1745,18 @@ Utility Functions Along with the usual array of testing, planning, and diagnostic functions, pTAP provides a few extra functions to make the work of testing more pleasant. +### `pgtap_version()` ### + + SELECT pgtap_version(); + +Returns the version of pgTAP installed in the server. The value is NUMERIC, +and thus suitable for comparing to a decimal value: + + SELECT CASE WHEN pgtap_version() < 0.14 + THEN skip('No index assertions before pgTAP 0.14') + ELSE has_index('users', 'idx_user_name') + END; + ### `pg_version()` ### SELECT pg_version(); @@ -1880,11 +1892,7 @@ and `lives_ok()` functions do not work under 8.0. Don't even use them there. To Do ----- -* Add a `pgtap_version()` function. * Update build for 8.4, where `pg_typeof()` is already included. -* Add xUnit-style support by adding a function to run test functions that emit - TAP. The function should support startup and shutdown and setup and teardown - functions, too. * Update the Makefile to process pg_prove and set the proper path to Perl on the shebang line. Will likely require a `$(PERL)` environment variable. * Update the Makefile to check for TAP::Harness and warn if it's not diff --git a/expected/util.out b/expected/util.out index 9174b1eafc07..cfd7a1459056 100644 --- a/expected/util.out +++ b/expected/util.out @@ -1,13 +1,14 @@ \unset ECHO -1..11 +1..12 ok 1 - pg_type(int) should work ok 2 - pg_type(numeric) should work ok 3 - pg_type(text) should work ok 4 - pg_version() should return text ok 5 - pg_version() should return same as "sever_version" setting -ok 6 - pg_version should work +ok 6 - pg_version() should work ok 7 - pg_version() should return same as "sever_version" setting ok 8 - pg_version_num() should return integer ok 9 - pg_version_num() should be correct ok 10 - os_name() should output something like an OS name ok 11 - findfincs() should return distinct values +ok 12 - pgtap_version() should work diff --git a/pgtap.sql.in b/pgtap.sql.in index d1d30325eb50..059858dd67b3 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -33,6 +33,10 @@ 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 diff --git a/sql/util.sql b/sql/util.sql index 3857eabf2aa0..77e1714a899c 100644 --- a/sql/util.sql +++ b/sql/util.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(11); +SELECT plan(12); --SELECT * FROM no_plan(); SELECT is( pg_typeof(42), 'integer', 'pg_type(int) should work' ); @@ -19,7 +19,7 @@ SELECT is( SELECT matches( pg_version(), '^8[.][[:digit:]]{1,2}[.][[:digit:]]{1,2}$', - 'pg_version should work' + 'pg_version() should work' ); SELECT CASE WHEN pg_version_num() < 81000 @@ -54,6 +54,13 @@ SELECT is( 'findfincs() should return distinct values' ); +SELECT matches( + pgtap_version()::text, + '^0[.][[:digit:]]{2}$', + 'pgtap_version() should work' +); + + /****************************************************************************/ /****************************************************************************/ -- Finish the tests and clean up. From 5a9e237c0b25fb0838c653e0fd693da137a11499 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 18 Jan 2009 19:42:07 +0000 Subject: [PATCH 0225/1195] Updated `uninstall_pgtqp.sql.in` to have newly-added functions. --- uninstall_pgtap.sql.in | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 3c78dedfd799..98059bda9816 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,5 +1,20 @@ -- $Id$ -- ## SET search_path TO TAPSCHEMA, public; +DROP FUNCTION runtests( ); +DROP FUNCTION runtests( TEXT ); +DROP FUNCTION runtests( NAME ); +DROP FUNCTION runtests( NAME, TEXT ); +DROP FUNCTION _runner( text[], text[], text[], text[], text[] ); +DROP FUNCTION _cleanup(); +DROP FUNCTION _currtest(); +DROP FUNCTION do_tap( ); +DROP FUNCTION do_tap( text ); +DROP FUNCTION do_tap( name ); +DROP FUNCTION do_tap( name, text ); +DROP FUNCTION _is_verbose(); +DROP FUNCTION _runem( text[], boolean ); +DROP FUNCTION findfuncs( TEXT ); +DROP FUNCTION findfuncs( NAME, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); @@ -218,6 +233,7 @@ DROP FUNCTION ok ( boolean ); DROP FUNCTION ok ( boolean, text ); DROP FUNCTION diag ( msg text ); DROP FUNCTION finish (); +DROP FUNCTION _finish ( INTEGER, INTEGER, INTEGER); DROP FUNCTION num_failed (); DROP FUNCTION add_result ( bool, bool, text, text, text ); DROP FUNCTION _add ( text, integer ); @@ -232,6 +248,7 @@ DROP FUNCTION _get_latest ( text ); DROP FUNCTION _get ( text ); DROP FUNCTION no_plan(); DROP FUNCTION plan( integer ); +DROP FUNCTION pgtap_version(); DROP FUNCTION os_name(); DROP FUNCTION pg_version_num(); DROP FUNCTION pg_version(); From dd31eb1d79ecdc717beebc78ed767e4c85007fce Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 18 Jan 2009 19:57:39 +0000 Subject: [PATCH 0226/1195] Added a bit on using the xUnit style of testing to the top of the README. --- README.pgtap | 46 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/README.pgtap b/README.pgtap index 9c61a034ba86..38ef145a42bd 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,9 +1,10 @@ pgTAP 0.15 ========== -pgTAP is a collection of TAP-emitting unit testing functions written in -PL/pgSQL and PL/SQL. It is based on the Test::More module distributed with -Perl 5. You could even consider it a port of sorts. +pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and +PL/SQL. It includes a comprehensive collection of TAP-emitting assertion +functions, as well as the ability to integrate with other TAP-emitting test +frameworks. It can also be used in the xUnit testing style. Installation ============ @@ -37,7 +38,7 @@ to your `$PATH` environment variable: env PATH=$PATH:/path/to/pgsql/bin USE_PGXS=1 make && make test && make install And finally, if all that fails, copy the entire distribution directory to the -`contrib/` subdirectory of the PostgreSQL source code and try it there without +`contrib/` subdirectory of the PostgreSQL source tree and try it there without the `$USE_PGXS` variable: make @@ -206,6 +207,11 @@ high legibility for humans. TAP started life as part of the test harness for Perl but now has implementations in C/C++, Python, PHP, JavaScript, Perl, and now PostgreSQL. +There are two ways to use pgTAP: 1) In simple test scripts that use a plan to +describe the tests in the script; or 2) In xUnit-style test functions that you +install into your database and run all at once in the PostgreSQL client of +your choice. + I love it when a plan comes together ------------------------------------ @@ -234,6 +240,38 @@ discrepancy between the planned number of tests and the number actually run: SELECT * FROM finish(); +What a sweet unit! +------------------ + +If you're used to xUnit testing frameworks, you can collect all of your tests +into database functions and run them all at once with `runtests()`. This is +similar to how [PGUnit](http://en.dklab.ru/lib/dklab_pgunit/) and +[Epic](http://www.epictest.org/) work. The `runtests()` function does all the +work of finding and running your test functions. It even supports setup and +teardown functions. To use it, write your unit test functions so that they +return a set of text results, and then use the pgTAP assertion functions to +return TAP values, like so: + + create or replace function setup_insert( + ) RETURNS SETOF TEXT AS $$ + RETURN NEXT is( MAX(nick), NULL, 'Should have no users') FROM users; + INSERT INTO users (nick) VALUES ('theory'); + $$ LANGUAGE plpgsql; + + CREATE OR REPLACE FUNCTION test_user( + ) RETURNS SETOF TEXT AS $$ + SELECT is( nick, 'theory', 'Should have nick') FROM users; + END; + $$ LANGUAGE sql; + +See below for details on the pgTAP assertion functions. Once you've defined +your unit testing functions, you can run your tests at any time using the +`runtests()` function: + + SELECT * FROM runtests(); + +The TAP results will be sent to your client. + Test names ---------- From 369249487e5d1f84c2ebe5dc0bdd92df30bfb101 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 18 Jan 2009 20:15:12 +0000 Subject: [PATCH 0227/1195] Started target for generating HTML documentation from Markdown in `README.pgtap` using discount. --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index a64cf52655f6..ea1c0cded07f 100644 --- a/Makefile +++ b/Makefile @@ -143,3 +143,6 @@ installcheck: test_setup.sql # In addition to installcheck, one can also run the tests through pg_prove. test: test_setup.sql ./bin/pg_prove --pset tuples_only=1 $(TESTS) + +markdown: + markdown -F 0x1000 README.pgtap > pgtap.html From c667c1f824ca1ab7daa4d81b4874ef8f0948ed9d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 19 Jan 2009 01:33:12 +0000 Subject: [PATCH 0228/1195] Added Perl code to generate a TOC for the docs. --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index ea1c0cded07f..2b0241677ef1 100644 --- a/Makefile +++ b/Makefile @@ -146,3 +146,4 @@ test: test_setup.sql markdown: markdown -F 0x1000 README.pgtap > pgtap.html + perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$4; $$lab = $$4; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? "
      \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$4 ? "$$4()" : $$3) . "" } END { print "
\n" }' pgtap.html > toc.html From e285849ce52075e11514cf150cf24e1f341127b5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 19 Jan 2009 01:36:00 +0000 Subject: [PATCH 0229/1195] Include a TOC header. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2b0241677ef1..f6eb4fc2dbc2 100644 --- a/Makefile +++ b/Makefile @@ -146,4 +146,4 @@ test: test_setup.sql markdown: markdown -F 0x1000 README.pgtap > pgtap.html - perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$4; $$lab = $$4; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? "
      \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$4 ? "$$4()" : $$3) . "" } END { print "
\n" }' pgtap.html > toc.html + perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Table of Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$4; $$lab = $$4; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? "
      \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$4 ? "$$4()" : $$3) . "" } END { print "
\n" }' pgtap.html > toc.html From e3080075abc056d45c2054ec780b8a2dd54fd185 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 19 Jan 2009 02:23:19 +0000 Subject: [PATCH 0230/1195] Updated 8.0 compat patch. --- compat/install-8.0.patch | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index f0a0d6e514ac..2f3a975308af 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.orig 2008-10-27 15:40:47.000000000 -0700 -+++ pgtap.sql 2008-10-27 15:40:47.000000000 -0700 +--- pgtap.sql.orig 2009-01-18 18:26:00.000000000 -0800 ++++ pgtap.sql 2009-01-18 18:26:02.000000000 -0800 @@ -12,6 +12,22 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA, public; @@ -23,7 +23,7 @@ CREATE OR REPLACE FUNCTION pg_typeof("any") RETURNS regtype AS '$libdir/pgtap' -@@ -90,53 +106,63 @@ +@@ -95,53 +111,63 @@ CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ DECLARE @@ -104,7 +104,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -200,10 +226,12 @@ +@@ -205,10 +231,12 @@ CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ DECLARE @@ -120,7 +120,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -474,13 +502,16 @@ +@@ -485,13 +513,16 @@ want ALIAS FOR $3; descr ALIAS FOR $4; result BOOLEAN; @@ -140,7 +140,7 @@ output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE '\n' || diag( ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -1039,15 +1070,16 @@ +@@ -1052,15 +1083,16 @@ CREATE OR REPLACE FUNCTION _def_is( TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -161,7 +161,7 @@ END; $$ LANGUAGE plpgsql; -@@ -2546,6 +2578,7 @@ +@@ -2559,6 +2591,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -169,7 +169,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -2556,8 +2589,10 @@ +@@ -2569,8 +2602,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. From 4aa81f8072e444bc9e8ec1cd46d3519f97d44fc5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 19 Jan 2009 02:24:12 +0000 Subject: [PATCH 0231/1195] Added toc and html to cleanup. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f6eb4fc2dbc2..015a5b049934 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # $Id$ TESTS = $(wildcard sql/*.sql) -EXTRA_CLEAN = test_setup.sql +EXTRA_CLEAN = test_setup.sql pgtap.html toc.html DATA_built = pgtap.sql uninstall_pgtap.sql MODULES = pgtap DOCS = README.pgtap From 552275e9e86238c6d62df6090270da77f330fcb2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 19 Jan 2009 22:01:19 +0000 Subject: [PATCH 0232/1195] Added a new cast for 8.2. --- README.pgtap | 1 + compat/install-8.2.sql | 7 +++++++ compat/uninstall-8.2.sql | 2 ++ 3 files changed, 10 insertions(+) diff --git a/README.pgtap b/README.pgtap index 38ef145a42bd..1a7523d13408 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1910,6 +1910,7 @@ No changes. Everything should just work. A number of casts are added to increase compatibility. The casts are: * `boolean` to `text` +* `text[]` to `text` * `name[]` to `text` * `regtype` to `text` diff --git a/compat/install-8.2.sql b/compat/install-8.2.sql index 623e7b2ba78d..34b204fa9aa1 100644 --- a/compat/install-8.2.sql +++ b/compat/install-8.2.sql @@ -6,6 +6,13 @@ LANGUAGE sql IMMUTABLE STRICT; CREATE CAST (boolean AS text) WITH FUNCTION booltext(boolean) AS IMPLICIT; +-- 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 IMPLICIT; + -- Cast name[]s to text like 8.3 does. CREATE OR REPLACE FUNCTION namearray_text(name[]) RETURNS TEXT AS 'SELECT textin(array_out($1));' diff --git a/compat/uninstall-8.2.sql b/compat/uninstall-8.2.sql index d28cccd002f0..8a2c7671d986 100644 --- a/compat/uninstall-8.2.sql +++ b/compat/uninstall-8.2.sql @@ -6,5 +6,7 @@ DROP OPERATOR = ( name[], name[] ); DROP FUNCTION namearray_eq( name[], name[] ); DROP CAST (name[] AS text); DROP FUNCTION namearray_text(name[]); +DROP CAST (text[] AS text); +DROP FUNCTION textarray_text(text[]); DROP CAST (boolean AS char(1); DROP FUNCTION booltext(boolean); From 0fa9d34c5c2603e76f5b26fc7768553446574b99 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 19 Jan 2009 23:37:27 +0000 Subject: [PATCH 0233/1195] For PostgreSQL 8.1 compatibility, we have to iterate with records rather than just text vars. :-( --- pgtap.sql.in | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 059858dd67b3..1ffba1c54cc4 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2679,7 +2679,8 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE - tap text; + -- We must iterate with a record rather than text for 8.1 compat. + rec record; lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; @@ -2687,8 +2688,8 @@ BEGIN -- 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; + FOR rec IN EXECUTE 'SELECT a FROM ' || $1[i] || '() AS b(a)' LOOP + RETURN NEXT rec.a; END LOOP; END LOOP; RETURN; @@ -2758,14 +2759,14 @@ DECLARE setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; - tap text; + rec record; verbose boolean := _is_verbose(); num_faild INTEGER := 0; BEGIN BEGIN -- No plan support. PERFORM * FROM no_plan(); - FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP; + FOR rec IN SELECT a FROM _runem(startup, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. @@ -2780,15 +2781,15 @@ BEGIN IF verbose THEN RETURN NEXT diag(tests[i] || '()'); END IF; -- Run the setup functions. - FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; + FOR rec IN SELECT a FROM _runem(setup, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; -- Run the actual test function. - FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP - RETURN NEXT tap; + FOR rec IN EXECUTE 'SELECT a FROM ' || tests[i] || '() AS b(a)' LOOP + RETURN NEXT rec.a; END LOOP; -- Run the teardown functions. - FOR tap IN SELECT * FROM _runem(teardown, false) LOOP RETURN NEXT tap; END LOOP; + FOR rec IN SELECT a FROM _runem(teardown, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; -- Remember how many failed and then roll back. num_faild := num_faild + num_failed(); @@ -2803,7 +2804,7 @@ BEGIN END LOOP; -- Run the shutdown functions. - FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; + FOR rec IN SELECT a FROM _runem(shutdown, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; -- Raise an exception to rollback any changes. RAISE EXCEPTION '__TAP_ROLLBACK__'; @@ -2814,8 +2815,8 @@ BEGIN END IF; END; -- Finish up. - FOR tap IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) LOOP - RETURN NEXT tap; + FOR rec IN SELECT a FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) AS b(a) LOOP + RETURN NEXT rec.a; END LOOP; -- Clean up and return. From ac6b658d5a5cfafef96d290a84a93832e8605edd Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Jan 2009 00:03:09 +0000 Subject: [PATCH 0234/1195] Better to patch for 8.1 compat. --- Makefile | 4 +- compat/install-8.1.patch | 79 ++++++++++++++++++++++++++++++++++++++++ pgtap.sql.in | 25 ++++++------- 3 files changed, 94 insertions(+), 14 deletions(-) create mode 100644 compat/install-8.1.patch diff --git a/Makefile b/Makefile index 015a5b049934..78df50d6f8c7 100644 --- a/Makefile +++ b/Makefile @@ -111,7 +111,9 @@ ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 3) cat compat/install-8.2.sql >> pgtap.sql ifneq ($(PGVER_MINOR), 2) -ifneq ($(PGVER_MINOR), 1) +ifeq ($(PGVER_MINOR), 1) + patch -p0 < compat/install-8.1.patch +else patch -p0 < compat/install-8.0.patch endif endif diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch new file mode 100644 index 000000000000..4d7e160ff955 --- /dev/null +++ b/compat/install-8.1.patch @@ -0,0 +1,79 @@ +--- pgtap.sql.orig 2009-01-19 16:02:49.000000000 -0800 ++++ pgtap.sql 2009-01-19 16:04:58.000000000 -0800 +@@ -2679,7 +2679,7 @@ + CREATE OR REPLACE FUNCTION _runem( text[], boolean ) + RETURNS SETOF TEXT AS $$ + DECLARE +- tap text; ++ rec record; + lbound int := array_lower($1, 1); + BEGIN + IF lbound IS NULL THEN RETURN; END IF; +@@ -2687,8 +2687,8 @@ + -- 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; ++ FOR rec IN EXECUTE 'SELECT * FROM ' || $1[i] || '() AS b(a)' LOOP ++ RETURN NEXT rec.a; + END LOOP; + END LOOP; + RETURN; +@@ -2758,14 +2758,14 @@ + setup ALIAS FOR $3; + teardown ALIAS FOR $4; + tests ALIAS FOR $5; +- tap text; ++ rec record; + verbose boolean := _is_verbose(); + num_faild INTEGER := 0; + BEGIN + BEGIN + -- No plan support. + PERFORM * FROM no_plan(); +- FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP; ++ FOR rec IN SELECT * FROM _runem(startup, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; + EXCEPTION + -- Catch all exceptions and simply rethrow custom exceptions. This + -- will roll back everything in the above block. +@@ -2780,15 +2780,15 @@ + IF verbose THEN RETURN NEXT diag(tests[i] || '()'); END IF; + + -- Run the setup functions. +- FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; ++ FOR rec IN SELECT * FROM _runem(setup, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; + + -- Run the actual test function. +- FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP +- RETURN NEXT tap; ++ FOR rec IN EXECUTE 'SELECT * FROM ' || tests[i] || '() AS b(a)' LOOP ++ RETURN NEXT rec.a; + END LOOP; + + -- Run the teardown functions. +- FOR tap IN SELECT * FROM _runem(teardown, false) LOOP RETURN NEXT tap; END LOOP; ++ FOR rec IN SELECT * FROM _runem(teardown, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; + + -- Remember how many failed and then roll back. + num_faild := num_faild + num_failed(); +@@ -2803,7 +2803,7 @@ + END LOOP; + + -- Run the shutdown functions. +- FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; ++ FOR rec IN SELECT * FROM _runem(shutdown, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; + + -- Raise an exception to rollback any changes. + RAISE EXCEPTION '__TAP_ROLLBACK__'; +@@ -2814,8 +2814,8 @@ + END IF; + END; + -- Finish up. +- FOR tap IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) LOOP +- RETURN NEXT tap; ++ FOR rec IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) AS b(a) LOOP ++ RETURN NEXT rec.a; + END LOOP; + + -- Clean up and return. diff --git a/pgtap.sql.in b/pgtap.sql.in index 1ffba1c54cc4..059858dd67b3 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2679,8 +2679,7 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE - -- We must iterate with a record rather than text for 8.1 compat. - rec record; + tap text; lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; @@ -2688,8 +2687,8 @@ BEGIN -- 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 rec IN EXECUTE 'SELECT a FROM ' || $1[i] || '() AS b(a)' LOOP - RETURN NEXT rec.a; + FOR tap IN EXECUTE 'SELECT * FROM ' || $1[i] || '()' LOOP + RETURN NEXT tap; END LOOP; END LOOP; RETURN; @@ -2759,14 +2758,14 @@ DECLARE setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; - rec record; + tap text; verbose boolean := _is_verbose(); num_faild INTEGER := 0; BEGIN BEGIN -- No plan support. PERFORM * FROM no_plan(); - FOR rec IN SELECT a FROM _runem(startup, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; + 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. @@ -2781,15 +2780,15 @@ BEGIN IF verbose THEN RETURN NEXT diag(tests[i] || '()'); END IF; -- Run the setup functions. - FOR rec IN SELECT a FROM _runem(setup, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; + FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; -- Run the actual test function. - FOR rec IN EXECUTE 'SELECT a FROM ' || tests[i] || '() AS b(a)' LOOP - RETURN NEXT rec.a; + FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP + RETURN NEXT tap; END LOOP; -- Run the teardown functions. - FOR rec IN SELECT a FROM _runem(teardown, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; + FOR tap IN SELECT * FROM _runem(teardown, false) LOOP RETURN NEXT tap; END LOOP; -- Remember how many failed and then roll back. num_faild := num_faild + num_failed(); @@ -2804,7 +2803,7 @@ BEGIN END LOOP; -- Run the shutdown functions. - FOR rec IN SELECT a FROM _runem(shutdown, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; + FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; -- Raise an exception to rollback any changes. RAISE EXCEPTION '__TAP_ROLLBACK__'; @@ -2815,8 +2814,8 @@ BEGIN END IF; END; -- Finish up. - FOR rec IN SELECT a FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) AS b(a) LOOP - RETURN NEXT rec.a; + FOR tap IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) LOOP + RETURN NEXT tap; END LOOP; -- Clean up and return. From b71b3bc9ee33db4d1adcb4c959c443f4d1f00709 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Jan 2009 01:20:01 +0000 Subject: [PATCH 0235/1195] `runtests()` removed on 8.0. --- Makefile | 6 +- README.pgtap | 19 +++--- compat/install-8.0.patch | 141 ++++++++++++++++++++++++++++++++++++++- sql/do_tap.sql | 2 + 4 files changed, 155 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 78df50d6f8c7..a82715c03eb6 100644 --- a/Makefile +++ b/Makefile @@ -35,9 +35,9 @@ ifeq ($(PGVER_MAJOR), 8) ifeq ($(PGVER_MINOR), 0) # Hack for E'' syntax (<= PG8.0) REMOVE_E := -e "s/ E'/ '/g" -# Throw isn't supported in 8.0. -TESTS := $(filter-out sql/throwtap.sql,$(TESTS)) -REGRESS := $(filter-out throwtap,$(REGRESS)) +# Throw and runtests aren't supported in 8.0. +TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql,$(TESTS)) +REGRESS := $(filter-out throwtap runtests,$(REGRESS)) endif endif diff --git a/README.pgtap b/README.pgtap index 1a7523d13408..463029ec3793 100644 --- a/README.pgtap +++ b/README.pgtap @@ -243,9 +243,10 @@ discrepancy between the planned number of tests and the number actually run: What a sweet unit! ------------------ -If you're used to xUnit testing frameworks, you can collect all of your tests -into database functions and run them all at once with `runtests()`. This is -similar to how [PGUnit](http://en.dklab.ru/lib/dklab_pgunit/) and +If you're used to xUnit testing frameworks and using PostgreSQL 8.1 or higher, +you can collect all of your tests into database functions and run them all at +once with `runtests()`. This is similar to how +[PGUnit](http://en.dklab.ru/lib/dklab_pgunit/) and [Epic](http://www.epictest.org/) work. The `runtests()` function does all the work of finding and running your test functions. It even supports setup and teardown functions. To use it, write your unit test functions so that they @@ -1756,7 +1757,7 @@ If you'd like pgTAP to plan, run all of your tests functions, and finish all in one fell swoop, use `runtests()`. This most closely emulates the xUnit testing environment, similar to the functionality of [PGUnit](http://en.dklab.ru/lib/dklab_pgunit/) and -[Epic](http://www.epictest.org/). +[Epic](http://www.epictest.org/). It requires PostgreSQL 8.1 or higher. As with `do_tap()`, you can pass in a schema argument and/or a pattern that the names of the tests functions can match. But unlike `do_tap()`, @@ -1919,14 +1920,16 @@ An `=` operator is also added that compares `name[]` values. 8.0 and Lower ------------- -A patch is applied that changes how some of the test functions are written. -Also, a few casts are added for compatibility: +A patch is applied that changes how some of the test functions are written and +removes the `runtests()` function. Also, a few casts are added for +compatibility: * `oidvector` to `regtypep[]`. * `int2vector` to `integer[]`. -Otherwise, all is the same as for 8.2 Do note, however, that the `throws_ok()` -and `lives_ok()` functions do not work under 8.0. Don't even use them there. +Otherwise, all is the same as for 8.2 Do note, however, that the +`throws_ok()`, `lives_ok()`, and `runtests()` functions do not work under 8.0. +Don't even use them there. To Do ----- diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index 2f3a975308af..a5244004762d 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.orig 2009-01-18 18:26:00.000000000 -0800 -+++ pgtap.sql 2009-01-18 18:26:02.000000000 -0800 +--- pgtap.sql.orig 2009-01-19 16:14:17.000000000 -0800 ++++ pgtap.sql 2009-01-19 16:16:18.000000000 -0800 @@ -12,6 +12,22 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA, public; @@ -182,3 +182,140 @@ -- Now delete those results. EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; +@@ -2679,7 +2714,7 @@ + CREATE OR REPLACE FUNCTION _runem( text[], boolean ) + RETURNS SETOF TEXT AS $$ + DECLARE +- tap text; ++ rec record; + lbound int := array_lower($1, 1); + BEGIN + IF lbound IS NULL THEN RETURN; END IF; +@@ -2687,8 +2722,8 @@ + -- 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; ++ FOR rec IN EXECUTE 'SELECT * FROM ' || $1[i] || '() AS b(a)' LOOP ++ RETURN NEXT rec.a; + END LOOP; + END LOOP; + RETURN; +@@ -2750,116 +2785,6 @@ + END + $$ LANGUAGE plpgsql; + +-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; +- verbose boolean := _is_verbose(); +- num_faild INTEGER := 0; +-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; +- +- BEGIN +- FOR i IN 1..array_upper(tests, 1) LOOP +- BEGIN +- -- What test are we running? +- IF verbose THEN RETURN NEXT diag(tests[i] || '()'); END IF; +- +- -- Run the setup functions. +- FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; +- +- -- Run the actual test function. +- FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP +- RETURN NEXT tap; +- END LOOP; +- +- -- Run the teardown functions. +- FOR tap IN SELECT * FROM _runem(teardown, false) LOOP RETURN NEXT tap; END LOOP; +- +- -- Remember how many failed and then roll back. +- num_faild := num_faild + num_failed(); +- RAISE EXCEPTION '__TAP_ROLLBACK__'; +- +- EXCEPTION WHEN raise_exception THEN +- IF SQLERRM <> '__TAP_ROLLBACK__' THEN +- -- We didn't raise it, so propagate it. +- RAISE EXCEPTION '%', SQLERRM; +- END IF; +- END; +- END LOOP; +- +- -- Run the shutdown functions. +- FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; +- +- -- Raise an exception to rollback any changes. +- RAISE EXCEPTION '__TAP_ROLLBACK__'; +- EXCEPTION WHEN raise_exception THEN +- IF SQLERRM <> '__TAP_ROLLBACK__' THEN +- -- We didn't raise it, so propagate it. +- RAISE EXCEPTION '%', SQLERRM; +- END IF; +- END; +- -- Finish up. +- FOR tap IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) 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 ) +- ); +-$$ 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 ) +- ); +-$$ LANGUAGE sql; +- +--- runtests( ) +-CREATE OR REPLACE FUNCTION runtests( ) +-RETURNS SETOF TEXT AS $$ +- SELECT * FROM runtests( '^test' ); +-$$ LANGUAGE sql; +- + -- 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;' diff --git a/sql/do_tap.sql b/sql/do_tap.sql index 105248b3db97..64bf5c8994cd 100644 --- a/sql/do_tap.sql +++ b/sql/do_tap.sql @@ -17,6 +17,7 @@ CREATE OR REPLACE FUNCTION public.testplpgsql() RETURNS SETOF TEXT AS $$ BEGIN RETURN NEXT pass( 'plpgsql simple' ); RETURN NEXT pass( 'plpgsql simple 2' ); + RETURN; END; $$ LANGUAGE plpgsql; @@ -24,6 +25,7 @@ CREATE OR REPLACE FUNCTION public."test ident"() RETURNS SETOF TEXT AS $$ BEGIN RETURN NEXT pass( 'ident' ); RETURN NEXT pass( 'ident 2' ); + RETURN; END; $$ LANGUAGE plpgsql; From 04812c516fa14f02d6ff4b3acdbebd4ccd05c2a7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Jan 2009 01:28:23 +0000 Subject: [PATCH 0236/1195] Note `markdown` make target. --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 275465f13059..ed09018d254d 100644 --- a/Changes +++ b/Changes @@ -19,6 +19,8 @@ Revision history for pgTAP - Fixed a typo in the documentation of the short version of the `--version` option to `pg_prove`, which is `-V`, not `-v`. - Added `pgtap_version()`. + - Added the `markdown` make target to generate the HTML documentation + for the Web site, including a table of contents. 0.14 2008-10-27T22:43:36 - Added `SET search_path` statements to `uninstall_pgtap.sql.in` so that From fe4335b9a6384760390a599c49daf00ae6cfa547 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Jan 2009 01:40:04 +0000 Subject: [PATCH 0237/1195] Changed the target to "html" and added a call to create documentation for `pg_prove`. --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a82715c03eb6..69cdc8fdf7fa 100644 --- a/Makefile +++ b/Makefile @@ -146,6 +146,7 @@ installcheck: test_setup.sql test: test_setup.sql ./bin/pg_prove --pset tuples_only=1 $(TESTS) -markdown: +html: markdown -F 0x1000 README.pgtap > pgtap.html perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Table of Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$4; $$lab = $$4; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? "
      \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$4 ? "$$4()" : $$3) . "" } END { print "
\n" }' pgtap.html > toc.html + perldoc -MPod::Simple::XHTML bin/pg_prove > pg_prove.html From 09494c47383d60d00e4b5cbd94ac1bc2a77b641a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Jan 2009 01:45:02 +0000 Subject: [PATCH 0238/1195] * Expanded `pg_prove` documentation a bit. * Delete `*.html`, which gets the new `pg_prove.html` file, too. --- Makefile | 2 +- bin/pg_prove | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 69cdc8fdf7fa..00f50fb046e2 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # $Id$ TESTS = $(wildcard sql/*.sql) -EXTRA_CLEAN = test_setup.sql pgtap.html toc.html +EXTRA_CLEAN = test_setup.sql *.html DATA_built = pgtap.sql uninstall_pgtap.sql MODULES = pgtap DOCS = README.pgtap diff --git a/bin/pg_prove b/bin/pg_prove index e3b7534a7326..dcd6960bcb53 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -96,11 +96,37 @@ pg_prove - A command-line tool for running pgTAP tests against TAP::Harness =head1 Description -C is a command-line interface to the test-running functionality of -L. Shell metacharacters may be used with command -lines options and will be exanded via "glob". - -The test scripts should be a series of SQL statements +C is a command-line application to run one or more pgTAP tests against +a PostgreSQL database. The output of the tests is harvested and processed by +L in order to summarize the results of the test. + +The pgTAP test scripts should consist of a series of SQL statements that +output TAP. Here's a simple example that assumes that the pgTAP functions have +been installed in the database: + + -- Start transaction and plan the tests. + BEGIN; + SELECT plan(1); + + -- Run the tests. + SELECT pass( 'My test passed, w00t!' ); + + -- Finish the tests and clean up. + SELECT * FROM finish(); + ROLLBACK; + +Now run the tests. Here's what it looks like when the pgTAP tests are run with +`pg_prove`: + + % pg_prove -U postgres sql/*.sql + sql/coltap.....ok + sql/hastap.....ok + sql/moretap....ok + sql/pg73.......ok + sql/pktap......ok + All tests successful. + Files=5, Tests=216, 1 wallclock secs ( 0.06 usr 0.02 sys + 0.08 cusr 0.07 csys = 0.23 CPU) + Result: PASS =head1 Options From d52a3e68e793d11377cda9dd7a5ffb1427030711 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Jan 2009 01:45:46 +0000 Subject: [PATCH 0239/1195] Changed the name of the target, added pg_prove.html. --- Changes | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index ed09018d254d..8452b7e2e1ae 100644 --- a/Changes +++ b/Changes @@ -19,8 +19,9 @@ Revision history for pgTAP - Fixed a typo in the documentation of the short version of the `--version` option to `pg_prove`, which is `-V`, not `-v`. - Added `pgtap_version()`. - - Added the `markdown` make target to generate the HTML documentation - for the Web site, including a table of contents. + - Added the "html" make target to generate the HTML documentation for + the Web site, including a table of contents and documenation for + `pg_prove`. 0.14 2008-10-27T22:43:36 - Added `SET search_path` statements to `uninstall_pgtap.sql.in` so that From db92aff9962e03732a2bc3a05ad7c92917299d1a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Jan 2009 01:49:07 +0000 Subject: [PATCH 0240/1195] Eliminated bogus character. --- bin/pg_prove | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/pg_prove b/bin/pg_prove index dcd6960bcb53..a81f1d2f6caf 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -242,7 +242,7 @@ Print elapsed time after each test file. Display test results in color. Colored test output is the default, but if output is not to a terminal, color is disabled. -Requires L on Unix‐like platforms and +Requires L on Unix-like platforms and L on Windows. If the necessary module is not installed colored output will not be available. From db1f026837317d2edc6eab467b1c1840b6f74ad6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Jan 2009 18:19:34 +0000 Subject: [PATCH 0241/1195] * Adjusted the generation of the TOC to link to h1-level tags, too. * Moved some things around in the documentation to make more sense. --- Makefile | 2 +- README.pgtap | 390 +++++++++++++++++++++++++-------------------------- 2 files changed, 196 insertions(+), 196 deletions(-) diff --git a/Makefile b/Makefile index 00f50fb046e2..52a44f36d084 100644 --- a/Makefile +++ b/Makefile @@ -148,5 +148,5 @@ test: test_setup.sql html: markdown -F 0x1000 README.pgtap > pgtap.html - perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Table of Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$4; $$lab = $$4; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? "
      \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$4 ? "$$4()" : $$3) . "" } END { print "
\n" }' pgtap.html > toc.html + perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Table of Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$4; $$lab = $$4; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$4 ? "$$4()" : $$3) . "" } END { print "
  • \n
\n" }' pgtap.html > toc.html perldoc -MPod::Simple::XHTML bin/pg_prove > pg_prove.html diff --git a/README.pgtap b/README.pgtap index 463029ec3793..59fe915dbdaa 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1499,6 +1499,201 @@ preceding `todo_start()` method call. Returns true if the test is currently inside a TODO block. +Utility Functions +----------------- + +Along with the usual array of testing, planning, and diagnostic functions, +pTAP provides a few extra functions to make the work of testing more pleasant. + +### `pgtap_version()` ### + + SELECT pgtap_version(); + +Returns the version of pgTAP installed in the server. The value is NUMERIC, +and thus suitable for comparing to a decimal value: + + SELECT CASE WHEN pgtap_version() < 0.14 + THEN skip('No index assertions before pgTAP 0.14') + ELSE has_index('users', 'idx_user_name') + END; + +### `pg_version()` ### + + SELECT pg_version(); + +Returns the server version number against which pgTAP was compiled. This is +the stringified version number displayed in the first part of the core +`version()` function and stored in the "server_version" setting: + + try=% select current_setting( 'server_version'), pg_version(); + current_setting | pg_version + -----------------+------------ + 8.3.4 | 8.3.4 + (1 row) + +### `pg_version_num()` ### + + SELECT pg_version_num(); + +Returns an integer representation of the server version number against which +pgTAP was compiled. This function is useful for determining whether or not +certain tests should be run or skipped (using `skip()`) depending on the +version of PostgreSQL. For example: + + SELECT CASE WHEN pg_version_num() < 80100 + THEN skip('throws_ok() not supported before 8.1' ) + ELSE throws_ok( 'SELECT 1/0', 22012, 'division by zero' ) + END; + +The revision level is in the tens position, the minor version in the thousands +position, and the major version in the ten thousands position and above +(assuming PostgreSQL 10 is ever released, it will be in the hundred thousands +position). This value is the same as the "sever_version_num" setting available +in PostgreSQL 8.2 and higher, but supported by this function back to +PostgreSQL 8.0: + + try=% select current_setting( 'server_version_num'), pg_version_num(); + current_setting | pg_version_num + -----------------+---------------- + 80304 | 80304 + +### `os_name()` ### + + SELECT os_name(); + +Returns a string representing the name of the operating system on which pgTAP +was compiled. This can be useful for determining whether or not to skip tests +on certain operating systems. + +This is usually the same a the ouput of `uname`, but converted to lower case. +There are some semantics in the pgTAP build process to detect other operating +systems, though assistance in improving such detection would be greatly +appreciated. + +**NOTE:** The values returned by this function may change in the future, +depending on how good the pgTAP build process gets at detecting a OS. + +### `pg_typeof(any)` ### + + SELECT pg_typeof(:value); + +Returns a `regtype` identifying the type of value passed to the function. This +function is used internally by `cmp_ok()` to properly construct types when +executing the comparison, but might be generally useful. + + try=% select pg_typeof(12), pg_typeof(100.2); + pg_typeof | pg_typeof + -----------+----------- + integer | numeric + +### `findfuncs( schema, pattern )` ### +### `findfuncs( pattern )` ### + + SELECT findfuncs('myschema', '^test' ); + +This function searches the named schema or, if no schema is passed, the search +patch, for all functions that match the regular expression pattern. The +functions it finds are returned as an array of text values, with each value +consisting of the schema name, a dot, and the function name. For example: + + SELECT findfuncs('tests', '^test); + findfuncs + ----------------------------------- + {tests.test_foo,tests."test bar"} + (1 row) + +Tap that Batch +-------------- + +Sometimes it can be useful to batch a lot of TAP tests into a function. The +simplest way to do so is to define a function that `RETURNS SETOF TEXT` and +then simply call `RETURN NEXT` for each TAP test. Here's a simple example: + + CREATE OR REPLACE FUNCTION my_tests( + ) RETURNS SETOF TEXT AS $$ + BEGIN + RETURN NEXT pass( 'plpgsql simple' ); + RETURN NEXT pass( 'plpgsql simple 2' ); + END; + $$ LANGUAGE plpgsql; + +Then you can just call the function to run all of your TAP tests at once: + + SELECT plan(2); + SELECT * FROM my_tests(); + SELECT * FROM finish(); + +### `do_tap( schema, pattern )` ### +### `do_tap( schema )` ### +### `do_tap( pattern )` ### +### `do_tap()` ### + + SELECT plan(32); + SELECT * FROM do_tap('testschema'); + SELECT * FROM finish(); + +If you like you can create a whole slew of these batched tap functions, and +then use the `do_tap()` function to run them all at once. If passed no +arguments, it will attempt to find all visible functions that start with +"test". If passed a schema name, it will look for and run test functions only +in that schema (be sure to cast the schema to `name` if it is the only +argument). If passed a regular expression pattern, it will look for function +names that match that pattern in the search path. If passed both, it will of +course only search for test functions that match the function in the named +schema. + +This can be very useful if you prefer to keep all of your TAP tests in +functions defined in the database. Simply call `plan()`, use `do_tap()` to +execute all of your tests, and then call `finish()`. + +As a bonus, if `client_min_messages` is set to "warning", "error", "fatal", or +"panic", the name of each function will be emitted as a diagnostic message +before it is called. For example, if `do_tap()` found and executed two TAP +testing functions an `client_min_messages` is set to "warning", output will +look something like this: + + # public.test_this() + ok 1 - simple pass + ok 2 - another simple pass + # public.test_that() + ok 3 - that simple + ok 4 - that simple 2 + +Which will make it much easier to tell what functions need to be examined for +failing tests. + +### `runtests( schema, match )` ### +### `runtests( schema )` ### +### `runtests( match )` ### +### `runtests( )` ### + + SELECT * FROM runtests('testschema'); + +If you'd like pgTAP to plan, run all of your tests functions, and finish all +in one fell swoop, use `runtests()`. This most closely emulates the xUnit +testing environment, similar to the functionality of +[PGUnit](http://en.dklab.ru/lib/dklab_pgunit/) and +[Epic](http://www.epictest.org/). It requires PostgreSQL 8.1 or higher. + +As with `do_tap()`, you can pass in a schema argument and/or a pattern that +the names of the tests functions can match. But unlike `do_tap()`, +`runtests()` fully supports startup, shutdown, setup, and teardown functions, +as well as transactional rollbacks between tests. It also outputs the test +plan and fishes the tests, so you don't have to call `plan()` or `finish()` +yourself. + +The fixture functions run by `runtests()` are as follows: + +* `^startup` - Functions whose names start with "startup" are run in + alphabetical order before any test functions are run. +* `^setup` - Functions whose names start with "setup" are run in alphabetical + order before each test function is run. +* `^teardown` - Functions whose names start with "teardown" are run in + alphabetical order after each test function is run. They will not be run, + however, after a test that has died. +* `^shutdown` - Functions whose names start with "shutdown" are run in + alphabetical order after all test functions have been run. + Writing Test Functions ====================== @@ -1686,201 +1881,6 @@ it gets the job done. Of course, if your diagnostics use something other than indented "have" and "want", such failures will be easier to read. But either way, do test your diagnostics! -Tap that Batch --------------- - -Sometimes it can be useful to batch a lot of TAP tests into a function. The -simplest way to do so is to define a function that `RETURNS SETOF TEXT` and -then simply call `RETURN NEXT` for each TAP test. Here's a simple example: - - CREATE OR REPLACE FUNCTION my_tests( - ) RETURNS SETOF TEXT AS $$ - BEGIN - RETURN NEXT pass( 'plpgsql simple' ); - RETURN NEXT pass( 'plpgsql simple 2' ); - END; - $$ LANGUAGE plpgsql; - -Then you can just call the function to run all of your TAP tests at once: - - SELECT plan(2); - SELECT * FROM my_tests(); - SELECT * FROM finish(); - -### `do_tap( schema, pattern )` ### -### `do_tap( schema )` ### -### `do_tap( pattern )` ### -### `do_tap()` ### - - SELECT plan(32); - SELECT * FROM do_tap('testschema'); - SELECT * FROM finish(); - -If you like you can create a whole slew of these batched tap functions, and -then use the `do_tap()` function to run them all at once. If passed no -arguments, it will attempt to find all visible functions that start with -"test". If passed a schema name, it will look for and run test functions only -in that schema (be sure to cast the schema to `name` if it is the only -argument). If passed a regular expression pattern, it will look for function -names that match that pattern in the search path. If passed both, it will of -course only search for test functions that match the function in the named -schema. - -This can be very useful if you prefer to keep all of your TAP tests in -functions defined in the database. Simply call `plan()`, use `do_tap()` to -execute all of your tests, and then call `finish()`. - -As a bonus, if `client_min_messages` is set to "warning", "error", "fatal", or -"panic", the name of each function will be emitted as a diagnostic message -before it is called. For example, if `do_tap()` found and executed two TAP -testing functions an `client_min_messages` is set to "warning", output will -look something like this: - - # public.test_this() - ok 1 - simple pass - ok 2 - another simple pass - # public.test_that() - ok 3 - that simple - ok 4 - that simple 2 - -Which will make it much easier to tell what functions need to be examined for -failing tests. - -### `runtests( schema, match )` ### -### `runtests( schema )` ### -### `runtests( match )` ### -### `runtests( )` ### - - SELECT * FROM runtests('testschema'); - -If you'd like pgTAP to plan, run all of your tests functions, and finish all -in one fell swoop, use `runtests()`. This most closely emulates the xUnit -testing environment, similar to the functionality of -[PGUnit](http://en.dklab.ru/lib/dklab_pgunit/) and -[Epic](http://www.epictest.org/). It requires PostgreSQL 8.1 or higher. - -As with `do_tap()`, you can pass in a schema argument and/or a pattern that -the names of the tests functions can match. But unlike `do_tap()`, -`runtests()` fully supports startup, shutdown, setup, and teardown functions, -as well as transactional rollbacks between tests. It also outputs the test -plan and fishes the tests, so you don't have to call `plan()` or `finish()` -yourself. - -The fixture functions run by `runtests()` are as follows: - -* `^startup` - Functions whose names start with "startup" are run in - alphabetical order before any test functions are run. -* `^setup` - Functions whose names start with "setup" are run in alphabetical - order before each test function is run. -* `^teardown` - Functions whose names start with "teardown" are run in - alphabetical order after each test function is run. They will not be run, - however, after a test that has died. -* `^shutdown` - Functions whose names start with "shutdown" are run in - alphabetical order after all test functions have been run. - -Utility Functions -================= - -Along with the usual array of testing, planning, and diagnostic functions, -pTAP provides a few extra functions to make the work of testing more pleasant. - -### `pgtap_version()` ### - - SELECT pgtap_version(); - -Returns the version of pgTAP installed in the server. The value is NUMERIC, -and thus suitable for comparing to a decimal value: - - SELECT CASE WHEN pgtap_version() < 0.14 - THEN skip('No index assertions before pgTAP 0.14') - ELSE has_index('users', 'idx_user_name') - END; - -### `pg_version()` ### - - SELECT pg_version(); - -Returns the server version number against which pgTAP was compiled. This is -the stringified version number displayed in the first part of the core -`version()` function and stored in the "server_version" setting: - - try=% select current_setting( 'server_version'), pg_version(); - current_setting | pg_version - -----------------+------------ - 8.3.4 | 8.3.4 - (1 row) - -### `pg_version_num()` ### - - SELECT pg_version_num(); - -Returns an integer representation of the server version number against which -pgTAP was compiled. This function is useful for determining whether or not -certain tests should be run or skipped (using `skip()`) depending on the -version of PostgreSQL. For example: - - SELECT CASE WHEN pg_version_num() < 80100 - THEN skip('throws_ok() not supported before 8.1' ) - ELSE throws_ok( 'SELECT 1/0', 22012, 'division by zero' ) - END; - -The revision level is in the tens position, the minor version in the thousands -position, and the major version in the ten thousands position and above -(assuming PostgreSQL 10 is ever released, it will be in the hundred thousands -position). This value is the same as the "sever_version_num" setting available -in PostgreSQL 8.2 and higher, but supported by this function back to -PostgreSQL 8.0: - - try=% select current_setting( 'server_version_num'), pg_version_num(); - current_setting | pg_version_num - -----------------+---------------- - 80304 | 80304 - -### `os_name()` ### - - SELECT os_name(); - -Returns a string representing the name of the operating system on which pgTAP -was compiled. This can be useful for determining whether or not to skip tests -on certain operating systems. - -This is usually the same a the ouput of `uname`, but converted to lower case. -There are some semantics in the pgTAP build process to detect other operating -systems, though assistance in improving such detection would be greatly -appreciated. - -**NOTE:** The values returned by this function may change in the future, -depending on how good the pgTAP build process gets at detecting a OS. - -### `pg_typeof(any)` ### - - SELECT pg_typeof(:value); - -Returns a `regtype` identifying the type of value passed to the function. This -function is used internally by `cmp_ok()` to properly construct types when -executing the comparison, but might be generally useful. - - try=% select pg_typeof(12), pg_typeof(100.2); - pg_typeof | pg_typeof - -----------+----------- - integer | numeric - -### `findfuncs( schema, pattern )` ### -### `findfuncs( pattern )` ### - - SELECT findfuncs('myschema', '^test' ); - -This function searches the named schema or, if no schema is passed, the search -patch, for all functions that match the regular expression pattern. The -functions it finds are returned as an array of text values, with each value -consisting of the schema name, a dot, and the function name. For example: - - SELECT findfuncs('tests', '^test); - findfuncs - ----------------------------------- - {tests.test_foo,tests."test bar"} - (1 row) - Compatibility ============= From c6501ae732980826d92bd87304b8e8a310a1dfcc Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Jan 2009 18:36:07 +0000 Subject: [PATCH 0242/1195] Timestamped for 0.15. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 8452b7e2e1ae..f4928c90bb32 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,6 @@ Revision history for pgTAP -0.15 +0.15 2009-01-20T18:41:24 - Changed `pg_typeof()` from immutable to stable, in compliance with its configuration in the forthcoming PostgreSQL 8.4. - Added `do_tap()`, which finds test functions and runs them, allowing From 24210ae85653cd5ad5a5373897babb181f60f91e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Jan 2009 18:43:32 +0000 Subject: [PATCH 0243/1195] Ignore .html files. From 3f8f9b274c60d4fc1f428021b99cd7ec856081cd Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Jan 2009 18:59:01 +0000 Subject: [PATCH 0244/1195] Incremented version number to 0.16. --- Changes | 2 ++ Makefile | 2 +- README.pgtap | 2 +- bin/pg_prove | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index f4928c90bb32..207c08cadfb8 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Revision history for pgTAP +0.16 + 0.15 2009-01-20T18:41:24 - Changed `pg_typeof()` from immutable to stable, in compliance with its configuration in the forthcoming PostgreSQL 8.4. diff --git a/Makefile b/Makefile index 52a44f36d084..04e3e6693a08 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ endif PGVER_MAJOR = $(shell echo $(VERSION) | awk -F. '{ print ($$1 + 0) }') PGVER_MINOR = $(shell echo $(VERSION) | awk -F. '{ print ($$2 + 0) }') PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') -PGTAP_VERSION = 0.15 +PGTAP_VERSION = 0.16 # We support 8.0 and later. ifneq ($(PGVER_MAJOR), 8) diff --git a/README.pgtap b/README.pgtap index 59fe915dbdaa..7d795dfda02b 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.15 +pgTAP 0.16 ========== pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/bin/pg_prove b/bin/pg_prove index a81f1d2f6caf..b9308e6d5d4c 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -6,7 +6,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.15'; +our $VERSION = '0.16'; Getopt::Long::Configure (qw(bundling)); From 77c9bac60bb6aa6dd732caafdad9d6d38c95b351 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Jan 2009 19:32:44 +0000 Subject: [PATCH 0245/1195] Typo. --- README.pgtap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.pgtap b/README.pgtap index 7d795dfda02b..1a4de334431b 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1451,8 +1451,8 @@ tests, is that they're like a programmatic todo list. You know how much work is left to be done, you're aware of what bugs there are, and you'll know immediately when they're fixed. -### `todo_start( :why ) ### -### `todo_start( ) ### +### `todo_start( :why )` ### +### `todo_start( )` ### This function allows you declare all subsequent tests as TODO tests, up until the `todo_end()` function is called. From c414fb0299a21719f168b13a2c21b3b1c772a06c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Jan 2009 19:39:05 +0000 Subject: [PATCH 0246/1195] Save docs to `readme.html` instead of `pgtap.html`. --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 04e3e6693a08..dac2064d2176 100644 --- a/Makefile +++ b/Makefile @@ -147,6 +147,6 @@ test: test_setup.sql ./bin/pg_prove --pset tuples_only=1 $(TESTS) html: - markdown -F 0x1000 README.pgtap > pgtap.html - perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Table of Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$4; $$lab = $$4; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$4 ? "$$4()" : $$3) . "" } END { print "
  • \n
\n" }' pgtap.html > toc.html + markdown -F 0x1000 README.pgtap > readme.html + perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$4; $$lab = $$4; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$4 ? "$$4()" : $$3) . "" } END { print "
  • \n
\n" }' readme.html > toc.html perldoc -MPod::Simple::XHTML bin/pg_prove > pg_prove.html From 1f3f739bf5de6ce9ae319f2a8a54d5cbfd3378cb Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 22 Jan 2009 22:54:41 +0000 Subject: [PATCH 0247/1195] Switched from a crazy trinary logic in `is()` and `isnt()` to the use of `IS NOT DISTINCT FROM` and `IS DISTINCT FROM`. Swiped from PGUnit. --- Changes | 2 ++ README.pgtap | 21 ++++++++++++--------- pgtap.sql.in | 9 +++------ 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Changes b/Changes index 207c08cadfb8..7335845420e4 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,8 @@ Revision history for pgTAP 0.16 + - Switched from a crazy trinary logic in `is()` and `isnt()` to the use + of `IS NOT DISTINCT FROM` and `IS DISTINCT FROM`. Swiped from PGUnit. 0.15 2009-01-20T18:41:24 - Changed `pg_typeof()` from immutable to stable, in compliance with its diff --git a/README.pgtap b/README.pgtap index 1a4de334431b..ff6fa0b56bdd 100644 --- a/README.pgtap +++ b/README.pgtap @@ -351,9 +351,9 @@ additional diagnostic: SELECT is( :this, :that, :description ); SELECT isnt( :this, :that, :description ); -Similar to `ok()`, `is()` and `isnt()` compare their two arguments with `=` -and `<>` respectively and use the result of that to determine if the test -succeeded or failed. So these: +Similar to `ok()`, `is()` and `isnt()` compare their two arguments with `IS +NOT DISTINCT FROM` (`=`) AND `IS DISTINCT FROM` (`<>`) respectively and use +the result of that to determine if the test succeeded or failed. So these: -- Is the ultimate answer 42? SELECT is( ultimate_answer(), 42, 'Meaning of Life' ); @@ -368,13 +368,15 @@ are similar to these: (Mnemonic: "This is that." "This isn't that.") -*Note:* `NULL`s are not treated as unknowns by `is()` or `isnt()`. That is, if -`:this` and `:that` are both `NULL`, the test will pass, and if only one of -them is `NULL`, the test will fail. +*Note:* Thanks to the use of the `IS [ NOT ] DISTINCT FROM` construct, `NULL`s +are not treated as unknowns by `is()` or `isnt()`. That is, if `:this` and +`:that` are both `NULL`, the test will pass, and if only one of them is +`NULL`, the test will fail. -So why use these? They produce better diagnostics on failure. `ok()` cannot -know what you are testing for (beyond the description), but `is()` and -`isnt()` know what the test was and why it failed. For example this test: +So why use these test functions? They produce better diagnostics on failure. +`ok()` cannot know what you are testing for (beyond the description), but +`is()` and `isnt()` know what the test was and why it failed. For example this +test: \set foo '\'' waffle '\'' \set bar '\'' yarblokos '\'' @@ -1935,6 +1937,7 @@ To Do ----- * Update build for 8.4, where `pg_typeof()` is already included. +* Add `has_type()`. * Update the Makefile to process pg_prove and set the proper path to Perl on the shebang line. Will likely require a `$(PERL)` environment variable. * Update the Makefile to check for TAP::Harness and warn if it's not diff --git a/pgtap.sql.in b/pgtap.sql.in index 059858dd67b3..bdc7089c8528 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -324,9 +324,8 @@ DECLARE result BOOLEAN; output TEXT; BEGIN - result := CASE WHEN $1 IS NULL AND $2 IS NULL THEN TRUE - WHEN $1 IS NULL OR $2 IS NULL THEN FALSE - ELSE $1 = $2 END; + -- 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: ' || COALESCE( $1::text, 'NULL' ) || @@ -346,9 +345,7 @@ DECLARE result BOOLEAN; output TEXT; BEGIN - result := CASE WHEN $1 IS NULL AND $2 IS NULL THEN FALSE - WHEN $1 IS NULL OR $2 IS NULL THEN TRUE - ELSE $1 <> $2 END; + result := $1 IS DISTINCT FROM $2; output := ok( result, $3 ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( ' ' || COALESCE( $1::text, 'NULL' ) || From 442b453fceb2be68b73af9a2557eb0f940699344 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 26 Jan 2009 23:53:07 +0000 Subject: [PATCH 0248/1195] Doc fix. --- Changes | 2 ++ bin/pg_prove | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 7335845420e4..6ddaf03478c0 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,8 @@ Revision history for pgTAP 0.16 - Switched from a crazy trinary logic in `is()` and `isnt()` to the use of `IS NOT DISTINCT FROM` and `IS DISTINCT FROM`. Swiped from PGUnit. + - Fixed documentation for the short version of `--help` in `pg_prove`. + It's `-H`, not `-h`. 0.15 2009-01-20T18:41:24 - Changed `pg_typeof()` from immutable to stable, in compliance with its diff --git a/bin/pg_prove b/bin/pg_prove index b9308e6d5d4c..ca9c2cfa5880 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -139,7 +139,7 @@ Now run the tests. Here's what it looks like when the pgTAP tests are run with -c --color Display colored test ouput. --nocolor Do not display colored test ouput. -v --verbose Display output of test scripts while running them. - -h --help Print a usage statement and exit. + -H --help Print a usage statement and exit. -m --man Print the complete documentation and exit. -V --version Print the version number and exit. -P --pset OPTION=VALUE Set psql printing option. @@ -255,7 +255,7 @@ Do not display test results in color. =item C<--help> pg_prove --help - pg_prove -h + pg_prove -H Outputs a brief description of the options supported by C and exits. From d1ec7e8db061d61d746ccfb84696cb357fa0415e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 26 Jan 2009 23:55:12 +0000 Subject: [PATCH 0249/1195] Idea for another improvement. I'm going to need help with configure, I think. --- README.pgtap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.pgtap b/README.pgtap index ff6fa0b56bdd..defe3cded409 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1942,6 +1942,8 @@ To Do the shebang line. Will likely require a `$(PERL)` environment variable. * Update the Makefile to check for TAP::Harness and warn if it's not installed. +* Update the installer to set the default value for `--psql-bin` in `pg_prove` + is in the directory returned by `pg_config --bindir`. Suported Versions ----------------- From b89100f8c258c75a5b8c999381f2a2175a472533 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 26 Jan 2009 23:58:50 +0000 Subject: [PATCH 0250/1195] Another idea. --- README.pgtap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.pgtap b/README.pgtap index defe3cded409..fb774aa0d290 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1944,6 +1944,8 @@ To Do installed. * Update the installer to set the default value for `--psql-bin` in `pg_prove` is in the directory returned by `pg_config --bindir`. +* Add options to `pg_prove` to allow it to run tests using the `runtests()` + function rather than test files. Suported Versions ----------------- From 39b2db00905488ad89559d6cdc9be3df57c008f4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 27 Jan 2009 00:07:58 +0000 Subject: [PATCH 0251/1195] Better idea. --- README.pgtap | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.pgtap b/README.pgtap index fb774aa0d290..aee31ad20c2e 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1942,8 +1942,9 @@ To Do the shebang line. Will likely require a `$(PERL)` environment variable. * Update the Makefile to check for TAP::Harness and warn if it's not installed. -* Update the installer to set the default value for `--psql-bin` in `pg_prove` - is in the directory returned by `pg_config --bindir`. +* Update `pg_prove` to look for `psql` in the directory returned by `pg_config + --bindir` if it's not in the path. The installer will likely need to modify + it. * Add options to `pg_prove` to allow it to run tests using the `runtests()` function rather than test files. From 07a3c38a926514c2bf96a65b67caec808679fded Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 27 Jan 2009 00:48:27 +0000 Subject: [PATCH 0252/1195] Fixed a bug detecting the OS. A dumb bug. I hate Make. --- Changes | 3 +++ Makefile | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 6ddaf03478c0..07c0a811d59a 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,9 @@ Revision history for pgTAP of `IS NOT DISTINCT FROM` and `IS DISTINCT FROM`. Swiped from PGUnit. - Fixed documentation for the short version of `--help` in `pg_prove`. It's `-H`, not `-h`. + - Fixed a bug in the makefile that prevented OS detection from working + properly on many platforms (including Linux, and probably many + others). 0.15 2009-01-20T18:41:24 - Changed `pg_typeof()` from immutable to stable, in compliance with its diff --git a/Makefile b/Makefile index dac2064d2176..6467f7f374a8 100644 --- a/Makefile +++ b/Makefile @@ -90,7 +90,7 @@ endif # Fallback on uname, if it's available. ifndef OSNAME -OSNAME = $(shell uname | awk '{print tolower($1)}') +OSNAME = $(shell uname | awk '{print tolower($$1)}') endif # Override how .sql targets are processed to add the schema info, if From ee33ddb621127b5cfec09022c78d0119eaf8169b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 27 Jan 2009 03:19:28 +0000 Subject: [PATCH 0253/1195] Don't compile the C code for 8.4. --- Changes | 2 ++ Makefile | 21 ++++++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 07c0a811d59a..855d9e003d07 100644 --- a/Changes +++ b/Changes @@ -8,6 +8,8 @@ Revision history for pgTAP - Fixed a bug in the makefile that prevented OS detection from working properly on many platforms (including Linux, and probably many others). + - No C code is compiled for 8.4, since the function we were compiling + ships with 8.4. 0.15 2009-01-20T18:41:24 - Changed `pg_typeof()` from immutable to stable, in compliance with its diff --git a/Makefile b/Makefile index 6467f7f374a8..1840c5097d6f 100644 --- a/Makefile +++ b/Makefile @@ -2,29 +2,40 @@ TESTS = $(wildcard sql/*.sql) EXTRA_CLEAN = test_setup.sql *.html DATA_built = pgtap.sql uninstall_pgtap.sql -MODULES = pgtap DOCS = README.pgtap SCRIPTS = bin/pg_prove REGRESS = $(patsubst sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --load-language=plpgsql +# Figure out where pg_config is, and set other vars we'll need depending on +# whether or not we're in the source tree. ifdef USE_PGXS PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) -include $(PGXS) else -subdir = contrib/citext top_builddir = ../.. -include $(top_builddir)/src/Makefile.global -include $(top_srcdir)/contrib/contrib-global.mk +PG_CONFIG := $(top_builddir)/src/bin/pg_config endif # We need to do various things with various versions of PostgreSQL. +VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') PGVER_MAJOR = $(shell echo $(VERSION) | awk -F. '{ print ($$1 + 0) }') PGVER_MINOR = $(shell echo $(VERSION) | awk -F. '{ print ($$2 + 0) }') PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') PGTAP_VERSION = 0.16 +# Compile the C code only if we're on 8.3 or older. +ifneq ($(PGVER_MINOR), 4) +MODULES = pgtap +endif + +ifdef PGXS +include $(PGXS) +else +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + # We support 8.0 and later. ifneq ($(PGVER_MAJOR), 8) $(error pgTAP requires PostgreSQL 8.0 or later. This is $(VERSION)) From 2e275e17cda7188e2ef78b5d1f320ecd42d4c780 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 27 Jan 2009 06:38:39 +0000 Subject: [PATCH 0254/1195] Rip out the `pg_typeof()` function definition when installing on 8.4. Will need to test this one of these days. --- Changes | 6 ++++-- Makefile | 13 ++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 855d9e003d07..d7e5ff795e71 100644 --- a/Changes +++ b/Changes @@ -8,8 +8,10 @@ Revision history for pgTAP - Fixed a bug in the makefile that prevented OS detection from working properly on many platforms (including Linux, and probably many others). - - No C code is compiled for 8.4, since the function we were compiling - ships with 8.4. + - The definition of the `pg_typeof()` function is removed when + installing on POstgresQL 8.4 and no C code is compiled, since the only + C code is for this function. This is because `pg_typeof()` is included + with PostgreSQL 8.4. 0.15 2009-01-20T18:41:24 - Changed `pg_typeof()` from immutable to stable, in compliance with its diff --git a/Makefile b/Makefile index 1840c5097d6f..08434ba5c8a7 100644 --- a/Makefile +++ b/Makefile @@ -45,10 +45,13 @@ endif ifeq ($(PGVER_MAJOR), 8) ifeq ($(PGVER_MINOR), 0) # Hack for E'' syntax (<= PG8.0) -REMOVE_E := -e "s/ E'/ '/g" +EXTRA_SUBS := -e "s/ E'/ '/g" # Throw and runtests aren't supported in 8.0. TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql,$(TESTS)) REGRESS := $(filter-out throwtap runtests,$(REGRESS)) +else ifeq ($(PGVER_MINOR), 4) +# Remove lines 15-20, which define pg_typeof(). +EXTRA_SUBS := -e '15,19d' endif endif @@ -114,11 +117,12 @@ endif pgtap.sql: pgtap.sql.in test_setup.sql ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' $(REMOVE_E) pgtap.sql.in > pgtap.sql + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' $(EXTRA_SUBS) pgtap.sql.in > pgtap.sql else - sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' $(REMOVE_E) pgtap.sql.in > pgtap.sql + sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' $(EXTRA_SUBS) pgtap.sql.in > pgtap.sql endif ifeq ($(PGVER_MAJOR), 8) +ifneq ($(PGVER_MINOR), 4) ifneq ($(PGVER_MINOR), 3) cat compat/install-8.2.sql >> pgtap.sql ifneq ($(PGVER_MINOR), 2) @@ -130,6 +134,7 @@ endif endif endif endif +endif uninstall_pgtap.sql: uninstall_pgtap.sql.in test_setup.sql ifdef TAPSCHEMA @@ -138,6 +143,7 @@ else cp uninstall_pgtap.sql.in uninstall_pgtap.sql endif ifeq ($(PGVER_MAJOR), 8) +ifneq ($(PGVER_MINOR), 4) ifneq ($(PGVER_MINOR), 3) mv uninstall_pgtap.sql uninstall_pgtap.tmp cat compat/uninstall-8.2.sql uninstall_pgtap.tmp >> uninstall_pgtap.sql @@ -149,6 +155,7 @@ ifeq ($(PGVER_MINOR), 0) rm uninstall_pgtap.tmp endif endif +endif # Make sure that we build the regression tests. installcheck: test_setup.sql From ff1c8058ff3f75e6a0aba93c99b626bcd8269f36 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 27 Jan 2009 06:38:56 +0000 Subject: [PATCH 0255/1195] That's done. --- README.pgtap | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.pgtap b/README.pgtap index aee31ad20c2e..b620733518a9 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1936,9 +1936,8 @@ Don't even use them there. To Do ----- -* Update build for 8.4, where `pg_typeof()` is already included. * Add `has_type()`. -* Update the Makefile to process pg_prove and set the proper path to Perl on +* Update the Makefile to process `pg_prove` and set the proper path to Perl on the shebang line. Will likely require a `$(PERL)` environment variable. * Update the Makefile to check for TAP::Harness and warn if it's not installed. From 349c4d261601a46625e920372232075ce363f1c2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 30 Jan 2009 19:27:44 +0000 Subject: [PATCH 0256/1195] Added `has_schema()` and `hasnt_schma()`. --- Changes | 1 + README.pgtap | 25 ++++++ expected/hastap.out | 206 +++++++++++++++++++++++++------------------- pgtap.sql.in | 38 +++++++- sql/hastap.sql | 70 ++++++++++++++- 5 files changed, 245 insertions(+), 95 deletions(-) diff --git a/Changes b/Changes index d7e5ff795e71..27c548a9c94e 100644 --- a/Changes +++ b/Changes @@ -12,6 +12,7 @@ Revision history for pgTAP installing on POstgresQL 8.4 and no C code is compiled, since the only C code is for this function. This is because `pg_typeof()` is included with PostgreSQL 8.4. + - Added `has_schema()` and `hasnt_schema()`. 0.15 2009-01-20T18:41:24 - Changed `pg_typeof()` from immutable to stable, in compliance with its diff --git a/README.pgtap b/README.pgtap index b620733518a9..ea933735406a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -602,6 +602,31 @@ A Wicked Schema Need to make sure that your database is designed just the way you think it should be? Use these test functions and rest easy. +### `has_schema( schema, description )` ### +### `has_schema( schema )` ### + + SELECT has_schema( + 'someschema', + 'I got someschema' + ); + +This function tests whether or not a schema exists in the database. The first +argument is a schema name and the second is the test description. If you omit +the schema, the schema must be visible in the search path. If you omit the +test description, it will be set to "Schema `:schema` should exist". + +### `hasnt_schema( schema, schema, description )` ### +### `hasnt_schema( schema, description )` ### +### `hasnt_schema( schema )` ### + + SELECT hasnt_schema( + 'someschema', + 'There should be no schema someschema' + ); + +This function is the inverse of `has_schema()`. The test passes if the +specified schema does *not* exist. + ### `has_table( schema, table, description )` ### ### `has_table( table, description )` ### ### `has_table( table )` ### diff --git a/expected/hastap.out b/expected/hastap.out index e211dcaaa94f..804eb98708e8 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,92 +1,116 @@ \unset ECHO -1..90 -ok 1 - has_table(non-existent table) should fail -ok 2 - has_table(non-existent table) should have the proper description -ok 3 - has_table(non-existent table) should have the proper diagnostics -ok 4 - has_table(non-existent schema, tab) should fail -ok 5 - has_table(non-existent schema, tab) should have the proper description -ok 6 - has_table(non-existent schema, tab) should have the proper diagnostics -ok 7 - has_table(sch, non-existent tab, desc) should fail -ok 8 - has_table(sch, non-existent tab, desc) should have the proper description -ok 9 - has_table(sch, non-existent tab, desc) should have the proper diagnostics -ok 10 - has_table(tab, desc) should pass -ok 11 - has_table(tab, desc) should have the proper description -ok 12 - has_table(tab, desc) should have the proper diagnostics -ok 13 - has_table(sch, tab, desc) should pass -ok 14 - has_table(sch, tab, desc) should have the proper description -ok 15 - has_table(sch, tab, desc) should have the proper diagnostics -ok 16 - hasnt_table(non-existent table) should pass -ok 17 - hasnt_table(non-existent table) should have the proper description -ok 18 - hasnt_table(non-existent table) should have the proper diagnostics -ok 19 - hasnt_table(non-existent schema, tab) should pass -ok 20 - hasnt_table(non-existent schema, tab) should have the proper description -ok 21 - hasnt_table(non-existent schema, tab) should have the proper diagnostics -ok 22 - hasnt_table(sch, non-existent tab, desc) should pass -ok 23 - hasnt_table(sch, non-existent tab, desc) should have the proper description -ok 24 - hasnt_table(sch, non-existent tab, desc) should have the proper diagnostics -ok 25 - hasnt_table(tab, desc) should fail -ok 26 - hasnt_table(tab, desc) should have the proper description -ok 27 - hasnt_table(tab, desc) should have the proper diagnostics -ok 28 - hasnt_table(sch, tab, desc) should fail -ok 29 - hasnt_table(sch, tab, desc) should have the proper description -ok 30 - hasnt_table(sch, tab, desc) should have the proper diagnostics -ok 31 - has_view(non-existent view) should fail -ok 32 - has_view(non-existent view) should have the proper description -ok 33 - has_view(non-existent view) should have the proper diagnostics -ok 34 - has_view(non-existent view, desc) should fail -ok 35 - has_view(non-existent view, desc) should have the proper description -ok 36 - has_view(non-existent view, desc) should have the proper diagnostics -ok 37 - has_view(sch, non-existtent view, desc) should fail -ok 38 - has_view(sch, non-existtent view, desc) should have the proper description -ok 39 - has_view(sch, non-existtent view, desc) should have the proper diagnostics -ok 40 - has_view(view, desc) should pass -ok 41 - has_view(view, desc) should have the proper description -ok 42 - has_view(view, desc) should have the proper diagnostics -ok 43 - has_view(sch, view, desc) should pass -ok 44 - has_view(sch, view, desc) should have the proper description -ok 45 - has_view(sch, view, desc) should have the proper diagnostics -ok 46 - hasnt_view(non-existent view) should pass -ok 47 - hasnt_view(non-existent view) should have the proper description -ok 48 - hasnt_view(non-existent view) should have the proper diagnostics -ok 49 - hasnt_view(non-existent view, desc) should pass -ok 50 - hasnt_view(non-existent view, desc) should have the proper description -ok 51 - hasnt_view(non-existent view, desc) should have the proper diagnostics -ok 52 - hasnt_view(sch, non-existtent view, desc) should pass -ok 53 - hasnt_view(sch, non-existtent view, desc) should have the proper description -ok 54 - hasnt_view(sch, non-existtent view, desc) should have the proper diagnostics -ok 55 - hasnt_view(view, desc) should fail -ok 56 - hasnt_view(view, desc) should have the proper description -ok 57 - hasnt_view(view, desc) should have the proper diagnostics -ok 58 - hasnt_view(sch, view, desc) should fail -ok 59 - hasnt_view(sch, view, desc) should have the proper description -ok 60 - hasnt_view(sch, view, desc) should have the proper diagnostics -ok 61 - has_column(non-existent tab, col) should fail -ok 62 - has_column(non-existent tab, col) should have the proper description -ok 63 - has_column(non-existent tab, col) should have the proper diagnostics -ok 64 - has_column(non-existent tab, col, desc) should fail -ok 65 - has_column(non-existent tab, col, desc) should have the proper description -ok 66 - has_column(non-existent tab, col, desc) should have the proper diagnostics -ok 67 - has_column(non-existent sch, tab, col, desc) should fail -ok 68 - has_column(non-existent sch, tab, col, desc) should have the proper description -ok 69 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 70 - has_column(table, column) should pass -ok 71 - has_column(table, column) should have the proper description -ok 72 - has_column(table, column) should have the proper diagnostics -ok 73 - has_column(sch, tab, col, desc) should pass -ok 74 - has_column(sch, tab, col, desc) should have the proper description -ok 75 - has_column(sch, tab, col, desc) should have the proper diagnostics -ok 76 - hasnt_column(non-existent tab, col) should pass -ok 77 - hasnt_column(non-existent tab, col) should have the proper description -ok 78 - hasnt_column(non-existent tab, col) should have the proper diagnostics -ok 79 - hasnt_column(non-existent tab, col, desc) should pass -ok 80 - hasnt_column(non-existent tab, col, desc) should have the proper description -ok 81 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics -ok 82 - hasnt_column(non-existent sch, tab, col, desc) should pass -ok 83 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description -ok 84 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 85 - hasnt_column(table, column) should fail -ok 86 - hasnt_column(table, column) should have the proper description -ok 87 - hasnt_column(table, column) should have the proper diagnostics -ok 88 - hasnt_column(sch, tab, col, desc) should fail -ok 89 - hasnt_column(sch, tab, col, desc) should have the proper description -ok 90 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics +1..114 +ok 1 - has_schema(non-existent schema) should fail +ok 2 - has_schema(non-existent schema) should have the proper description +ok 3 - has_schema(non-existent schema) should have the proper diagnostics +ok 4 - has_schema(non-existent schema, tab) should fail +ok 5 - has_schema(non-existent schema, tab) should have the proper description +ok 6 - has_schema(non-existent schema, tab) should have the proper diagnostics +ok 7 - has_schema(schema) should pass +ok 8 - has_schema(schema) should have the proper description +ok 9 - has_schema(schema) should have the proper diagnostics +ok 10 - has_schema(schema, desc) should pass +ok 11 - has_schema(schema, desc) should have the proper description +ok 12 - has_schema(schema, desc) should have the proper diagnostics +ok 13 - hasnt_schema(non-existent schema) should pass +ok 14 - hasnt_schema(non-existent schema) should have the proper description +ok 15 - hasnt_schema(non-existent schema) should have the proper diagnostics +ok 16 - hasnt_schema(non-existent schema, tab) should pass +ok 17 - hasnt_schema(non-existent schema, tab) should have the proper description +ok 18 - hasnt_schema(non-existent schema, tab) should have the proper diagnostics +ok 19 - hasnt_schema(schema) should fail +ok 20 - hasnt_schema(schema) should have the proper description +ok 21 - hasnt_schema(schema) should have the proper diagnostics +ok 22 - hasnt_schema(schema, desc) should fail +ok 23 - hasnt_schema(schema, desc) should have the proper description +ok 24 - hasnt_schema(schema, desc) should have the proper diagnostics +ok 25 - has_table(non-existent table) should fail +ok 26 - has_table(non-existent table) should have the proper description +ok 27 - has_table(non-existent table) should have the proper diagnostics +ok 28 - has_table(non-existent schema, tab) should fail +ok 29 - has_table(non-existent schema, tab) should have the proper description +ok 30 - has_table(non-existent schema, tab) should have the proper diagnostics +ok 31 - has_table(sch, non-existent table, desc) should fail +ok 32 - has_table(sch, non-existent table, desc) should have the proper description +ok 33 - has_table(sch, non-existent table, desc) should have the proper diagnostics +ok 34 - has_table(tab, desc) should pass +ok 35 - has_table(tab, desc) should have the proper description +ok 36 - has_table(tab, desc) should have the proper diagnostics +ok 37 - has_table(sch, tab, desc) should pass +ok 38 - has_table(sch, tab, desc) should have the proper description +ok 39 - has_table(sch, tab, desc) should have the proper diagnostics +ok 40 - hasnt_table(non-existent table) should pass +ok 41 - hasnt_table(non-existent table) should have the proper description +ok 42 - hasnt_table(non-existent table) should have the proper diagnostics +ok 43 - hasnt_table(non-existent schema, tab) should pass +ok 44 - hasnt_table(non-existent schema, tab) should have the proper description +ok 45 - hasnt_table(non-existent schema, tab) should have the proper diagnostics +ok 46 - hasnt_table(sch, non-existent tab, desc) should pass +ok 47 - hasnt_table(sch, non-existent tab, desc) should have the proper description +ok 48 - hasnt_table(sch, non-existent tab, desc) should have the proper diagnostics +ok 49 - hasnt_table(tab, desc) should fail +ok 50 - hasnt_table(tab, desc) should have the proper description +ok 51 - hasnt_table(tab, desc) should have the proper diagnostics +ok 52 - hasnt_table(sch, tab, desc) should fail +ok 53 - hasnt_table(sch, tab, desc) should have the proper description +ok 54 - hasnt_table(sch, tab, desc) should have the proper diagnostics +ok 55 - has_view(non-existent view) should fail +ok 56 - has_view(non-existent view) should have the proper description +ok 57 - has_view(non-existent view) should have the proper diagnostics +ok 58 - has_view(non-existent view, desc) should fail +ok 59 - has_view(non-existent view, desc) should have the proper description +ok 60 - has_view(non-existent view, desc) should have the proper diagnostics +ok 61 - has_view(sch, non-existtent view, desc) should fail +ok 62 - has_view(sch, non-existtent view, desc) should have the proper description +ok 63 - has_view(sch, non-existtent view, desc) should have the proper diagnostics +ok 64 - has_view(view, desc) should pass +ok 65 - has_view(view, desc) should have the proper description +ok 66 - has_view(view, desc) should have the proper diagnostics +ok 67 - has_view(sch, view, desc) should pass +ok 68 - has_view(sch, view, desc) should have the proper description +ok 69 - has_view(sch, view, desc) should have the proper diagnostics +ok 70 - hasnt_view(non-existent view) should pass +ok 71 - hasnt_view(non-existent view) should have the proper description +ok 72 - hasnt_view(non-existent view) should have the proper diagnostics +ok 73 - hasnt_view(non-existent view, desc) should pass +ok 74 - hasnt_view(non-existent view, desc) should have the proper description +ok 75 - hasnt_view(non-existent view, desc) should have the proper diagnostics +ok 76 - hasnt_view(sch, non-existtent view, desc) should pass +ok 77 - hasnt_view(sch, non-existtent view, desc) should have the proper description +ok 78 - hasnt_view(sch, non-existtent view, desc) should have the proper diagnostics +ok 79 - hasnt_view(view, desc) should fail +ok 80 - hasnt_view(view, desc) should have the proper description +ok 81 - hasnt_view(view, desc) should have the proper diagnostics +ok 82 - hasnt_view(sch, view, desc) should fail +ok 83 - hasnt_view(sch, view, desc) should have the proper description +ok 84 - hasnt_view(sch, view, desc) should have the proper diagnostics +ok 85 - has_column(non-existent tab, col) should fail +ok 86 - has_column(non-existent tab, col) should have the proper description +ok 87 - has_column(non-existent tab, col) should have the proper diagnostics +ok 88 - has_column(non-existent tab, col, desc) should fail +ok 89 - has_column(non-existent tab, col, desc) should have the proper description +ok 90 - has_column(non-existent tab, col, desc) should have the proper diagnostics +ok 91 - has_column(non-existent sch, tab, col, desc) should fail +ok 92 - has_column(non-existent sch, tab, col, desc) should have the proper description +ok 93 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 94 - has_column(table, column) should pass +ok 95 - has_column(table, column) should have the proper description +ok 96 - has_column(table, column) should have the proper diagnostics +ok 97 - has_column(sch, tab, col, desc) should pass +ok 98 - has_column(sch, tab, col, desc) should have the proper description +ok 99 - has_column(sch, tab, col, desc) should have the proper diagnostics +ok 100 - hasnt_column(non-existent tab, col) should pass +ok 101 - hasnt_column(non-existent tab, col) should have the proper description +ok 102 - hasnt_column(non-existent tab, col) should have the proper diagnostics +ok 103 - hasnt_column(non-existent tab, col, desc) should pass +ok 104 - hasnt_column(non-existent tab, col, desc) should have the proper description +ok 105 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics +ok 106 - hasnt_column(non-existent sch, tab, col, desc) should pass +ok 107 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description +ok 108 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 109 - hasnt_column(table, column) should fail +ok 110 - hasnt_column(table, column) should have the proper description +ok 111 - hasnt_column(table, column) should have the proper diagnostics +ok 112 - hasnt_column(sch, tab, col, desc) should fail +ok 113 - hasnt_column(sch, tab, col, desc) should have the proper description +ok 114 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index bdc7089c8528..ab53097adb6d 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2546,6 +2546,42 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; +-- has_schema( schema, desscription ) +CREATE OR REPLACE FUNCTION has_schema( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_namespace n + WHERE LOWER(n.nspname) = LOWER($1) + ), $2 + ); +$$ LANGUAGE sql; + +-- has_schema( schema ) +CREATE OR REPLACE FUNCTION has_schema( NAME ) +RETURNS TEXT AS $$ + SELECT has_schema( $1, 'Schema ' || $1 || ' should exist' ); +$$ LANGUAGE sql; + +-- hasnt_schema( schema, desscription ) +CREATE OR REPLACE FUNCTION hasnt_schema( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( + NOT EXISTS( + SELECT true + FROM pg_catalog.pg_namespace n + WHERE LOWER(n.nspname) = LOWER($1) + ), $2 + ); +$$ LANGUAGE sql; + +-- hasnt_schema( schema ) +CREATE OR REPLACE FUNCTION hasnt_schema( NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_schema( $1, 'Schema ' || $1 || ' should not exist' ); +$$ LANGUAGE sql; + -- check_test( test_output, pass, name, description, diag ) CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) RETURNS SETOF TEXT AS $$ @@ -2732,8 +2768,6 @@ EXCEPTION END; $$ LANGUAGE plpgsql; - - CREATE OR REPLACE FUNCTION _cleanup() RETURNS boolean AS $$ DECLARE diff --git a/sql/hastap.sql b/sql/hastap.sql index a91d8b2c233f..2718191e96e6 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -3,7 +3,8 @@ -- $Id$ -SELECT plan(90); +SELECT plan(114); +--SELECT * FROM no_plan(); -- This will be rolled back. :-) SET client_min_messages = warning; @@ -13,8 +14,73 @@ CREATE TABLE sometab( numb NUMERIC(10, 2), myint NUMERIC(8) ); +CREATE SCHEMA someschema; RESET client_min_messages; +/****************************************************************************/ +-- Test has_schema(). +SELECT * FROM check_test( + has_schema( '__SDFSDFD__' ), + false, + 'has_schema(non-existent schema)', + 'Schema __SDFSDFD__ should exist', + '' +); +SELECT * FROM check_test( + has_schema( '__SDFSDFD__', 'lol' ), + false, + 'has_schema(non-existent schema, tab)', + 'lol', + '' +); + +SELECT * FROM check_test( + has_schema( 'someschema' ), + true, + 'has_schema(schema)', + 'Schema someschema should exist', + '' +); +SELECT * FROM check_test( + has_schema( 'someschema', 'lol' ), + true, + 'has_schema(schema, desc)', + 'lol', + '' +); + +/****************************************************************************/ +-- Test hasnt_schema(). +SELECT * FROM check_test( + hasnt_schema( '__SDFSDFD__' ), + true, + 'hasnt_schema(non-existent schema)', + 'Schema __SDFSDFD__ should not exist', + '' +); +SELECT * FROM check_test( + hasnt_schema( '__SDFSDFD__', 'lol' ), + true, + 'hasnt_schema(non-existent schema, tab)', + 'lol', + '' +); + +SELECT * FROM check_test( + hasnt_schema( 'someschema' ), + false, + 'hasnt_schema(schema)', + 'Schema someschema should not exist', + '' +); +SELECT * FROM check_test( + hasnt_schema( 'someschema', 'lol' ), + false, + 'hasnt_schema(schema, desc)', + 'lol', + '' +); + /****************************************************************************/ -- Test has_table(). @@ -37,7 +103,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( has_table( 'foo', '__SDFSDFD__', 'desc' ), false, - 'has_table(sch, non-existent tab, desc)', + 'has_table(sch, non-existent table, desc)', 'desc', '' ); From ae86b43c81d455a81cf93f15c4995c391116703f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 30 Jan 2009 20:28:59 +0000 Subject: [PATCH 0257/1195] * Added `has_type()` and `hasnt_type()`. * Updated `uninstall_pgtap.sql.in`. --- Changes | 1 + README.pgtap | 56 +++++++++-- expected/hastap.out | 218 +++++++++++++++++++++++++++-------------- pgtap.sql.in | 70 +++++++++++++ sql/hastap.sql | 180 +++++++++++++++++++++++++++++++++- uninstall_pgtap.sql.in | 14 +++ 6 files changed, 455 insertions(+), 84 deletions(-) diff --git a/Changes b/Changes index 27c548a9c94e..062c858be99e 100644 --- a/Changes +++ b/Changes @@ -13,6 +13,7 @@ Revision history for pgTAP C code is for this function. This is because `pg_typeof()` is included with PostgreSQL 8.4. - Added `has_schema()` and `hasnt_schema()`. + - Added `has_type()` and `hasnt_type()`. 0.15 2009-01-20T18:41:24 - Changed `pg_typeof()` from immutable to stable, in compliance with its diff --git a/README.pgtap b/README.pgtap index ea933735406a..6d3d7aa1a268 100644 --- a/README.pgtap +++ b/README.pgtap @@ -681,6 +681,47 @@ Just like `has_table()`, only it tests for the existence of a view. This function is the inverse of `has_view()`. The test passes if the specified view does *not* exist. +### `has_type( schema, type, description )` ### +### `has_type( schema, type )` ### +### `has_type( type, description )` ### +### `has_type( type )` ### + + SELECT has_type( + 'myschema', + 'sometype', + 'I got myschema.sometype' + ); + +This function tests whether or not a type exists in the database. The first +argument is a schema name, the second is a type name, and the third is the +test description. If you omit the schema, the type must be visible in the +search path. If you omit the test description, it will be set to "Type `:type` +should exist". If you're passing a schema and type rather than type and +description, be sure to cast the arguments to `name` values so that your type +name doesn't get treated as a description. + +If you've created a composite type and want to test that the composed types +are a part of it, use the column testing functions to verify them, like so: + + CREATE TYPE foo AS (id int, name text); + SELECT has_type( 'foo' ); + SELECT has_column( 'foo', 'id' ); + SELECT col_type_is( 'foo', 'id', 'integer' ); + +### `hasnt_type( schema, type, description )` ### +### `hasnt_type( schema, type )` ### +### `hasnt_type( type, description )` ### +### `hasnt_type( type )` ### + + SELECT hasnt_type( + 'myschema', + 'sometype', + 'There should be no type myschema.sometype' + ); + +This function is the inverse of `has_type()`. The test passes if the specified +type does *not* exist. + ### `has_column( schema, table, column, description )` ### ### `has_column( table, column, description )` ### ### `has_column( table, column )` ### @@ -692,11 +733,12 @@ view does *not* exist. 'I got myschema.sometable.somecolumn' ); -Tests whether or not a column exists in a given table or view. The first -argument is the schema name, the second the table name, the third the column -name, and the fourth is the test description. If the schema is omitted, the -table must be visible in the search path. If the test description is omitted, -it will be set to "Column `:table`.`:column` should exist". +Tests whether or not a column exists in a given table, view, or composite +type. The first argument is the schema name, the second the table name, the +third the column name, and the fourth is the test description. If the schema +is omitted, the table must be visible in the search path. If the test +description is omitted, it will be set to "Column `:table`.`:column` should +exist". ### `hasnt_column( schema, table, column, description )` ### ### `hasnt_column( table, column, description )` ### @@ -710,7 +752,8 @@ it will be set to "Column `:table`.`:column` should exist". ); This function is the inverse of `has_column()`. The test passes if the -specified column does *not* exist. +specified column does *not* exist in the specified table, view, or composite +type. ### `col_not_null( schema, table, column, description )` ### ### `col_not_null( table, column, description )` ### @@ -1961,7 +2004,6 @@ Don't even use them there. To Do ----- -* Add `has_type()`. * Update the Makefile to process `pg_prove` and set the proper path to Perl on the shebang line. Will likely require a `$(PERL)` environment variable. * Update the Makefile to check for TAP::Harness and warn if it's not diff --git a/expected/hastap.out b/expected/hastap.out index 804eb98708e8..c862ef9ad8b3 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..114 +1..180 ok 1 - has_schema(non-existent schema) should fail ok 2 - has_schema(non-existent schema) should have the proper description ok 3 - has_schema(non-existent schema) should have the proper diagnostics @@ -39,78 +39,144 @@ ok 36 - has_table(tab, desc) should have the proper diagnostics ok 37 - has_table(sch, tab, desc) should pass ok 38 - has_table(sch, tab, desc) should have the proper description ok 39 - has_table(sch, tab, desc) should have the proper diagnostics -ok 40 - hasnt_table(non-existent table) should pass -ok 41 - hasnt_table(non-existent table) should have the proper description -ok 42 - hasnt_table(non-existent table) should have the proper diagnostics -ok 43 - hasnt_table(non-existent schema, tab) should pass -ok 44 - hasnt_table(non-existent schema, tab) should have the proper description -ok 45 - hasnt_table(non-existent schema, tab) should have the proper diagnostics -ok 46 - hasnt_table(sch, non-existent tab, desc) should pass -ok 47 - hasnt_table(sch, non-existent tab, desc) should have the proper description -ok 48 - hasnt_table(sch, non-existent tab, desc) should have the proper diagnostics -ok 49 - hasnt_table(tab, desc) should fail -ok 50 - hasnt_table(tab, desc) should have the proper description -ok 51 - hasnt_table(tab, desc) should have the proper diagnostics -ok 52 - hasnt_table(sch, tab, desc) should fail -ok 53 - hasnt_table(sch, tab, desc) should have the proper description -ok 54 - hasnt_table(sch, tab, desc) should have the proper diagnostics -ok 55 - has_view(non-existent view) should fail -ok 56 - has_view(non-existent view) should have the proper description -ok 57 - has_view(non-existent view) should have the proper diagnostics -ok 58 - has_view(non-existent view, desc) should fail -ok 59 - has_view(non-existent view, desc) should have the proper description -ok 60 - has_view(non-existent view, desc) should have the proper diagnostics -ok 61 - has_view(sch, non-existtent view, desc) should fail -ok 62 - has_view(sch, non-existtent view, desc) should have the proper description -ok 63 - has_view(sch, non-existtent view, desc) should have the proper diagnostics -ok 64 - has_view(view, desc) should pass -ok 65 - has_view(view, desc) should have the proper description -ok 66 - has_view(view, desc) should have the proper diagnostics -ok 67 - has_view(sch, view, desc) should pass -ok 68 - has_view(sch, view, desc) should have the proper description -ok 69 - has_view(sch, view, desc) should have the proper diagnostics -ok 70 - hasnt_view(non-existent view) should pass -ok 71 - hasnt_view(non-existent view) should have the proper description -ok 72 - hasnt_view(non-existent view) should have the proper diagnostics -ok 73 - hasnt_view(non-existent view, desc) should pass -ok 74 - hasnt_view(non-existent view, desc) should have the proper description -ok 75 - hasnt_view(non-existent view, desc) should have the proper diagnostics -ok 76 - hasnt_view(sch, non-existtent view, desc) should pass -ok 77 - hasnt_view(sch, non-existtent view, desc) should have the proper description -ok 78 - hasnt_view(sch, non-existtent view, desc) should have the proper diagnostics -ok 79 - hasnt_view(view, desc) should fail -ok 80 - hasnt_view(view, desc) should have the proper description -ok 81 - hasnt_view(view, desc) should have the proper diagnostics -ok 82 - hasnt_view(sch, view, desc) should fail -ok 83 - hasnt_view(sch, view, desc) should have the proper description -ok 84 - hasnt_view(sch, view, desc) should have the proper diagnostics -ok 85 - has_column(non-existent tab, col) should fail -ok 86 - has_column(non-existent tab, col) should have the proper description -ok 87 - has_column(non-existent tab, col) should have the proper diagnostics -ok 88 - has_column(non-existent tab, col, desc) should fail -ok 89 - has_column(non-existent tab, col, desc) should have the proper description -ok 90 - has_column(non-existent tab, col, desc) should have the proper diagnostics -ok 91 - has_column(non-existent sch, tab, col, desc) should fail -ok 92 - has_column(non-existent sch, tab, col, desc) should have the proper description -ok 93 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 94 - has_column(table, column) should pass -ok 95 - has_column(table, column) should have the proper description -ok 96 - has_column(table, column) should have the proper diagnostics -ok 97 - has_column(sch, tab, col, desc) should pass -ok 98 - has_column(sch, tab, col, desc) should have the proper description -ok 99 - has_column(sch, tab, col, desc) should have the proper diagnostics -ok 100 - hasnt_column(non-existent tab, col) should pass -ok 101 - hasnt_column(non-existent tab, col) should have the proper description -ok 102 - hasnt_column(non-existent tab, col) should have the proper diagnostics -ok 103 - hasnt_column(non-existent tab, col, desc) should pass -ok 104 - hasnt_column(non-existent tab, col, desc) should have the proper description -ok 105 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics -ok 106 - hasnt_column(non-existent sch, tab, col, desc) should pass -ok 107 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description -ok 108 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 109 - hasnt_column(table, column) should fail -ok 110 - hasnt_column(table, column) should have the proper description -ok 111 - hasnt_column(table, column) should have the proper diagnostics -ok 112 - hasnt_column(sch, tab, col, desc) should fail -ok 113 - hasnt_column(sch, tab, col, desc) should have the proper description -ok 114 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics +ok 40 - has_table(sch, view, desc) should fail +ok 41 - has_table(sch, view, desc) should have the proper description +ok 42 - has_table(sch, view, desc) should have the proper diagnostics +ok 43 - has_table(type, desc) should fail +ok 44 - has_table(type, desc) should have the proper description +ok 45 - has_table(type, desc) should have the proper diagnostics +ok 46 - hasnt_table(non-existent table) should pass +ok 47 - hasnt_table(non-existent table) should have the proper description +ok 48 - hasnt_table(non-existent table) should have the proper diagnostics +ok 49 - hasnt_table(non-existent schema, tab) should pass +ok 50 - hasnt_table(non-existent schema, tab) should have the proper description +ok 51 - hasnt_table(non-existent schema, tab) should have the proper diagnostics +ok 52 - hasnt_table(sch, non-existent tab, desc) should pass +ok 53 - hasnt_table(sch, non-existent tab, desc) should have the proper description +ok 54 - hasnt_table(sch, non-existent tab, desc) should have the proper diagnostics +ok 55 - hasnt_table(tab, desc) should fail +ok 56 - hasnt_table(tab, desc) should have the proper description +ok 57 - hasnt_table(tab, desc) should have the proper diagnostics +ok 58 - hasnt_table(sch, tab, desc) should fail +ok 59 - hasnt_table(sch, tab, desc) should have the proper description +ok 60 - hasnt_table(sch, tab, desc) should have the proper diagnostics +ok 61 - has_view(non-existent view) should fail +ok 62 - has_view(non-existent view) should have the proper description +ok 63 - has_view(non-existent view) should have the proper diagnostics +ok 64 - has_view(non-existent view, desc) should fail +ok 65 - has_view(non-existent view, desc) should have the proper description +ok 66 - has_view(non-existent view, desc) should have the proper diagnostics +ok 67 - has_view(sch, non-existtent view, desc) should fail +ok 68 - has_view(sch, non-existtent view, desc) should have the proper description +ok 69 - has_view(sch, non-existtent view, desc) should have the proper diagnostics +ok 70 - has_view(view, desc) should pass +ok 71 - has_view(view, desc) should have the proper description +ok 72 - has_view(view, desc) should have the proper diagnostics +ok 73 - has_view(sch, view, desc) should pass +ok 74 - has_view(sch, view, desc) should have the proper description +ok 75 - has_view(sch, view, desc) should have the proper diagnostics +ok 76 - hasnt_view(non-existent view) should pass +ok 77 - hasnt_view(non-existent view) should have the proper description +ok 78 - hasnt_view(non-existent view) should have the proper diagnostics +ok 79 - hasnt_view(non-existent view, desc) should pass +ok 80 - hasnt_view(non-existent view, desc) should have the proper description +ok 81 - hasnt_view(non-existent view, desc) should have the proper diagnostics +ok 82 - hasnt_view(sch, non-existtent view, desc) should pass +ok 83 - hasnt_view(sch, non-existtent view, desc) should have the proper description +ok 84 - hasnt_view(sch, non-existtent view, desc) should have the proper diagnostics +ok 85 - hasnt_view(view, desc) should fail +ok 86 - hasnt_view(view, desc) should have the proper description +ok 87 - hasnt_view(view, desc) should have the proper diagnostics +ok 88 - hasnt_view(sch, view, desc) should fail +ok 89 - hasnt_view(sch, view, desc) should have the proper description +ok 90 - hasnt_view(sch, view, desc) should have the proper diagnostics +ok 91 - has_type(type) should pass +ok 92 - has_type(type) should have the proper description +ok 93 - has_type(type) should have the proper diagnostics +ok 94 - has_type(type, desc) should pass +ok 95 - has_type(type, desc) should have the proper description +ok 96 - has_type(type, desc) should have the proper diagnostics +ok 97 - has_type(scheam, type) should pass +ok 98 - has_type(scheam, type) should have the proper description +ok 99 - has_type(scheam, type) should have the proper diagnostics +ok 100 - has_type(schema, type, desc) should pass +ok 101 - has_type(schema, type, desc) should have the proper description +ok 102 - has_type(schema, type, desc) should have the proper diagnostics +ok 103 - has_type(type) should fail +ok 104 - has_type(type) should have the proper description +ok 105 - has_type(type) should have the proper diagnostics +ok 106 - has_type(type, desc) should fail +ok 107 - has_type(type, desc) should have the proper description +ok 108 - has_type(type, desc) should have the proper diagnostics +ok 109 - has_type(scheam, type) should fail +ok 110 - has_type(scheam, type) should have the proper description +ok 111 - has_type(scheam, type) should have the proper diagnostics +ok 112 - has_type(schema, type, desc) should fail +ok 113 - has_type(schema, type, desc) should have the proper description +ok 114 - has_type(schema, type, desc) should have the proper diagnostics +ok 115 - hasnt_type(type) should pass +ok 116 - hasnt_type(type) should have the proper description +ok 117 - hasnt_type(type) should have the proper diagnostics +ok 118 - hasnt_type(type, desc) should pass +ok 119 - hasnt_type(type, desc) should have the proper description +ok 120 - hasnt_type(type, desc) should have the proper diagnostics +ok 121 - hasnt_type(scheam, type) should pass +ok 122 - hasnt_type(scheam, type) should have the proper description +ok 123 - hasnt_type(scheam, type) should have the proper diagnostics +ok 124 - hasnt_type(schema, type, desc) should pass +ok 125 - hasnt_type(schema, type, desc) should have the proper description +ok 126 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 127 - hasnt_type(type) should fail +ok 128 - hasnt_type(type) should have the proper description +ok 129 - hasnt_type(type) should have the proper diagnostics +ok 130 - hasnt_type(type, desc) should fail +ok 131 - hasnt_type(type, desc) should have the proper description +ok 132 - hasnt_type(type, desc) should have the proper diagnostics +ok 133 - hasnt_type(scheam, type) should fail +ok 134 - hasnt_type(scheam, type) should have the proper description +ok 135 - hasnt_type(scheam, type) should have the proper diagnostics +ok 136 - hasnt_type(schema, type, desc) should fail +ok 137 - hasnt_type(schema, type, desc) should have the proper description +ok 138 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 139 - has_column(non-existent tab, col) should fail +ok 140 - has_column(non-existent tab, col) should have the proper description +ok 141 - has_column(non-existent tab, col) should have the proper diagnostics +ok 142 - has_column(non-existent tab, col, desc) should fail +ok 143 - has_column(non-existent tab, col, desc) should have the proper description +ok 144 - has_column(non-existent tab, col, desc) should have the proper diagnostics +ok 145 - has_column(non-existent sch, tab, col, desc) should fail +ok 146 - has_column(non-existent sch, tab, col, desc) should have the proper description +ok 147 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 148 - has_column(table, column) should pass +ok 149 - has_column(table, column) should have the proper description +ok 150 - has_column(table, column) should have the proper diagnostics +ok 151 - has_column(sch, tab, col, desc) should pass +ok 152 - has_column(sch, tab, col, desc) should have the proper description +ok 153 - has_column(sch, tab, col, desc) should have the proper diagnostics +ok 154 - has_column(view, column) should pass +ok 155 - has_column(view, column) should have the proper description +ok 156 - has_column(view, column) should have the proper diagnostics +ok 157 - has_column(type, column) should pass +ok 158 - has_column(type, column) should have the proper description +ok 159 - has_column(type, column) should have the proper diagnostics +ok 160 - hasnt_column(non-existent tab, col) should pass +ok 161 - hasnt_column(non-existent tab, col) should have the proper description +ok 162 - hasnt_column(non-existent tab, col) should have the proper diagnostics +ok 163 - hasnt_column(non-existent tab, col, desc) should pass +ok 164 - hasnt_column(non-existent tab, col, desc) should have the proper description +ok 165 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics +ok 166 - hasnt_column(non-existent sch, tab, col, desc) should pass +ok 167 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description +ok 168 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 169 - hasnt_column(table, column) should fail +ok 170 - hasnt_column(table, column) should have the proper description +ok 171 - hasnt_column(table, column) should have the proper diagnostics +ok 172 - hasnt_column(sch, tab, col, desc) should fail +ok 173 - hasnt_column(sch, tab, col, desc) should have the proper description +ok 174 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics +ok 175 - hasnt_column(view, column) should pass +ok 176 - hasnt_column(view, column) should have the proper description +ok 177 - hasnt_column(view, column) should have the proper diagnostics +ok 178 - hasnt_column(type, column) should pass +ok 179 - hasnt_column(type, column) should have the proper description +ok 180 - hasnt_column(type, column) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index ab53097adb6d..7ce7c0e55105 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2582,6 +2582,76 @@ RETURNS TEXT AS $$ SELECT hasnt_schema( $1, 'Schema ' || $1 || ' should not exist' ); $$ LANGUAGE sql; +CREATE OR REPLACE FUNCTION _has_type( NAME, NAME ) +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 + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _has_type( NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_type t + WHERE t.typisdefined + AND t.typname = $1 + ); +$$ 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 ), $3 ); +$$ LANGUAGE sql; + +-- has_type( schema, type ) +CREATE OR REPLACE FUNCTION has_type( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT has_type( $1, $2, 'Type ' || $1 || '.' || $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 ), $2 ); +$$ LANGUAGE sql; + +-- has_type( type ) +CREATE OR REPLACE FUNCTION has_type( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _has_type( $1 ), ('Type ' || $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 ), $3 ); +$$ LANGUAGE sql; + +-- hasnt_type( schema, type ) +CREATE OR REPLACE FUNCTION hasnt_type( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT hasnt_type( $1, $2, 'Type ' || $1 || '.' || $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 ), $2 ); +$$ LANGUAGE sql; + +-- hasnt_type( type ) +CREATE OR REPLACE FUNCTION hasnt_type( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _has_type( $1 ), ('Type ' || $1 || ' should not exist')::text ); +$$ LANGUAGE sql; + -- check_test( test_output, pass, name, description, diag ) CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) RETURNS SETOF TEXT AS $$ diff --git a/sql/hastap.sql b/sql/hastap.sql index 2718191e96e6..e9e542abfa1e 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(114); +SELECT plan(180); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -14,6 +14,10 @@ CREATE TABLE sometab( numb NUMERIC(10, 2), myint NUMERIC(8) ); +CREATE TYPE sometype AS ( + id INT, + name TEXT +); CREATE SCHEMA someschema; RESET client_min_messages; @@ -124,6 +128,22 @@ SELECT * FROM check_test( '' ); +-- It should ignore views and types. +SELECT * FROM check_test( + has_table( 'pg_catalog', 'pg_tables', 'desc' ), + false, + 'has_table(sch, view, desc)', + 'desc', + '' +); +SELECT * FROM check_test( + has_table( 'sometype', 'desc' ), + false, + 'has_table(type, desc)', + 'desc', + '' +); + /****************************************************************************/ -- Test hasnt_table(). @@ -253,6 +273,128 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test has_type(). +SELECT * FROM check_test( + has_type( 'sometype' ), + true, + 'has_type(type)', + 'Type sometype should exist', + '' +); +SELECT * FROM check_test( + has_type( 'sometype', 'mydesc' ), + true, + 'has_type(type, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + has_type( 'public'::name, 'sometype'::name ), + true, + 'has_type(scheam, type)', + 'Type public.sometype should exist', + '' +); +SELECT * FROM check_test( + has_type( 'public', 'sometype', 'mydesc' ), + true, + 'has_type(schema, type, desc)', + 'mydesc', + '' +); + +-- Try failures. +SELECT * FROM check_test( + has_type( '__foobarbaz__' ), + false, + 'has_type(type)', + 'Type __foobarbaz__ should exist', + '' +); +SELECT * FROM check_test( + has_type( '__foobarbaz__', 'mydesc' ), + false, + 'has_type(type, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + has_type( 'public'::name, '__foobarbaz__'::name ), + false, + 'has_type(scheam, type)', + 'Type public.__foobarbaz__ should exist', + '' +); +SELECT * FROM check_test( + has_type( 'public', '__foobarbaz__', 'mydesc' ), + false, + 'has_type(schema, type, desc)', + 'mydesc', + '' +); + +/****************************************************************************/ +-- Test hasnt_type(). +SELECT * FROM check_test( + hasnt_type( '__foobarbaz__' ), + true, + 'hasnt_type(type)', + 'Type __foobarbaz__ should not exist', + '' +); +SELECT * FROM check_test( + hasnt_type( '__foobarbaz__', 'mydesc' ), + true, + 'hasnt_type(type, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + hasnt_type( 'public'::name, '__foobarbaz__'::name ), + true, + 'hasnt_type(scheam, type)', + 'Type public.__foobarbaz__ should not exist', + '' +); +SELECT * FROM check_test( + hasnt_type( 'public', '__foobarbaz__', 'mydesc' ), + true, + 'hasnt_type(schema, type, desc)', + 'mydesc', + '' +); + +-- Try failures. +SELECT * FROM check_test( + hasnt_type( 'sometype' ), + false, + 'hasnt_type(type)', + 'Type sometype should not exist', + '' +); +SELECT * FROM check_test( + hasnt_type( 'sometype', 'mydesc' ), + false, + 'hasnt_type(type, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + hasnt_type( 'public'::name, 'sometype'::name ), + false, + 'hasnt_type(scheam, type)', + 'Type public.sometype should not exist', + '' +); +SELECT * FROM check_test( + hasnt_type( 'public', 'sometype', 'mydesc' ), + false, + 'hasnt_type(schema, type, desc)', + 'mydesc', + '' +); + /****************************************************************************/ -- Test has_column(). @@ -296,6 +438,24 @@ SELECT * FROM check_test( '' ); +-- Make sure it works with views. +SELECT * FROM check_test( + has_column( 'pg_tables', 'schemaname' ), + true, + 'has_column(view, column)', + 'Column pg_tables(schemaname) should exist', + '' +); + +-- Make sure it works with composite types. +SELECT * FROM check_test( + has_column( 'sometype', 'name' ), + true, + 'has_column(type, column)', + 'Column sometype(name) should exist', + '' +); + /****************************************************************************/ -- Test hasnt_column(). @@ -339,6 +499,24 @@ SELECT * FROM check_test( '' ); +-- Make sure it works with views. +SELECT * FROM check_test( + hasnt_column( 'pg_tables', 'whatever' ), + true, + 'hasnt_column(view, column)', + 'Column pg_tables(whatever) should not exist', + '' +); + +-- Make sure it works with composite types. +SELECT * FROM check_test( + hasnt_column( 'sometype', 'foobar' ), + true, + 'hasnt_column(type, column)', + 'Column sometype(foobar) should not exist', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 98059bda9816..87b348c3e50c 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -19,6 +19,20 @@ DROP FUNCTION check_test( TEXT, BOOLEAN ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); +DROP FUNCTION hasnt_type( NAME ); +DROP FUNCTION hasnt_type( NAME, TEXT ); +DROP FUNCTION hasnt_type( NAME, NAME ); +DROP FUNCTION hasnt_type( NAME, NAME, TEXT ); +DROP FUNCTION has_type( NAME ); +DROP FUNCTION has_type( NAME, TEXT ); +DROP FUNCTION has_type( NAME, NAME ); +DROP FUNCTION has_type( NAME, NAME, TEXT ); +DROP FUNCTION _has_type( NAME ); +DROP FUNCTION _has_type( NAME, NAME ); +DROP FUNCTION hasnt_schema( NAME ); +DROP FUNCTION hasnt_schema( NAME, TEXT ); +DROP FUNCTION has_schema( NAME ); +DROP FUNCTION has_schema( NAME, TEXT ); DROP FUNCTION trigger_is ( NAME, NAME, NAME ); DROP FUNCTION trigger_is ( NAME, NAME, NAME, text ); DROP FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME ); From b45296a6c10db61622ad2138ecd0aeb6a11aae34 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 30 Jan 2009 22:51:52 +0000 Subject: [PATCH 0258/1195] Various installation improvements, related to better handling of pg_prove. --- Changes | 7 +++++++ Makefile | 37 ++++++++++++++++++++++++++++++++++++- README.pgtap | 7 ------- bin/pg_prove | 2 +- 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/Changes b/Changes index 062c858be99e..65c6ca236d77 100644 --- a/Changes +++ b/Changes @@ -14,6 +14,13 @@ Revision history for pgTAP with PostgreSQL 8.4. - Added `has_schema()` and `hasnt_schema()`. - Added `has_type()` and `hasnt_type()`. + - Updated the `Makefile` to fix the shebang line in `pg_prove` if Perl + is found, and to emite a warning if the `$PERL` variable is not set. + - Updated the `Makefile` so that if `$PERL` is set and Tap::Harness is + not installed, it warns the user to install it in order to use + `pg_prove`. + - Updated the `Makefile` to set the location of the `psql` binary to the + bin directory specified for PostgreSQL when it was built. 0.15 2009-01-20T18:41:24 - Changed `pg_typeof()` from immutable to stable, in compliance with its diff --git a/Makefile b/Makefile index 08434ba5c8a7..23de878b406f 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ TESTS = $(wildcard sql/*.sql) EXTRA_CLEAN = test_setup.sql *.html DATA_built = pgtap.sql uninstall_pgtap.sql DOCS = README.pgtap -SCRIPTS = bin/pg_prove +SCRIPTS = bbin/pg_prove REGRESS = $(patsubst sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --load-language=plpgsql @@ -29,6 +29,7 @@ ifneq ($(PGVER_MINOR), 4) MODULES = pgtap endif +# Load up the PostgreSQL makefiles. ifdef PGXS include $(PGXS) else @@ -36,6 +37,16 @@ include $(top_builddir)/src/Makefile.global include $(top_srcdir)/contrib/contrib-global.mk endif +# We need Perl. +ifndef PERL +PERL := $(shell which foo) +endif + +# Is Tap::Harness installed? +ifdef PERL +HAVE_HARNESS := $(shell $(PERL) -le 'eval { require Tap::Harness }; print 1 unless $$@' ) +endif + # We support 8.0 and later. ifneq ($(PGVER_MAJOR), 8) $(error pgTAP requires PostgreSQL 8.0 or later. This is $(VERSION)) @@ -157,9 +168,33 @@ endif endif endif +# Build pg_prove and holler if there's no Perl or Tap::Harness. +bbin/pg_prove: + mkdir bbin +# sed -e "s,\\('\\|F<\\)psql\\(['>]\\),\\1$(bindir)/psql\\2,g" bin/pg_prove > bbin/pg_prove + sed -e "s,\\'psql\\',\\'$(bindir)/psql\\'," -e 's,F,F<$(bindir)/psql>,' bin/pg_prove > bbin/pg_prove + chmod +x bbin/pg_prove +ifdef PERL + $(PERL) '-MExtUtils::MY' -e 'MY->fixin(shift)' bbin/pg_prove +ifndef HAVE_HARNESS + $(warning To use pg_prove, Tap::Harness Perl module must be installed from CPAN.) +endif +else + $(warning Could not find perl (required by pg_prove). Install it or set the PERL variable.) +endif + # Make sure that we build the regression tests. installcheck: test_setup.sql +# Make sure we build pg_prove. +all: bbin/pg_prove + +# Make sure we remove bbin. +clean: extraclean + +extraclean: + rm -rf bbin + # In addition to installcheck, one can also run the tests through pg_prove. test: test_setup.sql ./bin/pg_prove --pset tuples_only=1 $(TESTS) diff --git a/README.pgtap b/README.pgtap index 6d3d7aa1a268..99d3dda605f3 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2004,13 +2004,6 @@ Don't even use them there. To Do ----- -* Update the Makefile to process `pg_prove` and set the proper path to Perl on - the shebang line. Will likely require a `$(PERL)` environment variable. -* Update the Makefile to check for TAP::Harness and warn if it's not - installed. -* Update `pg_prove` to look for `psql` in the directory returned by `pg_config - --bindir` if it's not in the path. The installer will likely need to modify - it. * Add options to `pg_prove` to allow it to run tests using the `runtests()` function rather than test files. diff --git a/bin/pg_prove b/bin/pg_prove index ca9c2cfa5880..70713740e572 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -156,7 +156,7 @@ Now run the tests. Here's what it looks like when the pgTAP tests are run with pg_prove -b /usr/local/bin/psql Path to the C program, which will be used to actually run the tests. -Defaults to "psql", which should work fine it if happens to be in your path. +Defaults to F. =item C<-d> From 3562cb678290cc9db11ff1a69af37d52e993e165 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 31 Jan 2009 07:26:55 +0000 Subject: [PATCH 0259/1195] * Make sure that `has_type()` works for domains. * Added some more functions to the list of To Dos. --- README.pgtap | 26 ++++++--- expected/hastap.out | 137 ++++++++++++++++++++++---------------------- sql/hastap.sql | 16 +++++- 3 files changed, 103 insertions(+), 76 deletions(-) diff --git a/README.pgtap b/README.pgtap index 99d3dda605f3..1d72814555ea 100644 --- a/README.pgtap +++ b/README.pgtap @@ -692,13 +692,13 @@ view does *not* exist. 'I got myschema.sometype' ); -This function tests whether or not a type exists in the database. The first -argument is a schema name, the second is a type name, and the third is the -test description. If you omit the schema, the type must be visible in the -search path. If you omit the test description, it will be set to "Type `:type` -should exist". If you're passing a schema and type rather than type and -description, be sure to cast the arguments to `name` values so that your type -name doesn't get treated as a description. +This function tests whether or not a type or domain exists in the database. +The first argument is a schema name, the second is a type name, and the third +is the test description. If you omit the schema, the type must be visible in +the search path. If you omit the test description, it will be set to "Type +`:type` should exist". If you're passing a schema and type rather than type +and description, be sure to cast the arguments to `name` values so that your +type name doesn't get treated as a description. If you've created a composite type and want to test that the composed types are a part of it, use the column testing functions to verify them, like so: @@ -720,7 +720,7 @@ are a part of it, use the column testing functions to verify them, like so: ); This function is the inverse of `has_type()`. The test passes if the specified -type does *not* exist. +type or domain does *not* exist. ### `has_column( schema, table, column, description )` ### ### `has_column( table, column, description )` ### @@ -2006,6 +2006,16 @@ To Do * Add options to `pg_prove` to allow it to run tests using the `runtests()` function rather than test files. +* Useful schema testing functions to consider adding: + * `has_tablespace()` + * `has_cast()` + * `has_group()` + * `has_user()` + * `has_role()` + * `has_operator()` + * `has_operator_class()` + * `has_sequence()` + * `has_rule()` Suported Versions ----------------- diff --git a/expected/hastap.out b/expected/hastap.out index c862ef9ad8b3..10a5749268b5 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..180 +1..183 ok 1 - has_schema(non-existent schema) should fail ok 2 - has_schema(non-existent schema) should have the proper description ok 3 - has_schema(non-existent schema) should have the proper diagnostics @@ -114,69 +114,72 @@ ok 111 - has_type(scheam, type) should have the proper diagnostics ok 112 - has_type(schema, type, desc) should fail ok 113 - has_type(schema, type, desc) should have the proper description ok 114 - has_type(schema, type, desc) should have the proper diagnostics -ok 115 - hasnt_type(type) should pass -ok 116 - hasnt_type(type) should have the proper description -ok 117 - hasnt_type(type) should have the proper diagnostics -ok 118 - hasnt_type(type, desc) should pass -ok 119 - hasnt_type(type, desc) should have the proper description -ok 120 - hasnt_type(type, desc) should have the proper diagnostics -ok 121 - hasnt_type(scheam, type) should pass -ok 122 - hasnt_type(scheam, type) should have the proper description -ok 123 - hasnt_type(scheam, type) should have the proper diagnostics -ok 124 - hasnt_type(schema, type, desc) should pass -ok 125 - hasnt_type(schema, type, desc) should have the proper description -ok 126 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 127 - hasnt_type(type) should fail -ok 128 - hasnt_type(type) should have the proper description -ok 129 - hasnt_type(type) should have the proper diagnostics -ok 130 - hasnt_type(type, desc) should fail -ok 131 - hasnt_type(type, desc) should have the proper description -ok 132 - hasnt_type(type, desc) should have the proper diagnostics -ok 133 - hasnt_type(scheam, type) should fail -ok 134 - hasnt_type(scheam, type) should have the proper description -ok 135 - hasnt_type(scheam, type) should have the proper diagnostics -ok 136 - hasnt_type(schema, type, desc) should fail -ok 137 - hasnt_type(schema, type, desc) should have the proper description -ok 138 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 139 - has_column(non-existent tab, col) should fail -ok 140 - has_column(non-existent tab, col) should have the proper description -ok 141 - has_column(non-existent tab, col) should have the proper diagnostics -ok 142 - has_column(non-existent tab, col, desc) should fail -ok 143 - has_column(non-existent tab, col, desc) should have the proper description -ok 144 - has_column(non-existent tab, col, desc) should have the proper diagnostics -ok 145 - has_column(non-existent sch, tab, col, desc) should fail -ok 146 - has_column(non-existent sch, tab, col, desc) should have the proper description -ok 147 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 148 - has_column(table, column) should pass -ok 149 - has_column(table, column) should have the proper description -ok 150 - has_column(table, column) should have the proper diagnostics -ok 151 - has_column(sch, tab, col, desc) should pass -ok 152 - has_column(sch, tab, col, desc) should have the proper description -ok 153 - has_column(sch, tab, col, desc) should have the proper diagnostics -ok 154 - has_column(view, column) should pass -ok 155 - has_column(view, column) should have the proper description -ok 156 - has_column(view, column) should have the proper diagnostics -ok 157 - has_column(type, column) should pass -ok 158 - has_column(type, column) should have the proper description -ok 159 - has_column(type, column) should have the proper diagnostics -ok 160 - hasnt_column(non-existent tab, col) should pass -ok 161 - hasnt_column(non-existent tab, col) should have the proper description -ok 162 - hasnt_column(non-existent tab, col) should have the proper diagnostics -ok 163 - hasnt_column(non-existent tab, col, desc) should pass -ok 164 - hasnt_column(non-existent tab, col, desc) should have the proper description -ok 165 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics -ok 166 - hasnt_column(non-existent sch, tab, col, desc) should pass -ok 167 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description -ok 168 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 169 - hasnt_column(table, column) should fail -ok 170 - hasnt_column(table, column) should have the proper description -ok 171 - hasnt_column(table, column) should have the proper diagnostics -ok 172 - hasnt_column(sch, tab, col, desc) should fail -ok 173 - hasnt_column(sch, tab, col, desc) should have the proper description -ok 174 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics -ok 175 - hasnt_column(view, column) should pass -ok 176 - hasnt_column(view, column) should have the proper description -ok 177 - hasnt_column(view, column) should have the proper diagnostics -ok 178 - hasnt_column(type, column) should pass -ok 179 - hasnt_column(type, column) should have the proper description -ok 180 - hasnt_column(type, column) should have the proper diagnostics +ok 115 - has_type(domain) should pass +ok 116 - has_type(domain) should have the proper description +ok 117 - has_type(domain) should have the proper diagnostics +ok 118 - hasnt_type(type) should pass +ok 119 - hasnt_type(type) should have the proper description +ok 120 - hasnt_type(type) should have the proper diagnostics +ok 121 - hasnt_type(type, desc) should pass +ok 122 - hasnt_type(type, desc) should have the proper description +ok 123 - hasnt_type(type, desc) should have the proper diagnostics +ok 124 - hasnt_type(scheam, type) should pass +ok 125 - hasnt_type(scheam, type) should have the proper description +ok 126 - hasnt_type(scheam, type) should have the proper diagnostics +ok 127 - hasnt_type(schema, type, desc) should pass +ok 128 - hasnt_type(schema, type, desc) should have the proper description +ok 129 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 130 - hasnt_type(type) should fail +ok 131 - hasnt_type(type) should have the proper description +ok 132 - hasnt_type(type) should have the proper diagnostics +ok 133 - hasnt_type(type, desc) should fail +ok 134 - hasnt_type(type, desc) should have the proper description +ok 135 - hasnt_type(type, desc) should have the proper diagnostics +ok 136 - hasnt_type(scheam, type) should fail +ok 137 - hasnt_type(scheam, type) should have the proper description +ok 138 - hasnt_type(scheam, type) should have the proper diagnostics +ok 139 - hasnt_type(schema, type, desc) should fail +ok 140 - hasnt_type(schema, type, desc) should have the proper description +ok 141 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 142 - has_column(non-existent tab, col) should fail +ok 143 - has_column(non-existent tab, col) should have the proper description +ok 144 - has_column(non-existent tab, col) should have the proper diagnostics +ok 145 - has_column(non-existent tab, col, desc) should fail +ok 146 - has_column(non-existent tab, col, desc) should have the proper description +ok 147 - has_column(non-existent tab, col, desc) should have the proper diagnostics +ok 148 - has_column(non-existent sch, tab, col, desc) should fail +ok 149 - has_column(non-existent sch, tab, col, desc) should have the proper description +ok 150 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 151 - has_column(table, column) should pass +ok 152 - has_column(table, column) should have the proper description +ok 153 - has_column(table, column) should have the proper diagnostics +ok 154 - has_column(sch, tab, col, desc) should pass +ok 155 - has_column(sch, tab, col, desc) should have the proper description +ok 156 - has_column(sch, tab, col, desc) should have the proper diagnostics +ok 157 - has_column(view, column) should pass +ok 158 - has_column(view, column) should have the proper description +ok 159 - has_column(view, column) should have the proper diagnostics +ok 160 - has_column(type, column) should pass +ok 161 - has_column(type, column) should have the proper description +ok 162 - has_column(type, column) should have the proper diagnostics +ok 163 - hasnt_column(non-existent tab, col) should pass +ok 164 - hasnt_column(non-existent tab, col) should have the proper description +ok 165 - hasnt_column(non-existent tab, col) should have the proper diagnostics +ok 166 - hasnt_column(non-existent tab, col, desc) should pass +ok 167 - hasnt_column(non-existent tab, col, desc) should have the proper description +ok 168 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics +ok 169 - hasnt_column(non-existent sch, tab, col, desc) should pass +ok 170 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description +ok 171 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 172 - hasnt_column(table, column) should fail +ok 173 - hasnt_column(table, column) should have the proper description +ok 174 - hasnt_column(table, column) should have the proper diagnostics +ok 175 - hasnt_column(sch, tab, col, desc) should fail +ok 176 - hasnt_column(sch, tab, col, desc) should have the proper description +ok 177 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics +ok 178 - hasnt_column(view, column) should pass +ok 179 - hasnt_column(view, column) should have the proper description +ok 180 - hasnt_column(view, column) should have the proper diagnostics +ok 181 - hasnt_column(type, column) should pass +ok 182 - hasnt_column(type, column) should have the proper description +ok 183 - hasnt_column(type, column) should have the proper diagnostics diff --git a/sql/hastap.sql b/sql/hastap.sql index e9e542abfa1e..cd7fcf72b786 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(180); +SELECT plan(183); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -18,6 +18,11 @@ CREATE TYPE sometype AS ( id INT, name TEXT ); + +CREATE DOMAIN us_postal_code AS TEXT CHECK( + VALUE ~ '^[[:digit:]]{5}$' OR VALUE ~ '^[[:digit:]]{5}-[[:digit:]]{4}$' +); + CREATE SCHEMA someschema; RESET client_min_messages; @@ -334,6 +339,15 @@ SELECT * FROM check_test( '' ); +-- Make sure it works for domains. +SELECT * FROM check_test( + has_type( 'us_postal_code' ), + true, + 'has_type(domain)', + 'Type us_postal_code should exist', + '' +); + /****************************************************************************/ -- Test hasnt_type(). SELECT * FROM check_test( From 7ed2756684f8fd54d4719a6680b2046c74c362b3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 1 Feb 2009 18:05:36 +0000 Subject: [PATCH 0260/1195] Maybe I'll add this function. --- README.pgtap | 1 + 1 file changed, 1 insertion(+) diff --git a/README.pgtap b/README.pgtap index 1d72814555ea..e3db7b1d8262 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2007,6 +2007,7 @@ To Do * Add options to `pg_prove` to allow it to run tests using the `runtests()` function rather than test files. * Useful schema testing functions to consider adding: + * `has_domain()` * `has_tablespace()` * `has_cast()` * `has_group()` From 1284ccce6c99608e92fcab2103fc7976dfe51c98 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Feb 2009 17:54:32 +0000 Subject: [PATCH 0261/1195] Added `has_domain()`, `hasnt_domain()`, `has_enum()`, and `hasnt_enum()`. --- Changes | 17 +-- README.pgtap | 84 +++++++++++-- expected/hastap.out | 233 +++++++++++++++++++++++++----------- pgtap.sql.in | 114 ++++++++++++++++-- sql/hastap.sql | 259 ++++++++++++++++++++++++++++++++++++++++- uninstall_pgtap.sql.in | 16 +++ 6 files changed, 629 insertions(+), 94 deletions(-) diff --git a/Changes b/Changes index 65c6ca236d77..5bdb343db5d1 100644 --- a/Changes +++ b/Changes @@ -13,14 +13,15 @@ Revision history for pgTAP C code is for this function. This is because `pg_typeof()` is included with PostgreSQL 8.4. - Added `has_schema()` and `hasnt_schema()`. - - Added `has_type()` and `hasnt_type()`. - - Updated the `Makefile` to fix the shebang line in `pg_prove` if Perl - is found, and to emite a warning if the `$PERL` variable is not set. - - Updated the `Makefile` so that if `$PERL` is set and Tap::Harness is - not installed, it warns the user to install it in order to use - `pg_prove`. - - Updated the `Makefile` to set the location of the `psql` binary to the - bin directory specified for PostgreSQL when it was built. + - Added `has_type()`, `hasnt_type()`, `has_domain()`, `hasnt_domain()`, + `has_enum()`, and `hasnt_enum()`. + - Updated the `Makefile` to fix the shebang line in `pg_prove` if + Perl is found, and to emite a warning if the `$PERL` variable is not + set. - Updated the `Makefile` so that if `$PERL` is set and + Tap::Harness is not installed, it warns the user to install it in + order to use `pg_prove`. - Updated the `Makefile` to set the location + of the `psql` binary to the bin directory specified for PostgreSQL + when it was built. 0.15 2009-01-20T18:41:24 - Changed `pg_typeof()` from immutable to stable, in compliance with its diff --git a/README.pgtap b/README.pgtap index e3db7b1d8262..84e5ec4471bf 100644 --- a/README.pgtap +++ b/README.pgtap @@ -692,13 +692,14 @@ view does *not* exist. 'I got myschema.sometype' ); -This function tests whether or not a type or domain exists in the database. -The first argument is a schema name, the second is a type name, and the third -is the test description. If you omit the schema, the type must be visible in -the search path. If you omit the test description, it will be set to "Type -`:type` should exist". If you're passing a schema and type rather than type -and description, be sure to cast the arguments to `name` values so that your -type name doesn't get treated as a description. +This function tests whether or not a type exists in the database. Detects all +types of types, including base types, composite types, domains, enums, and +pseudo-types. The first argument is a schema name, the second is a type name, +and the third is the test description. If you omit the schema, the type must +be visible in the search path. If you omit the test description, it will be +set to "Type `:type` should exist". If you're passing a schema and type rather +than type and description, be sure to cast the arguments to `name` values so +that your type name doesn't get treated as a description. If you've created a composite type and want to test that the composed types are a part of it, use the column testing functions to verify them, like so: @@ -720,7 +721,73 @@ are a part of it, use the column testing functions to verify them, like so: ); This function is the inverse of `has_type()`. The test passes if the specified -type or domain does *not* exist. +type does *not* exist. + +### `has_domain( schema, domain, description )` ### +### `has_domain( schema, domain )` ### +### `has_domain( domain, description )` ### +### `has_domain( domain )` ### + + SELECT has_domain( + 'myschema', + 'somedomain', + 'I got myschema.somedomain' + ); + +This function tests whether or not a domain exists in the database. The first +argument is a schema name, the second is the name of a domain, and the third +is the test description. If you omit the schema, the domain must be visible in +the search path. If you omit the test description, it will be set to "Domain +`:domain` should exist". If you're passing a schema and domain rather than +domain and description, be sure to cast the arguments to `name` values so that +your domain name doesn't get treated as a description. + +### `hasnt_domain( schema, domain, description )` ### +### `hasnt_domain( schema, domain )` ### +### `hasnt_domain( domain, description )` ### +### `hasnt_domain( domain )` ### + + SELECT hasnt_domain( + 'myschema', + 'somedomain', + 'There should be no domain myschema.somedomain' + ); + +This function is the inverse of `has_domain()`. The test passes if the +specified domain does *not* exist. + +### `has_enum( schema, enum, description )` ### +### `has_enum( schema, enum )` ### +### `has_enum( enum, description )` ### +### `has_enum( enum )` ### + + SELECT has_enum( + 'myschema', + 'someenum', + 'I got myschema.someenum' + ); + +This function tests whether or not a enum exists in the database. The first +argument is a schema name, the second is the an enum name, and the third is +the test description. If you omit the schema, the enum must be visible in the +search path. If you omit the test description, it will be set to "Enum `:enum` +should exist". If you're passing a schema and enum rather than enum and +description, be sure to cast the arguments to `name` values so that your enum +name doesn't get treated as a description. + +### `hasnt_enum( schema, enum, description )` ### +### `hasnt_enum( schema, enum )` ### +### `hasnt_enum( enum, description )` ### +### `hasnt_enum( enum )` ### + + SELECT hasnt_enum( + 'myschema', + 'someenum', + 'There should be no enum myschema.someenum' + ); + +This function is the inverse of `has_enum()`. The test passes if the +specified enum does *not* exist. ### `has_column( schema, table, column, description )` ### ### `has_column( table, column, description )` ### @@ -2007,7 +2074,6 @@ To Do * Add options to `pg_prove` to allow it to run tests using the `runtests()` function rather than test files. * Useful schema testing functions to consider adding: - * `has_domain()` * `has_tablespace()` * `has_cast()` * `has_group()` diff --git a/expected/hastap.out b/expected/hastap.out index 10a5749268b5..d0e01af7502f 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..183 +1..282 ok 1 - has_schema(non-existent schema) should fail ok 2 - has_schema(non-existent schema) should have the proper description ok 3 - has_schema(non-existent schema) should have the proper diagnostics @@ -117,69 +117,168 @@ ok 114 - has_type(schema, type, desc) should have the proper diagnostics ok 115 - has_type(domain) should pass ok 116 - has_type(domain) should have the proper description ok 117 - has_type(domain) should have the proper diagnostics -ok 118 - hasnt_type(type) should pass -ok 119 - hasnt_type(type) should have the proper description -ok 120 - hasnt_type(type) should have the proper diagnostics -ok 121 - hasnt_type(type, desc) should pass -ok 122 - hasnt_type(type, desc) should have the proper description -ok 123 - hasnt_type(type, desc) should have the proper diagnostics -ok 124 - hasnt_type(scheam, type) should pass -ok 125 - hasnt_type(scheam, type) should have the proper description -ok 126 - hasnt_type(scheam, type) should have the proper diagnostics -ok 127 - hasnt_type(schema, type, desc) should pass -ok 128 - hasnt_type(schema, type, desc) should have the proper description -ok 129 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 130 - hasnt_type(type) should fail -ok 131 - hasnt_type(type) should have the proper description -ok 132 - hasnt_type(type) should have the proper diagnostics -ok 133 - hasnt_type(type, desc) should fail -ok 134 - hasnt_type(type, desc) should have the proper description -ok 135 - hasnt_type(type, desc) should have the proper diagnostics -ok 136 - hasnt_type(scheam, type) should fail -ok 137 - hasnt_type(scheam, type) should have the proper description -ok 138 - hasnt_type(scheam, type) should have the proper diagnostics -ok 139 - hasnt_type(schema, type, desc) should fail -ok 140 - hasnt_type(schema, type, desc) should have the proper description -ok 141 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 142 - has_column(non-existent tab, col) should fail -ok 143 - has_column(non-existent tab, col) should have the proper description -ok 144 - has_column(non-existent tab, col) should have the proper diagnostics -ok 145 - has_column(non-existent tab, col, desc) should fail -ok 146 - has_column(non-existent tab, col, desc) should have the proper description -ok 147 - has_column(non-existent tab, col, desc) should have the proper diagnostics -ok 148 - has_column(non-existent sch, tab, col, desc) should fail -ok 149 - has_column(non-existent sch, tab, col, desc) should have the proper description -ok 150 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 151 - has_column(table, column) should pass -ok 152 - has_column(table, column) should have the proper description -ok 153 - has_column(table, column) should have the proper diagnostics -ok 154 - has_column(sch, tab, col, desc) should pass -ok 155 - has_column(sch, tab, col, desc) should have the proper description -ok 156 - has_column(sch, tab, col, desc) should have the proper diagnostics -ok 157 - has_column(view, column) should pass -ok 158 - has_column(view, column) should have the proper description -ok 159 - has_column(view, column) should have the proper diagnostics -ok 160 - has_column(type, column) should pass -ok 161 - has_column(type, column) should have the proper description -ok 162 - has_column(type, column) should have the proper diagnostics -ok 163 - hasnt_column(non-existent tab, col) should pass -ok 164 - hasnt_column(non-existent tab, col) should have the proper description -ok 165 - hasnt_column(non-existent tab, col) should have the proper diagnostics -ok 166 - hasnt_column(non-existent tab, col, desc) should pass -ok 167 - hasnt_column(non-existent tab, col, desc) should have the proper description -ok 168 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics -ok 169 - hasnt_column(non-existent sch, tab, col, desc) should pass -ok 170 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description -ok 171 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 172 - hasnt_column(table, column) should fail -ok 173 - hasnt_column(table, column) should have the proper description -ok 174 - hasnt_column(table, column) should have the proper diagnostics -ok 175 - hasnt_column(sch, tab, col, desc) should fail -ok 176 - hasnt_column(sch, tab, col, desc) should have the proper description -ok 177 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics -ok 178 - hasnt_column(view, column) should pass -ok 179 - hasnt_column(view, column) should have the proper description -ok 180 - hasnt_column(view, column) should have the proper diagnostics -ok 181 - hasnt_column(type, column) should pass -ok 182 - hasnt_column(type, column) should have the proper description -ok 183 - hasnt_column(type, column) should have the proper diagnostics +ok 118 - has_type(enum) should pass +ok 119 - has_type(enum) should have the proper description +ok 120 - has_type(enum) should have the proper diagnostics +ok 121 - hasnt_type(type) should pass +ok 122 - hasnt_type(type) should have the proper description +ok 123 - hasnt_type(type) should have the proper diagnostics +ok 124 - hasnt_type(type, desc) should pass +ok 125 - hasnt_type(type, desc) should have the proper description +ok 126 - hasnt_type(type, desc) should have the proper diagnostics +ok 127 - hasnt_type(scheam, type) should pass +ok 128 - hasnt_type(scheam, type) should have the proper description +ok 129 - hasnt_type(scheam, type) should have the proper diagnostics +ok 130 - hasnt_type(schema, type, desc) should pass +ok 131 - hasnt_type(schema, type, desc) should have the proper description +ok 132 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 133 - hasnt_type(type) should fail +ok 134 - hasnt_type(type) should have the proper description +ok 135 - hasnt_type(type) should have the proper diagnostics +ok 136 - hasnt_type(type, desc) should fail +ok 137 - hasnt_type(type, desc) should have the proper description +ok 138 - hasnt_type(type, desc) should have the proper diagnostics +ok 139 - hasnt_type(scheam, type) should fail +ok 140 - hasnt_type(scheam, type) should have the proper description +ok 141 - hasnt_type(scheam, type) should have the proper diagnostics +ok 142 - hasnt_type(schema, type, desc) should fail +ok 143 - hasnt_type(schema, type, desc) should have the proper description +ok 144 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 145 - has_domain(domain) should pass +ok 146 - has_domain(domain) should have the proper description +ok 147 - has_domain(domain) should have the proper diagnostics +ok 148 - has_domain(domain, desc) should pass +ok 149 - has_domain(domain, desc) should have the proper description +ok 150 - has_domain(domain, desc) should have the proper diagnostics +ok 151 - has_domain(scheam, domain) should pass +ok 152 - has_domain(scheam, domain) should have the proper description +ok 153 - has_domain(scheam, domain) should have the proper diagnostics +ok 154 - has_domain(schema, domain, desc) should pass +ok 155 - has_domain(schema, domain, desc) should have the proper description +ok 156 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 157 - has_domain(domain) should fail +ok 158 - has_domain(domain) should have the proper description +ok 159 - has_domain(domain) should have the proper diagnostics +ok 160 - has_domain(domain, desc) should fail +ok 161 - has_domain(domain, desc) should have the proper description +ok 162 - has_domain(domain, desc) should have the proper diagnostics +ok 163 - has_domain(scheam, domain) should fail +ok 164 - has_domain(scheam, domain) should have the proper description +ok 165 - has_domain(scheam, domain) should have the proper diagnostics +ok 166 - has_domain(schema, domain, desc) should fail +ok 167 - has_domain(schema, domain, desc) should have the proper description +ok 168 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 169 - hasnt_domain(domain) should pass +ok 170 - hasnt_domain(domain) should have the proper description +ok 171 - hasnt_domain(domain) should have the proper diagnostics +ok 172 - hasnt_domain(domain, desc) should pass +ok 173 - hasnt_domain(domain, desc) should have the proper description +ok 174 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 175 - hasnt_domain(scheam, domain) should pass +ok 176 - hasnt_domain(scheam, domain) should have the proper description +ok 177 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 178 - hasnt_domain(schema, domain, desc) should pass +ok 179 - hasnt_domain(schema, domain, desc) should have the proper description +ok 180 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 181 - hasnt_domain(domain) should fail +ok 182 - hasnt_domain(domain) should have the proper description +ok 183 - hasnt_domain(domain) should have the proper diagnostics +ok 184 - hasnt_domain(domain, desc) should fail +ok 185 - hasnt_domain(domain, desc) should have the proper description +ok 186 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 187 - hasnt_domain(scheam, domain) should fail +ok 188 - hasnt_domain(scheam, domain) should have the proper description +ok 189 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 190 - hasnt_domain(schema, domain, desc) should fail +ok 191 - hasnt_domain(schema, domain, desc) should have the proper description +ok 192 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 193 - has_enum(enum) should pass +ok 194 - has_enum(enum) should have the proper description +ok 195 - has_enum(enum) should have the proper diagnostics +ok 196 - has_enum(enum, desc) should pass +ok 197 - has_enum(enum, desc) should have the proper description +ok 198 - has_enum(enum, desc) should have the proper diagnostics +ok 199 - has_enum(scheam, enum) should pass +ok 200 - has_enum(scheam, enum) should have the proper description +ok 201 - has_enum(scheam, enum) should have the proper diagnostics +ok 202 - has_enum(schema, enum, desc) should pass +ok 203 - has_enum(schema, enum, desc) should have the proper description +ok 204 - has_enum(schema, enum, desc) should have the proper diagnostics +ok 205 - has_enum(enum) should fail +ok 206 - has_enum(enum) should have the proper description +ok 207 - has_enum(enum) should have the proper diagnostics +ok 208 - has_enum(enum, desc) should fail +ok 209 - has_enum(enum, desc) should have the proper description +ok 210 - has_enum(enum, desc) should have the proper diagnostics +ok 211 - has_enum(scheam, enum) should fail +ok 212 - has_enum(scheam, enum) should have the proper description +ok 213 - has_enum(scheam, enum) should have the proper diagnostics +ok 214 - has_enum(schema, enum, desc) should fail +ok 215 - has_enum(schema, enum, desc) should have the proper description +ok 216 - has_enum(schema, enum, desc) should have the proper diagnostics +ok 217 - hasnt_enum(enum) should pass +ok 218 - hasnt_enum(enum) should have the proper description +ok 219 - hasnt_enum(enum) should have the proper diagnostics +ok 220 - hasnt_enum(enum, desc) should pass +ok 221 - hasnt_enum(enum, desc) should have the proper description +ok 222 - hasnt_enum(enum, desc) should have the proper diagnostics +ok 223 - hasnt_enum(scheam, enum) should pass +ok 224 - hasnt_enum(scheam, enum) should have the proper description +ok 225 - hasnt_enum(scheam, enum) should have the proper diagnostics +ok 226 - hasnt_enum(schema, enum, desc) should pass +ok 227 - hasnt_enum(schema, enum, desc) should have the proper description +ok 228 - hasnt_enum(schema, enum, desc) should have the proper diagnostics +ok 229 - hasnt_enum(enum) should fail +ok 230 - hasnt_enum(enum) should have the proper description +ok 231 - hasnt_enum(enum) should have the proper diagnostics +ok 232 - hasnt_enum(enum, desc) should fail +ok 233 - hasnt_enum(enum, desc) should have the proper description +ok 234 - hasnt_enum(enum, desc) should have the proper diagnostics +ok 235 - hasnt_enum(scheam, enum) should fail +ok 236 - hasnt_enum(scheam, enum) should have the proper description +ok 237 - hasnt_enum(scheam, enum) should have the proper diagnostics +ok 238 - hasnt_enum(schema, enum, desc) should fail +ok 239 - hasnt_enum(schema, enum, desc) should have the proper description +ok 240 - hasnt_enum(schema, enum, desc) should have the proper diagnostics +ok 241 - has_column(non-existent tab, col) should fail +ok 242 - has_column(non-existent tab, col) should have the proper description +ok 243 - has_column(non-existent tab, col) should have the proper diagnostics +ok 244 - has_column(non-existent tab, col, desc) should fail +ok 245 - has_column(non-existent tab, col, desc) should have the proper description +ok 246 - has_column(non-existent tab, col, desc) should have the proper diagnostics +ok 247 - has_column(non-existent sch, tab, col, desc) should fail +ok 248 - has_column(non-existent sch, tab, col, desc) should have the proper description +ok 249 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 250 - has_column(table, column) should pass +ok 251 - has_column(table, column) should have the proper description +ok 252 - has_column(table, column) should have the proper diagnostics +ok 253 - has_column(sch, tab, col, desc) should pass +ok 254 - has_column(sch, tab, col, desc) should have the proper description +ok 255 - has_column(sch, tab, col, desc) should have the proper diagnostics +ok 256 - has_column(view, column) should pass +ok 257 - has_column(view, column) should have the proper description +ok 258 - has_column(view, column) should have the proper diagnostics +ok 259 - has_column(type, column) should pass +ok 260 - has_column(type, column) should have the proper description +ok 261 - has_column(type, column) should have the proper diagnostics +ok 262 - hasnt_column(non-existent tab, col) should pass +ok 263 - hasnt_column(non-existent tab, col) should have the proper description +ok 264 - hasnt_column(non-existent tab, col) should have the proper diagnostics +ok 265 - hasnt_column(non-existent tab, col, desc) should pass +ok 266 - hasnt_column(non-existent tab, col, desc) should have the proper description +ok 267 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics +ok 268 - hasnt_column(non-existent sch, tab, col, desc) should pass +ok 269 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description +ok 270 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 271 - hasnt_column(table, column) should fail +ok 272 - hasnt_column(table, column) should have the proper description +ok 273 - hasnt_column(table, column) should have the proper diagnostics +ok 274 - hasnt_column(sch, tab, col, desc) should fail +ok 275 - hasnt_column(sch, tab, col, desc) should have the proper description +ok 276 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics +ok 277 - hasnt_column(view, column) should pass +ok 278 - hasnt_column(view, column) should have the proper description +ok 279 - hasnt_column(view, column) should have the proper diagnostics +ok 280 - hasnt_column(type, column) should pass +ok 281 - hasnt_column(type, column) should have the proper description +ok 282 - hasnt_column(type, column) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 7ce7c0e55105..5d5bc27b03c6 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2582,7 +2582,7 @@ RETURNS TEXT AS $$ SELECT hasnt_schema( $1, 'Schema ' || $1 || ' should not exist' ); $$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION _has_type( NAME, NAME ) +CREATE OR REPLACE FUNCTION _has_type( NAME, NAME, CHAR[] ) RETURNS BOOLEAN AS $$ SELECT EXISTS( SELECT true @@ -2591,23 +2591,25 @@ RETURNS BOOLEAN AS $$ 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 ) +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 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 ), $3 ); + SELECT ok( _has_type( $1, $2, NULL ), $3 ); $$ LANGUAGE sql; -- has_type( schema, type ) @@ -2619,19 +2621,19 @@ $$ LANGUAGE sql; -- has_type( type, description ) CREATE OR REPLACE FUNCTION has_type( NAME, TEXT ) RETURNS TEXT AS $$ - SELECT ok( _has_type( $1 ), $2 ); + 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 ), ('Type ' || $1 || ' should exist')::text ); + SELECT ok( _has_type( $1, NULL ), ('Type ' || $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 ), $3 ); + SELECT ok( NOT _has_type( $1, $2, NULL ), $3 ); $$ LANGUAGE sql; -- hasnt_type( schema, type ) @@ -2643,13 +2645,109 @@ $$ LANGUAGE sql; -- hasnt_type( type, description ) CREATE OR REPLACE FUNCTION hasnt_type( NAME, TEXT ) RETURNS TEXT AS $$ - SELECT ok( NOT _has_type( $1 ), $2 ); + 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 ), ('Type ' || $1 || ' should not exist')::text ); + SELECT ok( NOT _has_type( $1, NULL ), ('Type ' || $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 ' || $1 || '.' || $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 ' || $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 ' || $1 || '.' || $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 ' || $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 ' || $1 || '.' || $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 ' || $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 ' || $1 || '.' || $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 ' || $1 || ' should not exist')::text ); $$ LANGUAGE sql; -- check_test( test_output, pass, name, description, diag ) diff --git a/sql/hastap.sql b/sql/hastap.sql index cd7fcf72b786..5176dd2abc75 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(183); +SELECT plan(282); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -23,6 +23,8 @@ CREATE DOMAIN us_postal_code AS TEXT CHECK( VALUE ~ '^[[:digit:]]{5}$' OR VALUE ~ '^[[:digit:]]{5}-[[:digit:]]{4}$' ); +CREATE TYPE bug_status AS ENUM ('new', 'open', 'closed'); + CREATE SCHEMA someschema; RESET client_min_messages; @@ -339,7 +341,7 @@ SELECT * FROM check_test( '' ); --- Make sure it works for domains. +-- Make sure it works for domains and enums. SELECT * FROM check_test( has_type( 'us_postal_code' ), true, @@ -347,6 +349,13 @@ SELECT * FROM check_test( 'Type us_postal_code should exist', '' ); +SELECT * FROM check_test( + has_type( 'bug_status' ), + true, + 'has_type(enum)', + 'Type bug_status should exist', + '' +); /****************************************************************************/ -- Test hasnt_type(). @@ -409,6 +418,252 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test has_domain(). +SELECT * FROM check_test( + has_domain( 'us_postal_code' ), + true, + 'has_domain(domain)', + 'Domain us_postal_code should exist', + '' +); + +SELECT * FROM check_test( + has_domain( 'us_postal_code', 'mydesc' ), + true, + 'has_domain(domain, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + has_domain( 'public'::name, 'us_postal_code'::name ), + true, + 'has_domain(scheam, domain)', + 'Domain public.us_postal_code should exist', + '' +); +SELECT * FROM check_test( + has_domain( 'public', 'us_postal_code', 'mydesc' ), + true, + 'has_domain(schema, domain, desc)', + 'mydesc', + '' +); + +-- Try failures. +SELECT * FROM check_test( + has_domain( '__foobarbaz__' ), + false, + 'has_domain(domain)', + 'Domain __foobarbaz__ should exist', + '' +); +SELECT * FROM check_test( + has_domain( '__foobarbaz__', 'mydesc' ), + false, + 'has_domain(domain, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + has_domain( 'public'::name, '__foobarbaz__'::name ), + false, + 'has_domain(scheam, domain)', + 'Domain public.__foobarbaz__ should exist', + '' +); +SELECT * FROM check_test( + has_domain( 'public', '__foobarbaz__', 'mydesc' ), + false, + 'has_domain(schema, domain, desc)', + 'mydesc', + '' +); + +/****************************************************************************/ +-- Test hasnt_domain(). +SELECT * FROM check_test( + hasnt_domain( '__foobarbaz__' ), + true, + 'hasnt_domain(domain)', + 'Domain __foobarbaz__ should not exist', + '' +); +SELECT * FROM check_test( + hasnt_domain( '__foobarbaz__', 'mydesc' ), + true, + 'hasnt_domain(domain, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + hasnt_domain( 'public'::name, '__foobarbaz__'::name ), + true, + 'hasnt_domain(scheam, domain)', + 'Domain public.__foobarbaz__ should not exist', + '' +); +SELECT * FROM check_test( + hasnt_domain( 'public', '__foobarbaz__', 'mydesc' ), + true, + 'hasnt_domain(schema, domain, desc)', + 'mydesc', + '' +); + +-- Try failures. +SELECT * FROM check_test( + hasnt_domain( 'us_postal_code' ), + false, + 'hasnt_domain(domain)', + 'Domain us_postal_code should not exist', + '' +); +SELECT * FROM check_test( + hasnt_domain( 'us_postal_code', 'mydesc' ), + false, + 'hasnt_domain(domain, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + hasnt_domain( 'public'::name, 'us_postal_code'::name ), + false, + 'hasnt_domain(scheam, domain)', + 'Domain public.us_postal_code should not exist', + '' +); +SELECT * FROM check_test( + hasnt_domain( 'public', 'us_postal_code', 'mydesc' ), + false, + 'hasnt_domain(schema, domain, desc)', + 'mydesc', + '' +); + +/****************************************************************************/ +-- Test has_enum(). +SELECT * FROM check_test( + has_enum( 'bug_status' ), + true, + 'has_enum(enum)', + 'Enum bug_status should exist', + '' +); + +SELECT * FROM check_test( + has_enum( 'bug_status', 'mydesc' ), + true, + 'has_enum(enum, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + has_enum( 'public'::name, 'bug_status'::name ), + true, + 'has_enum(scheam, enum)', + 'Enum public.bug_status should exist', + '' +); +SELECT * FROM check_test( + has_enum( 'public', 'bug_status', 'mydesc' ), + true, + 'has_enum(schema, enum, desc)', + 'mydesc', + '' +); + +-- Try failures. +SELECT * FROM check_test( + has_enum( '__foobarbaz__' ), + false, + 'has_enum(enum)', + 'Enum __foobarbaz__ should exist', + '' +); +SELECT * FROM check_test( + has_enum( '__foobarbaz__', 'mydesc' ), + false, + 'has_enum(enum, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + has_enum( 'public'::name, '__foobarbaz__'::name ), + false, + 'has_enum(scheam, enum)', + 'Enum public.__foobarbaz__ should exist', + '' +); +SELECT * FROM check_test( + has_enum( 'public', '__foobarbaz__', 'mydesc' ), + false, + 'has_enum(schema, enum, desc)', + 'mydesc', + '' +); + +/****************************************************************************/ +-- Test hasnt_enum(). +SELECT * FROM check_test( + hasnt_enum( '__foobarbaz__' ), + true, + 'hasnt_enum(enum)', + 'Enum __foobarbaz__ should not exist', + '' +); +SELECT * FROM check_test( + hasnt_enum( '__foobarbaz__', 'mydesc' ), + true, + 'hasnt_enum(enum, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + hasnt_enum( 'public'::name, '__foobarbaz__'::name ), + true, + 'hasnt_enum(scheam, enum)', + 'Enum public.__foobarbaz__ should not exist', + '' +); +SELECT * FROM check_test( + hasnt_enum( 'public', '__foobarbaz__', 'mydesc' ), + true, + 'hasnt_enum(schema, enum, desc)', + 'mydesc', + '' +); + +-- Try failures. +SELECT * FROM check_test( + hasnt_enum( 'bug_status' ), + false, + 'hasnt_enum(enum)', + 'Enum bug_status should not exist', + '' +); +SELECT * FROM check_test( + hasnt_enum( 'bug_status', 'mydesc' ), + false, + 'hasnt_enum(enum, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + hasnt_enum( 'public'::name, 'bug_status'::name ), + false, + 'hasnt_enum(scheam, enum)', + 'Enum public.bug_status should not exist', + '' +); +SELECT * FROM check_test( + hasnt_enum( 'public', 'bug_status', 'mydesc' ), + false, + 'hasnt_enum(schema, enum, desc)', + 'mydesc', + '' +); + /****************************************************************************/ -- Test has_column(). diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 87b348c3e50c..d1e188021e23 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -19,6 +19,22 @@ DROP FUNCTION check_test( TEXT, BOOLEAN ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); +DROP FUNCTION hasnt_domain( NAME ); +DROP FUNCTION hasnt_domain( NAME, TEXT ); +DROP FUNCTION hasnt_domain( NAME, NAME ); +DROP FUNCTION hasnt_domain( NAME, NAME, TEXT ); +DROP FUNCTION has_domain( NAME ); +DROP FUNCTION has_domain( NAME, TEXT ); +DROP FUNCTION has_domain( NAME, NAME ); +DROP FUNCTION has_domain( NAME, NAME, TEXT ); +DROP FUNCTION hasnt_enum( NAME ); +DROP FUNCTION hasnt_enum( NAME, TEXT ); +DROP FUNCTION hasnt_enum( NAME, NAME ); +DROP FUNCTION hasnt_enum( NAME, NAME, TEXT ); +DROP FUNCTION has_enum( NAME ); +DROP FUNCTION has_enum( NAME, TEXT ); +DROP FUNCTION has_enum( NAME, NAME ); +DROP FUNCTION has_enum( NAME, NAME, TEXT ); DROP FUNCTION hasnt_type( NAME ); DROP FUNCTION hasnt_type( NAME, TEXT ); DROP FUNCTION hasnt_type( NAME, NAME ); From ae51ca1db607493c62d8e4fd205dea9f53a81c0b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Feb 2009 18:46:12 +0000 Subject: [PATCH 0262/1195] * Added `enum_has_labels()`. * Moved existing enum tests from `hastap.sql` to new `enumtap.sql` so that it can be excluded from testing on 8.2 and earlier. * Updated `Makefile` to implement said exclusions. * Fixed `_has_type()` to make sure that, when no schema is passed, that the type is visible. --- Changes | 1 + Makefile | 10 +- README.pgtap | 34 ++++- expected/enumtap.out | 74 +++++++++++ expected/hastap.out | 281 +++++++++++++++++------------------------ pgtap.sql.in | 58 +++++++++ sql/enumtap.sql | 207 ++++++++++++++++++++++++++++++ sql/hastap.sql | 136 +------------------- uninstall_pgtap.sql.in | 24 ++-- 9 files changed, 505 insertions(+), 320 deletions(-) create mode 100644 expected/enumtap.out create mode 100644 sql/enumtap.sql diff --git a/Changes b/Changes index 5bdb343db5d1..2b484f2c7e67 100644 --- a/Changes +++ b/Changes @@ -15,6 +15,7 @@ Revision history for pgTAP - Added `has_schema()` and `hasnt_schema()`. - Added `has_type()`, `hasnt_type()`, `has_domain()`, `hasnt_domain()`, `has_enum()`, and `hasnt_enum()`. + - Added `enum_has_labels()`. - Updated the `Makefile` to fix the shebang line in `pg_prove` if Perl is found, and to emite a warning if the `$PERL` variable is not set. - Updated the `Makefile` so that if `$PERL` is set and diff --git a/Makefile b/Makefile index 23de878b406f..e5c5ab501f39 100644 --- a/Makefile +++ b/Makefile @@ -57,12 +57,16 @@ ifeq ($(PGVER_MAJOR), 8) ifeq ($(PGVER_MINOR), 0) # Hack for E'' syntax (<= PG8.0) EXTRA_SUBS := -e "s/ E'/ '/g" -# Throw and runtests aren't supported in 8.0. -TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql,$(TESTS)) -REGRESS := $(filter-out throwtap runtests,$(REGRESS)) +# Throw, runtests, and enums aren't supported in 8.0. +TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql sql/enumtap.sql,$(TESTS)) +REGRESS := $(filter-out throwtap runtests enumtap,$(REGRESS)) else ifeq ($(PGVER_MINOR), 4) # Remove lines 15-20, which define pg_typeof(). EXTRA_SUBS := -e '15,19d' +else ifneq ($(PGVER_MINOR), 3) +# Enum test not supported by 8.2 and earlier. +TESTS := $(filter-out sql/enumtap.sql,$(TESTS)) +REGRESS := $(filter-out enumtap,$(REGRESS)) endif endif diff --git a/README.pgtap b/README.pgtap index 84e5ec4471bf..62e4f4cddeca 100644 --- a/README.pgtap +++ b/README.pgtap @@ -767,13 +767,14 @@ specified domain does *not* exist. 'I got myschema.someenum' ); -This function tests whether or not a enum exists in the database. The first -argument is a schema name, the second is the an enum name, and the third is -the test description. If you omit the schema, the enum must be visible in the -search path. If you omit the test description, it will be set to "Enum `:enum` -should exist". If you're passing a schema and enum rather than enum and -description, be sure to cast the arguments to `name` values so that your enum -name doesn't get treated as a description. +This function tests whether or not a enum exists in the database. Enums are +supported in PostgreSQL 8.3 or higher. The first argument is a schema name, +the second is the an enum name, and the third is the test description. If you +omit the schema, the enum must be visible in the search path. If you omit the +test description, it will be set to "Enum `:enum` should exist". If you're +passing a schema and enum rather than enum and description, be sure to cast +the arguments to `name` values so that your enum name doesn't get treated as a +description. ### `hasnt_enum( schema, enum, description )` ### ### `hasnt_enum( schema, enum )` ### @@ -789,6 +790,25 @@ name doesn't get treated as a description. This function is the inverse of `has_enum()`. The test passes if the specified enum does *not* exist. +### `enum_has_labels( schema, enum, labels, desc )` ### +### `enum_has_labels( schema, enum, labels )` ### +### `enum_has_labels( enum, labels, desc )` ### +### `enum_has_labels( enum, labels )` ### + + SELECT enum_has_labels( + 'myschema', + 'someenum', + ARRAY['foo', 'bar'], + 'Enum someenum should have labels foo, bar' + ); + +This function tests that an enum consists of an expected list of labels. Enums +are supported in PostgreSQL 8.3 or higher. The first argument is a schema +name, the second an enum name, the third an array of enum labels, and the +fourth a description. If you omit the schema, the enum must be visible in the +search path. If you omit the test description, it will be set to "Enum `:enum` +should have labels (`:labels`)". + ### `has_column( schema, table, column, description )` ### ### `has_column( table, column, description )` ### ### `has_column( table, column )` ### diff --git a/expected/enumtap.out b/expected/enumtap.out new file mode 100644 index 000000000000..31ee0de3917f --- /dev/null +++ b/expected/enumtap.out @@ -0,0 +1,74 @@ +\unset ECHO +1..72 +ok 1 - has_type(enum) should pass +ok 2 - has_type(enum) should have the proper description +ok 3 - has_type(enum) should have the proper diagnostics +ok 4 - has_enum(enum) should pass +ok 5 - has_enum(enum) should have the proper description +ok 6 - has_enum(enum) should have the proper diagnostics +ok 7 - has_enum(enum, desc) should pass +ok 8 - has_enum(enum, desc) should have the proper description +ok 9 - has_enum(enum, desc) should have the proper diagnostics +ok 10 - has_enum(scheam, enum) should pass +ok 11 - has_enum(scheam, enum) should have the proper description +ok 12 - has_enum(scheam, enum) should have the proper diagnostics +ok 13 - has_enum(schema, enum, desc) should pass +ok 14 - has_enum(schema, enum, desc) should have the proper description +ok 15 - has_enum(schema, enum, desc) should have the proper diagnostics +ok 16 - has_enum(enum) should fail +ok 17 - has_enum(enum) should have the proper description +ok 18 - has_enum(enum) should have the proper diagnostics +ok 19 - has_enum(enum, desc) should fail +ok 20 - has_enum(enum, desc) should have the proper description +ok 21 - has_enum(enum, desc) should have the proper diagnostics +ok 22 - has_enum(scheam, enum) should fail +ok 23 - has_enum(scheam, enum) should have the proper description +ok 24 - has_enum(scheam, enum) should have the proper diagnostics +ok 25 - has_enum(schema, enum, desc) should fail +ok 26 - has_enum(schema, enum, desc) should have the proper description +ok 27 - has_enum(schema, enum, desc) should have the proper diagnostics +ok 28 - hasnt_enum(enum) should pass +ok 29 - hasnt_enum(enum) should have the proper description +ok 30 - hasnt_enum(enum) should have the proper diagnostics +ok 31 - hasnt_enum(enum, desc) should pass +ok 32 - hasnt_enum(enum, desc) should have the proper description +ok 33 - hasnt_enum(enum, desc) should have the proper diagnostics +ok 34 - hasnt_enum(scheam, enum) should pass +ok 35 - hasnt_enum(scheam, enum) should have the proper description +ok 36 - hasnt_enum(scheam, enum) should have the proper diagnostics +ok 37 - hasnt_enum(schema, enum, desc) should pass +ok 38 - hasnt_enum(schema, enum, desc) should have the proper description +ok 39 - hasnt_enum(schema, enum, desc) should have the proper diagnostics +ok 40 - hasnt_enum(enum) should fail +ok 41 - hasnt_enum(enum) should have the proper description +ok 42 - hasnt_enum(enum) should have the proper diagnostics +ok 43 - hasnt_enum(enum, desc) should fail +ok 44 - hasnt_enum(enum, desc) should have the proper description +ok 45 - hasnt_enum(enum, desc) should have the proper diagnostics +ok 46 - hasnt_enum(scheam, enum) should fail +ok 47 - hasnt_enum(scheam, enum) should have the proper description +ok 48 - hasnt_enum(scheam, enum) should have the proper diagnostics +ok 49 - hasnt_enum(schema, enum, desc) should fail +ok 50 - hasnt_enum(schema, enum, desc) should have the proper description +ok 51 - hasnt_enum(schema, enum, desc) should have the proper diagnostics +ok 52 - enum_has_labels(schema, enum, labels, desc) should pass +ok 53 - enum_has_labels(schema, enum, labels, desc) should have the proper description +ok 54 - enum_has_labels(schema, enum, labels, desc) should have the proper diagnostics +ok 55 - enum_has_labels(schema, enum, labels) should pass +ok 56 - enum_has_labels(schema, enum, labels) should have the proper description +ok 57 - enum_has_labels(schema, enum, labels) should have the proper diagnostics +ok 58 - enum_has_labels(enum, labels, desc) should pass +ok 59 - enum_has_labels(enum, labels, desc) should have the proper description +ok 60 - enum_has_labels(enum, labels, desc) should have the proper diagnostics +ok 61 - enum_has_labels(enum, labels) should pass +ok 62 - enum_has_labels(enum, labels) should have the proper description +ok 63 - enum_has_labels(enum, labels) should have the proper diagnostics +ok 64 - enum_has_labels(schema, enum, labels, desc) fail should fail +ok 65 - enum_has_labels(schema, enum, labels, desc) fail should have the proper description +ok 66 - enum_has_labels(schema, enum, labels, desc) fail should have the proper diagnostics +ok 67 - enum_has_labels(schema, enum, labels, desc) fail should fail +ok 68 - enum_has_labels(schema, enum, labels, desc) fail should have the proper description +ok 69 - enum_has_labels(schema, enum, labels, desc) fail should have the proper diagnostics +ok 70 - enum_has_labels(enum, labels, desc) fail should fail +ok 71 - enum_has_labels(enum, labels, desc) fail should have the proper description +ok 72 - enum_has_labels(enum, labels, desc) fail should have the proper diagnostics diff --git a/expected/hastap.out b/expected/hastap.out index d0e01af7502f..0ae5638fb1d0 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..282 +1..231 ok 1 - has_schema(non-existent schema) should fail ok 2 - has_schema(non-existent schema) should have the proper description ok 3 - has_schema(non-existent schema) should have the proper diagnostics @@ -117,168 +117,117 @@ ok 114 - has_type(schema, type, desc) should have the proper diagnostics ok 115 - has_type(domain) should pass ok 116 - has_type(domain) should have the proper description ok 117 - has_type(domain) should have the proper diagnostics -ok 118 - has_type(enum) should pass -ok 119 - has_type(enum) should have the proper description -ok 120 - has_type(enum) should have the proper diagnostics -ok 121 - hasnt_type(type) should pass -ok 122 - hasnt_type(type) should have the proper description -ok 123 - hasnt_type(type) should have the proper diagnostics -ok 124 - hasnt_type(type, desc) should pass -ok 125 - hasnt_type(type, desc) should have the proper description -ok 126 - hasnt_type(type, desc) should have the proper diagnostics -ok 127 - hasnt_type(scheam, type) should pass -ok 128 - hasnt_type(scheam, type) should have the proper description -ok 129 - hasnt_type(scheam, type) should have the proper diagnostics -ok 130 - hasnt_type(schema, type, desc) should pass -ok 131 - hasnt_type(schema, type, desc) should have the proper description -ok 132 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 133 - hasnt_type(type) should fail -ok 134 - hasnt_type(type) should have the proper description -ok 135 - hasnt_type(type) should have the proper diagnostics -ok 136 - hasnt_type(type, desc) should fail -ok 137 - hasnt_type(type, desc) should have the proper description -ok 138 - hasnt_type(type, desc) should have the proper diagnostics -ok 139 - hasnt_type(scheam, type) should fail -ok 140 - hasnt_type(scheam, type) should have the proper description -ok 141 - hasnt_type(scheam, type) should have the proper diagnostics -ok 142 - hasnt_type(schema, type, desc) should fail -ok 143 - hasnt_type(schema, type, desc) should have the proper description -ok 144 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 145 - has_domain(domain) should pass -ok 146 - has_domain(domain) should have the proper description -ok 147 - has_domain(domain) should have the proper diagnostics -ok 148 - has_domain(domain, desc) should pass -ok 149 - has_domain(domain, desc) should have the proper description -ok 150 - has_domain(domain, desc) should have the proper diagnostics -ok 151 - has_domain(scheam, domain) should pass -ok 152 - has_domain(scheam, domain) should have the proper description -ok 153 - has_domain(scheam, domain) should have the proper diagnostics -ok 154 - has_domain(schema, domain, desc) should pass -ok 155 - has_domain(schema, domain, desc) should have the proper description -ok 156 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 157 - has_domain(domain) should fail -ok 158 - has_domain(domain) should have the proper description -ok 159 - has_domain(domain) should have the proper diagnostics -ok 160 - has_domain(domain, desc) should fail -ok 161 - has_domain(domain, desc) should have the proper description -ok 162 - has_domain(domain, desc) should have the proper diagnostics -ok 163 - has_domain(scheam, domain) should fail -ok 164 - has_domain(scheam, domain) should have the proper description -ok 165 - has_domain(scheam, domain) should have the proper diagnostics -ok 166 - has_domain(schema, domain, desc) should fail -ok 167 - has_domain(schema, domain, desc) should have the proper description -ok 168 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 169 - hasnt_domain(domain) should pass -ok 170 - hasnt_domain(domain) should have the proper description -ok 171 - hasnt_domain(domain) should have the proper diagnostics -ok 172 - hasnt_domain(domain, desc) should pass -ok 173 - hasnt_domain(domain, desc) should have the proper description -ok 174 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 175 - hasnt_domain(scheam, domain) should pass -ok 176 - hasnt_domain(scheam, domain) should have the proper description -ok 177 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 178 - hasnt_domain(schema, domain, desc) should pass -ok 179 - hasnt_domain(schema, domain, desc) should have the proper description -ok 180 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 181 - hasnt_domain(domain) should fail -ok 182 - hasnt_domain(domain) should have the proper description -ok 183 - hasnt_domain(domain) should have the proper diagnostics -ok 184 - hasnt_domain(domain, desc) should fail -ok 185 - hasnt_domain(domain, desc) should have the proper description -ok 186 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 187 - hasnt_domain(scheam, domain) should fail -ok 188 - hasnt_domain(scheam, domain) should have the proper description -ok 189 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 190 - hasnt_domain(schema, domain, desc) should fail -ok 191 - hasnt_domain(schema, domain, desc) should have the proper description -ok 192 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 193 - has_enum(enum) should pass -ok 194 - has_enum(enum) should have the proper description -ok 195 - has_enum(enum) should have the proper diagnostics -ok 196 - has_enum(enum, desc) should pass -ok 197 - has_enum(enum, desc) should have the proper description -ok 198 - has_enum(enum, desc) should have the proper diagnostics -ok 199 - has_enum(scheam, enum) should pass -ok 200 - has_enum(scheam, enum) should have the proper description -ok 201 - has_enum(scheam, enum) should have the proper diagnostics -ok 202 - has_enum(schema, enum, desc) should pass -ok 203 - has_enum(schema, enum, desc) should have the proper description -ok 204 - has_enum(schema, enum, desc) should have the proper diagnostics -ok 205 - has_enum(enum) should fail -ok 206 - has_enum(enum) should have the proper description -ok 207 - has_enum(enum) should have the proper diagnostics -ok 208 - has_enum(enum, desc) should fail -ok 209 - has_enum(enum, desc) should have the proper description -ok 210 - has_enum(enum, desc) should have the proper diagnostics -ok 211 - has_enum(scheam, enum) should fail -ok 212 - has_enum(scheam, enum) should have the proper description -ok 213 - has_enum(scheam, enum) should have the proper diagnostics -ok 214 - has_enum(schema, enum, desc) should fail -ok 215 - has_enum(schema, enum, desc) should have the proper description -ok 216 - has_enum(schema, enum, desc) should have the proper diagnostics -ok 217 - hasnt_enum(enum) should pass -ok 218 - hasnt_enum(enum) should have the proper description -ok 219 - hasnt_enum(enum) should have the proper diagnostics -ok 220 - hasnt_enum(enum, desc) should pass -ok 221 - hasnt_enum(enum, desc) should have the proper description -ok 222 - hasnt_enum(enum, desc) should have the proper diagnostics -ok 223 - hasnt_enum(scheam, enum) should pass -ok 224 - hasnt_enum(scheam, enum) should have the proper description -ok 225 - hasnt_enum(scheam, enum) should have the proper diagnostics -ok 226 - hasnt_enum(schema, enum, desc) should pass -ok 227 - hasnt_enum(schema, enum, desc) should have the proper description -ok 228 - hasnt_enum(schema, enum, desc) should have the proper diagnostics -ok 229 - hasnt_enum(enum) should fail -ok 230 - hasnt_enum(enum) should have the proper description -ok 231 - hasnt_enum(enum) should have the proper diagnostics -ok 232 - hasnt_enum(enum, desc) should fail -ok 233 - hasnt_enum(enum, desc) should have the proper description -ok 234 - hasnt_enum(enum, desc) should have the proper diagnostics -ok 235 - hasnt_enum(scheam, enum) should fail -ok 236 - hasnt_enum(scheam, enum) should have the proper description -ok 237 - hasnt_enum(scheam, enum) should have the proper diagnostics -ok 238 - hasnt_enum(schema, enum, desc) should fail -ok 239 - hasnt_enum(schema, enum, desc) should have the proper description -ok 240 - hasnt_enum(schema, enum, desc) should have the proper diagnostics -ok 241 - has_column(non-existent tab, col) should fail -ok 242 - has_column(non-existent tab, col) should have the proper description -ok 243 - has_column(non-existent tab, col) should have the proper diagnostics -ok 244 - has_column(non-existent tab, col, desc) should fail -ok 245 - has_column(non-existent tab, col, desc) should have the proper description -ok 246 - has_column(non-existent tab, col, desc) should have the proper diagnostics -ok 247 - has_column(non-existent sch, tab, col, desc) should fail -ok 248 - has_column(non-existent sch, tab, col, desc) should have the proper description -ok 249 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 250 - has_column(table, column) should pass -ok 251 - has_column(table, column) should have the proper description -ok 252 - has_column(table, column) should have the proper diagnostics -ok 253 - has_column(sch, tab, col, desc) should pass -ok 254 - has_column(sch, tab, col, desc) should have the proper description -ok 255 - has_column(sch, tab, col, desc) should have the proper diagnostics -ok 256 - has_column(view, column) should pass -ok 257 - has_column(view, column) should have the proper description -ok 258 - has_column(view, column) should have the proper diagnostics -ok 259 - has_column(type, column) should pass -ok 260 - has_column(type, column) should have the proper description -ok 261 - has_column(type, column) should have the proper diagnostics -ok 262 - hasnt_column(non-existent tab, col) should pass -ok 263 - hasnt_column(non-existent tab, col) should have the proper description -ok 264 - hasnt_column(non-existent tab, col) should have the proper diagnostics -ok 265 - hasnt_column(non-existent tab, col, desc) should pass -ok 266 - hasnt_column(non-existent tab, col, desc) should have the proper description -ok 267 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics -ok 268 - hasnt_column(non-existent sch, tab, col, desc) should pass -ok 269 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description -ok 270 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 271 - hasnt_column(table, column) should fail -ok 272 - hasnt_column(table, column) should have the proper description -ok 273 - hasnt_column(table, column) should have the proper diagnostics -ok 274 - hasnt_column(sch, tab, col, desc) should fail -ok 275 - hasnt_column(sch, tab, col, desc) should have the proper description -ok 276 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics -ok 277 - hasnt_column(view, column) should pass -ok 278 - hasnt_column(view, column) should have the proper description -ok 279 - hasnt_column(view, column) should have the proper diagnostics -ok 280 - hasnt_column(type, column) should pass -ok 281 - hasnt_column(type, column) should have the proper description -ok 282 - hasnt_column(type, column) should have the proper diagnostics +ok 118 - hasnt_type(type) should pass +ok 119 - hasnt_type(type) should have the proper description +ok 120 - hasnt_type(type) should have the proper diagnostics +ok 121 - hasnt_type(type, desc) should pass +ok 122 - hasnt_type(type, desc) should have the proper description +ok 123 - hasnt_type(type, desc) should have the proper diagnostics +ok 124 - hasnt_type(scheam, type) should pass +ok 125 - hasnt_type(scheam, type) should have the proper description +ok 126 - hasnt_type(scheam, type) should have the proper diagnostics +ok 127 - hasnt_type(schema, type, desc) should pass +ok 128 - hasnt_type(schema, type, desc) should have the proper description +ok 129 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 130 - hasnt_type(type) should fail +ok 131 - hasnt_type(type) should have the proper description +ok 132 - hasnt_type(type) should have the proper diagnostics +ok 133 - hasnt_type(type, desc) should fail +ok 134 - hasnt_type(type, desc) should have the proper description +ok 135 - hasnt_type(type, desc) should have the proper diagnostics +ok 136 - hasnt_type(scheam, type) should fail +ok 137 - hasnt_type(scheam, type) should have the proper description +ok 138 - hasnt_type(scheam, type) should have the proper diagnostics +ok 139 - hasnt_type(schema, type, desc) should fail +ok 140 - hasnt_type(schema, type, desc) should have the proper description +ok 141 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 142 - has_domain(domain) should pass +ok 143 - has_domain(domain) should have the proper description +ok 144 - has_domain(domain) should have the proper diagnostics +ok 145 - has_domain(domain, desc) should pass +ok 146 - has_domain(domain, desc) should have the proper description +ok 147 - has_domain(domain, desc) should have the proper diagnostics +ok 148 - has_domain(scheam, domain) should pass +ok 149 - has_domain(scheam, domain) should have the proper description +ok 150 - has_domain(scheam, domain) should have the proper diagnostics +ok 151 - has_domain(schema, domain, desc) should pass +ok 152 - has_domain(schema, domain, desc) should have the proper description +ok 153 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 154 - has_domain(domain) should fail +ok 155 - has_domain(domain) should have the proper description +ok 156 - has_domain(domain) should have the proper diagnostics +ok 157 - has_domain(domain, desc) should fail +ok 158 - has_domain(domain, desc) should have the proper description +ok 159 - has_domain(domain, desc) should have the proper diagnostics +ok 160 - has_domain(scheam, domain) should fail +ok 161 - has_domain(scheam, domain) should have the proper description +ok 162 - has_domain(scheam, domain) should have the proper diagnostics +ok 163 - has_domain(schema, domain, desc) should fail +ok 164 - has_domain(schema, domain, desc) should have the proper description +ok 165 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 166 - hasnt_domain(domain) should pass +ok 167 - hasnt_domain(domain) should have the proper description +ok 168 - hasnt_domain(domain) should have the proper diagnostics +ok 169 - hasnt_domain(domain, desc) should pass +ok 170 - hasnt_domain(domain, desc) should have the proper description +ok 171 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 172 - hasnt_domain(scheam, domain) should pass +ok 173 - hasnt_domain(scheam, domain) should have the proper description +ok 174 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 175 - hasnt_domain(schema, domain, desc) should pass +ok 176 - hasnt_domain(schema, domain, desc) should have the proper description +ok 177 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 178 - hasnt_domain(domain) should fail +ok 179 - hasnt_domain(domain) should have the proper description +ok 180 - hasnt_domain(domain) should have the proper diagnostics +ok 181 - hasnt_domain(domain, desc) should fail +ok 182 - hasnt_domain(domain, desc) should have the proper description +ok 183 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 184 - hasnt_domain(scheam, domain) should fail +ok 185 - hasnt_domain(scheam, domain) should have the proper description +ok 186 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 187 - hasnt_domain(schema, domain, desc) should fail +ok 188 - hasnt_domain(schema, domain, desc) should have the proper description +ok 189 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 190 - has_column(non-existent tab, col) should fail +ok 191 - has_column(non-existent tab, col) should have the proper description +ok 192 - has_column(non-existent tab, col) should have the proper diagnostics +ok 193 - has_column(non-existent tab, col, desc) should fail +ok 194 - has_column(non-existent tab, col, desc) should have the proper description +ok 195 - has_column(non-existent tab, col, desc) should have the proper diagnostics +ok 196 - has_column(non-existent sch, tab, col, desc) should fail +ok 197 - has_column(non-existent sch, tab, col, desc) should have the proper description +ok 198 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 199 - has_column(table, column) should pass +ok 200 - has_column(table, column) should have the proper description +ok 201 - has_column(table, column) should have the proper diagnostics +ok 202 - has_column(sch, tab, col, desc) should pass +ok 203 - has_column(sch, tab, col, desc) should have the proper description +ok 204 - has_column(sch, tab, col, desc) should have the proper diagnostics +ok 205 - has_column(view, column) should pass +ok 206 - has_column(view, column) should have the proper description +ok 207 - has_column(view, column) should have the proper diagnostics +ok 208 - has_column(type, column) should pass +ok 209 - has_column(type, column) should have the proper description +ok 210 - has_column(type, column) should have the proper diagnostics +ok 211 - hasnt_column(non-existent tab, col) should pass +ok 212 - hasnt_column(non-existent tab, col) should have the proper description +ok 213 - hasnt_column(non-existent tab, col) should have the proper diagnostics +ok 214 - hasnt_column(non-existent tab, col, desc) should pass +ok 215 - hasnt_column(non-existent tab, col, desc) should have the proper description +ok 216 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics +ok 217 - hasnt_column(non-existent sch, tab, col, desc) should pass +ok 218 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description +ok 219 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 220 - hasnt_column(table, column) should fail +ok 221 - hasnt_column(table, column) should have the proper description +ok 222 - hasnt_column(table, column) should have the proper diagnostics +ok 223 - hasnt_column(sch, tab, col, desc) should fail +ok 224 - hasnt_column(sch, tab, col, desc) should have the proper description +ok 225 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics +ok 226 - hasnt_column(view, column) should pass +ok 227 - hasnt_column(view, column) should have the proper description +ok 228 - hasnt_column(view, column) should have the proper diagnostics +ok 229 - hasnt_column(type, column) should pass +ok 230 - hasnt_column(type, column) should have the proper description +ok 231 - hasnt_column(type, column) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 5d5bc27b03c6..b2c64a593478 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2601,6 +2601,7 @@ RETURNS BOOLEAN AS $$ 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']) ) ); @@ -2750,6 +2751,63 @@ RETURNS TEXT AS $$ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || $1 || ' should not exist')::text ); $$ LANGUAGE sql; +-- enum_has_labels( schema, enum, labels, desc ) +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 ' || $1 || '.' || $2 || ' should have labels (' || array_to_string( $3, ', ' ) || ')' + ); +$$ LANGUAGE sql; + +-- enum_has_labels( enum, labels, desc ) +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 ' || $1 || ' should have labels (' || array_to_string( $2, ', ' ) || ')' + ); +$$ LANGUAGE sql; + -- check_test( test_output, pass, name, description, diag ) CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) RETURNS SETOF TEXT AS $$ diff --git a/sql/enumtap.sql b/sql/enumtap.sql new file mode 100644 index 000000000000..0f463a74661a --- /dev/null +++ b/sql/enumtap.sql @@ -0,0 +1,207 @@ +\unset ECHO +\i test_setup.sql + +-- $Id$ + +SELECT plan(72); +--SELECT * FROM no_plan(); + +-- This will be rolled back. :-) +SET client_min_messages = warning; +CREATE TYPE bug_status AS ENUM ('new', 'open', 'closed'); +RESET client_min_messages; + +/****************************************************************************/ +-- Make sure has_type for enums. +SELECT * FROM check_test( + has_type( 'bug_status' ), + true, + 'has_type(enum)', + 'Type bug_status should exist', + '' +); + +/****************************************************************************/ +-- Test has_enum(). +SELECT * FROM check_test( + has_enum( 'bug_status' ), + true, + 'has_enum(enum)', + 'Enum bug_status should exist', + '' +); + +SELECT * FROM check_test( + has_enum( 'bug_status', 'mydesc' ), + true, + 'has_enum(enum, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + has_enum( 'public'::name, 'bug_status'::name ), + true, + 'has_enum(scheam, enum)', + 'Enum public.bug_status should exist', + '' +); +SELECT * FROM check_test( + has_enum( 'public', 'bug_status', 'mydesc' ), + true, + 'has_enum(schema, enum, desc)', + 'mydesc', + '' +); + +-- Try failures. +SELECT * FROM check_test( + has_enum( '__foobarbaz__' ), + false, + 'has_enum(enum)', + 'Enum __foobarbaz__ should exist', + '' +); +SELECT * FROM check_test( + has_enum( '__foobarbaz__', 'mydesc' ), + false, + 'has_enum(enum, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + has_enum( 'public'::name, '__foobarbaz__'::name ), + false, + 'has_enum(scheam, enum)', + 'Enum public.__foobarbaz__ should exist', + '' +); +SELECT * FROM check_test( + has_enum( 'public', '__foobarbaz__', 'mydesc' ), + false, + 'has_enum(schema, enum, desc)', + 'mydesc', + '' +); + +/****************************************************************************/ +-- Test hasnt_enum(). +SELECT * FROM check_test( + hasnt_enum( '__foobarbaz__' ), + true, + 'hasnt_enum(enum)', + 'Enum __foobarbaz__ should not exist', + '' +); +SELECT * FROM check_test( + hasnt_enum( '__foobarbaz__', 'mydesc' ), + true, + 'hasnt_enum(enum, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + hasnt_enum( 'public'::name, '__foobarbaz__'::name ), + true, + 'hasnt_enum(scheam, enum)', + 'Enum public.__foobarbaz__ should not exist', + '' +); +SELECT * FROM check_test( + hasnt_enum( 'public', '__foobarbaz__', 'mydesc' ), + true, + 'hasnt_enum(schema, enum, desc)', + 'mydesc', + '' +); + +-- Try failures. +SELECT * FROM check_test( + hasnt_enum( 'bug_status' ), + false, + 'hasnt_enum(enum)', + 'Enum bug_status should not exist', + '' +); +SELECT * FROM check_test( + hasnt_enum( 'bug_status', 'mydesc' ), + false, + 'hasnt_enum(enum, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + hasnt_enum( 'public'::name, 'bug_status'::name ), + false, + 'hasnt_enum(scheam, enum)', + 'Enum public.bug_status should not exist', + '' +); +SELECT * FROM check_test( + hasnt_enum( 'public', 'bug_status', 'mydesc' ), + false, + 'hasnt_enum(schema, enum, desc)', + 'mydesc', + '' +); + +/****************************************************************************/ +-- Test enum_has_labels(). +SELECT * FROM check_test( + enum_has_labels( 'public', 'bug_status', ARRAY['new', 'open', 'closed'], 'mydesc' ), + true, + 'enum_has_labels(schema, enum, labels, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + enum_has_labels( 'public', 'bug_status', ARRAY['new', 'open', 'closed'] ), + true, + 'enum_has_labels(schema, enum, labels)', + 'Enum public.bug_status should have labels (new, open, closed)', + '' +); +SELECT * FROM check_test( + enum_has_labels( 'bug_status', ARRAY['new', 'open', 'closed'], 'mydesc' ), + true, + 'enum_has_labels(enum, labels, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + enum_has_labels( 'bug_status', ARRAY['new', 'open', 'closed'] ), + true, + 'enum_has_labels(enum, labels)', + 'Enum bug_status should have labels (new, open, closed)', + '' +); + +-- Try failures. +SELECT * FROM check_test( + enum_has_labels( 'public', 'bug_status', ARRAY['new', 'closed', 'open'], 'mydesc' ), + false, + 'enum_has_labels(schema, enum, labels, desc) fail', + 'mydesc', + ' have: {new,open,closed} + want: {new,closed,open}' +); +SELECT * FROM check_test( + enum_has_labels( 'public', 'bug_status', ARRAY['new', 'open', 'Closed'], 'mydesc' ), + false, + 'enum_has_labels(schema, enum, labels, desc) fail', + 'mydesc', + ' have: {new,open,closed} + want: {new,open,Closed}' +); +SELECT * FROM check_test( + enum_has_labels( 'bug_status', ARRAY['new', 'closed', 'open'], 'mydesc' ), + false, + 'enum_has_labels(enum, labels, desc) fail', + 'mydesc', + ' have: {new,open,closed} + want: {new,closed,open}' +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; diff --git a/sql/hastap.sql b/sql/hastap.sql index 5176dd2abc75..f23276967baa 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(282); +SELECT plan(231); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -23,8 +23,6 @@ CREATE DOMAIN us_postal_code AS TEXT CHECK( VALUE ~ '^[[:digit:]]{5}$' OR VALUE ~ '^[[:digit:]]{5}-[[:digit:]]{4}$' ); -CREATE TYPE bug_status AS ENUM ('new', 'open', 'closed'); - CREATE SCHEMA someschema; RESET client_min_messages; @@ -341,7 +339,7 @@ SELECT * FROM check_test( '' ); --- Make sure it works for domains and enums. +-- Make sure it works for domains. SELECT * FROM check_test( has_type( 'us_postal_code' ), true, @@ -349,13 +347,6 @@ SELECT * FROM check_test( 'Type us_postal_code should exist', '' ); -SELECT * FROM check_test( - has_type( 'bug_status' ), - true, - 'has_type(enum)', - 'Type bug_status should exist', - '' -); /****************************************************************************/ -- Test hasnt_type(). @@ -541,129 +532,6 @@ SELECT * FROM check_test( '' ); -/****************************************************************************/ --- Test has_enum(). -SELECT * FROM check_test( - has_enum( 'bug_status' ), - true, - 'has_enum(enum)', - 'Enum bug_status should exist', - '' -); - -SELECT * FROM check_test( - has_enum( 'bug_status', 'mydesc' ), - true, - 'has_enum(enum, desc)', - 'mydesc', - '' -); -SELECT * FROM check_test( - has_enum( 'public'::name, 'bug_status'::name ), - true, - 'has_enum(scheam, enum)', - 'Enum public.bug_status should exist', - '' -); -SELECT * FROM check_test( - has_enum( 'public', 'bug_status', 'mydesc' ), - true, - 'has_enum(schema, enum, desc)', - 'mydesc', - '' -); - --- Try failures. -SELECT * FROM check_test( - has_enum( '__foobarbaz__' ), - false, - 'has_enum(enum)', - 'Enum __foobarbaz__ should exist', - '' -); -SELECT * FROM check_test( - has_enum( '__foobarbaz__', 'mydesc' ), - false, - 'has_enum(enum, desc)', - 'mydesc', - '' -); -SELECT * FROM check_test( - has_enum( 'public'::name, '__foobarbaz__'::name ), - false, - 'has_enum(scheam, enum)', - 'Enum public.__foobarbaz__ should exist', - '' -); -SELECT * FROM check_test( - has_enum( 'public', '__foobarbaz__', 'mydesc' ), - false, - 'has_enum(schema, enum, desc)', - 'mydesc', - '' -); - -/****************************************************************************/ --- Test hasnt_enum(). -SELECT * FROM check_test( - hasnt_enum( '__foobarbaz__' ), - true, - 'hasnt_enum(enum)', - 'Enum __foobarbaz__ should not exist', - '' -); -SELECT * FROM check_test( - hasnt_enum( '__foobarbaz__', 'mydesc' ), - true, - 'hasnt_enum(enum, desc)', - 'mydesc', - '' -); -SELECT * FROM check_test( - hasnt_enum( 'public'::name, '__foobarbaz__'::name ), - true, - 'hasnt_enum(scheam, enum)', - 'Enum public.__foobarbaz__ should not exist', - '' -); -SELECT * FROM check_test( - hasnt_enum( 'public', '__foobarbaz__', 'mydesc' ), - true, - 'hasnt_enum(schema, enum, desc)', - 'mydesc', - '' -); - --- Try failures. -SELECT * FROM check_test( - hasnt_enum( 'bug_status' ), - false, - 'hasnt_enum(enum)', - 'Enum bug_status should not exist', - '' -); -SELECT * FROM check_test( - hasnt_enum( 'bug_status', 'mydesc' ), - false, - 'hasnt_enum(enum, desc)', - 'mydesc', - '' -); -SELECT * FROM check_test( - hasnt_enum( 'public'::name, 'bug_status'::name ), - false, - 'hasnt_enum(scheam, enum)', - 'Enum public.bug_status should not exist', - '' -); -SELECT * FROM check_test( - hasnt_enum( 'public', 'bug_status', 'mydesc' ), - false, - 'hasnt_enum(schema, enum, desc)', - 'mydesc', - '' -); - /****************************************************************************/ -- Test has_column(). diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index d1e188021e23..29475c96bf99 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -19,14 +19,10 @@ DROP FUNCTION check_test( TEXT, BOOLEAN ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); -DROP FUNCTION hasnt_domain( NAME ); -DROP FUNCTION hasnt_domain( NAME, TEXT ); -DROP FUNCTION hasnt_domain( NAME, NAME ); -DROP FUNCTION hasnt_domain( NAME, NAME, TEXT ); -DROP FUNCTION has_domain( NAME ); -DROP FUNCTION has_domain( NAME, TEXT ); -DROP FUNCTION has_domain( NAME, NAME ); -DROP FUNCTION has_domain( NAME, NAME, TEXT ); +DROP FUNCTION enum_has_labels( NAME, NAME[] ); +DROP FUNCTION enum_has_labels( NAME, NAME[], TEXT ); +DROP FUNCTION enum_has_labels( NAME, NAME, NAME[] ); +DROP FUNCTION enum_has_labels( NAME, NAME, NAME[], TEXT ); DROP FUNCTION hasnt_enum( NAME ); DROP FUNCTION hasnt_enum( NAME, TEXT ); DROP FUNCTION hasnt_enum( NAME, NAME ); @@ -35,6 +31,14 @@ DROP FUNCTION has_enum( NAME ); DROP FUNCTION has_enum( NAME, TEXT ); DROP FUNCTION has_enum( NAME, NAME ); DROP FUNCTION has_enum( NAME, NAME, TEXT ); +DROP FUNCTION hasnt_domain( NAME ); +DROP FUNCTION hasnt_domain( NAME, TEXT ); +DROP FUNCTION hasnt_domain( NAME, NAME ); +DROP FUNCTION hasnt_domain( NAME, NAME, TEXT ); +DROP FUNCTION has_domain( NAME ); +DROP FUNCTION has_domain( NAME, TEXT ); +DROP FUNCTION has_domain( NAME, NAME ); +DROP FUNCTION has_domain( NAME, NAME, TEXT ); DROP FUNCTION hasnt_type( NAME ); DROP FUNCTION hasnt_type( NAME, TEXT ); DROP FUNCTION hasnt_type( NAME, NAME ); @@ -43,8 +47,8 @@ DROP FUNCTION has_type( NAME ); DROP FUNCTION has_type( NAME, TEXT ); DROP FUNCTION has_type( NAME, NAME ); DROP FUNCTION has_type( NAME, NAME, TEXT ); -DROP FUNCTION _has_type( NAME ); -DROP FUNCTION _has_type( NAME, NAME ); +DROP FUNCTION _has_type( NAME, CHAR[] ); +DROP FUNCTION _has_type( NAME, NAME, CHAR[] ); DROP FUNCTION hasnt_schema( NAME ); DROP FUNCTION hasnt_schema( NAME, TEXT ); DROP FUNCTION has_schema( NAME ); From 5d0a0e4eef580327416370f2929bb91053954c4b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Feb 2009 19:10:39 +0000 Subject: [PATCH 0263/1195] Ignore bbin. --- README.pgtap | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.pgtap b/README.pgtap index 62e4f4cddeca..390125d0c45b 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2096,9 +2096,7 @@ To Do * Useful schema testing functions to consider adding: * `has_tablespace()` * `has_cast()` - * `has_group()` - * `has_user()` - * `has_role()` + * `has_role()`, `has_user()`, `has_group()` * `has_operator()` * `has_operator_class()` * `has_sequence()` @@ -2109,7 +2107,7 @@ Suported Versions pgTAP has been tested on the following builds of PostgreSQL: -* PostgreSQL 8.3.4 on i386-apple-darwin9.5.0 +* PostgreSQL 8.3.5 on i386-apple-darwin9.5.0 * PostgreSQL 8.2.10 on i386-apple-darwin9.5.0 * PostgreSQL 8.1.14 on i386-apple-darwin9.5.0 * PostgreSQL 8.0.19 on i686-apple-darwin9.5.0 From 5da7d1da3c0cfbddfdd45332888b472ef199ce1d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Feb 2009 19:46:16 +0000 Subject: [PATCH 0264/1195] * Updated patch for compat. * Added patches for 8.2 and earlier to remove `enum_has_labels()` and updated `Makefile` to apply them. * Updated tested platforms. --- Makefile | 9 ++++-- README.pgtap | 6 ++-- compat/install-8.0.patch | 18 +++++------ compat/install-8.2.patch | 66 ++++++++++++++++++++++++++++++++++++++ compat/uninstall-8.2.patch | 13 ++++++++ 5 files changed, 97 insertions(+), 15 deletions(-) create mode 100644 compat/install-8.2.patch create mode 100644 compat/uninstall-8.2.patch diff --git a/Makefile b/Makefile index e5c5ab501f39..f1ff66b67da1 100644 --- a/Makefile +++ b/Makefile @@ -140,16 +140,18 @@ ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 4) ifneq ($(PGVER_MINOR), 3) cat compat/install-8.2.sql >> pgtap.sql -ifneq ($(PGVER_MINOR), 2) -ifeq ($(PGVER_MINOR), 1) +ifeq ($(PGVER_MINOR), 2) + patch -p0 < compat/install-8.2.patch +else ifeq ($(PGVER_MINOR), 1) + patch -p0 < compat/install-8.2.patch patch -p0 < compat/install-8.1.patch else + patch -p0 < compat/install-8.2.patch patch -p0 < compat/install-8.0.patch endif endif endif endif -endif uninstall_pgtap.sql: uninstall_pgtap.sql.in test_setup.sql ifdef TAPSCHEMA @@ -160,6 +162,7 @@ endif ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 4) ifneq ($(PGVER_MINOR), 3) + patch -p0 < compat/uninstall-8.2.patch mv uninstall_pgtap.sql uninstall_pgtap.tmp cat compat/uninstall-8.2.sql uninstall_pgtap.tmp >> uninstall_pgtap.sql rm uninstall_pgtap.tmp diff --git a/README.pgtap b/README.pgtap index 390125d0c45b..ae606f6eb464 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2065,7 +2065,7 @@ No changes. Everything should just work. 8.2 and Lower ------------- -A number of casts are added to increase compatibility. The casts are: +A patch is applied that removes the `enum_has_lables()` function. Also, a number of casts are added to increase compatibility. The casts are: * `boolean` to `text` * `text[]` to `text` @@ -2108,8 +2108,8 @@ Suported Versions pgTAP has been tested on the following builds of PostgreSQL: * PostgreSQL 8.3.5 on i386-apple-darwin9.5.0 -* PostgreSQL 8.2.10 on i386-apple-darwin9.5.0 -* PostgreSQL 8.1.14 on i386-apple-darwin9.5.0 +* PostgreSQL 8.2.11 on i386-apple-darwin9.5.0 +* PostgreSQL 8.1.15 on i386-apple-darwin9.5.0 * PostgreSQL 8.0.19 on i686-apple-darwin9.5.0 If you know of others, please submit them! Use diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index a5244004762d..3b8a3eaef591 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.orig 2009-01-19 16:14:17.000000000 -0800 -+++ pgtap.sql 2009-01-19 16:16:18.000000000 -0800 +--- pgtap.sql 2009-02-02 11:39:23.000000000 -0800 ++++ pgtap.sql.saf 2009-02-02 11:39:10.000000000 -0800 @@ -12,6 +12,22 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA, public; @@ -120,7 +120,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -485,13 +513,16 @@ +@@ -482,13 +510,16 @@ want ALIAS FOR $3; descr ALIAS FOR $4; result BOOLEAN; @@ -140,7 +140,7 @@ output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE '\n' || diag( ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -1052,15 +1083,16 @@ +@@ -1049,15 +1080,16 @@ CREATE OR REPLACE FUNCTION _def_is( TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -161,7 +161,7 @@ END; $$ LANGUAGE plpgsql; -@@ -2559,6 +2591,7 @@ +@@ -2761,6 +2793,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -169,7 +169,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -2569,8 +2602,10 @@ +@@ -2771,8 +2804,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. @@ -182,7 +182,7 @@ -- Now delete those results. EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; -@@ -2679,7 +2714,7 @@ +@@ -2881,7 +2916,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -191,7 +191,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -2687,8 +2722,8 @@ +@@ -2889,8 +2924,8 @@ -- 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. @@ -202,7 +202,7 @@ END LOOP; END LOOP; RETURN; -@@ -2750,116 +2785,6 @@ +@@ -2950,116 +2985,6 @@ END $$ LANGUAGE plpgsql; diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch new file mode 100644 index 000000000000..96b6c1674eef --- /dev/null +++ b/compat/install-8.2.patch @@ -0,0 +1,66 @@ +--- pgtap.sql (revision 4476) ++++ pgtap.sql (working copy) +@@ -2751,63 +2751,6 @@ + SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || $1 || ' should not exist')::text ); + $$ LANGUAGE sql; + +--- enum_has_labels( schema, enum, labels, desc ) +-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 ' || $1 || '.' || $2 || ' should have labels (' || array_to_string( $3, ', ' ) || ')' +- ); +-$$ LANGUAGE sql; +- +--- enum_has_labels( enum, labels, desc ) +-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 ' || $1 || ' should have labels (' || array_to_string( $2, ', ' ) || ')' +- ); +-$$ LANGUAGE sql; +- + -- check_test( test_output, pass, name, description, diag ) + CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) + RETURNS SETOF TEXT AS $$ diff --git a/compat/uninstall-8.2.patch b/compat/uninstall-8.2.patch new file mode 100644 index 000000000000..34e1e3cc8c2b --- /dev/null +++ b/compat/uninstall-8.2.patch @@ -0,0 +1,13 @@ +--- uninstall_pgtap.sql (revision 4476) ++++ uninstall_pgtap.sql (working copy) +@@ -19,10 +19,6 @@ + DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); + DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); + DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); +-DROP FUNCTION enum_has_labels( NAME, NAME[] ); +-DROP FUNCTION enum_has_labels( NAME, NAME[], TEXT ); +-DROP FUNCTION enum_has_labels( NAME, NAME, NAME[] ); +-DROP FUNCTION enum_has_labels( NAME, NAME, NAME[], TEXT ); + DROP FUNCTION hasnt_enum( NAME ); + DROP FUNCTION hasnt_enum( NAME, TEXT ); + DROP FUNCTION hasnt_enum( NAME, NAME ); From 88020e6dc64956a754c396b43d51ba35bc0ab65c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Feb 2009 21:26:17 +0000 Subject: [PATCH 0265/1195] Fixes for 8.4devel. --- pgtap.sql.in | 4 +++- sql/todotap.sql | 3 ++- sql/util.sql | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index b2c64a593478..71da844eb3fd 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -23,7 +23,9 @@ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ - SELECT s.a[1]::int * 10000 + s.a[2]::int * 100 + s.a[3]::int + SELECT s.a[1]::int * 10000 + + COALESCE(substring(s.a[2] FROM '[[:digit:]]+')::int, 0) + + COALESCE(substring(s.a[3] FROM '[[:digit:]]+')::int, 0) FROM ( SELECT string_to_array(current_setting('server_version'), '.') AS a ) AS s; diff --git a/sql/todotap.sql b/sql/todotap.sql index ed168ccfab27..cc29a968d93f 100644 --- a/sql/todotap.sql +++ b/sql/todotap.sql @@ -102,13 +102,14 @@ SELECT * FROM todo('just because', 2 ); -- We have to use ok() instead of is() to get around lack of cast to text in 8.0. SELECT ok( ARRAY( - SELECT fail('This is a todo test 1') + SELECT fail('This is a todo test 1') AS stuff UNION SELECT todo::text FROM todo('inside') UNION SELECT fail('This is a todo test 2') UNION SELECT fail('This is a todo test 3') + ORDER BY stuff ) = ARRAY[ 'not ok 25 - This is a todo test 1 # TODO just because diff --git a/sql/util.sql b/sql/util.sql index 77e1714a899c..707884989948 100644 --- a/sql/util.sql +++ b/sql/util.sql @@ -18,7 +18,7 @@ SELECT is( ); SELECT matches( pg_version(), - '^8[.][[:digit:]]{1,2}[.][[:digit:]]{1,2}$', + '^8[.][[:digit:]]{1,2}([.][[:digit:]]{1,2}|devel)$', 'pg_version() should work' ); From 4946e0448ddd1d66b267728812a8e47c6b22e33f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Feb 2009 21:30:28 +0000 Subject: [PATCH 0266/1195] 8.4 fixes. --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 2b484f2c7e67..0b86f9ea66f0 100644 --- a/Changes +++ b/Changes @@ -23,6 +23,8 @@ Revision history for pgTAP order to use `pg_prove`. - Updated the `Makefile` to set the location of the `psql` binary to the bin directory specified for PostgreSQL when it was built. + - Fixed `pg_version_num() to work on PostgreSQL 8.4devel. + - Fixed a test failure on PostgreSQL 8.4devel. 0.15 2009-01-20T18:41:24 - Changed `pg_typeof()` from immutable to stable, in compliance with its From 9bfaea4415b2e07795162cb99c0186bc8b17b41d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Feb 2009 21:35:45 +0000 Subject: [PATCH 0267/1195] Updated build list. --- README.pgtap | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.pgtap b/README.pgtap index ae606f6eb464..eb637402736e 100644 --- a/README.pgtap +++ b/README.pgtap @@ -30,6 +30,10 @@ If you encounter an error such as: make: pg_config: Command not found +Or: + + Makefile:52: *** pgTAP requires PostgreSQL 8.0 or later. This is . Stop. + Be sure that you have `pg_config` installed and in your path. If you used a package management system such as RPM to install PostgreSQL, be sure that the `-devel` package is also installed. If necessary, add the path to `pg_config` @@ -2107,10 +2111,11 @@ Suported Versions pgTAP has been tested on the following builds of PostgreSQL: +* PostgreSQL 8.4devel on i386-apple-darwin9.6.0 * PostgreSQL 8.3.5 on i386-apple-darwin9.5.0 * PostgreSQL 8.2.11 on i386-apple-darwin9.5.0 -* PostgreSQL 8.1.15 on i386-apple-darwin9.5.0 -* PostgreSQL 8.0.19 on i686-apple-darwin9.5.0 +* PostgreSQL 8.1.15 on i686-apple-darwin9.6.0 +* PostgreSQL 8.0.19 on i686-apple-darwin9.6.0 If you know of others, please submit them! Use `psql -d template1 -c 'SELECT VERSION()'` to get the formal build version and OS. From 3f628bf656f4a1b6dfef772d68cac9ac75753769 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Feb 2009 21:40:40 +0000 Subject: [PATCH 0268/1195] Updated copyright dates. --- README.pgtap | 2 +- bin/pg_prove | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.pgtap b/README.pgtap index eb637402736e..a0fb125de8e3 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2133,7 +2133,7 @@ Credits Copyright and License --------------------- -Copyright (c) 2008 Kineticode, Inc. Some rights reserved. +Copyright (c) 2008-2009 Kineticode, Inc. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is diff --git a/bin/pg_prove b/bin/pg_prove index 70713740e572..5efa8cdbbee2 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -285,6 +285,6 @@ David E. Wheeler =head1 Copyright -Copyright (c) 2008 Kineticode, Inc. Some Rights Reserved. +Copyright (c) 2008-2009 Kineticode, Inc. Some Rights Reserved. =cut From 820349746cd0bd5d9731c549937f2ded817e5bc7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Feb 2009 21:42:02 +0000 Subject: [PATCH 0269/1195] Missed that. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 0b86f9ea66f0..509011006286 100644 --- a/Changes +++ b/Changes @@ -23,7 +23,7 @@ Revision history for pgTAP order to use `pg_prove`. - Updated the `Makefile` to set the location of the `psql` binary to the bin directory specified for PostgreSQL when it was built. - - Fixed `pg_version_num() to work on PostgreSQL 8.4devel. + - Fixed `pg_version_num()` to work on PostgreSQL 8.4devel. - Fixed a test failure on PostgreSQL 8.4devel. 0.15 2009-01-20T18:41:24 From 24c472ef0d0ca8bb3b930f9d4157495c19c3714d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Feb 2009 21:42:37 +0000 Subject: [PATCH 0270/1195] More than one failure fixed. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 509011006286..b2138cc4d059 100644 --- a/Changes +++ b/Changes @@ -24,7 +24,7 @@ Revision history for pgTAP of the `psql` binary to the bin directory specified for PostgreSQL when it was built. - Fixed `pg_version_num()` to work on PostgreSQL 8.4devel. - - Fixed a test failure on PostgreSQL 8.4devel. + - Fixed test failures on PostgreSQL 8.4devel. 0.15 2009-01-20T18:41:24 - Changed `pg_typeof()` from immutable to stable, in compliance with its From ea3915dd594150e2bd77bc04d68e1738ae0f72f5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 3 Feb 2009 17:31:37 +0000 Subject: [PATCH 0271/1195] Timestamped for 0.16 release. --- Changes | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index b2138cc4d059..3c292059411d 100644 --- a/Changes +++ b/Changes @@ -1,13 +1,13 @@ Revision history for pgTAP -0.16 +0.16 2009-02-03T17:37:03 - Switched from a crazy trinary logic in `is()` and `isnt()` to the use of `IS NOT DISTINCT FROM` and `IS DISTINCT FROM`. Swiped from PGUnit. - Fixed documentation for the short version of `--help` in `pg_prove`. It's `-H`, not `-h`. - Fixed a bug in the makefile that prevented OS detection from working properly on many platforms (including Linux, and probably many - others). + others). This allows `os_name()` to actually work on those platforms. - The definition of the `pg_typeof()` function is removed when installing on POstgresQL 8.4 and no C code is compiled, since the only C code is for this function. This is because `pg_typeof()` is included From 269c588863a03bba5e4944ec7344911dd40efcab Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 3 Feb 2009 17:42:27 +0000 Subject: [PATCH 0272/1195] Incremented version number to 0.17. --- Changes | 2 ++ Makefile | 2 +- README.pgtap | 4 ++-- bin/pg_prove | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 3c292059411d..76655cde6415 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Revision history for pgTAP +0.17 + 0.16 2009-02-03T17:37:03 - Switched from a crazy trinary logic in `is()` and `isnt()` to the use of `IS NOT DISTINCT FROM` and `IS DISTINCT FROM`. Swiped from PGUnit. diff --git a/Makefile b/Makefile index f1ff66b67da1..e03ede9fa168 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') PGVER_MAJOR = $(shell echo $(VERSION) | awk -F. '{ print ($$1 + 0) }') PGVER_MINOR = $(shell echo $(VERSION) | awk -F. '{ print ($$2 + 0) }') PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') -PGTAP_VERSION = 0.16 +PGTAP_VERSION = 0.17 # Compile the C code only if we're on 8.3 or older. ifneq ($(PGVER_MINOR), 4) diff --git a/README.pgtap b/README.pgtap index a0fb125de8e3..6b1e1d3dcf43 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.16 +pgTAP 0.17 ========== pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and @@ -262,7 +262,7 @@ return TAP values, like so: RETURN NEXT is( MAX(nick), NULL, 'Should have no users') FROM users; INSERT INTO users (nick) VALUES ('theory'); $$ LANGUAGE plpgsql; - + CREATE OR REPLACE FUNCTION test_user( ) RETURNS SETOF TEXT AS $$ SELECT is( nick, 'theory', 'Should have nick') FROM users; diff --git a/bin/pg_prove b/bin/pg_prove index 5efa8cdbbee2..445a7bd678c2 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -6,7 +6,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.16'; +our $VERSION = '0.17'; Getopt::Long::Configure (qw(bundling)); From 629dba003de44198d81ecacd5e7d47f27c67df33 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 4 Feb 2009 01:32:13 +0000 Subject: [PATCH 0273/1195] Bug report. --- README.pgtap | 1 + 1 file changed, 1 insertion(+) diff --git a/README.pgtap b/README.pgtap index 6b1e1d3dcf43..4fda34fe30f7 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2097,6 +2097,7 @@ To Do * Add options to `pg_prove` to allow it to run tests using the `runtests()` function rather than test files. +* Fix `col_has_default()` to check for `NULL` defaults. It's a bug that it doesn't. * Useful schema testing functions to consider adding: * `has_tablespace()` * `has_cast()` From beff3ba264f34311aa114409db5e987d8df9301b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 4 Feb 2009 01:41:45 +0000 Subject: [PATCH 0274/1195] Note about `pg_typeof()`. --- README.pgtap | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.pgtap b/README.pgtap index 4fda34fe30f7..c9b6d40201ca 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1747,6 +1747,11 @@ executing the comparison, but might be generally useful. -----------+----------- integer | numeric +*Note:* pgTAP does not build `pg_typeof()` on PostgreSQL 8.4 or higher, +because it's in core in 8.4. You only need to worry about this if you depend +on the function being in particular schema. It will always be in `pg_catalog` +in 8.4 and higher. + ### `findfuncs( schema, pattern )` ### ### `findfuncs( pattern )` ### From 6150eb34d3fdd015fc8c5fc6450e34674bf79429 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 4 Feb 2009 07:50:31 +0000 Subject: [PATCH 0275/1195] * Fixed a bunch of issues with `col_default_is()`. * Added `col_has_default()` and `col_hasnt_default()`. * Gave all three of these functions much better diagnostics. --- Changes | 10 ++ README.pgtap | 56 ++++++++++- expected/coltap.out | 104 +++++++++++++++++--- pgtap.sql.in | 189 ++++++++++++++++++++++++++++------- sql/coltap.sql | 217 ++++++++++++++++++++++++++++++++++++++++- uninstall_pgtap.sql.in | 12 ++- 6 files changed, 528 insertions(+), 60 deletions(-) diff --git a/Changes b/Changes index 76655cde6415..e46fcbd903f4 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,16 @@ Revision history for pgTAP 0.17 + - Fixed `col_default_is()` so that it works properly in cases where the + default value is `NULL`. + - Fixed `col_default_is()` to gracefully handle different data types, + columns without defaults, and nonexistent columns. + - Fixed the three-argument form of `col_default_is()` to accept any data + type for the default, not just text. + *NOTE:* You must `DROP FUNCTION col_default_is ( NAME, NAME, TEXT );` if + you have previous versions of this function installed, so as to + eliminate the incompatible version. + - Added `col_has_default()` and `col_hasnt_default()`. 0.16 2009-02-03T17:37:03 - Switched from a crazy trinary logic in `is()` and `isnt()` to the use diff --git a/README.pgtap b/README.pgtap index c9b6d40201ca..c9ef537d3890 100644 --- a/README.pgtap +++ b/README.pgtap @@ -883,6 +883,40 @@ the search path. If the test description is omitted, it will be set to "Column `:table`.`:column` should allow NULL". Note that this test will fail if the table or column in question does not exist. +### `col_has_default( schema, table, column, description )` ### +### `col_has_default( table, column, description )` ### +### `col_has_default( table, column )` ### + + SELECT col_has_default( + 'myschema', + 'sometable', + 'somecolumn', + 'Column myschema.sometable.somecolumn has a default' + ); + +Tests whether or not a column has a default value. Fails if the column dosn't +have a default value. It will also fail if the column doesn't exist, and emit +useful diagnostics to let you know: + + not ok 136 - desc + # Failed test 136: "desc" + # Column public.sometab.__asdfasdfs__ does not exist + +### `col_hasnt_default( schema, table, column, description )` ### +### `col_hasnt_default( table, column, description )` ### +### `col_hasnt_default( table, column )` ### + + SELECT col_hasnt_default( + 'myschema', + 'sometable', + 'somecolumn', + 'There should be no default on myschema.sometable.somecolumn' + ); + +This function is the inverse of `col_has_default()`. The test passes if the +specified column does *not* have a default. It will still fail if the column +does not exist. + ### `col_type_is( schema, table, column, type, description )` ### ### `col_type_is( table, column, type, description )` ### ### `col_type_is( table, column, type )` ### @@ -900,9 +934,9 @@ fails, it will emit diagnostics naming the actual type. The first argument is the schema name, the second the table name, the third the column name, the fourth the type, and the fifth is the test description. If the schema is omitted, the table and the type must be visible in the search path. If the -test description is omitted, it will be set to "Column `:table`.`:column` -should be type `:type`". Note that this test will fail if the table or column -in question does not exist. +test description is omitted, it will be set to "Column +`:schema.:table.:column` should be type `:type`". Note that this test will +fail if the table or column in question does not exist. The type argument should be formatted as it would be displayed in the view of a table using the `\d` command in `psql`. For example, if you have a numeric @@ -920,6 +954,11 @@ Will produce something like this: # have: name # want: text +It will even tell you if the test fails because a column doesn't exist or +actually has no default. But you use `has_column()` and `col_has_default()` to +test those conditions before you call `col_default_is()`, right? *Right???* +Yeah, good, I thought so. + ### `col_default_is( schema, table, column, default, description )` ### ### `col_default_is( table, column, default, description )` ### ### `col_default_is( table, column, type )` ### @@ -952,6 +991,11 @@ But this will not: SELECT col_default_is( 'tab', 'name', 'foo'::text ); +You even need to properly cast your default value when it's a NULL. Just cast +it to the same data type as the column itself: + + SELECT col_default_is( 'tab', age, NULL::integer ); + You can also test for functional defaults. Just specify the function call as a string: @@ -2102,7 +2146,11 @@ To Do * Add options to `pg_prove` to allow it to run tests using the `runtests()` function rather than test files. -* Fix `col_has_default()` to check for `NULL` defaults. It's a bug that it doesn't. +* Fix various column testing functions so that they emit useful diagnostics if + a column does not exist, like `col_has_default()` does. +* Change all the uses of `schema.table(column)` to `schema.table.column` in + diagnostics and descriptions. +* Use `quote_ident()` to quote all identifiers in diagnostics and descriptions. * Useful schema testing functions to consider adding: * `has_tablespace()` * `has_cast()` diff --git a/expected/coltap.out b/expected/coltap.out index 947c71cf7633..2f09f87695f9 100644 --- a/expected/coltap.out +++ b/expected/coltap.out @@ -1,5 +1,5 @@ \unset ECHO -1..57 +1..135 ok 1 - col_not_null( sch, tab, col, desc ) should pass ok 2 - col_not_null( sch, tab, col, desc ) should have the proper description ok 3 - col_not_null( sch, tab, col, desc ) should have the proper diagnostics @@ -45,15 +45,93 @@ ok 42 - col_type_is with precision should have the proper diagnostics ok 43 - col_type_is precision fail should fail ok 44 - col_type_is precision fail should have the proper description ok 45 - col_type_is precision fail should have the proper diagnostics -ok 46 - col_default_is( sch, tab, col, def, desc ) should pass -ok 47 - col_default_is( sch, tab, col, def, desc ) should have the proper description -ok 48 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics -ok 49 - col_default_is() fail should fail -ok 50 - col_default_is() fail should have the proper description -ok 51 - col_default_is() fail should have the proper diagnostics -ok 52 - col_default_is( tab, col, def, desc ) should pass -ok 53 - col_default_is( tab, col, def, desc ) should have the proper description -ok 54 - col_default_is( tab, col, def, desc ) should have the proper diagnostics -ok 55 - col_default_is( tab, col, def ) should pass -ok 56 - col_default_is( tab, col, def ) should have the proper description -ok 57 - col_default_is( tab, col, def ) should have the proper diagnostics +ok 46 - col_has_default( sch, tab, col, desc ) should pass +ok 47 - col_has_default( sch, tab, col, desc ) should have the proper description +ok 48 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics +ok 49 - col_has_default( tab, col, desc ) should pass +ok 50 - col_has_default( tab, col, desc ) should have the proper description +ok 51 - col_has_default( tab, col, desc ) should have the proper diagnostics +ok 52 - col_has_default( tab, col ) should pass +ok 53 - col_has_default( tab, col ) should have the proper description +ok 54 - col_has_default( tab, col ) should have the proper diagnostics +ok 55 - col_has_default( sch, tab, col, desc ) should fail +ok 56 - col_has_default( sch, tab, col, desc ) should have the proper description +ok 57 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics +ok 58 - col_has_default( tab, col, desc ) should fail +ok 59 - col_has_default( tab, col, desc ) should have the proper description +ok 60 - col_has_default( tab, col, desc ) should have the proper diagnostics +ok 61 - col_has_default( tab, col ) should fail +ok 62 - col_has_default( tab, col ) should have the proper description +ok 63 - col_has_default( tab, col ) should have the proper diagnostics +ok 64 - col_has_default( sch, tab, col, desc ) should fail +ok 65 - col_has_default( sch, tab, col, desc ) should have the proper description +ok 66 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics +ok 67 - col_has_default( tab, col, desc ) should fail +ok 68 - col_has_default( tab, col, desc ) should have the proper description +ok 69 - col_has_default( tab, col, desc ) should have the proper diagnostics +ok 70 - col_has_default( tab, col ) should fail +ok 71 - col_has_default( tab, col ) should have the proper description +ok 72 - col_has_default( tab, col ) should have the proper diagnostics +ok 73 - col_hasnt_default( sch, tab, col, desc ) should fail +ok 74 - col_hasnt_default( sch, tab, col, desc ) should have the proper description +ok 75 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics +ok 76 - col_hasnt_default( tab, col, desc ) should fail +ok 77 - col_hasnt_default( tab, col, desc ) should have the proper description +ok 78 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics +ok 79 - col_hasnt_default( tab, col ) should fail +ok 80 - col_hasnt_default( tab, col ) should have the proper description +ok 81 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 82 - col_hasnt_default( sch, tab, col, desc ) should pass +ok 83 - col_hasnt_default( sch, tab, col, desc ) should have the proper description +ok 84 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics +ok 85 - col_hasnt_default( tab, col, desc ) should pass +ok 86 - col_hasnt_default( tab, col, desc ) should have the proper description +ok 87 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics +ok 88 - col_hasnt_default( tab, col ) should pass +ok 89 - col_hasnt_default( tab, col ) should have the proper description +ok 90 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 91 - col_hasnt_default( sch, tab, col, desc ) should fail +ok 92 - col_hasnt_default( sch, tab, col, desc ) should have the proper description +ok 93 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics +ok 94 - col_hasnt_default( tab, col, desc ) should fail +ok 95 - col_hasnt_default( tab, col, desc ) should have the proper description +ok 96 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics +ok 97 - col_hasnt_default( tab, col ) should fail +ok 98 - col_hasnt_default( tab, col ) should have the proper description +ok 99 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 100 - col_default_is( sch, tab, col, def, desc ) should pass +ok 101 - col_default_is( sch, tab, col, def, desc ) should have the proper description +ok 102 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics +ok 103 - col_default_is() fail should fail +ok 104 - col_default_is() fail should have the proper description +ok 105 - col_default_is() fail should have the proper diagnostics +ok 106 - col_default_is( tab, col, def, desc ) should pass +ok 107 - col_default_is( tab, col, def, desc ) should have the proper description +ok 108 - col_default_is( tab, col, def, desc ) should have the proper diagnostics +ok 109 - col_default_is( tab, col, def ) should pass +ok 110 - col_default_is( tab, col, def ) should have the proper description +ok 111 - col_default_is( tab, col, def ) should have the proper diagnostics +ok 112 - col_default_is( tab, col, int ) should pass +ok 113 - col_default_is( tab, col, int ) should have the proper description +ok 114 - col_default_is( tab, col, int ) should have the proper diagnostics +ok 115 - col_default_is( tab, col, NULL, desc ) should pass +ok 116 - col_default_is( tab, col, NULL, desc ) should have the proper description +ok 117 - col_default_is( tab, col, NULL, desc ) should have the proper diagnostics +ok 118 - col_default_is( tab, col, NULL ) should pass +ok 119 - col_default_is( tab, col, NULL ) should have the proper description +ok 120 - col_default_is( tab, col, NULL ) should have the proper diagnostics +ok 121 - col_default_is( tab, col, bogus, desc ) should fail +ok 122 - col_default_is( tab, col, bogus, desc ) should have the proper description +ok 123 - col_default_is( tab, col, bogus, desc ) should have the proper diagnostics +ok 124 - col_default_is( tab, col, bogus ) should fail +ok 125 - col_default_is( tab, col, bogus ) should have the proper description +ok 126 - col_default_is( tab, col, bogus ) should have the proper diagnostics +ok 127 - col_default_is( sch, tab, col, def, desc ) should fail +ok 128 - col_default_is( sch, tab, col, def, desc ) should have the proper description +ok 129 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics +ok 130 - col_default_is( tab, col, def, desc ) should fail +ok 131 - col_default_is( tab, col, def, desc ) should have the proper description +ok 132 - col_default_is( tab, col, def, desc ) should have the proper diagnostics +ok 133 - col_default_is( tab, col, def ) should fail +ok 134 - col_default_is( tab, col, def ) should have the proper description +ok 135 - col_default_is( tab, col, def ) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 71da844eb3fd..fe161c0949ec 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1048,17 +1048,105 @@ RETURNS TEXT AS $$ SELECT col_type_is( $1, $2, $3, 'Column ' || $1 || '(' || $2 || ') should be type ' || $3 ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _def_is( TEXT, anyelement, TEXT ) +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 = LOWER($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 = LOWER($2) +$$ 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 ' || $1 || '.' || $2 || '.' || $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 ' || $1 || '.' || $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 ' || $1 || '.' || $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 ' || $1 || '.' || $2 || '.' || $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 ' || $1 || '.' || $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 ' || $1 || '.' || $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 IF $1 ~ '^[^'']+[(]' THEN -- It's a functional default. - RETURN is( $1, $2, $3 ); + RETURN is( $1, $3, $4 ); END IF; - EXECUTE 'SELECT is(' || COALESCE($1, 'NULL::text') || ', ' || quote_literal($2) || ', ' || quote_literal($3) || ')' - INTO thing; + + 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; @@ -1066,47 +1154,76 @@ $$ LANGUAGE plpgsql; -- col_default_is( schema, table, column, default, description ) CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, NAME, anyelement, TEXT ) RETURNS TEXT AS $$ - SELECT _def_is(( - SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid) - 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 - ), $4, $5 ); -$$ LANGUAGE sql; +BEGIN + IF NOT _cexists( $1, $2, $3 ) THEN + RETURN fail( $5 ) || E'\n' + || diag (' Column ' || $1 || '.' || $2 || '.' || $3 || ' does not exist' ); + END IF; + + IF NOT _has_def( $1, $2, $3 ) THEN + RETURN fail( $5 ) || E'\n' + || diag (' Column ' || $1 || '.' || $2 || '.' || $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; -- col_default_is( table, column, default, description ) CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, anyelement, TEXT ) RETURNS TEXT AS $$ - SELECT _def_is(( - SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid) - 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 - ), $3, $4 ); -$$ LANGUAGE sql; +BEGIN + IF NOT _cexists( $1, $2 ) THEN + RETURN fail( $4 ) || E'\n' + || diag (' Column ' || $1 || '.' || $2 || ' does not exist' ); + END IF; + + IF NOT _has_def( $1, $2 ) THEN + RETURN fail( $4 ) || E'\n' + || diag (' Column ' || $1 || '.' || $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; -- col_default_is( table, column, default ) -CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, TEXT ) +CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, anyelement ) RETURNS TEXT AS $$ SELECT col_default_is( $1, $2, $3, - 'Column ' || $1 || '(' || $2 || ') should default to ' || quote_literal($3) + 'Column ' || $1 || '.' || $2 || ' should default to ' + || COALESCE( quote_literal($3), 'NULL') ); $$ LANGUAGE sql; diff --git a/sql/coltap.sql b/sql/coltap.sql index 3638acd2c5b7..f3086ff522ad 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -3,15 +3,17 @@ -- $Id$ -SELECT plan(57); +SELECT plan(135); +--SELECT * from no_plan(); -- This will be rolled back. :-) SET client_min_messages = warning; CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '', - numb NUMERIC(10, 2), - myint NUMERIC(8) + numb NUMERIC(10, 2) DEFAULT NULL, + myint NUMERIC(8) DEFAULT 24, + plain INTEGER ); RESET client_min_messages; @@ -148,6 +150,146 @@ SELECT * FROM check_test( want: numeric(7)' ); +/****************************************************************************/ +-- Test col_has_default(). +SELECT * FROM check_test( + col_has_default( 'public', 'sometab', 'name', 'desc' ), + true, + 'col_has_default( sch, tab, col, desc )', + 'desc', + '' +); +SELECT * FROM check_test( + col_has_default( 'sometab', 'name', 'desc' ), + true, + 'col_has_default( tab, col, desc )', + 'desc', + '' +); +SELECT * FROM check_test( + col_has_default( 'sometab', 'name' ), + true, + 'col_has_default( tab, col )', + 'Column sometab.name should have a default', + '' +); + +-- Check with a column with no default. +SELECT * FROM check_test( + col_has_default( 'public', 'sometab', 'plain', 'desc' ), + false, + 'col_has_default( sch, tab, col, desc )', + 'desc', + '' +); +SELECT * FROM check_test( + col_has_default( 'sometab', 'plain', 'desc' ), + false, + 'col_has_default( tab, col, desc )', + 'desc', + '' +); +SELECT * FROM check_test( + col_has_default( 'sometab', 'plain' ), + false, + 'col_has_default( tab, col )', + 'Column sometab.plain should have a default', + '' +); + +-- Check with a nonexistent column. +SELECT * FROM check_test( + col_has_default( 'public', 'sometab', '__asdfasdfs__', 'desc' ), + false, + 'col_has_default( sch, tab, col, desc )', + 'desc', + ' Column public.sometab.__asdfasdfs__ does not exist' +); +SELECT * FROM check_test( + col_has_default( 'sometab', '__asdfasdfs__', 'desc' ), + false, + 'col_has_default( tab, col, desc )', + 'desc', + ' Column sometab.__asdfasdfs__ does not exist' +); +SELECT * FROM check_test( + col_has_default( 'sometab', '__asdfasdfs__' ), + false, + 'col_has_default( tab, col )', + 'Column sometab.__asdfasdfs__ should have a default', + ' Column sometab.__asdfasdfs__ does not exist' +); + +/****************************************************************************/ +-- Test col_hasnt_default(). +SELECT * FROM check_test( + col_hasnt_default( 'public', 'sometab', 'name', 'desc' ), + false, + 'col_hasnt_default( sch, tab, col, desc )', + 'desc', + '' +); +SELECT * FROM check_test( + col_hasnt_default( 'sometab', 'name', 'desc' ), + false, + 'col_hasnt_default( tab, col, desc )', + 'desc', + '' +); +SELECT * FROM check_test( + col_hasnt_default( 'sometab', 'name' ), + false, + 'col_hasnt_default( tab, col )', + 'Column sometab.name should not have a default', + '' +); + +-- Check with a column with no default. +SELECT * FROM check_test( + col_hasnt_default( 'public', 'sometab', 'plain', 'desc' ), + true, + 'col_hasnt_default( sch, tab, col, desc )', + 'desc', + '' +); +SELECT * FROM check_test( + col_hasnt_default( 'sometab', 'plain', 'desc' ), + true, + 'col_hasnt_default( tab, col, desc )', + 'desc', + '' +); +SELECT * FROM check_test( + col_hasnt_default( 'sometab', 'plain' ), + true, + 'col_hasnt_default( tab, col )', + 'Column sometab.plain should not have a default', + '' +); + +-- Check with a nonexistent column. +SELECT * FROM check_test( + col_hasnt_default( 'public', 'sometab', '__asdfasdfs__', 'desc' ), + false, + 'col_hasnt_default( sch, tab, col, desc )', + 'desc', + ' Column public.sometab.__asdfasdfs__ does not exist' +); +SELECT * FROM check_test( + col_hasnt_default( 'sometab', '__asdfasdfs__', 'desc' ), + false, + 'col_hasnt_default( tab, col, desc )', + 'desc', + ' Column sometab.__asdfasdfs__ does not exist' +); +SELECT * FROM check_test( + col_hasnt_default( 'sometab', '__asdfasdfs__' ), + false, + 'col_hasnt_default( tab, col )', + 'Column sometab.__asdfasdfs__ should not have a default', + ' Column sometab.__asdfasdfs__ does not exist' +); + /****************************************************************************/ -- Test col_default_is(). @@ -177,12 +319,77 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - col_default_is( 'sometab', 'name', '' ), + col_default_is( 'sometab', 'name', ''::text ), true, 'col_default_is( tab, col, def )', - 'Column sometab(name) should default to ''''', + 'Column sometab.name should default to ''''', + '' +); + +-- Make sure it works with a non-text column. +SELECT * FROM check_test( + col_default_is( 'sometab', 'myint', 24 ), + true, + 'col_default_is( tab, col, int )', + 'Column sometab.myint should default to ''24''', + '' +); + +-- Make sure it works with a NULL default. +SELECT * FROM check_test( + col_default_is( 'sometab', 'numb', NULL::numeric, 'desc' ), + true, + 'col_default_is( tab, col, NULL, desc )', + 'desc', '' ); +SELECT * FROM check_test( + col_default_is( 'sometab', 'numb', NULL::numeric ), + true, + 'col_default_is( tab, col, NULL )', + 'Column sometab.numb should default to NULL', + '' +); + +-- Make sure that it fails when there is no default. +SELECT * FROM check_test( + col_default_is( 'sometab', 'plain', 1::integer, 'desc' ), + false, + 'col_default_is( tab, col, bogus, desc )', + 'desc', + ' Column sometab.plain has no default' +); + +SELECT * FROM check_test( + col_default_is( 'sometab', 'plain', 1::integer ), + false, + 'col_default_is( tab, col, bogus )', + 'Column sometab.plain should default to ''1''', + ' Column sometab.plain has no default' +); + +-- Check with a nonexistent column. +SELECT * FROM check_test( + col_default_is( 'public', 'sometab', '__asdfasdfs__', NULL::text, 'desc' ), + false, + 'col_default_is( sch, tab, col, def, desc )', + 'desc', + ' Column public.sometab.__asdfasdfs__ does not exist' +); +SELECT * FROM check_test( + col_default_is( 'sometab', '__asdfasdfs__', NULL::text, 'desc' ), + false, + 'col_default_is( tab, col, def, desc )', + 'desc', + ' Column sometab.__asdfasdfs__ does not exist' +); +SELECT * FROM check_test( + col_default_is( 'sometab', '__asdfasdfs__', NULL::text ), + false, + 'col_default_is( tab, col, def )', + 'Column sometab.__asdfasdfs__ should default to NULL', + ' Column sometab.__asdfasdfs__ does not exist' +); /****************************************************************************/ -- Finish the tests and clean up. diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 29475c96bf99..dde469023aa7 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -177,10 +177,18 @@ DROP FUNCTION has_pk ( NAME, TEXT ); DROP FUNCTION has_pk ( NAME, NAME, TEXT ); DROP FUNCTION _hasc ( NAME, CHAR ); DROP FUNCTION _hasc ( NAME, NAME, CHAR ); -DROP FUNCTION col_default_is ( NAME, NAME, TEXT ); +DROP FUNCTION col_default_is ( NAME, NAME, anyelement ); DROP FUNCTION col_default_is ( NAME, NAME, anyelement, TEXT ); DROP FUNCTION col_default_is ( NAME, NAME, NAME, anyelement, TEXT ); -DROP FUNCTION _def_is( TEXT, anyelement, TEXT ); +DROP FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT ); +DROP FUNCTION col_hasnt_default ( NAME, NAME ); +DROP FUNCTION col_hasnt_default ( NAME, NAME, TEXT ); +DROP FUNCTION col_hasnt_default ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION col_has_default ( NAME, NAME ); +DROP FUNCTION col_has_default ( NAME, NAME, TEXT ); +DROP FUNCTION col_has_default ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION _has_def ( NAME, NAME ); +DROP FUNCTION _has_def ( NAME, NAME, NAME ); DROP FUNCTION col_type_is ( NAME, NAME, TEXT ); DROP FUNCTION col_type_is ( NAME, NAME, TEXT, TEXT ); DROP FUNCTION col_type_is ( NAME, NAME, NAME, TEXT, TEXT ); From 5c1eec0cfe791dd561477a0dca3665442ea30f19 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 4 Feb 2009 08:00:13 +0000 Subject: [PATCH 0276/1195] Changed default descriptions for column testing functions to refer to the columns as the more SQL-standard `schema.table.column` insteada of `schema.table(column)`. --- Changes | 3 +++ README.pgtap | 17 +++++++---------- pgtap.sql.in | 10 +++++----- sql/coltap.sql | 14 +++++++------- sql/hastap.sql | 16 ++++++++-------- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Changes b/Changes index e46fcbd903f4..5219b8af1500 100644 --- a/Changes +++ b/Changes @@ -11,6 +11,9 @@ Revision history for pgTAP you have previous versions of this function installed, so as to eliminate the incompatible version. - Added `col_has_default()` and `col_hasnt_default()`. + - Changed default descriptions for column testing functions to refer to + the columns as the more SQL-standard `schema.table.column` insteada of + `schema.table(column)`. 0.16 2009-02-03T17:37:03 - Switched from a crazy trinary logic in `is()` and `isnt()` to the use diff --git a/README.pgtap b/README.pgtap index c9ef537d3890..203564127471 100644 --- a/README.pgtap +++ b/README.pgtap @@ -828,7 +828,7 @@ Tests whether or not a column exists in a given table, view, or composite type. The first argument is the schema name, the second the table name, the third the column name, and the fourth is the test description. If the schema is omitted, the table must be visible in the search path. If the test -description is omitted, it will be set to "Column `:table`.`:column` should +description is omitted, it will be set to "Column `:table.:column` should exist". ### `hasnt_column( schema, table, column, description )` ### @@ -861,8 +861,8 @@ Tests whether the specified column has a `NOT NULL` constraint. The first argument is the schema name, the second the table name, the third the column name, and the fourth is the test description. If the schema is omitted, the table must be visible in the search path. If the test description is omitted, -it will be set to "Column `:table`.`:column` should be NOT NULL". Note that -this test will fail if the table or column in question does not exist. +it will be set to "Column `:table.:column` should be NOT NULL". Note that this +test will fail if the table or column in question does not exist. ### `col_is_null( schema, table, column, description )` ### ### `col_is_null( table, column, description )` ### @@ -880,7 +880,7 @@ column does not have a `NOT NULL` constraint. The first argument is the schema name, the second the table name, the third the column name, and the fourth is the test description. If the schema is omitted, the table must be visible in the search path. If the test description is omitted, it will be set to "Column -`:table`.`:column` should allow NULL". Note that this test will fail if the +`:table.:column` should allow NULL". Note that this test will fail if the table or column in question does not exist. ### `col_has_default( schema, table, column, description )` ### @@ -976,9 +976,8 @@ showing the actual default value. The first argument is the schema name, the second the table name, the third the column name, the fourth the default value, and the fifth is the test description. If the schema is omitted, the table must be visible in the search path. If the test description is omitted, -it will be set to "Column `:table`.`:column` should default to `:default`". -Note that this test will fail if the table or column in question does not -exist. +it will be set to "Column `:table.:column` should default to `:default`". Note +that this test will fail if the table or column in question does not exist. The default argument must have an unambiguous type in order for the call to succeed. If you see an error such as 'ERROR: could not determine polymorphic @@ -1102,7 +1101,7 @@ primary key columns, if any. The first argument is the schema name, the second the table name, the third the column name or an array of column names, and the fourth is the test description. If the schema is omitted, the table must be visible in the search path. If the test description is omitted, it will be set -to "Column `:table`.`:column` should be a primary key". Note that this test +to "Column `:table(:column)` should be a primary key". Note that this test will fail if the table or column in question does not exist. If the test fails, it will output useful diagnostics. For example this test: @@ -2148,8 +2147,6 @@ To Do function rather than test files. * Fix various column testing functions so that they emit useful diagnostics if a column does not exist, like `col_has_default()` does. -* Change all the uses of `schema.table(column)` to `schema.table.column` in - diagnostics and descriptions. * Use `quote_ident()` to quote all identifiers in diagnostics and descriptions. * Useful schema testing functions to consider adding: * `has_tablespace()` diff --git a/pgtap.sql.in b/pgtap.sql.in index fe161c0949ec..2f776bd2fe46 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -899,7 +899,7 @@ $$ LANGUAGE SQL; -- has_column( table, column ) CREATE OR REPLACE FUNCTION has_column ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT has_column( $1, $2, 'Column ' || $1 || '(' || $2 || ') should exist' ); + SELECT has_column( $1, $2, 'Column ' || $1 || '.' || $2 || ' should exist' ); $$ LANGUAGE SQL; -- hasnt_column( schema, table, column, description ) @@ -917,7 +917,7 @@ $$ LANGUAGE SQL; -- hasnt_column( table, column ) CREATE OR REPLACE FUNCTION hasnt_column ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT hasnt_column( $1, $2, 'Column ' || $1 || '(' || $2 || ') should not exist' ); + SELECT hasnt_column( $1, $2, 'Column ' || $1 || '.' || $2 || ' should not exist' ); $$ LANGUAGE SQL; -- _col_is_null( schema, table, column, desc, null ) @@ -972,7 +972,7 @@ $$ LANGUAGE SQL; -- col_not_null( table, column ) CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, 'Column ' || $1 || '(' || $2 || ') should be NOT NULL', true ); + SELECT _col_is_null( $1, $2, 'Column ' || $1 || '.' || $2 || ' should be NOT NULL', true ); $$ LANGUAGE SQL; -- col_is_null( schema, table, column, description ) @@ -990,7 +990,7 @@ $$ LANGUAGE SQL; -- col_is_null( table, column ) CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, 'Column ' || $1 || '(' || $2 || ') should allow NULL', false ); + SELECT _col_is_null( $1, $2, 'Column ' || $1 || '.' || $2 || ' should allow NULL', false ); $$ LANGUAGE SQL; -- col_type_is( schema, table, column, type, description ) @@ -1045,7 +1045,7 @@ $$ 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 ' || $1 || '(' || $2 || ') should be type ' || $3 ); + SELECT col_type_is( $1, $2, $3, 'Column ' || $1 || '.' || $2 || ' should be type ' || $3 ); $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _has_def ( NAME, NAME, NAME ) diff --git a/sql/coltap.sql b/sql/coltap.sql index f3086ff522ad..861780d0b8f8 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -39,7 +39,7 @@ SELECT * FROM check_test( col_not_null( 'sometab', 'id' ), true, 'col_not_null( table, column )', - 'Column sometab(id) should be NOT NULL', + 'Column sometab.id should be NOT NULL', '' ); @@ -48,7 +48,7 @@ SELECT * FROM check_test( col_not_null( 'sometab', 'name' ), false, 'col_not_null( table, column ) fail', - 'Column sometab(name) should be NOT NULL', + 'Column sometab.name should be NOT NULL', '' ); @@ -74,7 +74,7 @@ SELECT * FROM check_test( col_is_null( 'sometab', 'name' ), true, 'col_is_null( tab, col )', - 'Column sometab(name) should allow NULL', + 'Column sometab.name should allow NULL', '' ); -- Make sure failure is correct. @@ -82,7 +82,7 @@ SELECT * FROM check_test( col_is_null( 'sometab', 'id' ), false, 'col_is_null( tab, col ) fail', - 'Column sometab(id) should allow NULL', + 'Column sometab.id should allow NULL', '' ); @@ -108,7 +108,7 @@ SELECT * FROM check_test( col_type_is( 'sometab', 'name', 'text' ), true, 'col_type_is( tab, col, type )', - 'Column sometab(name) should be type text', + 'Column sometab.name should be type text', '' ); @@ -116,7 +116,7 @@ SELECT * FROM check_test( col_type_is( 'sometab', 'name', 'TEXT' ), true, 'col_type_is( tab, col, type ) insensitive', - 'Column sometab(name) should be type TEXT', + 'Column sometab.name should be type TEXT', '' ); @@ -125,7 +125,7 @@ SELECT * FROM check_test( col_type_is( 'sometab', 'name', 'int4' ), false, 'col_type_is( tab, col, type ) fail', - 'Column sometab(name) should be type int4', + 'Column sometab.name should be type int4', ' have: text want: int4' ); diff --git a/sql/hastap.sql b/sql/hastap.sql index f23276967baa..9057893465bb 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -539,7 +539,7 @@ SELECT * FROM check_test( has_column( '__SDFSDFD__', 'foo' ), false, 'has_column(non-existent tab, col)', - 'Column __SDFSDFD__(foo) should exist', + 'Column __SDFSDFD__.foo should exist', '' ); @@ -563,7 +563,7 @@ SELECT * FROM check_test( has_column( 'sometab', 'id' ), true, 'has_column(table, column)', - 'Column sometab(id) should exist', + 'Column sometab.id should exist', '' ); @@ -580,7 +580,7 @@ SELECT * FROM check_test( has_column( 'pg_tables', 'schemaname' ), true, 'has_column(view, column)', - 'Column pg_tables(schemaname) should exist', + 'Column pg_tables.schemaname should exist', '' ); @@ -589,7 +589,7 @@ SELECT * FROM check_test( has_column( 'sometype', 'name' ), true, 'has_column(type, column)', - 'Column sometype(name) should exist', + 'Column sometype.name should exist', '' ); @@ -600,7 +600,7 @@ SELECT * FROM check_test( hasnt_column( '__SDFSDFD__', 'foo' ), true, 'hasnt_column(non-existent tab, col)', - 'Column __SDFSDFD__(foo) should not exist', + 'Column __SDFSDFD__.foo should not exist', '' ); @@ -624,7 +624,7 @@ SELECT * FROM check_test( hasnt_column( 'sometab', 'id' ), false, 'hasnt_column(table, column)', - 'Column sometab(id) should not exist', + 'Column sometab.id should not exist', '' ); @@ -641,7 +641,7 @@ SELECT * FROM check_test( hasnt_column( 'pg_tables', 'whatever' ), true, 'hasnt_column(view, column)', - 'Column pg_tables(whatever) should not exist', + 'Column pg_tables.whatever should not exist', '' ); @@ -650,7 +650,7 @@ SELECT * FROM check_test( hasnt_column( 'sometype', 'foobar' ), true, 'hasnt_column(type, column)', - 'Column sometype(foobar) should not exist', + 'Column sometype.foobar should not exist', '' ); From 46c1b97cf5e49b6cbe96379ab78159809b118070 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 4 Feb 2009 08:00:59 +0000 Subject: [PATCH 0277/1195] typo. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 5219b8af1500..0e50582875cd 100644 --- a/Changes +++ b/Changes @@ -12,7 +12,7 @@ Revision history for pgTAP eliminate the incompatible version. - Added `col_has_default()` and `col_hasnt_default()`. - Changed default descriptions for column testing functions to refer to - the columns as the more SQL-standard `schema.table.column` insteada of + the columns as the more SQL-standard `schema.table.column` instead of `schema.table(column)`. 0.16 2009-02-03T17:37:03 From 1433619644d1f481d5e604cf6d29343ebbf371d9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 4 Feb 2009 18:19:08 +0000 Subject: [PATCH 0278/1195] Modified all diagnostic messages and test descriptions that reference database objects to use `quote_ident()` to quote the names of those objects, rather than emitting them with or without double-quotes in an ad-hoc way as had hitherto been the case. --- Changes | 4 + README.pgtap | 1 - pgtap.sql.in | 214 ++++++++++++++++++++++++------------------------- sql/hastap.sql | 16 ++-- sql/index.sql | 80 +++++++++--------- 5 files changed, 159 insertions(+), 156 deletions(-) diff --git a/Changes b/Changes index 0e50582875cd..e720aad6dcc3 100644 --- a/Changes +++ b/Changes @@ -14,6 +14,10 @@ Revision history for pgTAP - Changed default descriptions for column testing functions to refer to the columns as the more SQL-standard `schema.table.column` instead of `schema.table(column)`. + - Modified all diagnostic messages and test descriptions that reference + database objects to use `quote_ident()` to quote the names of those + objects, rather than emitting them with or without double-quotes in an + ad-hoc way as had hitherto been the case. 0.16 2009-02-03T17:37:03 - Switched from a crazy trinary logic in `is()` and `isnt()` to the use diff --git a/README.pgtap b/README.pgtap index 203564127471..34765984d564 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2147,7 +2147,6 @@ To Do function rather than test files. * Fix various column testing functions so that they emit useful diagnostics if a column does not exist, like `col_has_default()` does. -* Use `quote_ident()` to quote all identifiers in diagnostics and descriptions. * Useful schema testing functions to consider adding: * `has_tablespace()` * `has_cast()` diff --git a/pgtap.sql.in b/pgtap.sql.in index 2f776bd2fe46..c58834d6fe1a 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -798,7 +798,7 @@ $$ LANGUAGE SQL; -- has_table( table ) CREATE OR REPLACE FUNCTION has_table ( NAME ) RETURNS TEXT AS $$ - SELECT has_table( $1, 'Table ' || $1 || ' should exist' ); + SELECT has_table( $1, 'Table ' || quote_ident($1) || ' should exist' ); $$ LANGUAGE SQL; -- hasnt_table( schema, table, description ) @@ -816,7 +816,7 @@ $$ LANGUAGE SQL; -- hasnt_table( table ) CREATE OR REPLACE FUNCTION hasnt_table ( NAME ) RETURNS TEXT AS $$ - SELECT hasnt_table( $1, 'Table ' || $1 || ' should not exist' ); + SELECT hasnt_table( $1, 'Table ' || quote_ident($1) || ' should not exist' ); $$ LANGUAGE SQL; -- has_view( schema, view, description ) @@ -834,7 +834,7 @@ $$ LANGUAGE SQL; -- has_view( view ) CREATE OR REPLACE FUNCTION has_view ( NAME ) RETURNS TEXT AS $$ - SELECT has_view( $1, 'View ' || $1 || ' should exist' ); + SELECT has_view( $1, 'View ' || quote_ident($1) || ' should exist' ); $$ LANGUAGE SQL; -- hasnt_view( schema, view, description ) @@ -852,7 +852,7 @@ $$ LANGUAGE SQL; -- hasnt_view( view ) CREATE OR REPLACE FUNCTION hasnt_view ( NAME ) RETURNS TEXT AS $$ - SELECT hasnt_view( $1, 'View ' || $1 || ' should not exist' ); + SELECT hasnt_view( $1, 'View ' || quote_ident($1) || ' should not exist' ); $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _cexists ( NAME, NAME, NAME ) @@ -899,7 +899,7 @@ $$ LANGUAGE SQL; -- has_column( table, column ) CREATE OR REPLACE FUNCTION has_column ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT has_column( $1, $2, 'Column ' || $1 || '.' || $2 || ' should exist' ); + SELECT has_column( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); $$ LANGUAGE SQL; -- hasnt_column( schema, table, column, description ) @@ -917,7 +917,7 @@ $$ LANGUAGE SQL; -- hasnt_column( table, column ) CREATE OR REPLACE FUNCTION hasnt_column ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT hasnt_column( $1, $2, 'Column ' || $1 || '.' || $2 || ' should not exist' ); + 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 ) @@ -972,7 +972,7 @@ $$ LANGUAGE SQL; -- col_not_null( table, column ) CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, 'Column ' || $1 || '.' || $2 || ' should be NOT NULL', true ); + SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should be NOT NULL', true ); $$ LANGUAGE SQL; -- col_is_null( schema, table, column, description ) @@ -990,7 +990,7 @@ $$ LANGUAGE SQL; -- col_is_null( table, column ) CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, 'Column ' || $1 || '.' || $2 || ' should allow NULL', false ); + SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should allow NULL', false ); $$ LANGUAGE SQL; -- col_type_is( schema, table, column, type, description ) @@ -1045,7 +1045,7 @@ $$ 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 ' || $1 || '.' || $2 || ' should be type ' || $3 ); + 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 ) @@ -1078,7 +1078,7 @@ RETURNS TEXT AS $$ BEGIN IF NOT _cexists( $1, $2, $3 ) THEN RETURN fail( $4 ) || E'\n' - || diag (' Column ' || $1 || '.' || $2 || '.' || $3 || ' does not exist' ); + || 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 @@ -1090,7 +1090,7 @@ RETURNS TEXT AS $$ BEGIN IF NOT _cexists( $1, $2 ) THEN RETURN fail( $3 ) || E'\n' - || diag (' Column ' || $1 || '.' || $2 || ' does not exist' ); + || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' ); END IF; RETURN ok( _has_def( $1, $2 ), $3 ); END; @@ -1099,7 +1099,7 @@ $$ 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 ' || $1 || '.' || $2 || ' should have a default' ); + 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 ) @@ -1108,7 +1108,7 @@ RETURNS TEXT AS $$ BEGIN IF NOT _cexists( $1, $2, $3 ) THEN RETURN fail( $4 ) || E'\n' - || diag (' Column ' || $1 || '.' || $2 || '.' || $3 || ' does not exist' ); + || 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; @@ -1120,7 +1120,7 @@ RETURNS TEXT AS $$ BEGIN IF NOT _cexists( $1, $2 ) THEN RETURN fail( $3 ) || E'\n' - || diag (' Column ' || $1 || '.' || $2 || ' does not exist' ); + || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' ); END IF; RETURN ok( NOT _has_def( $1, $2 ), $3 ); END; @@ -1129,7 +1129,7 @@ $$ 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 ' || $1 || '.' || $2 || ' should not have a default' ); + 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 ) @@ -1157,12 +1157,12 @@ RETURNS TEXT AS $$ BEGIN IF NOT _cexists( $1, $2, $3 ) THEN RETURN fail( $5 ) || E'\n' - || diag (' Column ' || $1 || '.' || $2 || '.' || $3 || ' does not exist' ); + || 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 ' || $1 || '.' || $2 || '.' || $3 || ' has no default' ); + || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' has no default' ); END IF; RETURN _def_is( @@ -1191,12 +1191,12 @@ RETURNS TEXT AS $$ BEGIN IF NOT _cexists( $1, $2 ) THEN RETURN fail( $4 ) || E'\n' - || diag (' Column ' || $1 || '.' || $2 || ' does not exist' ); + || 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 ' || $1 || '.' || $2 || ' has no default' ); + || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || ' has no default' ); END IF; RETURN _def_is( @@ -1222,7 +1222,7 @@ CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, anyelement ) RETURNS TEXT AS $$ SELECT col_default_is( $1, $2, $3, - 'Column ' || $1 || '.' || $2 || ' should default to ' + 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should default to ' || COALESCE( quote_literal($3), 'NULL') ); $$ LANGUAGE sql; @@ -1272,7 +1272,7 @@ $$ LANGUAGE sql; -- has_pk( table ) CREATE OR REPLACE FUNCTION has_pk ( NAME ) RETURNS TEXT AS $$ - SELECT has_pk( $1, 'Table ' || $1 || ' should have a primary key' ); + SELECT has_pk( $1, 'Table ' || quote_ident($1) || ' should have a primary key' ); $$ LANGUAGE sql; -- hasnt_pk( schema, table, description ) @@ -1290,7 +1290,7 @@ $$ LANGUAGE sql; -- hasnt_pk( table ) CREATE OR REPLACE FUNCTION hasnt_pk ( NAME ) RETURNS TEXT AS $$ - SELECT hasnt_pk( $1, 'Table ' || $1 || ' should not have a primary key' ); + SELECT hasnt_pk( $1, 'Table ' || quote_ident($1) || ' should not have a primary key' ); $$ LANGUAGE sql; -- _ckeys( schema, table, constraint_type ) @@ -1429,7 +1429,7 @@ $$ 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 ' || $1 || '(' || array_to_string($2, ', ') || ') should be a primary key' ); + SELECT col_is_pk( $1, $2, 'Columns ' || quote_ident($1) || '(' || array_to_string($2, ', ') || ') should be a primary key' ); $$ LANGUAGE sql; -- col_is_pk( schema, table, column, description ) @@ -1447,7 +1447,7 @@ $$ 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 ' || $1 || '(' || $2 || ') should be a primary key' ); + 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 ) @@ -1465,7 +1465,7 @@ $$ 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 ' || $1 || '(' || array_to_string($2, ', ') || ') should not be a primary key' ); + SELECT col_isnt_pk( $1, $2, 'Columns ' || quote_ident($1) || '(' || array_to_string($2, ', ') || ') should not be a primary key' ); $$ LANGUAGE sql; -- col_isnt_pk( schema, table, column, description ) @@ -1483,7 +1483,7 @@ $$ 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 ' || $1 || '(' || $2 || ') should not be a primary key' ); + 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 ) @@ -1501,7 +1501,7 @@ $$ LANGUAGE sql; -- has_fk( table ) CREATE OR REPLACE FUNCTION has_fk ( NAME ) RETURNS TEXT AS $$ - SELECT has_fk( $1, 'Table ' || $1 || ' should have a foreign key constraint' ); + SELECT has_fk( $1, 'Table ' || quote_ident($1) || ' should have a foreign key constraint' ); $$ LANGUAGE sql; -- hasnt_fk( schema, table, description ) @@ -1519,7 +1519,7 @@ $$ LANGUAGE sql; -- hasnt_fk( table ) CREATE OR REPLACE FUNCTION hasnt_fk ( NAME ) RETURNS TEXT AS $$ - SELECT hasnt_fk( $1, 'Table ' || $1 || ' should not have a foreign key constraint' ); + 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[] ) @@ -1564,14 +1564,14 @@ BEGIN IF NAMES[1] IS NOT NULL THEN RETURN fail($4) || E'\n' || diag( - ' Table ' || $1 || '.' || $2 || E' has foreign key constraints on these columns:\n ' + ' 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 ' || $1 || '.' || $2 || ' has no foreign key columns' + ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' has no foreign key columns' ); END; $$ LANGUAGE plpgsql; @@ -1596,14 +1596,14 @@ BEGIN IF NAMES[1] IS NOT NULL THEN RETURN fail($3) || E'\n' || diag( - ' Table ' || $1 || E' has foreign key constraints on these columns:\n ' + ' 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 ' || $1 || ' has no foreign key columns' + ' Table ' || quote_ident($1) || ' has no foreign key columns' ); END; $$ LANGUAGE plpgsql; @@ -1611,7 +1611,7 @@ $$ 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 ' || $1 || '(' || array_to_string($2, ', ') || ') should be a foreign key' ); + SELECT col_is_fk( $1, $2, 'Columns ' || quote_ident($1) || '(' || array_to_string($2, ', ') || ') should be a foreign key' ); $$ LANGUAGE sql; -- col_is_fk( schema, table, column, description ) @@ -1629,7 +1629,7 @@ $$ 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 ' || $1 || '(' || $2 || ') should be a foreign key' ); + 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 ) @@ -1647,7 +1647,7 @@ $$ 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 ' || $1 || '(' || array_to_string($2, ', ') || ') should not be a foreign key' ); + SELECT col_isnt_fk( $1, $2, 'Columns ' || quote_ident($1) || '(' || array_to_string($2, ', ') || ') should not be a foreign key' ); $$ LANGUAGE sql; -- col_isnt_fk( schema, table, column, description ) @@ -1665,7 +1665,7 @@ $$ 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 ' || $1 || '(' || $2 || ') should not be a foreign key' ); + 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 ) @@ -1683,7 +1683,7 @@ $$ LANGUAGE sql; -- has_unique( table ) CREATE OR REPLACE FUNCTION has_unique ( TEXT ) RETURNS TEXT AS $$ - SELECT has_unique( $1, 'Table ' || $1 || ' should have a unique constraint' ); + SELECT has_unique( $1, 'Table ' || quote_ident($1) || ' should have a unique constraint' ); $$ LANGUAGE sql; -- col_is_unique( schema, table, column, description ) @@ -1701,7 +1701,7 @@ $$ 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 ' || $1 || '(' || array_to_string($2, ', ') || ') should have a unique constraint' ); + SELECT col_is_unique( $1, $2, 'Columns ' || quote_ident($1) || '(' || array_to_string($2, ', ') || ') should have a unique constraint' ); $$ LANGUAGE sql; -- col_is_unique( schema, table, column, description ) @@ -1719,7 +1719,7 @@ $$ 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 ' || $1 || '(' || $2 || ') should have a unique constraint' ); + 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 ) @@ -1737,7 +1737,7 @@ $$ LANGUAGE sql; -- has_check( table ) CREATE OR REPLACE FUNCTION has_check ( NAME ) RETURNS TEXT AS $$ - SELECT has_check( $1, 'Table ' || $1 || ' should have a check constraint' ); + SELECT has_check( $1, 'Table ' || quote_ident($1) || ' should have a check constraint' ); $$ LANGUAGE sql; -- col_has_check( schema, table, column, description ) @@ -1755,7 +1755,7 @@ $$ 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 ' || $1 || '(' || array_to_string($2, ', ') || ') should have a check constraint' ); + SELECT col_has_check( $1, $2, 'Columns ' || quote_ident($1) || '(' || array_to_string($2, ', ') || ') should have a check constraint' ); $$ LANGUAGE sql; -- col_has_check( schema, table, column, description ) @@ -1773,7 +1773,7 @@ $$ 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 ' || $1 || '(' || $2 || ') should have a check constraint' ); + 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 ) @@ -1793,10 +1793,10 @@ BEGIN RETURN is( -- have - $1 || '.' || $2 || '(' || array_to_string( $3, ', ' ) + quote_ident($1) || '.' || quote_ident($2) || '(' || array_to_string( $3, ', ' ) || ') REFERENCES ' || COALESCE ( sch || '.' || tab || '(' || array_to_string( cols, ', ' ) || ')', 'NOTHING' ), -- want - $1 || '.' || $2 || '(' || array_to_string( $3, ', ' ) + quote_ident($1) || '.' || quote_ident($2) || '(' || array_to_string( $3, ', ' ) || ') REFERENCES ' || $4 || '.' || $5 || '(' || array_to_string( $6, ', ' ) || ')', $7 @@ -1834,7 +1834,7 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME, NAME[], NAME, NAME, NAME[] ) RETURNS TEXT AS $$ SELECT fk_ok( $1, $2, $3, $4, $5, $6, - $1 || '.' || $2 || '(' || array_to_string( $3, ', ' ) + quote_ident($1) || '.' || quote_ident($2) || '(' || array_to_string( $3, ', ' ) || ') should reference ' || $4 || '.' || $5 || '(' || array_to_string( $6, ', ' ) || ')' ); @@ -1892,7 +1892,7 @@ $$ LANGUAGE SQL; -- can_ok( schema, func_name, args[] ) CREATE OR REPLACE FUNCTION can_ok( NAME, NAME, NAME[] ) RETURNS TEXT AS $$ - SELECT can_ok( $1, $2, $3, 'Function ' || $1 || '.' || $2 || '(' || + SELECT can_ok( $1, $2, $3, 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || array_to_string($3, ', ') || ') should exist' ); $$ LANGUAGE sql; @@ -1913,7 +1913,7 @@ $$ LANGUAGE SQL; -- can_ok( schema, func_name ) CREATE OR REPLACE FUNCTION can_ok( NAME, NAME ) RETURNS TEXT AS $$ - SELECT can_ok( $1, $2, 'Function ' || $1 || '.' || $2 || '() should exist' ); + SELECT can_ok( $1, $2, 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should exist' ); $$ LANGUAGE sql; -- can_ok( func_name, args[], description ) @@ -1933,7 +1933,7 @@ $$ LANGUAGE SQL; -- can_ok( func_name, args[] ) CREATE OR REPLACE FUNCTION can_ok( NAME, NAME[] ) RETURNS TEXT AS $$ - SELECT can_ok( $1, $2, 'Function ' || $1 || '(' || + SELECT can_ok( $1, $2, 'Function ' || quote_ident($1) || '(' || array_to_string($2, ', ') || ') should exist' ); $$ LANGUAGE sql; @@ -1953,7 +1953,7 @@ $$ LANGUAGE sql; -- can_ok( func_name ) CREATE OR REPLACE FUNCTION can_ok( NAME ) RETURNS TEXT AS $$ - SELECT can_ok( $1, 'Function ' || $1 || '() should exist' ); + SELECT can_ok( $1, 'Function ' || quote_ident($1) || '() should exist' ); $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _pg_sv_type_array( OID[] ) @@ -1986,8 +1986,8 @@ BEGIN RETURN ok( true, $3 ); END IF; RETURN ok( false, $3 ) || E'\n' || diag( - ' ' || $1 || '.' || - array_to_string( missing, E'() missing\n ' || $1 || '.') || + ' ' || quote_ident($1) || '.' || + array_to_string( missing, E'() missing\n ' || quote_ident($1) || '.') || '() missing' ); END; @@ -1996,7 +1996,7 @@ $$ LANGUAGE plpgsql; -- can( schema, func_names[] ) CREATE OR REPLACE FUNCTION can ( NAME, NAME[] ) RETURNS TEXT AS $$ - SELECT can( $1, $2, 'Schema ' || $1 || ' can' ); + SELECT can( $1, $2, 'Schema ' || quote_ident($1) || ' can' ); $$ LANGUAGE sql; -- can( func_names[], description ) @@ -2094,12 +2094,12 @@ BEGIN IF index_cols IS NULL OR index_cols = '{}'::name[] THEN RETURN ok( false, $5 ) || E'\n' - || diag( 'Index "' || $3 || '" ON ' || $1 || '.' || $2 || ' not found'); + || diag( 'Index ' || quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || ' not found'); END IF; RETURN is( - '"' || $3 || '" ON ' || $1 || '.' || $2 || '(' || array_to_string( index_cols, ', ' ) || ')', - '"' || $3 || '" ON ' || $1 || '.' || $2 || '(' || array_to_string( $4, ', ' ) || ')', + 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; @@ -2108,7 +2108,7 @@ $$ 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 "' || $3 || '" should exist' ); + SELECT has_index( $1, $2, $3, $4, 'Index ' || quote_ident($3) || ' should exist' ); $$ LANGUAGE sql; -- has_index( schema, table, index, column/expression, description ) @@ -2127,12 +2127,12 @@ BEGIN IF expr IS NULL THEN RETURN ok( false, $5 ) || E'\n' - || diag( 'Index "' || $3 || '" ON ' || $1 || '.' || $2 || ' not found'); + || diag( 'Index ' || quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || ' not found'); END IF; RETURN is( - '"' || $3 || '" ON ' || $1 || '.' || $2 || '(' || expr || ')', - '"' || $3 || '" ON ' || $1 || '.' || $2 || '(' || LOWER($4) || ')', + quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || expr || ')', + quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || LOWER($4) || ')', $5 ); END; @@ -2141,7 +2141,7 @@ $$ LANGUAGE plpgsql; -- has_index( schema, table, index, columns/expression ) CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME ) RETURNS TEXT AS $$ - SELECT has_index( $1, $2, $3, $4, 'Index "' || $3 || '" should exist' ); + SELECT has_index( $1, $2, $3, $4, 'Index ' || quote_ident($3) || ' should exist' ); $$ LANGUAGE sql; -- has_index( table, index, columns[], description ) @@ -2154,12 +2154,12 @@ BEGIN IF index_cols IS NULL OR index_cols = '{}'::name[] THEN RETURN ok( false, $4 ) || E'\n' - || diag( 'Index "' || $2 || '" ON ' || $1 || ' not found'); + || diag( 'Index ' || quote_ident($2) || ' ON ' || quote_ident($1) || ' not found'); END IF; RETURN is( - '"' || $2 || '" ON ' || $1 || '(' || array_to_string( index_cols, ', ' ) || ')', - '"' || $2 || '" ON ' || $1 || '(' || array_to_string( $3, ', ' ) || ')', + 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; @@ -2168,7 +2168,7 @@ $$ LANGUAGE plpgsql; -- has_index( table, index, columns[], description ) CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME[] ) RETURNS TEXT AS $$ - SELECT has_index( $1, $2, $3, 'Index "' || $2 || '" should exist' ); + SELECT has_index( $1, $2, $3, 'Index ' || quote_ident($2) || ' should exist' ); $$ LANGUAGE sql; -- _is_schema( schema ) @@ -2215,9 +2215,9 @@ BEGIN -- Looking for an index within a schema. have_expr := _iexpr($1, $2, $3); want_expr := lower($4); - descr := 'Index "' || $3 || '" should exist'; + descr := 'Index ' || quote_ident($3) || ' should exist'; idx := $3; - tab := $1 || '.' || $2; + tab := quote_ident($1) || '.' || quote_ident($2); ELSE -- Looking for an index without a schema spec. have_expr := _iexpr($1, $2); @@ -2229,12 +2229,12 @@ BEGIN IF have_expr IS NULL THEN RETURN ok( false, descr ) || E'\n' - || diag( 'Index "' || idx || '" ON ' || tab || ' not found'); + || diag( 'Index ' || idx || ' ON ' || tab || ' not found'); END IF; RETURN is( - '"' || idx || '" ON ' || tab || '(' || have_expr || ')', - '"' || idx || '" ON ' || tab || '(' || want_expr || ')', + quote_ident(idx) || ' ON ' || tab || '(' || have_expr || ')', + quote_ident(idx) || ' ON ' || tab || '(' || want_expr || ')', descr ); END; @@ -2247,10 +2247,10 @@ RETURNS TEXT AS $$ BEGIN IF _is_schema($1) THEN -- ( schema, table, index ) - RETURN ok( _has_index( $1, $2, $3 ), 'Index "' || $3 || '" should exist' ); + RETURN ok( _has_index( $1, $2, $3 ), 'Index ' || quote_ident($3) || ' should exist' ); ELSE -- ( table, index, column/expression ) - RETURN has_index( $1, $2, $3, 'Index "' || $2 || '" should exist' ); + RETURN has_index( $1, $2, $3, 'Index ' || quote_ident($2) || ' should exist' ); END IF; END; $$ LANGUAGE plpgsql; @@ -2267,7 +2267,7 @@ $$ LANGUAGE sql; -- has_index( table, index ) CREATE OR REPLACE FUNCTION has_index ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT ok( _has_index( $1, $2 ), 'Index "' || $2 || '" should exist' ); + SELECT ok( _has_index( $1, $2 ), 'Index ' || quote_ident($2) || ' should exist' ); $$ LANGUAGE sql; -- index_is_unique( schema, table, index, description ) @@ -2295,7 +2295,7 @@ CREATE OR REPLACE FUNCTION index_is_unique ( NAME, NAME, NAME ) RETURNS TEXT AS $$ SELECT index_is_unique( $1, $2, $3, - 'Index "' || $3 || '" should be unique' + 'Index ' || quote_ident($3) || ' should be unique' ); $$ LANGUAGE sql; @@ -2316,7 +2316,7 @@ BEGIN RETURN ok( COALESCE(res, false), - 'Index "' || $2 || '" should be unique' + 'Index ' || quote_ident($2) || ' should be unique' ); END; $$ LANGUAGE plpgsql; @@ -2337,7 +2337,7 @@ BEGIN RETURN ok( COALESCE(res, false), - 'Index "' || $1 || '" should be unique' + 'Index ' || quote_ident($1) || ' should be unique' ); END; $$ LANGUAGE plpgsql; @@ -2367,7 +2367,7 @@ CREATE OR REPLACE FUNCTION index_is_primary ( NAME, NAME, NAME ) RETURNS TEXT AS $$ SELECT index_is_primary( $1, $2, $3, - 'Index "' || $3 || '" should be on a primary key' + 'Index ' || quote_ident($3) || ' should be on a primary key' ); $$ LANGUAGE sql; @@ -2388,7 +2388,7 @@ BEGIN RETURN ok( COALESCE(res, false), - 'Index "' || $2 || '" should be on a primary key' + 'Index ' || quote_ident($2) || ' should be on a primary key' ); END; $$ LANGUAGE plpgsql; @@ -2409,7 +2409,7 @@ BEGIN RETURN ok( COALESCE(res, false), - 'Index "' || $1 || '" should be on a primary key' + 'Index ' || quote_ident($1) || ' should be on a primary key' ); END; $$ LANGUAGE plpgsql; @@ -2439,8 +2439,8 @@ CREATE OR REPLACE FUNCTION is_clustered ( NAME, NAME, NAME ) RETURNS TEXT AS $$ SELECT is_clustered( $1, $2, $3, - 'Table ' || $1 || '.' || $2 || - ' should be clustered on index "' || $3 || '"' + 'Table ' || quote_ident($1) || '.' || quote_ident($2) || + ' should be clustered on index ' || quote_ident($3) ); $$ LANGUAGE sql; @@ -2460,7 +2460,7 @@ BEGIN RETURN ok( COALESCE(res, false), - 'Table ' || $1 || ' should be clustered on index "' || $2 || '"' + 'Table ' || quote_ident($1) || ' should be clustered on index ' || quote_ident($2) ); END; $$ LANGUAGE plpgsql; @@ -2479,7 +2479,7 @@ BEGIN RETURN ok( COALESCE(res, false), - 'Table should be clustered on index "' || $1 || '"' + 'Table should be clustered on index ' || quote_ident($1) ); END; $$ LANGUAGE plpgsql; @@ -2510,7 +2510,7 @@ CREATE OR REPLACE FUNCTION index_is_type ( NAME, NAME, NAME, NAME ) RETURNS TEXT AS $$ SELECT index_is_type( $1, $2, $3, $4, - 'Index ' || $3 || ' should be a ' || $4 || ' index' + 'Index ' || quote_ident($3) || ' should be a ' || quote_ident($4) || ' index' ); $$ LANGUAGE SQL; @@ -2532,7 +2532,7 @@ BEGIN return is( aname, LOWER($3)::name, - 'Index ' || $2 || ' should be a ' || $3 || ' index' + 'Index ' || quote_ident($2) || ' should be a ' || quote_ident($3) || ' index' ); END; $$ LANGUAGE plpgsql; @@ -2553,7 +2553,7 @@ BEGIN return is( aname, LOWER($3)::name, - 'Index ' || $1 || ' should be a ' || $2 || ' index' + 'Index ' || quote_ident($1) || ' should be a ' || quote_ident($2) || ' index' ); END; $$ LANGUAGE plpgsql; @@ -2582,7 +2582,7 @@ CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME, NAME ) RETURNS TEXT AS $$ SELECT has_trigger( $1, $2, $3, - 'Table ' || $1 || '.' || $2 || ' should have trigger ' || $3 + 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have trigger ' || quote_ident($3) ); $$ LANGUAGE sql; @@ -2602,7 +2602,7 @@ BEGIN RETURN ok( COALESCE(res, false), - 'Table ' || $1 || ' should have trigger ' || $2 + 'Table ' || quote_ident($1) || ' should have trigger ' || quote_ident($2) ); END; $$ LANGUAGE plpgsql; @@ -2613,7 +2613,7 @@ RETURNS TEXT AS $$ DECLARE pname text; BEGIN - SELECT ni.nspname || '.' || p.proname + 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) @@ -2624,7 +2624,7 @@ BEGIN AND t.tgname = $3 INTO pname; - RETURN is( pname, $4 || '.' || $5, $6 ); + RETURN is( pname, quote_ident($4) || '.' || quote_ident($5), $6 ); END; $$ LANGUAGE plpgsql; @@ -2633,7 +2633,7 @@ CREATE OR REPLACE FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME ) RETURNS TEXT AS $$ SELECT trigger_is( $1, $2, $3, $4, $5, - 'Trigger ' || $3 || ' should call ' || $4 || '.' || $5 || '()' + 'Trigger ' || quote_ident($3) || ' should call ' || quote_ident($4) || '.' || quote_ident($5) || '()' ); $$ LANGUAGE sql; @@ -2661,7 +2661,7 @@ CREATE OR REPLACE FUNCTION trigger_is ( NAME, NAME, NAME ) RETURNS TEXT AS $$ SELECT trigger_is( $1, $2, $3, - 'Trigger ' || $2 || ' should call ' || $3 || '()' + 'Trigger ' || quote_ident($2) || ' should call ' || quote_ident($3) || '()' ); $$ LANGUAGE sql; @@ -2680,7 +2680,7 @@ $$ LANGUAGE sql; -- has_schema( schema ) CREATE OR REPLACE FUNCTION has_schema( NAME ) RETURNS TEXT AS $$ - SELECT has_schema( $1, 'Schema ' || $1 || ' should exist' ); + SELECT has_schema( $1, 'Schema ' || quote_ident($1) || ' should exist' ); $$ LANGUAGE sql; -- hasnt_schema( schema, desscription ) @@ -2698,7 +2698,7 @@ $$ LANGUAGE sql; -- hasnt_schema( schema ) CREATE OR REPLACE FUNCTION hasnt_schema( NAME ) RETURNS TEXT AS $$ - SELECT hasnt_schema( $1, 'Schema ' || $1 || ' should not exist' ); + SELECT hasnt_schema( $1, 'Schema ' || quote_ident($1) || ' should not exist' ); $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _has_type( NAME, NAME, CHAR[] ) @@ -2735,7 +2735,7 @@ $$ LANGUAGE sql; -- has_type( schema, type ) CREATE OR REPLACE FUNCTION has_type( NAME, NAME ) RETURNS TEXT AS $$ - SELECT has_type( $1, $2, 'Type ' || $1 || '.' || $2 || ' should exist' ); + SELECT has_type( $1, $2, 'Type ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); $$ LANGUAGE sql; -- has_type( type, description ) @@ -2747,7 +2747,7 @@ $$ LANGUAGE sql; -- has_type( type ) CREATE OR REPLACE FUNCTION has_type( NAME ) RETURNS TEXT AS $$ - SELECT ok( _has_type( $1, NULL ), ('Type ' || $1 || ' should exist')::text ); + SELECT ok( _has_type( $1, NULL ), ('Type ' || quote_ident($1) || ' should exist')::text ); $$ LANGUAGE sql; -- hasnt_type( schema, type, description ) @@ -2759,7 +2759,7 @@ $$ LANGUAGE sql; -- hasnt_type( schema, type ) CREATE OR REPLACE FUNCTION hasnt_type( NAME, NAME ) RETURNS TEXT AS $$ - SELECT hasnt_type( $1, $2, 'Type ' || $1 || '.' || $2 || ' should not exist' ); + SELECT hasnt_type( $1, $2, 'Type ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' ); $$ LANGUAGE sql; -- hasnt_type( type, description ) @@ -2771,7 +2771,7 @@ $$ LANGUAGE sql; -- hasnt_type( type ) CREATE OR REPLACE FUNCTION hasnt_type( NAME ) RETURNS TEXT AS $$ - SELECT ok( NOT _has_type( $1, NULL ), ('Type ' || $1 || ' should not exist')::text ); + SELECT ok( NOT _has_type( $1, NULL ), ('Type ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; -- has_domain( schema, domain, description ) @@ -2783,7 +2783,7 @@ $$ LANGUAGE sql; -- has_domain( schema, domain ) CREATE OR REPLACE FUNCTION has_domain( NAME, NAME ) RETURNS TEXT AS $$ - SELECT has_domain( $1, $2, 'Domain ' || $1 || '.' || $2 || ' should exist' ); + SELECT has_domain( $1, $2, 'Domain ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); $$ LANGUAGE sql; -- has_domain( domain, description ) @@ -2795,7 +2795,7 @@ $$ LANGUAGE sql; -- has_domain( domain ) CREATE OR REPLACE FUNCTION has_domain( NAME ) RETURNS TEXT AS $$ - SELECT ok( _has_type( $1, ARRAY['d'] ), ('Domain ' || $1 || ' should exist')::text ); + SELECT ok( _has_type( $1, ARRAY['d'] ), ('Domain ' || quote_ident($1) || ' should exist')::text ); $$ LANGUAGE sql; -- hasnt_domain( schema, domain, description ) @@ -2807,7 +2807,7 @@ $$ LANGUAGE sql; -- hasnt_domain( schema, domain ) CREATE OR REPLACE FUNCTION hasnt_domain( NAME, NAME ) RETURNS TEXT AS $$ - SELECT hasnt_domain( $1, $2, 'Domain ' || $1 || '.' || $2 || ' should not exist' ); + SELECT hasnt_domain( $1, $2, 'Domain ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' ); $$ LANGUAGE sql; -- hasnt_domain( domain, description ) @@ -2819,7 +2819,7 @@ $$ LANGUAGE sql; -- hasnt_domain( domain ) CREATE OR REPLACE FUNCTION hasnt_domain( NAME ) RETURNS TEXT AS $$ - SELECT ok( NOT _has_type( $1, ARRAY['d'] ), ('Domain ' || $1 || ' should not exist')::text ); + SELECT ok( NOT _has_type( $1, ARRAY['d'] ), ('Domain ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; -- has_enum( schema, enum, description ) @@ -2831,7 +2831,7 @@ $$ LANGUAGE sql; -- has_enum( schema, enum ) CREATE OR REPLACE FUNCTION has_enum( NAME, NAME ) RETURNS TEXT AS $$ - SELECT has_enum( $1, $2, 'Enum ' || $1 || '.' || $2 || ' should exist' ); + SELECT has_enum( $1, $2, 'Enum ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); $$ LANGUAGE sql; -- has_enum( enum, description ) @@ -2843,7 +2843,7 @@ $$ LANGUAGE sql; -- has_enum( enum ) CREATE OR REPLACE FUNCTION has_enum( NAME ) RETURNS TEXT AS $$ - SELECT ok( _has_type( $1, ARRAY['e'] ), ('Enum ' || $1 || ' should exist')::text ); + SELECT ok( _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should exist')::text ); $$ LANGUAGE sql; -- hasnt_enum( schema, enum, description ) @@ -2855,7 +2855,7 @@ $$ LANGUAGE sql; -- hasnt_enum( schema, enum ) CREATE OR REPLACE FUNCTION hasnt_enum( NAME, NAME ) RETURNS TEXT AS $$ - SELECT hasnt_enum( $1, $2, 'Enum ' || $1 || '.' || $2 || ' should not exist' ); + SELECT hasnt_enum( $1, $2, 'Enum ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' ); $$ LANGUAGE sql; -- hasnt_enum( enum, description ) @@ -2867,7 +2867,7 @@ $$ LANGUAGE sql; -- hasnt_enum( enum ) CREATE OR REPLACE FUNCTION hasnt_enum( NAME ) RETURNS TEXT AS $$ - SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || $1 || ' should not exist')::text ); + SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; -- enum_has_labels( schema, enum, labels, desc ) @@ -2895,7 +2895,7 @@ CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME, NAME[] ) RETURNS TEXT AS $$ SELECT enum_has_labels( $1, $2, $3, - 'Enum ' || $1 || '.' || $2 || ' should have labels (' || array_to_string( $3, ', ' ) || ')' + 'Enum ' || quote_ident($1) || '.' || quote_ident($2) || ' should have labels (' || array_to_string( $3, ', ' ) || ')' ); $$ LANGUAGE sql; @@ -2923,7 +2923,7 @@ CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME[] ) RETURNS TEXT AS $$ SELECT enum_has_labels( $1, $2, - 'Enum ' || $1 || ' should have labels (' || array_to_string( $2, ', ' ) || ')' + 'Enum ' || quote_ident($1) || ' should have labels (' || array_to_string( $2, ', ' ) || ')' ); $$ LANGUAGE sql; diff --git a/sql/hastap.sql b/sql/hastap.sql index 9057893465bb..521f47eb4a4d 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -32,7 +32,7 @@ SELECT * FROM check_test( has_schema( '__SDFSDFD__' ), false, 'has_schema(non-existent schema)', - 'Schema __SDFSDFD__ should exist', + 'Schema "__SDFSDFD__" should exist', '' ); SELECT * FROM check_test( @@ -64,7 +64,7 @@ SELECT * FROM check_test( hasnt_schema( '__SDFSDFD__' ), true, 'hasnt_schema(non-existent schema)', - 'Schema __SDFSDFD__ should not exist', + 'Schema "__SDFSDFD__" should not exist', '' ); SELECT * FROM check_test( @@ -97,7 +97,7 @@ SELECT * FROM check_test( has_table( '__SDFSDFD__' ), false, 'has_table(non-existent table)', - 'Table __SDFSDFD__ should exist', + 'Table "__SDFSDFD__" should exist', '' ); @@ -156,7 +156,7 @@ SELECT * FROM check_test( hasnt_table( '__SDFSDFD__' ), true, 'hasnt_table(non-existent table)', - 'Table __SDFSDFD__ should not exist', + 'Table "__SDFSDFD__" should not exist', '' ); @@ -199,7 +199,7 @@ SELECT * FROM check_test( has_view( '__SDFSDFD__' ), false, 'has_view(non-existent view)', - 'View __SDFSDFD__ should exist', + 'View "__SDFSDFD__" should exist', '' ); @@ -242,7 +242,7 @@ SELECT * FROM check_test( hasnt_view( '__SDFSDFD__' ), true, 'hasnt_view(non-existent view)', - 'View __SDFSDFD__ should not exist', + 'View "__SDFSDFD__" should not exist', '' ); @@ -539,7 +539,7 @@ SELECT * FROM check_test( has_column( '__SDFSDFD__', 'foo' ), false, 'has_column(non-existent tab, col)', - 'Column __SDFSDFD__.foo should exist', + 'Column "__SDFSDFD__".foo should exist', '' ); @@ -600,7 +600,7 @@ SELECT * FROM check_test( hasnt_column( '__SDFSDFD__', 'foo' ), true, 'hasnt_column(non-existent tab, col)', - 'Column __SDFSDFD__.foo should not exist', + 'Column "__SDFSDFD__".foo should not exist', '' ); diff --git a/sql/index.sql b/sql/index.sql index 33beae3b8528..0959e9238d1f 100644 --- a/sql/index.sql +++ b/sql/index.sql @@ -34,7 +34,7 @@ SELECT * FROM check_test( has_index( 'public', 'sometab', 'idx_foo', 'name'::name ), true, 'has_index() single column no desc', - 'Index "idx_foo" should exist', + 'Index idx_foo should exist', '' ); @@ -50,7 +50,7 @@ SELECT * FROM check_test( has_index( 'public', 'sometab', 'idx_bar', ARRAY['name', 'numb'] ), true, 'has_index() multi-column no desc', - 'Index "idx_bar" should exist', + 'Index idx_bar should exist', '' ); @@ -74,7 +74,7 @@ SELECT * FROM check_test( has_index( 'public', 'sometab', 'idx_baz'::name ), true, 'has_index() no cols no desc', - 'Index "idx_baz" should exist', + 'Index idx_baz should exist', '' ); @@ -90,7 +90,7 @@ SELECT * FROM check_test( has_index( 'sometab', 'idx_foo', 'name'::name ), true, 'has_index() no schema single column no desc', - 'Index "idx_foo" should exist', + 'Index idx_foo should exist', '' ); @@ -106,7 +106,7 @@ SELECT * FROM check_test( has_index( 'sometab', 'idx_bar', ARRAY['name', 'numb'] ), true, 'has_index() no schema multi-column no desc', - 'Index "idx_bar" should exist', + 'Index idx_bar should exist', '' ); @@ -122,7 +122,7 @@ SELECT * FROM check_test( has_index( 'sometab', 'idx_baz', 'LOWER(name)' ), true, 'has_index() no schema functional no desc', - 'Index "idx_baz" should exist', + 'Index idx_baz should exist', '' ); @@ -138,7 +138,7 @@ SELECT * FROM check_test( has_index( 'sometab', 'idx_baz' ), true, 'has_index() no schema or cols or desc', - 'Index "idx_baz" should exist', + 'Index idx_baz should exist', '' ); @@ -148,7 +148,7 @@ SELECT * FROM check_test( false, 'has_index() missing', 'whatever', - 'Index "blah" ON public.sometab not found' + 'Index blah ON public.sometab not found' ); SELECT * FROM check_test( @@ -156,8 +156,8 @@ SELECT * FROM check_test( false, 'has_index() invalid', 'whatever', - ' have: "idx_bar" ON public.sometab(name, numb) - want: "idx_bar" ON public.sometab(name, id)' + ' have: idx_bar ON public.sometab(name, numb) + want: idx_bar ON public.sometab(name, id)' ); SELECT * FROM check_test( @@ -165,7 +165,7 @@ SELECT * FROM check_test( false, 'has_index() missing no schema', 'whatever', - 'Index "blah" ON sometab not found' + 'Index blah ON sometab not found' ); SELECT * FROM check_test( @@ -173,8 +173,8 @@ SELECT * FROM check_test( false, 'has_index() invalid no schema', 'whatever', - ' have: "idx_bar" ON sometab(name, numb) - want: "idx_bar" ON sometab(name, id)' + ' have: idx_bar ON sometab(name, numb) + want: idx_bar ON sometab(name, id)' ); SELECT * FROM check_test( @@ -182,8 +182,8 @@ SELECT * FROM check_test( false, 'has_index() functional fail', 'whatever', - ' have: "idx_baz" ON public.sometab(lower(name)) - want: "idx_baz" ON public.sometab(lower(wank))' + ' have: idx_baz ON public.sometab(lower(name)) + want: idx_baz ON public.sometab(lower(wank))' ); SELECT * FROM check_test( @@ -191,8 +191,8 @@ SELECT * FROM check_test( false, 'has_index() functional fail no schema', 'whatever', - ' have: "idx_baz" ON sometab(lower(name)) - want: "idx_baz" ON sometab(lower(wank))' + ' have: idx_baz ON sometab(lower(name)) + want: idx_baz ON sometab(lower(wank))' ); /****************************************************************************/ @@ -209,7 +209,7 @@ SELECT * FROM check_test( index_is_unique( 'public', 'sometab', 'idx_baz' ), true, 'index_is_unique() no desc', - 'Index "idx_baz" should be unique', + 'Index idx_baz should be unique', '' ); @@ -217,7 +217,7 @@ SELECT * FROM check_test( index_is_unique( 'sometab', 'idx_baz' ), true, 'index_is_unique() no schema', - 'Index "idx_baz" should be unique', + 'Index idx_baz should be unique', '' ); @@ -225,7 +225,7 @@ SELECT * FROM check_test( index_is_unique( 'idx_baz' ), true, 'index_is_unique() index only', - 'Index "idx_baz" should be unique', + 'Index idx_baz should be unique', '' ); @@ -241,7 +241,7 @@ SELECT * FROM check_test( index_is_unique( 'public', 'sometab', 'sometab_pkey' ), true, 'index_is_unique() on pk no desc', - 'Index "sometab_pkey" should be unique', + 'Index sometab_pkey should be unique', '' ); @@ -249,7 +249,7 @@ SELECT * FROM check_test( index_is_unique( 'sometab', 'sometab_pkey' ), true, 'index_is_unique() on pk no schema', - 'Index "sometab_pkey" should be unique', + 'Index sometab_pkey should be unique', '' ); @@ -257,7 +257,7 @@ SELECT * FROM check_test( index_is_unique( 'sometab_pkey' ), true, 'index_is_unique() on pk index only', - 'Index "sometab_pkey" should be unique', + 'Index sometab_pkey should be unique', '' ); @@ -273,7 +273,7 @@ SELECT * FROM check_test( index_is_unique( 'public', 'sometab', 'idx_bar' ), false, 'index_is_unique() fail no desc', - 'Index "idx_bar" should be unique', + 'Index idx_bar should be unique', '' ); @@ -281,7 +281,7 @@ SELECT * FROM check_test( index_is_unique( 'sometab', 'idx_bar' ), false, 'index_is_unique() fail no schema', - 'Index "idx_bar" should be unique', + 'Index idx_bar should be unique', '' ); @@ -289,7 +289,7 @@ SELECT * FROM check_test( index_is_unique( 'idx_bar' ), false, 'index_is_unique() fail index only', - 'Index "idx_bar" should be unique', + 'Index idx_bar should be unique', '' ); @@ -297,7 +297,7 @@ SELECT * FROM check_test( index_is_unique( 'blahblah' ), false, 'index_is_unique() no such index', - 'Index "blahblah" should be unique', + 'Index blahblah should be unique', '' ); @@ -315,7 +315,7 @@ SELECT * FROM check_test( index_is_primary( 'public', 'sometab', 'sometab_pkey' ), true, 'index_is_primary() no desc', - 'Index "sometab_pkey" should be on a primary key', + 'Index sometab_pkey should be on a primary key', '' ); @@ -323,7 +323,7 @@ SELECT * FROM check_test( index_is_primary( 'sometab', 'sometab_pkey' ), true, 'index_is_primary() no schema', - 'Index "sometab_pkey" should be on a primary key', + 'Index sometab_pkey should be on a primary key', '' ); @@ -331,7 +331,7 @@ SELECT * FROM check_test( index_is_primary( 'sometab_pkey' ), true, 'index_is_primary() index only', - 'Index "sometab_pkey" should be on a primary key', + 'Index sometab_pkey should be on a primary key', '' ); @@ -347,7 +347,7 @@ SELECT * FROM check_test( index_is_primary( 'public', 'sometab', 'idx_baz' ), false, 'index_is_primary() fail no desc', - 'Index "idx_baz" should be on a primary key', + 'Index idx_baz should be on a primary key', '' ); @@ -355,7 +355,7 @@ SELECT * FROM check_test( index_is_primary( 'sometab', 'idx_baz' ), false, 'index_is_primary() fail no schema', - 'Index "idx_baz" should be on a primary key', + 'Index idx_baz should be on a primary key', '' ); @@ -363,7 +363,7 @@ SELECT * FROM check_test( index_is_primary( 'idx_baz' ), false, 'index_is_primary() fail index only', - 'Index "idx_baz" should be on a primary key', + 'Index idx_baz should be on a primary key', '' ); @@ -371,7 +371,7 @@ SELECT * FROM check_test( index_is_primary( 'blahblah' ), false, 'index_is_primary() no such index', - 'Index "blahblah" should be on a primary key', + 'Index blahblah should be on a primary key', '' ); @@ -389,7 +389,7 @@ SELECT * FROM check_test( is_clustered( 'public', 'sometab', 'idx_bar' ), false, 'is_clustered() fail no desc', - 'Table public.sometab should be clustered on index "idx_bar"', + 'Table public.sometab should be clustered on index idx_bar', '' ); @@ -397,7 +397,7 @@ SELECT * FROM check_test( is_clustered( 'sometab', 'idx_bar' ), false, 'is_clustered() fail no schema', - 'Table sometab should be clustered on index "idx_bar"', + 'Table sometab should be clustered on index idx_bar', '' ); @@ -405,7 +405,7 @@ SELECT * FROM check_test( is_clustered( 'idx_bar' ), false, 'is_clustered() fail index only', - 'Table should be clustered on index "idx_bar"', + 'Table should be clustered on index idx_bar', '' ); @@ -422,7 +422,7 @@ SELECT * FROM check_test( is_clustered( 'public', 'sometab', 'idx_bar' ), true, 'is_clustered() no desc', - 'Table public.sometab should be clustered on index "idx_bar"', + 'Table public.sometab should be clustered on index idx_bar', '' ); @@ -430,7 +430,7 @@ SELECT * FROM check_test( is_clustered( 'sometab', 'idx_bar' ), true, 'is_clustered() no schema', - 'Table sometab should be clustered on index "idx_bar"', + 'Table sometab should be clustered on index idx_bar', '' ); @@ -438,7 +438,7 @@ SELECT * FROM check_test( is_clustered( 'idx_bar' ), true, 'is_clustered() index only', - 'Table should be clustered on index "idx_bar"', + 'Table should be clustered on index idx_bar', '' ); From 00c4a005361f2eed01f477a255faea8ef22ef5e8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 5 Feb 2009 21:57:20 +0000 Subject: [PATCH 0279/1195] Modified `col_not_null()`, `col_is_null()` and `col_type_is()` to emit diagnostics if they fail because the column in question does not exist. --- Changes | 3 + README.pgtap | 15 ++- expected/coltap.out | 236 ++++++++++++++++++++++++-------------------- pgtap.sql.in | 30 +++++- sql/coltap.sql | 53 +++++++++- 5 files changed, 219 insertions(+), 118 deletions(-) diff --git a/Changes b/Changes index e720aad6dcc3..61c275480e59 100644 --- a/Changes +++ b/Changes @@ -18,6 +18,9 @@ Revision history for pgTAP database objects to use `quote_ident()` to quote the names of those objects, rather than emitting them with or without double-quotes in an ad-hoc way as had hitherto been the case. + - Modified `col_not_null()`, `col_is_null()` and `col_type_is()` to emit + diagnostics if they fail because the column in question does not + exist. 0.16 2009-02-03T17:37:03 - Switched from a crazy trinary logic in `is()` and `isnt()` to the use diff --git a/README.pgtap b/README.pgtap index 34765984d564..4b33c0ac7feb 100644 --- a/README.pgtap +++ b/README.pgtap @@ -862,7 +862,9 @@ argument is the schema name, the second the table name, the third the column name, and the fourth is the test description. If the schema is omitted, the table must be visible in the search path. If the test description is omitted, it will be set to "Column `:table.:column` should be NOT NULL". Note that this -test will fail if the table or column in question does not exist. +test will fail with a useful diagnostic message if the table or column in +question does not exist. But use `has_column()` to make sure the column exists +first, eh? ### `col_is_null( schema, table, column, description )` ### ### `col_is_null( table, column, description )` ### @@ -880,8 +882,9 @@ column does not have a `NOT NULL` constraint. The first argument is the schema name, the second the table name, the third the column name, and the fourth is the test description. If the schema is omitted, the table must be visible in the search path. If the test description is omitted, it will be set to "Column -`:table.:column` should allow NULL". Note that this test will fail if the -table or column in question does not exist. +`:table.:column` should allow NULL". Note that this test will fail with a +useful diagnostic message if the table or column in question does not exist. +But use `has_column()` to make sure the column exists first, eh? ### `col_has_default( schema, table, column, description )` ### ### `col_has_default( table, column, description )` ### @@ -915,7 +918,7 @@ useful diagnostics to let you know: This function is the inverse of `col_has_default()`. The test passes if the specified column does *not* have a default. It will still fail if the column -does not exist. +does not exist, and emit useful diagnostics to let you know. ### `col_type_is( schema, table, column, type, description )` ### ### `col_type_is( table, column, type, description )` ### @@ -1016,6 +1019,10 @@ Will produce something like this: # have: NULL # want: foo +And if the test fails because the table or column in question does not exist, +the diagnostics will tell you that, too. But use `has_column()` to make sure +the column exists first, eh? + ### `has_pk( schema, table, description )` ### ### `has_pk( table, description )` ### ### `has_pk( table )` ### diff --git a/expected/coltap.out b/expected/coltap.out index 2f09f87695f9..a00bf5a9dcb1 100644 --- a/expected/coltap.out +++ b/expected/coltap.out @@ -1,5 +1,5 @@ \unset ECHO -1..135 +1..153 ok 1 - col_not_null( sch, tab, col, desc ) should pass ok 2 - col_not_null( sch, tab, col, desc ) should have the proper description ok 3 - col_not_null( sch, tab, col, desc ) should have the proper diagnostics @@ -12,84 +12,84 @@ ok 9 - col_not_null( table, column ) should have the proper diagnostics ok 10 - col_not_null( table, column ) fail should fail ok 11 - col_not_null( table, column ) fail should have the proper description ok 12 - col_not_null( table, column ) fail should have the proper diagnostics -ok 13 - col_is_null( sch, tab, col, desc ) should pass -ok 14 - col_is_null( sch, tab, col, desc ) should have the proper description -ok 15 - col_is_null( sch, tab, col, desc ) should have the proper diagnostics -ok 16 - col_is_null( tab, col, desc ) should pass -ok 17 - col_is_null( tab, col, desc ) should have the proper description -ok 18 - col_is_null( tab, col, desc ) should have the proper diagnostics -ok 19 - col_is_null( tab, col ) should pass -ok 20 - col_is_null( tab, col ) should have the proper description -ok 21 - col_is_null( tab, col ) should have the proper diagnostics -ok 22 - col_is_null( tab, col ) fail should fail -ok 23 - col_is_null( tab, col ) fail should have the proper description -ok 24 - col_is_null( tab, col ) fail should have the proper diagnostics -ok 25 - col_type_is( sch, tab, col, type, desc ) should pass -ok 26 - col_type_is( sch, tab, col, type, desc ) should have the proper description -ok 27 - col_type_is( sch, tab, col, type, desc ) should have the proper diagnostics -ok 28 - col_type_is( tab, col, type, desc ) should pass -ok 29 - col_type_is( tab, col, type, desc ) should have the proper description -ok 30 - col_type_is( tab, col, type, desc ) should have the proper diagnostics -ok 31 - col_type_is( tab, col, type ) should pass -ok 32 - col_type_is( tab, col, type ) should have the proper description -ok 33 - col_type_is( tab, col, type ) should have the proper diagnostics -ok 34 - col_type_is( tab, col, type ) insensitive should pass -ok 35 - col_type_is( tab, col, type ) insensitive should have the proper description -ok 36 - col_type_is( tab, col, type ) insensitive should have the proper diagnostics -ok 37 - col_type_is( tab, col, type ) fail should fail -ok 38 - col_type_is( tab, col, type ) fail should have the proper description -ok 39 - col_type_is( tab, col, type ) fail should have the proper diagnostics -ok 40 - col_type_is with precision should pass -ok 41 - col_type_is with precision should have the proper description -ok 42 - col_type_is with precision should have the proper diagnostics -ok 43 - col_type_is precision fail should fail -ok 44 - col_type_is precision fail should have the proper description -ok 45 - col_type_is precision fail should have the proper diagnostics -ok 46 - col_has_default( sch, tab, col, desc ) should pass -ok 47 - col_has_default( sch, tab, col, desc ) should have the proper description -ok 48 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics -ok 49 - col_has_default( tab, col, desc ) should pass -ok 50 - col_has_default( tab, col, desc ) should have the proper description -ok 51 - col_has_default( tab, col, desc ) should have the proper diagnostics -ok 52 - col_has_default( tab, col ) should pass -ok 53 - col_has_default( tab, col ) should have the proper description -ok 54 - col_has_default( tab, col ) should have the proper diagnostics -ok 55 - col_has_default( sch, tab, col, desc ) should fail -ok 56 - col_has_default( sch, tab, col, desc ) should have the proper description -ok 57 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics -ok 58 - col_has_default( tab, col, desc ) should fail -ok 59 - col_has_default( tab, col, desc ) should have the proper description -ok 60 - col_has_default( tab, col, desc ) should have the proper diagnostics -ok 61 - col_has_default( tab, col ) should fail -ok 62 - col_has_default( tab, col ) should have the proper description -ok 63 - col_has_default( tab, col ) should have the proper diagnostics -ok 64 - col_has_default( sch, tab, col, desc ) should fail +ok 13 - col_not_null( sch, tab, noncol, desc ) should fail +ok 14 - col_not_null( sch, tab, noncol, desc ) should have the proper description +ok 15 - col_not_null( sch, tab, noncol, desc ) should have the proper diagnostics +ok 16 - col_not_null( table, noncolumn ) fail should fail +ok 17 - col_not_null( table, noncolumn ) fail should have the proper description +ok 18 - col_not_null( table, noncolumn ) fail should have the proper diagnostics +ok 19 - col_is_null( sch, tab, col, desc ) should pass +ok 20 - col_is_null( sch, tab, col, desc ) should have the proper description +ok 21 - col_is_null( sch, tab, col, desc ) should have the proper diagnostics +ok 22 - col_is_null( tab, col, desc ) should pass +ok 23 - col_is_null( tab, col, desc ) should have the proper description +ok 24 - col_is_null( tab, col, desc ) should have the proper diagnostics +ok 25 - col_is_null( tab, col ) should pass +ok 26 - col_is_null( tab, col ) should have the proper description +ok 27 - col_is_null( tab, col ) should have the proper diagnostics +ok 28 - col_is_null( tab, col ) fail should fail +ok 29 - col_is_null( tab, col ) fail should have the proper description +ok 30 - col_is_null( tab, col ) fail should have the proper diagnostics +ok 31 - col_is_null( sch, tab, noncol, desc ) should fail +ok 32 - col_is_null( sch, tab, noncol, desc ) should have the proper description +ok 33 - col_is_null( sch, tab, noncol, desc ) should have the proper diagnostics +ok 34 - col_is_null( table, noncolumn ) fail should fail +ok 35 - col_is_null( table, noncolumn ) fail should have the proper description +ok 36 - col_is_null( table, noncolumn ) fail should have the proper diagnostics +ok 37 - col_type_is( sch, tab, col, type, desc ) should pass +ok 38 - col_type_is( sch, tab, col, type, desc ) should have the proper description +ok 39 - col_type_is( sch, tab, col, type, desc ) should have the proper diagnostics +ok 40 - col_type_is( tab, col, type, desc ) should pass +ok 41 - col_type_is( tab, col, type, desc ) should have the proper description +ok 42 - col_type_is( tab, col, type, desc ) should have the proper diagnostics +ok 43 - col_type_is( tab, col, type ) should pass +ok 44 - col_type_is( tab, col, type ) should have the proper description +ok 45 - col_type_is( tab, col, type ) should have the proper diagnostics +ok 46 - col_type_is( tab, col, type ) insensitive should pass +ok 47 - col_type_is( tab, col, type ) insensitive should have the proper description +ok 48 - col_type_is( tab, col, type ) insensitive should have the proper diagnostics +ok 49 - col_type_is( tab, col, type ) fail should fail +ok 50 - col_type_is( tab, col, type ) fail should have the proper description +ok 51 - col_type_is( tab, col, type ) fail should have the proper diagnostics +ok 52 - col_type_is( tab, noncol, type ) fail should fail +ok 53 - col_type_is( tab, noncol, type ) fail should have the proper description +ok 54 - col_type_is( tab, noncol, type ) fail should have the proper diagnostics +ok 55 - col_type_is( sch, tab, noncol, type, desc ) fail should fail +ok 56 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper description +ok 57 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper diagnostics +ok 58 - col_type_is with precision should pass +ok 59 - col_type_is with precision should have the proper description +ok 60 - col_type_is with precision should have the proper diagnostics +ok 61 - col_type_is precision fail should fail +ok 62 - col_type_is precision fail should have the proper description +ok 63 - col_type_is precision fail should have the proper diagnostics +ok 64 - col_has_default( sch, tab, col, desc ) should pass ok 65 - col_has_default( sch, tab, col, desc ) should have the proper description ok 66 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics -ok 67 - col_has_default( tab, col, desc ) should fail +ok 67 - col_has_default( tab, col, desc ) should pass ok 68 - col_has_default( tab, col, desc ) should have the proper description ok 69 - col_has_default( tab, col, desc ) should have the proper diagnostics -ok 70 - col_has_default( tab, col ) should fail +ok 70 - col_has_default( tab, col ) should pass ok 71 - col_has_default( tab, col ) should have the proper description ok 72 - col_has_default( tab, col ) should have the proper diagnostics -ok 73 - col_hasnt_default( sch, tab, col, desc ) should fail -ok 74 - col_hasnt_default( sch, tab, col, desc ) should have the proper description -ok 75 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics -ok 76 - col_hasnt_default( tab, col, desc ) should fail -ok 77 - col_hasnt_default( tab, col, desc ) should have the proper description -ok 78 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics -ok 79 - col_hasnt_default( tab, col ) should fail -ok 80 - col_hasnt_default( tab, col ) should have the proper description -ok 81 - col_hasnt_default( tab, col ) should have the proper diagnostics -ok 82 - col_hasnt_default( sch, tab, col, desc ) should pass -ok 83 - col_hasnt_default( sch, tab, col, desc ) should have the proper description -ok 84 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics -ok 85 - col_hasnt_default( tab, col, desc ) should pass -ok 86 - col_hasnt_default( tab, col, desc ) should have the proper description -ok 87 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics -ok 88 - col_hasnt_default( tab, col ) should pass -ok 89 - col_hasnt_default( tab, col ) should have the proper description -ok 90 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 73 - col_has_default( sch, tab, col, desc ) should fail +ok 74 - col_has_default( sch, tab, col, desc ) should have the proper description +ok 75 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics +ok 76 - col_has_default( tab, col, desc ) should fail +ok 77 - col_has_default( tab, col, desc ) should have the proper description +ok 78 - col_has_default( tab, col, desc ) should have the proper diagnostics +ok 79 - col_has_default( tab, col ) should fail +ok 80 - col_has_default( tab, col ) should have the proper description +ok 81 - col_has_default( tab, col ) should have the proper diagnostics +ok 82 - col_has_default( sch, tab, col, desc ) should fail +ok 83 - col_has_default( sch, tab, col, desc ) should have the proper description +ok 84 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics +ok 85 - col_has_default( tab, col, desc ) should fail +ok 86 - col_has_default( tab, col, desc ) should have the proper description +ok 87 - col_has_default( tab, col, desc ) should have the proper diagnostics +ok 88 - col_has_default( tab, col ) should fail +ok 89 - col_has_default( tab, col ) should have the proper description +ok 90 - col_has_default( tab, col ) should have the proper diagnostics ok 91 - col_hasnt_default( sch, tab, col, desc ) should fail ok 92 - col_hasnt_default( sch, tab, col, desc ) should have the proper description ok 93 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics @@ -99,39 +99,57 @@ ok 96 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics ok 97 - col_hasnt_default( tab, col ) should fail ok 98 - col_hasnt_default( tab, col ) should have the proper description ok 99 - col_hasnt_default( tab, col ) should have the proper diagnostics -ok 100 - col_default_is( sch, tab, col, def, desc ) should pass -ok 101 - col_default_is( sch, tab, col, def, desc ) should have the proper description -ok 102 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics -ok 103 - col_default_is() fail should fail -ok 104 - col_default_is() fail should have the proper description -ok 105 - col_default_is() fail should have the proper diagnostics -ok 106 - col_default_is( tab, col, def, desc ) should pass -ok 107 - col_default_is( tab, col, def, desc ) should have the proper description -ok 108 - col_default_is( tab, col, def, desc ) should have the proper diagnostics -ok 109 - col_default_is( tab, col, def ) should pass -ok 110 - col_default_is( tab, col, def ) should have the proper description -ok 111 - col_default_is( tab, col, def ) should have the proper diagnostics -ok 112 - col_default_is( tab, col, int ) should pass -ok 113 - col_default_is( tab, col, int ) should have the proper description -ok 114 - col_default_is( tab, col, int ) should have the proper diagnostics -ok 115 - col_default_is( tab, col, NULL, desc ) should pass -ok 116 - col_default_is( tab, col, NULL, desc ) should have the proper description -ok 117 - col_default_is( tab, col, NULL, desc ) should have the proper diagnostics -ok 118 - col_default_is( tab, col, NULL ) should pass -ok 119 - col_default_is( tab, col, NULL ) should have the proper description -ok 120 - col_default_is( tab, col, NULL ) should have the proper diagnostics -ok 121 - col_default_is( tab, col, bogus, desc ) should fail -ok 122 - col_default_is( tab, col, bogus, desc ) should have the proper description -ok 123 - col_default_is( tab, col, bogus, desc ) should have the proper diagnostics -ok 124 - col_default_is( tab, col, bogus ) should fail -ok 125 - col_default_is( tab, col, bogus ) should have the proper description -ok 126 - col_default_is( tab, col, bogus ) should have the proper diagnostics -ok 127 - col_default_is( sch, tab, col, def, desc ) should fail -ok 128 - col_default_is( sch, tab, col, def, desc ) should have the proper description -ok 129 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics -ok 130 - col_default_is( tab, col, def, desc ) should fail -ok 131 - col_default_is( tab, col, def, desc ) should have the proper description -ok 132 - col_default_is( tab, col, def, desc ) should have the proper diagnostics -ok 133 - col_default_is( tab, col, def ) should fail -ok 134 - col_default_is( tab, col, def ) should have the proper description -ok 135 - col_default_is( tab, col, def ) should have the proper diagnostics +ok 100 - col_hasnt_default( sch, tab, col, desc ) should pass +ok 101 - col_hasnt_default( sch, tab, col, desc ) should have the proper description +ok 102 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics +ok 103 - col_hasnt_default( tab, col, desc ) should pass +ok 104 - col_hasnt_default( tab, col, desc ) should have the proper description +ok 105 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics +ok 106 - col_hasnt_default( tab, col ) should pass +ok 107 - col_hasnt_default( tab, col ) should have the proper description +ok 108 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 109 - col_hasnt_default( sch, tab, col, desc ) should fail +ok 110 - col_hasnt_default( sch, tab, col, desc ) should have the proper description +ok 111 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics +ok 112 - col_hasnt_default( tab, col, desc ) should fail +ok 113 - col_hasnt_default( tab, col, desc ) should have the proper description +ok 114 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics +ok 115 - col_hasnt_default( tab, col ) should fail +ok 116 - col_hasnt_default( tab, col ) should have the proper description +ok 117 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 118 - col_default_is( sch, tab, col, def, desc ) should pass +ok 119 - col_default_is( sch, tab, col, def, desc ) should have the proper description +ok 120 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics +ok 121 - col_default_is() fail should fail +ok 122 - col_default_is() fail should have the proper description +ok 123 - col_default_is() fail should have the proper diagnostics +ok 124 - col_default_is( tab, col, def, desc ) should pass +ok 125 - col_default_is( tab, col, def, desc ) should have the proper description +ok 126 - col_default_is( tab, col, def, desc ) should have the proper diagnostics +ok 127 - col_default_is( tab, col, def ) should pass +ok 128 - col_default_is( tab, col, def ) should have the proper description +ok 129 - col_default_is( tab, col, def ) should have the proper diagnostics +ok 130 - col_default_is( tab, col, int ) should pass +ok 131 - col_default_is( tab, col, int ) should have the proper description +ok 132 - col_default_is( tab, col, int ) should have the proper diagnostics +ok 133 - col_default_is( tab, col, NULL, desc ) should pass +ok 134 - col_default_is( tab, col, NULL, desc ) should have the proper description +ok 135 - col_default_is( tab, col, NULL, desc ) should have the proper diagnostics +ok 136 - col_default_is( tab, col, NULL ) should pass +ok 137 - col_default_is( tab, col, NULL ) should have the proper description +ok 138 - col_default_is( tab, col, NULL ) should have the proper diagnostics +ok 139 - col_default_is( tab, col, bogus, desc ) should fail +ok 140 - col_default_is( tab, col, bogus, desc ) should have the proper description +ok 141 - col_default_is( tab, col, bogus, desc ) should have the proper diagnostics +ok 142 - col_default_is( tab, col, bogus ) should fail +ok 143 - col_default_is( tab, col, bogus ) should have the proper description +ok 144 - col_default_is( tab, col, bogus ) should have the proper diagnostics +ok 145 - col_default_is( sch, tab, col, def, desc ) should fail +ok 146 - col_default_is( sch, tab, col, def, desc ) should have the proper description +ok 147 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics +ok 148 - col_default_is( tab, col, def, desc ) should fail +ok 149 - col_default_is( tab, col, def, desc ) should have the proper description +ok 150 - col_default_is( tab, col, def, desc ) should have the proper diagnostics +ok 151 - col_default_is( tab, col, def ) should fail +ok 152 - col_default_is( tab, col, def ) should have the proper description +ok 153 - col_default_is( tab, col, def ) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index c58834d6fe1a..e6acb5b41e06 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -923,7 +923,12 @@ $$ 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 $$ - SELECT ok( +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( EXISTS( SELECT true FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a @@ -937,12 +942,18 @@ RETURNS TEXT AS $$ AND a.attnotnull = $5 ), $4 ); -$$ LANGUAGE SQL; +END; +$$ LANGUAGE plpgsql; -- _col_is_null( table, column, desc, null ) CREATE OR REPLACE FUNCTION _col_is_null ( NAME, NAME, TEXT, bool ) RETURNS TEXT AS $$ - SELECT ok( +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( EXISTS( SELECT true FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a @@ -955,7 +966,8 @@ RETURNS TEXT AS $$ AND a.attnotnull = $4 ), $3 ); -$$ LANGUAGE SQL; +END; +$$ LANGUAGE plpgsql; -- col_not_null( schema, table, column, description ) CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, NAME, TEXT ) @@ -1001,6 +1013,11 @@ DECLARE BEGIN -- Get the data type. IF $1 IS NULL THEN + IF NOT _cexists( $2, $3 ) THEN + RETURN fail( $5 ) || E'\n' + || diag (' Column ' || quote_ident($2) || '.' || quote_ident($3) || ' does not exist' ); + END IF; + SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) into actual_type FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c WHERE a.attrelid = c.oid @@ -1011,6 +1028,11 @@ BEGIN AND NOT attisdropped AND a.attname = $3; ELSE + 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; + SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) into actual_type FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a WHERE n.oid = c.relnamespace diff --git a/sql/coltap.sql b/sql/coltap.sql index 861780d0b8f8..1ea419f3fd6c 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(135); +SELECT plan(153); --SELECT * from no_plan(); -- This will be rolled back. :-) @@ -52,6 +52,23 @@ SELECT * FROM check_test( '' ); +-- Make sure nonexisting column is correct +SELECT * FROM check_test( + col_not_null( 'pg_catalog', 'pg_type', 'foo', 'desc' ), + false, + 'col_not_null( sch, tab, noncol, desc )', + 'desc', + ' Column pg_catalog.pg_type.foo does not exist' +); + +SELECT * FROM check_test( + col_not_null( 'sometab', 'foo' ), + false, + 'col_not_null( table, noncolumn ) fail', + 'Column sometab.foo should be NOT NULL', + ' Column sometab.foo does not exist' +); + /****************************************************************************/ -- Test col_is_null(). SELECT * FROM check_test( @@ -86,6 +103,23 @@ SELECT * FROM check_test( '' ); +-- Make sure nonexisting column is correct +SELECT * FROM check_test( + col_is_null( 'pg_catalog', 'pg_type', 'foo', 'desc' ), + false, + 'col_is_null( sch, tab, noncol, desc )', + 'desc', + ' Column pg_catalog.pg_type.foo does not exist' +); + +SELECT * FROM check_test( + col_is_null( 'sometab', 'foo' ), + false, + 'col_is_null( table, noncolumn ) fail', + 'Column sometab.foo should allow NULL', + ' Column sometab.foo does not exist' +); + /****************************************************************************/ -- Test col_type_is(). SELECT * FROM check_test( @@ -130,6 +164,23 @@ SELECT * FROM check_test( want: int4' ); +-- Make sure missing column is in diagnostics. +SELECT * FROM check_test( + col_type_is( 'sometab', 'blah', 'int4' ), + false, + 'col_type_is( tab, noncol, type ) fail', + 'Column sometab.blah should be type int4', + ' Column sometab.blah does not exist' +); + +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'blah', 'text', 'blah is text' ), + false, + 'col_type_is( sch, tab, noncol, type, desc ) fail', + 'blah is text', + ' Column public.sometab.blah does not exist' +); + /****************************************************************************/ -- Try col_type_is() with precision. SELECT * FROM check_test( From d4c406fe983ed28f974a7e1145b7652d86976a86 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 5 Feb 2009 21:57:46 +0000 Subject: [PATCH 0280/1195] Did that. --- README.pgtap | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.pgtap b/README.pgtap index 4b33c0ac7feb..bc6c0c1fe593 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2152,8 +2152,6 @@ To Do * Add options to `pg_prove` to allow it to run tests using the `runtests()` function rather than test files. -* Fix various column testing functions so that they emit useful diagnostics if - a column does not exist, like `col_has_default()` does. * Useful schema testing functions to consider adding: * `has_tablespace()` * `has_cast()` From 7bab47059323ea5790af73f7fb30fede003f8839 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 5 Feb 2009 22:24:08 +0000 Subject: [PATCH 0281/1195] Added `has_tablespace()` and `hasnt_tablespace()`. --- Changes | 1 + README.pgtap | 29 ++- expected/hastap.out | 488 +++++++++++++++++++++-------------------- pgtap.sql.in | 61 +++++- sql/hastap.sql | 66 +++++- uninstall_pgtap.sql.in | 5 + 6 files changed, 410 insertions(+), 240 deletions(-) diff --git a/Changes b/Changes index 61c275480e59..cc08e61b8fb9 100644 --- a/Changes +++ b/Changes @@ -21,6 +21,7 @@ Revision history for pgTAP - Modified `col_not_null()`, `col_is_null()` and `col_type_is()` to emit diagnostics if they fail because the column in question does not exist. + - Added `has_tablespace()` and `hasnt_tablespace()`. 0.16 2009-02-03T17:37:03 - Switched from a crazy trinary logic in `is()` and `isnt()` to the use diff --git a/README.pgtap b/README.pgtap index bc6c0c1fe593..96597c278a24 100644 --- a/README.pgtap +++ b/README.pgtap @@ -606,6 +606,34 @@ A Wicked Schema Need to make sure that your database is designed just the way you think it should be? Use these test functions and rest easy. +### `has_tablespace( tablespace, location, description )` ### +### `has_tablespace( tablespace, description )` ### +### `has_tablespace( tablespace )` ### + + SELECT has_tablespace( + 'sometablespace', + '/data/dbs', + 'I got sometablespace in /data/dbs' + ); + +This function tests whether or not a tablespace exists in the database. The +first argument is a tablespace name. The second is either the a file system +path for the database or a test description. If you specify a location path, +you must pass a description as the third argument; otherwise, if you omit the +test description, it will be set to "Tablespace `:tablespace` should exist". + +### `hasnt_tablespace( tablespace, tablespace, description )` ### +### `hasnt_tablespace( tablespace, description )` ### +### `hasnt_tablespace( tablespace )` ### + + SELECT hasnt_tablespace( + 'sometablespace', + 'There should be no tablespace sometablespace' + ); + +This function is the inverse of `has_tablespace()`. The test passes if the +specified tablespace does *not* exist. + ### `has_schema( schema, description )` ### ### `has_schema( schema )` ### @@ -2153,7 +2181,6 @@ To Do * Add options to `pg_prove` to allow it to run tests using the `runtests()` function rather than test files. * Useful schema testing functions to consider adding: - * `has_tablespace()` * `has_cast()` * `has_role()`, `has_user()`, `has_group()` * `has_operator()` diff --git a/expected/hastap.out b/expected/hastap.out index 0ae5638fb1d0..f40fe96b0cab 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,233 +1,257 @@ \unset ECHO -1..231 -ok 1 - has_schema(non-existent schema) should fail -ok 2 - has_schema(non-existent schema) should have the proper description -ok 3 - has_schema(non-existent schema) should have the proper diagnostics -ok 4 - has_schema(non-existent schema, tab) should fail -ok 5 - has_schema(non-existent schema, tab) should have the proper description -ok 6 - has_schema(non-existent schema, tab) should have the proper diagnostics -ok 7 - has_schema(schema) should pass -ok 8 - has_schema(schema) should have the proper description -ok 9 - has_schema(schema) should have the proper diagnostics -ok 10 - has_schema(schema, desc) should pass -ok 11 - has_schema(schema, desc) should have the proper description -ok 12 - has_schema(schema, desc) should have the proper diagnostics -ok 13 - hasnt_schema(non-existent schema) should pass -ok 14 - hasnt_schema(non-existent schema) should have the proper description -ok 15 - hasnt_schema(non-existent schema) should have the proper diagnostics -ok 16 - hasnt_schema(non-existent schema, tab) should pass -ok 17 - hasnt_schema(non-existent schema, tab) should have the proper description -ok 18 - hasnt_schema(non-existent schema, tab) should have the proper diagnostics -ok 19 - hasnt_schema(schema) should fail -ok 20 - hasnt_schema(schema) should have the proper description -ok 21 - hasnt_schema(schema) should have the proper diagnostics -ok 22 - hasnt_schema(schema, desc) should fail -ok 23 - hasnt_schema(schema, desc) should have the proper description -ok 24 - hasnt_schema(schema, desc) should have the proper diagnostics -ok 25 - has_table(non-existent table) should fail -ok 26 - has_table(non-existent table) should have the proper description -ok 27 - has_table(non-existent table) should have the proper diagnostics -ok 28 - has_table(non-existent schema, tab) should fail -ok 29 - has_table(non-existent schema, tab) should have the proper description -ok 30 - has_table(non-existent schema, tab) should have the proper diagnostics -ok 31 - has_table(sch, non-existent table, desc) should fail -ok 32 - has_table(sch, non-existent table, desc) should have the proper description -ok 33 - has_table(sch, non-existent table, desc) should have the proper diagnostics -ok 34 - has_table(tab, desc) should pass -ok 35 - has_table(tab, desc) should have the proper description -ok 36 - has_table(tab, desc) should have the proper diagnostics -ok 37 - has_table(sch, tab, desc) should pass -ok 38 - has_table(sch, tab, desc) should have the proper description -ok 39 - has_table(sch, tab, desc) should have the proper diagnostics -ok 40 - has_table(sch, view, desc) should fail -ok 41 - has_table(sch, view, desc) should have the proper description -ok 42 - has_table(sch, view, desc) should have the proper diagnostics -ok 43 - has_table(type, desc) should fail -ok 44 - has_table(type, desc) should have the proper description -ok 45 - has_table(type, desc) should have the proper diagnostics -ok 46 - hasnt_table(non-existent table) should pass -ok 47 - hasnt_table(non-existent table) should have the proper description -ok 48 - hasnt_table(non-existent table) should have the proper diagnostics -ok 49 - hasnt_table(non-existent schema, tab) should pass -ok 50 - hasnt_table(non-existent schema, tab) should have the proper description -ok 51 - hasnt_table(non-existent schema, tab) should have the proper diagnostics -ok 52 - hasnt_table(sch, non-existent tab, desc) should pass -ok 53 - hasnt_table(sch, non-existent tab, desc) should have the proper description -ok 54 - hasnt_table(sch, non-existent tab, desc) should have the proper diagnostics -ok 55 - hasnt_table(tab, desc) should fail -ok 56 - hasnt_table(tab, desc) should have the proper description -ok 57 - hasnt_table(tab, desc) should have the proper diagnostics -ok 58 - hasnt_table(sch, tab, desc) should fail -ok 59 - hasnt_table(sch, tab, desc) should have the proper description -ok 60 - hasnt_table(sch, tab, desc) should have the proper diagnostics -ok 61 - has_view(non-existent view) should fail -ok 62 - has_view(non-existent view) should have the proper description -ok 63 - has_view(non-existent view) should have the proper diagnostics -ok 64 - has_view(non-existent view, desc) should fail -ok 65 - has_view(non-existent view, desc) should have the proper description -ok 66 - has_view(non-existent view, desc) should have the proper diagnostics -ok 67 - has_view(sch, non-existtent view, desc) should fail -ok 68 - has_view(sch, non-existtent view, desc) should have the proper description -ok 69 - has_view(sch, non-existtent view, desc) should have the proper diagnostics -ok 70 - has_view(view, desc) should pass -ok 71 - has_view(view, desc) should have the proper description -ok 72 - has_view(view, desc) should have the proper diagnostics -ok 73 - has_view(sch, view, desc) should pass -ok 74 - has_view(sch, view, desc) should have the proper description -ok 75 - has_view(sch, view, desc) should have the proper diagnostics -ok 76 - hasnt_view(non-existent view) should pass -ok 77 - hasnt_view(non-existent view) should have the proper description -ok 78 - hasnt_view(non-existent view) should have the proper diagnostics -ok 79 - hasnt_view(non-existent view, desc) should pass -ok 80 - hasnt_view(non-existent view, desc) should have the proper description -ok 81 - hasnt_view(non-existent view, desc) should have the proper diagnostics -ok 82 - hasnt_view(sch, non-existtent view, desc) should pass -ok 83 - hasnt_view(sch, non-existtent view, desc) should have the proper description -ok 84 - hasnt_view(sch, non-existtent view, desc) should have the proper diagnostics -ok 85 - hasnt_view(view, desc) should fail -ok 86 - hasnt_view(view, desc) should have the proper description -ok 87 - hasnt_view(view, desc) should have the proper diagnostics -ok 88 - hasnt_view(sch, view, desc) should fail -ok 89 - hasnt_view(sch, view, desc) should have the proper description -ok 90 - hasnt_view(sch, view, desc) should have the proper diagnostics -ok 91 - has_type(type) should pass -ok 92 - has_type(type) should have the proper description -ok 93 - has_type(type) should have the proper diagnostics -ok 94 - has_type(type, desc) should pass -ok 95 - has_type(type, desc) should have the proper description -ok 96 - has_type(type, desc) should have the proper diagnostics -ok 97 - has_type(scheam, type) should pass -ok 98 - has_type(scheam, type) should have the proper description -ok 99 - has_type(scheam, type) should have the proper diagnostics -ok 100 - has_type(schema, type, desc) should pass -ok 101 - has_type(schema, type, desc) should have the proper description -ok 102 - has_type(schema, type, desc) should have the proper diagnostics -ok 103 - has_type(type) should fail -ok 104 - has_type(type) should have the proper description -ok 105 - has_type(type) should have the proper diagnostics -ok 106 - has_type(type, desc) should fail -ok 107 - has_type(type, desc) should have the proper description -ok 108 - has_type(type, desc) should have the proper diagnostics -ok 109 - has_type(scheam, type) should fail -ok 110 - has_type(scheam, type) should have the proper description -ok 111 - has_type(scheam, type) should have the proper diagnostics -ok 112 - has_type(schema, type, desc) should fail -ok 113 - has_type(schema, type, desc) should have the proper description -ok 114 - has_type(schema, type, desc) should have the proper diagnostics -ok 115 - has_type(domain) should pass -ok 116 - has_type(domain) should have the proper description -ok 117 - has_type(domain) should have the proper diagnostics -ok 118 - hasnt_type(type) should pass -ok 119 - hasnt_type(type) should have the proper description -ok 120 - hasnt_type(type) should have the proper diagnostics -ok 121 - hasnt_type(type, desc) should pass -ok 122 - hasnt_type(type, desc) should have the proper description -ok 123 - hasnt_type(type, desc) should have the proper diagnostics -ok 124 - hasnt_type(scheam, type) should pass -ok 125 - hasnt_type(scheam, type) should have the proper description -ok 126 - hasnt_type(scheam, type) should have the proper diagnostics -ok 127 - hasnt_type(schema, type, desc) should pass -ok 128 - hasnt_type(schema, type, desc) should have the proper description -ok 129 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 130 - hasnt_type(type) should fail -ok 131 - hasnt_type(type) should have the proper description -ok 132 - hasnt_type(type) should have the proper diagnostics -ok 133 - hasnt_type(type, desc) should fail -ok 134 - hasnt_type(type, desc) should have the proper description -ok 135 - hasnt_type(type, desc) should have the proper diagnostics -ok 136 - hasnt_type(scheam, type) should fail -ok 137 - hasnt_type(scheam, type) should have the proper description -ok 138 - hasnt_type(scheam, type) should have the proper diagnostics -ok 139 - hasnt_type(schema, type, desc) should fail -ok 140 - hasnt_type(schema, type, desc) should have the proper description -ok 141 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 142 - has_domain(domain) should pass -ok 143 - has_domain(domain) should have the proper description -ok 144 - has_domain(domain) should have the proper diagnostics -ok 145 - has_domain(domain, desc) should pass -ok 146 - has_domain(domain, desc) should have the proper description -ok 147 - has_domain(domain, desc) should have the proper diagnostics -ok 148 - has_domain(scheam, domain) should pass -ok 149 - has_domain(scheam, domain) should have the proper description -ok 150 - has_domain(scheam, domain) should have the proper diagnostics -ok 151 - has_domain(schema, domain, desc) should pass -ok 152 - has_domain(schema, domain, desc) should have the proper description -ok 153 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 154 - has_domain(domain) should fail -ok 155 - has_domain(domain) should have the proper description -ok 156 - has_domain(domain) should have the proper diagnostics -ok 157 - has_domain(domain, desc) should fail -ok 158 - has_domain(domain, desc) should have the proper description -ok 159 - has_domain(domain, desc) should have the proper diagnostics -ok 160 - has_domain(scheam, domain) should fail -ok 161 - has_domain(scheam, domain) should have the proper description -ok 162 - has_domain(scheam, domain) should have the proper diagnostics -ok 163 - has_domain(schema, domain, desc) should fail -ok 164 - has_domain(schema, domain, desc) should have the proper description -ok 165 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 166 - hasnt_domain(domain) should pass -ok 167 - hasnt_domain(domain) should have the proper description -ok 168 - hasnt_domain(domain) should have the proper diagnostics -ok 169 - hasnt_domain(domain, desc) should pass -ok 170 - hasnt_domain(domain, desc) should have the proper description -ok 171 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 172 - hasnt_domain(scheam, domain) should pass -ok 173 - hasnt_domain(scheam, domain) should have the proper description -ok 174 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 175 - hasnt_domain(schema, domain, desc) should pass -ok 176 - hasnt_domain(schema, domain, desc) should have the proper description -ok 177 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 178 - hasnt_domain(domain) should fail -ok 179 - hasnt_domain(domain) should have the proper description -ok 180 - hasnt_domain(domain) should have the proper diagnostics -ok 181 - hasnt_domain(domain, desc) should fail -ok 182 - hasnt_domain(domain, desc) should have the proper description -ok 183 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 184 - hasnt_domain(scheam, domain) should fail -ok 185 - hasnt_domain(scheam, domain) should have the proper description -ok 186 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 187 - hasnt_domain(schema, domain, desc) should fail -ok 188 - hasnt_domain(schema, domain, desc) should have the proper description -ok 189 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 190 - has_column(non-existent tab, col) should fail -ok 191 - has_column(non-existent tab, col) should have the proper description -ok 192 - has_column(non-existent tab, col) should have the proper diagnostics -ok 193 - has_column(non-existent tab, col, desc) should fail -ok 194 - has_column(non-existent tab, col, desc) should have the proper description -ok 195 - has_column(non-existent tab, col, desc) should have the proper diagnostics -ok 196 - has_column(non-existent sch, tab, col, desc) should fail -ok 197 - has_column(non-existent sch, tab, col, desc) should have the proper description -ok 198 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 199 - has_column(table, column) should pass -ok 200 - has_column(table, column) should have the proper description -ok 201 - has_column(table, column) should have the proper diagnostics -ok 202 - has_column(sch, tab, col, desc) should pass -ok 203 - has_column(sch, tab, col, desc) should have the proper description -ok 204 - has_column(sch, tab, col, desc) should have the proper diagnostics -ok 205 - has_column(view, column) should pass -ok 206 - has_column(view, column) should have the proper description -ok 207 - has_column(view, column) should have the proper diagnostics -ok 208 - has_column(type, column) should pass -ok 209 - has_column(type, column) should have the proper description -ok 210 - has_column(type, column) should have the proper diagnostics -ok 211 - hasnt_column(non-existent tab, col) should pass -ok 212 - hasnt_column(non-existent tab, col) should have the proper description -ok 213 - hasnt_column(non-existent tab, col) should have the proper diagnostics -ok 214 - hasnt_column(non-existent tab, col, desc) should pass -ok 215 - hasnt_column(non-existent tab, col, desc) should have the proper description -ok 216 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics -ok 217 - hasnt_column(non-existent sch, tab, col, desc) should pass -ok 218 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description -ok 219 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 220 - hasnt_column(table, column) should fail -ok 221 - hasnt_column(table, column) should have the proper description -ok 222 - hasnt_column(table, column) should have the proper diagnostics -ok 223 - hasnt_column(sch, tab, col, desc) should fail -ok 224 - hasnt_column(sch, tab, col, desc) should have the proper description -ok 225 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics -ok 226 - hasnt_column(view, column) should pass -ok 227 - hasnt_column(view, column) should have the proper description -ok 228 - hasnt_column(view, column) should have the proper diagnostics -ok 229 - hasnt_column(type, column) should pass -ok 230 - hasnt_column(type, column) should have the proper description -ok 231 - hasnt_column(type, column) should have the proper diagnostics +1..255 +ok 1 - has_tablespace(non-existent tablespace) should fail +ok 2 - has_tablespace(non-existent tablespace) should have the proper description +ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics +ok 4 - has_tablespace(non-existent tablespace, tab) should fail +ok 5 - has_tablespace(non-existent tablespace, tab) should have the proper description +ok 6 - has_tablespace(non-existent tablespace, tab) should have the proper diagnostics +ok 7 - has_tablespace(tablespace) should pass +ok 8 - has_tablespace(tablespace) should have the proper description +ok 9 - has_tablespace(tablespace) should have the proper diagnostics +ok 10 - has_tablespace(tablespace, desc) should pass +ok 11 - has_tablespace(tablespace, desc) should have the proper description +ok 12 - has_tablespace(tablespace, desc) should have the proper diagnostics +ok 13 - hasnt_tablespace(non-existent tablespace) should pass +ok 14 - hasnt_tablespace(non-existent tablespace) should have the proper description +ok 15 - hasnt_tablespace(non-existent tablespace) should have the proper diagnostics +ok 16 - hasnt_tablespace(non-existent tablespace, tab) should pass +ok 17 - hasnt_tablespace(non-existent tablespace, tab) should have the proper description +ok 18 - hasnt_tablespace(non-existent tablespace, tab) should have the proper diagnostics +ok 19 - hasnt_tablespace(pg_default) should fail +ok 20 - hasnt_tablespace(pg_default) should have the proper description +ok 21 - hasnt_tablespace(pg_default) should have the proper diagnostics +ok 22 - hasnt_tablespace(tablespace, desc) should fail +ok 23 - hasnt_tablespace(tablespace, desc) should have the proper description +ok 24 - hasnt_tablespace(tablespace, desc) should have the proper diagnostics +ok 25 - has_schema(non-existent schema) should fail +ok 26 - has_schema(non-existent schema) should have the proper description +ok 27 - has_schema(non-existent schema) should have the proper diagnostics +ok 28 - has_schema(non-existent schema, tab) should fail +ok 29 - has_schema(non-existent schema, tab) should have the proper description +ok 30 - has_schema(non-existent schema, tab) should have the proper diagnostics +ok 31 - has_schema(schema) should pass +ok 32 - has_schema(schema) should have the proper description +ok 33 - has_schema(schema) should have the proper diagnostics +ok 34 - has_schema(schema, desc) should pass +ok 35 - has_schema(schema, desc) should have the proper description +ok 36 - has_schema(schema, desc) should have the proper diagnostics +ok 37 - hasnt_schema(non-existent schema) should pass +ok 38 - hasnt_schema(non-existent schema) should have the proper description +ok 39 - hasnt_schema(non-existent schema) should have the proper diagnostics +ok 40 - hasnt_schema(non-existent schema, tab) should pass +ok 41 - hasnt_schema(non-existent schema, tab) should have the proper description +ok 42 - hasnt_schema(non-existent schema, tab) should have the proper diagnostics +ok 43 - hasnt_schema(schema) should fail +ok 44 - hasnt_schema(schema) should have the proper description +ok 45 - hasnt_schema(schema) should have the proper diagnostics +ok 46 - hasnt_schema(schema, desc) should fail +ok 47 - hasnt_schema(schema, desc) should have the proper description +ok 48 - hasnt_schema(schema, desc) should have the proper diagnostics +ok 49 - has_table(non-existent table) should fail +ok 50 - has_table(non-existent table) should have the proper description +ok 51 - has_table(non-existent table) should have the proper diagnostics +ok 52 - has_table(non-existent schema, tab) should fail +ok 53 - has_table(non-existent schema, tab) should have the proper description +ok 54 - has_table(non-existent schema, tab) should have the proper diagnostics +ok 55 - has_table(sch, non-existent table, desc) should fail +ok 56 - has_table(sch, non-existent table, desc) should have the proper description +ok 57 - has_table(sch, non-existent table, desc) should have the proper diagnostics +ok 58 - has_table(tab, desc) should pass +ok 59 - has_table(tab, desc) should have the proper description +ok 60 - has_table(tab, desc) should have the proper diagnostics +ok 61 - has_table(sch, tab, desc) should pass +ok 62 - has_table(sch, tab, desc) should have the proper description +ok 63 - has_table(sch, tab, desc) should have the proper diagnostics +ok 64 - has_table(sch, view, desc) should fail +ok 65 - has_table(sch, view, desc) should have the proper description +ok 66 - has_table(sch, view, desc) should have the proper diagnostics +ok 67 - has_table(type, desc) should fail +ok 68 - has_table(type, desc) should have the proper description +ok 69 - has_table(type, desc) should have the proper diagnostics +ok 70 - hasnt_table(non-existent table) should pass +ok 71 - hasnt_table(non-existent table) should have the proper description +ok 72 - hasnt_table(non-existent table) should have the proper diagnostics +ok 73 - hasnt_table(non-existent schema, tab) should pass +ok 74 - hasnt_table(non-existent schema, tab) should have the proper description +ok 75 - hasnt_table(non-existent schema, tab) should have the proper diagnostics +ok 76 - hasnt_table(sch, non-existent tab, desc) should pass +ok 77 - hasnt_table(sch, non-existent tab, desc) should have the proper description +ok 78 - hasnt_table(sch, non-existent tab, desc) should have the proper diagnostics +ok 79 - hasnt_table(tab, desc) should fail +ok 80 - hasnt_table(tab, desc) should have the proper description +ok 81 - hasnt_table(tab, desc) should have the proper diagnostics +ok 82 - hasnt_table(sch, tab, desc) should fail +ok 83 - hasnt_table(sch, tab, desc) should have the proper description +ok 84 - hasnt_table(sch, tab, desc) should have the proper diagnostics +ok 85 - has_view(non-existent view) should fail +ok 86 - has_view(non-existent view) should have the proper description +ok 87 - has_view(non-existent view) should have the proper diagnostics +ok 88 - has_view(non-existent view, desc) should fail +ok 89 - has_view(non-existent view, desc) should have the proper description +ok 90 - has_view(non-existent view, desc) should have the proper diagnostics +ok 91 - has_view(sch, non-existtent view, desc) should fail +ok 92 - has_view(sch, non-existtent view, desc) should have the proper description +ok 93 - has_view(sch, non-existtent view, desc) should have the proper diagnostics +ok 94 - has_view(view, desc) should pass +ok 95 - has_view(view, desc) should have the proper description +ok 96 - has_view(view, desc) should have the proper diagnostics +ok 97 - has_view(sch, view, desc) should pass +ok 98 - has_view(sch, view, desc) should have the proper description +ok 99 - has_view(sch, view, desc) should have the proper diagnostics +ok 100 - hasnt_view(non-existent view) should pass +ok 101 - hasnt_view(non-existent view) should have the proper description +ok 102 - hasnt_view(non-existent view) should have the proper diagnostics +ok 103 - hasnt_view(non-existent view, desc) should pass +ok 104 - hasnt_view(non-existent view, desc) should have the proper description +ok 105 - hasnt_view(non-existent view, desc) should have the proper diagnostics +ok 106 - hasnt_view(sch, non-existtent view, desc) should pass +ok 107 - hasnt_view(sch, non-existtent view, desc) should have the proper description +ok 108 - hasnt_view(sch, non-existtent view, desc) should have the proper diagnostics +ok 109 - hasnt_view(view, desc) should fail +ok 110 - hasnt_view(view, desc) should have the proper description +ok 111 - hasnt_view(view, desc) should have the proper diagnostics +ok 112 - hasnt_view(sch, view, desc) should fail +ok 113 - hasnt_view(sch, view, desc) should have the proper description +ok 114 - hasnt_view(sch, view, desc) should have the proper diagnostics +ok 115 - has_type(type) should pass +ok 116 - has_type(type) should have the proper description +ok 117 - has_type(type) should have the proper diagnostics +ok 118 - has_type(type, desc) should pass +ok 119 - has_type(type, desc) should have the proper description +ok 120 - has_type(type, desc) should have the proper diagnostics +ok 121 - has_type(scheam, type) should pass +ok 122 - has_type(scheam, type) should have the proper description +ok 123 - has_type(scheam, type) should have the proper diagnostics +ok 124 - has_type(schema, type, desc) should pass +ok 125 - has_type(schema, type, desc) should have the proper description +ok 126 - has_type(schema, type, desc) should have the proper diagnostics +ok 127 - has_type(type) should fail +ok 128 - has_type(type) should have the proper description +ok 129 - has_type(type) should have the proper diagnostics +ok 130 - has_type(type, desc) should fail +ok 131 - has_type(type, desc) should have the proper description +ok 132 - has_type(type, desc) should have the proper diagnostics +ok 133 - has_type(scheam, type) should fail +ok 134 - has_type(scheam, type) should have the proper description +ok 135 - has_type(scheam, type) should have the proper diagnostics +ok 136 - has_type(schema, type, desc) should fail +ok 137 - has_type(schema, type, desc) should have the proper description +ok 138 - has_type(schema, type, desc) should have the proper diagnostics +ok 139 - has_type(domain) should pass +ok 140 - has_type(domain) should have the proper description +ok 141 - has_type(domain) should have the proper diagnostics +ok 142 - hasnt_type(type) should pass +ok 143 - hasnt_type(type) should have the proper description +ok 144 - hasnt_type(type) should have the proper diagnostics +ok 145 - hasnt_type(type, desc) should pass +ok 146 - hasnt_type(type, desc) should have the proper description +ok 147 - hasnt_type(type, desc) should have the proper diagnostics +ok 148 - hasnt_type(scheam, type) should pass +ok 149 - hasnt_type(scheam, type) should have the proper description +ok 150 - hasnt_type(scheam, type) should have the proper diagnostics +ok 151 - hasnt_type(schema, type, desc) should pass +ok 152 - hasnt_type(schema, type, desc) should have the proper description +ok 153 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 154 - hasnt_type(type) should fail +ok 155 - hasnt_type(type) should have the proper description +ok 156 - hasnt_type(type) should have the proper diagnostics +ok 157 - hasnt_type(type, desc) should fail +ok 158 - hasnt_type(type, desc) should have the proper description +ok 159 - hasnt_type(type, desc) should have the proper diagnostics +ok 160 - hasnt_type(scheam, type) should fail +ok 161 - hasnt_type(scheam, type) should have the proper description +ok 162 - hasnt_type(scheam, type) should have the proper diagnostics +ok 163 - hasnt_type(schema, type, desc) should fail +ok 164 - hasnt_type(schema, type, desc) should have the proper description +ok 165 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 166 - has_domain(domain) should pass +ok 167 - has_domain(domain) should have the proper description +ok 168 - has_domain(domain) should have the proper diagnostics +ok 169 - has_domain(domain, desc) should pass +ok 170 - has_domain(domain, desc) should have the proper description +ok 171 - has_domain(domain, desc) should have the proper diagnostics +ok 172 - has_domain(scheam, domain) should pass +ok 173 - has_domain(scheam, domain) should have the proper description +ok 174 - has_domain(scheam, domain) should have the proper diagnostics +ok 175 - has_domain(schema, domain, desc) should pass +ok 176 - has_domain(schema, domain, desc) should have the proper description +ok 177 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 178 - has_domain(domain) should fail +ok 179 - has_domain(domain) should have the proper description +ok 180 - has_domain(domain) should have the proper diagnostics +ok 181 - has_domain(domain, desc) should fail +ok 182 - has_domain(domain, desc) should have the proper description +ok 183 - has_domain(domain, desc) should have the proper diagnostics +ok 184 - has_domain(scheam, domain) should fail +ok 185 - has_domain(scheam, domain) should have the proper description +ok 186 - has_domain(scheam, domain) should have the proper diagnostics +ok 187 - has_domain(schema, domain, desc) should fail +ok 188 - has_domain(schema, domain, desc) should have the proper description +ok 189 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 190 - hasnt_domain(domain) should pass +ok 191 - hasnt_domain(domain) should have the proper description +ok 192 - hasnt_domain(domain) should have the proper diagnostics +ok 193 - hasnt_domain(domain, desc) should pass +ok 194 - hasnt_domain(domain, desc) should have the proper description +ok 195 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 196 - hasnt_domain(scheam, domain) should pass +ok 197 - hasnt_domain(scheam, domain) should have the proper description +ok 198 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 199 - hasnt_domain(schema, domain, desc) should pass +ok 200 - hasnt_domain(schema, domain, desc) should have the proper description +ok 201 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 202 - hasnt_domain(domain) should fail +ok 203 - hasnt_domain(domain) should have the proper description +ok 204 - hasnt_domain(domain) should have the proper diagnostics +ok 205 - hasnt_domain(domain, desc) should fail +ok 206 - hasnt_domain(domain, desc) should have the proper description +ok 207 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 208 - hasnt_domain(scheam, domain) should fail +ok 209 - hasnt_domain(scheam, domain) should have the proper description +ok 210 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 211 - hasnt_domain(schema, domain, desc) should fail +ok 212 - hasnt_domain(schema, domain, desc) should have the proper description +ok 213 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 214 - has_column(non-existent tab, col) should fail +ok 215 - has_column(non-existent tab, col) should have the proper description +ok 216 - has_column(non-existent tab, col) should have the proper diagnostics +ok 217 - has_column(non-existent tab, col, desc) should fail +ok 218 - has_column(non-existent tab, col, desc) should have the proper description +ok 219 - has_column(non-existent tab, col, desc) should have the proper diagnostics +ok 220 - has_column(non-existent sch, tab, col, desc) should fail +ok 221 - has_column(non-existent sch, tab, col, desc) should have the proper description +ok 222 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 223 - has_column(table, column) should pass +ok 224 - has_column(table, column) should have the proper description +ok 225 - has_column(table, column) should have the proper diagnostics +ok 226 - has_column(sch, tab, col, desc) should pass +ok 227 - has_column(sch, tab, col, desc) should have the proper description +ok 228 - has_column(sch, tab, col, desc) should have the proper diagnostics +ok 229 - has_column(view, column) should pass +ok 230 - has_column(view, column) should have the proper description +ok 231 - has_column(view, column) should have the proper diagnostics +ok 232 - has_column(type, column) should pass +ok 233 - has_column(type, column) should have the proper description +ok 234 - has_column(type, column) should have the proper diagnostics +ok 235 - hasnt_column(non-existent tab, col) should pass +ok 236 - hasnt_column(non-existent tab, col) should have the proper description +ok 237 - hasnt_column(non-existent tab, col) should have the proper diagnostics +ok 238 - hasnt_column(non-existent tab, col, desc) should pass +ok 239 - hasnt_column(non-existent tab, col, desc) should have the proper description +ok 240 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics +ok 241 - hasnt_column(non-existent sch, tab, col, desc) should pass +ok 242 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description +ok 243 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 244 - hasnt_column(table, column) should fail +ok 245 - hasnt_column(table, column) should have the proper description +ok 246 - hasnt_column(table, column) should have the proper diagnostics +ok 247 - hasnt_column(sch, tab, col, desc) should fail +ok 248 - hasnt_column(sch, tab, col, desc) should have the proper description +ok 249 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics +ok 250 - hasnt_column(view, column) should pass +ok 251 - hasnt_column(view, column) should have the proper description +ok 252 - hasnt_column(view, column) should have the proper diagnostics +ok 253 - hasnt_column(type, column) should pass +ok 254 - hasnt_column(type, column) should have the proper description +ok 255 - hasnt_column(type, column) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index e6acb5b41e06..5cc40d5ed3a4 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2687,14 +2687,14 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; --- has_schema( schema, desscription ) +-- 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 n - WHERE LOWER(n.nspname) = LOWER($1) + FROM pg_catalog.pg_namespace + WHERE LOWER(nspname) = LOWER($1) ), $2 ); $$ LANGUAGE sql; @@ -2705,14 +2705,14 @@ RETURNS TEXT AS $$ SELECT has_schema( $1, 'Schema ' || quote_ident($1) || ' should exist' ); $$ LANGUAGE sql; --- hasnt_schema( schema, desscription ) +-- 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 n - WHERE LOWER(n.nspname) = LOWER($1) + FROM pg_catalog.pg_namespace + WHERE LOWER(nspname) = LOWER($1) ), $2 ); $$ LANGUAGE sql; @@ -2723,6 +2723,55 @@ 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 $$ + SELECT ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_tablespace + WHERE LOWER(spcname) = LOWER($1) + AND spclocation = $2 + ), $3 + ); +$$ LANGUAGE sql; + +-- 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 LOWER(spcname) = LOWER($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 LOWER(spcname) = LOWER($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( diff --git a/sql/hastap.sql b/sql/hastap.sql index 521f47eb4a4d..baf072026a44 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(231); +SELECT plan(255); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -26,6 +26,70 @@ CREATE DOMAIN us_postal_code AS TEXT CHECK( CREATE SCHEMA someschema; RESET client_min_messages; +/****************************************************************************/ +-- Test has_tablespace(). Can't really test with the location argument, though. +SELECT * FROM check_test( + has_tablespace( '__SDFSDFD__' ), + false, + 'has_tablespace(non-existent tablespace)', + 'Tablespace "__SDFSDFD__" should exist', + '' +); +SELECT * FROM check_test( + has_tablespace( '__SDFSDFD__', 'lol' ), + false, + 'has_tablespace(non-existent tablespace, tab)', + 'lol', + '' +); + +SELECT * FROM check_test( + has_tablespace( 'pg_default' ), + true, + 'has_tablespace(tablespace)', + 'Tablespace pg_default should exist', + '' +); +SELECT * FROM check_test( + has_tablespace( 'pg_default', 'lol' ), + true, + 'has_tablespace(tablespace, desc)', + 'lol', + '' +); + +/****************************************************************************/ +-- Test hasnt_tablespace(). +SELECT * FROM check_test( + hasnt_tablespace( '__SDFSDFD__' ), + true, + 'hasnt_tablespace(non-existent tablespace)', + 'Tablespace "__SDFSDFD__" should not exist', + '' +); +SELECT * FROM check_test( + hasnt_tablespace( '__SDFSDFD__', 'lol' ), + true, + 'hasnt_tablespace(non-existent tablespace, tab)', + 'lol', + '' +); + +SELECT * FROM check_test( + hasnt_tablespace( 'pg_default' ), + false, + 'hasnt_tablespace(pg_default)', + 'Tablespace pg_default should not exist', + '' +); +SELECT * FROM check_test( + hasnt_tablespace( 'pg_default', 'lol' ), + false, + 'hasnt_tablespace(tablespace, desc)', + 'lol', + '' +); + /****************************************************************************/ -- Test has_schema(). SELECT * FROM check_test( diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index dde469023aa7..7848a05ff1ec 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -53,6 +53,11 @@ DROP FUNCTION hasnt_schema( NAME ); DROP FUNCTION hasnt_schema( NAME, TEXT ); DROP FUNCTION has_schema( NAME ); DROP FUNCTION has_schema( NAME, TEXT ); +DROP FUNCTION hasnt_tablespace( NAME ); +DROP FUNCTION hasnt_tablespace( NAME, TEXT ); +DROP FUNCTION has_tablespace( NAME ); +DROP FUNCTION has_tablespace( NAME, TEXT ); +DROP FUNCTION has_tablespace( NAME, TEXT, TEXT ); DROP FUNCTION trigger_is ( NAME, NAME, NAME ); DROP FUNCTION trigger_is ( NAME, NAME, NAME, text ); DROP FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME ); From f727ae973b9354d96df2045e8c7d323311c21509 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 5 Feb 2009 22:28:49 +0000 Subject: [PATCH 0282/1195] Another To-Do. Will it never end? --- README.pgtap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.pgtap b/README.pgtap index 96597c278a24..c79d43110a7f 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2180,6 +2180,8 @@ To Do * Add options to `pg_prove` to allow it to run tests using the `runtests()` function rather than test files. +* Update all functions that compare idents to use `quote_ident()` instead of + just straight-ahead comparisons or `LOWER()` comparisons. * Useful schema testing functions to consider adding: * `has_cast()` * `has_role()`, `has_user()`, `has_group()` From 686b309102450f7d56636d5a3e646a8850297f90 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 5 Feb 2009 22:40:34 +0000 Subject: [PATCH 0283/1195] * Added `has_sequence()` and `hasnt_sequence()`. * Added list of other sequence-related functions that could be added. --- Changes | 1 + README.pgtap | 30 +++- expected/hastap.out | 314 ++++++++++++++++++++++------------------- pgtap.sql.in | 36 +++++ sql/hastap.sql | 90 +++++++++++- uninstall_pgtap.sql.in | 6 + 6 files changed, 333 insertions(+), 144 deletions(-) diff --git a/Changes b/Changes index cc08e61b8fb9..f17a5f60dc70 100644 --- a/Changes +++ b/Changes @@ -22,6 +22,7 @@ Revision history for pgTAP diagnostics if they fail because the column in question does not exist. - Added `has_tablespace()` and `hasnt_tablespace()`. + - Added `has_sequence()` and `hasnt_sequence()`. 0.16 2009-02-03T17:37:03 - Switched from a crazy trinary logic in `is()` and `isnt()` to the use diff --git a/README.pgtap b/README.pgtap index c79d43110a7f..a5954b2e4ae7 100644 --- a/README.pgtap +++ b/README.pgtap @@ -713,6 +713,31 @@ Just like `has_table()`, only it tests for the existence of a view. This function is the inverse of `has_view()`. The test passes if the specified view does *not* exist. +### `has_sequence( schema, sequence, description )` ### +### `has_sequence( sequence, description )` ### +### `has_sequence( sequence )` ### + + SELECT has_sequence( + 'myschema', + 'somesequence', + 'I got myschema.somesequence' + ); + +Just like `has_table()`, only it tests for the existence of a sequence. + +### `hasnt_sequence( schema, sequence, description )` ### +### `hasnt_sequence( sequence, description )` ### +### `hasnt_sequence( sequence )` ### + + SELECT hasnt_sequence( + 'myschema', + 'somesequence', + 'There should be no myschema.somesequence' + ); + +This function is the inverse of `has_sequence()`. The test passes if the +specified sequence does *not* exist. + ### `has_type( schema, type, description )` ### ### `has_type( schema, type )` ### ### `has_type( type, description )` ### @@ -2187,8 +2212,11 @@ To Do * `has_role()`, `has_user()`, `has_group()` * `has_operator()` * `has_operator_class()` - * `has_sequence()` * `has_rule()` + * `seq_has_range()` + * `seq_increments_by()` + * `seq_starts_at()` + * `seq_cycles()` Suported Versions ----------------- diff --git a/expected/hastap.out b/expected/hastap.out index f40fe96b0cab..345b4f020b8a 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..255 +1..285 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -114,144 +114,174 @@ ok 111 - hasnt_view(view, desc) should have the proper diagnostics ok 112 - hasnt_view(sch, view, desc) should fail ok 113 - hasnt_view(sch, view, desc) should have the proper description ok 114 - hasnt_view(sch, view, desc) should have the proper diagnostics -ok 115 - has_type(type) should pass -ok 116 - has_type(type) should have the proper description -ok 117 - has_type(type) should have the proper diagnostics -ok 118 - has_type(type, desc) should pass -ok 119 - has_type(type, desc) should have the proper description -ok 120 - has_type(type, desc) should have the proper diagnostics -ok 121 - has_type(scheam, type) should pass -ok 122 - has_type(scheam, type) should have the proper description -ok 123 - has_type(scheam, type) should have the proper diagnostics -ok 124 - has_type(schema, type, desc) should pass -ok 125 - has_type(schema, type, desc) should have the proper description -ok 126 - has_type(schema, type, desc) should have the proper diagnostics -ok 127 - has_type(type) should fail -ok 128 - has_type(type) should have the proper description -ok 129 - has_type(type) should have the proper diagnostics -ok 130 - has_type(type, desc) should fail -ok 131 - has_type(type, desc) should have the proper description -ok 132 - has_type(type, desc) should have the proper diagnostics -ok 133 - has_type(scheam, type) should fail -ok 134 - has_type(scheam, type) should have the proper description -ok 135 - has_type(scheam, type) should have the proper diagnostics -ok 136 - has_type(schema, type, desc) should fail -ok 137 - has_type(schema, type, desc) should have the proper description -ok 138 - has_type(schema, type, desc) should have the proper diagnostics -ok 139 - has_type(domain) should pass -ok 140 - has_type(domain) should have the proper description -ok 141 - has_type(domain) should have the proper diagnostics -ok 142 - hasnt_type(type) should pass -ok 143 - hasnt_type(type) should have the proper description -ok 144 - hasnt_type(type) should have the proper diagnostics -ok 145 - hasnt_type(type, desc) should pass -ok 146 - hasnt_type(type, desc) should have the proper description -ok 147 - hasnt_type(type, desc) should have the proper diagnostics -ok 148 - hasnt_type(scheam, type) should pass -ok 149 - hasnt_type(scheam, type) should have the proper description -ok 150 - hasnt_type(scheam, type) should have the proper diagnostics -ok 151 - hasnt_type(schema, type, desc) should pass -ok 152 - hasnt_type(schema, type, desc) should have the proper description -ok 153 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 154 - hasnt_type(type) should fail -ok 155 - hasnt_type(type) should have the proper description -ok 156 - hasnt_type(type) should have the proper diagnostics -ok 157 - hasnt_type(type, desc) should fail -ok 158 - hasnt_type(type, desc) should have the proper description -ok 159 - hasnt_type(type, desc) should have the proper diagnostics -ok 160 - hasnt_type(scheam, type) should fail -ok 161 - hasnt_type(scheam, type) should have the proper description -ok 162 - hasnt_type(scheam, type) should have the proper diagnostics -ok 163 - hasnt_type(schema, type, desc) should fail -ok 164 - hasnt_type(schema, type, desc) should have the proper description -ok 165 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 166 - has_domain(domain) should pass -ok 167 - has_domain(domain) should have the proper description -ok 168 - has_domain(domain) should have the proper diagnostics -ok 169 - has_domain(domain, desc) should pass -ok 170 - has_domain(domain, desc) should have the proper description -ok 171 - has_domain(domain, desc) should have the proper diagnostics -ok 172 - has_domain(scheam, domain) should pass -ok 173 - has_domain(scheam, domain) should have the proper description -ok 174 - has_domain(scheam, domain) should have the proper diagnostics -ok 175 - has_domain(schema, domain, desc) should pass -ok 176 - has_domain(schema, domain, desc) should have the proper description -ok 177 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 178 - has_domain(domain) should fail -ok 179 - has_domain(domain) should have the proper description -ok 180 - has_domain(domain) should have the proper diagnostics -ok 181 - has_domain(domain, desc) should fail -ok 182 - has_domain(domain, desc) should have the proper description -ok 183 - has_domain(domain, desc) should have the proper diagnostics -ok 184 - has_domain(scheam, domain) should fail -ok 185 - has_domain(scheam, domain) should have the proper description -ok 186 - has_domain(scheam, domain) should have the proper diagnostics -ok 187 - has_domain(schema, domain, desc) should fail -ok 188 - has_domain(schema, domain, desc) should have the proper description -ok 189 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 190 - hasnt_domain(domain) should pass -ok 191 - hasnt_domain(domain) should have the proper description -ok 192 - hasnt_domain(domain) should have the proper diagnostics -ok 193 - hasnt_domain(domain, desc) should pass -ok 194 - hasnt_domain(domain, desc) should have the proper description -ok 195 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 196 - hasnt_domain(scheam, domain) should pass -ok 197 - hasnt_domain(scheam, domain) should have the proper description -ok 198 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 199 - hasnt_domain(schema, domain, desc) should pass -ok 200 - hasnt_domain(schema, domain, desc) should have the proper description -ok 201 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 202 - hasnt_domain(domain) should fail -ok 203 - hasnt_domain(domain) should have the proper description -ok 204 - hasnt_domain(domain) should have the proper diagnostics -ok 205 - hasnt_domain(domain, desc) should fail -ok 206 - hasnt_domain(domain, desc) should have the proper description -ok 207 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 208 - hasnt_domain(scheam, domain) should fail -ok 209 - hasnt_domain(scheam, domain) should have the proper description -ok 210 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 211 - hasnt_domain(schema, domain, desc) should fail -ok 212 - hasnt_domain(schema, domain, desc) should have the proper description -ok 213 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 214 - has_column(non-existent tab, col) should fail -ok 215 - has_column(non-existent tab, col) should have the proper description -ok 216 - has_column(non-existent tab, col) should have the proper diagnostics -ok 217 - has_column(non-existent tab, col, desc) should fail -ok 218 - has_column(non-existent tab, col, desc) should have the proper description -ok 219 - has_column(non-existent tab, col, desc) should have the proper diagnostics -ok 220 - has_column(non-existent sch, tab, col, desc) should fail -ok 221 - has_column(non-existent sch, tab, col, desc) should have the proper description -ok 222 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 223 - has_column(table, column) should pass -ok 224 - has_column(table, column) should have the proper description -ok 225 - has_column(table, column) should have the proper diagnostics -ok 226 - has_column(sch, tab, col, desc) should pass -ok 227 - has_column(sch, tab, col, desc) should have the proper description -ok 228 - has_column(sch, tab, col, desc) should have the proper diagnostics -ok 229 - has_column(view, column) should pass -ok 230 - has_column(view, column) should have the proper description -ok 231 - has_column(view, column) should have the proper diagnostics -ok 232 - has_column(type, column) should pass -ok 233 - has_column(type, column) should have the proper description -ok 234 - has_column(type, column) should have the proper diagnostics -ok 235 - hasnt_column(non-existent tab, col) should pass -ok 236 - hasnt_column(non-existent tab, col) should have the proper description -ok 237 - hasnt_column(non-existent tab, col) should have the proper diagnostics -ok 238 - hasnt_column(non-existent tab, col, desc) should pass -ok 239 - hasnt_column(non-existent tab, col, desc) should have the proper description -ok 240 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics -ok 241 - hasnt_column(non-existent sch, tab, col, desc) should pass -ok 242 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description -ok 243 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 244 - hasnt_column(table, column) should fail -ok 245 - hasnt_column(table, column) should have the proper description -ok 246 - hasnt_column(table, column) should have the proper diagnostics -ok 247 - hasnt_column(sch, tab, col, desc) should fail -ok 248 - hasnt_column(sch, tab, col, desc) should have the proper description -ok 249 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics -ok 250 - hasnt_column(view, column) should pass -ok 251 - hasnt_column(view, column) should have the proper description -ok 252 - hasnt_column(view, column) should have the proper diagnostics -ok 253 - hasnt_column(type, column) should pass -ok 254 - hasnt_column(type, column) should have the proper description -ok 255 - hasnt_column(type, column) should have the proper diagnostics +ok 115 - has_sequence(non-existent sequence) should fail +ok 116 - has_sequence(non-existent sequence) should have the proper description +ok 117 - has_sequence(non-existent sequence) should have the proper diagnostics +ok 118 - has_sequence(non-existent sequence, desc) should fail +ok 119 - has_sequence(non-existent sequence, desc) should have the proper description +ok 120 - has_sequence(non-existent sequence, desc) should have the proper diagnostics +ok 121 - has_sequence(sch, non-existtent sequence, desc) should fail +ok 122 - has_sequence(sch, non-existtent sequence, desc) should have the proper description +ok 123 - has_sequence(sch, non-existtent sequence, desc) should have the proper diagnostics +ok 124 - has_sequence(sequence, desc) should pass +ok 125 - has_sequence(sequence, desc) should have the proper description +ok 126 - has_sequence(sequence, desc) should have the proper diagnostics +ok 127 - has_sequence(sch, sequence, desc) should pass +ok 128 - has_sequence(sch, sequence, desc) should have the proper description +ok 129 - has_sequence(sch, sequence, desc) should have the proper diagnostics +ok 130 - hasnt_sequence(non-existent sequence) should pass +ok 131 - hasnt_sequence(non-existent sequence) should have the proper description +ok 132 - hasnt_sequence(non-existent sequence) should have the proper diagnostics +ok 133 - hasnt_sequence(non-existent sequence, desc) should pass +ok 134 - hasnt_sequence(non-existent sequence, desc) should have the proper description +ok 135 - hasnt_sequence(non-existent sequence, desc) should have the proper diagnostics +ok 136 - hasnt_sequence(sch, non-existtent sequence, desc) should pass +ok 137 - hasnt_sequence(sch, non-existtent sequence, desc) should have the proper description +ok 138 - hasnt_sequence(sch, non-existtent sequence, desc) should have the proper diagnostics +ok 139 - hasnt_sequence(sequence, desc) should fail +ok 140 - hasnt_sequence(sequence, desc) should have the proper description +ok 141 - hasnt_sequence(sequence, desc) should have the proper diagnostics +ok 142 - hasnt_sequence(sch, sequence, desc) should fail +ok 143 - hasnt_sequence(sch, sequence, desc) should have the proper description +ok 144 - hasnt_sequence(sch, sequence, desc) should have the proper diagnostics +ok 145 - has_type(type) should pass +ok 146 - has_type(type) should have the proper description +ok 147 - has_type(type) should have the proper diagnostics +ok 148 - has_type(type, desc) should pass +ok 149 - has_type(type, desc) should have the proper description +ok 150 - has_type(type, desc) should have the proper diagnostics +ok 151 - has_type(scheam, type) should pass +ok 152 - has_type(scheam, type) should have the proper description +ok 153 - has_type(scheam, type) should have the proper diagnostics +ok 154 - has_type(schema, type, desc) should pass +ok 155 - has_type(schema, type, desc) should have the proper description +ok 156 - has_type(schema, type, desc) should have the proper diagnostics +ok 157 - has_type(type) should fail +ok 158 - has_type(type) should have the proper description +ok 159 - has_type(type) should have the proper diagnostics +ok 160 - has_type(type, desc) should fail +ok 161 - has_type(type, desc) should have the proper description +ok 162 - has_type(type, desc) should have the proper diagnostics +ok 163 - has_type(scheam, type) should fail +ok 164 - has_type(scheam, type) should have the proper description +ok 165 - has_type(scheam, type) should have the proper diagnostics +ok 166 - has_type(schema, type, desc) should fail +ok 167 - has_type(schema, type, desc) should have the proper description +ok 168 - has_type(schema, type, desc) should have the proper diagnostics +ok 169 - has_type(domain) should pass +ok 170 - has_type(domain) should have the proper description +ok 171 - has_type(domain) should have the proper diagnostics +ok 172 - hasnt_type(type) should pass +ok 173 - hasnt_type(type) should have the proper description +ok 174 - hasnt_type(type) should have the proper diagnostics +ok 175 - hasnt_type(type, desc) should pass +ok 176 - hasnt_type(type, desc) should have the proper description +ok 177 - hasnt_type(type, desc) should have the proper diagnostics +ok 178 - hasnt_type(scheam, type) should pass +ok 179 - hasnt_type(scheam, type) should have the proper description +ok 180 - hasnt_type(scheam, type) should have the proper diagnostics +ok 181 - hasnt_type(schema, type, desc) should pass +ok 182 - hasnt_type(schema, type, desc) should have the proper description +ok 183 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 184 - hasnt_type(type) should fail +ok 185 - hasnt_type(type) should have the proper description +ok 186 - hasnt_type(type) should have the proper diagnostics +ok 187 - hasnt_type(type, desc) should fail +ok 188 - hasnt_type(type, desc) should have the proper description +ok 189 - hasnt_type(type, desc) should have the proper diagnostics +ok 190 - hasnt_type(scheam, type) should fail +ok 191 - hasnt_type(scheam, type) should have the proper description +ok 192 - hasnt_type(scheam, type) should have the proper diagnostics +ok 193 - hasnt_type(schema, type, desc) should fail +ok 194 - hasnt_type(schema, type, desc) should have the proper description +ok 195 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 196 - has_domain(domain) should pass +ok 197 - has_domain(domain) should have the proper description +ok 198 - has_domain(domain) should have the proper diagnostics +ok 199 - has_domain(domain, desc) should pass +ok 200 - has_domain(domain, desc) should have the proper description +ok 201 - has_domain(domain, desc) should have the proper diagnostics +ok 202 - has_domain(scheam, domain) should pass +ok 203 - has_domain(scheam, domain) should have the proper description +ok 204 - has_domain(scheam, domain) should have the proper diagnostics +ok 205 - has_domain(schema, domain, desc) should pass +ok 206 - has_domain(schema, domain, desc) should have the proper description +ok 207 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 208 - has_domain(domain) should fail +ok 209 - has_domain(domain) should have the proper description +ok 210 - has_domain(domain) should have the proper diagnostics +ok 211 - has_domain(domain, desc) should fail +ok 212 - has_domain(domain, desc) should have the proper description +ok 213 - has_domain(domain, desc) should have the proper diagnostics +ok 214 - has_domain(scheam, domain) should fail +ok 215 - has_domain(scheam, domain) should have the proper description +ok 216 - has_domain(scheam, domain) should have the proper diagnostics +ok 217 - has_domain(schema, domain, desc) should fail +ok 218 - has_domain(schema, domain, desc) should have the proper description +ok 219 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 220 - hasnt_domain(domain) should pass +ok 221 - hasnt_domain(domain) should have the proper description +ok 222 - hasnt_domain(domain) should have the proper diagnostics +ok 223 - hasnt_domain(domain, desc) should pass +ok 224 - hasnt_domain(domain, desc) should have the proper description +ok 225 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 226 - hasnt_domain(scheam, domain) should pass +ok 227 - hasnt_domain(scheam, domain) should have the proper description +ok 228 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 229 - hasnt_domain(schema, domain, desc) should pass +ok 230 - hasnt_domain(schema, domain, desc) should have the proper description +ok 231 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 232 - hasnt_domain(domain) should fail +ok 233 - hasnt_domain(domain) should have the proper description +ok 234 - hasnt_domain(domain) should have the proper diagnostics +ok 235 - hasnt_domain(domain, desc) should fail +ok 236 - hasnt_domain(domain, desc) should have the proper description +ok 237 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 238 - hasnt_domain(scheam, domain) should fail +ok 239 - hasnt_domain(scheam, domain) should have the proper description +ok 240 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 241 - hasnt_domain(schema, domain, desc) should fail +ok 242 - hasnt_domain(schema, domain, desc) should have the proper description +ok 243 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 244 - has_column(non-existent tab, col) should fail +ok 245 - has_column(non-existent tab, col) should have the proper description +ok 246 - has_column(non-existent tab, col) should have the proper diagnostics +ok 247 - has_column(non-existent tab, col, desc) should fail +ok 248 - has_column(non-existent tab, col, desc) should have the proper description +ok 249 - has_column(non-existent tab, col, desc) should have the proper diagnostics +ok 250 - has_column(non-existent sch, tab, col, desc) should fail +ok 251 - has_column(non-existent sch, tab, col, desc) should have the proper description +ok 252 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 253 - has_column(table, column) should pass +ok 254 - has_column(table, column) should have the proper description +ok 255 - has_column(table, column) should have the proper diagnostics +ok 256 - has_column(sch, tab, col, desc) should pass +ok 257 - has_column(sch, tab, col, desc) should have the proper description +ok 258 - has_column(sch, tab, col, desc) should have the proper diagnostics +ok 259 - has_column(view, column) should pass +ok 260 - has_column(view, column) should have the proper description +ok 261 - has_column(view, column) should have the proper diagnostics +ok 262 - has_column(type, column) should pass +ok 263 - has_column(type, column) should have the proper description +ok 264 - has_column(type, column) should have the proper diagnostics +ok 265 - hasnt_column(non-existent tab, col) should pass +ok 266 - hasnt_column(non-existent tab, col) should have the proper description +ok 267 - hasnt_column(non-existent tab, col) should have the proper diagnostics +ok 268 - hasnt_column(non-existent tab, col, desc) should pass +ok 269 - hasnt_column(non-existent tab, col, desc) should have the proper description +ok 270 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics +ok 271 - hasnt_column(non-existent sch, tab, col, desc) should pass +ok 272 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description +ok 273 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 274 - hasnt_column(table, column) should fail +ok 275 - hasnt_column(table, column) should have the proper description +ok 276 - hasnt_column(table, column) should have the proper diagnostics +ok 277 - hasnt_column(sch, tab, col, desc) should fail +ok 278 - hasnt_column(sch, tab, col, desc) should have the proper description +ok 279 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics +ok 280 - hasnt_column(view, column) should pass +ok 281 - hasnt_column(view, column) should have the proper description +ok 282 - hasnt_column(view, column) should have the proper diagnostics +ok 283 - hasnt_column(type, column) should pass +ok 284 - hasnt_column(type, column) should have the proper description +ok 285 - hasnt_column(type, column) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 5cc40d5ed3a4..be2ebdfef29d 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -855,6 +855,42 @@ 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( 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; + CREATE OR REPLACE FUNCTION _cexists ( NAME, NAME, NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( diff --git a/sql/hastap.sql b/sql/hastap.sql index baf072026a44..8d0cbe4a689e 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(255); +SELECT plan(285); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -23,6 +23,8 @@ CREATE DOMAIN us_postal_code AS TEXT CHECK( VALUE ~ '^[[:digit:]]{5}$' OR VALUE ~ '^[[:digit:]]{5}-[[:digit:]]{4}$' ); +CREATE SEQUENCE someseq; + CREATE SCHEMA someschema; RESET client_min_messages; @@ -342,6 +344,92 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test has_sequence(). + +SELECT * FROM check_test( + has_sequence( '__SDFSDFD__' ), + false, + 'has_sequence(non-existent sequence)', + 'Sequence "__SDFSDFD__" should exist', + '' +); + +SELECT * FROM check_test( + has_sequence( '__SDFSDFD__', 'howdy' ), + false, + 'has_sequence(non-existent sequence, desc)', + 'howdy', + '' +); + +SELECT * FROM check_test( + has_sequence( 'foo', '__SDFSDFD__', 'desc' ), + false, + 'has_sequence(sch, non-existtent sequence, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + has_sequence( 'someseq', 'yowza' ), + true, + 'has_sequence(sequence, desc)', + 'yowza', + '' +); + +SELECT * FROM check_test( + has_sequence( 'public', 'someseq', 'desc' ), + true, + 'has_sequence(sch, sequence, desc)', + 'desc', + '' +); + +/****************************************************************************/ +-- Test hasnt_sequence(). + +SELECT * FROM check_test( + hasnt_sequence( '__SDFSDFD__' ), + true, + 'hasnt_sequence(non-existent sequence)', + 'Sequence "__SDFSDFD__" should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_sequence( '__SDFSDFD__', 'howdy' ), + true, + 'hasnt_sequence(non-existent sequence, desc)', + 'howdy', + '' +); + +SELECT * FROM check_test( + hasnt_sequence( 'foo', '__SDFSDFD__', 'desc' ), + true, + 'hasnt_sequence(sch, non-existtent sequence, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_sequence( 'someseq', 'yowza' ), + false, + 'hasnt_sequence(sequence, desc)', + 'yowza', + '' +); + +SELECT * FROM check_test( + hasnt_sequence( 'public', 'someseq', 'desc' ), + false, + 'hasnt_sequence(sch, sequence, desc)', + 'desc', + '' +); + /****************************************************************************/ -- Test has_type(). SELECT * FROM check_test( diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 7848a05ff1ec..db6817f5cdc8 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -213,6 +213,12 @@ DROP FUNCTION has_column ( NAME, NAME, TEXT ); DROP FUNCTION has_column ( NAME, NAME, NAME, TEXT ); DROP FUNCTION _cexists ( NAME, NAME ); DROP FUNCTION _cexists ( NAME, NAME, NAME ); +DROP FUNCTION hasnt_sequence ( NAME ); +DROP FUNCTION hasnt_sequence ( NAME, TEXT ); +DROP FUNCTION hasnt_sequence ( NAME, NAME, TEXT ); +DROP FUNCTION has_sequence ( NAME ); +DROP FUNCTION has_sequence ( NAME, TEXT ); +DROP FUNCTION has_sequence ( NAME, NAME, TEXT ); DROP FUNCTION hasnt_view ( NAME ); DROP FUNCTION hasnt_view ( NAME, TEXT ); DROP FUNCTION hasnt_view ( NAME, NAME, TEXT ); From f2063bdcb1fc2c9cefa2eb7e74ca6dbc22a5b251 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 5 Feb 2009 22:45:28 +0000 Subject: [PATCH 0284/1195] Better example for `pgtap_version()`. --- README.pgtap | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.pgtap b/README.pgtap index a5954b2e4ae7..9c1b5e41a64b 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1773,12 +1773,12 @@ pTAP provides a few extra functions to make the work of testing more pleasant. SELECT pgtap_version(); -Returns the version of pgTAP installed in the server. The value is NUMERIC, +Returns the version of pgTAP installed in the server. The value is `NUMERIC`, and thus suitable for comparing to a decimal value: - SELECT CASE WHEN pgtap_version() < 0.14 - THEN skip('No index assertions before pgTAP 0.14') - ELSE has_index('users', 'idx_user_name') + SELECT CASE WHEN pgtap_version() < 0.17 + THEN skip('No sequence assertions before pgTAP 0.17') + ELSE has_sequence('my_big_seq') END; ### `pg_version()` ### From f26dcbf80deb30bb026400f2b3a9e0b5f1d88fee Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 6 Feb 2009 00:48:17 +0000 Subject: [PATCH 0285/1195] * Now using `quote_ident()` for all object name comparisons. * Spell-checked `Changes`. --- Changes | 21 ++-- README.pgtap | 35 +++++- pgtap.sql.in | 305 +++++++++++++++++++++++++++----------------------- sql/fktap.sql | 6 +- sql/index.sql | 10 +- 5 files changed, 218 insertions(+), 159 deletions(-) diff --git a/Changes b/Changes index f17a5f60dc70..ef4bd0307c50 100644 --- a/Changes +++ b/Changes @@ -23,6 +23,13 @@ Revision history for pgTAP exist. - Added `has_tablespace()` and `hasnt_tablespace()`. - Added `has_sequence()` and `hasnt_sequence()`. + - Changed `has_index()` so that expressions are compared case- + sensitively. + - In the schema testing functions, now using `quote_ident()` to compare + all identifiers, such as table names, schema names, column names, etc. + This means that tests should use only lowercase strings to specify + object names, unless mixed case objects were crated with double quote + characters. 0.16 2009-02-03T17:37:03 - Switched from a crazy trinary logic in `is()` and `isnt()` to the use @@ -33,7 +40,7 @@ Revision history for pgTAP properly on many platforms (including Linux, and probably many others). This allows `os_name()` to actually work on those platforms. - The definition of the `pg_typeof()` function is removed when - installing on POstgresQL 8.4 and no C code is compiled, since the only + installing on PostgreSQL 8.4 and no C code is compiled, since the only C code is for this function. This is because `pg_typeof()` is included with PostgreSQL 8.4. - Added `has_schema()` and `hasnt_schema()`. @@ -41,7 +48,7 @@ Revision history for pgTAP `has_enum()`, and `hasnt_enum()`. - Added `enum_has_labels()`. - Updated the `Makefile` to fix the shebang line in `pg_prove` if - Perl is found, and to emite a warning if the `$PERL` variable is not + Perl is found, and to emit a warning if the `$PERL` variable is not set. - Updated the `Makefile` so that if `$PERL` is set and Tap::Harness is not installed, it warns the user to install it in order to use `pg_prove`. - Updated the `Makefile` to set the location @@ -70,7 +77,7 @@ Revision history for pgTAP `--version` option to `pg_prove`, which is `-V`, not `-v`. - Added `pgtap_version()`. - Added the "html" make target to generate the HTML documentation for - the Web site, including a table of contents and documenation for + the Web site, including a table of contents and documentation for `pg_prove`. 0.14 2008-10-27T22:43:36 @@ -135,7 +142,7 @@ Revision history for pgTAP error codes. - Converted some more tests to use `check_test()`. - Added `can()` and `can_ok()`. - - Fixed a bug in `check_test()` where the leading whitespace for + - Fixed a bug in `check_test()` where the leading white space for diagnostic messages could be off by 1 or more characters. - Fixed the `installcheck` target so that it properly installs PL/pgSQL into the target database before the tests run. @@ -182,7 +189,7 @@ Revision history for pgTAP - Added the `col_type_is()`, `col_default_is()`, `col_is_pk()`, `col_is_fk()`, `col_is_unique()`, and `col_has_check()` test functions. - - Fixed `is()` and `isnt()` to better handle NULLs. + - Fixed `is()` and `isnt()` to better handle `NULL`s. - Added `cmp_ok()`. - Added the `--set ON_ERROR_STOP=1` option to the call to `psql` in `pg_prove`. @@ -204,12 +211,12 @@ Revision history for pgTAP 0.02 2008-06-17T16:26:41 - Converted the documentation to Markdown. - Added pg_prove, a Perl script that use TAP::Harness to run tests and - report the results, juse like the Perl program `prove`. + report the results, just like the Perl program `prove`. - Fixed `no_plan()` so that it no longer emits a plan, which apparently was wrong. Now `finish()` outputs it when there's no plan. - Fixed the test script so that it now emits a proper plan. - Removed all of the configuration settings from `pgtap.sql`, as they're - now handled by `pg_prove`. I've mengioned them in the README for + now handled by `pg_prove`. I've mentioned them in the README for reference. - Added `lives_ok()`. - Moved the creation of temporary tables into `plan()`, so that diff --git a/README.pgtap b/README.pgtap index 9c1b5e41a64b..99763613815a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -606,6 +606,33 @@ A Wicked Schema Need to make sure that your database is designed just the way you think it should be? Use these test functions and rest easy. +A note on comparisons: pgTAP uses the PostgreSQL function `quote_ident()` to +compare all SQL identifiers, such as the names of tables, schemas, functions, +indexes, and columns (but not data types). So in general, you should always +use lowercase strings when passing identifier arguments to the functions +below. Use mixed case strings only when the objects were declared in your +schema using double-quotes. For example, if you created a table like so: + + CREATE TABLE Foo (id integer); + +Then you *must* test for it using only lowercase characters (if you want the +test to pass): + + SELECT has_table('foo'); + +If, however, you declared the table using a double-quoted string, like so: + + CREATE TABLE "Foo" (id integer); + +Then you'd need to test for it using exactly the same string, including case, +like so: + + SELECT has_table('Foo'); + +In general, this should not be an issue, as mixed-case objects are created +only rarely. So if you just stick to lowercase-only arguments to these +functions, you should be in good shape. + ### `has_tablespace( tablespace, location, description )` ### ### `has_tablespace( tablespace, description )` ### ### `has_tablespace( tablespace )` ### @@ -1405,8 +1432,12 @@ Checks for the existence of an index associated with the named table. The `:schema` argument is optional, as is the column name or names or expression, and the description. The columns argument may be a string naming one column or an array of column names. It may also be a string representing an expression, -such as `LOWER(foo)`. If you find that the function call seems to be getting -confused, cast the index name to the `NAME` type: +such as `lower(foo)`. For expressions, you must use lowercase for all SQL +keywords and functions to properly compare to PostgreSQL's internal form of +the expression. + +If you find that the function call seems to be getting confused, cast the +index name to the `NAME` type: SELECT has_index( 'public', 'sometab', 'idx_foo', 'name'::name ); diff --git a/pgtap.sql.in b/pgtap.sql.in index be2ebdfef29d..4df8075a081c 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -767,8 +767,8 @@ RETURNS BOOLEAN AS $$ FROM pg_catalog.pg_namespace n JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace WHERE c.relkind = $1 - AND n.nspname = $2 - AND c.relname = $3 + AND quote_ident(n.nspname) = quote_ident($2) + AND quote_ident(c.relname) = quote_ident($3) ); $$ LANGUAGE SQL; @@ -779,7 +779,7 @@ RETURNS BOOLEAN AS $$ FROM pg_catalog.pg_class c WHERE c.relkind = $1 AND pg_catalog.pg_table_is_visible(c.oid) - AND c.relname = $2 + AND quote_ident(c.relname) = quote_ident($2) ); $$ LANGUAGE SQL; @@ -898,8 +898,8 @@ RETURNS BOOLEAN AS $$ 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 + WHERE quote_ident(n.nspname) = quote_ident($1) + AND quote_ident(c.relname) = quote_ident($2) AND a.attnum > 0 AND NOT a.attisdropped AND a.attname = LOWER($3) @@ -912,7 +912,7 @@ RETURNS BOOLEAN AS $$ SELECT true FROM pg_catalog.pg_class c JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid - WHERE c.relname = $1 + WHERE quote_ident(c.relname) = quote_ident($1) AND pg_catalog.pg_table_is_visible(c.oid) AND a.attnum > 0 AND NOT a.attisdropped @@ -970,11 +970,11 @@ BEGIN FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a WHERE n.oid = c.relnamespace AND c.oid = a.attrelid - AND n.nspname = $1 - AND c.relname = $2 + AND quote_ident(n.nspname) = quote_ident($1) + AND quote_ident(c.relname) = quote_ident($2) AND a.attnum > 0 AND NOT a.attisdropped - AND a.attname = LOWER($3) + AND quote_ident(a.attname) = quote_ident($3) AND a.attnotnull = $5 ), $4 ); @@ -995,10 +995,10 @@ BEGIN FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a WHERE c.oid = a.attrelid AND pg_catalog.pg_table_is_visible(c.oid) - AND c.relname = $1 + AND quote_ident(c.relname) = quote_ident($1) AND a.attnum > 0 AND NOT a.attisdropped - AND a.attname = LOWER($2) + AND quote_ident(a.attname) = quote_ident($2) AND a.attnotnull = $4 ), $3 ); @@ -1059,10 +1059,10 @@ BEGIN WHERE a.attrelid = c.oid AND pg_table_is_visible(c.oid) AND pg_type_is_visible(a.atttypid) - AND c.relname = $2 + AND quote_ident(c.relname) = quote_ident($2) AND attnum > 0 AND NOT attisdropped - AND a.attname = $3; + AND quote_ident(a.attname) = quote_ident($3); ELSE IF NOT _cexists( $1, $2, $3 ) THEN RETURN fail( $5 ) || E'\n' @@ -1074,11 +1074,11 @@ BEGIN WHERE n.oid = c.relnamespace AND pg_type_is_visible(a.atttypid) AND c.oid = a.attrelid - AND n.nspname = $1 - AND c.relname = $2 + AND quote_ident(n.nspname) = quote_ident($1) + AND quote_ident(c.relname) = quote_ident($2) AND attnum > 0 AND NOT attisdropped - AND a.attname = $3; + AND quote_ident(a.attname) = quote_ident($3); END IF; IF actual_type = LOWER($4) THEN @@ -1112,8 +1112,8 @@ RETURNS boolean AS $$ 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 + WHERE quote_ident(n.nspname) = quote_ident($1) + AND quote_ident(c.relname) = quote_ident($2) AND a.attnum > 0 AND NOT a.attisdropped AND a.attname = LOWER($3) @@ -1124,7 +1124,7 @@ 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 + WHERE quote_ident(c.relname) = quote_ident($1) AND a.attnum > 0 AND NOT a.attisdropped AND a.attname = LOWER($2) @@ -1235,8 +1235,8 @@ BEGIN AND a.atthasdef AND a.attrelid = d.adrelid AND a.attnum = d.adnum - AND n.nspname = $1 - AND c.relname = $2 + AND quote_ident(n.nspname) = quote_ident($1) + AND quote_ident(c.relname) = quote_ident($2) AND a.attnum > 0 AND NOT a.attisdropped AND a.attname = $3; @@ -1268,7 +1268,7 @@ BEGIN AND a.atthasdef AND a.attrelid = d.adrelid AND a.attnum = d.adnum - AND c.relname = $1 + AND quote_ident(c.relname) = quote_ident($1) AND a.attnum > 0 AND NOT a.attisdropped AND a.attname = $2; @@ -1296,8 +1296,8 @@ RETURNS BOOLEAN AS $$ AND c.oid = x.conrelid AND pg_table_is_visible(c.oid) AND c.relhaspkey = true - AND n.nspname = $1 - AND c.relname = $2 + AND quote_ident(n.nspname) = quote_ident($1) + AND quote_ident(c.relname) = quote_ident($2) AND x.contype = $3 ); $$ LANGUAGE sql; @@ -1310,7 +1310,7 @@ RETURNS BOOLEAN AS $$ FROM pg_catalog.pg_class c JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid WHERE c.relhaspkey = true - AND c.relname = $1 + AND quote_ident(c.relname) = quote_ident($1) AND x.contype = $2 ); $$ LANGUAGE sql; @@ -1360,8 +1360,8 @@ RETURNS NAME[] AS $$ 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_constraint x ON c.oid = x.conrelid AND a.attnum = ANY( x.conkey ) - WHERE n.nspname = $1 - AND c.relname = $2 + WHERE quote_ident(n.nspname) = quote_ident($1) + AND quote_ident(c.relname) = quote_ident($2) AND x.contype = $3 ); $$ LANGUAGE sql; @@ -1374,11 +1374,29 @@ RETURNS NAME[] AS $$ FROM pg_catalog.pg_class c JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid AND a.attnum = ANY( x.conkey ) - AND c.relname = $1 + AND quote_ident(c.relname) = quote_ident($1) AND x.contype = $2 ); $$ LANGUAGE sql; +CREATE OR REPLACE FUNCTION _quote_ident_array( name[] ) +RETURNS text[] AS $$ + SELECT ARRAY( + SELECT quote_ident($1[i]) + FROM generate_series(1, array_upper($1, 1)) s(i) + ORDER BY i + ) +$$ LANGUAGE SQL immutable; + +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: http://pgfoundry.org/projects/newsysviews/ CREATE OR REPLACE FUNCTION _pg_sv_column_array( OID, SMALLINT[] ) RETURNS NAME[] AS $$ @@ -1487,7 +1505,7 @@ $$ 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) || '(' || array_to_string($2, ', ') || ') should be a primary key' ); + 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 ) @@ -1523,7 +1541,7 @@ $$ 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) || '(' || array_to_string($2, ', ') || ') should not be a primary key' ); + 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 ) @@ -1585,9 +1603,9 @@ RETURNS BOOLEAN AS $$ SELECT EXISTS( SELECT TRUE FROM pg_all_foreign_keys - WHERE fk_schema_name = $1 - AND fk_table_name = $2 - AND fk_columns = $3 + WHERE quote_ident(fk_schema_name) = quote_ident($1) + AND quote_ident(fk_table_name) = quote_ident($2) + AND _quote_ident_array(fk_columns) = _quote_ident_array($3) ); $$ LANGUAGE SQL; @@ -1596,8 +1614,8 @@ RETURNS BOOLEAN AS $$ SELECT EXISTS( SELECT TRUE FROM pg_all_foreign_keys - WHERE fk_table_name = $1 - AND fk_columns = $2 + WHERE quote_ident(fk_table_name) = quote_ident($1) + AND _quote_ident_array(fk_columns) = _quote_ident_array($2) ); $$ LANGUAGE SQL; @@ -1605,7 +1623,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME, NAME[], TEXT ) RETURNS TEXT AS $$ DECLARE - names text[][]; + names text[]; BEGIN IF _fkexists($1, $2, $3) THEN RETURN pass( $4 ); @@ -1613,17 +1631,17 @@ BEGIN -- Try to show the columns. SELECT ARRAY( - SELECT fk_columns::text + SELECT _ident_array_to_string(fk_columns, ', ') FROM pg_all_foreign_keys - WHERE fk_schema_name = $1 - AND fk_table_name = $2 + WHERE quote_ident(fk_schema_name) = quote_ident($1) + AND quote_ident(fk_table_name) = quote_ident($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 ' ) + || array_to_string( names, E'\n ' ) ); END IF; @@ -1638,7 +1656,7 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION col_is_fk ( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ DECLARE - names text[][]; + names text[]; BEGIN IF _fkexists($1, $2) THEN RETURN pass( $3 ); @@ -1646,9 +1664,9 @@ BEGIN -- Try to show the columns. SELECT ARRAY( - SELECT fk_columns::text + SELECT _ident_array_to_string(fk_columns, ', ') FROM pg_all_foreign_keys - WHERE fk_table_name = $1 + WHERE quote_ident(fk_table_name) = quote_ident($1) ORDER BY fk_columns ) INTO names; @@ -1669,7 +1687,7 @@ $$ 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) || '(' || array_to_string($2, ', ') || ') should be a foreign key' ); + 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 ) @@ -1705,7 +1723,7 @@ $$ 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) || '(' || array_to_string($2, ', ') || ') should not be a foreign key' ); + 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 ) @@ -1759,7 +1777,7 @@ $$ 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) || '(' || array_to_string($2, ', ') || ') should have a unique constraint' ); + 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 ) @@ -1813,7 +1831,7 @@ $$ 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) || '(' || array_to_string($2, ', ') || ') should have a check constraint' ); + 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 ) @@ -1844,19 +1862,19 @@ DECLARE 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 + WHERE quote_ident(fk_schema_name) = quote_ident($1) + AND quote_ident(fk_table_name) = quote_ident($2) + AND _quote_ident_array(fk_columns) = _quote_ident_array($3) INTO sch, tab, cols; RETURN is( -- have - quote_ident($1) || '.' || quote_ident($2) || '(' || array_to_string( $3, ', ' ) - || ') REFERENCES ' || COALESCE ( sch || '.' || tab || '(' || array_to_string( cols, ', ' ) || ')', 'NOTHING' ), + 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) || '(' || array_to_string( $3, ', ' ) + quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string( $3, ', ' ) || ') REFERENCES ' || - $4 || '.' || $5 || '(' || array_to_string( $6, ', ' ) || ')', + $4 || '.' || $5 || '(' || _ident_array_to_string( $6, ', ' ) || ')', $7 ); END; @@ -1871,18 +1889,18 @@ DECLARE BEGIN SELECT pk_table_name, pk_columns FROM pg_all_foreign_keys - WHERE fk_table_name = $1 - AND fk_columns = $2 + WHERE quote_ident(fk_table_name) = quote_ident($1) + AND _quote_ident_array(fk_columns) = _quote_ident_array($2) INTO tab, cols; RETURN is( -- have - $1 || '(' || array_to_string( $2, ', ' ) - || ') REFERENCES ' || COALESCE( tab || '(' || array_to_string( cols, ', ' ) || ')', 'NOTHING'), + $1 || '(' || _ident_array_to_string( $2, ', ' ) + || ') REFERENCES ' || COALESCE( tab || '(' || _ident_array_to_string( cols, ', ' ) || ')', 'NOTHING'), -- want - $1 || '(' || array_to_string( $2, ', ' ) + $1 || '(' || _ident_array_to_string( $2, ', ' ) || ') REFERENCES ' || - $3 || '(' || array_to_string( $4, ', ' ) || ')', + $3 || '(' || _ident_array_to_string( $4, ', ' ) || ')', $5 ); END; @@ -1892,9 +1910,9 @@ $$ LANGUAGE plpgsql; 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) || '(' || array_to_string( $3, ', ' ) + quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string( $3, ', ' ) || ') should reference ' || - $4 || '.' || $5 || '(' || array_to_string( $6, ', ' ) || ')' + $4 || '.' || $5 || '(' || _ident_array_to_string( $6, ', ' ) || ')' ); $$ LANGUAGE sql; @@ -1902,9 +1920,9 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION fk_ok ( NAME, NAME[], NAME, NAME[] ) RETURNS TEXT AS $$ SELECT fk_ok( $1, $2, $3, $4, - $1 || '(' || array_to_string( $2, ', ' ) + $1 || '(' || _ident_array_to_string( $2, ', ' ) || ') should reference ' || - $3 || '(' || array_to_string( $4, ', ' ) || ')' + $3 || '(' || _ident_array_to_string( $4, ', ' ) || ')' ); $$ LANGUAGE sql; @@ -1940,8 +1958,8 @@ RETURNS TEXT AS $$ SELECT true 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 + WHERE quote_ident(n.nspname) = quote_ident($1) + AND quote_ident(p.proname) = quote_ident($2) AND array_to_string(p.proargtypes::regtype[], ',') = array_to_string($3, ',') ), $4 ); @@ -1962,8 +1980,8 @@ RETURNS TEXT AS $$ SELECT true 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 + WHERE quote_ident(n.nspname) = quote_ident($1) + AND quote_ident(p.proname) = quote_ident($2) ), $3 ); $$ LANGUAGE SQL; @@ -1981,7 +1999,7 @@ RETURNS TEXT AS $$ EXISTS( SELECT true FROM pg_catalog.pg_proc p - WHERE p.proname = $1 + WHERE quote_ident(p.proname) = quote_ident($1) AND pg_catalog.pg_function_is_visible(p.oid) AND array_to_string(p.proargtypes::regtype[], ',') = array_to_string($2, ',') ), $3 @@ -2002,7 +2020,7 @@ RETURNS TEXT AS $$ EXISTS( SELECT true FROM pg_catalog.pg_proc p - WHERE p.proname = $1 + WHERE quote_ident(p.proname) = quote_ident($1) AND pg_catalog.pg_function_is_visible(p.oid) ), $2 ); @@ -2016,12 +2034,12 @@ $$ 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 -) + 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, func_names[], description ) @@ -2031,10 +2049,10 @@ DECLARE missing name[]; BEGIN SELECT ARRAY( - SELECT $2[i] + SELECT quote_ident($2[i]) FROM generate_series(1, array_upper($2, 1)) s(i) LEFT JOIN pg_catalog.pg_proc p - ON ($2[i] = p.proname) + ON (quote_ident($2[i]) = quote_ident(p.proname)) LEFT JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid AND n.nspname = 'pg_catalog' WHERE p.oid IS NULL @@ -2064,10 +2082,10 @@ DECLARE missing name[]; BEGIN SELECT ARRAY( - SELECT $1[i] + 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)) + ON (quote_ident($1[i]) = quote_ident(p.proname) AND pg_catalog.pg_function_is_visible(p.oid)) WHERE p.oid IS NULL ORDER BY s.i ) INTO missing; @@ -2085,7 +2103,7 @@ $$ LANGUAGE plpgsql; -- can( func_names[] ) CREATE OR REPLACE FUNCTION can ( NAME[] ) RETURNS TEXT AS $$ - SELECT can( $1, 'Schema ' || array_to_string(current_schemas(true), ' or ') || ' can' ); + SELECT can( $1, 'Schema ' || _ident_array_to_string(current_schemas(true), ' or ') || ' can' ); $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _ikeys( NAME, NAME, NAME) @@ -2097,9 +2115,9 @@ RETURNS NAME[] AS $$ 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_attribute a ON (ct.oid = a.attrelid) - WHERE ct.relname = $2 - AND ci.relname = $3 - AND n.nspname = $1 + WHERE ct.relname = quote_ident($2) + AND ci.relname = quote_ident($3) + AND n.nspname = quote_ident($1) AND a.attnum = ANY(x.indkey::int[]) ); $$ LANGUAGE sql; @@ -2112,8 +2130,8 @@ RETURNS NAME[] AS $$ 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_attribute a ON (ct.oid = a.attrelid) - WHERE ct.relname = $1 - AND ci.relname = $2 + WHERE quote_ident(ct.relname) = quote_ident($1) + AND quote_ident(ci.relname) = quote_ident($2) AND a.attnum = ANY(x.indkey::int[]) AND pg_catalog.pg_table_is_visible(ct.oid) ); @@ -2126,9 +2144,9 @@ RETURNS TEXT AS $$ 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; + WHERE quote_ident(ct.relname) = quote_ident($2) + AND quote_ident(ci.relname) = quote_ident($3) + AND quote_ident(n.nspname) = quote_ident($1); $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME) @@ -2137,8 +2155,8 @@ RETURNS TEXT AS $$ 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 + WHERE quote_ident(ct.relname) = quote_ident($1) + AND quote_ident(ci.relname) = quote_ident($2) AND pg_catalog.pg_table_is_visible(ct.oid) $$ LANGUAGE sql; @@ -2156,8 +2174,8 @@ BEGIN 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, ', ' ) || ')', + quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')', + quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string( $4, ', ' ) || ')', $5 ); END; @@ -2190,7 +2208,7 @@ BEGIN RETURN is( quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || expr || ')', - quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || LOWER($4) || ')', + quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || $4 || ')', $5 ); END; @@ -2216,8 +2234,8 @@ BEGIN 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, ', ' ) || ')', + quote_ident($2) || ' ON ' || quote_ident($1) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')', + quote_ident($2) || ' ON ' || quote_ident($1) || '(' || _ident_array_to_string( $3, ', ' ) || ')', $4 ); END; @@ -2232,7 +2250,10 @@ $$ 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 = LOWER($1) ); + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_namespace + WHERE quote_ident(nspname) = quote_ident($1) ); $$ LANGUAGE sql; -- _has_index( schema, table, index ) @@ -2272,17 +2293,17 @@ BEGIN IF _is_schema( $1 ) THEN -- Looking for an index within a schema. have_expr := _iexpr($1, $2, $3); - want_expr := lower($4); + want_expr := $4; descr := 'Index ' || quote_ident($3) || ' should exist'; idx := $3; tab := quote_ident($1) || '.' || quote_ident($2); ELSE -- Looking for an index without a schema spec. have_expr := _iexpr($1, $2); - want_expr := lower($3); + want_expr := $3; descr := $4; idx := $2; - tab := $1; + tab := quote_ident($1); END IF; IF have_expr IS NULL THEN @@ -2339,9 +2360,9 @@ BEGIN 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 + WHERE quote_ident(ct.relname) = quote_ident($2) + AND quote_ident(ci.relname) = quote_ident($3) + AND quote_ident(n.nspname) = quote_ident($1) INTO res; RETURN ok( COALESCE(res, false), $4 ); @@ -2367,8 +2388,8 @@ BEGIN 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 + WHERE quote_ident(ct.relname) = quote_ident($1) + AND quote_ident(ci.relname) = quote_ident($2) AND pg_catalog.pg_table_is_visible(ct.oid) INTO res; @@ -2389,7 +2410,7 @@ BEGIN 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 + WHERE quote_ident(ci.relname) = quote_ident($1) AND pg_catalog.pg_table_is_visible(ct.oid) INTO res; @@ -2411,9 +2432,9 @@ BEGIN 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 + WHERE quote_ident(ct.relname) = quote_ident($2) + AND quote_ident(ci.relname) = quote_ident($3) + AND quote_ident(n.nspname) = quote_ident($1) INTO res; RETURN ok( COALESCE(res, false), $4 ); @@ -2439,8 +2460,8 @@ BEGIN 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 + WHERE quote_ident(ct.relname) = quote_ident($1) + AND quote_ident(ci.relname) = quote_ident($2) AND pg_catalog.pg_table_is_visible(ct.oid) INTO res; @@ -2461,7 +2482,7 @@ BEGIN 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 + WHERE quote_ident(ci.relname) = quote_ident($1) AND pg_catalog.pg_table_is_visible(ct.oid) INTO res; @@ -2483,9 +2504,9 @@ BEGIN 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 + WHERE quote_ident(ct.relname) = quote_ident($2) + AND quote_ident(ci.relname) = quote_ident($3) + AND quote_ident(n.nspname) = quote_ident($1) INTO res; RETURN ok( COALESCE(res, false), $4 ); @@ -2512,8 +2533,8 @@ BEGIN 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 + WHERE quote_ident(ct.relname) = quote_ident($1) + AND quote_ident(ci.relname) = quote_ident($2) INTO res; RETURN ok( @@ -2532,7 +2553,7 @@ 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 + WHERE quote_ident(ci.relname) = quote_ident($1) INTO res; RETURN ok( @@ -2554,9 +2575,9 @@ BEGIN 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 + WHERE quote_ident(ct.relname) = quote_ident($2) + AND quote_ident(ci.relname) = quote_ident($3) + AND quote_ident(n.nspname) = quote_ident($1) INTO aname; return is( aname, LOWER($4)::name, $5 ); @@ -2583,8 +2604,8 @@ BEGIN 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 + WHERE quote_ident(ct.relname) = quote_ident($1) + AND quote_ident(ci.relname) = quote_ident($2) INTO aname; return is( @@ -2605,7 +2626,7 @@ BEGIN 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 + WHERE quote_ident(ci.relname) = quote_ident($1) INTO aname; return is( @@ -2626,9 +2647,9 @@ BEGIN 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 + WHERE quote_ident(n.nspname) = quote_ident($1) + AND quote_ident(c.relname) = quote_ident($2) + AND quote_ident(t.tgname) = quote_ident($3) INTO res; RETURN ok( COALESCE(res, false), $4 ); @@ -2653,8 +2674,8 @@ BEGIN 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 + WHERE quote_ident(c.relname) = quote_ident($1) + AND quote_ident(t.tgname) = quote_ident($2) AND pg_catalog.pg_table_is_visible(c.oid) INTO res; @@ -2677,9 +2698,9 @@ BEGIN 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 + WHERE quote_ident(nt.nspname) = quote_ident($1) + AND quote_ident(ct.relname) = quote_ident($2) + AND quote_ident(t.tgname) = quote_ident($3) INTO pname; RETURN is( pname, quote_ident($4) || '.' || quote_ident($5), $6 ); @@ -2705,8 +2726,8 @@ BEGIN 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 + WHERE quote_ident(ct.relname) = quote_ident($1) + AND quote_ident(t.tgname) = quote_ident($2) AND pg_catalog.pg_table_is_visible(ct.oid) INTO pname; @@ -2730,7 +2751,7 @@ RETURNS TEXT AS $$ EXISTS( SELECT true FROM pg_catalog.pg_namespace - WHERE LOWER(nspname) = LOWER($1) + WHERE quote_ident(nspname) = quote_ident($1) ), $2 ); $$ LANGUAGE sql; @@ -2748,7 +2769,7 @@ RETURNS TEXT AS $$ NOT EXISTS( SELECT true FROM pg_catalog.pg_namespace - WHERE LOWER(nspname) = LOWER($1) + WHERE quote_ident(nspname) = quote_ident($1) ), $2 ); $$ LANGUAGE sql; @@ -2766,7 +2787,7 @@ RETURNS TEXT AS $$ EXISTS( SELECT true FROM pg_catalog.pg_tablespace - WHERE LOWER(spcname) = LOWER($1) + WHERE quote_ident(spcname) = quote_ident($1) AND spclocation = $2 ), $3 ); @@ -2779,7 +2800,7 @@ RETURNS TEXT AS $$ EXISTS( SELECT true FROM pg_catalog.pg_tablespace - WHERE LOWER(spcname) = LOWER($1) + WHERE quote_ident(spcname) = quote_ident($1) ), $2 ); $$ LANGUAGE sql; @@ -2797,7 +2818,7 @@ RETURNS TEXT AS $$ NOT EXISTS( SELECT true FROM pg_catalog.pg_tablespace - WHERE LOWER(spcname) = LOWER($1) + WHERE quote_ident(spcname) = quote_ident($1) ), $2 ); $$ LANGUAGE sql; @@ -2815,8 +2836,8 @@ RETURNS BOOLEAN AS $$ 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 quote_ident(n.nspname) = quote_ident($1) + AND quote_ident(t.typname) = quote_ident($2) AND t.typtype = ANY( COALESCE($3, ARRAY['b', 'c', 'd', 'p', 'e']) ) ); $$ LANGUAGE sql; @@ -2987,7 +3008,7 @@ RETURNS TEXT AS $$ 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 quote_ident(n.nspname) = quote_ident($1) AND t.typname = $2 AND t.typtype = 'e' ORDER BY e.oid diff --git a/sql/fktap.sql b/sql/fktap.sql index d6f91d4d051d..b7bbf894c27c 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -144,7 +144,7 @@ SELECT * FROM check_test( 'col_is_fk( schema, table, column, description )', 'public.fk.name should be an fk', ' Table public.fk has foreign key constraints on these columns: - {pk_id}' + pk_id' ); SELECT * FROM check_test( @@ -153,8 +153,8 @@ SELECT * FROM check_test( 'col_is_fk( table, column, description )', 'fk3.name should be an fk', ' Table fk3 has foreign key constraints on these columns: - {pk2_num,pk2_dot} - {pk_id}' + pk2_num, pk2_dot + pk_id' ); -- Check table with multiple FKs. diff --git a/sql/index.sql b/sql/index.sql index 0959e9238d1f..fd4c8961754b 100644 --- a/sql/index.sql +++ b/sql/index.sql @@ -55,7 +55,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_index( 'public', 'sometab', 'idx_baz', 'LOWER(name)', 'whatever' ), + has_index( 'public', 'sometab', 'idx_baz', 'lower(name)', 'whatever' ), true, 'has_index() functional', 'whatever', @@ -111,7 +111,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_index( 'sometab', 'idx_baz', 'LOWER(name)', 'whatever' ), + has_index( 'sometab', 'idx_baz', 'lower(name)', 'whatever' ), true, 'has_index() no schema functional', 'whatever', @@ -119,7 +119,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_index( 'sometab', 'idx_baz', 'LOWER(name)' ), + has_index( 'sometab', 'idx_baz', 'lower(name)' ), true, 'has_index() no schema functional no desc', 'Index idx_baz should exist', @@ -178,7 +178,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_index( 'public', 'sometab', 'idx_baz', 'LOWER(wank)', 'whatever' ), + has_index( 'public', 'sometab', 'idx_baz', 'lower(wank)', 'whatever' ), false, 'has_index() functional fail', 'whatever', @@ -187,7 +187,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_index( 'sometab', 'idx_baz', 'LOWER(wank)', 'whatever' ), + has_index( 'sometab', 'idx_baz', 'lower(wank)', 'whatever' ), false, 'has_index() functional fail no schema', 'whatever', From 7634ffe6f3391b184997eff42b60f66e8acc603b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 6 Feb 2009 00:51:06 +0000 Subject: [PATCH 0286/1195] More notes on the `quote_ident()` change, and to do removed. --- Changes | 7 ++++--- README.pgtap | 2 -- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index ef4bd0307c50..cfe43657abe9 100644 --- a/Changes +++ b/Changes @@ -27,9 +27,10 @@ Revision history for pgTAP sensitively. - In the schema testing functions, now using `quote_ident()` to compare all identifiers, such as table names, schema names, column names, etc. - This means that tests should use only lowercase strings to specify - object names, unless mixed case objects were crated with double quote - characters. + It had been a mix of straight-forward string comparison, and + case-insensitive matching. This means that tests should use only + lowercase strings to specify object names, unless mixed case objects + were crated with double quote characters. 0.16 2009-02-03T17:37:03 - Switched from a crazy trinary logic in `is()` and `isnt()` to the use diff --git a/README.pgtap b/README.pgtap index 99763613815a..abb264d80a84 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2236,8 +2236,6 @@ To Do * Add options to `pg_prove` to allow it to run tests using the `runtests()` function rather than test files. -* Update all functions that compare idents to use `quote_ident()` instead of - just straight-ahead comparisons or `LOWER()` comparisons. * Useful schema testing functions to consider adding: * `has_cast()` * `has_role()`, `has_user()`, `has_group()` From 8fd3ebc50a601c6101de8754f68ea5f58cb8256d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 6 Feb 2009 01:41:01 +0000 Subject: [PATCH 0287/1195] Polymorphic `todo()`. --- Changes | 2 ++ README.pgtap | 1 + expected/todotap.out | 7 +++++-- pgtap.sql.in | 8 ++++++++ sql/todotap.sql | 20 +++++++++++++++++++- 5 files changed, 35 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index cfe43657abe9..9cbbb05912db 100644 --- a/Changes +++ b/Changes @@ -31,6 +31,8 @@ Revision history for pgTAP case-insensitive matching. This means that tests should use only lowercase strings to specify object names, unless mixed case objects were crated with double quote characters. + - Added version of `todo()` with the `why` and `how_many` arugments + reversed, so that I don't have to remember a specific order. 0.16 2009-02-03T17:37:03 - Switched from a crazy trinary logic in `is()` and `isnt()` to the use diff --git a/README.pgtap b/README.pgtap index abb264d80a84..017a72cca99c 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1722,6 +1722,7 @@ This will cause it to skip the same number of rows as would have been tested had the `WHEN` condition been true. ### `todo( why, how_many )` ### +### `todo( how_many, why )` ### ### `todo( how_many )` ### ### `todo( why )` ### diff --git a/expected/todotap.out b/expected/todotap.out index e59349eab891..c4d976f3251a 100644 --- a/expected/todotap.out +++ b/expected/todotap.out @@ -1,5 +1,5 @@ \unset ECHO -1..33 +1..36 ok 1 - todo fail ok 2 - todo pass ok 3 - TODO tests should display properly @@ -32,4 +32,7 @@ ok 29 - todo fail ok 30 - todo fail ok 31 - todo fail ok 32 - todo_start() and todo_end() should work properly with in_todo() -ok 33 - Should get an exception when todo_end() is called without todo_start() +ok 33 - todo fail +ok 34 - todo pass +ok 35 - Should be able to revers the arguments to todo() +ok 36 - Should get an exception when todo_end() is called without todo_start() diff --git a/pgtap.sql.in b/pgtap.sql.in index 4df8075a081c..8c053d8d700a 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -533,6 +533,14 @@ BEGIN 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 diff --git a/sql/todotap.sql b/sql/todotap.sql index cc29a968d93f..1562f535bd0d 100644 --- a/sql/todotap.sql +++ b/sql/todotap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(33); +SELECT plan(36); --SELECT * FROM no_plan(); /****************************************************************************/ @@ -163,6 +163,24 @@ SELECT ok( UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 29, 30, 31 ); +/****************************************************************************/ +-- Make sure we can reverse the arguments. +\echo ok 33 - todo fail +\echo ok 34 - todo pass +SELECT * FROM todo(2, 'just because' ); +SELECT is( + fail('This is a todo test' ) || ' +' + || fail('Another todo test'), + 'not ok 33 - This is a todo test # TODO just because +# Failed (TODO) test 33: "This is a todo test" +not ok 34 - Another todo test # TODO just because +# Failed (TODO) test 34: "Another todo test"', + 'Should be able to revers the arguments to todo()' +); + +UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 32, 33 ); + -- Test the exception when throws_ok() is available. SELECT CASE WHEN pg_version_num() < 80100 THEN pass('Should get an exception when todo_end() is called without todo_start()') From 32db84af578d2ab681c70daeba2a80415369661c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 6 Feb 2009 05:47:38 +0000 Subject: [PATCH 0288/1195] 8.2 compatibility. --- README.pgtap | 20 +++++++++------- compat/install-8.2.patch | 14 +++++------ sql/coltap.sql | 51 +++++++++++++++++++++++++++++----------- 3 files changed, 56 insertions(+), 29 deletions(-) diff --git a/README.pgtap b/README.pgtap index 017a72cca99c..2df0e5359531 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1038,9 +1038,8 @@ Will produce something like this: # want: text It will even tell you if the test fails because a column doesn't exist or -actually has no default. But you use `has_column()` and `col_has_default()` to -test those conditions before you call `col_default_is()`, right? *Right???* -Yeah, good, I thought so. +actually has no default. But use `has_column()` to make sure the column exists +first, eh? ### `col_default_is( schema, table, column, default, description )` ### ### `col_default_is( table, column, default, description )` ### @@ -1073,8 +1072,9 @@ But this will not: SELECT col_default_is( 'tab', 'name', 'foo'::text ); -You even need to properly cast your default value when it's a NULL. Just cast -it to the same data type as the column itself: +You even need to properly cast your default value when it's a NULL (which, by +the way, you can only properly test for in PostgreSQL 8.3 and later). Just +cast it to the same data type as the column itself: SELECT col_default_is( 'tab', age, NULL::integer ); @@ -1100,8 +1100,9 @@ Will produce something like this: # want: foo And if the test fails because the table or column in question does not exist, -the diagnostics will tell you that, too. But use `has_column()` to make sure -the column exists first, eh? +the diagnostics will tell you that, too. But you use `has_column()` and +`col_has_default()` to test those conditions before you call +`col_default_is()`, right? *Right???* Yeah, good, I thought so. ### `has_pk( schema, table, description )` ### ### `has_pk( table, description )` ### @@ -2209,7 +2210,10 @@ No changes. Everything should just work. 8.2 and Lower ------------- -A patch is applied that removes the `enum_has_lables()` function. Also, a number of casts are added to increase compatibility. The casts are: +A patch is applied that removes the `enum_has_labels()` function, and +`col_has_default()` cannot be used to test for columns specified with `DEFAULT +NULL` (even though that's the implied default default). Also, a number of +casts are added to increase compatibility. The casts are: * `boolean` to `text` * `text[]` to `text` diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 96b6c1674eef..e6e5132088a6 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,7 +1,7 @@ ---- pgtap.sql (revision 4476) -+++ pgtap.sql (working copy) -@@ -2751,63 +2751,6 @@ - SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || $1 || ' should not exist')::text ); +--- pgtap.sql.saf 2009-02-05 19:45:38.000000000 -0800 ++++ pgtap.sql 2009-02-05 19:46:10.000000000 -0800 +@@ -3006,63 +3006,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, desc ) @@ -14,7 +14,7 @@ - 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 quote_ident(n.nspname) = quote_ident($1) - AND t.typname = $2 - AND t.typtype = 'e' - ORDER BY e.oid @@ -29,7 +29,7 @@ -RETURNS TEXT AS $$ - SELECT enum_has_labels( - $1, $2, $3, -- 'Enum ' || $1 || '.' || $2 || ' should have labels (' || array_to_string( $3, ', ' ) || ')' +- 'Enum ' || quote_ident($1) || '.' || quote_ident($2) || ' should have labels (' || array_to_string( $3, ', ' ) || ')' - ); -$$ LANGUAGE sql; - @@ -57,7 +57,7 @@ -RETURNS TEXT AS $$ - SELECT enum_has_labels( - $1, $2, -- 'Enum ' || $1 || ' should have labels (' || array_to_string( $2, ', ' ) || ')' +- 'Enum ' || quote_ident($1) || ' should have labels (' || array_to_string( $2, ', ' ) || ')' - ); -$$ LANGUAGE sql; - diff --git a/sql/coltap.sql b/sql/coltap.sql index 1ea419f3fd6c..4de9ebd548df 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -387,20 +387,43 @@ SELECT * FROM check_test( ); -- Make sure it works with a NULL default. -SELECT * FROM check_test( - col_default_is( 'sometab', 'numb', NULL::numeric, 'desc' ), - true, - 'col_default_is( tab, col, NULL, desc )', - 'desc', - '' -); -SELECT * FROM check_test( - col_default_is( 'sometab', 'numb', NULL::numeric ), - true, - 'col_default_is( tab, col, NULL )', - 'Column sometab.numb should default to NULL', - '' -); +CREATE OR REPLACE FUNCTION nulltest () RETURNS SETOF TEXT AS $$ +DECLARE + tap text; +BEGIN + IF pg_version_num() < 80300 THEN + -- Before 8.2, DEFAULT NULL was ignored. + RETURN NEXT pass('col_default_is( tab, col, NULL, desc ) should pass'); + RETURN NEXT pass('col_default_is( tab, col, NULL, desc ) should have the proper description'); + RETURN NEXT pass('col_default_is( tab, col, NULL, desc ) should have the proper diagnostics'); + RETURN NEXT pass('col_default_is( tab, col, NULL ) should pass'); + RETURN NEXT pass('col_default_is( tab, col, NULL ) should have the proper description'); + RETURN NEXT pass('col_default_is( tab, col, NULL ) should have the proper diagnostics'); + ELSE + -- In 8.3 and later, we can handle DEFAULT NULL correctly. + FOR tap IN SELECT * FROM check_test( + col_default_is( 'sometab', 'numb', NULL::numeric, 'desc' ), + true, + 'col_default_is( tab, col, NULL, desc )', + 'desc', + '' + ) LOOP + RETURN NEXT tap; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + col_default_is( 'sometab', 'numb', NULL::numeric ), + true, + 'col_default_is( tab, col, NULL )', + 'Column sometab.numb should default to NULL', + '' + ) LOOP + RETURN NEXT tap; + END LOOP; + END IF; +END; +$$ LANGUAGE plpgsql; +SELECT * FROM nulltest(); -- Make sure that it fails when there is no default. SELECT * FROM check_test( From c364ea37841e882a068d16d797e12ad1942d4582 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 6 Feb 2009 06:37:31 +0000 Subject: [PATCH 0289/1195] Compatibility fixes: * Modified `sql/coltap.sql` to run on 8.0 and later (lowest common denominator SKIP testing). * Updated `sql/enumtap.sql` and `sql/hastap.sql` to work properly when testing a build created with `TAPSCHEMA`. * Updated the 8.1 patch for new line numbers. * Updated the 8.0 patch for new line numbers, and to: * Just `RETURN` instead of `RETURN NULL` where possible. * Fix the incompatibility in the reformatting of the `_def_is()` function. --- compat/install-8.0.patch | 54 +++++++++++++++++++++++----------------- compat/install-8.1.patch | 16 ++++++------ sql/coltap.sql | 7 +++--- sql/enumtap.sql | 2 +- sql/hastap.sql | 8 +++--- 5 files changed, 48 insertions(+), 39 deletions(-) diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index 3b8a3eaef591..d7905fd836dc 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,5 +1,5 @@ ---- pgtap.sql 2009-02-02 11:39:23.000000000 -0800 -+++ pgtap.sql.saf 2009-02-02 11:39:10.000000000 -0800 +--- pgtap.sql.saf 2009-02-05 22:20:59.000000000 -0800 ++++ pgtap.sql 2009-02-05 22:29:29.000000000 -0800 @@ -12,6 +12,22 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA, public; @@ -23,7 +23,7 @@ CREATE OR REPLACE FUNCTION pg_typeof("any") RETURNS regtype AS '$libdir/pgtap' -@@ -95,53 +111,63 @@ +@@ -97,53 +113,63 @@ CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ DECLARE @@ -70,7 +70,7 @@ + quote_literal($1) || ' AND value = ' || $2 LOOP + RETURN rec.id; + END LOOP; -+ RETURN NULL; ++ RETURN; END; $$ LANGUAGE plpgsql strict; @@ -85,7 +85,7 @@ + FOR rec IN EXECUTE 'SELECT note FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' LOOP + RETURN rec.note; + END LOOP; -+ RETURN NULL; ++ RETURN; END; $$ LANGUAGE plpgsql strict; @@ -100,11 +100,11 @@ + FOR rec IN EXECUTE 'SELECT note FROM __tcache__ WHERE id = ' || $1 || ' LIMIT 1' LOOP + RETURN rec.note; + END LOOP; -+ RETURN NULL; ++ RETURN; END; $$ LANGUAGE plpgsql strict; -@@ -205,10 +231,12 @@ +@@ -207,10 +233,12 @@ CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ DECLARE @@ -116,11 +116,11 @@ + FOR rec IN EXECUTE 'SELECT COUNT(*)::INTEGER AS cnt FROM __tresults__ WHERE ok = FALSE' LOOP + RETURN rec.cnt; + END LOOP; -+ RETURN NULL; ++ RETURN; END; $$ LANGUAGE plpgsql strict; -@@ -482,13 +510,16 @@ +@@ -484,13 +512,16 @@ want ALIAS FOR $3; descr ALIAS FOR $4; result BOOLEAN; @@ -140,28 +140,36 @@ output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE '\n' || diag( ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -1049,15 +1080,16 @@ - CREATE OR REPLACE FUNCTION _def_is( TEXT, anyelement, TEXT ) +@@ -1201,19 +1232,21 @@ + CREATE OR REPLACE FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE - thing text; -+ rec RECORD; ++ ret RECORD; BEGIN IF $1 ~ '^[^'']+[(]' THEN -- It's a functional default. - RETURN is( $1, $2, $3 ); + RETURN is( $1, $3, $4 ); END IF; -- EXECUTE 'SELECT is(' || COALESCE($1, 'NULL::text') || ', ' || quote_literal($2) || ', ' || quote_literal($3) || ')' -- INTO thing; + +- EXECUTE 'SELECT is(' +- || COALESCE($1, 'NULL' || '::' || $2) || '::' || $2 || ', ' +- || COALESCE(quote_literal($3), 'NULL') || '::' || $2 || ', ' +- || COALESCE(quote_literal($4), 'NULL') +- || ')' INTO thing; - RETURN thing; -+ FOR rec IN EXECUTE 'SELECT is(' || COALESCE($1, 'NULL::text') || ', ' || quote_literal($2) || ', ' || quote_literal($3) || ')' || 'AS is' LOOP -+ RETURN rec.is; ++ FOR ret IN EXECUTE 'SELECT is(' ++ || COALESCE($1, 'NULL' || '::' || $2) || '::' || $2 || ', ' ++ || COALESCE(quote_literal($3), 'NULL') || '::' || $2 || ', ' ++ || COALESCE(quote_literal($4), 'NULL') ++ || ') AS a' LOOP ++ RETURN ret.a; + END LOOP; -+ RETURN NULL; ++ RETURN; END; $$ LANGUAGE plpgsql; -@@ -2761,6 +2793,7 @@ +@@ -3016,6 +3049,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -169,7 +177,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -2771,8 +2804,10 @@ +@@ -3026,8 +3060,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. @@ -182,7 +190,7 @@ -- Now delete those results. EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; -@@ -2881,7 +2916,7 @@ +@@ -3136,7 +3172,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -191,7 +199,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -2889,8 +2924,8 @@ +@@ -3144,8 +3180,8 @@ -- 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. @@ -202,7 +210,7 @@ END LOOP; END LOOP; RETURN; -@@ -2950,116 +2985,6 @@ +@@ -3205,116 +3241,6 @@ END $$ LANGUAGE plpgsql; diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index 4d7e160ff955..d4e19fc2fd2e 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ ---- pgtap.sql.orig 2009-01-19 16:02:49.000000000 -0800 -+++ pgtap.sql 2009-01-19 16:04:58.000000000 -0800 -@@ -2679,7 +2679,7 @@ +--- pgtap.sql 2009-02-05 22:14:29.000000000 -0800 ++++ pgtap.sql.new 2009-02-05 22:13:45.000000000 -0800 +@@ -3136,7 +3136,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -9,7 +9,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -2687,8 +2687,8 @@ +@@ -3144,8 +3144,8 @@ -- 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. @@ -20,7 +20,7 @@ END LOOP; END LOOP; RETURN; -@@ -2758,14 +2758,14 @@ +@@ -3213,14 +3213,14 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -37,7 +37,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -2780,15 +2780,15 @@ +@@ -3235,15 +3235,15 @@ IF verbose THEN RETURN NEXT diag(tests[i] || '()'); END IF; -- Run the setup functions. @@ -57,7 +57,7 @@ -- Remember how many failed and then roll back. num_faild := num_faild + num_failed(); -@@ -2803,7 +2803,7 @@ +@@ -3258,7 +3258,7 @@ END LOOP; -- Run the shutdown functions. @@ -66,7 +66,7 @@ -- Raise an exception to rollback any changes. RAISE EXCEPTION '__TAP_ROLLBACK__'; -@@ -2814,8 +2814,8 @@ +@@ -3269,8 +3269,8 @@ END IF; END; -- Finish up. diff --git a/sql/coltap.sql b/sql/coltap.sql index 4de9ebd548df..d7179f974c43 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -389,7 +389,7 @@ SELECT * FROM check_test( -- Make sure it works with a NULL default. CREATE OR REPLACE FUNCTION nulltest () RETURNS SETOF TEXT AS $$ DECLARE - tap text; + tap record; BEGIN IF pg_version_num() < 80300 THEN -- Before 8.2, DEFAULT NULL was ignored. @@ -417,10 +417,11 @@ BEGIN 'col_default_is( tab, col, NULL )', 'Column sometab.numb should default to NULL', '' - ) LOOP - RETURN NEXT tap; + ) AS a(b) LOOP + RETURN NEXT tap.b; END LOOP; END IF; + RETURN; END; $$ LANGUAGE plpgsql; SELECT * FROM nulltest(); diff --git a/sql/enumtap.sql b/sql/enumtap.sql index 0f463a74661a..ea2a231e04d6 100644 --- a/sql/enumtap.sql +++ b/sql/enumtap.sql @@ -8,7 +8,7 @@ SELECT plan(72); -- This will be rolled back. :-) SET client_min_messages = warning; -CREATE TYPE bug_status AS ENUM ('new', 'open', 'closed'); +CREATE TYPE public.bug_status AS ENUM ('new', 'open', 'closed'); RESET client_min_messages; /****************************************************************************/ diff --git a/sql/hastap.sql b/sql/hastap.sql index 8d0cbe4a689e..7022b08316fc 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -8,22 +8,22 @@ SELECT plan(285); -- This will be rolled back. :-) SET client_min_messages = warning; -CREATE TABLE sometab( +CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '', numb NUMERIC(10, 2), myint NUMERIC(8) ); -CREATE TYPE sometype AS ( +CREATE TYPE public.sometype AS ( id INT, name TEXT ); -CREATE DOMAIN us_postal_code AS TEXT CHECK( +CREATE DOMAIN public.us_postal_code AS TEXT CHECK( VALUE ~ '^[[:digit:]]{5}$' OR VALUE ~ '^[[:digit:]]{5}-[[:digit:]]{4}$' ); -CREATE SEQUENCE someseq; +CREATE SEQUENCE public.someseq; CREATE SCHEMA someschema; RESET client_min_messages; From bedf7d0b5d0e2bfa3330de12996bb60fe89d75cb Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 6 Feb 2009 17:45:01 +0000 Subject: [PATCH 0290/1195] Timestamped for 0.17 release. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 9cbbb05912db..abc16a081969 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,6 @@ Revision history for pgTAP -0.17 +0.17 2009-02-06T17:51:52 - Fixed `col_default_is()` so that it works properly in cases where the default value is `NULL`. - Fixed `col_default_is()` to gracefully handle different data types, From dcb3c2bf2b898bc4a43cd18c33c307203d2f4289 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 6 Feb 2009 19:19:03 +0000 Subject: [PATCH 0291/1195] * Incremented version number to 0.18. * Fixed bug where a table with dropped columns caused `col_def_is()` to die. --- Changes | 6 ++++++ Makefile | 2 +- README.pgtap | 2 +- bin/pg_prove | 2 +- pgtap.sql.in | 8 +++----- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Changes b/Changes index abc16a081969..527f637e5026 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,11 @@ Revision history for pgTAP +0.18 + - Fixed `pg_version_num()`. It was broken in 0.16; sorry about that! + - Fixed a bug in `col_type_is()` where it would die if it was looking + for a type in a table that had dropped columns. Thanks to depesz and + RhodiumToad on #postgresql for the help nailing that one down! + 0.17 2009-02-06T17:51:52 - Fixed `col_default_is()` so that it works properly in cases where the default value is `NULL`. diff --git a/Makefile b/Makefile index e03ede9fa168..7804cedd2deb 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') PGVER_MAJOR = $(shell echo $(VERSION) | awk -F. '{ print ($$1 + 0) }') PGVER_MINOR = $(shell echo $(VERSION) | awk -F. '{ print ($$2 + 0) }') PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') -PGTAP_VERSION = 0.17 +PGTAP_VERSION = 0.18 # Compile the C code only if we're on 8.3 or older. ifneq ($(PGVER_MINOR), 4) diff --git a/README.pgtap b/README.pgtap index 2df0e5359531..2f3b99245409 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.17 +pgTAP 0.18 ========== pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/bin/pg_prove b/bin/pg_prove index 445a7bd678c2..1f58ac6c9e9f 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -6,7 +6,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.17'; +our $VERSION = '0.18'; Getopt::Long::Configure (qw(bundling)); diff --git a/pgtap.sql.in b/pgtap.sql.in index 8c053d8d700a..d19dd8a6ca2a 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -24,7 +24,7 @@ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT s.a[1]::int * 10000 - + COALESCE(substring(s.a[2] FROM '[[:digit:]]+')::int, 0) + + 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 @@ -1066,10 +1066,9 @@ BEGIN FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c WHERE a.attrelid = c.oid AND pg_table_is_visible(c.oid) - AND pg_type_is_visible(a.atttypid) + AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END AND quote_ident(c.relname) = quote_ident($2) AND attnum > 0 - AND NOT attisdropped AND quote_ident(a.attname) = quote_ident($3); ELSE IF NOT _cexists( $1, $2, $3 ) THEN @@ -1080,12 +1079,11 @@ BEGIN SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) into actual_type FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a WHERE n.oid = c.relnamespace - AND pg_type_is_visible(a.atttypid) + AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END AND c.oid = a.attrelid AND quote_ident(n.nspname) = quote_ident($1) AND quote_ident(c.relname) = quote_ident($2) AND attnum > 0 - AND NOT attisdropped AND quote_ident(a.attname) = quote_ident($3); END IF; From 9cb5d6c10889e496c53c55874e49105bc33e0955 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 6 Feb 2009 19:35:06 +0000 Subject: [PATCH 0292/1195] * Fixed failing test in `sql/coltap.sql` on PostgreSQL 8.3. Grrr. * Updated 8.2 compat patch. --- Changes | 1 + compat/install-8.2.patch | 6 +++--- sql/coltap.sql | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 527f637e5026..b2c4f5315379 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,7 @@ Revision history for pgTAP - Fixed a bug in `col_type_is()` where it would die if it was looking for a type in a table that had dropped columns. Thanks to depesz and RhodiumToad on #postgresql for the help nailing that one down! + - Fixed a test failure in `sql/coltap.sql` on PostgreSQL 8.3. 0.17 2009-02-06T17:51:52 - Fixed `col_default_is()` so that it works properly in cases where the diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index e6e5132088a6..3354f9b68326 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,6 +1,6 @@ ---- pgtap.sql.saf 2009-02-05 19:45:38.000000000 -0800 -+++ pgtap.sql 2009-02-05 19:46:10.000000000 -0800 -@@ -3006,63 +3006,6 @@ +--- pgtap.sql.saf 2009-02-06 11:31:08.000000000 -0800 ++++ pgtap.sql 2009-02-06 11:31:14.000000000 -0800 +@@ -3004,63 +3004,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; diff --git a/sql/coltap.sql b/sql/coltap.sql index d7179f974c43..70856c2ebc0d 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -407,8 +407,8 @@ BEGIN 'col_default_is( tab, col, NULL, desc )', 'desc', '' - ) LOOP - RETURN NEXT tap; + ) AS a(b) LOOP + RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( From f7e0c8ac3a0c8d7a080b1e45116be5e6243e183a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 6 Feb 2009 19:58:20 +0000 Subject: [PATCH 0293/1195] * Updated compat patches. * Fixed some `Makefile` bugs. --- Changes | 5 +++++ Makefile | 3 ++- compat/install-8.0.patch | 16 ++++++++-------- compat/install-8.1.patch | 16 ++++++++-------- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/Changes b/Changes index b2c4f5315379..17ca5a64209e 100644 --- a/Changes +++ b/Changes @@ -6,6 +6,11 @@ Revision history for pgTAP for a type in a table that had dropped columns. Thanks to depesz and RhodiumToad on #postgresql for the help nailing that one down! - Fixed a test failure in `sql/coltap.sql` on PostgreSQL 8.3. + - Fixed a bug in the `Makefile` where it did not properly point to + `pg_config` when building from the `compat` directory in the + PostgreSQL source tree. + - Fixed a bug in the `Makefile` where the `test_setup.sql` file, which + is required for tests, was not always getting created. 0.17 2009-02-06T17:51:52 - Fixed `col_default_is()` so that it works properly in cases where the diff --git a/Makefile b/Makefile index 7804cedd2deb..a337e2fa95e5 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) else top_builddir = ../.. -PG_CONFIG := $(top_builddir)/src/bin/pg_config +PG_CONFIG := $(top_builddir)/src/bin/pg_config/pg_config endif # We need to do various things with various versions of PostgreSQL. @@ -127,6 +127,7 @@ endif test_setup.sql: test_setup.sql.in ifdef TAPSCHEMA sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' $< >$@ +else cp $< $@ endif diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index d7905fd836dc..c7b17ca3705e 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.saf 2009-02-05 22:20:59.000000000 -0800 -+++ pgtap.sql 2009-02-05 22:29:29.000000000 -0800 +--- pgtap.sql.saf 2009-02-06 11:49:08.000000000 -0800 ++++ pgtap.sql 2009-02-06 11:49:16.000000000 -0800 @@ -12,6 +12,22 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA, public; @@ -140,7 +140,7 @@ output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE '\n' || diag( ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -1201,19 +1232,21 @@ +@@ -1199,19 +1230,21 @@ CREATE OR REPLACE FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -169,7 +169,7 @@ END; $$ LANGUAGE plpgsql; -@@ -3016,6 +3049,7 @@ +@@ -3014,6 +3047,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -177,7 +177,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -3026,8 +3060,10 @@ +@@ -3024,8 +3058,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. @@ -190,7 +190,7 @@ -- Now delete those results. EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; -@@ -3136,7 +3172,7 @@ +@@ -3134,7 +3170,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -199,7 +199,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -3144,8 +3180,8 @@ +@@ -3142,8 +3178,8 @@ -- 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. @@ -210,7 +210,7 @@ END LOOP; END LOOP; RETURN; -@@ -3205,116 +3241,6 @@ +@@ -3203,116 +3239,6 @@ END $$ LANGUAGE plpgsql; diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index d4e19fc2fd2e..6e23b8681778 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ ---- pgtap.sql 2009-02-05 22:14:29.000000000 -0800 -+++ pgtap.sql.new 2009-02-05 22:13:45.000000000 -0800 -@@ -3136,7 +3136,7 @@ +--- pgtap.sql.saf 2009-02-06 11:43:37.000000000 -0800 ++++ pgtap.sql 2009-02-06 11:43:43.000000000 -0800 +@@ -3134,7 +3134,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -9,7 +9,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -3144,8 +3144,8 @@ +@@ -3142,8 +3142,8 @@ -- 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. @@ -20,7 +20,7 @@ END LOOP; END LOOP; RETURN; -@@ -3213,14 +3213,14 @@ +@@ -3211,14 +3211,14 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -37,7 +37,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -3235,15 +3235,15 @@ +@@ -3233,15 +3233,15 @@ IF verbose THEN RETURN NEXT diag(tests[i] || '()'); END IF; -- Run the setup functions. @@ -57,7 +57,7 @@ -- Remember how many failed and then roll back. num_faild := num_faild + num_failed(); -@@ -3258,7 +3258,7 @@ +@@ -3256,7 +3256,7 @@ END LOOP; -- Run the shutdown functions. @@ -66,7 +66,7 @@ -- Raise an exception to rollback any changes. RAISE EXCEPTION '__TAP_ROLLBACK__'; -@@ -3269,8 +3269,8 @@ +@@ -3267,8 +3267,8 @@ END IF; END; -- Finish up. From c320ad6fb5962b7c373a9194e234ed153a386ede Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 6 Feb 2009 19:59:22 +0000 Subject: [PATCH 0294/1195] Timestamped for 0.18 release. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 17ca5a64209e..d085ba6e4b55 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,6 @@ Revision history for pgTAP -0.18 +0.18 2009-02-06T20:06:00 - Fixed `pg_version_num()`. It was broken in 0.16; sorry about that! - Fixed a bug in `col_type_is()` where it would die if it was looking for a type in a table that had dropped columns. Thanks to depesz and From 79f506fe89b1628ffd7f9ed4d280cb58ecdd0984 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 6 Feb 2009 20:19:44 +0000 Subject: [PATCH 0295/1195] Incremented version number to 0.19. --- Changes | 2 ++ Makefile | 2 +- README.pgtap | 2 +- bin/pg_prove | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index d085ba6e4b55..185d87a2966d 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Revision history for pgTAP +0.19 + 0.18 2009-02-06T20:06:00 - Fixed `pg_version_num()`. It was broken in 0.16; sorry about that! - Fixed a bug in `col_type_is()` where it would die if it was looking diff --git a/Makefile b/Makefile index a337e2fa95e5..725af4a3e9a4 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') PGVER_MAJOR = $(shell echo $(VERSION) | awk -F. '{ print ($$1 + 0) }') PGVER_MINOR = $(shell echo $(VERSION) | awk -F. '{ print ($$2 + 0) }') PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') -PGTAP_VERSION = 0.18 +PGTAP_VERSION = 0.19 # Compile the C code only if we're on 8.3 or older. ifneq ($(PGVER_MINOR), 4) diff --git a/README.pgtap b/README.pgtap index 2f3b99245409..f1bcd3fc4276 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.18 +pgTAP 0.19 ========== pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/bin/pg_prove b/bin/pg_prove index 1f58ac6c9e9f..968496ca117e 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -6,7 +6,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.18'; +our $VERSION = '0.19'; Getopt::Long::Configure (qw(bundling)); From 81da0f6b394eab60dae1e4c11b31ba0bff2d2ebc Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 6 Feb 2009 20:24:04 +0000 Subject: [PATCH 0296/1195] Explain that functional defaults must be cast to `TEXT`. --- Changes | 3 +++ README.pgtap | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 185d87a2966d..19149b71e976 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Revision history for pgTAP 0.19 + - Fixed documentation for `col_default_is()` to explain that a + functional default must be cast to `TEXT` in the call to + `col_default_is()`. 0.18 2009-02-06T20:06:00 - Fixed `pg_version_num()`. It was broken in 0.16; sorry about that! diff --git a/README.pgtap b/README.pgtap index f1bcd3fc4276..5f6a601c59e1 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1079,9 +1079,10 @@ cast it to the same data type as the column itself: SELECT col_default_is( 'tab', age, NULL::integer ); You can also test for functional defaults. Just specify the function call as a -string: +string (and, yes, you have to cast it to `TEXT`; just think of the function as +code in a string): - SELECT col_default_is( 'user', 'created_at', 'now()' ); + SELECT col_default_is( 'user', 'created_at', 'now()'::text ); If the test fails, it will output useful diagnostics. For example, this test: From ee942a38922831b40b0b0dacfce49525323f048f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 7 Feb 2009 23:54:47 +0000 Subject: [PATCH 0297/1195] Simpler handling of `col_default_is()` by allowing defaults to be passed as raw string, rather than always having to cast them to `TEXT` (which was especially annoying for expression defaults!). --- Changes | 7 ++++--- README.pgtap | 19 +++++++----------- expected/coltap.out | 38 +++++++++++++++++++++++++---------- pgtap.sql.in | 48 +++++++++++++++++++++++++++++++++++++++------ sql/coltap.sql | 47 +++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 127 insertions(+), 32 deletions(-) diff --git a/Changes b/Changes index 19149b71e976..ee1499335568 100644 --- a/Changes +++ b/Changes @@ -1,9 +1,10 @@ Revision history for pgTAP 0.19 - - Fixed documentation for `col_default_is()` to explain that a - functional default must be cast to `TEXT` in the call to - `col_default_is()`. + - Added a alernate versions of `col_default_is()` to better handle the + common case when a default is specified as a string, such as a text or + expression default. This means that you have to do a lot less casting + of default values specified in the arguments to `col_default_is()`. 0.18 2009-02-06T20:06:00 - Fixed `pg_version_num()`. It was broken in 0.16; sorry about that! diff --git a/README.pgtap b/README.pgtap index 5f6a601c59e1..c8cf7a1e63be 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1064,25 +1064,20 @@ that this test will fail if the table or column in question does not exist. The default argument must have an unambiguous type in order for the call to succeed. If you see an error such as 'ERROR: could not determine polymorphic type because input has type "unknown"', it's because you forgot to cast the -expected value to its proper type. IOW, this will fail: +expected value, probabaly a `NULL` (which, by the way, you can only properly +test for in PostgreSQL 8.3 and later), to its proper type. IOW, this will +fail: - SELECT col_default_is( 'tab', 'name', 'foo' ); + SELECT col_default_is( 'tab', age, NULL ); But this will not: - SELECT col_default_is( 'tab', 'name', 'foo'::text ); - -You even need to properly cast your default value when it's a NULL (which, by -the way, you can only properly test for in PostgreSQL 8.3 and later). Just -cast it to the same data type as the column itself: - SELECT col_default_is( 'tab', age, NULL::integer ); You can also test for functional defaults. Just specify the function call as a -string (and, yes, you have to cast it to `TEXT`; just think of the function as -code in a string): +string: - SELECT col_default_is( 'user', 'created_at', 'now()'::text ); + SELECT col_default_is( 'user', 'created_at', 'now()' ); If the test fails, it will output useful diagnostics. For example, this test: @@ -1090,7 +1085,7 @@ If the test fails, it will output useful diagnostics. For example, this test: 'pg_catalog', 'pg_type', 'typname', - 'foo'::text, + 'foo', 'check typname' ); diff --git a/expected/coltap.out b/expected/coltap.out index a00bf5a9dcb1..3e7f5859baaf 100644 --- a/expected/coltap.out +++ b/expected/coltap.out @@ -1,5 +1,5 @@ \unset ECHO -1..153 +1..171 ok 1 - col_not_null( sch, tab, col, desc ) should pass ok 2 - col_not_null( sch, tab, col, desc ) should have the proper description ok 3 - col_not_null( sch, tab, col, desc ) should have the proper diagnostics @@ -144,12 +144,30 @@ ok 141 - col_default_is( tab, col, bogus, desc ) should have the proper diagnost ok 142 - col_default_is( tab, col, bogus ) should fail ok 143 - col_default_is( tab, col, bogus ) should have the proper description ok 144 - col_default_is( tab, col, bogus ) should have the proper diagnostics -ok 145 - col_default_is( sch, tab, col, def, desc ) should fail -ok 146 - col_default_is( sch, tab, col, def, desc ) should have the proper description -ok 147 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics -ok 148 - col_default_is( tab, col, def, desc ) should fail -ok 149 - col_default_is( tab, col, def, desc ) should have the proper description -ok 150 - col_default_is( tab, col, def, desc ) should have the proper diagnostics -ok 151 - col_default_is( tab, col, def ) should fail -ok 152 - col_default_is( tab, col, def ) should have the proper description -ok 153 - col_default_is( tab, col, def ) should have the proper diagnostics +ok 145 - col_default_is( tab, col, expression ) should pass +ok 146 - col_default_is( tab, col, expression ) should have the proper description +ok 147 - col_default_is( tab, col, expression ) should have the proper diagnostics +ok 148 - col_default_is( tab, col, expression::text ) should pass +ok 149 - col_default_is( tab, col, expression::text ) should have the proper description +ok 150 - col_default_is( tab, col, expression::text ) should have the proper diagnostics +ok 151 - col_default_is( tab, col, expression, desc ) should pass +ok 152 - col_default_is( tab, col, expression, desc ) should have the proper description +ok 153 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics +ok 154 - col_default_is( tab, col, expression, desc ) should pass +ok 155 - col_default_is( tab, col, expression, desc ) should have the proper description +ok 156 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics +ok 157 - col_default_is( schema, tab, col, expression, desc ) should pass +ok 158 - col_default_is( schema, tab, col, expression, desc ) should have the proper description +ok 159 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics +ok 160 - col_default_is( schema, tab, col, expression, desc ) should pass +ok 161 - col_default_is( schema, tab, col, expression, desc ) should have the proper description +ok 162 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics +ok 163 - col_default_is( sch, tab, col, def, desc ) should fail +ok 164 - col_default_is( sch, tab, col, def, desc ) should have the proper description +ok 165 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics +ok 166 - col_default_is( tab, col, def, desc ) should fail +ok 167 - col_default_is( tab, col, def, desc ) should have the proper description +ok 168 - col_default_is( tab, col, def, desc ) should have the proper diagnostics +ok 169 - col_default_is( tab, col, def ) should fail +ok 170 - col_default_is( tab, col, def ) should have the proper description +ok 171 - col_default_is( tab, col, def ) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index d19dd8a6ca2a..69daada2756a 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1215,8 +1215,8 @@ BEGIN END; $$ LANGUAGE plpgsql; --- col_default_is( schema, table, column, default, description ) -CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, NAME, anyelement, TEXT ) +-- _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 @@ -1249,8 +1249,8 @@ BEGIN END; $$ LANGUAGE plpgsql; --- col_default_is( table, column, default, description ) -CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, anyelement, TEXT ) +-- _cdi( table, column, default, description ) +CREATE OR REPLACE FUNCTION _cdi ( NAME, NAME, anyelement, TEXT ) RETURNS TEXT AS $$ BEGIN IF NOT _cexists( $1, $2 ) THEN @@ -1281,8 +1281,8 @@ BEGIN END; $$ LANGUAGE plpgsql; --- col_default_is( table, column, default ) -CREATE OR REPLACE FUNCTION col_default_is ( NAME, NAME, anyelement ) +-- _cdi( table, column, default ) +CREATE OR REPLACE FUNCTION _cdi ( NAME, NAME, anyelement ) RETURNS TEXT AS $$ SELECT col_default_is( $1, $2, $3, @@ -1291,6 +1291,42 @@ RETURNS TEXT AS $$ ); $$ 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 $$ diff --git a/sql/coltap.sql b/sql/coltap.sql index 70856c2ebc0d..3dffac2f8ae2 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(153); +SELECT plan(171); --SELECT * from no_plan(); -- This will be rolled back. :-) @@ -13,6 +13,7 @@ CREATE TABLE public.sometab( name TEXT DEFAULT '', numb NUMERIC(10, 2) DEFAULT NULL, myint NUMERIC(8) DEFAULT 24, + myat TIMESTAMP DEFAULT NOW(), plain INTEGER ); RESET client_min_messages; @@ -443,6 +444,50 @@ SELECT * FROM check_test( ' Column sometab.plain has no default' ); +-- Make sure that it works when the default is an expression. +SELECT * FROM check_test( + col_default_is( 'sometab', 'myat', 'now()' ), + true, + 'col_default_is( tab, col, expression )', + 'Column sometab.myat should default to ''now()''', + '' +); +SELECT * FROM check_test( + col_default_is( 'sometab', 'myat', 'now()'::text ), + true, + 'col_default_is( tab, col, expression::text )', + 'Column sometab.myat should default to ''now()''', + '' +); +SELECT * FROM check_test( + col_default_is( 'sometab', 'myat', 'now()', 'desc' ), + true, + 'col_default_is( tab, col, expression, desc )', + 'desc', + '' +); +SELECT * FROM check_test( + col_default_is( 'sometab', 'myat', 'now()', 'desc'::text ), + true, + 'col_default_is( tab, col, expression, desc )', + 'desc', + '' +); +SELECT * FROM check_test( + col_default_is( 'public', 'sometab', 'myat', 'now()', 'desc' ), + true, + 'col_default_is( schema, tab, col, expression, desc )', + 'desc', + '' +); +SELECT * FROM check_test( + col_default_is( 'public', 'sometab', 'myat', 'now()', 'desc'::text ), + true, + 'col_default_is( schema, tab, col, expression, desc )', + 'desc', + '' +); + -- Check with a nonexistent column. SELECT * FROM check_test( col_default_is( 'public', 'sometab', '__asdfasdfs__', NULL::text, 'desc' ), From 11b3d6367b8626d0e7cb1bed4327ab00b233c812 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 7 Feb 2009 23:58:22 +0000 Subject: [PATCH 0298/1195] Converted Chantes to Markdown. --- Changes | 501 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 257 insertions(+), 244 deletions(-) diff --git a/Changes b/Changes index ee1499335568..c9e12fe71154 100644 --- a/Changes +++ b/Changes @@ -1,269 +1,282 @@ Revision history for pgTAP +========================== 0.19 - - Added a alernate versions of `col_default_is()` to better handle the - common case when a default is specified as a string, such as a text or - expression default. This means that you have to do a lot less casting - of default values specified in the arguments to `col_default_is()`. +------------------------- + +* Added a alernate versions of `col_default_is()` to better handle the common + case when a default is specified as a string, such as a text or expression + default. This means that you have to do a lot less casting of default values + specified in the arguments to `col_default_is()`. 0.18 2009-02-06T20:06:00 - - Fixed `pg_version_num()`. It was broken in 0.16; sorry about that! - - Fixed a bug in `col_type_is()` where it would die if it was looking - for a type in a table that had dropped columns. Thanks to depesz and - RhodiumToad on #postgresql for the help nailing that one down! - - Fixed a test failure in `sql/coltap.sql` on PostgreSQL 8.3. - - Fixed a bug in the `Makefile` where it did not properly point to - `pg_config` when building from the `compat` directory in the - PostgreSQL source tree. - - Fixed a bug in the `Makefile` where the `test_setup.sql` file, which - is required for tests, was not always getting created. +------------------------- +* Fixed `pg_version_num()`. It was broken in 0.16; sorry about that! +* Fixed a bug in `col_type_is()` where it would die if it was looking + for a type in a table that had dropped columns. Thanks to depesz and + RhodiumToad on #postgresql for the help nailing that one down! +* Fixed a test failure in `sql/coltap.sql` on PostgreSQL 8.3. +* Fixed a bug in the `Makefile` where it did not properly point to + `pg_config` when building from the `compat` directory in the + PostgreSQL source tree. +* Fixed a bug in the `Makefile` where the `test_setup.sql` file, which + is required for tests, was not always getting created. 0.17 2009-02-06T17:51:52 - - Fixed `col_default_is()` so that it works properly in cases where the - default value is `NULL`. - - Fixed `col_default_is()` to gracefully handle different data types, - columns without defaults, and nonexistent columns. - - Fixed the three-argument form of `col_default_is()` to accept any data - type for the default, not just text. - *NOTE:* You must `DROP FUNCTION col_default_is ( NAME, NAME, TEXT );` if - you have previous versions of this function installed, so as to - eliminate the incompatible version. - - Added `col_has_default()` and `col_hasnt_default()`. - - Changed default descriptions for column testing functions to refer to - the columns as the more SQL-standard `schema.table.column` instead of - `schema.table(column)`. - - Modified all diagnostic messages and test descriptions that reference - database objects to use `quote_ident()` to quote the names of those - objects, rather than emitting them with or without double-quotes in an - ad-hoc way as had hitherto been the case. - - Modified `col_not_null()`, `col_is_null()` and `col_type_is()` to emit - diagnostics if they fail because the column in question does not - exist. - - Added `has_tablespace()` and `hasnt_tablespace()`. - - Added `has_sequence()` and `hasnt_sequence()`. - - Changed `has_index()` so that expressions are compared case- - sensitively. - - In the schema testing functions, now using `quote_ident()` to compare - all identifiers, such as table names, schema names, column names, etc. - It had been a mix of straight-forward string comparison, and - case-insensitive matching. This means that tests should use only - lowercase strings to specify object names, unless mixed case objects - were crated with double quote characters. - - Added version of `todo()` with the `why` and `how_many` arugments - reversed, so that I don't have to remember a specific order. +------------------------- +* Fixed `col_default_is()` so that it works properly in cases where the + default value is `NULL`. +* Fixed `col_default_is()` to gracefully handle different data types, + columns without defaults, and nonexistent columns. +* Fixed the three-argument form of `col_default_is()` to accept any data type + for the default, not just text. *NOTE:* You must `DROP FUNCTION + col_default_is ( NAME, NAME, TEXT );` if you have previous versions of this + function installed, so as to eliminate the incompatible version. +* Added `col_has_default()` and `col_hasnt_default()`. +* Changed default descriptions for column testing functions to refer to + the columns as the more SQL-standard `schema.table.column` instead of + `schema.table(column)`. +* Modified all diagnostic messages and test descriptions that reference + database objects to use `quote_ident()` to quote the names of those + objects, rather than emitting them with or without double-quotes in an + ad-hoc way as had hitherto been the case. +* Modified `col_not_null()`, `col_is_null()` and `col_type_is()` to emit + diagnostics if they fail because the column in question does not + exist. +* Added `has_tablespace()` and `hasnt_tablespace()`. +* Added `has_sequence()` and `hasnt_sequence()`. +* Changed `has_index()` so that expressions are compared case- + sensitively. +* In the schema testing functions, now using `quote_ident()` to compare + all identifiers, such as table names, schema names, column names, etc. + It had been a mix of straight-forward string comparison, and + case-insensitive matching. This means that tests should use only + lowercase strings to specify object names, unless mixed case objects + were crated with double quote characters. +* Added version of `todo()` with the `why` and `how_many` arugments + reversed, so that I don't have to remember a specific order. 0.16 2009-02-03T17:37:03 - - Switched from a crazy trinary logic in `is()` and `isnt()` to the use - of `IS NOT DISTINCT FROM` and `IS DISTINCT FROM`. Swiped from PGUnit. - - Fixed documentation for the short version of `--help` in `pg_prove`. - It's `-H`, not `-h`. - - Fixed a bug in the makefile that prevented OS detection from working - properly on many platforms (including Linux, and probably many - others). This allows `os_name()` to actually work on those platforms. - - The definition of the `pg_typeof()` function is removed when - installing on PostgreSQL 8.4 and no C code is compiled, since the only - C code is for this function. This is because `pg_typeof()` is included - with PostgreSQL 8.4. - - Added `has_schema()` and `hasnt_schema()`. - - Added `has_type()`, `hasnt_type()`, `has_domain()`, `hasnt_domain()`, - `has_enum()`, and `hasnt_enum()`. - - Added `enum_has_labels()`. - - Updated the `Makefile` to fix the shebang line in `pg_prove` if - Perl is found, and to emit a warning if the `$PERL` variable is not - set. - Updated the `Makefile` so that if `$PERL` is set and - Tap::Harness is not installed, it warns the user to install it in - order to use `pg_prove`. - Updated the `Makefile` to set the location - of the `psql` binary to the bin directory specified for PostgreSQL - when it was built. - - Fixed `pg_version_num()` to work on PostgreSQL 8.4devel. - - Fixed test failures on PostgreSQL 8.4devel. +------------------------- +* Switched from a crazy trinary logic in `is()` and `isnt()` to the use + of `IS NOT DISTINCT FROM` and `IS DISTINCT FROM`. Swiped from PGUnit. +* Fixed documentation for the short version of `--help` in `pg_prove`. + It's `-H`, not `-h`. +* Fixed a bug in the makefile that prevented OS detection from working + properly on many platforms (including Linux, and probably many + others). This allows `os_name()` to actually work on those platforms. +* The definition of the `pg_typeof()` function is removed when + installing on PostgreSQL 8.4 and no C code is compiled, since the only + C code is for this function. This is because `pg_typeof()` is included + with PostgreSQL 8.4. +* Added `has_schema()` and `hasnt_schema()`. +* Added `has_type()`, `hasnt_type()`, `has_domain()`, `hasnt_domain()`, + `has_enum()`, and `hasnt_enum()`. +* Added `enum_has_labels()`. +* Updated the `Makefile` to fix the shebang line in `pg_prove` if + Perl is found, and to emit a warning if the `$PERL` variable is not + set. - Updated the `Makefile` so that if `$PERL` is set and + Tap::Harness is not installed, it warns the user to install it in + order to use `pg_prove`. - Updated the `Makefile` to set the location + of the `psql` binary to the bin directory specified for PostgreSQL + when it was built. +* Fixed `pg_version_num()` to work on PostgreSQL 8.4devel. +* Fixed test failures on PostgreSQL 8.4devel. 0.15 2009-01-20T18:41:24 - - Changed `pg_typeof()` from immutable to stable, in compliance with its - configuration in the forthcoming PostgreSQL 8.4. - - Added `do_tap()`, which finds test functions and runs them, allowing - tap tests to be embedded in functions and then executed all at once. - - Added `runtests()`, which introduces xUnit-style test running, for - those who prefer to put their tests in a slew of test functions and - then run one function to run all of the test functions (plus startup, - setup, teardown, and shutdown tests). - - Added `findfuncs()`, which is used by `do_tap()` to find the functions - to execute. - - The `col_type_is()` function now requires that the type be visible in - the search path when it is called without a schema argument. - - The `plan()` function no longer resets the `client_min_messages` - setting to its default value, but leaves it set to whatever it was set - to before the function was called. - - Fixed a typo in the documentation of the short version of the - `--version` option to `pg_prove`, which is `-V`, not `-v`. - - Added `pgtap_version()`. - - Added the "html" make target to generate the HTML documentation for - the Web site, including a table of contents and documentation for - `pg_prove`. +------------------------- +* Changed `pg_typeof()` from immutable to stable, in compliance with its + configuration in the forthcoming PostgreSQL 8.4. +* Added `do_tap()`, which finds test functions and runs them, allowing + tap tests to be embedded in functions and then executed all at once. +* Added `runtests()`, which introduces xUnit-style test running, for + those who prefer to put their tests in a slew of test functions and + then run one function to run all of the test functions (plus startup, + setup, teardown, and shutdown tests). +* Added `findfuncs()`, which is used by `do_tap()` to find the functions + to execute. +* The `col_type_is()` function now requires that the type be visible in + the search path when it is called without a schema argument. +* The `plan()` function no longer resets the `client_min_messages` + setting to its default value, but leaves it set to whatever it was set + to before the function was called. +* Fixed a typo in the documentation of the short version of the + `--version` option to `pg_prove`, which is `-V`, not `-v`. +* Added `pgtap_version()`. +* Added the "html" make target to generate the HTML documentation for + the Web site, including a table of contents and documentation for + `pg_prove`. 0.14 2008-10-27T22:43:36 - - Added `SET search_path` statements to `uninstall_pgtap.sql.in` so that - it will work properly when TAP is installed in its own schema. Thanks to - Ben for the catch! - - Added commands to drop `pg_version()` and `pg_version_num()` - to`uninstall_pgtap.sql.in`. - - Added `has_index()`, `index_is_unique()`, `index_is_primary()`, - `is_clustered()`, and `index_is_type()`. - - Added `os_name()`. This is somewhat experimental. If you have `uname`, - it's probably correct, but assistance in improving OS detection in the - `Makefile` would be greatly appreciated. Notably, it does not detect - Windows. - - Made `ok()` smarter when the test result is passed as `NULL`. It was - dying, but now it simply fails and attaches a diagnostic message - reporting that the test result was `NULL`. Reported by Jason Gordon. - - Fixed an issue in `check_test()` where an extra character was removed - from the beginning of the diagnostic output before testing it. - - Fixed a bug comparing `name[]`s on PostgreSQL 8.2, previously hacked - around. - - Added `has_trigger()` and `trigger_is()`. - - Switched to pure SQL implementations of the `pg_version()` and - `pg_version_num()` functions, to simplify including pgTAP in module - distributions. - - Added a note to `README.pgtap` about the need to avoid `pg_typeof()` - and `cmp_ok()` in tests run as part of a distribution. +------------------------- +* Added `SET search_path` statements to `uninstall_pgtap.sql.in` so that + it will work properly when TAP is installed in its own schema. Thanks to + Ben for the catch! +* Added commands to drop `pg_version()` and `pg_version_num()` + to`uninstall_pgtap.sql.in`. +* Added `has_index()`, `index_is_unique()`, `index_is_primary()`, + `is_clustered()`, and `index_is_type()`. +* Added `os_name()`. This is somewhat experimental. If you have `uname`, + it's probably correct, but assistance in improving OS detection in the + `Makefile` would be greatly appreciated. Notably, it does not detect + Windows. +* Made `ok()` smarter when the test result is passed as `NULL`. It was + dying, but now it simply fails and attaches a diagnostic message + reporting that the test result was `NULL`. Reported by Jason Gordon. +* Fixed an issue in `check_test()` where an extra character was removed + from the beginning of the diagnostic output before testing it. +* Fixed a bug comparing `name[]`s on PostgreSQL 8.2, previously hacked + around. +* Added `has_trigger()` and `trigger_is()`. +* Switched to pure SQL implementations of the `pg_version()` and + `pg_version_num()` functions, to simplify including pgTAP in module + distributions. +* Added a note to `README.pgtap` about the need to avoid `pg_typeof()` + and `cmp_ok()` in tests run as part of a distribution. 0.13 2008-10-13T19:09:46 - - Added `pg_version()` and `pg_version_num()`. - - Documented `pg_typeof()`. +------------------------- +* Added `pg_version()` and `pg_version_num()`. +* Documented `pg_typeof()`. 0.12 2008-10-11T04:02:42 - - Updated `plan()` to disable warnings while it creates its tables. - This means that `plan()` no longer send NOTICE messages when they run, - although tests still might, depending on the setting of - `client_min_messages`. - - Added `hasnt_table()`, `hasnt_view()`, and `hasnt_column()`. - - Added `hasnt_pk()`, `hasnt_fk()`, `col_isnt_pk()`, and - `col_isnt_fk()`. - - Added missing `DROP` statements to `uninstall_pgtap.sql.in`. +------------------------- +* Updated `plan()` to disable warnings while it creates its tables. + This means that `plan()` no longer send NOTICE messages when they run, + although tests still might, depending on the setting of + `client_min_messages`. +* Added `hasnt_table()`, `hasnt_view()`, and `hasnt_column()`. +* Added `hasnt_pk()`, `hasnt_fk()`, `col_isnt_pk()`, and + `col_isnt_fk()`. +* Added missing `DROP` statements to `uninstall_pgtap.sql.in`. 0.11 2008-09-24T20:41:42 - - Simplified the tests so that they now load `test_setup.sql` instead of - setting a bunch of stuff themselves. Now only `test_setup.sql` needs - to be created from `test_setup.sql.in`, and the other `.sql` files - depend on it, meaning that one no longer has to specify `TAPSCHEMA` - for any `make` target other than the default. - - Eliminated all uses of `E''` in the tests, so that we don't have to - process them for testing on 8.0. - - Fixed the spelling of `ON_ROLLBACK` in the test setup. Can't believe I - had it with one L in all of the test files before! Thanks to Curtis - "Ovid" Poe for the spot. - - Added a couple of variants of `todo()` and `skip()`, since I can never - remember whether the numeric argument comes first or second. Thanks to - PostgreSQL's functional polymorphism, I don't have to. Also, there are - variants where the numeric value, if not passed, defaults to 1. - - Updated the link to the pgTAP home page in `pgtap.sql.in`. - - TODO tests can now nest. - - Added `todo_start()`, `todo_end()`, and `in_todo()`. - - Added variants of `throws_ok()` that test error messages as well as - error codes. - - Converted some more tests to use `check_test()`. - - Added `can()` and `can_ok()`. - - Fixed a bug in `check_test()` where the leading white space for - diagnostic messages could be off by 1 or more characters. - - Fixed the `installcheck` target so that it properly installs PL/pgSQL - into the target database before the tests run. +------------------------- +* Simplified the tests so that they now load `test_setup.sql` instead of + setting a bunch of stuff themselves. Now only `test_setup.sql` needs + to be created from `test_setup.sql.in`, and the other `.sql` files + depend on it, meaning that one no longer has to specify `TAPSCHEMA` + for any `make` target other than the default. +* Eliminated all uses of `E''` in the tests, so that we don't have to + process them for testing on 8.0. +* Fixed the spelling of `ON_ROLLBACK` in the test setup. Can't believe I + had it with one L in all of the test files before! Thanks to Curtis + "Ovid" Poe for the spot. +* Added a couple of variants of `todo()` and `skip()`, since I can never + remember whether the numeric argument comes first or second. Thanks to + PostgreSQL's functional polymorphism, I don't have to. Also, there are + variants where the numeric value, if not passed, defaults to 1. +* Updated the link to the pgTAP home page in `pgtap.sql.in`. +* TODO tests can now nest. +* Added `todo_start()`, `todo_end()`, and `in_todo()`. +* Added variants of `throws_ok()` that test error messages as well as + error codes. +* Converted some more tests to use `check_test()`. +* Added `can()` and `can_ok()`. +* Fixed a bug in `check_test()` where the leading white space for + diagnostic messages could be off by 1 or more characters. +* Fixed the `installcheck` target so that it properly installs PL/pgSQL + into the target database before the tests run. 0.10 2008-09-18T22:59:31 - - Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the - output of extraneous stuff. - - Added some `GRANT` statements to `plan()` in order to make it easier - to run tests using roles other than the one that called `plan()`. - Patch from Rod Taylor. - - Replaced a call to `lastval()` with a call to `currval()` in order to - improve compatibility with PostgreSQL 8.0. Reported by David Westbrook. - - Added support for TODO and SKIP tests. - - Removed the few uses of named parameters and added alias names instead. - This improves compatibility for versions of PostgreSQL prior to 8.0. - Reported by David Westbrook. - - Fixed the plural of "test" in the output from `finish()`. It was - plural when there was one test and singular otherwise. So I reversed - that. - - Moved assignment to declared variables from the `DECLARE` block to the - body of the functions. Improves compatibility with versions of - PostgreSQL prior to 8.0. Reported by David Westbrook. - - Eliminated passing the result of a function call to `RAISE` in order - to better support older versions of PostgreSQL. Reported by David - Westbrook. - - Switched from using `FOUND` to `GET DIAGNOSTICS ROW_COUNT` because the - former does not seem to be set for `EXECUTE` SQL in older versions of - PostgreSQL. Reported by David Westbrook. - - Added tests specifically targeting PostgreSQL 7.3. From David - Westbrook. - - Ported all the way back to PostgreSQL 8.0, thanks to some Makefile - hackery and a patch file. All tests pass on 8.0, 8.1, 8.2, and 8.3. - The only exception is `throws_ok()` and `lives_ok()`, which are not - supported on 8.0. Versions of PostgreSQL lower than 8.0 are not yet - supported, even though we have made some changes to simplify getting - things to work in earlier versions. - - Changed "got/expected" to "have/want", following Schwern's plans for - Test::Builder 2. Also changed "caught/expected" to "caught/wanted" for - nice parity when outputting diagnostics for exception testing. - - Added the `has_table()`, `has_view()`, `has_column()`, `has_pk()`, - `has_fk()`, `fk_ok()`, `has_unique()` and `has_check()` test - functions. - - Added the `col_not_null()` and `col_is_null()` test functions. - - Added the `col_type_is()`, `col_default_is()`, `col_is_pk()`, - `col_is_fk()`, `col_is_unique()`, and `col_has_check()` test - functions. - - Fixed `is()` and `isnt()` to better handle `NULL`s. - - Added `cmp_ok()`. - - Added the `--set ON_ERROR_STOP=1` option to the call to `psql` in - `pg_prove`. - - Renamed `drop_pgtap.sql.in` to `uninstall_pgtap.sql.in`, which is more - in line with typical PostgreSQL contrib modules. - - Removed verbose output of the `psql` command from `pg_prove`. Thanks - to Andy Armstrong for the spot. - - Divided the tests up into many separate test script files, so that - things are a bit better organized and easier to maintain. - - Added the `check_test()` function and started converting internal - tests to use it instead of the hacked stuff they were doing before. - - As in Test::Builder 0.81_01, changed the message for extra tests run - to show the number of tests run rather than the number extra to avoid - the user having to do mental math. - - The regression test files are now processed by `make installcheck` and - `make test` so that the schema can be properly set, if pgTAP is built - with a schema. +------------------------- +* Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the + output of extraneous stuff. +* Added some `GRANT` statements to `plan()` in order to make it easier + to run tests using roles other than the one that called `plan()`. + Patch from Rod Taylor. +* Replaced a call to `lastval()` with a call to `currval()` in order to + improve compatibility with PostgreSQL 8.0. Reported by David Westbrook. +* Added support for TODO and SKIP tests. +* Removed the few uses of named parameters and added alias names instead. + This improves compatibility for versions of PostgreSQL prior to 8.0. + Reported by David Westbrook. +* Fixed the plural of "test" in the output from `finish()`. It was + plural when there was one test and singular otherwise. So I reversed + that. +* Moved assignment to declared variables from the `DECLARE` block to the + body of the functions. Improves compatibility with versions of + PostgreSQL prior to 8.0. Reported by David Westbrook. +* Eliminated passing the result of a function call to `RAISE` in order + to better support older versions of PostgreSQL. Reported by David + Westbrook. +* Switched from using `FOUND` to `GET DIAGNOSTICS ROW_COUNT` because the + former does not seem to be set for `EXECUTE` SQL in older versions of + PostgreSQL. Reported by David Westbrook. +* Added tests specifically targeting PostgreSQL 7.3. From David + Westbrook. +* Ported all the way back to PostgreSQL 8.0, thanks to some Makefile + hackery and a patch file. All tests pass on 8.0, 8.1, 8.2, and 8.3. + The only exception is `throws_ok()` and `lives_ok()`, which are not + supported on 8.0. Versions of PostgreSQL lower than 8.0 are not yet + supported, even though we have made some changes to simplify getting + things to work in earlier versions. +* Changed "got/expected" to "have/want", following Schwern's plans for + Test::Builder 2. Also changed "caught/expected" to "caught/wanted" for + nice parity when outputting diagnostics for exception testing. +* Added the `has_table()`, `has_view()`, `has_column()`, `has_pk()`, + `has_fk()`, `fk_ok()`, `has_unique()` and `has_check()` test + functions. +* Added the `col_not_null()` and `col_is_null()` test functions. +* Added the `col_type_is()`, `col_default_is()`, `col_is_pk()`, + `col_is_fk()`, `col_is_unique()`, and `col_has_check()` test + functions. +* Fixed `is()` and `isnt()` to better handle `NULL`s. +* Added `cmp_ok()`. +* Added the `--set ON_ERROR_STOP=1` option to the call to `psql` in + `pg_prove`. +* Renamed `drop_pgtap.sql.in` to `uninstall_pgtap.sql.in`, which is more + in line with typical PostgreSQL contrib modules. +* Removed verbose output of the `psql` command from `pg_prove`. Thanks + to Andy Armstrong for the spot. +* Divided the tests up into many separate test script files, so that + things are a bit better organized and easier to maintain. +* Added the `check_test()` function and started converting internal + tests to use it instead of the hacked stuff they were doing before. +* As in Test::Builder 0.81_01, changed the message for extra tests run + to show the number of tests run rather than the number extra to avoid + the user having to do mental math. +* The regression test files are now processed by `make installcheck` and + `make test` so that the schema can be properly set, if pgTAP is built + with a schema. 0.02 2008-06-17T16:26:41 - - Converted the documentation to Markdown. - - Added pg_prove, a Perl script that use TAP::Harness to run tests and - report the results, just like the Perl program `prove`. - - Fixed `no_plan()` so that it no longer emits a plan, which apparently - was wrong. Now `finish()` outputs it when there's no plan. - - Fixed the test script so that it now emits a proper plan. - - Removed all of the configuration settings from `pgtap.sql`, as they're - now handled by `pg_prove`. I've mentioned them in the README for - reference. - - Added `lives_ok()`. - - Moved the creation of temporary tables into `plan()`, so that - everything is properly self-contained. - - Improved the handling of transactions. Test scripts are now assumed to - be single transactions with a ROLLBACK at the end. This makes it so - that test scripts don't have to include `drop_pgtap.sql`. - - Updated `pg_prove` to rollback on an error, rather than just stop. - This allows all test functions to be properly rolled back, too, in a - test script that includes them but then encounters an unhandled - exception. - - Updated `pg_prove` to emit an appropriate error message if no test - scripts are specified. - - Added a Makefile. It uses the typical PostgreSQL installation - procedures to install pgTAP. The SQL files have now been turned into - `.in` templates that are processed by `make`. - - Added support for schema qualification of test functions. Just set the - `$TAPSCHEMA` environment variable when running `make`. - - Added support for standard PostgreSQL-type regression testing by just - copying the test script, setting some variables inside it, and - providing an `expected/` directory. The test now lives in the `sql/` - directory. - - Changed all instances of `RETURN QUERY SELECT` to `RETURN NEXT`, which - should allow pgtap to run on versions of PostgreSQL earlier than 8.3. - Thanks to Neil Conway for the suggestion. +------------------------- +* Converted the documentation to Markdown. +* Added pg_prove, a Perl script that use TAP::Harness to run tests and + report the results, just like the Perl program `prove`. +* Fixed `no_plan()` so that it no longer emits a plan, which apparently + was wrong. Now `finish()` outputs it when there's no plan. +* Fixed the test script so that it now emits a proper plan. +* Removed all of the configuration settings from `pgtap.sql`, as they're + now handled by `pg_prove`. I've mentioned them in the README for + reference. +* Added `lives_ok()`. +* Moved the creation of temporary tables into `plan()`, so that + everything is properly self-contained. +* Improved the handling of transactions. Test scripts are now assumed to + be single transactions with a ROLLBACK at the end. This makes it so + that test scripts don't have to include `drop_pgtap.sql`. +* Updated `pg_prove` to rollback on an error, rather than just stop. + This allows all test functions to be properly rolled back, too, in a + test script that includes them but then encounters an unhandled + exception. +* Updated `pg_prove` to emit an appropriate error message if no test + scripts are specified. +* Added a Makefile. It uses the typical PostgreSQL installation + procedures to install pgTAP. The SQL files have now been turned into + `.in` templates that are processed by `make`. +* Added support for schema qualification of test functions. Just set the + `$TAPSCHEMA` environment variable when running `make`. +* Added support for standard PostgreSQL-type regression testing by just + copying the test script, setting some variables inside it, and + providing an `expected/` directory. The test now lives in the `sql/` + directory. +* Changed all instances of `RETURN QUERY SELECT` to `RETURN NEXT`, which + should allow pgtap to run on versions of PostgreSQL earlier than 8.3. + Thanks to Neil Conway for the suggestion. 0.01 2008-06-07T05:24:27 - - Initial public release. Announcement at - http://justatheory.com/computers/databases/postgresql/introducing_pgtap.html +------------------------- +* Initial public release. Announcement at + http://justatheory.com/computers/databases/postgresql/introducing_pgtap.html From 91e8c19f4f7f7ad4f25f41565a3446213718acdd Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 8 Feb 2009 00:02:27 +0000 Subject: [PATCH 0299/1195] No need to drop. --- Changes | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index c9e12fe71154..9a2ca792c00f 100644 --- a/Changes +++ b/Changes @@ -7,7 +7,8 @@ Revision history for pgTAP * Added a alernate versions of `col_default_is()` to better handle the common case when a default is specified as a string, such as a text or expression default. This means that you have to do a lot less casting of default values - specified in the arguments to `col_default_is()`. + specified in the arguments to `col_default_is()`. It also restores the + signature that 0.17 recommended be dropped. 0.18 2009-02-06T20:06:00 ------------------------- From f30e623e9c7949d96833c4e593167527288606ca Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 9 Feb 2009 20:02:24 +0000 Subject: [PATCH 0300/1195] Added `has_user()`, `isnt_user()`, `is_superuser()`, and `isnt_superuser()`. --- Changes | 1 + README.pgtap | 42 ++++++++++- expected/hastap.out | 50 ++++++++++++- pgtap.sql.in | 69 +++++++++++++++++ sql/hastap.sql | 165 ++++++++++++++++++++++++++++++++++++++++- uninstall_pgtap.sql.in | 26 ++++++- 6 files changed, 346 insertions(+), 7 deletions(-) diff --git a/Changes b/Changes index 9a2ca792c00f..7ed2c7597546 100644 --- a/Changes +++ b/Changes @@ -9,6 +9,7 @@ Revision history for pgTAP default. This means that you have to do a lot less casting of default values specified in the arguments to `col_default_is()`. It also restores the signature that 0.17 recommended be dropped. +* Added `has_user()`, `isnt_user()`, `is_superuser()`, and `isnt_superuser()`. 0.18 2009-02-06T20:06:00 ------------------------- diff --git a/README.pgtap b/README.pgtap index c8cf7a1e63be..33ea32502f0a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1631,6 +1631,44 @@ cast it to `name[]` to disambiguate it from a text string: SELECT can_ok( 'lower', '{text}'::name[] ); +### `has_user( user, desc )` ### +### `has_user( user )` ### + + SELECT has_user( 'theory', 'User "theory" should exist' ); + +Checks to ensure that a database user exists. If the description is omitted, +it will default to "User `:username` should exist". + +### `hasnt_user( user, desc )` ### +### `hasnt_user( user )` ### + + SELECT hasnt_user( 'theory', 'User "theory" should exist' ); + +The inverse of `has_user()`, this function tests for the *absence* of a database +user. + +### `is_superuser( user, desc )` ### +### `is_superuser( user )` ### + + SELECT is_superuser( 'theory', 'User "theory" should be a super user' ); + +Tests that a database user is a super user. If the description is omitted, it +will default to "User `:username` should be a super user". If the user does +not exist in the database, the diagnostics will say so. + +### `isnt_superuser( user, desc )` ### +### `isnt_superuser( user )` ### + + SELECT is_superuser( + 'dr_evil', + 'User "dr_evil" should not be a super user' + ); + +The inverse of `is_superuser()`, this function tests that a database user is +*not* a super user. Note that if the named user does not exist in the +database, the test is still considered a failure, and the diagnostics will say +so. + Diagnostics ----------- @@ -2239,10 +2277,12 @@ To Do function rather than test files. * Useful schema testing functions to consider adding: * `has_cast()` - * `has_role()`, `has_user()`, `has_group()` + * `has_role()`, `has_group()` * `has_operator()` * `has_operator_class()` * `has_rule()` + * `func_returns()` + * `func_lang_is()` * `seq_has_range()` * `seq_increments_by()` * `seq_starts_at()` diff --git a/expected/hastap.out b/expected/hastap.out index 345b4f020b8a..327a9ec42b9e 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..285 +1..333 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -285,3 +285,51 @@ ok 282 - hasnt_column(view, column) should have the proper diagnostics ok 283 - hasnt_column(type, column) should pass ok 284 - hasnt_column(type, column) should have the proper description ok 285 - hasnt_column(type, column) should have the proper diagnostics +ok 286 - has_user(current user) should pass +ok 287 - has_user(current user) should have the proper description +ok 288 - has_user(current user) should have the proper diagnostics +ok 289 - has_user(current user, desc) should pass +ok 290 - has_user(current user, desc) should have the proper description +ok 291 - has_user(current user, desc) should have the proper diagnostics +ok 292 - has_user(nonexistent user) should fail +ok 293 - has_user(nonexistent user) should have the proper description +ok 294 - has_user(nonexistent user) should have the proper diagnostics +ok 295 - has_user(nonexistent user, desc) should fail +ok 296 - has_user(nonexistent user, desc) should have the proper description +ok 297 - has_user(nonexistent user, desc) should have the proper diagnostics +ok 298 - hasnt_user(current user) should fail +ok 299 - hasnt_user(current user) should have the proper description +ok 300 - hasnt_user(current user) should have the proper diagnostics +ok 301 - hasnt_user(current user, desc) should fail +ok 302 - hasnt_user(current user, desc) should have the proper description +ok 303 - hasnt_user(current user, desc) should have the proper diagnostics +ok 304 - hasnt_user(nonexistent user) should pass +ok 305 - hasnt_user(nonexistent user) should have the proper description +ok 306 - hasnt_user(nonexistent user) should have the proper diagnostics +ok 307 - hasnt_user(nonexistent user, desc) should pass +ok 308 - hasnt_user(nonexistent user, desc) should have the proper description +ok 309 - hasnt_user(nonexistent user, desc) should have the proper diagnostics +ok 310 - is_superuser(nonexistent user, desc) should fail +ok 311 - is_superuser(nonexistent user, desc) should have the proper description +ok 312 - is_superuser(nonexistent user, desc) should have the proper diagnostics +ok 313 - is_superuser(nonexistent user) should fail +ok 314 - is_superuser(nonexistent user) should have the proper description +ok 315 - is_superuser(nonexistent user) should have the proper diagnostics +ok 316 - isnt_superuser(nonexistent user, desc) should fail +ok 317 - isnt_superuser(nonexistent user, desc) should have the proper description +ok 318 - isnt_superuser(nonexistent user, desc) should have the proper diagnostics +ok 319 - isnt_superuser(nonexistent user) should fail +ok 320 - isnt_superuser(nonexistent user) should have the proper description +ok 321 - isnt_superuser(nonexistent user) should have the proper diagnostics +ok 322 - is_superuser( current user ) should pass +ok 323 - is_superuser( current user ) should have the proper description +ok 324 - is_superuser( current user ) should have the proper diagnostics +ok 325 - is_superuser( current user, desc ) should pass +ok 326 - is_superuser( current user, desc ) should have the proper description +ok 327 - is_superuser( current user, desc ) should have the proper diagnostics +ok 328 - isnt_superuser( current user ) should pass +ok 329 - isnt_superuser( current user ) should have the proper description +ok 330 - isnt_superuser( current user ) should have the proper diagnostics +ok 331 - isnt_superuser( current user, desc ) should pass +ok 332 - isnt_superuser( current user, desc ) should have the proper description +ok 333 - isnt_superuser( current user, desc ) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 69daada2756a..22dd1be5444f 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3097,6 +3097,75 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; +CREATE OR REPLACE FUNCTION _is_super( NAME ) +RETURNS BOOLEAN AS $$ + SELECT usesuper + FROM pg_catalog.pg_user + WHERE quote_ident(usename) = quote_ident($1) +$$ LANGUAGE sql STRICT; + +-- has_user( user, desc ) +CREATE OR REPLACE FUNCTION has_user( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _is_super($1) IS NOT NULL, $2 ); +$$ LANGUAGE sql; + +-- has_user( user ) +CREATE OR REPLACE FUNCTION has_user( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _is_super( $1 ) IS NOT NULL, 'User ' || quote_ident($1) || ' should exist'); +$$ LANGUAGE sql; + +-- hasnt_user( user, desc ) +CREATE OR REPLACE FUNCTION hasnt_user( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _is_super($1) IS NULL, $2 ); +$$ LANGUAGE sql; + +-- hasnt_user( user ) +CREATE OR REPLACE FUNCTION hasnt_user( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _is_super( $1 ) IS NULL, 'User ' || quote_ident($1) || ' should not exist'); +$$ LANGUAGE sql; + +-- is_superuser( user, desc ) +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, desc ) +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; + -- check_test( test_output, pass, name, description, diag ) CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) RETURNS SETOF TEXT AS $$ diff --git a/sql/hastap.sql b/sql/hastap.sql index 7022b08316fc..a3a3ae0ce9a3 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(285); +SELECT plan(333); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -806,6 +806,169 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test has_user() and hasnt_user(). +SELECT * FROM check_test( + has_user(current_user), + true, + 'has_user(current user)', + 'User ' || quote_ident(current_user) || ' should exist', + '' +); +SELECT * FROM check_test( + has_user(current_user, 'whatever'), + true, + 'has_user(current user, desc)', + 'whatever', + '' +); +SELECT * FROM check_test( + has_user('aoijaoisjfaoidfjaisjdfosjf'), + false, + 'has_user(nonexistent user)', + 'User aoijaoisjfaoidfjaisjdfosjf should exist', + '' +); +SELECT * FROM check_test( + has_user('aoijaoisjfaoidfjaisjdfosjf', 'desc'), + false, + 'has_user(nonexistent user, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_user(current_user), + false, + 'hasnt_user(current user)', + 'User ' || quote_ident(current_user) || ' should not exist', + '' +); +SELECT * FROM check_test( + hasnt_user(current_user, 'whatever'), + false, + 'hasnt_user(current user, desc)', + 'whatever', + '' +); +SELECT * FROM check_test( + hasnt_user('aoijaoisjfaoidfjaisjdfosjf'), + true, + 'hasnt_user(nonexistent user)', + 'User aoijaoisjfaoidfjaisjdfosjf should not exist', + '' +); +SELECT * FROM check_test( + hasnt_user('aoijaoisjfaoidfjaisjdfosjf', 'desc'), + true, + 'hasnt_user(nonexistent user, desc)', + 'desc', + '' +); + +/****************************************************************************/ +-- Test is_superuser() and isnt_superuser(). +SELECT * FROM check_test( + is_superuser('aoijaoisjfaoidfjaisjdfosjf', 'desc'), + false, + 'is_superuser(nonexistent user, desc)', + 'desc', + ' User aoijaoisjfaoidfjaisjdfosjf does not exist' +); +SELECT * FROM check_test( + is_superuser('aoijaoisjfaoidfjaisjdfosjf'), + false, + 'is_superuser(nonexistent user)', + 'User aoijaoisjfaoidfjaisjdfosjf should be a super user', + ' User aoijaoisjfaoidfjaisjdfosjf does not exist' +); + +SELECT * FROM check_test( + isnt_superuser('aoijaoisjfaoidfjaisjdfosjf', 'desc'), + false, + 'isnt_superuser(nonexistent user, desc)', + 'desc', + ' User aoijaoisjfaoidfjaisjdfosjf does not exist' +); +SELECT * FROM check_test( + isnt_superuser('aoijaoisjfaoidfjaisjdfosjf'), + false, + 'isnt_superuser(nonexistent user)', + 'User aoijaoisjfaoidfjaisjdfosjf should not be a super user', + ' User aoijaoisjfaoidfjaisjdfosjf does not exist' +); + +CREATE OR REPLACE FUNCTION supertest() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF _is_super( current_user ) THEN + -- We can test is_superuser(). + FOR tap IN SELECT * FROM check_test( + is_superuser(current_user), + true, + 'is_superuser( current user )', + 'User ' || quote_ident(current_user) || ' should be a super user', + '' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + is_superuser(current_user, 'whatever'), + true, + 'is_superuser( current user, desc )', + 'whatever', + '' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + -- But we can't test isnt_superuser(). + RETURN NEXT pass('isnt_superuser( current user ) should pass'); + RETURN NEXT pass('isnt_superuser( current user ) should have the proper description'); + RETURN NEXT pass('isnt_superuser( current user ) should have the proper diagnostics'); + + RETURN NEXT pass('isnt_superuser( current user, desc ) should pass'); + RETURN NEXT pass('isnt_superuser( current user, desc ) should have the proper description'); + RETURN NEXT pass('isnt_superuser( current user, desc ) should have the proper diagnostics'); + + ELSE + -- We can't test is_superuser(). + RETURN NEXT pass('is_superuser( current user ) should pass'); + RETURN NEXT pass('is_superuser( current user ) should have the proper description'); + RETURN NEXT pass('is_superuser( current user ) should have the proper diagnostics'); + + RETURN NEXT pass('is_superuser( current user, desc ) should pass'); + RETURN NEXT pass('is_superuser( current user, desc ) should have the proper description'); + RETURN NEXT pass('is_superuser( current user, desc ) should have the proper diagnostics'); + + -- But we can test isnt_superuser(). + FOR tap IN SELECT * FROM check_test( + isnt_superuser(current_user), + true, + 'isnt_superuser( current user )', + 'User ' || quote_ident(current_user) || ' should not be a super user', + '' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + isnt_superuser(current_user, 'whatever'), + true, + 'isnt_superuser( current user, desc )', + 'whatever', + '' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + END IF; + RETURN; +END; +$$ LANGUAGE PLPGSQL; +SELECT * FROM supertest(); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index db6817f5cdc8..437cd6637264 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -19,6 +19,15 @@ DROP FUNCTION check_test( TEXT, BOOLEAN ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); +DROP FUNCTION isnt_superuser( NAME ); +DROP FUNCTION isnt_superuser( NAME, TEXT ); +DROP FUNCTION is_superuser( NAME ); +DROP FUNCTION is_superuser( NAME, TEXT ); +DROP FUNCTION hasnt_user( NAME ); +DROP FUNCTION hasnt_user( NAME, TEXT ); +DROP FUNCTION has_user( NAME ); +DROP FUNCTION has_user( NAME, TEXT ); +DROP FUNCTION _is_super( NAME ); DROP FUNCTION enum_has_labels( NAME, NAME[] ); DROP FUNCTION enum_has_labels( NAME, NAME[], TEXT ); DROP FUNCTION enum_has_labels( NAME, NAME, NAME[] ); @@ -49,15 +58,15 @@ DROP FUNCTION has_type( NAME, NAME ); DROP FUNCTION has_type( NAME, NAME, TEXT ); DROP FUNCTION _has_type( NAME, CHAR[] ); DROP FUNCTION _has_type( NAME, NAME, CHAR[] ); -DROP FUNCTION hasnt_schema( NAME ); -DROP FUNCTION hasnt_schema( NAME, TEXT ); -DROP FUNCTION has_schema( NAME ); -DROP FUNCTION has_schema( NAME, TEXT ); DROP FUNCTION hasnt_tablespace( NAME ); DROP FUNCTION hasnt_tablespace( NAME, TEXT ); DROP FUNCTION has_tablespace( NAME ); DROP FUNCTION has_tablespace( NAME, TEXT ); DROP FUNCTION has_tablespace( NAME, TEXT, TEXT ); +DROP FUNCTION hasnt_schema( NAME ); +DROP FUNCTION hasnt_schema( NAME, TEXT ); +DROP FUNCTION has_schema( NAME ); +DROP FUNCTION has_schema( NAME, TEXT ); DROP FUNCTION trigger_is ( NAME, NAME, NAME ); DROP FUNCTION trigger_is ( NAME, NAME, NAME, text ); DROP FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME ); @@ -172,6 +181,8 @@ DROP FUNCTION col_is_pk ( NAME, NAME, NAME[], TEXT ); DROP VIEW pg_all_foreign_keys; DROP FUNCTION _pg_sv_table_accessible( OID, OID ); DROP FUNCTION _pg_sv_column_array( OID, SMALLINT[] ); +DROP FUNCTION _ident_array_to_string( name[], text ); +DROP FUNCTION _quote_ident_array( name[] ); DROP FUNCTION _ckeys ( NAME, CHAR ); DROP FUNCTION _ckeys ( NAME, NAME, CHAR ); DROP FUNCTION hasnt_pk ( NAME ); @@ -182,9 +193,15 @@ DROP FUNCTION has_pk ( NAME, TEXT ); DROP FUNCTION has_pk ( NAME, NAME, TEXT ); DROP FUNCTION _hasc ( NAME, CHAR ); DROP FUNCTION _hasc ( NAME, NAME, CHAR ); +DROP FUNCTION col_default_is ( NAME, NAME, text ); DROP FUNCTION col_default_is ( NAME, NAME, anyelement ); +DROP FUNCTION col_default_is ( NAME, NAME, TEXT, TEXT ); DROP FUNCTION col_default_is ( NAME, NAME, anyelement, TEXT ); +DROP FUNCTION col_default_is ( NAME, NAME, NAME, TEXT, TEXT ); DROP FUNCTION col_default_is ( NAME, NAME, NAME, anyelement, TEXT ); +DROP FUNCTION _cdi ( NAME, NAME, anyelement ); +DROP FUNCTION _cdi ( NAME, NAME, anyelement, TEXT ); +DROP FUNCTION _cdi ( NAME, NAME, NAME, anyelement, TEXT ); DROP FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT ); DROP FUNCTION col_hasnt_default ( NAME, NAME ); DROP FUNCTION col_hasnt_default ( NAME, NAME, TEXT ); @@ -253,6 +270,7 @@ DROP FUNCTION todo_start (); DROP FUNCTION todo_start (text); DROP FUNCTION todo ( how_many int ); DROP FUNCTION todo ( why text ); +DROP FUNCTION todo ( how_many int, why text ); DROP FUNCTION todo ( why text, how_many int ); DROP FUNCTION fail (); DROP FUNCTION fail ( text ); From bef7a8298e116532f13a6e2803e5bc32941d150a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 11 Feb 2009 21:41:36 +0000 Subject: [PATCH 0301/1195] Bug, another test. --- README.pgtap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.pgtap b/README.pgtap index 33ea32502f0a..dc034a8dacd0 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2275,6 +2275,7 @@ To Do * Add options to `pg_prove` to allow it to run tests using the `runtests()` function rather than test files. +* Fix bug: Schema argument ignored in `can()`. * Useful schema testing functions to consider adding: * `has_cast()` * `has_role()`, `has_group()` @@ -2287,6 +2288,7 @@ To Do * `seq_increments_by()` * `seq_starts_at()` * `seq_cycles()` + * `can_only()` Suported Versions ----------------- From 4765643d9211893922eec4cbcfba25d20cdf57e3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 12 Feb 2009 01:02:18 +0000 Subject: [PATCH 0302/1195] Be more friendly to older versions of `make`. --- Changes | 2 ++ Makefile | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 7ed2c7597546..5e9a5816e44f 100644 --- a/Changes +++ b/Changes @@ -10,6 +10,8 @@ Revision history for pgTAP specified in the arguments to `col_default_is()`. It also restores the signature that 0.17 recommended be dropped. * Added `has_user()`, `isnt_user()`, `is_superuser()`, and `isnt_superuser()`. +* Fixed syntax in the `Makefile` to make it compatible with older versions of + `make`. Reported by Aaron Kangas. 0.18 2009-02-06T20:06:00 ------------------------- diff --git a/Makefile b/Makefile index 725af4a3e9a4..1e1d3387cc97 100644 --- a/Makefile +++ b/Makefile @@ -60,15 +60,19 @@ EXTRA_SUBS := -e "s/ E'/ '/g" # Throw, runtests, and enums aren't supported in 8.0. TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql sql/enumtap.sql,$(TESTS)) REGRESS := $(filter-out throwtap runtests enumtap,$(REGRESS)) -else ifeq ($(PGVER_MINOR), 4) +else +ifeq ($(PGVER_MINOR), 4) # Remove lines 15-20, which define pg_typeof(). EXTRA_SUBS := -e '15,19d' -else ifneq ($(PGVER_MINOR), 3) +else +ifneq ($(PGVER_MINOR), 3) # Enum test not supported by 8.2 and earlier. TESTS := $(filter-out sql/enumtap.sql,$(TESTS)) REGRESS := $(filter-out enumtap,$(REGRESS)) endif endif +endif +endif # Determine the OS. Borrowed from Perl's Configure. ifeq ($(wildcard /irix), /irix) From bf970c2f72209580c6cf84fe0cef8799d61f3a09 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 18 Feb 2009 02:06:03 +0000 Subject: [PATCH 0303/1195] Make `runtests()` transactionality clearer in the docs. --- Changes | 3 +++ README.pgtap | 20 +++++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 5e9a5816e44f..afd5a30aacab 100644 --- a/Changes +++ b/Changes @@ -12,6 +12,9 @@ Revision history for pgTAP * Added `has_user()`, `isnt_user()`, `is_superuser()`, and `isnt_superuser()`. * Fixed syntax in the `Makefile` to make it compatible with older versions of `make`. Reported by Aaron Kangas. +* Improved the documentation of `runtests()` to make it clearer that all tests + it runs are run in transactions that are rolled back after each test. + Suggested by Aaron Kangas. 0.18 2009-02-06T20:06:00 ------------------------- diff --git a/README.pgtap b/README.pgtap index dc034a8dacd0..cef6ee482a8e 100644 --- a/README.pgtap +++ b/README.pgtap @@ -252,10 +252,10 @@ you can collect all of your tests into database functions and run them all at once with `runtests()`. This is similar to how [PGUnit](http://en.dklab.ru/lib/dklab_pgunit/) and [Epic](http://www.epictest.org/) work. The `runtests()` function does all the -work of finding and running your test functions. It even supports setup and -teardown functions. To use it, write your unit test functions so that they -return a set of text results, and then use the pgTAP assertion functions to -return TAP values, like so: +work of finding and running your test functions in individual transactions. It +even supports setup and teardown functions. To use it, write your unit test +functions so that they return a set of text results, and then use the pgTAP +assertion functions to return TAP values, like so: create or replace function setup_insert( ) RETURNS SETOF TEXT AS $$ @@ -275,7 +275,9 @@ your unit testing functions, you can run your tests at any time using the SELECT * FROM runtests(); -The TAP results will be sent to your client. +Each test function will run within its own transaction, and rolled back when +the function completes (or after any teardown functions have run). The TAP +results will be sent to your client. Test names ---------- @@ -2030,6 +2032,14 @@ The fixture functions run by `runtests()` are as follows: * `^shutdown` - Functions whose names start with "shutdown" are run in alphabetical order after all test functions have been run. +Note that all tests executed by `runtests()` are run within a single +transaction, and each test is run in a subtransaction that also includes +execution all the setup and teardown functions. All transactions are rolled +back after each test function, and at the end of testing, leaving your +database in largely the same condition as it was in when you started it (the +one exception I'm aware of being sequences, which are not rolled back to the +value used at the beginning of a rolled-back transaction). + Writing Test Functions ====================== From 3d97a95586c9bb4e6946c02a825b41edf3703762 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 19 Feb 2009 23:51:13 +0000 Subject: [PATCH 0304/1195] Added `runtests()` support to `pg_prove`. W00t! --- Changes | 2 + README.pgtap | 37 ++++++++----- bin/pg_prove | 150 ++++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 163 insertions(+), 26 deletions(-) diff --git a/Changes b/Changes index afd5a30aacab..7bb588b0ec7a 100644 --- a/Changes +++ b/Changes @@ -15,6 +15,8 @@ Revision history for pgTAP * Improved the documentation of `runtests()` to make it clearer that all tests it runs are run in transactions that are rolled back after each test. Suggested by Aaron Kangas. +* Added the `--runtests`, `--schema`, and `--match` options to `pg_prove` so + that it can be used to run xUnit-style test functions without an SQL script. 0.18 2009-02-06T20:06:00 ------------------------- diff --git a/README.pgtap b/README.pgtap index cef6ee482a8e..ab62a94fc90f 100644 --- a/README.pgtap +++ b/README.pgtap @@ -168,10 +168,11 @@ loading of the test functions -- in the event of an uncaught exception. Using `pg_prove` ---------------- -Or save yourself some effort -- and run a batch of tests scripts at once -- by -using `pg_prove`. If you're not relying on `installcheck`, your test scripts -can be a lot less verbose; you don't need to set all the extra variables, -because `pg_prove` takes care of that for you: +Or save yourself some effort -- and run a batch of tests scripts or all of +your xUnit test functions at once -- by using `pg_prove`. If you're not +relying on `installcheck`, your test scripts can be a lot less verbose; you +don't need to set all the extra variables, because `pg_prove` takes care of +that for you: -- Start transaction and plan the tests. BEGIN; @@ -197,6 +198,11 @@ Now run the tests. Here's what it looks like when the pgTAP tests are run with Files=5, Tests=216, 1 wallclock secs ( 0.06 usr 0.02 sys + 0.08 cusr 0.07 csys = 0.23 CPU) Result: PASS +If you're using xUnit tests and just want to have `pg_prove` run them all +through the `runtests()` function, just tell it to do so: + + % pg_prove -d myapp --runtests + Yep, that's all there is to it. Call `pg_prove --help` to see other supported options, and `pg_prove --man` to see its entire documentation. @@ -257,7 +263,7 @@ even supports setup and teardown functions. To use it, write your unit test functions so that they return a set of text results, and then use the pgTAP assertion functions to return TAP values, like so: - create or replace function setup_insert( + CREATE OR REPLACE FUNCTION setup_insert( ) RETURNS SETOF TEXT AS $$ RETURN NEXT is( MAX(nick), NULL, 'Should have no users') FROM users; INSERT INTO users (nick) VALUES ('theory'); @@ -1967,7 +1973,7 @@ Then you can just call the function to run all of your TAP tests at once: ### `do_tap()` ### SELECT plan(32); - SELECT * FROM do_tap('testschema'); + SELECT * FROM do_tap('testschema'::name); SELECT * FROM finish(); If you like you can create a whole slew of these batched tap functions, and @@ -2005,7 +2011,7 @@ failing tests. ### `runtests( match )` ### ### `runtests( )` ### - SELECT * FROM runtests('testschema'); + SELECT * FROM runtests( 'testschema', '^test' ); If you'd like pgTAP to plan, run all of your tests functions, and finish all in one fell swoop, use `runtests()`. This most closely emulates the xUnit @@ -2014,11 +2020,16 @@ testing environment, similar to the functionality of [Epic](http://www.epictest.org/). It requires PostgreSQL 8.1 or higher. As with `do_tap()`, you can pass in a schema argument and/or a pattern that -the names of the tests functions can match. But unlike `do_tap()`, -`runtests()` fully supports startup, shutdown, setup, and teardown functions, -as well as transactional rollbacks between tests. It also outputs the test -plan and fishes the tests, so you don't have to call `plan()` or `finish()` -yourself. +the names of the tests functions can match. If you pass in only the schema +argument, be sure to cast it to `name` to identify it as a schema name rather +than a pattern: + + SELECT * FROM runtests('testschema'::name); + +Unlike `do_tap()`, `runtests()` fully supports startup, shutdown, setup, and +teardown functions, as well as transactional rollbacks between tests. It also +outputs the test plan and fishes the tests, so you don't have to call `plan()` +or `finish()` yourself. The fixture functions run by `runtests()` are as follows: @@ -2283,8 +2294,6 @@ Don't even use them there. To Do ----- -* Add options to `pg_prove` to allow it to run tests using the `runtests()` - function rather than test files. * Fix bug: Schema argument ignored in `can()`. * Useful schema testing functions to consider adding: * `has_cast()` diff --git a/bin/pg_prove b/bin/pg_prove index 968496ca117e..0cc5e6a8fef5 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -19,6 +19,9 @@ Getopt::Long::GetOptions( 'host|h=s' => \$opts->{host}, 'port|p=s' => \$opts->{port}, 'pset|P=s%' => \$opts->{pset}, + 'runtests|r' => \$opts->{runtests}, + 'schema|s|=s' => \$opts->{schema}, + 'match|x|=s' => \$opts->{match}, 'timer|t' => \$opts->{timer}, 'color|c!' => \$opts->{color}, 'verbose|v+' => \$opts->{verbose}, @@ -41,10 +44,13 @@ if ($opts->{version}) { exit; } -unless (@ARGV) { +# --schema and --match assume --runtests. +$opts->{runtests} ||= !!($opts->{schema} || $opts->{match}); + +unless ($opts->{runtests} or @ARGV) { require Pod::Usage; Pod::Usage::pod2usage( - '-message' => "\nOops! You didn't say what test scripts to run.\n", + '-message' => "\nOops! You didn't say what test scripts to run or specify --runtests.\n", '-sections' => '(?i:(Usage|Options))', '-verbose' => 99, '-exitval' => 1, @@ -75,34 +81,64 @@ if (my $pset = $opts->{pset}) { } } -push @command, '--file'; +# Figure out what tests to run. +my $tests; +if ($opts->{runtests}) { + # We're just going to call `runtests()`. + push @command, '--command'; + + my @args; + for my $key qw(schema match) { + next unless $opts->{$key}; + (my $arg = $opts->{$key}) =~ s/'/\\'/g; + # Gotta cast the arguments. + push @args, "'$arg'::" . ($key eq 'schema' ? 'name' : 'text'); + } + # This is the command we'll run. + $tests = [[ + "SELECT * FROM runtests(" . join( ', ', @args ) . ');', + 'runtests(' . join(', ', @args) . ')', + ]]; +} else { + # Each item in @ARGV is a file name. + $tests = \@ARGV; + push @command, '--file'; +} + +# Make it so! TAP::Harness->new({ verbosity => $opts->{verbose} || $ENV{TEST_VERBOSE}, timer => $opts->{timer}, color => $opts->{color}, exec => \@command, -})->runtests( @ARGV ); +})->runtests( @{ $tests } ); __END__ =head1 Name -pg_prove - A command-line tool for running pgTAP tests against TAP::Harness +pg_prove - A command-line tool for running and harnessing pgTAP tests =head1 Usage pg_prove -d template1 test*.sql + pg_prove -d testdb -s testschema =head1 Description -C is a command-line application to run one or more pgTAP tests against -a PostgreSQL database. The output of the tests is harvested and processed by +C is a command-line application to run one or more pgTAP tests in a +PostgreSQL database. The output of the tests is harvested and processed by L in order to summarize the results of the test. -The pgTAP test scripts should consist of a series of SQL statements that -output TAP. Here's a simple example that assumes that the pgTAP functions have -been installed in the database: +Tests can be written and run in one of two ways, as SQL scripts or as database +functions. + +=head2 Test Scripts + +pgTAP test scripts should consist of a series of SQL statements that output +TAP. Here's a simple example that assumes that the pgTAP functions have been +installed in the database: -- Start transaction and plan the tests. BEGIN; @@ -115,8 +151,8 @@ been installed in the database: SELECT * FROM finish(); ROLLBACK; -Now run the tests. Here's what it looks like when the pgTAP tests are run with -`pg_prove`: +Now run the tests by passing the list of SQL script names to C. +Here's what it looks like when the pgTAP tests are run with C % pg_prove -U postgres sql/*.sql sql/coltap.....ok @@ -128,6 +164,39 @@ Now run the tests. Here's what it looks like when the pgTAP tests are run with Files=5, Tests=216, 1 wallclock secs ( 0.06 usr 0.02 sys + 0.08 cusr 0.07 csys = 0.23 CPU) Result: PASS +=head2 xUnit Test Functions + +pgTAP test functions should return a set of text, and then simply return the +values returned by pgTAP functions, like so: + + CREATE OR REPLACE FUNCTION setup_insert( + ) RETURNS SETOF TEXT AS $$ + RETURN NEXT is( MAX(nick), NULL, 'Should have no users') FROM users; + INSERT INTO users (nick) VALUES ('theory'); + $$ LANGUAGE plpgsql; + + CREATE OR REPLACE FUNCTION test_user( + ) RETURNS SETOF TEXT AS $$ + SELECT is( nick, 'theory', 'Should have nick') FROM users; + END; + $$ LANGUAGE sql; + +Once you have these functions defined in your database, you can run them with +C by using the C<--runtests> option. + + % pg_prove -d myapp -r + runtests()....ok + All tests successful. + Files=1, Tests=32, 0 wallclock secs ( 0.02 usr 0.01 sys + 0.01 cusr 0.00 csys = 0.04 CPU) + Result: PASS + +Be sure to pass the C<--schema> option if your test functions are all in one +schema, and the C<--match> option if they have names that don't start with +"test". For example, if you have all of your test functions in "test" schema +and I with "test", run the tests like so: + + pg_prove -d myapp --schema test --match 'test$' + =head1 Options -b --psql-bin PSQL Location of the psql program. @@ -135,6 +204,9 @@ Now run the tests. Here's what it looks like when the pgTAP tests are run with -U --username USERNAME Username with which to connect. -h --host HOST Host to which to connect. -p --port PORT Port to which to connect. + -r --runtests Run tests using C. + -s --test-schema Schema in which to find test functions. + -x --test-match Regular expression to find test functions. -t --timer Print elapsed time after each test file. -c --color Display colored test ouput. --nocolor Do not display colored test ouput. @@ -223,6 +295,60 @@ Display standard output of test scripts while running them. This behavior can also be triggered by setting the C<$TEST_VERBOSE> environment variable to a true value. +=item C<-r> + +=item C<--runtests> + + pg_prove --runtests + pg_prove -r + +Don't run any test scripts, but just use the C pgTAP function to +run xUnit tests. This ends up looking like a single test script has been run, +when in fact no test scripts have been run. Instead, C tells C +to run something like: + + psql --command 'SELECT * FROM runtests()' + +You should use this option when you've written your tests in xUnit style, +where they're all defined in test functions already loaded in the database. + +=item C<-s> + +=item C<--schema> + + pg_prove --schema test + pg_prove -s mytest + +Used with C<--runtests>, and, in fact, implicitly forces C<--runtests> to be +true. This option can be used to specify the name of a schema in which to find +xUnit functions to run. Basically, it tells C to run something like: + + psql --command "SELECT * FROM runtests('test'::name)" + +=item C<-x> + +=item C<--match> + + pg_prove --match 'test$' + pg_prove -x _test_ + +Used with C<--runtests>, and, in fact, implicitly forces C<--runtests> to be +true. This option can be used to specify a POSIX regular expression that will +be used to search for xUnit functions to run. Basically, it tells C to +run something like: + + psql --command "SELECT * FROM runtests('_test_'::text)" + +This will run any visible functions with the string "_test_" in their names. +This can be especially useful if you just want to run a single test in a +given schema. For example, this: + + pg_prove --schema testing --match '^test_widgets$' + +Will have C execute the C function like so: + + SELECT * FROM runtests('testing'::name, '^test_widgets$'::text); + =item C<-t> =item C<--timer> From 4a993a8094eb8b8ced2cbcb76726aa2a10131485 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Feb 2009 00:38:03 +0000 Subject: [PATCH 0305/1195] Fixed a bug in `can()` where it could sometimes report that functions were available in a schema when in fact they were not. --- Changes | 2 ++ README.pgtap | 1 - expected/cantap.out | 23 +++++++++++++---------- pgtap.sql.in | 13 +++++++------ sql/cantap.sql | 13 ++++++++++++- 5 files changed, 34 insertions(+), 18 deletions(-) diff --git a/Changes b/Changes index 7bb588b0ec7a..714c3fe1d190 100644 --- a/Changes +++ b/Changes @@ -17,6 +17,8 @@ Revision history for pgTAP Suggested by Aaron Kangas. * Added the `--runtests`, `--schema`, and `--match` options to `pg_prove` so that it can be used to run xUnit-style test functions without an SQL script. +* Fixed a bug in `can()` where it could sometimes report that functions + were available in a schema when in fact they were not. 0.18 2009-02-06T20:06:00 ------------------------- diff --git a/README.pgtap b/README.pgtap index ab62a94fc90f..9397f2503d3c 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2294,7 +2294,6 @@ Don't even use them there. To Do ----- -* Fix bug: Schema argument ignored in `can()`. * Useful schema testing functions to consider adding: * `has_cast()` * `has_role()`, `has_group()` diff --git a/expected/cantap.out b/expected/cantap.out index ee0235e8313d..78a0afecb893 100644 --- a/expected/cantap.out +++ b/expected/cantap.out @@ -1,5 +1,5 @@ \unset ECHO -1..60 +1..63 ok 1 - simple function should pass ok 2 - simple function should have the proper description ok 3 - simple function should have the proper diagnostics @@ -51,12 +51,15 @@ ok 48 - can(schema) should have the proper diagnostics ok 49 - fail can(schema) with desc should fail ok 50 - fail can(schema) with desc should have the proper description ok 51 - fail can(schema) with desc should have the proper diagnostics -ok 52 - can() with desc should pass -ok 53 - can() with desc should have the proper description -ok 54 - can() with desc should have the proper diagnostics -ok 55 - can(schema) should pass -ok 56 - can(schema) should have the proper description -ok 57 - can(schema) should have the proper diagnostics -ok 58 - fail can() with desc should fail -ok 59 - fail can() with desc should have the proper description -ok 60 - fail can() with desc should have the proper diagnostics +ok 52 - fail can(someschema) with desc should fail +ok 53 - fail can(someschema) with desc should have the proper description +ok 54 - fail can(someschema) with desc should have the proper diagnostics +ok 55 - can() with desc should pass +ok 56 - can() with desc should have the proper description +ok 57 - can() with desc should have the proper diagnostics +ok 58 - can(schema) should pass +ok 59 - can(schema) should have the proper description +ok 60 - can(schema) should have the proper diagnostics +ok 61 - fail can() with desc should fail +ok 62 - fail can() with desc should have the proper description +ok 63 - fail can() with desc should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 22dd1be5444f..bdb5ab658583 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2093,12 +2093,13 @@ BEGIN SELECT ARRAY( SELECT quote_ident($2[i]) FROM generate_series(1, array_upper($2, 1)) s(i) - LEFT JOIN pg_catalog.pg_proc p - ON (quote_ident($2[i]) = quote_ident(p.proname)) - LEFT JOIN pg_catalog.pg_namespace n - ON p.pronamespace = n.oid AND n.nspname = 'pg_catalog' - WHERE p.oid IS NULL - ORDER BY s.i + WHERE quote_ident($2[i]) NOT IN ( + SELECT quote_ident(p.proname) + FROM pg_catalog.pg_proc p + JOIN pg_catalog.pg_namespace n + ON p.pronamespace = n.oid + AND quote_ident(n.nspname) = quote_ident($1) + ) ORDER BY s.i ) INTO missing; IF missing[1] IS NULL THEN RETURN ok( true, $3 ); diff --git a/sql/cantap.sql b/sql/cantap.sql index a586f1e4924a..c3690ce0c932 100644 --- a/sql/cantap.sql +++ b/sql/cantap.sql @@ -3,7 +3,9 @@ -- $Id$ -SELECT plan(60); +SELECT plan(63); +CREATE SCHEMA someschema; +CREATE FUNCTION someschema.huh () RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL; --SELECT * FROM no_plan(); /****************************************************************************/ @@ -158,6 +160,15 @@ SELECT * FROM check_test( pg_catalog.bar() missing' ); +SELECT * FROM check_test( + can( 'someschema', ARRAY['huh', 'lower', 'upper'], 'whatever' ), + false, + 'fail can(someschema) with desc', + 'whatever', + ' someschema.lower() missing + someschema.upper() missing' +); + SELECT * FROM check_test( can( ARRAY['__cat__', 'lower', 'upper'], 'whatever' ), true, From d3e16b1ce936d1d6df03e2997c739eb26243a19b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Feb 2009 01:13:21 +0000 Subject: [PATCH 0306/1195] More efficient `can()`. --- pgtap.sql.in | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index bdb5ab658583..0ecc79badad2 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2093,13 +2093,16 @@ BEGIN SELECT ARRAY( SELECT quote_ident($2[i]) FROM generate_series(1, array_upper($2, 1)) s(i) - WHERE quote_ident($2[i]) NOT IN ( - SELECT quote_ident(p.proname) - FROM pg_catalog.pg_proc p - JOIN pg_catalog.pg_namespace n - ON p.pronamespace = n.oid - AND quote_ident(n.nspname) = quote_ident($1) - ) ORDER BY s.i + LEFT JOIN pg_catalog.pg_proc p + ON p.proname = $2[i] + AND p.pronamespace = ( + SELECT oid + FROM pg_catalog.pg_namespace + WHERE nspname = $1 + ) + WHERE p.oid IS NULL + GROUP BY $2[i] + ORDER BY MIN(s.i) ) INTO missing; IF missing[1] IS NULL THEN RETURN ok( true, $3 ); From df360924c2040a173018b5de6fb2f27646d9486f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Feb 2009 01:22:15 +0000 Subject: [PATCH 0307/1195] Do not compare objects using `quote_ident()`. Per gripe from Andrew Gierth. --- Changes | 7 +++- README.pgtap | 12 +++---- compat/install-8.2.patch | 2 +- pgtap.sql.in | 72 ++++++++++++++++++++-------------------- 4 files changed, 49 insertions(+), 44 deletions(-) diff --git a/Changes b/Changes index 714c3fe1d190..76eb62739133 100644 --- a/Changes +++ b/Changes @@ -19,6 +19,11 @@ Revision history for pgTAP that it can be used to run xUnit-style test functions without an SQL script. * Fixed a bug in `can()` where it could sometimes report that functions were available in a schema when in fact they were not. +* In the schema testing functions, removed the use ofa `quote_ident()` to + compare all identifiers, such as table names, schema names, column names, + etc, added in 0.17. This is because `quote_ident(a) = quote_ident(b)` can't + give a different result than `a = b`, and besides, made things slower and + prevented the use of indexes. Thanks to Andrew Gierth for the spot. 0.18 2009-02-06T20:06:00 ------------------------- @@ -63,7 +68,7 @@ Revision history for pgTAP It had been a mix of straight-forward string comparison, and case-insensitive matching. This means that tests should use only lowercase strings to specify object names, unless mixed case objects - were crated with double quote characters. + were created with double quote characters. * Added version of `todo()` with the `why` and `how_many` arugments reversed, so that I don't have to remember a specific order. diff --git a/README.pgtap b/README.pgtap index 9397f2503d3c..91fc0164659c 100644 --- a/README.pgtap +++ b/README.pgtap @@ -614,12 +614,12 @@ A Wicked Schema Need to make sure that your database is designed just the way you think it should be? Use these test functions and rest easy. -A note on comparisons: pgTAP uses the PostgreSQL function `quote_ident()` to -compare all SQL identifiers, such as the names of tables, schemas, functions, -indexes, and columns (but not data types). So in general, you should always -use lowercase strings when passing identifier arguments to the functions -below. Use mixed case strings only when the objects were declared in your -schema using double-quotes. For example, if you created a table like so: +A note on comparisons: pgTAP uses a simple equivalence test (`=`) to compare +all SQL identifiers, such as the names of tables, schemas, functions, indexes, +and columns (but not data types). So in general, you should always use +lowercase strings when passing identifier arguments to the functions below. +Use mixed case strings only when the objects were declared in your schema +using double-quotes. For example, if you created a table like so: CREATE TABLE Foo (id integer); diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 3354f9b68326..5234bbd52f31 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -14,7 +14,7 @@ - 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 quote_ident(n.nspname) = quote_ident($1) +- AND n.nspname = $1 - AND t.typname = $2 - AND t.typtype = 'e' - ORDER BY e.oid diff --git a/pgtap.sql.in b/pgtap.sql.in index 0ecc79badad2..8bb685875a55 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -775,8 +775,8 @@ RETURNS BOOLEAN AS $$ FROM pg_catalog.pg_namespace n JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace WHERE c.relkind = $1 - AND quote_ident(n.nspname) = quote_ident($2) - AND quote_ident(c.relname) = quote_ident($3) + AND n.nspname = $2 + AND c.relname = $3 ); $$ LANGUAGE SQL; @@ -787,7 +787,7 @@ RETURNS BOOLEAN AS $$ FROM pg_catalog.pg_class c WHERE c.relkind = $1 AND pg_catalog.pg_table_is_visible(c.oid) - AND quote_ident(c.relname) = quote_ident($2) + AND c.relname = $2 ); $$ LANGUAGE SQL; @@ -906,8 +906,8 @@ RETURNS BOOLEAN AS $$ 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 quote_ident(n.nspname) = quote_ident($1) - AND quote_ident(c.relname) = quote_ident($2) + WHERE n.nspname = $1 + AND c.relname = $2 AND a.attnum > 0 AND NOT a.attisdropped AND a.attname = LOWER($3) @@ -920,7 +920,7 @@ RETURNS BOOLEAN AS $$ SELECT true FROM pg_catalog.pg_class c JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid - WHERE quote_ident(c.relname) = quote_ident($1) + WHERE c.relname = $1 AND pg_catalog.pg_table_is_visible(c.oid) AND a.attnum > 0 AND NOT a.attisdropped @@ -978,11 +978,11 @@ BEGIN FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a WHERE n.oid = c.relnamespace AND c.oid = a.attrelid - AND quote_ident(n.nspname) = quote_ident($1) - AND quote_ident(c.relname) = quote_ident($2) - AND a.attnum > 0 + AND n.nspname = $1 + AND c.relname = $2 + AND a.attnum > 0 AND NOT a.attisdropped - AND quote_ident(a.attname) = quote_ident($3) + AND a.attname = $3 AND a.attnotnull = $5 ), $4 ); @@ -1003,10 +1003,10 @@ BEGIN FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a WHERE c.oid = a.attrelid AND pg_catalog.pg_table_is_visible(c.oid) - AND quote_ident(c.relname) = quote_ident($1) + AND c.relname = $1 AND a.attnum > 0 AND NOT a.attisdropped - AND quote_ident(a.attname) = quote_ident($2) + AND a.attname = $2 AND a.attnotnull = $4 ), $3 ); @@ -1067,9 +1067,9 @@ BEGIN WHERE a.attrelid = c.oid AND pg_table_is_visible(c.oid) AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END - AND quote_ident(c.relname) = quote_ident($2) + AND c.relname = $2 AND attnum > 0 - AND quote_ident(a.attname) = quote_ident($3); + AND a.attname = $3; ELSE IF NOT _cexists( $1, $2, $3 ) THEN RETURN fail( $5 ) || E'\n' @@ -1081,10 +1081,10 @@ BEGIN WHERE n.oid = c.relnamespace AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END AND c.oid = a.attrelid - AND quote_ident(n.nspname) = quote_ident($1) - AND quote_ident(c.relname) = quote_ident($2) + AND n.nspname = $1 + AND c.relname = $2 AND attnum > 0 - AND quote_ident(a.attname) = quote_ident($3); + AND a.attname = $3; END IF; IF actual_type = LOWER($4) THEN @@ -1118,8 +1118,8 @@ RETURNS boolean AS $$ 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 quote_ident(n.nspname) = quote_ident($1) - AND quote_ident(c.relname) = quote_ident($2) + WHERE n.nspname = $1 + AND c.relname = $2 AND a.attnum > 0 AND NOT a.attisdropped AND a.attname = LOWER($3) @@ -1130,7 +1130,7 @@ RETURNS boolean AS $$ SELECT a.atthasdef FROM pg_catalog.pg_class c JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid - WHERE quote_ident(c.relname) = quote_ident($1) + WHERE c.relname = $1 AND a.attnum > 0 AND NOT a.attisdropped AND a.attname = LOWER($2) @@ -1241,8 +1241,8 @@ BEGIN AND a.atthasdef AND a.attrelid = d.adrelid AND a.attnum = d.adnum - AND quote_ident(n.nspname) = quote_ident($1) - AND quote_ident(c.relname) = quote_ident($2) + AND n.nspname = $1 + AND c.relname = $2 AND a.attnum > 0 AND NOT a.attisdropped AND a.attname = $3; @@ -1274,7 +1274,7 @@ BEGIN AND a.atthasdef AND a.attrelid = d.adrelid AND a.attnum = d.adnum - AND quote_ident(c.relname) = quote_ident($1) + AND c.relname = $1 AND a.attnum > 0 AND NOT a.attisdropped AND a.attname = $2; @@ -1338,8 +1338,8 @@ RETURNS BOOLEAN AS $$ AND c.oid = x.conrelid AND pg_table_is_visible(c.oid) AND c.relhaspkey = true - AND quote_ident(n.nspname) = quote_ident($1) - AND quote_ident(c.relname) = quote_ident($2) + AND n.nspname = $1 + AND c.relname = $2 AND x.contype = $3 ); $$ LANGUAGE sql; @@ -1352,7 +1352,7 @@ RETURNS BOOLEAN AS $$ FROM pg_catalog.pg_class c JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid WHERE c.relhaspkey = true - AND quote_ident(c.relname) = quote_ident($1) + AND c.relname = $1 AND x.contype = $2 ); $$ LANGUAGE sql; @@ -1402,8 +1402,8 @@ RETURNS NAME[] AS $$ 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_constraint x ON c.oid = x.conrelid AND a.attnum = ANY( x.conkey ) - WHERE quote_ident(n.nspname) = quote_ident($1) - AND quote_ident(c.relname) = quote_ident($2) + WHERE n.nspname = $1 + AND c.relname = $2 AND x.contype = $3 ); $$ LANGUAGE sql; @@ -1416,7 +1416,7 @@ RETURNS NAME[] AS $$ FROM pg_catalog.pg_class c JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid AND a.attnum = ANY( x.conkey ) - AND quote_ident(c.relname) = quote_ident($1) + AND c.relname = $1 AND x.contype = $2 ); $$ LANGUAGE sql; @@ -2000,7 +2000,7 @@ RETURNS TEXT AS $$ SELECT true FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid - WHERE quote_ident(n.nspname) = quote_ident($1) + WHERE n.nspname = $1 AND quote_ident(p.proname) = quote_ident($2) AND array_to_string(p.proargtypes::regtype[], ',') = array_to_string($3, ',') ), $4 @@ -2022,7 +2022,7 @@ RETURNS TEXT AS $$ SELECT true FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid - WHERE quote_ident(n.nspname) = quote_ident($1) + WHERE n.nspname = $1 AND quote_ident(p.proname) = quote_ident($2) ), $3 ); @@ -2693,8 +2693,8 @@ BEGIN 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 quote_ident(n.nspname) = quote_ident($1) - AND quote_ident(c.relname) = quote_ident($2) + WHERE n.nspname = $1 + AND c.relname = $2 AND quote_ident(t.tgname) = quote_ident($3) INTO res; @@ -2720,7 +2720,7 @@ BEGIN SELECT true FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON (c.oid = t.tgrelid) - WHERE quote_ident(c.relname) = quote_ident($1) + WHERE c.relname = $1 AND quote_ident(t.tgname) = quote_ident($2) AND pg_catalog.pg_table_is_visible(c.oid) INTO res; @@ -2882,7 +2882,7 @@ RETURNS BOOLEAN AS $$ FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n ON (t.typnamespace = n.oid) WHERE t.typisdefined - AND quote_ident(n.nspname) = quote_ident($1) + AND n.nspname = $1 AND quote_ident(t.typname) = quote_ident($2) AND t.typtype = ANY( COALESCE($3, ARRAY['b', 'c', 'd', 'p', 'e']) ) ); @@ -3054,7 +3054,7 @@ RETURNS TEXT AS $$ 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 quote_ident(n.nspname) = quote_ident($1) + AND n.nspname = $1 AND t.typname = $2 AND t.typtype = 'e' ORDER BY e.oid From d5010fd761bbf2c61f45c6ac0cc11dc03eb2c04e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Feb 2009 01:44:02 +0000 Subject: [PATCH 0308/1195] More `quote_ident()`s eliminated. Don't know how I missed them all before. --- pgtap.sql.in | 157 ++++++++++++++++++++++++--------------------------- 1 file changed, 75 insertions(+), 82 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 8bb685875a55..dba1cdc6e68f 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1421,15 +1421,6 @@ RETURNS NAME[] AS $$ ); $$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION _quote_ident_array( name[] ) -RETURNS text[] AS $$ - SELECT ARRAY( - SELECT quote_ident($1[i]) - FROM generate_series(1, array_upper($1, 1)) s(i) - ORDER BY i - ) -$$ LANGUAGE SQL immutable; - CREATE OR REPLACE FUNCTION _ident_array_to_string( name[], text ) RETURNS text AS $$ SELECT array_to_string(ARRAY( @@ -1645,9 +1636,9 @@ RETURNS BOOLEAN AS $$ SELECT EXISTS( SELECT TRUE FROM pg_all_foreign_keys - WHERE quote_ident(fk_schema_name) = quote_ident($1) + WHERE fk_schema_name = $1 AND quote_ident(fk_table_name) = quote_ident($2) - AND _quote_ident_array(fk_columns) = _quote_ident_array($3) + AND fk_columns = $3 ); $$ LANGUAGE SQL; @@ -1657,7 +1648,7 @@ RETURNS BOOLEAN AS $$ SELECT TRUE FROM pg_all_foreign_keys WHERE quote_ident(fk_table_name) = quote_ident($1) - AND _quote_ident_array(fk_columns) = _quote_ident_array($2) + AND fk_columns = $2 ); $$ LANGUAGE SQL; @@ -1675,8 +1666,8 @@ BEGIN SELECT ARRAY( SELECT _ident_array_to_string(fk_columns, ', ') FROM pg_all_foreign_keys - WHERE quote_ident(fk_schema_name) = quote_ident($1) - AND quote_ident(fk_table_name) = quote_ident($2) + WHERE fk_schema_name = $1 + AND fk_table_name = $2 ORDER BY fk_columns ) INTO names; @@ -1708,7 +1699,7 @@ BEGIN SELECT ARRAY( SELECT _ident_array_to_string(fk_columns, ', ') FROM pg_all_foreign_keys - WHERE quote_ident(fk_table_name) = quote_ident($1) + WHERE fk_table_name = $1 ORDER BY fk_columns ) INTO names; @@ -1904,9 +1895,9 @@ DECLARE BEGIN SELECT pk_schema_name, pk_table_name, pk_columns FROM pg_all_foreign_keys - WHERE quote_ident(fk_schema_name) = quote_ident($1) - AND quote_ident(fk_table_name) = quote_ident($2) - AND _quote_ident_array(fk_columns) = _quote_ident_array($3) + WHERE fk_schema_name = $1 + AND fk_table_name = $2 + AND fk_columns = $3 INTO sch, tab, cols; RETURN is( @@ -1931,8 +1922,8 @@ DECLARE BEGIN SELECT pk_table_name, pk_columns FROM pg_all_foreign_keys - WHERE quote_ident(fk_table_name) = quote_ident($1) - AND _quote_ident_array(fk_columns) = _quote_ident_array($2) + WHERE fk_table_name = $1 + AND fk_columns = $2 INTO tab, cols; RETURN is( @@ -2001,7 +1992,7 @@ RETURNS TEXT AS $$ FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = $1 - AND quote_ident(p.proname) = quote_ident($2) + AND p.proname = $2 AND array_to_string(p.proargtypes::regtype[], ',') = array_to_string($3, ',') ), $4 ); @@ -2023,7 +2014,7 @@ RETURNS TEXT AS $$ FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = $1 - AND quote_ident(p.proname) = quote_ident($2) + AND p.proname = $2 ), $3 ); $$ LANGUAGE SQL; @@ -2041,7 +2032,7 @@ RETURNS TEXT AS $$ EXISTS( SELECT true FROM pg_catalog.pg_proc p - WHERE quote_ident(p.proname) = quote_ident($1) + WHERE p.proname = $1 AND pg_catalog.pg_function_is_visible(p.oid) AND array_to_string(p.proargtypes::regtype[], ',') = array_to_string($2, ',') ), $3 @@ -2062,7 +2053,7 @@ RETURNS TEXT AS $$ EXISTS( SELECT true FROM pg_catalog.pg_proc p - WHERE quote_ident(p.proname) = quote_ident($1) + WHERE p.proname = $1 AND pg_catalog.pg_function_is_visible(p.oid) ), $2 ); @@ -2131,9 +2122,10 @@ BEGIN SELECT quote_ident($1[i]) FROM generate_series(1, array_upper($1, 1)) s(i) LEFT JOIN pg_catalog.pg_proc p - ON (quote_ident($1[i]) = quote_ident(p.proname) AND pg_catalog.pg_function_is_visible(p.oid)) - WHERE p.oid IS NULL - ORDER BY s.i + 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 ); @@ -2161,9 +2153,9 @@ RETURNS NAME[] AS $$ 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_attribute a ON (ct.oid = a.attrelid) - WHERE ct.relname = quote_ident($2) - AND ci.relname = quote_ident($3) - AND n.nspname = quote_ident($1) + WHERE ct.relname = $2 + AND ci.relname = $3 + AND n.nspname = $1 AND a.attnum = ANY(x.indkey::int[]) ); $$ LANGUAGE sql; @@ -2176,8 +2168,8 @@ RETURNS NAME[] AS $$ 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_attribute a ON (ct.oid = a.attrelid) - WHERE quote_ident(ct.relname) = quote_ident($1) - AND quote_ident(ci.relname) = quote_ident($2) + WHERE ct.relname = $1 + AND ci.relname = $2 AND a.attnum = ANY(x.indkey::int[]) AND pg_catalog.pg_table_is_visible(ct.oid) ); @@ -2190,9 +2182,9 @@ RETURNS TEXT AS $$ 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 quote_ident(ct.relname) = quote_ident($2) - AND quote_ident(ci.relname) = quote_ident($3) - AND quote_ident(n.nspname) = quote_ident($1); + WHERE ct.relname = $2 + AND ci.relname = $3 + AND n.nspname = $1; $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME) @@ -2201,8 +2193,8 @@ RETURNS TEXT AS $$ 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 quote_ident(ct.relname) = quote_ident($1) - AND quote_ident(ci.relname) = quote_ident($2) + WHERE ct.relname = $1 + AND ci.relname = $2 AND pg_catalog.pg_table_is_visible(ct.oid) $$ LANGUAGE sql; @@ -2299,7 +2291,8 @@ returns boolean AS $$ SELECT EXISTS( SELECT true FROM pg_catalog.pg_namespace - WHERE quote_ident(nspname) = quote_ident($1) ); + WHERE nspname = $1 + ); $$ LANGUAGE sql; -- _has_index( schema, table, index ) @@ -2340,16 +2333,16 @@ BEGIN -- Looking for an index within a schema. have_expr := _iexpr($1, $2, $3); want_expr := $4; - descr := 'Index ' || quote_ident($3) || ' should exist'; - idx := $3; - tab := quote_ident($1) || '.' || quote_ident($2); + descr := 'Index ' || quote_ident($3) || ' should exist'; + idx := $3; + tab := quote_ident($1) || '.' || quote_ident($2); ELSE -- Looking for an index without a schema spec. have_expr := _iexpr($1, $2); want_expr := $3; - descr := $4; - idx := $2; - tab := quote_ident($1); + descr := $4; + idx := $2; + tab := quote_ident($1); END IF; IF have_expr IS NULL THEN @@ -2406,9 +2399,9 @@ BEGIN 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 quote_ident(ct.relname) = quote_ident($2) - AND quote_ident(ci.relname) = quote_ident($3) - AND quote_ident(n.nspname) = quote_ident($1) + WHERE ct.relname = $2 + AND ci.relname = $3 + AND n.nspname = $1 INTO res; RETURN ok( COALESCE(res, false), $4 ); @@ -2434,8 +2427,8 @@ BEGIN 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 quote_ident(ct.relname) = quote_ident($1) - AND quote_ident(ci.relname) = quote_ident($2) + WHERE ct.relname = $1 + AND ci.relname = $2 AND pg_catalog.pg_table_is_visible(ct.oid) INTO res; @@ -2456,7 +2449,7 @@ BEGIN 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 quote_ident(ci.relname) = quote_ident($1) + WHERE ci.relname = $1 AND pg_catalog.pg_table_is_visible(ct.oid) INTO res; @@ -2478,9 +2471,9 @@ BEGIN 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 quote_ident(ct.relname) = quote_ident($2) - AND quote_ident(ci.relname) = quote_ident($3) - AND quote_ident(n.nspname) = quote_ident($1) + WHERE ct.relname = $2 + AND ci.relname = $3 + AND n.nspname = $1 INTO res; RETURN ok( COALESCE(res, false), $4 ); @@ -2506,8 +2499,8 @@ BEGIN 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 quote_ident(ct.relname) = quote_ident($1) - AND quote_ident(ci.relname) = quote_ident($2) + WHERE ct.relname = $1 + AND ci.relname = $2 AND pg_catalog.pg_table_is_visible(ct.oid) INTO res; @@ -2528,7 +2521,7 @@ BEGIN 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 quote_ident(ci.relname) = quote_ident($1) + WHERE ci.relname = $1 AND pg_catalog.pg_table_is_visible(ct.oid) INTO res; @@ -2550,9 +2543,9 @@ BEGIN 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 quote_ident(ct.relname) = quote_ident($2) - AND quote_ident(ci.relname) = quote_ident($3) - AND quote_ident(n.nspname) = quote_ident($1) + WHERE ct.relname = $2 + AND ci.relname = $3 + AND n.nspname = $1 INTO res; RETURN ok( COALESCE(res, false), $4 ); @@ -2579,8 +2572,8 @@ BEGIN 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 quote_ident(ct.relname) = quote_ident($1) - AND quote_ident(ci.relname) = quote_ident($2) + WHERE ct.relname = $1 + AND ci.relname = $2 INTO res; RETURN ok( @@ -2599,7 +2592,7 @@ BEGIN SELECT x.indisclustered FROM pg_catalog.pg_index x JOIN pg_catalog.pg_class ci ON (ci.oid = x.indexrelid) - WHERE quote_ident(ci.relname) = quote_ident($1) + WHERE ci.relname = $1 INTO res; RETURN ok( @@ -2621,9 +2614,9 @@ BEGIN 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 quote_ident(ct.relname) = quote_ident($2) - AND quote_ident(ci.relname) = quote_ident($3) - AND quote_ident(n.nspname) = quote_ident($1) + WHERE ct.relname = $2 + AND ci.relname = $3 + AND n.nspname = $1 INTO aname; return is( aname, LOWER($4)::name, $5 ); @@ -2650,8 +2643,8 @@ BEGIN 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 quote_ident(ct.relname) = quote_ident($1) - AND quote_ident(ci.relname) = quote_ident($2) + WHERE ct.relname = $1 + AND ci.relname = $2 INTO aname; return is( @@ -2672,7 +2665,7 @@ BEGIN 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 quote_ident(ci.relname) = quote_ident($1) + WHERE ci.relname = $1 INTO aname; return is( @@ -2695,7 +2688,7 @@ BEGIN JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) WHERE n.nspname = $1 AND c.relname = $2 - AND quote_ident(t.tgname) = quote_ident($3) + AND t.tgname = $3 INTO res; RETURN ok( COALESCE(res, false), $4 ); @@ -2721,7 +2714,7 @@ BEGIN FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON (c.oid = t.tgrelid) WHERE c.relname = $1 - AND quote_ident(t.tgname) = quote_ident($2) + AND t.tgname = $2 AND pg_catalog.pg_table_is_visible(c.oid) INTO res; @@ -2744,9 +2737,9 @@ BEGIN 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 quote_ident(nt.nspname) = quote_ident($1) - AND quote_ident(ct.relname) = quote_ident($2) - AND quote_ident(t.tgname) = quote_ident($3) + WHERE nt.nspname = $1 + AND ct.relname = $2 + AND t.tgname = $3 INTO pname; RETURN is( pname, quote_ident($4) || '.' || quote_ident($5), $6 ); @@ -2772,8 +2765,8 @@ BEGIN 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 quote_ident(ct.relname) = quote_ident($1) - AND quote_ident(t.tgname) = quote_ident($2) + WHERE ct.relname = $1 + AND t.tgname = $2 AND pg_catalog.pg_table_is_visible(ct.oid) INTO pname; @@ -2797,7 +2790,7 @@ RETURNS TEXT AS $$ EXISTS( SELECT true FROM pg_catalog.pg_namespace - WHERE quote_ident(nspname) = quote_ident($1) + WHERE nspname = $1 ), $2 ); $$ LANGUAGE sql; @@ -2815,7 +2808,7 @@ RETURNS TEXT AS $$ NOT EXISTS( SELECT true FROM pg_catalog.pg_namespace - WHERE quote_ident(nspname) = quote_ident($1) + WHERE nspname = $1 ), $2 ); $$ LANGUAGE sql; @@ -2833,7 +2826,7 @@ RETURNS TEXT AS $$ EXISTS( SELECT true FROM pg_catalog.pg_tablespace - WHERE quote_ident(spcname) = quote_ident($1) + WHERE spcname = $1 AND spclocation = $2 ), $3 ); @@ -2846,7 +2839,7 @@ RETURNS TEXT AS $$ EXISTS( SELECT true FROM pg_catalog.pg_tablespace - WHERE quote_ident(spcname) = quote_ident($1) + WHERE spcname = $1 ), $2 ); $$ LANGUAGE sql; @@ -2864,7 +2857,7 @@ RETURNS TEXT AS $$ NOT EXISTS( SELECT true FROM pg_catalog.pg_tablespace - WHERE quote_ident(spcname) = quote_ident($1) + WHERE spcname = $1 ), $2 ); $$ LANGUAGE sql; @@ -2883,7 +2876,7 @@ RETURNS BOOLEAN AS $$ JOIN pg_catalog.pg_namespace n ON (t.typnamespace = n.oid) WHERE t.typisdefined AND n.nspname = $1 - AND quote_ident(t.typname) = quote_ident($2) + AND t.typname = $2 AND t.typtype = ANY( COALESCE($3, ARRAY['b', 'c', 'd', 'p', 'e']) ) ); $$ LANGUAGE sql; @@ -3105,7 +3098,7 @@ CREATE OR REPLACE FUNCTION _is_super( NAME ) RETURNS BOOLEAN AS $$ SELECT usesuper FROM pg_catalog.pg_user - WHERE quote_ident(usename) = quote_ident($1) + WHERE usename = $1 $$ LANGUAGE sql STRICT; -- has_user( user, desc ) From 8f636cf04dcc1f4fa1834d93dc31ee15726f81e0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Feb 2009 19:48:50 +0000 Subject: [PATCH 0309/1195] * Added group and role test functions. * Moved user and group tests to `sql/usergroup.sql`. * Changed superuser tests to assume that the current user is always a super user. Also updated the README to reflect that this is a requirement. This simplifies things quite a bit, and I seriously doubt that tests are run as non-superusers very often. * Put role tests in `sql/roletap.sql`. This file will not be tested against PostgreSQL 8.0, which does not have roles. * Added a temporary function, `fakeout()`, to mimic the output of `check_test()` when I don't actually want to run a test in `sql/coltap.sql`. I had a pasted solution there before, but I might need this more in the future, too. --- Changes | 2 + Makefile | 7 +- README.pgtap | 86 ++++++++++++-- compat/uninstall-8.0.patch | 14 +++ expected/hastap.out | 50 +------- expected/roletap.out | 26 +++++ expected/usergroup.out | 80 +++++++++++++ pgtap.sql.in | 123 +++++++++++++++++++- sql/coltap.sql | 30 ++++- sql/hastap.sql | 165 +------------------------- sql/roletap.sql | 73 ++++++++++++ sql/usergroup.sql | 231 +++++++++++++++++++++++++++++++++++++ uninstall_pgtap.sql.in | 16 ++- 13 files changed, 667 insertions(+), 236 deletions(-) create mode 100644 compat/uninstall-8.0.patch create mode 100644 expected/roletap.out create mode 100644 expected/usergroup.out create mode 100644 sql/roletap.sql create mode 100644 sql/usergroup.sql diff --git a/Changes b/Changes index 76eb62739133..3967950fa480 100644 --- a/Changes +++ b/Changes @@ -9,7 +9,9 @@ Revision history for pgTAP default. This means that you have to do a lot less casting of default values specified in the arguments to `col_default_is()`. It also restores the signature that 0.17 recommended be dropped. +* Added `has_role()` and `isnt_role()`. * Added `has_user()`, `isnt_user()`, `is_superuser()`, and `isnt_superuser()`. +* Added `has_group()`, `isnt_group()`, and `is_member_of()`. * Fixed syntax in the `Makefile` to make it compatible with older versions of `make`. Reported by Aaron Kangas. * Improved the documentation of `runtests()` to make it clearer that all tests diff --git a/Makefile b/Makefile index 1e1d3387cc97..188829351cd2 100644 --- a/Makefile +++ b/Makefile @@ -57,9 +57,9 @@ ifeq ($(PGVER_MAJOR), 8) ifeq ($(PGVER_MINOR), 0) # Hack for E'' syntax (<= PG8.0) EXTRA_SUBS := -e "s/ E'/ '/g" -# Throw, runtests, and enums aren't supported in 8.0. -TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql sql/enumtap.sql,$(TESTS)) -REGRESS := $(filter-out throwtap runtests enumtap,$(REGRESS)) +# Throw, runtests, enums, and roles aren't supported in 8.0. +TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql sql/enumtap.sql sql/roletap.sql,$(TESTS)) +REGRESS := $(filter-out throwtap runtests enumtap roletap,$(REGRESS)) else ifeq ($(PGVER_MINOR), 4) # Remove lines 15-20, which define pg_typeof(). @@ -173,6 +173,7 @@ ifneq ($(PGVER_MINOR), 3) rm uninstall_pgtap.tmp endif ifeq ($(PGVER_MINOR), 0) + patch -p0 < compat/uninstall-8.0.patch mv uninstall_pgtap.sql uninstall_pgtap.tmp cat compat/uninstall-8.0.sql uninstall_pgtap.tmp >> uninstall_pgtap.sql rm uninstall_pgtap.tmp diff --git a/README.pgtap b/README.pgtap index 91fc0164659c..5f912db0b3f8 100644 --- a/README.pgtap +++ b/README.pgtap @@ -39,7 +39,8 @@ package management system such as RPM to install PostgreSQL, be sure that the `-devel` package is also installed. If necessary, add the path to `pg_config` to your `$PATH` environment variable: - env PATH=$PATH:/path/to/pgsql/bin USE_PGXS=1 make && make test && make install + env PATH=$PATH:/path/to/pgsql/bin USE_PGXS=1 \ + make && make install && make installcheck And finally, if all that fails, copy the entire distribution directory to the `contrib/` subdirectory of the PostgreSQL source tree and try it there without @@ -49,6 +50,15 @@ the `$USE_PGXS` variable: make install make installcheck +If you encounter an error such as: + + ERROR: must be owner of database regression + +You need to run the test suite using a super user, such as the default +"postgres" super user: + + make installcheck PGUSER=postgres + If you want to schema-qualify pgTAP (that is, install all of its functions into their own schema), set the `$TAPSCHEMA` variable to the name of the schema you'd like to be created, for example: @@ -74,7 +84,7 @@ PostgreSQL client environment variables: * `$PGPORT` * `$PGUSER` -You can use it to run the test suite like so: +You can use it to run the test suite as a database super user like so: make test USE_PGXS=1 PGUSER=postgres @@ -1639,21 +1649,37 @@ cast it to `name[]` to disambiguate it from a text string: SELECT can_ok( 'lower', '{text}'::name[] ); +### `has_role( role, desc )` ### +### `has_role( role )` ### + + SELECT has_role( 'theory', 'Role "theory" should exist' ); + +Checks to ensure that a database role exists. If the description is omitted, +it will default to "Role `:role` should exist". + +### `hasnt_role( role, desc )` ### +### `hasnt_role( role )` ### + + SELECT hasnt_role( 'theory', 'Role "theory" not should exist' ); + +The inverse of `has_role()`, this function tests for the *absence* of a +database role. + ### `has_user( user, desc )` ### ### `has_user( user )` ### SELECT has_user( 'theory', 'User "theory" should exist' ); Checks to ensure that a database user exists. If the description is omitted, -it will default to "User `:username` should exist". +it will default to "User `:user` should exist". ### `hasnt_user( user, desc )` ### ### `hasnt_user( user )` ### - SELECT hasnt_user( 'theory', 'User "theory" should exist' ); + SELECT hasnt_user( 'theory', 'User "theory" not should exist' ); -The inverse of `has_user()`, this function tests for the *absence* of a database -user. +The inverse of `has_user()`, this function tests for the *absence* of a +database user. ### `is_superuser( user, desc )` ### ### `is_superuser( user )` ### @@ -1661,8 +1687,8 @@ user. SELECT is_superuser( 'theory', 'User "theory" should be a super user' ); Tests that a database user is a super user. If the description is omitted, it -will default to "User `:username` should be a super user". If the user does -not exist in the database, the diagnostics will say so. +will default to "User `:user` should be a super user". If the user does not +exist in the database, the diagnostics will say so. ### `isnt_superuser( user, desc )` ### ### `isnt_superuser( user )` ### @@ -1677,6 +1703,45 @@ The inverse of `is_superuser()`, this function tests that a database user is database, the test is still considered a failure, and the diagnostics will say so. +### `has_group( group, desc )` ### +### `has_group( group )` ### + + SELECT has_group( 'sweeties, 'Group "sweeties should exist' ); + +Checks to ensure that a database group exists. If the description is omitted, +it will default to "Group `:group` should exist". + +### `hasnt_group( group, desc )` ### +### `hasnt_group( group )` ### + + SELECT hasnt_group( 'meanies, 'Group meaines should not exist' ); + +The inverse of `has_group()`, this function tests for the *absence* of a +database group. + +### `is_member_of( group, users[], desc )` ### +### `is_member_of( group, users[] )` ### +### `is_member_of( group, users, desc )` ### +### `is_member_of( group, users )` ### + + SELECT is_member_of( 'sweeties', 'anna' 'Anna should be a sweetie' ); + SELECT is_member_of( 'meanies', ARRAY['dr_evil', 'dr_no' ] ); + +Checks whether a group contains a user or all of an array of users. If the +description is omitted, it will default to "Should have members of group +`:group`." On failure, `is_member_of()` will output diagnostics listing the +missing users, like so: + + not ok 370 - Should have members of group meanies + # Failed test 370: "Should have members of group meanies" + # Users missing from the meanies group: + # theory + # agliodbs + +If the group does not exist, the diagnostics will tell you that, instead. But +you use `has_group()` to make sure the group exists before you check its +members, don't you? Of course you do. + Diagnostics ----------- @@ -2288,15 +2353,14 @@ compatibility: * `int2vector` to `integer[]`. Otherwise, all is the same as for 8.2 Do note, however, that the -`throws_ok()`, `lives_ok()`, and `runtests()` functions do not work under 8.0. -Don't even use them there. +`throws_ok()`, `lives_ok()`, `runtests()`, `has_role()`, and `hasnt_role()` +functions do not work under 8.0. Don't even use them there. To Do ----- * Useful schema testing functions to consider adding: * `has_cast()` - * `has_role()`, `has_group()` * `has_operator()` * `has_operator_class()` * `has_rule()` diff --git a/compat/uninstall-8.0.patch b/compat/uninstall-8.0.patch new file mode 100644 index 000000000000..35dc27815b35 --- /dev/null +++ b/compat/uninstall-8.0.patch @@ -0,0 +1,14 @@ +--- uninstall_pgtap.sql.orig 2009-02-19 20:04:48.000000000 -0800 ++++ uninstall_pgtap.sql.in 2009-02-19 20:04:50.000000000 -0800 +@@ -38,11 +38,6 @@ + DROP FUNCTION has_user( NAME ); + DROP FUNCTION has_user( NAME, TEXT ); + DROP FUNCTION _is_super( NAME ); +-DROP FUNCTION hasnt_role( NAME ); +-DROP FUNCTION hasnt_role( NAME, TEXT ); +-DROP FUNCTION has_role( NAME ); +-DROP FUNCTION has_role( NAME, TEXT ); +-DROP FUNCTION _has_role( NAME ); + DROP FUNCTION enum_has_labels( NAME, NAME[] ); + DROP FUNCTION enum_has_labels( NAME, NAME[], TEXT ); + DROP FUNCTION enum_has_labels( NAME, NAME, NAME[] ); diff --git a/expected/hastap.out b/expected/hastap.out index 327a9ec42b9e..345b4f020b8a 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..333 +1..285 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -285,51 +285,3 @@ ok 282 - hasnt_column(view, column) should have the proper diagnostics ok 283 - hasnt_column(type, column) should pass ok 284 - hasnt_column(type, column) should have the proper description ok 285 - hasnt_column(type, column) should have the proper diagnostics -ok 286 - has_user(current user) should pass -ok 287 - has_user(current user) should have the proper description -ok 288 - has_user(current user) should have the proper diagnostics -ok 289 - has_user(current user, desc) should pass -ok 290 - has_user(current user, desc) should have the proper description -ok 291 - has_user(current user, desc) should have the proper diagnostics -ok 292 - has_user(nonexistent user) should fail -ok 293 - has_user(nonexistent user) should have the proper description -ok 294 - has_user(nonexistent user) should have the proper diagnostics -ok 295 - has_user(nonexistent user, desc) should fail -ok 296 - has_user(nonexistent user, desc) should have the proper description -ok 297 - has_user(nonexistent user, desc) should have the proper diagnostics -ok 298 - hasnt_user(current user) should fail -ok 299 - hasnt_user(current user) should have the proper description -ok 300 - hasnt_user(current user) should have the proper diagnostics -ok 301 - hasnt_user(current user, desc) should fail -ok 302 - hasnt_user(current user, desc) should have the proper description -ok 303 - hasnt_user(current user, desc) should have the proper diagnostics -ok 304 - hasnt_user(nonexistent user) should pass -ok 305 - hasnt_user(nonexistent user) should have the proper description -ok 306 - hasnt_user(nonexistent user) should have the proper diagnostics -ok 307 - hasnt_user(nonexistent user, desc) should pass -ok 308 - hasnt_user(nonexistent user, desc) should have the proper description -ok 309 - hasnt_user(nonexistent user, desc) should have the proper diagnostics -ok 310 - is_superuser(nonexistent user, desc) should fail -ok 311 - is_superuser(nonexistent user, desc) should have the proper description -ok 312 - is_superuser(nonexistent user, desc) should have the proper diagnostics -ok 313 - is_superuser(nonexistent user) should fail -ok 314 - is_superuser(nonexistent user) should have the proper description -ok 315 - is_superuser(nonexistent user) should have the proper diagnostics -ok 316 - isnt_superuser(nonexistent user, desc) should fail -ok 317 - isnt_superuser(nonexistent user, desc) should have the proper description -ok 318 - isnt_superuser(nonexistent user, desc) should have the proper diagnostics -ok 319 - isnt_superuser(nonexistent user) should fail -ok 320 - isnt_superuser(nonexistent user) should have the proper description -ok 321 - isnt_superuser(nonexistent user) should have the proper diagnostics -ok 322 - is_superuser( current user ) should pass -ok 323 - is_superuser( current user ) should have the proper description -ok 324 - is_superuser( current user ) should have the proper diagnostics -ok 325 - is_superuser( current user, desc ) should pass -ok 326 - is_superuser( current user, desc ) should have the proper description -ok 327 - is_superuser( current user, desc ) should have the proper diagnostics -ok 328 - isnt_superuser( current user ) should pass -ok 329 - isnt_superuser( current user ) should have the proper description -ok 330 - isnt_superuser( current user ) should have the proper diagnostics -ok 331 - isnt_superuser( current user, desc ) should pass -ok 332 - isnt_superuser( current user, desc ) should have the proper description -ok 333 - isnt_superuser( current user, desc ) should have the proper diagnostics diff --git a/expected/roletap.out b/expected/roletap.out new file mode 100644 index 000000000000..52afede853a1 --- /dev/null +++ b/expected/roletap.out @@ -0,0 +1,26 @@ +\unset ECHO +1..24 +ok 1 - has_role(current role) should pass +ok 2 - has_role(current role) should have the proper description +ok 3 - has_role(current role) should have the proper diagnostics +ok 4 - has_role(current role, desc) should pass +ok 5 - has_role(current role, desc) should have the proper description +ok 6 - has_role(current role, desc) should have the proper diagnostics +ok 7 - has_role(nonexistent role) should fail +ok 8 - has_role(nonexistent role) should have the proper description +ok 9 - has_role(nonexistent role) should have the proper diagnostics +ok 10 - has_role(nonexistent role, desc) should fail +ok 11 - has_role(nonexistent role, desc) should have the proper description +ok 12 - has_role(nonexistent role, desc) should have the proper diagnostics +ok 13 - hasnt_role(current role) should fail +ok 14 - hasnt_role(current role) should have the proper description +ok 15 - hasnt_role(current role) should have the proper diagnostics +ok 16 - hasnt_role(current role, desc) should fail +ok 17 - hasnt_role(current role, desc) should have the proper description +ok 18 - hasnt_role(current role, desc) should have the proper diagnostics +ok 19 - hasnt_role(nonexistent role) should pass +ok 20 - hasnt_role(nonexistent role) should have the proper description +ok 21 - hasnt_role(nonexistent role) should have the proper diagnostics +ok 22 - hasnt_role(nonexistent role, desc) should pass +ok 23 - hasnt_role(nonexistent role, desc) should have the proper description +ok 24 - hasnt_role(nonexistent role, desc) should have the proper diagnostics diff --git a/expected/usergroup.out b/expected/usergroup.out new file mode 100644 index 000000000000..03bec0bf28dd --- /dev/null +++ b/expected/usergroup.out @@ -0,0 +1,80 @@ +\unset ECHO +1..78 +ok 1 - has_user(current user) should pass +ok 2 - has_user(current user) should have the proper description +ok 3 - has_user(current user) should have the proper diagnostics +ok 4 - has_user(current user, desc) should pass +ok 5 - has_user(current user, desc) should have the proper description +ok 6 - has_user(current user, desc) should have the proper diagnostics +ok 7 - has_user(nonexistent user) should fail +ok 8 - has_user(nonexistent user) should have the proper description +ok 9 - has_user(nonexistent user) should have the proper diagnostics +ok 10 - has_user(nonexistent user, desc) should fail +ok 11 - has_user(nonexistent user, desc) should have the proper description +ok 12 - has_user(nonexistent user, desc) should have the proper diagnostics +ok 13 - hasnt_user(current user) should fail +ok 14 - hasnt_user(current user) should have the proper description +ok 15 - hasnt_user(current user) should have the proper diagnostics +ok 16 - hasnt_user(current user, desc) should fail +ok 17 - hasnt_user(current user, desc) should have the proper description +ok 18 - hasnt_user(current user, desc) should have the proper diagnostics +ok 19 - hasnt_user(nonexistent user) should pass +ok 20 - hasnt_user(nonexistent user) should have the proper description +ok 21 - hasnt_user(nonexistent user) should have the proper diagnostics +ok 22 - hasnt_user(nonexistent user, desc) should pass +ok 23 - hasnt_user(nonexistent user, desc) should have the proper description +ok 24 - hasnt_user(nonexistent user, desc) should have the proper diagnostics +ok 25 - is_superuser(nonexistent user, desc) should fail +ok 26 - is_superuser(nonexistent user, desc) should have the proper description +ok 27 - is_superuser(nonexistent user, desc) should have the proper diagnostics +ok 28 - is_superuser(nonexistent user) should fail +ok 29 - is_superuser(nonexistent user) should have the proper description +ok 30 - is_superuser(nonexistent user) should have the proper diagnostics +ok 31 - isnt_superuser(nonexistent user, desc) should fail +ok 32 - isnt_superuser(nonexistent user, desc) should have the proper description +ok 33 - isnt_superuser(nonexistent user, desc) should have the proper diagnostics +ok 34 - isnt_superuser(nonexistent user) should fail +ok 35 - isnt_superuser(nonexistent user) should have the proper description +ok 36 - isnt_superuser(nonexistent user) should have the proper diagnostics +ok 37 - is_superuser( current user ) should pass +ok 38 - is_superuser( current user ) should have the proper description +ok 39 - is_superuser( current user ) should have the proper diagnostics +ok 40 - is_superuser( current user, desc ) should pass +ok 41 - is_superuser( current user, desc ) should have the proper description +ok 42 - is_superuser( current user, desc ) should have the proper diagnostics +ok 43 - has_group(group) should pass +ok 44 - has_group(group) should have the proper description +ok 45 - has_group(group) should have the proper diagnostics +ok 46 - has_group(group, desc) should pass +ok 47 - has_group(group, desc) should have the proper description +ok 48 - has_group(group, desc) should have the proper diagnostics +ok 49 - has_group(nonexistent group) should fail +ok 50 - has_group(nonexistent group) should have the proper description +ok 51 - has_group(nonexistent group) should have the proper diagnostics +ok 52 - has_group(nonexistent group, desc) should fail +ok 53 - has_group(nonexistent group, desc) should have the proper description +ok 54 - has_group(nonexistent group, desc) should have the proper diagnostics +ok 55 - hasnt_group(group) should fail +ok 56 - hasnt_group(group) should have the proper description +ok 57 - hasnt_group(group) should have the proper diagnostics +ok 58 - hasnt_group(group, desc) should fail +ok 59 - hasnt_group(group, desc) should have the proper description +ok 60 - hasnt_group(group, desc) should have the proper diagnostics +ok 61 - hasnt_group(nonexistent group) should pass +ok 62 - hasnt_group(nonexistent group) should have the proper description +ok 63 - hasnt_group(nonexistent group) should have the proper diagnostics +ok 64 - hasnt_group(nonexistent group, desc) should pass +ok 65 - hasnt_group(nonexistent group, desc) should have the proper description +ok 66 - hasnt_group(nonexistent group, desc) should have the proper diagnostics +ok 67 - is_member_of(meanies, [current_user], desc) should pass +ok 68 - is_member_of(meanies, [current_user], desc) should have the proper description +ok 69 - is_member_of(meanies, [current_user], desc) should have the proper diagnostics +ok 70 - is_member_of(meanies, [current_user]) should pass +ok 71 - is_member_of(meanies, [current_user]) should have the proper description +ok 72 - is_member_of(meanies, [current_user]) should have the proper diagnostics +ok 73 - is_member_of(meanies, current_user, desc) should pass +ok 74 - is_member_of(meanies, current_user, desc) should have the proper description +ok 75 - is_member_of(meanies, current_user, desc) should have the proper diagnostics +ok 76 - is_member_of(meanies, current_user) should pass +ok 77 - is_member_of(meanies, current_user) should have the proper description +ok 78 - is_member_of(meanies, current_user) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index dba1cdc6e68f..66ef8988c934 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2079,7 +2079,7 @@ $$ LANGUAGE SQL stable; CREATE OR REPLACE FUNCTION can ( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ DECLARE - missing name[]; + missing text[]; BEGIN SELECT ARRAY( SELECT quote_ident($2[i]) @@ -2116,7 +2116,7 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION can ( NAME[], TEXT ) RETURNS TEXT AS $$ DECLARE - missing name[]; + missing text[]; BEGIN SELECT ARRAY( SELECT quote_ident($1[i]) @@ -3094,6 +3094,39 @@ RETURNS TEXT AS $$ ); $$ 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, desc ) +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, desc ) +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 _is_super( NAME ) RETURNS BOOLEAN AS $$ SELECT usesuper @@ -3163,6 +3196,92 @@ 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, desc ) +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, desc ) +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 grolist FROM pg_catalog.pg_group WHERE groname = $1; +$$ LANGUAGE sql; + +-- is_member_of( group, user[], desc ) +CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + missing text[]; +BEGIN + IF NOT _has_group($1) THEN + RETURN fail( $3 ) || E'\n' || diag ( + ' Group ' || 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_user ON usename = $2[i] + WHERE usesysid IS NULL + OR usesysid <> 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( + ' Users missing from the ' || quote_ident($1) || E' group:\n ' || + array_to_string( missing, E'\n ') + ); +END; +$$ LANGUAGE plpgsql; + +-- is_member_of( group, user, desc ) +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( group, user[] ) +CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT is_member_of( $1, $2, 'Should have members of group ' || quote_ident($1) ); +$$ LANGUAGE SQL; + +-- is_member_of( group, user ) +CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT is_member_of( $1, ARRAY[$2] ); +$$ LANGUAGE SQL; + -- check_test( test_output, pass, name, description, diag ) CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) RETURNS SETOF TEXT AS $$ diff --git a/sql/coltap.sql b/sql/coltap.sql index 3dffac2f8ae2..2df94334ea4c 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -16,6 +16,19 @@ CREATE TABLE public.sometab( myat TIMESTAMP DEFAULT NOW(), plain INTEGER ); + +CREATE OR REPLACE FUNCTION fakeout( eok boolean, name text ) +RETURNS SETOF TEXT AS $$ +DECLARE + descr text := coalesce( name || ' ', 'Test ' ) || 'should '; +BEGIN + RETURN NEXT pass(descr || CASE eok WHEN true then 'pass' ELSE 'fail' END); + RETURN NEXT pass(descr || 'have the proper description'); + RETURN NEXT pass(descr || 'have the proper diagnostics'); + RETURN; +END; +$$ LANGUAGE PLPGSQL; + RESET client_min_messages; /****************************************************************************/ @@ -394,12 +407,17 @@ DECLARE BEGIN IF pg_version_num() < 80300 THEN -- Before 8.2, DEFAULT NULL was ignored. - RETURN NEXT pass('col_default_is( tab, col, NULL, desc ) should pass'); - RETURN NEXT pass('col_default_is( tab, col, NULL, desc ) should have the proper description'); - RETURN NEXT pass('col_default_is( tab, col, NULL, desc ) should have the proper diagnostics'); - RETURN NEXT pass('col_default_is( tab, col, NULL ) should pass'); - RETURN NEXT pass('col_default_is( tab, col, NULL ) should have the proper description'); - RETURN NEXT pass('col_default_is( tab, col, NULL ) should have the proper diagnostics'); + FOR tap IN SELECT * FROM fakeout( + true, 'col_default_is( tab, col, NULL, desc )' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM fakeout( + true, 'col_default_is( tab, col, NULL )' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; ELSE -- In 8.3 and later, we can handle DEFAULT NULL correctly. FOR tap IN SELECT * FROM check_test( diff --git a/sql/hastap.sql b/sql/hastap.sql index a3a3ae0ce9a3..7022b08316fc 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(333); +SELECT plan(285); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -806,169 +806,6 @@ SELECT * FROM check_test( '' ); -/****************************************************************************/ --- Test has_user() and hasnt_user(). -SELECT * FROM check_test( - has_user(current_user), - true, - 'has_user(current user)', - 'User ' || quote_ident(current_user) || ' should exist', - '' -); -SELECT * FROM check_test( - has_user(current_user, 'whatever'), - true, - 'has_user(current user, desc)', - 'whatever', - '' -); -SELECT * FROM check_test( - has_user('aoijaoisjfaoidfjaisjdfosjf'), - false, - 'has_user(nonexistent user)', - 'User aoijaoisjfaoidfjaisjdfosjf should exist', - '' -); -SELECT * FROM check_test( - has_user('aoijaoisjfaoidfjaisjdfosjf', 'desc'), - false, - 'has_user(nonexistent user, desc)', - 'desc', - '' -); - -SELECT * FROM check_test( - hasnt_user(current_user), - false, - 'hasnt_user(current user)', - 'User ' || quote_ident(current_user) || ' should not exist', - '' -); -SELECT * FROM check_test( - hasnt_user(current_user, 'whatever'), - false, - 'hasnt_user(current user, desc)', - 'whatever', - '' -); -SELECT * FROM check_test( - hasnt_user('aoijaoisjfaoidfjaisjdfosjf'), - true, - 'hasnt_user(nonexistent user)', - 'User aoijaoisjfaoidfjaisjdfosjf should not exist', - '' -); -SELECT * FROM check_test( - hasnt_user('aoijaoisjfaoidfjaisjdfosjf', 'desc'), - true, - 'hasnt_user(nonexistent user, desc)', - 'desc', - '' -); - -/****************************************************************************/ --- Test is_superuser() and isnt_superuser(). -SELECT * FROM check_test( - is_superuser('aoijaoisjfaoidfjaisjdfosjf', 'desc'), - false, - 'is_superuser(nonexistent user, desc)', - 'desc', - ' User aoijaoisjfaoidfjaisjdfosjf does not exist' -); -SELECT * FROM check_test( - is_superuser('aoijaoisjfaoidfjaisjdfosjf'), - false, - 'is_superuser(nonexistent user)', - 'User aoijaoisjfaoidfjaisjdfosjf should be a super user', - ' User aoijaoisjfaoidfjaisjdfosjf does not exist' -); - -SELECT * FROM check_test( - isnt_superuser('aoijaoisjfaoidfjaisjdfosjf', 'desc'), - false, - 'isnt_superuser(nonexistent user, desc)', - 'desc', - ' User aoijaoisjfaoidfjaisjdfosjf does not exist' -); -SELECT * FROM check_test( - isnt_superuser('aoijaoisjfaoidfjaisjdfosjf'), - false, - 'isnt_superuser(nonexistent user)', - 'User aoijaoisjfaoidfjaisjdfosjf should not be a super user', - ' User aoijaoisjfaoidfjaisjdfosjf does not exist' -); - -CREATE OR REPLACE FUNCTION supertest() RETURNS SETOF TEXT AS $$ -DECLARE - tap record; -BEGIN - IF _is_super( current_user ) THEN - -- We can test is_superuser(). - FOR tap IN SELECT * FROM check_test( - is_superuser(current_user), - true, - 'is_superuser( current user )', - 'User ' || quote_ident(current_user) || ' should be a super user', - '' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - - FOR tap IN SELECT * FROM check_test( - is_superuser(current_user, 'whatever'), - true, - 'is_superuser( current user, desc )', - 'whatever', - '' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - - -- But we can't test isnt_superuser(). - RETURN NEXT pass('isnt_superuser( current user ) should pass'); - RETURN NEXT pass('isnt_superuser( current user ) should have the proper description'); - RETURN NEXT pass('isnt_superuser( current user ) should have the proper diagnostics'); - - RETURN NEXT pass('isnt_superuser( current user, desc ) should pass'); - RETURN NEXT pass('isnt_superuser( current user, desc ) should have the proper description'); - RETURN NEXT pass('isnt_superuser( current user, desc ) should have the proper diagnostics'); - - ELSE - -- We can't test is_superuser(). - RETURN NEXT pass('is_superuser( current user ) should pass'); - RETURN NEXT pass('is_superuser( current user ) should have the proper description'); - RETURN NEXT pass('is_superuser( current user ) should have the proper diagnostics'); - - RETURN NEXT pass('is_superuser( current user, desc ) should pass'); - RETURN NEXT pass('is_superuser( current user, desc ) should have the proper description'); - RETURN NEXT pass('is_superuser( current user, desc ) should have the proper diagnostics'); - - -- But we can test isnt_superuser(). - FOR tap IN SELECT * FROM check_test( - isnt_superuser(current_user), - true, - 'isnt_superuser( current user )', - 'User ' || quote_ident(current_user) || ' should not be a super user', - '' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - - FOR tap IN SELECT * FROM check_test( - isnt_superuser(current_user, 'whatever'), - true, - 'isnt_superuser( current user, desc )', - 'whatever', - '' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - END IF; - RETURN; -END; -$$ LANGUAGE PLPGSQL; -SELECT * FROM supertest(); - /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/sql/roletap.sql b/sql/roletap.sql new file mode 100644 index 000000000000..2b08cf04db84 --- /dev/null +++ b/sql/roletap.sql @@ -0,0 +1,73 @@ +\unset ECHO +\i test_setup.sql + +-- $Id$ + +SELECT plan(24); +--SELECT * FROM no_plan(); + +/****************************************************************************/ +-- Test has_role() and hasnt_role(). + +SELECT * FROM check_test( + has_role(current_role), + true, + 'has_role(current role)', + 'Role ' || quote_ident(current_role) || ' should exist', + '' +); +SELECT * FROM check_test( + has_role(current_role, 'whatever'), + true, + 'has_role(current role, desc)', + 'whatever', + '' +); +SELECT * FROM check_test( + has_role('aoijaoisjfaoidfjaisjdfosjf'), + false, + 'has_role(nonexistent role)', + 'Role aoijaoisjfaoidfjaisjdfosjf should exist', + '' +); +SELECT * FROM check_test( + has_role('aoijaoisjfaoidfjaisjdfosjf', 'desc'), + false, + 'has_role(nonexistent role, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_role(current_role), + false, + 'hasnt_role(current role)', + 'Role ' || quote_ident(current_role) || ' should not exist', + '' +); +SELECT * FROM check_test( + hasnt_role(current_role, 'whatever'), + false, + 'hasnt_role(current role, desc)', + 'whatever', + '' +); +SELECT * FROM check_test( + hasnt_role('aoijaoisjfaoidfjaisjdfosjf'), + true, + 'hasnt_role(nonexistent role)', + 'Role aoijaoisjfaoidfjaisjdfosjf should not exist', + '' +); +SELECT * FROM check_test( + hasnt_role('aoijaoisjfaoidfjaisjdfosjf', 'desc'), + true, + 'hasnt_role(nonexistent role, desc)', + 'desc', + '' +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; diff --git a/sql/usergroup.sql b/sql/usergroup.sql new file mode 100644 index 000000000000..6ad17489c946 --- /dev/null +++ b/sql/usergroup.sql @@ -0,0 +1,231 @@ +\unset ECHO +\i test_setup.sql + +-- $Id$ + +SELECT plan(78); +--SELECT * FROM no_plan(); + +/****************************************************************************/ +-- Test has_user() and hasnt_user(). + +SELECT * FROM check_test( + has_user(current_user), + true, + 'has_user(current user)', + 'User ' || quote_ident(current_user) || ' should exist', + '' +); +SELECT * FROM check_test( + has_user(current_user, 'whatever'), + true, + 'has_user(current user, desc)', + 'whatever', + '' +); +SELECT * FROM check_test( + has_user('aoijaoisjfaoidfjaisjdfosjf'), + false, + 'has_user(nonexistent user)', + 'User aoijaoisjfaoidfjaisjdfosjf should exist', + '' +); +SELECT * FROM check_test( + has_user('aoijaoisjfaoidfjaisjdfosjf', 'desc'), + false, + 'has_user(nonexistent user, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_user(current_user), + false, + 'hasnt_user(current user)', + 'User ' || quote_ident(current_user) || ' should not exist', + '' +); +SELECT * FROM check_test( + hasnt_user(current_user, 'whatever'), + false, + 'hasnt_user(current user, desc)', + 'whatever', + '' +); +SELECT * FROM check_test( + hasnt_user('aoijaoisjfaoidfjaisjdfosjf'), + true, + 'hasnt_user(nonexistent user)', + 'User aoijaoisjfaoidfjaisjdfosjf should not exist', + '' +); +SELECT * FROM check_test( + hasnt_user('aoijaoisjfaoidfjaisjdfosjf', 'desc'), + true, + 'hasnt_user(nonexistent user, desc)', + 'desc', + '' +); + +/****************************************************************************/ +-- Test is_superuser() and isnt_superuser(). +SELECT * FROM check_test( + is_superuser('aoijaoisjfaoidfjaisjdfosjf', 'desc'), + false, + 'is_superuser(nonexistent user, desc)', + 'desc', + ' User aoijaoisjfaoidfjaisjdfosjf does not exist' +); +SELECT * FROM check_test( + is_superuser('aoijaoisjfaoidfjaisjdfosjf'), + false, + 'is_superuser(nonexistent user)', + 'User aoijaoisjfaoidfjaisjdfosjf should be a super user', + ' User aoijaoisjfaoidfjaisjdfosjf does not exist' +); + +SELECT * FROM check_test( + isnt_superuser('aoijaoisjfaoidfjaisjdfosjf', 'desc'), + false, + 'isnt_superuser(nonexistent user, desc)', + 'desc', + ' User aoijaoisjfaoidfjaisjdfosjf does not exist' +); +SELECT * FROM check_test( + isnt_superuser('aoijaoisjfaoidfjaisjdfosjf'), + false, + 'isnt_superuser(nonexistent user)', + 'User aoijaoisjfaoidfjaisjdfosjf should not be a super user', + ' User aoijaoisjfaoidfjaisjdfosjf does not exist' +); + +SELECT * FROM check_test( + is_superuser(current_user), + true, + 'is_superuser( current user )', + 'User ' || quote_ident(current_user) || ' should be a super user', + '' +); + +SELECT * FROM check_test( + is_superuser(current_user, 'whatever'), + true, + 'is_superuser( current user, desc )', + 'whatever', + '' +); + +/****************************************************************************/ +-- Test has_group() and hasnt_group(). +CREATE GROUP meanies; + +SELECT * FROM check_test( + has_group('meanies'), + true, + 'has_group(group)', + 'Group ' || quote_ident('meanies') || ' should exist', + '' +); + +SELECT * FROM check_test( + has_group('meanies', 'whatever'), + true, + 'has_group(group, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_group('aoijaoisjfaoidfjaisjdfosjf'), + false, + 'has_group(nonexistent group)', + 'Group aoijaoisjfaoidfjaisjdfosjf should exist', + '' +); + +SELECT * FROM check_test( + has_group('aoijaoisjfaoidfjaisjdfosjf', 'desc'), + false, + 'has_group(nonexistent group, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_group('meanies'), + false, + 'hasnt_group(group)', + 'Group ' || quote_ident('meanies') || ' should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_group('meanies', 'whatever'), + false, + 'hasnt_group(group, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_group('aoijaoisjfaoidfjaisjdfosjf'), + true, + 'hasnt_group(nonexistent group)', + 'Group aoijaoisjfaoidfjaisjdfosjf should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_group('aoijaoisjfaoidfjaisjdfosjf', 'desc'), + true, + 'hasnt_group(nonexistent group, desc)', + 'desc', + '' +); + +/****************************************************************************/ +-- Test is_member_of(). +CREATE OR REPLACE FUNCTION addmember() RETURNS SETOF TEXT AS $$ +BEGIN + EXECUTE 'ALTER GROUP meanies ADD USER ' || current_user; + RETURN; +END; +$$ LANGUAGE PLPGSQL; + +SELECT * FROM addmember(); +SELECT * FROM check_test( + is_member_of('meanies', ARRAY[current_user], 'whatever' ), + true, + 'is_member_of(meanies, [current_user], desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_member_of('meanies', ARRAY[current_user] ), + true, + 'is_member_of(meanies, [current_user])', + 'Should have members of group meanies', + '' +); + +SELECT * FROM check_test( + is_member_of('meanies', current_user, 'whatever' ), + true, + 'is_member_of(meanies, current_user, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_member_of('meanies', current_user ), + true, + 'is_member_of(meanies, current_user)', + 'Should have members of group meanies', + '' +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 437cd6637264..0e7b0f11da7f 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -19,6 +19,16 @@ DROP FUNCTION check_test( TEXT, BOOLEAN ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); +DROP FUNCTION is_member_of( NAME, NAME ); +DROP FUNCTION is_member_of( NAME, NAME[] ); +DROP FUNCTION is_member_of( NAME, NAME, TEXT ); +DROP FUNCTION is_member_of( NAME, NAME[], TEXT ); +DROP FUNCTION _grolist ( NAME ); +DROP FUNCTION hasnt_group( NAME ); +DROP FUNCTION hasnt_group( NAME, TEXT ); +DROP FUNCTION has_group( NAME ); +DROP FUNCTION has_group( NAME, TEXT ); +DROP FUNCTION _has_group( NAME ); DROP FUNCTION isnt_superuser( NAME ); DROP FUNCTION isnt_superuser( NAME, TEXT ); DROP FUNCTION is_superuser( NAME ); @@ -28,6 +38,11 @@ DROP FUNCTION hasnt_user( NAME, TEXT ); DROP FUNCTION has_user( NAME ); DROP FUNCTION has_user( NAME, TEXT ); DROP FUNCTION _is_super( NAME ); +DROP FUNCTION hasnt_role( NAME ); +DROP FUNCTION hasnt_role( NAME, TEXT ); +DROP FUNCTION has_role( NAME ); +DROP FUNCTION has_role( NAME, TEXT ); +DROP FUNCTION _has_role( NAME ); DROP FUNCTION enum_has_labels( NAME, NAME[] ); DROP FUNCTION enum_has_labels( NAME, NAME[], TEXT ); DROP FUNCTION enum_has_labels( NAME, NAME, NAME[] ); @@ -182,7 +197,6 @@ DROP VIEW pg_all_foreign_keys; DROP FUNCTION _pg_sv_table_accessible( OID, OID ); DROP FUNCTION _pg_sv_column_array( OID, SMALLINT[] ); DROP FUNCTION _ident_array_to_string( name[], text ); -DROP FUNCTION _quote_ident_array( name[] ); DROP FUNCTION _ckeys ( NAME, CHAR ); DROP FUNCTION _ckeys ( NAME, NAME, CHAR ); DROP FUNCTION hasnt_pk ( NAME ); From b31e55bf3af2ee63c5a4179c5b851f6057497b08 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Feb 2009 23:23:36 +0000 Subject: [PATCH 0310/1195] Try to get a little more information about failures out of TAP::Harness when not running in verbose mode. --- Changes | 4 ++++ bin/pg_prove | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 3967950fa480..a2fbe9d002e1 100644 --- a/Changes +++ b/Changes @@ -26,6 +26,10 @@ Revision history for pgTAP etc, added in 0.17. This is because `quote_ident(a) = quote_ident(b)` can't give a different result than `a = b`, and besides, made things slower and prevented the use of indexes. Thanks to Andrew Gierth for the spot. +* The output from `pg_prove` now includes a list of failing tests when it is + not run with `--verbose`. When using TAP::Harness 3.17 and later, it also + shows comments and diagnostics in the non-verbose output. Verbose output + still outputs everything. 0.18 2009-02-06T20:06:00 ------------------------- diff --git a/bin/pg_prove b/bin/pg_prove index 0cc5e6a8fef5..977955b70d97 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -106,9 +106,13 @@ if ($opts->{runtests}) { push @command, '--file'; } +my $verbosity = $opts->{verbose} || $ENV{TEST_VERBOSE}; + # Make it so! TAP::Harness->new({ - verbosity => $opts->{verbose} || $ENV{TEST_VERBOSE}, + verbosity => $verbosity, + failures => !$verbosity, + ( TAP::Harness->VERSION ge '3.17' ? (comments => !$verbosity) : ()), timer => $opts->{timer}, color => $opts->{color}, exec => \@command, From 23dc60327adb327316ced34403c7e9f230921812 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 21 Feb 2009 00:46:12 +0000 Subject: [PATCH 0311/1195] Updated for 8.2 compatibility. --- compat/install-8.2.patch | 12 ++--- compat/uninstall-8.2.patch | 12 ++--- sql/coltap.sql | 100 ++++++++++++++++++++++++++----------- 3 files changed, 83 insertions(+), 41 deletions(-) diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 5234bbd52f31..f92a978358e0 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,6 +1,6 @@ ---- pgtap.sql.saf 2009-02-06 11:31:08.000000000 -0800 -+++ pgtap.sql 2009-02-06 11:31:14.000000000 -0800 -@@ -3004,63 +3004,6 @@ +--- pgtap.sql.orig 2009-02-20 16:18:40.000000000 -0800 ++++ pgtap.sql 2009-02-20 16:20:17.000000000 -0800 +@@ -3037,63 +3037,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -61,6 +61,6 @@ - ); -$$ LANGUAGE sql; - - -- check_test( test_output, pass, name, description, diag ) - CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) - RETURNS SETOF TEXT AS $$ + CREATE OR REPLACE FUNCTION _has_role( NAME ) + RETURNS BOOLEAN AS $$ + SELECT EXISTS( diff --git a/compat/uninstall-8.2.patch b/compat/uninstall-8.2.patch index 34e1e3cc8c2b..6c52c9d5747e 100644 --- a/compat/uninstall-8.2.patch +++ b/compat/uninstall-8.2.patch @@ -1,9 +1,9 @@ ---- uninstall_pgtap.sql (revision 4476) -+++ uninstall_pgtap.sql (working copy) -@@ -19,10 +19,6 @@ - DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); - DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); - DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); +--- uninstall_pgtap.sql.orig 2009-02-20 16:24:08.000000000 -0800 ++++ uninstall_pgtap.sql 2009-02-20 16:24:40.000000000 -0800 +@@ -43,10 +43,6 @@ + DROP FUNCTION has_role( NAME ); + DROP FUNCTION has_role( NAME, TEXT ); + DROP FUNCTION _has_role( NAME ); -DROP FUNCTION enum_has_labels( NAME, NAME[] ); -DROP FUNCTION enum_has_labels( NAME, NAME[], TEXT ); -DROP FUNCTION enum_has_labels( NAME, NAME, NAME[] ); diff --git a/sql/coltap.sql b/sql/coltap.sql index 2df94334ea4c..3044dc77dfa5 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -391,22 +391,24 @@ SELECT * FROM check_test( '' ); --- Make sure it works with a non-text column. -SELECT * FROM check_test( - col_default_is( 'sometab', 'myint', 24 ), - true, - 'col_default_is( tab, col, int )', - 'Column sometab.myint should default to ''24''', - '' -); - -- Make sure it works with a NULL default. -CREATE OR REPLACE FUNCTION nulltest () RETURNS SETOF TEXT AS $$ +CREATE OR REPLACE FUNCTION versiontests () RETURNS SETOF TEXT AS $$ DECLARE tap record; BEGIN IF pg_version_num() < 80300 THEN - -- Before 8.2, DEFAULT NULL was ignored. + -- Before 8.3, have to cast to text. + FOR tap IN SELECT * FROM check_test( + col_default_is( 'sometab', 'myint', 24::text ), + true, + 'col_default_is( tab, col, int )', + 'Column sometab.myint should default to ''24''', + '' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + -- Before 8.3, DEFAULT NULL was ignored. FOR tap IN SELECT * FROM fakeout( true, 'col_default_is( tab, col, NULL, desc )' ) AS a(b) LOOP @@ -418,7 +420,41 @@ BEGIN ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; + + -- Make sure that it fails when there is no default. + -- Before 8.3, must cast values to text + FOR tap IN SELECT * FROM check_test( + col_default_is( 'sometab', 'plain', 1::text, 'desc' ), + false, + 'col_default_is( tab, col, bogus, desc )', + 'desc', + ' Column sometab.plain has no default' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + col_default_is( 'sometab', 'plain', 1::text ), + false, + 'col_default_is( tab, col, bogus )', + 'Column sometab.plain should default to ''1''', + ' Column sometab.plain has no default' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + ELSE + -- In 8.3 and later, can just use the raw value. + FOR tap IN SELECT * FROM check_test( + col_default_is( 'sometab', 'myint', 24 ), + true, + 'col_default_is( tab, col, int )', + 'Column sometab.myint should default to ''24''', + '' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + -- In 8.3 and later, we can handle DEFAULT NULL correctly. FOR tap IN SELECT * FROM check_test( col_default_is( 'sometab', 'numb', NULL::numeric, 'desc' ), @@ -439,28 +475,34 @@ BEGIN ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; + + -- Make sure that it fails when there is no default. + -- In 8.3 and later, can just use raw values. + FOR tap IN SELECT * FROM check_test( + col_default_is( 'sometab', 'plain', 1, 'desc' ), + false, + 'col_default_is( tab, col, bogus, desc )', + 'desc', + ' Column sometab.plain has no default' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + col_default_is( 'sometab', 'plain', 1 ), + false, + 'col_default_is( tab, col, bogus )', + 'Column sometab.plain should default to ''1''', + ' Column sometab.plain has no default' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + END IF; RETURN; END; $$ LANGUAGE plpgsql; -SELECT * FROM nulltest(); - --- Make sure that it fails when there is no default. -SELECT * FROM check_test( - col_default_is( 'sometab', 'plain', 1::integer, 'desc' ), - false, - 'col_default_is( tab, col, bogus, desc )', - 'desc', - ' Column sometab.plain has no default' -); - -SELECT * FROM check_test( - col_default_is( 'sometab', 'plain', 1::integer ), - false, - 'col_default_is( tab, col, bogus )', - 'Column sometab.plain should default to ''1''', - ' Column sometab.plain has no default' -); +SELECT * FROM versiontests(); -- Make sure that it works when the default is an expression. SELECT * FROM check_test( From 1e441009129b9bfba056c8845e4454f3c880bd47 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 21 Feb 2009 01:05:39 +0000 Subject: [PATCH 0312/1195] Updated for 8.1. --- Makefile | 4 +++- compat/install-8.1.patch | 16 ++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 188829351cd2..fef99e9cee72 100644 --- a/Makefile +++ b/Makefile @@ -147,7 +147,8 @@ ifneq ($(PGVER_MINOR), 3) cat compat/install-8.2.sql >> pgtap.sql ifeq ($(PGVER_MINOR), 2) patch -p0 < compat/install-8.2.patch -else ifeq ($(PGVER_MINOR), 1) +else +ifeq ($(PGVER_MINOR), 1) patch -p0 < compat/install-8.2.patch patch -p0 < compat/install-8.1.patch else @@ -157,6 +158,7 @@ endif endif endif endif +endif uninstall_pgtap.sql: uninstall_pgtap.sql.in test_setup.sql ifdef TAPSCHEMA diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index 6e23b8681778..9a197494dc19 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ ---- pgtap.sql.saf 2009-02-06 11:43:37.000000000 -0800 -+++ pgtap.sql 2009-02-06 11:43:43.000000000 -0800 -@@ -3134,7 +3134,7 @@ +--- pgtap.sql.orig 2009-02-20 17:08:42.000000000 -0800 ++++ pgtap.sql 2009-02-20 17:08:42.000000000 -0800 +@@ -3355,7 +3355,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -9,7 +9,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -3142,8 +3142,8 @@ +@@ -3363,8 +3363,8 @@ -- 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. @@ -20,7 +20,7 @@ END LOOP; END LOOP; RETURN; -@@ -3211,14 +3211,14 @@ +@@ -3432,14 +3432,14 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -37,7 +37,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -3233,15 +3233,15 @@ +@@ -3454,15 +3454,15 @@ IF verbose THEN RETURN NEXT diag(tests[i] || '()'); END IF; -- Run the setup functions. @@ -57,7 +57,7 @@ -- Remember how many failed and then roll back. num_faild := num_faild + num_failed(); -@@ -3256,7 +3256,7 @@ +@@ -3477,7 +3477,7 @@ END LOOP; -- Run the shutdown functions. @@ -66,7 +66,7 @@ -- Raise an exception to rollback any changes. RAISE EXCEPTION '__TAP_ROLLBACK__'; -@@ -3267,8 +3267,8 @@ +@@ -3488,8 +3488,8 @@ END IF; END; -- Finish up. From 2e6cad62fb0a6921b5e0d296041b77d9c158e47a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 21 Feb 2009 01:36:20 +0000 Subject: [PATCH 0313/1195] Patched for 8.0 compatibility. --- compat/install-8.0.patch | 63 +++++++++++++++++++++++++++++++++----- compat/uninstall-8.0.patch | 12 ++++---- pgtap.sql.in | 2 +- 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index c7b17ca3705e..385d7b045c38 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.saf 2009-02-06 11:49:08.000000000 -0800 -+++ pgtap.sql 2009-02-06 11:49:16.000000000 -0800 +--- pgtap.sql.orig 2009-02-20 17:35:51.000000000 -0800 ++++ pgtap.sql 2009-02-20 17:36:21.000000000 -0800 @@ -12,6 +12,22 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA, public; @@ -169,7 +169,56 @@ END; $$ LANGUAGE plpgsql; -@@ -3014,6 +3047,7 @@ +@@ -3037,39 +3070,6 @@ + SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); + $$ 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, desc ) +-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, desc ) +-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 _is_super( NAME ) + RETURNS BOOLEAN AS $$ + SELECT usesuper +@@ -3173,7 +3173,7 @@ + $$ LANGUAGE sql; + + CREATE OR REPLACE FUNCTION _grolist ( NAME ) +-RETURNS oid[] AS $$ ++RETURNS integer[] AS $$ + SELECT grolist FROM pg_catalog.pg_group WHERE groname = $1; + $$ LANGUAGE sql; + +@@ -3235,6 +3235,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -177,7 +226,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -3024,8 +3058,10 @@ +@@ -3245,8 +3246,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. @@ -190,7 +239,7 @@ -- Now delete those results. EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; -@@ -3134,7 +3170,7 @@ +@@ -3355,7 +3358,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -199,7 +248,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -3142,8 +3178,8 @@ +@@ -3363,8 +3366,8 @@ -- 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. @@ -210,7 +259,7 @@ END LOOP; END LOOP; RETURN; -@@ -3203,116 +3239,6 @@ +@@ -3424,116 +3427,6 @@ END $$ LANGUAGE plpgsql; diff --git a/compat/uninstall-8.0.patch b/compat/uninstall-8.0.patch index 35dc27815b35..53d70144b910 100644 --- a/compat/uninstall-8.0.patch +++ b/compat/uninstall-8.0.patch @@ -1,6 +1,6 @@ ---- uninstall_pgtap.sql.orig 2009-02-19 20:04:48.000000000 -0800 -+++ uninstall_pgtap.sql.in 2009-02-19 20:04:50.000000000 -0800 -@@ -38,11 +38,6 @@ +--- uninstall_pgtap.sql.orig 2009-02-20 17:29:32.000000000 -0800 ++++ uninstall_pgtap.sql 2009-02-20 17:30:34.000000000 -0800 +@@ -50,11 +50,6 @@ DROP FUNCTION has_user( NAME ); DROP FUNCTION has_user( NAME, TEXT ); DROP FUNCTION _is_super( NAME ); @@ -9,6 +9,6 @@ -DROP FUNCTION has_role( NAME ); -DROP FUNCTION has_role( NAME, TEXT ); -DROP FUNCTION _has_role( NAME ); - DROP FUNCTION enum_has_labels( NAME, NAME[] ); - DROP FUNCTION enum_has_labels( NAME, NAME[], TEXT ); - DROP FUNCTION enum_has_labels( NAME, NAME, NAME[] ); + DROP FUNCTION hasnt_enum( NAME ); + DROP FUNCTION hasnt_enum( NAME, TEXT ); + DROP FUNCTION hasnt_enum( NAME, NAME ); diff --git a/pgtap.sql.in b/pgtap.sql.in index 66ef8988c934..c6d841168479 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2092,7 +2092,7 @@ BEGIN WHERE nspname = $1 ) WHERE p.oid IS NULL - GROUP BY $2[i] + GROUP BY $2[i], s.i ORDER BY MIN(s.i) ) INTO missing; IF missing[1] IS NULL THEN From 68f6613ffc7702607a48ecf7013a8736dd5cb722 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 21 Feb 2009 02:01:27 +0000 Subject: [PATCH 0314/1195] Timestamped for 0.19 release. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index a2fbe9d002e1..8b66fdee0975 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.19 +0.19 2009-02-21T02:09:26 ------------------------- * Added a alernate versions of `col_default_is()` to better handle the common From eac8d6012cfbdfb7c58ba115ed79564691ab7ad5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 21 Feb 2009 04:38:37 +0000 Subject: [PATCH 0315/1195] Incremented version number to 0.20. --- Changes | 3 +++ Makefile | 2 +- README.pgtap | 4 ++-- bin/pg_prove | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 8b66fdee0975..388a51af7d8f 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Revision history for pgTAP ========================== +0.20 +------------------------- + 0.19 2009-02-21T02:09:26 ------------------------- diff --git a/Makefile b/Makefile index fef99e9cee72..1a4425fdf75b 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') PGVER_MAJOR = $(shell echo $(VERSION) | awk -F. '{ print ($$1 + 0) }') PGVER_MINOR = $(shell echo $(VERSION) | awk -F. '{ print ($$2 + 0) }') PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') -PGTAP_VERSION = 0.19 +PGTAP_VERSION = 0.20 # Compile the C code only if we're on 8.3 or older. ifneq ($(PGVER_MINOR), 4) diff --git a/README.pgtap b/README.pgtap index 5f912db0b3f8..4bbf082a1cb4 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.19 +pgTAP 0.20 ========== pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and @@ -1706,7 +1706,7 @@ so. ### `has_group( group, desc )` ### ### `has_group( group )` ### - SELECT has_group( 'sweeties, 'Group "sweeties should exist' ); + SELECT has_group( 'sweeties, 'Group "sweeties" should exist' ); Checks to ensure that a database group exists. If the description is omitted, it will default to "Group `:group` should exist". diff --git a/bin/pg_prove b/bin/pg_prove index 977955b70d97..f194d2034549 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -6,7 +6,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.19'; +our $VERSION = '0.20'; Getopt::Long::Configure (qw(bundling)); @@ -181,7 +181,7 @@ values returned by pgTAP functions, like so: CREATE OR REPLACE FUNCTION test_user( ) RETURNS SETOF TEXT AS $$ - SELECT is( nick, 'theory', 'Should have nick') FROM users; + SELECT is( nick, 'theory', 'Should have nick') FROM users; END; $$ LANGUAGE sql; From e419821bcf74e192041fb710b451abe7228ae813 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 21 Feb 2009 06:16:46 +0000 Subject: [PATCH 0316/1195] Use my patched version of Pod::Simple to generate the `pg_prove` HTML. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1a4425fdf75b..8e1ca3ef0402 100644 --- a/Makefile +++ b/Makefile @@ -217,4 +217,4 @@ test: test_setup.sql html: markdown -F 0x1000 README.pgtap > readme.html perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$4; $$lab = $$4; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$4 ? "$$4()" : $$3) . "" } END { print "
  • \n
\n" }' readme.html > toc.html - perldoc -MPod::Simple::XHTML bin/pg_prove > pg_prove.html + PERL5LIB=/Users/david/dev/perl/Pod-Simple/lib perldoc -MPod::Simple::XHTML bin/pg_prove > pg_prove.html From 7cc72dd65af1d0a2231f28c9602999113c097ca2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 24 Feb 2009 18:57:18 +0000 Subject: [PATCH 0317/1195] Changed the names of the functions tested in `sql/do_tap.sql` so that they are less likely to be ordered differently given a different locale. Reported by Ingmar Brouns. --- Changes | 3 +++ expected/do_tap.out | 40 ++++++++++++++++++++-------------------- sql/do_tap.sql | 6 +++--- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/Changes b/Changes index 388a51af7d8f..3f75e9945ce4 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,9 @@ Revision history for pgTAP 0.20 ------------------------- +* Changed the names of the functions tested in `sql/do_tap.sql` so that they + are less likely to be ordered differently given a different locale. Reported + by Ingmar Brouns. 0.19 2009-02-21T02:09:26 ------------------------- diff --git a/expected/do_tap.out b/expected/do_tap.out index aad638d4dace..de8b1b966503 100644 --- a/expected/do_tap.out +++ b/expected/do_tap.out @@ -5,36 +5,36 @@ ok 2 - findfuncs(^test) should work # public."test ident"() ok 3 - ident ok 4 - ident 2 -# public.test_this() -ok 5 - simple pass -ok 6 - another simple pass # public.testplpgsql() -ok 7 - plpgsql simple -ok 8 - plpgsql simple 2 +ok 5 - plpgsql simple +ok 6 - plpgsql simple 2 +# public.testthis() +ok 7 - simple pass +ok 8 - another simple pass # public."test ident"() ok 9 - ident ok 10 - ident 2 -# public.test_this() -ok 11 - simple pass -ok 12 - another simple pass # public.testplpgsql() -ok 13 - plpgsql simple -ok 14 - plpgsql simple 2 +ok 11 - plpgsql simple +ok 12 - plpgsql simple 2 +# public.testthis() +ok 13 - simple pass +ok 14 - another simple pass # public."test ident"() ok 15 - ident ok 16 - ident 2 -# public.test_this() -ok 17 - simple pass -ok 18 - another simple pass # public.testplpgsql() -ok 19 - plpgsql simple -ok 20 - plpgsql simple 2 +ok 17 - plpgsql simple +ok 18 - plpgsql simple 2 +# public.testthis() +ok 19 - simple pass +ok 20 - another simple pass # public."test ident"() ok 21 - ident ok 22 - ident 2 -# public.test_this() -ok 23 - simple pass -ok 24 - another simple pass # public.testplpgsql() -ok 25 - plpgsql simple -ok 26 - plpgsql simple 2 +ok 23 - plpgsql simple +ok 24 - plpgsql simple 2 +# public.testthis() +ok 25 - simple pass +ok 26 - another simple pass diff --git a/sql/do_tap.sql b/sql/do_tap.sql index 64bf5c8994cd..62728d4cd69a 100644 --- a/sql/do_tap.sql +++ b/sql/do_tap.sql @@ -7,7 +7,7 @@ SET client_min_messages = notice; SELECT plan(26); --SELECT * FROM no_plan(); -CREATE OR REPLACE FUNCTION public.test_this() RETURNS SETOF TEXT AS $$ +CREATE OR REPLACE FUNCTION public.testthis() RETURNS SETOF TEXT AS $$ SELECT pass('simple pass') AS foo UNION SELECT pass('another simple pass') ORDER BY foo ASC; @@ -31,13 +31,13 @@ $$ LANGUAGE plpgsql; SELECT is( findfuncs('public', '^test'), - ARRAY[ 'public."test ident"', 'public.test_this', 'public.testplpgsql' ], + ARRAY[ 'public."test ident"', 'public.testplpgsql', 'public.testthis' ], 'findfuncs(public, ^test) should work' ); SELECT is( findfuncs('^test'), - ARRAY[ 'public."test ident"', 'public.test_this', 'public.testplpgsql' ], + ARRAY[ 'public."test ident"', 'public.testplpgsql', 'public.testthis' ], 'findfuncs(^test) should work' ); From c52fc312376d0b20fdfc6c74bc4b209272fa9c76 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 25 Feb 2009 17:13:57 +0000 Subject: [PATCH 0318/1195] More collation fixes. --- expected/runtests.out | 24 ++++++++++++------------ sql/runtests.sql | 10 +++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/expected/runtests.out b/expected/runtests.out index 4df415942a45..a1f49ae31c79 100644 --- a/expected/runtests.out +++ b/expected/runtests.out @@ -9,21 +9,21 @@ ok 6 - ident ok 7 - ident 2 ok 8 - teardown ok 9 - teardown more -# whatever.test_this() +# whatever.testplpgsql() ok 10 - setup ok 11 - Should be nothing in the test table ok 12 - setup more -ok 13 - simple pass -ok 14 - another simple pass -ok 15 - teardown -ok 16 - teardown more -# whatever.testplpgsql() -ok 17 - setup -ok 18 - Should be nothing in the test table -ok 19 - setup more -ok 20 - plpgsql simple -ok 21 - plpgsql simple 2 -ok 22 - Should be a 1 in the test table +ok 13 - plpgsql simple +ok 14 - plpgsql simple 2 +ok 15 - Should be a 1 in the test table +ok 16 - teardown +ok 17 - teardown more +# whatever.testthis() +ok 18 - setup +ok 19 - Should be nothing in the test table +ok 20 - setup more +ok 21 - simple pass +ok 22 - another simple pass ok 23 - teardown ok 24 - teardown more # whatever.testz() diff --git a/sql/runtests.sql b/sql/runtests.sql index 1ccc456a81e2..aaf689c91ee5 100644 --- a/sql/runtests.sql +++ b/sql/runtests.sql @@ -14,7 +14,7 @@ CREATE OR REPLACE FUNCTION whatever.startup() RETURNS SETOF TEXT AS $$ SELECT pass('starting up'); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION whatever.startup_more() RETURNS SETOF TEXT AS $$ +CREATE OR REPLACE FUNCTION whatever.startupmore() RETURNS SETOF TEXT AS $$ SELECT pass('starting up some more'); $$ LANGUAGE SQL; @@ -24,7 +24,7 @@ CREATE OR REPLACE FUNCTION whatever.setup() RETURNS SETOF TEXT AS $$ SELECT is( MAX(id), NULL, 'Should be nothing in the test table') FROM whatever.foo; $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION whatever.setup_more() RETURNS SETOF TEXT AS $$ +CREATE OR REPLACE FUNCTION whatever.setupmore() RETURNS SETOF TEXT AS $$ SELECT pass('setup more'); $$ LANGUAGE SQL; @@ -32,7 +32,7 @@ CREATE OR REPLACE FUNCTION whatever.teardown() RETURNS SETOF TEXT AS $$ SELECT pass('teardown'); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION whatever.teardown_more() RETURNS SETOF TEXT AS $$ +CREATE OR REPLACE FUNCTION whatever.teardownmore() RETURNS SETOF TEXT AS $$ SELECT pass('teardown more'); $$ LANGUAGE SQL; @@ -40,11 +40,11 @@ CREATE OR REPLACE FUNCTION whatever.shutdown() RETURNS SETOF TEXT AS $$ SELECT pass('shutting down'); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION whatever.shutdown_more() RETURNS SETOF TEXT AS $$ +CREATE OR REPLACE FUNCTION whatever.shutdownmore() RETURNS SETOF TEXT AS $$ SELECT pass('shutting down more'); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION whatever.test_this() RETURNS SETOF TEXT AS $$ +CREATE OR REPLACE FUNCTION whatever.testthis() RETURNS SETOF TEXT AS $$ SELECT pass('simple pass') AS foo UNION SELECT pass('another simple pass') ORDER BY foo ASC; From 80d3734fc448bac8cbe28fe9a8b2c74171b603e0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 25 Feb 2009 17:15:07 +0000 Subject: [PATCH 0319/1195] Note stupide locale fixes. --- Changes | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 3f75e9945ce4..4ef2c50624b5 100644 --- a/Changes +++ b/Changes @@ -3,9 +3,10 @@ Revision history for pgTAP 0.20 ------------------------- -* Changed the names of the functions tested in `sql/do_tap.sql` so that they - are less likely to be ordered differently given a different locale. Reported - by Ingmar Brouns. +* Changed the names of the functions tested in `sql/do_tap.sql` and + `sql/runtests.sql` so that they are less likely to be ordered differently + given varying collation orders provided in different locales and by + different vendors. Reported by Ingmar Brouns. 0.19 2009-02-21T02:09:26 ------------------------- From 9622811856753127aaa55b5926c0680f31fc6489 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 3 Mar 2009 18:53:27 +0000 Subject: [PATCH 0320/1195] RedHat! --- README.pgtap | 1 + 1 file changed, 1 insertion(+) diff --git a/README.pgtap b/README.pgtap index 4bbf082a1cb4..c1287e7a3bb7 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2382,6 +2382,7 @@ pgTAP has been tested on the following builds of PostgreSQL: * PostgreSQL 8.2.11 on i386-apple-darwin9.5.0 * PostgreSQL 8.1.15 on i686-apple-darwin9.6.0 * PostgreSQL 8.0.19 on i686-apple-darwin9.6.0 +* PostgreSQL 8.3.6 on i386-redhat-linux-gnu If you know of others, please submit them! Use `psql -d template1 -c 'SELECT VERSION()'` to get the formal build version and OS. From a80855f96918c56078b007c98f5e7c7821d4638a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 19 Mar 2009 16:30:16 +0000 Subject: [PATCH 0321/1195] * Added the `--formatter` and `--archive` options to `pg_prove`. * Fixed the typos in `pg_prove` where the output of `--help` listed `--test-match` and `--test-schema` instead of `--match` and `--schema`. --- Changes | 3 +++ bin/pg_prove | 74 ++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/Changes b/Changes index 4ef2c50624b5..574abbf3738a 100644 --- a/Changes +++ b/Changes @@ -7,6 +7,9 @@ Revision history for pgTAP `sql/runtests.sql` so that they are less likely to be ordered differently given varying collation orders provided in different locales and by different vendors. Reported by Ingmar Brouns. +* Added the `--formatter` and `--archive` options to `pg_prove`. +* Fixed the typos in `pg_prove` where the output of `--help` listed + `--test-match` and `--test-schema` instead of `--match` and `--schema`. 0.19 2009-02-21T02:09:26 ------------------------- diff --git a/bin/pg_prove b/bin/pg_prove index f194d2034549..4e6f6b879d6e 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -24,6 +24,8 @@ Getopt::Long::GetOptions( 'match|x|=s' => \$opts->{match}, 'timer|t' => \$opts->{timer}, 'color|c!' => \$opts->{color}, + 'formatter|f=s' => \$opts->{formatter}, + 'archive|a=s' => \$opts->{archive}, 'verbose|v+' => \$opts->{verbose}, 'help|H' => \$opts->{help}, 'man|m' => \$opts->{man}, @@ -106,16 +108,25 @@ if ($opts->{runtests}) { push @command, '--file'; } +# Are we using TAP::Harness or TAP::Harness::Archive? +my $harness_class = 'TAP::Harness'; +if ( $opts->{archive} ) { + require TAP::Harness::Archive; + $harness_class = 'TAP::Harness::Archive'; +} + my $verbosity = $opts->{verbose} || $ENV{TEST_VERBOSE}; # Make it so! -TAP::Harness->new({ +$harness_class->new({ verbosity => $verbosity, failures => !$verbosity, ( TAP::Harness->VERSION ge '3.17' ? (comments => !$verbosity) : ()), - timer => $opts->{timer}, - color => $opts->{color}, - exec => \@command, + timer => $opts->{timer}, + color => $opts->{color}, + formatter_class => $opts->{formatter}, + ($opts->{archive} ? (archive => $opts->{archive}) : ()), + exec => \@command, })->runtests( @{ $tests } ); __END__ @@ -203,22 +214,24 @@ and I with "test", run the tests like so: =head1 Options - -b --psql-bin PSQL Location of the psql program. - -d --dbname DBNAME Database to which to connect. - -U --username USERNAME Username with which to connect. - -h --host HOST Host to which to connect. - -p --port PORT Port to which to connect. - -r --runtests Run tests using C. - -s --test-schema Schema in which to find test functions. - -x --test-match Regular expression to find test functions. - -t --timer Print elapsed time after each test file. - -c --color Display colored test ouput. - --nocolor Do not display colored test ouput. - -v --verbose Display output of test scripts while running them. - -H --help Print a usage statement and exit. - -m --man Print the complete documentation and exit. - -V --version Print the version number and exit. - -P --pset OPTION=VALUE Set psql printing option. + -b --psql-bin PSQL Location of the psql program. + -d --dbname DBNAME Database to which to connect. + -U --username USERNAME Username with which to connect. + -h --host HOST Host to which to connect. + -p --port PORT Port to which to connect. + -P --pset OPTION=VALUE Set psql printing option. + -v --verbose Display output of test scripts while running them. + -r --runtests Run tests using C. + -s --schema Schema in which to find test functions. + -x --match Regular expression to find test functions. + -t --timer Print elapsed time after each test file. + -c --color Display colored test ouput. + --nocolor Do not display colored test ouput. + -f --formatter FORMATTER TAP::Formatter class to format output. + -a --archive FILENAME Store the resulting TAP in an archive file. + -H --help Print a usage statement and exit. + -m --man Print the complete documentation and exit. + -V --version Print the version number and exit. =head1 Options Details @@ -380,6 +393,27 @@ installed colored output will not be available. Do not display test results in color. +=item C<-f> + +=item C<--formatter> + + pg_prove --formatter TAP::Formatter::File + pg_prove -f TAP::Formatter::Console + +The name of the class to use to format output. The default is +L, or +L if the output isn’t a TTY. + +=item C<-a> + +=item C<--archive> + + pg_prove --archive tap.tar.gz + pg_prove -a test_output.tar + +Send the TAP output to a TAP archive file as wel as to the normal output +destination. The archive formats supported are F<.tar> and F<.tar.gz>. + =item C<-H> =item C<--help> From 5866f587053073ba2709d95751ba480b2e721fbe Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 21 Mar 2009 04:02:07 +0000 Subject: [PATCH 0322/1195] * Added `has_cast()`, `hasnt_cast()`, and `cast_context_is()`. * Fixed a borked function signature in `has_trigger()`. --- Changes | 2 + README.pgtap | 69 ++++++++++- expected/hastap.out | 95 ++++++++++++++- pgtap.sql.in | 181 +++++++++++++++++++++++++++- sql/hastap.sql | 261 ++++++++++++++++++++++++++++++++++++++++- uninstall_pgtap.sql.in | 21 +++- 6 files changed, 624 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 574abbf3738a..2aea951a9fb4 100644 --- a/Changes +++ b/Changes @@ -10,6 +10,8 @@ Revision history for pgTAP * Added the `--formatter` and `--archive` options to `pg_prove`. * Fixed the typos in `pg_prove` where the output of `--help` listed `--test-match` and `--test-schema` instead of `--match` and `--schema`. +* Added `has_cast()`, `hasnt_cast()`, and `cast_context_is()`. +* Fixed a borked function signature in `has_trigger()`. 0.19 2009-02-21T02:09:26 ------------------------- diff --git a/README.pgtap b/README.pgtap index c1287e7a3bb7..c63142571ae8 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1649,6 +1649,74 @@ cast it to `name[]` to disambiguate it from a text string: SELECT can_ok( 'lower', '{text}'::name[] ); +### `has_cast( source_type, target_type, schema, function, description )` ### +### `has_cast( source_type, target_type, schema, function )` ### +### `has_cast( source_type, target_type, function, description )` ### +### `has_cast( source_type, target_type, function )` ### +### `has_cast( source_type, target_type, description )` ### +### `has_cast( source_type, target_type )` ### + + SELECT has_cast( + 'integer', + 'bigint', + 'pg_catalog', + 'int8' + 'We should have a cast from integer to bigint' + ); + +Tests for the existence of a cast. A cast consists of a source data type, a +target data type, and perhaps a (possibly schema-qualified) function. If you +omit the description four the 3- or 4- argument version, you'll need to cast +the function name to the `NAME` data type so that PostgreSQL doesn't resolve +the function name as a description. For example: + + SELECT has_cast( 'integer', 'bigint', 'int8'::NAME ); + +pgTAP will generate a useful description if you don't provide one. + +### `hasnt_cast( source_type, target_type, schema, function, description )` ### +### `hasnt_cast( source_type, target_type, schema, function )` ### +### `hasnt_cast( source_type, target_type, function, description )` ### +### `hasnt_cast( source_type, target_type, function )` ### +### `hasnt_cast( source_type, target_type, description )` ### +### `hasnt_cast( source_type, target_type )` ### + + SELECT hasnt_cast( 'integer', 'circle' ); + +This function is the inverse of `has_cast()`. The test passes if the specified +cast does *not* exist. + +### `cast_context_is( source_type, target_type, context, desc )` ### +### `cast_context_is( source_type, target_type, context )` ### + + SELECT cast_context_is( 'integer', 'bigint', 'implicit' ); + +Test that a cast from a source to a target data type has a particular context. +The data types should be passed as they are displayed by +`pg_catalog.format_type()`. For example, you would need to pass "character +varying", and not "VARCHAR". + +The The supported contexts are "implicit", "assignment", and "explicit". You +can also just pass in "i", "a", or "e". Consult the PostgreSQL `[CREATE +CAST](http://www.postgresql.org/docs/current/static/sql-createcast.html)` +documentation for the differences between these contexts (hint: they +correspond to the default context, `AS IMPLICIT`, and `AS ASSIGNMENT`). If you +don't supply a test description, pgTAP will create a reasonable one for you. + +Test failure will result in useful diagnostics, such as: + + # Failed test 124: "Cast ("integer" AS "bigint") context should be explicit" + # have: implicit + # want: explicit + +If the cast doesn't exist, you'll be told that, too: + + # Failed test 199: "Cast ("integer" AS foo) context should be explicit" + # Cast ("integer" AS foo) does not exist + +But you've already used `has_cast()` to make sure of that, right? + + ### `has_role( role, desc )` ### ### `has_role( role )` ### @@ -2360,7 +2428,6 @@ To Do ----- * Useful schema testing functions to consider adding: - * `has_cast()` * `has_operator()` * `has_operator_class()` * `has_rule()` diff --git a/expected/hastap.out b/expected/hastap.out index 345b4f020b8a..5fadeeb4b81d 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..285 +1..378 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -285,3 +285,96 @@ ok 282 - hasnt_column(view, column) should have the proper diagnostics ok 283 - hasnt_column(type, column) should pass ok 284 - hasnt_column(type, column) should have the proper description ok 285 - hasnt_column(type, column) should have the proper diagnostics +ok 286 - has_cast( src, targ, schema, func, desc) should pass +ok 287 - has_cast( src, targ, schema, func, desc) should have the proper description +ok 288 - has_cast( src, targ, schema, func, desc) should have the proper diagnostics +ok 289 - has_cast( src, targ, schema, func ) should pass +ok 290 - has_cast( src, targ, schema, func ) should have the proper description +ok 291 - has_cast( src, targ, schema, func ) should have the proper diagnostics +ok 292 - has_cast( src, targ, func, desc ) should pass +ok 293 - has_cast( src, targ, func, desc ) should have the proper description +ok 294 - has_cast( src, targ, func, desc ) should have the proper diagnostics +ok 295 - has_cast( src, targ, func) should pass +ok 296 - has_cast( src, targ, func) should have the proper description +ok 297 - has_cast( src, targ, func) should have the proper diagnostics +ok 298 - has_cast( src, targ, desc ) should pass +ok 299 - has_cast( src, targ, desc ) should have the proper description +ok 300 - has_cast( src, targ, desc ) should have the proper diagnostics +ok 301 - has_cast( src, targ ) should pass +ok 302 - has_cast( src, targ ) should have the proper description +ok 303 - has_cast( src, targ ) should have the proper diagnostics +ok 304 - has_cast( src, targ, schema, func, desc) fail should fail +ok 305 - has_cast( src, targ, schema, func, desc) fail should have the proper description +ok 306 - has_cast( src, targ, schema, func, desc) fail should have the proper diagnostics +ok 307 - has_cast( src, targ, func, desc ) fail should fail +ok 308 - has_cast( src, targ, func, desc ) fail should have the proper description +ok 309 - has_cast( src, targ, func, desc ) fail should have the proper diagnostics +ok 310 - has_cast( src, targ, desc ) fail should fail +ok 311 - has_cast( src, targ, desc ) fail should have the proper description +ok 312 - has_cast( src, targ, desc ) fail should have the proper diagnostics +ok 313 - hasnt_cast( src, targ, schema, func, desc) should fail +ok 314 - hasnt_cast( src, targ, schema, func, desc) should have the proper description +ok 315 - hasnt_cast( src, targ, schema, func, desc) should have the proper diagnostics +ok 316 - hasnt_cast( src, targ, schema, func ) should fail +ok 317 - hasnt_cast( src, targ, schema, func ) should have the proper description +ok 318 - hasnt_cast( src, targ, schema, func ) should have the proper diagnostics +ok 319 - hasnt_cast( src, targ, func, desc ) should fail +ok 320 - hasnt_cast( src, targ, func, desc ) should have the proper description +ok 321 - hasnt_cast( src, targ, func, desc ) should have the proper diagnostics +ok 322 - hasnt_cast( src, targ, func) should fail +ok 323 - hasnt_cast( src, targ, func) should have the proper description +ok 324 - hasnt_cast( src, targ, func) should have the proper diagnostics +ok 325 - hasnt_cast( src, targ, desc ) should fail +ok 326 - hasnt_cast( src, targ, desc ) should have the proper description +ok 327 - hasnt_cast( src, targ, desc ) should have the proper diagnostics +ok 328 - hasnt_cast( src, targ ) should fail +ok 329 - hasnt_cast( src, targ ) should have the proper description +ok 330 - hasnt_cast( src, targ ) should have the proper diagnostics +ok 331 - hasnt_cast( src, targ, schema, func, desc) fail should pass +ok 332 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper description +ok 333 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper diagnostics +ok 334 - hasnt_cast( src, targ, func, desc ) fail should pass +ok 335 - hasnt_cast( src, targ, func, desc ) fail should have the proper description +ok 336 - hasnt_cast( src, targ, func, desc ) fail should have the proper diagnostics +ok 337 - hasnt_cast( src, targ, desc ) fail should pass +ok 338 - hasnt_cast( src, targ, desc ) fail should have the proper description +ok 339 - hasnt_cast( src, targ, desc ) fail should have the proper diagnostics +ok 340 - cast_context_is( src, targ, context, desc ) should pass +ok 341 - cast_context_is( src, targ, context, desc ) should have the proper description +ok 342 - cast_context_is( src, targ, context, desc ) should have the proper diagnostics +ok 343 - cast_context_is( src, targ, context ) should pass +ok 344 - cast_context_is( src, targ, context ) should have the proper description +ok 345 - cast_context_is( src, targ, context ) should have the proper diagnostics +ok 346 - cast_context_is( src, targ, i, desc ) should pass +ok 347 - cast_context_is( src, targ, i, desc ) should have the proper description +ok 348 - cast_context_is( src, targ, i, desc ) should have the proper diagnostics +ok 349 - cast_context_is( src, targ, IMPL, desc ) should pass +ok 350 - cast_context_is( src, targ, IMPL, desc ) should have the proper description +ok 351 - cast_context_is( src, targ, IMPL, desc ) should have the proper diagnostics +ok 352 - cast_context_is( src, targ, assignment, desc ) should pass +ok 353 - cast_context_is( src, targ, assignment, desc ) should have the proper description +ok 354 - cast_context_is( src, targ, assignment, desc ) should have the proper diagnostics +ok 355 - cast_context_is( src, targ, a, desc ) should pass +ok 356 - cast_context_is( src, targ, a, desc ) should have the proper description +ok 357 - cast_context_is( src, targ, a, desc ) should have the proper diagnostics +ok 358 - cast_context_is( src, targ, ASS, desc ) should pass +ok 359 - cast_context_is( src, targ, ASS, desc ) should have the proper description +ok 360 - cast_context_is( src, targ, ASS, desc ) should have the proper diagnostics +ok 361 - cast_context_is( src, targ, explicit, desc ) should pass +ok 362 - cast_context_is( src, targ, explicit, desc ) should have the proper description +ok 363 - cast_context_is( src, targ, explicit, desc ) should have the proper diagnostics +ok 364 - cast_context_is( src, targ, e, desc ) should pass +ok 365 - cast_context_is( src, targ, e, desc ) should have the proper description +ok 366 - cast_context_is( src, targ, e, desc ) should have the proper diagnostics +ok 367 - cast_context_is( src, targ, EX, desc ) should pass +ok 368 - cast_context_is( src, targ, EX, desc ) should have the proper description +ok 369 - cast_context_is( src, targ, EX, desc ) should have the proper diagnostics +ok 370 - cast_context_is( src, targ, context, desc ) fail should fail +ok 371 - cast_context_is( src, targ, context, desc ) fail should have the proper description +ok 372 - cast_context_is( src, targ, context, desc ) fail should have the proper diagnostics +ok 373 - cast_context_is( src, targ, context ) fail should fail +ok 374 - cast_context_is( src, targ, context ) fail should have the proper description +ok 375 - cast_context_is( src, targ, context ) fail should have the proper diagnostics +ok 376 - cast_context_is( src, targ, context, desc ) noexist should fail +ok 377 - cast_context_is( src, targ, context, desc ) noexist should have the proper description +ok 378 - cast_context_is( src, targ, context, desc ) noexist should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index c6d841168479..8caa9b37dc45 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2677,7 +2677,7 @@ END; $$ LANGUAGE plpgsql; -- has_trigger( schema, table, trigger, description ) -CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME, NAME, NAME ) +CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME, NAME, TEXT ) RETURNS TEXT AS $$ DECLARE res boolean; @@ -3282,6 +3282,185 @@ RETURNS TEXT AS $$ SELECT is_member_of( $1, ARRAY[$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 pg_catalog.format_type(castsource, NULL) = $1 + AND pg_catalog.format_type(casttarget, NULL) = $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 pg_catalog.format_type(castsource, NULL) = $1 + AND pg_catalog.format_type(casttarget, NULL) = $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 pg_catalog.format_type(castsource, NULL) = $1 + AND pg_catalog.format_type(casttarget, NULL) = $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; + +CREATE OR REPLACE FUNCTION _get_context( NAME, NAME ) +RETURNS "char" AS $$ + SELECT c.castcontext + FROM pg_catalog.pg_cast c + WHERE pg_catalog.format_type(castsource, NULL) = $1 + AND pg_catalog.format_type(casttarget, NULL) = $2 +$$ LANGUAGE SQL; + +-- cast_context_is( source_type, target_type, context, desc ) +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; + -- check_test( test_output, pass, name, description, diag ) CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) RETURNS SETOF TEXT AS $$ diff --git a/sql/hastap.sql b/sql/hastap.sql index 7022b08316fc..3b96c9a2e86b 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(285); +SELECT plan(378); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -806,6 +806,265 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test has_cast(). + +SELECT * FROM check_test( + has_cast( 'integer', 'bigint', 'pg_catalog', 'int8', 'desc' ), + true, + 'has_cast( src, targ, schema, func, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + has_cast( 'integer', 'bigint', 'pg_catalog', 'int8'::name), + true, + 'has_cast( src, targ, schema, func )', + 'Cast ("integer" AS "bigint") WITH FUNCTION pg_catalog.int8() should exist', + '' +); + +SELECT * FROM check_test( + has_cast( 'integer', 'bigint', 'int8', 'desc' ), + true, + 'has_cast( src, targ, func, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + has_cast( 'integer', 'bigint', 'int8'::name), + true, + 'has_cast( src, targ, func)', + 'Cast ("integer" AS "bigint") WITH FUNCTION int8() should exist', + '' +); + +SELECT * FROM check_test( + has_cast( 'integer', 'bigint', 'desc' ), + true, + 'has_cast( src, targ, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + has_cast( 'integer', 'bigint' ), + true, + 'has_cast( src, targ )', + 'Cast ("integer" AS "bigint") should exist', + '' +); + +SELECT * FROM check_test( + has_cast( 'integer', 'bigint', 'pg_catalog', 'foo', 'desc' ), + false, + 'has_cast( src, targ, schema, func, desc) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + has_cast( 'integer', 'bigint', 'foo', 'desc' ), + false, + 'has_cast( src, targ, func, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + has_cast( 'integer', 'clue', 'desc' ), + false, + 'has_cast( src, targ, desc ) fail', + 'desc', + '' +); + +/****************************************************************************/ +-- Test hasnt_cast(). + +SELECT * FROM check_test( + hasnt_cast( 'integer', 'bigint', 'pg_catalog', 'int8', 'desc' ), + false, + 'hasnt_cast( src, targ, schema, func, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_cast( 'integer', 'bigint', 'pg_catalog', 'int8'::name), + false, + 'hasnt_cast( src, targ, schema, func )', + 'Cast ("integer" AS "bigint") WITH FUNCTION pg_catalog.int8() should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_cast( 'integer', 'bigint', 'int8', 'desc' ), + false, + 'hasnt_cast( src, targ, func, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_cast( 'integer', 'bigint', 'int8'::name), + false, + 'hasnt_cast( src, targ, func)', + 'Cast ("integer" AS "bigint") WITH FUNCTION int8() should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_cast( 'integer', 'bigint', 'desc' ), + false, + 'hasnt_cast( src, targ, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_cast( 'integer', 'bigint' ), + false, + 'hasnt_cast( src, targ )', + 'Cast ("integer" AS "bigint") should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_cast( 'integer', 'bigint', 'pg_catalog', 'foo', 'desc' ), + true, + 'hasnt_cast( src, targ, schema, func, desc) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_cast( 'integer', 'bigint', 'foo', 'desc' ), + true, + 'hasnt_cast( src, targ, func, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_cast( 'integer', 'clue', 'desc' ), + true, + 'hasnt_cast( src, targ, desc ) fail', + 'desc', + '' +); + +/****************************************************************************/ +-- Test cast_has_context(). + +SELECT * FROM check_test( + cast_context_is( 'integer', 'bigint', 'implicit', 'desc' ), + true, + 'cast_context_is( src, targ, context, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + cast_context_is( 'integer', 'bigint', 'implicit' ), + true, + 'cast_context_is( src, targ, context )', + 'Cast ("integer" AS "bigint") context should be implicit', + '' +); + +SELECT * FROM check_test( + cast_context_is( 'integer', 'bigint', 'i', 'desc' ), + true, + 'cast_context_is( src, targ, i, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + cast_context_is( 'integer', 'bigint', 'IMPL', 'desc' ), + true, + 'cast_context_is( src, targ, IMPL, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + cast_context_is( 'cidr', 'text', 'assignment', 'desc' ), + true, + 'cast_context_is( src, targ, assignment, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + cast_context_is( 'cidr', 'text', 'a', 'desc' ), + true, + 'cast_context_is( src, targ, a, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + cast_context_is( 'cidr', 'text', 'ASS', 'desc' ), + true, + 'cast_context_is( src, targ, ASS, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + cast_context_is( 'bit', 'integer', 'explicit', 'desc' ), + true, + 'cast_context_is( src, targ, explicit, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + cast_context_is( 'bit', 'integer', 'e', 'desc' ), + true, + 'cast_context_is( src, targ, e, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + cast_context_is( 'bit', 'integer', 'EX', 'desc' ), + true, + 'cast_context_is( src, targ, EX, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + cast_context_is( 'integer', 'bigint', 'ex', 'desc' ), + false, + 'cast_context_is( src, targ, context, desc ) fail', + 'desc', + ' have: implicit + want: explicit' +); + +SELECT * FROM check_test( + cast_context_is( 'integer', 'bigint', 'ex' ), + false, + 'cast_context_is( src, targ, context ) fail', + 'Cast ("integer" AS "bigint") context should be explicit', + ' have: implicit + want: explicit' +); + +SELECT * FROM check_test( + cast_context_is( 'integer', 'bogus', 'ex', 'desc' ), + false, + 'cast_context_is( src, targ, context, desc ) noexist', + 'desc', + ' Cast ("integer" AS bogus) does not exist' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 0e7b0f11da7f..775dfdb82f91 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -19,6 +19,25 @@ DROP FUNCTION check_test( TEXT, BOOLEAN ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); +DROP FUNCTION cast_context_is( NAME, NAME, TEXT ); +DROP FUNCTION cast_context_is( NAME, NAME, TEXT, TEXT ); +DROP FUNCTION _get_context( NAME, NAME ); +DROP FUNCTION _expand_context( char ); +DROP FUNCTION hasnt_cast ( NAME, NAME ); +DROP FUNCTION hasnt_cast ( NAME, NAME, TEXT ); +DROP FUNCTION hasnt_cast ( NAME, NAME, NAME ); +DROP FUNCTION hasnt_cast ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION hasnt_cast ( NAME, NAME, NAME, NAME ); +DROP FUNCTION hasnt_cast ( NAME, NAME, NAME, NAME, TEXT ); +DROP FUNCTION has_cast ( NAME, NAME ); +DROP FUNCTION has_cast ( NAME, NAME, TEXT ); +DROP FUNCTION has_cast ( NAME, NAME, NAME ); +DROP FUNCTION has_cast ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION has_cast ( NAME, NAME, NAME, NAME ); +DROP FUNCTION has_cast ( NAME, NAME, NAME, NAME, TEXT ); +DROP FUNCTION _cast_exists ( NAME, NAME ); +DROP FUNCTION _cast_exists ( NAME, NAME, NAME ); +DROP FUNCTION _cast_exists ( NAME, NAME, NAME, NAME ); DROP FUNCTION is_member_of( NAME, NAME ); DROP FUNCTION is_member_of( NAME, NAME[] ); DROP FUNCTION is_member_of( NAME, NAME, TEXT ); @@ -88,7 +107,7 @@ DROP FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME ); DROP FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME, text ); DROP FUNCTION has_trigger ( NAME, NAME ); DROP FUNCTION has_trigger ( NAME, NAME, NAME ); -DROP FUNCTION has_trigger ( NAME, NAME, NAME, NAME ); +DROP FUNCTION has_trigger ( NAME, NAME, NAME, TEXT ); DROP FUNCTION index_is_type ( NAME, NAME ); DROP FUNCTION index_is_type ( NAME, NAME, NAME ); DROP FUNCTION index_is_type ( NAME, NAME, NAME, NAME ); From 9cfe855588d7377ff0881faec81eb5599d7cf151 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 21 Mar 2009 04:03:54 +0000 Subject: [PATCH 0323/1195] Fixed link. --- README.pgtap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.pgtap b/README.pgtap index c63142571ae8..ea83e9cc4ffc 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1697,8 +1697,8 @@ The data types should be passed as they are displayed by varying", and not "VARCHAR". The The supported contexts are "implicit", "assignment", and "explicit". You -can also just pass in "i", "a", or "e". Consult the PostgreSQL `[CREATE -CAST](http://www.postgresql.org/docs/current/static/sql-createcast.html)` +can also just pass in "i", "a", or "e". Consult the PostgreSQL [`CREATE +CAST`](http://www.postgresql.org/docs/current/static/sql-createcast.html) documentation for the differences between these contexts (hint: they correspond to the default context, `AS IMPLICIT`, and `AS ASSIGNMENT`). If you don't supply a test description, pgTAP will create a reasonable one for you. From b09d78ccef70cb35e3e0f6f7c90f7b160c989731 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 21 Mar 2009 04:04:45 +0000 Subject: [PATCH 0324/1195] Space. --- README.pgtap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index ea83e9cc4ffc..3fd656947ebc 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1666,7 +1666,7 @@ cast it to `name[]` to disambiguate it from a text string: Tests for the existence of a cast. A cast consists of a source data type, a target data type, and perhaps a (possibly schema-qualified) function. If you -omit the description four the 3- or 4- argument version, you'll need to cast +omit the description four the 3- or 4-argument version, you'll need to cast the function name to the `NAME` data type so that PostgreSQL doesn't resolve the function name as a description. For example: From 437342336edf2c58a6b3b1c60355ba8e9935105c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 22 Mar 2009 22:23:46 +0000 Subject: [PATCH 0325/1195] Added `has_operator()`, `has_leftop()`, and `has_rightop()`. --- Changes | 1 + README.pgtap | 65 ++++++++- expected/hastap.out | 110 ++++++++++++++- pgtap.sql.in | 204 ++++++++++++++++++++++++++++ sql/hastap.sql | 299 ++++++++++++++++++++++++++++++++++++++++- uninstall_pgtap.sql.in | 21 +++ 6 files changed, 697 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 2aea951a9fb4..140debd8c8e6 100644 --- a/Changes +++ b/Changes @@ -12,6 +12,7 @@ Revision history for pgTAP `--test-match` and `--test-schema` instead of `--match` and `--schema`. * Added `has_cast()`, `hasnt_cast()`, and `cast_context_is()`. * Fixed a borked function signature in `has_trigger()`. +* Added `has_operator()`, `has_leftop()`, and `has_rightop()`. 0.19 2009-02-21T02:09:26 ------------------------- diff --git a/README.pgtap b/README.pgtap index 3fd656947ebc..a244d7b509fa 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1716,6 +1716,70 @@ If the cast doesn't exist, you'll be told that, too: But you've already used `has_cast()` to make sure of that, right? +### `has_operator( left_type, schema, name, right_type, return_type, desc )` ### +### `has_operator( left_type, schema, name, right_type, return_type )` ### +### `has_operator( left_type, name, right_type, return_type, desc )` ### +### `has_operator( left_type, name, right_type, return_type )` ### +### `has_operator( left_type, name, right_type, desc )` ### +### `has_operator( left_type, name, right_type )` ### + + SELECT has_operator( + 'integer', + 'pg_catalog', '<=', + 'integer', + 'boolean', + 'Operator (integer <= integer = boolean) should exist' + ); + +Tests for the presence of a binary operator. If the operator exists with the +given schema, name, left and right arguments, and return value, the test will +fail. If the operator does not exist, the test will fail. If you omit the +schema name, then the operator must be visible in the search path. If you omit +the test description, pgTAP will generate a reasonable one for you. The return +value is also optional. If you need to test for a left or right unary +operator, use `has_leftop()` or `has_rightop()` instead. + +### `has_leftop( schema, name, right_type, return_type, desc )` ### +### `has_leftop( schema, name, right_type, return_type )` ### +### `has_leftop( name, right_type, return_type, desc )` ### +### `has_leftop( name, right_type, return_type )` ### +### `has_leftop( name, right_type, desc )` ### +### `has_leftop( name, right_type )` ### + + SELECT has_leftop( + 'pg_catalog', '!!', + 'bigint', + 'numeric', + 'Operator (!! bigint = numeric) should exist' + ); + +Tests for the presence of a left-unary operator. If the operator exists with +the given schema, name, right argument, and return value, the test will fail. +If the operator does not exist, the test will fail. If you omit the schema +name, then the operator must be visible in the search path. If you omit the +test description, pgTAP will generate a reasonable one for you. The return +value is also optional. + +### `has_rightop( left_type, schema, name, return_type, desc )` ### +### `has_rightop( left_type, schema, name, return_type )` ### +### `has_rightop( left_type, name, return_type, desc )` ### +### `has_rightop( left_type, name, return_type )` ### +### `has_rightop( left_type, name, desc )` ### +### `has_rightop( left_type, name )` ### + + SELECT has_rightop( + 'bigint', + 'pg_catalog', '!', + 'numeric', + 'Operator (bigint ! = numeric) should exist' + ); + +Tests for the presence of a right-unary operator. If the operator exists with +the given left argument, schema, name, and return value, the test will fail. +If the operator does not exist, the test will fail. If you omit the schema +name, then the operator must be visible in the search path. If you omit the +test description, pgTAP will generate a reasonable one for you. The return +value is also optional. ### `has_role( role, desc )` ### ### `has_role( role )` ### @@ -2428,7 +2492,6 @@ To Do ----- * Useful schema testing functions to consider adding: - * `has_operator()` * `has_operator_class()` * `has_rule()` * `func_returns()` diff --git a/expected/hastap.out b/expected/hastap.out index 5fadeeb4b81d..3d256557c2cb 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..378 +1..486 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -378,3 +378,111 @@ ok 375 - cast_context_is( src, targ, context ) fail should have the proper diagn ok 376 - cast_context_is( src, targ, context, desc ) noexist should fail ok 377 - cast_context_is( src, targ, context, desc ) noexist should have the proper description ok 378 - cast_context_is( src, targ, context, desc ) noexist should have the proper diagnostics +ok 379 - has_operator( left, schema, name, right, result, desc ) should pass +ok 380 - has_operator( left, schema, name, right, result, desc ) should have the proper description +ok 381 - has_operator( left, schema, name, right, result, desc ) should have the proper diagnostics +ok 382 - has_operator( left, schema, name, right, result ) should pass +ok 383 - has_operator( left, schema, name, right, result ) should have the proper description +ok 384 - has_operator( left, schema, name, right, result ) should have the proper diagnostics +ok 385 - has_operator( left, name, right, result, desc ) should pass +ok 386 - has_operator( left, name, right, result, desc ) should have the proper description +ok 387 - has_operator( left, name, right, result, desc ) should have the proper diagnostics +ok 388 - has_operator( left, name, right, result ) should pass +ok 389 - has_operator( left, name, right, result ) should have the proper description +ok 390 - has_operator( left, name, right, result ) should have the proper diagnostics +ok 391 - has_operator( left, name, right, desc ) should pass +ok 392 - has_operator( left, name, right, desc ) should have the proper description +ok 393 - has_operator( left, name, right, desc ) should have the proper diagnostics +ok 394 - has_operator( left, name, right ) should pass +ok 395 - has_operator( left, name, right ) should have the proper description +ok 396 - has_operator( left, name, right ) should have the proper diagnostics +ok 397 - has_operator( left, schema, name, right, result, desc ) fail should fail +ok 398 - has_operator( left, schema, name, right, result, desc ) fail should have the proper description +ok 399 - has_operator( left, schema, name, right, result, desc ) fail should have the proper diagnostics +ok 400 - has_operator( left, schema, name, right, result ) fail should fail +ok 401 - has_operator( left, schema, name, right, result ) fail should have the proper description +ok 402 - has_operator( left, schema, name, right, result ) fail should have the proper diagnostics +ok 403 - has_operator( left, name, right, result, desc ) fail should fail +ok 404 - has_operator( left, name, right, result, desc ) fail should have the proper description +ok 405 - has_operator( left, name, right, result, desc ) fail should have the proper diagnostics +ok 406 - has_operator( left, name, right, result ) fail should fail +ok 407 - has_operator( left, name, right, result ) fail should have the proper description +ok 408 - has_operator( left, name, right, result ) fail should have the proper diagnostics +ok 409 - has_operator( left, name, right, desc ) fail should fail +ok 410 - has_operator( left, name, right, desc ) fail should have the proper description +ok 411 - has_operator( left, name, right, desc ) fail should have the proper diagnostics +ok 412 - has_operator( left, name, right ) fail should fail +ok 413 - has_operator( left, name, right ) fail should have the proper description +ok 414 - has_operator( left, name, right ) fail should have the proper diagnostics +ok 415 - has_leftop( schema, name, right, result, desc ) should pass +ok 416 - has_leftop( schema, name, right, result, desc ) should have the proper description +ok 417 - has_leftop( schema, name, right, result, desc ) should have the proper diagnostics +ok 418 - has_leftop( schema, name, right, result ) should pass +ok 419 - has_leftop( schema, name, right, result ) should have the proper description +ok 420 - has_leftop( schema, name, right, result ) should have the proper diagnostics +ok 421 - has_leftop( name, right, result, desc ) should pass +ok 422 - has_leftop( name, right, result, desc ) should have the proper description +ok 423 - has_leftop( name, right, result, desc ) should have the proper diagnostics +ok 424 - has_leftop( name, right, result ) should pass +ok 425 - has_leftop( name, right, result ) should have the proper description +ok 426 - has_leftop( name, right, result ) should have the proper diagnostics +ok 427 - has_leftop( name, right, desc ) should pass +ok 428 - has_leftop( name, right, desc ) should have the proper description +ok 429 - has_leftop( name, right, desc ) should have the proper diagnostics +ok 430 - has_leftop( name, right ) should pass +ok 431 - has_leftop( name, right ) should have the proper description +ok 432 - has_leftop( name, right ) should have the proper diagnostics +ok 433 - has_leftop( schema, name, right, result, desc ) fail should fail +ok 434 - has_leftop( schema, name, right, result, desc ) fail should have the proper description +ok 435 - has_leftop( schema, name, right, result, desc ) fail should have the proper diagnostics +ok 436 - has_leftop( schema, name, right, result ) fail should fail +ok 437 - has_leftop( schema, name, right, result ) fail should have the proper description +ok 438 - has_leftop( schema, name, right, result ) fail should have the proper diagnostics +ok 439 - has_leftop( name, right, result, desc ) fail should fail +ok 440 - has_leftop( name, right, result, desc ) fail should have the proper description +ok 441 - has_leftop( name, right, result, desc ) fail should have the proper diagnostics +ok 442 - has_leftop( name, right, result ) fail should fail +ok 443 - has_leftop( name, right, result ) fail should have the proper description +ok 444 - has_leftop( name, right, result ) fail should have the proper diagnostics +ok 445 - has_leftop( name, right, desc ) fail should fail +ok 446 - has_leftop( name, right, desc ) fail should have the proper description +ok 447 - has_leftop( name, right, desc ) fail should have the proper diagnostics +ok 448 - has_leftop( name, right ) fail should fail +ok 449 - has_leftop( name, right ) fail should have the proper description +ok 450 - has_leftop( name, right ) fail should have the proper diagnostics +ok 451 - has_rightop( left, schema, name, result, desc ) should pass +ok 452 - has_rightop( left, schema, name, result, desc ) should have the proper description +ok 453 - has_rightop( left, schema, name, result, desc ) should have the proper diagnostics +ok 454 - has_rightop( left, schema, name, result ) should pass +ok 455 - has_rightop( left, schema, name, result ) should have the proper description +ok 456 - has_rightop( left, schema, name, result ) should have the proper diagnostics +ok 457 - has_rightop( left, name, result, desc ) should pass +ok 458 - has_rightop( left, name, result, desc ) should have the proper description +ok 459 - has_rightop( left, name, result, desc ) should have the proper diagnostics +ok 460 - has_rightop( left, name, result ) should pass +ok 461 - has_rightop( left, name, result ) should have the proper description +ok 462 - has_rightop( left, name, result ) should have the proper diagnostics +ok 463 - has_rightop( left, name, desc ) should pass +ok 464 - has_rightop( left, name, desc ) should have the proper description +ok 465 - has_rightop( left, name, desc ) should have the proper diagnostics +ok 466 - has_rightop( left, name ) should pass +ok 467 - has_rightop( left, name ) should have the proper description +ok 468 - has_rightop( left, name ) should have the proper diagnostics +ok 469 - has_rightop( left, schema, name, result, desc ) fail should fail +ok 470 - has_rightop( left, schema, name, result, desc ) fail should have the proper description +ok 471 - has_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics +ok 472 - has_rightop( left, schema, name, result ) fail should fail +ok 473 - has_rightop( left, schema, name, result ) fail should have the proper description +ok 474 - has_rightop( left, schema, name, result ) fail should have the proper diagnostics +ok 475 - has_rightop( left, name, result, desc ) fail should fail +ok 476 - has_rightop( left, name, result, desc ) fail should have the proper description +ok 477 - has_rightop( left, name, result, desc ) fail should have the proper diagnostics +ok 478 - has_rightop( left, name, result ) fail should fail +ok 479 - has_rightop( left, name, result ) fail should have the proper description +ok 480 - has_rightop( left, name, result ) fail should have the proper diagnostics +ok 481 - has_rightop( left, name, desc ) fail should fail +ok 482 - has_rightop( left, name, desc ) fail should have the proper description +ok 483 - has_rightop( left, name, desc ) fail should have the proper diagnostics +ok 484 - has_rightop( left, name ) fail should fail +ok 485 - has_rightop( left, name ) fail should have the proper description +ok 486 - has_rightop( left, name ) fail should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 8caa9b37dc45..0a0399703f4c 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3461,6 +3461,210 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; + +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 pg_catalog.format_type(o.oprleft, NULL) = $1 END + AND CASE o.oprkind WHEN 'r' THEN $4 IS NULL + ELSE pg_catalog.format_type(o.oprright, NULL) = $4 END + AND pg_catalog.format_type(o.oprresult, NULL) = $5 + ); +$$ LANGUAGE SQL; + +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 pg_catalog.format_type(o.oprleft, NULL) = $1 END + AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL + ELSE pg_catalog.format_type(o.oprright, NULL) = $3 END + AND pg_catalog.format_type(o.oprresult, NULL) = $4 + ); +$$ LANGUAGE SQL; + +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 pg_catalog.format_type(o.oprleft, NULL) = $1 END + AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL + ELSE pg_catalog.format_type(o.oprright, NULL) = $3 END + ); +$$ LANGUAGE SQL; + +-- has_operator( left_type, schema, name, right_type, return_type, desc ) +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($1) + || ' ' || quote_ident($2) || '.' || $3 || ' ' + || quote_ident($4) + || ' = ' || quote_ident($5) || ') should exist' + ); +$$ LANGUAGE SQL; + +-- has_operator( left_type, name, right_type, return_type, desc ) +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 (' + || quote_ident($1) + || ' ' || $2 || ' ' + || quote_ident($3) + || ' = ' || quote_ident($4) || ') should exist' + ); +$$ LANGUAGE SQL; + +-- has_operator( left_type, name, right_type, desc ) +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 (' + || quote_ident($1) + || ' ' || $2 || ' ' + || quote_ident($3) + || ') should exist' + ); +$$ LANGUAGE SQL; + +-- has_leftop( schema, name, right_type, return_type, desc ) +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 ), + 'Operator (' + || quote_ident($1) || '.' || $2 || ' ' + || quote_ident($3) + || ' = ' || quote_ident($4) || ') should exist' + ); +$$ LANGUAGE SQL; + +-- has_leftop( name, right_type, return_type, desc ) +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 ), + 'Operator (' || $1 || ' ' + || quote_ident($2) + || ' = ' || quote_ident($3) || ') should exist' + ); +$$ LANGUAGE SQL; + +-- has_leftop( name, right_type, desc ) +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 ), + 'Operator (' || $1 || ' ' || quote_ident($2) || ') should exist' + ); +$$ LANGUAGE SQL; + +-- has_rightop( left_type, schema, name, return_type, desc ) +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 ), + 'Operator (' + || quote_ident($1) || ' ' + || quote_ident($2) || '.' || $3 + || ' = ' || quote_ident($4) || ') should exist' + ); +$$ LANGUAGE SQL; + +-- has_rightop( left_type, name, return_type, desc ) +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 ), + 'Operator (' + || quote_ident($1) || ' ' || $2 + || ' = ' || quote_ident($3) || ') should exist' + ); +$$ LANGUAGE SQL; + +-- has_rightop( left_type, name, desc ) +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 ), + 'Operator (' + || quote_ident($1) || ' ' || $2 || ') should exist' + ); +$$ LANGUAGE SQL; + -- check_test( test_output, pass, name, description, diag ) CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) RETURNS SETOF TEXT AS $$ diff --git a/sql/hastap.sql b/sql/hastap.sql index 3b96c9a2e86b..c8df8f7fc49a 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -3,7 +3,7 @@ -- $Id$ -SELECT plan(378); +SELECT plan(486); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -1065,6 +1065,303 @@ SELECT * FROM check_test( ' Cast ("integer" AS bogus) does not exist' ); +/****************************************************************************/ +-- Test has_operator(). + +SELECT * FROM check_test( + has_operator( 'integer', 'pg_catalog', '<=', 'integer', 'boolean', 'desc' ), + true, + 'has_operator( left, schema, name, right, result, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + has_operator( 'integer', 'pg_catalog', '<=', 'integer', 'boolean'::name ), + true, + 'has_operator( left, schema, name, right, result )', + 'Operator ("integer" pg_catalog.<= "integer" = "boolean") should exist', + '' +); + +SELECT * FROM check_test( + has_operator( 'integer', '<=', 'integer', 'boolean', 'desc' ), + true, + 'has_operator( left, name, right, result, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + has_operator( 'integer', '<=', 'integer', 'boolean'::name ), + true, + 'has_operator( left, name, right, result )', + 'Operator ("integer" <= "integer" = "boolean") should exist', + '' +); + +SELECT * FROM check_test( + has_operator( 'integer', '<=', 'integer', 'desc' ), + true, + 'has_operator( left, name, right, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + has_operator( 'integer', '<=', 'integer'::name ), + true, + 'has_operator( left, name, right )', + 'Operator ("integer" <= "integer") should exist', + '' +); + +SELECT * FROM check_test( + has_operator( 'integer', 'pg_catalog', '<=', 'text', 'boolean', 'desc' ), + false, + 'has_operator( left, schema, name, right, result, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + has_operator( 'integer', 'pg_catalog', '<=', 'text', 'boolean'::name ), + false, + 'has_operator( left, schema, name, right, result ) fail', + 'Operator ("integer" pg_catalog.<= text = "boolean") should exist', + '' +); + +SELECT * FROM check_test( + has_operator( 'integer', '<=', 'text', 'boolean', 'desc' ), + false, + 'has_operator( left, name, right, result, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + has_operator( 'integer', '<=', 'text', 'boolean'::name ), + false, + 'has_operator( left, name, right, result ) fail', + 'Operator ("integer" <= text = "boolean") should exist', + '' +); + +SELECT * FROM check_test( + has_operator( 'integer', '<=', 'text', 'desc' ), + false, + 'has_operator( left, name, right, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + has_operator( 'integer', '<=', 'text'::name ), + false, + 'has_operator( left, name, right ) fail', + 'Operator ("integer" <= text) should exist', + '' +); + +/****************************************************************************/ +-- Test has_leftop(). + +SELECT * FROM check_test( + has_leftop( 'pg_catalog', '!!', 'bigint', 'numeric', 'desc' ), + true, + 'has_leftop( schema, name, right, result, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + has_leftop( 'pg_catalog', '!!', 'bigint', 'numeric'::name ), + true, + 'has_leftop( schema, name, right, result )', + 'Operator (pg_catalog.!! "bigint" = "numeric") should exist', + '' +); + +SELECT * FROM check_test( + has_leftop( '!!', 'bigint', 'numeric', 'desc' ), + true, + 'has_leftop( name, right, result, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + has_leftop( '!!', 'bigint', 'numeric'::name ), + true, + 'has_leftop( name, right, result )', + 'Operator (!! "bigint" = "numeric") should exist', + '' +); + +SELECT * FROM check_test( + has_leftop( '!!', 'bigint', 'desc' ), + true, + 'has_leftop( name, right, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + has_leftop( '!!', 'bigint' ), + true, + 'has_leftop( name, right )', + 'Operator (!! "bigint") should exist', + '' +); + +SELECT * FROM check_test( + has_leftop( 'pg_catalog', '!!', 'text', 'numeric', 'desc' ), + false, + 'has_leftop( schema, name, right, result, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + has_leftop( 'pg_catalog', '!!', 'text', 'numeric'::name ), + false, + 'has_leftop( schema, name, right, result ) fail', + 'Operator (pg_catalog.!! text = "numeric") should exist', + '' +); + +SELECT * FROM check_test( + has_leftop( '!!', 'text', 'numeric', 'desc' ), + false, + 'has_leftop( name, right, result, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + has_leftop( '!!', 'text', 'numeric'::name ), + false, + 'has_leftop( name, right, result ) fail', + 'Operator (!! text = "numeric") should exist', + '' +); + +SELECT * FROM check_test( + has_leftop( '!!', 'text', 'desc' ), + false, + 'has_leftop( name, right, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + has_leftop( '!!', 'text' ), + false, + 'has_leftop( name, right ) fail', + 'Operator (!! text) should exist', + '' +); + +/****************************************************************************/ +-- Test has_rightop(). + +SELECT * FROM check_test( + has_rightop( 'bigint', 'pg_catalog', '!', 'numeric', 'desc' ), + true, + 'has_rightop( left, schema, name, result, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + has_rightop( 'bigint', 'pg_catalog', '!', 'numeric'::name ), + true, + 'has_rightop( left, schema, name, result )', + 'Operator ("bigint" pg_catalog.! = "numeric") should exist', + '' +); + +SELECT * FROM check_test( + has_rightop( 'bigint', '!', 'numeric', 'desc' ), + true, + 'has_rightop( left, name, result, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + has_rightop( 'bigint', '!', 'numeric'::name ), + true, + 'has_rightop( left, name, result )', + 'Operator ("bigint" ! = "numeric") should exist', + '' +); + +SELECT * FROM check_test( + has_rightop( 'bigint', '!', 'desc' ), + true, + 'has_rightop( left, name, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + has_rightop( 'bigint', '!' ), + true, + 'has_rightop( left, name )', + 'Operator ("bigint" !) should exist', + '' +); + +SELECT * FROM check_test( + has_rightop( 'text', 'pg_catalog', '!', 'numeric', 'desc' ), + false, + 'has_rightop( left, schema, name, result, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + has_rightop( 'text', 'pg_catalog', '!', 'numeric'::name ), + false, + 'has_rightop( left, schema, name, result ) fail', + 'Operator (text pg_catalog.! = "numeric") should exist', + '' +); + +SELECT * FROM check_test( + has_rightop( 'text', '!', 'numeric', 'desc' ), + false, + 'has_rightop( left, name, result, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + has_rightop( 'text', '!', 'numeric'::name ), + false, + 'has_rightop( left, name, result ) fail', + 'Operator (text ! = "numeric") should exist', + '' +); + +SELECT * FROM check_test( + has_rightop( 'text', '!', 'desc' ), + false, + 'has_rightop( left, name, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + has_rightop( 'text', '!' ), + false, + 'has_rightop( left, name ) fail', + 'Operator (text !) should exist', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 775dfdb82f91..e030f44bb00b 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -19,6 +19,27 @@ DROP FUNCTION check_test( TEXT, BOOLEAN ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); +DROP FUNCTION has_rightop ( NAME, NAME ); +DROP FUNCTION has_rightop ( NAME, NAME, TEXT ); +DROP FUNCTION has_rightop ( NAME, NAME, NAME ); +DROP FUNCTION has_rightop ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION has_rightop ( NAME, NAME, NAME, NAME ); +DROP FUNCTION has_rightop ( NAME, NAME, NAME, NAME, TEXT ); +DROP FUNCTION has_leftop ( NAME, NAME ); +DROP FUNCTION has_leftop ( NAME, NAME, TEXT ); +DROP FUNCTION has_leftop ( NAME, NAME, NAME ); +DROP FUNCTION has_leftop ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION has_leftop ( NAME, NAME, NAME, NAME ); +DROP FUNCTION has_leftop ( NAME, NAME, NAME, NAME, TEXT ); +DROP FUNCTION has_operator ( NAME, NAME, NAME ); +DROP FUNCTION has_operator ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION has_operator ( NAME, NAME, NAME, NAME ); +DROP FUNCTION has_operator ( NAME, NAME, NAME, NAME, TEXT ); +DROP FUNCTION has_operator ( NAME, NAME, NAME, NAME, NAME ); +DROP FUNCTION has_operator ( NAME, NAME, NAME, NAME, NAME, TEXT ); +DROP FUNCTION _op_exists ( NAME, NAME, NAME ); +DROP FUNCTION _op_exists ( NAME, NAME, NAME, NAME ); +DROP FUNCTION _op_exists ( NAME, NAME, NAME, NAME, NAME ); DROP FUNCTION cast_context_is( NAME, NAME, TEXT ); DROP FUNCTION cast_context_is( NAME, NAME, TEXT, TEXT ); DROP FUNCTION _get_context( NAME, NAME ); From 54ef62908354198874f019096662147ba56be48d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 24 Mar 2009 02:21:09 +0000 Subject: [PATCH 0326/1195] Fixed a bug where the order of columns found for multicolum indexes by `has_index()` could be wrong. Reported by Jeff Wartes. --- Changes | 2 ++ pgtap.sql.in | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Changes b/Changes index 140debd8c8e6..1a132ac90d8e 100644 --- a/Changes +++ b/Changes @@ -13,6 +13,8 @@ Revision history for pgTAP * Added `has_cast()`, `hasnt_cast()`, and `cast_context_is()`. * Fixed a borked function signature in `has_trigger()`. * Added `has_operator()`, `has_leftop()`, and `has_rightop()`. +* Fixed a bug where the order of columns found for multicolum indexes by + `has_index()` could be wrong. Reported by Jeff Wartes. 0.19 2009-02-21T02:09:26 ------------------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index 0a0399703f4c..cc97f93e14a4 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2157,6 +2157,7 @@ RETURNS NAME[] AS $$ AND ci.relname = $3 AND n.nspname = $1 AND a.attnum = ANY(x.indkey::int[]) + ORDER BY a.attnum ); $$ LANGUAGE sql; @@ -2172,6 +2173,7 @@ RETURNS NAME[] AS $$ AND ci.relname = $2 AND a.attnum = ANY(x.indkey::int[]) AND pg_catalog.pg_table_is_visible(ct.oid) + ORDER BY a.attnum ); $$ LANGUAGE sql; From fa80e093fe6c4f29708ce6137b7545beee26ef48 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 24 Mar 2009 22:02:02 +0000 Subject: [PATCH 0327/1195] Proper fix for index column ordering. --- Changes | 3 ++- pgtap.sql.in | 14 ++++++++------ sql/index.sql | 18 +++++++++--------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Changes b/Changes index 1a132ac90d8e..9daf402c0b95 100644 --- a/Changes +++ b/Changes @@ -14,7 +14,8 @@ Revision history for pgTAP * Fixed a borked function signature in `has_trigger()`. * Added `has_operator()`, `has_leftop()`, and `has_rightop()`. * Fixed a bug where the order of columns found for multicolum indexes by - `has_index()` could be wrong. Reported by Jeff Wartes. + `has_index()` could be wrong. Reported by Jeff Wartes. Thanks to Andrew + Gierth for help fixing the query. 0.19 2009-02-21T02:09:26 ------------------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index cc97f93e14a4..70384646aed4 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1436,7 +1436,7 @@ RETURNS NAME[] AS $$ SELECT ARRAY( SELECT a.attname FROM pg_catalog.pg_attribute a - JOIN generate_series(1, 32) s(i) ON (a.attnum = $2[i]) + JOIN generate_series(1, array_upper($2, 1)) s(i) ON (a.attnum = $2[i]) WHERE attrelid = $1 ORDER BY i ) @@ -2153,11 +2153,12 @@ RETURNS NAME[] AS $$ 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_attribute a ON (ct.oid = a.attrelid) + JOIN generate_series(0, current_setting('max_index_keys')::integer - 1) s(i) + ON a.attnum = x.indkey[s.i] WHERE ct.relname = $2 AND ci.relname = $3 AND n.nspname = $1 - AND a.attnum = ANY(x.indkey::int[]) - ORDER BY a.attnum + ORDER BY s.i ); $$ LANGUAGE sql; @@ -2169,11 +2170,12 @@ RETURNS NAME[] AS $$ 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_attribute a ON (ct.oid = a.attrelid) + JOIN generate_series(0, current_setting('max_index_keys')::integer - 1) s(i) + ON a.attnum = x.indkey[s.i] WHERE ct.relname = $1 AND ci.relname = $2 - AND a.attnum = ANY(x.indkey::int[]) AND pg_catalog.pg_table_is_visible(ct.oid) - ORDER BY a.attnum + ORDER BY s.i ); $$ LANGUAGE sql; @@ -2186,7 +2188,7 @@ RETURNS TEXT AS $$ JOIN pg_catalog.pg_namespace n ON (n.oid = ct.relnamespace) WHERE ct.relname = $2 AND ci.relname = $3 - AND n.nspname = $1; + AND n.nspname = $1 $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME) diff --git a/sql/index.sql b/sql/index.sql index fd4c8961754b..479dc9e6497d 100644 --- a/sql/index.sql +++ b/sql/index.sql @@ -15,7 +15,7 @@ CREATE TABLE public.sometab( myint NUMERIC(8) ); CREATE INDEX idx_foo ON public.sometab using hash(name); -CREATE INDEX idx_bar ON public.sometab(name, numb); +CREATE INDEX idx_bar ON public.sometab(numb, name); CREATE UNIQUE INDEX idx_baz ON public.sometab(LOWER(name)); RESET client_min_messages; @@ -39,7 +39,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_index( 'public', 'sometab', 'idx_bar', ARRAY['name', 'numb'], 'whatever' ), + has_index( 'public', 'sometab', 'idx_bar', ARRAY['numb', 'name'], 'whatever' ), true, 'has_index() multi-column', 'whatever', @@ -47,7 +47,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_index( 'public', 'sometab', 'idx_bar', ARRAY['name', 'numb'] ), + has_index( 'public', 'sometab', 'idx_bar', ARRAY['numb', 'name'] ), true, 'has_index() multi-column no desc', 'Index idx_bar should exist', @@ -95,7 +95,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_index( 'sometab', 'idx_bar', ARRAY['name', 'numb'], 'whatever' ), + has_index( 'sometab', 'idx_bar', ARRAY['numb', 'name'], 'whatever' ), true, 'has_index() no schema multi-column', 'whatever', @@ -103,7 +103,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_index( 'sometab', 'idx_bar', ARRAY['name', 'numb'] ), + has_index( 'sometab', 'idx_bar', ARRAY['numb', 'name'] ), true, 'has_index() no schema multi-column no desc', 'Index idx_bar should exist', @@ -144,7 +144,7 @@ SELECT * FROM check_test( -- Check failure diagnostics. SELECT * FROM check_test( - has_index( 'public', 'sometab', 'blah', ARRAY['name', 'numb'], 'whatever' ), + has_index( 'public', 'sometab', 'blah', ARRAY['numb', 'name'], 'whatever' ), false, 'has_index() missing', 'whatever', @@ -156,12 +156,12 @@ SELECT * FROM check_test( false, 'has_index() invalid', 'whatever', - ' have: idx_bar ON public.sometab(name, numb) + ' have: idx_bar ON public.sometab(numb, name) want: idx_bar ON public.sometab(name, id)' ); SELECT * FROM check_test( - has_index( 'sometab', 'blah', ARRAY['name', 'numb'], 'whatever' ), + has_index( 'sometab', 'blah', ARRAY['numb', 'name'], 'whatever' ), false, 'has_index() missing no schema', 'whatever', @@ -173,7 +173,7 @@ SELECT * FROM check_test( false, 'has_index() invalid no schema', 'whatever', - ' have: idx_bar ON sometab(name, numb) + ' have: idx_bar ON sometab(numb, name) want: idx_bar ON sometab(name, id)' ); From a7ad8f40ce1a4886fa9b3404827c080926f86366 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 24 Mar 2009 22:12:51 +0000 Subject: [PATCH 0328/1195] Eliminated (most) parens in `JOIN ON` expressions, and aligned the `ON`s. --- pgtap.sql.in | 120 +++++++++++++++++++++++++-------------------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 70384646aed4..a28f310d1b3d 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1436,7 +1436,7 @@ 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]) + JOIN generate_series(1, array_upper($2, 1)) s(i) ON a.attnum = $2[i] WHERE attrelid = $1 ORDER BY i ) @@ -1497,7 +1497,7 @@ AS 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 ( + JOIN pg_catalog.pg_depend d ON ( d.classid = 'pg_constraint'::regclass AND d.objid = k1.oid AND d.objsubid = 0 @@ -1506,7 +1506,7 @@ AS 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 ( + LEFT JOIN pg_depend d2 ON ( d2.classid = 'pg_class'::regclass AND d2.objid = ci.oid AND d2.objsubid = 0 @@ -2070,7 +2070,7 @@ 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]) + JOIN generate_series(1, array_upper($1, 1)) s(i) ON t.oid = $1[i] ORDER BY i ) $$ LANGUAGE SQL stable; @@ -2149,11 +2149,11 @@ RETURNS NAME[] AS $$ SELECT ARRAY( SELECT a.attname 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_attribute a ON (ct.oid = a.attrelid) - JOIN generate_series(0, current_setting('max_index_keys')::integer - 1) s(i) + 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_attribute a ON ct.oid = a.attrelid + JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) ON a.attnum = x.indkey[s.i] WHERE ct.relname = $2 AND ci.relname = $3 @@ -2167,10 +2167,10 @@ RETURNS NAME[] AS $$ SELECT ARRAY( SELECT a.attname 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_attribute a ON (ct.oid = a.attrelid) - JOIN generate_series(0, current_setting('max_index_keys')::integer - 1) s(i) + 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_attribute a ON ct.oid = a.attrelid + JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) ON a.attnum = x.indkey[s.i] WHERE ct.relname = $1 AND ci.relname = $2 @@ -2183,9 +2183,9 @@ CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME, NAME) RETURNS TEXT AS $$ SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) 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_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 @@ -2195,8 +2195,8 @@ CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME) RETURNS TEXT AS $$ SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) 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_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) @@ -2400,9 +2400,9 @@ DECLARE 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) + 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 @@ -2429,8 +2429,8 @@ DECLARE 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_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) @@ -2451,8 +2451,8 @@ DECLARE 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) + 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; @@ -2472,9 +2472,9 @@ DECLARE 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) + 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 @@ -2501,8 +2501,8 @@ DECLARE 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_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) @@ -2523,8 +2523,8 @@ DECLARE 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) + 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; @@ -2544,9 +2544,9 @@ DECLARE 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) + 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 @@ -2574,8 +2574,8 @@ DECLARE 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_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; @@ -2595,7 +2595,7 @@ DECLARE BEGIN SELECT x.indisclustered FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class ci ON (ci.oid = x.indexrelid) + JOIN pg_catalog.pg_class ci ON ci.oid = x.indexrelid WHERE ci.relname = $1 INTO res; @@ -2614,10 +2614,10 @@ DECLARE 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) + 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 @@ -2644,9 +2644,9 @@ DECLARE 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) + 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; @@ -2667,8 +2667,8 @@ DECLARE 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) + 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; @@ -2688,8 +2688,8 @@ DECLARE BEGIN 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) + 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 @@ -2716,7 +2716,7 @@ DECLARE BEGIN SELECT true FROM pg_catalog.pg_trigger t - JOIN pg_catalog.pg_class c ON (c.oid = t.tgrelid) + 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) @@ -2737,10 +2737,10 @@ DECLARE 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) + 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 @@ -2767,8 +2767,8 @@ DECLARE 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) + 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) @@ -2877,7 +2877,7 @@ RETURNS BOOLEAN AS $$ SELECT EXISTS( SELECT true FROM pg_catalog.pg_type t - JOIN pg_catalog.pg_namespace n ON (t.typnamespace = n.oid) + JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid WHERE t.typisdefined AND n.nspname = $1 AND t.typname = $2 @@ -3048,8 +3048,8 @@ RETURNS TEXT AS $$ 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) + 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 @@ -3077,7 +3077,7 @@ RETURNS TEXT AS $$ 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_enum e ON t.oid = e.enumtypid WHERE t.typisdefined AND pg_catalog.pg_type_is_visible(t.oid) AND t.typname = $1 From c211f7315d1bd14005e3189920b15638c70d8d05 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 24 Mar 2009 22:38:11 +0000 Subject: [PATCH 0329/1195] Use "RETURNING" instead of "=" for the `has_operator()` diagnostics. --- README.pgtap | 6 +++--- pgtap.sql.in | 12 ++++++------ sql/hastap.sql | 24 ++++++++++++------------ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/README.pgtap b/README.pgtap index a244d7b509fa..7604b288258b 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1728,7 +1728,7 @@ But you've already used `has_cast()` to make sure of that, right? 'pg_catalog', '<=', 'integer', 'boolean', - 'Operator (integer <= integer = boolean) should exist' + 'Operator (integer <= integer RETURNS boolean) should exist' ); Tests for the presence of a binary operator. If the operator exists with the @@ -1750,7 +1750,7 @@ operator, use `has_leftop()` or `has_rightop()` instead. 'pg_catalog', '!!', 'bigint', 'numeric', - 'Operator (!! bigint = numeric) should exist' + 'Operator (!! bigint RETURNS numeric) should exist' ); Tests for the presence of a left-unary operator. If the operator exists with @@ -1771,7 +1771,7 @@ value is also optional. 'bigint', 'pg_catalog', '!', 'numeric', - 'Operator (bigint ! = numeric) should exist' + 'Operator (bigint ! RETURNS numeric) should exist' ); Tests for the presence of a right-unary operator. If the operator exists with diff --git a/pgtap.sql.in b/pgtap.sql.in index a28f310d1b3d..aa10b3d9b3f8 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3526,7 +3526,7 @@ RETURNS TEXT AS $$ || quote_ident($1) || ' ' || quote_ident($2) || '.' || $3 || ' ' || quote_ident($4) - || ' = ' || quote_ident($5) || ') should exist' + || ' RETURNS ' || quote_ident($5) || ') should exist' ); $$ LANGUAGE SQL; @@ -3545,7 +3545,7 @@ RETURNS TEXT AS $$ || quote_ident($1) || ' ' || $2 || ' ' || quote_ident($3) - || ' = ' || quote_ident($4) || ') should exist' + || ' RETURNS ' || quote_ident($4) || ') should exist' ); $$ LANGUAGE SQL; @@ -3582,7 +3582,7 @@ RETURNS TEXT AS $$ 'Operator (' || quote_ident($1) || '.' || $2 || ' ' || quote_ident($3) - || ' = ' || quote_ident($4) || ') should exist' + || ' RETURNS ' || quote_ident($4) || ') should exist' ); $$ LANGUAGE SQL; @@ -3599,7 +3599,7 @@ RETURNS TEXT AS $$ _op_exists(NULL, $1, $2, $3 ), 'Operator (' || $1 || ' ' || quote_ident($2) - || ' = ' || quote_ident($3) || ') should exist' + || ' RETURNS ' || quote_ident($3) || ') should exist' ); $$ LANGUAGE SQL; @@ -3632,7 +3632,7 @@ RETURNS TEXT AS $$ 'Operator (' || quote_ident($1) || ' ' || quote_ident($2) || '.' || $3 - || ' = ' || quote_ident($4) || ') should exist' + || ' RETURNS ' || quote_ident($4) || ') should exist' ); $$ LANGUAGE SQL; @@ -3649,7 +3649,7 @@ RETURNS TEXT AS $$ _op_exists($1, $2, NULL, $3 ), 'Operator (' || quote_ident($1) || ' ' || $2 - || ' = ' || quote_ident($3) || ') should exist' + || ' RETURNS ' || quote_ident($3) || ') should exist' ); $$ LANGUAGE SQL; diff --git a/sql/hastap.sql b/sql/hastap.sql index c8df8f7fc49a..9fdebd589599 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -1080,7 +1080,7 @@ SELECT * FROM check_test( has_operator( 'integer', 'pg_catalog', '<=', 'integer', 'boolean'::name ), true, 'has_operator( left, schema, name, right, result )', - 'Operator ("integer" pg_catalog.<= "integer" = "boolean") should exist', + 'Operator ("integer" pg_catalog.<= "integer" RETURNS "boolean") should exist', '' ); @@ -1096,7 +1096,7 @@ SELECT * FROM check_test( has_operator( 'integer', '<=', 'integer', 'boolean'::name ), true, 'has_operator( left, name, right, result )', - 'Operator ("integer" <= "integer" = "boolean") should exist', + 'Operator ("integer" <= "integer" RETURNS "boolean") should exist', '' ); @@ -1128,7 +1128,7 @@ SELECT * FROM check_test( has_operator( 'integer', 'pg_catalog', '<=', 'text', 'boolean'::name ), false, 'has_operator( left, schema, name, right, result ) fail', - 'Operator ("integer" pg_catalog.<= text = "boolean") should exist', + 'Operator ("integer" pg_catalog.<= text RETURNS "boolean") should exist', '' ); @@ -1144,7 +1144,7 @@ SELECT * FROM check_test( has_operator( 'integer', '<=', 'text', 'boolean'::name ), false, 'has_operator( left, name, right, result ) fail', - 'Operator ("integer" <= text = "boolean") should exist', + 'Operator ("integer" <= text RETURNS "boolean") should exist', '' ); @@ -1179,7 +1179,7 @@ SELECT * FROM check_test( has_leftop( 'pg_catalog', '!!', 'bigint', 'numeric'::name ), true, 'has_leftop( schema, name, right, result )', - 'Operator (pg_catalog.!! "bigint" = "numeric") should exist', + 'Operator (pg_catalog.!! "bigint" RETURNS "numeric") should exist', '' ); @@ -1195,7 +1195,7 @@ SELECT * FROM check_test( has_leftop( '!!', 'bigint', 'numeric'::name ), true, 'has_leftop( name, right, result )', - 'Operator (!! "bigint" = "numeric") should exist', + 'Operator (!! "bigint" RETURNS "numeric") should exist', '' ); @@ -1227,7 +1227,7 @@ SELECT * FROM check_test( has_leftop( 'pg_catalog', '!!', 'text', 'numeric'::name ), false, 'has_leftop( schema, name, right, result ) fail', - 'Operator (pg_catalog.!! text = "numeric") should exist', + 'Operator (pg_catalog.!! text RETURNS "numeric") should exist', '' ); @@ -1243,7 +1243,7 @@ SELECT * FROM check_test( has_leftop( '!!', 'text', 'numeric'::name ), false, 'has_leftop( name, right, result ) fail', - 'Operator (!! text = "numeric") should exist', + 'Operator (!! text RETURNS "numeric") should exist', '' ); @@ -1278,7 +1278,7 @@ SELECT * FROM check_test( has_rightop( 'bigint', 'pg_catalog', '!', 'numeric'::name ), true, 'has_rightop( left, schema, name, result )', - 'Operator ("bigint" pg_catalog.! = "numeric") should exist', + 'Operator ("bigint" pg_catalog.! RETURNS "numeric") should exist', '' ); @@ -1294,7 +1294,7 @@ SELECT * FROM check_test( has_rightop( 'bigint', '!', 'numeric'::name ), true, 'has_rightop( left, name, result )', - 'Operator ("bigint" ! = "numeric") should exist', + 'Operator ("bigint" ! RETURNS "numeric") should exist', '' ); @@ -1326,7 +1326,7 @@ SELECT * FROM check_test( has_rightop( 'text', 'pg_catalog', '!', 'numeric'::name ), false, 'has_rightop( left, schema, name, result ) fail', - 'Operator (text pg_catalog.! = "numeric") should exist', + 'Operator (text pg_catalog.! RETURNS "numeric") should exist', '' ); @@ -1342,7 +1342,7 @@ SELECT * FROM check_test( has_rightop( 'text', '!', 'numeric'::name ), false, 'has_rightop( left, name, result ) fail', - 'Operator (text ! = "numeric") should exist', + 'Operator (text ! RETURNS "numeric") should exist', '' ); From b45d5ed1c12d4a873939ace915fc3a130ec3f6e2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 29 Mar 2009 16:15:05 +0000 Subject: [PATCH 0330/1195] Compatibility. --- README.pgtap | 8 ++++---- compat/install-8.0.patch | 18 +++++++++--------- compat/install-8.1.patch | 16 ++++++++-------- compat/install-8.2.patch | 12 ++++++------ compat/uninstall-8.0.patch | 6 +++--- compat/uninstall-8.2.patch | 6 +++--- sql/hastap.sql | 6 +++--- 7 files changed, 36 insertions(+), 36 deletions(-) diff --git a/README.pgtap b/README.pgtap index 7604b288258b..6f813be8f604 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2508,11 +2508,11 @@ Suported Versions pgTAP has been tested on the following builds of PostgreSQL: * PostgreSQL 8.4devel on i386-apple-darwin9.6.0 -* PostgreSQL 8.3.5 on i386-apple-darwin9.5.0 -* PostgreSQL 8.2.11 on i386-apple-darwin9.5.0 -* PostgreSQL 8.1.15 on i686-apple-darwin9.6.0 -* PostgreSQL 8.0.19 on i686-apple-darwin9.6.0 +* PostgreSQL 8.3.7 on i386-apple-darwin9.6.0 * PostgreSQL 8.3.6 on i386-redhat-linux-gnu +* PostgreSQL 8.2.13 on i386-apple-darwin9.6.0 +* PostgreSQL 8.1.17 on i686-apple-darwin9.6.0 +* PostgreSQL 8.0.21 on i686-apple-darwin9.6.0 If you know of others, please submit them! Use `psql -d template1 -c 'SELECT VERSION()'` to get the formal build version and OS. diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index 385d7b045c38..e2b42788e458 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.orig 2009-02-20 17:35:51.000000000 -0800 -+++ pgtap.sql 2009-02-20 17:36:21.000000000 -0800 +--- pgtap.sql.saf 2009-03-28 15:55:36.000000000 -0400 ++++ pgtap.sql 2009-03-28 15:55:45.000000000 -0400 @@ -12,6 +12,22 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA, public; @@ -169,7 +169,7 @@ END; $$ LANGUAGE plpgsql; -@@ -3037,39 +3070,6 @@ +@@ -3041,39 +3074,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -209,7 +209,7 @@ CREATE OR REPLACE FUNCTION _is_super( NAME ) RETURNS BOOLEAN AS $$ SELECT usesuper -@@ -3173,7 +3173,7 @@ +@@ -3177,7 +3177,7 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _grolist ( NAME ) @@ -218,7 +218,7 @@ SELECT grolist FROM pg_catalog.pg_group WHERE groname = $1; $$ LANGUAGE sql; -@@ -3235,6 +3235,7 @@ +@@ -3622,6 +3622,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -226,7 +226,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -3245,8 +3246,10 @@ +@@ -3632,8 +3633,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. @@ -239,7 +239,7 @@ -- Now delete those results. EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; -@@ -3355,7 +3358,7 @@ +@@ -3742,7 +3745,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -248,7 +248,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -3363,8 +3366,8 @@ +@@ -3750,8 +3753,8 @@ -- 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. @@ -259,7 +259,7 @@ END LOOP; END LOOP; RETURN; -@@ -3424,116 +3427,6 @@ +@@ -3811,116 +3814,6 @@ END $$ LANGUAGE plpgsql; diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index 9a197494dc19..04356429d8aa 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ ---- pgtap.sql.orig 2009-02-20 17:08:42.000000000 -0800 -+++ pgtap.sql 2009-02-20 17:08:42.000000000 -0800 -@@ -3355,7 +3355,7 @@ +--- pgtap.sql.orig 2009-03-28 15:49:36.000000000 -0400 ++++ pgtap.sql 2009-03-28 15:49:36.000000000 -0400 +@@ -3742,7 +3742,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -9,7 +9,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -3363,8 +3363,8 @@ +@@ -3750,8 +3750,8 @@ -- 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. @@ -20,7 +20,7 @@ END LOOP; END LOOP; RETURN; -@@ -3432,14 +3432,14 @@ +@@ -3819,14 +3819,14 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -37,7 +37,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -3454,15 +3454,15 @@ +@@ -3841,15 +3841,15 @@ IF verbose THEN RETURN NEXT diag(tests[i] || '()'); END IF; -- Run the setup functions. @@ -57,7 +57,7 @@ -- Remember how many failed and then roll back. num_faild := num_faild + num_failed(); -@@ -3477,7 +3477,7 @@ +@@ -3864,7 +3864,7 @@ END LOOP; -- Run the shutdown functions. @@ -66,7 +66,7 @@ -- Raise an exception to rollback any changes. RAISE EXCEPTION '__TAP_ROLLBACK__'; -@@ -3488,8 +3488,8 @@ +@@ -3875,8 +3875,8 @@ END IF; END; -- Finish up. diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index f92a978358e0..1daa9e2b0e3b 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,6 +1,6 @@ ---- pgtap.sql.orig 2009-02-20 16:18:40.000000000 -0800 -+++ pgtap.sql 2009-02-20 16:20:17.000000000 -0800 -@@ -3037,63 +3037,6 @@ +--- pgtap.sql.orig 2009-03-28 15:33:47.000000000 -0400 ++++ pgtap.sql 2009-03-28 15:34:28.000000000 -0400 +@@ -3041,63 +3041,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -11,8 +11,8 @@ - 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) +- 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 @@ -40,7 +40,7 @@ - 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_enum e ON t.oid = e.enumtypid - WHERE t.typisdefined - AND pg_catalog.pg_type_is_visible(t.oid) - AND t.typname = $1 diff --git a/compat/uninstall-8.0.patch b/compat/uninstall-8.0.patch index 53d70144b910..a3567c47bdef 100644 --- a/compat/uninstall-8.0.patch +++ b/compat/uninstall-8.0.patch @@ -1,6 +1,6 @@ ---- uninstall_pgtap.sql.orig 2009-02-20 17:29:32.000000000 -0800 -+++ uninstall_pgtap.sql 2009-02-20 17:30:34.000000000 -0800 -@@ -50,11 +50,6 @@ +--- uninstall_pgtap.sql.saf 2009-03-28 15:58:36.000000000 -0400 ++++ uninstall_pgtap.sql 2009-03-28 15:58:43.000000000 -0400 +@@ -90,11 +94,6 @@ DROP FUNCTION has_user( NAME ); DROP FUNCTION has_user( NAME, TEXT ); DROP FUNCTION _is_super( NAME ); diff --git a/compat/uninstall-8.2.patch b/compat/uninstall-8.2.patch index 6c52c9d5747e..2230f048bc29 100644 --- a/compat/uninstall-8.2.patch +++ b/compat/uninstall-8.2.patch @@ -1,6 +1,6 @@ ---- uninstall_pgtap.sql.orig 2009-02-20 16:24:08.000000000 -0800 -+++ uninstall_pgtap.sql 2009-02-20 16:24:40.000000000 -0800 -@@ -43,10 +43,6 @@ +--- uninstall_pgtap.sql.orig 2009-03-28 15:33:53.000000000 -0400 ++++ uninstall_pgtap.sql 2009-03-28 15:35:20.000000000 -0400 +@@ -83,10 +83,6 @@ DROP FUNCTION has_role( NAME ); DROP FUNCTION has_role( NAME, TEXT ); DROP FUNCTION _has_role( NAME ); diff --git a/sql/hastap.sql b/sql/hastap.sql index 9fdebd589599..f8c8daf229f9 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -992,7 +992,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - cast_context_is( 'cidr', 'text', 'assignment', 'desc' ), + cast_context_is( 'bigint', 'smallint', 'assignment', 'desc' ), true, 'cast_context_is( src, targ, assignment, desc )', 'desc', @@ -1000,7 +1000,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - cast_context_is( 'cidr', 'text', 'a', 'desc' ), + cast_context_is( 'bigint', 'smallint', 'a', 'desc' ), true, 'cast_context_is( src, targ, a, desc )', 'desc', @@ -1008,7 +1008,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - cast_context_is( 'cidr', 'text', 'ASS', 'desc' ), + cast_context_is( 'bigint', 'smallint', 'ASS', 'desc' ), true, 'cast_context_is( src, targ, ASS, desc )', 'desc', From fef6c2e0fe0f6868071662422466463aafbf9520 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 29 Mar 2009 19:04:59 +0000 Subject: [PATCH 0331/1195] Timestamped for 0.20. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 9daf402c0b95..fba302da2945 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.20 +0.20 2009-03-29T19:05:40 ------------------------- * Changed the names of the functions tested in `sql/do_tap.sql` and `sql/runtests.sql` so that they are less likely to be ordered differently From f7c3cd4a493a99062002fbdeff99a9fa1fcb27e2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 30 Mar 2009 00:19:52 +0000 Subject: [PATCH 0332/1195] * Incremented verison number to 0.21. * Fixed encoding issues with the `pg_prove` documentation. --- Changes | 3 +++ Makefile | 4 ++-- bin/pg_prove | 22 ++++++++++++---------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Changes b/Changes index fba302da2945..b506df30192b 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Revision history for pgTAP ========================== +0.21 +------------------------- + 0.20 2009-03-29T19:05:40 ------------------------- * Changed the names of the functions tested in `sql/do_tap.sql` and diff --git a/Makefile b/Makefile index 8e1ca3ef0402..8528cf565757 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') PGVER_MAJOR = $(shell echo $(VERSION) | awk -F. '{ print ($$1 + 0) }') PGVER_MINOR = $(shell echo $(VERSION) | awk -F. '{ print ($$2 + 0) }') PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') -PGTAP_VERSION = 0.20 +PGTAP_VERSION = 0.21 # Compile the C code only if we're on 8.3 or older. ifneq ($(PGVER_MINOR), 4) @@ -217,4 +217,4 @@ test: test_setup.sql html: markdown -F 0x1000 README.pgtap > readme.html perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$4; $$lab = $$4; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$4 ? "$$4()" : $$3) . "" } END { print "
  • \n
\n" }' readme.html > toc.html - PERL5LIB=/Users/david/dev/perl/Pod-Simple/lib perldoc -MPod::Simple::XHTML bin/pg_prove > pg_prove.html + PERL5LIB=/Users/david/dev/perl/Pod-Simple/lib perldoc -MPod::Simple::XHTML -d pg_prove.html -w html_header_tags:'' bin/pg_prove diff --git a/bin/pg_prove b/bin/pg_prove index 4e6f6b879d6e..919e7e5de9de 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -6,7 +6,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.20'; +our $VERSION = '0.21'; Getopt::Long::Configure (qw(bundling)); @@ -131,6 +131,8 @@ $harness_class->new({ __END__ +=encoding utf8 + =head1 Name pg_prove - A command-line tool for running and harnessing pgTAP tests @@ -152,7 +154,7 @@ functions. =head2 Test Scripts pgTAP test scripts should consist of a series of SQL statements that output -TAP. Here's a simple example that assumes that the pgTAP functions have been +TAP. Here’s a simple example that assumes that the pgTAP functions have been installed in the database: -- Start transaction and plan the tests. @@ -167,7 +169,7 @@ installed in the database: ROLLBACK; Now run the tests by passing the list of SQL script names to C. -Here's what it looks like when the pgTAP tests are run with C +Here’s what it looks like when the pgTAP tests are run with C % pg_prove -U postgres sql/*.sql sql/coltap.....ok @@ -206,9 +208,9 @@ C by using the C<--runtests> option. Result: PASS Be sure to pass the C<--schema> option if your test functions are all in one -schema, and the C<--match> option if they have names that don't start with -"test". For example, if you have all of your test functions in "test" schema -and I with "test", run the tests like so: +schema, and the C<--match> option if they have names that don’t start with +“test”. For example, if you have all of your test functions in “test” schema +and I with “test,” run the tests like so: pg_prove -d myapp --schema test --match 'test$' @@ -319,15 +321,15 @@ true value. pg_prove --runtests pg_prove -r -Don't run any test scripts, but just use the C pgTAP function to +Don’t run any test scripts, but just use the C pgTAP function to run xUnit tests. This ends up looking like a single test script has been run, when in fact no test scripts have been run. Instead, C tells C to run something like: psql --command 'SELECT * FROM runtests()' -You should use this option when you've written your tests in xUnit style, -where they're all defined in test functions already loaded in the database. +You should use this option when you’ve written your tests in xUnit style, +where they’re all defined in test functions already loaded in the database. =item C<-s> @@ -356,7 +358,7 @@ run something like: psql --command "SELECT * FROM runtests('_test_'::text)" -This will run any visible functions with the string "_test_" in their names. +This will run any visible functions with the string “_test_” in their names. This can be especially useful if you just want to run a single test in a given schema. For example, this: From 906a9ec8de4a8f5e1908d52b78d39e327279b07c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 2 Apr 2009 11:23:12 -0700 Subject: [PATCH 0333/1195] Added references to the mail list and the Git repository to the `README`. --- README.pgtap | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.pgtap b/README.pgtap index 6f813be8f604..89ff7aa63017 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2517,8 +2517,25 @@ pgTAP has been tested on the following builds of PostgreSQL: If you know of others, please submit them! Use `psql -d template1 -c 'SELECT VERSION()'` to get the formal build version and OS. +Public Repository +----------------- + +The source code for pgTAP is available on [GitHub][]. Please feel free to fork +and contribute! + +[GitHub]: http://github.com/theory/pgtap/tree/ + +Mail List +--------- + +Join the pgTAP community by subscribing to the [mail list][]. All questions, +comments, suggestions, and bug reports are welcomed there. + +[mail list]: http://pgfoundry.org/mail/?group_id=1000389 + Author ------ + [David E. Wheeler](http://justatheory.com/) Credits From 8c23f0f90eae76b3729033f552e9e819ce664659 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 2 Apr 2009 11:27:09 -0700 Subject: [PATCH 0334/1195] Made the links inline, and updated the mail list link. --- README.pgtap | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/README.pgtap b/README.pgtap index 89ff7aa63017..94325627bca1 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2520,19 +2520,17 @@ If you know of others, please submit them! Use Public Repository ----------------- -The source code for pgTAP is available on [GitHub][]. Please feel free to fork -and contribute! - -[GitHub]: http://github.com/theory/pgtap/tree/ +The source code for pgTAP is available on +[GitHub](http://github.com/theory/pgtap/tree/). Please feel free to fork and +contribute! Mail List --------- -Join the pgTAP community by subscribing to the [mail list][]. All questions, +Join the pgTAP community by subscribing to the [pgtap-users mail +list](http://pgfoundry.org/mailman/listinfo/pgtap-users). All questions, comments, suggestions, and bug reports are welcomed there. -[mail list]: http://pgfoundry.org/mail/?group_id=1000389 - Author ------ From 9e7a5463a5229a9468c45616ac94c462a7682097 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 2 Apr 2009 11:37:45 -0700 Subject: [PATCH 0335/1195] Updated URL for pgtap.sql.in for those who might be simply downloading and distributing the one file. --- pgtap.sql.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index aa10b3d9b3f8..fabcf33af359 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2,7 +2,7 @@ -- testing. It is distributed under the revised FreeBSD license. You can -- find the original here: -- --- $HeadURL: https://svn.kineticode.com/pgtap/trunk/pgtap.sql.in $ +-- http://github.com/theory/pgtap/raw/master/pgtap.sql.in -- -- The home page for the pgTAP project is: -- From 9eb32f8f3a161c97c34eb71899a1f9def81b3583 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 2 Apr 2009 16:22:15 -0700 Subject: [PATCH 0336/1195] Ignore file for Git. --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000000..1b97b5799837 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +pgtap.sql +uninstall_pgtap.sql +test_setup.sql +results +pgtap.so +regression.* +*.html +bbin From 525e18accf590532f4845985376e0603eca6d3c4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 20 Apr 2009 11:12:27 -0700 Subject: [PATCH 0337/1195] Fixed option processing with Getopt::Long 2.38 or higher. --- Changes | 2 ++ bin/pg_prove | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index b506df30192b..94128a98dde7 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,8 @@ Revision history for pgTAP 0.21 ------------------------- +* Fixed a bug in the processing of the `--schema` and `--match` options that + only shows up in Getopt::Long 2.38 or higher. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/bin/pg_prove b/bin/pg_prove index 919e7e5de9de..bb210803b463 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -20,8 +20,8 @@ Getopt::Long::GetOptions( 'port|p=s' => \$opts->{port}, 'pset|P=s%' => \$opts->{pset}, 'runtests|r' => \$opts->{runtests}, - 'schema|s|=s' => \$opts->{schema}, - 'match|x|=s' => \$opts->{match}, + 'schema|s=s' => \$opts->{schema}, + 'match|x=s' => \$opts->{match}, 'timer|t' => \$opts->{timer}, 'color|c!' => \$opts->{color}, 'formatter|f=s' => \$opts->{formatter}, From f8a7d0dc5aaa9aeafebf809aa25403b8c68df1ab Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 20 Apr 2009 11:43:40 -0700 Subject: [PATCH 0338/1195] Fixed some spacing issues. I was missing a space, so those lines were not displayed as code. --- README.pgtap | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.pgtap b/README.pgtap index 94325627bca1..b9750e9cadac 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1479,7 +1479,7 @@ incorrect, the diagnostics will look more like this: 'Index "myindex" should be unique' ); - SELECT index_is_unique( 'sometable', 'myindex' ); + SELECT index_is_unique( 'sometable', 'myindex' ); Tests whether an index is unique. @@ -1495,7 +1495,7 @@ Tests whether an index is unique. 'Index "myindex" should be on a primary key' ); - SELECT index_is_primary( 'sometable', 'myindex' ); + SELECT index_is_primary( 'sometable', 'myindex' ); Tests whether an index is on a primary key. @@ -1511,7 +1511,7 @@ Tests whether an index is on a primary key. 'Table sometable should be clustered on "myindex"' ); - SELECT is_clustered( 'sometable', 'myindex' ); + SELECT is_clustered( 'sometable', 'myindex' ); Tests whether a table is clustered on the given index. A table is clustered on an index when the SQL command `CLUSTER TABLE INDEXNAME` has been executed. @@ -1531,7 +1531,7 @@ order defined by the index. 'Index "myindex" should be a GIST index' ); - SELECT index_is_type( 'myindex', 'gin' ); + SELECT index_is_type( 'myindex', 'gin' ); Tests to ensure that an index is of a particular type. At the time of this writing, the supported types are: From 85b01bcee3fb3721154655a2858fb542ab72e733 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Apr 2009 12:28:46 -0700 Subject: [PATCH 0339/1195] Typos fixed by Gabrielle Roth. --- Changes | 1 + README.pgtap | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 94128a98dde7..fe3c6d5c511b 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,7 @@ Revision history for pgTAP ------------------------- * Fixed a bug in the processing of the `--schema` and `--match` options that only shows up in Getopt::Long 2.38 or higher. +* A few doc typos fixed thanks to Gabrielle Roth. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index b9750e9cadac..a99d0d77dba4 100644 --- a/README.pgtap +++ b/README.pgtap @@ -312,7 +312,7 @@ Or this? not ok 5 - simple exponential ok 6 - force == mass * acceleration -The later gives you some idea of what failed. It also makes it easier to find +The latter gives you some idea of what failed. It also makes it easier to find the test in your script, simply search for "simple exponential". All test functions take a name argument. It's optional, but highly suggested @@ -574,7 +574,7 @@ A.](http://www.postgresql.org/docs/current/static/errcodes-appendix.html documentation](http://www.postgresql.org/docs/current/static/). The third argument is an error message. This will be most useful for functions -you've written that raise exceptions, so that you can test the excption +you've written that raise exceptions, so that you can test the exception message that you've thrown. Otherwise, for core errors, you'll need to be careful of localized error messages. From ef3f850078361cc75f617280f056dbc481ea4f11 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Apr 2009 18:30:03 -0700 Subject: [PATCH 0340/1195] More typos and a bug fix from Gabrielle Roth. --- Changes | 3 ++- README.pgtap | 4 ++-- expected/util.out | 4 ++-- sql/util.sql | 8 ++++---- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Changes b/Changes index fe3c6d5c511b..a7d4f1976612 100644 --- a/Changes +++ b/Changes @@ -5,7 +5,8 @@ Revision history for pgTAP ------------------------- * Fixed a bug in the processing of the `--schema` and `--match` options that only shows up in Getopt::Long 2.38 or higher. -* A few doc typos fixed thanks to Gabrielle Roth. +* A few doc and test typos fixed thanks to Gabrielle Roth. +* Fixed failing test on Solaris 10 on Intel thanks to Gabrielle Roth. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index a99d0d77dba4..8a2926b18236 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2084,8 +2084,8 @@ version of PostgreSQL. For example: The revision level is in the tens position, the minor version in the thousands position, and the major version in the ten thousands position and above (assuming PostgreSQL 10 is ever released, it will be in the hundred thousands -position). This value is the same as the "sever_version_num" setting available -in PostgreSQL 8.2 and higher, but supported by this function back to +position). This value is the same as the "server_version_num" setting +available in PostgreSQL 8.2 and higher, but supported by this function back to PostgreSQL 8.0: try=% select current_setting( 'server_version_num'), pg_version_num(); diff --git a/expected/util.out b/expected/util.out index cfd7a1459056..219766842a46 100644 --- a/expected/util.out +++ b/expected/util.out @@ -4,9 +4,9 @@ ok 1 - pg_type(int) should work ok 2 - pg_type(numeric) should work ok 3 - pg_type(text) should work ok 4 - pg_version() should return text -ok 5 - pg_version() should return same as "sever_version" setting +ok 5 - pg_version() should return same as "server_version" setting ok 6 - pg_version() should work -ok 7 - pg_version() should return same as "sever_version" setting +ok 7 - pg_version() should return same as "server_version" setting ok 8 - pg_version_num() should return integer ok 9 - pg_version_num() should be correct ok 10 - os_name() should output something like an OS name diff --git a/sql/util.sql b/sql/util.sql index 707884989948..f6cd661e4e0a 100644 --- a/sql/util.sql +++ b/sql/util.sql @@ -14,7 +14,7 @@ SELECT is( pg_typeof( pg_version() ), 'text', 'pg_version() should return text' SELECT is( pg_version(), current_setting( 'server_version'), - 'pg_version() should return same as "sever_version" setting' + 'pg_version() should return same as "server_version" setting' ); SELECT matches( pg_version(), @@ -23,11 +23,11 @@ SELECT matches( ); SELECT CASE WHEN pg_version_num() < 81000 - THEN pass( 'pg_version() should return same as "sever_version" setting' ) + THEN pass( 'pg_version() should return same as "server_version" setting' ) ELSE is( pg_version_num(), current_setting( 'server_version_num')::integer, - 'pg_version() should return same as "sever_version" setting' + 'pg_version() should return same as "server_version" setting' ) END; @@ -44,7 +44,7 @@ SELECT matches( SELECT matches( os_name(), - '^[[:alpha:]]+$', + '^[[:alnum:]]+$', 'os_name() should output something like an OS name' ); From b6e74784c1245c91d183e6c1f5db089a4a5f1132 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 18 May 2009 12:25:44 -0700 Subject: [PATCH 0341/1195] Fixed failing test on 8.4 beta. --- Changes | 1 + sql/util.sql | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index a7d4f1976612..7a8bc553ccea 100644 --- a/Changes +++ b/Changes @@ -7,6 +7,7 @@ Revision history for pgTAP only shows up in Getopt::Long 2.38 or higher. * A few doc and test typos fixed thanks to Gabrielle Roth. * Fixed failing test on Solaris 10 on Intel thanks to Gabrielle Roth. +* Fixed a failing test for the version number string on 8.4 beta. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/sql/util.sql b/sql/util.sql index f6cd661e4e0a..3621605679c0 100644 --- a/sql/util.sql +++ b/sql/util.sql @@ -18,7 +18,7 @@ SELECT is( ); SELECT matches( pg_version(), - '^8[.][[:digit:]]{1,2}([.][[:digit:]]{1,2}|devel)$', + '^8[.][[:digit:]]{1,2}([.][[:digit:]]{1,2}|devel|beta[[:digit:]]+)$', 'pg_version() should work' ); From 620f64d293ce790f2536523ec593a5642b8b1fec Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 18 May 2009 22:26:33 -0700 Subject: [PATCH 0342/1195] Added `performs_ok()`. --- Changes | 1 + README.pgtap | 68 ++++++++++++++++++++++++++++------ expected/perform.out | 20 ++++++++++ pgtap.sql.in | 88 +++++++++++++++++++++++++++++++++----------- sql/perform.sql | 66 +++++++++++++++++++++++++++++++++ 5 files changed, 209 insertions(+), 34 deletions(-) create mode 100644 expected/perform.out create mode 100644 sql/perform.sql diff --git a/Changes b/Changes index 7a8bc553ccea..c1221df4ef7e 100644 --- a/Changes +++ b/Changes @@ -8,6 +8,7 @@ Revision history for pgTAP * A few doc and test typos fixed thanks to Gabrielle Roth. * Fixed failing test on Solaris 10 on Intel thanks to Gabrielle Roth. * Fixed a failing test for the version number string on 8.4 beta. +* Added `performs_ok()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index 8a2926b18236..5fd539f434d8 100644 --- a/README.pgtap +++ b/README.pgtap @@ -618,6 +618,40 @@ For example: Idea borrowed from the Test::Exception Perl module. +### `performs_ok ( sql, milliseconds, description )` ### +### `performs_ok ( sql, milliseconds )` ### + + SELECT performs_ok( + 'SELECT id FROM try WHERE name = ''Larry''', + 250, + 'A select by name should be fast' + ); + +This function makes sure that an SQL statement performs well. It does so by +timing its execution, and failing if execution takes longer than the specified +amount of time. + +The first argument should be a string representing the query to be executed. +`throws_ok()` will use the PL/pgSQL `EXECUTE` statement to execute it and +catch any exception. + +The second argument is the maximum number of milliseconds it should take for +the SQL statement to execute. This argument is numeric, so you can even use +fractions of milliseconds if it floats your boat. + +The third argument is the usual description. If not provided, `performs_ok()` +will generate a placeholder description "Should run in less than $milliseconds +ms". You'll likely want to provide your own description if you have more than +a couple of these in a test script or function. + +Should a `performs_ok()` test fail it produces appropriate diagnostic +messages. For example: + + not ok 19 - The lookup should be fast! + # Failed test 19: "The lookup should be fast!" + # runtime: 200.266 ms + # exceeds: 200 ms + A Wicked Schema --------------- @@ -2303,6 +2337,7 @@ Testing Test Functions Now you've written your test function. So how do you test it? Why, with this handy-dandy test function! +### `check_test( test_output, is_ok, name, want_description, want_diag, match_diag )` ### ### `check_test( test_output, is_ok, name, want_description, want_diag )` ### ### `check_test( test_output, is_ok, name, want_description )` ### ### `check_test( test_output, is_ok, name )` ### @@ -2334,6 +2369,9 @@ function. For the impatient, the arguments are: Optional. * `:want_diag` - Expected diagnostic message output during the execution of a test. Must always follow whatever is output by the call to `ok()`. +* `:match_diag` - Use `matches()` to compare the diagnostics rather than + `:is()`. Useful for those situations where you're not sure what will be in + the output, but you can match it with a regular expression. Now, on with the detailed documentation. At its simplest, you just pass it the output of your test (and it must be one and **only one** test function's @@ -2388,21 +2426,21 @@ See how there are two tests run for a single call to `check_test()`? Be sure to adjust your plan accordingly. Also note how the test name was used in the descriptions for both tests. -If the test had failed, it would output a nice diagnostics (internally it just -uses `is()` to compare the strings): +If the test had failed, it would output a nice diagnostics. Internally it just +uses `is()` to compare the strings: not ok 43 - lc_eq() test should have the proper description # Failed test 43: "lc_eq() test should have the proper description" # have: 'this is this' # want: 'this is THIS' -The fifth argument, also optional, of course, compares the diagnostics -generated during the test to an expected string. Such diagnostics **must** -follow whatever is output by the call to `ok()` in your test. Your test -fuction should not call `diag()` until after it calls `ok()` or things will -get truly funky. +The fifth argument, `:want_diag`, which is also optional, compares the +diagnostics generated during the test to an expected string. Such diagnostics +**must** follow whatever is output by the call to `ok()` in your test. Your +test fuction should not call `diag()` until after it calls `ok()` or things +will get truly funky. -Assumign you've followed that rule in your `lc_eq()` tset function, to see +Assuming you've followed that rule in your `lc_eq()` tset function, to see what happens when a `lc_eq()` fails. Write your test to test the diagnostics like so: @@ -2430,10 +2468,16 @@ like a description failure would, something like this: # want: Have: this # Want: THat -I realize that can be a bit confusing, given the various haves and wants, but -it gets the job done. Of course, if your diagnostics use something other than -indented "have" and "want", such failures will be easier to read. But either -way, do test your diagnostics! +If you pass in the optional sixth argument, `:match_diag`, the `:want_diag` +argument will be compared to the actual diagnostic output using `matches()` +instead of `is()`. This allows you to use a regular expression in the +`:want_diag` argument to match the output, for those situations where some +part of the output might vary, such as time-based diagnostics. + +I realize that all of this can be a bit confusing, given the various haves and +wants, but it gets the job done. Of course, if your diagnostics use something +other than indented "have" and "want", such failures will be easier to read. +But either way, *do* test your diagnostics! Compatibility ============= diff --git a/expected/perform.out b/expected/perform.out new file mode 100644 index 000000000000..c3072b14d6c9 --- /dev/null +++ b/expected/perform.out @@ -0,0 +1,20 @@ +\unset ECHO +1..18 +ok 1 - simple select should pass +ok 2 - simple select should have the proper description +ok 3 - simple select should have the proper diagnostics +ok 4 - simple select no desc should pass +ok 5 - simple select no desc should have the proper description +ok 6 - simple select no desc should have the proper diagnostics +ok 7 - simple select numeric should pass +ok 8 - simple select numeric should have the proper description +ok 9 - simple select numeric should have the proper diagnostics +ok 10 - simple select fail should fail +ok 11 - simple select fail should have the proper description +ok 12 - simple select fail should have the proper diagnostics +ok 13 - simple select no desc fail should fail +ok 14 - simple select no desc fail should have the proper description +ok 15 - simple select no desc fail should have the proper diagnostics +ok 16 - simple select no desc numeric fail should fail +ok 17 - simple select no desc numeric fail should have the proper description +ok 18 - simple select no desc numeric fail should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index fabcf33af359..a2783c30b95c 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -374,7 +374,7 @@ 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' ) + E'\n doesn''t match: ' || COALESCE( quote_literal(rx), 'NULL' ) ) END; END; $$ LANGUAGE plpgsql; @@ -768,6 +768,35 @@ 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 ALIAS FOR $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; + CREATE OR REPLACE FUNCTION _rexists ( CHAR, NAME, NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( @@ -3669,21 +3698,22 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; --- check_test( test_output, pass, name, description, diag ) -CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ) +-- 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; + 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'); @@ -3741,11 +3771,19 @@ BEGIN adiag := replace( substring(adiag from 3), E'\n# ', E'\n' ); -- Now compare the diagnostics. - RETURN NEXT is( - adiag, - ediag, - descr || 'have the proper 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 @@ -3753,22 +3791,28 @@ BEGIN 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 ); + 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 ); + 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 ); + SELECT * FROM check_test( $1, $2, NULL, NULL, NULL, FALSE ); $$ LANGUAGE sql; diff --git a/sql/perform.sql b/sql/perform.sql new file mode 100644 index 000000000000..7570760137a4 --- /dev/null +++ b/sql/perform.sql @@ -0,0 +1,66 @@ +\unset ECHO +\i test_setup.sql + +SELECT plan(18); +--SELECT * FROM no_plan(); + +/****************************************************************************/ +-- Test performs_ok(). +SELECT * FROM check_test( + performs_ok( 'SELECT TRUE', 500, 'whatever' ), + true, + 'simple select', + 'whatever', + '' +); + +SELECT * FROM check_test( + performs_ok( 'SELECT TRUE', 500 ), + true, + 'simple select no desc', + 'Should run in less than 500 ms', + '' +); + +SELECT * FROM check_test( + performs_ok( 'SELECT TRUE', 99.99 ), + true, + 'simple select numeric', + 'Should run in less than 99.99 ms', + '' +); + +SELECT * FROM check_test( + performs_ok( 'SELECT TRUE', 0, 'whatever' ), + false, + 'simple select fail', + 'whatever', + ' runtime: [[:digit:]]+([.][[:digit:]]+)? ms + exceeds: 0 ms', + true +); + +SELECT * FROM check_test( + performs_ok( 'SELECT TRUE', 0 ), + false, + 'simple select no desc fail', + 'Should run in less than 0 ms', + ' runtime: [[:digit:]]+([.][[:digit:]]+)? ms + exceeds: 0 ms', + true +); + +SELECT * FROM check_test( + performs_ok( 'SELECT TRUE', 0.00 ), + false, + 'simple select no desc numeric fail', + 'Should run in less than 0.00 ms', + ' runtime: [[:digit:]]+([.][[:digit:]]+)? ms + exceeds: 0.00 ms', + true +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From 903aec33330504b33b23cc4749e2f15fe1aba6e5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 20 May 2009 12:33:32 -0400 Subject: [PATCH 0343/1195] Add note about the overhead of `EXECUTE` in `performs_ok()`. --- README.pgtap | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.pgtap b/README.pgtap index 5fd539f434d8..bfb89ac5021c 100644 --- a/README.pgtap +++ b/README.pgtap @@ -652,6 +652,12 @@ messages. For example: # runtime: 200.266 ms # exceeds: 200 ms +*Note:* There is a little extra time included in the execution time for the +the overhead of PL/pgSQL's `EXECUTE`, which must compile and execute the SQL +string. You will want to account for this and pad your estimates accordingly. +It's best to think of this as a brute force comparison of runtimes, in order +to ensure that a query is not *really* slow (think seconds). + A Wicked Schema --------------- From 3037d3f33fc09decf435c14351bd68db044eb6b5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 May 2009 11:01:55 -0400 Subject: [PATCH 0344/1195] Got rid of the old SVN `$Id` property. --- pgtap.sql.in | 1 - sql/cantap.sql | 2 -- sql/check.sql | 2 -- sql/cmpok.sql | 2 -- sql/coltap.sql | 2 -- sql/do_tap.sql | 2 -- sql/enumtap.sql | 2 -- sql/fktap.sql | 2 -- sql/hastap.sql | 2 -- sql/index.sql | 2 -- sql/istap.sql | 2 -- sql/matching.sql | 2 -- sql/moretap.sql | 2 -- sql/pg73.sql | 2 -- sql/pktap.sql | 2 -- sql/roletap.sql | 2 -- sql/runtests.sql | 2 -- sql/throwtap.sql | 2 -- sql/todotap.sql | 2 -- sql/trigger.sql | 2 -- sql/unique.sql | 2 -- sql/usergroup.sql | 2 -- sql/util.sql | 2 -- test_setup.sql.in | 2 -- uninstall_pgtap.sql.in | 1 - 25 files changed, 48 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index a2783c30b95c..153881d11ed0 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -8,7 +8,6 @@ -- -- http://pgtap.projects.postgresql.org/ --- $Id$ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA, public; diff --git a/sql/cantap.sql b/sql/cantap.sql index c3690ce0c932..3678e5570e39 100644 --- a/sql/cantap.sql +++ b/sql/cantap.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(63); CREATE SCHEMA someschema; CREATE FUNCTION someschema.huh () RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL; diff --git a/sql/check.sql b/sql/check.sql index c342d80e6ba4..a83b4674dabc 100644 --- a/sql/check.sql +++ b/sql/check.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(39); -- This will be rolled back. :-) diff --git a/sql/cmpok.sql b/sql/cmpok.sql index f77a7af8a3ac..2d59cdf18e79 100644 --- a/sql/cmpok.sql +++ b/sql/cmpok.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(20); /****************************************************************************/ diff --git a/sql/coltap.sql b/sql/coltap.sql index 3044dc77dfa5..99b5edf4689d 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(171); --SELECT * from no_plan(); diff --git a/sql/do_tap.sql b/sql/do_tap.sql index 62728d4cd69a..90d0ffa94946 100644 --- a/sql/do_tap.sql +++ b/sql/do_tap.sql @@ -2,8 +2,6 @@ \i test_setup.sql SET client_min_messages = notice; --- $Id$ - SELECT plan(26); --SELECT * FROM no_plan(); diff --git a/sql/enumtap.sql b/sql/enumtap.sql index ea2a231e04d6..f21ea850b57b 100644 --- a/sql/enumtap.sql +++ b/sql/enumtap.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(72); --SELECT * FROM no_plan(); diff --git a/sql/fktap.sql b/sql/fktap.sql index b7bbf894c27c..dfa7dff51440 100644 --- a/sql/fktap.sql +++ b/sql/fktap.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(128); --SELECT * from no_plan(); diff --git a/sql/hastap.sql b/sql/hastap.sql index f8c8daf229f9..fabaa478e54c 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(486); --SELECT * FROM no_plan(); diff --git a/sql/index.sql b/sql/index.sql index 479dc9e6497d..78da130ef801 100644 --- a/sql/index.sql +++ b/sql/index.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(180); --SELECT * FROM no_plan(); diff --git a/sql/istap.sql b/sql/istap.sql index 9a1e357f08d9..fcf4e7861f4d 100644 --- a/sql/istap.sql +++ b/sql/istap.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(37); /****************************************************************************/ diff --git a/sql/matching.sql b/sql/matching.sql index 5b39b48d7eae..c2d6039974c2 100644 --- a/sql/matching.sql +++ b/sql/matching.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(24); /****************************************************************************/ diff --git a/sql/moretap.sql b/sql/moretap.sql index 2df92ff3f2ea..caae6e74e0f3 100644 --- a/sql/moretap.sql +++ b/sql/moretap.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - \set numb_tests 40 SELECT plan(:numb_tests); diff --git a/sql/pg73.sql b/sql/pg73.sql index 555eb90e8d16..98abb49a9671 100644 --- a/sql/pg73.sql +++ b/sql/pg73.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - select plan(39); select ok(true); diff --git a/sql/pktap.sql b/sql/pktap.sql index bf6dd4fedfe4..04d996ea66f3 100644 --- a/sql/pktap.sql +++ b/sql/pktap.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(78); --SELECT * FROM no_plan(); diff --git a/sql/roletap.sql b/sql/roletap.sql index 2b08cf04db84..e71a5fffaa6a 100644 --- a/sql/roletap.sql +++ b/sql/roletap.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(24); --SELECT * FROM no_plan(); diff --git a/sql/runtests.sql b/sql/runtests.sql index aaf689c91ee5..a7343ae17da5 100644 --- a/sql/runtests.sql +++ b/sql/runtests.sql @@ -2,8 +2,6 @@ \i test_setup.sql SET client_min_messages = warning; --- $Id$ - CREATE SCHEMA whatever; CREATE TABLE whatever.foo ( id serial primary key ); diff --git a/sql/throwtap.sql b/sql/throwtap.sql index 9a869e4d0a97..db9f13eff4b1 100644 --- a/sql/throwtap.sql +++ b/sql/throwtap.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(28); --SELECT * FROM no_plan(); diff --git a/sql/todotap.sql b/sql/todotap.sql index 1562f535bd0d..ac1220f8872a 100644 --- a/sql/todotap.sql +++ b/sql/todotap.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(36); --SELECT * FROM no_plan(); diff --git a/sql/trigger.sql b/sql/trigger.sql index 023fd5fd6b92..2e6c5c10a6f6 100644 --- a/sql/trigger.sql +++ b/sql/trigger.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(33); --SELECT * FROM no_plan(); diff --git a/sql/unique.sql b/sql/unique.sql index 794f7e7f2fdc..99867cc5bcc5 100644 --- a/sql/unique.sql +++ b/sql/unique.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(39); -- This will be rolled back. :-) diff --git a/sql/usergroup.sql b/sql/usergroup.sql index 6ad17489c946..f39100b657e2 100644 --- a/sql/usergroup.sql +++ b/sql/usergroup.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(78); --SELECT * FROM no_plan(); diff --git a/sql/util.sql b/sql/util.sql index 3621605679c0..d99af9626015 100644 --- a/sql/util.sql +++ b/sql/util.sql @@ -1,8 +1,6 @@ \unset ECHO \i test_setup.sql --- $Id$ - SELECT plan(12); --SELECT * FROM no_plan(); diff --git a/test_setup.sql.in b/test_setup.sql.in index 1aeb5b3068bd..3218b8dc36b1 100644 --- a/test_setup.sql.in +++ b/test_setup.sql.in @@ -4,8 +4,6 @@ -- Tests for pgTAP. -- -- --- $Id$ - -- Format the output for nice TAP. \pset format unaligned \pset tuples_only true diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index e030f44bb00b..3e1d71148630 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,4 +1,3 @@ --- $Id$ -- ## SET search_path TO TAPSCHEMA, public; DROP FUNCTION runtests( ); DROP FUNCTION runtests( TEXT ); From e56475a0a71e48cbf2e50348756cd477d6162deb Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 22 May 2009 15:27:11 -0400 Subject: [PATCH 0345/1195] Added `tables_are()`, `views_are()`, and `sequences_are()`. --- Changes | 1 + Makefile | 2 +- README.pgtap | 124 ++++++++++++++++- expected/aretap.out | 92 ++++++++++++ pgtap.sql.in | 188 ++++++++++++++++++++++++- sql/aretap.sql | 330 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 733 insertions(+), 4 deletions(-) create mode 100644 expected/aretap.out create mode 100644 sql/aretap.sql diff --git a/Changes b/Changes index c1221df4ef7e..51f68576d484 100644 --- a/Changes +++ b/Changes @@ -9,6 +9,7 @@ Revision history for pgTAP * Fixed failing test on Solaris 10 on Intel thanks to Gabrielle Roth. * Fixed a failing test for the version number string on 8.4 beta. * Added `performs_ok()`. +* Added `tables_are()`, `views_are()`, and `sequences_are()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/Makefile b/Makefile index 8528cf565757..78988e317bf3 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,7 @@ REGRESS := $(filter-out throwtap runtests enumtap roletap,$(REGRESS)) else ifeq ($(PGVER_MINOR), 4) # Remove lines 15-20, which define pg_typeof(). -EXTRA_SUBS := -e '15,19d' +EXTRA_SUBS := -e '14,18d' else ifneq ($(PGVER_MINOR), 3) # Enum test not supported by 8.2 and earlier. diff --git a/README.pgtap b/README.pgtap index bfb89ac5021c..8c74e8759d9a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -744,6 +744,44 @@ test description, it will be set to "Schema `:schema` should exist". This function is the inverse of `has_schema()`. The test passes if the specified schema does *not* exist. +### `tables_are( schema, tables, description )` ### +### `tables_are( tables, description )` ### +### `tables_are( schema, tables )` ### +### `tables_are( tables )` ### + + SELECT tables_are( + 'myschema', + ARRAY[ 'users', 'widgets', 'gadgets', 'session' ], + 'Should have the correct tables in myschema' + ); + +This function tests that all of the tables in the named schema, or that are +visible in the search path, are only the tables that *should* be there. In +other words, given a list of tables, this assertion will fail if there are +tables that don't exist in the list, or if there are tables in the list that +are missing from the database. + +This test is useful in environments where many developers may be making +changes to the database, or where replication has been deployed, and you want +to make sure that the tables in a database are exactly the tables that should +exist in the database, no more, no less. + +If the `:schema` argument is omitted, tables will be sought in the search +path, excluding `pg_catalog.` If the description is omitted, a generally +useful default description will be generated. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing tables, like so: + + not ok 91 - Schema public should have the correct tables + # Failed test 91: "Schema public should have the correct tables" + # These are extra tables: + # mallots + # __test_table + # These tables are missing: + # users + # widgets + ### `has_table( schema, table, description )` ### ### `has_table( table, description )` ### ### `has_table( table )` ### @@ -773,6 +811,44 @@ search path. If you omit the test description, it will be set to "Table This function is the inverse of `has_table()`. The test passes if the specified table does *not* exist. +### `views_are( schema, views, description )` ### +### `views_are( views, description )` ### +### `views_are( schema, views )` ### +### `views_are( views )` ### + + SELECT views_are( + 'myschema', + ARRAY[ 'users', 'widgets', 'gadgets', 'session' ], + 'Should have the correct views in myschema' + ); + +This function tests that all of the views in the named schema, or that are +visible in the search path, are only the views that *should* be there. In +other words, given a list of views, this assertion will fail if there are +views that don't exist in the list, or if there are views in the list that are +missing from the database. + +This test is useful in environments where many developers may be making +changes to the database, or where replication has been deployed, and you want +to make sure that the views in a database are exactly the views that should +exist in the database, no more, no less. + +If the `:schema` argument is omitted, views will be sought in the search +path, excluding `pg_catalog.` If the description is omitted, a generally +useful default description will be generated. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing views, like so: + + not ok 92 - Schema public should have the correct views + # Failed test 92: "Schema public should have the correct views" + # These are extra views: + # v_userlog_tmp + # __test_view + # These views are missing: + # v_userlog + # eated + ### `has_view( schema, view, description )` ### ### `has_view( view, description )` ### ### `has_view( view )` ### @@ -798,6 +874,44 @@ Just like `has_table()`, only it tests for the existence of a view. This function is the inverse of `has_view()`. The test passes if the specified view does *not* exist. +### `sequences_are( schema, sequences, description )` ### +### `sequences_are( sequences, description )` ### +### `sequences_are( schema, sequences )` ### +### `sequences_are( sequences )` ### + + SELECT sequences_are( + 'myschema', + ARRAY[ 'users', 'widgets', 'gadgets', 'session' ], + 'Should have the correct sequences in myschema' + ); + +This function tests that all of the sequences in the named schema, or that are +visible in the search path, are only the sequences that *should* be there. In +other words, given a list of sequences, this assertion will fail if there are +sequences that don't exist in the list, or if there are sequences in the list +that are missing from the database. + +This test is useful in environments where many developers may be making +changes to the database, or where replication has been deployed, and you want +to make sure that the sequences in a database are exactly the sequences that +should exist in the database, no more, no less. + +If the `:schema` argument is omitted, sequences will be sought in the search +path, excluding `pg_catalog.` If the description is omitted, a generally +useful default description will be generated. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing sequences, like so: + + not ok 93 - Schema public should have the correct sequences + # Failed test 93: "Schema public should have the correct sequences" + # These are extra sequences: + # seq_mallots + # __test_table_seq + # These sequences are missing: + # users_seq + # widgets_seq + ### `has_sequence( schema, sequence, description )` ### ### `has_sequence( sequence, description )` ### ### `has_sequence( sequence )` ### @@ -2542,10 +2656,16 @@ To Do ----- * Useful schema testing functions to consider adding: + * `schemas_are()` + * `tablespaces_are()` + * `functions_are()` + * `indexes_are()` + * `users_are()` + * `groups_are()` * `has_operator_class()` * `has_rule()` - * `func_returns()` - * `func_lang_is()` + * `function_returns()` + * `function_lang_is()` * `seq_has_range()` * `seq_increments_by()` * `seq_starts_at()` diff --git a/expected/aretap.out b/expected/aretap.out new file mode 100644 index 000000000000..8f1507ecebc8 --- /dev/null +++ b/expected/aretap.out @@ -0,0 +1,92 @@ +\unset ECHO +1..90 +ok 1 - tables_are(schema, tables, desc) should pass +ok 2 - tables_are(schema, tables, desc) should have the proper description +ok 3 - tables_are(schema, tables, desc) should have the proper diagnostics +ok 4 - tables_are(schema, tables) should pass +ok 5 - tables_are(schema, tables) should have the proper description +ok 6 - tables_are(schema, tables) should have the proper diagnostics +ok 7 - tables_are(tables) should pass +ok 8 - tables_are(tables) should have the proper description +ok 9 - tables_are(tables) should have the proper diagnostics +ok 10 - tables_are(tables, desc) should pass +ok 11 - tables_are(tables, desc) should have the proper description +ok 12 - tables_are(tables, desc) should have the proper diagnostics +ok 13 - tables_are(schema, tables) missing should fail +ok 14 - tables_are(schema, tables) missing should have the proper description +ok 15 - tables_are(schema, tables) missing should have the proper diagnostics +ok 16 - tables_are(tables) missing should fail +ok 17 - tables_are(tables) missing should have the proper description +ok 18 - tables_are(tables) missing should have the proper diagnostics +ok 19 - tables_are(schema, tables) extra should fail +ok 20 - tables_are(schema, tables) extra should have the proper description +ok 21 - tables_are(schema, tables) extra should have the proper diagnostics +ok 22 - tables_are(tables) extra should fail +ok 23 - tables_are(tables) extra should have the proper description +ok 24 - tables_are(tables) extra should have the proper diagnostics +ok 25 - tables_are(schema, tables) extra and missing should fail +ok 26 - tables_are(schema, tables) extra and missing should have the proper description +ok 27 - tables_are(schema, tables) extra and missing should have the proper diagnostics +ok 28 - tables_are(tables) extra and missing should fail +ok 29 - tables_are(tables) extra and missing should have the proper description +ok 30 - tables_are(tables) extra and missing should have the proper diagnostics +ok 31 - views_are(schema, views, desc) should pass +ok 32 - views_are(schema, views, desc) should have the proper description +ok 33 - views_are(schema, views, desc) should have the proper diagnostics +ok 34 - views_are(schema, views) should pass +ok 35 - views_are(schema, views) should have the proper description +ok 36 - views_are(schema, views) should have the proper diagnostics +ok 37 - views_are(views) should pass +ok 38 - views_are(views) should have the proper description +ok 39 - views_are(views) should have the proper diagnostics +ok 40 - views_are(views, desc) should pass +ok 41 - views_are(views, desc) should have the proper description +ok 42 - views_are(views, desc) should have the proper diagnostics +ok 43 - views_are(schema, views) missing should fail +ok 44 - views_are(schema, views) missing should have the proper description +ok 45 - views_are(schema, views) missing should have the proper diagnostics +ok 46 - views_are(views) missing should fail +ok 47 - views_are(views) missing should have the proper description +ok 48 - views_are(views) missing should have the proper diagnostics +ok 49 - views_are(schema, views) extra should fail +ok 50 - views_are(schema, views) extra should have the proper description +ok 51 - views_are(schema, views) extra should have the proper diagnostics +ok 52 - views_are(views) extra should fail +ok 53 - views_are(views) extra should have the proper description +ok 54 - views_are(views) extra should have the proper diagnostics +ok 55 - views_are(schema, views) extra and missing should fail +ok 56 - views_are(schema, views) extra and missing should have the proper description +ok 57 - views_are(schema, views) extra and missing should have the proper diagnostics +ok 58 - views_are(views) extra and missing should fail +ok 59 - views_are(views) extra and missing should have the proper description +ok 60 - views_are(views) extra and missing should have the proper diagnostics +ok 61 - sequences_are(schema, sequences, desc) should pass +ok 62 - sequences_are(schema, sequences, desc) should have the proper description +ok 63 - sequences_are(schema, sequences, desc) should have the proper diagnostics +ok 64 - sequences_are(schema, sequences) should pass +ok 65 - sequences_are(schema, sequences) should have the proper description +ok 66 - sequences_are(schema, sequences) should have the proper diagnostics +ok 67 - sequences_are(sequences) should pass +ok 68 - sequences_are(sequences) should have the proper description +ok 69 - sequences_are(sequences) should have the proper diagnostics +ok 70 - sequences_are(sequences, desc) should pass +ok 71 - sequences_are(sequences, desc) should have the proper description +ok 72 - sequences_are(sequences, desc) should have the proper diagnostics +ok 73 - sequences_are(schema, sequences) missing should fail +ok 74 - sequences_are(schema, sequences) missing should have the proper description +ok 75 - sequences_are(schema, sequences) missing should have the proper diagnostics +ok 76 - sequences_are(sequences) missing should fail +ok 77 - sequences_are(sequences) missing should have the proper description +ok 78 - sequences_are(sequences) missing should have the proper diagnostics +ok 79 - sequences_are(schema, sequences) extra should fail +ok 80 - sequences_are(schema, sequences) extra should have the proper description +ok 81 - sequences_are(schema, sequences) extra should have the proper diagnostics +ok 82 - sequences_are(sequences) extra should fail +ok 83 - sequences_are(sequences) extra should have the proper description +ok 84 - sequences_are(sequences) extra should have the proper diagnostics +ok 85 - sequences_are(schema, sequences) extra and missing should fail +ok 86 - sequences_are(schema, sequences) extra and missing should have the proper description +ok 87 - sequences_are(schema, sequences) extra and missing should have the proper diagnostics +ok 88 - sequences_are(sequences) extra and missing should fail +ok 89 - sequences_are(sequences) extra and missing should have the proper description +ok 90 - sequences_are(sequences) extra and missing should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 153881d11ed0..0ff98b095485 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1699,7 +1699,7 @@ BEGIN ORDER BY fk_columns ) INTO names; - IF NAMES[1] IS NOT NULL THEN + 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 ' ) @@ -3697,6 +3697,192 @@ RETURNS TEXT AS $$ ); $$ 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 = $1 + AND n.nspname = $2 + AND c.relname <> 'pg_all_foreign_keys' + 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 = $1 + AND c.relname NOT IN ('__tcache__', '__tresults__', 'pg_all_foreign_keys', '__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 _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 = $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 <> 'pg_catalog' + AND c.relkind = $1 + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _rels_are ( CHAR, NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + extras name[] := _extras( $1, $3, $4 ); + missing name[] := _missing( $1, $3, $4 ); + msg TEXT := ''; + res BOOLEAN := TRUE; +BEGIN + IF extras[1] IS NOT NULL THEN + res = FALSE; + msg := E'\n' || diag( + ' Schema ' || quote_ident($3) || ' has these extra ' || $2 || E':\n ' + || array_to_string( extras, E'\n ' ) + ); + END IF; + IF missing[1] IS NOT NULL THEN + res = FALSE; + msg := msg || E'\n' || diag( + ' Schema ' || quote_ident($3) || ' is missing these ' || $2 || E':\n ' + || array_to_string( missing, E'\n ' ) + ); + END IF; + + RETURN ok(res, $5) || msg; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _rels_are ( CHAR, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + extras name[] := _extras( $1, $3 ); + missing name[] := _missing( $1, $3 ); + msg TEXT := ''; + res BOOLEAN := TRUE; +BEGIN + IF extras[1] IS NOT NULL THEN + res = FALSE; + msg := E'\n' || diag( + ' These are extra ' || $2 || E':\n ' + || array_to_string( extras, E'\n ' ) + ); + END IF; + IF missing[1] IS NOT NULL THEN + res = FALSE; + msg := msg || E'\n' || diag( + ' These ' || $2 || E' are missing:\n ' + || array_to_string( missing, E'\n ' ) + ); + END IF; + + RETURN ok(res, $4) || msg; +END; +$$ LANGUAGE plpgsql; + +-- tables_are( schema, tables, description ) +CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _rels_are('r', 'tables', $1, $2, $3 ); +$$ LANGUAGE SQL; + +-- tables_are( tables, description ) +CREATE OR REPLACE FUNCTION tables_are ( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _rels_are('r', 'tables', $1, $2 ); +$$ LANGUAGE SQL; + +-- tables_are( schema, tables ) +CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _rels_are('r', 'tables', $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 _rels_are('r', 'tables', $1, 'There should be the correct tables' ); +$$ LANGUAGE SQL; + +-- views_are( schema, views, description ) +CREATE OR REPLACE FUNCTION views_are ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _rels_are('v', 'views', $1, $2, $3 ); +$$ LANGUAGE SQL; + +-- views_are( views, description ) +CREATE OR REPLACE FUNCTION views_are ( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _rels_are('v', 'views', $1, $2 ); +$$ LANGUAGE SQL; + +-- views_are( schema, views ) +CREATE OR REPLACE FUNCTION views_are ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _rels_are('v', 'views', $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 _rels_are('v', 'views', $1, 'There should be the correct views' ); +$$ LANGUAGE SQL; + +-- sequences_are( schema, sequences, description ) +CREATE OR REPLACE FUNCTION sequences_are ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _rels_are('S', 'sequences', $1, $2, $3 ); +$$ LANGUAGE SQL; + +-- sequences_are( sequences, description ) +CREATE OR REPLACE FUNCTION sequences_are ( NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _rels_are('S', 'sequences', $1, $2 ); +$$ LANGUAGE SQL; + +-- sequences_are( schema, sequences ) +CREATE OR REPLACE FUNCTION sequences_are ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _rels_are('S', 'sequences', $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 _rels_are('S', 'sequences', $1, 'There should be the correct sequences' ); +$$ 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 $$ diff --git a/sql/aretap.sql b/sql/aretap.sql new file mode 100644 index 000000000000..dd0c9911f3ae --- /dev/null +++ b/sql/aretap.sql @@ -0,0 +1,330 @@ +\unset ECHO +\i test_setup.sql + +SELECT plan(90); +--SELECT * FROM no_plan(); + +-- This will be rolled back. :-) +SET client_min_messages = warning; + +CREATE TABLE public.fou( + id INT NOT NULL PRIMARY KEY, + name TEXT DEFAULT '', + numb NUMERIC(10, 2), + myint NUMERIC(8) +); +CREATE TABLE public.foo( + id INT NOT NULL PRIMARY KEY +); +CREATE TYPE public.sometype AS ( + id INT, + name TEXT +); + +CREATE VIEW voo AS SELECT * FROM foo; +CREATE VIEW vou AS SELECT * FROM fou; + +CREATE SEQUENCE public.someseq; +CREATE SEQUENCE public.sumeseq; + +CREATE SCHEMA someschema; +RESET client_min_messages; + +/****************************************************************************/ +-- Test tables_are(). +SELECT * FROM check_test( + tables_are( 'public', ARRAY['fou', 'foo'], 'whatever' ), + true, + 'tables_are(schema, tables, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + tables_are( 'public', ARRAY['fou', 'foo'] ), + true, + 'tables_are(schema, tables)', + 'Schema public should have the correct tables', + '' +); + +SELECT * FROM check_test( + tables_are( ARRAY['fou', 'foo'] ), + true, + 'tables_are(tables)', + 'There should be the correct tables', + '' +); + +SELECT * FROM check_test( + tables_are( ARRAY['fou', 'foo'], 'whatever' ), + true, + 'tables_are(tables, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + tables_are( 'public', ARRAY['fou', 'foo', 'bar'] ), + false, + 'tables_are(schema, tables) missing', + 'Schema public should have the correct tables', + ' Schema public is missing these tables: + bar' +); + +SELECT * FROM check_test( + tables_are( ARRAY['fou', 'foo', 'bar'] ), + false, + 'tables_are(tables) missing', + 'There should be the correct tables', + ' These tables are missing: + bar' +); + +SELECT * FROM check_test( + tables_are( 'public', ARRAY['fou'] ), + false, + 'tables_are(schema, tables) extra', + 'Schema public should have the correct tables', + ' Schema public has these extra tables: + foo' +); + +SELECT * FROM check_test( + tables_are( ARRAY['fou'] ), + false, + 'tables_are(tables) extra', + 'There should be the correct tables', + ' These are extra tables: + foo' +); + +SELECT * FROM check_test( + tables_are( 'public', ARRAY['bar', 'baz'] ), + false, + 'tables_are(schema, tables) extra and missing', + 'Schema public should have the correct tables', + ' Schema public has these extra tables: + fo[ou] + fo[ou] + Schema public is missing these tables: + ba[rz] + ba[rz]', + true +); + +SELECT * FROM check_test( + tables_are( ARRAY['bar', 'baz'] ), + false, + 'tables_are(tables) extra and missing', + 'There should be the correct tables', + ' These are extra tables: + fo[ou] + fo[ou] + These tables are missing: + ba[rz] + ba[rz]', + true +); + +/****************************************************************************/ +-- Test views_are(). +SELECT * FROM check_test( + views_are( 'public', ARRAY['vou', 'voo'], 'whatever' ), + true, + 'views_are(schema, views, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + views_are( 'public', ARRAY['vou', 'voo'] ), + true, + 'views_are(schema, views)', + 'Schema public should have the correct views', + '' +); + +SELECT * FROM check_test( + views_are( ARRAY['vou', 'voo'] ), + true, + 'views_are(views)', + 'There should be the correct views', + '' +); + +SELECT * FROM check_test( + views_are( ARRAY['vou', 'voo'], 'whatever' ), + true, + 'views_are(views, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + views_are( 'public', ARRAY['vou', 'voo', 'bar'] ), + false, + 'views_are(schema, views) missing', + 'Schema public should have the correct views', + ' Schema public is missing these views: + bar' +); + +SELECT * FROM check_test( + views_are( ARRAY['vou', 'voo', 'bar'] ), + false, + 'views_are(views) missing', + 'There should be the correct views', + ' These views are missing: + bar' +); + +SELECT * FROM check_test( + views_are( 'public', ARRAY['vou'] ), + false, + 'views_are(schema, views) extra', + 'Schema public should have the correct views', + ' Schema public has these extra views: + voo' +); + +SELECT * FROM check_test( + views_are( ARRAY['vou'] ), + false, + 'views_are(views) extra', + 'There should be the correct views', + ' These are extra views: + voo' +); + +SELECT * FROM check_test( + views_are( 'public', ARRAY['bar', 'baz'] ), + false, + 'views_are(schema, views) extra and missing', + 'Schema public should have the correct views', + ' Schema public has these extra views: + vo[ou] + vo[ou] + Schema public is missing these views: + ba[rz] + ba[rz]', + true +); + +SELECT * FROM check_test( + views_are( ARRAY['bar', 'baz'] ), + false, + 'views_are(views) extra and missing', + 'There should be the correct views', + ' These are extra views: + vo[ou] + vo[ou] + These views are missing: + ba[rz] + ba[rz]', + true +); + +/****************************************************************************/ +-- Test sequences_are(). +SELECT * FROM check_test( + sequences_are( 'public', ARRAY['sumeseq', 'someseq'], 'whatever' ), + true, + 'sequences_are(schema, sequences, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + sequences_are( 'public', ARRAY['sumeseq', 'someseq'] ), + true, + 'sequences_are(schema, sequences)', + 'Schema public should have the correct sequences', + '' +); + +SELECT * FROM check_test( + sequences_are( ARRAY['sumeseq', 'someseq'] ), + true, + 'sequences_are(sequences)', + 'There should be the correct sequences', + '' +); + +SELECT * FROM check_test( + sequences_are( ARRAY['sumeseq', 'someseq'], 'whatever' ), + true, + 'sequences_are(sequences, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + sequences_are( 'public', ARRAY['sumeseq', 'someseq', 'bar'] ), + false, + 'sequences_are(schema, sequences) missing', + 'Schema public should have the correct sequences', + ' Schema public is missing these sequences: + bar' +); + +SELECT * FROM check_test( + sequences_are( ARRAY['sumeseq', 'someseq', 'bar'] ), + false, + 'sequences_are(sequences) missing', + 'There should be the correct sequences', + ' These sequences are missing: + bar' +); + +SELECT * FROM check_test( + sequences_are( 'public', ARRAY['sumeseq'] ), + false, + 'sequences_are(schema, sequences) extra', + 'Schema public should have the correct sequences', + ' Schema public has these extra sequences: + someseq' +); + +SELECT * FROM check_test( + sequences_are( ARRAY['sumeseq'] ), + false, + 'sequences_are(sequences) extra', + 'There should be the correct sequences', + ' These are extra sequences: + someseq' +); + +SELECT * FROM check_test( + sequences_are( 'public', ARRAY['bar', 'baz'] ), + false, + 'sequences_are(schema, sequences) extra and missing', + 'Schema public should have the correct sequences', + ' Schema public has these extra sequences: + s[ou]meseq + s[ou]meseq + Schema public is missing these sequences: + ba[rz] + ba[rz]', + true +); + +SELECT * FROM check_test( + sequences_are( ARRAY['bar', 'baz'] ), + false, + 'sequences_are(sequences) extra and missing', + 'There should be the correct sequences', + ' These are extra sequences: + s[ou]meseq + s[ou]meseq + These sequences are missing: + ba[rz] + ba[rz]', + true +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From 2c76789c8970f27a7166496868e1dc7a2a2afa0b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 22 May 2009 16:06:24 -0400 Subject: [PATCH 0346/1195] Added `schemas_are()`. --- Changes | 2 +- README.pgtap | 45 ++++++++-- expected/aretap.out | 197 ++++++++++++++++++++++++-------------------- pgtap.sql.in | 54 ++++++++++++ sql/aretap.sql | 61 +++++++++++++- 5 files changed, 260 insertions(+), 99 deletions(-) diff --git a/Changes b/Changes index 51f68576d484..ab39f7f1ee9b 100644 --- a/Changes +++ b/Changes @@ -9,7 +9,7 @@ Revision history for pgTAP * Fixed failing test on Solaris 10 on Intel thanks to Gabrielle Roth. * Fixed a failing test for the version number string on 8.4 beta. * Added `performs_ok()`. -* Added `tables_are()`, `views_are()`, and `sequences_are()`. +* Added `schemas_are()`, `tables_are()`, `views_are()`, and `sequences_are()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index 8c74e8759d9a..46746aa39471 100644 --- a/README.pgtap +++ b/README.pgtap @@ -719,6 +719,40 @@ test description, it will be set to "Tablespace `:tablespace` should exist". This function is the inverse of `has_tablespace()`. The test passes if the specified tablespace does *not* exist. +### `schemas_are( schema, schemas, description )` ### +### `schemas_are( schemas, description )` ### +### `schemas_are( schema, schemas )` ### +### `schemas_are( schemas )` ### + + SELECT schemas_are( + ARRAY[ 'public, 'contrib, 'tap' ], + 'Should have the correct schemas + ); + +This function tests that all of the schemas in the databse only the schemas +that *should* be there. In other words, given a list of schemas, this +assertion will fail if there are schemas that are not in the list, or if there +are schemas in the list that are missing from the database. + +This test is useful in environments where many developers may be making +changes to the database, or where replication has been deployed, and you want +to make sure that the schemas in a database are exactly the schemas that should +exist in the database, no more, no less. + +If the `:schema` argument is omitted, schemas will be sought in the search +path, excluding `pg_catalog.` If the description is omitted, a generally +useful default description will be generated. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing schemas, like so: + + not ok 106 - There should be the correct schemas + # Failed test 106: "There should be the correct schemas" + # These are extra schemas: + # __howdy__ + # These schemas are missing: + # someschema + ### `has_schema( schema, description )` ### ### `has_schema( schema )` ### @@ -758,8 +792,8 @@ specified schema does *not* exist. This function tests that all of the tables in the named schema, or that are visible in the search path, are only the tables that *should* be there. In other words, given a list of tables, this assertion will fail if there are -tables that don't exist in the list, or if there are tables in the list that -are missing from the database. +tables that are not in the list, or if there are tables in the list that are +missing from the database. This test is useful in environments where many developers may be making changes to the database, or where replication has been deployed, and you want @@ -825,7 +859,7 @@ specified table does *not* exist. This function tests that all of the views in the named schema, or that are visible in the search path, are only the views that *should* be there. In other words, given a list of views, this assertion will fail if there are -views that don't exist in the list, or if there are views in the list that are +views that are not in the list, or if there are views in the list that are missing from the database. This test is useful in environments where many developers may be making @@ -888,8 +922,8 @@ view does *not* exist. This function tests that all of the sequences in the named schema, or that are visible in the search path, are only the sequences that *should* be there. In other words, given a list of sequences, this assertion will fail if there are -sequences that don't exist in the list, or if there are sequences in the list -that are missing from the database. +sequences that are not in the list, or if there are sequences in the list that +are missing from the database. This test is useful in environments where many developers may be making changes to the database, or where replication has been deployed, and you want @@ -2656,7 +2690,6 @@ To Do ----- * Useful schema testing functions to consider adding: - * `schemas_are()` * `tablespaces_are()` * `functions_are()` * `indexes_are()` diff --git a/expected/aretap.out b/expected/aretap.out index 8f1507ecebc8..e1e2a160c60c 100644 --- a/expected/aretap.out +++ b/expected/aretap.out @@ -1,92 +1,107 @@ \unset ECHO -1..90 -ok 1 - tables_are(schema, tables, desc) should pass -ok 2 - tables_are(schema, tables, desc) should have the proper description -ok 3 - tables_are(schema, tables, desc) should have the proper diagnostics -ok 4 - tables_are(schema, tables) should pass -ok 5 - tables_are(schema, tables) should have the proper description -ok 6 - tables_are(schema, tables) should have the proper diagnostics -ok 7 - tables_are(tables) should pass -ok 8 - tables_are(tables) should have the proper description -ok 9 - tables_are(tables) should have the proper diagnostics -ok 10 - tables_are(tables, desc) should pass -ok 11 - tables_are(tables, desc) should have the proper description -ok 12 - tables_are(tables, desc) should have the proper diagnostics -ok 13 - tables_are(schema, tables) missing should fail -ok 14 - tables_are(schema, tables) missing should have the proper description -ok 15 - tables_are(schema, tables) missing should have the proper diagnostics -ok 16 - tables_are(tables) missing should fail -ok 17 - tables_are(tables) missing should have the proper description -ok 18 - tables_are(tables) missing should have the proper diagnostics -ok 19 - tables_are(schema, tables) extra should fail -ok 20 - tables_are(schema, tables) extra should have the proper description -ok 21 - tables_are(schema, tables) extra should have the proper diagnostics -ok 22 - tables_are(tables) extra should fail -ok 23 - tables_are(tables) extra should have the proper description -ok 24 - tables_are(tables) extra should have the proper diagnostics -ok 25 - tables_are(schema, tables) extra and missing should fail -ok 26 - tables_are(schema, tables) extra and missing should have the proper description -ok 27 - tables_are(schema, tables) extra and missing should have the proper diagnostics -ok 28 - tables_are(tables) extra and missing should fail -ok 29 - tables_are(tables) extra and missing should have the proper description -ok 30 - tables_are(tables) extra and missing should have the proper diagnostics -ok 31 - views_are(schema, views, desc) should pass -ok 32 - views_are(schema, views, desc) should have the proper description -ok 33 - views_are(schema, views, desc) should have the proper diagnostics -ok 34 - views_are(schema, views) should pass -ok 35 - views_are(schema, views) should have the proper description -ok 36 - views_are(schema, views) should have the proper diagnostics -ok 37 - views_are(views) should pass -ok 38 - views_are(views) should have the proper description -ok 39 - views_are(views) should have the proper diagnostics -ok 40 - views_are(views, desc) should pass -ok 41 - views_are(views, desc) should have the proper description -ok 42 - views_are(views, desc) should have the proper diagnostics -ok 43 - views_are(schema, views) missing should fail -ok 44 - views_are(schema, views) missing should have the proper description -ok 45 - views_are(schema, views) missing should have the proper diagnostics -ok 46 - views_are(views) missing should fail -ok 47 - views_are(views) missing should have the proper description -ok 48 - views_are(views) missing should have the proper diagnostics -ok 49 - views_are(schema, views) extra should fail -ok 50 - views_are(schema, views) extra should have the proper description -ok 51 - views_are(schema, views) extra should have the proper diagnostics -ok 52 - views_are(views) extra should fail -ok 53 - views_are(views) extra should have the proper description -ok 54 - views_are(views) extra should have the proper diagnostics -ok 55 - views_are(schema, views) extra and missing should fail -ok 56 - views_are(schema, views) extra and missing should have the proper description -ok 57 - views_are(schema, views) extra and missing should have the proper diagnostics -ok 58 - views_are(views) extra and missing should fail -ok 59 - views_are(views) extra and missing should have the proper description -ok 60 - views_are(views) extra and missing should have the proper diagnostics -ok 61 - sequences_are(schema, sequences, desc) should pass -ok 62 - sequences_are(schema, sequences, desc) should have the proper description -ok 63 - sequences_are(schema, sequences, desc) should have the proper diagnostics -ok 64 - sequences_are(schema, sequences) should pass -ok 65 - sequences_are(schema, sequences) should have the proper description -ok 66 - sequences_are(schema, sequences) should have the proper diagnostics -ok 67 - sequences_are(sequences) should pass -ok 68 - sequences_are(sequences) should have the proper description -ok 69 - sequences_are(sequences) should have the proper diagnostics -ok 70 - sequences_are(sequences, desc) should pass -ok 71 - sequences_are(sequences, desc) should have the proper description -ok 72 - sequences_are(sequences, desc) should have the proper diagnostics -ok 73 - sequences_are(schema, sequences) missing should fail -ok 74 - sequences_are(schema, sequences) missing should have the proper description -ok 75 - sequences_are(schema, sequences) missing should have the proper diagnostics -ok 76 - sequences_are(sequences) missing should fail -ok 77 - sequences_are(sequences) missing should have the proper description -ok 78 - sequences_are(sequences) missing should have the proper diagnostics -ok 79 - sequences_are(schema, sequences) extra should fail -ok 80 - sequences_are(schema, sequences) extra should have the proper description -ok 81 - sequences_are(schema, sequences) extra should have the proper diagnostics -ok 82 - sequences_are(sequences) extra should fail -ok 83 - sequences_are(sequences) extra should have the proper description -ok 84 - sequences_are(sequences) extra should have the proper diagnostics -ok 85 - sequences_are(schema, sequences) extra and missing should fail -ok 86 - sequences_are(schema, sequences) extra and missing should have the proper description -ok 87 - sequences_are(schema, sequences) extra and missing should have the proper diagnostics -ok 88 - sequences_are(sequences) extra and missing should fail -ok 89 - sequences_are(sequences) extra and missing should have the proper description -ok 90 - sequences_are(sequences) extra and missing should have the proper diagnostics +1..105 +ok 1 - schemas_are(schemas, desc) should pass +ok 2 - schemas_are(schemas, desc) should have the proper description +ok 3 - schemas_are(schemas, desc) should have the proper diagnostics +ok 4 - schemas_are(schemas) should pass +ok 5 - schemas_are(schemas) should have the proper description +ok 6 - schemas_are(schemas) should have the proper diagnostics +ok 7 - schemas_are(schemas, desc) missing should fail +ok 8 - schemas_are(schemas, desc) missing should have the proper description +ok 9 - schemas_are(schemas, desc) missing should have the proper diagnostics +ok 10 - schemas_are(schemas, desc) extras should fail +ok 11 - schemas_are(schemas, desc) extras should have the proper description +ok 12 - schemas_are(schemas, desc) extras should have the proper diagnostics +ok 13 - schemas_are(schemas, desc) missing and extras should fail +ok 14 - schemas_are(schemas, desc) missing and extras should have the proper description +ok 15 - schemas_are(schemas, desc) missing and extras should have the proper diagnostics +ok 16 - tables_are(schema, tables, desc) should pass +ok 17 - tables_are(schema, tables, desc) should have the proper description +ok 18 - tables_are(schema, tables, desc) should have the proper diagnostics +ok 19 - tables_are(schema, tables) should pass +ok 20 - tables_are(schema, tables) should have the proper description +ok 21 - tables_are(schema, tables) should have the proper diagnostics +ok 22 - tables_are(tables) should pass +ok 23 - tables_are(tables) should have the proper description +ok 24 - tables_are(tables) should have the proper diagnostics +ok 25 - tables_are(tables, desc) should pass +ok 26 - tables_are(tables, desc) should have the proper description +ok 27 - tables_are(tables, desc) should have the proper diagnostics +ok 28 - tables_are(schema, tables) missing should fail +ok 29 - tables_are(schema, tables) missing should have the proper description +ok 30 - tables_are(schema, tables) missing should have the proper diagnostics +ok 31 - tables_are(tables) missing should fail +ok 32 - tables_are(tables) missing should have the proper description +ok 33 - tables_are(tables) missing should have the proper diagnostics +ok 34 - tables_are(schema, tables) extra should fail +ok 35 - tables_are(schema, tables) extra should have the proper description +ok 36 - tables_are(schema, tables) extra should have the proper diagnostics +ok 37 - tables_are(tables) extra should fail +ok 38 - tables_are(tables) extra should have the proper description +ok 39 - tables_are(tables) extra should have the proper diagnostics +ok 40 - tables_are(schema, tables) extra and missing should fail +ok 41 - tables_are(schema, tables) extra and missing should have the proper description +ok 42 - tables_are(schema, tables) extra and missing should have the proper diagnostics +ok 43 - tables_are(tables) extra and missing should fail +ok 44 - tables_are(tables) extra and missing should have the proper description +ok 45 - tables_are(tables) extra and missing should have the proper diagnostics +ok 46 - views_are(schema, views, desc) should pass +ok 47 - views_are(schema, views, desc) should have the proper description +ok 48 - views_are(schema, views, desc) should have the proper diagnostics +ok 49 - views_are(schema, views) should pass +ok 50 - views_are(schema, views) should have the proper description +ok 51 - views_are(schema, views) should have the proper diagnostics +ok 52 - views_are(views) should pass +ok 53 - views_are(views) should have the proper description +ok 54 - views_are(views) should have the proper diagnostics +ok 55 - views_are(views, desc) should pass +ok 56 - views_are(views, desc) should have the proper description +ok 57 - views_are(views, desc) should have the proper diagnostics +ok 58 - views_are(schema, views) missing should fail +ok 59 - views_are(schema, views) missing should have the proper description +ok 60 - views_are(schema, views) missing should have the proper diagnostics +ok 61 - views_are(views) missing should fail +ok 62 - views_are(views) missing should have the proper description +ok 63 - views_are(views) missing should have the proper diagnostics +ok 64 - views_are(schema, views) extra should fail +ok 65 - views_are(schema, views) extra should have the proper description +ok 66 - views_are(schema, views) extra should have the proper diagnostics +ok 67 - views_are(views) extra should fail +ok 68 - views_are(views) extra should have the proper description +ok 69 - views_are(views) extra should have the proper diagnostics +ok 70 - views_are(schema, views) extra and missing should fail +ok 71 - views_are(schema, views) extra and missing should have the proper description +ok 72 - views_are(schema, views) extra and missing should have the proper diagnostics +ok 73 - views_are(views) extra and missing should fail +ok 74 - views_are(views) extra and missing should have the proper description +ok 75 - views_are(views) extra and missing should have the proper diagnostics +ok 76 - sequences_are(schema, sequences, desc) should pass +ok 77 - sequences_are(schema, sequences, desc) should have the proper description +ok 78 - sequences_are(schema, sequences, desc) should have the proper diagnostics +ok 79 - sequences_are(schema, sequences) should pass +ok 80 - sequences_are(schema, sequences) should have the proper description +ok 81 - sequences_are(schema, sequences) should have the proper diagnostics +ok 82 - sequences_are(sequences) should pass +ok 83 - sequences_are(sequences) should have the proper description +ok 84 - sequences_are(sequences) should have the proper diagnostics +ok 85 - sequences_are(sequences, desc) should pass +ok 86 - sequences_are(sequences, desc) should have the proper description +ok 87 - sequences_are(sequences, desc) should have the proper diagnostics +ok 88 - sequences_are(schema, sequences) missing should fail +ok 89 - sequences_are(schema, sequences) missing should have the proper description +ok 90 - sequences_are(schema, sequences) missing should have the proper diagnostics +ok 91 - sequences_are(sequences) missing should fail +ok 92 - sequences_are(sequences) missing should have the proper description +ok 93 - sequences_are(sequences) missing should have the proper diagnostics +ok 94 - sequences_are(schema, sequences) extra should fail +ok 95 - sequences_are(schema, sequences) extra should have the proper description +ok 96 - sequences_are(schema, sequences) extra should have the proper diagnostics +ok 97 - sequences_are(sequences) extra should fail +ok 98 - sequences_are(sequences) extra should have the proper description +ok 99 - sequences_are(sequences) extra should have the proper diagnostics +ok 100 - sequences_are(schema, sequences) extra and missing should fail +ok 101 - sequences_are(schema, sequences) extra and missing should have the proper description +ok 102 - sequences_are(schema, sequences) extra and missing should have the proper diagnostics +ok 103 - sequences_are(sequences) extra and missing should fail +ok 104 - sequences_are(sequences) extra and missing should have the proper description +ok 105 - sequences_are(sequences) extra and missing should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 0ff98b095485..1bb0d143ce5e 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3697,6 +3697,60 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; +-- schemas_are( schemas, description ) +CREATE OR REPLACE FUNCTION schemas_are ( NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + extras name[]; + missing name[]; + msg TEXT := ''; + res BOOLEAN := TRUE; +BEGIN + SELECT 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) + ) INTO extras; + + SELECT 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' + ) INTO missing; + + IF extras[1] IS NOT NULL THEN + res = FALSE; + msg := E'\n' || diag( + E' These are extra schemas:\n ' + || array_to_string( extras, E'\n ' ) + ); + END IF; + IF missing[1] IS NOT NULL THEN + res = FALSE; + msg := msg || E'\n' || diag( + E' These schemas are missing:\n ' + || array_to_string( missing, E'\n ' ) + ); + END IF; + + RETURN ok(res, $2) || msg; +END; +$$ LANGUAGE plpgsql; + +-- 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( diff --git a/sql/aretap.sql b/sql/aretap.sql index dd0c9911f3ae..bc62a3d0b47c 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(90); +SELECT plan(105); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -30,6 +30,65 @@ CREATE SEQUENCE public.sumeseq; CREATE SCHEMA someschema; RESET client_min_messages; +/****************************************************************************/ +-- Test schemas_are(). + +CREATE FUNCTION ___mysch(ex text) RETURNS NAME[] AS $$ + SELECT ARRAY( + SELECT nspname + FROM pg_catalog.pg_namespace + WHERE nspname NOT LIKE 'pg_%' + AND nspname <> 'information_schema' + AND nspname <> $1 + ); +$$ LANGUAGE SQL; + +SELECT * FROM check_test( + schemas_are( ___mysch(''), 'whatever' ), + true, + 'schemas_are(schemas, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + schemas_are( ___mysch('') ), + true, + 'schemas_are(schemas)', + 'There should be the correct schemas', + '' +); + +SELECT * FROM check_test( + schemas_are( array_append(___mysch(''), '__howdy__'), 'whatever' ), + false, + 'schemas_are(schemas, desc) missing', + 'whatever', + ' These schemas are missing: + __howdy__' +); + +SELECT * FROM check_test( + schemas_are( ___mysch('someschema'), 'whatever' ), + false, + 'schemas_are(schemas, desc) extras', + 'whatever', + ' These are extra schemas: + someschema' +); + +SELECT * FROM check_test( + schemas_are( array_append(___mysch('someschema'), '__howdy__'), 'whatever' ), + false, + 'schemas_are(schemas, desc) missing and extras', + 'whatever', + ' These are extra schemas: + someschema + These schemas are missing: + __howdy__' +); + + /****************************************************************************/ -- Test tables_are(). SELECT * FROM check_test( From 081694899f098e48befaa8ab9b6dabce28522e6d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 22 May 2009 16:44:56 -0400 Subject: [PATCH 0347/1195] Added `tablespaces_are()`. --- Changes | 3 +- README.pgtap | 35 +++++-- expected/aretap.out | 227 +++++++++++++++++++++++--------------------- pgtap.sql.in | 91 ++++++++++++------ sql/aretap.sql | 58 ++++++++++- 5 files changed, 273 insertions(+), 141 deletions(-) diff --git a/Changes b/Changes index ab39f7f1ee9b..dfe66bfe91cc 100644 --- a/Changes +++ b/Changes @@ -9,7 +9,8 @@ Revision history for pgTAP * Fixed failing test on Solaris 10 on Intel thanks to Gabrielle Roth. * Fixed a failing test for the version number string on 8.4 beta. * Added `performs_ok()`. -* Added `schemas_are()`, `tables_are()`, `views_are()`, and `sequences_are()`. +* Added `tablespaces_are()`, `schemas_are()`, `tables_are()`, `views_are()`, + and `sequences_are()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index 46746aa39471..3cfb15d93ea2 100644 --- a/README.pgtap +++ b/README.pgtap @@ -691,6 +691,35 @@ In general, this should not be an issue, as mixed-case objects are created only rarely. So if you just stick to lowercase-only arguments to these functions, you should be in good shape. +### `tablespaces_are( tablespaces, description )` ### +### `tablespaces_are( tablespaces )` ### + + SELECT tablespaces_are( + ARRAY[ 'public, 'contrib, 'tap' ], + 'Should have the correct tablespaces + ); + +This function tests that all of the tablespaces in the databse only the +tablespaces that *should* be there. In other words, given a list of +tablespaces, this assertion will fail if there are tablespaces that are not in +the list, or if there are tablespaces in the list that are missing from the +database. + +This test is useful in environments where many developers may be making +changes to the database, or where replication has been deployed, and you want +to make sure that the tablespaces in a database are exactly the tablespaces +that should exist in the database, no more, no less. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing tablespaces, like so: + + not ok 121 - There should be the correct tablespaces + # Failed test 121: "There should be the correct tablespaces" + # These are extra tablespaces: + # pg_default + # These tablespaces are missing: + # __booya__ + ### `has_tablespace( tablespace, location, description )` ### ### `has_tablespace( tablespace, description )` ### ### `has_tablespace( tablespace )` ### @@ -719,9 +748,7 @@ test description, it will be set to "Tablespace `:tablespace` should exist". This function is the inverse of `has_tablespace()`. The test passes if the specified tablespace does *not* exist. -### `schemas_are( schema, schemas, description )` ### ### `schemas_are( schemas, description )` ### -### `schemas_are( schema, schemas )` ### ### `schemas_are( schemas )` ### SELECT schemas_are( @@ -739,10 +766,6 @@ changes to the database, or where replication has been deployed, and you want to make sure that the schemas in a database are exactly the schemas that should exist in the database, no more, no less. -If the `:schema` argument is omitted, schemas will be sought in the search -path, excluding `pg_catalog.` If the description is omitted, a generally -useful default description will be generated. - In the event of a failure, you'll see diagnostics listing the extra and/or missing schemas, like so: diff --git a/expected/aretap.out b/expected/aretap.out index e1e2a160c60c..a88a6d790a29 100644 --- a/expected/aretap.out +++ b/expected/aretap.out @@ -1,107 +1,122 @@ \unset ECHO -1..105 -ok 1 - schemas_are(schemas, desc) should pass -ok 2 - schemas_are(schemas, desc) should have the proper description -ok 3 - schemas_are(schemas, desc) should have the proper diagnostics -ok 4 - schemas_are(schemas) should pass -ok 5 - schemas_are(schemas) should have the proper description -ok 6 - schemas_are(schemas) should have the proper diagnostics -ok 7 - schemas_are(schemas, desc) missing should fail -ok 8 - schemas_are(schemas, desc) missing should have the proper description -ok 9 - schemas_are(schemas, desc) missing should have the proper diagnostics -ok 10 - schemas_are(schemas, desc) extras should fail -ok 11 - schemas_are(schemas, desc) extras should have the proper description -ok 12 - schemas_are(schemas, desc) extras should have the proper diagnostics -ok 13 - schemas_are(schemas, desc) missing and extras should fail -ok 14 - schemas_are(schemas, desc) missing and extras should have the proper description -ok 15 - schemas_are(schemas, desc) missing and extras should have the proper diagnostics -ok 16 - tables_are(schema, tables, desc) should pass -ok 17 - tables_are(schema, tables, desc) should have the proper description -ok 18 - tables_are(schema, tables, desc) should have the proper diagnostics -ok 19 - tables_are(schema, tables) should pass -ok 20 - tables_are(schema, tables) should have the proper description -ok 21 - tables_are(schema, tables) should have the proper diagnostics -ok 22 - tables_are(tables) should pass -ok 23 - tables_are(tables) should have the proper description -ok 24 - tables_are(tables) should have the proper diagnostics -ok 25 - tables_are(tables, desc) should pass -ok 26 - tables_are(tables, desc) should have the proper description -ok 27 - tables_are(tables, desc) should have the proper diagnostics -ok 28 - tables_are(schema, tables) missing should fail -ok 29 - tables_are(schema, tables) missing should have the proper description -ok 30 - tables_are(schema, tables) missing should have the proper diagnostics -ok 31 - tables_are(tables) missing should fail -ok 32 - tables_are(tables) missing should have the proper description -ok 33 - tables_are(tables) missing should have the proper diagnostics -ok 34 - tables_are(schema, tables) extra should fail -ok 35 - tables_are(schema, tables) extra should have the proper description -ok 36 - tables_are(schema, tables) extra should have the proper diagnostics -ok 37 - tables_are(tables) extra should fail -ok 38 - tables_are(tables) extra should have the proper description -ok 39 - tables_are(tables) extra should have the proper diagnostics -ok 40 - tables_are(schema, tables) extra and missing should fail -ok 41 - tables_are(schema, tables) extra and missing should have the proper description -ok 42 - tables_are(schema, tables) extra and missing should have the proper diagnostics -ok 43 - tables_are(tables) extra and missing should fail -ok 44 - tables_are(tables) extra and missing should have the proper description -ok 45 - tables_are(tables) extra and missing should have the proper diagnostics -ok 46 - views_are(schema, views, desc) should pass -ok 47 - views_are(schema, views, desc) should have the proper description -ok 48 - views_are(schema, views, desc) should have the proper diagnostics -ok 49 - views_are(schema, views) should pass -ok 50 - views_are(schema, views) should have the proper description -ok 51 - views_are(schema, views) should have the proper diagnostics -ok 52 - views_are(views) should pass -ok 53 - views_are(views) should have the proper description -ok 54 - views_are(views) should have the proper diagnostics -ok 55 - views_are(views, desc) should pass -ok 56 - views_are(views, desc) should have the proper description -ok 57 - views_are(views, desc) should have the proper diagnostics -ok 58 - views_are(schema, views) missing should fail -ok 59 - views_are(schema, views) missing should have the proper description -ok 60 - views_are(schema, views) missing should have the proper diagnostics -ok 61 - views_are(views) missing should fail -ok 62 - views_are(views) missing should have the proper description -ok 63 - views_are(views) missing should have the proper diagnostics -ok 64 - views_are(schema, views) extra should fail -ok 65 - views_are(schema, views) extra should have the proper description -ok 66 - views_are(schema, views) extra should have the proper diagnostics -ok 67 - views_are(views) extra should fail -ok 68 - views_are(views) extra should have the proper description -ok 69 - views_are(views) extra should have the proper diagnostics -ok 70 - views_are(schema, views) extra and missing should fail -ok 71 - views_are(schema, views) extra and missing should have the proper description -ok 72 - views_are(schema, views) extra and missing should have the proper diagnostics -ok 73 - views_are(views) extra and missing should fail -ok 74 - views_are(views) extra and missing should have the proper description -ok 75 - views_are(views) extra and missing should have the proper diagnostics -ok 76 - sequences_are(schema, sequences, desc) should pass -ok 77 - sequences_are(schema, sequences, desc) should have the proper description -ok 78 - sequences_are(schema, sequences, desc) should have the proper diagnostics -ok 79 - sequences_are(schema, sequences) should pass -ok 80 - sequences_are(schema, sequences) should have the proper description -ok 81 - sequences_are(schema, sequences) should have the proper diagnostics -ok 82 - sequences_are(sequences) should pass -ok 83 - sequences_are(sequences) should have the proper description -ok 84 - sequences_are(sequences) should have the proper diagnostics -ok 85 - sequences_are(sequences, desc) should pass -ok 86 - sequences_are(sequences, desc) should have the proper description -ok 87 - sequences_are(sequences, desc) should have the proper diagnostics -ok 88 - sequences_are(schema, sequences) missing should fail -ok 89 - sequences_are(schema, sequences) missing should have the proper description -ok 90 - sequences_are(schema, sequences) missing should have the proper diagnostics -ok 91 - sequences_are(sequences) missing should fail -ok 92 - sequences_are(sequences) missing should have the proper description -ok 93 - sequences_are(sequences) missing should have the proper diagnostics -ok 94 - sequences_are(schema, sequences) extra should fail -ok 95 - sequences_are(schema, sequences) extra should have the proper description -ok 96 - sequences_are(schema, sequences) extra should have the proper diagnostics -ok 97 - sequences_are(sequences) extra should fail -ok 98 - sequences_are(sequences) extra should have the proper description -ok 99 - sequences_are(sequences) extra should have the proper diagnostics -ok 100 - sequences_are(schema, sequences) extra and missing should fail -ok 101 - sequences_are(schema, sequences) extra and missing should have the proper description -ok 102 - sequences_are(schema, sequences) extra and missing should have the proper diagnostics -ok 103 - sequences_are(sequences) extra and missing should fail -ok 104 - sequences_are(sequences) extra and missing should have the proper description -ok 105 - sequences_are(sequences) extra and missing should have the proper diagnostics +1..120 +ok 1 - tablespaces_are(schemas, desc) should pass +ok 2 - tablespaces_are(schemas, desc) should have the proper description +ok 3 - tablespaces_are(schemas, desc) should have the proper diagnostics +ok 4 - tablespaces_are(schemas) should pass +ok 5 - tablespaces_are(schemas) should have the proper description +ok 6 - tablespaces_are(schemas) should have the proper diagnostics +ok 7 - tablespaces_are(schemas, desc) missing should fail +ok 8 - tablespaces_are(schemas, desc) missing should have the proper description +ok 9 - tablespaces_are(schemas, desc) missing should have the proper diagnostics +ok 10 - tablespaces_are(schemas, desc) extra should fail +ok 11 - tablespaces_are(schemas, desc) extra should have the proper description +ok 12 - tablespaces_are(schemas, desc) extra should have the proper diagnostics +ok 13 - tablespaces_are(schemas, desc) extras and missing should fail +ok 14 - tablespaces_are(schemas, desc) extras and missing should have the proper description +ok 15 - tablespaces_are(schemas, desc) extras and missing should have the proper diagnostics +ok 16 - schemas_are(schemas, desc) should pass +ok 17 - schemas_are(schemas, desc) should have the proper description +ok 18 - schemas_are(schemas, desc) should have the proper diagnostics +ok 19 - schemas_are(schemas) should pass +ok 20 - schemas_are(schemas) should have the proper description +ok 21 - schemas_are(schemas) should have the proper diagnostics +ok 22 - schemas_are(schemas, desc) missing should fail +ok 23 - schemas_are(schemas, desc) missing should have the proper description +ok 24 - schemas_are(schemas, desc) missing should have the proper diagnostics +ok 25 - schemas_are(schemas, desc) extras should fail +ok 26 - schemas_are(schemas, desc) extras should have the proper description +ok 27 - schemas_are(schemas, desc) extras should have the proper diagnostics +ok 28 - schemas_are(schemas, desc) missing and extras should fail +ok 29 - schemas_are(schemas, desc) missing and extras should have the proper description +ok 30 - schemas_are(schemas, desc) missing and extras should have the proper diagnostics +ok 31 - tables_are(schema, tables, desc) should pass +ok 32 - tables_are(schema, tables, desc) should have the proper description +ok 33 - tables_are(schema, tables, desc) should have the proper diagnostics +ok 34 - tables_are(schema, tables) should pass +ok 35 - tables_are(schema, tables) should have the proper description +ok 36 - tables_are(schema, tables) should have the proper diagnostics +ok 37 - tables_are(tables) should pass +ok 38 - tables_are(tables) should have the proper description +ok 39 - tables_are(tables) should have the proper diagnostics +ok 40 - tables_are(tables, desc) should pass +ok 41 - tables_are(tables, desc) should have the proper description +ok 42 - tables_are(tables, desc) should have the proper diagnostics +ok 43 - tables_are(schema, tables) missing should fail +ok 44 - tables_are(schema, tables) missing should have the proper description +ok 45 - tables_are(schema, tables) missing should have the proper diagnostics +ok 46 - tables_are(tables) missing should fail +ok 47 - tables_are(tables) missing should have the proper description +ok 48 - tables_are(tables) missing should have the proper diagnostics +ok 49 - tables_are(schema, tables) extra should fail +ok 50 - tables_are(schema, tables) extra should have the proper description +ok 51 - tables_are(schema, tables) extra should have the proper diagnostics +ok 52 - tables_are(tables) extra should fail +ok 53 - tables_are(tables) extra should have the proper description +ok 54 - tables_are(tables) extra should have the proper diagnostics +ok 55 - tables_are(schema, tables) extra and missing should fail +ok 56 - tables_are(schema, tables) extra and missing should have the proper description +ok 57 - tables_are(schema, tables) extra and missing should have the proper diagnostics +ok 58 - tables_are(tables) extra and missing should fail +ok 59 - tables_are(tables) extra and missing should have the proper description +ok 60 - tables_are(tables) extra and missing should have the proper diagnostics +ok 61 - views_are(schema, views, desc) should pass +ok 62 - views_are(schema, views, desc) should have the proper description +ok 63 - views_are(schema, views, desc) should have the proper diagnostics +ok 64 - views_are(schema, views) should pass +ok 65 - views_are(schema, views) should have the proper description +ok 66 - views_are(schema, views) should have the proper diagnostics +ok 67 - views_are(views) should pass +ok 68 - views_are(views) should have the proper description +ok 69 - views_are(views) should have the proper diagnostics +ok 70 - views_are(views, desc) should pass +ok 71 - views_are(views, desc) should have the proper description +ok 72 - views_are(views, desc) should have the proper diagnostics +ok 73 - views_are(schema, views) missing should fail +ok 74 - views_are(schema, views) missing should have the proper description +ok 75 - views_are(schema, views) missing should have the proper diagnostics +ok 76 - views_are(views) missing should fail +ok 77 - views_are(views) missing should have the proper description +ok 78 - views_are(views) missing should have the proper diagnostics +ok 79 - views_are(schema, views) extra should fail +ok 80 - views_are(schema, views) extra should have the proper description +ok 81 - views_are(schema, views) extra should have the proper diagnostics +ok 82 - views_are(views) extra should fail +ok 83 - views_are(views) extra should have the proper description +ok 84 - views_are(views) extra should have the proper diagnostics +ok 85 - views_are(schema, views) extra and missing should fail +ok 86 - views_are(schema, views) extra and missing should have the proper description +ok 87 - views_are(schema, views) extra and missing should have the proper diagnostics +ok 88 - views_are(views) extra and missing should fail +ok 89 - views_are(views) extra and missing should have the proper description +ok 90 - views_are(views) extra and missing should have the proper diagnostics +ok 91 - sequences_are(schema, sequences, desc) should pass +ok 92 - sequences_are(schema, sequences, desc) should have the proper description +ok 93 - sequences_are(schema, sequences, desc) should have the proper diagnostics +ok 94 - sequences_are(schema, sequences) should pass +ok 95 - sequences_are(schema, sequences) should have the proper description +ok 96 - sequences_are(schema, sequences) should have the proper diagnostics +ok 97 - sequences_are(sequences) should pass +ok 98 - sequences_are(sequences) should have the proper description +ok 99 - sequences_are(sequences) should have the proper diagnostics +ok 100 - sequences_are(sequences, desc) should pass +ok 101 - sequences_are(sequences, desc) should have the proper description +ok 102 - sequences_are(sequences, desc) should have the proper diagnostics +ok 103 - sequences_are(schema, sequences) missing should fail +ok 104 - sequences_are(schema, sequences) missing should have the proper description +ok 105 - sequences_are(schema, sequences) missing should have the proper diagnostics +ok 106 - sequences_are(sequences) missing should fail +ok 107 - sequences_are(sequences) missing should have the proper description +ok 108 - sequences_are(sequences) missing should have the proper diagnostics +ok 109 - sequences_are(schema, sequences) extra should fail +ok 110 - sequences_are(schema, sequences) extra should have the proper description +ok 111 - sequences_are(schema, sequences) extra should have the proper diagnostics +ok 112 - sequences_are(sequences) extra should fail +ok 113 - sequences_are(sequences) extra should have the proper description +ok 114 - sequences_are(sequences) extra should have the proper diagnostics +ok 115 - sequences_are(schema, sequences) extra and missing should fail +ok 116 - sequences_are(schema, sequences) extra and missing should have the proper description +ok 117 - sequences_are(schema, sequences) extra and missing should have the proper diagnostics +ok 118 - sequences_are(sequences) extra and missing should fail +ok 119 - sequences_are(sequences) extra and missing should have the proper description +ok 120 - sequences_are(sequences) extra and missing should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 1bb0d143ce5e..228b7430b87b 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3697,54 +3697,91 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; --- schemas_are( schemas, description ) -CREATE OR REPLACE FUNCTION schemas_are ( NAME[], TEXT ) +CREATE OR REPLACE FUNCTION _are ( text, name[], name[], TEXT ) RETURNS TEXT AS $$ DECLARE - extras name[]; - missing name[]; + what ALIAS FOR $1; + extras ALIAS FOR $2; + missing ALIAS FOR $3; + descr ALIAS FOR $4; msg TEXT := ''; res BOOLEAN := TRUE; BEGIN - SELECT 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) - ) INTO extras; - - SELECT 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' - ) INTO missing; - IF extras[1] IS NOT NULL THEN res = FALSE; msg := E'\n' || diag( - E' These are extra schemas:\n ' + ' These are extra ' || what || E':\n ' || array_to_string( extras, E'\n ' ) ); END IF; IF missing[1] IS NOT NULL THEN res = FALSE; msg := msg || E'\n' || diag( - E' These schemas are missing:\n ' + ' These ' || what || E' are missing:\n ' || array_to_string( missing, E'\n ' ) ); END IF; - RETURN ok(res, $2) || msg; + 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 $$ diff --git a/sql/aretap.sql b/sql/aretap.sql index bc62a3d0b47c..c77cbd3f6277 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(105); +SELECT plan(120); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -30,6 +30,62 @@ CREATE SEQUENCE public.sumeseq; CREATE SCHEMA someschema; RESET client_min_messages; +/****************************************************************************/ +-- Test tablespaces_are(). + +CREATE FUNCTION ___myts(ex text) RETURNS NAME[] AS $$ + SELECT ARRAY( + SELECT spcname + FROM pg_catalog.pg_tablespace + WHERE spcname <> $1 + ); +$$ LANGUAGE SQL; + +SELECT * FROM check_test( + tablespaces_are( ___myts(''), 'whatever' ), + true, + 'tablespaces_are(schemas, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + tablespaces_are( ___myts('') ), + true, + 'tablespaces_are(schemas)', + 'There should be the correct tablespaces', + '' +); + +SELECT * FROM check_test( + tablespaces_are( array_append(___myts(''), '__booya__'), 'whatever' ), + false, + 'tablespaces_are(schemas, desc) missing', + 'whatever', + ' These tablespaces are missing: + __booya__' +); + +SELECT * FROM check_test( + tablespaces_are( ___myts('pg_default'), 'whatever' ), + false, + 'tablespaces_are(schemas, desc) extra', + 'whatever', + ' These are extra tablespaces: + pg_default' +); + +SELECT * FROM check_test( + tablespaces_are( array_append(___myts('pg_default'), '__booya__'), 'whatever' ), + false, + 'tablespaces_are(schemas, desc) extras and missing', + 'whatever', + ' These are extra tablespaces: + pg_default + These tablespaces are missing: + __booya__' +); + /****************************************************************************/ -- Test schemas_are(). From 6cc627e93bb9db75b0a6adcf67bd92a62dc95b61 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 22 May 2009 16:56:27 -0400 Subject: [PATCH 0348/1195] Quote idents. --- README.pgtap | 2 -- pgtap.sql.in | 12 ++++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/README.pgtap b/README.pgtap index 3cfb15d93ea2..542c0be50579 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2713,7 +2713,6 @@ To Do ----- * Useful schema testing functions to consider adding: - * `tablespaces_are()` * `functions_are()` * `indexes_are()` * `users_are()` @@ -2726,7 +2725,6 @@ To Do * `seq_increments_by()` * `seq_starts_at()` * `seq_cycles()` - * `can_only()` Suported Versions ----------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index 228b7430b87b..b4b31e1aac0b 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3711,14 +3711,14 @@ BEGIN res = FALSE; msg := E'\n' || diag( ' These are extra ' || what || E':\n ' - || array_to_string( extras, E'\n ' ) + || _ident_array_to_string( extras, E'\n ' ) ); END IF; IF missing[1] IS NOT NULL THEN res = FALSE; msg := msg || E'\n' || diag( ' These ' || what || E' are missing:\n ' - || array_to_string( missing, E'\n ' ) + || _ident_array_to_string( missing, E'\n ' ) ); END IF; @@ -3860,14 +3860,14 @@ BEGIN res = FALSE; msg := E'\n' || diag( ' Schema ' || quote_ident($3) || ' has these extra ' || $2 || E':\n ' - || array_to_string( extras, E'\n ' ) + || _ident_array_to_string( extras, E'\n ' ) ); END IF; IF missing[1] IS NOT NULL THEN res = FALSE; msg := msg || E'\n' || diag( ' Schema ' || quote_ident($3) || ' is missing these ' || $2 || E':\n ' - || array_to_string( missing, E'\n ' ) + || _ident_array_to_string( missing, E'\n ' ) ); END IF; @@ -3887,14 +3887,14 @@ BEGIN res = FALSE; msg := E'\n' || diag( ' These are extra ' || $2 || E':\n ' - || array_to_string( extras, E'\n ' ) + || _ident_array_to_string( extras, E'\n ' ) ); END IF; IF missing[1] IS NOT NULL THEN res = FALSE; msg := msg || E'\n' || diag( ' These ' || $2 || E' are missing:\n ' - || array_to_string( missing, E'\n ' ) + || _ident_array_to_string( missing, E'\n ' ) ); END IF; From 1a32311590af386b53ed4c379343135dac1adb66 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 23 May 2009 13:21:21 -0400 Subject: [PATCH 0349/1195] Added `functions_are()`. --- Changes | 2 +- README.pgtap | 63 ++++++++++++++++++----- expected/aretap.out | 31 +++++++++++- pgtap.sql.in | 118 ++++++++++++++++++++++++++++++++++++++++++-- sql/aretap.sql | 111 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 306 insertions(+), 19 deletions(-) diff --git a/Changes b/Changes index dfe66bfe91cc..5509564cf01d 100644 --- a/Changes +++ b/Changes @@ -10,7 +10,7 @@ Revision history for pgTAP * Fixed a failing test for the version number string on 8.4 beta. * Added `performs_ok()`. * Added `tablespaces_are()`, `schemas_are()`, `tables_are()`, `views_are()`, - and `sequences_are()`. + `sequences_are()`, and `functions_are()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index 542c0be50579..582279db71b5 100644 --- a/README.pgtap +++ b/README.pgtap @@ -658,8 +658,8 @@ string. You will want to account for this and pad your estimates accordingly. It's best to think of this as a brute force comparison of runtimes, in order to ensure that a query is not *really* slow (think seconds). -A Wicked Schema ---------------- +The Schema Things +----------------- Need to make sure that your database is designed just the way you think it should be? Use these test functions and rest easy. @@ -759,7 +759,7 @@ specified tablespace does *not* exist. This function tests that all of the schemas in the databse only the schemas that *should* be there. In other words, given a list of schemas, this assertion will fail if there are schemas that are not in the list, or if there -are schemas in the list that are missing from the database. +are schemas in the list that are not found in the search path. This test is useful in environments where many developers may be making changes to the database, or where replication has been deployed, and you want @@ -816,7 +816,7 @@ This function tests that all of the tables in the named schema, or that are visible in the search path, are only the tables that *should* be there. In other words, given a list of tables, this assertion will fail if there are tables that are not in the list, or if there are tables in the list that are -missing from the database. +not found in the search path. This test is useful in environments where many developers may be making changes to the database, or where replication has been deployed, and you want @@ -883,7 +883,7 @@ This function tests that all of the views in the named schema, or that are visible in the search path, are only the views that *should* be there. In other words, given a list of views, this assertion will fail if there are views that are not in the list, or if there are views in the list that are -missing from the database. +not found in the search path. This test is useful in environments where many developers may be making changes to the database, or where replication has been deployed, and you want @@ -946,7 +946,7 @@ This function tests that all of the sequences in the named schema, or that are visible in the search path, are only the sequences that *should* be there. In other words, given a list of sequences, this assertion will fail if there are sequences that are not in the list, or if there are sequences in the list that -are missing from the database. +are not found in the search path. This test is useful in environments where many developers may be making changes to the database, or where replication has been deployed, and you want @@ -1797,6 +1797,41 @@ a useful diagnostic: # have: hash_pass # want: hash_password +### `functions_are( schema, functions[], description )` ### +### `functions_are( schema, functions[] )` ### +### `functions_are( functions[], description )` ### +### `functions_are( functions[] )` ### + + SELECT functions_are( + 'myschema', + ARRAY[ 'foo', 'bar', 'frobnitz' ], + 'Should have the correct functions in myschema' + ); + +This function tests that all of the functions in the named schema, or that are +visible in the search path, are only the functions that *should* be there. In +other words, given a list of functions, this assertion will fail if there are +functions that are not in the list, or if there are functions in the list that +are not found in the search path. + +This test is useful in environments where many developers may be making +changes to the database, or where replication has been deployed, and you want +to make sure that the functions in a database are exactly the functions that +should exist in the database, no more, no less. + +If the `:schema` argument is omitted, functions will be sought in the search +path, excluding `pg_catalog.` If the description is omitted, a generally +useful default description will be generated. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing functions, like so: + + # Failed test 150: "whatever" + # Schema someschema has these extra functions: + # schnauinatify + # Schema someschema is missing these functions: + # frobnitz + ### `can( schema, functions[], description )` ### ### `can( schema, functions[] )` ### ### `can( functions[], description )` ### @@ -1804,11 +1839,16 @@ a useful diagnostic: SELECT can( 'pg_catalog', ARRAY['upper', 'lower'] ); -Checks to be sure that `:schema` has `:functions[]` defined. If `:schema` is -omitted, then `can()` will look for functions defined in schemas defined in -the search path. No matter how many functions are listed in `:functions[]`, a -single call to `can()` counts as one test. If you want otherwise, call `can()` -once for each function -- or better yet, use `can_ok()`. +Checks to be sure that `:schema` has `:functions[]` defined. This is subtley +different from `functions_are()`. `functions_are()` fails if the functions +defined in `:schema` are not exactly the functions defined in `:functions[]`. +`can()`, on the other hand, just makes sure that `functions[]` exist. + +If `:schema` is omitted, then `can()` will look for functions defined in +schemas defined in the search path. No matter how many functions are listed in +`:functions[]`, a single call to `can()` counts as one test. If you want +otherwise, call `can()` once for each function -- or better yet, use +`can_ok()`. If any of the functions are not defined, the test will fail and the diagnostics will output a list of the functions that are missing, like so: @@ -2713,7 +2753,6 @@ To Do ----- * Useful schema testing functions to consider adding: - * `functions_are()` * `indexes_are()` * `users_are()` * `groups_are()` diff --git a/expected/aretap.out b/expected/aretap.out index a88a6d790a29..df4131fb5fb9 100644 --- a/expected/aretap.out +++ b/expected/aretap.out @@ -1,5 +1,5 @@ \unset ECHO -1..120 +1..149 ok 1 - tablespaces_are(schemas, desc) should pass ok 2 - tablespaces_are(schemas, desc) should have the proper description ok 3 - tablespaces_are(schemas, desc) should have the proper diagnostics @@ -120,3 +120,32 @@ ok 117 - sequences_are(schema, sequences) extra and missing should have the prop ok 118 - sequences_are(sequences) extra and missing should fail ok 119 - sequences_are(sequences) extra and missing should have the proper description ok 120 - sequences_are(sequences) extra and missing should have the proper diagnostics +ok 121 - functions_are(schema, functions, desc) should pass +ok 122 - functions_are(schema, functions, desc) should have the proper description +ok 123 - functions_are(schema, functions, desc) should have the proper diagnostics +ok 124 - functions_are(schema, functions) should pass +ok 125 - functions_are(schema, functions) should have the proper description +ok 126 - functions_are(schema, functions, desc) + missing should fail +ok 127 - functions_are(schema, functions, desc) + missing should have the proper description +ok 128 - functions_are(schema, functions, desc) + missing should have the proper diagnostics +ok 129 - functions_are(schema, functions, desc) + extra should fail +ok 130 - functions_are(schema, functions, desc) + extra should have the proper description +ok 131 - functions_are(schema, functions, desc) + extra should have the proper diagnostics +ok 132 - functions_are(schema, functions, desc) + extra & missing should fail +ok 133 - functions_are(schema, functions, desc) + extra & missing should have the proper description +ok 134 - functions_are(schema, functions, desc) + extra & missing should have the proper diagnostics +ok 135 - functions_are(functions, desc) should pass +ok 136 - functions_are(functions, desc) should have the proper description +ok 137 - functions_are(functions, desc) should have the proper diagnostics +ok 138 - functions_are(functions) should pass +ok 139 - functions_are(functions) should have the proper description +ok 140 - functions_are(functions) should have the proper diagnostics +ok 141 - functions_are(functions, desc) + missing should fail +ok 142 - functions_are(functions, desc) + missing should have the proper description +ok 143 - functions_are(functions, desc) + missing should have the proper diagnostics +ok 144 - functions_are(functions, desc) + extra should fail +ok 145 - functions_are(functions, desc) + extra should have the proper description +ok 146 - functions_are(functions, desc) + extra should have the proper diagnostics +ok 147 - functions_are(functions, desc) + extra & missing should fail +ok 148 - functions_are(functions, desc) + extra & missing should have the proper description +ok 149 - functions_are(functions, desc) + extra & missing should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index b4b31e1aac0b..013f8e77bdc4 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2103,7 +2103,7 @@ RETURNS NAME[] AS $$ ) $$ LANGUAGE SQL stable; --- can( schema, func_names[], description ) +-- can( schema, functions[], description ) CREATE OR REPLACE FUNCTION can ( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ DECLARE @@ -2134,13 +2134,13 @@ BEGIN END; $$ LANGUAGE plpgsql; --- can( schema, func_names[] ) +-- 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( func_names[], description ) +-- can( functions[], description ) CREATE OR REPLACE FUNCTION can ( NAME[], TEXT ) RETURNS TEXT AS $$ DECLARE @@ -2166,7 +2166,7 @@ BEGIN END; $$ LANGUAGE plpgsql; --- can( func_names[] ) +-- can( functions[] ) CREATE OR REPLACE FUNCTION can ( NAME[] ) RETURNS TEXT AS $$ SELECT can( $1, 'Schema ' || _ident_array_to_string(current_schemas(true), ' or ') || ' can' ); @@ -3974,6 +3974,116 @@ RETURNS TEXT AS $$ SELECT _rels_are('S', 'sequences', $1, 'There should be the correct sequences' ); $$ LANGUAGE SQL; +-- functions_are( schema, functions[], description ) +CREATE OR REPLACE FUNCTION functions_are ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + extras name[]; + missing name[]; + msg TEXT := ''; + res BOOLEAN := TRUE; +BEGIN + SELECT ARRAY( + SELECT p.proname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace + WHERE n.nspname = $1 + EXCEPT + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + ) INTO extras; + + SELECT ARRAY( + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + EXCEPT + SELECT p.proname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace + WHERE n.nspname = $1 + ) INTO missing; + + IF extras[1] IS NOT NULL THEN + res = FALSE; + msg := E'\n' || diag( + ' Schema ' || quote_ident($1) || E' has these extra functions:\n ' + || _ident_array_to_string( extras, E'\n ' ) + ); + END IF; + IF missing[1] IS NOT NULL THEN + res = FALSE; + msg := msg || E'\n' || diag( + ' Schema ' || quote_ident($1) || E' is missing these functions:\n ' + || _ident_array_to_string( missing, E'\n ' ) + ); + END IF; + + RETURN ok(res, $3) || msg; +END; +$$ LANGUAGE plpgsql; + +-- 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 $$ +DECLARE + extras name[]; + missing name[]; + msg TEXT := ''; + res BOOLEAN := TRUE; +BEGIN + SELECT ARRAY( + SELECT p.proname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace + WHERE pg_catalog.pg_function_is_visible(p.oid) + AND n.nspname <> 'pg_catalog' + EXCEPT + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + ) INTO extras; + + SELECT ARRAY( + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + EXCEPT + SELECT p.proname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace + WHERE pg_catalog.pg_function_is_visible(p.oid) + AND n.nspname <> 'pg_catalog' + ) INTO missing; + + IF extras[1] IS NOT NULL THEN + res = FALSE; + msg := E'\n' || diag( + E' These are extra functions:\n ' + || _ident_array_to_string( extras, E'\n ' ) + ); + END IF; + IF missing[1] IS NOT NULL THEN + res = FALSE; + msg := msg || E'\n' || diag( + E' These functions are missing:\n ' + || _ident_array_to_string( missing, E'\n ' ) + ); + END IF; + + RETURN ok(res, $2) || msg; +END; +$$ LANGUAGE plpgsql; + +-- functions_are( functions[] ) +CREATE OR REPLACE FUNCTION functions_are ( NAME[] ) +RETURNS TEXT AS $$ + SELECT functions_are( $1, 'There should be the correct functions' ); +$$ 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 $$ diff --git a/sql/aretap.sql b/sql/aretap.sql index c77cbd3f6277..0638ada0d8cd 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(120); +SELECT plan(149); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -28,6 +28,10 @@ CREATE SEQUENCE public.someseq; CREATE SEQUENCE public.sumeseq; CREATE SCHEMA someschema; + +CREATE FUNCTION someschema.yip() returns boolean as 'SELECT TRUE' language SQL; +CREATE FUNCTION someschema.yap() returns boolean as 'SELECT TRUE' language SQL; + RESET client_min_messages; /****************************************************************************/ @@ -147,6 +151,7 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test tables_are(). + SELECT * FROM check_test( tables_are( 'public', ARRAY['fou', 'foo'], 'whatever' ), true, @@ -439,6 +444,110 @@ SELECT * FROM check_test( true ); +/****************************************************************************/ +-- Test functions_are(). + +SELECT * FROM check_test( + functions_are( 'someschema', ARRAY['yip', 'yap'], 'whatever' ), + true, + 'functions_are(schema, functions, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + functions_are( 'someschema', ARRAY['yip', 'yap'] ), + true, + 'functions_are(schema, functions)', + 'Schema someschema should have the correct functions' + '' +); + +SELECT * FROM check_test( + functions_are( 'someschema', ARRAY['yip', 'yap', 'yop'], 'whatever' ), + false, + 'functions_are(schema, functions, desc) + missing', + 'whatever', + ' Schema someschema is missing these functions: + yop' +); + +SELECT * FROM check_test( + functions_are( 'someschema', ARRAY['yip'], 'whatever' ), + false, + 'functions_are(schema, functions, desc) + extra', + 'whatever', + ' Schema someschema has these extra functions: + yap' +); + +SELECT * FROM check_test( + functions_are( 'someschema', ARRAY['yap', 'yop'], 'whatever' ), + false, + 'functions_are(schema, functions, desc) + extra & missing', + 'whatever', + ' Schema someschema has these extra functions: + yip + Schema someschema is missing these functions: + yop' +); + +CREATE FUNCTION ___myfunk(ex text) RETURNS NAME[] AS $$ + SELECT ARRAY( + SELECT p.proname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace + WHERE pg_catalog.pg_function_is_visible(p.oid) + AND n.nspname <> 'pg_catalog' + AND p.proname <> $1 + ); +$$ LANGUAGE SQL; + +SELECT * FROM check_test( + functions_are( ___myfunk(''), 'whatever' ), + true, + 'functions_are(functions, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + functions_are( ___myfunk('') ), + true, + 'functions_are(functions)', + 'There should be the correct functions', + '' +); + +SELECT * FROM check_test( + functions_are( array_append(___myfunk(''), '__booyah__'), 'whatever' ), + false, + 'functions_are(functions, desc) + missing', + 'whatever', + ' These functions are missing: + __booyah__' +); + +SELECT * FROM check_test( + functions_are( ___myfunk('check_test'), 'whatever' ), + false, + 'functions_are(functions, desc) + extra', + 'whatever', + ' These are extra functions: + check_test' +); + +SELECT * FROM check_test( + functions_are( array_append(___myfunk('check_test'), '__booyah__'), 'whatever' ), + false, + 'functions_are(functions, desc) + extra & missing', + 'whatever', + ' These are extra functions: + check_test + These functions are missing: + __booyah__' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From f0ff19477e60a3a94d2d6ef85bd3f07890a144a6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 23 May 2009 13:58:10 -0400 Subject: [PATCH 0350/1195] Added search path to default descriptions and diagnostics for the `*_are()` functions. --- pgtap.sql.in | 16 +++++++------- sql/aretap.sql | 58 +++++++++++++++++++++++++------------------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 013f8e77bdc4..f212e2eb39b4 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3886,14 +3886,14 @@ BEGIN IF extras[1] IS NOT NULL THEN res = FALSE; msg := E'\n' || diag( - ' These are extra ' || $2 || E':\n ' + E' Search path ' || pg_catalog.current_setting('search_path') || ' has these extra ' || $2 || E':\n ' || _ident_array_to_string( extras, E'\n ' ) ); END IF; IF missing[1] IS NOT NULL THEN res = FALSE; msg := msg || E'\n' || diag( - ' These ' || $2 || E' are missing:\n ' + E' Search path ' || pg_catalog.current_setting('search_path') || ' is missing these ' || $2 || E':\n ' || _ident_array_to_string( missing, E'\n ' ) ); END IF; @@ -3923,7 +3923,7 @@ $$ LANGUAGE SQL; -- tables_are( tables ) CREATE OR REPLACE FUNCTION tables_are ( NAME[] ) RETURNS TEXT AS $$ - SELECT _rels_are('r', 'tables', $1, 'There should be the correct tables' ); + SELECT _rels_are('r', 'tables', $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct tables' ); $$ LANGUAGE SQL; -- views_are( schema, views, description ) @@ -3947,7 +3947,7 @@ $$ LANGUAGE SQL; -- views_are( views ) CREATE OR REPLACE FUNCTION views_are ( NAME[] ) RETURNS TEXT AS $$ - SELECT _rels_are('v', 'views', $1, 'There should be the correct views' ); + SELECT _rels_are('v', 'views', $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views' ); $$ LANGUAGE SQL; -- sequences_are( schema, sequences, description ) @@ -3971,7 +3971,7 @@ $$ LANGUAGE SQL; -- sequences_are( sequences ) CREATE OR REPLACE FUNCTION sequences_are ( NAME[] ) RETURNS TEXT AS $$ - SELECT _rels_are('S', 'sequences', $1, 'There should be the correct sequences' ); + SELECT _rels_are('S', 'sequences', $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct sequences' ); $$ LANGUAGE SQL; -- functions_are( schema, functions[], description ) @@ -4062,14 +4062,14 @@ BEGIN IF extras[1] IS NOT NULL THEN res = FALSE; msg := E'\n' || diag( - E' These are extra functions:\n ' + ' Search path ' || pg_catalog.current_setting('search_path') || E' has these extra functions:\n ' || _ident_array_to_string( extras, E'\n ' ) ); END IF; IF missing[1] IS NOT NULL THEN res = FALSE; msg := msg || E'\n' || diag( - E' These functions are missing:\n ' + ' Search path ' || pg_catalog.current_setting('search_path') || E' is missing these functions:\n ' || _ident_array_to_string( missing, E'\n ' ) ); END IF; @@ -4081,7 +4081,7 @@ $$ LANGUAGE plpgsql; -- functions_are( functions[] ) CREATE OR REPLACE FUNCTION functions_are ( NAME[] ) RETURNS TEXT AS $$ - SELECT functions_are( $1, 'There should be the correct functions' ); + SELECT functions_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct functions' ); $$ LANGUAGE SQL; -- check_test( test_output, pass, name, description, diag, match_diag ) diff --git a/sql/aretap.sql b/sql/aretap.sql index 0638ada0d8cd..dcb2a5fa90ec 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -172,7 +172,7 @@ SELECT * FROM check_test( tables_are( ARRAY['fou', 'foo'] ), true, 'tables_are(tables)', - 'There should be the correct tables', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct tables', '' ); @@ -197,8 +197,8 @@ SELECT * FROM check_test( tables_are( ARRAY['fou', 'foo', 'bar'] ), false, 'tables_are(tables) missing', - 'There should be the correct tables', - ' These tables are missing: + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct tables', + ' Search path ' || pg_catalog.current_setting('search_path') || ' is missing these tables: bar' ); @@ -215,8 +215,8 @@ SELECT * FROM check_test( tables_are( ARRAY['fou'] ), false, 'tables_are(tables) extra', - 'There should be the correct tables', - ' These are extra tables: + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct tables', + ' Search path ' || pg_catalog.current_setting('search_path') || ' has these extra tables: foo' ); @@ -238,11 +238,11 @@ SELECT * FROM check_test( tables_are( ARRAY['bar', 'baz'] ), false, 'tables_are(tables) extra and missing', - 'There should be the correct tables', - ' These are extra tables: + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct tables', + ' Search path ' || replace(pg_catalog.current_setting('search_path'), '$', E'\\$') || ' has these extra tables:' || ' fo[ou] fo[ou] - These tables are missing: + Search path ' || replace(pg_catalog.current_setting('search_path'), '$', E'\\$') || ' is missing these tables:' || ' ba[rz] ba[rz]', true @@ -270,7 +270,7 @@ SELECT * FROM check_test( views_are( ARRAY['vou', 'voo'] ), true, 'views_are(views)', - 'There should be the correct views', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views', '' ); @@ -295,8 +295,8 @@ SELECT * FROM check_test( views_are( ARRAY['vou', 'voo', 'bar'] ), false, 'views_are(views) missing', - 'There should be the correct views', - ' These views are missing: + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views', + ' Search path ' || pg_catalog.current_setting('search_path') || ' is missing these views: bar' ); @@ -313,8 +313,8 @@ SELECT * FROM check_test( views_are( ARRAY['vou'] ), false, 'views_are(views) extra', - 'There should be the correct views', - ' These are extra views: + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views', + ' Search path ' || pg_catalog.current_setting('search_path') || ' has these extra views: voo' ); @@ -336,11 +336,11 @@ SELECT * FROM check_test( views_are( ARRAY['bar', 'baz'] ), false, 'views_are(views) extra and missing', - 'There should be the correct views', - ' These are extra views: + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views', + ' Search path ' || replace(pg_catalog.current_setting('search_path'), '$', E'\\$') || ' has these extra views:' || ' vo[ou] vo[ou] - These views are missing: + Search path ' || replace(pg_catalog.current_setting('search_path'), '$', E'\\$') || ' is missing these views:' || ' ba[rz] ba[rz]', true @@ -368,7 +368,7 @@ SELECT * FROM check_test( sequences_are( ARRAY['sumeseq', 'someseq'] ), true, 'sequences_are(sequences)', - 'There should be the correct sequences', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct sequences', '' ); @@ -393,8 +393,8 @@ SELECT * FROM check_test( sequences_are( ARRAY['sumeseq', 'someseq', 'bar'] ), false, 'sequences_are(sequences) missing', - 'There should be the correct sequences', - ' These sequences are missing: + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct sequences', + ' Search path ' || pg_catalog.current_setting('search_path') || ' is missing these sequences: bar' ); @@ -411,8 +411,8 @@ SELECT * FROM check_test( sequences_are( ARRAY['sumeseq'] ), false, 'sequences_are(sequences) extra', - 'There should be the correct sequences', - ' These are extra sequences: + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct sequences', + ' Search path ' || pg_catalog.current_setting('search_path') || ' has these extra sequences: someseq' ); @@ -434,11 +434,11 @@ SELECT * FROM check_test( sequences_are( ARRAY['bar', 'baz'] ), false, 'sequences_are(sequences) extra and missing', - 'There should be the correct sequences', - ' These are extra sequences: + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct sequences', + ' Search path ' || replace(pg_catalog.current_setting('search_path'), '$', E'\\$') || ' has these extra sequences:' || ' s[ou]meseq s[ou]meseq - These sequences are missing: + Search path ' || replace(pg_catalog.current_setting('search_path'), '$', E'\\$') || ' is missing these sequences:' || ' ba[rz] ba[rz]', true @@ -515,7 +515,7 @@ SELECT * FROM check_test( functions_are( ___myfunk('') ), true, 'functions_are(functions)', - 'There should be the correct functions', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct functions', '' ); @@ -524,7 +524,7 @@ SELECT * FROM check_test( false, 'functions_are(functions, desc) + missing', 'whatever', - ' These functions are missing: + ' Search path ' || pg_catalog.current_setting('search_path') || ' is missing these functions: __booyah__' ); @@ -533,7 +533,7 @@ SELECT * FROM check_test( false, 'functions_are(functions, desc) + extra', 'whatever', - ' These are extra functions: + ' Search path ' || pg_catalog.current_setting('search_path') || ' has these extra functions: check_test' ); @@ -542,9 +542,9 @@ SELECT * FROM check_test( false, 'functions_are(functions, desc) + extra & missing', 'whatever', - ' These are extra functions: + ' Search path ' || pg_catalog.current_setting('search_path') || ' has these extra functions: check_test - These functions are missing: + Search path ' || pg_catalog.current_setting('search_path') || ' is missing these functions: __booyah__' ); From 6f3446440c39120647b8dc06d55430676ddc5e1e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 23 May 2009 14:42:35 -0400 Subject: [PATCH 0351/1195] Added `indexes_are()`. --- Changes | 2 +- README.pgtap | 42 ++++++++++++++-- expected/aretap.out | 32 +++++++++++- pgtap.sql.in | 82 ++++++++++++++++++++++++++++++- sql/aretap.sql | 117 ++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 257 insertions(+), 18 deletions(-) diff --git a/Changes b/Changes index 5509564cf01d..4f28f751f06f 100644 --- a/Changes +++ b/Changes @@ -10,7 +10,7 @@ Revision history for pgTAP * Fixed a failing test for the version number string on 8.4 beta. * Added `performs_ok()`. * Added `tablespaces_are()`, `schemas_are()`, `tables_are()`, `views_are()`, - `sequences_are()`, and `functions_are()`. + `sequences_are()`, `functions_are()`, and `indexes_are()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index 582279db71b5..d08028451940 100644 --- a/README.pgtap +++ b/README.pgtap @@ -715,9 +715,9 @@ missing tablespaces, like so: not ok 121 - There should be the correct tablespaces # Failed test 121: "There should be the correct tablespaces" - # These are extra tablespaces: + # Extra tablespaces: # pg_default - # These tablespaces are missing: + # Missing tablespaces: # __booya__ ### `has_tablespace( tablespace, location, description )` ### @@ -1629,6 +1629,41 @@ not exist. Just like `col_is_pk()`, except that it test that the column or array of columns have a check constraint on them. +### `indexes_are( schema, table, indexes[], description )` ### +### `indexes_are( schema, table, indexes[] )` ### +### `indexes_are( table, indexes[], description )` ### +### `indexes_are( table, indexes[] )` ### + + SELECT indexes_are( + 'myschema', + 'atable', + ARRAY[ 'atable_pkey', 'idx_atable_name' ], + 'Should have the correct indexes on myschema.atable' + ); + +This function tests that all of the indexes on the named table are only the +indexes that *should* be on that table. In other words, given a list of +indexes, this assertion will fail if there are indexes that are not in the +list, or if there are indexes in the list that are not on the table. + +This test is useful in environments where many developers may be making +changes to the database, or where replication has been deployed, and you want +to make sure that the indexes on a table are exactly the indexes that should +exist on a table, no more, no less. + +If the `:schema` argument is omitted, the table must be visible in the search +path. If the description is omitted, a generally useful default description +will be generated. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing indexes, like so: + + # Failed test 180: "Table fou should have the correct indexes" + # Extra indexes: + # fou_pkey + # Missing indexes: + # idx_fou_name + ### `has_index( schema, table, index, columns[], description )` ### ### `has_index( schema, table, index, columns[] )` ### ### `has_index( schema, table, index, column/expression, description )` ### @@ -1828,7 +1863,7 @@ missing functions, like so: # Failed test 150: "whatever" # Schema someschema has these extra functions: - # schnauinatify + # schnauzify # Schema someschema is missing these functions: # frobnitz @@ -2753,7 +2788,6 @@ To Do ----- * Useful schema testing functions to consider adding: - * `indexes_are()` * `users_are()` * `groups_are()` * `has_operator_class()` diff --git a/expected/aretap.out b/expected/aretap.out index df4131fb5fb9..d0329ad9ec1c 100644 --- a/expected/aretap.out +++ b/expected/aretap.out @@ -1,5 +1,5 @@ \unset ECHO -1..149 +1..179 ok 1 - tablespaces_are(schemas, desc) should pass ok 2 - tablespaces_are(schemas, desc) should have the proper description ok 3 - tablespaces_are(schemas, desc) should have the proper diagnostics @@ -149,3 +149,33 @@ ok 146 - functions_are(functions, desc) + extra should have the proper diagnosti ok 147 - functions_are(functions, desc) + extra & missing should fail ok 148 - functions_are(functions, desc) + extra & missing should have the proper description ok 149 - functions_are(functions, desc) + extra & missing should have the proper diagnostics +ok 150 - indexes_are(schema, table, indexes, desc) should pass +ok 151 - indexes_are(schema, table, indexes, desc) should have the proper description +ok 152 - indexes_are(schema, table, indexes, desc) should have the proper diagnostics +ok 153 - indexes_are(schema, table, indexes) should pass +ok 154 - indexes_are(schema, table, indexes) should have the proper description +ok 155 - indexes_are(schema, table, indexes) should have the proper diagnostics +ok 156 - indexes_are(schema, table, indexes) + extra should fail +ok 157 - indexes_are(schema, table, indexes) + extra should have the proper description +ok 158 - indexes_are(schema, table, indexes) + extra should have the proper diagnostics +ok 159 - indexes_are(schema, table, indexes) + missing should fail +ok 160 - indexes_are(schema, table, indexes) + missing should have the proper description +ok 161 - indexes_are(schema, table, indexes) + missing should have the proper diagnostics +ok 162 - indexes_are(schema, table, indexes) + extra & missing should fail +ok 163 - indexes_are(schema, table, indexes) + extra & missing should have the proper description +ok 164 - indexes_are(schema, table, indexes) + extra & missing should have the proper diagnostics +ok 165 - indexes_are(table, indexes, desc) should pass +ok 166 - indexes_are(table, indexes, desc) should have the proper description +ok 167 - indexes_are(table, indexes, desc) should have the proper diagnostics +ok 168 - indexes_are(table, indexes) should pass +ok 169 - indexes_are(table, indexes) should have the proper description +ok 170 - indexes_are(table, indexes) should have the proper diagnostics +ok 171 - indexes_are(table, indexes) + extra should fail +ok 172 - indexes_are(table, indexes) + extra should have the proper description +ok 173 - indexes_are(table, indexes) + extra should have the proper diagnostics +ok 174 - indexes_are(table, indexes) + missing should fail +ok 175 - indexes_are(table, indexes) + missing should have the proper description +ok 176 - indexes_are(table, indexes) + missing should have the proper diagnostics +ok 177 - indexes_are(table, indexes) + extra & missing should fail +ok 178 - indexes_are(table, indexes) + extra & missing should have the proper description +ok 179 - indexes_are(table, indexes) + extra & missing should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index f212e2eb39b4..e88eb74f8ab2 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3710,14 +3710,14 @@ BEGIN IF extras[1] IS NOT NULL THEN res = FALSE; msg := E'\n' || diag( - ' These are extra ' || what || E':\n ' + ' Extra ' || what || E':\n ' || _ident_array_to_string( extras, E'\n ' ) ); END IF; IF missing[1] IS NOT NULL THEN res = FALSE; msg := msg || E'\n' || diag( - ' These ' || what || E' are missing:\n ' + ' Missing ' || what || E':\n ' || _ident_array_to_string( missing, E'\n ' ) ); END IF; @@ -4084,6 +4084,84 @@ 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 + WHERE ct.relname = $1 + AND pg_catalog.pg_table_is_visible(ct.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 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 + WHERE ct.relname = $1 + AND pg_catalog.pg_table_is_visible(ct.oid) + ), + $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; + -- 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 $$ diff --git a/sql/aretap.sql b/sql/aretap.sql index dcb2a5fa90ec..b3424f037a63 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(149); +SELECT plan(179); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -21,6 +21,10 @@ CREATE TYPE public.sometype AS ( name TEXT ); +CREATE INDEX idx_fou_id ON public.fou(id); +CREATE INDEX idx_fou_name ON public.fou(name); +CREATE INDEX idx_foo_id ON public.foo(id); + CREATE VIEW voo AS SELECT * FROM foo; CREATE VIEW vou AS SELECT * FROM fou; @@ -66,7 +70,7 @@ SELECT * FROM check_test( false, 'tablespaces_are(schemas, desc) missing', 'whatever', - ' These tablespaces are missing: + ' Missing tablespaces: __booya__' ); @@ -75,7 +79,7 @@ SELECT * FROM check_test( false, 'tablespaces_are(schemas, desc) extra', 'whatever', - ' These are extra tablespaces: + ' Extra tablespaces: pg_default' ); @@ -84,9 +88,9 @@ SELECT * FROM check_test( false, 'tablespaces_are(schemas, desc) extras and missing', 'whatever', - ' These are extra tablespaces: + ' Extra tablespaces: pg_default - These tablespaces are missing: + Missing tablespaces: __booya__' ); @@ -124,7 +128,7 @@ SELECT * FROM check_test( false, 'schemas_are(schemas, desc) missing', 'whatever', - ' These schemas are missing: + ' Missing schemas: __howdy__' ); @@ -133,7 +137,7 @@ SELECT * FROM check_test( false, 'schemas_are(schemas, desc) extras', 'whatever', - ' These are extra schemas: + ' Extra schemas: someschema' ); @@ -142,13 +146,12 @@ SELECT * FROM check_test( false, 'schemas_are(schemas, desc) missing and extras', 'whatever', - ' These are extra schemas: + ' Extra schemas: someschema - These schemas are missing: + Missing schemas: __howdy__' ); - /****************************************************************************/ -- Test tables_are(). @@ -548,6 +551,100 @@ SELECT * FROM check_test( __booyah__' ); +/****************************************************************************/ +-- Test indexes_are(). +SELECT * FROM check_test( + indexes_are( 'public', 'fou', ARRAY['idx_fou_id', 'idx_fou_name', 'fou_pkey'], 'whatever' ), + true, + 'indexes_are(schema, table, indexes, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + indexes_are( 'public', 'fou', ARRAY['idx_fou_id', 'idx_fou_name', 'fou_pkey'] ), + true, + 'indexes_are(schema, table, indexes)', + 'Table public.fou should have the correct indexes', + '' +); + +SELECT * FROM check_test( + indexes_are( 'public', 'fou', ARRAY['idx_fou_id', 'idx_fou_name'] ), + false, + 'indexes_are(schema, table, indexes) + extra', + 'Table public.fou should have the correct indexes', + ' Extra indexes: + fou_pkey' +); + +SELECT * FROM check_test( + indexes_are( 'public', 'fou', ARRAY['idx_fou_id', 'idx_fou_name', 'fou_pkey', 'howdy'] ), + false, + 'indexes_are(schema, table, indexes) + missing', + 'Table public.fou should have the correct indexes', + ' Missing indexes: + howdy' +); + +SELECT * FROM check_test( + indexes_are( 'public', 'fou', ARRAY['idx_fou_id', 'idx_fou_name', 'howdy'] ), + false, + 'indexes_are(schema, table, indexes) + extra & missing', + 'Table public.fou should have the correct indexes', + ' Extra indexes: + fou_pkey + Missing indexes: + howdy' +); + +SELECT * FROM check_test( + indexes_are( 'fou', ARRAY['idx_fou_id', 'idx_fou_name', 'fou_pkey'], 'whatever' ), + true, + 'indexes_are(table, indexes, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + indexes_are( 'fou', ARRAY['idx_fou_id', 'idx_fou_name', 'fou_pkey'] ), + true, + 'indexes_are(table, indexes)', + 'Table fou should have the correct indexes', + '' +); + +SELECT * FROM check_test( + indexes_are( 'fou', ARRAY['idx_fou_id', 'idx_fou_name'] ), + false, + 'indexes_are(table, indexes) + extra', + 'Table fou should have the correct indexes', + ' Extra indexes: + fou_pkey' +); + +SELECT * FROM check_test( + indexes_are( 'fou', ARRAY['idx_fou_id', 'idx_fou_name', 'fou_pkey', 'howdy'] ), + false, + 'indexes_are(table, indexes) + missing', + 'Table fou should have the correct indexes', + ' Missing indexes: + howdy' +); + +SELECT * FROM check_test( + indexes_are( 'fou', ARRAY['idx_fou_id', 'idx_fou_name', 'howdy'] ), + false, + 'indexes_are(table, indexes) + extra & missing', + 'Table fou should have the correct indexes', + ' Extra indexes: + fou_pkey + Missing indexes: + howdy' +); + +SELECT indexes_are( 'fou', ARRAY['idx_fou_id', 'idx_fou_name', 'howdy'] ); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From d182365d1b24d82464d909bae328c455eef9dc78 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 23 May 2009 14:58:37 -0400 Subject: [PATCH 0352/1195] Simplified the diagnostic headers for the `*_are()` functions. The name of the schema or search path ought to appear in the description, so there is no need to repeat it in the diagnostics. --- README.pgtap | 18 ++++----- pgtap.sql.in | 104 ++++++++++++++++--------------------------------- sql/aretap.sql | 66 +++++++++++++++---------------- 3 files changed, 75 insertions(+), 113 deletions(-) diff --git a/README.pgtap b/README.pgtap index d08028451940..00e794b81c2a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -771,9 +771,9 @@ missing schemas, like so: not ok 106 - There should be the correct schemas # Failed test 106: "There should be the correct schemas" - # These are extra schemas: + # Extra schemas: # __howdy__ - # These schemas are missing: + # Missing schemas: # someschema ### `has_schema( schema, description )` ### @@ -832,10 +832,10 @@ missing tables, like so: not ok 91 - Schema public should have the correct tables # Failed test 91: "Schema public should have the correct tables" - # These are extra tables: + # Extra tables: # mallots # __test_table - # These tables are missing: + # Missing tables: # users # widgets @@ -899,10 +899,10 @@ missing views, like so: not ok 92 - Schema public should have the correct views # Failed test 92: "Schema public should have the correct views" - # These are extra views: + # Extra views: # v_userlog_tmp # __test_view - # These views are missing: + # Missing views: # v_userlog # eated @@ -1861,10 +1861,10 @@ useful default description will be generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing functions, like so: - # Failed test 150: "whatever" - # Schema someschema has these extra functions: + # Failed test 150: "Schema someschema should have the correct functions" + # Extra functions: # schnauzify - # Schema someschema is missing these functions: + # Missing functions: # frobnitz ### `can( schema, functions[], description )` ### diff --git a/pgtap.sql.in b/pgtap.sql.in index e88eb74f8ab2..fe4e1087eb2a 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3848,130 +3848,94 @@ RETURNS NAME[] AS $$ ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _rels_are ( CHAR, NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - extras name[] := _extras( $1, $3, $4 ); - missing name[] := _missing( $1, $3, $4 ); - msg TEXT := ''; - res BOOLEAN := TRUE; -BEGIN - IF extras[1] IS NOT NULL THEN - res = FALSE; - msg := E'\n' || diag( - ' Schema ' || quote_ident($3) || ' has these extra ' || $2 || E':\n ' - || _ident_array_to_string( extras, E'\n ' ) - ); - END IF; - IF missing[1] IS NOT NULL THEN - res = FALSE; - msg := msg || E'\n' || diag( - ' Schema ' || quote_ident($3) || ' is missing these ' || $2 || E':\n ' - || _ident_array_to_string( missing, E'\n ' ) - ); - END IF; - - RETURN ok(res, $5) || msg; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION _rels_are ( CHAR, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -DECLARE - extras name[] := _extras( $1, $3 ); - missing name[] := _missing( $1, $3 ); - msg TEXT := ''; - res BOOLEAN := TRUE; -BEGIN - IF extras[1] IS NOT NULL THEN - res = FALSE; - msg := E'\n' || diag( - E' Search path ' || pg_catalog.current_setting('search_path') || ' has these extra ' || $2 || E':\n ' - || _ident_array_to_string( extras, E'\n ' ) - ); - END IF; - IF missing[1] IS NOT NULL THEN - res = FALSE; - msg := msg || E'\n' || diag( - E' Search path ' || pg_catalog.current_setting('search_path') || ' is missing these ' || $2 || E':\n ' - || _ident_array_to_string( missing, E'\n ' ) - ); - END IF; - - RETURN ok(res, $4) || msg; -END; -$$ LANGUAGE plpgsql; - -- tables_are( schema, tables, description ) CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ - SELECT _rels_are('r', 'tables', $1, $2, $3 ); + SELECT _are( 'tables', _extras('r', $1, $2), _missing('r', $1, $2), $3); $$ LANGUAGE SQL; -- tables_are( tables, description ) CREATE OR REPLACE FUNCTION tables_are ( NAME[], TEXT ) RETURNS TEXT AS $$ - SELECT _rels_are('r', 'tables', $1, $2 ); + SELECT _are( 'tables', _extras('r', $1), _missing('r', $1), $2); $$ LANGUAGE SQL; -- tables_are( schema, tables ) CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[] ) RETURNS TEXT AS $$ - SELECT _rels_are('r', 'tables', $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct tables' ); + SELECT _are( + 'tables', _extras('r', $1, $2), _missing('r', $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 _rels_are('r', 'tables', $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct tables' ); + SELECT _are( + 'tables', _extras('r', $1), _missing('r', $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 _rels_are('v', 'views', $1, $2, $3 ); + 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 _rels_are('v', 'views', $1, $2 ); + 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 _rels_are('v', 'views', $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct views' ); + 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 _rels_are('v', 'views', $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views' ); + 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 _rels_are('S', 'sequences', $1, $2, $3 ); + 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 _rels_are('S', 'sequences', $1, $2 ); + 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 _rels_are('S', 'sequences', $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct sequences' ); + 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 _rels_are('S', 'sequences', $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct sequences' ); + 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 ) @@ -4006,14 +3970,14 @@ BEGIN IF extras[1] IS NOT NULL THEN res = FALSE; msg := E'\n' || diag( - ' Schema ' || quote_ident($1) || E' has these extra functions:\n ' + E' Extra functions:\n ' || _ident_array_to_string( extras, E'\n ' ) ); END IF; IF missing[1] IS NOT NULL THEN res = FALSE; msg := msg || E'\n' || diag( - ' Schema ' || quote_ident($1) || E' is missing these functions:\n ' + E' Missing functions:\n ' || _ident_array_to_string( missing, E'\n ' ) ); END IF; @@ -4062,14 +4026,14 @@ BEGIN IF extras[1] IS NOT NULL THEN res = FALSE; msg := E'\n' || diag( - ' Search path ' || pg_catalog.current_setting('search_path') || E' has these extra functions:\n ' + E' Extra functions:\n ' || _ident_array_to_string( extras, E'\n ' ) ); END IF; IF missing[1] IS NOT NULL THEN res = FALSE; msg := msg || E'\n' || diag( - ' Search path ' || pg_catalog.current_setting('search_path') || E' is missing these functions:\n ' + E' Missing functions:\n ' || _ident_array_to_string( missing, E'\n ' ) ); END IF; diff --git a/sql/aretap.sql b/sql/aretap.sql index b3424f037a63..b0980dec4d5c 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -192,7 +192,7 @@ SELECT * FROM check_test( false, 'tables_are(schema, tables) missing', 'Schema public should have the correct tables', - ' Schema public is missing these tables: + ' Missing tables: bar' ); @@ -201,7 +201,7 @@ SELECT * FROM check_test( false, 'tables_are(tables) missing', 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct tables', - ' Search path ' || pg_catalog.current_setting('search_path') || ' is missing these tables: + ' Missing tables: bar' ); @@ -210,7 +210,7 @@ SELECT * FROM check_test( false, 'tables_are(schema, tables) extra', 'Schema public should have the correct tables', - ' Schema public has these extra tables: + ' Extra tables: foo' ); @@ -219,7 +219,7 @@ SELECT * FROM check_test( false, 'tables_are(tables) extra', 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct tables', - ' Search path ' || pg_catalog.current_setting('search_path') || ' has these extra tables: + ' Extra tables: foo' ); @@ -228,10 +228,10 @@ SELECT * FROM check_test( false, 'tables_are(schema, tables) extra and missing', 'Schema public should have the correct tables', - ' Schema public has these extra tables: + ' Extra tables: fo[ou] fo[ou] - Schema public is missing these tables: + Missing tables: ba[rz] ba[rz]', true @@ -242,10 +242,10 @@ SELECT * FROM check_test( false, 'tables_are(tables) extra and missing', 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct tables', - ' Search path ' || replace(pg_catalog.current_setting('search_path'), '$', E'\\$') || ' has these extra tables:' || ' + ' Extra tables:' || ' fo[ou] fo[ou] - Search path ' || replace(pg_catalog.current_setting('search_path'), '$', E'\\$') || ' is missing these tables:' || ' + Missing tables:' || ' ba[rz] ba[rz]', true @@ -290,7 +290,7 @@ SELECT * FROM check_test( false, 'views_are(schema, views) missing', 'Schema public should have the correct views', - ' Schema public is missing these views: + ' Missing views: bar' ); @@ -299,7 +299,7 @@ SELECT * FROM check_test( false, 'views_are(views) missing', 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views', - ' Search path ' || pg_catalog.current_setting('search_path') || ' is missing these views: + ' Missing views: bar' ); @@ -308,7 +308,7 @@ SELECT * FROM check_test( false, 'views_are(schema, views) extra', 'Schema public should have the correct views', - ' Schema public has these extra views: + ' Extra views: voo' ); @@ -317,7 +317,7 @@ SELECT * FROM check_test( false, 'views_are(views) extra', 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views', - ' Search path ' || pg_catalog.current_setting('search_path') || ' has these extra views: + ' Extra views: voo' ); @@ -326,10 +326,10 @@ SELECT * FROM check_test( false, 'views_are(schema, views) extra and missing', 'Schema public should have the correct views', - ' Schema public has these extra views: + ' Extra views: vo[ou] vo[ou] - Schema public is missing these views: + Missing views: ba[rz] ba[rz]', true @@ -340,10 +340,10 @@ SELECT * FROM check_test( false, 'views_are(views) extra and missing', 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views', - ' Search path ' || replace(pg_catalog.current_setting('search_path'), '$', E'\\$') || ' has these extra views:' || ' + ' Extra views:' || ' vo[ou] vo[ou] - Search path ' || replace(pg_catalog.current_setting('search_path'), '$', E'\\$') || ' is missing these views:' || ' + Missing views:' || ' ba[rz] ba[rz]', true @@ -388,7 +388,7 @@ SELECT * FROM check_test( false, 'sequences_are(schema, sequences) missing', 'Schema public should have the correct sequences', - ' Schema public is missing these sequences: + ' Missing sequences: bar' ); @@ -397,7 +397,7 @@ SELECT * FROM check_test( false, 'sequences_are(sequences) missing', 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct sequences', - ' Search path ' || pg_catalog.current_setting('search_path') || ' is missing these sequences: + ' Missing sequences: bar' ); @@ -406,7 +406,7 @@ SELECT * FROM check_test( false, 'sequences_are(schema, sequences) extra', 'Schema public should have the correct sequences', - ' Schema public has these extra sequences: + ' Extra sequences: someseq' ); @@ -415,7 +415,7 @@ SELECT * FROM check_test( false, 'sequences_are(sequences) extra', 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct sequences', - ' Search path ' || pg_catalog.current_setting('search_path') || ' has these extra sequences: + ' Extra sequences: someseq' ); @@ -424,10 +424,10 @@ SELECT * FROM check_test( false, 'sequences_are(schema, sequences) extra and missing', 'Schema public should have the correct sequences', - ' Schema public has these extra sequences: + ' Extra sequences: s[ou]meseq s[ou]meseq - Schema public is missing these sequences: + Missing sequences: ba[rz] ba[rz]', true @@ -438,10 +438,10 @@ SELECT * FROM check_test( false, 'sequences_are(sequences) extra and missing', 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct sequences', - ' Search path ' || replace(pg_catalog.current_setting('search_path'), '$', E'\\$') || ' has these extra sequences:' || ' + ' Extra sequences:' || ' s[ou]meseq s[ou]meseq - Search path ' || replace(pg_catalog.current_setting('search_path'), '$', E'\\$') || ' is missing these sequences:' || ' + Missing sequences:' || ' ba[rz] ba[rz]', true @@ -471,7 +471,7 @@ SELECT * FROM check_test( false, 'functions_are(schema, functions, desc) + missing', 'whatever', - ' Schema someschema is missing these functions: + ' Missing functions: yop' ); @@ -480,7 +480,7 @@ SELECT * FROM check_test( false, 'functions_are(schema, functions, desc) + extra', 'whatever', - ' Schema someschema has these extra functions: + ' Extra functions: yap' ); @@ -489,9 +489,9 @@ SELECT * FROM check_test( false, 'functions_are(schema, functions, desc) + extra & missing', 'whatever', - ' Schema someschema has these extra functions: + ' Extra functions: yip - Schema someschema is missing these functions: + Missing functions: yop' ); @@ -527,7 +527,7 @@ SELECT * FROM check_test( false, 'functions_are(functions, desc) + missing', 'whatever', - ' Search path ' || pg_catalog.current_setting('search_path') || ' is missing these functions: + ' Missing functions: __booyah__' ); @@ -536,7 +536,7 @@ SELECT * FROM check_test( false, 'functions_are(functions, desc) + extra', 'whatever', - ' Search path ' || pg_catalog.current_setting('search_path') || ' has these extra functions: + ' Extra functions: check_test' ); @@ -545,9 +545,9 @@ SELECT * FROM check_test( false, 'functions_are(functions, desc) + extra & missing', 'whatever', - ' Search path ' || pg_catalog.current_setting('search_path') || ' has these extra functions: + ' Extra functions: check_test - Search path ' || pg_catalog.current_setting('search_path') || ' is missing these functions: + Missing functions: __booyah__' ); @@ -643,8 +643,6 @@ SELECT * FROM check_test( howdy' ); -SELECT indexes_are( 'fou', ARRAY['idx_fou_id', 'idx_fou_name', 'howdy'] ); - /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From f6dbb815545d0facd9f2ef6b05307b0e8b1580bb Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 23 May 2009 15:17:35 -0400 Subject: [PATCH 0353/1195] Added `users_are()` and `groups_are()`. --- Changes | 3 +- README.pgtap | 60 ++++++++++++++++++++++-- expected/aretap.out | 32 ++++++++++++- pgtap.sql.in | 60 +++++++++++++++++++++++- sql/aretap.sql | 112 +++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 254 insertions(+), 13 deletions(-) diff --git a/Changes b/Changes index 4f28f751f06f..466c7a3a9b2f 100644 --- a/Changes +++ b/Changes @@ -10,7 +10,8 @@ Revision history for pgTAP * Fixed a failing test for the version number string on 8.4 beta. * Added `performs_ok()`. * Added `tablespaces_are()`, `schemas_are()`, `tables_are()`, `views_are()`, - `sequences_are()`, `functions_are()`, and `indexes_are()`. + `sequences_are()`, `functions_are()`, `indexes_are()`, `users_are()`, + and `groups_are()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index 00e794b81c2a..f902f38ddddb 100644 --- a/README.pgtap +++ b/README.pgtap @@ -759,7 +759,7 @@ specified tablespace does *not* exist. This function tests that all of the schemas in the databse only the schemas that *should* be there. In other words, given a list of schemas, this assertion will fail if there are schemas that are not in the list, or if there -are schemas in the list that are not found in the search path. +are schemas in the list that are missing from the database. This test is useful in environments where many developers may be making changes to the database, or where replication has been deployed, and you want @@ -2083,6 +2083,34 @@ it will default to "Role `:role` should exist". The inverse of `has_role()`, this function tests for the *absence* of a database role. +### `users_are( users[], description )` ### +### `users_are( users[] )` ### + + SELECT users_are( + ARRAY[ 'postgres', 'someone', 'root' ], + 'Should have the correct users + ); + +This function tests that all of the users in the databse only the users that +*should* be there. In other words, given a list of users, this assertion will +fail if there are users that are not in the list, or if there are users in the +list that are missing from the database. + +This test is useful in environments where many developers may be making +changes to the database, or where replication has been deployed, and you want +to make sure that the users in a database are exactly the users that should +exist in the database, no more, no less. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing users, like so: + + not ok 195 - whatever + # Failed test 195: "There should be the correct users" + # Extra users: + # root + # Missing users: + # bobby + ### `has_user( user, desc )` ### ### `has_user( user )` ### @@ -2121,6 +2149,34 @@ The inverse of `is_superuser()`, this function tests that a database user is database, the test is still considered a failure, and the diagnostics will say so. +### `groups_are( groups[], description )` ### +### `groups_are( groups[] )` ### + + SELECT groups_are( + ARRAY[ 'postgres', 'admins, 'l0s3rs' ], + 'Should have the correct groups + ); + +This function tests that all of the groups in the databse only the groups that +*should* be there. In other words, given a list of groups, this assertion will +fail if there are groups that are not in the list, or if there are groups in the +list that are missing from the database. + +This test is useful in environments where many developers may be making +changes to the database, or where replication has been deployed, and you want +to make sure that the groups in a database are exactly the groups that should +exist in the database, no more, no less. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing groups, like so: + + not ok 210 - There should be the correct groups + # Failed test 210: "There should be the correct groups" + # Extra groups: + # meanies + # Missing groups: + # __howdy__ + ### `has_group( group, desc )` ### ### `has_group( group )` ### @@ -2788,8 +2844,6 @@ To Do ----- * Useful schema testing functions to consider adding: - * `users_are()` - * `groups_are()` * `has_operator_class()` * `has_rule()` * `function_returns()` diff --git a/expected/aretap.out b/expected/aretap.out index d0329ad9ec1c..4a03f4e42ed3 100644 --- a/expected/aretap.out +++ b/expected/aretap.out @@ -1,5 +1,5 @@ \unset ECHO -1..179 +1..209 ok 1 - tablespaces_are(schemas, desc) should pass ok 2 - tablespaces_are(schemas, desc) should have the proper description ok 3 - tablespaces_are(schemas, desc) should have the proper diagnostics @@ -179,3 +179,33 @@ ok 176 - indexes_are(table, indexes) + missing should have the proper diagnostic ok 177 - indexes_are(table, indexes) + extra & missing should fail ok 178 - indexes_are(table, indexes) + extra & missing should have the proper description ok 179 - indexes_are(table, indexes) + extra & missing should have the proper diagnostics +ok 180 - users_are(users, desc) should pass +ok 181 - users_are(users, desc) should have the proper description +ok 182 - users_are(users, desc) should have the proper diagnostics +ok 183 - users_are(users) should pass +ok 184 - users_are(users) should have the proper description +ok 185 - users_are(users) should have the proper diagnostics +ok 186 - users_are(users, desc) missing should fail +ok 187 - users_are(users, desc) missing should have the proper description +ok 188 - users_are(users, desc) missing should have the proper diagnostics +ok 189 - users_are(users, desc) extras should fail +ok 190 - users_are(users, desc) extras should have the proper description +ok 191 - users_are(users, desc) extras should have the proper diagnostics +ok 192 - users_are(users, desc) missing and extras should fail +ok 193 - users_are(users, desc) missing and extras should have the proper description +ok 194 - users_are(users, desc) missing and extras should have the proper diagnostics +ok 195 - groups_are(groups, desc) should pass +ok 196 - groups_are(groups, desc) should have the proper description +ok 197 - groups_are(groups, desc) should have the proper diagnostics +ok 198 - groups_are(groups) should pass +ok 199 - groups_are(groups) should have the proper description +ok 200 - groups_are(groups) should have the proper diagnostics +ok 201 - groups_are(groups, desc) missing should fail +ok 202 - groups_are(groups, desc) missing should have the proper description +ok 203 - groups_are(groups, desc) missing should have the proper diagnostics +ok 204 - groups_are(groups, desc) extras should fail +ok 205 - groups_are(groups, desc) extras should have the proper description +ok 206 - groups_are(groups, desc) extras should have the proper diagnostics +ok 207 - groups_are(groups, desc) missing and extras should fail +ok 208 - groups_are(groups, desc) missing and extras should have the proper description +ok 209 - groups_are(groups, desc) missing and extras should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index fe4e1087eb2a..39d758190bbd 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -4065,7 +4065,6 @@ RETURNS TEXT AS $$ 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) @@ -4104,7 +4103,6 @@ RETURNS TEXT AS $$ 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) @@ -4126,6 +4124,64 @@ 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; + -- 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 $$ diff --git a/sql/aretap.sql b/sql/aretap.sql index b0980dec4d5c..53df97f94411 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(179); +SELECT plan(209); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -16,14 +16,9 @@ CREATE TABLE public.fou( CREATE TABLE public.foo( id INT NOT NULL PRIMARY KEY ); -CREATE TYPE public.sometype AS ( - id INT, - name TEXT -); CREATE INDEX idx_fou_id ON public.fou(id); CREATE INDEX idx_fou_name ON public.fou(name); -CREATE INDEX idx_foo_id ON public.foo(id); CREATE VIEW voo AS SELECT * FROM foo; CREATE VIEW vou AS SELECT * FROM fou; @@ -643,6 +638,111 @@ SELECT * FROM check_test( howdy' ); +/****************************************************************************/ +-- Test users_are(). + +CREATE FUNCTION ___myusers(ex text) RETURNS NAME[] AS $$ + SELECT ARRAY( SELECT usename FROM pg_catalog.pg_user WHERE usename <> $1 ); +$$ LANGUAGE SQL; + +SELECT * FROM check_test( + users_are( ___myusers(''), 'whatever' ), + true, + 'users_are(users, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + users_are( ___myusers('') ), + true, + 'users_are(users)', + 'There should be the correct users', + '' +); + +SELECT * FROM check_test( + users_are( array_append(___myusers(''), '__howdy__'), 'whatever' ), + false, + 'users_are(users, desc) missing', + 'whatever', + ' Missing users: + __howdy__' +); + +SELECT * FROM check_test( + users_are( ___myusers(current_user), 'whatever' ), + false, + 'users_are(users, desc) extras', + 'whatever', + ' Extra users: + ' || current_user +); + +SELECT * FROM check_test( + users_are( array_append(___myusers(current_user), '__howdy__'), 'whatever' ), + false, + 'users_are(users, desc) missing and extras', + 'whatever', + ' Extra users: + ' || current_user || ' + Missing users: + __howdy__' +); + +/****************************************************************************/ +-- Test groups_are(). + +CREATE GROUP meanies; +CREATE FUNCTION ___mygroups(ex text) RETURNS NAME[] AS $$ + SELECT ARRAY( SELECT groname FROM pg_catalog.pg_group WHERE groname <> $1 ); +$$ LANGUAGE SQL; + +SELECT * FROM check_test( + groups_are( ___mygroups(''), 'whatever' ), + true, + 'groups_are(groups, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + groups_are( ___mygroups('') ), + true, + 'groups_are(groups)', + 'There should be the correct groups', + '' +); + +SELECT * FROM check_test( + groups_are( array_append(___mygroups(''), '__howdy__'), 'whatever' ), + false, + 'groups_are(groups, desc) missing', + 'whatever', + ' Missing groups: + __howdy__' +); + +SELECT * FROM check_test( + groups_are( ___mygroups('meanies'), 'whatever' ), + false, + 'groups_are(groups, desc) extras', + 'whatever', + ' Extra groups: + meanies' +); + +SELECT * FROM check_test( + groups_are( array_append(___mygroups('meanies'), '__howdy__'), 'whatever' ), + false, + 'groups_are(groups, desc) missing and extras', + 'whatever', + ' Extra groups: + meanies + Missing groups: + __howdy__' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 2401c0ec000b27aea0105e8565d8c8b334334cd2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 23 May 2009 15:21:26 -0400 Subject: [PATCH 0354/1195] Refactored `functions_are()` to use `_are()` like all the other `*_are()` functions do. --- pgtap.sql.in | 138 ++++++++++++++++++--------------------------------- 1 file changed, 48 insertions(+), 90 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 39d758190bbd..6fcb1fdb8658 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3941,50 +3941,29 @@ $$ LANGUAGE SQL; -- functions_are( schema, functions[], description ) CREATE OR REPLACE FUNCTION functions_are ( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ -DECLARE - extras name[]; - missing name[]; - msg TEXT := ''; - res BOOLEAN := TRUE; -BEGIN - SELECT ARRAY( - SELECT p.proname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace - WHERE n.nspname = $1 - EXCEPT - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - ) INTO extras; - - SELECT ARRAY( - SELECT $2[i] - FROM generate_series(1, array_upper($2, 1)) s(i) - EXCEPT - SELECT p.proname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace - WHERE n.nspname = $1 - ) INTO missing; - - IF extras[1] IS NOT NULL THEN - res = FALSE; - msg := E'\n' || diag( - E' Extra functions:\n ' - || _ident_array_to_string( extras, E'\n ' ) - ); - END IF; - IF missing[1] IS NOT NULL THEN - res = FALSE; - msg := msg || E'\n' || diag( - E' Missing functions:\n ' - || _ident_array_to_string( missing, E'\n ' ) - ); - END IF; - - RETURN ok(res, $3) || msg; -END; -$$ LANGUAGE plpgsql; + SELECT _are( + 'functions', + ARRAY( + SELECT p.proname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace + 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 p.proname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace + WHERE n.nspname = $1 + ), + $3 + ); +$$ LANGUAGE SQL; -- functions_are( schema, functions[] ) CREATE OR REPLACE FUNCTION functions_are ( NAME, NAME[] ) @@ -3995,52 +3974,31 @@ $$ LANGUAGE SQL; -- functions_are( functions[], description ) CREATE OR REPLACE FUNCTION functions_are ( NAME[], TEXT ) RETURNS TEXT AS $$ -DECLARE - extras name[]; - missing name[]; - msg TEXT := ''; - res BOOLEAN := TRUE; -BEGIN - SELECT ARRAY( - SELECT p.proname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace - WHERE pg_catalog.pg_function_is_visible(p.oid) - AND n.nspname <> 'pg_catalog' - EXCEPT - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - ) INTO extras; - - SELECT ARRAY( - SELECT $1[i] - FROM generate_series(1, array_upper($1, 1)) s(i) - EXCEPT - SELECT p.proname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace - WHERE pg_catalog.pg_function_is_visible(p.oid) - AND n.nspname <> 'pg_catalog' - ) INTO missing; - - IF extras[1] IS NOT NULL THEN - res = FALSE; - msg := E'\n' || diag( - E' Extra functions:\n ' - || _ident_array_to_string( extras, E'\n ' ) - ); - END IF; - IF missing[1] IS NOT NULL THEN - res = FALSE; - msg := msg || E'\n' || diag( - E' Missing functions:\n ' - || _ident_array_to_string( missing, E'\n ' ) - ); - END IF; - - RETURN ok(res, $2) || msg; -END; -$$ LANGUAGE plpgsql; + SELECT _are( + 'functions', + ARRAY( + SELECT p.proname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace + WHERE pg_catalog.pg_function_is_visible(p.oid) + AND n.nspname <> 'pg_catalog' + 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 p.proname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace + WHERE pg_catalog.pg_function_is_visible(p.oid) + AND n.nspname <> 'pg_catalog' + ), + $2 + ); +$$ LANGUAGE SQL; -- functions_are( functions[] ) CREATE OR REPLACE FUNCTION functions_are ( NAME[] ) From d8a1f068281fe9a181a111b2c3281e2481c917e6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 23 May 2009 16:19:40 -0400 Subject: [PATCH 0355/1195] Re-arranged `README.pgtap`. * Added new headers and grouped schema assertion functions into subcategories. * Spell-checked the whole damned thing. * Fixed the "Conditional Tests" section, which I had never really properl done before (there was still some Test::More-ish stuff in there). * Fixed a few other minor formatting issues and the names of functions on the to-do list. --- README.pgtap | 1457 ++++++++++++++++++++++++-------------------------- 1 file changed, 712 insertions(+), 745 deletions(-) diff --git a/README.pgtap b/README.pgtap index f902f38ddddb..ef4ff7a818f8 100644 --- a/README.pgtap +++ b/README.pgtap @@ -75,7 +75,7 @@ target uses the included `pg_prove` Perl program to do its testing, which requires that TAP::Harness, included in [Test::Harness](http://search.cpan.org/dist/Test-Harness/ "Test::Harness on CPAN") 3.x. You'll need to make sure that you use a database with PL/pgSQL -loaded, or else the tests wont't work. `pg_prove` supports a number of +loaded, or else the tests won't work. `pg_prove` supports a number of environment variables that you might need to use, including all the usual PostgreSQL client environment variables: @@ -123,7 +123,7 @@ pgTAP Test Scripts You can distribute `pgtap.sql` with any PostgreSQL distribution, such as a custom data type. For such a case, if your users want to run your test suite using PostgreSQL's standard `installcheck` make target, just be sure to set -varibles to keep the tests quiet, start a transaction, load the functions in +variables to keep the tests quiet, start a transaction, load the functions in your test script, and then rollback the transaction at the end of the script. Here's an example: @@ -659,7 +659,7 @@ It's best to think of this as a brute force comparison of runtimes, in order to ensure that a query is not *really* slow (think seconds). The Schema Things ------------------ +================= Need to make sure that your database is designed just the way you think it should be? Use these test functions and rest easy. @@ -691,6 +691,26 @@ In general, this should not be an issue, as mixed-case objects are created only rarely. So if you just stick to lowercase-only arguments to these functions, you should be in good shape. +I Object! +--------- + +In a busy development environment, you might have a number of users who make +changes to the database schema. Sometimes you have to really work to keep +these folks in line. For example, do they add objects to the database without +adding tests? Do they drop objects that they shouldn't? These assertions are +designed to help you ensure that the objects in the database are exactly the +objects that should be in the database, no more, no less. + +Each tests tests that all of the objects in the database are only the objects +that *should* be there. In other words, given a list of objects, say tables in +a call to `tables_are()`, this assertion will fail if there are tables that +are not in the list, or if there are tables in the list that are missing from +the database. It can also be useful for testing replication and the success or +failure of schema change deployments. + +If you're more interested in the specifics of particular objects, skip to +the next section. + ### `tablespaces_are( tablespaces, description )` ### ### `tablespaces_are( tablespaces )` ### @@ -699,19 +719,9 @@ functions, you should be in good shape. 'Should have the correct tablespaces ); -This function tests that all of the tablespaces in the databse only the -tablespaces that *should* be there. In other words, given a list of -tablespaces, this assertion will fail if there are tablespaces that are not in -the list, or if there are tablespaces in the list that are missing from the -database. - -This test is useful in environments where many developers may be making -changes to the database, or where replication has been deployed, and you want -to make sure that the tablespaces in a database are exactly the tablespaces -that should exist in the database, no more, no less. - -In the event of a failure, you'll see diagnostics listing the extra and/or -missing tablespaces, like so: +This function tests that all of the tablespaces in the database only the +tablespaces that *should* be there. In the event of a failure, you'll see +diagnostics listing the extra and/or missing tablespaces, like so: not ok 121 - There should be the correct tablespaces # Failed test 121: "There should be the correct tablespaces" @@ -720,34 +730,6 @@ missing tablespaces, like so: # Missing tablespaces: # __booya__ -### `has_tablespace( tablespace, location, description )` ### -### `has_tablespace( tablespace, description )` ### -### `has_tablespace( tablespace )` ### - - SELECT has_tablespace( - 'sometablespace', - '/data/dbs', - 'I got sometablespace in /data/dbs' - ); - -This function tests whether or not a tablespace exists in the database. The -first argument is a tablespace name. The second is either the a file system -path for the database or a test description. If you specify a location path, -you must pass a description as the third argument; otherwise, if you omit the -test description, it will be set to "Tablespace `:tablespace` should exist". - -### `hasnt_tablespace( tablespace, tablespace, description )` ### -### `hasnt_tablespace( tablespace, description )` ### -### `hasnt_tablespace( tablespace )` ### - - SELECT hasnt_tablespace( - 'sometablespace', - 'There should be no tablespace sometablespace' - ); - -This function is the inverse of `has_tablespace()`. The test passes if the -specified tablespace does *not* exist. - ### `schemas_are( schemas, description )` ### ### `schemas_are( schemas )` ### @@ -756,18 +738,9 @@ specified tablespace does *not* exist. 'Should have the correct schemas ); -This function tests that all of the schemas in the databse only the schemas -that *should* be there. In other words, given a list of schemas, this -assertion will fail if there are schemas that are not in the list, or if there -are schemas in the list that are missing from the database. - -This test is useful in environments where many developers may be making -changes to the database, or where replication has been deployed, and you want -to make sure that the schemas in a database are exactly the schemas that should -exist in the database, no more, no less. - -In the event of a failure, you'll see diagnostics listing the extra and/or -missing schemas, like so: +This function tests that all of the schemas in the database only the schemas +that *should* be there. In the event of a failure, you'll see diagnostics +listing the extra and/or missing schemas, like so: not ok 106 - There should be the correct schemas # Failed test 106: "There should be the correct schemas" @@ -776,31 +749,6 @@ missing schemas, like so: # Missing schemas: # someschema -### `has_schema( schema, description )` ### -### `has_schema( schema )` ### - - SELECT has_schema( - 'someschema', - 'I got someschema' - ); - -This function tests whether or not a schema exists in the database. The first -argument is a schema name and the second is the test description. If you omit -the schema, the schema must be visible in the search path. If you omit the -test description, it will be set to "Schema `:schema` should exist". - -### `hasnt_schema( schema, schema, description )` ### -### `hasnt_schema( schema, description )` ### -### `hasnt_schema( schema )` ### - - SELECT hasnt_schema( - 'someschema', - 'There should be no schema someschema' - ); - -This function is the inverse of `has_schema()`. The test passes if the -specified schema does *not* exist. - ### `tables_are( schema, tables, description )` ### ### `tables_are( tables, description )` ### ### `tables_are( schema, tables )` ### @@ -813,19 +761,10 @@ specified schema does *not* exist. ); This function tests that all of the tables in the named schema, or that are -visible in the search path, are only the tables that *should* be there. In -other words, given a list of tables, this assertion will fail if there are -tables that are not in the list, or if there are tables in the list that are -not found in the search path. - -This test is useful in environments where many developers may be making -changes to the database, or where replication has been deployed, and you want -to make sure that the tables in a database are exactly the tables that should -exist in the database, no more, no less. - -If the `:schema` argument is omitted, tables will be sought in the search -path, excluding `pg_catalog.` If the description is omitted, a generally -useful default description will be generated. +visible in the search path, are only the tables that *should* be there. If the +`:schema` argument is omitted, tables will be sought in the search path, +excluding `pg_catalog.` If the description is omitted, a generally useful +default description will be generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing tables, like so: @@ -839,35 +778,6 @@ missing tables, like so: # users # widgets -### `has_table( schema, table, description )` ### -### `has_table( table, description )` ### -### `has_table( table )` ### - - SELECT has_table( - 'myschema', - 'sometable', - 'I got myschema.sometable' - ); - -This function tests whether or not a table exists in the database. The first -argument is a schema name, the second is a table name, and the third is the -test description. If you omit the schema, the table must be visible in the -search path. If you omit the test description, it will be set to "Table -`:table` should exist". - -### `hasnt_table( schema, table, description )` ### -### `hasnt_table( table, description )` ### -### `hasnt_table( table )` ### - - SELECT hasnt_table( - 'myschema', - 'sometable', - 'There should be no table myschema.sometable' - ); - -This function is the inverse of `has_table()`. The test passes if the -specified table does *not* exist. - ### `views_are( schema, views, description )` ### ### `views_are( views, description )` ### ### `views_are( schema, views )` ### @@ -880,19 +790,10 @@ specified table does *not* exist. ); This function tests that all of the views in the named schema, or that are -visible in the search path, are only the views that *should* be there. In -other words, given a list of views, this assertion will fail if there are -views that are not in the list, or if there are views in the list that are -not found in the search path. - -This test is useful in environments where many developers may be making -changes to the database, or where replication has been deployed, and you want -to make sure that the views in a database are exactly the views that should -exist in the database, no more, no less. - -If the `:schema` argument is omitted, views will be sought in the search -path, excluding `pg_catalog.` If the description is omitted, a generally -useful default description will be generated. +visible in the search path, are only the views that *should* be there. If the +`:schema` argument is omitted, views will be sought in the search path, +excluding `pg_catalog.` If the description is omitted, a generally useful +default description will be generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing views, like so: @@ -906,31 +807,6 @@ missing views, like so: # v_userlog # eated -### `has_view( schema, view, description )` ### -### `has_view( view, description )` ### -### `has_view( view )` ### - - SELECT has_view( - 'myschema', - 'someview', - 'I got myschema.someview' - ); - -Just like `has_table()`, only it tests for the existence of a view. - -### `hasnt_view( schema, view, description )` ### -### `hasnt_view( view, description )` ### -### `hasnt_view( view )` ### - - SELECT hasnt_view( - 'myschema', - 'someview', - 'There should be no myschema.someview' - ); - -This function is the inverse of `has_view()`. The test passes if the specified -view does *not* exist. - ### `sequences_are( schema, sequences, description )` ### ### `sequences_are( sequences, description )` ### ### `sequences_are( schema, sequences )` ### @@ -943,17 +819,8 @@ view does *not* exist. ); This function tests that all of the sequences in the named schema, or that are -visible in the search path, are only the sequences that *should* be there. In -other words, given a list of sequences, this assertion will fail if there are -sequences that are not in the list, or if there are sequences in the list that -are not found in the search path. - -This test is useful in environments where many developers may be making -changes to the database, or where replication has been deployed, and you want -to make sure that the sequences in a database are exactly the sequences that -should exist in the database, no more, no less. - -If the `:schema` argument is omitted, sequences will be sought in the search +visible in the search path, are only the sequences that *should* be there. If +the `:schema` argument is omitted, sequences will be sought in the search path, excluding `pg_catalog.` If the description is omitted, a generally useful default description will be generated. @@ -969,158 +836,624 @@ missing sequences, like so: # users_seq # widgets_seq -### `has_sequence( schema, sequence, description )` ### -### `has_sequence( sequence, description )` ### -### `has_sequence( sequence )` ### +### `indexes_are( schema, table, indexes[], description )` ### +### `indexes_are( schema, table, indexes[] )` ### +### `indexes_are( table, indexes[], description )` ### +### `indexes_are( table, indexes[] )` ### - SELECT has_sequence( + SELECT indexes_are( 'myschema', - 'somesequence', - 'I got myschema.somesequence' + 'atable', + ARRAY[ 'atable_pkey', 'idx_atable_name' ], + 'Should have the correct indexes on myschema.atable' ); -Just like `has_table()`, only it tests for the existence of a sequence. - -### `hasnt_sequence( schema, sequence, description )` ### -### `hasnt_sequence( sequence, description )` ### -### `hasnt_sequence( sequence )` ### +This function tests that all of the indexes on the named table are only the +indexes that *should* be on that table. Iftable.If the `:schema` argument is omitted, +the table must be visible in the search path. If the description is omitted, a +generally useful default description will be generated. - SELECT hasnt_sequence( - 'myschema', - 'somesequence', - 'There should be no myschema.somesequence' - ); +In the event of a failure, you'll see diagnostics listing the extra and/or +missing indexes, like so: -This function is the inverse of `has_sequence()`. The test passes if the -specified sequence does *not* exist. + # Failed test 180: "Table fou should have the correct indexes" + # Extra indexes: + # fou_pkey + # Missing indexes: + # idx_fou_name -### `has_type( schema, type, description )` ### -### `has_type( schema, type )` ### -### `has_type( type, description )` ### -### `has_type( type )` ### +### `functions_are( schema, functions[], description )` ### +### `functions_are( schema, functions[] )` ### +### `functions_are( functions[], description )` ### +### `functions_are( functions[] )` ### - SELECT has_type( + SELECT functions_are( 'myschema', - 'sometype', - 'I got myschema.sometype' + ARRAY[ 'foo', 'bar', 'frobnitz' ], + 'Should have the correct functions in myschema' ); -This function tests whether or not a type exists in the database. Detects all -types of types, including base types, composite types, domains, enums, and -pseudo-types. The first argument is a schema name, the second is a type name, -and the third is the test description. If you omit the schema, the type must -be visible in the search path. If you omit the test description, it will be -set to "Type `:type` should exist". If you're passing a schema and type rather -than type and description, be sure to cast the arguments to `name` values so -that your type name doesn't get treated as a description. +This function tests that all of the functions in the named schema, or that are +visible in the search path, are only the functions that *should* be there. If +the `:schema` argument is omitted, functions will be sought in the search +path, excluding `pg_catalog.` If the description is omitted, a generally +useful default description will be generated. -If you've created a composite type and want to test that the composed types -are a part of it, use the column testing functions to verify them, like so: +In the event of a failure, you'll see diagnostics listing the extra and/or +missing functions, like so: - CREATE TYPE foo AS (id int, name text); - SELECT has_type( 'foo' ); - SELECT has_column( 'foo', 'id' ); - SELECT col_type_is( 'foo', 'id', 'integer' ); + # Failed test 150: "Schema someschema should have the correct functions" + # Extra functions: + # schnauzify + # Missing functions: + # frobnitz -### `hasnt_type( schema, type, description )` ### -### `hasnt_type( schema, type )` ### -### `hasnt_type( type, description )` ### -### `hasnt_type( type )` ### +### `users_are( users[], description )` ### +### `users_are( users[] )` ### - SELECT hasnt_type( - 'myschema', - 'sometype', - 'There should be no type myschema.sometype' + SELECT users_are( + ARRAY[ 'postgres', 'someone', 'root' ], + 'Should have the correct users ); -This function is the inverse of `has_type()`. The test passes if the specified -type does *not* exist. +This function tests that all of the users in the database only the users that +*should* be there. In the event of a failure, you'll see diagnostics listing +the extra and/or missing users, like so: -### `has_domain( schema, domain, description )` ### -### `has_domain( schema, domain )` ### -### `has_domain( domain, description )` ### -### `has_domain( domain )` ### + not ok 195 - whatever + # Failed test 195: "There should be the correct users" + # Extra users: + # root + # Missing users: + # bobby - SELECT has_domain( - 'myschema', - 'somedomain', - 'I got myschema.somedomain' +### `groups_are( groups[], description )` ### +### `groups_are( groups[] )` ### + + SELECT groups_are( + ARRAY[ 'postgres', 'admins, 'l0s3rs' ], + 'Should have the correct groups ); -This function tests whether or not a domain exists in the database. The first -argument is a schema name, the second is the name of a domain, and the third -is the test description. If you omit the schema, the domain must be visible in -the search path. If you omit the test description, it will be set to "Domain -`:domain` should exist". If you're passing a schema and domain rather than -domain and description, be sure to cast the arguments to `name` values so that -your domain name doesn't get treated as a description. +This function tests that all of the groups in the database only the groups +that *should* be there. In the event of a failure, you'll see diagnostics +listing the extra and/or missing groups, like so: -### `hasnt_domain( schema, domain, description )` ### -### `hasnt_domain( schema, domain )` ### -### `hasnt_domain( domain, description )` ### -### `hasnt_domain( domain )` ### + not ok 210 - There should be the correct groups + # Failed test 210: "There should be the correct groups" + # Extra groups: + # meanies + # Missing groups: + # __howdy__ + +To Have or Have Not +------------------- + +Perhaps you're not so concerned with ensuring the [precise correlation of +database objects](#I+Object! "I Object!"). Perhaps you just need to make sure +that certain objects exist (or that certain objects *don't* exist). You've +come to the right place. + +### `has_tablespace( tablespace, location, description )` ### +### `has_tablespace( tablespace, description )` ### +### `has_tablespace( tablespace )` ### + + SELECT has_tablespace( + 'sometablespace', + '/data/dbs', + 'I got sometablespace in /data/dbs' + ); + +This function tests whether or not a tablespace exists in the database. The +first argument is a tablespace name. The second is either the a file system +path for the database or a test description. If you specify a location path, +you must pass a description as the third argument; otherwise, if you omit the +test description, it will be set to "Tablespace `:tablespace` should exist". + +### `hasnt_tablespace( tablespace, tablespace, description )` ### +### `hasnt_tablespace( tablespace, description )` ### +### `hasnt_tablespace( tablespace )` ### + + SELECT hasnt_tablespace( + 'sometablespace', + 'There should be no tablespace sometablespace' + ); + +This function is the inverse of `has_tablespace()`. The test passes if the +specified tablespace does *not* exist. + +### `has_schema( schema, description )` ### +### `has_schema( schema )` ### + + SELECT has_schema( + 'someschema', + 'I got someschema' + ); + +This function tests whether or not a schema exists in the database. The first +argument is a schema name and the second is the test description. If you omit +the schema, the schema must be visible in the search path. If you omit the +test description, it will be set to "Schema `:schema` should exist". + +### `hasnt_schema( schema, schema, description )` ### +### `hasnt_schema( schema, description )` ### +### `hasnt_schema( schema )` ### + + SELECT hasnt_schema( + 'someschema', + 'There should be no schema someschema' + ); + +This function is the inverse of `has_schema()`. The test passes if the +specified schema does *not* exist. + +### `has_table( schema, table, description )` ### +### `has_table( table, description )` ### +### `has_table( table )` ### + + SELECT has_table( + 'myschema', + 'sometable', + 'I got myschema.sometable' + ); + +This function tests whether or not a table exists in the database. The first +argument is a schema name, the second is a table name, and the third is the +test description. If you omit the schema, the table must be visible in the +search path. If you omit the test description, it will be set to "Table +`:table` should exist". + +### `hasnt_table( schema, table, description )` ### +### `hasnt_table( table, description )` ### +### `hasnt_table( table )` ### + + SELECT hasnt_table( + 'myschema', + 'sometable', + 'There should be no table myschema.sometable' + ); + +This function is the inverse of `has_table()`. The test passes if the +specified table does *not* exist. + +### `has_view( schema, view, description )` ### +### `has_view( view, description )` ### +### `has_view( view )` ### + + SELECT has_view( + 'myschema', + 'someview', + 'I got myschema.someview' + ); + +Just like `has_table()`, only it tests for the existence of a view. + +### `hasnt_view( schema, view, description )` ### +### `hasnt_view( view, description )` ### +### `hasnt_view( view )` ### + + SELECT hasnt_view( + 'myschema', + 'someview', + 'There should be no myschema.someview' + ); + +This function is the inverse of `has_view()`. The test passes if the specified +view does *not* exist. + +### `has_sequence( schema, sequence, description )` ### +### `has_sequence( sequence, description )` ### +### `has_sequence( sequence )` ### + + SELECT has_sequence( + 'myschema', + 'somesequence', + 'I got myschema.somesequence' + ); + +Just like `has_table()`, only it tests for the existence of a sequence. + +### `hasnt_sequence( schema, sequence, description )` ### +### `hasnt_sequence( sequence, description )` ### +### `hasnt_sequence( sequence )` ### + + SELECT hasnt_sequence( + 'myschema', + 'somesequence', + 'There should be no myschema.somesequence' + ); + +This function is the inverse of `has_sequence()`. The test passes if the +specified sequence does *not* exist. + +### `has_type( schema, type, description )` ### +### `has_type( schema, type )` ### +### `has_type( type, description )` ### +### `has_type( type )` ### + + SELECT has_type( + 'myschema', + 'sometype', + 'I got myschema.sometype' + ); + +This function tests whether or not a type exists in the database. Detects all +types of types, including base types, composite types, domains, enums, and +pseudo-types. The first argument is a schema name, the second is a type name, +and the third is the test description. If you omit the schema, the type must +be visible in the search path. If you omit the test description, it will be +set to "Type `:type` should exist". If you're passing a schema and type rather +than type and description, be sure to cast the arguments to `name` values so +that your type name doesn't get treated as a description. + +If you've created a composite type and want to test that the composed types +are a part of it, use the column testing functions to verify them, like so: + + CREATE TYPE foo AS (id int, name text); + SELECT has_type( 'foo' ); + SELECT has_column( 'foo', 'id' ); + SELECT col_type_is( 'foo', 'id', 'integer' ); + +### `hasnt_type( schema, type, description )` ### +### `hasnt_type( schema, type )` ### +### `hasnt_type( type, description )` ### +### `hasnt_type( type )` ### + + SELECT hasnt_type( + 'myschema', + 'sometype', + 'There should be no type myschema.sometype' + ); + +This function is the inverse of `has_type()`. The test passes if the specified +type does *not* exist. + +### `has_domain( schema, domain, description )` ### +### `has_domain( schema, domain )` ### +### `has_domain( domain, description )` ### +### `has_domain( domain )` ### + + SELECT has_domain( + 'myschema', + 'somedomain', + 'I got myschema.somedomain' + ); + +This function tests whether or not a domain exists in the database. The first +argument is a schema name, the second is the name of a domain, and the third +is the test description. If you omit the schema, the domain must be visible in +the search path. If you omit the test description, it will be set to "Domain +`:domain` should exist". If you're passing a schema and domain rather than +domain and description, be sure to cast the arguments to `name` values so that +your domain name doesn't get treated as a description. + +### `hasnt_domain( schema, domain, description )` ### +### `hasnt_domain( schema, domain )` ### +### `hasnt_domain( domain, description )` ### +### `hasnt_domain( domain )` ### + + SELECT hasnt_domain( + 'myschema', + 'somedomain', + 'There should be no domain myschema.somedomain' + ); + +This function is the inverse of `has_domain()`. The test passes if the +specified domain does *not* exist. + +### `has_enum( schema, enum, description )` ### +### `has_enum( schema, enum )` ### +### `has_enum( enum, description )` ### +### `has_enum( enum )` ### + + SELECT has_enum( + 'myschema', + 'someenum', + 'I got myschema.someenum' + ); + +This function tests whether or not a enum exists in the database. Enums are +supported in PostgreSQL 8.3 or higher. The first argument is a schema name, +the second is the an enum name, and the third is the test description. If you +omit the schema, the enum must be visible in the search path. If you omit the +test description, it will be set to "Enum `:enum` should exist". If you're +passing a schema and enum rather than enum and description, be sure to cast +the arguments to `name` values so that your enum name doesn't get treated as a +description. + +### `has_index( schema, table, index, columns[], description )` ### +### `has_index( schema, table, index, columns[] )` ### +### `has_index( schema, table, index, column/expression, description )` ### +### `has_index( schema, table, index, columns/expression )` ### +### `has_index( table, index, columns[], description )` ### +### `has_index( table, index, columns[], description )` ### +### `has_index( table, index, column/expression, description )` ### +### `has_index( schema, table, index, column/expression )` ### +### `has_index( table, index, column/expression )` ### +### `has_index( schema, table, index )` ### +### `has_index( table, index, description )` ### +### `has_index( table, index )` ### + + SELECT has_index( + 'myschema', + 'sometable', + 'myindex', + ARRAY[ 'somecolumn', 'anothercolumn' ], + 'Index "myindex" should exist' + ); + + SELECT has_index('myschema', 'sometable', 'anidx', 'somecolumn'); + SELECT has_index('myschema', 'sometable', 'loweridx', 'LOWER(somecolumn)'); + SELECT has_index('sometable', 'someindex'); + +Checks for the existence of an index associated with the named table. The +`:schema` argument is optional, as is the column name or names or expression, +and the description. The columns argument may be a string naming one column or +an array of column names. It may also be a string representing an expression, +such as `lower(foo)`. For expressions, you must use lowercase for all SQL +keywords and functions to properly compare to PostgreSQL's internal form of +the expression. + +If you find that the function call seems to be getting confused, cast the +index name to the `NAME` type: + + SELECT has_index( 'public', 'sometab', 'idx_foo', 'name'::name ); + +If the index does not exist, `has_column()` will output a diagnostic message +such as: + + # Index "blah" ON public.sometab not found + +If the index was found but the column specification or expression is +incorrect, the diagnostics will look more like this: + + # have: "idx_baz" ON public.sometab(lower(name)) + # want: "idx_baz" ON public.sometab(lower(lname)) + +### `has_trigger( schema, table, trigger, description )` ### +### `has_trigger( schema, table, trigger )` ### +### `has_trigger( table, trigger )` ### + + SELECT has_trigger( + 'myschema', + 'sometable', + 'sometrigger', + 'Trigger "sometrigger" should exist' + ); + + SELECT has_trigger( 'sometable', 'sometrigger' ); + +Tests to see if the specified table has the named trigger. The `:description` +is optional, and if the schema is omitted, the table with which the trigger is +associated must be visible. + +### `can( schema, functions[], description )` ### +### `can( schema, functions[] )` ### +### `can( functions[], description )` ### +### `can( functions[] )` ### + + SELECT can( 'pg_catalog', ARRAY['upper', 'lower'] ); + +Checks to be sure that `:schema` has `:functions[]` defined. This is subtly +different from `functions_are()`. `functions_are()` fails if the functions +defined in `:schema` are not exactly the functions defined in `:functions[]`. +`can()`, on the other hand, just makes sure that `functions[]` exist. + +If `:schema` is omitted, then `can()` will look for functions defined in +schemas defined in the search path. No matter how many functions are listed in +`:functions[]`, a single call to `can()` counts as one test. If you want +otherwise, call `can()` once for each function -- or better yet, use +`can_ok()`. + +If any of the functions are not defined, the test will fail and the +diagnostics will output a list of the functions that are missing, like so: + + # Failed test 52: "Schema pg_catalog can" + # pg_catalog.foo() missing + # pg_catalog.bar() missing + +### `can_ok( schema, function, args[], description )` ### +### `can_ok( schema, function, args[] )` ### +### `can_ok( schema, function, description )` ### +### `can_ok( schema, function )` ### +### `can_ok( function, args[], description )` ### +### `can_ok( function, args[] )` ### +### `can_ok( function, description )` ### +### `can_ok( function )` ### + + SELECT can_ok( + 'pg_catalog', + 'decode', + ARRAY[ 'text', 'text' ], + 'Function decode(text, text) should exist' + ); + + SELECT can_ok( 'do_something' ); + SELECT can_ok( 'do_something', ARRAY['integer'] ); + SELECT can_ok( 'do_something', ARRAY['numeric'] ); + +Checks to be sure that the given function exists in the named schema and with +the specified argument data types. If `:schema` is omitted, `can_ok()` will +search for the function in the schemas defined in the search path. If +`:args[]` is omitted, `can_ok()` will see if the function exists without +regard to its arguments. + +The `:args[]` argument should be formatted as it would be displayed in the +view of a function using the `\df` command in `psql`. For example, even if you +have a numeric column with a precision of 8, you should specify +`ARRAY['numeric']`". If you created a `varchar(64)` column, you should pass +the `:args[]` argument as `ARRAY['character varying']`. + +If you wish to use the two-argument form of `can_ok()`, specifying only the +schema and the function name, you must cast the `:function` argument to +`:name` in order to disambiguate it from from the +`can_ok(`:function`, `:description)` form. If you neglect to do so, your +results will be unexpected. + +Also, if you use the string form to specify the `:args[]` array, be sure to +cast it to `name[]` to disambiguate it from a text string: + + SELECT can_ok( 'lower', '{text}'::name[] ); + +### `has_cast( source_type, target_type, schema, function, description )` ### +### `has_cast( source_type, target_type, schema, function )` ### +### `has_cast( source_type, target_type, function, description )` ### +### `has_cast( source_type, target_type, function )` ### +### `has_cast( source_type, target_type, description )` ### +### `has_cast( source_type, target_type )` ### + + SELECT has_cast( + 'integer', + 'bigint', + 'pg_catalog', + 'int8' + 'We should have a cast from integer to bigint' + ); + +Tests for the existence of a cast. A cast consists of a source data type, a +target data type, and perhaps a (possibly schema-qualified) function. If you +omit the description four the 3- or 4-argument version, you'll need to cast +the function name to the `NAME` data type so that PostgreSQL doesn't resolve +the function name as a description. For example: + + SELECT has_cast( 'integer', 'bigint', 'int8'::NAME ); + +pgTAP will generate a useful description if you don't provide one. + +### `hasnt_cast( source_type, target_type, schema, function, description )` ### +### `hasnt_cast( source_type, target_type, schema, function )` ### +### `hasnt_cast( source_type, target_type, function, description )` ### +### `hasnt_cast( source_type, target_type, function )` ### +### `hasnt_cast( source_type, target_type, description )` ### +### `hasnt_cast( source_type, target_type )` ### + + SELECT hasnt_cast( 'integer', 'circle' ); + +This function is the inverse of `has_cast()`. The test passes if the specified +cast does *not* exist. + +### `has_operator( left_type, schema, name, right_type, return_type, desc )` ### +### `has_operator( left_type, schema, name, right_type, return_type )` ### +### `has_operator( left_type, name, right_type, return_type, desc )` ### +### `has_operator( left_type, name, right_type, return_type )` ### +### `has_operator( left_type, name, right_type, desc )` ### +### `has_operator( left_type, name, right_type )` ### + + SELECT has_operator( + 'integer', + 'pg_catalog', '<=', + 'integer', + 'boolean', + 'Operator (integer <= integer RETURNS boolean) should exist' + ); + +Tests for the presence of a binary operator. If the operator exists with the +given schema, name, left and right arguments, and return value, the test will +fail. If the operator does not exist, the test will fail. If you omit the +schema name, then the operator must be visible in the search path. If you omit +the test description, pgTAP will generate a reasonable one for you. The return +value is also optional. If you need to test for a left or right unary +operator, use `has_leftop()` or `has_rightop()` instead. + +### `has_leftop( schema, name, right_type, return_type, desc )` ### +### `has_leftop( schema, name, right_type, return_type )` ### +### `has_leftop( name, right_type, return_type, desc )` ### +### `has_leftop( name, right_type, return_type )` ### +### `has_leftop( name, right_type, desc )` ### +### `has_leftop( name, right_type )` ### + + SELECT has_leftop( + 'pg_catalog', '!!', + 'bigint', + 'numeric', + 'Operator (!! bigint RETURNS numeric) should exist' + ); + +Tests for the presence of a left-unary operator. If the operator exists with +the given schema, name, right argument, and return value, the test will fail. +If the operator does not exist, the test will fail. If you omit the schema +name, then the operator must be visible in the search path. If you omit the +test description, pgTAP will generate a reasonable one for you. The return +value is also optional. + +### `has_rightop( left_type, schema, name, return_type, desc )` ### +### `has_rightop( left_type, schema, name, return_type )` ### +### `has_rightop( left_type, name, return_type, desc )` ### +### `has_rightop( left_type, name, return_type )` ### +### `has_rightop( left_type, name, desc )` ### +### `has_rightop( left_type, name )` ### + + SELECT has_rightop( + 'bigint', + 'pg_catalog', '!', + 'numeric', + 'Operator (bigint ! RETURNS numeric) should exist' + ); + +Tests for the presence of a right-unary operator. If the operator exists with +the given left argument, schema, name, and return value, the test will fail. +If the operator does not exist, the test will fail. If you omit the schema +name, then the operator must be visible in the search path. If you omit the +test description, pgTAP will generate a reasonable one for you. The return +value is also optional. + +### `has_role( role, desc )` ### +### `has_role( role )` ### + + SELECT has_role( 'theory', 'Role "theory" should exist' ); + +Checks to ensure that a database role exists. If the description is omitted, +it will default to "Role `:role` should exist". + +### `hasnt_role( role, desc )` ### +### `hasnt_role( role )` ### + + SELECT hasnt_role( 'theory', 'Role "theory" not should exist' ); + +The inverse of `has_role()`, this function tests for the *absence* of a +database role. + +### `has_user( user, desc )` ### +### `has_user( user )` ### + + SELECT has_user( 'theory', 'User "theory" should exist' ); + +Checks to ensure that a database user exists. If the description is omitted, +it will default to "User `:user` should exist". - SELECT hasnt_domain( - 'myschema', - 'somedomain', - 'There should be no domain myschema.somedomain' - ); +### `hasnt_user( user, desc )` ### +### `hasnt_user( user )` ### -This function is the inverse of `has_domain()`. The test passes if the -specified domain does *not* exist. + SELECT hasnt_user( 'theory', 'User "theory" not should exist' ); -### `has_enum( schema, enum, description )` ### -### `has_enum( schema, enum )` ### -### `has_enum( enum, description )` ### -### `has_enum( enum )` ### +The inverse of `has_user()`, this function tests for the *absence* of a +database user. - SELECT has_enum( - 'myschema', - 'someenum', - 'I got myschema.someenum' - ); +### `has_group( group, desc )` ### +### `has_group( group )` ### -This function tests whether or not a enum exists in the database. Enums are -supported in PostgreSQL 8.3 or higher. The first argument is a schema name, -the second is the an enum name, and the third is the test description. If you -omit the schema, the enum must be visible in the search path. If you omit the -test description, it will be set to "Enum `:enum` should exist". If you're -passing a schema and enum rather than enum and description, be sure to cast -the arguments to `name` values so that your enum name doesn't get treated as a -description. + SELECT has_group( 'sweeties, 'Group "sweeties" should exist' ); -### `hasnt_enum( schema, enum, description )` ### -### `hasnt_enum( schema, enum )` ### -### `hasnt_enum( enum, description )` ### -### `hasnt_enum( enum )` ### +Checks to ensure that a database group exists. If the description is omitted, +it will default to "Group `:group` should exist". - SELECT hasnt_enum( - 'myschema', - 'someenum', - 'There should be no enum myschema.someenum' - ); +### `hasnt_group( group, desc )` ### +### `hasnt_group( group )` ### -This function is the inverse of `has_enum()`. The test passes if the -specified enum does *not* exist. + SELECT hasnt_group( 'meanies, 'Group meaines should not exist' ); -### `enum_has_labels( schema, enum, labels, desc )` ### -### `enum_has_labels( schema, enum, labels )` ### -### `enum_has_labels( enum, labels, desc )` ### -### `enum_has_labels( enum, labels )` ### +The inverse of `has_group()`, this function tests for the *absence* of a +database group. - SELECT enum_has_labels( - 'myschema', - 'someenum', - ARRAY['foo', 'bar'], - 'Enum someenum should have labels foo, bar' - ); +Table For One +------------- -This function tests that an enum consists of an expected list of labels. Enums -are supported in PostgreSQL 8.3 or higher. The first argument is a schema -name, the second an enum name, the third an array of enum labels, and the -fourth a description. If you omit the schema, the enum must be visible in the -search path. If you omit the test description, it will be set to "Enum `:enum` -should have labels (`:labels`)". +Okay, you're sure that your database has exactly the [right schema](#I+Object! +"I Object!") and that all of the objects you need [are +there](#To+Have+or+Have+Not "To Have or Have Not"). So let's take a closer +look at tables. There are a lot of ways to look at tables, to make sure that +they have all the columns, indexes, constraints, keys, and indexes they need. +So we have the assertions to validate 'em. ### `has_column( schema, table, column, description )` ### ### `has_column( table, column, description )` ### @@ -1206,7 +1539,7 @@ But use `has_column()` to make sure the column exists first, eh? 'Column myschema.sometable.somecolumn has a default' ); -Tests whether or not a column has a default value. Fails if the column dosn't +Tests whether or not a column has a default value. Fails if the column doesn't have a default value. It will also fail if the column doesn't exist, and emit useful diagnostics to let you know: @@ -1293,7 +1626,7 @@ that this test will fail if the table or column in question does not exist. The default argument must have an unambiguous type in order for the call to succeed. If you see an error such as 'ERROR: could not determine polymorphic type because input has type "unknown"', it's because you forgot to cast the -expected value, probabaly a `NULL` (which, by the way, you can only properly +expected value, probably a `NULL` (which, by the way, you can only properly test for in PostgreSQL 8.3 and later), to its proper type. IOW, this will fail: @@ -1531,15 +1864,15 @@ between the foreign and primary key tables. To properly test your relationships, this should be your main test function of choice. The first three arguments are the schema, table, and column or array of -columns that constitue the foreign key constraint. The schema name is +columns that constitute the foreign key constraint. The schema name is optional, and the columns can be specified as a string for a single column or an array of strings for multiple columns. The next three arguments are the schema, table, and column or columns that constitute the corresponding primary key. Again, the schema is optional and the columns may be a string or array of strings (though of course it should have the same number of elements as the foreign key column argument). The seventh argument is an optional description -If it's not included, it will be set to ":fk_schema.:fk_table(:fk_column) -should reference :pk_column.pk_table(:pk_column). +If it's not included, it will be set to `:fk_schema.:fk_table(:fk_column)` +should reference `:pk_column.pk_table(:pk_column)`. If the test fails, it will output useful diagnostics. For example this test: @@ -1629,90 +1962,6 @@ not exist. Just like `col_is_pk()`, except that it test that the column or array of columns have a check constraint on them. -### `indexes_are( schema, table, indexes[], description )` ### -### `indexes_are( schema, table, indexes[] )` ### -### `indexes_are( table, indexes[], description )` ### -### `indexes_are( table, indexes[] )` ### - - SELECT indexes_are( - 'myschema', - 'atable', - ARRAY[ 'atable_pkey', 'idx_atable_name' ], - 'Should have the correct indexes on myschema.atable' - ); - -This function tests that all of the indexes on the named table are only the -indexes that *should* be on that table. In other words, given a list of -indexes, this assertion will fail if there are indexes that are not in the -list, or if there are indexes in the list that are not on the table. - -This test is useful in environments where many developers may be making -changes to the database, or where replication has been deployed, and you want -to make sure that the indexes on a table are exactly the indexes that should -exist on a table, no more, no less. - -If the `:schema` argument is omitted, the table must be visible in the search -path. If the description is omitted, a generally useful default description -will be generated. - -In the event of a failure, you'll see diagnostics listing the extra and/or -missing indexes, like so: - - # Failed test 180: "Table fou should have the correct indexes" - # Extra indexes: - # fou_pkey - # Missing indexes: - # idx_fou_name - -### `has_index( schema, table, index, columns[], description )` ### -### `has_index( schema, table, index, columns[] )` ### -### `has_index( schema, table, index, column/expression, description )` ### -### `has_index( schema, table, index, columns/expression )` ### -### `has_index( table, index, columns[], description )` ### -### `has_index( table, index, columns[], description )` ### -### `has_index( table, index, column/expression, description )` ### -### `has_index( schema, table, index, column/expression )` ### -### `has_index( table, index, column/expression )` ### -### `has_index( schema, table, index )` ### -### `has_index( table, index, description )` ### -### `has_index( table, index )` ### - - SELECT has_index( - 'myschema', - 'sometable', - 'myindex', - ARRAY[ 'somecolumn', 'anothercolumn' ], - 'Index "myindex" should exist' - ); - - SELECT has_index('myschema', 'sometable', 'anidx', 'somecolumn'); - SELECT has_index('myschema', 'sometable', 'loweridx', 'LOWER(somecolumn)'); - SELECT has_index('sometable', 'someindex'); - -Checks for the existence of an index associated with the named table. The -`:schema` argument is optional, as is the column name or names or expression, -and the description. The columns argument may be a string naming one column or -an array of column names. It may also be a string representing an expression, -such as `lower(foo)`. For expressions, you must use lowercase for all SQL -keywords and functions to properly compare to PostgreSQL's internal form of -the expression. - -If you find that the function call seems to be getting confused, cast the -index name to the `NAME` type: - - SELECT has_index( 'public', 'sometab', 'idx_foo', 'name'::name ); - -If the index does not exist, `has_column()` will output a diagnostic message -such as: - - # Index "blah" ON public.sometab not found - -If the index was found but the column specification or expression is -incorrect, the diagnostics will look more like this: - - # have: "idx_baz" ON public.sometab(lower(name)) - # want: "idx_baz" ON public.sometab(lower(lname)) - ### `index_is_unique( schema, table, index, description )` ### ### `index_is_unique( schema, table, index )` ### ### `index_is_unique( table, index )` ### @@ -1787,190 +2036,73 @@ writing, the supported types are: * gist * gin -If the test fails, it will emit a diagnostic message with the actual index -type, like so: - - # Failed test 175: "Index idx_bar should be a hash index" - # have: btree - # want: hash - -### `has_trigger( schema, table, trigger, description )` ### -### `has_trigger( schema, table, trigger )` ### -### `has_trigger( table, trigger )` ### - - SELECT has_trigger( - 'myschema', - 'sometable', - 'sometrigger', - 'Trigger "sometrigger" should exist' - ); - - SELECT has_trigger( 'sometable', 'sometrigger' ); - -Tests to see if the specified table has the named trigger. The `:description` -is optional, and if the schema is omitted, the table with which the trigger is -associated must be visible. - -### `trigger_is( schema, table, trigger, schema, function, description )` ### -### `trigger_is( schema, table, trigger, schema, function )` ### -### `trigger_is( table, trigger, function, description )` ### -### `trigger_is( table, trigger, function )` ### - - SELECT trigger_is( - 'myschema', - 'sometable', - 'sometrigger', - 'myschema', - 'somefunction', - 'Trigger "sometrigger" should call somefunction()' - ); - -Tests that the specified trigger calls the named function. If not, it outputs -a useful diagnostic: - - # Failed test 31: "Trigger set_users_pass should call hash_password()" - # have: hash_pass - # want: hash_password - -### `functions_are( schema, functions[], description )` ### -### `functions_are( schema, functions[] )` ### -### `functions_are( functions[], description )` ### -### `functions_are( functions[] )` ### - - SELECT functions_are( - 'myschema', - ARRAY[ 'foo', 'bar', 'frobnitz' ], - 'Should have the correct functions in myschema' - ); - -This function tests that all of the functions in the named schema, or that are -visible in the search path, are only the functions that *should* be there. In -other words, given a list of functions, this assertion will fail if there are -functions that are not in the list, or if there are functions in the list that -are not found in the search path. - -This test is useful in environments where many developers may be making -changes to the database, or where replication has been deployed, and you want -to make sure that the functions in a database are exactly the functions that -should exist in the database, no more, no less. - -If the `:schema` argument is omitted, functions will be sought in the search -path, excluding `pg_catalog.` If the description is omitted, a generally -useful default description will be generated. - -In the event of a failure, you'll see diagnostics listing the extra and/or -missing functions, like so: - - # Failed test 150: "Schema someschema should have the correct functions" - # Extra functions: - # schnauzify - # Missing functions: - # frobnitz - -### `can( schema, functions[], description )` ### -### `can( schema, functions[] )` ### -### `can( functions[], description )` ### -### `can( functions[] )` ### - - SELECT can( 'pg_catalog', ARRAY['upper', 'lower'] ); - -Checks to be sure that `:schema` has `:functions[]` defined. This is subtley -different from `functions_are()`. `functions_are()` fails if the functions -defined in `:schema` are not exactly the functions defined in `:functions[]`. -`can()`, on the other hand, just makes sure that `functions[]` exist. - -If `:schema` is omitted, then `can()` will look for functions defined in -schemas defined in the search path. No matter how many functions are listed in -`:functions[]`, a single call to `can()` counts as one test. If you want -otherwise, call `can()` once for each function -- or better yet, use -`can_ok()`. - -If any of the functions are not defined, the test will fail and the -diagnostics will output a list of the functions that are missing, like so: - - # Failed test 52: "Schema pg_catalog can" - # pg_catalog.foo() missing - # pg_catalog.bar() missing - -### `can_ok( schema, function, args[], description )` ### -### `can_ok( schema, function, args[] )` ### -### `can_ok( schema, function, description )` ### -### `can_ok( schema, function )` ### -### `can_ok( function, args[], description )` ### -### `can_ok( function, args[] )` ### -### `can_ok( function, description )` ### -### `can_ok( function )` ### - - SELECT can_ok( - 'pg_catalog', - 'decode', - ARRAY[ 'text', 'text' ], - 'Function decode(text, text) should exist' - ); - - SELECT can_ok( 'do_something' ); - SELECT can_ok( 'do_something', ARRAY['integer'] ); - SELECT can_ok( 'do_something', ARRAY['numeric'] ); - -Checks to be sure that the given function exists in the named schema and with -the specified argument data types. If `:schema` is omitted, `can_ok()` will -search for the function in the schemas defined in the search path. If -`:args[]` is omitted, `can_ok()` will see if the function exists without -regard to its arguments. +If the test fails, it will emit a diagnostic message with the actual index +type, like so: -The `:args[]` argument should be formatted as it would be displayed in the -view of a function using the `\df` command in `psql`. For example, even if you -have a numeric column with a precision of 8, you should specify -`ARRAY['numeric']`". If you created a `varchar(64)` column, you should pass -the `:args[]` argument as `ARRAY['character varying']`. + # Failed test 175: "Index idx_bar should be a hash index" + # have: btree + # want: hash -If you wish to use the two-argument form of `can_ok()`, specifying only the -schema and the function name, you must cast the `:function` argument to -`:name` in order to disambiguate it from from the -`can_ok(`:function`, `:description)` form. If you neglect to do so, your -results will be unexpected. +### `trigger_is( schema, table, trigger, schema, function, description )` ### +### `trigger_is( schema, table, trigger, schema, function )` ### +### `trigger_is( table, trigger, function, description )` ### +### `trigger_is( table, trigger, function )` ### -Also, if you use the string form to specify the `:args[]` array, be sure to -cast it to `name[]` to disambiguate it from a text string: + SELECT trigger_is( + 'myschema', + 'sometable', + 'sometrigger', + 'myschema', + 'somefunction', + 'Trigger "sometrigger" should call somefunction()' + ); - SELECT can_ok( 'lower', '{text}'::name[] ); +Tests that the specified trigger calls the named function. If not, it outputs +a useful diagnostic: -### `has_cast( source_type, target_type, schema, function, description )` ### -### `has_cast( source_type, target_type, schema, function )` ### -### `has_cast( source_type, target_type, function, description )` ### -### `has_cast( source_type, target_type, function )` ### -### `has_cast( source_type, target_type, description )` ### -### `has_cast( source_type, target_type )` ### + # Failed test 31: "Trigger set_users_pass should call hash_password()" + # have: hash_pass + # want: hash_password - SELECT has_cast( - 'integer', - 'bigint', - 'pg_catalog', - 'int8' - 'We should have a cast from integer to bigint' - ); +Database Deets +-------------- -Tests for the existence of a cast. A cast consists of a source data type, a -target data type, and perhaps a (possibly schema-qualified) function. If you -omit the description four the 3- or 4-argument version, you'll need to cast -the function name to the `NAME` data type so that PostgreSQL doesn't resolve -the function name as a description. For example: +Tables aren't the only objects in the database, as you well know. These +assertions close the gap by letting you test the attributes of other database +objects. - SELECT has_cast( 'integer', 'bigint', 'int8'::NAME ); +### `hasnt_enum( schema, enum, description )` ### +### `hasnt_enum( schema, enum )` ### +### `hasnt_enum( enum, description )` ### +### `hasnt_enum( enum )` ### -pgTAP will generate a useful description if you don't provide one. + SELECT hasnt_enum( + 'myschema', + 'someenum', + 'There should be no enum myschema.someenum' + ); -### `hasnt_cast( source_type, target_type, schema, function, description )` ### -### `hasnt_cast( source_type, target_type, schema, function )` ### -### `hasnt_cast( source_type, target_type, function, description )` ### -### `hasnt_cast( source_type, target_type, function )` ### -### `hasnt_cast( source_type, target_type, description )` ### -### `hasnt_cast( source_type, target_type )` ### +This function is the inverse of `has_enum()`. The test passes if the +specified enum does *not* exist. - SELECT hasnt_cast( 'integer', 'circle' ); +### `enum_has_labels( schema, enum, labels, desc )` ### +### `enum_has_labels( schema, enum, labels )` ### +### `enum_has_labels( enum, labels, desc )` ### +### `enum_has_labels( enum, labels )` ### -This function is the inverse of `has_cast()`. The test passes if the specified -cast does *not* exist. + SELECT enum_has_labels( + 'myschema', + 'someenum', + ARRAY['foo', 'bar'], + 'Enum someenum should have labels foo, bar' + ); + +This function tests that an enum consists of an expected list of labels. Enums +are supported in PostgreSQL 8.3 or higher. The first argument is a schema +name, the second an enum name, the third an array of enum labels, and the +fourth a description. If you omit the schema, the enum must be visible in the +search path. If you omit the test description, it will be set to "Enum `:enum` +should have labels (`:labels`)". ### `cast_context_is( source_type, target_type, context, desc )` ### ### `cast_context_is( source_type, target_type, context )` ### @@ -2002,131 +2134,6 @@ If the cast doesn't exist, you'll be told that, too: But you've already used `has_cast()` to make sure of that, right? -### `has_operator( left_type, schema, name, right_type, return_type, desc )` ### -### `has_operator( left_type, schema, name, right_type, return_type )` ### -### `has_operator( left_type, name, right_type, return_type, desc )` ### -### `has_operator( left_type, name, right_type, return_type )` ### -### `has_operator( left_type, name, right_type, desc )` ### -### `has_operator( left_type, name, right_type )` ### - - SELECT has_operator( - 'integer', - 'pg_catalog', '<=', - 'integer', - 'boolean', - 'Operator (integer <= integer RETURNS boolean) should exist' - ); - -Tests for the presence of a binary operator. If the operator exists with the -given schema, name, left and right arguments, and return value, the test will -fail. If the operator does not exist, the test will fail. If you omit the -schema name, then the operator must be visible in the search path. If you omit -the test description, pgTAP will generate a reasonable one for you. The return -value is also optional. If you need to test for a left or right unary -operator, use `has_leftop()` or `has_rightop()` instead. - -### `has_leftop( schema, name, right_type, return_type, desc )` ### -### `has_leftop( schema, name, right_type, return_type )` ### -### `has_leftop( name, right_type, return_type, desc )` ### -### `has_leftop( name, right_type, return_type )` ### -### `has_leftop( name, right_type, desc )` ### -### `has_leftop( name, right_type )` ### - - SELECT has_leftop( - 'pg_catalog', '!!', - 'bigint', - 'numeric', - 'Operator (!! bigint RETURNS numeric) should exist' - ); - -Tests for the presence of a left-unary operator. If the operator exists with -the given schema, name, right argument, and return value, the test will fail. -If the operator does not exist, the test will fail. If you omit the schema -name, then the operator must be visible in the search path. If you omit the -test description, pgTAP will generate a reasonable one for you. The return -value is also optional. - -### `has_rightop( left_type, schema, name, return_type, desc )` ### -### `has_rightop( left_type, schema, name, return_type )` ### -### `has_rightop( left_type, name, return_type, desc )` ### -### `has_rightop( left_type, name, return_type )` ### -### `has_rightop( left_type, name, desc )` ### -### `has_rightop( left_type, name )` ### - - SELECT has_rightop( - 'bigint', - 'pg_catalog', '!', - 'numeric', - 'Operator (bigint ! RETURNS numeric) should exist' - ); - -Tests for the presence of a right-unary operator. If the operator exists with -the given left argument, schema, name, and return value, the test will fail. -If the operator does not exist, the test will fail. If you omit the schema -name, then the operator must be visible in the search path. If you omit the -test description, pgTAP will generate a reasonable one for you. The return -value is also optional. - -### `has_role( role, desc )` ### -### `has_role( role )` ### - - SELECT has_role( 'theory', 'Role "theory" should exist' ); - -Checks to ensure that a database role exists. If the description is omitted, -it will default to "Role `:role` should exist". - -### `hasnt_role( role, desc )` ### -### `hasnt_role( role )` ### - - SELECT hasnt_role( 'theory', 'Role "theory" not should exist' ); - -The inverse of `has_role()`, this function tests for the *absence* of a -database role. - -### `users_are( users[], description )` ### -### `users_are( users[] )` ### - - SELECT users_are( - ARRAY[ 'postgres', 'someone', 'root' ], - 'Should have the correct users - ); - -This function tests that all of the users in the databse only the users that -*should* be there. In other words, given a list of users, this assertion will -fail if there are users that are not in the list, or if there are users in the -list that are missing from the database. - -This test is useful in environments where many developers may be making -changes to the database, or where replication has been deployed, and you want -to make sure that the users in a database are exactly the users that should -exist in the database, no more, no less. - -In the event of a failure, you'll see diagnostics listing the extra and/or -missing users, like so: - - not ok 195 - whatever - # Failed test 195: "There should be the correct users" - # Extra users: - # root - # Missing users: - # bobby - -### `has_user( user, desc )` ### -### `has_user( user )` ### - - SELECT has_user( 'theory', 'User "theory" should exist' ); - -Checks to ensure that a database user exists. If the description is omitted, -it will default to "User `:user` should exist". - -### `hasnt_user( user, desc )` ### -### `hasnt_user( user )` ### - - SELECT hasnt_user( 'theory', 'User "theory" not should exist' ); - -The inverse of `has_user()`, this function tests for the *absence* of a -database user. - ### `is_superuser( user, desc )` ### ### `is_superuser( user )` ### @@ -2149,50 +2156,6 @@ The inverse of `is_superuser()`, this function tests that a database user is database, the test is still considered a failure, and the diagnostics will say so. -### `groups_are( groups[], description )` ### -### `groups_are( groups[] )` ### - - SELECT groups_are( - ARRAY[ 'postgres', 'admins, 'l0s3rs' ], - 'Should have the correct groups - ); - -This function tests that all of the groups in the databse only the groups that -*should* be there. In other words, given a list of groups, this assertion will -fail if there are groups that are not in the list, or if there are groups in the -list that are missing from the database. - -This test is useful in environments where many developers may be making -changes to the database, or where replication has been deployed, and you want -to make sure that the groups in a database are exactly the groups that should -exist in the database, no more, no less. - -In the event of a failure, you'll see diagnostics listing the extra and/or -missing groups, like so: - - not ok 210 - There should be the correct groups - # Failed test 210: "There should be the correct groups" - # Extra groups: - # meanies - # Missing groups: - # __howdy__ - -### `has_group( group, desc )` ### -### `has_group( group )` ### - - SELECT has_group( 'sweeties, 'Group "sweeties" should exist' ); - -Checks to ensure that a database group exists. If the description is omitted, -it will default to "Group `:group` should exist". - -### `hasnt_group( group, desc )` ### -### `hasnt_group( group )` ### - - SELECT hasnt_group( 'meanies, 'Group meaines should not exist' ); - -The inverse of `has_group()`, this function tests for the *absence* of a -database group. - ### `is_member_of( group, users[], desc )` ### ### `is_member_of( group, users[] )` ### ### `is_member_of( group, users, desc )` ### @@ -2216,6 +2179,15 @@ If the group does not exist, the diagnostics will tell you that, instead. But you use `has_group()` to make sure the group exists before you check its members, don't you? Of course you do. +No Test for the Wicked +====================== + +There is more to pgTAP. Oh *so* much more! You can output your own +[diagnostics](#Diagnostics). You can write [conditional +tests](#Conditional+Tests) based on the output of [utility +functions](#Utility+Functions). You can [batch up tests in +functions](#Tap+That+Batch). Read on to learn all about it. + Diagnostics ----------- @@ -2249,17 +2221,11 @@ Conditional Tests ----------------- Sometimes running a test under certain conditions will cause the test script -to die. A certain function or method isn't implemented (such as fork() on -MacOS), some resource isn't available (like a net connection) or a module -isn't available. In these cases it's necessary to skip tests, or declare that -they are supposed to fail but will work in the future (a todo test). - -Sometimes you might have tests that you want to pass, but you haven't gotten -around to implementing the logic required to make them pass Other times, you -might have tests that pass only under certain circumstances, such as with -particular versions of PostgreSQL. In these cases it's necessary to skip -tests, or to declare that they are supposed to fail but will work in the -future (a todo test). +or function to die. A certain function or feature isn't implemented (such as +`pg_sleep()` prior to PostgreSQL 8.2), some resource isn't available (like a +procedural language), or a contrib module isn't available. In these cases it's +necessary to skip tests, or declare that they are supposed to fail but will +work in the future (a todo test). ### `skip( why, how_many )` ### ### `skip( how_many, why )` ### @@ -2426,7 +2392,7 @@ version of PostgreSQL. For example: The revision level is in the tens position, the minor version in the thousands position, and the major version in the ten thousands position and above (assuming PostgreSQL 10 is ever released, it will be in the hundred thousands -position). This value is the same as the "server_version_num" setting +position). This value is the same as the `server_version_num` setting available in PostgreSQL 8.2 and higher, but supported by this function back to PostgreSQL 8.0: @@ -2443,7 +2409,7 @@ Returns a string representing the name of the operating system on which pgTAP was compiled. This can be useful for determining whether or not to skip tests on certain operating systems. -This is usually the same a the ouput of `uname`, but converted to lower case. +This is usually the same a the output of `uname`, but converted to lower case. There are some semantics in the pgTAP build process to detect other operating systems, though assistance in improving such detection would be greatly appreciated. @@ -2590,8 +2556,8 @@ database in largely the same condition as it was in when you started it (the one exception I'm aware of being sequences, which are not rolled back to the value used at the beginning of a rolled-back transaction). -Writing Test Functions -====================== +Compose Yourself +================ So, you've been using pgTAP for a while, and now you want to write your own test functions. Go ahead; I don't mind. In fact, I encourage it. How? Why, @@ -2618,8 +2584,8 @@ want to simplify things. Here's how to go about it: ' Have: ' || $1 || E'\n Want: ' || $2; ) END; -END; -$$ LANGUAGE plpgsql; + END; + $$ LANGUAGE plpgsql; Yep, that's it. The key is to always use pgTAP's `ok()` function to guarantee that the output is properly formatted, uses the next number in the sequence, @@ -2745,10 +2711,10 @@ uses `is()` to compare the strings: The fifth argument, `:want_diag`, which is also optional, compares the diagnostics generated during the test to an expected string. Such diagnostics **must** follow whatever is output by the call to `ok()` in your test. Your -test fuction should not call `diag()` until after it calls `ok()` or things +test function should not call `diag()` until after it calls `ok()` or things will get truly funky. -Assuming you've followed that rule in your `lc_eq()` tset function, to see +Assuming you've followed that rule in your `lc_eq()` test function, to see what happens when a `lc_eq()` fails. Write your test to test the diagnostics like so: @@ -2846,14 +2812,15 @@ To Do * Useful schema testing functions to consider adding: * `has_operator_class()` * `has_rule()` + * `has_pl()` * `function_returns()` * `function_lang_is()` - * `seq_has_range()` - * `seq_increments_by()` - * `seq_starts_at()` - * `seq_cycles()` + * `sequence_has_range()` + * `sequence_increments_by()` + * `sequence_starts_at()` + * `sequence_cycles()` -Suported Versions +Supported Versions ----------------- pgTAP has been tested on the following builds of PostgreSQL: From 4d1ffc2dae411f4673bab1375d1acf859994529f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 23 May 2009 17:17:55 -0400 Subject: [PATCH 0356/1195] Added functions testing procedural languages. * `languages_are()` * `has_language()` * `hasnt_language()` * `language_is_trusted()` --- Changes | 3 +- README.pgtap | 56 ++++++++++++++++++++++++ expected/aretap.out | 17 +++++++- expected/hastap.out | 38 +++++++++++++++- pgtap.sql.in | 79 +++++++++++++++++++++++++++++++++ sql/aretap.sql | 54 ++++++++++++++++++++++- sql/hastap.sql | 103 +++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 345 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 466c7a3a9b2f..a3d2634d6f96 100644 --- a/Changes +++ b/Changes @@ -11,7 +11,8 @@ Revision history for pgTAP * Added `performs_ok()`. * Added `tablespaces_are()`, `schemas_are()`, `tables_are()`, `views_are()`, `sequences_are()`, `functions_are()`, `indexes_are()`, `users_are()`, - and `groups_are()`. + `groups_are()`, and `languages_are()`. +* Added `has_language()`, `hasnt_language()`, and `language_is_trusted()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index ef4ff7a818f8..691e84a084be 100644 --- a/README.pgtap +++ b/README.pgtap @@ -926,6 +926,25 @@ listing the extra and/or missing groups, like so: # Missing groups: # __howdy__ +### `languages_are( languages[], description )` ### +### `languages_are( languages[] )` ### + + SELECT languages_are( + ARRAY[ 'plpgsql', 'plperl', 'pllolcode' ], + 'Should have the correct procedural languages + ); + +This function tests that all of the procedural languages in the database only +the languages that *should* be there. In the event of a failure, you'll see +diagnostics listing the extra and/or missing languages, like so: + + not ok 225 - There should be the correct procedural languages + # Failed test 225: "There should be the correct procedural languages" + # Extra languages: + # pllolcode + # Missing languages: + # plpgsql + To Have or Have Not ------------------- @@ -1445,6 +1464,22 @@ it will default to "Group `:group` should exist". The inverse of `has_group()`, this function tests for the *absence* of a database group. +### `has_language( language, desc )` ### +### `has_language( language )` ### + + SELECT has_language( 'plpgsql', 'Language "plpgsql" should exist' ); + +Checks to ensure that a procedural language exists. If the description is +omitted, it will default to "Procedural language `:language` should exist". + +### `hasnt_language( language, desc )` ### +### `hasnt_language( language )` ### + + SELECT hasnt_language( 'plpgsql', 'Language "plpgsql" not should exist' ); + +The inverse of `has_language()`, this function tests for the *absence* of a +procedural language. + Table For One ------------- @@ -2064,6 +2099,27 @@ a useful diagnostic: # have: hash_pass # want: hash_password +### `language_is_trusted( language, description )` ### +### `language_is_trusted( language )` ### + + SELECT language_is_trusted( 'plperl', 'PL/Perl should be trusted' ); + +Tests that the specified procedural language is trusted. See the [CREATE +LANGUAGE](http://www.postgresql.org/docs/current/static/sql-createlanguage.html +"CREATE LANGUAGE") documentation for details on trusted and untrusted +procedural languages. If the `:description` argument is not passed, a suitably +useful default will be created. + +In the event that the language in question does not exist in the database, +`language_is_trusted()` will emit a diagnostic message to alert you to this +fact, like so: + + # Failed test 523: "Procedural language plomgwtf should be trusted" + # Procedural language plomgwtf does not exist + +But you really ought to call `has_language()` first so that you never get that +far. + Database Deets -------------- diff --git a/expected/aretap.out b/expected/aretap.out index 4a03f4e42ed3..9b90a78d3c24 100644 --- a/expected/aretap.out +++ b/expected/aretap.out @@ -1,5 +1,5 @@ \unset ECHO -1..209 +1..224 ok 1 - tablespaces_are(schemas, desc) should pass ok 2 - tablespaces_are(schemas, desc) should have the proper description ok 3 - tablespaces_are(schemas, desc) should have the proper diagnostics @@ -209,3 +209,18 @@ ok 206 - groups_are(groups, desc) extras should have the proper diagnostics ok 207 - groups_are(groups, desc) missing and extras should fail ok 208 - groups_are(groups, desc) missing and extras should have the proper description ok 209 - groups_are(groups, desc) missing and extras should have the proper diagnostics +ok 210 - languages_are(languages, desc) should pass +ok 211 - languages_are(languages, desc) should have the proper description +ok 212 - languages_are(languages, desc) should have the proper diagnostics +ok 213 - languages_are(languages) should pass +ok 214 - languages_are(languages) should have the proper description +ok 215 - languages_are(languages) should have the proper diagnostics +ok 216 - languages_are(languages, desc) missing should fail +ok 217 - languages_are(languages, desc) missing should have the proper description +ok 218 - languages_are(languages, desc) missing should have the proper diagnostics +ok 219 - languages_are(languages, desc) extras should fail +ok 220 - languages_are(languages, desc) extras should have the proper description +ok 221 - languages_are(languages, desc) extras should have the proper diagnostics +ok 222 - languages_are(languages, desc) missing and extras should fail +ok 223 - languages_are(languages, desc) missing and extras should have the proper description +ok 224 - languages_are(languages, desc) missing and extras should have the proper diagnostics diff --git a/expected/hastap.out b/expected/hastap.out index 3d256557c2cb..054eb096e975 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..486 +1..522 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -486,3 +486,39 @@ ok 483 - has_rightop( left, name, desc ) fail should have the proper diagnostics ok 484 - has_rightop( left, name ) fail should fail ok 485 - has_rightop( left, name ) fail should have the proper description ok 486 - has_rightop( left, name ) fail should have the proper diagnostics +ok 487 - has_language(language) should pass +ok 488 - has_language(language) should have the proper description +ok 489 - has_language(language) should have the proper diagnostics +ok 490 - has_language(language, desc) should pass +ok 491 - has_language(language, desc) should have the proper description +ok 492 - has_language(language, desc) should have the proper diagnostics +ok 493 - has_language(nonexistent language) should fail +ok 494 - has_language(nonexistent language) should have the proper description +ok 495 - has_language(nonexistent language) should have the proper diagnostics +ok 496 - has_language(nonexistent language, desc) should fail +ok 497 - has_language(nonexistent language, desc) should have the proper description +ok 498 - has_language(nonexistent language, desc) should have the proper diagnostics +ok 499 - hasnt_language(language) should fail +ok 500 - hasnt_language(language) should have the proper description +ok 501 - hasnt_language(language) should have the proper diagnostics +ok 502 - hasnt_language(language, desc) should fail +ok 503 - hasnt_language(language, desc) should have the proper description +ok 504 - hasnt_language(language, desc) should have the proper diagnostics +ok 505 - hasnt_language(nonexistent language) should pass +ok 506 - hasnt_language(nonexistent language) should have the proper description +ok 507 - hasnt_language(nonexistent language) should have the proper diagnostics +ok 508 - hasnt_language(nonexistent language, desc) should pass +ok 509 - hasnt_language(nonexistent language, desc) should have the proper description +ok 510 - hasnt_language(nonexistent language, desc) should have the proper diagnostics +ok 511 - language_is_trusted(language, desc) should pass +ok 512 - language_is_trusted(language, desc) should have the proper description +ok 513 - language_is_trusted(language, desc) should have the proper diagnostics +ok 514 - language_is_trusted(language) should pass +ok 515 - language_is_trusted(language) should have the proper description +ok 516 - language_is_trusted(language) should have the proper diagnostics +ok 517 - language_is_trusted(language, desc) fail should fail +ok 518 - language_is_trusted(language, desc) fail should have the proper description +ok 519 - language_is_trusted(language, desc) fail should have the proper diagnostics +ok 520 - language_is_trusted(language, desc) non-existent should fail +ok 521 - language_is_trusted(language, desc) non-existent should have the proper description +ok 522 - language_is_trusted(language, desc) non-existent should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 6fcb1fdb8658..03adb3a2b530 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -4140,6 +4140,85 @@ 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; + -- 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 $$ diff --git a/sql/aretap.sql b/sql/aretap.sql index 53df97f94411..e1fd5db0503d 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(209); +SELECT plan(224); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -743,6 +743,58 @@ SELECT * FROM check_test( __howdy__' ); +/****************************************************************************/ +-- Test languages_are(). + +CREATE FUNCTION ___mylangs(ex text) RETURNS NAME[] AS $$ + SELECT ARRAY( SELECT lanname FROM pg_catalog.pg_language WHERE lanispl AND lanname <> $1 ); +$$ LANGUAGE SQL; + +SELECT * FROM check_test( + languages_are( ___mylangs(''), 'whatever' ), + true, + 'languages_are(languages, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + languages_are( ___mylangs('') ), + true, + 'languages_are(languages)', + 'There should be the correct procedural languages', + '' +); + +SELECT * FROM check_test( + languages_are( array_append(___mylangs(''), 'plomgwtf'), 'whatever' ), + false, + 'languages_are(languages, desc) missing', + 'whatever', + ' Missing languages: + plomgwtf' +); + +SELECT * FROM check_test( + languages_are( ___mylangs('plpgsql'), 'whatever' ), + false, + 'languages_are(languages, desc) extras', + 'whatever', + ' Extra languages: + plpgsql' +); + +SELECT * FROM check_test( + languages_are( array_append(___mylangs('plpgsql'), 'plomgwtf'), 'whatever' ), + false, + 'languages_are(languages, desc) missing and extras', + 'whatever', + ' Extra languages: + plpgsql + Missing languages: + plomgwtf' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/sql/hastap.sql b/sql/hastap.sql index fabaa478e54c..892597247c93 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(486); +SELECT plan(522); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -1360,6 +1360,107 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test has_language() and hasnt_language(). + +SELECT * FROM check_test( + has_language('plpgsql'), + true, + 'has_language(language)', + 'Procedural language ' || quote_ident('plpgsql') || ' should exist', + '' +); + +SELECT * FROM check_test( + has_language('plpgsql', 'whatever'), + true, + 'has_language(language, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_language('aoijaoisjfaoidfjaisjdfosjf'), + false, + 'has_language(nonexistent language)', + 'Procedural language aoijaoisjfaoidfjaisjdfosjf should exist', + '' +); + +SELECT * FROM check_test( + has_language('aoijaoisjfaoidfjaisjdfosjf', 'desc'), + false, + 'has_language(nonexistent language, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_language('plpgsql'), + false, + 'hasnt_language(language)', + 'Procedural language ' || quote_ident('plpgsql') || ' should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_language('plpgsql', 'whatever'), + false, + 'hasnt_language(language, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_language('plomgwtf'), + true, + 'hasnt_language(nonexistent language)', + 'Procedural language plomgwtf should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_language('plomgwtf', 'desc'), + true, + 'hasnt_language(nonexistent language, desc)', + 'desc', + '' +); + +/****************************************************************************/ +-- Test language_is_trusted(). +SELECT * FROM check_test( + language_is_trusted('sql', 'whatever'), + true, + 'language_is_trusted(language, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + language_is_trusted('sql'), + true, + 'language_is_trusted(language)', + 'Procedural language sql should be trusted', + '' +); + +SELECT * FROM check_test( + language_is_trusted('c', 'whatever'), + false, + 'language_is_trusted(language, desc) fail', + 'whatever', + '' +); + +SELECT * FROM check_test( + language_is_trusted('plomgwtf', 'whatever'), + false, + 'language_is_trusted(language, desc) non-existent', + 'whatever', + ' Procedural language plomgwtf does not exist' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 00e860fa5d338ec0230e62ebf9e038a046085f4f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 23 May 2009 17:43:11 -0700 Subject: [PATCH 0357/1195] Added operator class assertion functions. * Added `opclasses_are()`. * Added `has_opclass()` and `hasnt_opclass()`. * Fixed `make test` so that it uses `bbin/pg_prove`. * Fixed the `make` target for `bbin/pg_prove` so that it correctly sets the executable bit. * Removed redundant test number lines from diagnostic examples in `README.pgtap`. * Noted in the docs that `schemas_are()` ignores `information_schema`. * Changed "desc" to "description" in some comments. --- Changes | 5 +- Makefile | 6 +- README.pgtap | 84 ++++++++++++++++------ expected/aretap.out | 28 +++++++- expected/hastap.out | 38 +++++++++- pgtap.sql.in | 171 ++++++++++++++++++++++++++++++++++++++------ sql/aretap.sql | 120 ++++++++++++++++++++++++++++++- sql/hastap.sql | 101 +++++++++++++++++++++++++- 8 files changed, 500 insertions(+), 53 deletions(-) diff --git a/Changes b/Changes index a3d2634d6f96..a44b3617f047 100644 --- a/Changes +++ b/Changes @@ -9,10 +9,11 @@ Revision history for pgTAP * Fixed failing test on Solaris 10 on Intel thanks to Gabrielle Roth. * Fixed a failing test for the version number string on 8.4 beta. * Added `performs_ok()`. +* Added `has_language()`, `hasnt_language()`, and `language_is_trusted()`. +* Added `has_opclass()` and `hasnt_opclass()`. * Added `tablespaces_are()`, `schemas_are()`, `tables_are()`, `views_are()`, `sequences_are()`, `functions_are()`, `indexes_are()`, `users_are()`, - `groups_are()`, and `languages_are()`. -* Added `has_language()`, `hasnt_language()`, and `language_is_trusted()`. + `groups_are()`, `opclasses_are()`, and `languages_are()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/Makefile b/Makefile index 78988e317bf3..774b83138a75 100644 --- a/Makefile +++ b/Makefile @@ -188,7 +188,6 @@ bbin/pg_prove: mkdir bbin # sed -e "s,\\('\\|F<\\)psql\\(['>]\\),\\1$(bindir)/psql\\2,g" bin/pg_prove > bbin/pg_prove sed -e "s,\\'psql\\',\\'$(bindir)/psql\\'," -e 's,F,F<$(bindir)/psql>,' bin/pg_prove > bbin/pg_prove - chmod +x bbin/pg_prove ifdef PERL $(PERL) '-MExtUtils::MY' -e 'MY->fixin(shift)' bbin/pg_prove ifndef HAVE_HARNESS @@ -197,6 +196,7 @@ endif else $(warning Could not find perl (required by pg_prove). Install it or set the PERL variable.) endif + chmod +x bbin/pg_prove # Make sure that we build the regression tests. installcheck: test_setup.sql @@ -211,8 +211,8 @@ extraclean: rm -rf bbin # In addition to installcheck, one can also run the tests through pg_prove. -test: test_setup.sql - ./bin/pg_prove --pset tuples_only=1 $(TESTS) +test: test_setup.sql bbin/pg_prove + ./bbin/pg_prove --pset tuples_only=1 $(TESTS) html: markdown -F 0x1000 README.pgtap > readme.html diff --git a/README.pgtap b/README.pgtap index 691e84a084be..201475ed4b9e 100644 --- a/README.pgtap +++ b/README.pgtap @@ -506,7 +506,7 @@ Its advantage over `ok()` is that when the test fails you'll know what `:this` and `:that` were: not ok 1 - # Failed test + # Failed test 1: # '23' # && # NULL @@ -590,8 +590,7 @@ four-argument form. Should a `throws_ok()` test fail it produces appropriate diagnostic messages. For example: - not ok 81 - simple error - # Failed test "This should not die" + # Failed test 81: "This should not die" # caught: 23505: duplicate key value violates unique constraint "try_pkey" # wanted: 23502: null value in column "id" violates not-null constraint @@ -612,8 +611,7 @@ Supported by 8.1 or higher. Should a `lives_ok()` test fail, it produces appropriate diagnostic messages. For example: - not ok 85 - simple success - # Failed test "simple success" + # Failed test 85: "simple success" # died: 23505: duplicate key value violates unique constraint "try_pkey" Idea borrowed from the Test::Exception Perl module. @@ -647,7 +645,6 @@ a couple of these in a test script or function. Should a `performs_ok()` test fail it produces appropriate diagnostic messages. For example: - not ok 19 - The lookup should be fast! # Failed test 19: "The lookup should be fast!" # runtime: 200.266 ms # exceeds: 200 ms @@ -723,7 +720,6 @@ This function tests that all of the tablespaces in the database only the tablespaces that *should* be there. In the event of a failure, you'll see diagnostics listing the extra and/or missing tablespaces, like so: - not ok 121 - There should be the correct tablespaces # Failed test 121: "There should be the correct tablespaces" # Extra tablespaces: # pg_default @@ -739,10 +735,10 @@ diagnostics listing the extra and/or missing tablespaces, like so: ); This function tests that all of the schemas in the database only the schemas -that *should* be there. In the event of a failure, you'll see diagnostics -listing the extra and/or missing schemas, like so: +that *should* be there, excluding system schemas and `information_schema`. In +the event of a failure, you'll see diagnostics listing the extra and/or +missing schemas, like so: - not ok 106 - There should be the correct schemas # Failed test 106: "There should be the correct schemas" # Extra schemas: # __howdy__ @@ -769,7 +765,6 @@ default description will be generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing tables, like so: - not ok 91 - Schema public should have the correct tables # Failed test 91: "Schema public should have the correct tables" # Extra tables: # mallots @@ -798,7 +793,6 @@ default description will be generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing views, like so: - not ok 92 - Schema public should have the correct views # Failed test 92: "Schema public should have the correct views" # Extra views: # v_userlog_tmp @@ -827,7 +821,6 @@ useful default description will be generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing sequences, like so: - not ok 93 - Schema public should have the correct sequences # Failed test 93: "Schema public should have the correct sequences" # These are extra sequences: # seq_mallots @@ -900,7 +893,6 @@ This function tests that all of the users in the database only the users that *should* be there. In the event of a failure, you'll see diagnostics listing the extra and/or missing users, like so: - not ok 195 - whatever # Failed test 195: "There should be the correct users" # Extra users: # root @@ -919,7 +911,6 @@ This function tests that all of the groups in the database only the groups that *should* be there. In the event of a failure, you'll see diagnostics listing the extra and/or missing groups, like so: - not ok 210 - There should be the correct groups # Failed test 210: "There should be the correct groups" # Extra groups: # meanies @@ -938,13 +929,38 @@ This function tests that all of the procedural languages in the database only the languages that *should* be there. In the event of a failure, you'll see diagnostics listing the extra and/or missing languages, like so: - not ok 225 - There should be the correct procedural languages # Failed test 225: "There should be the correct procedural languages" # Extra languages: # pllolcode # Missing languages: # plpgsql +### `opclasses_are( schema, opclasses[], description )` ### +### `opclasses_are( schema, opclasses[] )` ### +### `opclasses_are( opclasses[], description )` ### +### `opclasses_are( opclasses[] )` ### + + SELECT opclasses_are( + 'myschema', + ARRAY[ 'foo', 'bar', 'frobnitz' ], + 'Should have the correct opclasses in myschema' + ); + +This function tests that all of the operator classes in the named schema, or +that are visible in the search path, are only the opclasses that *should* be +there. If the `:schema` argument is omitted, opclasses will be sought in the +search path, excluding `pg_catalog.` If the description is omitted, a +generally useful default description will be generated. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing opclasses, like so: + + # Failed test 251: "Schema public should have the correct operator classes" + # Extra operator classes: + # goofy_ops + # Missing operator classes: + # custom_ops + To Have or Have Not ------------------- @@ -1416,6 +1432,36 @@ name, then the operator must be visible in the search path. If you omit the test description, pgTAP will generate a reasonable one for you. The return value is also optional. +### `has_opclass( schema, name, description )` ### +### `has_opclass( schema, name )` ### +### `has_opclass( name, description )` ### +### `has_opclass( name )` ### + + SELECT has_opclass( + 'myschema', + 'my_ops', + 'We should have the "my_ops" operator class' + ); + +Tests for the presence of an operator class. If you omit the schema name, then +the operator must be visible in the search path. If you omit the test +description, pgTAP will generate a reasonable one for you. The return value is +also optional. + +### `hasnt_opclass( schema, name, description )` ### +### `hasnt_opclass( schema, name )` ### +### `hasnt_opclass( name, description )` ### +### `hasnt_opclass( name )` ### + + SELECT hasnt_opclass( + 'myschema', + 'your_ops', + 'We should not have the "your_ops" operator class' + ); + +This function is the inverse of `has_opclass()`. The test passes if the +specified operator class does *not* exist. + ### `has_role( role, desc )` ### ### `has_role( role )` ### @@ -1578,7 +1624,6 @@ Tests whether or not a column has a default value. Fails if the column doesn't have a default value. It will also fail if the column doesn't exist, and emit useful diagnostics to let you know: - not ok 136 - desc # Failed test 136: "desc" # Column public.sometab.__asdfasdfs__ does not exist @@ -2225,7 +2270,6 @@ description is omitted, it will default to "Should have members of group `:group`." On failure, `is_member_of()` will output diagnostics listing the missing users, like so: - not ok 370 - Should have members of group meanies # Failed test 370: "Should have members of group meanies" # Users missing from the meanies group: # theory @@ -2759,7 +2803,6 @@ descriptions for both tests. If the test had failed, it would output a nice diagnostics. Internally it just uses `is()` to compare the strings: - not ok 43 - lc_eq() test should have the proper description # Failed test 43: "lc_eq() test should have the proper description" # have: 'this is this' # want: 'this is THIS' @@ -2791,7 +2834,6 @@ This of course triggers a third test to run. The output will look like so: And of course, it the diagnostic test fails, it will output diagnostics just like a description failure would, something like this: - not ok 46 - lc_eq() failing test should have the proper diagnostics # Failed test 46: "lc_eq() failing test should have the proper diagnostics" # have: Have: this # Want: that @@ -2866,9 +2908,7 @@ To Do ----- * Useful schema testing functions to consider adding: - * `has_operator_class()` * `has_rule()` - * `has_pl()` * `function_returns()` * `function_lang_is()` * `sequence_has_range()` diff --git a/expected/aretap.out b/expected/aretap.out index 9b90a78d3c24..d058e6d640a4 100644 --- a/expected/aretap.out +++ b/expected/aretap.out @@ -1,5 +1,5 @@ \unset ECHO -1..224 +1..250 ok 1 - tablespaces_are(schemas, desc) should pass ok 2 - tablespaces_are(schemas, desc) should have the proper description ok 3 - tablespaces_are(schemas, desc) should have the proper diagnostics @@ -224,3 +224,29 @@ ok 221 - languages_are(languages, desc) extras should have the proper diagnostic ok 222 - languages_are(languages, desc) missing and extras should fail ok 223 - languages_are(languages, desc) missing and extras should have the proper description ok 224 - languages_are(languages, desc) missing and extras should have the proper diagnostics +ok 225 - opclasses_are(schema, opclasses, desc) should pass +ok 226 - opclasses_are(schema, opclasses, desc) should have the proper description +ok 227 - opclasses_are(schema, opclasses, desc) should have the proper diagnostics +ok 228 - opclasses_are(schema, opclasses) should pass +ok 229 - opclasses_are(schema, opclasses) should have the proper description +ok 230 - opclasses_are(schema, opclasses, desc) + missing should fail +ok 231 - opclasses_are(schema, opclasses, desc) + missing should have the proper description +ok 232 - opclasses_are(schema, opclasses, desc) + missing should have the proper diagnostics +ok 233 - opclasses_are(schema, opclasses, desc) + extra should fail +ok 234 - opclasses_are(schema, opclasses, desc) + extra should have the proper description +ok 235 - opclasses_are(schema, opclasses, desc) + extra should have the proper diagnostics +ok 236 - opclasses_are(schema, opclasses, desc) + extra & missing should fail +ok 237 - opclasses_are(schema, opclasses, desc) + extra & missing should have the proper description +ok 238 - opclasses_are(schema, opclasses, desc) + extra & missing should have the proper diagnostics +ok 239 - opclasses_are(opclasses) should pass +ok 240 - opclasses_are(opclasses) should have the proper description +ok 241 - opclasses_are(opclasses) should have the proper diagnostics +ok 242 - opclasses_are(opclasses, desc) + missing should fail +ok 243 - opclasses_are(opclasses, desc) + missing should have the proper description +ok 244 - opclasses_are(opclasses, desc) + missing should have the proper diagnostics +ok 245 - opclasses_are(opclasses, desc) + extra should fail +ok 246 - opclasses_are(opclasses, desc) + extra should have the proper description +ok 247 - opclasses_are(opclasses, desc) + extra should have the proper diagnostics +ok 248 - opclasses_are(opclasses, desc) + extra & missing should fail +ok 249 - opclasses_are(opclasses, desc) + extra & missing should have the proper description +ok 250 - opclasses_are(opclasses, desc) + extra & missing should have the proper diagnostics diff --git a/expected/hastap.out b/expected/hastap.out index 054eb096e975..f81f2873568b 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..522 +1..558 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -522,3 +522,39 @@ ok 519 - language_is_trusted(language, desc) fail should have the proper diagnos ok 520 - language_is_trusted(language, desc) non-existent should fail ok 521 - language_is_trusted(language, desc) non-existent should have the proper description ok 522 - language_is_trusted(language, desc) non-existent should have the proper diagnostics +ok 523 - has_opclass( schema, name, desc ) should pass +ok 524 - has_opclass( schema, name, desc ) should have the proper description +ok 525 - has_opclass( schema, name, desc ) should have the proper diagnostics +ok 526 - has_opclass( schema, name ) should pass +ok 527 - has_opclass( schema, name ) should have the proper description +ok 528 - has_opclass( schema, name ) should have the proper diagnostics +ok 529 - has_opclass( name, desc ) should pass +ok 530 - has_opclass( name, desc ) should have the proper description +ok 531 - has_opclass( name, desc ) should have the proper diagnostics +ok 532 - has_opclass( name ) should pass +ok 533 - has_opclass( name ) should have the proper description +ok 534 - has_opclass( name ) should have the proper diagnostics +ok 535 - has_opclass( schema, name, desc ) fail should fail +ok 536 - has_opclass( schema, name, desc ) fail should have the proper description +ok 537 - has_opclass( schema, name, desc ) fail should have the proper diagnostics +ok 538 - has_opclass( name, desc ) fail should fail +ok 539 - has_opclass( name, desc ) fail should have the proper description +ok 540 - has_opclass( name, desc ) fail should have the proper diagnostics +ok 541 - hasnt_opclass( schema, name, desc ) should fail +ok 542 - hasnt_opclass( schema, name, desc ) should have the proper description +ok 543 - hasnt_opclass( schema, name, desc ) should have the proper diagnostics +ok 544 - hasnt_opclass( schema, name ) should fail +ok 545 - hasnt_opclass( schema, name ) should have the proper description +ok 546 - hasnt_opclass( schema, name ) should have the proper diagnostics +ok 547 - hasnt_opclass( name, desc ) should fail +ok 548 - hasnt_opclass( name, desc ) should have the proper description +ok 549 - hasnt_opclass( name, desc ) should have the proper diagnostics +ok 550 - hasnt_opclass( name ) should fail +ok 551 - hasnt_opclass( name ) should have the proper description +ok 552 - hasnt_opclass( name ) should have the proper diagnostics +ok 553 - hasnt_opclass( schema, name, desc ) fail should pass +ok 554 - hasnt_opclass( schema, name, desc ) fail should have the proper description +ok 555 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnostics +ok 556 - hasnt_opclass( name, desc ) fail should pass +ok 557 - hasnt_opclass( name, desc ) fail should have the proper description +ok 558 - hasnt_opclass( name, desc ) fail should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 03adb3a2b530..feca1f85b37f 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3069,7 +3069,7 @@ 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, desc ) +-- enum_has_labels( schema, enum, labels, description ) CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME, NAME[], TEXT ) RETURNS TEXT AS $$ SELECT is( @@ -3098,7 +3098,7 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; --- enum_has_labels( enum, labels, desc ) +-- enum_has_labels( enum, labels, description ) CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ SELECT is( @@ -3135,7 +3135,7 @@ RETURNS BOOLEAN AS $$ ); $$ LANGUAGE sql STRICT; --- has_role( role, desc ) +-- has_role( role, description ) CREATE OR REPLACE FUNCTION has_role( NAME, TEXT ) RETURNS TEXT AS $$ SELECT ok( _has_role($1), $2 ); @@ -3147,7 +3147,7 @@ RETURNS TEXT AS $$ SELECT ok( _has_role($1), 'Role ' || quote_ident($1) || ' should exist' ); $$ LANGUAGE sql; --- hasnt_role( role, desc ) +-- hasnt_role( role, description ) CREATE OR REPLACE FUNCTION hasnt_role( NAME, TEXT ) RETURNS TEXT AS $$ SELECT ok( NOT _has_role($1), $2 ); @@ -3166,7 +3166,7 @@ RETURNS BOOLEAN AS $$ WHERE usename = $1 $$ LANGUAGE sql STRICT; --- has_user( user, desc ) +-- has_user( user, description ) CREATE OR REPLACE FUNCTION has_user( NAME, TEXT ) RETURNS TEXT AS $$ SELECT ok( _is_super($1) IS NOT NULL, $2 ); @@ -3178,7 +3178,7 @@ RETURNS TEXT AS $$ SELECT ok( _is_super( $1 ) IS NOT NULL, 'User ' || quote_ident($1) || ' should exist'); $$ LANGUAGE sql; --- hasnt_user( user, desc ) +-- hasnt_user( user, description ) CREATE OR REPLACE FUNCTION hasnt_user( NAME, TEXT ) RETURNS TEXT AS $$ SELECT ok( _is_super($1) IS NULL, $2 ); @@ -3190,7 +3190,7 @@ RETURNS TEXT AS $$ SELECT ok( _is_super( $1 ) IS NULL, 'User ' || quote_ident($1) || ' should not exist'); $$ LANGUAGE sql; --- is_superuser( user, desc ) +-- is_superuser( user, description ) CREATE OR REPLACE FUNCTION is_superuser( NAME, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -3209,7 +3209,7 @@ RETURNS TEXT AS $$ SELECT is_superuser( $1, 'User ' || quote_ident($1) || ' should be a super user' ); $$ LANGUAGE sql; --- isnt_superuser( user, desc ) +-- isnt_superuser( user, description ) CREATE OR REPLACE FUNCTION isnt_superuser( NAME, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -3237,7 +3237,7 @@ RETURNS BOOLEAN AS $$ ); $$ LANGUAGE sql STRICT; --- has_group( group, desc ) +-- has_group( group, description ) CREATE OR REPLACE FUNCTION has_group( NAME, TEXT ) RETURNS TEXT AS $$ SELECT ok( _has_group($1), $2 ); @@ -3249,7 +3249,7 @@ RETURNS TEXT AS $$ SELECT ok( _has_group($1), 'Group ' || quote_ident($1) || ' should exist' ); $$ LANGUAGE sql; --- hasnt_group( group, desc ) +-- hasnt_group( group, description ) CREATE OR REPLACE FUNCTION hasnt_group( NAME, TEXT ) RETURNS TEXT AS $$ SELECT ok( NOT _has_group($1), $2 ); @@ -3266,7 +3266,7 @@ RETURNS oid[] AS $$ SELECT grolist FROM pg_catalog.pg_group WHERE groname = $1; $$ LANGUAGE sql; --- is_member_of( group, user[], desc ) +-- is_member_of( group, user[], description ) CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ DECLARE @@ -3296,7 +3296,7 @@ BEGIN END; $$ LANGUAGE plpgsql; --- is_member_of( group, user, desc ) +-- is_member_of( group, user, description ) CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME, TEXT ) RETURNS TEXT AS $$ SELECT is_member_of( $1, ARRAY[$2], $3 ); @@ -3465,7 +3465,7 @@ RETURNS "char" AS $$ AND pg_catalog.format_type(casttarget, NULL) = $2 $$ LANGUAGE SQL; --- cast_context_is( source_type, target_type, context, desc ) +-- cast_context_is( source_type, target_type, context, description ) CREATE OR REPLACE FUNCTION cast_context_is( NAME, NAME, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -3539,7 +3539,7 @@ RETURNS BOOLEAN AS $$ ); $$ LANGUAGE SQL; --- has_operator( left_type, schema, name, right_type, return_type, desc ) +-- 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 ); @@ -3558,7 +3558,7 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; --- has_operator( left_type, name, right_type, return_type, desc ) +-- 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 ); @@ -3577,7 +3577,7 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; --- has_operator( left_type, name, right_type, desc ) +-- 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 ); @@ -3596,7 +3596,7 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; --- has_leftop( schema, name, right_type, return_type, desc ) +-- 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 ); @@ -3614,7 +3614,7 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; --- has_leftop( name, right_type, return_type, desc ) +-- 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 ); @@ -3631,7 +3631,7 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; --- has_leftop( name, right_type, desc ) +-- 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 ); @@ -3646,7 +3646,7 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; --- has_rightop( left_type, schema, name, return_type, desc ) +-- 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 ); @@ -3664,7 +3664,7 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; --- has_rightop( left_type, name, return_type, desc ) +-- 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 ); @@ -3681,7 +3681,7 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; --- has_rightop( left_type, name, desc ) +-- 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 ); @@ -4219,6 +4219,133 @@ 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 = COALESCE($1, n.nspname) + AND oc.opcname = $2 + ); +$$ 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( NULL, $1 ), $2) +$$ LANGUAGE SQL; + +-- has_opclass( name ) +CREATE OR REPLACE FUNCTION has_opclass( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _opc_exists( NULL, $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 exist' ); +$$ LANGUAGE SQL; + +-- hasnt_opclass( name, description ) +CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _opc_exists( NULL, $1 ), $2) +$$ LANGUAGE SQL; + +-- hasnt_opclass( name ) +CREATE OR REPLACE FUNCTION hasnt_opclass( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _opc_exists( NULL, $1 ), 'Operator class ' || quote_ident($1) || ' should 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 + WHERE n.nspname <> 'pg_catalog' + 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 + WHERE n.nspname <> 'pg_catalog' + 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; + -- 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 $$ diff --git a/sql/aretap.sql b/sql/aretap.sql index e1fd5db0503d..9621c5101b3d 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(224); +SELECT plan(250); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -31,6 +31,27 @@ CREATE SCHEMA someschema; CREATE FUNCTION someschema.yip() returns boolean as 'SELECT TRUE' language SQL; CREATE FUNCTION someschema.yap() returns boolean as 'SELECT TRUE' language SQL; +CREATE DOMAIN goofy AS text CHECK ( TRUE ); +CREATE OR REPLACE FUNCTION goofy_cmp(goofy,goofy) +RETURNS INTEGER AS $$ + SELECT CASE WHEN $1 = $2 THEN 0 + WHEN $1 > $2 THEN 1 + ELSE -1 + END; +$$ LANGUAGE SQL IMMUTABLE STRICT; + +CREATE OR REPLACE FUNCTION goofy_eq (goofy, goofy) RETURNS boolean AS $$ + SELECT $1 = $2; +$$ LANGUAGE SQL IMMUTABLE STRICT; + +CREATE OPERATOR = ( PROCEDURE = goofy_eq, LEFTARG = goofy, RIGHTARG = goofy); + +CREATE OPERATOR CLASS goofy_ops +DEFAULT FOR TYPE goofy USING BTREE AS + OPERATOR 1 =, + FUNCTION 1 goofy_cmp(goofy,goofy) +; + RESET client_min_messages; /****************************************************************************/ @@ -795,6 +816,103 @@ SELECT * FROM check_test( plomgwtf' ); +/****************************************************************************/ +-- Test opclasses_are(). + +SELECT * FROM check_test( + opclasses_are( 'public', ARRAY['goofy_ops'], 'whatever' ), + true, + 'opclasses_are(schema, opclasses, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + opclasses_are( 'public', ARRAY['goofy_ops'] ), + true, + 'opclasses_are(schema, opclasses)', + 'Schema public should have the correct operator classes' + '' +); + +SELECT * FROM check_test( + opclasses_are( 'public', ARRAY['goofy_ops', 'yops'], 'whatever' ), + false, + 'opclasses_are(schema, opclasses, desc) + missing', + 'whatever', + ' Missing operator classes: + yops' +); + +SELECT * FROM check_test( + opclasses_are( 'public', '{}'::name[], 'whatever' ), + false, + 'opclasses_are(schema, opclasses, desc) + extra', + 'whatever', + ' Extra operator classes: + goofy_ops' +); + +SELECT * FROM check_test( + opclasses_are( 'public', ARRAY['yops'], 'whatever' ), + false, + 'opclasses_are(schema, opclasses, desc) + extra & missing', + 'whatever', + ' Extra operator classes: + goofy_ops + Missing operator classes: + yops' +); + +CREATE FUNCTION ___myopc(ex text) RETURNS NAME[] AS $$ + SELECT ARRAY( + SELECT oc.opcname + FROM pg_catalog.pg_opclass oc + JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid + WHERE n.nspname <> 'pg_catalog' + AND oc.opcname <> $1 + AND pg_catalog.pg_opclass_is_visible(oc.oid) + ); +$$ LANGUAGE SQL; + + +SELECT * FROM check_test( + opclasses_are( ___myopc('') ), + true, + 'opclasses_are(opclasses)', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct operator classes', + '' +); + +SELECT * FROM check_test( + opclasses_are( array_append(___myopc(''), 'sillyops'), 'whatever' ), + false, + 'opclasses_are(opclasses, desc) + missing', + 'whatever', + ' Missing operator classes: + sillyops' +); + +SELECT * FROM check_test( + opclasses_are( ___myopc('goofy_ops'), 'whatever' ), + false, + 'opclasses_are(opclasses, desc) + extra', + 'whatever', + ' Extra operator classes: + goofy_ops' +); + +SELECT * FROM check_test( + opclasses_are( array_append(___myopc('goofy_ops'), 'sillyops'), 'whatever' ), + false, + 'opclasses_are(opclasses, desc) + extra & missing', + 'whatever', + ' Extra operator classes: + goofy_ops + Missing operator classes: + sillyops' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/sql/hastap.sql b/sql/hastap.sql index 892597247c93..2397aeaa0978 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(522); +SELECT plan(558); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -1461,6 +1461,105 @@ SELECT * FROM check_test( ' Procedural language plomgwtf does not exist' ); +/****************************************************************************/ +-- Test has_opclass() and hasnt_opclass(). + +SELECT * FROM check_test( + has_opclass( 'pg_catalog', 'int4_ops', 'whatever' ), + true, + 'has_opclass( schema, name, desc )', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_opclass( 'pg_catalog', 'int4_ops'::name ), + true, + 'has_opclass( schema, name )', + 'Operator class pg_catalog.int4_ops should exist', + '' +); + +SELECT * FROM check_test( + has_opclass( 'int4_ops', 'whatever' ), + true, + 'has_opclass( name, desc )', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_opclass( 'int4_ops' ), + true, + 'has_opclass( name )', + 'Operator class int4_ops should exist', + '' +); + +SELECT * FROM check_test( + has_opclass( 'pg_catalog', 'int4_opss', 'whatever' ), + false, + 'has_opclass( schema, name, desc ) fail', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_opclass( 'int4_opss', 'whatever' ), + false, + 'has_opclass( name, desc ) fail', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_opclass( 'pg_catalog', 'int4_ops', 'whatever' ), + false, + 'hasnt_opclass( schema, name, desc )', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_opclass( 'pg_catalog', 'int4_ops'::name ), + false, + 'hasnt_opclass( schema, name )', + 'Operator class pg_catalog.int4_ops should exist', + '' +); + +SELECT * FROM check_test( + hasnt_opclass( 'int4_ops', 'whatever' ), + false, + 'hasnt_opclass( name, desc )', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_opclass( 'int4_ops' ), + false, + 'hasnt_opclass( name )', + 'Operator class int4_ops should exist', + '' +); + +SELECT * FROM check_test( + hasnt_opclass( 'pg_catalog', 'int4_opss', 'whatever' ), + true, + 'hasnt_opclass( schema, name, desc ) fail', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_opclass( 'int4_opss', 'whatever' ), + true, + 'hasnt_opclass( name, desc ) fail', + 'whatever', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 9c26b73eb947146ebe62e2d5918b5b41397ddee7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 24 May 2009 16:39:00 -0700 Subject: [PATCH 0358/1195] Added rule testing assertion functions. * Added `rules_are()`. * Added `has_rule()` and `hasnt_rule()`. * Added note that `indexes_are()` checks for indexes on tables visible in the search path if the `:schema` argument is omitted. * Fixed a typo in the docs that referred to `has_column()` when it meant `has_index()`. --- Changes | 2 +- README.pgtap | 66 ++++++++++++++++++-- expected/aretap.out | 32 +++++++++- expected/hastap.out | 38 ++++++++++- pgtap.sql.in | 149 ++++++++++++++++++++++++++++++++++++++++++++ sql/aretap.sql | 111 ++++++++++++++++++++++++++++++--- sql/hastap.sql | 104 ++++++++++++++++++++++++++++++- 7 files changed, 485 insertions(+), 17 deletions(-) diff --git a/Changes b/Changes index a44b3617f047..74e4c7fb8346 100644 --- a/Changes +++ b/Changes @@ -13,7 +13,7 @@ Revision history for pgTAP * Added `has_opclass()` and `hasnt_opclass()`. * Added `tablespaces_are()`, `schemas_are()`, `tables_are()`, `views_are()`, `sequences_are()`, `functions_are()`, `indexes_are()`, `users_are()`, - `groups_are()`, `opclasses_are()`, and `languages_are()`. + `groups_are()`, `opclasses_are()`, `languages_are()`, and `rules_are()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index 201475ed4b9e..c11dacf941d8 100644 --- a/README.pgtap +++ b/README.pgtap @@ -842,9 +842,10 @@ missing sequences, like so: ); This function tests that all of the indexes on the named table are only the -indexes that *should* be on that table. Iftable.If the `:schema` argument is omitted, -the table must be visible in the search path. If the description is omitted, a -generally useful default description will be generated. +indexes that *should* be on that table. If the `:schema` argument is omitted, +the table must be visible in the search path, excluding `pg_catalog`. If the +description is omitted, a generally useful default description will be +generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing indexes, like so: @@ -949,7 +950,7 @@ diagnostics listing the extra and/or missing languages, like so: This function tests that all of the operator classes in the named schema, or that are visible in the search path, are only the opclasses that *should* be there. If the `:schema` argument is omitted, opclasses will be sought in the -search path, excluding `pg_catalog.` If the description is omitted, a +search path, excluding `pg_catalog`. If the description is omitted, a generally useful default description will be generated. In the event of a failure, you'll see diagnostics listing the extra and/or @@ -961,6 +962,33 @@ missing opclasses, like so: # Missing operator classes: # custom_ops +### `rules_are( schema, table, rules[], description )` ### +### `rules_are( schema, table, rules[] )` ### +### `rules_are( table, rules[], description )` ### +### `rules_are( table, rules[] )` ### + + SELECT rules_are( + 'myschema', + 'atable', + ARRAY[ 'on_insert', 'on_update', 'on_delete' ], + 'Should have the correct rules on myschema.atable' + ); + +This function tests that all of the rules on the named relation are only the +rules that *should* be on that relation (a table or a view). If the `:schema` +argument is omitted, the relation must be visible in the search path, +excluding `pg_catalog`. If the description is omitted, a generally useful +default description will be generated. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing rules, like so: + + # Failed test 281: "Relation public.users should have the correct rules" + # Extra rules: + # on_select + # Missing rules: + # on_delete + To Have or Have Not ------------------- @@ -1234,7 +1262,7 @@ index name to the `NAME` type: SELECT has_index( 'public', 'sometab', 'idx_foo', 'name'::name ); -If the index does not exist, `has_column()` will output a diagnostic message +If the index does not exist, `has_index()` will output a diagnostic message such as: # Index "blah" ON public.sometab not found @@ -1262,6 +1290,34 @@ Tests to see if the specified table has the named trigger. The `:description` is optional, and if the schema is omitted, the table with which the trigger is associated must be visible. +### `has_rule( schema, table, rule, description )` ### +### `has_rule( schema, table, rule )` ### +### `has_rule( table, rule, description )` ### +### `has_rule( table, rule )` ### + + SELECT has_rule( + 'myschema', + 'sometable', + 'somerule, + 'Trigger "somerule" should exist' + ); + + SELECT has_rule( 'sometable', 'somerule' ); + +Tests to see if the specified table has the named rule. The `:description` is +optional, and if the schema is omitted, the table with which the trigger is +associated must be visible in the search path. + +### `hasnt_rule( schema, table, rule, description )` ### +### `hasnt_rule( schema, table, rule )` ### +### `hasnt_rule( table, rule, description )` ### +### `hasnt_rule( table, rule )` ### + + SELECT hasnt_rule( 'sometable', 'somerule' ); + +This function is the inverse of `has_rule()`. The test passes if the specified +rule does *not* exist. + ### `can( schema, functions[], description )` ### ### `can( schema, functions[] )` ### ### `can( functions[], description )` ### diff --git a/expected/aretap.out b/expected/aretap.out index d058e6d640a4..e8ab27a97ec6 100644 --- a/expected/aretap.out +++ b/expected/aretap.out @@ -1,5 +1,5 @@ \unset ECHO -1..250 +1..280 ok 1 - tablespaces_are(schemas, desc) should pass ok 2 - tablespaces_are(schemas, desc) should have the proper description ok 3 - tablespaces_are(schemas, desc) should have the proper diagnostics @@ -250,3 +250,33 @@ ok 247 - opclasses_are(opclasses, desc) + extra should have the proper diagnosti ok 248 - opclasses_are(opclasses, desc) + extra & missing should fail ok 249 - opclasses_are(opclasses, desc) + extra & missing should have the proper description ok 250 - opclasses_are(opclasses, desc) + extra & missing should have the proper diagnostics +ok 251 - rules_are(schema, table, rules, desc) should pass +ok 252 - rules_are(schema, table, rules, desc) should have the proper description +ok 253 - rules_are(schema, table, rules, desc) should have the proper diagnostics +ok 254 - rules_are(schema, table, rules) should pass +ok 255 - rules_are(schema, table, rules) should have the proper description +ok 256 - rules_are(schema, table, rules) should have the proper diagnostics +ok 257 - rules_are(schema, table, rules) + extra should fail +ok 258 - rules_are(schema, table, rules) + extra should have the proper description +ok 259 - rules_are(schema, table, rules) + extra should have the proper diagnostics +ok 260 - rules_are(schema, table, rules) + missing should fail +ok 261 - rules_are(schema, table, rules) + missing should have the proper description +ok 262 - rules_are(schema, table, rules) + missing should have the proper diagnostics +ok 263 - rules_are(schema, table, rules) + extra & missing should fail +ok 264 - rules_are(schema, table, rules) + extra & missing should have the proper description +ok 265 - rules_are(schema, table, rules) + extra & missing should have the proper diagnostics +ok 266 - rules_are(table, rules, desc) should pass +ok 267 - rules_are(table, rules, desc) should have the proper description +ok 268 - rules_are(table, rules, desc) should have the proper diagnostics +ok 269 - rules_are(table, rules) should pass +ok 270 - rules_are(table, rules) should have the proper description +ok 271 - rules_are(table, rules) should have the proper diagnostics +ok 272 - rules_are(table, rules) + extra should fail +ok 273 - rules_are(table, rules) + extra should have the proper description +ok 274 - rules_are(table, rules) + extra should have the proper diagnostics +ok 275 - rules_are(table, rules) + missing should fail +ok 276 - rules_are(table, rules) + missing should have the proper description +ok 277 - rules_are(table, rules) + missing should have the proper diagnostics +ok 278 - rules_are(table, rules) + extra & missing should fail +ok 279 - rules_are(table, rules) + extra & missing should have the proper description +ok 280 - rules_are(table, rules) + extra & missing should have the proper diagnostics diff --git a/expected/hastap.out b/expected/hastap.out index f81f2873568b..ff0edc1afe3a 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..558 +1..594 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -558,3 +558,39 @@ ok 555 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnos ok 556 - hasnt_opclass( name, desc ) fail should pass ok 557 - hasnt_opclass( name, desc ) fail should have the proper description ok 558 - hasnt_opclass( name, desc ) fail should have the proper diagnostics +ok 559 - has_rule(schema, table, rule, desc) should pass +ok 560 - has_rule(schema, table, rule, desc) should have the proper description +ok 561 - has_rule(schema, table, rule, desc) should have the proper diagnostics +ok 562 - has_rule(schema, table, rule) should pass +ok 563 - has_rule(schema, table, rule) should have the proper description +ok 564 - has_rule(schema, table, rule) should have the proper diagnostics +ok 565 - has_rule(schema, table, rule, desc) fail should fail +ok 566 - has_rule(schema, table, rule, desc) fail should have the proper description +ok 567 - has_rule(schema, table, rule, desc) fail should have the proper diagnostics +ok 568 - has_rule(table, rule, desc) should pass +ok 569 - has_rule(table, rule, desc) should have the proper description +ok 570 - has_rule(table, rule, desc) should have the proper diagnostics +ok 571 - has_rule(table, rule) should pass +ok 572 - has_rule(table, rule) should have the proper description +ok 573 - has_rule(table, rule) should have the proper diagnostics +ok 574 - has_rule(table, rule, desc) fail should fail +ok 575 - has_rule(table, rule, desc) fail should have the proper description +ok 576 - has_rule(table, rule, desc) fail should have the proper diagnostics +ok 577 - hasnt_rule(schema, table, rule, desc) should fail +ok 578 - hasnt_rule(schema, table, rule, desc) should have the proper description +ok 579 - hasnt_rule(schema, table, rule, desc) should have the proper diagnostics +ok 580 - hasnt_rule(schema, table, rule) should fail +ok 581 - hasnt_rule(schema, table, rule) should have the proper description +ok 582 - hasnt_rule(schema, table, rule) should have the proper diagnostics +ok 583 - hasnt_rule(schema, table, rule, desc) fail should pass +ok 584 - hasnt_rule(schema, table, rule, desc) fail should have the proper description +ok 585 - hasnt_rule(schema, table, rule, desc) fail should have the proper diagnostics +ok 586 - hasnt_rule(table, rule, desc) should fail +ok 587 - hasnt_rule(table, rule, desc) should have the proper description +ok 588 - hasnt_rule(table, rule, desc) should have the proper diagnostics +ok 589 - hasnt_rule(table, rule) should fail +ok 590 - hasnt_rule(table, rule) should have the proper description +ok 591 - hasnt_rule(table, rule) should have the proper diagnostics +ok 592 - hasnt_rule(table, rule, desc) fail should pass +ok 593 - hasnt_rule(table, rule, desc) fail should have the proper description +ok 594 - hasnt_rule(table, rule, desc) fail should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index feca1f85b37f..844c69ec6054 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -4346,6 +4346,155 @@ 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 <> 'pg_catalog' + 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 + WHERE n.nspname <> 'pg_catalog' + 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_on( schema, table, rule, type, description ) + +-- rule_is_instead( schema, table, rule, description ) + -- 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 $$ diff --git a/sql/aretap.sql b/sql/aretap.sql index 9621c5101b3d..ae6de479d8d1 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(250); +SELECT plan(280); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -17,11 +17,14 @@ CREATE TABLE public.foo( id INT NOT NULL PRIMARY KEY ); +CREATE RULE ins_me AS ON INSERT TO public.fou DO NOTHING; +CREATE RULE upd_me AS ON UPDATE TO public.fou DO NOTHING; + CREATE INDEX idx_fou_id ON public.fou(id); CREATE INDEX idx_fou_name ON public.fou(name); -CREATE VIEW voo AS SELECT * FROM foo; -CREATE VIEW vou AS SELECT * FROM fou; +CREATE VIEW public.voo AS SELECT * FROM foo; +CREATE VIEW public.vou AS SELECT * FROM fou; CREATE SEQUENCE public.someseq; CREATE SEQUENCE public.sumeseq; @@ -31,8 +34,8 @@ CREATE SCHEMA someschema; CREATE FUNCTION someschema.yip() returns boolean as 'SELECT TRUE' language SQL; CREATE FUNCTION someschema.yap() returns boolean as 'SELECT TRUE' language SQL; -CREATE DOMAIN goofy AS text CHECK ( TRUE ); -CREATE OR REPLACE FUNCTION goofy_cmp(goofy,goofy) +CREATE DOMAIN public.goofy AS text CHECK ( TRUE ); +CREATE OR REPLACE FUNCTION public.goofy_cmp(goofy,goofy) RETURNS INTEGER AS $$ SELECT CASE WHEN $1 = $2 THEN 0 WHEN $1 > $2 THEN 1 @@ -40,13 +43,13 @@ RETURNS INTEGER AS $$ END; $$ LANGUAGE SQL IMMUTABLE STRICT; -CREATE OR REPLACE FUNCTION goofy_eq (goofy, goofy) RETURNS boolean AS $$ +CREATE OR REPLACE FUNCTION public.goofy_eq (goofy, goofy) RETURNS boolean AS $$ SELECT $1 = $2; $$ LANGUAGE SQL IMMUTABLE STRICT; -CREATE OPERATOR = ( PROCEDURE = goofy_eq, LEFTARG = goofy, RIGHTARG = goofy); +CREATE OPERATOR public.= ( PROCEDURE = goofy_eq, LEFTARG = goofy, RIGHTARG = goofy); -CREATE OPERATOR CLASS goofy_ops +CREATE OPERATOR CLASS public.goofy_ops DEFAULT FOR TYPE goofy USING BTREE AS OPERATOR 1 =, FUNCTION 1 goofy_cmp(goofy,goofy) @@ -913,6 +916,98 @@ SELECT * FROM check_test( sillyops' ); +/****************************************************************************/ +-- Test rules_are(). +SELECT * FROM check_test( + rules_are( 'public', 'fou', ARRAY['ins_me', 'upd_me'], 'whatever' ), + true, + 'rules_are(schema, table, rules, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rules_are( 'public', 'fou', ARRAY['ins_me', 'upd_me'] ), + true, + 'rules_are(schema, table, rules)', + 'Relation public.fou should have the correct rules', + '' +); + +SELECT * FROM check_test( + rules_are( 'public', 'fou', ARRAY['ins_me'] ), + false, + 'rules_are(schema, table, rules) + extra', + 'Relation public.fou should have the correct rules', + ' Extra rules: + upd_me' +); + +SELECT * FROM check_test( + rules_are( 'public', 'fou', ARRAY['ins_me', 'upd_me', 'del_me'] ), + false, + 'rules_are(schema, table, rules) + missing', + 'Relation public.fou should have the correct rules', + ' Missing rules: + del_me' +); + +SELECT * FROM check_test( + rules_are( 'public', 'fou', ARRAY['ins_me', 'del_me'] ), + false, + 'rules_are(schema, table, rules) + extra & missing', + 'Relation public.fou should have the correct rules', + ' Extra rules: + upd_me + Missing rules: + del_me' +); + +SELECT * FROM check_test( + rules_are( 'fou', ARRAY['ins_me', 'upd_me'], 'whatever' ), + true, + 'rules_are(table, rules, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rules_are( 'fou', ARRAY['ins_me', 'upd_me'] ), + true, + 'rules_are(table, rules)', + 'Relation fou should have the correct rules', + '' +); + +SELECT * FROM check_test( + rules_are( 'fou', ARRAY['ins_me'] ), + false, + 'rules_are(table, rules) + extra', + 'Relation fou should have the correct rules', + ' Extra rules: + upd_me' +); + +SELECT * FROM check_test( + rules_are( 'fou', ARRAY['ins_me', 'upd_me', 'del_me'] ), + false, + 'rules_are(table, rules) + missing', + 'Relation fou should have the correct rules', + ' Missing rules: + del_me' +); + +SELECT * FROM check_test( + rules_are( 'fou', ARRAY['ins_me', 'del_me'] ), + false, + 'rules_are(table, rules) + extra & missing', + 'Relation fou should have the correct rules', + ' Extra rules: + upd_me + Missing rules: + del_me' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/sql/hastap.sql b/sql/hastap.sql index 2397aeaa0978..35c56d38e568 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(558); +SELECT plan(594); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -12,6 +12,9 @@ CREATE TABLE public.sometab( numb NUMERIC(10, 2), myint NUMERIC(8) ); +CREATE RULE ins_me AS ON INSERT TO public.sometab DO NOTHING; +CREATE RULE upd_me AS ON UPDATE TO public.sometab DO NOTHING; + CREATE TYPE public.sometype AS ( id INT, name TEXT @@ -1560,6 +1563,105 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test has_rule() and hasnt_rule(). + +SELECT * FROM check_test( + has_rule( 'public', 'sometab', 'ins_me', 'whatever' ), + true, + 'has_rule(schema, table, rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_rule( 'public', 'sometab', 'ins_me'::name ), + true, + 'has_rule(schema, table, rule)', + 'Relation public.sometab should have rule ins_me', + '' +); + +SELECT * FROM check_test( + has_rule( 'public', 'sometab', 'del_me', 'whatever' ), + false, + 'has_rule(schema, table, rule, desc) fail', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_rule( 'sometab', 'ins_me', 'whatever' ), + true, + 'has_rule(table, rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_rule( 'sometab', 'ins_me'::name ), + true, + 'has_rule(table, rule)', + 'Relation sometab should have rule ins_me', + '' +); + +SELECT * FROM check_test( + has_rule( 'sometab', 'del_me', 'whatever' ), + false, + 'has_rule(table, rule, desc) fail', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_rule( 'public', 'sometab', 'ins_me', 'whatever' ), + false, + 'hasnt_rule(schema, table, rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_rule( 'public', 'sometab', 'ins_me'::name ), + false, + 'hasnt_rule(schema, table, rule)', + 'Relation public.sometab should not have rule ins_me', + '' +); + +SELECT * FROM check_test( + hasnt_rule( 'public', 'sometab', 'del_me', 'whatever' ), + true, + 'hasnt_rule(schema, table, rule, desc) fail', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_rule( 'sometab', 'ins_me', 'whatever' ), + false, + 'hasnt_rule(table, rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_rule( 'sometab', 'ins_me'::name ), + false, + 'hasnt_rule(table, rule)', + 'Relation sometab should not have rule ins_me', + '' +); + +SELECT * FROM check_test( + hasnt_rule( 'sometab', 'del_me', 'whatever' ), + true, + 'hasnt_rule(table, rule, desc) fail', + 'whatever', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 59b67a0e91e22ce66494eda103b9d404c43977fe Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 24 May 2009 17:31:51 -0700 Subject: [PATCH 0359/1195] Added `rule_is_instead()`. Also documented the addition of `has_rule()` and `hasnt_rule()` in `Changes`. Next tasks: * Add `rule_is_on()`. * Add `hasnt_trigger()` (and any other missing `hasnt_*()` functions). * Update `uninstall_pgtap.sql.in`. * Test on all supported versions. * Release! --- Changes | 1 + README.pgtap | 26 ++++++++++++- expected/hastap.out | 32 +++++++++++++++- pgtap.sql.in | 43 ++++++++++++++++++++- sql/hastap.sql | 91 ++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 187 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index 74e4c7fb8346..1ee57266f75d 100644 --- a/Changes +++ b/Changes @@ -11,6 +11,7 @@ Revision history for pgTAP * Added `performs_ok()`. * Added `has_language()`, `hasnt_language()`, and `language_is_trusted()`. * Added `has_opclass()` and `hasnt_opclass()`. +* Added `has_rule()`, `hasnt_rule()`, and `rule_is_instead()`. * Added `tablespaces_are()`, `schemas_are()`, `tables_are()`, `views_are()`, `sequences_are()`, `functions_are()`, `indexes_are()`, `users_are()`, `groups_are()`, `opclasses_are()`, `languages_are()`, and `rules_are()`. diff --git a/README.pgtap b/README.pgtap index c11dacf941d8..82e4f4a35aae 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2335,6 +2335,30 @@ If the group does not exist, the diagnostics will tell you that, instead. But you use `has_group()` to make sure the group exists before you check its members, don't you? Of course you do. +### `rule_is_instead( schema, table, rule, description )` ### +### `rule_is_instead( schema, table, rule )` ### +### `rule_is_instead( table, rule, description )` ### +### `rule_is_instead( table, rule )` ### + + SELECT rule_is_instead( + 'public', + 'users', + 'on_insert', + 'Rule "on_insert" should be on on relation public.users' + ); + +Checks whether a rule on the specified relation is an `INSTEAD` rule. See the +[`CREATE RULE` +Documentation](http://www.postgresql.org/docs/current/static/sql-createrule.html) +for details. If the `:schema` argument is omitted, the relation must be +visible in the search path. If the `:description` argument is omitted, an +appropriate description will be created. In the event that the test fails +because the rule in question does not actually exist, you will see an +appropriate diagnostic such as: + + # Failed test 625: "Rule on_insert on relation public.users should be an INSTEAD rule" + # Rule on_insert does not exist + No Test for the Wicked ====================== @@ -2964,7 +2988,7 @@ To Do ----- * Useful schema testing functions to consider adding: - * `has_rule()` + * `rule_is_on()` (INSERT, UPDATE, DELETE, or SELECT) * `function_returns()` * `function_lang_is()` * `sequence_has_range()` diff --git a/expected/hastap.out b/expected/hastap.out index ff0edc1afe3a..0405900f9f5c 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..594 +1..624 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -594,3 +594,33 @@ ok 591 - hasnt_rule(table, rule) should have the proper diagnostics ok 592 - hasnt_rule(table, rule, desc) fail should pass ok 593 - hasnt_rule(table, rule, desc) fail should have the proper description ok 594 - hasnt_rule(table, rule, desc) fail should have the proper diagnostics +ok 595 - rule_is_instead(schema, table, rule, desc) should pass +ok 596 - rule_is_instead(schema, table, rule, desc) should have the proper description +ok 597 - rule_is_instead(schema, table, rule, desc) should have the proper diagnostics +ok 598 - rule_is_instead(schema, table, rule) should pass +ok 599 - rule_is_instead(schema, table, rule) should have the proper description +ok 600 - rule_is_instead(schema, table, rule) should have the proper diagnostics +ok 601 - rule_is_instead(schema, table, nothing rule, desc) should fail +ok 602 - rule_is_instead(schema, table, nothing rule, desc) should have the proper description +ok 603 - rule_is_instead(schema, table, nothing rule, desc) should have the proper diagnostics +ok 604 - rule_is_instead(schema, table, also rule, desc) should fail +ok 605 - rule_is_instead(schema, table, also rule, desc) should have the proper description +ok 606 - rule_is_instead(schema, table, also rule, desc) should have the proper diagnostics +ok 607 - rule_is_instead(table, rule, desc) should pass +ok 608 - rule_is_instead(table, rule, desc) should have the proper description +ok 609 - rule_is_instead(table, rule, desc) should have the proper diagnostics +ok 610 - rule_is_instead(table, rule) should pass +ok 611 - rule_is_instead(table, rule) should have the proper description +ok 612 - rule_is_instead(table, rule) should have the proper diagnostics +ok 613 - rule_is_instead(table, nothing rule, desc) should fail +ok 614 - rule_is_instead(table, nothing rule, desc) should have the proper description +ok 615 - rule_is_instead(table, nothing rule, desc) should have the proper diagnostics +ok 616 - rule_is_instead(table, also rule, desc) should fail +ok 617 - rule_is_instead(table, also rule, desc) should have the proper description +ok 618 - rule_is_instead(table, also rule, desc) should have the proper diagnostics +ok 619 - rule_is_instead(schema, table, non-existent rule, desc) should fail +ok 620 - rule_is_instead(schema, table, non-existent rule, desc) should have the proper description +ok 621 - rule_is_instead(schema, table, non-existent rule, desc) should have the proper diagnostics +ok 622 - rule_is_instead(table, non-existent rule, desc) should fail +ok 623 - rule_is_instead(table, non-existent rule, desc) should have the proper description +ok 624 - rule_is_instead(table, non-existent rule, desc) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 844c69ec6054..e0980ebdc4d0 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -4491,9 +4491,48 @@ 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_on( schema, table, rule, type, description ) - -- 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; + +-- rule_is_on( schema, table, rule, type, description ) +-- rule_is_on( schema, table, rule, type ) +-- rule_is_on( table, rule, type, description ) +-- rule_is_on( table, rule, type ) -- check_test( test_output, pass, name, description, diag, match_diag ) CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT, BOOLEAN ) diff --git a/sql/hastap.sql b/sql/hastap.sql index 35c56d38e568..c8be98546471 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(594); +SELECT plan(624); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -13,7 +13,10 @@ CREATE TABLE public.sometab( myint NUMERIC(8) ); CREATE RULE ins_me AS ON INSERT TO public.sometab DO NOTHING; -CREATE RULE upd_me AS ON UPDATE TO public.sometab DO NOTHING; +CREATE RULE upd_me AS ON UPDATE TO public.sometab DO ALSO SELECT now(); + +CREATE TABLE public.toview ( id INT ); +CREATE RULE "_RETURN" AS ON SELECT TO public.toview DO INSTEAD SELECT 42 AS id; CREATE TYPE public.sometype AS ( id INT, @@ -1662,6 +1665,90 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test rule_is_instead(). + +SELECT * FROM check_test( + rule_is_instead( 'public', 'toview', '_RETURN', 'whatever' ), + true, + 'rule_is_instead(schema, table, rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_instead( 'public', 'toview', '_RETURN'::name ), + true, + 'rule_is_instead(schema, table, rule)', + 'Rule "_RETURN" on relation public.toview should be an INSTEAD rule', + '' +); + +SELECT * FROM check_test( + rule_is_instead( 'public', 'sometab', 'ins_me', 'whatever' ), + false, + 'rule_is_instead(schema, table, nothing rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_instead( 'public', 'sometab', 'upd_me', 'whatever' ), + false, + 'rule_is_instead(schema, table, also rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_instead( 'toview', '_RETURN', 'whatever' ), + true, + 'rule_is_instead(table, rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_instead( 'toview', '_RETURN'::name ), + true, + 'rule_is_instead(table, rule)', + 'Rule "_RETURN" on relation toview should be an INSTEAD rule', + '' +); + +SELECT * FROM check_test( + rule_is_instead( 'sometab', 'ins_me', 'whatever' ), + false, + 'rule_is_instead(table, nothing rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_instead( 'sometab', 'upd_me', 'whatever' ), + false, + 'rule_is_instead(table, also rule, desc)', + 'whatever', + '' +); + +-- Check failure diagnostics for non-existent rules. +SELECT * FROM check_test( + rule_is_instead( 'public', 'sometab', 'blah', 'whatever' ), + false, + 'rule_is_instead(schema, table, non-existent rule, desc)', + 'whatever', + ' Rule blah does not exist' +); + +SELECT * FROM check_test( + rule_is_instead( 'sometab', 'blah', 'whatever' ), + false, + 'rule_is_instead(table, non-existent rule, desc)', + 'whatever', + ' Rule blah does not exist' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 063e3477ce28f250ccf22525c929dcb281c0705f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 24 May 2009 17:33:52 -0700 Subject: [PATCH 0360/1195] Added notes for more functions to be added. --- README.pgtap | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.pgtap b/README.pgtap index 82e4f4a35aae..942f3981b95a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2995,6 +2995,10 @@ To Do * `sequence_increments_by()` * `sequence_starts_at()` * `sequence_cycles()` + * `hasnt_trigger()` + * `hasnt_enum()` + * `hasnt_index()` + * `hasnt_function()` Supported Versions ----------------- From 3267258d5365f03a19b916e690d840c9c2bddb34 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 24 May 2009 20:21:50 -0700 Subject: [PATCH 0361/1195] Moved rule tests to their own test script. --- expected/hastap.out | 68 +------------- expected/ruletap.out | 68 ++++++++++++++ sql/hastap.sql | 190 +-------------------------------------- sql/ruletap.sql | 209 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 279 insertions(+), 256 deletions(-) create mode 100644 expected/ruletap.out create mode 100644 sql/ruletap.sql diff --git a/expected/hastap.out b/expected/hastap.out index 0405900f9f5c..f81f2873568b 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..624 +1..558 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -558,69 +558,3 @@ ok 555 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnos ok 556 - hasnt_opclass( name, desc ) fail should pass ok 557 - hasnt_opclass( name, desc ) fail should have the proper description ok 558 - hasnt_opclass( name, desc ) fail should have the proper diagnostics -ok 559 - has_rule(schema, table, rule, desc) should pass -ok 560 - has_rule(schema, table, rule, desc) should have the proper description -ok 561 - has_rule(schema, table, rule, desc) should have the proper diagnostics -ok 562 - has_rule(schema, table, rule) should pass -ok 563 - has_rule(schema, table, rule) should have the proper description -ok 564 - has_rule(schema, table, rule) should have the proper diagnostics -ok 565 - has_rule(schema, table, rule, desc) fail should fail -ok 566 - has_rule(schema, table, rule, desc) fail should have the proper description -ok 567 - has_rule(schema, table, rule, desc) fail should have the proper diagnostics -ok 568 - has_rule(table, rule, desc) should pass -ok 569 - has_rule(table, rule, desc) should have the proper description -ok 570 - has_rule(table, rule, desc) should have the proper diagnostics -ok 571 - has_rule(table, rule) should pass -ok 572 - has_rule(table, rule) should have the proper description -ok 573 - has_rule(table, rule) should have the proper diagnostics -ok 574 - has_rule(table, rule, desc) fail should fail -ok 575 - has_rule(table, rule, desc) fail should have the proper description -ok 576 - has_rule(table, rule, desc) fail should have the proper diagnostics -ok 577 - hasnt_rule(schema, table, rule, desc) should fail -ok 578 - hasnt_rule(schema, table, rule, desc) should have the proper description -ok 579 - hasnt_rule(schema, table, rule, desc) should have the proper diagnostics -ok 580 - hasnt_rule(schema, table, rule) should fail -ok 581 - hasnt_rule(schema, table, rule) should have the proper description -ok 582 - hasnt_rule(schema, table, rule) should have the proper diagnostics -ok 583 - hasnt_rule(schema, table, rule, desc) fail should pass -ok 584 - hasnt_rule(schema, table, rule, desc) fail should have the proper description -ok 585 - hasnt_rule(schema, table, rule, desc) fail should have the proper diagnostics -ok 586 - hasnt_rule(table, rule, desc) should fail -ok 587 - hasnt_rule(table, rule, desc) should have the proper description -ok 588 - hasnt_rule(table, rule, desc) should have the proper diagnostics -ok 589 - hasnt_rule(table, rule) should fail -ok 590 - hasnt_rule(table, rule) should have the proper description -ok 591 - hasnt_rule(table, rule) should have the proper diagnostics -ok 592 - hasnt_rule(table, rule, desc) fail should pass -ok 593 - hasnt_rule(table, rule, desc) fail should have the proper description -ok 594 - hasnt_rule(table, rule, desc) fail should have the proper diagnostics -ok 595 - rule_is_instead(schema, table, rule, desc) should pass -ok 596 - rule_is_instead(schema, table, rule, desc) should have the proper description -ok 597 - rule_is_instead(schema, table, rule, desc) should have the proper diagnostics -ok 598 - rule_is_instead(schema, table, rule) should pass -ok 599 - rule_is_instead(schema, table, rule) should have the proper description -ok 600 - rule_is_instead(schema, table, rule) should have the proper diagnostics -ok 601 - rule_is_instead(schema, table, nothing rule, desc) should fail -ok 602 - rule_is_instead(schema, table, nothing rule, desc) should have the proper description -ok 603 - rule_is_instead(schema, table, nothing rule, desc) should have the proper diagnostics -ok 604 - rule_is_instead(schema, table, also rule, desc) should fail -ok 605 - rule_is_instead(schema, table, also rule, desc) should have the proper description -ok 606 - rule_is_instead(schema, table, also rule, desc) should have the proper diagnostics -ok 607 - rule_is_instead(table, rule, desc) should pass -ok 608 - rule_is_instead(table, rule, desc) should have the proper description -ok 609 - rule_is_instead(table, rule, desc) should have the proper diagnostics -ok 610 - rule_is_instead(table, rule) should pass -ok 611 - rule_is_instead(table, rule) should have the proper description -ok 612 - rule_is_instead(table, rule) should have the proper diagnostics -ok 613 - rule_is_instead(table, nothing rule, desc) should fail -ok 614 - rule_is_instead(table, nothing rule, desc) should have the proper description -ok 615 - rule_is_instead(table, nothing rule, desc) should have the proper diagnostics -ok 616 - rule_is_instead(table, also rule, desc) should fail -ok 617 - rule_is_instead(table, also rule, desc) should have the proper description -ok 618 - rule_is_instead(table, also rule, desc) should have the proper diagnostics -ok 619 - rule_is_instead(schema, table, non-existent rule, desc) should fail -ok 620 - rule_is_instead(schema, table, non-existent rule, desc) should have the proper description -ok 621 - rule_is_instead(schema, table, non-existent rule, desc) should have the proper diagnostics -ok 622 - rule_is_instead(table, non-existent rule, desc) should fail -ok 623 - rule_is_instead(table, non-existent rule, desc) should have the proper description -ok 624 - rule_is_instead(table, non-existent rule, desc) should have the proper diagnostics diff --git a/expected/ruletap.out b/expected/ruletap.out new file mode 100644 index 000000000000..70dabcccca6b --- /dev/null +++ b/expected/ruletap.out @@ -0,0 +1,68 @@ +\unset ECHO +1..66 +ok 1 - has_rule(schema, table, rule, desc) should pass +ok 2 - has_rule(schema, table, rule, desc) should have the proper description +ok 3 - has_rule(schema, table, rule, desc) should have the proper diagnostics +ok 4 - has_rule(schema, table, rule) should pass +ok 5 - has_rule(schema, table, rule) should have the proper description +ok 6 - has_rule(schema, table, rule) should have the proper diagnostics +ok 7 - has_rule(schema, table, rule, desc) fail should fail +ok 8 - has_rule(schema, table, rule, desc) fail should have the proper description +ok 9 - has_rule(schema, table, rule, desc) fail should have the proper diagnostics +ok 10 - has_rule(table, rule, desc) should pass +ok 11 - has_rule(table, rule, desc) should have the proper description +ok 12 - has_rule(table, rule, desc) should have the proper diagnostics +ok 13 - has_rule(table, rule) should pass +ok 14 - has_rule(table, rule) should have the proper description +ok 15 - has_rule(table, rule) should have the proper diagnostics +ok 16 - has_rule(table, rule, desc) fail should fail +ok 17 - has_rule(table, rule, desc) fail should have the proper description +ok 18 - has_rule(table, rule, desc) fail should have the proper diagnostics +ok 19 - hasnt_rule(schema, table, rule, desc) should fail +ok 20 - hasnt_rule(schema, table, rule, desc) should have the proper description +ok 21 - hasnt_rule(schema, table, rule, desc) should have the proper diagnostics +ok 22 - hasnt_rule(schema, table, rule) should fail +ok 23 - hasnt_rule(schema, table, rule) should have the proper description +ok 24 - hasnt_rule(schema, table, rule) should have the proper diagnostics +ok 25 - hasnt_rule(schema, table, rule, desc) fail should pass +ok 26 - hasnt_rule(schema, table, rule, desc) fail should have the proper description +ok 27 - hasnt_rule(schema, table, rule, desc) fail should have the proper diagnostics +ok 28 - hasnt_rule(table, rule, desc) should fail +ok 29 - hasnt_rule(table, rule, desc) should have the proper description +ok 30 - hasnt_rule(table, rule, desc) should have the proper diagnostics +ok 31 - hasnt_rule(table, rule) should fail +ok 32 - hasnt_rule(table, rule) should have the proper description +ok 33 - hasnt_rule(table, rule) should have the proper diagnostics +ok 34 - hasnt_rule(table, rule, desc) fail should pass +ok 35 - hasnt_rule(table, rule, desc) fail should have the proper description +ok 36 - hasnt_rule(table, rule, desc) fail should have the proper diagnostics +ok 37 - rule_is_instead(schema, table, rule, desc) should pass +ok 38 - rule_is_instead(schema, table, rule, desc) should have the proper description +ok 39 - rule_is_instead(schema, table, rule, desc) should have the proper diagnostics +ok 40 - rule_is_instead(schema, table, rule) should pass +ok 41 - rule_is_instead(schema, table, rule) should have the proper description +ok 42 - rule_is_instead(schema, table, rule) should have the proper diagnostics +ok 43 - rule_is_instead(schema, table, nothing rule, desc) should fail +ok 44 - rule_is_instead(schema, table, nothing rule, desc) should have the proper description +ok 45 - rule_is_instead(schema, table, nothing rule, desc) should have the proper diagnostics +ok 46 - rule_is_instead(schema, table, also rule, desc) should fail +ok 47 - rule_is_instead(schema, table, also rule, desc) should have the proper description +ok 48 - rule_is_instead(schema, table, also rule, desc) should have the proper diagnostics +ok 49 - rule_is_instead(table, rule, desc) should pass +ok 50 - rule_is_instead(table, rule, desc) should have the proper description +ok 51 - rule_is_instead(table, rule, desc) should have the proper diagnostics +ok 52 - rule_is_instead(table, rule) should pass +ok 53 - rule_is_instead(table, rule) should have the proper description +ok 54 - rule_is_instead(table, rule) should have the proper diagnostics +ok 55 - rule_is_instead(table, nothing rule, desc) should fail +ok 56 - rule_is_instead(table, nothing rule, desc) should have the proper description +ok 57 - rule_is_instead(table, nothing rule, desc) should have the proper diagnostics +ok 58 - rule_is_instead(table, also rule, desc) should fail +ok 59 - rule_is_instead(table, also rule, desc) should have the proper description +ok 60 - rule_is_instead(table, also rule, desc) should have the proper diagnostics +ok 61 - rule_is_instead(schema, table, non-existent rule, desc) should fail +ok 62 - rule_is_instead(schema, table, non-existent rule, desc) should have the proper description +ok 63 - rule_is_instead(schema, table, non-existent rule, desc) should have the proper diagnostics +ok 64 - rule_is_instead(table, non-existent rule, desc) should fail +ok 65 - rule_is_instead(table, non-existent rule, desc) should have the proper description +ok 66 - rule_is_instead(table, non-existent rule, desc) should have the proper diagnostics diff --git a/sql/hastap.sql b/sql/hastap.sql index c8be98546471..2443497e193a 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(624); +SELECT plan(558); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -12,11 +12,6 @@ CREATE TABLE public.sometab( numb NUMERIC(10, 2), myint NUMERIC(8) ); -CREATE RULE ins_me AS ON INSERT TO public.sometab DO NOTHING; -CREATE RULE upd_me AS ON UPDATE TO public.sometab DO ALSO SELECT now(); - -CREATE TABLE public.toview ( id INT ); -CREATE RULE "_RETURN" AS ON SELECT TO public.toview DO INSTEAD SELECT 42 AS id; CREATE TYPE public.sometype AS ( id INT, @@ -1566,189 +1561,6 @@ SELECT * FROM check_test( '' ); -/****************************************************************************/ --- Test has_rule() and hasnt_rule(). - -SELECT * FROM check_test( - has_rule( 'public', 'sometab', 'ins_me', 'whatever' ), - true, - 'has_rule(schema, table, rule, desc)', - 'whatever', - '' -); - -SELECT * FROM check_test( - has_rule( 'public', 'sometab', 'ins_me'::name ), - true, - 'has_rule(schema, table, rule)', - 'Relation public.sometab should have rule ins_me', - '' -); - -SELECT * FROM check_test( - has_rule( 'public', 'sometab', 'del_me', 'whatever' ), - false, - 'has_rule(schema, table, rule, desc) fail', - 'whatever', - '' -); - -SELECT * FROM check_test( - has_rule( 'sometab', 'ins_me', 'whatever' ), - true, - 'has_rule(table, rule, desc)', - 'whatever', - '' -); - -SELECT * FROM check_test( - has_rule( 'sometab', 'ins_me'::name ), - true, - 'has_rule(table, rule)', - 'Relation sometab should have rule ins_me', - '' -); - -SELECT * FROM check_test( - has_rule( 'sometab', 'del_me', 'whatever' ), - false, - 'has_rule(table, rule, desc) fail', - 'whatever', - '' -); - -SELECT * FROM check_test( - hasnt_rule( 'public', 'sometab', 'ins_me', 'whatever' ), - false, - 'hasnt_rule(schema, table, rule, desc)', - 'whatever', - '' -); - -SELECT * FROM check_test( - hasnt_rule( 'public', 'sometab', 'ins_me'::name ), - false, - 'hasnt_rule(schema, table, rule)', - 'Relation public.sometab should not have rule ins_me', - '' -); - -SELECT * FROM check_test( - hasnt_rule( 'public', 'sometab', 'del_me', 'whatever' ), - true, - 'hasnt_rule(schema, table, rule, desc) fail', - 'whatever', - '' -); - -SELECT * FROM check_test( - hasnt_rule( 'sometab', 'ins_me', 'whatever' ), - false, - 'hasnt_rule(table, rule, desc)', - 'whatever', - '' -); - -SELECT * FROM check_test( - hasnt_rule( 'sometab', 'ins_me'::name ), - false, - 'hasnt_rule(table, rule)', - 'Relation sometab should not have rule ins_me', - '' -); - -SELECT * FROM check_test( - hasnt_rule( 'sometab', 'del_me', 'whatever' ), - true, - 'hasnt_rule(table, rule, desc) fail', - 'whatever', - '' -); - -/****************************************************************************/ --- Test rule_is_instead(). - -SELECT * FROM check_test( - rule_is_instead( 'public', 'toview', '_RETURN', 'whatever' ), - true, - 'rule_is_instead(schema, table, rule, desc)', - 'whatever', - '' -); - -SELECT * FROM check_test( - rule_is_instead( 'public', 'toview', '_RETURN'::name ), - true, - 'rule_is_instead(schema, table, rule)', - 'Rule "_RETURN" on relation public.toview should be an INSTEAD rule', - '' -); - -SELECT * FROM check_test( - rule_is_instead( 'public', 'sometab', 'ins_me', 'whatever' ), - false, - 'rule_is_instead(schema, table, nothing rule, desc)', - 'whatever', - '' -); - -SELECT * FROM check_test( - rule_is_instead( 'public', 'sometab', 'upd_me', 'whatever' ), - false, - 'rule_is_instead(schema, table, also rule, desc)', - 'whatever', - '' -); - -SELECT * FROM check_test( - rule_is_instead( 'toview', '_RETURN', 'whatever' ), - true, - 'rule_is_instead(table, rule, desc)', - 'whatever', - '' -); - -SELECT * FROM check_test( - rule_is_instead( 'toview', '_RETURN'::name ), - true, - 'rule_is_instead(table, rule)', - 'Rule "_RETURN" on relation toview should be an INSTEAD rule', - '' -); - -SELECT * FROM check_test( - rule_is_instead( 'sometab', 'ins_me', 'whatever' ), - false, - 'rule_is_instead(table, nothing rule, desc)', - 'whatever', - '' -); - -SELECT * FROM check_test( - rule_is_instead( 'sometab', 'upd_me', 'whatever' ), - false, - 'rule_is_instead(table, also rule, desc)', - 'whatever', - '' -); - --- Check failure diagnostics for non-existent rules. -SELECT * FROM check_test( - rule_is_instead( 'public', 'sometab', 'blah', 'whatever' ), - false, - 'rule_is_instead(schema, table, non-existent rule, desc)', - 'whatever', - ' Rule blah does not exist' -); - -SELECT * FROM check_test( - rule_is_instead( 'sometab', 'blah', 'whatever' ), - false, - 'rule_is_instead(table, non-existent rule, desc)', - 'whatever', - ' Rule blah does not exist' -); - /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/sql/ruletap.sql b/sql/ruletap.sql new file mode 100644 index 000000000000..f8300523477f --- /dev/null +++ b/sql/ruletap.sql @@ -0,0 +1,209 @@ +\unset ECHO +\i test_setup.sql + +SELECT plan(66); +--SELECT * FROM no_plan(); + +-- This will be rolled back. :-) +SET client_min_messages = warning; +CREATE TABLE public.sometab( + id INT NOT NULL PRIMARY KEY, + name TEXT DEFAULT '', + numb NUMERIC(10, 2), + myint NUMERIC(8) +); +CREATE RULE ins_me AS ON INSERT TO public.sometab DO NOTHING; +CREATE RULE upd_me AS ON UPDATE TO public.sometab DO ALSO SELECT now(); + +CREATE TABLE public.toview ( id INT ); +CREATE RULE "_RETURN" AS ON SELECT TO public.toview DO INSTEAD SELECT 42 AS id; + +RESET client_min_messages; + +/****************************************************************************/ +-- Test has_rule() and hasnt_rule(). + +SELECT * FROM check_test( + has_rule( 'public', 'sometab', 'ins_me', 'whatever' ), + true, + 'has_rule(schema, table, rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_rule( 'public', 'sometab', 'ins_me'::name ), + true, + 'has_rule(schema, table, rule)', + 'Relation public.sometab should have rule ins_me', + '' +); + +SELECT * FROM check_test( + has_rule( 'public', 'sometab', 'del_me', 'whatever' ), + false, + 'has_rule(schema, table, rule, desc) fail', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_rule( 'sometab', 'ins_me', 'whatever' ), + true, + 'has_rule(table, rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_rule( 'sometab', 'ins_me'::name ), + true, + 'has_rule(table, rule)', + 'Relation sometab should have rule ins_me', + '' +); + +SELECT * FROM check_test( + has_rule( 'sometab', 'del_me', 'whatever' ), + false, + 'has_rule(table, rule, desc) fail', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_rule( 'public', 'sometab', 'ins_me', 'whatever' ), + false, + 'hasnt_rule(schema, table, rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_rule( 'public', 'sometab', 'ins_me'::name ), + false, + 'hasnt_rule(schema, table, rule)', + 'Relation public.sometab should not have rule ins_me', + '' +); + +SELECT * FROM check_test( + hasnt_rule( 'public', 'sometab', 'del_me', 'whatever' ), + true, + 'hasnt_rule(schema, table, rule, desc) fail', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_rule( 'sometab', 'ins_me', 'whatever' ), + false, + 'hasnt_rule(table, rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_rule( 'sometab', 'ins_me'::name ), + false, + 'hasnt_rule(table, rule)', + 'Relation sometab should not have rule ins_me', + '' +); + +SELECT * FROM check_test( + hasnt_rule( 'sometab', 'del_me', 'whatever' ), + true, + 'hasnt_rule(table, rule, desc) fail', + 'whatever', + '' +); + +/****************************************************************************/ +-- Test rule_is_instead(). + +SELECT * FROM check_test( + rule_is_instead( 'public', 'toview', '_RETURN', 'whatever' ), + true, + 'rule_is_instead(schema, table, rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_instead( 'public', 'toview', '_RETURN'::name ), + true, + 'rule_is_instead(schema, table, rule)', + 'Rule "_RETURN" on relation public.toview should be an INSTEAD rule', + '' +); + +SELECT * FROM check_test( + rule_is_instead( 'public', 'sometab', 'ins_me', 'whatever' ), + false, + 'rule_is_instead(schema, table, nothing rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_instead( 'public', 'sometab', 'upd_me', 'whatever' ), + false, + 'rule_is_instead(schema, table, also rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_instead( 'toview', '_RETURN', 'whatever' ), + true, + 'rule_is_instead(table, rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_instead( 'toview', '_RETURN'::name ), + true, + 'rule_is_instead(table, rule)', + 'Rule "_RETURN" on relation toview should be an INSTEAD rule', + '' +); + +SELECT * FROM check_test( + rule_is_instead( 'sometab', 'ins_me', 'whatever' ), + false, + 'rule_is_instead(table, nothing rule, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_instead( 'sometab', 'upd_me', 'whatever' ), + false, + 'rule_is_instead(table, also rule, desc)', + 'whatever', + '' +); + +-- Check failure diagnostics for non-existent rules. +SELECT * FROM check_test( + rule_is_instead( 'public', 'sometab', 'blah', 'whatever' ), + false, + 'rule_is_instead(schema, table, non-existent rule, desc)', + 'whatever', + ' Rule blah does not exist' +); + +SELECT * FROM check_test( + rule_is_instead( 'sometab', 'blah', 'whatever' ), + false, + 'rule_is_instead(table, non-existent rule, desc)', + 'whatever', + ' Rule blah does not exist' +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From 0bb1fef3813942dac9b7e57a889b1962e6601794 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 24 May 2009 21:04:03 -0700 Subject: [PATCH 0362/1195] Added `hasnt_trigger()` and another three-argument form of `has_trigger()`. --- Changes | 4 ++ README.pgtap | 19 ++++++++- expected/trigger.out | 89 ++++++++++++++++++++++++++----------------- pgtap.sql.in | 91 +++++++++++++++++++++++++++++--------------- sql/trigger.sql | 72 +++++++++++++++++++++++++++++++---- 5 files changed, 200 insertions(+), 75 deletions(-) diff --git a/Changes b/Changes index 1ee57266f75d..7014986e4890 100644 --- a/Changes +++ b/Changes @@ -15,6 +15,10 @@ Revision history for pgTAP * Added `tablespaces_are()`, `schemas_are()`, `tables_are()`, `views_are()`, `sequences_are()`, `functions_are()`, `indexes_are()`, `users_are()`, `groups_are()`, `opclasses_are()`, `languages_are()`, and `rules_are()`. +* Added a `has_trigger(table, trigger, description)`. Note that this means + that if you were previously using `has_trigger(schema, table, trigger)`, you + will need to cast the third argument to `NAME` to get it working again. +* Added `hasnt_trigger()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index 942f3981b95a..55f37a219481 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1275,6 +1275,7 @@ incorrect, the diagnostics will look more like this: ### `has_trigger( schema, table, trigger, description )` ### ### `has_trigger( schema, table, trigger )` ### +### `has_trigger( table, trigger, description )` ### ### `has_trigger( table, trigger )` ### SELECT has_trigger( @@ -1288,7 +1289,22 @@ incorrect, the diagnostics will look more like this: Tests to see if the specified table has the named trigger. The `:description` is optional, and if the schema is omitted, the table with which the trigger is -associated must be visible. +associated must be visible in the search path. + +### `hasnt_trigger( schema, table, trigger, description )` ### +### `hasnt_trigger( schema, table, trigger )` ### +### `hasnt_trigger( table, trigger, description )` ### +### `hasnt_trigger( table, trigger )` ### + + SELECT hasnt_trigger( + 'myschema', + 'sometable', + 'sometrigger', + 'Trigger "sometrigger" not should exist' + ); + +This function is the inverse of `has_trigger()`. The test passes if the +specified trigger does *not* exist. ### `has_rule( schema, table, rule, description )` ### ### `has_rule( schema, table, rule )` ### @@ -2995,7 +3011,6 @@ To Do * `sequence_increments_by()` * `sequence_starts_at()` * `sequence_cycles()` - * `hasnt_trigger()` * `hasnt_enum()` * `hasnt_index()` * `hasnt_function()` diff --git a/expected/trigger.out b/expected/trigger.out index df61ddfca660..7014628eddec 100644 --- a/expected/trigger.out +++ b/expected/trigger.out @@ -1,35 +1,56 @@ \unset ECHO -1..33 -ok 1 - has_trigger() should pass -ok 2 - has_trigger() should have the proper description -ok 3 - has_trigger() should have the proper diagnostics -ok 4 - has_trigger() no desc should pass -ok 5 - has_trigger() no desc should have the proper description -ok 6 - has_trigger() no desc should have the proper diagnostics -ok 7 - has_trigger() no schema should pass -ok 8 - has_trigger() no schema should have the proper description -ok 9 - has_trigger() no schema should have the proper diagnostics -ok 10 - has_trigger() fail should fail -ok 11 - has_trigger() fail should have the proper description -ok 12 - has_trigger() fail should have the proper diagnostics -ok 13 - has_trigger() no schema fail should fail -ok 14 - has_trigger() no schema fail should have the proper description -ok 15 - has_trigger() no schema fail should have the proper diagnostics -ok 16 - trigger_is() should pass -ok 17 - trigger_is() should have the proper description -ok 18 - trigger_is() should have the proper diagnostics -ok 19 - trigger_is() no desc should pass -ok 20 - trigger_is() no desc should have the proper description -ok 21 - trigger_is() no desc should have the proper diagnostics -ok 22 - trigger_is() no schema should pass -ok 23 - trigger_is() no schema should have the proper description -ok 24 - trigger_is() no schema should have the proper diagnostics -ok 25 - trigger_is() no schema or desc should pass -ok 26 - trigger_is() no schema or desc should have the proper description -ok 27 - trigger_is() no schema or desc should have the proper diagnostics -ok 28 - trigger_is() fail should fail -ok 29 - trigger_is() fail should have the proper description -ok 30 - trigger_is() fail should have the proper diagnostics -ok 31 - trigger_is() no schema fail should fail -ok 32 - trigger_is() no schema fail should have the proper description -ok 33 - trigger_is() no schema fail should have the proper diagnostics +1..54 +ok 1 - has_trigger(schema, table, trigger, desc) should pass +ok 2 - has_trigger(schema, table, trigger, desc) should have the proper description +ok 3 - has_trigger(schema, table, trigger, desc) should have the proper diagnostics +ok 4 - has_trigger(schema, table, trigger) should pass +ok 5 - has_trigger(schema, table, trigger) should have the proper description +ok 6 - has_trigger(schema, table, trigger) should have the proper diagnostics +ok 7 - has_trigger(table, trigger, desc) should pass +ok 8 - has_trigger(table, trigger, desc) should have the proper description +ok 9 - has_trigger(table, trigger, desc) should have the proper diagnostics +ok 10 - has_trigger(table, trigger) should pass +ok 11 - has_trigger(table, trigger) should have the proper description +ok 12 - has_trigger(table, trigger) should have the proper diagnostics +ok 13 - has_trigger(schema, table, nonexistent, desc) should fail +ok 14 - has_trigger(schema, table, nonexistent, desc) should have the proper description +ok 15 - has_trigger(schema, table, nonexistent, desc) should have the proper diagnostics +ok 16 - has_trigger(table, nonexistent) no schema fail should fail +ok 17 - has_trigger(table, nonexistent) no schema fail should have the proper description +ok 18 - has_trigger(table, nonexistent) no schema fail should have the proper diagnostics +ok 19 - hasnt_trigger(schema, table, trigger, desc) should fail +ok 20 - hasnt_trigger(schema, table, trigger, desc) should have the proper description +ok 21 - hasnt_trigger(schema, table, trigger, desc) should have the proper diagnostics +ok 22 - hasnt_trigger(schema, table, trigger) should fail +ok 23 - hasnt_trigger(schema, table, trigger) should have the proper description +ok 24 - hasnt_trigger(schema, table, trigger) should have the proper diagnostics +ok 25 - hasnt_trigger(table, trigger, desc) should fail +ok 26 - hasnt_trigger(table, trigger, desc) should have the proper description +ok 27 - hasnt_trigger(table, trigger, desc) should have the proper diagnostics +ok 28 - hasnt_trigger(table, trigger) should fail +ok 29 - hasnt_trigger(table, trigger) should have the proper description +ok 30 - hasnt_trigger(table, trigger) should have the proper diagnostics +ok 31 - hasnt_trigger(schema, table, nonexistent, desc) should pass +ok 32 - hasnt_trigger(schema, table, nonexistent, desc) should have the proper description +ok 33 - hasnt_trigger(schema, table, nonexistent, desc) should have the proper diagnostics +ok 34 - hasnt_trigger(table, nonexistent) no schema fail should pass +ok 35 - hasnt_trigger(table, nonexistent) no schema fail should have the proper description +ok 36 - hasnt_trigger(table, nonexistent) no schema fail should have the proper diagnostics +ok 37 - trigger_is() should pass +ok 38 - trigger_is() should have the proper description +ok 39 - trigger_is() should have the proper diagnostics +ok 40 - trigger_is() no desc should pass +ok 41 - trigger_is() no desc should have the proper description +ok 42 - trigger_is() no desc should have the proper diagnostics +ok 43 - trigger_is() no schema should pass +ok 44 - trigger_is() no schema should have the proper description +ok 45 - trigger_is() no schema should have the proper diagnostics +ok 46 - trigger_is() no schema or desc should pass +ok 47 - trigger_is() no schema or desc should have the proper description +ok 48 - trigger_is() no schema or desc should have the proper diagnostics +ok 49 - trigger_is() fail should fail +ok 50 - trigger_is() fail should have the proper description +ok 51 - trigger_is() fail should have the proper diagnostics +ok 52 - trigger_is() no schema fail should fail +ok 53 - trigger_is() no schema fail should have the proper description +ok 54 - trigger_is() no schema fail should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index e0980ebdc4d0..d384d0ad4946 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2708,24 +2708,35 @@ BEGIN 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 + ); +$$ LANGUAGE SQL; + -- has_trigger( schema, table, trigger, description ) CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME, NAME, TEXT ) RETURNS TEXT AS $$ -DECLARE - res boolean; -BEGIN - 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 - INTO res; - - RETURN ok( COALESCE(res, false), $4 ); -END; -$$ LANGUAGE plpgsql; + SELECT ok( _trig($1, $2, $3), $4); +$$ LANGUAGE SQL; -- has_trigger( schema, table, trigger ) CREATE OR REPLACE FUNCTION has_trigger ( NAME, NAME, NAME ) @@ -2736,26 +2747,44 @@ RETURNS TEXT AS $$ ); $$ 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 $$ -DECLARE - res boolean; -BEGIN - 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) - INTO res; + SELECT ok( _trig($1, $2), 'Table ' || quote_ident($1) || ' should have trigger ' || quote_ident($2)); +$$ LANGUAGE SQL; - RETURN ok( - COALESCE(res, false), - 'Table ' || quote_ident($1) || ' should have trigger ' || quote_ident($2) - ); -END; -$$ LANGUAGE plpgsql; +-- 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 ) diff --git a/sql/trigger.sql b/sql/trigger.sql index 2e6c5c10a6f6..eadb479ef4f3 100644 --- a/sql/trigger.sql +++ b/sql/trigger.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(33); +SELECT plan(54); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -23,28 +23,36 @@ FOR EACH ROW EXECUTE PROCEDURE hash_pass(); RESET client_min_messages; /****************************************************************************/ --- Test has_trigger(). +-- Test has_trigger() and hasnt_trigger(). SELECT * FROM check_test( has_trigger( 'public', 'users', 'set_users_pass', 'whatever' ), true, - 'has_trigger()', + 'has_trigger(schema, table, trigger, desc)', 'whatever', '' ); SELECT * FROM check_test( - has_trigger( 'public', 'users', 'set_users_pass' ), + has_trigger( 'public', 'users', 'set_users_pass'::name ), true, - 'has_trigger() no desc', + 'has_trigger(schema, table, trigger)', 'Table public.users should have trigger set_users_pass', '' ); +SELECT * FROM check_test( + has_trigger( 'users', 'set_users_pass', 'whatever' ), + true, + 'has_trigger(table, trigger, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( has_trigger( 'users', 'set_users_pass' ), true, - 'has_trigger() no schema', + 'has_trigger(table, trigger)', 'Table users should have trigger set_users_pass', '' ); @@ -52,7 +60,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( has_trigger( 'public', 'users', 'nosuch', 'whatever' ), false, - 'has_trigger() fail', + 'has_trigger(schema, table, nonexistent, desc)', 'whatever', '' ); @@ -60,11 +68,59 @@ SELECT * FROM check_test( SELECT * FROM check_test( has_trigger( 'users', 'nosuch' ), false, - 'has_trigger() no schema fail', + 'has_trigger(table, nonexistent) no schema fail', 'Table users should have trigger nosuch', '' ); +SELECT * FROM check_test( + hasnt_trigger( 'public', 'users', 'set_users_pass', 'whatever' ), + false, + 'hasnt_trigger(schema, table, trigger, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_trigger( 'public', 'users', 'set_users_pass'::name ), + false, + 'hasnt_trigger(schema, table, trigger)', + 'Table public.users should not have trigger set_users_pass', + '' +); + +SELECT * FROM check_test( + hasnt_trigger( 'users', 'set_users_pass', 'whatever' ), + false, + 'hasnt_trigger(table, trigger, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_trigger( 'users', 'set_users_pass' ), + false, + 'hasnt_trigger(table, trigger)', + 'Table users should not have trigger set_users_pass', + '' +); + +SELECT * FROM check_test( + hasnt_trigger( 'public', 'users', 'nosuch', 'whatever' ), + true, + 'hasnt_trigger(schema, table, nonexistent, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_trigger( 'users', 'nosuch' ), + true, + 'hasnt_trigger(table, nonexistent) no schema fail', + 'Table users should not have trigger nosuch', + '' +); + /****************************************************************************/ -- test trigger_is() From 97a40ff6cdca423b89d97e2616692c8107e5c698 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 24 May 2009 21:06:21 -0700 Subject: [PATCH 0363/1195] Documented `hasnt_enum()`. --- Changes | 2 ++ README.pgtap | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 7014986e4890..dd579b99f639 100644 --- a/Changes +++ b/Changes @@ -19,6 +19,8 @@ Revision history for pgTAP that if you were previously using `has_trigger(schema, table, trigger)`, you will need to cast the third argument to `NAME` to get it working again. * Added `hasnt_trigger()`. +* Documented `hasnt_enum()`, which has actually been around for as long as + `has_enum()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index 55f37a219481..5babff43834a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1224,6 +1224,20 @@ passing a schema and enum rather than enum and description, be sure to cast the arguments to `name` values so that your enum name doesn't get treated as a description. +### `hasnt_enum( schema, enum, description )` ### +### `hasnt_enum( schema, enum )` ### +### `hasnt_enum( enum, description )` ### +### `hasnt_enum( enum )` ### + + SELECT hasnt_enum( + 'myschema', + 'someenum', + 'I don''t got myschema.someenum' + ); + +This function is the inverse of `has_enum()`. The test passes if the specified +enum does *not* exist. + ### `has_index( schema, table, index, columns[], description )` ### ### `has_index( schema, table, index, columns[] )` ### ### `has_index( schema, table, index, column/expression, description )` ### @@ -3011,7 +3025,6 @@ To Do * `sequence_increments_by()` * `sequence_starts_at()` * `sequence_cycles()` - * `hasnt_enum()` * `hasnt_index()` * `hasnt_function()` From afb6a9685b24ab0db389ff5d40881d1006e1697a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 24 May 2009 21:16:15 -0700 Subject: [PATCH 0364/1195] Eliminated the unnecessary private function `_has_index()`. --- Changes | 1 + pgtap.sql.in | 20 ++++---------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/Changes b/Changes index dd579b99f639..f74351f72466 100644 --- a/Changes +++ b/Changes @@ -21,6 +21,7 @@ Revision history for pgTAP * Added `hasnt_trigger()`. * Documented `hasnt_enum()`, which has actually been around for as long as `has_enum()`. +* Eliminated the unnecessary private function `_has_index()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index d384d0ad4946..13866519aa7c 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2327,18 +2327,6 @@ returns boolean AS $$ ); $$ LANGUAGE sql; --- _has_index( schema, table, index ) -CREATE OR REPLACE FUNCTION _has_index( NAME, NAME, NAME ) -RETURNS boolean AS $$ - SELECT EXISTS ( SELECT _iexpr( $1, $2, $3 ) ); -$$ LANGUAGE sql; - --- _has_index( table, index ) -CREATE OR REPLACE FUNCTION _has_index( NAME, NAME ) -RETURNS boolean AS $$ - SELECT EXISTS ( SELECT _iexpr( $1, $2 ) ); -$$ 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 ) @@ -2354,7 +2342,7 @@ BEGIN -- Not a functional index. IF _is_schema( $1 ) THEN -- Looking for schema.table index. - RETURN ok ( _has_index( $1, $2, $3 ), $4); + RETURN ok ( _iexpr( $1, $2, $3 ) IS NOT NULL, $4); END IF; -- Looking for particular columns. RETURN has_index( $1, $2, ARRAY[$3], $4 ); @@ -2397,7 +2385,7 @@ RETURNS TEXT AS $$ BEGIN IF _is_schema($1) THEN -- ( schema, table, index ) - RETURN ok( _has_index( $1, $2, $3 ), 'Index ' || quote_ident($3) || ' should exist' ); + RETURN ok( _iexpr( $1, $2, $3 ) IS NOT NULL, 'Index ' || quote_ident($3) || ' should exist' ); ELSE -- ( table, index, column/expression ) RETURN has_index( $1, $2, $3, 'Index ' || quote_ident($2) || ' should exist' ); @@ -2410,14 +2398,14 @@ 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( _has_index( $1, $2 ), $3 ) + ELSE ok( _iexpr( $1, $2 ) IS NOT NULL, $3 ) END; $$ LANGUAGE sql; -- has_index( table, index ) CREATE OR REPLACE FUNCTION has_index ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT ok( _has_index( $1, $2 ), 'Index ' || quote_ident($2) || ' should exist' ); + SELECT ok( _iexpr( $1, $2 ) IS NOT NULL, 'Index ' || quote_ident($2) || ' should exist' ); $$ LANGUAGE sql; -- index_is_unique( schema, table, index, description ) From 3e8c20d0b17da835375df1e93a8ad6bbe6767e43 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 26 May 2009 14:58:05 -0700 Subject: [PATCH 0365/1195] Added `hasnt_index()`. * Added `hasnt_index()`. * Restored `_have_index()`, turns out I needed it after all. It was broken, though, so I refactored it to do a proper index lookup. I added a bunch of new tests in the process of tracking down the problem. * Fixed a bunch of "not should" typos; they are supposed to be "should not". --- Changes | 3 +- README.pgtap | 24 ++- expected/index.out | 398 +++++++++++++++++++++++++-------------------- pgtap.sql.in | 66 +++++++- sql/index.sql | 139 +++++++++++++++- 5 files changed, 441 insertions(+), 189 deletions(-) diff --git a/Changes b/Changes index f74351f72466..0fcd165db879 100644 --- a/Changes +++ b/Changes @@ -18,10 +18,9 @@ Revision history for pgTAP * Added a `has_trigger(table, trigger, description)`. Note that this means that if you were previously using `has_trigger(schema, table, trigger)`, you will need to cast the third argument to `NAME` to get it working again. -* Added `hasnt_trigger()`. +* Added `hasnt_trigger()` and `hasnt_index()`. * Documented `hasnt_enum()`, which has actually been around for as long as `has_enum()`. -* Eliminated the unnecessary private function `_has_index()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index 5babff43834a..35c30606b681 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1287,6 +1287,21 @@ incorrect, the diagnostics will look more like this: # have: "idx_baz" ON public.sometab(lower(name)) # want: "idx_baz" ON public.sometab(lower(lname)) +### `hasnt_index( schema, table, index, description )` ### +### `hasnt_index( schema, table, index )` ### +### `hasnt_index( table, index, description )` ### +### `hasnt_index( table, index )` ### + + SELECT hasnt_index( + 'myschema', + 'sometable', + 'someindex', + 'Index "someindex" should not exist' + ); + +This function is the inverse of `has_index()`. The test passes if the +specified index does *not* exist. + ### `has_trigger( schema, table, trigger, description )` ### ### `has_trigger( schema, table, trigger )` ### ### `has_trigger( table, trigger, description )` ### @@ -1314,7 +1329,7 @@ associated must be visible in the search path. 'myschema', 'sometable', 'sometrigger', - 'Trigger "sometrigger" not should exist' + 'Trigger "sometrigger" should not exist' ); This function is the inverse of `has_trigger()`. The test passes if the @@ -1559,7 +1574,7 @@ it will default to "Role `:role` should exist". ### `hasnt_role( role, desc )` ### ### `hasnt_role( role )` ### - SELECT hasnt_role( 'theory', 'Role "theory" not should exist' ); + SELECT hasnt_role( 'theory', 'Role "theory" should not exist' ); The inverse of `has_role()`, this function tests for the *absence* of a database role. @@ -1575,7 +1590,7 @@ it will default to "User `:user` should exist". ### `hasnt_user( user, desc )` ### ### `hasnt_user( user )` ### - SELECT hasnt_user( 'theory', 'User "theory" not should exist' ); + SELECT hasnt_user( 'theory', 'User "theory" should not exist' ); The inverse of `has_user()`, this function tests for the *absence* of a database user. @@ -1607,7 +1622,7 @@ omitted, it will default to "Procedural language `:language` should exist". ### `hasnt_language( language, desc )` ### ### `hasnt_language( language )` ### - SELECT hasnt_language( 'plpgsql', 'Language "plpgsql" not should exist' ); + SELECT hasnt_language( 'plpgsql', 'Language "plpgsql" should not exist' ); The inverse of `has_language()`, this function tests for the *absence* of a procedural language. @@ -3025,7 +3040,6 @@ To Do * `sequence_increments_by()` * `sequence_starts_at()` * `sequence_cycles()` - * `hasnt_index()` * `hasnt_function()` Supported Versions diff --git a/expected/index.out b/expected/index.out index d32a67210e14..d3101ec43181 100644 --- a/expected/index.out +++ b/expected/index.out @@ -1,182 +1,230 @@ \unset ECHO -1..180 +1..228 ok 1 - has_index() single column should pass ok 2 - has_index() single column should have the proper description ok 3 - has_index() single column should have the proper diagnostics ok 4 - has_index() single column no desc should pass ok 5 - has_index() single column no desc should have the proper description ok 6 - has_index() single column no desc should have the proper diagnostics -ok 7 - has_index() multi-column should pass -ok 8 - has_index() multi-column should have the proper description -ok 9 - has_index() multi-column should have the proper diagnostics -ok 10 - has_index() multi-column no desc should pass -ok 11 - has_index() multi-column no desc should have the proper description -ok 12 - has_index() multi-column no desc should have the proper diagnostics -ok 13 - has_index() functional should pass -ok 14 - has_index() functional should have the proper description -ok 15 - has_index() functional should have the proper diagnostics -ok 16 - has_index() no cols should pass -ok 17 - has_index() no cols should have the proper description -ok 18 - has_index() no cols should have the proper diagnostics -ok 19 - has_index() no cols no desc should pass -ok 20 - has_index() no cols no desc should have the proper description -ok 21 - has_index() no cols no desc should have the proper diagnostics -ok 22 - has_index() no schema single column should pass -ok 23 - has_index() no schema single column should have the proper description -ok 24 - has_index() no schema single column should have the proper diagnostics -ok 25 - has_index() no schema single column no desc should pass -ok 26 - has_index() no schema single column no desc should have the proper description -ok 27 - has_index() no schema single column no desc should have the proper diagnostics -ok 28 - has_index() no schema multi-column should pass -ok 29 - has_index() no schema multi-column should have the proper description -ok 30 - has_index() no schema multi-column should have the proper diagnostics -ok 31 - has_index() no schema multi-column no desc should pass -ok 32 - has_index() no schema multi-column no desc should have the proper description -ok 33 - has_index() no schema multi-column no desc should have the proper diagnostics -ok 34 - has_index() no schema functional should pass -ok 35 - has_index() no schema functional should have the proper description -ok 36 - has_index() no schema functional should have the proper diagnostics -ok 37 - has_index() no schema functional no desc should pass -ok 38 - has_index() no schema functional no desc should have the proper description -ok 39 - has_index() no schema functional no desc should have the proper diagnostics -ok 40 - has_index() no schema or cols should pass -ok 41 - has_index() no schema or cols should have the proper description -ok 42 - has_index() no schema or cols should have the proper diagnostics -ok 43 - has_index() no schema or cols or desc should pass -ok 44 - has_index() no schema or cols or desc should have the proper description -ok 45 - has_index() no schema or cols or desc should have the proper diagnostics -ok 46 - has_index() missing should fail -ok 47 - has_index() missing should have the proper description -ok 48 - has_index() missing should have the proper diagnostics -ok 49 - has_index() invalid should fail -ok 50 - has_index() invalid should have the proper description -ok 51 - has_index() invalid should have the proper diagnostics -ok 52 - has_index() missing no schema should fail -ok 53 - has_index() missing no schema should have the proper description -ok 54 - has_index() missing no schema should have the proper diagnostics -ok 55 - has_index() invalid no schema should fail -ok 56 - has_index() invalid no schema should have the proper description -ok 57 - has_index() invalid no schema should have the proper diagnostics -ok 58 - has_index() functional fail should fail -ok 59 - has_index() functional fail should have the proper description -ok 60 - has_index() functional fail should have the proper diagnostics -ok 61 - has_index() functional fail no schema should fail -ok 62 - has_index() functional fail no schema should have the proper description -ok 63 - has_index() functional fail no schema should have the proper diagnostics -ok 64 - index_is_unique() should pass -ok 65 - index_is_unique() should have the proper description -ok 66 - index_is_unique() should have the proper diagnostics -ok 67 - index_is_unique() no desc should pass -ok 68 - index_is_unique() no desc should have the proper description -ok 69 - index_is_unique() no desc should have the proper diagnostics -ok 70 - index_is_unique() no schema should pass -ok 71 - index_is_unique() no schema should have the proper description -ok 72 - index_is_unique() no schema should have the proper diagnostics -ok 73 - index_is_unique() index only should pass -ok 74 - index_is_unique() index only should have the proper description -ok 75 - index_is_unique() index only should have the proper diagnostics -ok 76 - index_is_unique() on pk should pass -ok 77 - index_is_unique() on pk should have the proper description -ok 78 - index_is_unique() on pk should have the proper diagnostics -ok 79 - index_is_unique() on pk no desc should pass -ok 80 - index_is_unique() on pk no desc should have the proper description -ok 81 - index_is_unique() on pk no desc should have the proper diagnostics -ok 82 - index_is_unique() on pk no schema should pass -ok 83 - index_is_unique() on pk no schema should have the proper description -ok 84 - index_is_unique() on pk no schema should have the proper diagnostics -ok 85 - index_is_unique() on pk index only should pass -ok 86 - index_is_unique() on pk index only should have the proper description -ok 87 - index_is_unique() on pk index only should have the proper diagnostics -ok 88 - index_is_unique() fail should fail -ok 89 - index_is_unique() fail should have the proper description -ok 90 - index_is_unique() fail should have the proper diagnostics -ok 91 - index_is_unique() fail no desc should fail -ok 92 - index_is_unique() fail no desc should have the proper description -ok 93 - index_is_unique() fail no desc should have the proper diagnostics -ok 94 - index_is_unique() fail no schema should fail -ok 95 - index_is_unique() fail no schema should have the proper description -ok 96 - index_is_unique() fail no schema should have the proper diagnostics -ok 97 - index_is_unique() fail index only should fail -ok 98 - index_is_unique() fail index only should have the proper description -ok 99 - index_is_unique() fail index only should have the proper diagnostics -ok 100 - index_is_unique() no such index should fail -ok 101 - index_is_unique() no such index should have the proper description -ok 102 - index_is_unique() no such index should have the proper diagnostics -ok 103 - index_is_primary() should pass -ok 104 - index_is_primary() should have the proper description -ok 105 - index_is_primary() should have the proper diagnostics -ok 106 - index_is_primary() no desc should pass -ok 107 - index_is_primary() no desc should have the proper description -ok 108 - index_is_primary() no desc should have the proper diagnostics -ok 109 - index_is_primary() no schema should pass -ok 110 - index_is_primary() no schema should have the proper description -ok 111 - index_is_primary() no schema should have the proper diagnostics -ok 112 - index_is_primary() index only should pass -ok 113 - index_is_primary() index only should have the proper description -ok 114 - index_is_primary() index only should have the proper diagnostics -ok 115 - index_is_primary() fail should fail -ok 116 - index_is_primary() fail should have the proper description -ok 117 - index_is_primary() fail should have the proper diagnostics -ok 118 - index_is_primary() fail no desc should fail -ok 119 - index_is_primary() fail no desc should have the proper description -ok 120 - index_is_primary() fail no desc should have the proper diagnostics -ok 121 - index_is_primary() fail no schema should fail -ok 122 - index_is_primary() fail no schema should have the proper description -ok 123 - index_is_primary() fail no schema should have the proper diagnostics -ok 124 - index_is_primary() fail index only should fail -ok 125 - index_is_primary() fail index only should have the proper description -ok 126 - index_is_primary() fail index only should have the proper diagnostics -ok 127 - index_is_primary() no such index should fail -ok 128 - index_is_primary() no such index should have the proper description -ok 129 - index_is_primary() no such index should have the proper diagnostics -ok 130 - is_clustered() fail should fail -ok 131 - is_clustered() fail should have the proper description -ok 132 - is_clustered() fail should have the proper diagnostics -ok 133 - is_clustered() fail no desc should fail -ok 134 - is_clustered() fail no desc should have the proper description -ok 135 - is_clustered() fail no desc should have the proper diagnostics -ok 136 - is_clustered() fail no schema should fail -ok 137 - is_clustered() fail no schema should have the proper description -ok 138 - is_clustered() fail no schema should have the proper diagnostics -ok 139 - is_clustered() fail index only should fail -ok 140 - is_clustered() fail index only should have the proper description -ok 141 - is_clustered() fail index only should have the proper diagnostics -ok 142 - is_clustered() should pass -ok 143 - is_clustered() should have the proper description -ok 144 - is_clustered() should have the proper diagnostics -ok 145 - is_clustered() no desc should pass -ok 146 - is_clustered() no desc should have the proper description -ok 147 - is_clustered() no desc should have the proper diagnostics -ok 148 - is_clustered() no schema should pass -ok 149 - is_clustered() no schema should have the proper description -ok 150 - is_clustered() no schema should have the proper diagnostics -ok 151 - is_clustered() index only should pass -ok 152 - is_clustered() index only should have the proper description -ok 153 - is_clustered() index only should have the proper diagnostics -ok 154 - index_is_type() should pass -ok 155 - index_is_type() should have the proper description -ok 156 - index_is_type() should have the proper diagnostics -ok 157 - index_is_type() ci should pass -ok 158 - index_is_type() ci should have the proper description -ok 159 - index_is_type() ci should have the proper diagnostics -ok 160 - index_is_type() no desc should pass -ok 161 - index_is_type() no desc should have the proper description -ok 162 - index_is_type() no desc should have the proper diagnostics -ok 163 - index_is_type() fail should fail -ok 164 - index_is_type() fail should have the proper description -ok 165 - index_is_type() fail should have the proper diagnostics -ok 166 - index_is_type() no schema should pass -ok 167 - index_is_type() no schema should have the proper description -ok 168 - index_is_type() no schema should have the proper diagnostics -ok 169 - index_is_type() no schema fail should fail -ok 170 - index_is_type() no schema fail should have the proper description -ok 171 - index_is_type() no schema fail should have the proper diagnostics -ok 172 - index_is_type() no table should pass -ok 173 - index_is_type() no table should have the proper description -ok 174 - index_is_type() no table should have the proper diagnostics -ok 175 - index_is_type() no table fail should fail -ok 176 - index_is_type() no table fail should have the proper description -ok 177 - index_is_type() no table fail should have the proper diagnostics -ok 178 - index_is_type() hash should pass -ok 179 - index_is_type() hash should have the proper description -ok 180 - index_is_type() hash should have the proper diagnostics +ok 7 - has_index() hash index should pass +ok 8 - has_index() hash index should have the proper description +ok 9 - has_index() hash index should have the proper diagnostics +ok 10 - has_index() hash index no desc should pass +ok 11 - has_index() hash index no desc should have the proper description +ok 12 - has_index() hash index no desc should have the proper diagnostics +ok 13 - has_index() multi-column should pass +ok 14 - has_index() multi-column should have the proper description +ok 15 - has_index() multi-column should have the proper diagnostics +ok 16 - has_index() multi-column no desc should pass +ok 17 - has_index() multi-column no desc should have the proper description +ok 18 - has_index() multi-column no desc should have the proper diagnostics +ok 19 - has_index() functional should pass +ok 20 - has_index() functional should have the proper description +ok 21 - has_index() functional should have the proper diagnostics +ok 22 - has_index() no cols should pass +ok 23 - has_index() no cols should have the proper description +ok 24 - has_index() no cols should have the proper diagnostics +ok 25 - has_index() hash index should pass +ok 26 - has_index() hash index should have the proper description +ok 27 - has_index() hash index should have the proper diagnostics +ok 28 - has_index() no cols no desc should pass +ok 29 - has_index() no cols no desc should have the proper description +ok 30 - has_index() no cols no desc should have the proper diagnostics +ok 31 - has_index() no cols hash index no desc should pass +ok 32 - has_index() no cols hash index no desc should have the proper description +ok 33 - has_index() no cols hash index no desc should have the proper diagnostics +ok 34 - has_index() no schema single column should pass +ok 35 - has_index() no schema single column should have the proper description +ok 36 - has_index() no schema single column should have the proper diagnostics +ok 37 - has_index() no schema single column no desc should pass +ok 38 - has_index() no schema single column no desc should have the proper description +ok 39 - has_index() no schema single column no desc should have the proper diagnostics +ok 40 - has_index() no schema multi-column should pass +ok 41 - has_index() no schema multi-column should have the proper description +ok 42 - has_index() no schema multi-column should have the proper diagnostics +ok 43 - has_index() no schema multi-column no desc should pass +ok 44 - has_index() no schema multi-column no desc should have the proper description +ok 45 - has_index() no schema multi-column no desc should have the proper diagnostics +ok 46 - has_index() no schema functional should pass +ok 47 - has_index() no schema functional should have the proper description +ok 48 - has_index() no schema functional should have the proper diagnostics +ok 49 - has_index() no schema functional no desc should pass +ok 50 - has_index() no schema functional no desc should have the proper description +ok 51 - has_index() no schema functional no desc should have the proper diagnostics +ok 52 - has_index() no schema or cols should pass +ok 53 - has_index() no schema or cols should have the proper description +ok 54 - has_index() no schema or cols should have the proper diagnostics +ok 55 - has_index() hash index no schema or cols should pass +ok 56 - has_index() hash index no schema or cols should have the proper description +ok 57 - has_index() hash index no schema or cols should have the proper diagnostics +ok 58 - has_index() no schema or cols or desc should pass +ok 59 - has_index() no schema or cols or desc should have the proper description +ok 60 - has_index() no schema or cols or desc should have the proper diagnostics +ok 61 - has_index() hash index no schema or cols or desc should pass +ok 62 - has_index() hash index no schema or cols or desc should have the proper description +ok 63 - has_index() hash index no schema or cols or desc should have the proper diagnostics +ok 64 - has_index() non-existent should fail +ok 65 - has_index() non-existent should have the proper description +ok 66 - has_index() non-existent should have the proper diagnostics +ok 67 - has_index() missing should fail +ok 68 - has_index() missing should have the proper description +ok 69 - has_index() missing should have the proper diagnostics +ok 70 - has_index() invalid should fail +ok 71 - has_index() invalid should have the proper description +ok 72 - has_index() invalid should have the proper diagnostics +ok 73 - has_index() missing column should fail +ok 74 - has_index() missing column should have the proper description +ok 75 - has_index() missing column should have the proper diagnostics +ok 76 - has_index() missing no schema should fail +ok 77 - has_index() missing no schema should have the proper description +ok 78 - has_index() missing no schema should have the proper diagnostics +ok 79 - has_index() invalid no schema should fail +ok 80 - has_index() invalid no schema should have the proper description +ok 81 - has_index() invalid no schema should have the proper diagnostics +ok 82 - has_index() functional fail should fail +ok 83 - has_index() functional fail should have the proper description +ok 84 - has_index() functional fail should have the proper diagnostics +ok 85 - has_index() functional fail no schema should fail +ok 86 - has_index() functional fail no schema should have the proper description +ok 87 - has_index() functional fail no schema should have the proper diagnostics +ok 88 - hasnt_index(schema, table, index, desc) should fail +ok 89 - hasnt_index(schema, table, index, desc) should have the proper description +ok 90 - hasnt_index(schema, table, index, desc) should have the proper diagnostics +ok 91 - hasnt_index(schema, table, index) should fail +ok 92 - hasnt_index(schema, table, index) should have the proper description +ok 93 - hasnt_index(schema, table, index) should have the proper diagnostics +ok 94 - hasnt_index(schema, table, non-index, desc) should pass +ok 95 - hasnt_index(schema, table, non-index, desc) should have the proper description +ok 96 - hasnt_index(schema, table, non-index, desc) should have the proper diagnostics +ok 97 - hasnt_index(schema, table, non-index) should pass +ok 98 - hasnt_index(schema, table, non-index) should have the proper description +ok 99 - hasnt_index(schema, table, non-index) should have the proper diagnostics +ok 100 - hasnt_index(table, index, desc) should fail +ok 101 - hasnt_index(table, index, desc) should have the proper description +ok 102 - hasnt_index(table, index, desc) should have the proper diagnostics +ok 103 - hasnt_index(table, index) should fail +ok 104 - hasnt_index(table, index) should have the proper description +ok 105 - hasnt_index(table, index) should have the proper diagnostics +ok 106 - hasnt_index(table, non-index, desc) should pass +ok 107 - hasnt_index(table, non-index, desc) should have the proper description +ok 108 - hasnt_index(table, non-index, desc) should have the proper diagnostics +ok 109 - hasnt_index(table, non-index) should pass +ok 110 - hasnt_index(table, non-index) should have the proper description +ok 111 - hasnt_index(table, non-index) should have the proper diagnostics +ok 112 - index_is_unique() should pass +ok 113 - index_is_unique() should have the proper description +ok 114 - index_is_unique() should have the proper diagnostics +ok 115 - index_is_unique() no desc should pass +ok 116 - index_is_unique() no desc should have the proper description +ok 117 - index_is_unique() no desc should have the proper diagnostics +ok 118 - index_is_unique() no schema should pass +ok 119 - index_is_unique() no schema should have the proper description +ok 120 - index_is_unique() no schema should have the proper diagnostics +ok 121 - index_is_unique() index only should pass +ok 122 - index_is_unique() index only should have the proper description +ok 123 - index_is_unique() index only should have the proper diagnostics +ok 124 - index_is_unique() on pk should pass +ok 125 - index_is_unique() on pk should have the proper description +ok 126 - index_is_unique() on pk should have the proper diagnostics +ok 127 - index_is_unique() on pk no desc should pass +ok 128 - index_is_unique() on pk no desc should have the proper description +ok 129 - index_is_unique() on pk no desc should have the proper diagnostics +ok 130 - index_is_unique() on pk no schema should pass +ok 131 - index_is_unique() on pk no schema should have the proper description +ok 132 - index_is_unique() on pk no schema should have the proper diagnostics +ok 133 - index_is_unique() on pk index only should pass +ok 134 - index_is_unique() on pk index only should have the proper description +ok 135 - index_is_unique() on pk index only should have the proper diagnostics +ok 136 - index_is_unique() fail should fail +ok 137 - index_is_unique() fail should have the proper description +ok 138 - index_is_unique() fail should have the proper diagnostics +ok 139 - index_is_unique() fail no desc should fail +ok 140 - index_is_unique() fail no desc should have the proper description +ok 141 - index_is_unique() fail no desc should have the proper diagnostics +ok 142 - index_is_unique() fail no schema should fail +ok 143 - index_is_unique() fail no schema should have the proper description +ok 144 - index_is_unique() fail no schema should have the proper diagnostics +ok 145 - index_is_unique() fail index only should fail +ok 146 - index_is_unique() fail index only should have the proper description +ok 147 - index_is_unique() fail index only should have the proper diagnostics +ok 148 - index_is_unique() no such index should fail +ok 149 - index_is_unique() no such index should have the proper description +ok 150 - index_is_unique() no such index should have the proper diagnostics +ok 151 - index_is_primary() should pass +ok 152 - index_is_primary() should have the proper description +ok 153 - index_is_primary() should have the proper diagnostics +ok 154 - index_is_primary() no desc should pass +ok 155 - index_is_primary() no desc should have the proper description +ok 156 - index_is_primary() no desc should have the proper diagnostics +ok 157 - index_is_primary() no schema should pass +ok 158 - index_is_primary() no schema should have the proper description +ok 159 - index_is_primary() no schema should have the proper diagnostics +ok 160 - index_is_primary() index only should pass +ok 161 - index_is_primary() index only should have the proper description +ok 162 - index_is_primary() index only should have the proper diagnostics +ok 163 - index_is_primary() fail should fail +ok 164 - index_is_primary() fail should have the proper description +ok 165 - index_is_primary() fail should have the proper diagnostics +ok 166 - index_is_primary() fail no desc should fail +ok 167 - index_is_primary() fail no desc should have the proper description +ok 168 - index_is_primary() fail no desc should have the proper diagnostics +ok 169 - index_is_primary() fail no schema should fail +ok 170 - index_is_primary() fail no schema should have the proper description +ok 171 - index_is_primary() fail no schema should have the proper diagnostics +ok 172 - index_is_primary() fail index only should fail +ok 173 - index_is_primary() fail index only should have the proper description +ok 174 - index_is_primary() fail index only should have the proper diagnostics +ok 175 - index_is_primary() no such index should fail +ok 176 - index_is_primary() no such index should have the proper description +ok 177 - index_is_primary() no such index should have the proper diagnostics +ok 178 - is_clustered() fail should fail +ok 179 - is_clustered() fail should have the proper description +ok 180 - is_clustered() fail should have the proper diagnostics +ok 181 - is_clustered() fail no desc should fail +ok 182 - is_clustered() fail no desc should have the proper description +ok 183 - is_clustered() fail no desc should have the proper diagnostics +ok 184 - is_clustered() fail no schema should fail +ok 185 - is_clustered() fail no schema should have the proper description +ok 186 - is_clustered() fail no schema should have the proper diagnostics +ok 187 - is_clustered() fail index only should fail +ok 188 - is_clustered() fail index only should have the proper description +ok 189 - is_clustered() fail index only should have the proper diagnostics +ok 190 - is_clustered() should pass +ok 191 - is_clustered() should have the proper description +ok 192 - is_clustered() should have the proper diagnostics +ok 193 - is_clustered() no desc should pass +ok 194 - is_clustered() no desc should have the proper description +ok 195 - is_clustered() no desc should have the proper diagnostics +ok 196 - is_clustered() no schema should pass +ok 197 - is_clustered() no schema should have the proper description +ok 198 - is_clustered() no schema should have the proper diagnostics +ok 199 - is_clustered() index only should pass +ok 200 - is_clustered() index only should have the proper description +ok 201 - is_clustered() index only should have the proper diagnostics +ok 202 - index_is_type() should pass +ok 203 - index_is_type() should have the proper description +ok 204 - index_is_type() should have the proper diagnostics +ok 205 - index_is_type() ci should pass +ok 206 - index_is_type() ci should have the proper description +ok 207 - index_is_type() ci should have the proper diagnostics +ok 208 - index_is_type() no desc should pass +ok 209 - index_is_type() no desc should have the proper description +ok 210 - index_is_type() no desc should have the proper diagnostics +ok 211 - index_is_type() fail should fail +ok 212 - index_is_type() fail should have the proper description +ok 213 - index_is_type() fail should have the proper diagnostics +ok 214 - index_is_type() no schema should pass +ok 215 - index_is_type() no schema should have the proper description +ok 216 - index_is_type() no schema should have the proper diagnostics +ok 217 - index_is_type() no schema fail should fail +ok 218 - index_is_type() no schema fail should have the proper description +ok 219 - index_is_type() no schema fail should have the proper diagnostics +ok 220 - index_is_type() no table should pass +ok 221 - index_is_type() no table should have the proper description +ok 222 - index_is_type() no table should have the proper diagnostics +ok 223 - index_is_type() no table fail should fail +ok 224 - index_is_type() no table fail should have the proper description +ok 225 - index_is_type() no table fail should have the proper diagnostics +ok 226 - index_is_type() hash should pass +ok 227 - index_is_type() hash should have the proper description +ok 228 - index_is_type() hash should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 13866519aa7c..f87debfb32b1 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2207,6 +2207,32 @@ RETURNS NAME[] AS $$ ); $$ 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 ct.relname = $2 + AND ci.relname = $3 + AND n.nspname = $1 + ); +$$ 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 + ); +$$ LANGUAGE sql; + CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME, NAME) RETURNS TEXT AS $$ SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) @@ -2342,7 +2368,7 @@ BEGIN -- Not a functional index. IF _is_schema( $1 ) THEN -- Looking for schema.table index. - RETURN ok ( _iexpr( $1, $2, $3 ) IS NOT NULL, $4); + RETURN ok ( _have_index( $1, $2, $3 ), $4); END IF; -- Looking for particular columns. RETURN has_index( $1, $2, ARRAY[$3], $4 ); @@ -2385,7 +2411,7 @@ RETURNS TEXT AS $$ BEGIN IF _is_schema($1) THEN -- ( schema, table, index ) - RETURN ok( _iexpr( $1, $2, $3 ) IS NOT NULL, 'Index ' || quote_ident($3) || ' should exist' ); + 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' ); @@ -2398,16 +2424,48 @@ 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( _iexpr( $1, $2 ) IS NOT NULL, $3 ) + 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( _iexpr( $1, $2 ) IS NOT NULL, 'Index ' || quote_ident($2) || ' should exist' ); + 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; + -- index_is_unique( schema, table, index, description ) CREATE OR REPLACE FUNCTION index_is_unique ( NAME, NAME, NAME, text ) RETURNS TEXT AS $$ diff --git a/sql/index.sql b/sql/index.sql index 78da130ef801..f79141893270 100644 --- a/sql/index.sql +++ b/sql/index.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(180); +SELECT plan(228); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -12,6 +12,7 @@ CREATE TABLE public.sometab( numb NUMERIC(10, 2), myint NUMERIC(8) ); +CREATE INDEX idx_hey ON public.sometab(numb); CREATE INDEX idx_foo ON public.sometab using hash(name); CREATE INDEX idx_bar ON public.sometab(numb, name); CREATE UNIQUE INDEX idx_baz ON public.sometab(LOWER(name)); @@ -21,7 +22,7 @@ RESET client_min_messages; -- Test has_index(). SELECT * FROM check_test( - has_index( 'public', 'sometab', 'idx_foo', 'name', 'whatever' ), + has_index( 'public', 'sometab', 'idx_hey', 'numb', 'whatever' ), true, 'has_index() single column', 'whatever', @@ -29,9 +30,25 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_index( 'public', 'sometab', 'idx_foo', 'name'::name ), + has_index( 'public', 'sometab', 'idx_hey', 'numb'::name ), true, 'has_index() single column no desc', + 'Index idx_hey should exist', + '' +); + +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'idx_foo', 'name', 'whatever' ), + true, + 'has_index() hash index', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'idx_foo', 'name'::name ), + true, + 'has_index() hash index no desc', 'Index idx_foo should exist', '' ); @@ -68,6 +85,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'idx_foo', 'whatever' ), + true, + 'has_index() hash index', + 'whatever', + '' +); + SELECT * FROM check_test( has_index( 'public', 'sometab', 'idx_baz'::name ), true, @@ -76,6 +101,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'idx_foo'::name ), + true, + 'has_index() no cols hash index no desc', + 'Index idx_foo should exist', + '' +); + SELECT * FROM check_test( has_index( 'sometab', 'idx_foo', 'name', 'whatever' ), true, @@ -132,6 +165,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + has_index( 'sometab', 'idx_foo', 'whatever' ), + true, + 'has_index() hash index no schema or cols', + 'whatever', + '' +); + SELECT * FROM check_test( has_index( 'sometab', 'idx_baz' ), true, @@ -140,7 +181,23 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + has_index( 'sometab', 'idx_foo' ), + true, + 'has_index() hash index no schema or cols or desc', + 'Index idx_foo should exist', + '' +); + -- Check failure diagnostics. +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'idx_heya', 'numb', 'whatever' ), + false, + 'has_index() non-existent', + 'whatever', + 'Index idx_heya ON public.sometab not found' +); + SELECT * FROM check_test( has_index( 'public', 'sometab', 'blah', ARRAY['numb', 'name'], 'whatever' ), false, @@ -158,6 +215,15 @@ SELECT * FROM check_test( want: idx_bar ON public.sometab(name, id)' ); +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'idx_bar', ARRAY['name'], 'whatever' ), + false, + 'has_index() missing column', + 'whatever', + ' have: idx_bar ON public.sometab(numb, name) + want: idx_bar ON public.sometab(name)' +); + SELECT * FROM check_test( has_index( 'sometab', 'blah', ARRAY['numb', 'name'], 'whatever' ), false, @@ -193,6 +259,73 @@ SELECT * FROM check_test( want: idx_baz ON sometab(lower(wank))' ); +/****************************************************************************/ +-- Test hasnt_index(). + +SELECT * FROM check_test( + hasnt_index( 'public', 'sometab', 'idx_foo', 'whatever' ), + false, + 'hasnt_index(schema, table, index, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_index( 'public', 'sometab', 'idx_foo'::name ), + false, + 'hasnt_index(schema, table, index)', + 'Index idx_foo should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_index( 'public', 'sometab', 'idx_blah', 'whatever' ), + true, + 'hasnt_index(schema, table, non-index, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_index( 'public', 'sometab', 'idx_blah'::name ), + true, + 'hasnt_index(schema, table, non-index)', + 'Index idx_blah should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_index( 'sometab', 'idx_foo', 'whatever' ), + false, + 'hasnt_index(table, index, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_index( 'sometab', 'idx_foo'::name ), + false, + 'hasnt_index(table, index)', + 'Index idx_foo should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_index( 'sometab', 'idx_blah', 'whatever' ), + true, + 'hasnt_index(table, non-index, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_index( 'sometab', 'idx_blah'::name ), + true, + 'hasnt_index(table, non-index)', + 'Index idx_blah should not exist', + '' +); + /****************************************************************************/ -- Test index_is_unique(). SELECT * FROM check_test( From 0e94595c222407271e4731951b28d9cbe9fc1026 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 26 May 2009 15:11:29 -0700 Subject: [PATCH 0366/1195] Renamed `can_ok()` to `has_function()` and deprecated the old name. --- Changes | 4 ++ README.pgtap | 47 ++------------- expected/cantap.out | 86 +++++++++++++++++++------- pgtap.sql.in | 104 +++++++++++++++++++++++++------- sql/cantap.sql | 143 ++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 287 insertions(+), 97 deletions(-) diff --git a/Changes b/Changes index 0fcd165db879..bfbd66a51e79 100644 --- a/Changes +++ b/Changes @@ -21,6 +21,10 @@ Revision history for pgTAP * Added `hasnt_trigger()` and `hasnt_index()`. * Documented `hasnt_enum()`, which has actually been around for as long as `has_enum()`. +* Changed `can_ok()` to `has_function()`, so that it's named like all of the + other functions that check for the presence of database objects. The old + `can_ok()` function is still available as an alias, but it emits a warning + and will be removed in a future version of pgTAP. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index 35c30606b681..585ae82cd3f0 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1379,7 +1379,7 @@ If `:schema` is omitted, then `can()` will look for functions defined in schemas defined in the search path. No matter how many functions are listed in `:functions[]`, a single call to `can()` counts as one test. If you want otherwise, call `can()` once for each function -- or better yet, use -`can_ok()`. +`has_function()`. If any of the functions are not defined, the test will fail and the diagnostics will output a list of the functions that are missing, like so: @@ -1388,48 +1388,9 @@ diagnostics will output a list of the functions that are missing, like so: # pg_catalog.foo() missing # pg_catalog.bar() missing -### `can_ok( schema, function, args[], description )` ### -### `can_ok( schema, function, args[] )` ### -### `can_ok( schema, function, description )` ### -### `can_ok( schema, function )` ### -### `can_ok( function, args[], description )` ### -### `can_ok( function, args[] )` ### -### `can_ok( function, description )` ### -### `can_ok( function )` ### - - SELECT can_ok( - 'pg_catalog', - 'decode', - ARRAY[ 'text', 'text' ], - 'Function decode(text, text) should exist' - ); - - SELECT can_ok( 'do_something' ); - SELECT can_ok( 'do_something', ARRAY['integer'] ); - SELECT can_ok( 'do_something', ARRAY['numeric'] ); - -Checks to be sure that the given function exists in the named schema and with -the specified argument data types. If `:schema` is omitted, `can_ok()` will -search for the function in the schemas defined in the search path. If -`:args[]` is omitted, `can_ok()` will see if the function exists without -regard to its arguments. - -The `:args[]` argument should be formatted as it would be displayed in the -view of a function using the `\df` command in `psql`. For example, even if you -have a numeric column with a precision of 8, you should specify -`ARRAY['numeric']`". If you created a `varchar(64)` column, you should pass -the `:args[]` argument as `ARRAY['character varying']`. - -If you wish to use the two-argument form of `can_ok()`, specifying only the -schema and the function name, you must cast the `:function` argument to -`:name` in order to disambiguate it from from the -`can_ok(`:function`, `:description)` form. If you neglect to do so, your -results will be unexpected. - -Also, if you use the string form to specify the `:args[]` array, be sure to -cast it to `name[]` to disambiguate it from a text string: - - SELECT can_ok( 'lower', '{text}'::name[] ); +**Deprecation notice:** The old name for this test function `can_ok()`, is +still available, but emits a warning when called. It will be removed in a +future version of pgTAP. ### `has_cast( source_type, target_type, schema, function, description )` ### ### `has_cast( source_type, target_type, schema, function )` ### diff --git a/expected/cantap.out b/expected/cantap.out index 78a0afecb893..0517e236100a 100644 --- a/expected/cantap.out +++ b/expected/cantap.out @@ -1,5 +1,5 @@ \unset ECHO -1..63 +1..105 ok 1 - simple function should pass ok 2 - simple function should have the proper description ok 3 - simple function should have the proper diagnostics @@ -42,24 +42,66 @@ ok 39 - custom numeric function should have the proper diagnostics ok 40 - failure output should fail ok 41 - failure output should have the proper description ok 42 - failure output should have the proper diagnostics -ok 43 - can(schema) with desc should pass -ok 44 - can(schema) with desc should have the proper description -ok 45 - can(schema) with desc should have the proper diagnostics -ok 46 - can(schema) should pass -ok 47 - can(schema) should have the proper description -ok 48 - can(schema) should have the proper diagnostics -ok 49 - fail can(schema) with desc should fail -ok 50 - fail can(schema) with desc should have the proper description -ok 51 - fail can(schema) with desc should have the proper diagnostics -ok 52 - fail can(someschema) with desc should fail -ok 53 - fail can(someschema) with desc should have the proper description -ok 54 - fail can(someschema) with desc should have the proper diagnostics -ok 55 - can() with desc should pass -ok 56 - can() with desc should have the proper description -ok 57 - can() with desc should have the proper diagnostics -ok 58 - can(schema) should pass -ok 59 - can(schema) should have the proper description -ok 60 - can(schema) should have the proper diagnostics -ok 61 - fail can() with desc should fail -ok 62 - fail can() with desc should have the proper description -ok 63 - fail can() with desc should have the proper diagnostics +ok 43 - simple function should pass +ok 44 - simple function should have the proper description +ok 45 - simple function should have the proper diagnostics +ok 46 - simple schema.function should pass +ok 47 - simple schema.function should have the proper description +ok 48 - simple schema.function should have the proper diagnostics +ok 49 - simple function desc should pass +ok 50 - simple function desc should have the proper description +ok 51 - simple function desc should have the proper diagnostics +ok 52 - simple with 0 args should pass +ok 53 - simple with 0 args should have the proper description +ok 54 - simple with 0 args should have the proper diagnostics +ok 55 - simple with 0 args desc should pass +ok 56 - simple with 0 args desc should have the proper description +ok 57 - simple with 0 args desc should have the proper diagnostics +ok 58 - simple schema.func with 0 args should pass +ok 59 - simple schema.func with 0 args should have the proper description +ok 60 - simple schema.func with 0 args should have the proper diagnostics +ok 61 - simple schema.func with desc should pass +ok 62 - simple schema.func with desc should have the proper description +ok 63 - simple schema.func with desc should have the proper diagnostics +ok 64 - simple scchma.func with 0 args, desc should pass +ok 65 - simple scchma.func with 0 args, desc should have the proper description +ok 66 - simple scchma.func with 0 args, desc should have the proper diagnostics +ok 67 - simple function with 1 arg should pass +ok 68 - simple function with 1 arg should have the proper description +ok 69 - simple function with 1 arg should have the proper diagnostics +ok 70 - simple function with 2 args should pass +ok 71 - simple function with 2 args should have the proper description +ok 72 - simple function with 2 args should have the proper diagnostics +ok 73 - simple array function should pass +ok 74 - simple array function should have the proper description +ok 75 - simple array function should have the proper diagnostics +ok 76 - custom array function should pass +ok 77 - custom array function should have the proper description +ok 78 - custom array function should have the proper diagnostics +ok 79 - custom numeric function should pass +ok 80 - custom numeric function should have the proper description +ok 81 - custom numeric function should have the proper diagnostics +ok 82 - failure output should fail +ok 83 - failure output should have the proper description +ok 84 - failure output should have the proper diagnostics +ok 85 - can(schema) with desc should pass +ok 86 - can(schema) with desc should have the proper description +ok 87 - can(schema) with desc should have the proper diagnostics +ok 88 - can(schema) should pass +ok 89 - can(schema) should have the proper description +ok 90 - can(schema) should have the proper diagnostics +ok 91 - fail can(schema) with desc should fail +ok 92 - fail can(schema) with desc should have the proper description +ok 93 - fail can(schema) with desc should have the proper diagnostics +ok 94 - fail can(someschema) with desc should fail +ok 95 - fail can(someschema) with desc should have the proper description +ok 96 - fail can(someschema) with desc should have the proper diagnostics +ok 97 - can() with desc should pass +ok 98 - can() with desc should have the proper description +ok 99 - can() with desc should have the proper diagnostics +ok 100 - can(schema) should pass +ok 101 - can(schema) should have the proper description +ok 102 - can(schema) should have the proper diagnostics +ok 103 - fail can() with desc should fail +ok 104 - fail can() with desc should have the proper description +ok 105 - fail can() with desc should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index f87debfb32b1..76dcb57bfc07 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2011,8 +2011,8 @@ RETURNS TEXT AS $$ SELECT fk_ok( $1, ARRAY[$2], $3, ARRAY[$4] ); $$ LANGUAGE sql; --- can_ok( schema, func_name, args[], description ) -CREATE OR REPLACE FUNCTION can_ok ( NAME, NAME, NAME[], TEXT ) +-- has_function( schema, func_name, args[], description ) +CREATE OR REPLACE FUNCTION has_function ( NAME, NAME, NAME[], TEXT ) RETURNS TEXT AS $$ SELECT ok( EXISTS( @@ -2026,15 +2026,15 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; --- can_ok( schema, func_name, args[] ) -CREATE OR REPLACE FUNCTION can_ok( NAME, NAME, NAME[] ) +-- has_function( schema, func_name, args[] ) +CREATE OR REPLACE FUNCTION has_function( NAME, NAME, NAME[] ) RETURNS TEXT AS $$ - SELECT can_ok( $1, $2, $3, 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + SELECT has_function( $1, $2, $3, 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || array_to_string($3, ', ') || ') should exist' ); $$ LANGUAGE sql; --- can_ok( schema, func_name, description ) -CREATE OR REPLACE FUNCTION can_ok ( NAME, NAME, TEXT ) +-- has_function( schema, func_name, description ) +CREATE OR REPLACE FUNCTION has_function ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ SELECT ok( EXISTS( @@ -2047,14 +2047,14 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; --- can_ok( schema, func_name ) -CREATE OR REPLACE FUNCTION can_ok( NAME, NAME ) +-- has_function( schema, func_name ) +CREATE OR REPLACE FUNCTION has_function( NAME, NAME ) RETURNS TEXT AS $$ - SELECT can_ok( $1, $2, 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should exist' ); + SELECT has_function( $1, $2, 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should exist' ); $$ LANGUAGE sql; --- can_ok( func_name, args[], description ) -CREATE OR REPLACE FUNCTION can_ok ( NAME, NAME[], TEXT ) +-- has_function( func_name, args[], description ) +CREATE OR REPLACE FUNCTION has_function ( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ SELECT ok( EXISTS( @@ -2067,15 +2067,15 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; --- can_ok( func_name, args[] ) -CREATE OR REPLACE FUNCTION can_ok( NAME, NAME[] ) +-- has_function( func_name, args[] ) +CREATE OR REPLACE FUNCTION has_function( NAME, NAME[] ) RETURNS TEXT AS $$ - SELECT can_ok( $1, $2, 'Function ' || quote_ident($1) || '(' || + SELECT has_function( $1, $2, 'Function ' || quote_ident($1) || '(' || array_to_string($2, ', ') || ') should exist' ); $$ LANGUAGE sql; --- can_ok( func_name, description ) -CREATE OR REPLACE FUNCTION can_ok( NAME, TEXT ) +-- has_function( func_name, description ) +CREATE OR REPLACE FUNCTION has_function( NAME, TEXT ) RETURNS TEXT AS $$ SELECT ok( EXISTS( @@ -2087,12 +2087,76 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; --- can_ok( func_name ) -CREATE OR REPLACE FUNCTION can_ok( NAME ) +-- has_function( func_name ) +CREATE OR REPLACE FUNCTION has_function( NAME ) RETURNS TEXT AS $$ - SELECT can_ok( $1, 'Function ' || quote_ident($1) || '() should exist' ); + SELECT has_function( $1, 'Function ' || quote_ident($1) || '() should exist' ); $$ LANGUAGE sql; +CREATE OR REPLACE FUNCTION can_ok ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +BEGIN + RAISE WARNING '%', 'can_ok() is deprecated; use has_function() instead'; + RETURN has_function($1, $2, $3, $4); +END; +$$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION can_ok( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ +BEGIN + RAISE WARNING '%', 'can_ok() is deprecated; use has_function() instead'; + RETURN has_function($1, $2, $3); +END; +$$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION can_ok ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ +BEGIN + RAISE WARNING '%', 'can_ok() is deprecated; use has_function() instead'; + RETURN has_function($1, $2, $3); +END; +$$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION can_ok( NAME, NAME ) +RETURNS TEXT AS $$ +BEGIN + RAISE WARNING '%', 'can_ok() is deprecated; use has_function() instead'; + RETURN has_function($1, $2); +END; +$$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION can_ok ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +BEGIN + RAISE WARNING '%', 'can_ok() is deprecated; use has_function() instead'; + RETURN has_function($1, $2, $3); +END; +$$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION can_ok( NAME, NAME[] ) +RETURNS TEXT AS $$ +BEGIN + RAISE WARNING '%', 'can_ok() is deprecated; use has_function() instead'; + RETURN has_function($1, $2); +END; +$$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION can_ok( NAME, TEXT ) +RETURNS TEXT AS $$ +BEGIN + RAISE WARNING '%', 'can_ok() is deprecated; use has_function() instead'; + RETURN has_function($1, $2); +END; +$$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION can_ok( NAME ) +RETURNS TEXT AS $$ +BEGIN + RAISE WARNING '%', 'can_ok() is deprecated; use has_function() instead'; + RETURN has_function($1); +END; +$$ LANGUAGE PLPGSQL; + CREATE OR REPLACE FUNCTION _pg_sv_type_array( OID[] ) RETURNS NAME[] AS $$ SELECT ARRAY( diff --git a/sql/cantap.sql b/sql/cantap.sql index 3678e5570e39..cb5a2afc4d39 100644 --- a/sql/cantap.sql +++ b/sql/cantap.sql @@ -1,10 +1,139 @@ \unset ECHO \i test_setup.sql -SELECT plan(63); +SELECT plan(105); +--SELECT * FROM no_plan(); + CREATE SCHEMA someschema; CREATE FUNCTION someschema.huh () RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL; ---SELECT * FROM no_plan(); + +-- XXX Delete when can_ok() is removed. +SET client_min_messages = error; + +/****************************************************************************/ +-- Test has_function(). +SELECT * FROM check_test( + has_function( 'now' ), + true, + 'simple function', + 'Function now() should exist', + '' +); + +SELECT * FROM check_test( + has_function( 'pg_catalog', 'now'::name ), + true, + 'simple schema.function', + 'Function pg_catalog.now() should exist', + '' +); + +SELECT * FROM check_test( + has_function( 'now', 'whatever' ), + true, + 'simple function desc', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_function( 'now', '{}'::name[] ), + true, + 'simple with 0 args', + 'Function now() should exist', + '' +); + +SELECT * FROM check_test( + has_function( 'now', '{}'::name[], 'whatever' ), + true, + 'simple with 0 args desc', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_function( 'pg_catalog', 'now', '{}'::name[] ), + true, + 'simple schema.func with 0 args', + 'Function pg_catalog.now() should exist', + '' +); + +SELECT * FROM check_test( + has_function( 'pg_catalog', 'now', 'whatever' ), + true, + 'simple schema.func with desc', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_function( 'pg_catalog', 'now', '{}'::name[], 'whatever' ), + true, + 'simple scchma.func with 0 args, desc', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_function( 'lower', '{text}'::name[] ), + true, + 'simple function with 1 arg', + 'Function lower(text) should exist', + '' +); + +SELECT * FROM check_test( + has_function( 'decode', '{text,text}'::name[] ), + true, + 'simple function with 2 args', + 'Function decode(text, text) should exist', + '' +); + +SELECT * FROM check_test( + has_function( 'array_cat', ARRAY['anyarray','anyarray'] ), + true, + 'simple array function', + 'Function array_cat(anyarray, anyarray) should exist', + '' +); + +-- Check a custom function with an array argument. +CREATE FUNCTION __cat__ (text[]) RETURNS BOOLEAN +AS 'SELECT TRUE' +LANGUAGE SQL; + +SELECT * FROM check_test( + has_function( '__cat__', '{text[]}'::name[] ), + true, + 'custom array function', + 'Function __cat__(text[]) should exist', + '' +); + +-- Check a custom function with a numeric argument. +CREATE FUNCTION __cat__ (numeric(10,2)) RETURNS BOOLEAN +AS 'SELECT TRUE' +LANGUAGE SQL; + +SELECT * FROM check_test( + has_function( '__cat__', '{numeric}'::name[] ), + true, + 'custom numeric function', + 'Function __cat__(numeric) should exist', + '' +); + +-- Check failure output. +SELECT * FROM check_test( + has_function( '__cat__', '{varchar[]}'::name[] ), + false, + 'failure output', + 'Function __cat__(varchar[]) should exist', + '' -- No diagnostics. +); /****************************************************************************/ -- Test can_ok(). @@ -96,11 +225,6 @@ SELECT * FROM check_test( '' ); --- Check a custom function with an array argument. -CREATE FUNCTION __cat__ (text[]) RETURNS BOOLEAN -AS 'SELECT TRUE' -LANGUAGE SQL; - SELECT * FROM check_test( can_ok( '__cat__', '{text[]}'::name[] ), true, @@ -109,11 +233,6 @@ SELECT * FROM check_test( '' ); --- Check a custom function with a numeric argument. -CREATE FUNCTION __cat__ (numeric(10,2)) RETURNS BOOLEAN -AS 'SELECT TRUE' -LANGUAGE SQL; - SELECT * FROM check_test( can_ok( '__cat__', '{numeric}'::name[] ), true, From 790357ef1d09a0926996b24cdc6506cd670707df Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 26 May 2009 15:15:15 -0700 Subject: [PATCH 0367/1195] Don't know how I managed to delete a bunch of stuff in that last commit, rather than properly modify it. I blame Emacs. --- README.pgtap | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index 585ae82cd3f0..36eaa1138c6a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1388,7 +1388,50 @@ diagnostics will output a list of the functions that are missing, like so: # pg_catalog.foo() missing # pg_catalog.bar() missing -**Deprecation notice:** The old name for this test function `can_ok()`, is +### `has_function( schema, function, args[], description )` ### +### `has_function( schema, function, args[] )` ### +### `has_function( schema, function, description )` ### +### `has_function( schema, function )` ### +### `has_function( function, args[], description )` ### +### `has_function( function, args[] )` ### +### `has_function( function, description )` ### +### `has_function( function )` ### + + SELECT has_function( + 'pg_catalog', + 'decode', + ARRAY[ 'text', 'text' ], + 'Function decode(text, text) should exist' + ); + + SELECT has_function( 'do_something' ); + SELECT has_function( 'do_something', ARRAY['integer'] ); + SELECT has_function( 'do_something', ARRAY['numeric'] ); + +Checks to be sure that the given function exists in the named schema and with +the specified argument data types. If `:schema` is omitted, `has_function()` will +search for the function in the schemas defined in the search path. If +`:args[]` is omitted, `has_function()` will see if the function exists without +regard to its arguments. + +The `:args[]` argument should be formatted as it would be displayed in the +view of a function using the `\df` command in `psql`. For example, even if you +have a numeric column with a precision of 8, you should specify +`ARRAY['numeric']`". If you created a `varchar(64)` column, you should pass +the `:args[]` argument as `ARRAY['character varying']`. + +If you wish to use the two-argument form of `has_function()`, specifying only the +schema and the function name, you must cast the `:function` argument to +`:name` in order to disambiguate it from from the +`has_function(`:function`, `:description)` form. If you neglect to do so, your +results will be unexpected. + +Also, if you use the string form to specify the `:args[]` array, be sure to +cast it to `name[]` to disambiguate it from a text string: + + SELECT has_function( 'lower', '{text}'::name[] ); + +**Deprecation notice:** The old name for this test function, `can_ok()`, is still available, but emits a warning when called. It will be removed in a future version of pgTAP. From b47515bf6f181220f7eb17c3b4d89688ccb26f73 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 26 May 2009 15:18:34 -0700 Subject: [PATCH 0368/1195] Moved the docs for `can_ok()`. Also removed duplicate documentation for `hasnt_enum()`. Duh. --- README.pgtap | 57 +++++++++++++++++++++------------------------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/README.pgtap b/README.pgtap index 36eaa1138c6a..02d69abb04b5 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1363,31 +1363,6 @@ associated must be visible in the search path. This function is the inverse of `has_rule()`. The test passes if the specified rule does *not* exist. -### `can( schema, functions[], description )` ### -### `can( schema, functions[] )` ### -### `can( functions[], description )` ### -### `can( functions[] )` ### - - SELECT can( 'pg_catalog', ARRAY['upper', 'lower'] ); - -Checks to be sure that `:schema` has `:functions[]` defined. This is subtly -different from `functions_are()`. `functions_are()` fails if the functions -defined in `:schema` are not exactly the functions defined in `:functions[]`. -`can()`, on the other hand, just makes sure that `functions[]` exist. - -If `:schema` is omitted, then `can()` will look for functions defined in -schemas defined in the search path. No matter how many functions are listed in -`:functions[]`, a single call to `can()` counts as one test. If you want -otherwise, call `can()` once for each function -- or better yet, use -`has_function()`. - -If any of the functions are not defined, the test will fail and the -diagnostics will output a list of the functions that are missing, like so: - - # Failed test 52: "Schema pg_catalog can" - # pg_catalog.foo() missing - # pg_catalog.bar() missing - ### `has_function( schema, function, args[], description )` ### ### `has_function( schema, function, args[] )` ### ### `has_function( schema, function, description )` ### @@ -2277,16 +2252,30 @@ Tables aren't the only objects in the database, as you well know. These assertions close the gap by letting you test the attributes of other database objects. -### `hasnt_enum( schema, enum, description )` ### -### `hasnt_enum( schema, enum )` ### -### `hasnt_enum( enum, description )` ### -### `hasnt_enum( enum )` ### +### `can( schema, functions[], description )` ### +### `can( schema, functions[] )` ### +### `can( functions[], description )` ### +### `can( functions[] )` ### - SELECT hasnt_enum( - 'myschema', - 'someenum', - 'There should be no enum myschema.someenum' - ); + SELECT can( 'pg_catalog', ARRAY['upper', 'lower'] ); + +Checks to be sure that `:schema` has `:functions[]` defined. This is subtly +different from `functions_are()`. `functions_are()` fails if the functions +defined in `:schema` are not exactly the functions defined in `:functions[]`. +`can()`, on the other hand, just makes sure that `functions[]` exist. + +If `:schema` is omitted, then `can()` will look for functions defined in +schemas defined in the search path. No matter how many functions are listed in +`:functions[]`, a single call to `can()` counts as one test. If you want +otherwise, call `can()` once for each function -- or better yet, use +`has_function()`. + +If any of the functions are not defined, the test will fail and the +diagnostics will output a list of the functions that are missing, like so: + + # Failed test 52: "Schema pg_catalog can" + # pg_catalog.foo() missing + # pg_catalog.bar() missing This function is the inverse of `has_enum()`. The test passes if the specified enum does *not* exist. From a035a1136d6ebafb707d4497fa00a4e387df1d17 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 26 May 2009 15:40:01 -0700 Subject: [PATCH 0369/1195] Added `hasnt_function()`. * Added `hasnt_function()`. * Renamed `sql/cantap.sql` to `sql/functap.sql`. * Renamed `expected/cantap.out` to `expected/functap.out`. --- Changes | 4 +- README.pgtap | 24 +++- expected/{cantap.out => functap.out} | 115 ++++++++++++------- pgtap.sql.in | 160 ++++++++++++++++++++------- sql/{cantap.sql => functap.sql} | 108 +++++++++++++++++- 5 files changed, 328 insertions(+), 83 deletions(-) rename expected/{cantap.out => functap.out} (50%) rename sql/{cantap.sql => functap.sql} (74%) diff --git a/Changes b/Changes index bfbd66a51e79..f4851509c22b 100644 --- a/Changes +++ b/Changes @@ -18,13 +18,11 @@ Revision history for pgTAP * Added a `has_trigger(table, trigger, description)`. Note that this means that if you were previously using `has_trigger(schema, table, trigger)`, you will need to cast the third argument to `NAME` to get it working again. -* Added `hasnt_trigger()` and `hasnt_index()`. -* Documented `hasnt_enum()`, which has actually been around for as long as - `has_enum()`. * Changed `can_ok()` to `has_function()`, so that it's named like all of the other functions that check for the presence of database objects. The old `can_ok()` function is still available as an alias, but it emits a warning and will be removed in a future version of pgTAP. +* Added `hasnt_trigger()`, `hasnt_index()`, and `hasnt_function()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index 02d69abb04b5..32dc6d13db6f 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1410,6 +1410,29 @@ cast it to `name[]` to disambiguate it from a text string: still available, but emits a warning when called. It will be removed in a future version of pgTAP. +### `hasnt_function( schema, function, args[], description )` ### +### `hasnt_function( schema, function, args[] )` ### +### `hasnt_function( schema, function, description )` ### +### `hasnt_function( schema, function )` ### +### `hasnt_function( function, args[], description )` ### +### `hasnt_function( function, args[] )` ### +### `hasnt_function( function, description )` ### +### `hasnt_function( function )` ### + + SELECT hasnt_function( + 'pg_catalog', + 'alamode', + ARRAY[ 'text', 'text' ], + 'Function alamode(text, text) should not exist' + ); + + SELECT hasnt_function( 'bogus' ); + SELECT hasnt_function( 'bogus', ARRAY['integer'] ); + SELECT hasnt_function( 'bogus', ARRAY['numeric'] ); + +This function is the inverse of `has_function()`. The test passes if the +specified function (optionally with the specified signature) does *not* exist. + ### `has_cast( source_type, target_type, schema, function, description )` ### ### `has_cast( source_type, target_type, schema, function )` ### ### `has_cast( source_type, target_type, function, description )` ### @@ -3033,7 +3056,6 @@ To Do * `sequence_increments_by()` * `sequence_starts_at()` * `sequence_cycles()` - * `hasnt_function()` Supported Versions ----------------- diff --git a/expected/cantap.out b/expected/functap.out similarity index 50% rename from expected/cantap.out rename to expected/functap.out index 0517e236100a..503072626108 100644 --- a/expected/cantap.out +++ b/expected/functap.out @@ -1,5 +1,5 @@ \unset ECHO -1..105 +1..144 ok 1 - simple function should pass ok 2 - simple function should have the proper description ok 3 - simple function should have the proper diagnostics @@ -42,66 +42,105 @@ ok 39 - custom numeric function should have the proper diagnostics ok 40 - failure output should fail ok 41 - failure output should have the proper description ok 42 - failure output should have the proper diagnostics -ok 43 - simple function should pass +ok 43 - simple function should fail ok 44 - simple function should have the proper description ok 45 - simple function should have the proper diagnostics -ok 46 - simple schema.function should pass +ok 46 - simple schema.function should fail ok 47 - simple schema.function should have the proper description ok 48 - simple schema.function should have the proper diagnostics -ok 49 - simple function desc should pass +ok 49 - simple function desc should fail ok 50 - simple function desc should have the proper description ok 51 - simple function desc should have the proper diagnostics -ok 52 - simple with 0 args should pass +ok 52 - simple with 0 args should fail ok 53 - simple with 0 args should have the proper description ok 54 - simple with 0 args should have the proper diagnostics -ok 55 - simple with 0 args desc should pass +ok 55 - simple with 0 args desc should fail ok 56 - simple with 0 args desc should have the proper description ok 57 - simple with 0 args desc should have the proper diagnostics -ok 58 - simple schema.func with 0 args should pass +ok 58 - simple schema.func with 0 args should fail ok 59 - simple schema.func with 0 args should have the proper description ok 60 - simple schema.func with 0 args should have the proper diagnostics -ok 61 - simple schema.func with desc should pass +ok 61 - simple schema.func with desc should fail ok 62 - simple schema.func with desc should have the proper description ok 63 - simple schema.func with desc should have the proper diagnostics -ok 64 - simple scchma.func with 0 args, desc should pass +ok 64 - simple scchma.func with 0 args, desc should fail ok 65 - simple scchma.func with 0 args, desc should have the proper description ok 66 - simple scchma.func with 0 args, desc should have the proper diagnostics -ok 67 - simple function with 1 arg should pass +ok 67 - simple function with 1 arg should fail ok 68 - simple function with 1 arg should have the proper description ok 69 - simple function with 1 arg should have the proper diagnostics -ok 70 - simple function with 2 args should pass +ok 70 - simple function with 2 args should fail ok 71 - simple function with 2 args should have the proper description ok 72 - simple function with 2 args should have the proper diagnostics -ok 73 - simple array function should pass +ok 73 - simple array function should fail ok 74 - simple array function should have the proper description ok 75 - simple array function should have the proper diagnostics -ok 76 - custom array function should pass +ok 76 - custom array function should fail ok 77 - custom array function should have the proper description ok 78 - custom array function should have the proper diagnostics -ok 79 - custom numeric function should pass +ok 79 - custom numeric function should fail ok 80 - custom numeric function should have the proper description ok 81 - custom numeric function should have the proper diagnostics -ok 82 - failure output should fail -ok 83 - failure output should have the proper description -ok 84 - failure output should have the proper diagnostics -ok 85 - can(schema) with desc should pass -ok 86 - can(schema) with desc should have the proper description -ok 87 - can(schema) with desc should have the proper diagnostics -ok 88 - can(schema) should pass -ok 89 - can(schema) should have the proper description -ok 90 - can(schema) should have the proper diagnostics -ok 91 - fail can(schema) with desc should fail -ok 92 - fail can(schema) with desc should have the proper description -ok 93 - fail can(schema) with desc should have the proper diagnostics -ok 94 - fail can(someschema) with desc should fail -ok 95 - fail can(someschema) with desc should have the proper description -ok 96 - fail can(someschema) with desc should have the proper diagnostics -ok 97 - can() with desc should pass -ok 98 - can() with desc should have the proper description -ok 99 - can() with desc should have the proper diagnostics -ok 100 - can(schema) should pass -ok 101 - can(schema) should have the proper description -ok 102 - can(schema) should have the proper diagnostics -ok 103 - fail can() with desc should fail -ok 104 - fail can() with desc should have the proper description -ok 105 - fail can() with desc should have the proper diagnostics +ok 82 - simple function should pass +ok 83 - simple function should have the proper description +ok 84 - simple function should have the proper diagnostics +ok 85 - simple schema.function should pass +ok 86 - simple schema.function should have the proper description +ok 87 - simple schema.function should have the proper diagnostics +ok 88 - simple function desc should pass +ok 89 - simple function desc should have the proper description +ok 90 - simple function desc should have the proper diagnostics +ok 91 - simple with 0 args should pass +ok 92 - simple with 0 args should have the proper description +ok 93 - simple with 0 args should have the proper diagnostics +ok 94 - simple with 0 args desc should pass +ok 95 - simple with 0 args desc should have the proper description +ok 96 - simple with 0 args desc should have the proper diagnostics +ok 97 - simple schema.func with 0 args should pass +ok 98 - simple schema.func with 0 args should have the proper description +ok 99 - simple schema.func with 0 args should have the proper diagnostics +ok 100 - simple schema.func with desc should pass +ok 101 - simple schema.func with desc should have the proper description +ok 102 - simple schema.func with desc should have the proper diagnostics +ok 103 - simple scchma.func with 0 args, desc should pass +ok 104 - simple scchma.func with 0 args, desc should have the proper description +ok 105 - simple scchma.func with 0 args, desc should have the proper diagnostics +ok 106 - simple function with 1 arg should pass +ok 107 - simple function with 1 arg should have the proper description +ok 108 - simple function with 1 arg should have the proper diagnostics +ok 109 - simple function with 2 args should pass +ok 110 - simple function with 2 args should have the proper description +ok 111 - simple function with 2 args should have the proper diagnostics +ok 112 - simple array function should pass +ok 113 - simple array function should have the proper description +ok 114 - simple array function should have the proper diagnostics +ok 115 - custom array function should pass +ok 116 - custom array function should have the proper description +ok 117 - custom array function should have the proper diagnostics +ok 118 - custom numeric function should pass +ok 119 - custom numeric function should have the proper description +ok 120 - custom numeric function should have the proper diagnostics +ok 121 - failure output should fail +ok 122 - failure output should have the proper description +ok 123 - failure output should have the proper diagnostics +ok 124 - can(schema) with desc should pass +ok 125 - can(schema) with desc should have the proper description +ok 126 - can(schema) with desc should have the proper diagnostics +ok 127 - can(schema) should pass +ok 128 - can(schema) should have the proper description +ok 129 - can(schema) should have the proper diagnostics +ok 130 - fail can(schema) with desc should fail +ok 131 - fail can(schema) with desc should have the proper description +ok 132 - fail can(schema) with desc should have the proper diagnostics +ok 133 - fail can(someschema) with desc should fail +ok 134 - fail can(someschema) with desc should have the proper description +ok 135 - fail can(someschema) with desc should have the proper diagnostics +ok 136 - can() with desc should pass +ok 137 - can() with desc should have the proper description +ok 138 - can() with desc should have the proper diagnostics +ok 139 - can(schema) should pass +ok 140 - can(schema) should have the proper description +ok 141 - can(schema) should have the proper diagnostics +ok 142 - fail can() with desc should fail +ok 143 - fail can() with desc should have the proper description +ok 144 - fail can() with desc should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 76dcb57bfc07..ce4d76e4f433 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2011,86 +2011,166 @@ RETURNS TEXT AS $$ SELECT fk_ok( $1, ARRAY[$2], $3, ARRAY[$4] ); $$ LANGUAGE sql; --- has_function( schema, func_name, args[], description ) -CREATE OR REPLACE FUNCTION has_function ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT ok( - EXISTS( - SELECT true +CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT TRUE 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 array_to_string(p.proargtypes::regtype[], ',') = array_to_string($3, ',') - ), $4 - ); + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT TRUE + 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 + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT TRUE + FROM pg_catalog.pg_proc p + WHERE p.proname = $1 + AND pg_catalog.pg_function_is_visible(p.oid) + AND array_to_string(p.proargtypes::regtype[], ',') = array_to_string($2, ',') + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _got_func ( NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT TRUE + FROM pg_catalog.pg_proc p + WHERE p.proname = $1 + AND pg_catalog.pg_function_is_visible(p.oid) + ); +$$ LANGUAGE SQL; + +-- has_function( schema, func_name, 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, func_name, args[] ) CREATE OR REPLACE FUNCTION has_function( NAME, NAME, NAME[] ) RETURNS TEXT AS $$ - SELECT has_function( $1, $2, $3, 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should exist' ); + 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, func_name, description ) CREATE OR REPLACE FUNCTION has_function ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ - SELECT ok( - EXISTS( - SELECT true - 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 - ), $3 - ); + SELECT ok( _got_func($1, $2), $3 ); $$ LANGUAGE SQL; -- has_function( schema, func_name ) CREATE OR REPLACE FUNCTION has_function( NAME, NAME ) RETURNS TEXT AS $$ - SELECT has_function( $1, $2, 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should exist' ); + SELECT ok( + _got_func($1, $2), + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should exist' + ); $$ LANGUAGE sql; -- has_function( func_name, args[], description ) CREATE OR REPLACE FUNCTION has_function ( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ - SELECT ok( - EXISTS( - SELECT true - FROM pg_catalog.pg_proc p - WHERE p.proname = $1 - AND pg_catalog.pg_function_is_visible(p.oid) - AND array_to_string(p.proargtypes::regtype[], ',') = array_to_string($2, ',') - ), $3 - ); + SELECT ok( _got_func($1, $2), $3 ); $$ LANGUAGE SQL; -- has_function( func_name, args[] ) CREATE OR REPLACE FUNCTION has_function( NAME, NAME[] ) RETURNS TEXT AS $$ - SELECT has_function( $1, $2, 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should exist' ); + SELECT ok( + _got_func($1, $2), + 'Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') should exist' + ); $$ LANGUAGE sql; -- has_function( func_name, description ) CREATE OR REPLACE FUNCTION has_function( NAME, TEXT ) RETURNS TEXT AS $$ - SELECT ok( - EXISTS( - SELECT true - FROM pg_catalog.pg_proc p - WHERE p.proname = $1 - AND pg_catalog.pg_function_is_visible(p.oid) - ), $2 - ); + SELECT ok( _got_func($1), $2 ); $$ LANGUAGE sql; -- has_function( func_name ) CREATE OR REPLACE FUNCTION has_function( NAME ) RETURNS TEXT AS $$ - SELECT has_function( $1, 'Function ' || quote_ident($1) || '() should exist' ); + SELECT ok( _got_func($1), 'Function ' || quote_ident($1) || '() should exist' ); +$$ LANGUAGE sql; + +-- hasnt_function( schema, func_name, 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, func_name, 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, func_name, 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, func_name ) +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( func_name, 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( func_name, 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( func_name, description ) +CREATE OR REPLACE FUNCTION hasnt_function( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _got_func($1), $2 ); +$$ LANGUAGE sql; + +-- hasnt_function( func_name ) +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 can_ok ( NAME, NAME, NAME[], TEXT ) diff --git a/sql/cantap.sql b/sql/functap.sql similarity index 74% rename from sql/cantap.sql rename to sql/functap.sql index cb5a2afc4d39..7fa6f5cc9112 100644 --- a/sql/cantap.sql +++ b/sql/functap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(105); +SELECT plan(144); --SELECT * FROM no_plan(); CREATE SCHEMA someschema; @@ -135,6 +135,112 @@ SELECT * FROM check_test( '' -- No diagnostics. ); +/****************************************************************************/ +-- Test hasnt_function(). +SELECT * FROM check_test( + hasnt_function( 'now' ), + false, + 'simple function', + 'Function now() should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_function( 'pg_catalog', 'now'::name ), + false, + 'simple schema.function', + 'Function pg_catalog.now() should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_function( 'now', 'whatever' ), + false, + 'simple function desc', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_function( 'now', '{}'::name[] ), + false, + 'simple with 0 args', + 'Function now() should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_function( 'now', '{}'::name[], 'whatever' ), + false, + 'simple with 0 args desc', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_function( 'pg_catalog', 'now', '{}'::name[] ), + false, + 'simple schema.func with 0 args', + 'Function pg_catalog.now() should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_function( 'pg_catalog', 'now', 'whatever' ), + false, + 'simple schema.func with desc', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_function( 'pg_catalog', 'now', '{}'::name[], 'whatever' ), + false, + 'simple scchma.func with 0 args, desc', + 'whatever', + '' +); + +SELECT * FROM check_test( + hasnt_function( 'lower', '{text}'::name[] ), + false, + 'simple function with 1 arg', + 'Function lower(text) should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_function( 'decode', '{text,text}'::name[] ), + false, + 'simple function with 2 args', + 'Function decode(text, text) should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_function( 'array_cat', ARRAY['anyarray','anyarray'] ), + false, + 'simple array function', + 'Function array_cat(anyarray, anyarray) should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_function( '__cat__', '{text[]}'::name[] ), + false, + 'custom array function', + 'Function __cat__(text[]) should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_function( '__cat__', '{numeric}'::name[] ), + false, + 'custom numeric function', + 'Function __cat__(numeric) should not exist', + '' +); + /****************************************************************************/ -- Test can_ok(). SELECT * FROM check_test( From 8ff8ff4badb6f0c3410b06eca0fa83eaef9fb37d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 26 May 2009 16:34:40 -0700 Subject: [PATCH 0370/1195] Added `rule_is_on()`. --- Changes | 2 +- README.pgtap | 35 +++++++- expected/ruletap.out | 68 +++++++++++++++- pgtap.sql.in | 100 +++++++++++++++++++++-- sql/ruletap.sql | 186 ++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 381 insertions(+), 10 deletions(-) diff --git a/Changes b/Changes index f4851509c22b..160ffcd02331 100644 --- a/Changes +++ b/Changes @@ -11,7 +11,7 @@ Revision history for pgTAP * Added `performs_ok()`. * Added `has_language()`, `hasnt_language()`, and `language_is_trusted()`. * Added `has_opclass()` and `hasnt_opclass()`. -* Added `has_rule()`, `hasnt_rule()`, and `rule_is_instead()`. +* Added `has_rule()`, `hasnt_rule()`, `rule_is_instead()`, and `rule_is_on()`. * Added `tablespaces_are()`, `schemas_are()`, `tables_are()`, `views_are()`, `sequences_are()`, `functions_are()`, `indexes_are()`, `users_are()`, `groups_are()`, `opclasses_are()`, `languages_are()`, and `rules_are()`. diff --git a/README.pgtap b/README.pgtap index 32dc6d13db6f..2f7e26ec2e25 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2420,6 +2420,38 @@ appropriate diagnostic such as: # Failed test 625: "Rule on_insert on relation public.users should be an INSTEAD rule" # Rule on_insert does not exist +### `rule_is_on( schema, table, rule, event, description )` ### +### `rule_is_on( schema, table, rule, event )` ### +### `rule_is_on( table, rule, event, description )` ### +### `rule_is_on( table, rule, event )` ### + + SELECT rule_is_on( + 'public', + 'users', + 'on_insert', + 'INSERT', + 'Rule "on_insert" be on insert to on relation public.users' + ); + +Tests the event for a rule, which may be one of `SELECT`, `INSERT`, `UPDATE`, +or `DELETE`. For the `:event` argument, you can specify the name of the event +in any case, or even with a single letter ("s", "i", "u", or "d"). If the +`:schema` argument is omitted, then the table must be visible in the search +path. If the `:description` is omitted, a reasonable default will be created. + +If the test fails, you'll see useful diagnostics, such as: + + # Failed test 133: "Rule ins_me should be on INSERT to public.widgets + # have: UPDATE + # want: INSERT + +If the rule in question does not exist, you'll be told that, too: + + # Failed test 134: "Rule upd_me should be on UPDATE to public.widgets" + # Rule upd_me does not exist on public.widgets + +But then you run `has_rule()` first, don't you? + No Test for the Wicked ====================== @@ -3049,9 +3081,8 @@ To Do ----- * Useful schema testing functions to consider adding: - * `rule_is_on()` (INSERT, UPDATE, DELETE, or SELECT) * `function_returns()` - * `function_lang_is()` + * `function_lang_is()` (or `function_in()`?) * `sequence_has_range()` * `sequence_increments_by()` * `sequence_starts_at()` diff --git a/expected/ruletap.out b/expected/ruletap.out index 70dabcccca6b..d5c3fcbe0544 100644 --- a/expected/ruletap.out +++ b/expected/ruletap.out @@ -1,5 +1,5 @@ \unset ECHO -1..66 +1..132 ok 1 - has_rule(schema, table, rule, desc) should pass ok 2 - has_rule(schema, table, rule, desc) should have the proper description ok 3 - has_rule(schema, table, rule, desc) should have the proper diagnostics @@ -66,3 +66,69 @@ ok 63 - rule_is_instead(schema, table, non-existent rule, desc) should have the ok 64 - rule_is_instead(table, non-existent rule, desc) should fail ok 65 - rule_is_instead(table, non-existent rule, desc) should have the proper description ok 66 - rule_is_instead(table, non-existent rule, desc) should have the proper diagnostics +ok 67 - rule_is_on(schema, table, rule, insert, desc) should pass +ok 68 - rule_is_on(schema, table, rule, insert, desc) should have the proper description +ok 69 - rule_is_on(schema, table, rule, insert, desc) should have the proper diagnostics +ok 70 - rule_is_on(schema, table, rule, update, desc) should pass +ok 71 - rule_is_on(schema, table, rule, update, desc) should have the proper description +ok 72 - rule_is_on(schema, table, rule, update, desc) should have the proper diagnostics +ok 73 - rule_is_on(schema, table, rule, SELECT, desc) should pass +ok 74 - rule_is_on(schema, table, rule, SELECT, desc) should have the proper description +ok 75 - rule_is_on(schema, table, rule, SELECT, desc) should have the proper diagnostics +ok 76 - rule_is_on(schema, table, rule, delete, desc) should pass +ok 77 - rule_is_on(schema, table, rule, delete, desc) should have the proper description +ok 78 - rule_is_on(schema, table, rule, delete, desc) should have the proper diagnostics +ok 79 - rule_is_on(schema, table, dupe rule, insert, desc) should pass +ok 80 - rule_is_on(schema, table, dupe rule, insert, desc) should have the proper description +ok 81 - rule_is_on(schema, table, dupe rule, insert, desc) should have the proper diagnostics +ok 82 - rule_is_on(schema, table, rule, INSERT, desc) should pass +ok 83 - rule_is_on(schema, table, rule, INSERT, desc) should have the proper description +ok 84 - rule_is_on(schema, table, rule, INSERT, desc) should have the proper diagnostics +ok 85 - rule_is_on(schema, table, rule, i, desc) should pass +ok 86 - rule_is_on(schema, table, rule, i, desc) should have the proper description +ok 87 - rule_is_on(schema, table, rule, i, desc) should have the proper diagnostics +ok 88 - rule_is_on(schema, table, rule, indigo, desc) should pass +ok 89 - rule_is_on(schema, table, rule, indigo, desc) should have the proper description +ok 90 - rule_is_on(schema, table, rule, indigo, desc) should have the proper diagnostics +ok 91 - rule_is_on(schema, table, rule, i, desc) should pass +ok 92 - rule_is_on(schema, table, rule, i, desc) should have the proper description +ok 93 - rule_is_on(schema, table, rule, i, desc) should have the proper diagnostics +ok 94 - rule_is_on(schema, table, rule, u, desc) fail should fail +ok 95 - rule_is_on(schema, table, rule, u, desc) fail should have the proper description +ok 96 - rule_is_on(schema, table, rule, u, desc) fail should have the proper diagnostics +ok 97 - rule_is_on(schema, table, invalid rule, u, desc) should fail +ok 98 - rule_is_on(schema, table, invalid rule, u, desc) should have the proper description +ok 99 - rule_is_on(schema, table, invalid rule, u, desc) should have the proper diagnostics +ok 100 - rule_is_on(table, rule, insert, desc) should pass +ok 101 - rule_is_on(table, rule, insert, desc) should have the proper description +ok 102 - rule_is_on(table, rule, insert, desc) should have the proper diagnostics +ok 103 - rule_is_on(table, rule, update, desc) should pass +ok 104 - rule_is_on(table, rule, update, desc) should have the proper description +ok 105 - rule_is_on(table, rule, update, desc) should have the proper diagnostics +ok 106 - rule_is_on(table, rule, SELECT, desc) should pass +ok 107 - rule_is_on(table, rule, SELECT, desc) should have the proper description +ok 108 - rule_is_on(table, rule, SELECT, desc) should have the proper diagnostics +ok 109 - rule_is_on(table, rule, delete, desc) should pass +ok 110 - rule_is_on(table, rule, delete, desc) should have the proper description +ok 111 - rule_is_on(table, rule, delete, desc) should have the proper diagnostics +ok 112 - rule_is_on(table, dupe rule, insert, desc) should pass +ok 113 - rule_is_on(table, dupe rule, insert, desc) should have the proper description +ok 114 - rule_is_on(table, dupe rule, insert, desc) should have the proper diagnostics +ok 115 - rule_is_on(table, rule, INSERT, desc) should pass +ok 116 - rule_is_on(table, rule, INSERT, desc) should have the proper description +ok 117 - rule_is_on(table, rule, INSERT, desc) should have the proper diagnostics +ok 118 - rule_is_on(table, rule, i, desc) should pass +ok 119 - rule_is_on(table, rule, i, desc) should have the proper description +ok 120 - rule_is_on(table, rule, i, desc) should have the proper diagnostics +ok 121 - rule_is_on(table, rule, indigo, desc) should pass +ok 122 - rule_is_on(table, rule, indigo, desc) should have the proper description +ok 123 - rule_is_on(table, rule, indigo, desc) should have the proper diagnostics +ok 124 - rule_is_on(table, rule, i, desc) should pass +ok 125 - rule_is_on(table, rule, i, desc) should have the proper description +ok 126 - rule_is_on(table, rule, i, desc) should have the proper diagnostics +ok 127 - rule_is_on(table, rule, u, desc) fail should fail +ok 128 - rule_is_on(table, rule, u, desc) fail should have the proper description +ok 129 - rule_is_on(table, rule, u, desc) fail should have the proper diagnostics +ok 130 - rule_is_on(table, invalid rule, u, desc) should fail +ok 131 - rule_is_on(table, invalid rule, u, desc) should have the proper description +ok 132 - rule_is_on(table, invalid rule, u, desc) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index ce4d76e4f433..d6a250400931 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3712,7 +3712,6 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; - CREATE OR REPLACE FUNCTION _op_exists ( NAME, NAME, NAME, NAME, NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS ( @@ -4748,10 +4747,101 @@ 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; --- rule_is_on( schema, table, rule, type, description ) --- rule_is_on( schema, table, rule, type ) --- rule_is_on( table, rule, type, description ) --- rule_is_on( table, rule, type ) +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; + +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; + +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; -- check_test( test_output, pass, name, description, diag, match_diag ) CREATE OR REPLACE FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT, BOOLEAN ) diff --git a/sql/ruletap.sql b/sql/ruletap.sql index f8300523477f..b989e95e52a5 100644 --- a/sql/ruletap.sql +++ b/sql/ruletap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(66); +SELECT plan(132); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -18,6 +18,10 @@ CREATE RULE upd_me AS ON UPDATE TO public.sometab DO ALSO SELECT now(); CREATE TABLE public.toview ( id INT ); CREATE RULE "_RETURN" AS ON SELECT TO public.toview DO INSTEAD SELECT 42 AS id; +CREATE TABLE widgets (id int); +CREATE RULE del_me AS ON DELETE TO public.widgets DO NOTHING; +CREATE RULE ins_me AS ON INSERT TO public.widgets DO NOTHING; + RESET client_min_messages; /****************************************************************************/ @@ -203,6 +207,186 @@ SELECT * FROM check_test( ' Rule blah does not exist' ); +/****************************************************************************/ +-- Test rule_is_on(). +SELECT * FROM check_test( + rule_is_on( 'public', 'sometab', 'ins_me', 'insert', 'whatever' ), + true, + 'rule_is_on(schema, table, rule, insert, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'public', 'sometab', 'upd_me', 'update', 'whatever' ), + true, + 'rule_is_on(schema, table, rule, update, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'public', 'toview', '_RETURN', 'SELECT', 'whatever' ), + true, + 'rule_is_on(schema, table, rule, SELECT, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'public', 'widgets', 'del_me', 'delete', 'whatever' ), + true, + 'rule_is_on(schema, table, rule, delete, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'public', 'widgets', 'ins_me', 'insert', 'whatever' ), + true, + 'rule_is_on(schema, table, dupe rule, insert, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'public', 'sometab', 'ins_me', 'INSERT', 'whatever' ), + true, + 'rule_is_on(schema, table, rule, INSERT, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'public', 'sometab', 'ins_me', 'i', 'whatever' ), + true, + 'rule_is_on(schema, table, rule, i, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'public', 'sometab', 'ins_me', 'indigo', 'whatever' ), + true, + 'rule_is_on(schema, table, rule, indigo, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'public', 'sometab', 'ins_me'::name, 'i' ), + true, + 'rule_is_on(schema, table, rule, i, desc)', + 'Rule ins_me should be on INSERT to public.sometab', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'public', 'sometab', 'ins_me'::name, 'u' ), + false, + 'rule_is_on(schema, table, rule, u, desc) fail', + 'Rule ins_me should be on UPDATE to public.sometab', + ' have: INSERT + want: UPDATE' +); + +SELECT * FROM check_test( + rule_is_on( 'public', 'sometab', 'foo_me'::name, 'u' ), + false, + 'rule_is_on(schema, table, invalid rule, u, desc)', + 'Rule foo_me should be on UPDATE to public.sometab', + ' Rule foo_me does not exist on public.sometab' +); + +SELECT * FROM check_test( + rule_is_on( 'sometab', 'ins_me', 'insert', 'whatever' ), + true, + 'rule_is_on(table, rule, insert, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'sometab', 'upd_me', 'update', 'whatever' ), + true, + 'rule_is_on(table, rule, update, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'toview', '_RETURN', 'SELECT', 'whatever' ), + true, + 'rule_is_on(table, rule, SELECT, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'widgets', 'del_me', 'delete', 'whatever' ), + true, + 'rule_is_on(table, rule, delete, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'widgets', 'ins_me', 'insert', 'whatever' ), + true, + 'rule_is_on(table, dupe rule, insert, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'sometab', 'ins_me', 'INSERT', 'whatever' ), + true, + 'rule_is_on(table, rule, INSERT, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'sometab', 'ins_me', 'i', 'whatever' ), + true, + 'rule_is_on(table, rule, i, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'sometab', 'ins_me', 'indigo', 'whatever' ), + true, + 'rule_is_on(table, rule, indigo, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'sometab', 'ins_me'::name, 'i' ), + true, + 'rule_is_on(table, rule, i, desc)', + 'Rule ins_me should be on INSERT to sometab', + '' +); + +SELECT * FROM check_test( + rule_is_on( 'sometab', 'ins_me'::name, 'u' ), + false, + 'rule_is_on(table, rule, u, desc) fail', + 'Rule ins_me should be on UPDATE to sometab', + ' have: INSERT + want: UPDATE' +); + +SELECT * FROM check_test( + rule_is_on( 'sometab', 'foo_me'::name, 'u' ), + false, + 'rule_is_on(table, invalid rule, u, desc)', + 'Rule foo_me should be on UPDATE to sometab', + ' Rule foo_me does not exist on sometab' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 59f8c1d4a70e640e2c02c1b56387b80b283d1478 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 26 May 2009 17:28:12 -0700 Subject: [PATCH 0371/1195] Added `function_lang_is()`. --- Changes | 1 + README.pgtap | 46 ++++++++++- expected/functap.out | 68 +++++++++++++++- pgtap.sql.in | 171 +++++++++++++++++++++++++++++++++++---- sql/functap.sql | 189 ++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 454 insertions(+), 21 deletions(-) diff --git a/Changes b/Changes index 160ffcd02331..7cbe51318c50 100644 --- a/Changes +++ b/Changes @@ -23,6 +23,7 @@ Revision history for pgTAP `can_ok()` function is still available as an alias, but it emits a warning and will be removed in a future version of pgTAP. * Added `hasnt_trigger()`, `hasnt_index()`, and `hasnt_function()`. +* Added `function_lang_is()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index 2f7e26ec2e25..062b6ace0642 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2300,8 +2300,49 @@ diagnostics will output a list of the functions that are missing, like so: # pg_catalog.foo() missing # pg_catalog.bar() missing -This function is the inverse of `has_enum()`. The test passes if the -specified enum does *not* exist. +### `function_lang_is( schema, function, args[], language, description )` ### +### `function_lang_is( schema, function, args[], language )` ### +### `function_lang_is( schema, function, language, description )` ### +### `function_lang_is( schema, function, language )` ### +### `function_lang_is( function, args[], language, description )` ### +### `function_lang_is( function, args[], language )` ### +### `function_lang_is( function, language, description )` ### +### `function_lang_is( function, language )` ### + + SELECT function_lang_is( + 'myschema', + 'foo', + ARRAY['integer', 'text'], + 'perl', + 'The myschema.foo() function should be written in Perl' + ); + + + SELECT function_lang_is( 'do_something', 'sql' ); + SELECT function_lang_is( 'do_something', ARRAY['integer'], 'plpgsql' ); + SELECT function_lang_is( 'do_something', ARRAY['numeric'], 'plpgsql' ); + +Tests that a particular function is implemented in a particular procedural +language. The function name is required. If the `:schema` argument is omitted, +then the function must be visible in the search path. If the `:args[]` +argument is passed, then the function with that argument signature will be the +one tested; otherwise, a function with any signature will be checked (pass an +empty array to specify a function with an empty signature). If the +`:description` is omitted, a reasonable substitute will be created. + +In the event of a failure, you'll useful diagnostics will tell you what went +wrong, for example: + + # Failed test 211: "Function mychema.eat(integer, text) should be written in perl" + # have: plpgsql + # want: perl + +If the function does not exist, you'll be told that, too. + + # Failed test 212: "Function myschema.grab() should be written in sql + # Function myschema.grab() does not exist + +But then you check with `has_function()` first, right? ### `enum_has_labels( schema, enum, labels, desc )` ### ### `enum_has_labels( schema, enum, labels )` ### @@ -3082,7 +3123,6 @@ To Do * Useful schema testing functions to consider adding: * `function_returns()` - * `function_lang_is()` (or `function_in()`?) * `sequence_has_range()` * `sequence_increments_by()` * `sequence_starts_at()` diff --git a/expected/functap.out b/expected/functap.out index 503072626108..0b1615ccadb5 100644 --- a/expected/functap.out +++ b/expected/functap.out @@ -1,5 +1,5 @@ \unset ECHO -1..144 +1..210 ok 1 - simple function should pass ok 2 - simple function should have the proper description ok 3 - simple function should have the proper diagnostics @@ -144,3 +144,69 @@ ok 141 - can(schema) should have the proper diagnostics ok 142 - fail can() with desc should fail ok 143 - fail can() with desc should have the proper description ok 144 - fail can() with desc should have the proper diagnostics +ok 145 - function_lang_is(schema, func, 0 args, sql, desc ) should pass +ok 146 - function_lang_is(schema, func, 0 args, sql, desc ) should have the proper description +ok 147 - function_lang_is(schema, func, 0 args, sql, desc ) should have the proper diagnostics +ok 148 - function_lang_is(schema, func, 0 args, sql ) should pass +ok 149 - function_lang_is(schema, func, 0 args, sql ) should have the proper description +ok 150 - function_lang_is(schema, func, 0 args, sql ) should have the proper diagnostics +ok 151 - function_lang_is(schema, func, args, plpgsql, desc ) should pass +ok 152 - function_lang_is(schema, func, args, plpgsql, desc ) should have the proper description +ok 153 - function_lang_is(schema, func, args, plpgsql, desc ) should have the proper diagnostics +ok 154 - function_lang_is(schema, func, args, plpgsql ) should pass +ok 155 - function_lang_is(schema, func, args, plpgsql ) should have the proper description +ok 156 - function_lang_is(schema, func, args, plpgsql ) should have the proper diagnostics +ok 157 - function_lang_is(schema, func, 0 args, perl, desc ) should fail +ok 158 - function_lang_is(schema, func, 0 args, perl, desc ) should have the proper description +ok 159 - function_lang_is(schema, func, 0 args, perl, desc ) should have the proper diagnostics +ok 160 - function_lang_is(schema, non-func, 0 args, sql, desc ) should fail +ok 161 - function_lang_is(schema, non-func, 0 args, sql, desc ) should have the proper description +ok 162 - function_lang_is(schema, non-func, 0 args, sql, desc ) should have the proper diagnostics +ok 163 - function_lang_is(schema, func, args, plpgsql ) should fail +ok 164 - function_lang_is(schema, func, args, plpgsql ) should have the proper description +ok 165 - function_lang_is(schema, func, args, plpgsql ) should have the proper diagnostics +ok 166 - function_lang_is(schema, func, sql, desc ) should pass +ok 167 - function_lang_is(schema, func, sql, desc ) should have the proper description +ok 168 - function_lang_is(schema, func, sql, desc ) should have the proper diagnostics +ok 169 - function_lang_is(schema, func, sql ) should pass +ok 170 - function_lang_is(schema, func, sql ) should have the proper description +ok 171 - function_lang_is(schema, func, sql ) should have the proper diagnostics +ok 172 - function_lang_is(schema, func, perl, desc ) should fail +ok 173 - function_lang_is(schema, func, perl, desc ) should have the proper description +ok 174 - function_lang_is(schema, func, perl, desc ) should have the proper diagnostics +ok 175 - function_lang_is(schema, non-func, sql, desc ) should fail +ok 176 - function_lang_is(schema, non-func, sql, desc ) should have the proper description +ok 177 - function_lang_is(schema, non-func, sql, desc ) should have the proper diagnostics +ok 178 - function_lang_is(func, 0 args, sql, desc ) should pass +ok 179 - function_lang_is(func, 0 args, sql, desc ) should have the proper description +ok 180 - function_lang_is(func, 0 args, sql, desc ) should have the proper diagnostics +ok 181 - function_lang_is(func, 0 args, sql ) should pass +ok 182 - function_lang_is(func, 0 args, sql ) should have the proper description +ok 183 - function_lang_is(func, 0 args, sql ) should have the proper diagnostics +ok 184 - function_lang_is(func, args, plpgsql, desc ) should pass +ok 185 - function_lang_is(func, args, plpgsql, desc ) should have the proper description +ok 186 - function_lang_is(func, args, plpgsql, desc ) should have the proper diagnostics +ok 187 - function_lang_is(func, args, plpgsql ) should pass +ok 188 - function_lang_is(func, args, plpgsql ) should have the proper description +ok 189 - function_lang_is(func, args, plpgsql ) should have the proper diagnostics +ok 190 - function_lang_is(func, 0 args, perl, desc ) should fail +ok 191 - function_lang_is(func, 0 args, perl, desc ) should have the proper description +ok 192 - function_lang_is(func, 0 args, perl, desc ) should have the proper diagnostics +ok 193 - function_lang_is(non-func, 0 args, sql, desc ) should fail +ok 194 - function_lang_is(non-func, 0 args, sql, desc ) should have the proper description +ok 195 - function_lang_is(non-func, 0 args, sql, desc ) should have the proper diagnostics +ok 196 - function_lang_is(func, args, plpgsql ) should fail +ok 197 - function_lang_is(func, args, plpgsql ) should have the proper description +ok 198 - function_lang_is(func, args, plpgsql ) should have the proper diagnostics +ok 199 - function_lang_is(func, sql, desc ) should pass +ok 200 - function_lang_is(func, sql, desc ) should have the proper description +ok 201 - function_lang_is(func, sql, desc ) should have the proper diagnostics +ok 202 - function_lang_is(func, sql ) should pass +ok 203 - function_lang_is(func, sql ) should have the proper description +ok 204 - function_lang_is(func, sql ) should have the proper diagnostics +ok 205 - function_lang_is(func, perl, desc ) should fail +ok 206 - function_lang_is(func, perl, desc ) should have the proper description +ok 207 - function_lang_is(func, perl, desc ) should have the proper diagnostics +ok 208 - function_lang_is(non-func, sql, desc ) should fail +ok 209 - function_lang_is(non-func, sql, desc ) should have the proper description +ok 210 - function_lang_is(non-func, sql, desc ) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index d6a250400931..f504806d6ee5 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2055,13 +2055,13 @@ RETURNS BOOLEAN AS $$ ); $$ LANGUAGE SQL; --- has_function( schema, func_name, args[], description ) +-- 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, func_name, args[] ) +-- has_function( schema, function, args[] ) CREATE OR REPLACE FUNCTION has_function( NAME, NAME, NAME[] ) RETURNS TEXT AS $$ SELECT ok( @@ -2071,13 +2071,13 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; --- has_function( schema, func_name, description ) +-- 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, func_name ) +-- has_function( schema, function ) CREATE OR REPLACE FUNCTION has_function( NAME, NAME ) RETURNS TEXT AS $$ SELECT ok( @@ -2086,13 +2086,13 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; --- has_function( func_name, args[], description ) +-- 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( func_name, args[] ) +-- has_function( function, args[] ) CREATE OR REPLACE FUNCTION has_function( NAME, NAME[] ) RETURNS TEXT AS $$ SELECT ok( @@ -2102,25 +2102,25 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; --- has_function( func_name, description ) +-- 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( func_name ) +-- 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, func_name, args[], description ) +-- 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, func_name, args[] ) +-- hasnt_function( schema, function, args[] ) CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME, NAME[] ) RETURNS TEXT AS $$ SELECT ok( @@ -2130,13 +2130,13 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; --- hasnt_function( schema, func_name, description ) +-- 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, func_name ) +-- hasnt_function( schema, function ) CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME ) RETURNS TEXT AS $$ SELECT ok( @@ -2145,13 +2145,13 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; --- hasnt_function( func_name, args[], description ) +-- 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( func_name, args[] ) +-- hasnt_function( function, args[] ) CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME[] ) RETURNS TEXT AS $$ SELECT ok( @@ -2161,13 +2161,13 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; --- hasnt_function( func_name, description ) +-- 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( func_name ) +-- 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' ); @@ -4843,6 +4843,145 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; +CREATE OR REPLACE FUNCTION _lang ( NAME, NAME, NAME[] ) +RETURNS NAME AS $$ + SELECT l.lanname + FROM pg_catalog.pg_proc p + JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid + JOIN pg_catalog.pg_language l ON p.prolang = l.oid + WHERE n.nspname = $1 + AND p.proname = $2 + AND array_to_string(p.proargtypes::regtype[], ',') = array_to_string($3, ','); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _lang ( NAME, NAME ) +RETURNS NAME AS $$ + SELECT l.lanname + FROM pg_catalog.pg_proc p + JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid + JOIN pg_catalog.pg_language l ON p.prolang = l.oid + WHERE n.nspname = $1 + AND p.proname = $2 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _lang ( NAME, NAME[] ) +RETURNS NAME AS $$ + SELECT l.lanname + FROM pg_catalog.pg_proc p + JOIN pg_catalog.pg_language l ON p.prolang = l.oid + WHERE p.proname = $1 + AND array_to_string(p.proargtypes::regtype[], ',') = array_to_string($2, ',') + AND pg_catalog.pg_function_is_visible(p.oid); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _lang ( NAME ) +RETURNS NAME AS $$ + SELECT l.lanname + FROM pg_catalog.pg_proc p + JOIN pg_catalog.pg_language l ON p.prolang = l.oid + WHERE p.proname = $1 + AND pg_catalog.pg_function_is_visible(p.oid); +$$ 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 $$ +DECLARE + have name := _lang($1, $2, $3); +BEGIN + IF have IS NOT NULL THEN RETURN is( have, $4, $5 ); END IF; + + RETURN ok( FALSE, $5 ) || E'\n' || diag( + ' Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || + array_to_string($3, ', ') || ') does not exist' + ); +END; +$$ LANGUAGE plpgsql; + +-- 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 $$ +DECLARE + have name := _lang($1, $2); +BEGIN + IF have IS NOT NULL THEN RETURN is( have, $3, $4 ); END IF; + + RETURN ok( FALSE, $4 ) || E'\n' || diag( + ' Function ' || quote_ident($1) || '.' || quote_ident($2) + || '() does not exist' + ); +END; +$$ LANGUAGE plpgsql; + +-- 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 $$ +DECLARE + have name := _lang($1, $2); +BEGIN + IF have IS NOT NULL THEN RETURN is( have, $3, $4 ); END IF; + + RETURN ok( FALSE, $4 ) || E'\n' || diag( + ' Function ' || quote_ident($1) || '(' || + array_to_string($2, ', ') || ') does not exist' + ); +END; +$$ LANGUAGE plpgsql; + +-- 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 $$ +DECLARE + have name := _lang($1); +BEGIN + IF have IS NOT NULL THEN RETURN is( have, $2, $3 ); END IF; + + RETURN ok( FALSE, $3 ) || E'\n' || diag( + ' Function ' || quote_ident($1) || '() does not exist' + ); +END; +$$ LANGUAGE plpgsql; + +-- 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; + -- 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 $$ diff --git a/sql/functap.sql b/sql/functap.sql index 7fa6f5cc9112..71098dd2b1b9 100644 --- a/sql/functap.sql +++ b/sql/functap.sql @@ -1,11 +1,17 @@ \unset ECHO \i test_setup.sql -SELECT plan(144); +SELECT plan(210); --SELECT * FROM no_plan(); CREATE SCHEMA someschema; CREATE FUNCTION someschema.huh () RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL; +CREATE FUNCTION someschema.bah (int, text) RETURNS BOOL +AS 'BEGIN RETURN TRUE; END;' LANGUAGE plpgsql; + +CREATE FUNCTION public.yay () RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL; +CREATE FUNCTION public.oww (int, text) RETURNS BOOL +AS 'BEGIN RETURN TRUE; END;' LANGUAGE plpgsql; -- XXX Delete when can_ok() is removed. SET client_min_messages = error; @@ -417,6 +423,187 @@ SELECT * FROM check_test( bar() missing' ); +/****************************************************************************/ +-- Test function_lang_is(). +SELECT * FROM check_test( + function_lang_is( 'someschema', 'huh', '{}'::name[], 'sql', 'whatever' ), + true, + 'function_lang_is(schema, func, 0 args, sql, desc )', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_lang_is( 'someschema', 'huh', '{}'::name[], 'sql' ), + true, + 'function_lang_is(schema, func, 0 args, sql )', + 'Function someschema.huh() should be written in sql', + '' +); + +SELECT * FROM check_test( + function_lang_is( 'someschema', 'bah', '{"integer", "text"}'::name[], 'plpgsql', 'whatever' ), + true, + 'function_lang_is(schema, func, args, plpgsql, desc )', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_lang_is( 'someschema', 'bah', '{"integer", "text"}'::name[], 'plpgsql' ), + true, + 'function_lang_is(schema, func, args, plpgsql )', + 'Function someschema.bah(integer, text) should be written in plpgsql', + '' +); + +SELECT * FROM check_test( + function_lang_is( 'someschema', 'huh', '{}'::name[], 'perl', 'whatever' ), + false, + 'function_lang_is(schema, func, 0 args, perl, desc )', + 'whatever', + ' have: sql + want: perl' +); + +SELECT * FROM check_test( + function_lang_is( 'someschema', 'why', '{}'::name[], 'sql', 'whatever' ), + false, + 'function_lang_is(schema, non-func, 0 args, sql, desc )', + 'whatever', + ' Function someschema.why() does not exist' +); + +SELECT * FROM check_test( + function_lang_is( 'someschema', 'why', '{"integer", "text"}'::name[], 'plpgsql' ), + false, + 'function_lang_is(schema, func, args, plpgsql )', + 'Function someschema.why(integer, text) should be written in plpgsql', + ' Function someschema.why(integer, text) does not exist' +); + +SELECT * FROM check_test( + function_lang_is( 'someschema', 'huh', 'sql', 'whatever' ), + true, + 'function_lang_is(schema, func, sql, desc )', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_lang_is( 'someschema', 'huh', 'sql'::name ), + true, + 'function_lang_is(schema, func, sql )', + 'Function someschema.huh() should be written in sql', + '' +); + +SELECT * FROM check_test( + function_lang_is( 'someschema', 'huh', 'perl', 'whatever' ), + false, + 'function_lang_is(schema, func, perl, desc )', + 'whatever', + ' have: sql + want: perl' +); + +SELECT * FROM check_test( + function_lang_is( 'someschema', 'why', 'sql', 'whatever' ), + false, + 'function_lang_is(schema, non-func, sql, desc )', + 'whatever', + ' Function someschema.why() does not exist' +); + +SELECT * FROM check_test( + function_lang_is( 'yay', '{}'::name[], 'sql', 'whatever' ), + true, + 'function_lang_is(func, 0 args, sql, desc )', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_lang_is( 'yay', '{}'::name[], 'sql' ), + true, + 'function_lang_is(func, 0 args, sql )', + 'Function yay() should be written in sql', + '' +); + +SELECT * FROM check_test( + function_lang_is( 'oww', '{"integer", "text"}'::name[], 'plpgsql', 'whatever' ), + true, + 'function_lang_is(func, args, plpgsql, desc )', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_lang_is( 'oww', '{"integer", "text"}'::name[], 'plpgsql' ), + true, + 'function_lang_is(func, args, plpgsql )', + 'Function oww(integer, text) should be written in plpgsql', + '' +); + +SELECT * FROM check_test( + function_lang_is( 'yay', '{}'::name[], 'perl', 'whatever' ), + false, + 'function_lang_is(func, 0 args, perl, desc )', + 'whatever', + ' have: sql + want: perl' +); + +SELECT * FROM check_test( + function_lang_is( 'why', '{}'::name[], 'sql', 'whatever' ), + false, + 'function_lang_is(non-func, 0 args, sql, desc )', + 'whatever', + ' Function why() does not exist' +); + +SELECT * FROM check_test( + function_lang_is( 'why', '{"integer", "text"}'::name[], 'plpgsql' ), + false, + 'function_lang_is(func, args, plpgsql )', + 'Function why(integer, text) should be written in plpgsql', + ' Function why(integer, text) does not exist' +); + +SELECT * FROM check_test( + function_lang_is( 'yay', 'sql', 'whatever' ), + true, + 'function_lang_is(func, sql, desc )', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_lang_is( 'yay', 'sql' ), + true, + 'function_lang_is(func, sql )', + 'Function yay() should be written in sql', + '' +); + +SELECT * FROM check_test( + function_lang_is( 'yay', 'perl', 'whatever' ), + false, + 'function_lang_is(func, perl, desc )', + 'whatever', + ' have: sql + want: perl' +); + +SELECT * FROM check_test( + function_lang_is( 'why', 'sql', 'whatever' ), + false, + 'function_lang_is(non-func, sql, desc )', + 'whatever', + ' Function why() does not exist' +); /****************************************************************************/ -- Finish the tests and clean up. From 0c610aa40d1f2a19c29812ee7f98011217dc2489 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 26 May 2009 22:25:08 -0700 Subject: [PATCH 0372/1195] Added `function_returns()`. * Added `function_returns()`. * Refactored all of the function-related tests to use a new view, `tap_funky`, to make them all quite a bit simpler. There was too much redundant code! * Added some notes for some other function-testing assertions to add. I've already added the necessary code to `tap_funky` to make them work. * Eliminated some extra spaces in the test descriptions in `sql/functap.sql`. --- Changes | 2 +- README.pgtap | 50 ++++++- expected/functap.out | 206 ++++++++++++++++++---------- pgtap.sql.in | 310 +++++++++++++++++++++++++++---------------- sql/functap.sql | 242 +++++++++++++++++++++++++++++---- 5 files changed, 603 insertions(+), 207 deletions(-) diff --git a/Changes b/Changes index 7cbe51318c50..831e1988bf70 100644 --- a/Changes +++ b/Changes @@ -23,7 +23,7 @@ Revision history for pgTAP `can_ok()` function is still available as an alias, but it emits a warning and will be removed in a future version of pgTAP. * Added `hasnt_trigger()`, `hasnt_index()`, and `hasnt_function()`. -* Added `function_lang_is()`. +* Added `function_lang_is()` and `function_returns()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index 062b6ace0642..357b041557ba 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2344,6 +2344,51 @@ If the function does not exist, you'll be told that, too. But then you check with `has_function()` first, right? +### `function_returns( schema, function, args[], type, description )` ### +### `function_returns( schema, function, args[], type )` ### +### `function_returns( schema, function, type, description )` ### +### `function_returns( schema, function, type )` ### +### `function_returns( function, args[], type, description )` ### +### `function_returns( function, args[], type )` ### +### `function_returns( function, type, description )` ### +### `function_returns( function, type )` ### + + SELECT function_returns( + 'myschema', + 'foo', + ARRAY['integer', 'text'], + 'integer', + 'The myschema.foo() function should return an integer' + ); + + + SELECT function_returns( 'do_something', 'setof bool' ); + SELECT function_returns( 'do_something', ARRAY['integer'], 'bool' ); + SELECT function_returns( 'do_something', ARRAY['numeric'], 'numeric' ); + +Tests that a particular function returns a particular data type. For set +returning functions, the `:type` argument should start with "setof " (yes, +lowercase). If the `:schema` argument is omitted, then the function must be +visible in the search path. If the `:args[]` argument is passed, then the +function with that argument signature will be the one tested; otherwise, a +function with any signature will be checked (pass an empty array to specify a +function with an empty signature). If the `:description` is omitted, a +reasonable substitute will be created. + +In the event of a failure, you'll useful diagnostics will tell you what went +wrong, for example: + + # Failed test 283: "Function oww(integer, text) should return integer" + # have: bool + # want: integer + +If the function does not exist, you'll be told that, too. + + # Failed test 284: "Function oui(integer, text) should return integer" + # Function oui(integer, text) does not exist + +But then you check with `has_function()` first, right? + ### `enum_has_labels( schema, enum, labels, desc )` ### ### `enum_has_labels( schema, enum, labels )` ### ### `enum_has_labels( enum, labels, desc )` ### @@ -3122,7 +3167,10 @@ To Do ----- * Useful schema testing functions to consider adding: - * `function_returns()` + * `function_is_definer()` + * `function_is_agg()` + * `function_is_strict()` + * `function_is_volatile()` * `sequence_has_range()` * `sequence_increments_by()` * `sequence_starts_at()` diff --git a/expected/functap.out b/expected/functap.out index 0b1615ccadb5..f8ffef01e15b 100644 --- a/expected/functap.out +++ b/expected/functap.out @@ -1,5 +1,5 @@ \unset ECHO -1..210 +1..282 ok 1 - simple function should pass ok 2 - simple function should have the proper description ok 3 - simple function should have the proper diagnostics @@ -144,69 +144,141 @@ ok 141 - can(schema) should have the proper diagnostics ok 142 - fail can() with desc should fail ok 143 - fail can() with desc should have the proper description ok 144 - fail can() with desc should have the proper diagnostics -ok 145 - function_lang_is(schema, func, 0 args, sql, desc ) should pass -ok 146 - function_lang_is(schema, func, 0 args, sql, desc ) should have the proper description -ok 147 - function_lang_is(schema, func, 0 args, sql, desc ) should have the proper diagnostics -ok 148 - function_lang_is(schema, func, 0 args, sql ) should pass -ok 149 - function_lang_is(schema, func, 0 args, sql ) should have the proper description -ok 150 - function_lang_is(schema, func, 0 args, sql ) should have the proper diagnostics -ok 151 - function_lang_is(schema, func, args, plpgsql, desc ) should pass -ok 152 - function_lang_is(schema, func, args, plpgsql, desc ) should have the proper description -ok 153 - function_lang_is(schema, func, args, plpgsql, desc ) should have the proper diagnostics -ok 154 - function_lang_is(schema, func, args, plpgsql ) should pass -ok 155 - function_lang_is(schema, func, args, plpgsql ) should have the proper description -ok 156 - function_lang_is(schema, func, args, plpgsql ) should have the proper diagnostics -ok 157 - function_lang_is(schema, func, 0 args, perl, desc ) should fail -ok 158 - function_lang_is(schema, func, 0 args, perl, desc ) should have the proper description -ok 159 - function_lang_is(schema, func, 0 args, perl, desc ) should have the proper diagnostics -ok 160 - function_lang_is(schema, non-func, 0 args, sql, desc ) should fail -ok 161 - function_lang_is(schema, non-func, 0 args, sql, desc ) should have the proper description -ok 162 - function_lang_is(schema, non-func, 0 args, sql, desc ) should have the proper diagnostics -ok 163 - function_lang_is(schema, func, args, plpgsql ) should fail -ok 164 - function_lang_is(schema, func, args, plpgsql ) should have the proper description -ok 165 - function_lang_is(schema, func, args, plpgsql ) should have the proper diagnostics -ok 166 - function_lang_is(schema, func, sql, desc ) should pass -ok 167 - function_lang_is(schema, func, sql, desc ) should have the proper description -ok 168 - function_lang_is(schema, func, sql, desc ) should have the proper diagnostics -ok 169 - function_lang_is(schema, func, sql ) should pass -ok 170 - function_lang_is(schema, func, sql ) should have the proper description -ok 171 - function_lang_is(schema, func, sql ) should have the proper diagnostics -ok 172 - function_lang_is(schema, func, perl, desc ) should fail -ok 173 - function_lang_is(schema, func, perl, desc ) should have the proper description -ok 174 - function_lang_is(schema, func, perl, desc ) should have the proper diagnostics -ok 175 - function_lang_is(schema, non-func, sql, desc ) should fail -ok 176 - function_lang_is(schema, non-func, sql, desc ) should have the proper description -ok 177 - function_lang_is(schema, non-func, sql, desc ) should have the proper diagnostics -ok 178 - function_lang_is(func, 0 args, sql, desc ) should pass -ok 179 - function_lang_is(func, 0 args, sql, desc ) should have the proper description -ok 180 - function_lang_is(func, 0 args, sql, desc ) should have the proper diagnostics -ok 181 - function_lang_is(func, 0 args, sql ) should pass -ok 182 - function_lang_is(func, 0 args, sql ) should have the proper description -ok 183 - function_lang_is(func, 0 args, sql ) should have the proper diagnostics -ok 184 - function_lang_is(func, args, plpgsql, desc ) should pass -ok 185 - function_lang_is(func, args, plpgsql, desc ) should have the proper description -ok 186 - function_lang_is(func, args, plpgsql, desc ) should have the proper diagnostics -ok 187 - function_lang_is(func, args, plpgsql ) should pass -ok 188 - function_lang_is(func, args, plpgsql ) should have the proper description -ok 189 - function_lang_is(func, args, plpgsql ) should have the proper diagnostics -ok 190 - function_lang_is(func, 0 args, perl, desc ) should fail -ok 191 - function_lang_is(func, 0 args, perl, desc ) should have the proper description -ok 192 - function_lang_is(func, 0 args, perl, desc ) should have the proper diagnostics -ok 193 - function_lang_is(non-func, 0 args, sql, desc ) should fail -ok 194 - function_lang_is(non-func, 0 args, sql, desc ) should have the proper description -ok 195 - function_lang_is(non-func, 0 args, sql, desc ) should have the proper diagnostics -ok 196 - function_lang_is(func, args, plpgsql ) should fail -ok 197 - function_lang_is(func, args, plpgsql ) should have the proper description -ok 198 - function_lang_is(func, args, plpgsql ) should have the proper diagnostics -ok 199 - function_lang_is(func, sql, desc ) should pass -ok 200 - function_lang_is(func, sql, desc ) should have the proper description -ok 201 - function_lang_is(func, sql, desc ) should have the proper diagnostics -ok 202 - function_lang_is(func, sql ) should pass -ok 203 - function_lang_is(func, sql ) should have the proper description -ok 204 - function_lang_is(func, sql ) should have the proper diagnostics -ok 205 - function_lang_is(func, perl, desc ) should fail -ok 206 - function_lang_is(func, perl, desc ) should have the proper description -ok 207 - function_lang_is(func, perl, desc ) should have the proper diagnostics -ok 208 - function_lang_is(non-func, sql, desc ) should fail -ok 209 - function_lang_is(non-func, sql, desc ) should have the proper description -ok 210 - function_lang_is(non-func, sql, desc ) should have the proper diagnostics +ok 145 - function_lang_is(schema, func, 0 args, sql, desc) should pass +ok 146 - function_lang_is(schema, func, 0 args, sql, desc) should have the proper description +ok 147 - function_lang_is(schema, func, 0 args, sql, desc) should have the proper diagnostics +ok 148 - function_lang_is(schema, func, 0 args, sql) should pass +ok 149 - function_lang_is(schema, func, 0 args, sql) should have the proper description +ok 150 - function_lang_is(schema, func, 0 args, sql) should have the proper diagnostics +ok 151 - function_lang_is(schema, func, args, plpgsql, desc) should pass +ok 152 - function_lang_is(schema, func, args, plpgsql, desc) should have the proper description +ok 153 - function_lang_is(schema, func, args, plpgsql, desc) should have the proper diagnostics +ok 154 - function_lang_is(schema, func, args, plpgsql) should pass +ok 155 - function_lang_is(schema, func, args, plpgsql) should have the proper description +ok 156 - function_lang_is(schema, func, args, plpgsql) should have the proper diagnostics +ok 157 - function_lang_is(schema, func, 0 args, perl, desc) should fail +ok 158 - function_lang_is(schema, func, 0 args, perl, desc) should have the proper description +ok 159 - function_lang_is(schema, func, 0 args, perl, desc) should have the proper diagnostics +ok 160 - function_lang_is(schema, non-func, 0 args, sql, desc) should fail +ok 161 - function_lang_is(schema, non-func, 0 args, sql, desc) should have the proper description +ok 162 - function_lang_is(schema, non-func, 0 args, sql, desc) should have the proper diagnostics +ok 163 - function_lang_is(schema, func, args, plpgsql) should fail +ok 164 - function_lang_is(schema, func, args, plpgsql) should have the proper description +ok 165 - function_lang_is(schema, func, args, plpgsql) should have the proper diagnostics +ok 166 - function_lang_is(schema, func, sql, desc) should pass +ok 167 - function_lang_is(schema, func, sql, desc) should have the proper description +ok 168 - function_lang_is(schema, func, sql, desc) should have the proper diagnostics +ok 169 - function_lang_is(schema, func, sql) should pass +ok 170 - function_lang_is(schema, func, sql) should have the proper description +ok 171 - function_lang_is(schema, func, sql) should have the proper diagnostics +ok 172 - function_lang_is(schema, func, perl, desc) should fail +ok 173 - function_lang_is(schema, func, perl, desc) should have the proper description +ok 174 - function_lang_is(schema, func, perl, desc) should have the proper diagnostics +ok 175 - function_lang_is(schema, non-func, sql, desc) should fail +ok 176 - function_lang_is(schema, non-func, sql, desc) should have the proper description +ok 177 - function_lang_is(schema, non-func, sql, desc) should have the proper diagnostics +ok 178 - function_lang_is(func, 0 args, sql, desc) should pass +ok 179 - function_lang_is(func, 0 args, sql, desc) should have the proper description +ok 180 - function_lang_is(func, 0 args, sql, desc) should have the proper diagnostics +ok 181 - function_lang_is(func, 0 args, sql) should pass +ok 182 - function_lang_is(func, 0 args, sql) should have the proper description +ok 183 - function_lang_is(func, 0 args, sql) should have the proper diagnostics +ok 184 - function_lang_is(func, args, plpgsql, desc) should pass +ok 185 - function_lang_is(func, args, plpgsql, desc) should have the proper description +ok 186 - function_lang_is(func, args, plpgsql, desc) should have the proper diagnostics +ok 187 - function_lang_is(func, args, plpgsql) should pass +ok 188 - function_lang_is(func, args, plpgsql) should have the proper description +ok 189 - function_lang_is(func, args, plpgsql) should have the proper diagnostics +ok 190 - function_lang_is(func, 0 args, perl, desc) should fail +ok 191 - function_lang_is(func, 0 args, perl, desc) should have the proper description +ok 192 - function_lang_is(func, 0 args, perl, desc) should have the proper diagnostics +ok 193 - function_lang_is(non-func, 0 args, sql, desc) should fail +ok 194 - function_lang_is(non-func, 0 args, sql, desc) should have the proper description +ok 195 - function_lang_is(non-func, 0 args, sql, desc) should have the proper diagnostics +ok 196 - function_lang_is(func, args, plpgsql) should fail +ok 197 - function_lang_is(func, args, plpgsql) should have the proper description +ok 198 - function_lang_is(func, args, plpgsql) should have the proper diagnostics +ok 199 - function_lang_is(func, sql, desc) should pass +ok 200 - function_lang_is(func, sql, desc) should have the proper description +ok 201 - function_lang_is(func, sql, desc) should have the proper diagnostics +ok 202 - function_lang_is(func, sql) should pass +ok 203 - function_lang_is(func, sql) should have the proper description +ok 204 - function_lang_is(func, sql) should have the proper diagnostics +ok 205 - function_lang_is(func, perl, desc) should fail +ok 206 - function_lang_is(func, perl, desc) should have the proper description +ok 207 - function_lang_is(func, perl, desc) should have the proper diagnostics +ok 208 - function_lang_is(non-func, sql, desc) should fail +ok 209 - function_lang_is(non-func, sql, desc) should have the proper description +ok 210 - function_lang_is(non-func, sql, desc) should have the proper diagnostics +ok 211 - function_returns(schema, func, 0 args, bool, desc) should pass +ok 212 - function_returns(schema, func, 0 args, bool, desc) should have the proper description +ok 213 - function_returns(schema, func, 0 args, bool, desc) should have the proper diagnostics +ok 214 - function_returns(schema, func, 0 args, bool) should pass +ok 215 - function_returns(schema, func, 0 args, bool) should have the proper description +ok 216 - function_returns(schema, func, 0 args, bool) should have the proper diagnostics +ok 217 - function_returns(schema, func, args, bool, false) should pass +ok 218 - function_returns(schema, func, args, bool, false) should have the proper description +ok 219 - function_returns(schema, func, args, bool, false) should have the proper diagnostics +ok 220 - function_returns(schema, func, args, bool) should pass +ok 221 - function_returns(schema, func, args, bool) should have the proper description +ok 222 - function_returns(schema, func, args, bool) should have the proper diagnostics +ok 223 - function_returns(schema, func, 0 args, setof bool, desc) should pass +ok 224 - function_returns(schema, func, 0 args, setof bool, desc) should have the proper description +ok 225 - function_returns(schema, func, 0 args, setof bool, desc) should have the proper diagnostics +ok 226 - function_returns(schema, func, 0 args, setof bool) should pass +ok 227 - function_returns(schema, func, 0 args, setof bool) should have the proper description +ok 228 - function_returns(schema, func, 0 args, setof bool) should have the proper diagnostics +ok 229 - function_returns(schema, func, bool, desc) should pass +ok 230 - function_returns(schema, func, bool, desc) should have the proper description +ok 231 - function_returns(schema, func, bool, desc) should have the proper diagnostics +ok 232 - function_returns(schema, func, bool) should pass +ok 233 - function_returns(schema, func, bool) should have the proper description +ok 234 - function_returns(schema, func, bool) should have the proper diagnostics +ok 235 - function_returns(schema, other func, bool, false) should pass +ok 236 - function_returns(schema, other func, bool, false) should have the proper description +ok 237 - function_returns(schema, other func, bool, false) should have the proper diagnostics +ok 238 - function_returns(schema, other func, bool) should pass +ok 239 - function_returns(schema, other func, bool) should have the proper description +ok 240 - function_returns(schema, other func, bool) should have the proper diagnostics +ok 241 - function_returns(schema, func, setof bool, desc) should pass +ok 242 - function_returns(schema, func, setof bool, desc) should have the proper description +ok 243 - function_returns(schema, func, setof bool, desc) should have the proper diagnostics +ok 244 - function_returns(schema, func, setof bool) should pass +ok 245 - function_returns(schema, func, setof bool) should have the proper description +ok 246 - function_returns(schema, func, setof bool) should have the proper diagnostics +ok 247 - function_returns(func, 0 args, bool, desc) should pass +ok 248 - function_returns(func, 0 args, bool, desc) should have the proper description +ok 249 - function_returns(func, 0 args, bool, desc) should have the proper diagnostics +ok 250 - function_returns(func, 0 args, bool) should pass +ok 251 - function_returns(func, 0 args, bool) should have the proper description +ok 252 - function_returns(func, 0 args, bool) should have the proper diagnostics +ok 253 - function_returns(func, args, bool, false) should pass +ok 254 - function_returns(func, args, bool, false) should have the proper description +ok 255 - function_returns(func, args, bool, false) should have the proper diagnostics +ok 256 - function_returns(func, args, bool) should pass +ok 257 - function_returns(func, args, bool) should have the proper description +ok 258 - function_returns(func, args, bool) should have the proper diagnostics +ok 259 - function_returns(func, 0 args, setof bool, desc) should pass +ok 260 - function_returns(func, 0 args, setof bool, desc) should have the proper description +ok 261 - function_returns(func, 0 args, setof bool, desc) should have the proper diagnostics +ok 262 - function_returns(func, 0 args, setof bool) should pass +ok 263 - function_returns(func, 0 args, setof bool) should have the proper description +ok 264 - function_returns(func, 0 args, setof bool) should have the proper diagnostics +ok 265 - function_returns(func, bool, desc) should pass +ok 266 - function_returns(func, bool, desc) should have the proper description +ok 267 - function_returns(func, bool, desc) should have the proper diagnostics +ok 268 - function_returns(func, bool) should pass +ok 269 - function_returns(func, bool) should have the proper description +ok 270 - function_returns(func, bool) should have the proper diagnostics +ok 271 - function_returns(other func, bool, false) should pass +ok 272 - function_returns(other func, bool, false) should have the proper description +ok 273 - function_returns(other func, bool, false) should have the proper diagnostics +ok 274 - function_returns(other func, bool) should pass +ok 275 - function_returns(other func, bool) should have the proper description +ok 276 - function_returns(other func, bool) should have the proper diagnostics +ok 277 - function_returns(func, setof bool, desc) should pass +ok 278 - function_returns(func, setof bool, desc) should have the proper description +ok 279 - function_returns(func, setof bool, desc) should have the proper diagnostics +ok 280 - function_returns(func, setof bool) should pass +ok 281 - function_returns(func, setof bool) should have the proper description +ok 282 - function_returns(func, setof bool) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index f504806d6ee5..ce1b1d278bc7 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2011,48 +2011,54 @@ RETURNS TEXT AS $$ SELECT fk_ok( $1, ARRAY[$2], $3, ARRAY[$4] ); $$ LANGUAGE sql; +CREATE OR REPLACE VIEW tap_funky AS + SELECT p.oid AS oid, + n.nspname AS schema, + p.proname AS name, + array_to_string(p.proargtypes::regtype[], ',') AS args, + CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END || t.typname AS returns, + p.prolang AS langoid, + p.proisstrict AS is_strict, + p.proisagg AS is_agg, + p.prosecdef AS is_definer, + p.proretset AS returns_set, + p.provolatile AS is_volatile, + 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 + JOIN pg_catalog.pg_type t ON p.prorettype = t.oid +; + CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME, NAME[] ) RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT TRUE - 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 array_to_string(p.proargtypes::regtype[], ',') = array_to_string($3, ',') - ); + SELECT EXISTS( + SELECT TRUE + FROM tap_funky + WHERE schema = $1 + AND name = $2 + AND args = array_to_string($3, ',') + ); $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME ) RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT TRUE - 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 - ); + 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 pg_catalog.pg_proc p - WHERE p.proname = $1 - AND pg_catalog.pg_function_is_visible(p.oid) - AND array_to_string(p.proargtypes::regtype[], ',') = array_to_string($2, ',') - ); + SELECT EXISTS( + SELECT TRUE + FROM tap_funky + WHERE name = $1 + AND args = array_to_string($2, ',') + AND is_visible + ); $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _got_func ( NAME ) RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT TRUE - FROM pg_catalog.pg_proc p - WHERE p.proname = $1 - AND pg_catalog.pg_function_is_visible(p.oid) - ); + SELECT EXISTS( SELECT TRUE FROM tap_funky WHERE name = $1 AND is_visible); $$ LANGUAGE SQL; -- has_function( schema, function, args[], description ) @@ -2256,14 +2262,8 @@ BEGIN SELECT ARRAY( SELECT quote_ident($2[i]) FROM generate_series(1, array_upper($2, 1)) s(i) - LEFT JOIN pg_catalog.pg_proc p - ON p.proname = $2[i] - AND p.pronamespace = ( - SELECT oid - FROM pg_catalog.pg_namespace - WHERE nspname = $1 - ) - WHERE p.oid IS NULL + 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; @@ -4014,7 +4014,7 @@ RETURNS NAME[] AS $$ JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace WHERE c.relkind = $1 AND n.nspname = $2 - AND c.relname <> 'pg_all_foreign_keys' + AND c.relname NOT IN('pg_all_foreign_keys', 'tap_funky') EXCEPT SELECT $3[i] FROM generate_series(1, array_upper($3, 1)) s(i) @@ -4030,7 +4030,7 @@ RETURNS NAME[] AS $$ WHERE pg_catalog.pg_table_is_visible(c.oid) AND n.nspname <> 'pg_catalog' AND c.relkind = $1 - AND c.relname NOT IN ('__tcache__', '__tresults__', 'pg_all_foreign_keys', '__tresults___numb_seq', '__tcache___id_seq') + AND c.relname NOT IN ('__tcache__', '__tresults__', '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) @@ -4162,10 +4162,7 @@ RETURNS TEXT AS $$ SELECT _are( 'functions', ARRAY( - SELECT p.proname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace - WHERE n.nspname = $1 + SELECT name FROM tap_funky WHERE schema = $1 EXCEPT SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) @@ -4174,10 +4171,7 @@ RETURNS TEXT AS $$ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT - SELECT p.proname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace - WHERE n.nspname = $1 + SELECT name FROM tap_funky WHERE schema = $1 ), $3 ); @@ -4195,11 +4189,7 @@ RETURNS TEXT AS $$ SELECT _are( 'functions', ARRAY( - SELECT p.proname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace - WHERE pg_catalog.pg_function_is_visible(p.oid) - AND n.nspname <> 'pg_catalog' + SELECT name FROM tap_funky WHERE is_visible AND schema <> 'pg_catalog' EXCEPT SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) @@ -4208,11 +4198,7 @@ RETURNS TEXT AS $$ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT - SELECT p.proname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace - WHERE pg_catalog.pg_function_is_visible(p.oid) - AND n.nspname <> 'pg_catalog' + SELECT name FROM tap_funky WHERE is_visible AND schema <> 'pg_catalog' ), $2 ); @@ -4846,58 +4832,87 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _lang ( NAME, NAME, NAME[] ) RETURNS NAME AS $$ SELECT l.lanname - FROM pg_catalog.pg_proc p - JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid - JOIN pg_catalog.pg_language l ON p.prolang = l.oid - WHERE n.nspname = $1 - AND p.proname = $2 - AND array_to_string(p.proargtypes::regtype[], ',') = array_to_string($3, ','); + 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 = array_to_string($3, ',') $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _lang ( NAME, NAME ) RETURNS NAME AS $$ SELECT l.lanname - FROM pg_catalog.pg_proc p - JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid - JOIN pg_catalog.pg_language l ON p.prolang = l.oid - WHERE n.nspname = $1 - AND p.proname = $2 + 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 pg_catalog.pg_proc p - JOIN pg_catalog.pg_language l ON p.prolang = l.oid - WHERE p.proname = $1 - AND array_to_string(p.proargtypes::regtype[], ',') = array_to_string($2, ',') - AND pg_catalog.pg_function_is_visible(p.oid); + FROM tap_funky f + JOIN pg_catalog.pg_language l ON f.langoid = l.oid + WHERE f.name = $1 + AND f.args = array_to_string($2, ',') + AND f.is_visible; $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _lang ( NAME ) RETURNS NAME AS $$ SELECT l.lanname - FROM pg_catalog.pg_proc p - JOIN pg_catalog.pg_language l ON p.prolang = l.oid - WHERE p.proname = $1 - AND pg_catalog.pg_function_is_visible(p.oid); + 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 ) +CREATE OR REPLACE FUNCTION _func_compare( NAME, NAME, NAME[], anyelement, anyelement, TEXT) RETURNS TEXT AS $$ DECLARE - have name := _lang($1, $2, $3); + schema ALIAS FOR $1; + name ALIAS FOR $2; + args ALIAS FOR $3; + have ALIAS FOR $4; + want ALIAS FOR $5; + descr ALIAS FOR $6; BEGIN - IF have IS NOT NULL THEN RETURN is( have, $4, $5 ); END IF; + IF have IS NOT NULL THEN RETURN is( have, want, descr ); END IF; - RETURN ok( FALSE, $5 ) || E'\n' || diag( - ' Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') does not exist' + RETURN ok( FALSE, descr ) || E'\n' || diag( + ' Function ' + || CASE WHEN schema IS NOT NULL THEN quote_ident(schema) || '.' ELSE '' END + || quote_ident(name) || '(' + || array_to_string(args, ', ') || ') does not exist' ); END; $$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION _func_compare( NAME, NAME, anyelement, anyelement, TEXT) +RETURNS TEXT AS $$ +DECLARE + schema ALIAS FOR $1; + name ALIAS FOR $2; + have ALIAS FOR $3; + want ALIAS FOR $4; + descr ALIAS FOR $5; +BEGIN + IF have IS NOT NULL THEN RETURN is( have, want, descr ); END IF; + + RETURN ok( FALSE, descr ) || E'\n' || diag( + ' Function ' + || CASE WHEN schema IS NOT NULL THEN quote_ident(schema) || '.' ELSE '' END + || quote_ident(name) || '() does not exist' + ); +END; +$$ LANGUAGE plpgsql; + +-- 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 $$ @@ -4911,17 +4926,8 @@ $$ LANGUAGE SQL; -- function_lang_is( schema, function, language, description ) CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME, NAME, TEXT ) RETURNS TEXT AS $$ -DECLARE - have name := _lang($1, $2); -BEGIN - IF have IS NOT NULL THEN RETURN is( have, $3, $4 ); END IF; - - RETURN ok( FALSE, $4 ) || E'\n' || diag( - ' Function ' || quote_ident($1) || '.' || quote_ident($2) - || '() does not exist' - ); -END; -$$ LANGUAGE plpgsql; + 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 ) @@ -4936,17 +4942,8 @@ $$ LANGUAGE SQL; -- function_lang_is( function, args[], language, description ) CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME[], NAME, TEXT ) RETURNS TEXT AS $$ -DECLARE - have name := _lang($1, $2); -BEGIN - IF have IS NOT NULL THEN RETURN is( have, $3, $4 ); END IF; - - RETURN ok( FALSE, $4 ) || E'\n' || diag( - ' Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') does not exist' - ); -END; -$$ LANGUAGE plpgsql; + 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 ) @@ -4961,16 +4958,8 @@ $$ LANGUAGE SQL; -- function_lang_is( function, language, description ) CREATE OR REPLACE FUNCTION function_lang_is( NAME, NAME, TEXT ) RETURNS TEXT AS $$ -DECLARE - have name := _lang($1); -BEGIN - IF have IS NOT NULL THEN RETURN is( have, $2, $3 ); END IF; - - RETURN ok( FALSE, $3 ) || E'\n' || diag( - ' Function ' || quote_ident($1) || '() does not exist' - ); -END; -$$ LANGUAGE plpgsql; + 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 ) @@ -4982,6 +4971,97 @@ RETURNS TEXT AS $$ ); $$ 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 = array_to_string($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 = array_to_string($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; + +-- 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), $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), $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), $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), $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; + -- 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 $$ diff --git a/sql/functap.sql b/sql/functap.sql index 71098dd2b1b9..5f7f35c6f10e 100644 --- a/sql/functap.sql +++ b/sql/functap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(210); +SELECT plan(282); --SELECT * FROM no_plan(); CREATE SCHEMA someschema; @@ -12,6 +12,8 @@ AS 'BEGIN RETURN TRUE; END;' LANGUAGE plpgsql; CREATE FUNCTION public.yay () RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL; CREATE FUNCTION public.oww (int, text) RETURNS BOOL AS 'BEGIN RETURN TRUE; END;' LANGUAGE plpgsql; +CREATE FUNCTION public.set () RETURNS SETOF BOOL +AS 'BEGIN RETURN NEXT TRUE; RETURN; END;' LANGUAGE plpgsql; -- XXX Delete when can_ok() is removed. SET client_min_messages = error; @@ -428,7 +430,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'someschema', 'huh', '{}'::name[], 'sql', 'whatever' ), true, - 'function_lang_is(schema, func, 0 args, sql, desc )', + 'function_lang_is(schema, func, 0 args, sql, desc)', 'whatever', '' ); @@ -436,7 +438,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'someschema', 'huh', '{}'::name[], 'sql' ), true, - 'function_lang_is(schema, func, 0 args, sql )', + 'function_lang_is(schema, func, 0 args, sql)', 'Function someschema.huh() should be written in sql', '' ); @@ -444,7 +446,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'someschema', 'bah', '{"integer", "text"}'::name[], 'plpgsql', 'whatever' ), true, - 'function_lang_is(schema, func, args, plpgsql, desc )', + 'function_lang_is(schema, func, args, plpgsql, desc)', 'whatever', '' ); @@ -452,7 +454,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'someschema', 'bah', '{"integer", "text"}'::name[], 'plpgsql' ), true, - 'function_lang_is(schema, func, args, plpgsql )', + 'function_lang_is(schema, func, args, plpgsql)', 'Function someschema.bah(integer, text) should be written in plpgsql', '' ); @@ -460,7 +462,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'someschema', 'huh', '{}'::name[], 'perl', 'whatever' ), false, - 'function_lang_is(schema, func, 0 args, perl, desc )', + 'function_lang_is(schema, func, 0 args, perl, desc)', 'whatever', ' have: sql want: perl' @@ -469,7 +471,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'someschema', 'why', '{}'::name[], 'sql', 'whatever' ), false, - 'function_lang_is(schema, non-func, 0 args, sql, desc )', + 'function_lang_is(schema, non-func, 0 args, sql, desc)', 'whatever', ' Function someschema.why() does not exist' ); @@ -477,7 +479,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'someschema', 'why', '{"integer", "text"}'::name[], 'plpgsql' ), false, - 'function_lang_is(schema, func, args, plpgsql )', + 'function_lang_is(schema, func, args, plpgsql)', 'Function someschema.why(integer, text) should be written in plpgsql', ' Function someschema.why(integer, text) does not exist' ); @@ -485,7 +487,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'someschema', 'huh', 'sql', 'whatever' ), true, - 'function_lang_is(schema, func, sql, desc )', + 'function_lang_is(schema, func, sql, desc)', 'whatever', '' ); @@ -493,7 +495,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'someschema', 'huh', 'sql'::name ), true, - 'function_lang_is(schema, func, sql )', + 'function_lang_is(schema, func, sql)', 'Function someschema.huh() should be written in sql', '' ); @@ -501,7 +503,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'someschema', 'huh', 'perl', 'whatever' ), false, - 'function_lang_is(schema, func, perl, desc )', + 'function_lang_is(schema, func, perl, desc)', 'whatever', ' have: sql want: perl' @@ -510,7 +512,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'someschema', 'why', 'sql', 'whatever' ), false, - 'function_lang_is(schema, non-func, sql, desc )', + 'function_lang_is(schema, non-func, sql, desc)', 'whatever', ' Function someschema.why() does not exist' ); @@ -518,7 +520,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'yay', '{}'::name[], 'sql', 'whatever' ), true, - 'function_lang_is(func, 0 args, sql, desc )', + 'function_lang_is(func, 0 args, sql, desc)', 'whatever', '' ); @@ -526,7 +528,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'yay', '{}'::name[], 'sql' ), true, - 'function_lang_is(func, 0 args, sql )', + 'function_lang_is(func, 0 args, sql)', 'Function yay() should be written in sql', '' ); @@ -534,7 +536,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'oww', '{"integer", "text"}'::name[], 'plpgsql', 'whatever' ), true, - 'function_lang_is(func, args, plpgsql, desc )', + 'function_lang_is(func, args, plpgsql, desc)', 'whatever', '' ); @@ -542,7 +544,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'oww', '{"integer", "text"}'::name[], 'plpgsql' ), true, - 'function_lang_is(func, args, plpgsql )', + 'function_lang_is(func, args, plpgsql)', 'Function oww(integer, text) should be written in plpgsql', '' ); @@ -550,7 +552,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'yay', '{}'::name[], 'perl', 'whatever' ), false, - 'function_lang_is(func, 0 args, perl, desc )', + 'function_lang_is(func, 0 args, perl, desc)', 'whatever', ' have: sql want: perl' @@ -559,7 +561,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'why', '{}'::name[], 'sql', 'whatever' ), false, - 'function_lang_is(non-func, 0 args, sql, desc )', + 'function_lang_is(non-func, 0 args, sql, desc)', 'whatever', ' Function why() does not exist' ); @@ -567,7 +569,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'why', '{"integer", "text"}'::name[], 'plpgsql' ), false, - 'function_lang_is(func, args, plpgsql )', + 'function_lang_is(func, args, plpgsql)', 'Function why(integer, text) should be written in plpgsql', ' Function why(integer, text) does not exist' ); @@ -575,7 +577,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'yay', 'sql', 'whatever' ), true, - 'function_lang_is(func, sql, desc )', + 'function_lang_is(func, sql, desc)', 'whatever', '' ); @@ -583,7 +585,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'yay', 'sql' ), true, - 'function_lang_is(func, sql )', + 'function_lang_is(func, sql)', 'Function yay() should be written in sql', '' ); @@ -591,7 +593,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'yay', 'perl', 'whatever' ), false, - 'function_lang_is(func, perl, desc )', + 'function_lang_is(func, perl, desc)', 'whatever', ' have: sql want: perl' @@ -600,11 +602,205 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_lang_is( 'why', 'sql', 'whatever' ), false, - 'function_lang_is(non-func, sql, desc )', + 'function_lang_is(non-func, sql, desc)', 'whatever', ' Function why() does not exist' ); +/****************************************************************************/ +-- Test function_returns(). +SELECT * FROM check_test( + function_returns( 'someschema', 'huh', '{}'::name[], 'bool', 'whatever' ), + true, + 'function_returns(schema, func, 0 args, bool, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_returns( 'someschema', 'huh', '{}'::name[], 'bool' ), + true, + 'function_returns(schema, func, 0 args, bool)', + 'Function someschema.huh() should return bool', + '' +); + +SELECT * FROM check_test( + function_returns( 'someschema', 'bah', ARRAY['integer', 'text'], 'bool', 'whatever' ), + true, + 'function_returns(schema, func, args, bool, false)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_returns( 'someschema', 'bah', ARRAY['integer', 'text'], 'bool' ), + true, + 'function_returns(schema, func, args, bool)', + 'Function someschema.bah(integer, text) should return bool', + '' +); + +SELECT * FROM check_test( + function_returns( 'public', 'set', '{}'::name[], 'setof bool', 'whatever' ), + true, + 'function_returns(schema, func, 0 args, setof bool, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_returns( 'public', 'set', '{}'::name[], 'setof bool' ), + true, + 'function_returns(schema, func, 0 args, setof bool)', + 'Function public.set() should return setof bool', + '' +); + +SELECT * FROM check_test( + function_returns( 'someschema', 'huh', 'bool', 'whatever' ), + true, + 'function_returns(schema, func, bool, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_returns( 'someschema', 'huh'::name, 'bool' ), + true, + 'function_returns(schema, func, bool)', + 'Function someschema.huh() should return bool', + '' +); + +SELECT * FROM check_test( + function_returns( 'someschema', 'bah', 'bool', 'whatever' ), + true, + 'function_returns(schema, other func, bool, false)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_returns( 'someschema', 'bah'::name, 'bool' ), + true, + 'function_returns(schema, other func, bool)', + 'Function someschema.bah() should return bool', + '' +); + +SELECT * FROM check_test( + function_returns( 'public', 'set', 'setof bool', 'whatever' ), + true, + 'function_returns(schema, func, setof bool, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_returns( 'public', 'set'::name, 'setof bool' ), + true, + 'function_returns(schema, func, setof bool)', + 'Function public.set() should return setof bool', + '' +); + +SELECT * FROM check_test( + function_returns( 'yay', '{}'::name[], 'bool', 'whatever' ), + true, + 'function_returns(func, 0 args, bool, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_returns( 'yay', '{}'::name[], 'bool' ), + true, + 'function_returns(func, 0 args, bool)', + 'Function yay() should return bool', + '' +); + +SELECT * FROM check_test( + function_returns( 'oww', ARRAY['integer', 'text'], 'bool', 'whatever' ), + true, + 'function_returns(func, args, bool, false)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_returns( 'oww', ARRAY['integer', 'text'], 'bool' ), + true, + 'function_returns(func, args, bool)', + 'Function oww(integer, text) should return bool', + '' +); + +SELECT * FROM check_test( + function_returns( 'set', '{}'::name[], 'setof bool', 'whatever' ), + true, + 'function_returns(func, 0 args, setof bool, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_returns( 'set', '{}'::name[], 'setof bool' ), + true, + 'function_returns(func, 0 args, setof bool)', + 'Function set() should return setof bool', + '' +); + +SELECT * FROM check_test( + function_returns( 'yay', 'bool', 'whatever' ), + true, + 'function_returns(func, bool, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_returns( 'yay', 'bool' ), + true, + 'function_returns(func, bool)', + 'Function yay() should return bool', + '' +); + +SELECT * FROM check_test( + function_returns( 'oww', 'bool', 'whatever' ), + true, + 'function_returns(other func, bool, false)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_returns( 'oww', 'bool' ), + true, + 'function_returns(other func, bool)', + 'Function oww() should return bool', + '' +); + +SELECT * FROM check_test( + function_returns( 'set', 'setof bool', 'whatever' ), + true, + 'function_returns(func, setof bool, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_returns( 'set', 'setof bool' ), + true, + 'function_returns(func, setof bool)', + 'Function set() should return setof bool', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From b3c2d277ed53c0b7c53341705a309fd4e152ee3c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 26 May 2009 22:57:24 -0700 Subject: [PATCH 0373/1195] Added `function_is_definer()`. --- Changes | 3 +- README.pgtap | 25 +++++++- expected/functap.out | 56 +++++++++++++++- pgtap.sql.in | 101 ++++++++++++++++++++++++++++- sql/functap.sql | 150 ++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 328 insertions(+), 7 deletions(-) diff --git a/Changes b/Changes index 831e1988bf70..62f9065a7739 100644 --- a/Changes +++ b/Changes @@ -23,7 +23,8 @@ Revision history for pgTAP `can_ok()` function is still available as an alias, but it emits a warning and will be removed in a future version of pgTAP. * Added `hasnt_trigger()`, `hasnt_index()`, and `hasnt_function()`. -* Added `function_lang_is()` and `function_returns()`. +* Added `function_lang_is()`, `function_returns()`, and + `function_is_definer()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index 357b041557ba..901937f53924 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2389,6 +2389,30 @@ If the function does not exist, you'll be told that, too. But then you check with `has_function()` first, right? +### `function_is_definer( schema, function, args[], description )` ### +### `function_is_definer( schema, function, args[] )` ### +### `function_is_definer( schema, function, description )` ### +### `function_is_definer( schema, function )` ### +### `function_is_definer( function, args[], description )` ### +### `function_is_definer( function, args[] )` ### +### `function_is_definer( function, description )` ### +### `function_is_definer( function )` ### + +Tests that a function is a security definer (i.e., a "setuid" function). If +the `:schema` argument is omitted, then the function must be visible in the +search path. If the `:args[]` argument is passed, then the function with that +argument signature will be the one tested; otherwise, a function with any +signature will be checked (pass an empty array to specify a function with an +empty signature). If the `:description` is omitted, a reasonable substitute +will be created. + +If the function does not exist, a handy diagnostic message will let you know: + + # Failed test 290: "Function nasty() should be security definer" + # Function nasty() does not exist + +But then you check with `has_function()` first, right? + ### `enum_has_labels( schema, enum, labels, desc )` ### ### `enum_has_labels( schema, enum, labels )` ### ### `enum_has_labels( enum, labels, desc )` ### @@ -3167,7 +3191,6 @@ To Do ----- * Useful schema testing functions to consider adding: - * `function_is_definer()` * `function_is_agg()` * `function_is_strict()` * `function_is_volatile()` diff --git a/expected/functap.out b/expected/functap.out index f8ffef01e15b..9b9136d4043c 100644 --- a/expected/functap.out +++ b/expected/functap.out @@ -1,5 +1,5 @@ \unset ECHO -1..282 +1..336 ok 1 - simple function should pass ok 2 - simple function should have the proper description ok 3 - simple function should have the proper diagnostics @@ -282,3 +282,57 @@ ok 279 - function_returns(func, setof bool, desc) should have the proper diagnos ok 280 - function_returns(func, setof bool) should pass ok 281 - function_returns(func, setof bool) should have the proper description ok 282 - function_returns(func, setof bool) should have the proper diagnostics +ok 283 - function_is_definer(schema, func, 0 args, desc) should pass +ok 284 - function_is_definer(schema, func, 0 args, desc) should have the proper description +ok 285 - function_is_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 286 - function_is_definer(schema, func, 0 args) should pass +ok 287 - function_is_definer(schema, func, 0 args) should have the proper description +ok 288 - function_is_definer(schema, func, 0 args) should have the proper diagnostics +ok 289 - function_is_definer(schema, func, args, desc) should fail +ok 290 - function_is_definer(schema, func, args, desc) should have the proper description +ok 291 - function_is_definer(schema, func, args, desc) should have the proper diagnostics +ok 292 - function_is_definer(schema, func, args) should fail +ok 293 - function_is_definer(schema, func, args) should have the proper description +ok 294 - function_is_definer(schema, func, args) should have the proper diagnostics +ok 295 - function_is_definer(schema, func, desc) should pass +ok 296 - function_is_definer(schema, func, desc) should have the proper description +ok 297 - function_is_definer(schema, func, desc) should have the proper diagnostics +ok 298 - function_is_definer(schema, func) should pass +ok 299 - function_is_definer(schema, func) should have the proper description +ok 300 - function_is_definer(schema, func) should have the proper diagnostics +ok 301 - function_is_definer(schema, func, 0 args, desc) should pass +ok 302 - function_is_definer(schema, func, 0 args, desc) should have the proper description +ok 303 - function_is_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 304 - function_is_definer(schema, func, 0 args) should pass +ok 305 - function_is_definer(schema, func, 0 args) should have the proper description +ok 306 - function_is_definer(schema, func, 0 args) should have the proper diagnostics +ok 307 - function_is_definer(schema, func, args, desc) should fail +ok 308 - function_is_definer(schema, func, args, desc) should have the proper description +ok 309 - function_is_definer(schema, func, args, desc) should have the proper diagnostics +ok 310 - function_is_definer(schema, func, args) should fail +ok 311 - function_is_definer(schema, func, args) should have the proper description +ok 312 - function_is_definer(schema, func, args) should have the proper diagnostics +ok 313 - function_is_definer(schema, func, desc) should pass +ok 314 - function_is_definer(schema, func, desc) should have the proper description +ok 315 - function_is_definer(schema, func, desc) should have the proper diagnostics +ok 316 - function_is_definer(schema, func) should pass +ok 317 - function_is_definer(schema, func) should have the proper description +ok 318 - function_is_definer(schema, func) should have the proper diagnostics +ok 319 - function_is_definer(func, 0 args, desc) should pass +ok 320 - function_is_definer(func, 0 args, desc) should have the proper description +ok 321 - function_is_definer(func, 0 args, desc) should have the proper diagnostics +ok 322 - function_is_definer(func, 0 args) should pass +ok 323 - function_is_definer(func, 0 args) should have the proper description +ok 324 - function_is_definer(func, 0 args) should have the proper diagnostics +ok 325 - function_is_definer(func, args, desc) should fail +ok 326 - function_is_definer(func, args, desc) should have the proper description +ok 327 - function_is_definer(func, args, desc) should have the proper diagnostics +ok 328 - function_is_definer(func, args) should fail +ok 329 - function_is_definer(func, args) should have the proper description +ok 330 - function_is_definer(func, args) should have the proper diagnostics +ok 331 - function_is_definer(func, desc) should pass +ok 332 - function_is_definer(func, desc) should have the proper description +ok 333 - function_is_definer(func, desc) should have the proper diagnostics +ok 334 - function_is_definer(func) should pass +ok 335 - function_is_definer(func) should have the proper description +ok 336 - function_is_definer(func) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index ce1b1d278bc7..4a0a5ace160c 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -4877,7 +4877,12 @@ DECLARE want ALIAS FOR $5; descr ALIAS FOR $6; BEGIN - IF have IS NOT NULL THEN RETURN is( have, want, descr ); END IF; + IF have IS NOT NULL THEN + RETURN CASE WHEN want IS NULL + THEN ok(have::boolean, descr) + ELSE is( have, want, descr ) + END; + END IF; RETURN ok( FALSE, descr ) || E'\n' || diag( ' Function ' @@ -4897,7 +4902,12 @@ DECLARE want ALIAS FOR $4; descr ALIAS FOR $5; BEGIN - IF have IS NOT NULL THEN RETURN is( have, want, descr ); END IF; + IF have IS NOT NULL THEN + RETURN CASE WHEN want IS NULL + THEN ok(have::boolean, descr) + ELSE is( have, want, descr ) + END; + END IF; RETURN ok( FALSE, descr ) || E'\n' || diag( ' Function ' @@ -5062,6 +5072,93 @@ RETURNS TEXT AS $$ ); $$ 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 = array_to_string($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 = array_to_string($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; + +-- function_is_definer( schema, function, args[], description ) +CREATE OR REPLACE FUNCTION function_is_definer ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, $3, _definer($1, $2, $3), NULL, $4 ); +$$ LANGUAGE SQL; + +-- function_is_definer( schema, function, args[] ) +CREATE OR REPLACE FUNCTION 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; + +-- function_is_definer( schema, function, description ) +CREATE OR REPLACE FUNCTION function_is_definer ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare($1, $2, _definer($1, $2), NULL, $3 ); +$$ LANGUAGE SQL; + +-- function_is_definer( schema, function ) +CREATE OR REPLACE FUNCTION 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; + +-- function_is_definer( function, args[], description ) +CREATE OR REPLACE FUNCTION function_is_definer ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, $2, _definer($1, $2), NULL, $3 ); +$$ LANGUAGE SQL; + +-- function_is_definer( function, args[] ) +CREATE OR REPLACE FUNCTION 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; + +-- function_is_definer( function, description ) +CREATE OR REPLACE FUNCTION function_is_definer( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _func_compare(NULL, $1, _definer($1), NULL, $2 ); +$$ LANGUAGE sql; + +-- function_is_definer( function ) +CREATE OR REPLACE FUNCTION function_is_definer( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _definer($1), 'Function ' || quote_ident($1) || '() should be security definer' ); +$$ 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 $$ diff --git a/sql/functap.sql b/sql/functap.sql index 5f7f35c6f10e..c0b62b5afa87 100644 --- a/sql/functap.sql +++ b/sql/functap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(282); +SELECT plan(336); --SELECT * FROM no_plan(); CREATE SCHEMA someschema; @@ -9,7 +9,7 @@ CREATE FUNCTION someschema.huh () RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL; CREATE FUNCTION someschema.bah (int, text) RETURNS BOOL AS 'BEGIN RETURN TRUE; END;' LANGUAGE plpgsql; -CREATE FUNCTION public.yay () RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL; +CREATE FUNCTION public.yay () RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL SECURITY DEFINER; CREATE FUNCTION public.oww (int, text) RETURNS BOOL AS 'BEGIN RETURN TRUE; END;' LANGUAGE plpgsql; CREATE FUNCTION public.set () RETURNS SETOF BOOL @@ -801,6 +801,152 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test function_is_definer(). +SELECT * FROM check_test( + function_is_definer( 'public', 'yay', '{}'::name[], 'whatever' ), + true, + 'function_is_definer(schema, func, 0 args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_is_definer( 'public', 'yay', '{}'::name[] ), + true, + 'function_is_definer(schema, func, 0 args)', + 'Function public.yay() should be security definer', + '' +); + +SELECT * FROM check_test( + function_is_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + false, + 'function_is_definer(schema, func, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_is_definer( 'public', 'oww', ARRAY['integer', 'text'] ), + false, + 'function_is_definer(schema, func, args)', + 'Function public.oww(integer, text) should be security definer', + '' +); + +SELECT * FROM check_test( + function_is_definer( 'public', 'yay', 'whatever' ), + true, + 'function_is_definer(schema, func, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_is_definer( 'public', 'yay'::name ), + true, + 'function_is_definer(schema, func)', + 'Function public.yay() should be security definer', + '' +); + +SELECT * FROM check_test( + function_is_definer( 'public', 'yay', '{}'::name[], 'whatever' ), + true, + 'function_is_definer(schema, func, 0 args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_is_definer( 'public', 'yay', '{}'::name[] ), + true, + 'function_is_definer(schema, func, 0 args)', + 'Function public.yay() should be security definer', + '' +); + +SELECT * FROM check_test( + function_is_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + false, + 'function_is_definer(schema, func, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_is_definer( 'public', 'oww', ARRAY['integer', 'text'] ), + false, + 'function_is_definer(schema, func, args)', + 'Function public.oww(integer, text) should be security definer', + '' +); + +SELECT * FROM check_test( + function_is_definer( 'public', 'yay', 'whatever' ), + true, + 'function_is_definer(schema, func, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_is_definer( 'public', 'yay'::name ), + true, + 'function_is_definer(schema, func)', + 'Function public.yay() should be security definer', + '' +); + +SELECT * FROM check_test( + function_is_definer( 'yay', '{}'::name[], 'whatever' ), + true, + 'function_is_definer(func, 0 args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_is_definer( 'yay', '{}'::name[] ), + true, + 'function_is_definer(func, 0 args)', + 'Function yay() should be security definer', + '' +); + +SELECT * FROM check_test( + function_is_definer( 'oww', ARRAY['integer', 'text'], 'whatever' ), + false, + 'function_is_definer(func, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_is_definer( 'oww', ARRAY['integer', 'text'] ), + false, + 'function_is_definer(func, args)', + 'Function oww(integer, text) should be security definer', + '' +); + +SELECT * FROM check_test( + function_is_definer( 'yay', 'whatever' ), + true, + 'function_is_definer(func, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_is_definer( 'yay'::name ), + true, + 'function_is_definer(func)', + 'Function yay() should be security definer', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From da3204047bee1f1a53a7ec262f7310eb62ac9d8f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 27 May 2009 11:44:08 -0700 Subject: [PATCH 0374/1195] Added more function assertions. * Added `is_aggregate()`. * Added `is_strict()`. * Added `volatility_is()`. * Renamed `function_is_definer()` to `is_definer()`. Still weighing whether to change it to `is_setuid()` or `is_secdef()`. `is_scurity_definer()` feelds too long. --- Changes | 4 +- README.pgtap | 155 ++++++++++-- expected/functap.out | 282 +++++++++++++++++----- pgtap.sql.in | 397 +++++++++++++++++++++++++----- sql/functap.sql | 559 +++++++++++++++++++++++++++++++++++++++---- 5 files changed, 1224 insertions(+), 173 deletions(-) diff --git a/Changes b/Changes index 62f9065a7739..ac8ddbd33601 100644 --- a/Changes +++ b/Changes @@ -23,8 +23,8 @@ Revision history for pgTAP `can_ok()` function is still available as an alias, but it emits a warning and will be removed in a future version of pgTAP. * Added `hasnt_trigger()`, `hasnt_index()`, and `hasnt_function()`. -* Added `function_lang_is()`, `function_returns()`, and - `function_is_definer()`. +* Added `function_lang_is()`, `function_returns()`, `is_definer()`, + `is_aggregate()`, `is_strict()`, and `volatility_is()`. 0.20 2009-03-29T19:05:40 ------------------------- diff --git a/README.pgtap b/README.pgtap index 901937f53924..5b5d53440ddb 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2361,7 +2361,6 @@ But then you check with `has_function()` first, right? 'The myschema.foo() function should return an integer' ); - SELECT function_returns( 'do_something', 'setof bool' ); SELECT function_returns( 'do_something', ARRAY['integer'], 'bool' ); SELECT function_returns( 'do_something', ARRAY['numeric'], 'numeric' ); @@ -2389,14 +2388,25 @@ If the function does not exist, you'll be told that, too. But then you check with `has_function()` first, right? -### `function_is_definer( schema, function, args[], description )` ### -### `function_is_definer( schema, function, args[] )` ### -### `function_is_definer( schema, function, description )` ### -### `function_is_definer( schema, function )` ### -### `function_is_definer( function, args[], description )` ### -### `function_is_definer( function, args[] )` ### -### `function_is_definer( function, description )` ### -### `function_is_definer( function )` ### +### `is_definer( schema, function, args[], description )` ### +### `is_definer( schema, function, args[] )` ### +### `is_definer( schema, function, description )` ### +### `is_definer( schema, function )` ### +### `is_definer( function, args[], description )` ### +### `is_definer( function, args[] )` ### +### `is_definer( function, description )` ### +### `is_definer( function )` ### + + SELECT is_definer( + 'myschema', + 'foo', + ARRAY['integer', 'text'], + 'The myschema.foo() function should be security definer' + ); + + SELECT is_definer( 'do_something' ); + SELECT is_definer( 'do_something', ARRAY['integer'] ); + SELECT is_definer( 'do_something', ARRAY['numeric'] ); Tests that a function is a security definer (i.e., a "setuid" function). If the `:schema` argument is omitted, then the function must be visible in the @@ -2413,6 +2423,121 @@ If the function does not exist, a handy diagnostic message will let you know: But then you check with `has_function()` first, right? +### `is_strict( schema, function, args[], description )` ### +### `is_strict( schema, function, args[] )` ### +### `is_strict( schema, function, description )` ### +### `is_strict( schema, function )` ### +### `is_strict( function, args[], description )` ### +### `is_strict( function, args[] )` ### +### `is_strict( function, description )` ### +### `is_strict( function )` ### + + SELECT is_strict( + 'myschema', + 'foo', + ARRAY['integer', 'text'], + 'The myschema.foo() function should be strict + ); + + SELECT is_strict( 'do_something' ); + SELECT is_strict( 'do_something', ARRAY['integer'] ); + SELECT is_strict( 'do_something', ARRAY['numeric'] ); + +Tests that a function is a strict, meaning that the function returns null if +any argument is null. If the `:schema` argument is omitted, then the function +must be visible in the search path. If the `:args[]` argument is passed, then +the function with that argument signature will be the one tested; otherwise, a +function with any signature will be checked (pass an empty array to specify a +function with an empty signature). If the `:description` is omitted, a +reasonable substitute will be created. + +If the function does not exist, a handy diagnostic message will let you know: + + # Failed test 290: "Function nasty() should be strict + # Function nasty() does not exist + +But then you check with `has_function()` first, right? + +### `is_aggregate( schema, function, args[], description )` ### +### `is_aggregate( schema, function, args[] )` ### +### `is_aggregate( schema, function, description )` ### +### `is_aggregate( schema, function )` ### +### `is_aggregate( function, args[], description )` ### +### `is_aggregate( function, args[] )` ### +### `is_aggregate( function, description )` ### +### `is_aggregate( function )` ### + + SELECT is_aggregate( + 'myschema', + 'foo', + ARRAY['integer', 'text'], + 'The myschema.foo() function should be strict + ); + + SELECT is_aggregate( 'do_something' ); + SELECT is_aggregate( 'do_something', ARRAY['integer'] ); + SELECT is_aggregate( 'do_something', ARRAY['numeric'] ); + +Tests that a function is an aggregate function. If the `:schema` argument is +omitted, then the function must be visible in the search path. If the +`:args[]` argument is passed, then the function with that argument signature +will be the one tested; otherwise, a function with any signature will be +checked (pass an empty array to specify a function with an empty signature). +If the `:description` is omitted, a reasonable substitute will be created. + +If the function does not exist, a handy diagnostic message will let you know: + + # Failed test 290: "Function nasty() should be strict + # Function nasty() does not exist + +But then you check with `has_function()` first, right? + +### `volatility_is( schema, function, args[], volatility, description )` ### +### `volatility_is( schema, function, args[], volatility )` ### +### `volatility_is( schema, function, volatility, description )` ### +### `volatility_is( schema, function, volatility )` ### +### `volatility_is( function, args[], volatility, description )` ### +### `volatility_is( function, args[], volatility )` ### +### `volatility_is( function, volatility, description )` ### +### `volatility_is( function, volatility )` ### + + SELECT volatility_is( + 'myschema', + 'foo', + ARRAY['integer', 'text'], + 'stable', + 'The myschema.foo() function should be stable + ); + + + SELECT volatility_is( 'do_something', 'immutable' ); + SELECT volatility_is( 'do_something', ARRAY['integer'], 'stable' ); + SELECT volatility_is( 'do_something', ARRAY['numeric'], 'volatile' ); + +Tests the volatility of a function. Supported volailities are "volatile", +"stable", and "immutable". Consult the [`CREATE FUNCTION` documentation +A.](http://www.postgresql.org/docs/current/static/sql-createfunction.html) for +details. The function name is required. If the `:schema` argument is omitted, +then the function must be visible in the search path. If the `:args[]` +argument is passed, then the function with that argument signature will be the +one tested; otherwise, a function with any signature will be checked (pass an +empty array to specify a function with an empty signature). If the +`:description` is omitted, a reasonable substitute will be created. + +In the event of a failure, you'll useful diagnostics will tell you what went +wrong, for example: + + # Failed test 211: "Function mychema.eat(integer, text) should be IMMUTABLE" + # have: VOLATILE + # want: IMMUTABLE + +If the function does not exist, you'll be told that, too. + + # Failed test 212: "Function myschema.grab() should be IMMUTABLE + # Function myschema.grab() does not exist + +But then you check with `has_function()` first, right? + ### `enum_has_labels( schema, enum, labels, desc )` ### ### `enum_has_labels( schema, enum, labels )` ### ### `enum_has_labels( enum, labels, desc )` ### @@ -3189,15 +3314,11 @@ functions do not work under 8.0. Don't even use them there. To Do ----- - * Useful schema testing functions to consider adding: - * `function_is_agg()` - * `function_is_strict()` - * `function_is_volatile()` - * `sequence_has_range()` - * `sequence_increments_by()` - * `sequence_starts_at()` - * `sequence_cycles()` +* `sequence_has_range()` +* `sequence_increments_by()` +* `sequence_starts_at()` +* `sequence_cycles()` Supported Versions ----------------- diff --git a/expected/functap.out b/expected/functap.out index 9b9136d4043c..7f43402069a2 100644 --- a/expected/functap.out +++ b/expected/functap.out @@ -1,5 +1,5 @@ \unset ECHO -1..336 +1..508 ok 1 - simple function should pass ok 2 - simple function should have the proper description ok 3 - simple function should have the proper diagnostics @@ -282,57 +282,229 @@ ok 279 - function_returns(func, setof bool, desc) should have the proper diagnos ok 280 - function_returns(func, setof bool) should pass ok 281 - function_returns(func, setof bool) should have the proper description ok 282 - function_returns(func, setof bool) should have the proper diagnostics -ok 283 - function_is_definer(schema, func, 0 args, desc) should pass -ok 284 - function_is_definer(schema, func, 0 args, desc) should have the proper description -ok 285 - function_is_definer(schema, func, 0 args, desc) should have the proper diagnostics -ok 286 - function_is_definer(schema, func, 0 args) should pass -ok 287 - function_is_definer(schema, func, 0 args) should have the proper description -ok 288 - function_is_definer(schema, func, 0 args) should have the proper diagnostics -ok 289 - function_is_definer(schema, func, args, desc) should fail -ok 290 - function_is_definer(schema, func, args, desc) should have the proper description -ok 291 - function_is_definer(schema, func, args, desc) should have the proper diagnostics -ok 292 - function_is_definer(schema, func, args) should fail -ok 293 - function_is_definer(schema, func, args) should have the proper description -ok 294 - function_is_definer(schema, func, args) should have the proper diagnostics -ok 295 - function_is_definer(schema, func, desc) should pass -ok 296 - function_is_definer(schema, func, desc) should have the proper description -ok 297 - function_is_definer(schema, func, desc) should have the proper diagnostics -ok 298 - function_is_definer(schema, func) should pass -ok 299 - function_is_definer(schema, func) should have the proper description -ok 300 - function_is_definer(schema, func) should have the proper diagnostics -ok 301 - function_is_definer(schema, func, 0 args, desc) should pass -ok 302 - function_is_definer(schema, func, 0 args, desc) should have the proper description -ok 303 - function_is_definer(schema, func, 0 args, desc) should have the proper diagnostics -ok 304 - function_is_definer(schema, func, 0 args) should pass -ok 305 - function_is_definer(schema, func, 0 args) should have the proper description -ok 306 - function_is_definer(schema, func, 0 args) should have the proper diagnostics -ok 307 - function_is_definer(schema, func, args, desc) should fail -ok 308 - function_is_definer(schema, func, args, desc) should have the proper description -ok 309 - function_is_definer(schema, func, args, desc) should have the proper diagnostics -ok 310 - function_is_definer(schema, func, args) should fail -ok 311 - function_is_definer(schema, func, args) should have the proper description -ok 312 - function_is_definer(schema, func, args) should have the proper diagnostics -ok 313 - function_is_definer(schema, func, desc) should pass -ok 314 - function_is_definer(schema, func, desc) should have the proper description -ok 315 - function_is_definer(schema, func, desc) should have the proper diagnostics -ok 316 - function_is_definer(schema, func) should pass -ok 317 - function_is_definer(schema, func) should have the proper description -ok 318 - function_is_definer(schema, func) should have the proper diagnostics -ok 319 - function_is_definer(func, 0 args, desc) should pass -ok 320 - function_is_definer(func, 0 args, desc) should have the proper description -ok 321 - function_is_definer(func, 0 args, desc) should have the proper diagnostics -ok 322 - function_is_definer(func, 0 args) should pass -ok 323 - function_is_definer(func, 0 args) should have the proper description -ok 324 - function_is_definer(func, 0 args) should have the proper diagnostics -ok 325 - function_is_definer(func, args, desc) should fail -ok 326 - function_is_definer(func, args, desc) should have the proper description -ok 327 - function_is_definer(func, args, desc) should have the proper diagnostics -ok 328 - function_is_definer(func, args) should fail -ok 329 - function_is_definer(func, args) should have the proper description -ok 330 - function_is_definer(func, args) should have the proper diagnostics -ok 331 - function_is_definer(func, desc) should pass -ok 332 - function_is_definer(func, desc) should have the proper description -ok 333 - function_is_definer(func, desc) should have the proper diagnostics -ok 334 - function_is_definer(func) should pass -ok 335 - function_is_definer(func) should have the proper description -ok 336 - function_is_definer(func) should have the proper diagnostics +ok 283 - is_definer(schema, func, 0 args, desc) should pass +ok 284 - is_definer(schema, func, 0 args, desc) should have the proper description +ok 285 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 286 - is_definer(schema, func, 0 args) should pass +ok 287 - is_definer(schema, func, 0 args) should have the proper description +ok 288 - is_definer(schema, func, 0 args) should have the proper diagnostics +ok 289 - is_definer(schema, func, args, desc) should fail +ok 290 - is_definer(schema, func, args, desc) should have the proper description +ok 291 - is_definer(schema, func, args, desc) should have the proper diagnostics +ok 292 - is_definer(schema, func, args) should fail +ok 293 - is_definer(schema, func, args) should have the proper description +ok 294 - is_definer(schema, func, args) should have the proper diagnostics +ok 295 - is_definer(schema, func, desc) should pass +ok 296 - is_definer(schema, func, desc) should have the proper description +ok 297 - is_definer(schema, func, desc) should have the proper diagnostics +ok 298 - is_definer(schema, func) should pass +ok 299 - is_definer(schema, func) should have the proper description +ok 300 - is_definer(schema, func) should have the proper diagnostics +ok 301 - is_definer(schema, func, 0 args, desc) should pass +ok 302 - is_definer(schema, func, 0 args, desc) should have the proper description +ok 303 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 304 - is_definer(schema, func, 0 args) should pass +ok 305 - is_definer(schema, func, 0 args) should have the proper description +ok 306 - is_definer(schema, func, 0 args) should have the proper diagnostics +ok 307 - is_definer(schema, func, args, desc) should fail +ok 308 - is_definer(schema, func, args, desc) should have the proper description +ok 309 - is_definer(schema, func, args, desc) should have the proper diagnostics +ok 310 - is_definer(schema, func, args) should fail +ok 311 - is_definer(schema, func, args) should have the proper description +ok 312 - is_definer(schema, func, args) should have the proper diagnostics +ok 313 - is_definer(schema, func, desc) should pass +ok 314 - is_definer(schema, func, desc) should have the proper description +ok 315 - is_definer(schema, func, desc) should have the proper diagnostics +ok 316 - is_definer(schema, func) should pass +ok 317 - is_definer(schema, func) should have the proper description +ok 318 - is_definer(schema, func) should have the proper diagnostics +ok 319 - is_definer(func, 0 args, desc) should pass +ok 320 - is_definer(func, 0 args, desc) should have the proper description +ok 321 - is_definer(func, 0 args, desc) should have the proper diagnostics +ok 322 - is_definer(func, 0 args) should pass +ok 323 - is_definer(func, 0 args) should have the proper description +ok 324 - is_definer(func, 0 args) should have the proper diagnostics +ok 325 - is_definer(func, args, desc) should fail +ok 326 - is_definer(func, args, desc) should have the proper description +ok 327 - is_definer(func, args, desc) should have the proper diagnostics +ok 328 - is_definer(func, args) should fail +ok 329 - is_definer(func, args) should have the proper description +ok 330 - is_definer(func, args) should have the proper diagnostics +ok 331 - is_definer(func, desc) should pass +ok 332 - is_definer(func, desc) should have the proper description +ok 333 - is_definer(func, desc) should have the proper diagnostics +ok 334 - is_definer(func) should pass +ok 335 - is_definer(func) should have the proper description +ok 336 - is_definer(func) should have the proper diagnostics +ok 337 - is_aggregate(schema, func, arg, desc) should pass +ok 338 - is_aggregate(schema, func, arg, desc) should have the proper description +ok 339 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 340 - is_aggregate(schema, func, arg) should pass +ok 341 - is_aggregate(schema, func, arg) should have the proper description +ok 342 - is_aggregate(schema, func, arg) should have the proper diagnostics +ok 343 - is_aggregate(schema, func, args, desc) should fail +ok 344 - is_aggregate(schema, func, args, desc) should have the proper description +ok 345 - is_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 346 - is_aggregate(schema, func, args) should fail +ok 347 - is_aggregate(schema, func, args) should have the proper description +ok 348 - is_aggregate(schema, func, args) should have the proper diagnostics +ok 349 - is_aggregate(schema, func, desc) should pass +ok 350 - is_aggregate(schema, func, desc) should have the proper description +ok 351 - is_aggregate(schema, func, desc) should have the proper diagnostics +ok 352 - is_aggregate(schema, func) should pass +ok 353 - is_aggregate(schema, func) should have the proper description +ok 354 - is_aggregate(schema, func) should have the proper diagnostics +ok 355 - is_aggregate(schema, func, arg, desc) should pass +ok 356 - is_aggregate(schema, func, arg, desc) should have the proper description +ok 357 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 358 - is_aggregate(schema, func, arg) should pass +ok 359 - is_aggregate(schema, func, arg) should have the proper description +ok 360 - is_aggregate(schema, func, arg) should have the proper diagnostics +ok 361 - is_aggregate(schema, func, args, desc) should fail +ok 362 - is_aggregate(schema, func, args, desc) should have the proper description +ok 363 - is_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 364 - is_aggregate(schema, func, args) should fail +ok 365 - is_aggregate(schema, func, args) should have the proper description +ok 366 - is_aggregate(schema, func, args) should have the proper diagnostics +ok 367 - is_aggregate(schema, func, desc) should pass +ok 368 - is_aggregate(schema, func, desc) should have the proper description +ok 369 - is_aggregate(schema, func, desc) should have the proper diagnostics +ok 370 - is_aggregate(schema, func) should pass +ok 371 - is_aggregate(schema, func) should have the proper description +ok 372 - is_aggregate(schema, func) should have the proper diagnostics +ok 373 - is_aggregate(func, arg, desc) should pass +ok 374 - is_aggregate(func, arg, desc) should have the proper description +ok 375 - is_aggregate(func, arg, desc) should have the proper diagnostics +ok 376 - is_aggregate(func, arg) should pass +ok 377 - is_aggregate(func, arg) should have the proper description +ok 378 - is_aggregate(func, arg) should have the proper diagnostics +ok 379 - is_aggregate(func, args, desc) should fail +ok 380 - is_aggregate(func, args, desc) should have the proper description +ok 381 - is_aggregate(func, args, desc) should have the proper diagnostics +ok 382 - is_aggregate(func, args) should fail +ok 383 - is_aggregate(func, args) should have the proper description +ok 384 - is_aggregate(func, args) should have the proper diagnostics +ok 385 - is_aggregate(func, desc) should pass +ok 386 - is_aggregate(func, desc) should have the proper description +ok 387 - is_aggregate(func, desc) should have the proper diagnostics +ok 388 - is_aggregate(func) should pass +ok 389 - is_aggregate(func) should have the proper description +ok 390 - is_aggregate(func) should have the proper diagnostics +ok 391 - is_strict(schema, func, 0 args, desc) should pass +ok 392 - is_strict(schema, func, 0 args, desc) should have the proper description +ok 393 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 394 - is_strict(schema, func, 0 args) should pass +ok 395 - is_strict(schema, func, 0 args) should have the proper description +ok 396 - is_strict(schema, func, 0 args) should have the proper diagnostics +ok 397 - is_strict(schema, func, args, desc) should fail +ok 398 - is_strict(schema, func, args, desc) should have the proper description +ok 399 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 400 - is_strict(schema, func, args) should fail +ok 401 - is_strict(schema, func, args) should have the proper description +ok 402 - is_strict(schema, func, args) should have the proper diagnostics +ok 403 - is_strict(schema, func, desc) should pass +ok 404 - is_strict(schema, func, desc) should have the proper description +ok 405 - is_strict(schema, func, desc) should have the proper diagnostics +ok 406 - is_strict(schema, func) should pass +ok 407 - is_strict(schema, func) should have the proper description +ok 408 - is_strict(schema, func) should have the proper diagnostics +ok 409 - is_strict(schema, func, 0 args, desc) should pass +ok 410 - is_strict(schema, func, 0 args, desc) should have the proper description +ok 411 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 412 - is_strict(schema, func, 0 args) should pass +ok 413 - is_strict(schema, func, 0 args) should have the proper description +ok 414 - is_strict(schema, func, 0 args) should have the proper diagnostics +ok 415 - is_strict(schema, func, args, desc) should fail +ok 416 - is_strict(schema, func, args, desc) should have the proper description +ok 417 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 418 - is_strict(schema, func, args) should fail +ok 419 - is_strict(schema, func, args) should have the proper description +ok 420 - is_strict(schema, func, args) should have the proper diagnostics +ok 421 - is_strict(schema, func, desc) should pass +ok 422 - is_strict(schema, func, desc) should have the proper description +ok 423 - is_strict(schema, func, desc) should have the proper diagnostics +ok 424 - is_strict(schema, func) should pass +ok 425 - is_strict(schema, func) should have the proper description +ok 426 - is_strict(schema, func) should have the proper diagnostics +ok 427 - is_strict(func, 0 args, desc) should pass +ok 428 - is_strict(func, 0 args, desc) should have the proper description +ok 429 - is_strict(func, 0 args, desc) should have the proper diagnostics +ok 430 - is_strict(func, 0 args) should pass +ok 431 - is_strict(func, 0 args) should have the proper description +ok 432 - is_strict(func, 0 args) should have the proper diagnostics +ok 433 - is_strict(func, args, desc) should fail +ok 434 - is_strict(func, args, desc) should have the proper description +ok 435 - is_strict(func, args, desc) should have the proper diagnostics +ok 436 - is_strict(func, args) should fail +ok 437 - is_strict(func, args) should have the proper description +ok 438 - is_strict(func, args) should have the proper diagnostics +ok 439 - is_strict(func, desc) should pass +ok 440 - is_strict(func, desc) should have the proper description +ok 441 - is_strict(func, desc) should have the proper diagnostics +ok 442 - is_strict(func) should pass +ok 443 - is_strict(func) should have the proper description +ok 444 - is_strict(func) should have the proper diagnostics +ok 445 - function_volatility(schema, func, 0 args, volatile, desc) should pass +ok 446 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper description +ok 447 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper diagnostics +ok 448 - function_volatility(schema, func, 0 args, VOLATILE, desc) should pass +ok 449 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper description +ok 450 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper diagnostics +ok 451 - function_volatility(schema, func, 0 args, v, desc) should pass +ok 452 - function_volatility(schema, func, 0 args, v, desc) should have the proper description +ok 453 - function_volatility(schema, func, 0 args, v, desc) should have the proper diagnostics +ok 454 - function_volatility(schema, func, args, immutable, desc) should pass +ok 455 - function_volatility(schema, func, args, immutable, desc) should have the proper description +ok 456 - function_volatility(schema, func, args, immutable, desc) should have the proper diagnostics +ok 457 - function_volatility(schema, func, 0 args, stable, desc) should pass +ok 458 - function_volatility(schema, func, 0 args, stable, desc) should have the proper description +ok 459 - function_volatility(schema, func, 0 args, stable, desc) should have the proper diagnostics +ok 460 - function_volatility(schema, func, 0 args, volatile) should pass +ok 461 - function_volatility(schema, func, 0 args, volatile) should have the proper description +ok 462 - function_volatility(schema, func, 0 args, volatile) should have the proper diagnostics +ok 463 - function_volatility(schema, func, args, immutable) should pass +ok 464 - function_volatility(schema, func, args, immutable) should have the proper description +ok 465 - function_volatility(schema, func, volatile, desc) should pass +ok 466 - function_volatility(schema, func, volatile, desc) should have the proper description +ok 467 - function_volatility(schema, func, volatile, desc) should have the proper diagnostics +ok 468 - function_volatility(schema, func, volatile) should pass +ok 469 - function_volatility(schema, func, volatile) should have the proper description +ok 470 - function_volatility(schema, func, volatile) should have the proper diagnostics +ok 471 - function_volatility(schema, func, immutable, desc) should pass +ok 472 - function_volatility(schema, func, immutable, desc) should have the proper description +ok 473 - function_volatility(schema, func, immutable, desc) should have the proper diagnostics +ok 474 - function_volatility(schema, func, stable, desc) should pass +ok 475 - function_volatility(schema, func, stable, desc) should have the proper description +ok 476 - function_volatility(schema, func, stable, desc) should have the proper diagnostics +ok 477 - function_volatility(func, 0 args, volatile, desc) should pass +ok 478 - function_volatility(func, 0 args, volatile, desc) should have the proper description +ok 479 - function_volatility(func, 0 args, volatile, desc) should have the proper diagnostics +ok 480 - function_volatility(func, 0 args, VOLATILE, desc) should pass +ok 481 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper description +ok 482 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper diagnostics +ok 483 - function_volatility(func, 0 args, v, desc) should pass +ok 484 - function_volatility(func, 0 args, v, desc) should have the proper description +ok 485 - function_volatility(func, 0 args, v, desc) should have the proper diagnostics +ok 486 - function_volatility(func, args, immutable, desc) should pass +ok 487 - function_volatility(func, args, immutable, desc) should have the proper description +ok 488 - function_volatility(func, args, immutable, desc) should have the proper diagnostics +ok 489 - function_volatility(func, 0 args, stable, desc) should pass +ok 490 - function_volatility(func, 0 args, stable, desc) should have the proper description +ok 491 - function_volatility(func, 0 args, stable, desc) should have the proper diagnostics +ok 492 - function_volatility(func, 0 args, volatile) should pass +ok 493 - function_volatility(func, 0 args, volatile) should have the proper description +ok 494 - function_volatility(func, 0 args, volatile) should have the proper diagnostics +ok 495 - function_volatility(func, args, immutable) should pass +ok 496 - function_volatility(func, args, immutable) should have the proper description +ok 497 - function_volatility(func, volatile, desc) should pass +ok 498 - function_volatility(func, volatile, desc) should have the proper description +ok 499 - function_volatility(func, volatile, desc) should have the proper diagnostics +ok 500 - function_volatility(func, volatile) should pass +ok 501 - function_volatility(func, volatile) should have the proper description +ok 502 - function_volatility(func, volatile) should have the proper diagnostics +ok 503 - function_volatility(func, immutable, desc) should pass +ok 504 - function_volatility(func, immutable, desc) should have the proper description +ok 505 - function_volatility(func, immutable, desc) should have the proper diagnostics +ok 506 - function_volatility(func, stable, desc) should pass +ok 507 - function_volatility(func, stable, desc) should have the proper description +ok 508 - function_volatility(func, stable, desc) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 4a0a5ace160c..fcea6965e8bb 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2022,7 +2022,7 @@ CREATE OR REPLACE VIEW tap_funky AS p.proisagg AS is_agg, p.prosecdef AS is_definer, p.proretset AS returns_set, - p.provolatile AS is_volatile, + 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 @@ -3674,7 +3674,7 @@ RETURNS text AS $$ WHEN 'a' THEN 'assignment' WHEN 'e' THEN 'explicit' ELSE 'unknown' END -$$ LANGUAGE SQL; +$$ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION _get_context( NAME, NAME ) RETURNS "char" AS $$ @@ -4741,7 +4741,7 @@ RETURNS text AS $$ WHEN '3' THEN 'INSERT' WHEN '4' THEN 'DELETE' ELSE 'UNKNOWN' END -$$ LANGUAGE SQL; +$$ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION _contract_on( TEXT ) RETURNS "char" AS $$ @@ -4751,7 +4751,7 @@ RETURNS "char" AS $$ WHEN 'i' THEN '3'::"char" WHEN 'd' THEN '4'::"char" ELSE '0'::"char" END -$$ LANGUAGE SQL; +$$ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION _rule_on( NAME, NAME, NAME ) RETURNS "char" AS $$ @@ -4829,44 +4829,6 @@ RETURNS TEXT AS $$ ); $$ 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 = array_to_string($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 = array_to_string($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; - CREATE OR REPLACE FUNCTION _func_compare( NAME, NAME, NAME[], anyelement, anyelement, TEXT) RETURNS TEXT AS $$ DECLARE @@ -4917,6 +4879,44 @@ BEGIN END; $$ LANGUAGE plpgsql; +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 = array_to_string($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 = array_to_string($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 $$ @@ -5100,14 +5100,14 @@ RETURNS BOOLEAN AS $$ SELECT is_definer FROM tap_funky WHERE name = $1 AND is_visible; $$ LANGUAGE SQL; --- function_is_definer( schema, function, args[], description ) -CREATE OR REPLACE FUNCTION function_is_definer ( NAME, NAME, NAME[], TEXT ) +-- 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), NULL, $4 ); $$ LANGUAGE SQL; --- function_is_definer( schema, function, args[] ) -CREATE OR REPLACE FUNCTION function_is_definer( NAME, NAME, NAME[] ) +-- is_definer( schema, function, args[] ) +CREATE OR REPLACE FUNCTION is_definer( NAME, NAME, NAME[] ) RETURNS TEXT AS $$ SELECT ok( _definer($1, $2, $3), @@ -5116,14 +5116,14 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; --- function_is_definer( schema, function, description ) -CREATE OR REPLACE FUNCTION function_is_definer ( NAME, NAME, TEXT ) +-- 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), NULL, $3 ); $$ LANGUAGE SQL; --- function_is_definer( schema, function ) -CREATE OR REPLACE FUNCTION function_is_definer( NAME, NAME ) +-- is_definer( schema, function ) +CREATE OR REPLACE FUNCTION is_definer( NAME, NAME ) RETURNS TEXT AS $$ SELECT ok( _definer($1, $2), @@ -5131,14 +5131,14 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; --- function_is_definer( function, args[], description ) -CREATE OR REPLACE FUNCTION function_is_definer ( NAME, NAME[], TEXT ) +-- 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), NULL, $3 ); $$ LANGUAGE SQL; --- function_is_definer( function, args[] ) -CREATE OR REPLACE FUNCTION function_is_definer( NAME, NAME[] ) +-- is_definer( function, args[] ) +CREATE OR REPLACE FUNCTION is_definer( NAME, NAME[] ) RETURNS TEXT AS $$ SELECT ok( _definer($1, $2), @@ -5147,18 +5147,299 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; --- function_is_definer( function, description ) -CREATE OR REPLACE FUNCTION function_is_definer( NAME, TEXT ) +-- is_definer( function, description ) +CREATE OR REPLACE FUNCTION is_definer( NAME, TEXT ) RETURNS TEXT AS $$ SELECT _func_compare(NULL, $1, _definer($1), NULL, $2 ); $$ LANGUAGE sql; --- function_is_definer( function ) -CREATE OR REPLACE FUNCTION function_is_definer( NAME ) +-- 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; +CREATE OR REPLACE FUNCTION _agg ( NAME, NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT is_agg + FROM tap_funky + WHERE schema = $1 + AND name = $2 + AND args = array_to_string($3, ',') +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _agg ( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT is_agg FROM tap_funky WHERE schema = $1 AND name = $2 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _agg ( NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT is_agg + FROM tap_funky + WHERE name = $1 + AND args = array_to_string($2, ',') + AND is_visible; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _agg ( NAME ) +RETURNS BOOLEAN AS $$ + SELECT is_agg FROM tap_funky WHERE name = $1 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, _agg($1, $2, $3), NULL, $4 ); +$$ LANGUAGE SQL; + +-- is_aggregate( schema, function, args[] ) +CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + _agg($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, _agg($1, $2), NULL, $3 ); +$$ LANGUAGE SQL; + +-- is_aggregate( schema, function ) +CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _agg($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, _agg($1, $2), NULL, $3 ); +$$ LANGUAGE SQL; + +-- is_aggregate( function, args[] ) +CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + _agg($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, _agg($1), NULL, $2 ); +$$ LANGUAGE sql; + +-- is_aggregate( function ) +CREATE OR REPLACE FUNCTION is_aggregate( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _agg($1), 'Function ' || quote_ident($1) || '() should 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 = array_to_string($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 = array_to_string($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), NULL, $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), NULL, $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), NULL, $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), NULL, $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; + +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 = array_to_string($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 = array_to_string($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 $$ diff --git a/sql/functap.sql b/sql/functap.sql index c0b62b5afa87..403d4dc96614 100644 --- a/sql/functap.sql +++ b/sql/functap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(336); +SELECT plan(508); --SELECT * FROM no_plan(); CREATE SCHEMA someschema; @@ -9,11 +9,18 @@ CREATE FUNCTION someschema.huh () RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL; CREATE FUNCTION someschema.bah (int, text) RETURNS BOOL AS 'BEGIN RETURN TRUE; END;' LANGUAGE plpgsql; -CREATE FUNCTION public.yay () RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL SECURITY DEFINER; +CREATE FUNCTION public.yay () RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL STRICT SECURITY DEFINER VOLATILE; CREATE FUNCTION public.oww (int, text) RETURNS BOOL -AS 'BEGIN RETURN TRUE; END;' LANGUAGE plpgsql; +AS 'BEGIN RETURN TRUE; END;' LANGUAGE plpgsql IMMUTABLE; CREATE FUNCTION public.set () RETURNS SETOF BOOL -AS 'BEGIN RETURN NEXT TRUE; RETURN; END;' LANGUAGE plpgsql; +AS 'BEGIN RETURN NEXT TRUE; RETURN; END;' LANGUAGE plpgsql STABLE; + +CREATE AGGREGATE public.tap_accum ( + sfunc = array_append, + basetype = anyelement, + stype = anyarray, + initcond = '{}' +); -- XXX Delete when can_ok() is removed. SET client_min_messages = error; @@ -802,151 +809,621 @@ SELECT * FROM check_test( ); /****************************************************************************/ --- Test function_is_definer(). +-- Test is_definer(). SELECT * FROM check_test( - function_is_definer( 'public', 'yay', '{}'::name[], 'whatever' ), + is_definer( 'public', 'yay', '{}'::name[], 'whatever' ), true, - 'function_is_definer(schema, func, 0 args, desc)', + 'is_definer(schema, func, 0 args, desc)', 'whatever', '' ); SELECT * FROM check_test( - function_is_definer( 'public', 'yay', '{}'::name[] ), + is_definer( 'public', 'yay', '{}'::name[] ), true, - 'function_is_definer(schema, func, 0 args)', + 'is_definer(schema, func, 0 args)', 'Function public.yay() should be security definer', '' ); SELECT * FROM check_test( - function_is_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + is_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), false, - 'function_is_definer(schema, func, args, desc)', + 'is_definer(schema, func, args, desc)', 'whatever', '' ); SELECT * FROM check_test( - function_is_definer( 'public', 'oww', ARRAY['integer', 'text'] ), + is_definer( 'public', 'oww', ARRAY['integer', 'text'] ), false, - 'function_is_definer(schema, func, args)', + 'is_definer(schema, func, args)', 'Function public.oww(integer, text) should be security definer', '' ); SELECT * FROM check_test( - function_is_definer( 'public', 'yay', 'whatever' ), + is_definer( 'public', 'yay', 'whatever' ), true, - 'function_is_definer(schema, func, desc)', + 'is_definer(schema, func, desc)', 'whatever', '' ); SELECT * FROM check_test( - function_is_definer( 'public', 'yay'::name ), + is_definer( 'public', 'yay'::name ), true, - 'function_is_definer(schema, func)', + 'is_definer(schema, func)', 'Function public.yay() should be security definer', '' ); SELECT * FROM check_test( - function_is_definer( 'public', 'yay', '{}'::name[], 'whatever' ), + is_definer( 'public', 'yay', '{}'::name[], 'whatever' ), true, - 'function_is_definer(schema, func, 0 args, desc)', + 'is_definer(schema, func, 0 args, desc)', 'whatever', '' ); SELECT * FROM check_test( - function_is_definer( 'public', 'yay', '{}'::name[] ), + is_definer( 'public', 'yay', '{}'::name[] ), true, - 'function_is_definer(schema, func, 0 args)', + 'is_definer(schema, func, 0 args)', 'Function public.yay() should be security definer', '' ); SELECT * FROM check_test( - function_is_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + is_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), false, - 'function_is_definer(schema, func, args, desc)', + 'is_definer(schema, func, args, desc)', 'whatever', '' ); SELECT * FROM check_test( - function_is_definer( 'public', 'oww', ARRAY['integer', 'text'] ), + is_definer( 'public', 'oww', ARRAY['integer', 'text'] ), false, - 'function_is_definer(schema, func, args)', + 'is_definer(schema, func, args)', 'Function public.oww(integer, text) should be security definer', '' ); SELECT * FROM check_test( - function_is_definer( 'public', 'yay', 'whatever' ), + is_definer( 'public', 'yay', 'whatever' ), true, - 'function_is_definer(schema, func, desc)', + 'is_definer(schema, func, desc)', 'whatever', '' ); SELECT * FROM check_test( - function_is_definer( 'public', 'yay'::name ), + is_definer( 'public', 'yay'::name ), true, - 'function_is_definer(schema, func)', + 'is_definer(schema, func)', 'Function public.yay() should be security definer', '' ); SELECT * FROM check_test( - function_is_definer( 'yay', '{}'::name[], 'whatever' ), + is_definer( 'yay', '{}'::name[], 'whatever' ), true, - 'function_is_definer(func, 0 args, desc)', + 'is_definer(func, 0 args, desc)', 'whatever', '' ); SELECT * FROM check_test( - function_is_definer( 'yay', '{}'::name[] ), + is_definer( 'yay', '{}'::name[] ), true, - 'function_is_definer(func, 0 args)', + 'is_definer(func, 0 args)', 'Function yay() should be security definer', '' ); SELECT * FROM check_test( - function_is_definer( 'oww', ARRAY['integer', 'text'], 'whatever' ), + is_definer( 'oww', ARRAY['integer', 'text'], 'whatever' ), false, - 'function_is_definer(func, args, desc)', + 'is_definer(func, args, desc)', 'whatever', '' ); SELECT * FROM check_test( - function_is_definer( 'oww', ARRAY['integer', 'text'] ), + is_definer( 'oww', ARRAY['integer', 'text'] ), false, - 'function_is_definer(func, args)', + 'is_definer(func, args)', 'Function oww(integer, text) should be security definer', '' ); SELECT * FROM check_test( - function_is_definer( 'yay', 'whatever' ), + is_definer( 'yay', 'whatever' ), true, - 'function_is_definer(func, desc)', + 'is_definer(func, desc)', 'whatever', '' ); SELECT * FROM check_test( - function_is_definer( 'yay'::name ), + is_definer( 'yay'::name ), true, - 'function_is_definer(func)', + 'is_definer(func)', 'Function yay() should be security definer', '' ); +/****************************************************************************/ +-- Test is_aggregate(). +SELECT * FROM check_test( + is_aggregate( 'public', 'tap_accum', ARRAY['anyelement'], 'whatever' ), + true, + 'is_aggregate(schema, func, arg, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'public', 'tap_accum', ARRAY['anyelement'] ), + true, + 'is_aggregate(schema, func, arg)', + 'Function public.tap_accum(anyelement) should be an aggregate function', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + false, + 'is_aggregate(schema, func, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), + false, + 'is_aggregate(schema, func, args)', + 'Function public.oww(integer, text) should be an aggregate function', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'public', 'tap_accum', 'whatever' ), + true, + 'is_aggregate(schema, func, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'public', 'tap_accum'::name ), + true, + 'is_aggregate(schema, func)', + 'Function public.tap_accum() should be an aggregate function', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'public', 'tap_accum', ARRAY['anyelement'], 'whatever' ), + true, + 'is_aggregate(schema, func, arg, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'public', 'tap_accum', ARRAY['anyelement'] ), + true, + 'is_aggregate(schema, func, arg)', + 'Function public.tap_accum(anyelement) should be an aggregate function', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + false, + 'is_aggregate(schema, func, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), + false, + 'is_aggregate(schema, func, args)', + 'Function public.oww(integer, text) should be an aggregate function', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'public', 'tap_accum', 'whatever' ), + true, + 'is_aggregate(schema, func, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'public', 'tap_accum'::name ), + true, + 'is_aggregate(schema, func)', + 'Function public.tap_accum() should be an aggregate function', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'tap_accum', ARRAY['anyelement'], 'whatever' ), + true, + 'is_aggregate(func, arg, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'tap_accum', ARRAY['anyelement'] ), + true, + 'is_aggregate(func, arg)', + 'Function tap_accum(anyelement) should be an aggregate function', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'oww', ARRAY['integer', 'text'], 'whatever' ), + false, + 'is_aggregate(func, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'oww', ARRAY['integer', 'text'] ), + false, + 'is_aggregate(func, args)', + 'Function oww(integer, text) should be an aggregate function', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'tap_accum', 'whatever' ), + true, + 'is_aggregate(func, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'tap_accum'::name ), + true, + 'is_aggregate(func)', + 'Function tap_accum() should be an aggregate function', + '' +); + +/****************************************************************************/ +-- Test is_strict(). +SELECT * FROM check_test( + is_strict( 'public', 'yay', '{}'::name[], 'whatever' ), + true, + 'is_strict(schema, func, 0 args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_strict( 'public', 'yay', '{}'::name[] ), + true, + 'is_strict(schema, func, 0 args)', + 'Function public.yay() should be strict', + '' +); + +SELECT * FROM check_test( + is_strict( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + false, + 'is_strict(schema, func, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_strict( 'public', 'oww', ARRAY['integer', 'text'] ), + false, + 'is_strict(schema, func, args)', + 'Function public.oww(integer, text) should be strict', + '' +); + +SELECT * FROM check_test( + is_strict( 'public', 'yay', 'whatever' ), + true, + 'is_strict(schema, func, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_strict( 'public', 'yay'::name ), + true, + 'is_strict(schema, func)', + 'Function public.yay() should be strict', + '' +); + +SELECT * FROM check_test( + is_strict( 'public', 'yay', '{}'::name[], 'whatever' ), + true, + 'is_strict(schema, func, 0 args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_strict( 'public', 'yay', '{}'::name[] ), + true, + 'is_strict(schema, func, 0 args)', + 'Function public.yay() should be strict', + '' +); + +SELECT * FROM check_test( + is_strict( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + false, + 'is_strict(schema, func, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_strict( 'public', 'oww', ARRAY['integer', 'text'] ), + false, + 'is_strict(schema, func, args)', + 'Function public.oww(integer, text) should be strict', + '' +); + +SELECT * FROM check_test( + is_strict( 'public', 'yay', 'whatever' ), + true, + 'is_strict(schema, func, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_strict( 'public', 'yay'::name ), + true, + 'is_strict(schema, func)', + 'Function public.yay() should be strict', + '' +); + +SELECT * FROM check_test( + is_strict( 'yay', '{}'::name[], 'whatever' ), + true, + 'is_strict(func, 0 args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_strict( 'yay', '{}'::name[] ), + true, + 'is_strict(func, 0 args)', + 'Function yay() should be strict', + '' +); + +SELECT * FROM check_test( + is_strict( 'oww', ARRAY['integer', 'text'], 'whatever' ), + false, + 'is_strict(func, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_strict( 'oww', ARRAY['integer', 'text'] ), + false, + 'is_strict(func, args)', + 'Function oww(integer, text) should be strict', + '' +); + +SELECT * FROM check_test( + is_strict( 'yay', 'whatever' ), + true, + 'is_strict(func, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_strict( 'yay'::name ), + true, + 'is_strict(func)', + 'Function yay() should be strict', + '' +); + +/****************************************************************************/ +-- Test volatility_is(). +SELECT * FROM check_test( + volatility_is( 'public', 'yay', '{}'::name[], 'volatile', 'whatever' ), + true, + 'function_volatility(schema, func, 0 args, volatile, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + volatility_is( 'public', 'yay', '{}'::name[], 'VOLATILE', 'whatever' ), + true, + 'function_volatility(schema, func, 0 args, VOLATILE, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + volatility_is( 'public', 'yay', '{}'::name[], 'v', 'whatever' ), + true, + 'function_volatility(schema, func, 0 args, v, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + volatility_is( 'public', 'oww', ARRAY['integer', 'text'], 'immutable', 'whatever' ), + true, + 'function_volatility(schema, func, args, immutable, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + volatility_is( 'public', 'set', '{}'::name[], 'stable', 'whatever' ), + true, + 'function_volatility(schema, func, 0 args, stable, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + volatility_is( 'public', 'yay', '{}'::name[], 'volatile' ), + true, + 'function_volatility(schema, func, 0 args, volatile)', + 'Function public.yay() should be VOLATILE', + '' +); + +SELECT * FROM check_test( + volatility_is( 'public', 'oww', ARRAY['integer', 'text'], 'immutable' ), + true, + 'function_volatility(schema, func, args, immutable)', + 'Function public.oww(integer, text) should be IMMUTABLE' + '' +); + +SELECT * FROM check_test( + volatility_is( 'public', 'yay', 'volatile', 'whatever' ), + true, + 'function_volatility(schema, func, volatile, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + volatility_is( 'public', 'yay'::name, 'volatile' ), + true, + 'function_volatility(schema, func, volatile)', + 'Function public.yay() should be VOLATILE', + '' +); + +SELECT * FROM check_test( + volatility_is( 'public', 'oww', 'immutable', 'whatever' ), + true, + 'function_volatility(schema, func, immutable, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + volatility_is( 'public', 'set', 'stable', 'whatever' ), + true, + 'function_volatility(schema, func, stable, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + volatility_is( 'yay', '{}'::name[], 'volatile', 'whatever' ), + true, + 'function_volatility(func, 0 args, volatile, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + volatility_is( 'yay', '{}'::name[], 'VOLATILE', 'whatever' ), + true, + 'function_volatility(func, 0 args, VOLATILE, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + volatility_is( 'yay', '{}'::name[], 'v', 'whatever' ), + true, + 'function_volatility(func, 0 args, v, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + volatility_is( 'oww', ARRAY['integer', 'text'], 'immutable', 'whatever' ), + true, + 'function_volatility(func, args, immutable, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + volatility_is( 'set', '{}'::name[], 'stable', 'whatever' ), + true, + 'function_volatility(func, 0 args, stable, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + volatility_is( 'yay', '{}'::name[], 'volatile' ), + true, + 'function_volatility(func, 0 args, volatile)', + 'Function yay() should be VOLATILE', + '' +); + +SELECT * FROM check_test( + volatility_is( 'oww', ARRAY['integer', 'text'], 'immutable' ), + true, + 'function_volatility(func, args, immutable)', + 'Function oww(integer, text) should be IMMUTABLE' + '' +); + +SELECT * FROM check_test( + volatility_is( 'yay', 'volatile', 'whatever' ), + true, + 'function_volatility(func, volatile, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + volatility_is( 'yay'::name, 'volatile' ), + true, + 'function_volatility(func, volatile)', + 'Function yay() should be VOLATILE', + '' +); + +SELECT * FROM check_test( + volatility_is( 'oww', 'immutable', 'whatever' ), + true, + 'function_volatility(func, immutable, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + volatility_is( 'set', 'stable', 'whatever' ), + true, + 'function_volatility(func, stable, desc)', + 'whatever', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From c8c6ec708a0897b162bcef1f6b9ccdac5c5d9d0b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 27 May 2009 11:47:19 -0700 Subject: [PATCH 0375/1195] Put function testing assertions in their own doc section. --- README.pgtap | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/README.pgtap b/README.pgtap index 5b5d53440ddb..27cc14103d3d 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2268,12 +2268,14 @@ fact, like so: But you really ought to call `has_language()` first so that you never get that far. -Database Deets --------------- +Feeling Funky +------------- -Tables aren't the only objects in the database, as you well know. These -assertions close the gap by letting you test the attributes of other database -objects. +Perhaps more important than testing the database schema is testing your custom +functions. Especially if you write functions that provide the interface for +clients to interact with the database, making sure that they work will save +you time in the long run. So check out these assertions to maintain your +sanity. ### `can( schema, functions[], description )` ### ### `can( schema, functions[] )` ### @@ -2538,6 +2540,13 @@ If the function does not exist, you'll be told that, too. But then you check with `has_function()` first, right? +Database Deets +-------------- + +Tables and functions aren't the only objects in the database, as you well +know. These assertions close the gap by letting you test the attributes of +other database objects. + ### `enum_has_labels( schema, enum, labels, desc )` ### ### `enum_has_labels( schema, enum, labels )` ### ### `enum_has_labels( enum, labels, desc )` ### From 1f12dc0d6a4588951389a5b822392719401376da Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 27 May 2009 11:53:24 -0700 Subject: [PATCH 0376/1195] Moved a couple of functions from the tables section to the functions and deets sections of the docs. --- README.pgtap | 84 ++++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/README.pgtap b/README.pgtap index 27cc14103d3d..b3b903c0fc98 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2226,48 +2226,6 @@ type, like so: # have: btree # want: hash -### `trigger_is( schema, table, trigger, schema, function, description )` ### -### `trigger_is( schema, table, trigger, schema, function )` ### -### `trigger_is( table, trigger, function, description )` ### -### `trigger_is( table, trigger, function )` ### - - SELECT trigger_is( - 'myschema', - 'sometable', - 'sometrigger', - 'myschema', - 'somefunction', - 'Trigger "sometrigger" should call somefunction()' - ); - -Tests that the specified trigger calls the named function. If not, it outputs -a useful diagnostic: - - # Failed test 31: "Trigger set_users_pass should call hash_password()" - # have: hash_pass - # want: hash_password - -### `language_is_trusted( language, description )` ### -### `language_is_trusted( language )` ### - - SELECT language_is_trusted( 'plperl', 'PL/Perl should be trusted' ); - -Tests that the specified procedural language is trusted. See the [CREATE -LANGUAGE](http://www.postgresql.org/docs/current/static/sql-createlanguage.html -"CREATE LANGUAGE") documentation for details on trusted and untrusted -procedural languages. If the `:description` argument is not passed, a suitably -useful default will be created. - -In the event that the language in question does not exist in the database, -`language_is_trusted()` will emit a diagnostic message to alert you to this -fact, like so: - - # Failed test 523: "Procedural language plomgwtf should be trusted" - # Procedural language plomgwtf does not exist - -But you really ought to call `has_language()` first so that you never get that -far. - Feeling Funky ------------- @@ -2540,6 +2498,27 @@ If the function does not exist, you'll be told that, too. But then you check with `has_function()` first, right? +### `trigger_is( schema, table, trigger, schema, function, description )` ### +### `trigger_is( schema, table, trigger, schema, function )` ### +### `trigger_is( table, trigger, function, description )` ### +### `trigger_is( table, trigger, function )` ### + + SELECT trigger_is( + 'myschema', + 'sometable', + 'sometrigger', + 'myschema', + 'somefunction', + 'Trigger "sometrigger" should call somefunction()' + ); + +Tests that the specified trigger calls the named function. If not, it outputs +a useful diagnostic: + + # Failed test 31: "Trigger set_users_pass should call hash_password()" + # have: hash_pass + # want: hash_password + Database Deets -------------- @@ -2547,6 +2526,27 @@ Tables and functions aren't the only objects in the database, as you well know. These assertions close the gap by letting you test the attributes of other database objects. +### `language_is_trusted( language, description )` ### +### `language_is_trusted( language )` ### + + SELECT language_is_trusted( 'plperl', 'PL/Perl should be trusted' ); + +Tests that the specified procedural language is trusted. See the [CREATE +LANGUAGE](http://www.postgresql.org/docs/current/static/sql-createlanguage.html +"CREATE LANGUAGE") documentation for details on trusted and untrusted +procedural languages. If the `:description` argument is not passed, a suitably +useful default will be created. + +In the event that the language in question does not exist in the database, +`language_is_trusted()` will emit a diagnostic message to alert you to this +fact, like so: + + # Failed test 523: "Procedural language plomgwtf should be trusted" + # Procedural language plomgwtf does not exist + +But you really ought to call `has_language()` first so that you never get that +far. + ### `enum_has_labels( schema, enum, labels, desc )` ### ### `enum_has_labels( schema, enum, labels )` ### ### `enum_has_labels( enum, labels, desc )` ### From b23db4313c64b66021e318e7b5ff166778b1a31d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 27 May 2009 11:55:04 -0700 Subject: [PATCH 0377/1195] Somehow missed this when incrementing the version number a while back. --- README.pgtap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index b3b903c0fc98..c418adfb890e 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.20 +pgTAP 0.21 ========== pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and From d4f8ed71a2c984e7770174508f13edbced9ac3fa Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 27 May 2009 11:56:37 -0700 Subject: [PATCH 0378/1195] Updated `uninstall_pgtap.sql.in`. --- uninstall_pgtap.sql.in | 196 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 194 insertions(+), 2 deletions(-) diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 3e1d71148630..d8bcb0397217 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -18,6 +18,164 @@ DROP FUNCTION check_test( TEXT, BOOLEAN ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); +DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT, BOOLEAN ); +DROP FUNCTION volatility_is( NAME, TEXT ); +DROP FUNCTION volatility_is( NAME, TEXT, TEXT ); +DROP FUNCTION volatility_is( NAME, NAME[], TEXT ); +DROP FUNCTION volatility_is( NAME, NAME[], TEXT, TEXT ); +DROP FUNCTION volatility_is( NAME, NAME, TEXT ); +DROP FUNCTION volatility_is( NAME, NAME, TEXT, TEXT ); +DROP FUNCTION volatility_is( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION volatility_is( NAME, NAME, NAME[], TEXT, TEXT ); +DROP FUNCTION _vol ( NAME ); +DROP FUNCTION _vol ( NAME, NAME[] ); +DROP FUNCTION _vol ( NAME, NAME ); +DROP FUNCTION _vol ( NAME, NAME, NAME[] ); +DROP FUNCTION _refine_vol( text ); +DROP FUNCTION _expand_vol( char ); +DROP FUNCTION is_strict( NAME ); +DROP FUNCTION is_strict( NAME, TEXT ); +DROP FUNCTION is_strict( NAME, NAME[] ); +DROP FUNCTION is_strict ( NAME, NAME[], TEXT ); +DROP FUNCTION is_strict( NAME, NAME ); +DROP FUNCTION is_strict ( NAME, NAME, TEXT ); +DROP FUNCTION is_strict( NAME, NAME, NAME[] ); +DROP FUNCTION is_strict ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _strict ( NAME ); +DROP FUNCTION _strict ( NAME, NAME[] ); +DROP FUNCTION _strict ( NAME, NAME ); +DROP FUNCTION _strict ( NAME, NAME, NAME[] ); +DROP FUNCTION is_aggregate( NAME ); +DROP FUNCTION is_aggregate( NAME, TEXT ); +DROP FUNCTION is_aggregate( NAME, NAME[] ); +DROP FUNCTION is_aggregate ( NAME, NAME[], TEXT ); +DROP FUNCTION is_aggregate( NAME, NAME ); +DROP FUNCTION is_aggregate ( NAME, NAME, TEXT ); +DROP FUNCTION is_aggregate( NAME, NAME, NAME[] ); +DROP FUNCTION is_aggregate ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _agg ( NAME ); +DROP FUNCTION _agg ( NAME, NAME[] ); +DROP FUNCTION _agg ( NAME, NAME ); +DROP FUNCTION _agg ( NAME, NAME, NAME[] ); +DROP FUNCTION is_definer( NAME ); +DROP FUNCTION is_definer( NAME, TEXT ); +DROP FUNCTION is_definer( NAME, NAME[] ); +DROP FUNCTION is_definer ( NAME, NAME[], TEXT ); +DROP FUNCTION is_definer( NAME, NAME ); +DROP FUNCTION is_definer ( NAME, NAME, TEXT ); +DROP FUNCTION is_definer( NAME, NAME, NAME[] ); +DROP FUNCTION is_definer ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _definer ( NAME ); +DROP FUNCTION _definer ( NAME, NAME[] ); +DROP FUNCTION _definer ( NAME, NAME ); +DROP FUNCTION _definer ( NAME, NAME, NAME[] ); +DROP FUNCTION function_returns( NAME, TEXT ); +DROP FUNCTION function_returns( NAME, TEXT, TEXT ); +DROP FUNCTION function_returns( NAME, NAME[], TEXT ); +DROP FUNCTION function_returns( NAME, NAME[], TEXT, TEXT ); +DROP FUNCTION function_returns( NAME, NAME, TEXT ); +DROP FUNCTION function_returns( NAME, NAME, TEXT, TEXT ); +DROP FUNCTION function_returns( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION function_returns( NAME, NAME, NAME[], TEXT, TEXT ); +DROP FUNCTION _returns ( NAME ); +DROP FUNCTION _returns ( NAME, NAME[] ); +DROP FUNCTION _returns ( NAME, NAME ); +DROP FUNCTION _returns ( NAME, NAME, NAME[] ); +DROP FUNCTION function_lang_is( NAME, NAME ); +DROP FUNCTION function_lang_is( NAME, NAME, TEXT ); +DROP FUNCTION function_lang_is( NAME, NAME[], NAME ); +DROP FUNCTION function_lang_is( NAME, NAME[], NAME, TEXT ); +DROP FUNCTION function_lang_is( NAME, NAME, NAME ); +DROP FUNCTION function_lang_is( NAME, NAME, NAME, TEXT ); +DROP FUNCTION function_lang_is( NAME, NAME, NAME[], NAME ); +DROP FUNCTION function_lang_is( NAME, NAME, NAME[], NAME, TEXT ); +DROP FUNCTION _lang ( NAME ); +DROP FUNCTION _lang ( NAME, NAME[] ); +DROP FUNCTION _lang ( NAME, NAME ); +DROP FUNCTION _lang ( NAME, NAME, NAME[] ); +DROP FUNCTION _func_compare( NAME, NAME, anyelement, anyelement, TEXT); +DROP FUNCTION _func_compare( NAME, NAME, NAME[], anyelement, anyelement, TEXT); +DROP FUNCTION rule_is_on( NAME, NAME, TEXT ); +DROP FUNCTION rule_is_on( NAME, NAME, TEXT, TEXT ); +DROP FUNCTION rule_is_on( NAME, NAME, NAME, TEXT ); +DROP FUNCTION rule_is_on( NAME, NAME, NAME, TEXT, TEXT ); +DROP FUNCTION _rule_on( NAME, NAME ); +DROP FUNCTION _rule_on( NAME, NAME, NAME ); +DROP FUNCTION _contract_on( TEXT ); +DROP FUNCTION _expand_on( char ); +DROP FUNCTION rule_is_instead( NAME, NAME ); +DROP FUNCTION rule_is_instead( NAME, NAME, TEXT ); +DROP FUNCTION rule_is_instead( NAME, NAME, NAME ); +DROP FUNCTION rule_is_instead( NAME, NAME, NAME, TEXT ); +DROP FUNCTION hasnt_rule( NAME, NAME ); +DROP FUNCTION hasnt_rule( NAME, NAME, TEXT ); +DROP FUNCTION hasnt_rule( NAME, NAME, NAME ); +DROP FUNCTION hasnt_rule( NAME, NAME, NAME, TEXT ); +DROP FUNCTION has_rule( NAME, NAME ); +DROP FUNCTION has_rule( NAME, NAME, TEXT ); +DROP FUNCTION has_rule( NAME, NAME, NAME ); +DROP FUNCTION has_rule( NAME, NAME, NAME, TEXT ); +DROP FUNCTION _is_instead( NAME, NAME ); +DROP FUNCTION _is_instead( NAME, NAME, NAME ); +DROP FUNCTION rules_are( NAME, NAME[] ); +DROP FUNCTION rules_are( NAME, NAME[], TEXT ); +DROP FUNCTION rules_are( NAME, NAME, NAME[] ); +DROP FUNCTION rules_are( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION opclasses_are ( NAME[] ); +DROP FUNCTION opclasses_are ( NAME[], TEXT ); +DROP FUNCTION opclasses_are ( NAME, NAME[] ); +DROP FUNCTION opclasses_are ( NAME, NAME[], TEXT ); +DROP FUNCTION hasnt_opclass( NAME ); +DROP FUNCTION hasnt_opclass( NAME, TEXT ); +DROP FUNCTION hasnt_opclass( NAME, NAME ); +DROP FUNCTION hasnt_opclass( NAME, NAME, TEXT ); +DROP FUNCTION has_opclass( NAME ); +DROP FUNCTION has_opclass( NAME, TEXT ); +DROP FUNCTION has_opclass( NAME, NAME ); +DROP FUNCTION has_opclass( NAME, NAME, TEXT ); +DROP FUNCTION _opc_exists( NAME, NAME ); +DROP FUNCTION language_is_trusted( NAME ); +DROP FUNCTION language_is_trusted( NAME, TEXT ); +DROP FUNCTION hasnt_language( NAME ); +DROP FUNCTION hasnt_language( NAME, TEXT ); +DROP FUNCTION has_language( NAME ); +DROP FUNCTION has_language( NAME, TEXT ); +DROP FUNCTION _is_trusted( NAME ); +DROP FUNCTION languages_are( NAME[] ); +DROP FUNCTION languages_are( NAME[], TEXT ); +DROP FUNCTION groups_are( NAME[] ); +DROP FUNCTION groups_are( NAME[], TEXT ); +DROP FUNCTION users_are( NAME[] ); +DROP FUNCTION users_are( NAME[], TEXT ); +DROP FUNCTION indexes_are( NAME, NAME[] ); +DROP FUNCTION indexes_are( NAME, NAME[], TEXT ); +DROP FUNCTION indexes_are( NAME, NAME, NAME[] ); +DROP FUNCTION indexes_are( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION functions_are ( NAME[] ); +DROP FUNCTION functions_are ( NAME[], TEXT ); +DROP FUNCTION functions_are ( NAME, NAME[] ); +DROP FUNCTION functions_are ( NAME, NAME[], TEXT ); +DROP FUNCTION sequences_are ( NAME[] ); +DROP FUNCTION sequences_are ( NAME, NAME[] ); +DROP FUNCTION sequences_are ( NAME[], TEXT ); +DROP FUNCTION sequences_are ( NAME, NAME[], TEXT ); +DROP FUNCTION views_are ( NAME[] ); +DROP FUNCTION views_are ( NAME, NAME[] ); +DROP FUNCTION views_are ( NAME[], TEXT ); +DROP FUNCTION views_are ( NAME, NAME[], TEXT ); +DROP FUNCTION tables_are ( NAME[] ); +DROP FUNCTION tables_are ( NAME, NAME[] ); +DROP FUNCTION tables_are ( NAME[], TEXT ); +DROP FUNCTION tables_are ( NAME, NAME[], TEXT ); +DROP FUNCTION _missing ( CHAR, NAME[] ); +DROP FUNCTION _missing ( CHAR, NAME, NAME[] ); +DROP FUNCTION _extras ( CHAR, NAME[] ); +DROP FUNCTION _extras ( CHAR, NAME, NAME[] ); +DROP FUNCTION schemas_are ( NAME[] ); +DROP FUNCTION schemas_are ( NAME[], TEXT ); +DROP FUNCTION tablespaces_are ( NAME[] ); +DROP FUNCTION tablespaces_are ( NAME[], TEXT ); +DROP FUNCTION _are ( text, name[], name[], TEXT ); DROP FUNCTION has_rightop ( NAME, NAME ); DROP FUNCTION has_rightop ( NAME, NAME, TEXT ); DROP FUNCTION has_rightop ( NAME, NAME, NAME ); @@ -125,9 +283,16 @@ DROP FUNCTION trigger_is ( NAME, NAME, NAME ); DROP FUNCTION trigger_is ( NAME, NAME, NAME, text ); DROP FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME ); DROP FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME, text ); +DROP FUNCTION hasnt_trigger ( NAME, NAME ); +DROP FUNCTION hasnt_trigger ( NAME, NAME, TEXT ); +DROP FUNCTION hasnt_trigger ( NAME, NAME, NAME ); +DROP FUNCTION hasnt_trigger ( NAME, NAME, NAME, TEXT ); DROP FUNCTION has_trigger ( NAME, NAME ); +DROP FUNCTION has_trigger ( NAME, NAME, TEXT ); DROP FUNCTION has_trigger ( NAME, NAME, NAME ); DROP FUNCTION has_trigger ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION _trig ( NAME, NAME ); +DROP FUNCTION _trig ( NAME, NAME, NAME ); DROP FUNCTION index_is_type ( NAME, NAME ); DROP FUNCTION index_is_type ( NAME, NAME, NAME ); DROP FUNCTION index_is_type ( NAME, NAME, NAME, NAME ); @@ -144,12 +309,14 @@ DROP FUNCTION index_is_unique ( NAME ); DROP FUNCTION index_is_unique ( NAME, NAME ); DROP FUNCTION index_is_unique ( NAME, NAME, NAME ); DROP FUNCTION index_is_unique ( NAME, NAME, NAME, text ); +DROP FUNCTION hasnt_index ( NAME, NAME ); +DROP FUNCTION hasnt_index ( NAME, NAME, TEXT ); +DROP FUNCTION hasnt_index ( NAME, NAME, NAME ); +DROP FUNCTION hasnt_index ( NAME, NAME, NAME, TEXT ); DROP FUNCTION has_index ( NAME, NAME ); DROP FUNCTION has_index ( NAME, NAME, text ); DROP FUNCTION has_index ( NAME, NAME, NAME ); DROP FUNCTION has_index ( NAME, NAME, NAME, text ); -DROP FUNCTION _has_index( NAME, NAME ); -DROP FUNCTION _has_index( NAME, NAME, NAME ); DROP FUNCTION _is_schema( NAME ); DROP FUNCTION has_index ( NAME, NAME, NAME[] ); DROP FUNCTION has_index ( NAME, NAME, NAME[], text ); @@ -159,6 +326,8 @@ DROP FUNCTION has_index ( NAME, NAME, NAME, NAME[] ); DROP FUNCTION has_index ( NAME, NAME, NAME, NAME[], text ); DROP FUNCTION _iexpr( NAME, NAME); DROP FUNCTION _iexpr( NAME, NAME, NAME); +DROP FUNCTION _have_index( NAME, NAME); +DROP FUNCTION _have_index( NAME, NAME, NAME); DROP FUNCTION _ikeys( NAME, NAME); DROP FUNCTION _ikeys( NAME, NAME, NAME); DROP FUNCTION can ( NAME[] ); @@ -174,6 +343,27 @@ DROP FUNCTION can_ok( NAME, NAME ); DROP FUNCTION can_ok ( NAME, NAME, TEXT ); DROP FUNCTION can_ok( NAME, NAME, NAME[] ); DROP FUNCTION can_ok ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION hasnt_function( NAME ); +DROP FUNCTION hasnt_function( NAME, TEXT ); +DROP FUNCTION hasnt_function( NAME, NAME[] ); +DROP FUNCTION hasnt_function ( NAME, NAME[], TEXT ); +DROP FUNCTION hasnt_function( NAME, NAME ); +DROP FUNCTION hasnt_function ( NAME, NAME, TEXT ); +DROP FUNCTION hasnt_function( NAME, NAME, NAME[] ); +DROP FUNCTION hasnt_function ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION has_function( NAME ); +DROP FUNCTION has_function( NAME, TEXT ); +DROP FUNCTION has_function( NAME, NAME[] ); +DROP FUNCTION has_function ( NAME, NAME[], TEXT ); +DROP FUNCTION has_function( NAME, NAME ); +DROP FUNCTION has_function ( NAME, NAME, TEXT ); +DROP FUNCTION has_function( NAME, NAME, NAME[] ); +DROP FUNCTION has_function ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _got_func ( NAME ); +DROP FUNCTION _got_func ( NAME, NAME[] ); +DROP FUNCTION _got_func ( NAME, NAME ); +DROP FUNCTION _got_func ( NAME, NAME, NAME[] ); +DROP VIEW tap_funky AS; DROP FUNCTION fk_ok ( NAME, NAME, NAME, NAME ); DROP FUNCTION fk_ok ( NAME, NAME, NAME, NAME, TEXT ); DROP FUNCTION fk_ok ( NAME, NAME, NAME, NAME, NAME, TEXT ); @@ -303,6 +493,8 @@ DROP FUNCTION has_table ( NAME, TEXT ); DROP FUNCTION has_table ( NAME, NAME, TEXT ); DROP FUNCTION _rexists ( CHAR, NAME ); DROP FUNCTION _rexists ( CHAR, NAME, NAME ); +DROP FUNCTION performs_ok ( TEXT, NUMERIC ); +DROP FUNCTION performs_ok ( TEXT, NUMERIC, TEXT ); DROP FUNCTION lives_ok ( TEXT ); DROP FUNCTION lives_ok ( TEXT, TEXT ); DROP FUNCTION throws_ok ( TEXT, int4 ); From a050b3bf1f75e25e4ea5b1330b52484983c2b481 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 28 May 2009 10:37:18 -0700 Subject: [PATCH 0379/1195] Fixed test failure when $TAPSCHEMA` is set. --- sql/ruletap.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/ruletap.sql b/sql/ruletap.sql index b989e95e52a5..c431982c6aeb 100644 --- a/sql/ruletap.sql +++ b/sql/ruletap.sql @@ -18,7 +18,7 @@ CREATE RULE upd_me AS ON UPDATE TO public.sometab DO ALSO SELECT now(); CREATE TABLE public.toview ( id INT ); CREATE RULE "_RETURN" AS ON SELECT TO public.toview DO INSTEAD SELECT 42 AS id; -CREATE TABLE widgets (id int); +CREATE TABLE public.widgets (id int); CREATE RULE del_me AS ON DELETE TO public.widgets DO NOTHING; CREATE RULE ins_me AS ON INSERT TO public.widgets DO NOTHING; From 3c2f7ef58f365a3fd5bb67f76d52aa3053e91544 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 28 May 2009 11:11:52 -0700 Subject: [PATCH 0380/1195] Fix for 8.3 compatibility. It's better to use dispatch to polymorphic functions, anyway. --- pgtap.sql.in | 100 ++++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 54 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index fcea6965e8bb..8e6e77f29544 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -4829,55 +4829,47 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _func_compare( NAME, NAME, NAME[], anyelement, anyelement, TEXT) +CREATE OR REPLACE FUNCTION _nosuch( NAME, NAME, NAME[]) RETURNS TEXT AS $$ -DECLARE - schema ALIAS FOR $1; - name ALIAS FOR $2; - args ALIAS FOR $3; - have ALIAS FOR $4; - want ALIAS FOR $5; - descr ALIAS FOR $6; -BEGIN - IF have IS NOT NULL THEN - RETURN CASE WHEN want IS NULL - THEN ok(have::boolean, descr) - ELSE is( have, want, descr ) - END; - END IF; - - RETURN ok( FALSE, descr ) || E'\n' || diag( + SELECT E'\n' || diag( ' Function ' - || CASE WHEN schema IS NOT NULL THEN quote_ident(schema) || '.' ELSE '' END - || quote_ident(name) || '(' - || array_to_string(args, ', ') || ') does not exist' + || CASE WHEN $1 IS NOT NULL THEN quote_ident($1) || '.' ELSE '' END + || quote_ident($2) || '(' + || array_to_string($3, ', ') || ') does not exist' ); -END; -$$ LANGUAGE plpgsql; +$$ 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 $$ -DECLARE - schema ALIAS FOR $1; - name ALIAS FOR $2; - have ALIAS FOR $3; - want ALIAS FOR $4; - descr ALIAS FOR $5; -BEGIN - IF have IS NOT NULL THEN - RETURN CASE WHEN want IS NULL - THEN ok(have::boolean, descr) - ELSE is( have, want, descr ) - END; - END IF; + SELECT CASE WHEN $3 IS NULL + THEN ok( FALSE, $5 ) || _nosuch($1, $2, '{}') + ELSE is( $3, $4, $5 ) + END; +$$ LANGUAGE SQL; - RETURN ok( FALSE, descr ) || E'\n' || diag( - ' Function ' - || CASE WHEN schema IS NOT NULL THEN quote_ident(schema) || '.' ELSE '' END - || quote_ident(name) || '() does not exist' - ); -END; -$$ LANGUAGE plpgsql; +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 $$ @@ -5103,7 +5095,7 @@ $$ 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), NULL, $4 ); + SELECT _func_compare($1, $2, $3, _definer($1, $2, $3), $4 ); $$ LANGUAGE SQL; -- is_definer( schema, function, args[] ) @@ -5119,7 +5111,7 @@ $$ 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), NULL, $3 ); + SELECT _func_compare($1, $2, _definer($1, $2), $3 ); $$ LANGUAGE SQL; -- is_definer( schema, function ) @@ -5134,7 +5126,7 @@ $$ 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), NULL, $3 ); + SELECT _func_compare(NULL, $1, $2, _definer($1, $2), $3 ); $$ LANGUAGE SQL; -- is_definer( function, args[] ) @@ -5150,7 +5142,7 @@ $$ LANGUAGE sql; -- is_definer( function, description ) CREATE OR REPLACE FUNCTION is_definer( NAME, TEXT ) RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, _definer($1), NULL, $2 ); + SELECT _func_compare(NULL, $1, _definer($1), $2 ); $$ LANGUAGE sql; -- is_definer( function ) @@ -5190,7 +5182,7 @@ $$ 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, _agg($1, $2, $3), NULL, $4 ); + SELECT _func_compare($1, $2, $3, _agg($1, $2, $3), $4 ); $$ LANGUAGE SQL; -- is_aggregate( schema, function, args[] ) @@ -5206,7 +5198,7 @@ $$ LANGUAGE sql; -- is_aggregate( schema, function, description ) CREATE OR REPLACE FUNCTION is_aggregate ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, _agg($1, $2), NULL, $3 ); + SELECT _func_compare($1, $2, _agg($1, $2), $3 ); $$ LANGUAGE SQL; -- is_aggregate( schema, function ) @@ -5221,7 +5213,7 @@ $$ 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, _agg($1, $2), NULL, $3 ); + SELECT _func_compare(NULL, $1, $2, _agg($1, $2), $3 ); $$ LANGUAGE SQL; -- is_aggregate( function, args[] ) @@ -5237,7 +5229,7 @@ $$ LANGUAGE sql; -- is_aggregate( function, description ) CREATE OR REPLACE FUNCTION is_aggregate( NAME, TEXT ) RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, _agg($1), NULL, $2 ); + SELECT _func_compare(NULL, $1, _agg($1), $2 ); $$ LANGUAGE sql; -- is_aggregate( function ) @@ -5277,7 +5269,7 @@ $$ 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), NULL, $4 ); + SELECT _func_compare($1, $2, $3, _strict($1, $2, $3), $4 ); $$ LANGUAGE SQL; -- is_strict( schema, function, args[] ) @@ -5293,7 +5285,7 @@ $$ 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), NULL, $3 ); + SELECT _func_compare($1, $2, _strict($1, $2), $3 ); $$ LANGUAGE SQL; -- is_strict( schema, function ) @@ -5308,7 +5300,7 @@ $$ 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), NULL, $3 ); + SELECT _func_compare(NULL, $1, $2, _strict($1, $2), $3 ); $$ LANGUAGE SQL; -- is_strict( function, args[] ) @@ -5324,7 +5316,7 @@ $$ LANGUAGE sql; -- is_strict( function, description ) CREATE OR REPLACE FUNCTION is_strict( NAME, TEXT ) RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, _strict($1), NULL, $2 ); + SELECT _func_compare(NULL, $1, _strict($1), $2 ); $$ LANGUAGE sql; -- is_strict( function ) From 8ea975698f0282cae9b18aa27390bb34805034b8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 28 May 2009 11:15:38 -0700 Subject: [PATCH 0381/1195] Added new functions to `uninstall_pgtap.sql.in`. --- uninstall_pgtap.sql.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index d8bcb0397217..711c963754aa 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -93,8 +93,11 @@ DROP FUNCTION _lang ( NAME ); DROP FUNCTION _lang ( NAME, NAME[] ); DROP FUNCTION _lang ( NAME, NAME ); DROP FUNCTION _lang ( NAME, NAME, NAME[] ); +DROP FUNCTION _func_compare( NAME, NAME, boolean, TEXT); DROP FUNCTION _func_compare( NAME, NAME, anyelement, anyelement, TEXT); +DROP FUNCTION _func_compare( NAME, NAME, NAME[], boolean, TEXT); DROP FUNCTION _func_compare( NAME, NAME, NAME[], anyelement, anyelement, TEXT); +DROP FUNCTION _nosuch( NAME, NAME, NAME[]); DROP FUNCTION rule_is_on( NAME, NAME, TEXT ); DROP FUNCTION rule_is_on( NAME, NAME, TEXT, TEXT ); DROP FUNCTION rule_is_on( NAME, NAME, NAME, TEXT ); From 5bd89e31889d55a4db055587a9a20f677b8f38f6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 28 May 2009 11:39:04 -0700 Subject: [PATCH 0382/1195] Fixed compatibility on 8.2. * "set" was reserved back then, I guess, so changed the test function fro `set()` to `pet()`. * Updated patches. --- compat/install-8.2.patch | 10 +++++----- compat/uninstall-8.2.patch | 6 +++--- sql/functap.sql | 34 +++++++++++++++++----------------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 1daa9e2b0e3b..8d9d5fac0ef4 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,10 +1,10 @@ ---- pgtap.sql.orig 2009-03-28 15:33:47.000000000 -0400 -+++ pgtap.sql 2009-03-28 15:34:28.000000000 -0400 -@@ -3041,63 +3041,6 @@ +--- pgtap.sql.orig 2009-05-28 11:32:50.000000000 -0700 ++++ pgtap.sql 2009-05-28 11:34:37.000000000 -0700 +@@ -3288,63 +3288,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, desc ) +--- enum_has_labels( schema, enum, labels, description ) -CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT is( @@ -33,7 +33,7 @@ - ); -$$ LANGUAGE sql; - ---- enum_has_labels( enum, labels, desc ) +--- enum_has_labels( enum, labels, description ) -CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT is( diff --git a/compat/uninstall-8.2.patch b/compat/uninstall-8.2.patch index 2230f048bc29..b2046a1c39e7 100644 --- a/compat/uninstall-8.2.patch +++ b/compat/uninstall-8.2.patch @@ -1,6 +1,6 @@ ---- uninstall_pgtap.sql.orig 2009-03-28 15:33:53.000000000 -0400 -+++ uninstall_pgtap.sql 2009-03-28 15:35:20.000000000 -0400 -@@ -83,10 +83,6 @@ +--- uninstall_pgtap.sql.in 2009-05-28 11:16:45.000000000 -0700 ++++ uninstall_pgtap.sql 2009-05-28 11:24:32.000000000 -0700 +@@ -243,10 +243,6 @@ DROP FUNCTION has_role( NAME ); DROP FUNCTION has_role( NAME, TEXT ); DROP FUNCTION _has_role( NAME ); diff --git a/sql/functap.sql b/sql/functap.sql index 403d4dc96614..235312ab9faf 100644 --- a/sql/functap.sql +++ b/sql/functap.sql @@ -12,7 +12,7 @@ AS 'BEGIN RETURN TRUE; END;' LANGUAGE plpgsql; CREATE FUNCTION public.yay () RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL STRICT SECURITY DEFINER VOLATILE; CREATE FUNCTION public.oww (int, text) RETURNS BOOL AS 'BEGIN RETURN TRUE; END;' LANGUAGE plpgsql IMMUTABLE; -CREATE FUNCTION public.set () RETURNS SETOF BOOL +CREATE FUNCTION public.pet () RETURNS SETOF BOOL AS 'BEGIN RETURN NEXT TRUE; RETURN; END;' LANGUAGE plpgsql STABLE; CREATE AGGREGATE public.tap_accum ( @@ -649,7 +649,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'public', 'set', '{}'::name[], 'setof bool', 'whatever' ), + function_returns( 'public', 'pet', '{}'::name[], 'setof bool', 'whatever' ), true, 'function_returns(schema, func, 0 args, setof bool, desc)', 'whatever', @@ -657,10 +657,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'public', 'set', '{}'::name[], 'setof bool' ), + function_returns( 'public', 'pet', '{}'::name[], 'setof bool' ), true, 'function_returns(schema, func, 0 args, setof bool)', - 'Function public.set() should return setof bool', + 'Function public.pet() should return setof bool', '' ); @@ -697,7 +697,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'public', 'set', 'setof bool', 'whatever' ), + function_returns( 'public', 'pet', 'setof bool', 'whatever' ), true, 'function_returns(schema, func, setof bool, desc)', 'whatever', @@ -705,10 +705,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'public', 'set'::name, 'setof bool' ), + function_returns( 'public', 'pet'::name, 'setof bool' ), true, 'function_returns(schema, func, setof bool)', - 'Function public.set() should return setof bool', + 'Function public.pet() should return setof bool', '' ); @@ -745,7 +745,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'set', '{}'::name[], 'setof bool', 'whatever' ), + function_returns( 'pet', '{}'::name[], 'setof bool', 'whatever' ), true, 'function_returns(func, 0 args, setof bool, desc)', 'whatever', @@ -753,10 +753,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'set', '{}'::name[], 'setof bool' ), + function_returns( 'pet', '{}'::name[], 'setof bool' ), true, 'function_returns(func, 0 args, setof bool)', - 'Function set() should return setof bool', + 'Function pet() should return setof bool', '' ); @@ -793,7 +793,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'set', 'setof bool', 'whatever' ), + function_returns( 'pet', 'setof bool', 'whatever' ), true, 'function_returns(func, setof bool, desc)', 'whatever', @@ -801,10 +801,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'set', 'setof bool' ), + function_returns( 'pet', 'setof bool' ), true, 'function_returns(func, setof bool)', - 'Function set() should return setof bool', + 'Function pet() should return setof bool', '' ); @@ -1281,7 +1281,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - volatility_is( 'public', 'set', '{}'::name[], 'stable', 'whatever' ), + volatility_is( 'public', 'pet', '{}'::name[], 'stable', 'whatever' ), true, 'function_volatility(schema, func, 0 args, stable, desc)', 'whatever', @@ -1329,7 +1329,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - volatility_is( 'public', 'set', 'stable', 'whatever' ), + volatility_is( 'public', 'pet', 'stable', 'whatever' ), true, 'function_volatility(schema, func, stable, desc)', 'whatever', @@ -1369,7 +1369,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - volatility_is( 'set', '{}'::name[], 'stable', 'whatever' ), + volatility_is( 'pet', '{}'::name[], 'stable', 'whatever' ), true, 'function_volatility(func, 0 args, stable, desc)', 'whatever', @@ -1417,7 +1417,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - volatility_is( 'set', 'stable', 'whatever' ), + volatility_is( 'pet', 'stable', 'whatever' ), true, 'function_volatility(func, stable, desc)', 'whatever', From 3e95d7c7c64bee7ee58e85950662326a25305a8f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 28 May 2009 15:21:30 -0700 Subject: [PATCH 0383/1195] Repair 8.1 compatibility. * Update `compat/install_8.1.patch`. * Fix test functions in `sql/aretap.sql` that returned `NULL` instead of an empty array. --- compat/install-8.1.patch | 16 ++++++++-------- sql/aretap.sql | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index 04356429d8aa..732379f8d5b3 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ ---- pgtap.sql.orig 2009-03-28 15:49:36.000000000 -0400 -+++ pgtap.sql 2009-03-28 15:49:36.000000000 -0400 -@@ -3742,7 +3742,7 @@ +--- pgtap.sql.orig 2009-05-28 14:44:51.000000000 -0700 ++++ pgtap.sql 2009-05-28 14:44:51.000000000 -0700 +@@ -5520,7 +5520,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -9,7 +9,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -3750,8 +3750,8 @@ +@@ -5528,8 +5528,8 @@ -- 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. @@ -20,7 +20,7 @@ END LOOP; END LOOP; RETURN; -@@ -3819,14 +3819,14 @@ +@@ -5597,14 +5597,14 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -37,7 +37,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -3841,15 +3841,15 @@ +@@ -5619,15 +5619,15 @@ IF verbose THEN RETURN NEXT diag(tests[i] || '()'); END IF; -- Run the setup functions. @@ -57,7 +57,7 @@ -- Remember how many failed and then roll back. num_faild := num_faild + num_failed(); -@@ -3864,7 +3864,7 @@ +@@ -5642,7 +5642,7 @@ END LOOP; -- Run the shutdown functions. @@ -66,7 +66,7 @@ -- Raise an exception to rollback any changes. RAISE EXCEPTION '__TAP_ROLLBACK__'; -@@ -3875,8 +3875,8 @@ +@@ -5653,8 +5653,8 @@ END IF; END; -- Finish up. diff --git a/sql/aretap.sql b/sql/aretap.sql index ae6de479d8d1..46f6611ea7d1 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -666,7 +666,7 @@ SELECT * FROM check_test( -- Test users_are(). CREATE FUNCTION ___myusers(ex text) RETURNS NAME[] AS $$ - SELECT ARRAY( SELECT usename FROM pg_catalog.pg_user WHERE usename <> $1 ); + SELECT COALESCE(ARRAY( SELECT usename FROM pg_catalog.pg_user WHERE usename <> $1 ), '{}'::name[]);; $$ LANGUAGE SQL; SELECT * FROM check_test( @@ -719,7 +719,7 @@ SELECT * FROM check_test( CREATE GROUP meanies; CREATE FUNCTION ___mygroups(ex text) RETURNS NAME[] AS $$ - SELECT ARRAY( SELECT groname FROM pg_catalog.pg_group WHERE groname <> $1 ); + SELECT COALESCE(ARRAY( SELECT groname FROM pg_catalog.pg_group WHERE groname <> $1 ), '{}'::name[]);; $$ LANGUAGE SQL; SELECT * FROM check_test( @@ -771,7 +771,7 @@ SELECT * FROM check_test( -- Test languages_are(). CREATE FUNCTION ___mylangs(ex text) RETURNS NAME[] AS $$ - SELECT ARRAY( SELECT lanname FROM pg_catalog.pg_language WHERE lanispl AND lanname <> $1 ); + SELECT COALESCE(ARRAY( SELECT lanname FROM pg_catalog.pg_language WHERE lanispl AND lanname <> $1 ), '{}'::name[]);; $$ LANGUAGE SQL; SELECT * FROM check_test( @@ -868,14 +868,14 @@ SELECT * FROM check_test( ); CREATE FUNCTION ___myopc(ex text) RETURNS NAME[] AS $$ - SELECT ARRAY( + SELECT COALESCE(ARRAY( SELECT oc.opcname FROM pg_catalog.pg_opclass oc JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid WHERE n.nspname <> 'pg_catalog' AND oc.opcname <> $1 AND pg_catalog.pg_opclass_is_visible(oc.oid) - ); + ), '{}'::name[]);; $$ LANGUAGE SQL; From 2e4227c6837144a0e4ab883ce7a16f63e7cd641a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 28 May 2009 15:55:50 -0700 Subject: [PATCH 0384/1195] Fixed 8.0 compatibility. * Updated patches. * `RAISE` supports only variables for arguments, not expressions. But we can just use a format string instead. Should work on the other versions. --- compat/install-8.0.patch | 57 ++++++++++++++++++-------------------- compat/uninstall-8.0.patch | 6 ++-- pgtap.sql.in | 16 +++++------ 3 files changed, 38 insertions(+), 41 deletions(-) diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index e2b42788e458..ba11679504a6 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,6 +1,6 @@ ---- pgtap.sql.saf 2009-03-28 15:55:36.000000000 -0400 -+++ pgtap.sql 2009-03-28 15:55:45.000000000 -0400 -@@ -12,6 +12,22 @@ +--- pgtap.sql.orig 2009-05-28 15:45:03.000000000 -0700 ++++ pgtap.sql 2009-05-28 15:41:05.000000000 -0700 +@@ -11,6 +11,22 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA, public; @@ -23,7 +23,7 @@ CREATE OR REPLACE FUNCTION pg_typeof("any") RETURNS regtype AS '$libdir/pgtap' -@@ -97,53 +113,63 @@ +@@ -96,53 +112,63 @@ CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ DECLARE @@ -104,7 +104,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -207,10 +233,12 @@ +@@ -206,10 +232,12 @@ CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ DECLARE @@ -120,7 +120,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -484,13 +512,16 @@ +@@ -483,13 +511,16 @@ want ALIAS FOR $3; descr ALIAS FOR $4; result BOOLEAN; @@ -140,7 +140,7 @@ output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE '\n' || diag( ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -1199,19 +1230,21 @@ +@@ -1227,19 +1258,21 @@ CREATE OR REPLACE FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -153,15 +153,12 @@ END IF; - EXECUTE 'SELECT is(' -- || COALESCE($1, 'NULL' || '::' || $2) || '::' || $2 || ', ' -- || COALESCE(quote_literal($3), 'NULL') || '::' || $2 || ', ' -- || COALESCE(quote_literal($4), 'NULL') ++ FOR ret IN EXECUTE 'SELECT is(' + || COALESCE($1, 'NULL' || '::' || $2) || '::' || $2 || ', ' + || COALESCE(quote_literal($3), 'NULL') || '::' || $2 || ', ' + || COALESCE(quote_literal($4), 'NULL') - || ')' INTO thing; - RETURN thing; -+ FOR ret IN EXECUTE 'SELECT is(' -+ || COALESCE($1, 'NULL' || '::' || $2) || '::' || $2 || ', ' -+ || COALESCE(quote_literal($3), 'NULL') || '::' || $2 || ', ' -+ || COALESCE(quote_literal($4), 'NULL') + || ') AS a' LOOP + RETURN ret.a; + END LOOP; @@ -169,7 +166,7 @@ END; $$ LANGUAGE plpgsql; -@@ -3041,39 +3074,6 @@ +@@ -3288,39 +3321,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -182,7 +179,7 @@ - ); -$$ LANGUAGE sql STRICT; - ---- has_role( role, desc ) +--- has_role( role, description ) -CREATE OR REPLACE FUNCTION has_role( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _has_role($1), $2 ); @@ -194,7 +191,7 @@ - SELECT ok( _has_role($1), 'Role ' || quote_ident($1) || ' should exist' ); -$$ LANGUAGE sql; - ---- hasnt_role( role, desc ) +--- hasnt_role( role, description ) -CREATE OR REPLACE FUNCTION hasnt_role( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _has_role($1), $2 ); @@ -209,7 +206,7 @@ CREATE OR REPLACE FUNCTION _is_super( NAME ) RETURNS BOOLEAN AS $$ SELECT usesuper -@@ -3177,7 +3177,7 @@ +@@ -3424,7 +3424,7 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _grolist ( NAME ) @@ -218,15 +215,15 @@ SELECT grolist FROM pg_catalog.pg_group WHERE groname = $1; $$ LANGUAGE sql; -@@ -3622,6 +3622,7 @@ - res BOOLEAN; - descr TEXT; - adiag TEXT; -+ rec RECORD; - have ALIAS FOR $1; - eok ALIAS FOR $2; - name ALIAS FOR $3; -@@ -3632,8 +3633,10 @@ +@@ -5385,6 +5385,7 @@ + res BOOLEAN; + descr TEXT; + adiag TEXT; ++ rec RECORD; + have ALIAS FOR $1; + eok ALIAS FOR $2; + name ALIAS FOR $3; +@@ -5396,8 +5397,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. @@ -239,7 +236,7 @@ -- Now delete those results. EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; -@@ -3742,7 +3745,7 @@ +@@ -5520,7 +5523,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -248,7 +245,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -3750,8 +3753,8 @@ +@@ -5528,8 +5531,8 @@ -- 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. @@ -259,7 +256,7 @@ END LOOP; END LOOP; RETURN; -@@ -3811,116 +3814,6 @@ +@@ -5589,116 +5592,6 @@ END $$ LANGUAGE plpgsql; diff --git a/compat/uninstall-8.0.patch b/compat/uninstall-8.0.patch index a3567c47bdef..b4f1d10d2102 100644 --- a/compat/uninstall-8.0.patch +++ b/compat/uninstall-8.0.patch @@ -1,6 +1,6 @@ ---- uninstall_pgtap.sql.saf 2009-03-28 15:58:36.000000000 -0400 -+++ uninstall_pgtap.sql 2009-03-28 15:58:43.000000000 -0400 -@@ -90,11 +94,6 @@ +--- uninstall_pgtap.sql.orig 2009-05-28 15:35:12.000000000 -0700 ++++ uninstall_pgtap.sql 2009-05-28 15:35:12.000000000 -0700 +@@ -250,11 +250,6 @@ DROP FUNCTION has_user( NAME ); DROP FUNCTION has_user( NAME, TEXT ); DROP FUNCTION _is_super( NAME ); diff --git a/pgtap.sql.in b/pgtap.sql.in index 8e6e77f29544..607e54e9fa8f 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2182,7 +2182,7 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION can_ok ( NAME, NAME, NAME[], TEXT ) RETURNS TEXT AS $$ BEGIN - RAISE WARNING '%', 'can_ok() is deprecated; use has_function() instead'; + RAISE WARNING 'can_ok() is deprecated; use has_function() instead'; RETURN has_function($1, $2, $3, $4); END; $$ LANGUAGE PLPGSQL; @@ -2190,7 +2190,7 @@ $$ LANGUAGE PLPGSQL; CREATE OR REPLACE FUNCTION can_ok( NAME, NAME, NAME[] ) RETURNS TEXT AS $$ BEGIN - RAISE WARNING '%', 'can_ok() is deprecated; use has_function() instead'; + RAISE WARNING 'can_ok() is deprecated; use has_function() instead'; RETURN has_function($1, $2, $3); END; $$ LANGUAGE PLPGSQL; @@ -2198,7 +2198,7 @@ $$ LANGUAGE PLPGSQL; CREATE OR REPLACE FUNCTION can_ok ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ BEGIN - RAISE WARNING '%', 'can_ok() is deprecated; use has_function() instead'; + RAISE WARNING 'can_ok() is deprecated; use has_function() instead'; RETURN has_function($1, $2, $3); END; $$ LANGUAGE PLPGSQL; @@ -2206,7 +2206,7 @@ $$ LANGUAGE PLPGSQL; CREATE OR REPLACE FUNCTION can_ok( NAME, NAME ) RETURNS TEXT AS $$ BEGIN - RAISE WARNING '%', 'can_ok() is deprecated; use has_function() instead'; + RAISE WARNING 'can_ok() is deprecated; use has_function() instead'; RETURN has_function($1, $2); END; $$ LANGUAGE PLPGSQL; @@ -2214,7 +2214,7 @@ $$ LANGUAGE PLPGSQL; CREATE OR REPLACE FUNCTION can_ok ( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ BEGIN - RAISE WARNING '%', 'can_ok() is deprecated; use has_function() instead'; + RAISE WARNING 'can_ok() is deprecated; use has_function() instead'; RETURN has_function($1, $2, $3); END; $$ LANGUAGE PLPGSQL; @@ -2222,7 +2222,7 @@ $$ LANGUAGE PLPGSQL; CREATE OR REPLACE FUNCTION can_ok( NAME, NAME[] ) RETURNS TEXT AS $$ BEGIN - RAISE WARNING '%', 'can_ok() is deprecated; use has_function() instead'; + RAISE WARNING 'can_ok() is deprecated; use has_function() instead'; RETURN has_function($1, $2); END; $$ LANGUAGE PLPGSQL; @@ -2230,7 +2230,7 @@ $$ LANGUAGE PLPGSQL; CREATE OR REPLACE FUNCTION can_ok( NAME, TEXT ) RETURNS TEXT AS $$ BEGIN - RAISE WARNING '%', 'can_ok() is deprecated; use has_function() instead'; + RAISE WARNING 'can_ok() is deprecated; use has_function() instead'; RETURN has_function($1, $2); END; $$ LANGUAGE PLPGSQL; @@ -2238,7 +2238,7 @@ $$ LANGUAGE PLPGSQL; CREATE OR REPLACE FUNCTION can_ok( NAME ) RETURNS TEXT AS $$ BEGIN - RAISE WARNING '%', 'can_ok() is deprecated; use has_function() instead'; + RAISE WARNING 'can_ok() is deprecated; use has_function() instead'; RETURN has_function($1); END; $$ LANGUAGE PLPGSQL; From 16daa6e59d6b080a3e1196fd430a8ea46138e348 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 28 May 2009 17:25:12 -0700 Subject: [PATCH 0385/1195] Forgot to timestamp 0.21. Oh well. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index ac8ddbd33601..3f20aa9b0f17 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.21 +0.21 2009-05-29T00:04:31 ------------------------- * Fixed a bug in the processing of the `--schema` and `--match` options that only shows up in Getopt::Long 2.38 or higher. From 84235d91f85869855d7a4655c75786dda3b53249 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 28 May 2009 17:30:35 -0700 Subject: [PATCH 0386/1195] Incremented version number to 0.22. --- Changes | 3 +++ Makefile | 2 +- README.pgtap | 2 +- bin/pg_prove | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 3f20aa9b0f17..cd061d6909f4 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Revision history for pgTAP ========================== +0.22 +------------------------- + 0.21 2009-05-29T00:04:31 ------------------------- * Fixed a bug in the processing of the `--schema` and `--match` options that diff --git a/Makefile b/Makefile index 774b83138a75..4421a8172bfc 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') PGVER_MAJOR = $(shell echo $(VERSION) | awk -F. '{ print ($$1 + 0) }') PGVER_MINOR = $(shell echo $(VERSION) | awk -F. '{ print ($$2 + 0) }') PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') -PGTAP_VERSION = 0.21 +PGTAP_VERSION = 0.22 # Compile the C code only if we're on 8.3 or older. ifneq ($(PGVER_MINOR), 4) diff --git a/README.pgtap b/README.pgtap index c418adfb890e..de865a758a19 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.21 +pgTAP 0.22 ========== pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/bin/pg_prove b/bin/pg_prove index bb210803b463..a1a1da8f49a2 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -6,7 +6,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.21'; +our $VERSION = '0.22'; Getopt::Long::Configure (qw(bundling)); From 2892f91d9a8a5bd5f035a465ac221d851725409e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 28 May 2009 17:32:23 -0700 Subject: [PATCH 0387/1195] Extra line eliminated. --- Changes | 1 - 1 file changed, 1 deletion(-) diff --git a/Changes b/Changes index cd061d6909f4..9d0b8c892f6f 100644 --- a/Changes +++ b/Changes @@ -47,7 +47,6 @@ Revision history for pgTAP 0.19 2009-02-21T02:09:26 ------------------------- - * Added a alernate versions of `col_default_is()` to better handle the common case when a default is specified as a string, such as a text or expression default. This means that you have to do a lot less casting of default values From af78e4e67ea232d8323ff4f1a4a9675229bd8e7b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 28 May 2009 17:40:54 -0700 Subject: [PATCH 0388/1195] Added `throws_like()` to To Do list. --- README.pgtap | 1 + 1 file changed, 1 insertion(+) diff --git a/README.pgtap b/README.pgtap index de865a758a19..7bee989f867a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3324,6 +3324,7 @@ functions do not work under 8.0. Don't even use them there. To Do ----- * Useful schema testing functions to consider adding: +* `throws_like()` * `sequence_has_range()` * `sequence_increments_by()` * `sequence_starts_at()` From 97063dd83d8c2ac9ff9503b1c2b0c1d5fbae160f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 28 May 2009 17:51:56 -0700 Subject: [PATCH 0389/1195] Not sure how I neglected to update the version of 8.4 tested. --- README.pgtap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index 7bee989f867a..d2a86d75a81c 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3335,7 +3335,7 @@ Supported Versions pgTAP has been tested on the following builds of PostgreSQL: -* PostgreSQL 8.4devel on i386-apple-darwin9.6.0 +* PostgreSQL 8.4beta2 on i386-apple-darwin9.7.0 * PostgreSQL 8.3.7 on i386-apple-darwin9.6.0 * PostgreSQL 8.3.6 on i386-redhat-linux-gnu * PostgreSQL 8.2.13 on i386-apple-darwin9.6.0 From 60ea606307817ef1ea301042a68077fc06313ffc Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 1 Jun 2009 14:14:26 -0700 Subject: [PATCH 0390/1195] Somehow deleted that closing ". --- README.pgtap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index d2a86d75a81c..d75feba24add 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2685,7 +2685,7 @@ path. If the `:description` is omitted, a reasonable default will be created. If the test fails, you'll see useful diagnostics, such as: - # Failed test 133: "Rule ins_me should be on INSERT to public.widgets + # Failed test 133: "Rule ins_me should be on INSERT to public.widgets" # have: UPDATE # want: INSERT From 5f5151782833a8700d695f976093ec8bba754f51 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 10 Jun 2009 12:41:25 -0700 Subject: [PATCH 0391/1195] Eliminated old SVN `$Id$` keywords. --- Makefile | 1 - bin/pg_prove | 2 -- 2 files changed, 3 deletions(-) diff --git a/Makefile b/Makefile index 4421a8172bfc..53cc77db727d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,3 @@ -# $Id$ TESTS = $(wildcard sql/*.sql) EXTRA_CLEAN = test_setup.sql *.html DATA_built = pgtap.sql uninstall_pgtap.sql diff --git a/bin/pg_prove b/bin/pg_prove index a1a1da8f49a2..35a2f70eb4f0 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -1,7 +1,5 @@ #!/usr/bin/perl -w -# $Id$ - use strict; use warnings; use TAP::Harness; From afe2b7c52a85a92f3f421337813314d4dfada392 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 26 Jun 2009 18:00:08 -0700 Subject: [PATCH 0392/1195] Added `set_eq()` and fixed failing test on 8.4rc2. --- Changes | 2 + README.pgtap | 14 +- expected/set.out | 32 ++++ pgtap.sql.in | 88 +++++++++++ sql/set.sql | 383 +++++++++++++++++++++++++++++++++++++++++++++++ sql/util.sql | 2 +- 6 files changed, 515 insertions(+), 6 deletions(-) create mode 100644 expected/set.out create mode 100644 sql/set.sql diff --git a/Changes b/Changes index 9d0b8c892f6f..d66fa2d3f44f 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,8 @@ Revision history for pgTAP 0.22 ------------------------- +* Fixed failing test on 8.4rc2. +* Added `set_eq()`. 0.21 2009-05-29T00:04:31 ------------------------- diff --git a/README.pgtap b/README.pgtap index d75feba24add..156417eee725 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3324,11 +3324,15 @@ functions do not work under 8.0. Don't even use them there. To Do ----- * Useful schema testing functions to consider adding: -* `throws_like()` -* `sequence_has_range()` -* `sequence_increments_by()` -* `sequence_starts_at()` -* `sequence_cycles()` + + `throws_like()` + + `sequence_has_range()` + + `sequence_increments_by()` + + `sequence_starts_at()` + + `sequence_cycles()` +* Result set testing functions: + + `set_eq()` + + `poset_eq()` + + `pobag_eq()` Supported Versions ----------------- diff --git a/expected/set.out b/expected/set.out new file mode 100644 index 000000000000..af06d00efdf0 --- /dev/null +++ b/expected/set.out @@ -0,0 +1,32 @@ +\unset ECHO +1..30 +ok 1 - simple set test should pass +ok 2 - simple set test should have the proper description +ok 3 - simple set test should have the proper diagnostics +ok 4 - simple set test without descr should pass +ok 5 - simple set test without descr should have the proper description +ok 6 - simple set test without descr should have the proper diagnostics +ok 7 - execute set test should pass +ok 8 - execute set test should have the proper description +ok 9 - execute set test should have the proper diagnostics +ok 10 - select set test should pass +ok 11 - select set test should have the proper description +ok 12 - select set test should have the proper diagnostics +ok 13 - fail with extra record should fail +ok 14 - fail with extra record should have the proper description +ok 15 - fail with extra record should have the proper diagnostics +ok 16 - fail with 2 extra records should fail +ok 17 - fail with 2 extra records should have the proper description +ok 18 - fail with 2 extra records should have the proper diagnostics +ok 19 - fail with missing record should fail +ok 20 - fail with missing record should have the proper description +ok 21 - fail with missing record should have the proper diagnostics +ok 22 - fail with 2 missing records should fail +ok 23 - fail with 2 missing records should have the proper description +ok 24 - fail with 2 missing records should have the proper diagnostics +ok 25 - fail with extra and missing should fail +ok 26 - fail with extra and missing should have the proper description +ok 27 - fail with extra and missing should have the proper diagnostics +ok 28 - fail with 2 extras and 2 missings should fail +ok 29 - fail with 2 extras and 2 missings should have the proper description +ok 30 - fail with 2 extras and 2 missings should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 607e54e9fa8f..50edf52ff214 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -5755,3 +5755,91 @@ CREATE OR REPLACE FUNCTION runtests( ) RETURNS SETOF TEXT AS $$ SELECT * FROM runtests( '^test' ); $$ LANGUAGE sql; + +-- set_eq( sql, sql, description ) +CREATE OR REPLACE FUNCTION set_eq( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + have TEXT := CASE WHEN $1 ~ '[[:space:]]' THEN $1 ELSE 'EXECUTE ' || $1 END; + want TEXT := CASE WHEN $2 ~ '[[:space:]]' THEN $2 ELSE 'EXECUTE ' || $2 END; + extras TEXT[] := '{}'; + missing TEXT[] := '{}'; + res BOOLEAN := TRUE; + msg TEXT := ''; + rec RECORD; +BEGIN + EXECUTE 'CREATE TEMP TABLE __taphave__ AS ' || have; + EXECUTE 'CREATE TEMP TABLE __tapwant__ AS ' || want; + + FOR rec in EXECUTE 'SELECT * FROM __taphave__ EXCEPT SELECT * FROM __tapwant__' LOOP + extras := extras || rec::text; + END LOOP; + + FOR rec in EXECUTE 'SELECT * FROM __tapwant__ EXCEPT SELECT * FROM __taphave__' LOOP + missing := missing || rec::text; + END LOOP; + + 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; + + 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; + + EXECUTE 'DROP TABLE __taphave__'; + EXECUTE 'DROP TABLE __tapwant__'; + RETURN ok(res, $3) || msg; +END; +$$ LANGUAGE plpgsql; + +-- set_eq( sql, sql ) +CREATE OR REPLACE FUNCTION set_eq( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT set_eq( $1, $2, NULL::text ); +$$ LANGUAGE sql; + + +-- query_gets() +-- oquery_gets() +-- squery_gets() + +-- cursor_gets() +-- ocursor_gets() +-- scursor_gets() + +-- results_count_is + +-- results_equal +-- results_intersect +-- results_except + + + + +-- set_equals -- set equivalence +-- set_includes -- set intersection +-- set_excludes -- set difference +-- set_size_is + +-- set_equals( + + +-- poset_equals +-- poset_includes +-- poset_excludes + +-- bag_equals +-- bag_includes +-- bag_excludes +-- bag_size_is + + diff --git a/sql/set.sql b/sql/set.sql new file mode 100644 index 000000000000..f137454ae5ea --- /dev/null +++ b/sql/set.sql @@ -0,0 +1,383 @@ +\unset ECHO +\i test_setup.sql + +SELECT plan(30); +--SELECT * FROM no_plan(); + +-- This will be rolled back. :-) +SET client_min_messages = warning; + +CREATE TABLE names ( + id SERIAL NOT NULL PRIMARY KEY, + name TEXT DEFAULT '' +); + +RESET client_min_messages; + +-- Top 100 boy an 100 girl names in 2005. http://www.ssa.gov/OACT/babynames/ +INSERT INTO names (name) VALUES ('Jacob'); +INSERT INTO names (name) VALUES ('Emily'); +INSERT INTO names (name) VALUES ('Michael'); +INSERT INTO names (name) VALUES ('Emma'); +INSERT INTO names (name) VALUES ('Joshua'); +INSERT INTO names (name) VALUES ('Madison'); +INSERT INTO names (name) VALUES ('Matthew'); +INSERT INTO names (name) VALUES ('Abigail'); +INSERT INTO names (name) VALUES ('Ethan'); +INSERT INTO names (name) VALUES ('Olivia'); +INSERT INTO names (name) VALUES ('Andrew'); +INSERT INTO names (name) VALUES ('Isabella'); +INSERT INTO names (name) VALUES ('Daniel'); +INSERT INTO names (name) VALUES ('Hannah'); +INSERT INTO names (name) VALUES ('Anthony'); +INSERT INTO names (name) VALUES ('Samantha'); +INSERT INTO names (name) VALUES ('Christopher'); +INSERT INTO names (name) VALUES ('Ava'); +INSERT INTO names (name) VALUES ('Joseph'); +INSERT INTO names (name) VALUES ('Ashley'); +INSERT INTO names (name) VALUES ('William'); +INSERT INTO names (name) VALUES ('Elizabeth'); +INSERT INTO names (name) VALUES ('Alexander'); +INSERT INTO names (name) VALUES ('Sophia'); +INSERT INTO names (name) VALUES ('David'); +INSERT INTO names (name) VALUES ('Alexis'); +INSERT INTO names (name) VALUES ('Ryan'); +INSERT INTO names (name) VALUES ('Grace'); +INSERT INTO names (name) VALUES ('Nicholas'); +INSERT INTO names (name) VALUES ('Sarah'); +INSERT INTO names (name) VALUES ('Tyler'); +INSERT INTO names (name) VALUES ('Alyssa'); +INSERT INTO names (name) VALUES ('James'); +INSERT INTO names (name) VALUES ('Mia'); +INSERT INTO names (name) VALUES ('John'); +INSERT INTO names (name) VALUES ('Natalie'); +INSERT INTO names (name) VALUES ('Jonathan'); +INSERT INTO names (name) VALUES ('Chloe'); +INSERT INTO names (name) VALUES ('Nathan'); +INSERT INTO names (name) VALUES ('Brianna'); +INSERT INTO names (name) VALUES ('Samuel'); +INSERT INTO names (name) VALUES ('Lauren'); +INSERT INTO names (name) VALUES ('Christian'); +INSERT INTO names (name) VALUES ('Anna'); +INSERT INTO names (name) VALUES ('Noah'); +INSERT INTO names (name) VALUES ('Ella'); +INSERT INTO names (name) VALUES ('Dylan'); +INSERT INTO names (name) VALUES ('Taylor'); +INSERT INTO names (name) VALUES ('Benjamin'); +INSERT INTO names (name) VALUES ('Kayla'); +INSERT INTO names (name) VALUES ('Logan'); +INSERT INTO names (name) VALUES ('Hailey'); +INSERT INTO names (name) VALUES ('Brandon'); +INSERT INTO names (name) VALUES ('Jessica'); +INSERT INTO names (name) VALUES ('Gabriel'); +INSERT INTO names (name) VALUES ('Victoria'); +INSERT INTO names (name) VALUES ('Zachary'); +INSERT INTO names (name) VALUES ('Jasmine'); +INSERT INTO names (name) VALUES ('Jose'); +INSERT INTO names (name) VALUES ('Sydney'); +INSERT INTO names (name) VALUES ('Elijah'); +INSERT INTO names (name) VALUES ('Julia'); +INSERT INTO names (name) VALUES ('Angel'); +INSERT INTO names (name) VALUES ('Destiny'); +INSERT INTO names (name) VALUES ('Kevin'); +INSERT INTO names (name) VALUES ('Morgan'); +INSERT INTO names (name) VALUES ('Jack'); +INSERT INTO names (name) VALUES ('Kaitlyn'); +INSERT INTO names (name) VALUES ('Caleb'); +INSERT INTO names (name) VALUES ('Savannah'); +INSERT INTO names (name) VALUES ('Justin'); +INSERT INTO names (name) VALUES ('Katherine'); +INSERT INTO names (name) VALUES ('Robert'); +INSERT INTO names (name) VALUES ('Alexandra'); +INSERT INTO names (name) VALUES ('Austin'); +INSERT INTO names (name) VALUES ('Rachel'); +INSERT INTO names (name) VALUES ('Evan'); +INSERT INTO names (name) VALUES ('Lily'); +INSERT INTO names (name) VALUES ('Thomas'); +INSERT INTO names (name) VALUES ('Kaylee'); +INSERT INTO names (name) VALUES ('Luke'); +INSERT INTO names (name) VALUES ('Megan'); +INSERT INTO names (name) VALUES ('Mason'); +INSERT INTO names (name) VALUES ('Jennifer'); +INSERT INTO names (name) VALUES ('Aidan'); +INSERT INTO names (name) VALUES ('Angelina'); +INSERT INTO names (name) VALUES ('Jackson'); +INSERT INTO names (name) VALUES ('Makayla'); +INSERT INTO names (name) VALUES ('Isaiah'); +INSERT INTO names (name) VALUES ('Allison'); +INSERT INTO names (name) VALUES ('Jordan'); +INSERT INTO names (name) VALUES ('Maria'); +INSERT INTO names (name) VALUES ('Gavin'); +INSERT INTO names (name) VALUES ('Brooke'); +INSERT INTO names (name) VALUES ('Connor'); +INSERT INTO names (name) VALUES ('Trinity'); +INSERT INTO names (name) VALUES ('Isaac'); +INSERT INTO names (name) VALUES ('Faith'); +INSERT INTO names (name) VALUES ('Aiden'); +INSERT INTO names (name) VALUES ('Lillian'); +INSERT INTO names (name) VALUES ('Jason'); +INSERT INTO names (name) VALUES ('Mackenzie'); +INSERT INTO names (name) VALUES ('Cameron'); +INSERT INTO names (name) VALUES ('Sofia'); +INSERT INTO names (name) VALUES ('Hunter'); +INSERT INTO names (name) VALUES ('Riley'); +INSERT INTO names (name) VALUES ('Jayden'); +INSERT INTO names (name) VALUES ('Haley'); +INSERT INTO names (name) VALUES ('Juan'); +INSERT INTO names (name) VALUES ('Gabrielle'); +INSERT INTO names (name) VALUES ('Charles'); +INSERT INTO names (name) VALUES ('Nicole'); +INSERT INTO names (name) VALUES ('Aaron'); +INSERT INTO names (name) VALUES ('Kylie'); +INSERT INTO names (name) VALUES ('Lucas'); +INSERT INTO names (name) VALUES ('Zoe'); +INSERT INTO names (name) VALUES ('Luis'); +INSERT INTO names (name) VALUES ('Katelyn'); +INSERT INTO names (name) VALUES ('Owen'); +INSERT INTO names (name) VALUES ('Paige'); +INSERT INTO names (name) VALUES ('Landon'); +INSERT INTO names (name) VALUES ('Gabriella'); +INSERT INTO names (name) VALUES ('Diego'); +INSERT INTO names (name) VALUES ('Jenna'); +INSERT INTO names (name) VALUES ('Brian'); +INSERT INTO names (name) VALUES ('Kimberly'); +INSERT INTO names (name) VALUES ('Adam'); +INSERT INTO names (name) VALUES ('Stephanie'); +INSERT INTO names (name) VALUES ('Adrian'); +INSERT INTO names (name) VALUES ('Andrea'); +INSERT INTO names (name) VALUES ('Eric'); +INSERT INTO names (name) VALUES ('Alexa'); +INSERT INTO names (name) VALUES ('Kyle'); +INSERT INTO names (name) VALUES ('Avery'); +INSERT INTO names (name) VALUES ('Ian'); +INSERT INTO names (name) VALUES ('Leah'); +INSERT INTO names (name) VALUES ('Nathaniel'); +INSERT INTO names (name) VALUES ('Nevaeh'); +INSERT INTO names (name) VALUES ('Carlos'); +INSERT INTO names (name) VALUES ('Madeline'); +INSERT INTO names (name) VALUES ('Alex'); +INSERT INTO names (name) VALUES ('Evelyn'); +INSERT INTO names (name) VALUES ('Bryan'); +INSERT INTO names (name) VALUES ('Mary'); +INSERT INTO names (name) VALUES ('Jesus'); +INSERT INTO names (name) VALUES ('Maya'); +INSERT INTO names (name) VALUES ('Julian'); +INSERT INTO names (name) VALUES ('Michelle'); +INSERT INTO names (name) VALUES ('Sean'); +INSERT INTO names (name) VALUES ('Sara'); +INSERT INTO names (name) VALUES ('Hayden'); +INSERT INTO names (name) VALUES ('Jada'); +INSERT INTO names (name) VALUES ('Carter'); +INSERT INTO names (name) VALUES ('Audrey'); +INSERT INTO names (name) VALUES ('Jeremiah'); +INSERT INTO names (name) VALUES ('Brooklyn'); +INSERT INTO names (name) VALUES ('Cole'); +INSERT INTO names (name) VALUES ('Vanessa'); +INSERT INTO names (name) VALUES ('Brayden'); +INSERT INTO names (name) VALUES ('Amanda'); +INSERT INTO names (name) VALUES ('Wyatt'); +INSERT INTO names (name) VALUES ('Rebecca'); +INSERT INTO names (name) VALUES ('Chase'); +INSERT INTO names (name) VALUES ('Caroline'); +INSERT INTO names (name) VALUES ('Steven'); +INSERT INTO names (name) VALUES ('Ariana'); +INSERT INTO names (name) VALUES ('Timothy'); +INSERT INTO names (name) VALUES ('Amelia'); +INSERT INTO names (name) VALUES ('Dominic'); +INSERT INTO names (name) VALUES ('Mariah'); +INSERT INTO names (name) VALUES ('Sebastian'); +INSERT INTO names (name) VALUES ('Jordan'); +INSERT INTO names (name) VALUES ('Xavier'); +INSERT INTO names (name) VALUES ('Jocelyn'); +INSERT INTO names (name) VALUES ('Jaden'); +INSERT INTO names (name) VALUES ('Arianna'); +INSERT INTO names (name) VALUES ('Jesse'); +INSERT INTO names (name) VALUES ('Isabel'); +INSERT INTO names (name) VALUES ('Seth'); +INSERT INTO names (name) VALUES ('Marissa'); +INSERT INTO names (name) VALUES ('Devin'); +INSERT INTO names (name) VALUES ('Autumn'); +INSERT INTO names (name) VALUES ('Antonio'); +INSERT INTO names (name) VALUES ('Melanie'); +INSERT INTO names (name) VALUES ('Miguel'); +INSERT INTO names (name) VALUES ('Aaliyah'); +INSERT INTO names (name) VALUES ('Richard'); +INSERT INTO names (name) VALUES ('Gracie'); +INSERT INTO names (name) VALUES ('Colin'); +INSERT INTO names (name) VALUES ('Claire'); +INSERT INTO names (name) VALUES ('Cody'); +INSERT INTO names (name) VALUES ('Isabelle'); +INSERT INTO names (name) VALUES ('Alejandro'); +INSERT INTO names (name) VALUES ('Molly'); +INSERT INTO names (name) VALUES ('Caden'); +INSERT INTO names (name) VALUES ('Mya'); +INSERT INTO names (name) VALUES ('Blake'); +INSERT INTO names (name) VALUES ('Diana'); +INSERT INTO names (name) VALUES ('Kaden'); +INSERT INTO names (name) VALUES ('Katie'); + +CREATE TABLE annames AS +SELECT id, name FROM names WHERE name like 'An%'; + +/****************************************************************************/ +-- Start by testing simple rows with prepared statement names. +PREPARE anames AS SELECT id, name FROM names WHERE name like 'An%'; +PREPARE expect AS VALUES (11, 'Andrew'), (15, 'Anthony'), ( 44, 'Anna'), + (63, 'Angel'), (86, 'Angelina'), (130, 'Andrea'), + (183, 'Antonio'); + +SELECT * FROM check_test( + set_eq( 'anames', 'expect', 'first set test' ), + true, + 'simple set test', + 'first set test', + '' +); +SELECT * FROM check_test( + set_eq( 'anames', 'expect' ), + true, + 'simple set test without descr', + '', + '' +); + +-- Pass a full SQL statement for the prepared statements. +SELECT * FROM check_test( + set_eq( 'EXECUTE anames', 'EXECUTE expect' ), + true, + 'execute set test', + '', + '' +); + +-- Compare actual SELECT statements. +SELECT * FROM check_test( + set_eq( + 'SELECT id, name FROM names WHERE name like ''An%''', + 'SELECT id, name FROM annames' + ), + true, + 'select set test', + '', + '' +); + +-- Try some failures. +SELECT * FROM check_test( + set_eq( + 'anames', + 'SELECT id, name FROM annames WHERE name <> ''Anna''' + ), + false, + 'fail with extra record', + '', + ' Extra records: + (44,Anna)' +); + +SELECT * FROM check_test( + set_eq( + 'anames', + 'SELECT id, name FROM annames WHERE name NOT IN (''Anna'', ''Angelina'')' + ), + false, + 'fail with 2 extra records', + '', + E' Extra records: + \\((44,Anna|86,Angelina)\\) + \\((44,Anna|86,Angelina)\\)', + true +); + +SELECT * FROM check_test( + set_eq( + 'SELECT id, name FROM annames WHERE name <> ''Anna''', + 'expect' + ), + false, + 'fail with missing record', + '', + ' Missing records: + (44,Anna)' +); + +SELECT * FROM check_test( + set_eq( + 'SELECT id, name FROM annames WHERE name NOT IN (''Anna'', ''Angelina'')', + 'expect' + ), + false, + 'fail with 2 missing records', + '', + E' Missing records: + \\((44,Anna|86,Angelina)\\) + \\((44,Anna|86,Angelina)\\)', + true +); + + +SELECT * FROM check_test( + set_eq( + 'SELECT id, name FROM names WHERE name ~ ''^(An|Jacob)'' AND name <> ''Anna''', + 'SELECT id, name FROM annames' + ), + false, + 'fail with extra and missing', + '', + ' Extra records: + (1,Jacob) + Missing records: + (44,Anna)' +); + +SELECT * FROM check_test( + set_eq( + 'SELECT id, name FROM names WHERE name ~ ''^(An|Jacob|Jacks)'' AND name NOT IN (''Anna'', ''Angelina'')', + 'SELECT id, name FROM annames' + ), + false, + 'fail with 2 extras and 2 missings', + '', + E' Extra records: + \\((1,Jacob|87,Jackson)\\) + \\((1,Jacob|87,Jackson)\\) + Missing records: + \\((44,Anna|86,Angelina)\\) + \\((44,Anna|86,Angelina)\\)', + true +); + + + + + +-- SELECT set_eq( 'have', ARRAY[1, 2, 4] ); +-- SELECT set_eq( 'have', ARRAY[[1, 2], [3, 4]] ); + +-- SELECT poset_eq( 'have', 'want' ); +-- SELECT poset_eq( 'have', ARRAY[1, 2, 4] ); +-- SELECT poset_eq( 'have', ARRAY[[1, 2], [3, 4]] ); + + + + + + + +-- DECLARE want CURSOR FOR SELECT * FROM users WHERE active; +-- DECLARE have CURSOR FOR SELECT * FROM get_active_users(); +-- SELECT bag_eq( 'have', 'want' ); +-- SELECT bag_eq( 'have', ARRAY[1, 2, 4, 4] ); +-- SELECT bag_eq( 'have', ARRAY[[1, 2], [3, 4], [1, 2]] ); + +-- SELECT pobag_eq( 'have', 'want' ); +-- SELECT pobag_eq( 'have', ARRAY[1, 2, 4, 4] ); +-- SELECT pobag_eq( 'have', ARRAY[[1, 2], [3, 4], [1, 2]] ); + + + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; + diff --git a/sql/util.sql b/sql/util.sql index d99af9626015..744f5916a9d0 100644 --- a/sql/util.sql +++ b/sql/util.sql @@ -16,7 +16,7 @@ SELECT is( ); SELECT matches( pg_version(), - '^8[.][[:digit:]]{1,2}([.][[:digit:]]{1,2}|devel|beta[[:digit:]]+)$', + '^8[.][[:digit:]]{1,2}([.][[:digit:]]{1,2}|devel|(beta|rc)[[:digit:]]+)$', 'pg_version() should work' ); From 8f6ea54e20283b46bd7781c3ae88631e68b99911 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 27 Jun 2009 21:06:41 -0700 Subject: [PATCH 0393/1195] Handle common error conditions when comparing sets. --- expected/set.out | 8 +++++++- pgtap.sql.in | 38 +++++++++++++++++++++++++++++--------- sql/set.sql | 20 +++++++++++++++++++- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/expected/set.out b/expected/set.out index af06d00efdf0..809eed0d37cd 100644 --- a/expected/set.out +++ b/expected/set.out @@ -1,5 +1,5 @@ \unset ECHO -1..30 +1..36 ok 1 - simple set test should pass ok 2 - simple set test should have the proper description ok 3 - simple set test should have the proper diagnostics @@ -30,3 +30,9 @@ ok 27 - fail with extra and missing should have the proper diagnostics ok 28 - fail with 2 extras and 2 missings should fail ok 29 - fail with 2 extras and 2 missings should have the proper description ok 30 - fail with 2 extras and 2 missings should have the proper diagnostics +ok 31 - fail with column mismatch should fail +ok 32 - fail with column mismatch should have the proper description +ok 33 - fail with column mismatch should have the proper diagnostics +ok 34 - fail with different col counts should fail +ok 35 - fail with different col counts should have the proper description +ok 36 - fail with different col counts should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 50edf52ff214..733e758010cd 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -5771,14 +5771,35 @@ BEGIN EXECUTE 'CREATE TEMP TABLE __taphave__ AS ' || have; EXECUTE 'CREATE TEMP TABLE __tapwant__ AS ' || want; - FOR rec in EXECUTE 'SELECT * FROM __taphave__ EXCEPT SELECT * FROM __tapwant__' LOOP - extras := extras || rec::text; - END LOOP; + BEGIN + -- Find extra records. + FOR rec in EXECUTE 'SELECT * FROM __taphave__ EXCEPT SELECT * FROM __tapwant__' LOOP + extras := extras || rec::text; + END LOOP; - FOR rec in EXECUTE 'SELECT * FROM __tapwant__ EXCEPT SELECT * FROM __taphave__' LOOP - missing := missing || rec::text; - END LOOP; + -- Find missing records. + FOR rec in EXECUTE 'SELECT * FROM __tapwant__ EXCEPT SELECT * FROM __taphave__' LOOP + missing := missing || rec::text; + END LOOP; + + -- Drop the temporary tables. + EXECUTE 'DROP TABLE __taphave__'; + EXECUTE 'DROP TABLE __tapwant__'; + EXCEPTION WHEN others THEN + -- The failure likely happend before the dropping of tables. + EXECUTE 'DROP TABLE __taphave__'; + EXECUTE 'DROP TABLE __tapwant__'; + IF SQLSTATE = '42601' THEN + RETURN ok(FALSE, $3) || E'\n Queries do not have same number of columns'; + ELSIF SQLSTATE = '42804' THEN + RETURN ok(FALSE, $3) || E'\n Column types do not match between the queries'; + ELSE + -- This should not happen. + RAISE EXCEPTION '%', SQLERRM; + END IF; + END; + -- What extra records do we have? IF extras[1] IS NOT NULL THEN res := FALSE; msg := E'\n' || diag( @@ -5786,7 +5807,8 @@ BEGIN || 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( @@ -5795,8 +5817,6 @@ BEGIN ); END IF; - EXECUTE 'DROP TABLE __taphave__'; - EXECUTE 'DROP TABLE __tapwant__'; RETURN ok(res, $3) || msg; END; $$ LANGUAGE plpgsql; diff --git a/sql/set.sql b/sql/set.sql index f137454ae5ea..f9ca510fa09c 100644 --- a/sql/set.sql +++ b/sql/set.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(30); +SELECT plan(36); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -347,6 +347,24 @@ SELECT * FROM check_test( true ); +-- Handle falure due to column mismatch. +SELECT * FROM check_test( + set_eq( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'fail with column mismatch', + '', + ' Column types do not match between the queries' +); + +-- Handle falure due to column count mismatch. +SELECT * FROM check_test( + set_eq( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'fail with different col counts', + '', + ' Queries do not have same number of columns' +); + From d8ef96c4316cc516975d1f9a2ad57c595aec31d5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 29 Jun 2009 11:31:50 -0700 Subject: [PATCH 0394/1195] Implemented basic set- and bag-comparing functions. Just need to write tests. --- pgtap.sql.in | 148 +++++++++++++++++++++++++++++++++++++++++++++++---- sql/set.sql | 37 +++++++++++-- 2 files changed, 171 insertions(+), 14 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 733e758010cd..a40766fcf52b 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -5756,29 +5756,43 @@ RETURNS SETOF TEXT AS $$ SELECT * FROM runtests( '^test' ); $$ LANGUAGE sql; --- set_eq( sql, sql, description ) -CREATE OR REPLACE FUNCTION set_eq( TEXT, TEXT, TEXT ) +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; + +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 _relcomp( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE - have TEXT := CASE WHEN $1 ~ '[[:space:]]' THEN $1 ELSE 'EXECUTE ' || $1 END; - want TEXT := CASE WHEN $2 ~ '[[:space:]]' THEN $2 ELSE 'EXECUTE ' || $2 END; + have TEXT := _temptable( $1, '__taphave__' ); + want TEXT := _temptable( $2, '__tapwant__' ); extras TEXT[] := '{}'; missing TEXT[] := '{}'; res BOOLEAN := TRUE; msg TEXT := ''; rec RECORD; BEGIN - EXECUTE 'CREATE TEMP TABLE __taphave__ AS ' || have; - EXECUTE 'CREATE TEMP TABLE __tapwant__ AS ' || want; - BEGIN -- Find extra records. - FOR rec in EXECUTE 'SELECT * FROM __taphave__ EXCEPT SELECT * FROM __tapwant__' LOOP + FOR rec in EXECUTE 'SELECT * FROM __taphave__ EXCEPT ' || $4 + || 'SELECT * FROM __tapwant__' LOOP extras := extras || rec::text; END LOOP; -- Find missing records. - FOR rec in EXECUTE 'SELECT * FROM __tapwant__ EXCEPT SELECT * FROM __taphave__' LOOP + FOR rec in EXECUTE 'SELECT * FROM __tapwant__ EXCEPT ' || $4 + || 'SELECT * FROM __taphave__' LOOP missing := missing || rec::text; END LOOP; @@ -5821,10 +5835,124 @@ BEGIN END; $$ LANGUAGE plpgsql; +-- 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 set_eq( $1, $2, NULL::text ); + SELECT _relcomp( $1, $2, NULL::text, '' ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _potemptable ( TEXT, TEXT ) +RETURNS TEXT AS $$ +BEGIN + EXECUTE 'CREATE TEMP SEQUENCE ' || $2 || '_seq'; + EXECUTE 'CREATE TEMP TABLE ' || $2 || ' AS SELECT nextval(''' || $2 || '_seq'') AS __tapseq__ * FROM (' || _query($1) || ') x'; + return $2; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _tempcols ( TEXT ) +RETURNS TEXT AS $$ + SELECT array_to_string( ARRAY( + SELECT quote_ident(a.attname::text) + 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 LIKE 'pg_temp%' + AND c.relname = $1 + AND a.attnum > 0 + AND NOT a.attisdropped + AND a.attname <> '__tapseq__' + ORDER BY a.attnum + ), ', '); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _cmp( refcursor, refcursor, text ) +RETURNS TEXT AS $$ +DECLARE + have ALIAS FOR $1; + want ALIAS FOR $2; + rec_have RECORD; + rec_want RECORD; + rownum INTEGER := 1; +BEGIN + FETCH have INTO rec_have; + FETCH want INTO rec_want; + WHILE have IS NOT NULL OR want IS NOT NULL LOOP + IF have <> want THEN + RETURN ok( false, $3 ) || E'\n' || diag( + ' Records differ at row ' || rownum || E'\n' || + ' have: ' || have || E'\n' || + ' want: ' || want + ); + END IF; + rownum = rownum + 1; + OPEN have FOR EXECUTE _query($1); + OPEN want FOR EXECUTE _query($2); + END LOOP; + + RETURN ok( true, $3 ); +END; +$$ LANGUAGE plpgsql; + + +-- poset_eq( sql, sql, description ); +CREATE OR REPLACE FUNCTION poset_eq( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + have_table TEXT := _potemptable( $1, '__taphave__' ); + want_table TEXT := _potemptable( $2, '__tapwant__' ); + have REFCURSOR; + want REFCURSOR; +BEGIN + OPEN have FOR EXECUTE 'SELECT DISTINCT ' || _tempcols(have_table) + || ' FROM ' || have_table || ' ORDER BY __tapseq__'; + OPEN want FOR EXECUTE 'SELECT DISTINCT ' || _tempcols(want_table) + || ' FROM ' || want_table || ' ORDER BY __tapseq__'; + RETURN _cmp(have, want, $3); +END; +$$ LANGUAGE plpgsql; + +-- poset_eq( sql, sql ) +CREATE OR REPLACE FUNCTION poset_eq( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT poset_eq( $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; + +-- pobag_eq( sql, sql, description ) +CREATE OR REPLACE FUNCTION pobag_eq( TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + have REFCURSOR; + want REFCURSOR; +BEGIN + OPEN have FOR EXECUTE _query($1); + OPEN want FOR EXECUTE _query($2); + RETURN _cmp(have, want, $3); +END; +$$ LANGUAGE plpgsql; + +-- pobag_eq( sql, sql ) +CREATE OR REPLACE FUNCTION pobag_eq( TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT pobag_eq( $1, $2, NULL::text ); $$ LANGUAGE sql; diff --git a/sql/set.sql b/sql/set.sql index f9ca510fa09c..13e91f78274a 100644 --- a/sql/set.sql +++ b/sql/set.sql @@ -1,8 +1,8 @@ \unset ECHO \i test_setup.sql -SELECT plan(36); ---SELECT * FROM no_plan(); +--SELECT plan(36); +SELECT * FROM no_plan(); -- This will be rolled back. :-) SET client_min_messages = warning; @@ -219,13 +219,42 @@ INSERT INTO names (name) VALUES ('Katie'); CREATE TABLE annames AS SELECT id, name FROM names WHERE name like 'An%'; -/****************************************************************************/ --- Start by testing simple rows with prepared statement names. +-- We'll use these prepared statements. PREPARE anames AS SELECT id, name FROM names WHERE name like 'An%'; PREPARE expect AS VALUES (11, 'Andrew'), (15, 'Anthony'), ( 44, 'Anna'), (63, 'Angel'), (86, 'Angelina'), (130, 'Andrea'), (183, 'Antonio'); +/****************************************************************************/ +-- First, test _temptable. + +SELECT is( + _temptable('SELECT * FROM names', '__foonames__'), + '__foonames__', + 'Should create temp table with simple query' +); +SELECT has_table('__foonames__' ); + +SELECT is( + _temptable( 'anames', '__somenames__' ), + '__somenames__', + 'Should create a temp table for a prepared statement' +); +SELECT has_table('__somenames__' ); + +PREPARE "something cool" AS VALUES (1, 2), (3, 4); +SELECT is( + _temptable( '"something cool"', '__spacenames__' ), + '__spacenames__', + 'Should create a temp table for a prepared statement with space' +); +SELECT has_table('__somenames__' ); +SELECT has_table('__spacenames__' ); + + +/****************************************************************************/ +-- Now test set_eq(). + SELECT * FROM check_test( set_eq( 'anames', 'expect', 'first set test' ), true, From 9aebf5749d765eb2d1b06ba62b8c8b1d91998ee3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 29 Jun 2009 11:36:27 -0700 Subject: [PATCH 0395/1195] Renamed `set` test files to `resultset`. --- expected/resultset.out | 45 ++++++++++++++++++++++++++++++++++ expected/set.out | 38 ---------------------------- pgtap.sql.in | 1 - sql/{set.sql => resultset.sql} | 4 +-- 4 files changed, 47 insertions(+), 41 deletions(-) create mode 100644 expected/resultset.out delete mode 100644 expected/set.out rename sql/{set.sql => resultset.sql} (99%) diff --git a/expected/resultset.out b/expected/resultset.out new file mode 100644 index 000000000000..e6babcf75959 --- /dev/null +++ b/expected/resultset.out @@ -0,0 +1,45 @@ +\unset ECHO +1..43 +ok 1 - Should create temp table with simple query +ok 2 - Table __foonames__ should exist +ok 3 - Should create a temp table for a prepared statement +ok 4 - Table __somenames__ should exist +ok 5 - Should create a temp table for a prepared statement with space +ok 6 - Table __somenames__ should exist +ok 7 - Table __spacenames__ should exist +ok 8 - simple set test should pass +ok 9 - simple set test should have the proper description +ok 10 - simple set test should have the proper diagnostics +ok 11 - simple set test without descr should pass +ok 12 - simple set test without descr should have the proper description +ok 13 - simple set test without descr should have the proper diagnostics +ok 14 - execute set test should pass +ok 15 - execute set test should have the proper description +ok 16 - execute set test should have the proper diagnostics +ok 17 - select set test should pass +ok 18 - select set test should have the proper description +ok 19 - select set test should have the proper diagnostics +ok 20 - fail with extra record should fail +ok 21 - fail with extra record should have the proper description +ok 22 - fail with extra record should have the proper diagnostics +ok 23 - fail with 2 extra records should fail +ok 24 - fail with 2 extra records should have the proper description +ok 25 - fail with 2 extra records should have the proper diagnostics +ok 26 - fail with missing record should fail +ok 27 - fail with missing record should have the proper description +ok 28 - fail with missing record should have the proper diagnostics +ok 29 - fail with 2 missing records should fail +ok 30 - fail with 2 missing records should have the proper description +ok 31 - fail with 2 missing records should have the proper diagnostics +ok 32 - fail with extra and missing should fail +ok 33 - fail with extra and missing should have the proper description +ok 34 - fail with extra and missing should have the proper diagnostics +ok 35 - fail with 2 extras and 2 missings should fail +ok 36 - fail with 2 extras and 2 missings should have the proper description +ok 37 - fail with 2 extras and 2 missings should have the proper diagnostics +ok 38 - fail with column mismatch should fail +ok 39 - fail with column mismatch should have the proper description +ok 40 - fail with column mismatch should have the proper diagnostics +ok 41 - fail with different col counts should fail +ok 42 - fail with different col counts should have the proper description +ok 43 - fail with different col counts should have the proper diagnostics diff --git a/expected/set.out b/expected/set.out deleted file mode 100644 index 809eed0d37cd..000000000000 --- a/expected/set.out +++ /dev/null @@ -1,38 +0,0 @@ -\unset ECHO -1..36 -ok 1 - simple set test should pass -ok 2 - simple set test should have the proper description -ok 3 - simple set test should have the proper diagnostics -ok 4 - simple set test without descr should pass -ok 5 - simple set test without descr should have the proper description -ok 6 - simple set test without descr should have the proper diagnostics -ok 7 - execute set test should pass -ok 8 - execute set test should have the proper description -ok 9 - execute set test should have the proper diagnostics -ok 10 - select set test should pass -ok 11 - select set test should have the proper description -ok 12 - select set test should have the proper diagnostics -ok 13 - fail with extra record should fail -ok 14 - fail with extra record should have the proper description -ok 15 - fail with extra record should have the proper diagnostics -ok 16 - fail with 2 extra records should fail -ok 17 - fail with 2 extra records should have the proper description -ok 18 - fail with 2 extra records should have the proper diagnostics -ok 19 - fail with missing record should fail -ok 20 - fail with missing record should have the proper description -ok 21 - fail with missing record should have the proper diagnostics -ok 22 - fail with 2 missing records should fail -ok 23 - fail with 2 missing records should have the proper description -ok 24 - fail with 2 missing records should have the proper diagnostics -ok 25 - fail with extra and missing should fail -ok 26 - fail with extra and missing should have the proper description -ok 27 - fail with extra and missing should have the proper diagnostics -ok 28 - fail with 2 extras and 2 missings should fail -ok 29 - fail with 2 extras and 2 missings should have the proper description -ok 30 - fail with 2 extras and 2 missings should have the proper diagnostics -ok 31 - fail with column mismatch should fail -ok 32 - fail with column mismatch should have the proper description -ok 33 - fail with column mismatch should have the proper diagnostics -ok 34 - fail with different col counts should fail -ok 35 - fail with different col counts should have the proper description -ok 36 - fail with different col counts should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index a40766fcf52b..89f557b99e87 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -5900,7 +5900,6 @@ BEGIN END; $$ LANGUAGE plpgsql; - -- poset_eq( sql, sql, description ); CREATE OR REPLACE FUNCTION poset_eq( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ diff --git a/sql/set.sql b/sql/resultset.sql similarity index 99% rename from sql/set.sql rename to sql/resultset.sql index 13e91f78274a..17b295f59b42 100644 --- a/sql/set.sql +++ b/sql/resultset.sql @@ -1,8 +1,8 @@ \unset ECHO \i test_setup.sql ---SELECT plan(36); -SELECT * FROM no_plan(); +SELECT plan(43); +--SELECT * FROM no_plan(); -- This will be rolled back. :-) SET client_min_messages = warning; From c5c9b68156fbce88c00b2751a07b7180ccb40ce6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 29 Jun 2009 17:11:34 -0700 Subject: [PATCH 0396/1195] Filled out the result set comparison functions. * Dropped `poset_eq()`. It was just way too hard to do. And how do you really ignore dupes, since the dupes can effect order? It's just not worth it at this point. * Added tests for `bag_eq()`. * Added tests for `pobag_eq()` and fixed it up to actually work. This included making it display "()" in the diagnostics when a row is empty, as I think that this will be more intuitive than parens with a bunch of commas. * Tweaked the wording of the diagnostic message for `pobag_eq()`. * Added an implementation of `pobag_eq()` that can take cursor arguments directly. * Deleted a bunch of comments that were playing with various function names. --- expected/resultset.out | 125 ++++++++++++---- pgtap.sql.in | 120 +++------------- sql/resultset.sql | 320 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 421 insertions(+), 144 deletions(-) diff --git a/expected/resultset.out b/expected/resultset.out index e6babcf75959..d261593ffb2b 100644 --- a/expected/resultset.out +++ b/expected/resultset.out @@ -1,5 +1,5 @@ \unset ECHO -1..43 +1..118 ok 1 - Should create temp table with simple query ok 2 - Table __foonames__ should exist ok 3 - Should create a temp table for a prepared statement @@ -19,27 +19,102 @@ ok 16 - execute set test should have the proper diagnostics ok 17 - select set test should pass ok 18 - select set test should have the proper description ok 19 - select set test should have the proper diagnostics -ok 20 - fail with extra record should fail -ok 21 - fail with extra record should have the proper description -ok 22 - fail with extra record should have the proper diagnostics -ok 23 - fail with 2 extra records should fail -ok 24 - fail with 2 extra records should have the proper description -ok 25 - fail with 2 extra records should have the proper diagnostics -ok 26 - fail with missing record should fail -ok 27 - fail with missing record should have the proper description -ok 28 - fail with missing record should have the proper diagnostics -ok 29 - fail with 2 missing records should fail -ok 30 - fail with 2 missing records should have the proper description -ok 31 - fail with 2 missing records should have the proper diagnostics -ok 32 - fail with extra and missing should fail -ok 33 - fail with extra and missing should have the proper description -ok 34 - fail with extra and missing should have the proper diagnostics -ok 35 - fail with 2 extras and 2 missings should fail -ok 36 - fail with 2 extras and 2 missings should have the proper description -ok 37 - fail with 2 extras and 2 missings should have the proper diagnostics -ok 38 - fail with column mismatch should fail -ok 39 - fail with column mismatch should have the proper description -ok 40 - fail with column mismatch should have the proper diagnostics -ok 41 - fail with different col counts should fail -ok 42 - fail with different col counts should have the proper description -ok 43 - fail with different col counts should have the proper diagnostics +ok 20 - dupe rows ignored in set test should pass +ok 21 - dupe rows ignored in set test should have the proper description +ok 22 - dupe rows ignored in set test should have the proper diagnostics +ok 23 - fail with extra record should fail +ok 24 - fail with extra record should have the proper description +ok 25 - fail with extra record should have the proper diagnostics +ok 26 - fail with 2 extra records should fail +ok 27 - fail with 2 extra records should have the proper description +ok 28 - fail with 2 extra records should have the proper diagnostics +ok 29 - fail with missing record should fail +ok 30 - fail with missing record should have the proper description +ok 31 - fail with missing record should have the proper diagnostics +ok 32 - fail with 2 missing records should fail +ok 33 - fail with 2 missing records should have the proper description +ok 34 - fail with 2 missing records should have the proper diagnostics +ok 35 - fail with extra and missing should fail +ok 36 - fail with extra and missing should have the proper description +ok 37 - fail with extra and missing should have the proper diagnostics +ok 38 - fail with 2 extras and 2 missings should fail +ok 39 - fail with 2 extras and 2 missings should have the proper description +ok 40 - fail with 2 extras and 2 missings should have the proper diagnostics +ok 41 - fail with column mismatch should fail +ok 42 - fail with column mismatch should have the proper description +ok 43 - fail with column mismatch should have the proper diagnostics +ok 44 - fail with different col counts should fail +ok 45 - fail with different col counts should have the proper description +ok 46 - fail with different col counts should have the proper diagnostics +ok 47 - simple bag test should pass +ok 48 - simple bag test should have the proper description +ok 49 - simple bag test should have the proper diagnostics +ok 50 - simple bag test without descr should pass +ok 51 - simple bag test without descr should have the proper description +ok 52 - simple bag test without descr should have the proper diagnostics +ok 53 - execute bag test should pass +ok 54 - execute bag test should have the proper description +ok 55 - execute bag test should have the proper diagnostics +ok 56 - select bag test should pass +ok 57 - select bag test should have the proper description +ok 58 - select bag test should have the proper diagnostics +ok 59 - bag test with dupes should pass +ok 60 - bag test with dupes should have the proper description +ok 61 - bag test with dupes should have the proper diagnostics +ok 62 - fail with extra record should fail +ok 63 - fail with extra record should have the proper description +ok 64 - fail with extra record should have the proper diagnostics +ok 65 - fail with 2 extra records should fail +ok 66 - fail with 2 extra records should have the proper description +ok 67 - fail with 2 extra records should have the proper diagnostics +ok 68 - fail with missing record should fail +ok 69 - fail with missing record should have the proper description +ok 70 - fail with missing record should have the proper diagnostics +ok 71 - fail with 2 missing records should fail +ok 72 - fail with 2 missing records should have the proper description +ok 73 - fail with 2 missing records should have the proper diagnostics +ok 74 - fail with extra and missing should fail +ok 75 - fail with extra and missing should have the proper description +ok 76 - fail with extra and missing should have the proper diagnostics +ok 77 - fail with 2 extras and 2 missings should fail +ok 78 - fail with 2 extras and 2 missings should have the proper description +ok 79 - fail with 2 extras and 2 missings should have the proper diagnostics +ok 80 - fail with column mismatch should fail +ok 81 - fail with column mismatch should have the proper description +ok 82 - fail with column mismatch should have the proper diagnostics +ok 83 - fail with different col counts should fail +ok 84 - fail with different col counts should have the proper description +ok 85 - fail with different col counts should have the proper diagnostics +ok 86 - bag fail with missing dupe should fail +ok 87 - bag fail with missing dupe should have the proper description +ok 88 - bag fail with missing dupe should have the proper diagnostics +ok 89 - simple pobag test should pass +ok 90 - simple pobag test should have the proper description +ok 91 - simple pobag test should have the proper diagnostics +ok 92 - simple pobag test without desc should pass +ok 93 - simple pobag test without desc should have the proper description +ok 94 - simple pobag test without desc should have the proper diagnostics +ok 95 - execute pobag test should pass +ok 96 - execute pobag test should have the proper description +ok 97 - execute pobag test should have the proper diagnostics +ok 98 - select pobag test should pass +ok 99 - select pobag test should have the proper description +ok 100 - select pobag test should have the proper diagnostics +ok 101 - pobag test with dupes should pass +ok 102 - pobag test with dupes should have the proper description +ok 103 - pobag test with dupes should have the proper diagnostics +ok 104 - fail with extra record should fail +ok 105 - fail with extra record should have the proper description +ok 106 - fail with extra record should have the proper diagnostics +ok 107 - fail with missing record should fail +ok 108 - fail with missing record should have the proper description +ok 109 - fail with missing record should have the proper diagnostics +ok 110 - fail with missing record should fail +ok 111 - fail with missing record should have the proper description +ok 112 - fail with missing record should have the proper diagnostics +ok 113 - pobag fail with missing dupe should fail +ok 114 - pobag fail with missing dupe should have the proper description +ok 115 - pobag fail with missing dupe should have the proper diagnostics +ok 116 - simple pobag with cursors should pass +ok 117 - simple pobag with cursors should have the proper description +ok 118 - simple pobag with cursors should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 89f557b99e87..fdb221aa73d6 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -5847,92 +5847,51 @@ RETURNS TEXT AS $$ SELECT _relcomp( $1, $2, NULL::text, '' ); $$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION _potemptable ( TEXT, TEXT ) +-- bag_eq( sql, sql, description ) +CREATE OR REPLACE FUNCTION bag_eq( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ -BEGIN - EXECUTE 'CREATE TEMP SEQUENCE ' || $2 || '_seq'; - EXECUTE 'CREATE TEMP TABLE ' || $2 || ' AS SELECT nextval(''' || $2 || '_seq'') AS __tapseq__ * FROM (' || _query($1) || ') x'; - return $2; -END; -$$ LANGUAGE plpgsql; + SELECT _relcomp( $1, $2, $3, 'ALL ' ); +$$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION _tempcols ( TEXT ) +-- bag_eq( sql, sql ) +CREATE OR REPLACE FUNCTION bag_eq( TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT array_to_string( ARRAY( - SELECT quote_ident(a.attname::text) - 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 LIKE 'pg_temp%' - AND c.relname = $1 - AND a.attnum > 0 - AND NOT a.attisdropped - AND a.attname <> '__tapseq__' - ORDER BY a.attnum - ), ', '); + SELECT _relcomp( $1, $2, NULL::text, 'ALL ' ); $$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION _cmp( refcursor, refcursor, text ) +-- pobag_eq( cursor, cursor, description ) +CREATE OR REPLACE FUNCTION pobag_eq( refcursor, refcursor, text ) RETURNS TEXT AS $$ DECLARE - have ALIAS FOR $1; - want ALIAS FOR $2; + have ALIAS FOR $1; + want ALIAS FOR $2; rec_have RECORD; rec_want RECORD; rownum INTEGER := 1; BEGIN FETCH have INTO rec_have; FETCH want INTO rec_want; - WHILE have IS NOT NULL OR want IS NOT NULL LOOP - IF have <> want THEN + WHILE rec_have IS NOT NULL OR rec_want IS NOT NULL LOOP + IF rec_have <> rec_want THEN RETURN ok( false, $3 ) || E'\n' || diag( - ' Records differ at row ' || rownum || E'\n' || - ' have: ' || have || E'\n' || - ' want: ' || want + ' Results differ beginning at row ' || rownum || E':\n' || + ' have: ' || CASE WHEN rec_have IS NULL THEN '()' ELSE rec_have::text END || E'\n' || + ' want: ' || CASE WHEN rec_want IS NULL THEN '()' ELSE rec_want::text END ); END IF; rownum = rownum + 1; - OPEN have FOR EXECUTE _query($1); - OPEN want FOR EXECUTE _query($2); + FETCH have INTO rec_have; + FETCH want INTO rec_want; END LOOP; RETURN ok( true, $3 ); END; $$ LANGUAGE plpgsql; --- poset_eq( sql, sql, description ); -CREATE OR REPLACE FUNCTION poset_eq( TEXT, TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - have_table TEXT := _potemptable( $1, '__taphave__' ); - want_table TEXT := _potemptable( $2, '__tapwant__' ); - have REFCURSOR; - want REFCURSOR; -BEGIN - OPEN have FOR EXECUTE 'SELECT DISTINCT ' || _tempcols(have_table) - || ' FROM ' || have_table || ' ORDER BY __tapseq__'; - OPEN want FOR EXECUTE 'SELECT DISTINCT ' || _tempcols(want_table) - || ' FROM ' || want_table || ' ORDER BY __tapseq__'; - RETURN _cmp(have, want, $3); -END; -$$ LANGUAGE plpgsql; - --- poset_eq( sql, sql ) -CREATE OR REPLACE FUNCTION poset_eq( TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT poset_eq( $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 ) +-- pobag_eq( cursor, cursor ) +CREATE OR REPLACE FUNCTION pobag_eq( refcursor, refcursor ) RETURNS TEXT AS $$ - SELECT _relcomp( $1, $2, NULL::text, 'ALL ' ); + SELECT pobag_eq( $1, $2, NULL::text ); $$ LANGUAGE sql; -- pobag_eq( sql, sql, description ) @@ -5944,7 +5903,7 @@ DECLARE BEGIN OPEN have FOR EXECUTE _query($1); OPEN want FOR EXECUTE _query($2); - RETURN _cmp(have, want, $3); + RETURN pobag_eq(have, want, $3); END; $$ LANGUAGE plpgsql; @@ -5955,38 +5914,3 @@ RETURNS TEXT AS $$ $$ LANGUAGE sql; --- query_gets() --- oquery_gets() --- squery_gets() - --- cursor_gets() --- ocursor_gets() --- scursor_gets() - --- results_count_is - --- results_equal --- results_intersect --- results_except - - - - --- set_equals -- set equivalence --- set_includes -- set intersection --- set_excludes -- set difference --- set_size_is - --- set_equals( - - --- poset_equals --- poset_includes --- poset_excludes - --- bag_equals --- bag_includes --- bag_excludes --- bag_size_is - - diff --git a/sql/resultset.sql b/sql/resultset.sql index 17b295f59b42..826e4e88e1dd 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(43); +SELECT plan(118); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -221,9 +221,9 @@ SELECT id, name FROM names WHERE name like 'An%'; -- We'll use these prepared statements. PREPARE anames AS SELECT id, name FROM names WHERE name like 'An%'; -PREPARE expect AS VALUES (11, 'Andrew'), (15, 'Anthony'), ( 44, 'Anna'), - (63, 'Angel'), (86, 'Angelina'), (130, 'Andrea'), - (183, 'Antonio'); +PREPARE expect AS VALUES (11, 'Andrew'), ( 44, 'Anna'), (15, 'Anthony'), + (183, 'Antonio'), (86, 'Angelina'), (130, 'Andrea'), + (63, 'Angel'); /****************************************************************************/ -- First, test _temptable. @@ -251,17 +251,17 @@ SELECT is( SELECT has_table('__somenames__' ); SELECT has_table('__spacenames__' ); - /****************************************************************************/ -- Now test set_eq(). SELECT * FROM check_test( - set_eq( 'anames', 'expect', 'first set test' ), + set_eq( 'anames', 'expect', 'whatever' ), true, 'simple set test', - 'first set test', + 'whatever', '' ); + SELECT * FROM check_test( set_eq( 'anames', 'expect' ), true, @@ -291,6 +291,18 @@ SELECT * FROM check_test( '' ); +-- Make sure that dupes are disregarded. +SELECT * FROM check_test( + set_eq( + 'VALUES (1, ''Anna'')', + 'VALUES (1, ''Anna''), (1, ''Anna'')' + ), + true, + 'dupe rows ignored in set test', + '', + '' +); + -- Try some failures. SELECT * FROM check_test( set_eq( @@ -395,36 +407,302 @@ SELECT * FROM check_test( ); +/****************************************************************************/ +-- Now test bag_eq(). +SELECT * FROM check_test( + bag_eq( 'anames', 'expect', 'whatever' ), + true, + 'simple bag test', + 'whatever', + '' +); +SELECT * FROM check_test( + bag_eq( 'anames', 'expect' ), + true, + 'simple bag test without descr', + '', + '' +); --- SELECT set_eq( 'have', ARRAY[1, 2, 4] ); --- SELECT set_eq( 'have', ARRAY[[1, 2], [3, 4]] ); +-- Pass a full SQL statement for the prepared statements. +SELECT * FROM check_test( + bag_eq( 'EXECUTE anames', 'EXECUTE expect' ), + true, + 'execute bag test', + '', + '' +); --- SELECT poset_eq( 'have', 'want' ); --- SELECT poset_eq( 'have', ARRAY[1, 2, 4] ); --- SELECT poset_eq( 'have', ARRAY[[1, 2], [3, 4]] ); +-- Compare actual SELECT statements. +SELECT * FROM check_test( + bag_eq( + 'SELECT id, name FROM names WHERE name like ''An%''', + 'SELECT id, name FROM annames' + ), + true, + 'select bag test', + '', + '' +); +-- Compare with dupes. +SELECT * FROM check_test( + bag_eq( + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (1, ''Anna''), (1, ''Anna''), (86, ''Angelina'')' + ), + true, + 'bag test with dupes', + '', + '' +); +-- And now some failures. +SELECT * FROM check_test( + bag_eq( + 'anames', + 'SELECT id, name FROM annames WHERE name <> ''Anna''' + ), + false, + 'fail with extra record', + '', + ' Extra records: + (44,Anna)' +); +SELECT * FROM check_test( + bag_eq( + 'anames', + 'SELECT id, name FROM annames WHERE name NOT IN (''Anna'', ''Angelina'')' + ), + false, + 'fail with 2 extra records', + '', + E' Extra records: + \\((44,Anna|86,Angelina)\\) + \\((44,Anna|86,Angelina)\\)', + true +); +SELECT * FROM check_test( + bag_eq( + 'SELECT id, name FROM annames WHERE name <> ''Anna''', + 'expect' + ), + false, + 'fail with missing record', + '', + ' Missing records: + (44,Anna)' +); +SELECT * FROM check_test( + bag_eq( + 'SELECT id, name FROM annames WHERE name NOT IN (''Anna'', ''Angelina'')', + 'expect' + ), + false, + 'fail with 2 missing records', + '', + E' Missing records: + \\((44,Anna|86,Angelina)\\) + \\((44,Anna|86,Angelina)\\)', + true +); --- DECLARE want CURSOR FOR SELECT * FROM users WHERE active; --- DECLARE have CURSOR FOR SELECT * FROM get_active_users(); --- SELECT bag_eq( 'have', 'want' ); --- SELECT bag_eq( 'have', ARRAY[1, 2, 4, 4] ); --- SELECT bag_eq( 'have', ARRAY[[1, 2], [3, 4], [1, 2]] ); +SELECT * FROM check_test( + bag_eq( + 'SELECT id, name FROM names WHERE name ~ ''^(An|Jacob)'' AND name <> ''Anna''', + 'SELECT id, name FROM annames' + ), + false, + 'fail with extra and missing', + '', + ' Extra records: + (1,Jacob) + Missing records: + (44,Anna)' +); --- SELECT pobag_eq( 'have', 'want' ); --- SELECT pobag_eq( 'have', ARRAY[1, 2, 4, 4] ); --- SELECT pobag_eq( 'have', ARRAY[[1, 2], [3, 4], [1, 2]] ); +SELECT * FROM check_test( + bag_eq( + 'SELECT id, name FROM names WHERE name ~ ''^(An|Jacob|Jacks)'' AND name NOT IN (''Anna'', ''Angelina'')', + 'SELECT id, name FROM annames' + ), + false, + 'fail with 2 extras and 2 missings', + '', + E' Extra records: + \\((1,Jacob|87,Jackson)\\) + \\((1,Jacob|87,Jackson)\\) + Missing records: + \\((44,Anna|86,Angelina)\\) + \\((44,Anna|86,Angelina)\\)', + true +); +-- Handle falure due to column mismatch. +SELECT * FROM check_test( + bag_eq( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'fail with column mismatch', + '', + ' Column types do not match between the queries' +); + +-- Handle falure due to column count mismatch. +SELECT * FROM check_test( + bag_eq( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'fail with different col counts', + '', + ' Queries do not have same number of columns' +); + +-- Handle failure due to missing dupe. +SELECT * FROM check_test( + bag_eq( + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (1, ''Anna''), (86, ''Angelina'')' + ), + false, + 'bag fail with missing dupe', + '', + ' Extra records: + (1,Anna)' +); + +/****************************************************************************/ +-- Now test pobag_eq(). + +PREPARE anames_ord AS SELECT id, name FROM names WHERE name like 'An%' ORDER BY id; +PREPARE expect_ord AS VALUES (11, 'Andrew'), (15, 'Anthony'), ( 44, 'Anna'), + (63, 'Angel'), (86, 'Angelina'), (130, 'Andrea'), + (183, 'Antonio'); + +SELECT * FROM check_test( + pobag_eq( 'anames_ord', 'expect_ord', 'whatever' ), + true, + 'simple pobag test', + 'whatever', + '' +); + +SELECT * FROM check_test( + pobag_eq( 'anames_ord', 'expect_ord' ), + true, + 'simple pobag test without desc', + '', + '' +); + +-- Pass a full SQL statement for the prepared statements. +SELECT * FROM check_test( + pobag_eq( 'EXECUTE anames_ord', 'EXECUTE expect_ord' ), + true, + 'execute pobag test', + '', + '' +); + +-- Compare actual SELECT statements. +SELECT * FROM check_test( + pobag_eq( + 'SELECT id, name FROM names WHERE name like ''An%'' ORDER BY id', + 'SELECT id, name FROM annames ORDER BY id' + ), + true, + 'select pobag test', + '', + '' +); + +-- Compare with dupes. +SELECT * FROM check_test( + pobag_eq( + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')' + ), + true, + 'pobag test with dupes', + '', + '' +); + +-- And now some failures. +SELECT * FROM check_test( + pobag_eq( + 'anames_ord', + 'SELECT id, name FROM annames WHERE name <> ''Anna''' + ), + false, + 'fail with extra record', + '', + ' Results differ beginning at row 3: + have: (44,Anna) + want: (63,Angel)' +); + +-- Now when the last row is missing. +SELECT * FROM check_test( + pobag_eq( + 'SELECT id, name FROM annames WHERE name <> ''Antonio''', + 'anames_ord' + ), + false, + 'fail with missing record', + '', + ' Results differ beginning at row 7: + have: () + want: (183,Antonio)' +); + +-- Invert that. +SELECT * FROM check_test( + pobag_eq( + 'anames_ord', + 'SELECT id, name FROM annames WHERE name <> ''Antonio''' + ), + false, + 'fail with missing record', + '', + ' Results differ beginning at row 7: + have: (183,Antonio) + want: ()' +); + + +-- Compare with missing dupe. +SELECT * FROM check_test( + pobag_eq( + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (1, ''Anna''), (86, ''Angelina'')' + ), + false, + 'pobag fail with missing dupe', + '', + ' Results differ beginning at row 3: + have: (1,Anna) + want: ()' +); + +-- Compare with cursors. +DECLARE cwant CURSOR FOR SELECT id, name FROM names WHERE name like 'An%' ORDER BY id; +DECLARE chave CURSOR FOR SELECT id, name from annames ORDER BY id; + +SELECT * FROM check_test( + pobag_eq( 'cwant'::refcursor, 'chave'::refcursor ), + true, + 'simple pobag with cursors', + '', + '' +); /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); ROLLBACK; - From 9de854a02077de21fc7ff77ae65be7324c0494bd Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 29 Jun 2009 17:15:39 -0700 Subject: [PATCH 0397/1195] Renamed `pobag_eq()` to `results_eq()`. --- expected/resultset.out | 42 +++++++++++++++++++++--------------------- pgtap.sql.in | 22 +++++++++++----------- sql/resultset.sql | 36 ++++++++++++++++++------------------ 3 files changed, 50 insertions(+), 50 deletions(-) diff --git a/expected/resultset.out b/expected/resultset.out index d261593ffb2b..61d2a660c329 100644 --- a/expected/resultset.out +++ b/expected/resultset.out @@ -88,21 +88,21 @@ ok 85 - fail with different col counts should have the proper diagnostics ok 86 - bag fail with missing dupe should fail ok 87 - bag fail with missing dupe should have the proper description ok 88 - bag fail with missing dupe should have the proper diagnostics -ok 89 - simple pobag test should pass -ok 90 - simple pobag test should have the proper description -ok 91 - simple pobag test should have the proper diagnostics -ok 92 - simple pobag test without desc should pass -ok 93 - simple pobag test without desc should have the proper description -ok 94 - simple pobag test without desc should have the proper diagnostics -ok 95 - execute pobag test should pass -ok 96 - execute pobag test should have the proper description -ok 97 - execute pobag test should have the proper diagnostics -ok 98 - select pobag test should pass -ok 99 - select pobag test should have the proper description -ok 100 - select pobag test should have the proper diagnostics -ok 101 - pobag test with dupes should pass -ok 102 - pobag test with dupes should have the proper description -ok 103 - pobag test with dupes should have the proper diagnostics +ok 89 - simple results test should pass +ok 90 - simple results test should have the proper description +ok 91 - simple results test should have the proper diagnostics +ok 92 - simple results test without desc should pass +ok 93 - simple results test without desc should have the proper description +ok 94 - simple results test without desc should have the proper diagnostics +ok 95 - execute results test should pass +ok 96 - execute results test should have the proper description +ok 97 - execute results test should have the proper diagnostics +ok 98 - select results test should pass +ok 99 - select results test should have the proper description +ok 100 - select results test should have the proper diagnostics +ok 101 - results test with dupes should pass +ok 102 - results test with dupes should have the proper description +ok 103 - results test with dupes should have the proper diagnostics ok 104 - fail with extra record should fail ok 105 - fail with extra record should have the proper description ok 106 - fail with extra record should have the proper diagnostics @@ -112,9 +112,9 @@ ok 109 - fail with missing record should have the proper diagnostics ok 110 - fail with missing record should fail ok 111 - fail with missing record should have the proper description ok 112 - fail with missing record should have the proper diagnostics -ok 113 - pobag fail with missing dupe should fail -ok 114 - pobag fail with missing dupe should have the proper description -ok 115 - pobag fail with missing dupe should have the proper diagnostics -ok 116 - simple pobag with cursors should pass -ok 117 - simple pobag with cursors should have the proper description -ok 118 - simple pobag with cursors should have the proper diagnostics +ok 113 - results fail with missing dupe should fail +ok 114 - results fail with missing dupe should have the proper description +ok 115 - results fail with missing dupe should have the proper diagnostics +ok 116 - simple results with cursors should pass +ok 117 - simple results with cursors should have the proper description +ok 118 - simple results with cursors should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index fdb221aa73d6..3457699da980 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -5859,8 +5859,8 @@ RETURNS TEXT AS $$ SELECT _relcomp( $1, $2, NULL::text, 'ALL ' ); $$ LANGUAGE sql; --- pobag_eq( cursor, cursor, description ) -CREATE OR REPLACE FUNCTION pobag_eq( refcursor, refcursor, text ) +-- results_eq( cursor, cursor, description ) +CREATE OR REPLACE FUNCTION results_eq( refcursor, refcursor, text ) RETURNS TEXT AS $$ DECLARE have ALIAS FOR $1; @@ -5888,14 +5888,14 @@ BEGIN END; $$ LANGUAGE plpgsql; --- pobag_eq( cursor, cursor ) -CREATE OR REPLACE FUNCTION pobag_eq( refcursor, refcursor ) +-- results_eq( cursor, cursor ) +CREATE OR REPLACE FUNCTION results_eq( refcursor, refcursor ) RETURNS TEXT AS $$ - SELECT pobag_eq( $1, $2, NULL::text ); + SELECT results_eq( $1, $2, NULL::text ); $$ LANGUAGE sql; --- pobag_eq( sql, sql, description ) -CREATE OR REPLACE FUNCTION pobag_eq( TEXT, TEXT, TEXT ) +-- results_eq( sql, sql, description ) +CREATE OR REPLACE FUNCTION results_eq( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE have REFCURSOR; @@ -5903,14 +5903,14 @@ DECLARE BEGIN OPEN have FOR EXECUTE _query($1); OPEN want FOR EXECUTE _query($2); - RETURN pobag_eq(have, want, $3); + RETURN results_eq(have, want, $3); END; $$ LANGUAGE plpgsql; --- pobag_eq( sql, sql ) -CREATE OR REPLACE FUNCTION pobag_eq( TEXT, TEXT ) +-- results_eq( sql, sql ) +CREATE OR REPLACE FUNCTION results_eq( TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT pobag_eq( $1, $2, NULL::text ); + SELECT results_eq( $1, $2, NULL::text ); $$ LANGUAGE sql; diff --git a/sql/resultset.sql b/sql/resultset.sql index 826e4e88e1dd..5535043b3c41 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -576,7 +576,7 @@ SELECT * FROM check_test( ); /****************************************************************************/ --- Now test pobag_eq(). +-- Now test results_eq(). PREPARE anames_ord AS SELECT id, name FROM names WHERE name like 'An%' ORDER BY id; PREPARE expect_ord AS VALUES (11, 'Andrew'), (15, 'Anthony'), ( 44, 'Anna'), @@ -584,57 +584,57 @@ PREPARE expect_ord AS VALUES (11, 'Andrew'), (15, 'Anthony'), ( 44, 'Anna'), (183, 'Antonio'); SELECT * FROM check_test( - pobag_eq( 'anames_ord', 'expect_ord', 'whatever' ), + results_eq( 'anames_ord', 'expect_ord', 'whatever' ), true, - 'simple pobag test', + 'simple results test', 'whatever', '' ); SELECT * FROM check_test( - pobag_eq( 'anames_ord', 'expect_ord' ), + results_eq( 'anames_ord', 'expect_ord' ), true, - 'simple pobag test without desc', + 'simple results test without desc', '', '' ); -- Pass a full SQL statement for the prepared statements. SELECT * FROM check_test( - pobag_eq( 'EXECUTE anames_ord', 'EXECUTE expect_ord' ), + results_eq( 'EXECUTE anames_ord', 'EXECUTE expect_ord' ), true, - 'execute pobag test', + 'execute results test', '', '' ); -- Compare actual SELECT statements. SELECT * FROM check_test( - pobag_eq( + results_eq( 'SELECT id, name FROM names WHERE name like ''An%'' ORDER BY id', 'SELECT id, name FROM annames ORDER BY id' ), true, - 'select pobag test', + 'select results test', '', '' ); -- Compare with dupes. SELECT * FROM check_test( - pobag_eq( + results_eq( 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')' ), true, - 'pobag test with dupes', + 'results test with dupes', '', '' ); -- And now some failures. SELECT * FROM check_test( - pobag_eq( + results_eq( 'anames_ord', 'SELECT id, name FROM annames WHERE name <> ''Anna''' ), @@ -648,7 +648,7 @@ SELECT * FROM check_test( -- Now when the last row is missing. SELECT * FROM check_test( - pobag_eq( + results_eq( 'SELECT id, name FROM annames WHERE name <> ''Antonio''', 'anames_ord' ), @@ -662,7 +662,7 @@ SELECT * FROM check_test( -- Invert that. SELECT * FROM check_test( - pobag_eq( + results_eq( 'anames_ord', 'SELECT id, name FROM annames WHERE name <> ''Antonio''' ), @@ -677,12 +677,12 @@ SELECT * FROM check_test( -- Compare with missing dupe. SELECT * FROM check_test( - pobag_eq( + results_eq( 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', 'VALUES (1, ''Anna''), (86, ''Angelina'')' ), false, - 'pobag fail with missing dupe', + 'results fail with missing dupe', '', ' Results differ beginning at row 3: have: (1,Anna) @@ -694,9 +694,9 @@ DECLARE cwant CURSOR FOR SELECT id, name FROM names WHERE name like 'An%' ORDER DECLARE chave CURSOR FOR SELECT id, name from annames ORDER BY id; SELECT * FROM check_test( - pobag_eq( 'cwant'::refcursor, 'chave'::refcursor ), + results_eq( 'cwant'::refcursor, 'chave'::refcursor ), true, - 'simple pobag with cursors', + 'simple results with cursors', '', '' ); From 39044875c4a23f2ae6a89af9895cc5a85b3f6060 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 29 Jun 2009 17:19:36 -0700 Subject: [PATCH 0398/1195] Updated `uninstall_pgtap.sql.in` with resultset functions. --- Changes | 2 +- uninstall_pgtap.sql.in | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index d66fa2d3f44f..55d266911424 100644 --- a/Changes +++ b/Changes @@ -4,7 +4,7 @@ Revision history for pgTAP 0.22 ------------------------- * Fixed failing test on 8.4rc2. -* Added `set_eq()`. +* Added `results_eq(), `set_eq()`, and `bag_eq()`. 0.21 2009-05-29T00:04:31 ------------------------- diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 711c963754aa..c37a45c84edd 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,4 +1,15 @@ -- ## SET search_path TO TAPSCHEMA, public; +DROP FUNCTION results_eq( TEXT, TEXT ); +DROP FUNCTION results_eq( TEXT, TEXT, TEXT ); +DROP FUNCTION results_eq( refcursor, refcursor ); +DROP FUNCTION results_eq( refcursor, refcursor, text ); +DROP FUNCTION bag_eq( TEXT, TEXT ); +DROP FUNCTION bag_eq( TEXT, TEXT, TEXT ); +DROP FUNCTION set_eq( TEXT, TEXT ); +DROP FUNCTION set_eq( TEXT, TEXT, TEXT ); +DROP FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION _temptable ( TEXT, TEXT ); +DROP FUNCTION _query( TEXT ); DROP FUNCTION runtests( ); DROP FUNCTION runtests( TEXT ); DROP FUNCTION runtests( NAME ); From 73450ef9e02584615ee6a72efc4f517c179bc9bd Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 30 Jun 2009 14:21:37 -0700 Subject: [PATCH 0399/1195] Documented `results_eq()`. * Added `results_eq()`, `set_eq()`, and `bag_eq()` to `Changes`. * Noted a few other To-Dos for the result comparison functions. * Made varying column count and type diagnostics more succinct. * Switched `restuls_eq()` to `IS DISTINCT FROM` instead of `<>` so that `NULL`s will be compared in a reasonable way. * Added exception handling to deal with data type mismatches when comparing results in `results_eq()`. * Added code to close cursors opened by `results_eq()`. Cursors passed from userland will not be touched. * Added variations of `results_eq()` to mix and match cursor and SQL statement arguments. --- Changes | 3 +- README.pgtap | 140 +++++++++++++++++++++++++++++++++++++++-- expected/resultset.out | 53 ++++++++++++---- pgtap.sql.in | 62 ++++++++++++++++-- sql/resultset.sql | 113 +++++++++++++++++++++++++++++++-- 5 files changed, 341 insertions(+), 30 deletions(-) diff --git a/Changes b/Changes index 55d266911424..5a6363aaedb2 100644 --- a/Changes +++ b/Changes @@ -4,7 +4,8 @@ Revision history for pgTAP 0.22 ------------------------- * Fixed failing test on 8.4rc2. -* Added `results_eq(), `set_eq()`, and `bag_eq()`. +* Added `results_eq(), `set_eq()`, and `bag_eq()`, functions to test query + results. 0.21 2009-05-29T00:04:31 ------------------------- diff --git a/README.pgtap b/README.pgtap index 156417eee725..fcc94c88a0ca 100644 --- a/README.pgtap +++ b/README.pgtap @@ -655,6 +655,139 @@ string. You will want to account for this and pad your estimates accordingly. It's best to think of this as a brute force comparison of runtimes, in order to ensure that a query is not *really* slow (think seconds). +Can You Relate? +--------------- + +So you've got your basic scalar comparison funtions, what about relations? +Mabye you have some pretty hairy `SELECT` statements in views or functions to +test? We've got your relation-testing functions right here. + +### `results_eq( sql, sql, description )` ### +### `results_eq( sql, sql )` ### +### `results_eq( cursor, cursor, description )` ### +### `results_eq( cursor, cursor )` ### +### `results_eq( sql, cursor, description )` ### +### `results_eq( sql, cursor )` ### +### `results_eq( cursor, sql, description )` ### +### `results_eq( cursor, sql )` ### + + PREPARE users_test AS SELECT * FROM active_users(); + PREPARE users_expect AS + VALUES ( 42, 'Anna'), (19, 'Strongrrl'), (39, 'Theory'); + + SELECT results_eq( 'users_test', 'users_expect', 'We should have users' ); + +There are three ways to test result sets in pgTAP. The most intuitive is to do +a direct row-by-row comparison of results to ensure that they are exactly what +you expect, in the order you expect. Coincidentally, this is exactly how +`results_eq()` behaves. Here's how you use it: simply pass in two SQL +statements as strings and an optional description. Yep, that's it. It will do +the rest. + +For example, say that you have a function, `active_users()`, that returns a +set of rows from the users table. To make sure that it returns the rows you +expect, you might do something like this: + + SELECT results_eq( + 'SELECT * FROM active_users()', + 'SELECT * FROM users WHERE active', + 'active_users() should return active users' + ); + +Tip: If you want to hard-code the values to compare, use a `VALUES` statement +instead of a query, like so: + + SELECT results_eq( + 'SELECT * FROM active_users()', + $$VALUES ( 42, 'Anna'), (19, 'Strongrrl'), (39, 'Theory')$$, + 'active_users() should return active users' + ); + +Of course, you might often need to do something more complex in your SQL, and +quoting SQL in strings in what is, after all, a SQL application, is an +unnecessary PITA. There are two ways to make your use of `results_eq()` feel +more SQLish. + +The first is to use prepared statements. This allows you to just write SQL and +simply pass the prepared statement names to `results_eq()`, as in th example +at beginning of this section. pgTAP assumes that a SQL statement argument with +no space or starting with a double quote character is a prepared statement and +simply `EXECUTE`s it. + +If you need to pass arguments to a prepared statement, perhaps because you +plan to use it in multiple tests to return different values, just include +`EXECUTE` in the SQL string. Here's an example with a prepared statement with +a space in its name, and one where arguments need to be passed: + + PREPARE "my test" AS SELECT * FROM active_users() WHERE name LIKE 'A%'; + PREPARE expect AS SELECT * FROM users WHERE active = $1 AND name LIKE $2; + + SELECT results_eq( + '"my test"', + 'EXECUTE expect( true, ''A%'' )', + 'See about those "A" users' + ); + +Since the "my test" was declared with double quotes, it therefore must be +passed with double quotes. And since the call to the "expect" prepared +statement included spaces (to keep it legible), the `EXECUTE` keyword was +required. + +Internally, `results_eq()` turns your SQL statements into cursors so that it +can iterate over them one row at a time. Conveniently, this behavior is +directly available to you, as well. Rather than pass in some arbitrary SQL +statement or the name of a prepared statement, simply create a cursor and just +pass *it* in, like so: + + DECLARE cwant CURSOR FOR SELECT * FROM active_users(); + DECLARE chave CURSOR FOR SELECT * FROM users WHERE active ORDER BY name; + + SELECT results_eq( + 'cwant'::refcursor, + 'chave'::refcursor, + 'Gotta have those active users!' + ); + +The key here is to ensure that the cursor names are passed as `refcursor`s. +This allows `results_eq()` to disambiguate the arguments from prepared +statements. And of course, you can mix and match cursors, prepared statements, +and SQL as much as you like. Here's an example using a prepared statement and +a (rewound) cursor for the expected results: + + PREPARE users_test AS SELECT * FROM active_users(); + MOVE BACKWARD ALL IN chave; + + SELECT results_eq( + 'users_test', + 'chave'::refcursor, + 'Gotta have those active users!' + ); + +Regardless of which types of arguments you pass, in the event of a test +failure due to different results, `results_eq()` will offer a nice diagnostic +message to tell you at what row the results differ, something like: + + # Failed test 146 + # Results differ beginning at row 3: + # have: (1,Anna) + # want: (22,Betty) + +If there are different numbers of rows in each result set, you'll see an empty +row in the comparison: + + # Failed test 147 + # Results differ beginning at row 5: + # have: (1,Anna) + # want: () + +If the number of columns varies between result sets, or if results are of +different data types, you'll get diagnostics like so: + + # Failed test 148 + # Column types differ between queries: + # have: (1) + # want: (foo,1) + The Schema Things ================= @@ -3323,16 +3456,15 @@ functions do not work under 8.0. Don't even use them there. To Do ----- +* Document `set_eq()` and `bag_eq()`. +* Support prepared statement names in throws_ok, lives_ok, and performs_ok. +* Get more diagnostics for data type and column type failures in set_eq(). * Useful schema testing functions to consider adding: + `throws_like()` + `sequence_has_range()` + `sequence_increments_by()` + `sequence_starts_at()` + `sequence_cycles()` -* Result set testing functions: - + `set_eq()` - + `poset_eq()` - + `pobag_eq()` Supported Versions ----------------- diff --git a/expected/resultset.out b/expected/resultset.out index 61d2a660c329..b9bbf55845db 100644 --- a/expected/resultset.out +++ b/expected/resultset.out @@ -1,5 +1,5 @@ \unset ECHO -1..118 +1..145 ok 1 - Should create temp table with simple query ok 2 - Table __foonames__ should exist ok 3 - Should create a temp table for a prepared statement @@ -103,18 +103,45 @@ ok 100 - select results test should have the proper diagnostics ok 101 - results test with dupes should pass ok 102 - results test with dupes should have the proper description ok 103 - results test with dupes should have the proper diagnostics -ok 104 - fail with extra record should fail -ok 105 - fail with extra record should have the proper description -ok 106 - fail with extra record should have the proper diagnostics -ok 107 - fail with missing record should fail -ok 108 - fail with missing record should have the proper description -ok 109 - fail with missing record should have the proper diagnostics +ok 104 - results test with nulls should pass +ok 105 - results test with nulls should have the proper description +ok 106 - results test with nulls should have the proper diagnostics +ok 107 - fail with extra record should fail +ok 108 - fail with extra record should have the proper description +ok 109 - fail with extra record should have the proper diagnostics ok 110 - fail with missing record should fail ok 111 - fail with missing record should have the proper description ok 112 - fail with missing record should have the proper diagnostics -ok 113 - results fail with missing dupe should fail -ok 114 - results fail with missing dupe should have the proper description -ok 115 - results fail with missing dupe should have the proper diagnostics -ok 116 - simple results with cursors should pass -ok 117 - simple results with cursors should have the proper description -ok 118 - simple results with cursors should have the proper diagnostics +ok 113 - fail with missing record should fail +ok 114 - fail with missing record should have the proper description +ok 115 - fail with missing record should have the proper diagnostics +ok 116 - results fail with missing dupe should fail +ok 117 - results fail with missing dupe should have the proper description +ok 118 - results fail with missing dupe should have the proper diagnostics +ok 119 - fail with NULL should fail +ok 120 - fail with NULL should have the proper description +ok 121 - fail with NULL should have the proper diagnostics +ok 122 - fail with column mismatch should fail +ok 123 - fail with column mismatch should have the proper description +ok 124 - fail with column mismatch should have the proper diagnostics +ok 125 - fail with subtle column mismatch should fail +ok 126 - fail with subtle column mismatch should have the proper description +ok 127 - fail with subtle column mismatch should have the proper diagnostics +ok 128 - fail with different col counts should fail +ok 129 - fail with different col counts should have the proper description +ok 130 - fail with different col counts should have the proper diagnostics +ok 131 - simple results with cursors should pass +ok 132 - simple results with cursors should have the proper description +ok 133 - simple results with cursors should have the proper diagnostics +ok 134 - results_eq(cursor, prepared) should pass +ok 135 - results_eq(cursor, prepared) should have the proper description +ok 136 - results_eq(cursor, prepared) should have the proper diagnostics +ok 137 - results_eq(prepared, cursor) should pass +ok 138 - results_eq(prepared, cursor) should have the proper description +ok 139 - results_eq(prepared, cursor) should have the proper diagnostics +ok 140 - results_eq(cursor, sql) should pass +ok 141 - results_eq(cursor, sql) should have the proper description +ok 142 - results_eq(cursor, sql) should have the proper diagnostics +ok 143 - results_eq(sql, cursor) should pass +ok 144 - results_eq(sql, cursor) should have the proper description +ok 145 - results_eq(sql, cursor) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 3457699da980..b1ea38b4df8f 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -5804,9 +5804,9 @@ BEGIN EXECUTE 'DROP TABLE __taphave__'; EXECUTE 'DROP TABLE __tapwant__'; IF SQLSTATE = '42601' THEN - RETURN ok(FALSE, $3) || E'\n Queries do not have same number of columns'; + RETURN ok(FALSE, $3) || E'\n Number of columns differs between queries'; ELSIF SQLSTATE = '42804' THEN - RETURN ok(FALSE, $3) || E'\n Column types do not match between the queries'; + RETURN ok(FALSE, $3) || E'\n Column types differ between queries'; ELSE -- This should not happen. RAISE EXCEPTION '%', SQLERRM; @@ -5872,7 +5872,7 @@ BEGIN FETCH have INTO rec_have; FETCH want INTO rec_want; WHILE rec_have IS NOT NULL OR rec_want IS NOT NULL LOOP - IF rec_have <> rec_want THEN + IF rec_have IS DISTINCT FROM rec_want THEN RETURN ok( false, $3 ) || E'\n' || diag( ' Results differ beginning at row ' || rownum || E':\n' || ' have: ' || CASE WHEN rec_have IS NULL THEN '()' ELSE rec_have::text END || E'\n' || @@ -5885,6 +5885,13 @@ BEGIN END LOOP; RETURN ok( true, $3 ); +EXCEPTION + WHEN datatype_mismatch THEN + RETURN ok( false, $3 ) || E'\n' || diag( + E' Column types differ between queries:\n' + ' have: ' || CASE WHEN rec_have IS NULL THEN '()' ELSE rec_have::text END || E'\n' || + ' want: ' || CASE WHEN rec_want IS NULL THEN '()' ELSE rec_want::text END + ); END; $$ LANGUAGE plpgsql; @@ -5898,12 +5905,16 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION results_eq( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE - have REFCURSOR; - want REFCURSOR; + have REFCURSOR; + want REFCURSOR; + res TEXT; BEGIN OPEN have FOR EXECUTE _query($1); OPEN want FOR EXECUTE _query($2); - RETURN results_eq(have, want, $3); + res := results_eq(have, want, $3); + CLOSE have; + CLOSE want; + RETURN res; END; $$ LANGUAGE plpgsql; @@ -5913,4 +5924,43 @@ 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; diff --git a/sql/resultset.sql b/sql/resultset.sql index 5535043b3c41..d86a18b17836 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(118); +SELECT plan(145); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -394,7 +394,7 @@ SELECT * FROM check_test( false, 'fail with column mismatch', '', - ' Column types do not match between the queries' + ' Column types differ between queries' ); -- Handle falure due to column count mismatch. @@ -403,10 +403,9 @@ SELECT * FROM check_test( false, 'fail with different col counts', '', - ' Queries do not have same number of columns' + ' Number of columns differs between queries' ); - /****************************************************************************/ -- Now test bag_eq(). @@ -550,7 +549,7 @@ SELECT * FROM check_test( false, 'fail with column mismatch', '', - ' Column types do not match between the queries' + ' Column types differ between queries' ); -- Handle falure due to column count mismatch. @@ -559,7 +558,7 @@ SELECT * FROM check_test( false, 'fail with different col counts', '', - ' Queries do not have same number of columns' + ' Number of columns differs between queries' ); -- Handle failure due to missing dupe. @@ -632,6 +631,18 @@ SELECT * FROM check_test( '' ); +-- Compare with nulls. +SELECT * FROM check_test( + results_eq( + 'VALUES (4, NULL), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (4, NULL), (86, ''Angelina''), (1, ''Anna'')' + ), + true, + 'results test with nulls', + '', + '' +); + -- And now some failures. SELECT * FROM check_test( results_eq( @@ -689,6 +700,57 @@ SELECT * FROM check_test( want: ()' ); +-- Handle failure with null. +SELECT * FROM check_test( + results_eq( + 'VALUES (1, NULL), (86, ''Angelina'')', + 'VALUES (1, ''Anna''), (86, ''Angelina'')' + ), + false, + 'fail with NULL', + '', + ' Results differ beginning at row 1: + have: (1,) + want: (1,Anna)' +); + + +-- Handle falure due to column mismatch. +SELECT * FROM check_test( + results_eq( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'fail with column mismatch', + '', + ' Column types differ between queries: + have: (1,foo) + want: (foo,1)' +); + +-- Handle falure due to more subtle column mismatch. +SELECT * FROM check_test( + results_eq( + 'VALUES (1, ''foo''::varchar), (2, ''bar''::varchar)', + 'VALUES (1, ''foo''), (2, ''bar'')' + ), + false, + 'fail with subtle column mismatch', + '', + ' Column types differ between queries: + have: (1,foo) + want: (1,foo)' +); + +-- Handle falure due to column count mismatch. +SELECT * FROM check_test( + results_eq( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'fail with different col counts', + '', + ' Column types differ between queries: + have: (1) + want: (foo,1)' +); + -- Compare with cursors. DECLARE cwant CURSOR FOR SELECT id, name FROM names WHERE name like 'An%' ORDER BY id; DECLARE chave CURSOR FOR SELECT id, name from annames ORDER BY id; @@ -701,6 +763,45 @@ SELECT * FROM check_test( '' ); +-- Mix cursors and prepared statements +PREPARE annames_ord AS SELECT id, name FROM annames ORDER BY id; +MOVE BACKWARD ALL IN cwant; + +SELECT * FROM check_test( + results_eq( 'cwant'::refcursor, 'annames_ord' ), + true, + 'results_eq(cursor, prepared)', + '', + '' +); + +MOVE BACKWARD ALL IN chave; +SELECT * FROM check_test( + results_eq( 'annames_ord', 'chave'::refcursor ), + true, + 'results_eq(prepared, cursor)', + '', + '' +); + +-- Mix cursor and SQL. +MOVE BACKWARD ALL IN cwant; +SELECT * FROM check_test( + results_eq( 'cwant'::refcursor, 'SELECT id, name FROM annames ORDER BY id' ), + true, + 'results_eq(cursor, sql)', + '', + '' +); + +MOVE BACKWARD ALL IN chave; +SELECT * FROM check_test( + results_eq( 'SELECT id, name FROM annames ORDER BY id', 'chave'::refcursor ), + true, + 'results_eq(sql, cursor)', + '', + '' +); /****************************************************************************/ -- Finish the tests and clean up. From b37e2d1f84ab8ff964429fa9b160bae1e8795143 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 30 Jun 2009 14:59:47 -0700 Subject: [PATCH 0400/1195] Make diagnostics more useful for column mismatches in `set_eq()` and `bag_eq()`. --- README.pgtap | 1 - pgtap.sql.in | 49 +++++++++++++++++++++++++++++------------------ sql/resultset.sql | 22 ++++++++++++++------- 3 files changed, 45 insertions(+), 27 deletions(-) diff --git a/README.pgtap b/README.pgtap index fcc94c88a0ca..f5ed9c7fb1f8 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3458,7 +3458,6 @@ To Do ----- * Document `set_eq()` and `bag_eq()`. * Support prepared statement names in throws_ok, lives_ok, and performs_ok. -* Get more diagnostics for data type and column type failures in set_eq(). * Useful schema testing functions to consider adding: + `throws_like()` + `sequence_has_range()` diff --git a/pgtap.sql.in b/pgtap.sql.in index b1ea38b4df8f..131963a687f4 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -5772,6 +5772,20 @@ BEGIN 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, pg_catalog.pg_class c + WHERE a.attrelid = c.oid + AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END + AND c.relname = $1 + AND c.relistemp + AND attnum > 0 + ORDER BY attnum + ), ','); +$$ LANGUAGE sql; + CREATE OR REPLACE FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -5785,32 +5799,29 @@ DECLARE BEGIN BEGIN -- Find extra records. - FOR rec in EXECUTE 'SELECT * FROM __taphave__ EXCEPT ' || $4 - || 'SELECT * FROM __tapwant__' LOOP + 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 __tapwant__ EXCEPT ' || $4 - || 'SELECT * FROM __taphave__' LOOP + 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 __taphave__'; - EXECUTE 'DROP TABLE __tapwant__'; - EXCEPTION WHEN others THEN - -- The failure likely happend before the dropping of tables. - EXECUTE 'DROP TABLE __taphave__'; - EXECUTE 'DROP TABLE __tapwant__'; - IF SQLSTATE = '42601' THEN - RETURN ok(FALSE, $3) || E'\n Number of columns differs between queries'; - ELSIF SQLSTATE = '42804' THEN - RETURN ok(FALSE, $3) || E'\n Column types differ between queries'; - ELSE - -- This should not happen. - RAISE EXCEPTION '%', SQLERRM; - END IF; + EXECUTE 'DROP TABLE ' || have; + EXECUTE 'DROP TABLE ' || want; + EXCEPTION WHEN syntax_error OR datatype_mismatch THEN + msg := diag( + E'\n 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? @@ -5888,7 +5899,7 @@ BEGIN EXCEPTION WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( - E' Column types differ between queries:\n' + E' Columns differ between queries:\n' ' have: ' || CASE WHEN rec_have IS NULL THEN '()' ELSE rec_have::text END || E'\n' || ' want: ' || CASE WHEN rec_want IS NULL THEN '()' ELSE rec_want::text END ); diff --git a/sql/resultset.sql b/sql/resultset.sql index d86a18b17836..62f646aff444 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -394,7 +394,9 @@ SELECT * FROM check_test( false, 'fail with column mismatch', '', - ' Column types differ between queries' + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' ); -- Handle falure due to column count mismatch. @@ -403,7 +405,9 @@ SELECT * FROM check_test( false, 'fail with different col counts', '', - ' Number of columns differs between queries' + ' Columns differ between queries: + have: (integer) + want: (text,integer)' ); /****************************************************************************/ @@ -549,7 +553,9 @@ SELECT * FROM check_test( false, 'fail with column mismatch', '', - ' Column types differ between queries' + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' ); -- Handle falure due to column count mismatch. @@ -558,7 +564,9 @@ SELECT * FROM check_test( false, 'fail with different col counts', '', - ' Number of columns differs between queries' + ' Columns differ between queries: + have: (integer) + want: (text,integer)' ); -- Handle failure due to missing dupe. @@ -721,7 +729,7 @@ SELECT * FROM check_test( false, 'fail with column mismatch', '', - ' Column types differ between queries: + ' Columns differ between queries: have: (1,foo) want: (foo,1)' ); @@ -735,7 +743,7 @@ SELECT * FROM check_test( false, 'fail with subtle column mismatch', '', - ' Column types differ between queries: + ' Columns differ between queries: have: (1,foo) want: (1,foo)' ); @@ -746,7 +754,7 @@ SELECT * FROM check_test( false, 'fail with different col counts', '', - ' Column types differ between queries: + ' Columns differ between queries: have: (1) want: (foo,1)' ); From 4c058a1d9d54556a561bb50cdfb8f3485564a350 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 30 Jun 2009 16:47:06 -0700 Subject: [PATCH 0401/1195] Broader support for prepared statement name arguments; more documentation. * Added support for prepared statement names in `throws_ok()`, `lives_ok()`, and `performs_ok()`. * Moved documentation for sql query arguments, including support for prepared statement names, to a new top-level header, "Submit Your Query" (better name requested!), which encapsulates the above three functions as well as the resultset-comparing functions. * Renamed the "query" argument to `throws_ok()`, `lives_ok()`, and `performs_ok()` functions to "sql". * Completed the documentation for the result set testing functions. * Eliminated bogus extra newline in the diagnostics from `set_eq()` and `bag_eq()`. --- README.pgtap | 189 ++++++++++++++++++++++++++++++------------ expected/perform.out | 26 +++--- expected/throwtap.out | 32 ++++--- pgtap.sql.in | 40 ++++----- sql/perform.sql | 19 ++++- sql/resultset.sql | 8 +- sql/throwtap.sql | 37 ++++++++- 7 files changed, 249 insertions(+), 102 deletions(-) diff --git a/README.pgtap b/README.pgtap index f5ed9c7fb1f8..51542c4a92d1 100644 --- a/README.pgtap +++ b/README.pgtap @@ -534,6 +534,49 @@ you've got some complicated condition that is difficult to wedge into an Use these functions very, very, very sparingly. +Submit Your Query +================= + +Sometimes, you've just gotta run tests on a query. I mean a full blown query, +not just the scalare type assertion functions we've seen so far. pgTAP provides +a number of functions to help you test your queries, each of which takes one +or two SQL statements as arguments. Yes, as strings: + + SELECT throws_ok('SELECT divide_by(0)'); + +Of course, you'll often need to do something complex in your SQL, and quoting +SQL in strings in what is, after all, an SQL application, is an unnecessary +PITA. Each of the query-executing functions in this section thus support an +alternative to make your tests more SQLish: using prepared statements. + +Prepared statements allow you to just write SQL and simply pass the prepared +statement names. For example, the above example can be rewritten as: + + PREPARE mythrow AS SELECT divide_by(0); + SELECT throws_ok('mythrow'); + +pgTAP assumes that an SQL argument with no space or starting with a double +quote character is a prepared statement and simply `EXECUTE`s it. If you need +to pass arguments to a prepared statement, perhaps because you plan to use it +in multiple tests to return different values, just include `EXECUTE` in the +SQL string. Here's an example with a prepared statement with a space in its +name, and one where arguments need to be passed: + + PREPARE "my test" AS SELECT * FROM active_users() WHERE name LIKE 'A%'; + PREPARE expect AS SELECT * FROM users WHERE active = $1 AND name LIKE $2; + + SELECT results_eq( + '"my test"', + 'EXECUTE expect( true, ''A%'' )' + ); + +Since "my test" was declared with double quotes, it must be passed with double +quotes. And since the call to "expect" included spaces (to keep it legible), +the `EXECUTE` keyword was required. + +So keeping these techniques in mind, read on for all of the query-testing +goodness. + To Error is Human ----------------- @@ -542,15 +585,16 @@ Or maybe you want to make sure a query *does not* trigger an error. For such cases, we provide a couple of test functions to make sure your queries are as error-prone as you think they should be. -### `throws_ok( query, errcode, errmsg, description )` ### -### `throws_ok( query, errcode, errmsg )` ### -### `throws_ok( query, errmsg, description )` ### -### `throws_ok( query, errcode )` ### -### `throws_ok( query, errmsg )` ### -### `throws_ok( query )` ### +### `throws_ok( sql, errcode, errmsg, description )` ### +### `throws_ok( sql, errcode, errmsg )` ### +### `throws_ok( sql, errmsg, description )` ### +### `throws_ok( sql, errcode )` ### +### `throws_ok( sql, errmsg )` ### +### `throws_ok( sql )` ### + PREPARE my_thrower AS INSERT INTO try (id) VALUES (1); SELECT throws_ok( - 'INSERT INTO try (id) VALUES (1)', + 'my_thrower', '23505', 'duplicate key value violates unique constraint "try_pkey"', 'We should get a unique violation for a duplicate PK' @@ -560,9 +604,10 @@ When you want to make sure that an exception is thrown by PostgreSQL under certain circumstances, use `throws_ok()` to test for those circumstances. Supported by 8.1 or higher. -The first argument should be a string representing the query to be executed. -`throws_ok()` will use the PL/pgSQL `EXECUTE` statement to execute it and -catch any exception. +The first argument should be the name of a prepared statement or else a string +representing the query to be executed (see the [summary](#Can+You+Relate?) for +query argument details). `throws_ok()` will use the PL/pgSQL `EXECUTE` +statement to execute it and catch any exception. The second argument should be an exception error code, which is a five-character string (if it happens to consist only of numbers and you pass @@ -604,9 +649,11 @@ Idea borrowed from the Test::Exception Perl module. 'We should not get a unique violation for a new PK' ); -The inverse of `throws_ok()`, these functions test to ensure that a SQL -statement does *not* throw an exception. -Supported by 8.1 or higher. +The inverse of `throws_ok()`, these functions test to ensure that an SQL +statement does *not* throw an exception. Supported by 8.1 or higher. Pass in +the name of a prepared statement or string of SQL code (see the +[summary](#Can+You+Relate?) for query argument details). The optional second +argument is the test description. Should a `lives_ok()` test fail, it produces appropriate diagnostic messages. For example: @@ -619,8 +666,9 @@ Idea borrowed from the Test::Exception Perl module. ### `performs_ok ( sql, milliseconds, description )` ### ### `performs_ok ( sql, milliseconds )` ### + PREPARE fast_query AS SELECT id FROM try WHERE name = 'Larry'; SELECT performs_ok( - 'SELECT id FROM try WHERE name = ''Larry''', + 'fast_query', 250, 'A select by name should be fast' ); @@ -629,9 +677,10 @@ This function makes sure that an SQL statement performs well. It does so by timing its execution, and failing if execution takes longer than the specified amount of time. -The first argument should be a string representing the query to be executed. -`throws_ok()` will use the PL/pgSQL `EXECUTE` statement to execute it and -catch any exception. +The first argument should be the name of a prepared statement or a string +representing the query to be executed (see the [summary](#Can+You+Relate?) for +query argument details). `throws_ok()` will use the PL/pgSQL `EXECUTE` +statement to execute it and catch any exception. The second argument is the maximum number of milliseconds it should take for the SQL statement to execute. This argument is numeric, so you can even use @@ -681,8 +730,9 @@ There are three ways to test result sets in pgTAP. The most intuitive is to do a direct row-by-row comparison of results to ensure that they are exactly what you expect, in the order you expect. Coincidentally, this is exactly how `results_eq()` behaves. Here's how you use it: simply pass in two SQL -statements as strings and an optional description. Yep, that's it. It will do -the rest. +statements or prepared statement names (or some combination; (see the +[summary](#Can+You+Relate?) for query argument details) and an optional +description. Yep, that's it. It will do the rest. For example, say that you have a function, `active_users()`, that returns a set of rows from the users table. To make sure that it returns the rows you @@ -703,39 +753,12 @@ instead of a query, like so: 'active_users() should return active users' ); -Of course, you might often need to do something more complex in your SQL, and -quoting SQL in strings in what is, after all, a SQL application, is an -unnecessary PITA. There are two ways to make your use of `results_eq()` feel -more SQLish. - -The first is to use prepared statements. This allows you to just write SQL and -simply pass the prepared statement names to `results_eq()`, as in th example -at beginning of this section. pgTAP assumes that a SQL statement argument with -no space or starting with a double quote character is a prepared statement and -simply `EXECUTE`s it. - -If you need to pass arguments to a prepared statement, perhaps because you -plan to use it in multiple tests to return different values, just include -`EXECUTE` in the SQL string. Here's an example with a prepared statement with -a space in its name, and one where arguments need to be passed: - - PREPARE "my test" AS SELECT * FROM active_users() WHERE name LIKE 'A%'; - PREPARE expect AS SELECT * FROM users WHERE active = $1 AND name LIKE $2; - - SELECT results_eq( - '"my test"', - 'EXECUTE expect( true, ''A%'' )', - 'See about those "A" users' - ); - -Since the "my test" was declared with double quotes, it therefore must be -passed with double quotes. And since the call to the "expect" prepared -statement included spaces (to keep it legible), the `EXECUTE` keyword was -required. +In general, the use of prepared statements is highly recommended to keep your +test code SQLish (you can even use `VALUES` statements in prepared statements!): Internally, `results_eq()` turns your SQL statements into cursors so that it can iterate over them one row at a time. Conveniently, this behavior is -directly available to you, as well. Rather than pass in some arbitrary SQL +directly available to you, too. Rather than pass in some arbitrary SQL statement or the name of a prepared statement, simply create a cursor and just pass *it* in, like so: @@ -749,10 +772,10 @@ pass *it* in, like so: ); The key here is to ensure that the cursor names are passed as `refcursor`s. -This allows `results_eq()` to disambiguate the arguments from prepared -statements. And of course, you can mix and match cursors, prepared statements, -and SQL as much as you like. Here's an example using a prepared statement and -a (rewound) cursor for the expected results: +This allows `results_eq()` to disambiguate them from prepared statements. And +of course, you can mix and match cursors, prepared statements, and SQL as much +as you like. Here's an example using a prepared statement and a (reset) cursor +for the expected results: PREPARE users_test AS SELECT * FROM active_users(); MOVE BACKWARD ALL IN chave; @@ -788,6 +811,64 @@ different data types, you'll get diagnostics like so: # have: (1) # want: (foo,1) + +### `set_eq( sql, sql, description )` ### +### `set_eq( sql, sql )` ### + + PREPARE testq AS SELECT * FROM users('a%'); + PREPARE expect AS SELECT * FROM USERS where name LIKE 'a%'; + SELECT set_eq( 'testq', 'expect', 'gotta have the A listers' ); + +Sometimes you don't care what order query results are in, or if there are +duplicates. In those cases, use `set_eq()` to do a simple set comparison of +your result sets. As long as both queries return the same records, regardless +of duplicates or ordering, a `set_eq()` test will pass. + +The SQL arguments can be the names of prepared statements or strings +containing an SQL query (see the [summary](#Can+You+Relate?) for query +argument details), or even one of each. In whatever case, a failing test +will yield useful diagnostics, such as: + + # Failed test 146 + # Extra records: + # (87,Jackson) + # (1,Jacob) + # Missing records: + # (44,Anna) + # (86,Angelina) + +In the event that you somehow pass queries that return rows with different +types of columns, pgTAP will tell you that, too: + + # Failed test 147 + # Columns differ between queries: + # have: (integer,text) + # want: (text,integer) + +This of course extends to sets with different numbers of columns: + + # Failed test 148 + # Columns differ between queries: + # have: (integer) + # want: (text,integer) + +### `bag_eq( sql, sql, description )` ### +### `bag_eq( sql, sql )` ### + + PREPARE testq AS SELECT * FROM users('a%'); + PREPARE expect AS SELECT * FROM USERS where name LIKE 'a%'; + SELECT bag_eq( 'testq', 'expect', 'gotta have the A listers' ); + +The `bag_eq()` function is just like `set_eq()`, except that it considers the +results as bags rather than as sets. A bag is a set with duplicates. What this +means, effectively, is that you can use `bag_eq()` to test result sets where +order doesn't matter, but duplication does. In other words if a two rows are +the same in the first result set, the same row must appear twice in the second +result set. + +Otherwise, this function behaves exactly like `set_eq()`, including the +utility of its diagnostics. + The Schema Things ================= @@ -3456,8 +3537,6 @@ functions do not work under 8.0. Don't even use them there. To Do ----- -* Document `set_eq()` and `bag_eq()`. -* Support prepared statement names in throws_ok, lives_ok, and performs_ok. * Useful schema testing functions to consider adding: + `throws_like()` + `sequence_has_range()` diff --git a/expected/perform.out b/expected/perform.out index c3072b14d6c9..25f9e68e1812 100644 --- a/expected/perform.out +++ b/expected/perform.out @@ -1,5 +1,5 @@ \unset ECHO -1..18 +1..24 ok 1 - simple select should pass ok 2 - simple select should have the proper description ok 3 - simple select should have the proper diagnostics @@ -9,12 +9,18 @@ ok 6 - simple select no desc should have the proper diagnostics ok 7 - simple select numeric should pass ok 8 - simple select numeric should have the proper description ok 9 - simple select numeric should have the proper diagnostics -ok 10 - simple select fail should fail -ok 11 - simple select fail should have the proper description -ok 12 - simple select fail should have the proper diagnostics -ok 13 - simple select no desc fail should fail -ok 14 - simple select no desc fail should have the proper description -ok 15 - simple select no desc fail should have the proper diagnostics -ok 16 - simple select no desc numeric fail should fail -ok 17 - simple select no desc numeric fail should have the proper description -ok 18 - simple select no desc numeric fail should have the proper diagnostics +ok 10 - simple prepare should pass +ok 11 - simple prepare should have the proper description +ok 12 - simple prepare should have the proper diagnostics +ok 13 - simple execute should pass +ok 14 - simple execute should have the proper description +ok 15 - simple execute should have the proper diagnostics +ok 16 - simple select fail should fail +ok 17 - simple select fail should have the proper description +ok 18 - simple select fail should have the proper diagnostics +ok 19 - simple select no desc fail should fail +ok 20 - simple select no desc fail should have the proper description +ok 21 - simple select no desc fail should have the proper diagnostics +ok 22 - simple select no desc numeric fail should fail +ok 23 - simple select no desc numeric fail should have the proper description +ok 24 - simple select no desc numeric fail should have the proper diagnostics diff --git a/expected/throwtap.out b/expected/throwtap.out index f9aab366f45c..c38035aa32c5 100644 --- a/expected/throwtap.out +++ b/expected/throwtap.out @@ -1,5 +1,5 @@ \unset ECHO -1..28 +1..36 ok 1 - four-argument form should pass ok 2 - four-argument form should have the proper description ok 3 - four-argument form should have the proper diagnostics @@ -17,14 +17,22 @@ ok 14 - two-argument errmsg should have the proper diagnostics ok 15 - single-argument form should pass ok 16 - single-argument form should have the proper description ok 17 - single-argument form should have the proper diagnostics -ok 18 - invalid errcode should fail -ok 19 - invalid errcode should have the proper description -ok 20 - invalid errcode should have the proper diagnostics -ok 21 - throws_ok(1/0, NULL) should work -ok 22 - throws_ok diagnostics should fail -ok 23 - throws_ok diagnostics should have the proper description -ok 24 - throws_ok diagnostics should have the proper diagnostics -ok 25 - lives_ok() should work -ok 26 - lives_ok failure diagnostics should fail -ok 27 - lives_ok failure diagnostics should have the proper description -ok 28 - lives_ok failure diagnostics should have the proper diagnostics +ok 18 - prepared statement & errcode should pass +ok 19 - prepared statement & errcode should have the proper description +ok 20 - execute & errcode should pass +ok 21 - execute & errcode should have the proper description +ok 22 - invalid errcode should fail +ok 23 - invalid errcode should have the proper description +ok 24 - invalid errcode should have the proper diagnostics +ok 25 - throws_ok(1/0, NULL) should work +ok 26 - throws_ok diagnostics should fail +ok 27 - throws_ok diagnostics should have the proper description +ok 28 - throws_ok diagnostics should have the proper diagnostics +ok 29 - lives_ok() should work +ok 30 - lives_ok(prepared) should pass +ok 31 - lives_ok(prepared) should have the proper description +ok 32 - lives_ok(execute) should pass +ok 33 - lives_ok(execute) should have the proper description +ok 34 - lives_ok failure diagnostics should fail +ok 35 - lives_ok failure diagnostics should have the proper description +ok 36 - lives_ok failure diagnostics should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 131963a687f4..9eab1a3ae4ba 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -656,11 +656,19 @@ 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 ALIAS FOR $1; + query TEXT := _query($1); errcode ALIAS FOR $2; errmsg ALIAS FOR $3; desctext ALIAS FOR $4; @@ -695,8 +703,8 @@ EXCEPTION WHEN OTHERS THEN END; $$ LANGUAGE plpgsql; --- throws_ok ( query, errcode, errmsg ) --- throws_ok ( query, errmsg, description ) +-- throws_ok ( sql, errcode, errmsg ) +-- throws_ok ( sql, errmsg, description ) CREATE OR REPLACE FUNCTION throws_ok ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ BEGIN @@ -721,35 +729,36 @@ BEGIN END; $$ LANGUAGE plpgsql; --- throws_ok ( query ) +-- 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 ( query, errcode, errmsg, description ) +-- 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 ( query, errcode, errmsg ) +-- 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 ( query, errcode ) +-- 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; +-- lives_ok( sql, description ) CREATE OR REPLACE FUNCTION lives_ok ( TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE - code ALIAS FOR $1; + code TEXT := _query($1); descr ALIAS FOR $2; BEGIN EXECUTE code; @@ -762,6 +771,7 @@ EXCEPTION WHEN OTHERS THEN END; $$ LANGUAGE plpgsql; +-- lives_ok( sql ) CREATE OR REPLACE FUNCTION lives_ok ( TEXT ) RETURNS TEXT AS $$ SELECT lives_ok( $1, NULL ); @@ -771,7 +781,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION performs_ok ( TEXT, NUMERIC, TEXT ) RETURNS TEXT AS $$ DECLARE - query ALIAS FOR $1; + query TEXT := _query($1); max_time ALIAS FOR $2; descr ALIAS FOR $3; starts_at TEXT; @@ -5756,14 +5766,6 @@ RETURNS SETOF TEXT AS $$ SELECT * FROM runtests( '^test' ); $$ 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; - CREATE OR REPLACE FUNCTION _temptable ( TEXT, TEXT ) RETURNS TEXT AS $$ BEGIN @@ -5814,8 +5816,8 @@ BEGIN EXECUTE 'DROP TABLE ' || have; EXECUTE 'DROP TABLE ' || want; EXCEPTION WHEN syntax_error OR datatype_mismatch THEN - msg := diag( - E'\n Columns differ between queries:\n' + msg := E'\n' || diag( + E' Columns differ between queries:\n' || ' have: (' || _temptypes(have) || E')\n' || ' want: (' || _temptypes(want) || ')' ); diff --git a/sql/perform.sql b/sql/perform.sql index 7570760137a4..914ff04b7a06 100644 --- a/sql/perform.sql +++ b/sql/perform.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(18); +SELECT plan(24); --SELECT * FROM no_plan(); /****************************************************************************/ @@ -30,6 +30,23 @@ SELECT * FROM check_test( '' ); +PREPARE mytest AS SELECT TRUE; +SELECT * FROM check_test( + performs_ok( 'mytest', 100 ), + true, + 'simple prepare', + 'Should run in less than 100 ms', + '' +); + +SELECT * FROM check_test( + performs_ok( 'EXECUTE mytest', 100 ), + true, + 'simple execute', + 'Should run in less than 100 ms', + '' +); + SELECT * FROM check_test( performs_ok( 'SELECT TRUE', 0, 'whatever' ), false, diff --git a/sql/resultset.sql b/sql/resultset.sql index 62f646aff444..f4483ac0006c 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -394,7 +394,7 @@ SELECT * FROM check_test( false, 'fail with column mismatch', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -405,7 +405,7 @@ SELECT * FROM check_test( false, 'fail with different col counts', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); @@ -553,7 +553,7 @@ SELECT * FROM check_test( false, 'fail with column mismatch', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -564,7 +564,7 @@ SELECT * FROM check_test( false, 'fail with different col counts', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); diff --git a/sql/throwtap.sql b/sql/throwtap.sql index db9f13eff4b1..0db145704f3d 100644 --- a/sql/throwtap.sql +++ b/sql/throwtap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(28); +SELECT plan(36); --SELECT * FROM no_plan(); /****************************************************************************/ @@ -54,6 +54,24 @@ SELECT * FROM check_test( '' ); +-- Try using a prepared statement. +PREPARE mytest AS SELECT * FROM todo_end(); +SELECT * FROM check_test( + throws_ok( 'mytest', 'P0001'), + true, + 'prepared statement & errcode', + 'threw P0001' + '' +); + +SELECT * FROM check_test( + throws_ok( 'EXECUTE mytest', 'P0001'), + true, + 'execute & errcode', + 'threw P0001' + '' +); + -- Check its diagnostics for an invalid error code. SELECT * FROM check_test( throws_ok( 'SELECT * FROM todo_end()', 97212 ), @@ -81,6 +99,23 @@ SELECT * FROM check_test( -- test lives_ok(). SELECT lives_ok( 'SELECT 1', 'lives_ok() should work' ); +PREPARE livetest AS SELECT 1; +SELECT * FROM check_test( + lives_ok( 'livetest'), + true, + 'lives_ok(prepared)' + '', + '' +); + +SELECT * FROM check_test( + lives_ok( 'EXECUTE livetest'), + true, + 'lives_ok(execute)' + '', + '' +); + -- Check its diagnostics when there is an exception. SELECT * FROM check_test( lives_ok( 'SELECT * FROM todo_end()' ), From a2c9671a2b561b34a9ce1097405700bd4637f0a3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 30 Jun 2009 16:57:16 -0700 Subject: [PATCH 0402/1195] Fixed broken xref links. --- README.pgtap | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.pgtap b/README.pgtap index 51542c4a92d1..6d06ff05dbd7 100644 --- a/README.pgtap +++ b/README.pgtap @@ -605,7 +605,7 @@ certain circumstances, use `throws_ok()` to test for those circumstances. Supported by 8.1 or higher. The first argument should be the name of a prepared statement or else a string -representing the query to be executed (see the [summary](#Can+You+Relate?) for +representing the query to be executed (see the [summary](#Submit+Your+Query) for query argument details). `throws_ok()` will use the PL/pgSQL `EXECUTE` statement to execute it and catch any exception. @@ -652,7 +652,7 @@ Idea borrowed from the Test::Exception Perl module. The inverse of `throws_ok()`, these functions test to ensure that an SQL statement does *not* throw an exception. Supported by 8.1 or higher. Pass in the name of a prepared statement or string of SQL code (see the -[summary](#Can+You+Relate?) for query argument details). The optional second +[summary](#Submit+Your+Query) for query argument details). The optional second argument is the test description. Should a `lives_ok()` test fail, it produces appropriate diagnostic messages. @@ -678,7 +678,7 @@ timing its execution, and failing if execution takes longer than the specified amount of time. The first argument should be the name of a prepared statement or a string -representing the query to be executed (see the [summary](#Can+You+Relate?) for +representing the query to be executed (see the [summary](#Submit+Your+Query) for query argument details). `throws_ok()` will use the PL/pgSQL `EXECUTE` statement to execute it and catch any exception. @@ -731,7 +731,7 @@ a direct row-by-row comparison of results to ensure that they are exactly what you expect, in the order you expect. Coincidentally, this is exactly how `results_eq()` behaves. Here's how you use it: simply pass in two SQL statements or prepared statement names (or some combination; (see the -[summary](#Can+You+Relate?) for query argument details) and an optional +[summary](#Submit+Your+Query) for query argument details) and an optional description. Yep, that's it. It will do the rest. For example, say that you have a function, `active_users()`, that returns a @@ -825,7 +825,7 @@ your result sets. As long as both queries return the same records, regardless of duplicates or ordering, a `set_eq()` test will pass. The SQL arguments can be the names of prepared statements or strings -containing an SQL query (see the [summary](#Can+You+Relate?) for query +containing an SQL query (see the [summary](#Submit+Your+Query) for query argument details), or even one of each. In whatever case, a failing test will yield useful diagnostics, such as: From 2653a083b2c540cca1ca72d89c099d67c32fe488 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 30 Jun 2009 17:01:39 -0700 Subject: [PATCH 0403/1195] Noted other result testing functions to consider adding. --- README.pgtap | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.pgtap b/README.pgtap index 6d06ff05dbd7..09786427e819 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3543,6 +3543,11 @@ To Do + `sequence_increments_by()` + `sequence_starts_at()` + `sequence_cycles()` +* Useful result tsting functions to consider adding: + + `set_includes()` and `bag_includes()`. + + `set_excludes()` and `bag_excludes()`. + + `col_eq()` + + `row_eq()` Supported Versions ----------------- From d7f6cb48509b13db63c9da046f2835a8e8992794 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 1 Jul 2009 10:18:10 -0700 Subject: [PATCH 0404/1195] Changed some internal queries to use explicit `JOIN`s. --- Changes | 1 + README.pgtap | 1 + pgtap.sql.in | 40 ++++++++++++++++++++-------------------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Changes b/Changes index 5a6363aaedb2..7ac8799d269a 100644 --- a/Changes +++ b/Changes @@ -6,6 +6,7 @@ Revision history for pgTAP * Fixed failing test on 8.4rc2. * Added `results_eq(), `set_eq()`, and `bag_eq()`, functions to test query results. +* Changed some internal queries to use explicit `JOIN`s. 0.21 2009-05-29T00:04:31 ------------------------- diff --git a/README.pgtap b/README.pgtap index 09786427e819..f68c0d7ddf97 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3548,6 +3548,7 @@ To Do + `set_excludes()` and `bag_excludes()`. + `col_eq()` + `row_eq()` + + `rowtype_is()` Supported Versions ----------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index 9eab1a3ae4ba..8a6a0762154a 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1013,10 +1013,10 @@ BEGIN RETURN ok( EXISTS( SELECT true - FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a - WHERE n.oid = c.relnamespace - AND c.oid = a.attrelid - AND n.nspname = $1 + 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 @@ -1038,9 +1038,9 @@ BEGIN RETURN ok( EXISTS( SELECT true - FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a - WHERE c.oid = a.attrelid - AND pg_catalog.pg_table_is_visible(c.oid) + 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 @@ -1101,9 +1101,9 @@ BEGIN END IF; SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) into actual_type - FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c - WHERE a.attrelid = c.oid - AND pg_table_is_visible(c.oid) + FROM pg_catalog.pg_attribute a + JOIN pg_catalog.pg_class c ON a.attrelid = c.oid + WHERE pg_table_is_visible(c.oid) AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END AND c.relname = $2 AND attnum > 0 @@ -1115,14 +1115,14 @@ BEGIN END IF; SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) into actual_type - FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a - WHERE n.oid = c.relnamespace - AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END - AND c.oid = a.attrelid - AND n.nspname = $1 + 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 attnum > 0 - AND a.attname = $3; + AND a.attname = $3 + AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END; END IF; IF actual_type = LOWER($4) THEN @@ -5778,12 +5778,12 @@ 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, pg_catalog.pg_class c - WHERE a.attrelid = c.oid - AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END - AND c.relname = $1 + FROM pg_catalog.pg_attribute a + JOIN pg_catalog.pg_class c ON a.attrelid = c.oid + WHERE c.relname = $1 AND c.relistemp AND attnum > 0 + AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END ORDER BY attnum ), ','); $$ LANGUAGE sql; From 3c08c5d4eb45f364ff192448e18240327b3d08e7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 1 Jul 2009 11:43:58 -0700 Subject: [PATCH 0405/1195] Ported `results_eq()` to 8.3 and earlier, with caveats. --- Makefile | 1 + README.pgtap | 24 ++++++++++++++++----- compat/install-8.3.patch | 22 ++++++++++++++++++++ pgtap.sql.in | 2 +- sql/resultset.sql | 45 +++++++++++++++++++++++++++------------- 5 files changed, 74 insertions(+), 20 deletions(-) create mode 100644 compat/install-8.3.patch diff --git a/Makefile b/Makefile index 53cc77db727d..6d4b944d933c 100644 --- a/Makefile +++ b/Makefile @@ -142,6 +142,7 @@ else endif ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 4) + patch -p0 < compat/install-8.3.patch ifneq ($(PGVER_MINOR), 3) cat compat/install-8.2.sql >> pgtap.sql ifeq ($(PGVER_MINOR), 2) diff --git a/README.pgtap b/README.pgtap index f68c0d7ddf97..43a9b7dcb668 100644 --- a/README.pgtap +++ b/README.pgtap @@ -795,22 +795,28 @@ message to tell you at what row the results differ, something like: # have: (1,Anna) # want: (22,Betty) -If there are different numbers of rows in each result set, you'll see an empty -row in the comparison: +On PostgreSQL 8.4 or higher, if there are different numbers of rows in each +result set, you'll see an empty row in the comparison: # Failed test 147 # Results differ beginning at row 5: # have: (1,Anna) # want: () -If the number of columns varies between result sets, or if results are of -different data types, you'll get diagnostics like so: +Also on 8.4 or higher, if the number of columns varies between result sets, or +if results are of different data types, you'll get diagnostics like so: # Failed test 148 # Column types differ between queries: # have: (1) # want: (foo,1) +On PostgreSQL 8.3 and lower, the rows are cast to text for comparison, rather +than compared as `record` objects`, so the test cannot detect +incompatibilities in column numbers or types, or differences in columns that +convert to the same text representation. For example, a `NULL` column will be +equivalent to an empty string. The upshot: read failure diagnostics carefully! +and pay attention to data types on 8.3. ### `set_eq( sql, sql, description )` ### ### `set_eq( sql, sql )` ### @@ -3501,11 +3507,19 @@ To see the specifics for each version of PostgreSQL, consult the files in the The `pg_typeof()` function will not be built, as it is included in PostgreSQL 8.4. -8.3 and Higher +8.4 and Higher -------------- No changes. Everything should just work. +8.3 and Lower +------------- +A patch is applied to modify `results_eq()` to cast records to text before +comparing them. This means that things will mainly be correct, but it also +means that two queries with incopatible types that convert to the same text +string may be considered incorrectly equivalent. + + 8.2 and Lower ------------- diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch new file mode 100644 index 000000000000..6fd9540a66fb --- /dev/null +++ b/compat/install-8.3.patch @@ -0,0 +1,22 @@ +--- pgtap.sql.orig 2009-07-01 10:24:04.000000000 -0700 ++++ pgtap.sql 2009-07-01 10:35:45.000000000 -0700 +@@ -5780,8 +5780,9 @@ + 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 ++ JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid + WHERE c.relname = $1 +- AND c.relistemp ++ AND n.nspname LIKE 'pg_temp%' + AND attnum > 0 + AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END + ORDER BY attnum +@@ -5885,7 +5886,7 @@ + FETCH have INTO rec_have; + FETCH want INTO rec_want; + WHILE rec_have IS NOT NULL OR rec_want IS NOT NULL LOOP +- IF rec_have IS DISTINCT FROM rec_want THEN ++ IF rec_have::text IS DISTINCT FROM rec_want::text THEN + RETURN ok( false, $3 ) || E'\n' || diag( + ' Results differ beginning at row ' || rownum || E':\n' || + ' have: ' || CASE WHEN rec_have IS NULL THEN '()' ELSE rec_have::text END || E'\n' || diff --git a/pgtap.sql.in b/pgtap.sql.in index 8a6a0762154a..2c442d80636b 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -5901,7 +5901,7 @@ BEGIN EXCEPTION WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( - E' Columns differ between queries:\n' + E' Columns differ between queries:\n' || ' have: ' || CASE WHEN rec_have IS NULL THEN '()' ELSE rec_have::text END || E'\n' || ' want: ' || CASE WHEN rec_want IS NULL THEN '()' ELSE rec_want::text END ); diff --git a/sql/resultset.sql b/sql/resultset.sql index f4483ac0006c..a916f87c3d83 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -729,24 +729,41 @@ SELECT * FROM check_test( false, 'fail with column mismatch', '', - ' Columns differ between queries: + CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Columns differ between queries:' END || ' have: (1,foo) want: (foo,1)' ); --- Handle falure due to more subtle column mismatch. -SELECT * FROM check_test( - results_eq( - 'VALUES (1, ''foo''::varchar), (2, ''bar''::varchar)', - 'VALUES (1, ''foo''), (2, ''bar'')' - ), - false, - 'fail with subtle column mismatch', - '', - ' Columns differ between queries: +-- Handle falure due to more subtle column mismatch, valid only on 8.4. +CREATE OR REPLACE FUNCTION subtlefail() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() < 80400 THEN + -- 8.3 and earlier cast records to text, so subtlety is out. + RETURN NEXT pass('fail with subtle column mismatch should fail'); + RETURN NEXT pass('fail with subtle column mismatch should have the proper description'); + RETURN NEXT pass('fail with subtle column mismatch should have the proper diagnostics'); + ELSE + -- 8.4 does true record comparisions, yay! + FOR tap IN SELECT * FROM check_test( + results_eq( + 'VALUES (1, ''foo''::varchar), (2, ''bar''::varchar)', + 'VALUES (1, ''foo''), (2, ''bar'')' + ), + false, + 'fail with subtle column mismatch', + '', + ' Columns differ between queries: have: (1,foo) - want: (1,foo)' -); + want: (1,foo)' ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + END IF; + RETURN; +END; +$$ LANGUAGE plpgsql; +SELECT * FROM subtlefail(); -- Handle falure due to column count mismatch. SELECT * FROM check_test( @@ -754,7 +771,7 @@ SELECT * FROM check_test( false, 'fail with different col counts', '', - ' Columns differ between queries: + CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Columns differ between queries:' END || ' have: (1) want: (foo,1)' ); From 7d757ad012b9876378c8ebabb4b60cb6373f0f16 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 1 Jul 2009 14:50:58 -0700 Subject: [PATCH 0406/1195] Add notes about sort ordering to the `results_eq()` documentation. --- README.pgtap | 54 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/README.pgtap b/README.pgtap index 43a9b7dcb668..ba70e677895c 100644 --- a/README.pgtap +++ b/README.pgtap @@ -754,7 +754,41 @@ instead of a query, like so: ); In general, the use of prepared statements is highly recommended to keep your -test code SQLish (you can even use `VALUES` statements in prepared statements!): +test code SQLish (you can even use `VALUES` statements in prepared +statements!). But note that, because `results_eq()` does a row-by-row +comparision, the results of the two query arguments must be in exactly the +same order, with exactly the same data types, in order to pass. In practical +terms, it means that you must make sure that your results are never +unambiguously ordered. + +For example, say that you want to compare queries against a `persons` table. +The simplest way to sort is by `name`, as in: + + try=# select * from people order by name; + name | age + --------+----- + Damian | 19 + Larry | 53 + Tom | 44 + Tom | 35 + (4 rows) + +But a different run of the same query could have the rows in different order: + + try=# select * from people order by name; + name | age + --------+----- + Damian | 19 + Larry | 53 + Tom | 35 + Tom | 44 + (4 rows) + +Notice how the two "Tom" rows are reversed. The upshot is that you want to +make sure that your rows are always fully ordered, so that they always appear +in the same order. In a case like that, it means sorting on both the `name` +column and the `age` column. If the sort order of your results isn't +important, consider using `set_eq()` or `bag_eq()` instead. Internally, `results_eq()` turns your SQL statements into cursors so that it can iterate over them one row at a time. Conveniently, this behavior is @@ -771,10 +805,10 @@ pass *it* in, like so: 'Gotta have those active users!' ); -The key here is to ensure that the cursor names are passed as `refcursor`s. -This allows `results_eq()` to disambiguate them from prepared statements. And -of course, you can mix and match cursors, prepared statements, and SQL as much -as you like. Here's an example using a prepared statement and a (reset) cursor +The key is to ensure that the cursor names are passed as `refcursor`s. This +allows `results_eq()` to disambiguate them from prepared statements. And of +course, you can mix and match cursors, prepared statements, and SQL as much as +you like. Here's an example using a prepared statement and a (reset) cursor for the expected results: PREPARE users_test AS SELECT * FROM active_users(); @@ -812,11 +846,11 @@ if results are of different data types, you'll get diagnostics like so: # want: (foo,1) On PostgreSQL 8.3 and lower, the rows are cast to text for comparison, rather -than compared as `record` objects`, so the test cannot detect -incompatibilities in column numbers or types, or differences in columns that -convert to the same text representation. For example, a `NULL` column will be -equivalent to an empty string. The upshot: read failure diagnostics carefully! -and pay attention to data types on 8.3. +than compared as `record` objects, so the test cannot detect incompatibilities +in column numbers or types, or differences in columns that convert to the same +text representation. For example, a `NULL` column will be equivalent to an +empty string. The upshot: read failure diagnostics carefully and pay attention +to data types on 8.3. ### `set_eq( sql, sql, description )` ### ### `set_eq( sql, sql )` ### From 6d1fe80756fe8e7d6019b02ea280918835ed09f3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 2 Jul 2009 18:10:57 -0700 Subject: [PATCH 0407/1195] Aye kant spel, damitt. --- Changes | 4 +++- Makefile | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 7ac8799d269a..c3d607c90e05 100644 --- a/Changes +++ b/Changes @@ -7,6 +7,8 @@ Revision history for pgTAP * Added `results_eq(), `set_eq()`, and `bag_eq()`, functions to test query results. * Changed some internal queries to use explicit `JOIN`s. +* Fixed capitalization of `TAP::Harness` in the `Makefile`. Thanks to + Quinn Weaver. 0.21 2009-05-29T00:04:31 ------------------------- @@ -145,7 +147,7 @@ Revision history for pgTAP * Updated the `Makefile` to fix the shebang line in `pg_prove` if Perl is found, and to emit a warning if the `$PERL` variable is not set. - Updated the `Makefile` so that if `$PERL` is set and - Tap::Harness is not installed, it warns the user to install it in + TAP::Harness is not installed, it warns the user to install it in order to use `pg_prove`. - Updated the `Makefile` to set the location of the `psql` binary to the bin directory specified for PostgreSQL when it was built. diff --git a/Makefile b/Makefile index 6d4b944d933c..48f944bde8b1 100644 --- a/Makefile +++ b/Makefile @@ -41,9 +41,9 @@ ifndef PERL PERL := $(shell which foo) endif -# Is Tap::Harness installed? +# Is TAP::Harness installed? ifdef PERL -HAVE_HARNESS := $(shell $(PERL) -le 'eval { require Tap::Harness }; print 1 unless $$@' ) +HAVE_HARNESS := $(shell $(PERL) -le 'eval { require TAP::Harness }; print 1 unless $$@' ) endif # We support 8.0 and later. @@ -183,7 +183,7 @@ endif endif endif -# Build pg_prove and holler if there's no Perl or Tap::Harness. +# Build pg_prove and holler if there's no Perl or TAP::Harness. bbin/pg_prove: mkdir bbin # sed -e "s,\\('\\|F<\\)psql\\(['>]\\),\\1$(bindir)/psql\\2,g" bin/pg_prove > bbin/pg_prove @@ -191,7 +191,7 @@ bbin/pg_prove: ifdef PERL $(PERL) '-MExtUtils::MY' -e 'MY->fixin(shift)' bbin/pg_prove ifndef HAVE_HARNESS - $(warning To use pg_prove, Tap::Harness Perl module must be installed from CPAN.) + $(warning To use pg_prove, TAP::Harness Perl module must be installed from CPAN.) endif else $(warning Could not find perl (required by pg_prove). Install it or set the PERL variable.) From 3191bbe1f5e49c7366b608dbbdc9e03065a18a28 Mon Sep 17 00:00:00 2001 From: Quinn Weaver Date: Sat, 4 Jul 2009 06:53:06 +0800 Subject: [PATCH 0408/1195] Added a missing closing parenthesis, which was causing uninstall to fail. Signed-off-by: David E. Wheeler --- compat/uninstall-8.2.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compat/uninstall-8.2.sql b/compat/uninstall-8.2.sql index 8a2c7671d986..bf0bdb92ed21 100644 --- a/compat/uninstall-8.2.sql +++ b/compat/uninstall-8.2.sql @@ -8,5 +8,5 @@ DROP CAST (name[] AS text); DROP FUNCTION namearray_text(name[]); DROP CAST (text[] AS text); DROP FUNCTION textarray_text(text[]); -DROP CAST (boolean AS char(1); +DROP CAST (boolean AS char(1)); DROP FUNCTION booltext(boolean); From a4c52c222f990bbcc76879c5c76c751b947a0085 Mon Sep 17 00:00:00 2001 From: Quinn Weaver Date: Sat, 4 Jul 2009 07:33:30 +0800 Subject: [PATCH 0409/1195] Fixed a syntax error that affected uninstall_pgtap.sql on old pg versions Signed-off-by: David E. Wheeler --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index c3d607c90e05..2dff357953c9 100644 --- a/Changes +++ b/Changes @@ -9,6 +9,8 @@ Revision history for pgTAP * Changed some internal queries to use explicit `JOIN`s. * Fixed capitalization of `TAP::Harness` in the `Makefile`. Thanks to Quinn Weaver. +* Fixed a syntax error that caused uninstall_pgtap.sql to fail on older + PostgreSQL versions (fix by Quinn Weaver) 0.21 2009-05-29T00:04:31 ------------------------- From 5cbb65c65966a36a1354966f97970cf19384271a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 9 Jul 2009 20:43:50 -0700 Subject: [PATCH 0410/1195] Add set_has(), bag_has(), set_hasnt(), and bag_hasnt() Add the `set_has()`, `bag_has()`, `set_hasnt()`, and `bag_hasnt()` functions to test subsets of query results. Also eliminated some trailing whitespace and updated `uninstal_pgtap.sql.in`. --- Changes | 4 +- README.pgtap | 86 +++++++- expected/resultset.out | 131 +++++++++++- pgtap.sql.in | 117 +++++++++-- sql/resultset.sql | 458 ++++++++++++++++++++++++++++++++++++++++- uninstall_pgtap.sql.in | 16 +- 6 files changed, 790 insertions(+), 22 deletions(-) diff --git a/Changes b/Changes index 2dff357953c9..9735f6ded0b5 100644 --- a/Changes +++ b/Changes @@ -4,8 +4,8 @@ Revision history for pgTAP 0.22 ------------------------- * Fixed failing test on 8.4rc2. -* Added `results_eq(), `set_eq()`, and `bag_eq()`, functions to test query - results. +* Added `results_eq(), `set_eq()`, `bag_eq()`, `set_has()`, `bag_has()`, + `set_hasnt()`, and `bag_hasnt()` functions to test query results. * Changed some internal queries to use explicit `JOIN`s. * Fixed capitalization of `TAP::Harness` in the `Makefile`. Thanks to Quinn Weaver. diff --git a/README.pgtap b/README.pgtap index ba70e677895c..fe0b5ee79c3b 100644 --- a/README.pgtap +++ b/README.pgtap @@ -902,13 +902,95 @@ This of course extends to sets with different numbers of columns: The `bag_eq()` function is just like `set_eq()`, except that it considers the results as bags rather than as sets. A bag is a set with duplicates. What this means, effectively, is that you can use `bag_eq()` to test result sets where -order doesn't matter, but duplication does. In other words if a two rows are +order doesn't matter, but duplication does. In other words, if a two rows are the same in the first result set, the same row must appear twice in the second result set. Otherwise, this function behaves exactly like `set_eq()`, including the utility of its diagnostics. +### `set_has( sql, sql, description )` ### +### `set_has( sql, sql )` ### + + PREPARE testq AS SELECT * FROM users('a%'); + PREPARE subset AS SELECT * FROM USERS where name LIKE 'a%'; + SELECT set_has( 'testq', 'subset', 'gotta have at least the A listers' ); + +When you need to test that a query returns at least some subset of records, +`set_has()` is the hammer you're looking for. It tests that the the results of +a query contain at least the results returned by another query, if not more. +That is, the test passes if the second query's results are a subset of the +first query's results. The second query can even return an empty set, in which +case the test will pass no matter what the first query returns. Not very +useful perhaps, but set-theoretically correct. + +As with `set_eq()`. the SQL arguments can be the names of prepared statements +or strings containing an SQL query (see the [summary](#Submit+Your+Query) for +query argument details), or one of each. In whatever case, a failing test will +yield useful diagnostics just like: + + # Failed test 122 + # Missing records: + # (44,Anna) + # (86,Angelina) + +As with `set_eq()`, it will also provide useful diagnostics when the queries +return incompatible columns. Internally, it uses an `EXCEPT` query to +determine if there any any unexpectedly missing results. + +### `bag_has( sql, sql, description )` ### +### `bag_has( sql, sql )` ### + + PREPARE testq AS SELECT * FROM users('a%'); + PREPARE subset AS SELECT * FROM USERS where name LIKE 'a%'; + SELECT bag_has( 'testq', 'subset', 'gotta have at least the A listers' ); + +The `bag_has()` function is just like `set_has()`, except that it considers +the results as bags rather than as sets. A bag is a set with duplicates. What +this means, effectively, is that you can use `bag_has()` to test result sets +where order doesn't matter, but duplication does. Internally, it uses an +`EXCEPT ALL` query to determine if there any any unexpectedly missing results. + +### `set_hasnt( sql, sql, description )` ### +### `set_hasnt( sql, sql )` ### + + PREPARE testq AS SELECT * FROM users('a%'); + PREPARE exclude AS SELECT * FROM USERS where name LIKE 'b%'; + SELECT set_has( 'testq', 'exclude', 'Must not have the Bs' ); + +This test function is the inverse of `set_has()`: the test passes when the +results of the first query have none of the results of the second query. +Diagnostics are similarly useful: + + # Failed test 198 + # Extra records: + # (44,Anna) + # (86,Angelina) + +Internally, the function uses an `INTERSECT` query to determine if there is +any unexpected overlap between the query results. + +### `bag_hasnt( sql, sql, description )` ### +### `bag_hasnt( sql, sql )` ### + + PREPARE testq AS SELECT * FROM users('a%'); + PREPARE exclude AS SELECT * FROM USERS where name LIKE 'b%'; + SELECT bag_has( 'testq', 'exclude', 'Must not have the Bs' ); + +This test function is the inverse of `bag_hasnt()`: the test passes when the +results of the first query have none of the results of the second query. +Diagnostics are similarly useful: + + # Failed test 198 + # Extra records: + # (44,Anna) + # (86,Angelina) + +Internally, the function uses an `INTERSECT ALL` query to determine if there +is any unexpected overlap between the query results. This means that a +duplicate row in the first query will appear twice in the diagnostics if it is +also duplicated in the second query. + The Schema Things ================= @@ -3592,8 +3674,6 @@ To Do + `sequence_starts_at()` + `sequence_cycles()` * Useful result tsting functions to consider adding: - + `set_includes()` and `bag_includes()`. - + `set_excludes()` and `bag_excludes()`. + `col_eq()` + `row_eq()` + `rowtype_is()` diff --git a/expected/resultset.out b/expected/resultset.out index b9bbf55845db..6682864ffd38 100644 --- a/expected/resultset.out +++ b/expected/resultset.out @@ -1,5 +1,5 @@ \unset ECHO -1..145 +1..274 ok 1 - Should create temp table with simple query ok 2 - Table __foonames__ should exist ok 3 - Should create a temp table for a prepared statement @@ -145,3 +145,132 @@ ok 142 - results_eq(cursor, sql) should have the proper diagnostics ok 143 - results_eq(sql, cursor) should pass ok 144 - results_eq(sql, cursor) should have the proper description ok 145 - results_eq(sql, cursor) should have the proper diagnostics +ok 146 - set_has( prepared, prepared, description ) should pass +ok 147 - set_has( prepared, prepared, description ) should have the proper description +ok 148 - set_has( prepared, prepared, description ) should have the proper diagnostics +ok 149 - set_has( prepared, subprepared ) should pass +ok 150 - set_has( prepared, subprepared ) should have the proper description +ok 151 - set_has( prepared, subprepared ) should have the proper diagnostics +ok 152 - set_has( execute, execute ) should pass +ok 153 - set_has( execute, execute ) should have the proper description +ok 154 - set_has( execute, execute ) should have the proper diagnostics +ok 155 - set_has( select, select ) should pass +ok 156 - set_has( select, select ) should have the proper description +ok 157 - set_has( select, select ) should have the proper diagnostics +ok 158 - set_has( prepared, empty ) should pass +ok 159 - set_has( prepared, empty ) should have the proper description +ok 160 - set_has( prepared, empty ) should have the proper diagnostics +ok 161 - set_has( prepared, dupes ) should pass +ok 162 - set_has( prepared, dupes ) should have the proper description +ok 163 - set_has( prepared, dupes ) should have the proper diagnostics +ok 164 - set_has( dupes, values ) should pass +ok 165 - set_has( dupes, values ) should have the proper description +ok 166 - set_has( dupes, values ) should have the proper diagnostics +ok 167 - set_has( missing1, expect ) should fail +ok 168 - set_has( missing1, expect ) should have the proper description +ok 169 - set_has( missing1, expect ) should have the proper diagnostics +ok 170 - set_has(missing2, expect ) should fail +ok 171 - set_has(missing2, expect ) should have the proper description +ok 172 - set_has(missing2, expect ) should have the proper diagnostics +ok 173 - set_has((int,text), (text,int)) should fail +ok 174 - set_has((int,text), (text,int)) should have the proper description +ok 175 - set_has((int,text), (text,int)) should have the proper diagnostics +ok 176 - set_has((int), (text,int)) should fail +ok 177 - set_has((int), (text,int)) should have the proper description +ok 178 - set_has((int), (text,int)) should have the proper diagnostics +ok 179 - bag_has( prepared, prepared, description ) should pass +ok 180 - bag_has( prepared, prepared, description ) should have the proper description +ok 181 - bag_has( prepared, prepared, description ) should have the proper diagnostics +ok 182 - bag_has( prepared, subprepared ) should pass +ok 183 - bag_has( prepared, subprepared ) should have the proper description +ok 184 - bag_has( prepared, subprepared ) should have the proper diagnostics +ok 185 - bag_has( execute, execute ) should pass +ok 186 - bag_has( execute, execute ) should have the proper description +ok 187 - bag_has( execute, execute ) should have the proper diagnostics +ok 188 - bag_has( select, select ) should pass +ok 189 - bag_has( select, select ) should have the proper description +ok 190 - bag_has( select, select ) should have the proper diagnostics +ok 191 - bag_has( prepared, empty ) should pass +ok 192 - bag_has( prepared, empty ) should have the proper description +ok 193 - bag_has( prepared, empty ) should have the proper diagnostics +ok 194 - bag_has( prepared, dupes ) should fail +ok 195 - bag_has( prepared, dupes ) should have the proper description +ok 196 - bag_has( prepared, dupes ) should have the proper diagnostics +ok 197 - bag_has( dupes, values ) should pass +ok 198 - bag_has( dupes, values ) should have the proper description +ok 199 - bag_has( dupes, values ) should have the proper diagnostics +ok 200 - bag_has( missing1, expect ) should fail +ok 201 - bag_has( missing1, expect ) should have the proper description +ok 202 - bag_has( missing1, expect ) should have the proper diagnostics +ok 203 - bag_has(missing2, expect ) should fail +ok 204 - bag_has(missing2, expect ) should have the proper description +ok 205 - bag_has(missing2, expect ) should have the proper diagnostics +ok 206 - bag_has((int,text), (text,int)) should fail +ok 207 - bag_has((int,text), (text,int)) should have the proper description +ok 208 - bag_has((int,text), (text,int)) should have the proper diagnostics +ok 209 - bag_has((int), (text,int)) should fail +ok 210 - bag_has((int), (text,int)) should have the proper description +ok 211 - bag_has((int), (text,int)) should have the proper diagnostics +ok 212 - set_hasnt( prepared, prepared, description ) should pass +ok 213 - set_hasnt( prepared, prepared, description ) should have the proper description +ok 214 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 215 - set_hasnt( prepared, prepared, description ) should pass +ok 216 - set_hasnt( prepared, prepared, description ) should have the proper description +ok 217 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 218 - set_hasnt( execute, execute ) should pass +ok 219 - set_hasnt( execute, execute ) should have the proper description +ok 220 - set_hasnt( execute, execute ) should have the proper diagnostics +ok 221 - set_hasnt( select, select ) should pass +ok 222 - set_hasnt( select, select ) should have the proper description +ok 223 - set_hasnt( select, select ) should have the proper diagnostics +ok 224 - set_hasnt( prepared, empty ) should pass +ok 225 - set_hasnt( prepared, empty ) should have the proper description +ok 226 - set_hasnt( prepared, empty ) should have the proper diagnostics +ok 227 - set_hasnt( prepared, dupes ) should pass +ok 228 - set_hasnt( prepared, dupes ) should have the proper description +ok 229 - set_hasnt( prepared, dupes ) should have the proper diagnostics +ok 230 - set_hasnt( prepared, value ) should fail +ok 231 - set_hasnt( prepared, value ) should have the proper description +ok 232 - set_hasnt( prepared, value ) should have the proper diagnostics +ok 233 - set_hasnt( prepared, values ) should fail +ok 234 - set_hasnt( prepared, values ) should have the proper description +ok 235 - set_hasnt( prepared, values ) should have the proper diagnostics +ok 236 - set_hasnt((int,text), (text,int)) should fail +ok 237 - set_hasnt((int,text), (text,int)) should have the proper description +ok 238 - set_hasnt((int,text), (text,int)) should have the proper diagnostics +ok 239 - set_hasnt((int), (text,int)) should fail +ok 240 - set_hasnt((int), (text,int)) should have the proper description +ok 241 - set_hasnt((int), (text,int)) should have the proper diagnostics +ok 242 - bag_hasnt( prepared, prepared, description ) should pass +ok 243 - bag_hasnt( prepared, prepared, description ) should have the proper description +ok 244 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 245 - bag_hasnt( prepared, prepared, description ) should pass +ok 246 - bag_hasnt( prepared, prepared, description ) should have the proper description +ok 247 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 248 - bag_hasnt( execute, execute ) should pass +ok 249 - bag_hasnt( execute, execute ) should have the proper description +ok 250 - bag_hasnt( execute, execute ) should have the proper diagnostics +ok 251 - bag_hasnt( select, select ) should pass +ok 252 - bag_hasnt( select, select ) should have the proper description +ok 253 - bag_hasnt( select, select ) should have the proper diagnostics +ok 254 - bag_hasnt( prepared, empty ) should pass +ok 255 - bag_hasnt( prepared, empty ) should have the proper description +ok 256 - bag_hasnt( prepared, empty ) should have the proper diagnostics +ok 257 - bag_hasnt( prepared, value ) should fail +ok 258 - bag_hasnt( prepared, value ) should have the proper description +ok 259 - bag_hasnt( prepared, value ) should have the proper diagnostics +ok 260 - bag_hasnt( prepared, values ) should fail +ok 261 - bag_hasnt( prepared, values ) should have the proper description +ok 262 - bag_hasnt( prepared, values ) should have the proper diagnostics +ok 263 - bag_hasnt((int,text), (text,int)) should fail +ok 264 - bag_hasnt((int,text), (text,int)) should have the proper description +ok 265 - bag_hasnt((int,text), (text,int)) should have the proper diagnostics +ok 266 - bag_hasnt((int), (text,int)) should fail +ok 267 - bag_hasnt((int), (text,int)) should have the proper description +ok 268 - bag_hasnt((int), (text,int)) should have the proper diagnostics +ok 269 - bag_hasnt( dupes, dupes ) should fail +ok 270 - bag_hasnt( dupes, dupes ) should have the proper description +ok 271 - bag_hasnt( dupes, dupes ) should have the proper diagnostics +ok 272 - bag_hasnt( value, dupes ) should fail +ok 273 - bag_hasnt( value, dupes ) should have the proper description +ok 274 - bag_hasnt( value, dupes ) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 2c442d80636b..1e8225dc1843 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -797,7 +797,7 @@ BEGIN ); END; $$ LANGUAGE plpgsql; - + -- performs_ok ( sql, milliseconds ) CREATE OR REPLACE FUNCTION performs_ok ( TEXT, NUMERIC ) RETURNS TEXT AS $$ @@ -1244,7 +1244,7 @@ BEGIN RETURN is( $1, $3, $4 ); END IF; - EXECUTE 'SELECT is(' + EXECUTE 'SELECT is(' || COALESCE($1, 'NULL' || '::' || $2) || '::' || $2 || ', ' || COALESCE(quote_literal($3), 'NULL') || '::' || $2 || ', ' || COALESCE(quote_literal($4), 'NULL') @@ -1324,7 +1324,7 @@ 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 ' + 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should default to ' || COALESCE( quote_literal($3), 'NULL') ); $$ LANGUAGE sql; @@ -2043,7 +2043,7 @@ CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME, NAME[] ) RETURNS BOOLEAN AS $$ SELECT EXISTS( SELECT TRUE - FROM tap_funky + FROM tap_funky WHERE schema = $1 AND name = $2 AND args = array_to_string($3, ',') @@ -2059,7 +2059,7 @@ CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME[] ) RETURNS BOOLEAN AS $$ SELECT EXISTS( SELECT TRUE - FROM tap_funky + FROM tap_funky WHERE name = $1 AND args = array_to_string($2, ',') AND is_visible @@ -3528,13 +3528,13 @@ $$ LANGUAGE plpgsql; -- is_member_of( group, user, description ) CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME, TEXT ) RETURNS TEXT AS $$ - SELECT is_member_of( $1, ARRAY[$2], $3 ); + SELECT is_member_of( $1, ARRAY[$2], $3 ); $$ LANGUAGE SQL; -- is_member_of( group, user[] ) CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[] ) RETURNS TEXT AS $$ - SELECT is_member_of( $1, $2, 'Should have members of group ' || quote_ident($1) ); + SELECT is_member_of( $1, $2, 'Should have members of group ' || quote_ident($1) ); $$ LANGUAGE SQL; -- is_member_of( group, user ) @@ -3950,7 +3950,7 @@ BEGIN ); END IF; - RETURN ok(res, descr) || msg; + RETURN ok(res, descr) || msg; END; $$ LANGUAGE plpgsql; @@ -4030,7 +4030,7 @@ RETURNS NAME[] AS $$ FROM generate_series(1, array_upper($3, 1)) s(i) ); $$ LANGUAGE SQL; - + CREATE OR REPLACE FUNCTION _extras ( CHAR, NAME[] ) RETURNS NAME[] AS $$ SELECT ARRAY( @@ -4060,7 +4060,7 @@ RETURNS NAME[] AS $$ AND n.nspname = $2 ); $$ LANGUAGE SQL; - + CREATE OR REPLACE FUNCTION _missing ( CHAR, NAME[] ) RETURNS NAME[] AS $$ SELECT ARRAY( @@ -5583,7 +5583,7 @@ RETURNS TEXT[] AS $$ ORDER BY pname ); $$ LANGUAGE sql; - + CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -5844,7 +5844,7 @@ BEGIN ); END IF; - RETURN ok(res, $3) || msg; + RETURN ok(res, $3) || msg; END; $$ LANGUAGE plpgsql; @@ -5872,6 +5872,98 @@ RETURNS TEXT AS $$ SELECT _relcomp( $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 $$ @@ -5976,4 +6068,3 @@ CREATE OR REPLACE FUNCTION results_eq( refcursor, TEXT ) RETURNS TEXT AS $$ SELECT results_eq( $1, $2, NULL::text ); $$ LANGUAGE sql; - diff --git a/sql/resultset.sql b/sql/resultset.sql index a916f87c3d83..783165c5a377 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(145); +SELECT plan(274); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -221,7 +221,7 @@ SELECT id, name FROM names WHERE name like 'An%'; -- We'll use these prepared statements. PREPARE anames AS SELECT id, name FROM names WHERE name like 'An%'; -PREPARE expect AS VALUES (11, 'Andrew'), ( 44, 'Anna'), (15, 'Anthony'), +PREPARE expect AS VALUES (11, 'Andrew'), ( 44, 'Anna'), (15, 'Anthony'), (183, 'Antonio'), (86, 'Angelina'), (130, 'Andrea'), (63, 'Angel'); @@ -828,6 +828,460 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Now test set_has(). +SELECT * FROM check_test( + set_has( 'anames', 'expect', 'whatever' ), + true, + 'set_has( prepared, prepared, description )', + 'whatever', + '' +); + +PREPARE subset AS VALUES (11, 'Andrew'), ( 44, 'Anna'), (63, 'Angel'); + +SELECT * FROM check_test( + set_has( 'anames', 'subset' ), + true, + 'set_has( prepared, subprepared )', + '', + '' +); + +SELECT * FROM check_test( + set_has( 'EXECUTE anames', 'EXECUTE subset' ), + true, + 'set_has( execute, execute )', + '', + '' +); + +-- Compare actual SELECT statements. +SELECT * FROM check_test( + set_has( + 'SELECT id, name FROM names WHERE name like ''An%''', + 'SELECT id, name FROM annames' + ), + true, + 'set_has( select, select )', + '', + '' +); + +-- Try an empty set in the second arg. +SELECT * FROM check_test( + set_has( 'anames', 'SELECT id, name FROM annames WHERE false' ), + true, + 'set_has( prepared, empty )', + '', + '' +); + +-- Make sure that dupes are ignored. +SELECT * FROM check_test( + set_has( 'anames', 'VALUES (44, ''Anna''), (44, ''Anna'')' ), + true, + 'set_has( prepared, dupes )', + '', + '' +); + +SELECT * FROM check_test( + set_has( 'VALUES (44, ''Anna''), (44, ''Anna'')', 'VALUES(44, ''Anna'')' ), + true, + 'set_has( dupes, values )', + '', + '' +); + +-- Check failures. +SELECT * FROM check_test( + set_has( + 'SELECT id, name FROM annames WHERE name <> ''Anna''', + 'expect' + ), + false, + 'set_has( missing1, expect )', + '', + ' Missing records: + (44,Anna)' +); + +SELECT * FROM check_test( + set_has( + 'SELECT id, name FROM annames WHERE name NOT IN (''Anna'', ''Angelina'')', + 'expect' + ), + false, + 'set_has(missing2, expect )', + '', + E' Missing records: + \\((44,Anna|86,Angelina)\\) + \\((44,Anna|86,Angelina)\\)', + true +); + +-- Handle falure due to column mismatch. +SELECT * FROM check_test( + set_has( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'set_has((int,text), (text,int))', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' +); + +-- Handle falure due to column count mismatch. +SELECT * FROM check_test( + set_has( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'set_has((int), (text,int))', + '', + ' Columns differ between queries: + have: (integer) + want: (text,integer)' +); + + +/****************************************************************************/ +-- Now test bag_has(). +SELECT * FROM check_test( + bag_has( 'anames', 'expect', 'whatever' ), + true, + 'bag_has( prepared, prepared, description )', + 'whatever', + '' +); + +SELECT * FROM check_test( + bag_has( 'anames', 'subset' ), + true, + 'bag_has( prepared, subprepared )', + '', + '' +); + +SELECT * FROM check_test( + bag_has( 'EXECUTE anames', 'EXECUTE subset' ), + true, + 'bag_has( execute, execute )', + '', + '' +); + +-- Compare actual SELECT statements. +SELECT * FROM check_test( + bag_has( + 'SELECT id, name FROM names WHERE name like ''An%''', + 'SELECT id, name FROM annames' + ), + true, + 'bag_has( select, select )', + '', + '' +); + +-- Try an empty set in the second arg. +SELECT * FROM check_test( + bag_has( 'anames', 'SELECT id, name FROM annames WHERE false' ), + true, + 'bag_has( prepared, empty )', + '', + '' +); + +-- Make sure that dupes are not ignored. +SELECT * FROM check_test( + bag_has( 'anames', 'VALUES (44, ''Anna''), (44, ''Anna'')' ), + false, + 'bag_has( prepared, dupes )', + '', + ' Missing records: + (44,Anna)' +); + +SELECT * FROM check_test( + bag_has( 'VALUES (44, ''Anna''), (44, ''Anna'')', 'VALUES(44, ''Anna'')' ), + true, + 'bag_has( dupes, values )', + '', + '' +); + + +SELECT * FROM check_test( + bag_has( + 'SELECT id, name FROM annames WHERE name <> ''Anna''', + 'expect' + ), + false, + 'bag_has( missing1, expect )', + '', + ' Missing records: + (44,Anna)' +); + +SELECT * FROM check_test( + bag_has( + 'SELECT id, name FROM annames WHERE name NOT IN (''Anna'', ''Angelina'')', + 'expect' + ), + false, + 'bag_has(missing2, expect )', + '', + E' Missing records: + \\((44,Anna|86,Angelina)\\) + \\((44,Anna|86,Angelina)\\)', + true +); + +-- Handle falure due to column mismatch. +SELECT * FROM check_test( + bag_has( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'bag_has((int,text), (text,int))', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' +); + +-- Handle falure due to column count mismatch. +SELECT * FROM check_test( + bag_has( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'bag_has((int), (text,int))', + '', + ' Columns differ between queries: + have: (integer) + want: (text,integer)' +); + + +/****************************************************************************/ +-- Now test set_hasnt(). + +PREPARE others AS VALUES ( 44, 'Larry' ), (52, 'Tom'), (23, 'Damian' ); + +SELECT * FROM check_test( + set_hasnt( 'anames', 'others', 'whatever' ), + true, + 'set_hasnt( prepared, prepared, description )', + 'whatever', + '' +); + +SELECT * FROM check_test( + set_hasnt( 'anames', 'others' ), + true, + 'set_hasnt( prepared, prepared, description )', + '', + '' +); + +SELECT * FROM check_test( + set_hasnt( 'EXECUTE anames', 'EXECUTE others' ), + true, + 'set_hasnt( execute, execute )', + '', + '' +); + +-- Compare actual SELECT statements. +SELECT * FROM check_test( + set_hasnt( + 'SELECT id, name FROM names WHERE name like ''An%''', + 'SELECT id, name FROM names WHERE name like ''B%''' + ), + true, + 'set_hasnt( select, select )', + '', + '' +); + +-- Try an empty set in the second arg. +SELECT * FROM check_test( + set_hasnt( 'anames', 'SELECT id, name FROM annames WHERE false' ), + true, + 'set_hasnt( prepared, empty )', + '', + '' +); + +-- Make sure that dupes are ignored. +SELECT * FROM check_test( + set_hasnt( 'anames', 'VALUES (44, ''Bob''), (44, ''Bob'')' ), + true, + 'set_hasnt( prepared, dupes )', + '', + '' +); + +PREPARE overlap AS VALUES ( 44, 'Larry' ), (52, 'Tom'), (23, 'Damian' ); + +SELECT * FROM check_test( + set_hasnt( 'anames', 'VALUES (44,''Anna'')' ), + false, + 'set_hasnt( prepared, value )', + '', + ' Extra records: + (44,Anna)' +); + + +SELECT * FROM check_test( + set_hasnt( 'anames', 'VALUES (44, ''Anna''), (86, ''Angelina'')' ), + false, + 'set_hasnt( prepared, values )', + '', + E' Extra records: + \\((44,Anna|86,Angelina)\\) + \\((44,Anna|86,Angelina)\\)', + true +); + +-- Handle falure due to column mismatch. +SELECT * FROM check_test( + set_hasnt( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'set_hasnt((int,text), (text,int))', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' +); + +-- Handle falure due to column count mismatch. +SELECT * FROM check_test( + set_hasnt( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'set_hasnt((int), (text,int))', + '', + ' Columns differ between queries: + have: (integer) + want: (text,integer)' +); + + +/****************************************************************************/ +-- Now test bag_hasnt(). + +SELECT * FROM check_test( + bag_hasnt( 'anames', 'others', 'whatever' ), + true, + 'bag_hasnt( prepared, prepared, description )', + 'whatever', + '' +); + +SELECT * FROM check_test( + bag_hasnt( 'anames', 'others' ), + true, + 'bag_hasnt( prepared, prepared, description )', + '', + '' +); + +SELECT * FROM check_test( + bag_hasnt( 'EXECUTE anames', 'EXECUTE others' ), + true, + 'bag_hasnt( execute, execute )', + '', + '' +); + +-- Compare actual SELECT statements. +SELECT * FROM check_test( + bag_hasnt( + 'SELECT id, name FROM names WHERE name like ''An%''', + 'SELECT id, name FROM names WHERE name like ''B%''' + ), + true, + 'bag_hasnt( select, select )', + '', + '' +); + +-- Try an empty bag in the second arg. +SELECT * FROM check_test( + bag_hasnt( 'anames', 'SELECT id, name FROM annames WHERE false' ), + true, + 'bag_hasnt( prepared, empty )', + '', + '' +); + +SELECT * FROM check_test( + bag_hasnt( 'anames', 'VALUES (44,''Anna'')' ), + false, + 'bag_hasnt( prepared, value )', + '', + ' Extra records: + (44,Anna)' +); + + +SELECT * FROM check_test( + bag_hasnt( 'anames', 'VALUES (44, ''Anna''), (86, ''Angelina'')' ), + false, + 'bag_hasnt( prepared, values )', + '', + E' Extra records: + \\((44,Anna|86,Angelina)\\) + \\((44,Anna|86,Angelina)\\)', + true +); + +-- Handle falure due to column mismatch. +SELECT * FROM check_test( + bag_hasnt( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'bag_hasnt((int,text), (text,int))', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' +); + +-- Handle falure due to column count mismatch. +SELECT * FROM check_test( + bag_hasnt( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'bag_hasnt((int), (text,int))', + '', + ' Columns differ between queries: + have: (integer) + want: (text,integer)' +); + +-- Make sure that dupes are not ignored. +SELECT * FROM check_test( + bag_hasnt( + 'VALUES (44, ''Anna''), (44, ''Anna'')', + 'VALUES (44, ''Anna''), (44, ''Anna'')' + ), + false, + 'bag_hasnt( dupes, dupes )', + '', + ' Extra records: + (44,Anna) + (44,Anna)' +); + +-- But a dupe that appears only once should be in the list only once. +SELECT * FROM check_test( + bag_hasnt( + 'VALUES (44, ''Anna'')', + 'VALUES (44, ''Anna''), (44, ''Anna'')' + ), + false, + 'bag_hasnt( value, dupes )', + '', + ' Extra records: + (44,Anna)' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index c37a45c84edd..2db4b4486165 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,15 +1,28 @@ -- ## SET search_path TO TAPSCHEMA, public; +DROP FUNCTION results_eq( refcursor, TEXT ); +DROP FUNCTION results_eq( refcursor, TEXT, TEXT ); +DROP FUNCTION results_eq( TEXT, refcursor ); +DROP FUNCTION results_eq( TEXT, refcursor, TEXT ); DROP FUNCTION results_eq( TEXT, TEXT ); DROP FUNCTION results_eq( TEXT, TEXT, TEXT ); DROP FUNCTION results_eq( refcursor, refcursor ); DROP FUNCTION results_eq( refcursor, refcursor, text ); +DROP FUNCTION bag_hasnt( TEXT, TEXT ); +DROP FUNCTION bag_hasnt( TEXT, TEXT, TEXT ); +DROP FUNCTION set_hasnt( TEXT, TEXT ); +DROP FUNCTION set_hasnt( TEXT, TEXT, TEXT ); +DROP FUNCTION bag_has( TEXT, TEXT ); +DROP FUNCTION bag_has( TEXT, TEXT, TEXT ); +DROP FUNCTION set_has( TEXT, TEXT ); +DROP FUNCTION set_has( TEXT, TEXT, TEXT ); +DROP FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT, TEXT ); DROP FUNCTION bag_eq( TEXT, TEXT ); DROP FUNCTION bag_eq( TEXT, TEXT, TEXT ); DROP FUNCTION set_eq( TEXT, TEXT ); DROP FUNCTION set_eq( TEXT, TEXT, TEXT ); DROP FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION _temptypes( TEXT ); DROP FUNCTION _temptable ( TEXT, TEXT ); -DROP FUNCTION _query( TEXT ); DROP FUNCTION runtests( ); DROP FUNCTION runtests( TEXT ); DROP FUNCTION runtests( NAME ); @@ -518,6 +531,7 @@ DROP FUNCTION throws_ok ( TEXT ); DROP FUNCTION throws_ok ( TEXT, TEXT ); DROP FUNCTION throws_ok ( TEXT, TEXT, TEXT ); DROP FUNCTION throws_ok ( TEXT, CHAR(5), TEXT, TEXT ); +DROP FUNCTION _query( TEXT ); DROP FUNCTION skip( int ); DROP FUNCTION skip( int, text ); DROP FUNCTION skip ( text ); From 16fe8b18f0415409ff9f41231648bb9eb692fffe Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Jul 2009 16:29:24 -0700 Subject: [PATCH 0411/1195] Added `set_ne()` and `bag_ne()`. --- Changes | 5 +- README.pgtap | 30 +++ expected/resultset.out | 413 ++++++++++++++++++++++------------------- pgtap.sql.in | 64 +++++++ sql/resultset.sql | 149 ++++++++++++++- 5 files changed, 471 insertions(+), 190 deletions(-) diff --git a/Changes b/Changes index 9735f6ded0b5..7ec6ddf90c06 100644 --- a/Changes +++ b/Changes @@ -4,8 +4,9 @@ Revision history for pgTAP 0.22 ------------------------- * Fixed failing test on 8.4rc2. -* Added `results_eq(), `set_eq()`, `bag_eq()`, `set_has()`, `bag_has()`, - `set_hasnt()`, and `bag_hasnt()` functions to test query results. +* Added `results_eq(), `set_eq()`, `bag_eq()`, `set_ne()`, `bag_ne()`, + `set_has()`, `bag_has()`, `set_hasnt()`, and `bag_hasnt()` functions to test + query results. * Changed some internal queries to use explicit `JOIN`s. * Fixed capitalization of `TAP::Harness` in the `Makefile`. Thanks to Quinn Weaver. diff --git a/README.pgtap b/README.pgtap index fe0b5ee79c3b..073f771c6408 100644 --- a/README.pgtap +++ b/README.pgtap @@ -909,6 +909,36 @@ result set. Otherwise, this function behaves exactly like `set_eq()`, including the utility of its diagnostics. +### `set_ne( sql, sql, description )` ### +### `set_ne( sql, sql )` ### + + PREPARE testq AS SELECT * FROM users('a%'); + PREPARE expect AS SELECT * FROM USERS where name LIKE 'b%'; + SELECT set_ne( 'testq', 'expect', 'gotta have the A listers' ); + +The inverse of `set_eq()`, this function tests that the results of two queries +are *not* the same. The two queries, which The can of course be the names of +prepared statements or strings containing an SQL query (see the +[summary](#Submit+Your+Query) for query argument details), or even one of +each. The two queries, however, must return results that are directly +comparable -- that is, with the same number and types of columns in the same +orders. + +### `bag_ne( sql, sql, description )` ### +### `bag_ne( sql, sql )` ### + + PREPARE testq AS SELECT * FROM users('a%'); + PREPARE expect AS SELECT * FROM USERS where name LIKE 'b%'; + SELECT bag_ne( 'testq', 'expect', 'gotta have the A listers' ); + +The inverse of `bag_eq()`, this function tests that the results of two queries +are *not* the same, including duplicates. The two queries, which The can of +course be the names of prepared statements or strings containing an SQL query +(see the [summary](#Submit+Your+Query) for query argument details), or even +one of each. The two queries, however, must return results that are directly +comparable -- that is, with the same number and types of columns in the same +orders. + ### `set_has( sql, sql, description )` ### ### `set_has( sql, sql )` ### diff --git a/expected/resultset.out b/expected/resultset.out index 6682864ffd38..e52ca0f45cac 100644 --- a/expected/resultset.out +++ b/expected/resultset.out @@ -1,5 +1,5 @@ \unset ECHO -1..274 +1..313 ok 1 - Should create temp table with simple query ok 2 - Table __foonames__ should exist ok 3 - Should create a temp table for a prepared statement @@ -88,189 +88,228 @@ ok 85 - fail with different col counts should have the proper diagnostics ok 86 - bag fail with missing dupe should fail ok 87 - bag fail with missing dupe should have the proper description ok 88 - bag fail with missing dupe should have the proper diagnostics -ok 89 - simple results test should pass -ok 90 - simple results test should have the proper description -ok 91 - simple results test should have the proper diagnostics -ok 92 - simple results test without desc should pass -ok 93 - simple results test without desc should have the proper description -ok 94 - simple results test without desc should have the proper diagnostics -ok 95 - execute results test should pass -ok 96 - execute results test should have the proper description -ok 97 - execute results test should have the proper diagnostics -ok 98 - select results test should pass -ok 99 - select results test should have the proper description -ok 100 - select results test should have the proper diagnostics -ok 101 - results test with dupes should pass -ok 102 - results test with dupes should have the proper description -ok 103 - results test with dupes should have the proper diagnostics -ok 104 - results test with nulls should pass -ok 105 - results test with nulls should have the proper description -ok 106 - results test with nulls should have the proper diagnostics -ok 107 - fail with extra record should fail -ok 108 - fail with extra record should have the proper description -ok 109 - fail with extra record should have the proper diagnostics -ok 110 - fail with missing record should fail -ok 111 - fail with missing record should have the proper description -ok 112 - fail with missing record should have the proper diagnostics -ok 113 - fail with missing record should fail -ok 114 - fail with missing record should have the proper description -ok 115 - fail with missing record should have the proper diagnostics -ok 116 - results fail with missing dupe should fail -ok 117 - results fail with missing dupe should have the proper description -ok 118 - results fail with missing dupe should have the proper diagnostics -ok 119 - fail with NULL should fail -ok 120 - fail with NULL should have the proper description -ok 121 - fail with NULL should have the proper diagnostics -ok 122 - fail with column mismatch should fail -ok 123 - fail with column mismatch should have the proper description -ok 124 - fail with column mismatch should have the proper diagnostics -ok 125 - fail with subtle column mismatch should fail -ok 126 - fail with subtle column mismatch should have the proper description -ok 127 - fail with subtle column mismatch should have the proper diagnostics -ok 128 - fail with different col counts should fail -ok 129 - fail with different col counts should have the proper description -ok 130 - fail with different col counts should have the proper diagnostics -ok 131 - simple results with cursors should pass -ok 132 - simple results with cursors should have the proper description -ok 133 - simple results with cursors should have the proper diagnostics -ok 134 - results_eq(cursor, prepared) should pass -ok 135 - results_eq(cursor, prepared) should have the proper description -ok 136 - results_eq(cursor, prepared) should have the proper diagnostics -ok 137 - results_eq(prepared, cursor) should pass -ok 138 - results_eq(prepared, cursor) should have the proper description -ok 139 - results_eq(prepared, cursor) should have the proper diagnostics -ok 140 - results_eq(cursor, sql) should pass -ok 141 - results_eq(cursor, sql) should have the proper description -ok 142 - results_eq(cursor, sql) should have the proper diagnostics -ok 143 - results_eq(sql, cursor) should pass -ok 144 - results_eq(sql, cursor) should have the proper description -ok 145 - results_eq(sql, cursor) should have the proper diagnostics -ok 146 - set_has( prepared, prepared, description ) should pass -ok 147 - set_has( prepared, prepared, description ) should have the proper description -ok 148 - set_has( prepared, prepared, description ) should have the proper diagnostics -ok 149 - set_has( prepared, subprepared ) should pass -ok 150 - set_has( prepared, subprepared ) should have the proper description -ok 151 - set_has( prepared, subprepared ) should have the proper diagnostics -ok 152 - set_has( execute, execute ) should pass -ok 153 - set_has( execute, execute ) should have the proper description -ok 154 - set_has( execute, execute ) should have the proper diagnostics -ok 155 - set_has( select, select ) should pass -ok 156 - set_has( select, select ) should have the proper description -ok 157 - set_has( select, select ) should have the proper diagnostics -ok 158 - set_has( prepared, empty ) should pass -ok 159 - set_has( prepared, empty ) should have the proper description -ok 160 - set_has( prepared, empty ) should have the proper diagnostics -ok 161 - set_has( prepared, dupes ) should pass -ok 162 - set_has( prepared, dupes ) should have the proper description -ok 163 - set_has( prepared, dupes ) should have the proper diagnostics -ok 164 - set_has( dupes, values ) should pass -ok 165 - set_has( dupes, values ) should have the proper description -ok 166 - set_has( dupes, values ) should have the proper diagnostics -ok 167 - set_has( missing1, expect ) should fail -ok 168 - set_has( missing1, expect ) should have the proper description -ok 169 - set_has( missing1, expect ) should have the proper diagnostics -ok 170 - set_has(missing2, expect ) should fail -ok 171 - set_has(missing2, expect ) should have the proper description -ok 172 - set_has(missing2, expect ) should have the proper diagnostics -ok 173 - set_has((int,text), (text,int)) should fail -ok 174 - set_has((int,text), (text,int)) should have the proper description -ok 175 - set_has((int,text), (text,int)) should have the proper diagnostics -ok 176 - set_has((int), (text,int)) should fail -ok 177 - set_has((int), (text,int)) should have the proper description -ok 178 - set_has((int), (text,int)) should have the proper diagnostics -ok 179 - bag_has( prepared, prepared, description ) should pass -ok 180 - bag_has( prepared, prepared, description ) should have the proper description -ok 181 - bag_has( prepared, prepared, description ) should have the proper diagnostics -ok 182 - bag_has( prepared, subprepared ) should pass -ok 183 - bag_has( prepared, subprepared ) should have the proper description -ok 184 - bag_has( prepared, subprepared ) should have the proper diagnostics -ok 185 - bag_has( execute, execute ) should pass -ok 186 - bag_has( execute, execute ) should have the proper description -ok 187 - bag_has( execute, execute ) should have the proper diagnostics -ok 188 - bag_has( select, select ) should pass -ok 189 - bag_has( select, select ) should have the proper description -ok 190 - bag_has( select, select ) should have the proper diagnostics -ok 191 - bag_has( prepared, empty ) should pass -ok 192 - bag_has( prepared, empty ) should have the proper description -ok 193 - bag_has( prepared, empty ) should have the proper diagnostics -ok 194 - bag_has( prepared, dupes ) should fail -ok 195 - bag_has( prepared, dupes ) should have the proper description -ok 196 - bag_has( prepared, dupes ) should have the proper diagnostics -ok 197 - bag_has( dupes, values ) should pass -ok 198 - bag_has( dupes, values ) should have the proper description -ok 199 - bag_has( dupes, values ) should have the proper diagnostics -ok 200 - bag_has( missing1, expect ) should fail -ok 201 - bag_has( missing1, expect ) should have the proper description -ok 202 - bag_has( missing1, expect ) should have the proper diagnostics -ok 203 - bag_has(missing2, expect ) should fail -ok 204 - bag_has(missing2, expect ) should have the proper description -ok 205 - bag_has(missing2, expect ) should have the proper diagnostics -ok 206 - bag_has((int,text), (text,int)) should fail -ok 207 - bag_has((int,text), (text,int)) should have the proper description -ok 208 - bag_has((int,text), (text,int)) should have the proper diagnostics -ok 209 - bag_has((int), (text,int)) should fail -ok 210 - bag_has((int), (text,int)) should have the proper description -ok 211 - bag_has((int), (text,int)) should have the proper diagnostics -ok 212 - set_hasnt( prepared, prepared, description ) should pass -ok 213 - set_hasnt( prepared, prepared, description ) should have the proper description -ok 214 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics -ok 215 - set_hasnt( prepared, prepared, description ) should pass -ok 216 - set_hasnt( prepared, prepared, description ) should have the proper description -ok 217 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics -ok 218 - set_hasnt( execute, execute ) should pass -ok 219 - set_hasnt( execute, execute ) should have the proper description -ok 220 - set_hasnt( execute, execute ) should have the proper diagnostics -ok 221 - set_hasnt( select, select ) should pass -ok 222 - set_hasnt( select, select ) should have the proper description -ok 223 - set_hasnt( select, select ) should have the proper diagnostics -ok 224 - set_hasnt( prepared, empty ) should pass -ok 225 - set_hasnt( prepared, empty ) should have the proper description -ok 226 - set_hasnt( prepared, empty ) should have the proper diagnostics -ok 227 - set_hasnt( prepared, dupes ) should pass -ok 228 - set_hasnt( prepared, dupes ) should have the proper description -ok 229 - set_hasnt( prepared, dupes ) should have the proper diagnostics -ok 230 - set_hasnt( prepared, value ) should fail -ok 231 - set_hasnt( prepared, value ) should have the proper description -ok 232 - set_hasnt( prepared, value ) should have the proper diagnostics -ok 233 - set_hasnt( prepared, values ) should fail -ok 234 - set_hasnt( prepared, values ) should have the proper description -ok 235 - set_hasnt( prepared, values ) should have the proper diagnostics -ok 236 - set_hasnt((int,text), (text,int)) should fail -ok 237 - set_hasnt((int,text), (text,int)) should have the proper description -ok 238 - set_hasnt((int,text), (text,int)) should have the proper diagnostics -ok 239 - set_hasnt((int), (text,int)) should fail -ok 240 - set_hasnt((int), (text,int)) should have the proper description -ok 241 - set_hasnt((int), (text,int)) should have the proper diagnostics -ok 242 - bag_hasnt( prepared, prepared, description ) should pass -ok 243 - bag_hasnt( prepared, prepared, description ) should have the proper description -ok 244 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics -ok 245 - bag_hasnt( prepared, prepared, description ) should pass -ok 246 - bag_hasnt( prepared, prepared, description ) should have the proper description -ok 247 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics -ok 248 - bag_hasnt( execute, execute ) should pass -ok 249 - bag_hasnt( execute, execute ) should have the proper description -ok 250 - bag_hasnt( execute, execute ) should have the proper diagnostics -ok 251 - bag_hasnt( select, select ) should pass -ok 252 - bag_hasnt( select, select ) should have the proper description -ok 253 - bag_hasnt( select, select ) should have the proper diagnostics -ok 254 - bag_hasnt( prepared, empty ) should pass -ok 255 - bag_hasnt( prepared, empty ) should have the proper description -ok 256 - bag_hasnt( prepared, empty ) should have the proper diagnostics -ok 257 - bag_hasnt( prepared, value ) should fail -ok 258 - bag_hasnt( prepared, value ) should have the proper description -ok 259 - bag_hasnt( prepared, value ) should have the proper diagnostics -ok 260 - bag_hasnt( prepared, values ) should fail -ok 261 - bag_hasnt( prepared, values ) should have the proper description -ok 262 - bag_hasnt( prepared, values ) should have the proper diagnostics -ok 263 - bag_hasnt((int,text), (text,int)) should fail -ok 264 - bag_hasnt((int,text), (text,int)) should have the proper description -ok 265 - bag_hasnt((int,text), (text,int)) should have the proper diagnostics -ok 266 - bag_hasnt((int), (text,int)) should fail -ok 267 - bag_hasnt((int), (text,int)) should have the proper description -ok 268 - bag_hasnt((int), (text,int)) should have the proper diagnostics -ok 269 - bag_hasnt( dupes, dupes ) should fail -ok 270 - bag_hasnt( dupes, dupes ) should have the proper description -ok 271 - bag_hasnt( dupes, dupes ) should have the proper diagnostics -ok 272 - bag_hasnt( value, dupes ) should fail -ok 273 - bag_hasnt( value, dupes ) should have the proper description -ok 274 - bag_hasnt( value, dupes ) should have the proper diagnostics +ok 89 - set_ne(prepared, select, desc) should pass +ok 90 - set_ne(prepared, select, desc) should have the proper description +ok 91 - set_ne(prepared, select, desc) should have the proper diagnostics +ok 92 - set_ne(prepared, select) should pass +ok 93 - set_ne(prepared, select) should have the proper description +ok 94 - set_ne(prepared, select) should have the proper diagnostics +ok 95 - set_ne(prepared, prepared) fail should fail +ok 96 - set_ne(prepared, prepared) fail should have the proper description +ok 97 - set_ne(prepared, prepared) fail should have the proper diagnostics +ok 98 - set_ne fail with column mismatch should fail +ok 99 - set_ne fail with column mismatch should have the proper description +ok 100 - set_ne fail with column mismatch should have the proper diagnostics +ok 101 - set_ne fail with different col counts should fail +ok 102 - set_ne fail with different col counts should have the proper description +ok 103 - set_ne fail with different col counts should have the proper diagnostics +ok 104 - set_ne fail with dupe should fail +ok 105 - set_ne fail with dupe should have the proper description +ok 106 - set_ne fail with dupe should have the proper diagnostics +ok 107 - bag_ne(prepared, select, desc) should pass +ok 108 - bag_ne(prepared, select, desc) should have the proper description +ok 109 - bag_ne(prepared, select, desc) should have the proper diagnostics +ok 110 - bag_ne(prepared, select) should pass +ok 111 - bag_ne(prepared, select) should have the proper description +ok 112 - bag_ne(prepared, select) should have the proper diagnostics +ok 113 - bag_ne(prepared, prepared) fail should fail +ok 114 - bag_ne(prepared, prepared) fail should have the proper description +ok 115 - bag_ne(prepared, prepared) fail should have the proper diagnostics +ok 116 - bag_ne fail with column mismatch should fail +ok 117 - bag_ne fail with column mismatch should have the proper description +ok 118 - bag_ne fail with column mismatch should have the proper diagnostics +ok 119 - set_ne pass with dupe should pass +ok 120 - set_ne pass with dupe should have the proper description +ok 121 - set_ne pass with dupe should have the proper diagnostics +ok 122 - bag_ne fail with column mismatch should fail +ok 123 - bag_ne fail with column mismatch should have the proper description +ok 124 - bag_ne fail with column mismatch should have the proper diagnostics +ok 125 - bag_ne fail with different col counts should fail +ok 126 - bag_ne fail with different col counts should have the proper description +ok 127 - bag_ne fail with different col counts should have the proper diagnostics +ok 128 - simple results test should pass +ok 129 - simple results test should have the proper description +ok 130 - simple results test should have the proper diagnostics +ok 131 - simple results test without desc should pass +ok 132 - simple results test without desc should have the proper description +ok 133 - simple results test without desc should have the proper diagnostics +ok 134 - execute results test should pass +ok 135 - execute results test should have the proper description +ok 136 - execute results test should have the proper diagnostics +ok 137 - select results test should pass +ok 138 - select results test should have the proper description +ok 139 - select results test should have the proper diagnostics +ok 140 - results test with dupes should pass +ok 141 - results test with dupes should have the proper description +ok 142 - results test with dupes should have the proper diagnostics +ok 143 - results test with nulls should pass +ok 144 - results test with nulls should have the proper description +ok 145 - results test with nulls should have the proper diagnostics +ok 146 - fail with extra record should fail +ok 147 - fail with extra record should have the proper description +ok 148 - fail with extra record should have the proper diagnostics +ok 149 - fail with missing record should fail +ok 150 - fail with missing record should have the proper description +ok 151 - fail with missing record should have the proper diagnostics +ok 152 - fail with missing record should fail +ok 153 - fail with missing record should have the proper description +ok 154 - fail with missing record should have the proper diagnostics +ok 155 - results fail with missing dupe should fail +ok 156 - results fail with missing dupe should have the proper description +ok 157 - results fail with missing dupe should have the proper diagnostics +ok 158 - fail with NULL should fail +ok 159 - fail with NULL should have the proper description +ok 160 - fail with NULL should have the proper diagnostics +ok 161 - fail with column mismatch should fail +ok 162 - fail with column mismatch should have the proper description +ok 163 - fail with column mismatch should have the proper diagnostics +ok 164 - fail with subtle column mismatch should fail +ok 165 - fail with subtle column mismatch should have the proper description +ok 166 - fail with subtle column mismatch should have the proper diagnostics +ok 167 - fail with different col counts should fail +ok 168 - fail with different col counts should have the proper description +ok 169 - fail with different col counts should have the proper diagnostics +ok 170 - simple results with cursors should pass +ok 171 - simple results with cursors should have the proper description +ok 172 - simple results with cursors should have the proper diagnostics +ok 173 - results_eq(cursor, prepared) should pass +ok 174 - results_eq(cursor, prepared) should have the proper description +ok 175 - results_eq(cursor, prepared) should have the proper diagnostics +ok 176 - results_eq(prepared, cursor) should pass +ok 177 - results_eq(prepared, cursor) should have the proper description +ok 178 - results_eq(prepared, cursor) should have the proper diagnostics +ok 179 - results_eq(cursor, sql) should pass +ok 180 - results_eq(cursor, sql) should have the proper description +ok 181 - results_eq(cursor, sql) should have the proper diagnostics +ok 182 - results_eq(sql, cursor) should pass +ok 183 - results_eq(sql, cursor) should have the proper description +ok 184 - results_eq(sql, cursor) should have the proper diagnostics +ok 185 - set_has( prepared, prepared, description ) should pass +ok 186 - set_has( prepared, prepared, description ) should have the proper description +ok 187 - set_has( prepared, prepared, description ) should have the proper diagnostics +ok 188 - set_has( prepared, subprepared ) should pass +ok 189 - set_has( prepared, subprepared ) should have the proper description +ok 190 - set_has( prepared, subprepared ) should have the proper diagnostics +ok 191 - set_has( execute, execute ) should pass +ok 192 - set_has( execute, execute ) should have the proper description +ok 193 - set_has( execute, execute ) should have the proper diagnostics +ok 194 - set_has( select, select ) should pass +ok 195 - set_has( select, select ) should have the proper description +ok 196 - set_has( select, select ) should have the proper diagnostics +ok 197 - set_has( prepared, empty ) should pass +ok 198 - set_has( prepared, empty ) should have the proper description +ok 199 - set_has( prepared, empty ) should have the proper diagnostics +ok 200 - set_has( prepared, dupes ) should pass +ok 201 - set_has( prepared, dupes ) should have the proper description +ok 202 - set_has( prepared, dupes ) should have the proper diagnostics +ok 203 - set_has( dupes, values ) should pass +ok 204 - set_has( dupes, values ) should have the proper description +ok 205 - set_has( dupes, values ) should have the proper diagnostics +ok 206 - set_has( missing1, expect ) should fail +ok 207 - set_has( missing1, expect ) should have the proper description +ok 208 - set_has( missing1, expect ) should have the proper diagnostics +ok 209 - set_has(missing2, expect ) should fail +ok 210 - set_has(missing2, expect ) should have the proper description +ok 211 - set_has(missing2, expect ) should have the proper diagnostics +ok 212 - set_has((int,text), (text,int)) should fail +ok 213 - set_has((int,text), (text,int)) should have the proper description +ok 214 - set_has((int,text), (text,int)) should have the proper diagnostics +ok 215 - set_has((int), (text,int)) should fail +ok 216 - set_has((int), (text,int)) should have the proper description +ok 217 - set_has((int), (text,int)) should have the proper diagnostics +ok 218 - bag_has( prepared, prepared, description ) should pass +ok 219 - bag_has( prepared, prepared, description ) should have the proper description +ok 220 - bag_has( prepared, prepared, description ) should have the proper diagnostics +ok 221 - bag_has( prepared, subprepared ) should pass +ok 222 - bag_has( prepared, subprepared ) should have the proper description +ok 223 - bag_has( prepared, subprepared ) should have the proper diagnostics +ok 224 - bag_has( execute, execute ) should pass +ok 225 - bag_has( execute, execute ) should have the proper description +ok 226 - bag_has( execute, execute ) should have the proper diagnostics +ok 227 - bag_has( select, select ) should pass +ok 228 - bag_has( select, select ) should have the proper description +ok 229 - bag_has( select, select ) should have the proper diagnostics +ok 230 - bag_has( prepared, empty ) should pass +ok 231 - bag_has( prepared, empty ) should have the proper description +ok 232 - bag_has( prepared, empty ) should have the proper diagnostics +ok 233 - bag_has( prepared, dupes ) should fail +ok 234 - bag_has( prepared, dupes ) should have the proper description +ok 235 - bag_has( prepared, dupes ) should have the proper diagnostics +ok 236 - bag_has( dupes, values ) should pass +ok 237 - bag_has( dupes, values ) should have the proper description +ok 238 - bag_has( dupes, values ) should have the proper diagnostics +ok 239 - bag_has( missing1, expect ) should fail +ok 240 - bag_has( missing1, expect ) should have the proper description +ok 241 - bag_has( missing1, expect ) should have the proper diagnostics +ok 242 - bag_has(missing2, expect ) should fail +ok 243 - bag_has(missing2, expect ) should have the proper description +ok 244 - bag_has(missing2, expect ) should have the proper diagnostics +ok 245 - bag_has((int,text), (text,int)) should fail +ok 246 - bag_has((int,text), (text,int)) should have the proper description +ok 247 - bag_has((int,text), (text,int)) should have the proper diagnostics +ok 248 - bag_has((int), (text,int)) should fail +ok 249 - bag_has((int), (text,int)) should have the proper description +ok 250 - bag_has((int), (text,int)) should have the proper diagnostics +ok 251 - set_hasnt( prepared, prepared, description ) should pass +ok 252 - set_hasnt( prepared, prepared, description ) should have the proper description +ok 253 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 254 - set_hasnt( prepared, prepared, description ) should pass +ok 255 - set_hasnt( prepared, prepared, description ) should have the proper description +ok 256 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 257 - set_hasnt( execute, execute ) should pass +ok 258 - set_hasnt( execute, execute ) should have the proper description +ok 259 - set_hasnt( execute, execute ) should have the proper diagnostics +ok 260 - set_hasnt( select, select ) should pass +ok 261 - set_hasnt( select, select ) should have the proper description +ok 262 - set_hasnt( select, select ) should have the proper diagnostics +ok 263 - set_hasnt( prepared, empty ) should pass +ok 264 - set_hasnt( prepared, empty ) should have the proper description +ok 265 - set_hasnt( prepared, empty ) should have the proper diagnostics +ok 266 - set_hasnt( prepared, dupes ) should pass +ok 267 - set_hasnt( prepared, dupes ) should have the proper description +ok 268 - set_hasnt( prepared, dupes ) should have the proper diagnostics +ok 269 - set_hasnt( prepared, value ) should fail +ok 270 - set_hasnt( prepared, value ) should have the proper description +ok 271 - set_hasnt( prepared, value ) should have the proper diagnostics +ok 272 - set_hasnt( prepared, values ) should fail +ok 273 - set_hasnt( prepared, values ) should have the proper description +ok 274 - set_hasnt( prepared, values ) should have the proper diagnostics +ok 275 - set_hasnt((int,text), (text,int)) should fail +ok 276 - set_hasnt((int,text), (text,int)) should have the proper description +ok 277 - set_hasnt((int,text), (text,int)) should have the proper diagnostics +ok 278 - set_hasnt((int), (text,int)) should fail +ok 279 - set_hasnt((int), (text,int)) should have the proper description +ok 280 - set_hasnt((int), (text,int)) should have the proper diagnostics +ok 281 - bag_hasnt( prepared, prepared, description ) should pass +ok 282 - bag_hasnt( prepared, prepared, description ) should have the proper description +ok 283 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 284 - bag_hasnt( prepared, prepared, description ) should pass +ok 285 - bag_hasnt( prepared, prepared, description ) should have the proper description +ok 286 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 287 - bag_hasnt( execute, execute ) should pass +ok 288 - bag_hasnt( execute, execute ) should have the proper description +ok 289 - bag_hasnt( execute, execute ) should have the proper diagnostics +ok 290 - bag_hasnt( select, select ) should pass +ok 291 - bag_hasnt( select, select ) should have the proper description +ok 292 - bag_hasnt( select, select ) should have the proper diagnostics +ok 293 - bag_hasnt( prepared, empty ) should pass +ok 294 - bag_hasnt( prepared, empty ) should have the proper description +ok 295 - bag_hasnt( prepared, empty ) should have the proper diagnostics +ok 296 - bag_hasnt( prepared, value ) should fail +ok 297 - bag_hasnt( prepared, value ) should have the proper description +ok 298 - bag_hasnt( prepared, value ) should have the proper diagnostics +ok 299 - bag_hasnt( prepared, values ) should fail +ok 300 - bag_hasnt( prepared, values ) should have the proper description +ok 301 - bag_hasnt( prepared, values ) should have the proper diagnostics +ok 302 - bag_hasnt((int,text), (text,int)) should fail +ok 303 - bag_hasnt((int,text), (text,int)) should have the proper description +ok 304 - bag_hasnt((int,text), (text,int)) should have the proper diagnostics +ok 305 - bag_hasnt((int), (text,int)) should fail +ok 306 - bag_hasnt((int), (text,int)) should have the proper description +ok 307 - bag_hasnt((int), (text,int)) should have the proper diagnostics +ok 308 - bag_hasnt( dupes, dupes ) should fail +ok 309 - bag_hasnt( dupes, dupes ) should have the proper description +ok 310 - bag_hasnt( dupes, dupes ) should have the proper diagnostics +ok 311 - bag_hasnt( value, dupes ) should fail +ok 312 - bag_hasnt( value, dupes ) should have the proper description +ok 313 - bag_hasnt( value, dupes ) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 1e8225dc1843..2ad1fd15ab79 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -5872,6 +5872,70 @@ RETURNS TEXT AS $$ SELECT _relcomp( $1, $2, NULL::text, 'ALL ' ); $$ LANGUAGE sql; +CREATE OR REPLACE FUNCTION _relne( TEXT, TEXT, TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + have TEXT := _temptable( $1, '__taphave__' ); + want TEXT := _temptable( $2, '__tapwant__' ); + extras TEXT[] := '{}'; + missing TEXT[] := '{}'; + res BOOLEAN := TRUE; + msg TEXT := ''; + rec RECORD; +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; + +-- 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; + +-- 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; + CREATE OR REPLACE FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE diff --git a/sql/resultset.sql b/sql/resultset.sql index 783165c5a377..a5aef2e0b51a 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(274); +SELECT plan(313); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -582,6 +582,153 @@ SELECT * FROM check_test( (1,Anna)' ); +/****************************************************************************/ +-- Now test set_eq(). + +SELECT * FROM check_test( + set_ne( + 'anames', + 'SELECT id, name FROM annames WHERE name <> ''Anna''', + 'whatever' + ), + true, + 'set_ne(prepared, select, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + set_ne( + 'anames', + 'SELECT id, name FROM annames WHERE name <> ''Anna''' + ), + true, + 'set_ne(prepared, select)', + '', + '' +); + +SELECT * FROM check_test( + set_ne( 'anames', 'expect' ), + false, + 'set_ne(prepared, prepared) fail', + '', + '' +); + +-- Handle fail with column mismatch. +SELECT * FROM check_test( + set_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'set_ne fail with column mismatch', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' +); + +-- Handle falure due to column count mismatch. +SELECT * FROM check_test( + set_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'set_ne fail with different col counts', + '', + ' Columns differ between queries: + have: (integer) + want: (text,integer)' +); + +-- Handle fail with a dupe. +SELECT * FROM check_test( + set_ne( + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (1, ''Anna''), (86, ''Angelina'')' + ), + false, + 'set_ne fail with dupe', + '', + '' +); + +/****************************************************************************/ +-- Now test bag_ne(). + +SELECT * FROM check_test( + bag_ne( + 'anames', + 'SELECT id, name FROM annames WHERE name <> ''Anna''', + 'whatever' + ), + true, + 'bag_ne(prepared, select, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + bag_ne( + 'anames', + 'SELECT id, name FROM annames WHERE name <> ''Anna''' + ), + true, + 'bag_ne(prepared, select)', + '', + '' +); + +SELECT * FROM check_test( + bag_ne( 'anames', 'expect' ), + false, + 'bag_ne(prepared, prepared) fail', + '', + '' +); + +SELECT * FROM check_test( + bag_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'bag_ne fail with column mismatch', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' +); + + +-- Handle pass with a dupe. +SELECT * FROM check_test( + bag_ne( + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (1, ''Anna''), (86, ''Angelina'')' + ), + true, + 'set_ne pass with dupe', + '', + '' +); + +-- Handle fail with column mismatch. +SELECT * FROM check_test( + bag_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'bag_ne fail with column mismatch', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' +); + +-- Handle falure due to column count mismatch. +SELECT * FROM check_test( + bag_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'bag_ne fail with different col counts', + '', + ' Columns differ between queries: + have: (integer) + want: (text,integer)' +); + /****************************************************************************/ -- Now test results_eq(). From 96aa704ed2cd425194734bb05f5ccd93b6d71864 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Jul 2009 23:44:36 -0700 Subject: [PATCH 0412/1195] More precise descriptions for --schema and --match Modified the summary documentation for the `--schema` and `--match` options to better reflect that they are for finding xUnit tests, rather than for finding pgTAP functions. --- Changes | 5 ++++- bin/pg_prove | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 7ec6ddf90c06..a113ddf9b733 100644 --- a/Changes +++ b/Changes @@ -11,7 +11,10 @@ Revision history for pgTAP * Fixed capitalization of `TAP::Harness` in the `Makefile`. Thanks to Quinn Weaver. * Fixed a syntax error that caused uninstall_pgtap.sql to fail on older - PostgreSQL versions (fix by Quinn Weaver) + PostgreSQL versions (fix by Quinn Weaver). +* Modified the summary documentation for the `--schema` and `--match` options + to better reflect that they are for finding xUnit tests, rather than for + finding pgTAP functions. 0.21 2009-05-29T00:04:31 ------------------------- diff --git a/bin/pg_prove b/bin/pg_prove index 35a2f70eb4f0..71a2c189a16d 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -221,9 +221,9 @@ and I with “test,” run the tests like so: -p --port PORT Port to which to connect. -P --pset OPTION=VALUE Set psql printing option. -v --verbose Display output of test scripts while running them. - -r --runtests Run tests using C. - -s --schema Schema in which to find test functions. - -x --match Regular expression to find test functions. + -r --runtests Run xUnit test using C. + -s --schema Schema in which to find xUnit tests. + -x --match Regular expression to find xUnit tests. -t --timer Print elapsed time after each test file. -c --color Display colored test ouput. --nocolor Do not display colored test ouput. From a5ec23c5e3e1ea5903b3b5d5b178776c4698e808 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Jul 2009 00:42:14 -0700 Subject: [PATCH 0413/1195] Added array argument methods for set and bag comparison functions. --- README.pgtap | 23 +- expected/resultset.out | 332 ++++++++++++++++++----------- pgtap.sql.in | 107 +++++++++- sql/resultset.sql | 465 +++++++++++++++++++++++++++++++++++------ 4 files changed, 727 insertions(+), 200 deletions(-) diff --git a/README.pgtap b/README.pgtap index 073f771c6408..dc21940d2cba 100644 --- a/README.pgtap +++ b/README.pgtap @@ -854,6 +854,8 @@ to data types on 8.3. ### `set_eq( sql, sql, description )` ### ### `set_eq( sql, sql )` ### +### `set_eq( sql, array, description )` ### +### `set_eq( sql, array )` ### PREPARE testq AS SELECT * FROM users('a%'); PREPARE expect AS SELECT * FROM USERS where name LIKE 'a%'; @@ -866,8 +868,16 @@ of duplicates or ordering, a `set_eq()` test will pass. The SQL arguments can be the names of prepared statements or strings containing an SQL query (see the [summary](#Submit+Your+Query) for query -argument details), or even one of each. In whatever case, a failing test -will yield useful diagnostics, such as: +argument details), or even one of each. If the results returned by the first +argument consist of a single column, the second argument may be an array: + + SELECT set_eq( + 'SELECT * FROM active_user_ids()', + ARRAY[ 2, 3, 4, 5] + ); + +In whatever case you choose to pass arguments, a failing test will yield +useful diagnostics, such as: # Failed test 146 # Extra records: @@ -894,6 +904,8 @@ This of course extends to sets with different numbers of columns: ### `bag_eq( sql, sql, description )` ### ### `bag_eq( sql, sql )` ### +### `bag_eq( sql, array, description )` ### +### `bag_eq( sql, array )` ### PREPARE testq AS SELECT * FROM users('a%'); PREPARE expect AS SELECT * FROM USERS where name LIKE 'a%'; @@ -911,6 +923,8 @@ utility of its diagnostics. ### `set_ne( sql, sql, description )` ### ### `set_ne( sql, sql )` ### +### `set_ne( sql, array, description )` ### +### `set_ne( sql, array )` ### PREPARE testq AS SELECT * FROM users('a%'); PREPARE expect AS SELECT * FROM USERS where name LIKE 'b%'; @@ -926,6 +940,8 @@ orders. ### `bag_ne( sql, sql, description )` ### ### `bag_ne( sql, sql )` ### +### `bag_ne( sql, array, description )` ### +### `bag_ne( sql, array )` ### PREPARE testq AS SELECT * FROM users('a%'); PREPARE expect AS SELECT * FROM USERS where name LIKE 'b%'; @@ -3703,8 +3719,7 @@ To Do + `sequence_increments_by()` + `sequence_starts_at()` + `sequence_cycles()` -* Useful result tsting functions to consider adding: - + `col_eq()` +* Useful result testing functions to consider adding: + `row_eq()` + `rowtype_is()` diff --git a/expected/resultset.out b/expected/resultset.out index e52ca0f45cac..d0459deea1ab 100644 --- a/expected/resultset.out +++ b/expected/resultset.out @@ -1,5 +1,5 @@ \unset ECHO -1..313 +1..391 ok 1 - Should create temp table with simple query ok 2 - Table __foonames__ should exist ok 3 - Should create a temp table for a prepared statement @@ -7,87 +7,87 @@ ok 4 - Table __somenames__ should exist ok 5 - Should create a temp table for a prepared statement with space ok 6 - Table __somenames__ should exist ok 7 - Table __spacenames__ should exist -ok 8 - simple set test should pass -ok 9 - simple set test should have the proper description -ok 10 - simple set test should have the proper diagnostics -ok 11 - simple set test without descr should pass -ok 12 - simple set test without descr should have the proper description -ok 13 - simple set test without descr should have the proper diagnostics -ok 14 - execute set test should pass -ok 15 - execute set test should have the proper description -ok 16 - execute set test should have the proper diagnostics -ok 17 - select set test should pass -ok 18 - select set test should have the proper description -ok 19 - select set test should have the proper diagnostics -ok 20 - dupe rows ignored in set test should pass -ok 21 - dupe rows ignored in set test should have the proper description -ok 22 - dupe rows ignored in set test should have the proper diagnostics -ok 23 - fail with extra record should fail -ok 24 - fail with extra record should have the proper description -ok 25 - fail with extra record should have the proper diagnostics -ok 26 - fail with 2 extra records should fail -ok 27 - fail with 2 extra records should have the proper description -ok 28 - fail with 2 extra records should have the proper diagnostics -ok 29 - fail with missing record should fail -ok 30 - fail with missing record should have the proper description -ok 31 - fail with missing record should have the proper diagnostics -ok 32 - fail with 2 missing records should fail -ok 33 - fail with 2 missing records should have the proper description -ok 34 - fail with 2 missing records should have the proper diagnostics -ok 35 - fail with extra and missing should fail -ok 36 - fail with extra and missing should have the proper description -ok 37 - fail with extra and missing should have the proper diagnostics -ok 38 - fail with 2 extras and 2 missings should fail -ok 39 - fail with 2 extras and 2 missings should have the proper description -ok 40 - fail with 2 extras and 2 missings should have the proper diagnostics -ok 41 - fail with column mismatch should fail -ok 42 - fail with column mismatch should have the proper description -ok 43 - fail with column mismatch should have the proper diagnostics -ok 44 - fail with different col counts should fail -ok 45 - fail with different col counts should have the proper description -ok 46 - fail with different col counts should have the proper diagnostics -ok 47 - simple bag test should pass -ok 48 - simple bag test should have the proper description -ok 49 - simple bag test should have the proper diagnostics -ok 50 - simple bag test without descr should pass -ok 51 - simple bag test without descr should have the proper description -ok 52 - simple bag test without descr should have the proper diagnostics -ok 53 - execute bag test should pass -ok 54 - execute bag test should have the proper description -ok 55 - execute bag test should have the proper diagnostics -ok 56 - select bag test should pass -ok 57 - select bag test should have the proper description -ok 58 - select bag test should have the proper diagnostics -ok 59 - bag test with dupes should pass -ok 60 - bag test with dupes should have the proper description -ok 61 - bag test with dupes should have the proper diagnostics -ok 62 - fail with extra record should fail -ok 63 - fail with extra record should have the proper description -ok 64 - fail with extra record should have the proper diagnostics -ok 65 - fail with 2 extra records should fail -ok 66 - fail with 2 extra records should have the proper description -ok 67 - fail with 2 extra records should have the proper diagnostics -ok 68 - fail with missing record should fail -ok 69 - fail with missing record should have the proper description -ok 70 - fail with missing record should have the proper diagnostics -ok 71 - fail with 2 missing records should fail -ok 72 - fail with 2 missing records should have the proper description -ok 73 - fail with 2 missing records should have the proper diagnostics -ok 74 - fail with extra and missing should fail -ok 75 - fail with extra and missing should have the proper description -ok 76 - fail with extra and missing should have the proper diagnostics -ok 77 - fail with 2 extras and 2 missings should fail -ok 78 - fail with 2 extras and 2 missings should have the proper description -ok 79 - fail with 2 extras and 2 missings should have the proper diagnostics -ok 80 - fail with column mismatch should fail -ok 81 - fail with column mismatch should have the proper description -ok 82 - fail with column mismatch should have the proper diagnostics -ok 83 - fail with different col counts should fail -ok 84 - fail with different col counts should have the proper description -ok 85 - fail with different col counts should have the proper diagnostics -ok 86 - bag fail with missing dupe should fail -ok 87 - bag fail with missing dupe should have the proper description -ok 88 - bag fail with missing dupe should have the proper diagnostics +ok 8 - set_eq(prepared, prepared, desc) should pass +ok 9 - set_eq(prepared, prepared, desc) should have the proper description +ok 10 - set_eq(prepared, prepared, desc) should have the proper diagnostics +ok 11 - set_eq(prepared, prepared) should pass +ok 12 - set_eq(prepared, prepared) should have the proper description +ok 13 - set_eq(prepared, prepared) should have the proper diagnostics +ok 14 - set_eq(execute, execute, desc) should pass +ok 15 - set_eq(execute, execute, desc) should have the proper description +ok 16 - set_eq(execute, execute, desc) should have the proper diagnostics +ok 17 - set_eq(select, select) should pass +ok 18 - set_eq(select, select) should have the proper description +ok 19 - set_eq(select, select) should have the proper diagnostics +ok 20 - set_eq(values, dupe values) should pass +ok 21 - set_eq(values, dupe values) should have the proper description +ok 22 - set_eq(values, dupe values) should have the proper diagnostics +ok 23 - set_eq(prepared, select) fail extra should fail +ok 24 - set_eq(prepared, select) fail extra should have the proper description +ok 25 - set_eq(prepared, select) fail extra should have the proper diagnostics +ok 26 - set_eq(prepared, select) fail extras should fail +ok 27 - set_eq(prepared, select) fail extras should have the proper description +ok 28 - set_eq(prepared, select) fail extras should have the proper diagnostics +ok 29 - set_eq(select, prepared) fail missing should fail +ok 30 - set_eq(select, prepared) fail missing should have the proper description +ok 31 - set_eq(select, prepared) fail missing should have the proper diagnostics +ok 32 - set_eq(select, prepared) fail missings should fail +ok 33 - set_eq(select, prepared) fail missings should have the proper description +ok 34 - set_eq(select, prepared) fail missings should have the proper diagnostics +ok 35 - set_eq(select, select) fail extra & missing should fail +ok 36 - set_eq(select, select) fail extra & missing should have the proper description +ok 37 - set_eq(select, select) fail extra & missing should have the proper diagnostics +ok 38 - set_eq(select, select) fail extras & missings should fail +ok 39 - set_eq(select, select) fail extras & missings should have the proper description +ok 40 - set_eq(select, select) fail extras & missings should have the proper diagnostics +ok 41 - set_eq(values, values) fail mismatch should fail +ok 42 - set_eq(values, values) fail mismatch should have the proper description +ok 43 - set_eq(values, values) fail mismatch should have the proper diagnostics +ok 44 - set_eq(values, values) fail column count should fail +ok 45 - set_eq(values, values) fail column count should have the proper description +ok 46 - set_eq(values, values) fail column count should have the proper diagnostics +ok 47 - bag_eq(prepared, prepared, desc) should pass +ok 48 - bag_eq(prepared, prepared, desc) should have the proper description +ok 49 - bag_eq(prepared, prepared, desc) should have the proper diagnostics +ok 50 - bag_eq(prepared, prepared) should pass +ok 51 - bag_eq(prepared, prepared) should have the proper description +ok 52 - bag_eq(prepared, prepared) should have the proper diagnostics +ok 53 - bag_eq(execute, execute) should pass +ok 54 - bag_eq(execute, execute) should have the proper description +ok 55 - bag_eq(execute, execute) should have the proper diagnostics +ok 56 - bag_eq(select, select) should pass +ok 57 - bag_eq(select, select) should have the proper description +ok 58 - bag_eq(select, select) should have the proper diagnostics +ok 59 - bag_eq(dupe values, dupe values) should pass +ok 60 - bag_eq(dupe values, dupe values) should have the proper description +ok 61 - bag_eq(dupe values, dupe values) should have the proper diagnostics +ok 62 - bag_eq(prepared, select) fail extra should fail +ok 63 - bag_eq(prepared, select) fail extra should have the proper description +ok 64 - bag_eq(prepared, select) fail extra should have the proper diagnostics +ok 65 - bag_eq(prepared, select) fail extras should fail +ok 66 - bag_eq(prepared, select) fail extras should have the proper description +ok 67 - bag_eq(prepared, select) fail extras should have the proper diagnostics +ok 68 - bag_eq(select, prepared) fail missing should fail +ok 69 - bag_eq(select, prepared) fail missing should have the proper description +ok 70 - bag_eq(select, prepared) fail missing should have the proper diagnostics +ok 71 - bag_eq(select, prepared) fail missings should fail +ok 72 - bag_eq(select, prepared) fail missings should have the proper description +ok 73 - bag_eq(select, prepared) fail missings should have the proper diagnostics +ok 74 - bag_eq(select, select) fail extra & missing should fail +ok 75 - bag_eq(select, select) fail extra & missing should have the proper description +ok 76 - bag_eq(select, select) fail extra & missing should have the proper diagnostics +ok 77 - bag_eq(select, select) fail extras & missings should fail +ok 78 - bag_eq(select, select) fail extras & missings should have the proper description +ok 79 - bag_eq(select, select) fail extras & missings should have the proper diagnostics +ok 80 - bag_eq(values, values) fail mismatch should fail +ok 81 - bag_eq(values, values) fail mismatch should have the proper description +ok 82 - bag_eq(values, values) fail mismatch should have the proper diagnostics +ok 83 - bag_eq(values, values) fail column count should fail +ok 84 - bag_eq(values, values) fail column count should have the proper description +ok 85 - bag_eq(values, values) fail column count should have the proper diagnostics +ok 86 - bag_eq(values, values) fail missing dupe should fail +ok 87 - bag_eq(values, values) fail missing dupe should have the proper description +ok 88 - bag_eq(values, values) fail missing dupe should have the proper diagnostics ok 89 - set_ne(prepared, select, desc) should pass ok 90 - set_ne(prepared, select, desc) should have the proper description ok 91 - set_ne(prepared, select, desc) should have the proper diagnostics @@ -127,51 +127,51 @@ ok 124 - bag_ne fail with column mismatch should have the proper diagnostics ok 125 - bag_ne fail with different col counts should fail ok 126 - bag_ne fail with different col counts should have the proper description ok 127 - bag_ne fail with different col counts should have the proper diagnostics -ok 128 - simple results test should pass -ok 129 - simple results test should have the proper description -ok 130 - simple results test should have the proper diagnostics -ok 131 - simple results test without desc should pass -ok 132 - simple results test without desc should have the proper description -ok 133 - simple results test without desc should have the proper diagnostics -ok 134 - execute results test should pass -ok 135 - execute results test should have the proper description -ok 136 - execute results test should have the proper diagnostics -ok 137 - select results test should pass -ok 138 - select results test should have the proper description -ok 139 - select results test should have the proper diagnostics -ok 140 - results test with dupes should pass -ok 141 - results test with dupes should have the proper description -ok 142 - results test with dupes should have the proper diagnostics -ok 143 - results test with nulls should pass -ok 144 - results test with nulls should have the proper description -ok 145 - results test with nulls should have the proper diagnostics -ok 146 - fail with extra record should fail -ok 147 - fail with extra record should have the proper description -ok 148 - fail with extra record should have the proper diagnostics -ok 149 - fail with missing record should fail -ok 150 - fail with missing record should have the proper description -ok 151 - fail with missing record should have the proper diagnostics -ok 152 - fail with missing record should fail -ok 153 - fail with missing record should have the proper description -ok 154 - fail with missing record should have the proper diagnostics -ok 155 - results fail with missing dupe should fail -ok 156 - results fail with missing dupe should have the proper description -ok 157 - results fail with missing dupe should have the proper diagnostics -ok 158 - fail with NULL should fail -ok 159 - fail with NULL should have the proper description -ok 160 - fail with NULL should have the proper diagnostics -ok 161 - fail with column mismatch should fail -ok 162 - fail with column mismatch should have the proper description -ok 163 - fail with column mismatch should have the proper diagnostics -ok 164 - fail with subtle column mismatch should fail -ok 165 - fail with subtle column mismatch should have the proper description -ok 166 - fail with subtle column mismatch should have the proper diagnostics -ok 167 - fail with different col counts should fail -ok 168 - fail with different col counts should have the proper description -ok 169 - fail with different col counts should have the proper diagnostics -ok 170 - simple results with cursors should pass -ok 171 - simple results with cursors should have the proper description -ok 172 - simple results with cursors should have the proper diagnostics +ok 128 - results_eq(prepared, prepared, desc) should pass +ok 129 - results_eq(prepared, prepared, desc) should have the proper description +ok 130 - results_eq(prepared, prepared, desc) should have the proper diagnostics +ok 131 - results_eq(prepared, prepared) should pass +ok 132 - results_eq(prepared, prepared) should have the proper description +ok 133 - results_eq(prepared, prepared) should have the proper diagnostics +ok 134 - results_eq(execute, execute) should pass +ok 135 - results_eq(execute, execute) should have the proper description +ok 136 - results_eq(execute, execute) should have the proper diagnostics +ok 137 - results_eq(select, select) should pass +ok 138 - results_eq(select, select) should have the proper description +ok 139 - results_eq(select, select) should have the proper diagnostics +ok 140 - results_eq(dupe values, dupe values) should pass +ok 141 - results_eq(dupe values, dupe values) should have the proper description +ok 142 - results_eq(dupe values, dupe values) should have the proper diagnostics +ok 143 - results_eq(values with null, values with null) should pass +ok 144 - results_eq(values with null, values with null) should have the proper description +ok 145 - results_eq(values with null, values with null) should have the proper diagnostics +ok 146 - results_eq(prepared, select) fail should fail +ok 147 - results_eq(prepared, select) fail should have the proper description +ok 148 - results_eq(prepared, select) fail should have the proper diagnostics +ok 149 - results_eq(select, prepared) fail missing last row should fail +ok 150 - results_eq(select, prepared) fail missing last row should have the proper description +ok 151 - results_eq(select, prepared) fail missing last row should have the proper diagnostics +ok 152 - results_eq(prepared, select) fail missing first row should fail +ok 153 - results_eq(prepared, select) fail missing first row should have the proper description +ok 154 - results_eq(prepared, select) fail missing first row should have the proper diagnostics +ok 155 - results_eq(values dupe, values) should fail +ok 156 - results_eq(values dupe, values) should have the proper description +ok 157 - results_eq(values dupe, values) should have the proper diagnostics +ok 158 - results_eq(values null, values) should fail +ok 159 - results_eq(values null, values) should have the proper description +ok 160 - results_eq(values null, values) should have the proper diagnostics +ok 161 - results_eq(values, values) mismatch should fail +ok 162 - results_eq(values, values) mismatch should have the proper description +ok 163 - results_eq(values, values) mismatch should have the proper diagnostics +ok 164 - results_eq(values, values) subtle mismatch should fail +ok 165 - results_eq(values, values) subtle mismatch should have the proper description +ok 166 - results_eq(values, values) subtle mismatch should have the proper diagnostics +ok 167 - results_eq(values, values) fail column count should fail +ok 168 - results_eq(values, values) fail column count should have the proper description +ok 169 - results_eq(values, values) fail column count should have the proper diagnostics +ok 170 - results_eq(cursor, cursor) should pass +ok 171 - results_eq(cursor, cursor) should have the proper description +ok 172 - results_eq(cursor, cursor) should have the proper diagnostics ok 173 - results_eq(cursor, prepared) should pass ok 174 - results_eq(cursor, prepared) should have the proper description ok 175 - results_eq(cursor, prepared) should have the proper diagnostics @@ -313,3 +313,81 @@ ok 310 - bag_hasnt( dupes, dupes ) should have the proper diagnostics ok 311 - bag_hasnt( value, dupes ) should fail ok 312 - bag_hasnt( value, dupes ) should have the proper description ok 313 - bag_hasnt( value, dupes ) should have the proper diagnostics +ok 314 - set_eq(sql, array, desc) should pass +ok 315 - set_eq(sql, array, desc) should have the proper description +ok 316 - set_eq(sql, array, desc) should have the proper diagnostics +ok 317 - set_eq(sql, array) should pass +ok 318 - set_eq(sql, array) should have the proper description +ok 319 - set_eq(sql, array) should have the proper diagnostics +ok 320 - set_eq(sql, dupe array) should pass +ok 321 - set_eq(sql, dupe array) should have the proper description +ok 322 - set_eq(sql, dupe array) should have the proper diagnostics +ok 323 - set_eq(sql, array) extra record should fail +ok 324 - set_eq(sql, array) extra record should have the proper description +ok 325 - set_eq(sql, array) extra record should have the proper diagnostics +ok 326 - set_eq(sql, array) missing record should fail +ok 327 - set_eq(sql, array) missing record should have the proper description +ok 328 - set_eq(sql, array) missing record should have the proper diagnostics +ok 329 - set_eq(sql, array) incompatible types should fail +ok 330 - set_eq(sql, array) incompatible types should have the proper description +ok 331 - set_eq(sql, array) incompatible types should have the proper diagnostics +ok 332 - set_eq(sql, array) incompatible types should fail +ok 333 - set_eq(sql, array) incompatible types should have the proper description +ok 334 - set_eq(sql, array) incompatible types should have the proper diagnostics +ok 335 - bag_eq(sql, array, desc) should pass +ok 336 - bag_eq(sql, array, desc) should have the proper description +ok 337 - bag_eq(sql, array, desc) should have the proper diagnostics +ok 338 - bag_eq(sql, array) should pass +ok 339 - bag_eq(sql, array) should have the proper description +ok 340 - bag_eq(sql, array) should have the proper diagnostics +ok 341 - bag_eq(sql, dupe array) fail should fail +ok 342 - bag_eq(sql, dupe array) fail should have the proper description +ok 343 - bag_eq(sql, dupe array) fail should have the proper diagnostics +ok 344 - bag_eq(sql, array) extra record should fail +ok 345 - bag_eq(sql, array) extra record should have the proper description +ok 346 - bag_eq(sql, array) extra record should have the proper diagnostics +ok 347 - bag_eq(sql, array) missing record should fail +ok 348 - bag_eq(sql, array) missing record should have the proper description +ok 349 - bag_eq(sql, array) missing record should have the proper diagnostics +ok 350 - bag_eq(sql, array) incompatible types should fail +ok 351 - bag_eq(sql, array) incompatible types should have the proper description +ok 352 - bag_eq(sql, array) incompatible types should have the proper diagnostics +ok 353 - bag_eq(sql, array) incompatible types should fail +ok 354 - bag_eq(sql, array) incompatible types should have the proper description +ok 355 - bag_eq(sql, array) incompatible types should have the proper diagnostics +ok 356 - set_ne(sql, array, desc) should pass +ok 357 - set_ne(sql, array, desc) should have the proper description +ok 358 - set_ne(sql, array, desc) should have the proper diagnostics +ok 359 - set_ne(sql, array) should pass +ok 360 - set_ne(sql, array) should have the proper description +ok 361 - set_ne(sql, array) should have the proper diagnostics +ok 362 - set_ne(sql, array) fail should fail +ok 363 - set_ne(sql, array) fail should have the proper description +ok 364 - set_ne(sql, array) fail should have the proper diagnostics +ok 365 - set_ne(sql, dupes array) fail should fail +ok 366 - set_ne(sql, dupes array) fail should have the proper description +ok 367 - set_ne(sql, dupes array) fail should have the proper diagnostics +ok 368 - set_ne(sql, array) incompatible types should fail +ok 369 - set_ne(sql, array) incompatible types should have the proper description +ok 370 - set_ne(sql, array) incompatible types should have the proper diagnostics +ok 371 - set_ne(sql, array) incompatible types should fail +ok 372 - set_ne(sql, array) incompatible types should have the proper description +ok 373 - set_ne(sql, array) incompatible types should have the proper diagnostics +ok 374 - bag_ne(sql, array, desc) should pass +ok 375 - bag_ne(sql, array, desc) should have the proper description +ok 376 - bag_ne(sql, array, desc) should have the proper diagnostics +ok 377 - bag_ne(sql, array) should pass +ok 378 - bag_ne(sql, array) should have the proper description +ok 379 - bag_ne(sql, array) should have the proper diagnostics +ok 380 - bag_ne(sql, array) fail should fail +ok 381 - bag_ne(sql, array) fail should have the proper description +ok 382 - bag_ne(sql, array) fail should have the proper diagnostics +ok 383 - bag_ne(sql, dupes array) should pass +ok 384 - bag_ne(sql, dupes array) should have the proper description +ok 385 - bag_ne(sql, dupes array) should have the proper diagnostics +ok 386 - bag_ne(sql, array) incompatible types should fail +ok 387 - bag_ne(sql, array) incompatible types should have the proper description +ok 388 - bag_ne(sql, array) incompatible types should have the proper diagnostics +ok 389 - bag_ne(sql, array) incompatible types should fail +ok 390 - bag_ne(sql, array) incompatible types should have the proper description +ok 391 - bag_ne(sql, array) incompatible types should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 2ad1fd15ab79..160f07738c11 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -5774,6 +5774,17 @@ BEGIN 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( @@ -5788,11 +5799,11 @@ RETURNS TEXT AS $$ ), ','); $$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT ) +CREATE OR REPLACE FUNCTION _docomp( TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE - have TEXT := _temptable( $1, '__taphave__' ); - want TEXT := _temptable( $2, '__tapwant__' ); + have ALIAS FOR $1; + want ALIAS FOR $2; extras TEXT[] := '{}'; missing TEXT[] := '{}'; res BOOLEAN := TRUE; @@ -5848,6 +5859,24 @@ BEGIN 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 $$ @@ -5860,6 +5889,18 @@ 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 $$ @@ -5872,11 +5913,23 @@ RETURNS TEXT AS $$ SELECT _relcomp( $1, $2, NULL::text, 'ALL ' ); $$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION _relne( TEXT, TEXT, TEXT, TEXT ) +-- 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 TEXT := _temptable( $1, '__taphave__' ); - want TEXT := _temptable( $2, '__tapwant__' ); + have ALIAS FOR $1; + want ALIAS FOR $2; extras TEXT[] := '{}'; missing TEXT[] := '{}'; res BOOLEAN := TRUE; @@ -5912,6 +5965,24 @@ BEGIN 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 $$ @@ -5924,6 +5995,18 @@ 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 $$ @@ -5936,6 +6019,18 @@ 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 diff --git a/sql/resultset.sql b/sql/resultset.sql index a5aef2e0b51a..b45a25c76ecc 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(313); +SELECT plan(391); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -257,7 +257,7 @@ SELECT has_table('__spacenames__' ); SELECT * FROM check_test( set_eq( 'anames', 'expect', 'whatever' ), true, - 'simple set test', + 'set_eq(prepared, prepared, desc)', 'whatever', '' ); @@ -265,7 +265,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( set_eq( 'anames', 'expect' ), true, - 'simple set test without descr', + 'set_eq(prepared, prepared)', '', '' ); @@ -274,7 +274,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( set_eq( 'EXECUTE anames', 'EXECUTE expect' ), true, - 'execute set test', + 'set_eq(execute, execute, desc)', '', '' ); @@ -286,7 +286,7 @@ SELECT * FROM check_test( 'SELECT id, name FROM annames' ), true, - 'select set test', + 'set_eq(select, select)', '', '' ); @@ -298,7 +298,7 @@ SELECT * FROM check_test( 'VALUES (1, ''Anna''), (1, ''Anna'')' ), true, - 'dupe rows ignored in set test', + 'set_eq(values, dupe values)', '', '' ); @@ -310,7 +310,7 @@ SELECT * FROM check_test( 'SELECT id, name FROM annames WHERE name <> ''Anna''' ), false, - 'fail with extra record', + 'set_eq(prepared, select) fail extra', '', ' Extra records: (44,Anna)' @@ -322,7 +322,7 @@ SELECT * FROM check_test( 'SELECT id, name FROM annames WHERE name NOT IN (''Anna'', ''Angelina'')' ), false, - 'fail with 2 extra records', + 'set_eq(prepared, select) fail extras', '', E' Extra records: \\((44,Anna|86,Angelina)\\) @@ -336,7 +336,7 @@ SELECT * FROM check_test( 'expect' ), false, - 'fail with missing record', + 'set_eq(select, prepared) fail missing', '', ' Missing records: (44,Anna)' @@ -348,7 +348,7 @@ SELECT * FROM check_test( 'expect' ), false, - 'fail with 2 missing records', + 'set_eq(select, prepared) fail missings', '', E' Missing records: \\((44,Anna|86,Angelina)\\) @@ -363,7 +363,7 @@ SELECT * FROM check_test( 'SELECT id, name FROM annames' ), false, - 'fail with extra and missing', + 'set_eq(select, select) fail extra & missing', '', ' Extra records: (1,Jacob) @@ -377,7 +377,7 @@ SELECT * FROM check_test( 'SELECT id, name FROM annames' ), false, - 'fail with 2 extras and 2 missings', + 'set_eq(select, select) fail extras & missings', '', E' Extra records: \\((1,Jacob|87,Jackson)\\) @@ -388,22 +388,22 @@ SELECT * FROM check_test( true ); --- Handle falure due to column mismatch. +-- Handle failure due to column mismatch. SELECT * FROM check_test( set_eq( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), false, - 'fail with column mismatch', + 'set_eq(values, values) fail mismatch', '', ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); --- Handle falure due to column count mismatch. +-- Handle failure due to column count mismatch. SELECT * FROM check_test( set_eq( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), false, - 'fail with different col counts', + 'set_eq(values, values) fail column count', '', ' Columns differ between queries: have: (integer) @@ -416,7 +416,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( bag_eq( 'anames', 'expect', 'whatever' ), true, - 'simple bag test', + 'bag_eq(prepared, prepared, desc)', 'whatever', '' ); @@ -424,7 +424,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( bag_eq( 'anames', 'expect' ), true, - 'simple bag test without descr', + 'bag_eq(prepared, prepared)', '', '' ); @@ -433,7 +433,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( bag_eq( 'EXECUTE anames', 'EXECUTE expect' ), true, - 'execute bag test', + 'bag_eq(execute, execute)', '', '' ); @@ -445,7 +445,7 @@ SELECT * FROM check_test( 'SELECT id, name FROM annames' ), true, - 'select bag test', + 'bag_eq(select, select)', '', '' ); @@ -457,7 +457,7 @@ SELECT * FROM check_test( 'VALUES (1, ''Anna''), (1, ''Anna''), (86, ''Angelina'')' ), true, - 'bag test with dupes', + 'bag_eq(dupe values, dupe values)', '', '' ); @@ -469,7 +469,7 @@ SELECT * FROM check_test( 'SELECT id, name FROM annames WHERE name <> ''Anna''' ), false, - 'fail with extra record', + 'bag_eq(prepared, select) fail extra', '', ' Extra records: (44,Anna)' @@ -481,7 +481,7 @@ SELECT * FROM check_test( 'SELECT id, name FROM annames WHERE name NOT IN (''Anna'', ''Angelina'')' ), false, - 'fail with 2 extra records', + 'bag_eq(prepared, select) fail extras', '', E' Extra records: \\((44,Anna|86,Angelina)\\) @@ -495,7 +495,7 @@ SELECT * FROM check_test( 'expect' ), false, - 'fail with missing record', + 'bag_eq(select, prepared) fail missing', '', ' Missing records: (44,Anna)' @@ -507,7 +507,7 @@ SELECT * FROM check_test( 'expect' ), false, - 'fail with 2 missing records', + 'bag_eq(select, prepared) fail missings', '', E' Missing records: \\((44,Anna|86,Angelina)\\) @@ -522,7 +522,7 @@ SELECT * FROM check_test( 'SELECT id, name FROM annames' ), false, - 'fail with extra and missing', + 'bag_eq(select, select) fail extra & missing', '', ' Extra records: (1,Jacob) @@ -536,7 +536,7 @@ SELECT * FROM check_test( 'SELECT id, name FROM annames' ), false, - 'fail with 2 extras and 2 missings', + 'bag_eq(select, select) fail extras & missings', '', E' Extra records: \\((1,Jacob|87,Jackson)\\) @@ -547,22 +547,22 @@ SELECT * FROM check_test( true ); --- Handle falure due to column mismatch. +-- Handle failure due to column mismatch. SELECT * FROM check_test( bag_eq( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), false, - 'fail with column mismatch', + 'bag_eq(values, values) fail mismatch', '', ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); --- Handle falure due to column count mismatch. +-- Handle failure due to column count mismatch. SELECT * FROM check_test( bag_eq( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), false, - 'fail with different col counts', + 'bag_eq(values, values) fail column count', '', ' Columns differ between queries: have: (integer) @@ -576,7 +576,7 @@ SELECT * FROM check_test( 'VALUES (1, ''Anna''), (86, ''Angelina'')' ), false, - 'bag fail with missing dupe', + 'bag_eq(values, values) fail missing dupe', '', ' Extra records: (1,Anna)' @@ -627,7 +627,7 @@ SELECT * FROM check_test( want: (text,integer)' ); --- Handle falure due to column count mismatch. +-- Handle failure due to column count mismatch. SELECT * FROM check_test( set_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), false, @@ -718,7 +718,7 @@ SELECT * FROM check_test( want: (text,integer)' ); --- Handle falure due to column count mismatch. +-- Handle failure due to column count mismatch. SELECT * FROM check_test( bag_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), false, @@ -740,7 +740,7 @@ PREPARE expect_ord AS VALUES (11, 'Andrew'), (15, 'Anthony'), ( 44, 'Anna'), SELECT * FROM check_test( results_eq( 'anames_ord', 'expect_ord', 'whatever' ), true, - 'simple results test', + 'results_eq(prepared, prepared, desc)', 'whatever', '' ); @@ -748,7 +748,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( results_eq( 'anames_ord', 'expect_ord' ), true, - 'simple results test without desc', + 'results_eq(prepared, prepared)', '', '' ); @@ -757,7 +757,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( results_eq( 'EXECUTE anames_ord', 'EXECUTE expect_ord' ), true, - 'execute results test', + 'results_eq(execute, execute)', '', '' ); @@ -769,7 +769,7 @@ SELECT * FROM check_test( 'SELECT id, name FROM annames ORDER BY id' ), true, - 'select results test', + 'results_eq(select, select)', '', '' ); @@ -781,7 +781,7 @@ SELECT * FROM check_test( 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')' ), true, - 'results test with dupes', + 'results_eq(dupe values, dupe values)', '', '' ); @@ -793,7 +793,7 @@ SELECT * FROM check_test( 'VALUES (4, NULL), (86, ''Angelina''), (1, ''Anna'')' ), true, - 'results test with nulls', + 'results_eq(values with null, values with null)', '', '' ); @@ -805,7 +805,7 @@ SELECT * FROM check_test( 'SELECT id, name FROM annames WHERE name <> ''Anna''' ), false, - 'fail with extra record', + 'results_eq(prepared, select) fail', '', ' Results differ beginning at row 3: have: (44,Anna) @@ -819,7 +819,7 @@ SELECT * FROM check_test( 'anames_ord' ), false, - 'fail with missing record', + 'results_eq(select, prepared) fail missing last row', '', ' Results differ beginning at row 7: have: () @@ -833,7 +833,7 @@ SELECT * FROM check_test( 'SELECT id, name FROM annames WHERE name <> ''Antonio''' ), false, - 'fail with missing record', + 'results_eq(prepared, select) fail missing first row', '', ' Results differ beginning at row 7: have: (183,Antonio) @@ -848,7 +848,7 @@ SELECT * FROM check_test( 'VALUES (1, ''Anna''), (86, ''Angelina'')' ), false, - 'results fail with missing dupe', + 'results_eq(values dupe, values)', '', ' Results differ beginning at row 3: have: (1,Anna) @@ -862,7 +862,7 @@ SELECT * FROM check_test( 'VALUES (1, ''Anna''), (86, ''Angelina'')' ), false, - 'fail with NULL', + 'results_eq(values null, values)', '', ' Results differ beginning at row 1: have: (1,) @@ -870,27 +870,27 @@ SELECT * FROM check_test( ); --- Handle falure due to column mismatch. +-- Handle failure due to column mismatch. SELECT * FROM check_test( results_eq( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), false, - 'fail with column mismatch', + 'results_eq(values, values) mismatch', '', CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Columns differ between queries:' END || ' have: (1,foo) want: (foo,1)' ); --- Handle falure due to more subtle column mismatch, valid only on 8.4. +-- Handle failure due to more subtle column mismatch, valid only on 8.4. CREATE OR REPLACE FUNCTION subtlefail() RETURNS SETOF TEXT AS $$ DECLARE tap record; BEGIN IF pg_version_num() < 80400 THEN -- 8.3 and earlier cast records to text, so subtlety is out. - RETURN NEXT pass('fail with subtle column mismatch should fail'); - RETURN NEXT pass('fail with subtle column mismatch should have the proper description'); - RETURN NEXT pass('fail with subtle column mismatch should have the proper diagnostics'); + RETURN NEXT pass('results_eq(values, values) subtle mismatch should fail'); + RETURN NEXT pass('results_eq(values, values) subtle mismatch should have the proper description'); + RETURN NEXT pass('results_eq(values, values) subtle mismatch should have the proper diagnostics'); ELSE -- 8.4 does true record comparisions, yay! FOR tap IN SELECT * FROM check_test( @@ -899,7 +899,7 @@ BEGIN 'VALUES (1, ''foo''), (2, ''bar'')' ), false, - 'fail with subtle column mismatch', + 'results_eq(values, values) subtle mismatch', '', ' Columns differ between queries: have: (1,foo) @@ -912,11 +912,11 @@ END; $$ LANGUAGE plpgsql; SELECT * FROM subtlefail(); --- Handle falure due to column count mismatch. +-- Handle failure due to column count mismatch. SELECT * FROM check_test( results_eq( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), false, - 'fail with different col counts', + 'results_eq(values, values) fail column count', '', CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Columns differ between queries:' END || ' have: (1) @@ -930,7 +930,7 @@ DECLARE chave CURSOR FOR SELECT id, name from annames ORDER BY id; SELECT * FROM check_test( results_eq( 'cwant'::refcursor, 'chave'::refcursor ), true, - 'simple results with cursors', + 'results_eq(cursor, cursor)', '', '' ); @@ -1068,7 +1068,7 @@ SELECT * FROM check_test( true ); --- Handle falure due to column mismatch. +-- Handle failure due to column mismatch. SELECT * FROM check_test( set_has( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), false, @@ -1079,7 +1079,7 @@ SELECT * FROM check_test( want: (text,integer)' ); --- Handle falure due to column count mismatch. +-- Handle failure due to column count mismatch. SELECT * FROM check_test( set_has( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), false, @@ -1183,7 +1183,7 @@ SELECT * FROM check_test( true ); --- Handle falure due to column mismatch. +-- Handle failure due to column mismatch. SELECT * FROM check_test( bag_has( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), false, @@ -1194,7 +1194,7 @@ SELECT * FROM check_test( want: (text,integer)' ); --- Handle falure due to column count mismatch. +-- Handle failure due to column count mismatch. SELECT * FROM check_test( bag_has( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), false, @@ -1288,7 +1288,7 @@ SELECT * FROM check_test( true ); --- Handle falure due to column mismatch. +-- Handle failure due to column mismatch. SELECT * FROM check_test( set_hasnt( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), false, @@ -1299,7 +1299,7 @@ SELECT * FROM check_test( want: (text,integer)' ); --- Handle falure due to column count mismatch. +-- Handle failure due to column count mismatch. SELECT * FROM check_test( set_hasnt( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), false, @@ -1380,7 +1380,7 @@ SELECT * FROM check_test( true ); --- Handle falure due to column mismatch. +-- Handle failure due to column mismatch. SELECT * FROM check_test( bag_hasnt( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), false, @@ -1391,7 +1391,7 @@ SELECT * FROM check_test( want: (text,integer)' ); --- Handle falure due to column count mismatch. +-- Handle failure due to column count mismatch. SELECT * FROM check_test( bag_hasnt( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), false, @@ -1429,6 +1429,345 @@ SELECT * FROM check_test( (44,Anna)' ); + +/****************************************************************************/ +-- Test set_eq() with an array argument. +SELECT * FROM check_test( + set_eq( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel' ], + 'whatever' + ), + true, + 'set_eq(sql, array, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + set_eq( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + true, + 'set_eq(sql, array)', + '', + '' +); + +SELECT * FROM check_test( + set_eq( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel', 'Andrew', 'Anna' ] + ), + true, + 'set_eq(sql, dupe array)', + '', + '' +); + +-- Fail with an extra record. +SELECT * FROM check_test( + set_eq( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + false, + 'set_eq(sql, array) extra record', + '', + ' Extra records: + (Anthony)' +); + + +-- Fail with an extra record. +SELECT * FROM check_test( + set_eq( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna', 'Anthony', 'Alan', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + false, + 'set_eq(sql, array) missing record', + '', + ' Missing records: + (Alan)' +); + +-- Fail with incompatible columns. +SELECT * FROM check_test( + set_eq( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY[1, 2, 3] + ), + false, + 'set_eq(sql, array) incompatible types', + '', + ' Columns differ between queries: + have: (text) + want: (integer)' +); + +-- Fail with invalid column counts. +SELECT * FROM check_test( + set_eq( + 'anames', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + false, + 'set_eq(sql, array) incompatible types', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text)' +); + +/****************************************************************************/ +-- Test bag_eq() with an array argument. +SELECT * FROM check_test( + bag_eq( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel' ], + 'whatever' + ), + true, + 'bag_eq(sql, array, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + bag_eq( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + true, + 'bag_eq(sql, array)', + '', + '' +); + +SELECT * FROM check_test( + bag_eq( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel', 'Anna' ] + ), + false, + 'bag_eq(sql, dupe array) fail', + '', + ' Missing records: + (Anna)' +); + +-- Fail with an extra record. +SELECT * FROM check_test( + bag_eq( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + false, + 'bag_eq(sql, array) extra record', + '', + ' Extra records: + (Anthony)' +); + + +-- Fail with an extra record. +SELECT * FROM check_test( + bag_eq( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna', 'Anthony', 'Alan', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + false, + 'bag_eq(sql, array) missing record', + '', + ' Missing records: + (Alan)' +); + +-- Fail with incompatible columns. +SELECT * FROM check_test( + bag_eq( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY[1, 2, 3] + ), + false, + 'bag_eq(sql, array) incompatible types', + '', + ' Columns differ between queries: + have: (text) + want: (integer)' +); + +-- Fail with invalid column counts. +SELECT * FROM check_test( + bag_eq( + 'anames', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + false, + 'bag_eq(sql, array) incompatible types', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text)' +); + + +/****************************************************************************/ +-- Test set_ne() with an array argument. +SELECT * FROM check_test( + set_ne( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna' ], + 'whatever' + ), + true, + 'set_ne(sql, array, desc)', + 'whatever', + '' +); + + +SELECT * FROM check_test( + set_ne( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna' ] + ), + true, + 'set_ne(sql, array)', + '', + '' +); + +SELECT * FROM check_test( + set_ne( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + false, + 'set_ne(sql, array) fail', + '', + '' +); + +-- Fail with dupes. +SELECT * FROM check_test( + set_ne( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel', 'Anna' ] + ), + false, + 'set_ne(sql, dupes array) fail', + '', + '' +); + +-- Fail with incompatible columns. +SELECT * FROM check_test( + set_ne( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY[1, 2, 3] + ), + false, + 'set_ne(sql, array) incompatible types', + '', + ' Columns differ between queries: + have: (text) + want: (integer)' +); + +-- Fail with invalid column counts. +SELECT * FROM check_test( + set_ne( + 'anames', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + false, + 'set_ne(sql, array) incompatible types', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text)' +); + +/****************************************************************************/ +-- Test bag_ne() with an array argument. +SELECT * FROM check_test( + bag_ne( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna' ], + 'whatever' + ), + true, + 'bag_ne(sql, array, desc)', + 'whatever', + '' +); + + +SELECT * FROM check_test( + bag_ne( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna' ] + ), + true, + 'bag_ne(sql, array)', + '', + '' +); + +SELECT * FROM check_test( + bag_ne( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + false, + 'bag_ne(sql, array) fail', + '', + '' +); + +-- Pass with dupes. +SELECT * FROM check_test( + bag_ne( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel', 'Anna' ] + ), + true, + 'bag_ne(sql, dupes array)', + '', + '' +); + +-- Fail with incompatible columns. +SELECT * FROM check_test( + bag_ne( + 'SELECT name FROM names WHERE name like ''An%''', + ARRAY[1, 2, 3] + ), + false, + 'bag_ne(sql, array) incompatible types', + '', + ' Columns differ between queries: + have: (text) + want: (integer)' +); + +-- Fail with invalid column counts. +SELECT * FROM check_test( + bag_ne( + 'anames', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + false, + 'bag_ne(sql, array) incompatible types', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text)' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 025f63aed07e26da884571a86a5855b7c78f7f54 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Jul 2009 01:44:10 -0700 Subject: [PATCH 0414/1195] Added array support for the second argument to `results_eq()`. --- README.pgtap | 12 +++++++ expected/resultset.out | 20 ++++++++++- pgtap.sql.in | 45 ++++++++++++++++++++++++ sql/resultset.sql | 79 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 154 insertions(+), 2 deletions(-) diff --git a/README.pgtap b/README.pgtap index dc21940d2cba..e3a9bb3e0db2 100644 --- a/README.pgtap +++ b/README.pgtap @@ -713,12 +713,16 @@ test? We've got your relation-testing functions right here. ### `results_eq( sql, sql, description )` ### ### `results_eq( sql, sql )` ### +### `results_eq( sql, array, description )` ### +### `results_eq( sql, array )` ### ### `results_eq( cursor, cursor, description )` ### ### `results_eq( cursor, cursor )` ### ### `results_eq( sql, cursor, description )` ### ### `results_eq( sql, cursor )` ### ### `results_eq( cursor, sql, description )` ### ### `results_eq( cursor, sql )` ### +### `results_eq( cursor, array, description )` ### +### `results_eq( cursor, array )` ### PREPARE users_test AS SELECT * FROM active_users(); PREPARE users_expect AS @@ -753,6 +757,14 @@ instead of a query, like so: 'active_users() should return active users' ); +If the results returned by the first argument consist of a single column, the +second argument may be an array: + + SELECT results_eq( + 'SELECT * FROM active_user_ids()', + ARRAY[ 2, 3, 4, 5] + ); + In general, the use of prepared statements is highly recommended to keep your test code SQLish (you can even use `VALUES` statements in prepared statements!). But note that, because `results_eq()` does a row-by-row diff --git a/expected/resultset.out b/expected/resultset.out index d0459deea1ab..401b24f39cb4 100644 --- a/expected/resultset.out +++ b/expected/resultset.out @@ -1,5 +1,5 @@ \unset ECHO -1..391 +1..409 ok 1 - Should create temp table with simple query ok 2 - Table __foonames__ should exist ok 3 - Should create a temp table for a prepared statement @@ -391,3 +391,21 @@ ok 388 - bag_ne(sql, array) incompatible types should have the proper diagnostic ok 389 - bag_ne(sql, array) incompatible types should fail ok 390 - bag_ne(sql, array) incompatible types should have the proper description ok 391 - bag_ne(sql, array) incompatible types should have the proper diagnostics +ok 392 - results_eq(prepared, array, desc) should pass +ok 393 - results_eq(prepared, array, desc) should have the proper description +ok 394 - results_eq(prepared, array, desc) should have the proper diagnostics +ok 395 - results_eq(prepared, array) should pass +ok 396 - results_eq(prepared, array) should have the proper description +ok 397 - results_eq(prepared, array) should have the proper diagnostics +ok 398 - results_eq(sql, array, desc) should pass +ok 399 - results_eq(sql, array, desc) should have the proper description +ok 400 - results_eq(sql, array, desc) should have the proper diagnostics +ok 401 - results_eq(sql, array, desc) should pass +ok 402 - results_eq(sql, array, desc) should have the proper description +ok 403 - results_eq(sql, array, desc) should have the proper diagnostics +ok 404 - results_eq(prepared, array) extra record should fail +ok 405 - results_eq(prepared, array) extra record should have the proper description +ok 406 - results_eq(prepared, array) extra record should have the proper diagnostics +ok 407 - results_eq(select, array) missing record should fail +ok 408 - results_eq(select, array) missing record should have the proper description +ok 409 - results_eq(select, array) missing record should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 160f07738c11..f7ddfec2e7c7 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -6188,6 +6188,30 @@ 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 $$ @@ -6227,3 +6251,24 @@ 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; diff --git a/sql/resultset.sql b/sql/resultset.sql index b45a25c76ecc..b08c9a1e8cc5 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(391); +SELECT plan(409); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -1768,6 +1768,83 @@ SELECT * FROM check_test( want: (text)' ); +/****************************************************************************/ +-- Now test results_eq() with an array argument. + +PREPARE anames_only AS SELECT name FROM names WHERE name like 'An%' ORDER BY name; + +SELECT * FROM check_test( + results_eq( + 'anames_only', + ARRAY['Andrea', 'Andrew', 'Angel', 'Angelina', 'Anna', 'Anthony', 'Antonio' ], + 'whatever' + ), + true, + 'results_eq(prepared, array, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + results_eq( + 'anames_only', + ARRAY['Andrea', 'Andrew', 'Angel', 'Angelina', 'Anna', 'Anthony', 'Antonio' ] + ), + true, + 'results_eq(prepared, array)', + '', + '' +); + +SELECT * FROM check_test( + results_eq( + 'SELECT name FROM names WHERE name like ''An%'' ORDER BY name', + ARRAY['Andrea', 'Andrew', 'Angel', 'Angelina', 'Anna', 'Anthony', 'Antonio' ], + 'whatever' + ), + true, + 'results_eq(sql, array, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + results_eq( + 'SELECT name FROM names WHERE name like ''An%'' ORDER BY name', + ARRAY['Andrea', 'Andrew', 'Angel', 'Angelina', 'Anna', 'Anthony', 'Antonio' ] + ), + true, + 'results_eq(sql, array, desc)', + '', + '' +); + +SELECT * FROM check_test( + results_eq( + 'anames_only', + ARRAY['Andrea', 'Andrew', 'Angel', 'Angelina', 'Anna', 'Anthony' ] + ), + false, + 'results_eq(prepared, array) extra record', + '', + ' Results differ beginning at row 7: + have: (Antonio) + want: ()' +); + +SELECT * FROM check_test( + results_eq( + 'SELECT name FROM names WHERE name like ''An%'' AND name <> ''Anna'' ORDER BY name', + ARRAY['Andrea', 'Andrew', 'Angel', 'Angelina', 'Anna', 'Anthony', 'Antonio' ] + ), + false, + 'results_eq(select, array) missing record', + '', + ' Results differ beginning at row 5: + have: (Anthony) + want: (Anna)' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From b78cc71902028937fbf517017aca0df424852bfc Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Jul 2009 01:46:03 -0700 Subject: [PATCH 0415/1195] Updated `uninstall_pgtap.sql.in` to pick up new array comparison functions. --- uninstall_pgtap.sql.in | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 2db4b4486165..d43a95a1054c 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,8 +1,12 @@ -- ## SET search_path TO TAPSCHEMA, public; +DROP FUNCTION results_eq( refcursor, anyarray ); +DROP FUNCTION results_eq( refcursor, anyarray, TEXT ); DROP FUNCTION results_eq( refcursor, TEXT ); DROP FUNCTION results_eq( refcursor, TEXT, TEXT ); DROP FUNCTION results_eq( TEXT, refcursor ); DROP FUNCTION results_eq( TEXT, refcursor, TEXT ); +DROP FUNCTION results_eq( TEXT, anyarray ); +DROP FUNCTION results_eq( TEXT, anyarray, TEXT ); DROP FUNCTION results_eq( TEXT, TEXT ); DROP FUNCTION results_eq( TEXT, TEXT, TEXT ); DROP FUNCTION results_eq( refcursor, refcursor ); @@ -16,12 +20,30 @@ DROP FUNCTION bag_has( TEXT, TEXT, TEXT ); DROP FUNCTION set_has( TEXT, TEXT ); DROP FUNCTION set_has( TEXT, TEXT, TEXT ); DROP FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION bag_ne( TEXT, anyarray ); +DROP FUNCTION bag_ne( TEXT, anyarray, TEXT ); +DROP FUNCTION bag_ne( TEXT, TEXT ); +DROP FUNCTION bag_ne( TEXT, TEXT, TEXT ); +DROP FUNCTION set_ne( TEXT, anyarray ); +DROP FUNCTION set_ne( TEXT, anyarray, TEXT ); +DROP FUNCTION set_ne( TEXT, TEXT ); +DROP FUNCTION set_ne( TEXT, TEXT, TEXT ); +DROP FUNCTION _relne( TEXT, anyarray, TEXT, TEXT ); +DROP FUNCTION _relne( TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION _do_ne( TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION bag_eq( TEXT, anyarray ); +DROP FUNCTION bag_eq( TEXT, anyarray, TEXT ); DROP FUNCTION bag_eq( TEXT, TEXT ); DROP FUNCTION bag_eq( TEXT, TEXT, TEXT ); +DROP FUNCTION set_eq( TEXT, anyarray ); +DROP FUNCTION set_eq( TEXT, anyarray, TEXT ); DROP FUNCTION set_eq( TEXT, TEXT ); DROP FUNCTION set_eq( TEXT, TEXT, TEXT ); +DROP FUNCTION _relcomp( TEXT, anyarray, TEXT, TEXT ); DROP FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT ); +DROP FUNCTION _docomp( TEXT, TEXT, TEXT, TEXT ); DROP FUNCTION _temptypes( TEXT ); +DROP FUNCTION _temptable ( anyarray, TEXT ); DROP FUNCTION _temptable ( TEXT, TEXT ); DROP FUNCTION runtests( ); DROP FUNCTION runtests( TEXT ); From adf699d1598da7aa8f0d7069b595755bac13d62f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 28 Jul 2009 14:50:33 -0700 Subject: [PATCH 0416/1195] Added results_ne(). Added `results_ne()` and fixed a bug in `results_ne()` where `NULL` columns were not properly respected. See [the blog entry](http://justatheory.com/computers/databases/postgresql/neither-null-nor-not-null.html) for more on this particular SQL pathology. --- Changes | 6 +- README.pgtap | 29 ++ expected/resultset.out | 596 +++++++++++++++++++++++------------------ pgtap.sql.in | 186 +++++++++++-- sql/resultset.sql | 293 ++++++++++++++++++-- uninstall_pgtap.sql.in | 12 + 6 files changed, 821 insertions(+), 301 deletions(-) diff --git a/Changes b/Changes index a113ddf9b733..c4910fda7a48 100644 --- a/Changes +++ b/Changes @@ -4,9 +4,9 @@ Revision history for pgTAP 0.22 ------------------------- * Fixed failing test on 8.4rc2. -* Added `results_eq(), `set_eq()`, `bag_eq()`, `set_ne()`, `bag_ne()`, - `set_has()`, `bag_has()`, `set_hasnt()`, and `bag_hasnt()` functions to test - query results. +* Added `results_eq(), `results_ne()`, `set_eq()`, `bag_eq()`, `set_ne()`, + `bag_ne()`, `set_has()`, `bag_has()`, `set_hasnt()`, and `bag_hasnt()` + functions to test query results. * Changed some internal queries to use explicit `JOIN`s. * Fixed capitalization of `TAP::Harness` in the `Makefile`. Thanks to Quinn Weaver. diff --git a/README.pgtap b/README.pgtap index e3a9bb3e0db2..92514c6709ae 100644 --- a/README.pgtap +++ b/README.pgtap @@ -864,6 +864,35 @@ text representation. For example, a `NULL` column will be equivalent to an empty string. The upshot: read failure diagnostics carefully and pay attention to data types on 8.3. +### `results_ne( sql, sql, description )` ### +### `results_ne( sql, sql )` ### +### `results_ne( sql, array, description )` ### +### `results_ne( sql, array )` ### +### `results_ne( cursor, cursor, description )` ### +### `results_ne( cursor, cursor )` ### +### `results_ne( sql, cursor, description )` ### +### `results_ne( sql, cursor )` ### +### `results_ne( cursor, sql, description )` ### +### `results_ne( cursor, sql )` ### +### `results_ne( cursor, array, description )` ### +### `results_ne( cursor, array )` ### + + PREPARE users_test AS SELECT * FROM active_users(); + PREPARE not_users AS + VALUES ( 42, 'Anna'), (19, 'Strongrrl'), (39, 'Theory'); + + SELECT results_ne( 'users_test', 'not_users', 'We should get only users' ); + +The inverse of `results_eq()`, this function tests that query results are not +equivalent. Note that, like `results_ne()`, order matters, so you can actually +have the same sets of results in the two query arguments and the test will +pass if they're merely in a different order. More than likely what you really +want is `results_eq()` or `set_ne()`. But this function is included for +completeness and is kind of cute, so enjoy. If a `results_ne()` test fails, +however, there will be no diagnostics, becaus, well, the results will be the +same! + + ### `set_eq( sql, sql, description )` ### ### `set_eq( sql, sql )` ### ### `set_eq( sql, array, description )` ### diff --git a/expected/resultset.out b/expected/resultset.out index 401b24f39cb4..0f7b66fde428 100644 --- a/expected/resultset.out +++ b/expected/resultset.out @@ -1,5 +1,5 @@ \unset ECHO -1..409 +1..475 ok 1 - Should create temp table with simple query ok 2 - Table __foonames__ should exist ok 3 - Should create a temp table for a prepared statement @@ -145,267 +145,333 @@ ok 142 - results_eq(dupe values, dupe values) should have the proper diagnostics ok 143 - results_eq(values with null, values with null) should pass ok 144 - results_eq(values with null, values with null) should have the proper description ok 145 - results_eq(values with null, values with null) should have the proper diagnostics -ok 146 - results_eq(prepared, select) fail should fail -ok 147 - results_eq(prepared, select) fail should have the proper description -ok 148 - results_eq(prepared, select) fail should have the proper diagnostics -ok 149 - results_eq(select, prepared) fail missing last row should fail -ok 150 - results_eq(select, prepared) fail missing last row should have the proper description -ok 151 - results_eq(select, prepared) fail missing last row should have the proper diagnostics -ok 152 - results_eq(prepared, select) fail missing first row should fail -ok 153 - results_eq(prepared, select) fail missing first row should have the proper description -ok 154 - results_eq(prepared, select) fail missing first row should have the proper diagnostics -ok 155 - results_eq(values dupe, values) should fail -ok 156 - results_eq(values dupe, values) should have the proper description -ok 157 - results_eq(values dupe, values) should have the proper diagnostics -ok 158 - results_eq(values null, values) should fail -ok 159 - results_eq(values null, values) should have the proper description -ok 160 - results_eq(values null, values) should have the proper diagnostics -ok 161 - results_eq(values, values) mismatch should fail -ok 162 - results_eq(values, values) mismatch should have the proper description -ok 163 - results_eq(values, values) mismatch should have the proper diagnostics -ok 164 - results_eq(values, values) subtle mismatch should fail -ok 165 - results_eq(values, values) subtle mismatch should have the proper description -ok 166 - results_eq(values, values) subtle mismatch should have the proper diagnostics -ok 167 - results_eq(values, values) fail column count should fail -ok 168 - results_eq(values, values) fail column count should have the proper description -ok 169 - results_eq(values, values) fail column count should have the proper diagnostics -ok 170 - results_eq(cursor, cursor) should pass -ok 171 - results_eq(cursor, cursor) should have the proper description -ok 172 - results_eq(cursor, cursor) should have the proper diagnostics -ok 173 - results_eq(cursor, prepared) should pass -ok 174 - results_eq(cursor, prepared) should have the proper description -ok 175 - results_eq(cursor, prepared) should have the proper diagnostics -ok 176 - results_eq(prepared, cursor) should pass -ok 177 - results_eq(prepared, cursor) should have the proper description -ok 178 - results_eq(prepared, cursor) should have the proper diagnostics -ok 179 - results_eq(cursor, sql) should pass -ok 180 - results_eq(cursor, sql) should have the proper description -ok 181 - results_eq(cursor, sql) should have the proper diagnostics -ok 182 - results_eq(sql, cursor) should pass -ok 183 - results_eq(sql, cursor) should have the proper description -ok 184 - results_eq(sql, cursor) should have the proper diagnostics -ok 185 - set_has( prepared, prepared, description ) should pass -ok 186 - set_has( prepared, prepared, description ) should have the proper description -ok 187 - set_has( prepared, prepared, description ) should have the proper diagnostics -ok 188 - set_has( prepared, subprepared ) should pass -ok 189 - set_has( prepared, subprepared ) should have the proper description -ok 190 - set_has( prepared, subprepared ) should have the proper diagnostics -ok 191 - set_has( execute, execute ) should pass -ok 192 - set_has( execute, execute ) should have the proper description -ok 193 - set_has( execute, execute ) should have the proper diagnostics -ok 194 - set_has( select, select ) should pass -ok 195 - set_has( select, select ) should have the proper description -ok 196 - set_has( select, select ) should have the proper diagnostics -ok 197 - set_has( prepared, empty ) should pass -ok 198 - set_has( prepared, empty ) should have the proper description -ok 199 - set_has( prepared, empty ) should have the proper diagnostics -ok 200 - set_has( prepared, dupes ) should pass -ok 201 - set_has( prepared, dupes ) should have the proper description -ok 202 - set_has( prepared, dupes ) should have the proper diagnostics -ok 203 - set_has( dupes, values ) should pass -ok 204 - set_has( dupes, values ) should have the proper description -ok 205 - set_has( dupes, values ) should have the proper diagnostics -ok 206 - set_has( missing1, expect ) should fail -ok 207 - set_has( missing1, expect ) should have the proper description -ok 208 - set_has( missing1, expect ) should have the proper diagnostics -ok 209 - set_has(missing2, expect ) should fail -ok 210 - set_has(missing2, expect ) should have the proper description -ok 211 - set_has(missing2, expect ) should have the proper diagnostics -ok 212 - set_has((int,text), (text,int)) should fail -ok 213 - set_has((int,text), (text,int)) should have the proper description -ok 214 - set_has((int,text), (text,int)) should have the proper diagnostics -ok 215 - set_has((int), (text,int)) should fail -ok 216 - set_has((int), (text,int)) should have the proper description -ok 217 - set_has((int), (text,int)) should have the proper diagnostics -ok 218 - bag_has( prepared, prepared, description ) should pass -ok 219 - bag_has( prepared, prepared, description ) should have the proper description -ok 220 - bag_has( prepared, prepared, description ) should have the proper diagnostics -ok 221 - bag_has( prepared, subprepared ) should pass -ok 222 - bag_has( prepared, subprepared ) should have the proper description -ok 223 - bag_has( prepared, subprepared ) should have the proper diagnostics -ok 224 - bag_has( execute, execute ) should pass -ok 225 - bag_has( execute, execute ) should have the proper description -ok 226 - bag_has( execute, execute ) should have the proper diagnostics -ok 227 - bag_has( select, select ) should pass -ok 228 - bag_has( select, select ) should have the proper description -ok 229 - bag_has( select, select ) should have the proper diagnostics -ok 230 - bag_has( prepared, empty ) should pass -ok 231 - bag_has( prepared, empty ) should have the proper description -ok 232 - bag_has( prepared, empty ) should have the proper diagnostics -ok 233 - bag_has( prepared, dupes ) should fail -ok 234 - bag_has( prepared, dupes ) should have the proper description -ok 235 - bag_has( prepared, dupes ) should have the proper diagnostics -ok 236 - bag_has( dupes, values ) should pass -ok 237 - bag_has( dupes, values ) should have the proper description -ok 238 - bag_has( dupes, values ) should have the proper diagnostics -ok 239 - bag_has( missing1, expect ) should fail -ok 240 - bag_has( missing1, expect ) should have the proper description -ok 241 - bag_has( missing1, expect ) should have the proper diagnostics -ok 242 - bag_has(missing2, expect ) should fail -ok 243 - bag_has(missing2, expect ) should have the proper description -ok 244 - bag_has(missing2, expect ) should have the proper diagnostics -ok 245 - bag_has((int,text), (text,int)) should fail -ok 246 - bag_has((int,text), (text,int)) should have the proper description -ok 247 - bag_has((int,text), (text,int)) should have the proper diagnostics -ok 248 - bag_has((int), (text,int)) should fail -ok 249 - bag_has((int), (text,int)) should have the proper description -ok 250 - bag_has((int), (text,int)) should have the proper diagnostics -ok 251 - set_hasnt( prepared, prepared, description ) should pass -ok 252 - set_hasnt( prepared, prepared, description ) should have the proper description -ok 253 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics -ok 254 - set_hasnt( prepared, prepared, description ) should pass -ok 255 - set_hasnt( prepared, prepared, description ) should have the proper description -ok 256 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics -ok 257 - set_hasnt( execute, execute ) should pass -ok 258 - set_hasnt( execute, execute ) should have the proper description -ok 259 - set_hasnt( execute, execute ) should have the proper diagnostics -ok 260 - set_hasnt( select, select ) should pass -ok 261 - set_hasnt( select, select ) should have the proper description -ok 262 - set_hasnt( select, select ) should have the proper diagnostics -ok 263 - set_hasnt( prepared, empty ) should pass -ok 264 - set_hasnt( prepared, empty ) should have the proper description -ok 265 - set_hasnt( prepared, empty ) should have the proper diagnostics -ok 266 - set_hasnt( prepared, dupes ) should pass -ok 267 - set_hasnt( prepared, dupes ) should have the proper description -ok 268 - set_hasnt( prepared, dupes ) should have the proper diagnostics -ok 269 - set_hasnt( prepared, value ) should fail -ok 270 - set_hasnt( prepared, value ) should have the proper description -ok 271 - set_hasnt( prepared, value ) should have the proper diagnostics -ok 272 - set_hasnt( prepared, values ) should fail -ok 273 - set_hasnt( prepared, values ) should have the proper description -ok 274 - set_hasnt( prepared, values ) should have the proper diagnostics -ok 275 - set_hasnt((int,text), (text,int)) should fail -ok 276 - set_hasnt((int,text), (text,int)) should have the proper description -ok 277 - set_hasnt((int,text), (text,int)) should have the proper diagnostics -ok 278 - set_hasnt((int), (text,int)) should fail -ok 279 - set_hasnt((int), (text,int)) should have the proper description -ok 280 - set_hasnt((int), (text,int)) should have the proper diagnostics -ok 281 - bag_hasnt( prepared, prepared, description ) should pass -ok 282 - bag_hasnt( prepared, prepared, description ) should have the proper description -ok 283 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics -ok 284 - bag_hasnt( prepared, prepared, description ) should pass -ok 285 - bag_hasnt( prepared, prepared, description ) should have the proper description -ok 286 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics -ok 287 - bag_hasnt( execute, execute ) should pass -ok 288 - bag_hasnt( execute, execute ) should have the proper description -ok 289 - bag_hasnt( execute, execute ) should have the proper diagnostics -ok 290 - bag_hasnt( select, select ) should pass -ok 291 - bag_hasnt( select, select ) should have the proper description -ok 292 - bag_hasnt( select, select ) should have the proper diagnostics -ok 293 - bag_hasnt( prepared, empty ) should pass -ok 294 - bag_hasnt( prepared, empty ) should have the proper description -ok 295 - bag_hasnt( prepared, empty ) should have the proper diagnostics -ok 296 - bag_hasnt( prepared, value ) should fail -ok 297 - bag_hasnt( prepared, value ) should have the proper description -ok 298 - bag_hasnt( prepared, value ) should have the proper diagnostics -ok 299 - bag_hasnt( prepared, values ) should fail -ok 300 - bag_hasnt( prepared, values ) should have the proper description -ok 301 - bag_hasnt( prepared, values ) should have the proper diagnostics -ok 302 - bag_hasnt((int,text), (text,int)) should fail -ok 303 - bag_hasnt((int,text), (text,int)) should have the proper description -ok 304 - bag_hasnt((int,text), (text,int)) should have the proper diagnostics -ok 305 - bag_hasnt((int), (text,int)) should fail -ok 306 - bag_hasnt((int), (text,int)) should have the proper description -ok 307 - bag_hasnt((int), (text,int)) should have the proper diagnostics -ok 308 - bag_hasnt( dupes, dupes ) should fail -ok 309 - bag_hasnt( dupes, dupes ) should have the proper description -ok 310 - bag_hasnt( dupes, dupes ) should have the proper diagnostics -ok 311 - bag_hasnt( value, dupes ) should fail -ok 312 - bag_hasnt( value, dupes ) should have the proper description -ok 313 - bag_hasnt( value, dupes ) should have the proper diagnostics -ok 314 - set_eq(sql, array, desc) should pass -ok 315 - set_eq(sql, array, desc) should have the proper description -ok 316 - set_eq(sql, array, desc) should have the proper diagnostics -ok 317 - set_eq(sql, array) should pass -ok 318 - set_eq(sql, array) should have the proper description -ok 319 - set_eq(sql, array) should have the proper diagnostics -ok 320 - set_eq(sql, dupe array) should pass -ok 321 - set_eq(sql, dupe array) should have the proper description -ok 322 - set_eq(sql, dupe array) should have the proper diagnostics -ok 323 - set_eq(sql, array) extra record should fail -ok 324 - set_eq(sql, array) extra record should have the proper description -ok 325 - set_eq(sql, array) extra record should have the proper diagnostics -ok 326 - set_eq(sql, array) missing record should fail -ok 327 - set_eq(sql, array) missing record should have the proper description -ok 328 - set_eq(sql, array) missing record should have the proper diagnostics -ok 329 - set_eq(sql, array) incompatible types should fail -ok 330 - set_eq(sql, array) incompatible types should have the proper description -ok 331 - set_eq(sql, array) incompatible types should have the proper diagnostics -ok 332 - set_eq(sql, array) incompatible types should fail -ok 333 - set_eq(sql, array) incompatible types should have the proper description -ok 334 - set_eq(sql, array) incompatible types should have the proper diagnostics -ok 335 - bag_eq(sql, array, desc) should pass -ok 336 - bag_eq(sql, array, desc) should have the proper description -ok 337 - bag_eq(sql, array, desc) should have the proper diagnostics -ok 338 - bag_eq(sql, array) should pass -ok 339 - bag_eq(sql, array) should have the proper description -ok 340 - bag_eq(sql, array) should have the proper diagnostics -ok 341 - bag_eq(sql, dupe array) fail should fail -ok 342 - bag_eq(sql, dupe array) fail should have the proper description -ok 343 - bag_eq(sql, dupe array) fail should have the proper diagnostics -ok 344 - bag_eq(sql, array) extra record should fail -ok 345 - bag_eq(sql, array) extra record should have the proper description -ok 346 - bag_eq(sql, array) extra record should have the proper diagnostics -ok 347 - bag_eq(sql, array) missing record should fail -ok 348 - bag_eq(sql, array) missing record should have the proper description -ok 349 - bag_eq(sql, array) missing record should have the proper diagnostics -ok 350 - bag_eq(sql, array) incompatible types should fail -ok 351 - bag_eq(sql, array) incompatible types should have the proper description -ok 352 - bag_eq(sql, array) incompatible types should have the proper diagnostics -ok 353 - bag_eq(sql, array) incompatible types should fail -ok 354 - bag_eq(sql, array) incompatible types should have the proper description -ok 355 - bag_eq(sql, array) incompatible types should have the proper diagnostics -ok 356 - set_ne(sql, array, desc) should pass -ok 357 - set_ne(sql, array, desc) should have the proper description -ok 358 - set_ne(sql, array, desc) should have the proper diagnostics -ok 359 - set_ne(sql, array) should pass -ok 360 - set_ne(sql, array) should have the proper description -ok 361 - set_ne(sql, array) should have the proper diagnostics -ok 362 - set_ne(sql, array) fail should fail -ok 363 - set_ne(sql, array) fail should have the proper description -ok 364 - set_ne(sql, array) fail should have the proper diagnostics -ok 365 - set_ne(sql, dupes array) fail should fail -ok 366 - set_ne(sql, dupes array) fail should have the proper description -ok 367 - set_ne(sql, dupes array) fail should have the proper diagnostics -ok 368 - set_ne(sql, array) incompatible types should fail -ok 369 - set_ne(sql, array) incompatible types should have the proper description -ok 370 - set_ne(sql, array) incompatible types should have the proper diagnostics -ok 371 - set_ne(sql, array) incompatible types should fail -ok 372 - set_ne(sql, array) incompatible types should have the proper description -ok 373 - set_ne(sql, array) incompatible types should have the proper diagnostics -ok 374 - bag_ne(sql, array, desc) should pass -ok 375 - bag_ne(sql, array, desc) should have the proper description -ok 376 - bag_ne(sql, array, desc) should have the proper diagnostics -ok 377 - bag_ne(sql, array) should pass -ok 378 - bag_ne(sql, array) should have the proper description -ok 379 - bag_ne(sql, array) should have the proper diagnostics -ok 380 - bag_ne(sql, array) fail should fail -ok 381 - bag_ne(sql, array) fail should have the proper description -ok 382 - bag_ne(sql, array) fail should have the proper diagnostics -ok 383 - bag_ne(sql, dupes array) should pass -ok 384 - bag_ne(sql, dupes array) should have the proper description -ok 385 - bag_ne(sql, dupes array) should have the proper diagnostics -ok 386 - bag_ne(sql, array) incompatible types should fail -ok 387 - bag_ne(sql, array) incompatible types should have the proper description -ok 388 - bag_ne(sql, array) incompatible types should have the proper diagnostics -ok 389 - bag_ne(sql, array) incompatible types should fail -ok 390 - bag_ne(sql, array) incompatible types should have the proper description -ok 391 - bag_ne(sql, array) incompatible types should have the proper diagnostics -ok 392 - results_eq(prepared, array, desc) should pass -ok 393 - results_eq(prepared, array, desc) should have the proper description -ok 394 - results_eq(prepared, array, desc) should have the proper diagnostics -ok 395 - results_eq(prepared, array) should pass -ok 396 - results_eq(prepared, array) should have the proper description -ok 397 - results_eq(prepared, array) should have the proper diagnostics -ok 398 - results_eq(sql, array, desc) should pass -ok 399 - results_eq(sql, array, desc) should have the proper description -ok 400 - results_eq(sql, array, desc) should have the proper diagnostics -ok 401 - results_eq(sql, array, desc) should pass -ok 402 - results_eq(sql, array, desc) should have the proper description -ok 403 - results_eq(sql, array, desc) should have the proper diagnostics -ok 404 - results_eq(prepared, array) extra record should fail -ok 405 - results_eq(prepared, array) extra record should have the proper description -ok 406 - results_eq(prepared, array) extra record should have the proper diagnostics -ok 407 - results_eq(select, array) missing record should fail -ok 408 - results_eq(select, array) missing record should have the proper description -ok 409 - results_eq(select, array) missing record should have the proper diagnostics +ok 146 - results_eq(nulls, nulls) should pass +ok 147 - results_eq(nulls, nulls) should have the proper description +ok 148 - results_eq(nulls, nulls) should have the proper diagnostics +ok 149 - results_eq(nulls, nulls) fail should fail +ok 150 - results_eq(nulls, nulls) fail should have the proper description +ok 151 - results_eq(nulls, nulls) fail should have the proper diagnostics +ok 152 - results_eq(prepared, select) fail should fail +ok 153 - results_eq(prepared, select) fail should have the proper description +ok 154 - results_eq(prepared, select) fail should have the proper diagnostics +ok 155 - results_eq(select, prepared) fail missing last row should fail +ok 156 - results_eq(select, prepared) fail missing last row should have the proper description +ok 157 - results_eq(select, prepared) fail missing last row should have the proper diagnostics +ok 158 - results_eq(prepared, select) fail missing first row should fail +ok 159 - results_eq(prepared, select) fail missing first row should have the proper description +ok 160 - results_eq(prepared, select) fail missing first row should have the proper diagnostics +ok 161 - results_eq(values dupe, values) should fail +ok 162 - results_eq(values dupe, values) should have the proper description +ok 163 - results_eq(values dupe, values) should have the proper diagnostics +ok 164 - results_eq(values null, values) should fail +ok 165 - results_eq(values null, values) should have the proper description +ok 166 - results_eq(values null, values) should have the proper diagnostics +ok 167 - results_eq(values, values) mismatch should fail +ok 168 - results_eq(values, values) mismatch should have the proper description +ok 169 - results_eq(values, values) mismatch should have the proper diagnostics +ok 170 - results_eq(values, values) subtle mismatch should fail +ok 171 - results_eq(values, values) subtle mismatch should have the proper description +ok 172 - results_eq(values, values) subtle mismatch should have the proper diagnostics +ok 173 - results_eq(values, values) fail column count should fail +ok 174 - results_eq(values, values) fail column count should have the proper description +ok 175 - results_eq(values, values) fail column count should have the proper diagnostics +ok 176 - results_eq(cursor, cursor) should pass +ok 177 - results_eq(cursor, cursor) should have the proper description +ok 178 - results_eq(cursor, cursor) should have the proper diagnostics +ok 179 - results_eq(cursor, prepared) should pass +ok 180 - results_eq(cursor, prepared) should have the proper description +ok 181 - results_eq(cursor, prepared) should have the proper diagnostics +ok 182 - results_eq(prepared, cursor) should pass +ok 183 - results_eq(prepared, cursor) should have the proper description +ok 184 - results_eq(prepared, cursor) should have the proper diagnostics +ok 185 - results_eq(cursor, sql) should pass +ok 186 - results_eq(cursor, sql) should have the proper description +ok 187 - results_eq(cursor, sql) should have the proper diagnostics +ok 188 - results_eq(sql, cursor) should pass +ok 189 - results_eq(sql, cursor) should have the proper description +ok 190 - results_eq(sql, cursor) should have the proper diagnostics +ok 191 - set_has( prepared, prepared, description ) should pass +ok 192 - set_has( prepared, prepared, description ) should have the proper description +ok 193 - set_has( prepared, prepared, description ) should have the proper diagnostics +ok 194 - set_has( prepared, subprepared ) should pass +ok 195 - set_has( prepared, subprepared ) should have the proper description +ok 196 - set_has( prepared, subprepared ) should have the proper diagnostics +ok 197 - set_has( execute, execute ) should pass +ok 198 - set_has( execute, execute ) should have the proper description +ok 199 - set_has( execute, execute ) should have the proper diagnostics +ok 200 - set_has( select, select ) should pass +ok 201 - set_has( select, select ) should have the proper description +ok 202 - set_has( select, select ) should have the proper diagnostics +ok 203 - set_has( prepared, empty ) should pass +ok 204 - set_has( prepared, empty ) should have the proper description +ok 205 - set_has( prepared, empty ) should have the proper diagnostics +ok 206 - set_has( prepared, dupes ) should pass +ok 207 - set_has( prepared, dupes ) should have the proper description +ok 208 - set_has( prepared, dupes ) should have the proper diagnostics +ok 209 - set_has( dupes, values ) should pass +ok 210 - set_has( dupes, values ) should have the proper description +ok 211 - set_has( dupes, values ) should have the proper diagnostics +ok 212 - set_has( missing1, expect ) should fail +ok 213 - set_has( missing1, expect ) should have the proper description +ok 214 - set_has( missing1, expect ) should have the proper diagnostics +ok 215 - set_has(missing2, expect ) should fail +ok 216 - set_has(missing2, expect ) should have the proper description +ok 217 - set_has(missing2, expect ) should have the proper diagnostics +ok 218 - set_has((int,text), (text,int)) should fail +ok 219 - set_has((int,text), (text,int)) should have the proper description +ok 220 - set_has((int,text), (text,int)) should have the proper diagnostics +ok 221 - set_has((int), (text,int)) should fail +ok 222 - set_has((int), (text,int)) should have the proper description +ok 223 - set_has((int), (text,int)) should have the proper diagnostics +ok 224 - bag_has( prepared, prepared, description ) should pass +ok 225 - bag_has( prepared, prepared, description ) should have the proper description +ok 226 - bag_has( prepared, prepared, description ) should have the proper diagnostics +ok 227 - bag_has( prepared, subprepared ) should pass +ok 228 - bag_has( prepared, subprepared ) should have the proper description +ok 229 - bag_has( prepared, subprepared ) should have the proper diagnostics +ok 230 - bag_has( execute, execute ) should pass +ok 231 - bag_has( execute, execute ) should have the proper description +ok 232 - bag_has( execute, execute ) should have the proper diagnostics +ok 233 - bag_has( select, select ) should pass +ok 234 - bag_has( select, select ) should have the proper description +ok 235 - bag_has( select, select ) should have the proper diagnostics +ok 236 - bag_has( prepared, empty ) should pass +ok 237 - bag_has( prepared, empty ) should have the proper description +ok 238 - bag_has( prepared, empty ) should have the proper diagnostics +ok 239 - bag_has( prepared, dupes ) should fail +ok 240 - bag_has( prepared, dupes ) should have the proper description +ok 241 - bag_has( prepared, dupes ) should have the proper diagnostics +ok 242 - bag_has( dupes, values ) should pass +ok 243 - bag_has( dupes, values ) should have the proper description +ok 244 - bag_has( dupes, values ) should have the proper diagnostics +ok 245 - bag_has( missing1, expect ) should fail +ok 246 - bag_has( missing1, expect ) should have the proper description +ok 247 - bag_has( missing1, expect ) should have the proper diagnostics +ok 248 - bag_has(missing2, expect ) should fail +ok 249 - bag_has(missing2, expect ) should have the proper description +ok 250 - bag_has(missing2, expect ) should have the proper diagnostics +ok 251 - bag_has((int,text), (text,int)) should fail +ok 252 - bag_has((int,text), (text,int)) should have the proper description +ok 253 - bag_has((int,text), (text,int)) should have the proper diagnostics +ok 254 - bag_has((int), (text,int)) should fail +ok 255 - bag_has((int), (text,int)) should have the proper description +ok 256 - bag_has((int), (text,int)) should have the proper diagnostics +ok 257 - set_hasnt( prepared, prepared, description ) should pass +ok 258 - set_hasnt( prepared, prepared, description ) should have the proper description +ok 259 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 260 - set_hasnt( prepared, prepared, description ) should pass +ok 261 - set_hasnt( prepared, prepared, description ) should have the proper description +ok 262 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 263 - set_hasnt( execute, execute ) should pass +ok 264 - set_hasnt( execute, execute ) should have the proper description +ok 265 - set_hasnt( execute, execute ) should have the proper diagnostics +ok 266 - set_hasnt( select, select ) should pass +ok 267 - set_hasnt( select, select ) should have the proper description +ok 268 - set_hasnt( select, select ) should have the proper diagnostics +ok 269 - set_hasnt( prepared, empty ) should pass +ok 270 - set_hasnt( prepared, empty ) should have the proper description +ok 271 - set_hasnt( prepared, empty ) should have the proper diagnostics +ok 272 - set_hasnt( prepared, dupes ) should pass +ok 273 - set_hasnt( prepared, dupes ) should have the proper description +ok 274 - set_hasnt( prepared, dupes ) should have the proper diagnostics +ok 275 - set_hasnt( prepared, value ) should fail +ok 276 - set_hasnt( prepared, value ) should have the proper description +ok 277 - set_hasnt( prepared, value ) should have the proper diagnostics +ok 278 - set_hasnt( prepared, values ) should fail +ok 279 - set_hasnt( prepared, values ) should have the proper description +ok 280 - set_hasnt( prepared, values ) should have the proper diagnostics +ok 281 - set_hasnt((int,text), (text,int)) should fail +ok 282 - set_hasnt((int,text), (text,int)) should have the proper description +ok 283 - set_hasnt((int,text), (text,int)) should have the proper diagnostics +ok 284 - set_hasnt((int), (text,int)) should fail +ok 285 - set_hasnt((int), (text,int)) should have the proper description +ok 286 - set_hasnt((int), (text,int)) should have the proper diagnostics +ok 287 - bag_hasnt( prepared, prepared, description ) should pass +ok 288 - bag_hasnt( prepared, prepared, description ) should have the proper description +ok 289 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 290 - bag_hasnt( prepared, prepared, description ) should pass +ok 291 - bag_hasnt( prepared, prepared, description ) should have the proper description +ok 292 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 293 - bag_hasnt( execute, execute ) should pass +ok 294 - bag_hasnt( execute, execute ) should have the proper description +ok 295 - bag_hasnt( execute, execute ) should have the proper diagnostics +ok 296 - bag_hasnt( select, select ) should pass +ok 297 - bag_hasnt( select, select ) should have the proper description +ok 298 - bag_hasnt( select, select ) should have the proper diagnostics +ok 299 - bag_hasnt( prepared, empty ) should pass +ok 300 - bag_hasnt( prepared, empty ) should have the proper description +ok 301 - bag_hasnt( prepared, empty ) should have the proper diagnostics +ok 302 - bag_hasnt( prepared, value ) should fail +ok 303 - bag_hasnt( prepared, value ) should have the proper description +ok 304 - bag_hasnt( prepared, value ) should have the proper diagnostics +ok 305 - bag_hasnt( prepared, values ) should fail +ok 306 - bag_hasnt( prepared, values ) should have the proper description +ok 307 - bag_hasnt( prepared, values ) should have the proper diagnostics +ok 308 - bag_hasnt((int,text), (text,int)) should fail +ok 309 - bag_hasnt((int,text), (text,int)) should have the proper description +ok 310 - bag_hasnt((int,text), (text,int)) should have the proper diagnostics +ok 311 - bag_hasnt((int), (text,int)) should fail +ok 312 - bag_hasnt((int), (text,int)) should have the proper description +ok 313 - bag_hasnt((int), (text,int)) should have the proper diagnostics +ok 314 - bag_hasnt( dupes, dupes ) should fail +ok 315 - bag_hasnt( dupes, dupes ) should have the proper description +ok 316 - bag_hasnt( dupes, dupes ) should have the proper diagnostics +ok 317 - bag_hasnt( value, dupes ) should fail +ok 318 - bag_hasnt( value, dupes ) should have the proper description +ok 319 - bag_hasnt( value, dupes ) should have the proper diagnostics +ok 320 - set_eq(sql, array, desc) should pass +ok 321 - set_eq(sql, array, desc) should have the proper description +ok 322 - set_eq(sql, array, desc) should have the proper diagnostics +ok 323 - set_eq(sql, array) should pass +ok 324 - set_eq(sql, array) should have the proper description +ok 325 - set_eq(sql, array) should have the proper diagnostics +ok 326 - set_eq(sql, dupe array) should pass +ok 327 - set_eq(sql, dupe array) should have the proper description +ok 328 - set_eq(sql, dupe array) should have the proper diagnostics +ok 329 - set_eq(sql, array) extra record should fail +ok 330 - set_eq(sql, array) extra record should have the proper description +ok 331 - set_eq(sql, array) extra record should have the proper diagnostics +ok 332 - set_eq(sql, array) missing record should fail +ok 333 - set_eq(sql, array) missing record should have the proper description +ok 334 - set_eq(sql, array) missing record should have the proper diagnostics +ok 335 - set_eq(sql, array) incompatible types should fail +ok 336 - set_eq(sql, array) incompatible types should have the proper description +ok 337 - set_eq(sql, array) incompatible types should have the proper diagnostics +ok 338 - set_eq(sql, array) incompatible types should fail +ok 339 - set_eq(sql, array) incompatible types should have the proper description +ok 340 - set_eq(sql, array) incompatible types should have the proper diagnostics +ok 341 - bag_eq(sql, array, desc) should pass +ok 342 - bag_eq(sql, array, desc) should have the proper description +ok 343 - bag_eq(sql, array, desc) should have the proper diagnostics +ok 344 - bag_eq(sql, array) should pass +ok 345 - bag_eq(sql, array) should have the proper description +ok 346 - bag_eq(sql, array) should have the proper diagnostics +ok 347 - bag_eq(sql, dupe array) fail should fail +ok 348 - bag_eq(sql, dupe array) fail should have the proper description +ok 349 - bag_eq(sql, dupe array) fail should have the proper diagnostics +ok 350 - bag_eq(sql, array) extra record should fail +ok 351 - bag_eq(sql, array) extra record should have the proper description +ok 352 - bag_eq(sql, array) extra record should have the proper diagnostics +ok 353 - bag_eq(sql, array) missing record should fail +ok 354 - bag_eq(sql, array) missing record should have the proper description +ok 355 - bag_eq(sql, array) missing record should have the proper diagnostics +ok 356 - bag_eq(sql, array) incompatible types should fail +ok 357 - bag_eq(sql, array) incompatible types should have the proper description +ok 358 - bag_eq(sql, array) incompatible types should have the proper diagnostics +ok 359 - bag_eq(sql, array) incompatible types should fail +ok 360 - bag_eq(sql, array) incompatible types should have the proper description +ok 361 - bag_eq(sql, array) incompatible types should have the proper diagnostics +ok 362 - set_ne(sql, array, desc) should pass +ok 363 - set_ne(sql, array, desc) should have the proper description +ok 364 - set_ne(sql, array, desc) should have the proper diagnostics +ok 365 - set_ne(sql, array) should pass +ok 366 - set_ne(sql, array) should have the proper description +ok 367 - set_ne(sql, array) should have the proper diagnostics +ok 368 - set_ne(sql, array) fail should fail +ok 369 - set_ne(sql, array) fail should have the proper description +ok 370 - set_ne(sql, array) fail should have the proper diagnostics +ok 371 - set_ne(sql, dupes array) fail should fail +ok 372 - set_ne(sql, dupes array) fail should have the proper description +ok 373 - set_ne(sql, dupes array) fail should have the proper diagnostics +ok 374 - set_ne(sql, array) incompatible types should fail +ok 375 - set_ne(sql, array) incompatible types should have the proper description +ok 376 - set_ne(sql, array) incompatible types should have the proper diagnostics +ok 377 - set_ne(sql, array) incompatible types should fail +ok 378 - set_ne(sql, array) incompatible types should have the proper description +ok 379 - set_ne(sql, array) incompatible types should have the proper diagnostics +ok 380 - bag_ne(sql, array, desc) should pass +ok 381 - bag_ne(sql, array, desc) should have the proper description +ok 382 - bag_ne(sql, array, desc) should have the proper diagnostics +ok 383 - bag_ne(sql, array) should pass +ok 384 - bag_ne(sql, array) should have the proper description +ok 385 - bag_ne(sql, array) should have the proper diagnostics +ok 386 - bag_ne(sql, array) fail should fail +ok 387 - bag_ne(sql, array) fail should have the proper description +ok 388 - bag_ne(sql, array) fail should have the proper diagnostics +ok 389 - bag_ne(sql, dupes array) should pass +ok 390 - bag_ne(sql, dupes array) should have the proper description +ok 391 - bag_ne(sql, dupes array) should have the proper diagnostics +ok 392 - bag_ne(sql, array) incompatible types should fail +ok 393 - bag_ne(sql, array) incompatible types should have the proper description +ok 394 - bag_ne(sql, array) incompatible types should have the proper diagnostics +ok 395 - bag_ne(sql, array) incompatible types should fail +ok 396 - bag_ne(sql, array) incompatible types should have the proper description +ok 397 - bag_ne(sql, array) incompatible types should have the proper diagnostics +ok 398 - results_eq(prepared, array, desc) should pass +ok 399 - results_eq(prepared, array, desc) should have the proper description +ok 400 - results_eq(prepared, array, desc) should have the proper diagnostics +ok 401 - results_eq(prepared, array) should pass +ok 402 - results_eq(prepared, array) should have the proper description +ok 403 - results_eq(prepared, array) should have the proper diagnostics +ok 404 - results_eq(sql, array, desc) should pass +ok 405 - results_eq(sql, array, desc) should have the proper description +ok 406 - results_eq(sql, array, desc) should have the proper diagnostics +ok 407 - results_eq(sql, array, desc) should pass +ok 408 - results_eq(sql, array, desc) should have the proper description +ok 409 - results_eq(sql, array, desc) should have the proper diagnostics +ok 410 - results_eq(prepared, array) extra record should fail +ok 411 - results_eq(prepared, array) extra record should have the proper description +ok 412 - results_eq(prepared, array) extra record should have the proper diagnostics +ok 413 - results_eq(select, array) missing record should fail +ok 414 - results_eq(select, array) missing record should have the proper description +ok 415 - results_eq(select, array) missing record should have the proper diagnostics +ok 416 - results_ne(prepared, prepared, desc) should pass +ok 417 - results_ne(prepared, prepared, desc) should have the proper description +ok 418 - results_ne(prepared, prepared, desc) should have the proper diagnostics +ok 419 - results_ne(prepared, prepared) should pass +ok 420 - results_ne(prepared, prepared) should have the proper description +ok 421 - results_ne(prepared, prepared) should have the proper diagnostics +ok 422 - results_ne(execute, execute) should pass +ok 423 - results_ne(execute, execute) should have the proper description +ok 424 - results_ne(execute, execute) should have the proper diagnostics +ok 425 - results_ne(select, select) should pass +ok 426 - results_ne(select, select) should have the proper description +ok 427 - results_ne(select, select) should have the proper diagnostics +ok 428 - results_ne(dupe values, dupe values) should pass +ok 429 - results_ne(dupe values, dupe values) should have the proper description +ok 430 - results_ne(dupe values, dupe values) should have the proper diagnostics +ok 431 - results_ne(values with null, values with null) should pass +ok 432 - results_ne(values with null, values with null) should have the proper description +ok 433 - results_ne(values with null, values with null) should have the proper diagnostics +ok 434 - results_ne(nulls, nulls) should pass +ok 435 - results_ne(nulls, nulls) should have the proper description +ok 436 - results_ne(nulls, nulls) should have the proper diagnostics +ok 437 - results_ne(prepared, select) fail should fail +ok 438 - results_ne(prepared, select) fail should have the proper description +ok 439 - results_ne(prepared, select) fail should have the proper diagnostics +ok 440 - results_ne(select, prepared) missing last row should pass +ok 441 - results_ne(select, prepared) missing last row should have the proper description +ok 442 - results_ne(select, prepared) missing last row should have the proper diagnostics +ok 443 - results_ne(prepared, select) missing first row should pass +ok 444 - results_ne(prepared, select) missing first row should have the proper description +ok 445 - results_ne(prepared, select) missing first row should have the proper diagnostics +ok 446 - results_ne(values dupe, values) should pass +ok 447 - results_ne(values dupe, values) should have the proper description +ok 448 - results_ne(values dupe, values) should have the proper diagnostics +ok 449 - results_ne(values null, values) should pass +ok 450 - results_ne(values null, values) should have the proper description +ok 451 - results_ne(values null, values) should have the proper diagnostics +ok 452 - results_ne(values, values) mismatch should fail +ok 453 - results_ne(values, values) mismatch should have the proper description +ok 454 - results_ne(values, values) mismatch should have the proper diagnostics +ok 455 - results_ne(values, values) subtle mismatch should fail +ok 456 - results_ne(values, values) subtle mismatch should have the proper description +ok 457 - results_ne(values, values) subtle mismatch should have the proper diagnostics +ok 458 - results_ne(values, values) fail column count should fail +ok 459 - results_ne(values, values) fail column count should have the proper description +ok 460 - results_ne(values, values) fail column count should have the proper diagnostics +ok 461 - results_ne(cursor, cursor) should fail +ok 462 - results_ne(cursor, cursor) should have the proper description +ok 463 - results_ne(cursor, cursor) should have the proper diagnostics +ok 464 - results_ne(cursor, prepared) should fail +ok 465 - results_ne(cursor, prepared) should have the proper description +ok 466 - results_ne(cursor, prepared) should have the proper diagnostics +ok 467 - results_ne(prepared, cursor) should fail +ok 468 - results_ne(prepared, cursor) should have the proper description +ok 469 - results_ne(prepared, cursor) should have the proper diagnostics +ok 470 - results_ne(cursor, sql) should pass +ok 471 - results_ne(cursor, sql) should have the proper description +ok 472 - results_ne(cursor, sql) should have the proper diagnostics +ok 473 - results_ne(sql, cursor) should fail +ok 474 - results_ne(sql, cursor) should have the proper description +ok 475 - results_ne(sql, cursor) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index f7ddfec2e7c7..7dba4f774006 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -6127,25 +6127,31 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION results_eq( refcursor, refcursor, text ) RETURNS TEXT AS $$ DECLARE - have ALIAS FOR $1; - want ALIAS FOR $2; - rec_have RECORD; - rec_want RECORD; - rownum INTEGER := 1; + have ALIAS FOR $1; + want ALIAS FOR $2; + have_rec RECORD; + want_rec RECORD; + have_found BOOLEAN; + want_found BOOLEAN; + rownum INTEGER := 1; BEGIN - FETCH have INTO rec_have; - FETCH want INTO rec_want; - WHILE rec_have IS NOT NULL OR rec_want IS NOT NULL LOOP - IF rec_have IS DISTINCT FROM rec_want THEN + 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 rec_have IS NULL THEN '()' ELSE rec_have::text END || E'\n' || - ' want: ' || CASE WHEN rec_want IS NULL THEN '()' ELSE rec_want::text END + ' have: ' || CASE WHEN have_found THEN have_rec::text ELSE '()' END || E'\n' || + ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE '()' END ); END IF; rownum = rownum + 1; - FETCH have INTO rec_have; - FETCH want INTO rec_want; + FETCH have INTO have_rec; + have_found := FOUND; + FETCH want INTO want_rec; + want_found := FOUND; END LOOP; RETURN ok( true, $3 ); @@ -6153,8 +6159,8 @@ EXCEPTION WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || - ' have: ' || CASE WHEN rec_have IS NULL THEN '()' ELSE rec_have::text END || E'\n' || - ' want: ' || CASE WHEN rec_want IS NULL THEN '()' ELSE rec_want::text END + ' have: ' || CASE WHEN have_found THEN have_rec::text ELSE '()' END || E'\n' || + ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE '()' END ); END; $$ LANGUAGE plpgsql; @@ -6272,3 +6278,153 @@ 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; +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 + RETURN ok( false, $3 ) || E'\n' || diag( + E' Columns differ between queries:\n' || + ' have: ' || CASE WHEN have_found THEN have_rec::text ELSE '()' END || E'\n' || + ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE '()' END + ); +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; diff --git a/sql/resultset.sql b/sql/resultset.sql index b08c9a1e8cc5..bbb61deff13d 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(409); +SELECT plan(475); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -356,7 +356,6 @@ SELECT * FROM check_test( true ); - SELECT * FROM check_test( set_eq( 'SELECT id, name FROM names WHERE name ~ ''^(An|Jacob)'' AND name <> ''Anna''', @@ -515,7 +514,6 @@ SELECT * FROM check_test( true ); - SELECT * FROM check_test( bag_eq( 'SELECT id, name FROM names WHERE name ~ ''^(An|Jacob)'' AND name <> ''Anna''', @@ -694,7 +692,6 @@ SELECT * FROM check_test( want: (text,integer)' ); - -- Handle pass with a dupe. SELECT * FROM check_test( bag_ne( @@ -798,6 +795,32 @@ SELECT * FROM check_test( '' ); +-- Compare only NULLs +SELECT * FROM check_test( + results_eq( + 'VALUES (NULL, NULL), (NULL, NULL)', + 'VALUES (NULL, NULL), (NULL, NULL)' + ), + true, + 'results_eq(nulls, nulls)', + '', + '' +); + +-- Compare only NULLs +SELECT * FROM check_test( + results_eq( + 'VALUES (NULL, NULL), (NULL, NULL)', + 'VALUES (NULL, NULL)' + ), + false, + 'results_eq(nulls, nulls) fail', + '', + ' Results differ beginning at row 2: + have: (,) + want: ()' +); + -- And now some failures. SELECT * FROM check_test( results_eq( @@ -840,7 +863,6 @@ SELECT * FROM check_test( want: ()' ); - -- Compare with missing dupe. SELECT * FROM check_test( results_eq( @@ -869,7 +891,6 @@ SELECT * FROM check_test( want: (1,Anna)' ); - -- Handle failure due to column mismatch. SELECT * FROM check_test( results_eq( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), @@ -1090,7 +1111,6 @@ SELECT * FROM check_test( want: (text,integer)' ); - /****************************************************************************/ -- Now test bag_has(). SELECT * FROM check_test( @@ -1156,7 +1176,6 @@ SELECT * FROM check_test( '' ); - SELECT * FROM check_test( bag_has( 'SELECT id, name FROM annames WHERE name <> ''Anna''', @@ -1205,7 +1224,6 @@ SELECT * FROM check_test( want: (text,integer)' ); - /****************************************************************************/ -- Now test set_hasnt(). @@ -1276,7 +1294,6 @@ SELECT * FROM check_test( (44,Anna)' ); - SELECT * FROM check_test( set_hasnt( 'anames', 'VALUES (44, ''Anna''), (86, ''Angelina'')' ), false, @@ -1310,7 +1327,6 @@ SELECT * FROM check_test( want: (text,integer)' ); - /****************************************************************************/ -- Now test bag_hasnt(). @@ -1368,7 +1384,6 @@ SELECT * FROM check_test( (44,Anna)' ); - SELECT * FROM check_test( bag_hasnt( 'anames', 'VALUES (44, ''Anna''), (86, ''Angelina'')' ), false, @@ -1429,7 +1444,6 @@ SELECT * FROM check_test( (44,Anna)' ); - /****************************************************************************/ -- Test set_eq() with an array argument. SELECT * FROM check_test( @@ -1479,7 +1493,6 @@ SELECT * FROM check_test( (Anthony)' ); - -- Fail with an extra record. SELECT * FROM check_test( set_eq( @@ -1571,7 +1584,6 @@ SELECT * FROM check_test( (Anthony)' ); - -- Fail with an extra record. SELECT * FROM check_test( bag_eq( @@ -1613,7 +1625,6 @@ SELECT * FROM check_test( want: (text)' ); - /****************************************************************************/ -- Test set_ne() with an array argument. SELECT * FROM check_test( @@ -1628,7 +1639,6 @@ SELECT * FROM check_test( '' ); - SELECT * FROM check_test( set_ne( 'SELECT name FROM names WHERE name like ''An%''', @@ -1705,7 +1715,6 @@ SELECT * FROM check_test( '' ); - SELECT * FROM check_test( bag_ne( 'SELECT name FROM names WHERE name like ''An%''', @@ -1845,6 +1854,254 @@ SELECT * FROM check_test( want: (Anna)' ); +/****************************************************************************/ +-- Now test results_eq(). + +PREPARE nenames_ord AS SELECT id, name FROM names WHERE name like 'An%' ORDER BY id; +PREPARE nexpect_ord AS VALUES (15, 'Anthony'), ( 44, 'Anna'), (11, 'Andrew'), + (63, 'Angel'), (86, 'Angelina'), (130, 'Andrea'), + (183, 'Antonio'); + +SELECT * FROM check_test( + results_ne( 'nenames_ord', 'nexpect_ord', 'whatever' ), + true, + 'results_ne(prepared, prepared, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + results_ne( 'nenames_ord', 'nexpect_ord' ), + true, + 'results_ne(prepared, prepared)', + '', + '' +); + +-- Pass a full SQL statement for the prepared statements. +SELECT * FROM check_test( + results_ne( 'EXECUTE nenames_ord', 'EXECUTE nexpect_ord' ), + true, + 'results_ne(execute, execute)', + '', + '' +); + +-- Compare actual SELECT statements. +SELECT * FROM check_test( + results_ne( + 'SELECT id, name FROM names WHERE name like ''An%'' ORDER BY id', + 'SELECT id, name FROM annames WHERE name <> ''Anna'' ORDER BY id' + ), + true, + 'results_ne(select, select)', + '', + '' +); + +-- Compare with dupes. +SELECT * FROM check_test( + results_ne( + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (2, ''Anna''), (86, ''Angelina''), (2, ''Anna'')' + ), + true, + 'results_ne(dupe values, dupe values)', + '', + '' +); + +-- Compare with nulls. +SELECT * FROM check_test( + results_ne( + 'VALUES (4, NULL), (86, ''Angelina''), (1, NULL)', + 'VALUES (4, NULL), (86, ''Angelina''), (1, ''Anna'')' + ), + true, + 'results_ne(values with null, values with null)', + '', + '' +); + +-- Compare only NULLs +SELECT * FROM check_test( + results_ne( + 'VALUES (NULL, NULL), (NULL, NULL), (NULL, NULL)', + 'VALUES (NULL, NULL), (NULL, NULL)' + ), + true, + 'results_ne(nulls, nulls)', + '', + '' +); + +-- And now a failure. +SELECT * FROM check_test( + results_ne( + 'nenames_ord', + 'SELECT id, name FROM annames' + ), + false, + 'results_ne(prepared, select) fail', + '', + '' +); + +-- -- Now when the last row is missing. +SELECT * FROM check_test( + results_ne( + 'SELECT id, name FROM annames WHERE name <> ''Antonio''', + 'nenames_ord' + ), + true, + 'results_ne(select, prepared) missing last row', + '', + '' +); + +-- Invert that. +SELECT * FROM check_test( + results_ne( + 'nenames_ord', + 'SELECT id, name FROM annames WHERE name <> ''Antonio''' + ), + true, + 'results_ne(prepared, select) missing first row', + '', + '' +); + +-- Compare with missing dupe. +SELECT * FROM check_test( + results_ne( + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (1, ''Anna''), (86, ''Angelina'')' + ), + true, + 'results_ne(values dupe, values)', + '', + '' +); + +-- Handle pass with null. +SELECT * FROM check_test( + results_ne( + 'VALUES (1, NULL), (86, ''Angelina'')', + 'VALUES (1, ''Anna''), (86, ''Angelina'')' + ), + true, + 'results_ne(values null, values)', + '', + '' +); + +-- Handle failure due to column mismatch. +SELECT * FROM check_test( + results_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'results_ne(values, values) mismatch', + '', + CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Columns differ between queries:' END || ' + have: (1,foo) + want: (foo,1)' +); + +-- -- Handle failure due to more subtle column mismatch, valid only on 8.4. +CREATE OR REPLACE FUNCTION subtlefail() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() < 80400 THEN + -- 8.3 and earlier cast records to text, so subtlety is out. + RETURN NEXT pass('results_ne(values, values) subtle mismatch should fail'); + RETURN NEXT pass('results_ne(values, values) subtle mismatch should have the proper description'); + RETURN NEXT pass('results_ne(values, values) subtle mismatch should have the proper diagnostics'); + ELSE + -- 8.4 does true record comparisions, yay! + FOR tap IN SELECT * FROM check_test( + results_ne( + 'VALUES (1, ''foo''::varchar), (2, ''bar''::varchar)', + 'VALUES (1, ''foo''), (2, ''bar'')' + ), + false, + 'results_ne(values, values) subtle mismatch', + '', + ' Columns differ between queries: + have: (1,foo) + want: (1,foo)' ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + END IF; + RETURN; +END; +$$ LANGUAGE plpgsql; +SELECT * FROM subtlefail(); + +-- Handle failure due to column count mismatch. +SELECT * FROM check_test( + results_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'results_ne(values, values) fail column count', + '', + CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Columns differ between queries:' END || ' + have: (1) + want: (foo,1)' +); + +-- Compare with cursors. +CLOSE cwant; +CLOSE chave; +DECLARE cwant CURSOR FOR SELECT id, name FROM names WHERE name like 'An%' ORDER BY id; +DECLARE chave CURSOR FOR SELECT id, name from annames ORDER BY id; + +SELECT * FROM check_test( + results_ne( 'cwant'::refcursor, 'chave'::refcursor ), + false, + 'results_ne(cursor, cursor)', + '', + '' +); + +-- Mix cursors and prepared statements +DEALLOCATE annames_ord; +PREPARE annames_ord AS SELECT id, name FROM annames ORDER BY id; +MOVE BACKWARD ALL IN cwant; + +SELECT * FROM check_test( + results_ne( 'cwant'::refcursor, 'annames_ord' ), + false, + 'results_ne(cursor, prepared)', + '', + '' +); + +MOVE BACKWARD ALL IN chave; +SELECT * FROM check_test( + results_ne( 'annames_ord', 'chave'::refcursor ), + false, + 'results_ne(prepared, cursor)', + '', + '' +); + +-- Mix cursor and SQL. +SELECT * FROM check_test( + results_ne( 'cwant'::refcursor, 'SELECT id, name FROM annames ORDER BY id' ), + true, + 'results_ne(cursor, sql)', + '', + '' +); + +MOVE BACKWARD ALL IN chave; +SELECT * FROM check_test( + results_ne( 'SELECT id, name FROM annames ORDER BY id', 'chave'::refcursor ), + false, + 'results_ne(sql, cursor)', + '', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index d43a95a1054c..8b4723f3897d 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,4 +1,16 @@ -- ## SET search_path TO TAPSCHEMA, public; +DROP FUNCTION results_ne( refcursor, anyarray ); +DROP FUNCTION results_ne( refcursor, anyarray, TEXT ); +DROP FUNCTION results_ne( refcursor, TEXT ); +DROP FUNCTION results_ne( refcursor, TEXT, TEXT ); +DROP FUNCTION results_ne( TEXT, refcursor ); +DROP FUNCTION results_ne( TEXT, refcursor, TEXT ); +DROP FUNCTION results_ne( TEXT, anyarray ); +DROP FUNCTION results_ne( TEXT, anyarray, TEXT ); +DROP FUNCTION results_ne( TEXT, TEXT ); +DROP FUNCTION results_ne( TEXT, TEXT, TEXT ); +DROP FUNCTION results_ne( refcursor, refcursor ); +DROP FUNCTION results_ne( refcursor, refcursor, text ); DROP FUNCTION results_eq( refcursor, anyarray ); DROP FUNCTION results_eq( refcursor, anyarray, TEXT ); DROP FUNCTION results_eq( refcursor, TEXT ); From 43848a674d49b7784d577ff68f4be15e938136d5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 28 Jul 2009 15:56:41 -0700 Subject: [PATCH 0417/1195] Reprsent non-existent rows as "NULL" instead of "()". --- README.pgtap | 37 +++++++++++++++++++------------------ pgtap.sql.in | 12 ++++++------ sql/resultset.sql | 10 +++++----- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/README.pgtap b/README.pgtap index 92514c6709ae..80aa0ca98641 100644 --- a/README.pgtap +++ b/README.pgtap @@ -538,9 +538,9 @@ Submit Your Query ================= Sometimes, you've just gotta run tests on a query. I mean a full blown query, -not just the scalare type assertion functions we've seen so far. pgTAP provides -a number of functions to help you test your queries, each of which takes one -or two SQL statements as arguments. Yes, as strings: +not just the scalar assertion functions we've seen so far. pgTAP provides a +number of functions to help you test your queries, each of which takes one or +two SQL statements as arguments. Yes, as strings: SELECT throws_ok('SELECT divide_by(0)'); @@ -555,12 +555,12 @@ statement names. For example, the above example can be rewritten as: PREPARE mythrow AS SELECT divide_by(0); SELECT throws_ok('mythrow'); -pgTAP assumes that an SQL argument with no space or starting with a double -quote character is a prepared statement and simply `EXECUTE`s it. If you need -to pass arguments to a prepared statement, perhaps because you plan to use it -in multiple tests to return different values, just include `EXECUTE` in the -SQL string. Here's an example with a prepared statement with a space in its -name, and one where arguments need to be passed: +pgTAP assumes that an SQL argument without space characters or starting with a +double quote character is a prepared statement and simply `EXECUTE`s it. If +you need to pass arguments to a prepared statement, perhaps because you plan +to use it in multiple tests to return different values, just include `EXECUTE` +in the SQL string. Here's an example with a prepared statement with a space in +its name, and one where arguments need to be passed: PREPARE "my test" AS SELECT * FROM active_users() WHERE name LIKE 'A%'; PREPARE expect AS SELECT * FROM users WHERE active = $1 AND name LIKE $2; @@ -797,10 +797,10 @@ But a different run of the same query could have the rows in different order: (4 rows) Notice how the two "Tom" rows are reversed. The upshot is that you want to -make sure that your rows are always fully ordered, so that they always appear -in the same order. In a case like that, it means sorting on both the `name` -column and the `age` column. If the sort order of your results isn't -important, consider using `set_eq()` or `bag_eq()` instead. +make sure that your rows are always fully ordered. In a case like the above, +it means sorting on both the `name` column and the `age` column. If the sort +order of your results isn't important, consider using `set_eq()` or `bag_eq()` +instead. Internally, `results_eq()` turns your SQL statements into cursors so that it can iterate over them one row at a time. Conveniently, this behavior is @@ -841,16 +841,17 @@ message to tell you at what row the results differ, something like: # have: (1,Anna) # want: (22,Betty) -On PostgreSQL 8.4 or higher, if there are different numbers of rows in each -result set, you'll see an empty row in the comparison: +If there are different numbers of rows in each result set, an non-existent row +will be represented as "NULL": # Failed test 147 # Results differ beginning at row 5: # have: (1,Anna) - # want: () + # want: NULL -Also on 8.4 or higher, if the number of columns varies between result sets, or -if results are of different data types, you'll get diagnostics like so: +On PostgreSQL 8.4 or higher, if the number of columns varies between result +sets, or if results are of different data types, you'll get diagnostics like +so: # Failed test 148 # Column types differ between queries: diff --git a/pgtap.sql.in b/pgtap.sql.in index 7dba4f774006..6074b0361ba7 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -6143,8 +6143,8 @@ BEGIN 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 '()' END || E'\n' || - ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE '()' END + ' 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; @@ -6159,8 +6159,8 @@ EXCEPTION 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 '()' END || E'\n' || - ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE '()' END + ' 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; $$ LANGUAGE plpgsql; @@ -6309,8 +6309,8 @@ EXCEPTION 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 '()' END || E'\n' || - ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE '()' END + ' 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; $$ LANGUAGE plpgsql; diff --git a/sql/resultset.sql b/sql/resultset.sql index bbb61deff13d..2025405ca129 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -818,7 +818,7 @@ SELECT * FROM check_test( '', ' Results differ beginning at row 2: have: (,) - want: ()' + want: NULL' ); -- And now some failures. @@ -845,7 +845,7 @@ SELECT * FROM check_test( 'results_eq(select, prepared) fail missing last row', '', ' Results differ beginning at row 7: - have: () + have: NULL want: (183,Antonio)' ); @@ -860,7 +860,7 @@ SELECT * FROM check_test( '', ' Results differ beginning at row 7: have: (183,Antonio) - want: ()' + want: NULL' ); -- Compare with missing dupe. @@ -874,7 +874,7 @@ SELECT * FROM check_test( '', ' Results differ beginning at row 3: have: (1,Anna) - want: ()' + want: NULL' ); -- Handle failure with null. @@ -1838,7 +1838,7 @@ SELECT * FROM check_test( '', ' Results differ beginning at row 7: have: (Antonio) - want: ()' + want: NULL' ); SELECT * FROM check_test( From 0971f1955f4b37023efeb70d25ac1b6f600d7657 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 29 Jul 2009 16:49:32 -0700 Subject: [PATCH 0418/1195] Note that we might need C for some future functions. --- README.pgtap | 2 +- pgtap.sql.in | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index 80aa0ca98641..3039fd562953 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3761,7 +3761,7 @@ To Do + `sequence_increments_by()` + `sequence_starts_at()` + `sequence_cycles()` -* Useful result testing functions to consider adding: +* Useful result testing functions to consider adding (but might require C code): + `row_eq()` + `rowtype_is()` diff --git a/pgtap.sql.in b/pgtap.sql.in index 6074b0361ba7..2165a34573aa 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -6428,3 +6428,4 @@ CREATE OR REPLACE FUNCTION results_ne( refcursor, anyarray ) RETURNS TEXT AS $$ SELECT results_ne( $1, $2, NULL::text ); $$ LANGUAGE sql; + From a64fffed9e43c4b21196ffb3c79b145a5ea9afce Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 29 Jul 2009 17:18:45 -0700 Subject: [PATCH 0419/1195] Fixed compatibility with 8.3. --- README.pgtap | 2 +- compat/install-8.3.patch | 29 +++++++++++++------- sql/resultset.sql | 57 ++++++++++++++++++++++++---------------- 3 files changed, 55 insertions(+), 33 deletions(-) diff --git a/README.pgtap b/README.pgtap index 3039fd562953..a438a8937922 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3770,7 +3770,7 @@ Supported Versions pgTAP has been tested on the following builds of PostgreSQL: -* PostgreSQL 8.4beta2 on i386-apple-darwin9.7.0 +* PostgreSQL 8.4.0 on i386-apple-darwin9.7.0 * PostgreSQL 8.3.7 on i386-apple-darwin9.6.0 * PostgreSQL 8.3.6 on i386-redhat-linux-gnu * PostgreSQL 8.2.13 on i386-apple-darwin9.6.0 diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index 6fd9540a66fb..24081adec96d 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -1,6 +1,6 @@ ---- pgtap.sql.orig 2009-07-01 10:24:04.000000000 -0700 -+++ pgtap.sql 2009-07-01 10:35:45.000000000 -0700 -@@ -5780,8 +5780,9 @@ +--- pgtap.sql.orig 2009-07-29 17:03:30.000000000 -0700 ++++ pgtap.sql 2009-07-29 17:05:34.000000000 -0700 +@@ -5791,8 +5791,9 @@ 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 @@ -11,12 +11,21 @@ AND attnum > 0 AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END ORDER BY attnum -@@ -5885,7 +5886,7 @@ - FETCH have INTO rec_have; - FETCH want INTO rec_want; - WHILE rec_have IS NOT NULL OR rec_want IS NOT NULL LOOP -- IF rec_have IS DISTINCT FROM rec_want THEN -+ IF rec_have::text IS DISTINCT FROM rec_want::text THEN +@@ -6140,7 +6141,7 @@ + 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 ++ IF have_rec::text IS DISTINCT FROM want_rec::text OR have_found <> want_found THEN RETURN ok( false, $3 ) || E'\n' || diag( ' Results differ beginning at row ' || rownum || E':\n' || - ' have: ' || CASE WHEN rec_have IS NULL THEN '()' ELSE rec_have::text END || E'\n' || + ' have: ' || CASE WHEN have_found THEN have_rec::text ELSE 'NULL' END || E'\n' || +@@ -6295,7 +6296,7 @@ + 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 ++ IF have_rec::text IS DISTINCT FROM want_rec::text OR have_found <> want_found THEN + RETURN ok( true, $3 ); + ELSE + FETCH have INTO have_rec; diff --git a/sql/resultset.sql b/sql/resultset.sql index 2025405ca129..4f40cb3262e8 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -1995,29 +1995,38 @@ SELECT * FROM check_test( '' ); --- Handle failure due to column mismatch. -SELECT * FROM check_test( - results_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), - false, - 'results_ne(values, values) mismatch', - '', - CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Columns differ between queries:' END || ' - have: (1,foo) - want: (foo,1)' -); - --- -- Handle failure due to more subtle column mismatch, valid only on 8.4. +-- Handle failure due to more subtle column mismatch, valid only on 8.4. CREATE OR REPLACE FUNCTION subtlefail() RETURNS SETOF TEXT AS $$ DECLARE tap record; BEGIN IF pg_version_num() < 80400 THEN -- 8.3 and earlier cast records to text, so subtlety is out. + RETURN NEXT pass('results_ne(values, values) mismatch should fail'); + RETURN NEXT pass('results_ne(values, values) mismatch should have the proper description'); + RETURN NEXT pass('results_ne(values, values) mismatch should have the proper diagnostics'); RETURN NEXT pass('results_ne(values, values) subtle mismatch should fail'); RETURN NEXT pass('results_ne(values, values) subtle mismatch should have the proper description'); RETURN NEXT pass('results_ne(values, values) subtle mismatch should have the proper diagnostics'); + RETURN NEXT pass('results_ne(values, values) fail column count should fail'); + RETURN NEXT pass('results_ne(values, values) fail column count should have the proper description'); + RETURN NEXT pass('results_ne(values, values) fail column count should have the proper diagnostics'); ELSE -- 8.4 does true record comparisions, yay! + -- Handle failure due to column mismatch. + FOR tap IN SELECT * FROM check_test( + results_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'results_ne(values, values) mismatch', + '', + ' Columns differ between queries: + have: (1,foo) + want: (foo,1)' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + -- Handle failure due to subtle column mismatch. FOR tap IN SELECT * FROM check_test( results_ne( 'VALUES (1, ''foo''::varchar), (2, ''bar''::varchar)', @@ -2031,22 +2040,26 @@ BEGIN want: (1,foo)' ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; + + -- Handle failure due to column count mismatch. + FOR tap IN SELECT * FROM check_test( + results_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'results_ne(values, values) fail column count', + '', + ' Columns differ between queries: + have: (1) + want: (foo,1)' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + END IF; RETURN; END; $$ LANGUAGE plpgsql; SELECT * FROM subtlefail(); --- Handle failure due to column count mismatch. -SELECT * FROM check_test( - results_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), - false, - 'results_ne(values, values) fail column count', - '', - CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Columns differ between queries:' END || ' - have: (1) - want: (foo,1)' -); -- Compare with cursors. CLOSE cwant; From 9b292999af3a4b3ed5b11fbe1c0b2fe7c4fd4214 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 29 Jul 2009 17:34:37 -0700 Subject: [PATCH 0420/1195] Moved pg_typeof() to a patch. The pg_typeof() function is included in 8.4, so there was code removing it from `pgtap.sql` when building on 8.4 and later. I'd rather have the latest version of PostgreSQL make no changes to the file, so I've changed things around. Now `pg_typeof()` is no longer in `pgtap.sql.in`, but is added by `compat/install-8.3.patch.`. This keeps things clean (and line numbers correct!) when I'm doing development on 8.4. --- Makefile | 3 +-- compat/install-8.3.patch | 22 +++++++++++++++++----- pgtap.sql.in | 5 ----- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 48f944bde8b1..1609b4ae4f43 100644 --- a/Makefile +++ b/Makefile @@ -61,8 +61,7 @@ TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql sql/enumtap.sql sql/role REGRESS := $(filter-out throwtap runtests enumtap roletap,$(REGRESS)) else ifeq ($(PGVER_MINOR), 4) -# Remove lines 15-20, which define pg_typeof(). -EXTRA_SUBS := -e '14,18d' +# Do nothing. else ifneq ($(PGVER_MINOR), 3) # Enum test not supported by 8.2 and earlier. diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index 24081adec96d..190d71ea0d89 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -1,6 +1,18 @@ ---- pgtap.sql.orig 2009-07-29 17:03:30.000000000 -0700 -+++ pgtap.sql 2009-07-29 17:05:34.000000000 -0700 -@@ -5791,8 +5791,9 @@ +--- pgtap.sql.orig 2009-07-29 17:31:13.000000000 -0700 ++++ pgtap.sql 2009-07-29 17:32:02.000000000 -0700 +@@ -15,6 +15,11 @@ + RETURNS text AS 'SELECT current_setting(''server_version'')' + LANGUAGE SQL IMMUTABLE; + ++CREATE OR REPLACE FUNCTION pg_typeof("any") ++RETURNS regtype ++AS '$libdir/pgtap' ++LANGUAGE C STABLE; ++ + CREATE OR REPLACE FUNCTION pg_version_num() + RETURNS integer AS $$ + SELECT s.a[1]::int * 10000 +@@ -5786,8 +5791,9 @@ 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 @@ -11,7 +23,7 @@ AND attnum > 0 AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END ORDER BY attnum -@@ -6140,7 +6141,7 @@ +@@ -6135,7 +6141,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -20,7 +32,7 @@ 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' || -@@ -6295,7 +6296,7 @@ +@@ -6290,7 +6296,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP diff --git a/pgtap.sql.in b/pgtap.sql.in index 2165a34573aa..efc36f92d070 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -11,11 +11,6 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA, public; -CREATE OR REPLACE FUNCTION pg_typeof("any") -RETURNS regtype -AS 'MODULE_PATHNAME' -LANGUAGE C STABLE; - CREATE OR REPLACE FUNCTION pg_version() RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; From 2633e7fb08fd9cdf5af103030be194590bdd82b2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 29 Jul 2009 17:53:04 -0700 Subject: [PATCH 0421/1195] Updated patch files for 8.2. --- compat/install-8.2.patch | 6 +++--- compat/uninstall-8.2.patch | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 8d9d5fac0ef4..f7576e9046a3 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,6 +1,6 @@ ---- pgtap.sql.orig 2009-05-28 11:32:50.000000000 -0700 -+++ pgtap.sql 2009-05-28 11:34:37.000000000 -0700 -@@ -3288,63 +3288,6 @@ +--- pgtap.sql.orig 2009-07-29 17:45:37.000000000 -0700 ++++ pgtap.sql 2009-07-29 17:46:25.000000000 -0700 +@@ -3298,63 +3298,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; diff --git a/compat/uninstall-8.2.patch b/compat/uninstall-8.2.patch index b2046a1c39e7..e41830836f3d 100644 --- a/compat/uninstall-8.2.patch +++ b/compat/uninstall-8.2.patch @@ -1,6 +1,6 @@ ---- uninstall_pgtap.sql.in 2009-05-28 11:16:45.000000000 -0700 -+++ uninstall_pgtap.sql 2009-05-28 11:24:32.000000000 -0700 -@@ -243,10 +243,6 @@ +--- uninstall_pgtap.sql.orig 2009-07-29 17:49:53.000000000 -0700 ++++ uninstall_pgtap.sql 2009-07-29 17:50:01.000000000 -0700 +@@ -301,10 +301,6 @@ DROP FUNCTION has_role( NAME ); DROP FUNCTION has_role( NAME, TEXT ); DROP FUNCTION _has_role( NAME ); From b9ca38e885af6478a24f96ec700c183d87520b77 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 29 Jul 2009 21:26:38 -0700 Subject: [PATCH 0422/1195] Updated for 8.2 --- compat/install-8.2.patch | 104 ++++++++++++++++++++++++++++++++++++++- compat/install-8.2.sql | 8 +-- 2 files changed, 106 insertions(+), 6 deletions(-) diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index f7576e9046a3..737a89fc004b 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,5 +1,34 @@ ---- pgtap.sql.orig 2009-07-29 17:45:37.000000000 -0700 -+++ pgtap.sql 2009-07-29 17:46:25.000000000 -0700 +--- pgtap.sql.orig 2009-07-29 21:23:40.000000000 -0700 ++++ pgtap.sql 2009-07-29 21:23:57.000000000 -0700 +@@ -194,11 +194,11 @@ + RETURNS integer AS $$ + BEGIN + EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) +- VALUES( ' || $1 || ', ' +- || $2 || ', ' +- || quote_literal(COALESCE($3, '')) || ', ' +- || quote_literal($4) || ', ' +- || quote_literal($5) || ' )'; ++ VALUES( ' || $1::text || ', ' ++ || $2::text || ', ' ++ || quote_literal(COALESCE($3, '')::text) || ', ' ++ || quote_literal($4::text) || ', ' ++ || quote_literal($5::text) || ' )'; + RETURN currval('__tresults___numb_seq'); + END; + $$ LANGUAGE plpgsql; +@@ -486,9 +486,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( @@ -3298,63 +3298,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -64,3 +93,74 @@ CREATE OR REPLACE FUNCTION _has_role( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( +@@ -5815,13 +5758,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. +@@ -6046,7 +5989,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. +@@ -6141,11 +6084,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; +@@ -6160,8 +6103,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; +@@ -6296,7 +6239,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; +@@ -6310,8 +6253,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; diff --git a/compat/install-8.2.sql b/compat/install-8.2.sql index 34b204fa9aa1..064793476870 100644 --- a/compat/install-8.2.sql +++ b/compat/install-8.2.sql @@ -4,21 +4,21 @@ 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 IMPLICIT; +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 IMPLICIT; +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 IMPLICIT; +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[] ) @@ -50,4 +50,4 @@ 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 IMPLICIT; +CREATE CAST (regtype AS text) WITH FUNCTION regtypetext(regtype) AS ASSIGNMENT; From 95a245a1e3ed77cca3baaf5b9910af576a1965f1 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 29 Jul 2009 21:55:06 -0700 Subject: [PATCH 0423/1195] Add pg_typeof() to uninstall only if necesary. --- Makefile | 1 + compat/uninstall-8.3.patch | 10 ++++++++++ uninstall_pgtap.sql.in | 1 - 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 compat/uninstall-8.3.patch diff --git a/Makefile b/Makefile index 1609b4ae4f43..596712de2927 100644 --- a/Makefile +++ b/Makefile @@ -167,6 +167,7 @@ else endif ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 4) + patch -p0 < compat/uninstall-8.3.patch ifneq ($(PGVER_MINOR), 3) patch -p0 < compat/uninstall-8.2.patch mv uninstall_pgtap.sql uninstall_pgtap.tmp diff --git a/compat/uninstall-8.3.patch b/compat/uninstall-8.3.patch new file mode 100644 index 000000000000..788d7bcbb21d --- /dev/null +++ b/compat/uninstall-8.3.patch @@ -0,0 +1,10 @@ +--- uninstall_pgtap.sql.orig 2009-07-29 21:51:55.000000000 -0700 ++++ uninstall_pgtap.sql 2009-07-29 21:52:53.000000000 -0700 +@@ -626,6 +626,7 @@ + DROP FUNCTION _get ( text ); + DROP FUNCTION no_plan(); + DROP FUNCTION plan( integer ); ++DROP FUNCTION pg_typeof("any"); + DROP FUNCTION pgtap_version(); + DROP FUNCTION os_name(); + DROP FUNCTION pg_version_num(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 8b4723f3897d..5bf09552c1c8 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -630,6 +630,5 @@ DROP FUNCTION pgtap_version(); DROP FUNCTION os_name(); DROP FUNCTION pg_version_num(); DROP FUNCTION pg_version(); -DROP FUNCTION pg_typeof("any"); -- ## SET search_path TO public; -- ## DROP SCHEMA TAPSCHEMA; From a3a3773178875502975d08f5e2def51cff1f9276 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 29 Jul 2009 23:07:29 -0700 Subject: [PATCH 0424/1195] Updated diff for 8.1. --- compat/install-8.1.patch | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index 732379f8d5b3..1a90968200e1 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ ---- pgtap.sql.orig 2009-05-28 14:44:51.000000000 -0700 -+++ pgtap.sql 2009-05-28 14:44:51.000000000 -0700 -@@ -5520,7 +5520,7 @@ +--- pgtap.sql.orig 2009-07-29 22:02:08.000000000 -0700 ++++ pgtap.sql 2009-07-29 22:02:23.000000000 -0700 +@@ -5530,7 +5530,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -9,7 +9,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -5528,8 +5528,8 @@ +@@ -5538,8 +5538,8 @@ -- 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. @@ -20,7 +20,7 @@ END LOOP; END LOOP; RETURN; -@@ -5597,14 +5597,14 @@ +@@ -5607,14 +5607,14 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -37,7 +37,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -5619,15 +5619,15 @@ +@@ -5629,15 +5629,15 @@ IF verbose THEN RETURN NEXT diag(tests[i] || '()'); END IF; -- Run the setup functions. @@ -57,7 +57,7 @@ -- Remember how many failed and then roll back. num_faild := num_faild + num_failed(); -@@ -5642,7 +5642,7 @@ +@@ -5652,7 +5652,7 @@ END LOOP; -- Run the shutdown functions. @@ -66,7 +66,7 @@ -- Raise an exception to rollback any changes. RAISE EXCEPTION '__TAP_ROLLBACK__'; -@@ -5653,8 +5653,8 @@ +@@ -5663,8 +5663,8 @@ END IF; END; -- Finish up. From 975956c6063b63d22f193efa997f2e840c433844 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 30 Jul 2009 10:08:23 -0700 Subject: [PATCH 0425/1195] Move use of VALUES to new test file. It turns out that PostgreSQL 8.1 and earlier don't support the `VALUES` statement. So I moved all the tests in `sql/resultset.sql` to `sql/valueset.sql` and disabled them for 8.1 and earlier. Then I changed all the uses of `VALUES` in `sql/resultset.sql` to use either `UNION` queries (for set- and bag-comparing functions, where order doesn't matter) or temporary tables (for result-comparing functions, where order does matter). --- Makefile | 7 +- README.pgtap | 10 +- expected/valueset.out | 351 +++++++++ sql/resultset.sql | 276 ++++--- sql/valueset.sql | 1626 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 2182 insertions(+), 88 deletions(-) create mode 100644 expected/valueset.out create mode 100644 sql/valueset.sql diff --git a/Makefile b/Makefile index 596712de2927..5c9e9e9f5e95 100644 --- a/Makefile +++ b/Makefile @@ -64,10 +64,15 @@ ifeq ($(PGVER_MINOR), 4) # Do nothing. else ifneq ($(PGVER_MINOR), 3) -# Enum test not supported by 8.2 and earlier. +# Enum tests not supported by 8.2 and earlier. TESTS := $(filter-out sql/enumtap.sql,$(TESTS)) REGRESS := $(filter-out enumtap,$(REGRESS)) endif +ifneq ($(PGVER_MINOR), 2) +# Values tests not supported by 8.1 and earlier. +TESTS := $(filter-out sql/valueset.sql,$(TESTS)) +REGRESS := $(filter-out valueset,$(REGRESS)) +endif endif endif endif diff --git a/README.pgtap b/README.pgtap index a438a8937922..29efd32018fd 100644 --- a/README.pgtap +++ b/README.pgtap @@ -766,11 +766,11 @@ second argument may be an array: ); In general, the use of prepared statements is highly recommended to keep your -test code SQLish (you can even use `VALUES` statements in prepared -statements!). But note that, because `results_eq()` does a row-by-row -comparision, the results of the two query arguments must be in exactly the -same order, with exactly the same data types, in order to pass. In practical -terms, it means that you must make sure that your results are never +test code SQLish (you can even use `VALUES` statements in prepared statements +in PostgreSQL 8.2 and up!). But note that, because `results_eq()` does a +row-by-row comparision, the results of the two query arguments must be in +exactly the same order, with exactly the same data types, in order to pass. In +practical terms, it means that you must make sure that your results are never unambiguously ordered. For example, say that you want to compare queries against a `persons` table. diff --git a/expected/valueset.out b/expected/valueset.out new file mode 100644 index 000000000000..e0d92b750ba8 --- /dev/null +++ b/expected/valueset.out @@ -0,0 +1,351 @@ +\unset ECHO +1..349 +ok 1 - Should create a temp table for a prepared statement with space and values +ok 2 - Table __spacenames__ should exist +ok 3 - Should create a temp table for a values statement +ok 4 - Table __somevals__ should exist +ok 5 - set_eq(prepared, prepared, desc) should pass +ok 6 - set_eq(prepared, prepared, desc) should have the proper description +ok 7 - set_eq(prepared, prepared, desc) should have the proper diagnostics +ok 8 - set_eq(prepared, prepared) should pass +ok 9 - set_eq(prepared, prepared) should have the proper description +ok 10 - set_eq(prepared, prepared) should have the proper diagnostics +ok 11 - set_eq(execute, execute, desc) should pass +ok 12 - set_eq(execute, execute, desc) should have the proper description +ok 13 - set_eq(execute, execute, desc) should have the proper diagnostics +ok 14 - set_eq(values, dupe values) should pass +ok 15 - set_eq(values, dupe values) should have the proper description +ok 16 - set_eq(values, dupe values) should have the proper diagnostics +ok 17 - set_eq(select, prepared) fail missing should fail +ok 18 - set_eq(select, prepared) fail missing should have the proper description +ok 19 - set_eq(select, prepared) fail missing should have the proper diagnostics +ok 20 - set_eq(select, prepared) fail missings should fail +ok 21 - set_eq(select, prepared) fail missings should have the proper description +ok 22 - set_eq(select, prepared) fail missings should have the proper diagnostics +ok 23 - set_eq(values, values) fail mismatch should fail +ok 24 - set_eq(values, values) fail mismatch should have the proper description +ok 25 - set_eq(values, values) fail mismatch should have the proper diagnostics +ok 26 - set_eq(values, values) fail column count should fail +ok 27 - set_eq(values, values) fail column count should have the proper description +ok 28 - set_eq(values, values) fail column count should have the proper diagnostics +ok 29 - bag_eq(prepared, prepared, desc) should pass +ok 30 - bag_eq(prepared, prepared, desc) should have the proper description +ok 31 - bag_eq(prepared, prepared, desc) should have the proper diagnostics +ok 32 - bag_eq(prepared, prepared) should pass +ok 33 - bag_eq(prepared, prepared) should have the proper description +ok 34 - bag_eq(prepared, prepared) should have the proper diagnostics +ok 35 - bag_eq(execute, execute) should pass +ok 36 - bag_eq(execute, execute) should have the proper description +ok 37 - bag_eq(execute, execute) should have the proper diagnostics +ok 38 - bag_eq(dupe values, dupe values) should pass +ok 39 - bag_eq(dupe values, dupe values) should have the proper description +ok 40 - bag_eq(dupe values, dupe values) should have the proper diagnostics +ok 41 - bag_eq(select, prepared) fail missing should fail +ok 42 - bag_eq(select, prepared) fail missing should have the proper description +ok 43 - bag_eq(select, prepared) fail missing should have the proper diagnostics +ok 44 - bag_eq(select, prepared) fail missings should fail +ok 45 - bag_eq(select, prepared) fail missings should have the proper description +ok 46 - bag_eq(select, prepared) fail missings should have the proper diagnostics +ok 47 - bag_eq(values, values) fail mismatch should fail +ok 48 - bag_eq(values, values) fail mismatch should have the proper description +ok 49 - bag_eq(values, values) fail mismatch should have the proper diagnostics +ok 50 - bag_eq(values, values) fail column count should fail +ok 51 - bag_eq(values, values) fail column count should have the proper description +ok 52 - bag_eq(values, values) fail column count should have the proper diagnostics +ok 53 - bag_eq(values, values) fail missing dupe should fail +ok 54 - bag_eq(values, values) fail missing dupe should have the proper description +ok 55 - bag_eq(values, values) fail missing dupe should have the proper diagnostics +ok 56 - set_ne(prepared, prepared) fail should fail +ok 57 - set_ne(prepared, prepared) fail should have the proper description +ok 58 - set_ne(prepared, prepared) fail should have the proper diagnostics +ok 59 - set_ne fail with column mismatch should fail +ok 60 - set_ne fail with column mismatch should have the proper description +ok 61 - set_ne fail with column mismatch should have the proper diagnostics +ok 62 - set_ne fail with different col counts should fail +ok 63 - set_ne fail with different col counts should have the proper description +ok 64 - set_ne fail with different col counts should have the proper diagnostics +ok 65 - set_ne fail with dupe should fail +ok 66 - set_ne fail with dupe should have the proper description +ok 67 - set_ne fail with dupe should have the proper diagnostics +ok 68 - bag_ne(prepared, prepared) fail should fail +ok 69 - bag_ne(prepared, prepared) fail should have the proper description +ok 70 - bag_ne(prepared, prepared) fail should have the proper diagnostics +ok 71 - bag_ne fail with column mismatch should fail +ok 72 - bag_ne fail with column mismatch should have the proper description +ok 73 - bag_ne fail with column mismatch should have the proper diagnostics +ok 74 - set_ne pass with dupe should pass +ok 75 - set_ne pass with dupe should have the proper description +ok 76 - set_ne pass with dupe should have the proper diagnostics +ok 77 - bag_ne fail with column mismatch should fail +ok 78 - bag_ne fail with column mismatch should have the proper description +ok 79 - bag_ne fail with column mismatch should have the proper diagnostics +ok 80 - bag_ne fail with different col counts should fail +ok 81 - bag_ne fail with different col counts should have the proper description +ok 82 - bag_ne fail with different col counts should have the proper diagnostics +ok 83 - results_eq(prepared, prepared, desc) should pass +ok 84 - results_eq(prepared, prepared, desc) should have the proper description +ok 85 - results_eq(prepared, prepared, desc) should have the proper diagnostics +ok 86 - results_eq(prepared, prepared) should pass +ok 87 - results_eq(prepared, prepared) should have the proper description +ok 88 - results_eq(prepared, prepared) should have the proper diagnostics +ok 89 - results_eq(execute, execute) should pass +ok 90 - results_eq(execute, execute) should have the proper description +ok 91 - results_eq(execute, execute) should have the proper diagnostics +ok 92 - results_eq(dupe values, dupe values) should pass +ok 93 - results_eq(dupe values, dupe values) should have the proper description +ok 94 - results_eq(dupe values, dupe values) should have the proper diagnostics +ok 95 - results_eq(values with null, values with null) should pass +ok 96 - results_eq(values with null, values with null) should have the proper description +ok 97 - results_eq(values with null, values with null) should have the proper diagnostics +ok 98 - results_eq(nulls, nulls) should pass +ok 99 - results_eq(nulls, nulls) should have the proper description +ok 100 - results_eq(nulls, nulls) should have the proper diagnostics +ok 101 - results_eq(nulls, nulls) fail should fail +ok 102 - results_eq(nulls, nulls) fail should have the proper description +ok 103 - results_eq(nulls, nulls) fail should have the proper diagnostics +ok 104 - results_eq(select, prepared) fail missing last row should fail +ok 105 - results_eq(select, prepared) fail missing last row should have the proper description +ok 106 - results_eq(select, prepared) fail missing last row should have the proper diagnostics +ok 107 - results_eq(prepared, select) fail missing first row should fail +ok 108 - results_eq(prepared, select) fail missing first row should have the proper description +ok 109 - results_eq(prepared, select) fail missing first row should have the proper diagnostics +ok 110 - results_eq(values dupe, values) should fail +ok 111 - results_eq(values dupe, values) should have the proper description +ok 112 - results_eq(values dupe, values) should have the proper diagnostics +ok 113 - results_eq(values null, values) should fail +ok 114 - results_eq(values null, values) should have the proper description +ok 115 - results_eq(values null, values) should have the proper diagnostics +ok 116 - results_eq(values, values) mismatch should fail +ok 117 - results_eq(values, values) mismatch should have the proper description +ok 118 - results_eq(values, values) mismatch should have the proper diagnostics +ok 119 - results_eq(values, values) subtle mismatch should fail +ok 120 - results_eq(values, values) subtle mismatch should have the proper description +ok 121 - results_eq(values, values) subtle mismatch should have the proper diagnostics +ok 122 - results_eq(values, values) fail column count should fail +ok 123 - results_eq(values, values) fail column count should have the proper description +ok 124 - results_eq(values, values) fail column count should have the proper diagnostics +ok 125 - results_eq(cursor, prepared) should pass +ok 126 - results_eq(cursor, prepared) should have the proper description +ok 127 - results_eq(cursor, prepared) should have the proper diagnostics +ok 128 - results_eq(prepared, cursor) should pass +ok 129 - results_eq(prepared, cursor) should have the proper description +ok 130 - results_eq(prepared, cursor) should have the proper diagnostics +ok 131 - set_has( prepared, prepared, description ) should pass +ok 132 - set_has( prepared, prepared, description ) should have the proper description +ok 133 - set_has( prepared, prepared, description ) should have the proper diagnostics +ok 134 - set_has( prepared, subprepared ) should pass +ok 135 - set_has( prepared, subprepared ) should have the proper description +ok 136 - set_has( prepared, subprepared ) should have the proper diagnostics +ok 137 - set_has( execute, execute ) should pass +ok 138 - set_has( execute, execute ) should have the proper description +ok 139 - set_has( execute, execute ) should have the proper diagnostics +ok 140 - set_has( prepared, dupes ) should pass +ok 141 - set_has( prepared, dupes ) should have the proper description +ok 142 - set_has( prepared, dupes ) should have the proper diagnostics +ok 143 - set_has( dupes, values ) should pass +ok 144 - set_has( dupes, values ) should have the proper description +ok 145 - set_has( dupes, values ) should have the proper diagnostics +ok 146 - set_has( missing1, expect ) should fail +ok 147 - set_has( missing1, expect ) should have the proper description +ok 148 - set_has( missing1, expect ) should have the proper diagnostics +ok 149 - set_has(missing2, expect ) should fail +ok 150 - set_has(missing2, expect ) should have the proper description +ok 151 - set_has(missing2, expect ) should have the proper diagnostics +ok 152 - set_has((int,text), (text,int)) should fail +ok 153 - set_has((int,text), (text,int)) should have the proper description +ok 154 - set_has((int,text), (text,int)) should have the proper diagnostics +ok 155 - set_has((int), (text,int)) should fail +ok 156 - set_has((int), (text,int)) should have the proper description +ok 157 - set_has((int), (text,int)) should have the proper diagnostics +ok 158 - bag_has( prepared, prepared, description ) should pass +ok 159 - bag_has( prepared, prepared, description ) should have the proper description +ok 160 - bag_has( prepared, prepared, description ) should have the proper diagnostics +ok 161 - bag_has( prepared, subprepared ) should pass +ok 162 - bag_has( prepared, subprepared ) should have the proper description +ok 163 - bag_has( prepared, subprepared ) should have the proper diagnostics +ok 164 - bag_has( execute, execute ) should pass +ok 165 - bag_has( execute, execute ) should have the proper description +ok 166 - bag_has( execute, execute ) should have the proper diagnostics +ok 167 - bag_has( prepared, dupes ) should fail +ok 168 - bag_has( prepared, dupes ) should have the proper description +ok 169 - bag_has( prepared, dupes ) should have the proper diagnostics +ok 170 - bag_has( dupes, values ) should pass +ok 171 - bag_has( dupes, values ) should have the proper description +ok 172 - bag_has( dupes, values ) should have the proper diagnostics +ok 173 - bag_has( missing1, expect ) should fail +ok 174 - bag_has( missing1, expect ) should have the proper description +ok 175 - bag_has( missing1, expect ) should have the proper diagnostics +ok 176 - bag_has(missing2, expect ) should fail +ok 177 - bag_has(missing2, expect ) should have the proper description +ok 178 - bag_has(missing2, expect ) should have the proper diagnostics +ok 179 - bag_has((int,text), (text,int)) should fail +ok 180 - bag_has((int,text), (text,int)) should have the proper description +ok 181 - bag_has((int,text), (text,int)) should have the proper diagnostics +ok 182 - bag_has((int), (text,int)) should fail +ok 183 - bag_has((int), (text,int)) should have the proper description +ok 184 - bag_has((int), (text,int)) should have the proper diagnostics +ok 185 - set_hasnt( prepared, prepared, description ) should pass +ok 186 - set_hasnt( prepared, prepared, description ) should have the proper description +ok 187 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 188 - set_hasnt( prepared, prepared, description ) should pass +ok 189 - set_hasnt( prepared, prepared, description ) should have the proper description +ok 190 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 191 - set_hasnt( execute, execute ) should pass +ok 192 - set_hasnt( execute, execute ) should have the proper description +ok 193 - set_hasnt( execute, execute ) should have the proper diagnostics +ok 194 - set_hasnt( prepared, dupes ) should pass +ok 195 - set_hasnt( prepared, dupes ) should have the proper description +ok 196 - set_hasnt( prepared, dupes ) should have the proper diagnostics +ok 197 - set_hasnt( prepared, value ) should fail +ok 198 - set_hasnt( prepared, value ) should have the proper description +ok 199 - set_hasnt( prepared, value ) should have the proper diagnostics +ok 200 - set_hasnt( prepared, values ) should fail +ok 201 - set_hasnt( prepared, values ) should have the proper description +ok 202 - set_hasnt( prepared, values ) should have the proper diagnostics +ok 203 - set_hasnt((int,text), (text,int)) should fail +ok 204 - set_hasnt((int,text), (text,int)) should have the proper description +ok 205 - set_hasnt((int,text), (text,int)) should have the proper diagnostics +ok 206 - set_hasnt((int), (text,int)) should fail +ok 207 - set_hasnt((int), (text,int)) should have the proper description +ok 208 - set_hasnt((int), (text,int)) should have the proper diagnostics +ok 209 - bag_hasnt( prepared, prepared, description ) should pass +ok 210 - bag_hasnt( prepared, prepared, description ) should have the proper description +ok 211 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 212 - bag_hasnt( prepared, prepared, description ) should pass +ok 213 - bag_hasnt( prepared, prepared, description ) should have the proper description +ok 214 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 215 - bag_hasnt( execute, execute ) should pass +ok 216 - bag_hasnt( execute, execute ) should have the proper description +ok 217 - bag_hasnt( execute, execute ) should have the proper diagnostics +ok 218 - bag_hasnt( prepared, value ) should fail +ok 219 - bag_hasnt( prepared, value ) should have the proper description +ok 220 - bag_hasnt( prepared, value ) should have the proper diagnostics +ok 221 - bag_hasnt( prepared, values ) should fail +ok 222 - bag_hasnt( prepared, values ) should have the proper description +ok 223 - bag_hasnt( prepared, values ) should have the proper diagnostics +ok 224 - bag_hasnt((int,text), (text,int)) should fail +ok 225 - bag_hasnt((int,text), (text,int)) should have the proper description +ok 226 - bag_hasnt((int,text), (text,int)) should have the proper diagnostics +ok 227 - bag_hasnt((int), (text,int)) should fail +ok 228 - bag_hasnt((int), (text,int)) should have the proper description +ok 229 - bag_hasnt((int), (text,int)) should have the proper diagnostics +ok 230 - bag_hasnt( dupes, dupes ) should fail +ok 231 - bag_hasnt( dupes, dupes ) should have the proper description +ok 232 - bag_hasnt( dupes, dupes ) should have the proper diagnostics +ok 233 - bag_hasnt( value, dupes ) should fail +ok 234 - bag_hasnt( value, dupes ) should have the proper description +ok 235 - bag_hasnt( value, dupes ) should have the proper diagnostics +ok 236 - set_eq(prepared, array, desc) should pass +ok 237 - set_eq(prepared, array, desc) should have the proper description +ok 238 - set_eq(prepared, array, desc) should have the proper diagnostics +ok 239 - set_eq(prepared, array) should pass +ok 240 - set_eq(prepared, array) should have the proper description +ok 241 - set_eq(prepared, array) should have the proper diagnostics +ok 242 - set_eq(prepared, dupe array) should pass +ok 243 - set_eq(prepared, dupe array) should have the proper description +ok 244 - set_eq(prepared, dupe array) should have the proper diagnostics +ok 245 - set_eq(prepared, array) extra record should fail +ok 246 - set_eq(prepared, array) extra record should have the proper description +ok 247 - set_eq(prepared, array) extra record should have the proper diagnostics +ok 248 - set_eq(prepared, array) missing record should fail +ok 249 - set_eq(prepared, array) missing record should have the proper description +ok 250 - set_eq(prepared, array) missing record should have the proper diagnostics +ok 251 - set_eq(sql, array) incompatible types should fail +ok 252 - set_eq(sql, array) incompatible types should have the proper description +ok 253 - set_eq(sql, array) incompatible types should have the proper diagnostics +ok 254 - bag_eq(prepared, array, desc) should pass +ok 255 - bag_eq(prepared, array, desc) should have the proper description +ok 256 - bag_eq(prepared, array, desc) should have the proper diagnostics +ok 257 - bag_eq(prepared, array) should pass +ok 258 - bag_eq(prepared, array) should have the proper description +ok 259 - bag_eq(prepared, array) should have the proper diagnostics +ok 260 - bag_eq(prepared, dupe array) fail should fail +ok 261 - bag_eq(prepared, dupe array) fail should have the proper description +ok 262 - bag_eq(prepared, dupe array) fail should have the proper diagnostics +ok 263 - bag_eq(prepared, array) extra record should fail +ok 264 - bag_eq(prepared, array) extra record should have the proper description +ok 265 - bag_eq(prepared, array) extra record should have the proper diagnostics +ok 266 - bag_eq(prepared, array) missing record should fail +ok 267 - bag_eq(prepared, array) missing record should have the proper description +ok 268 - bag_eq(prepared, array) missing record should have the proper diagnostics +ok 269 - bag_eq(prepared, array) incompatible types should fail +ok 270 - bag_eq(prepared, array) incompatible types should have the proper description +ok 271 - bag_eq(prepared, array) incompatible types should have the proper diagnostics +ok 272 - set_ne(prepared, array, desc) should pass +ok 273 - set_ne(prepared, array, desc) should have the proper description +ok 274 - set_ne(prepared, array, desc) should have the proper diagnostics +ok 275 - set_ne(prepared, array) should pass +ok 276 - set_ne(prepared, array) should have the proper description +ok 277 - set_ne(prepared, array) should have the proper diagnostics +ok 278 - set_ne(prepared, array) fail should fail +ok 279 - set_ne(prepared, array) fail should have the proper description +ok 280 - set_ne(prepared, array) fail should have the proper diagnostics +ok 281 - set_ne(prepared, dupes array) fail should fail +ok 282 - set_ne(prepared, dupes array) fail should have the proper description +ok 283 - set_ne(prepared, dupes array) fail should have the proper diagnostics +ok 284 - set_ne(sql, array) incompatible types should fail +ok 285 - set_ne(sql, array) incompatible types should have the proper description +ok 286 - set_ne(sql, array) incompatible types should have the proper diagnostics +ok 287 - bag_ne(prepared, array, desc) should pass +ok 288 - bag_ne(prepared, array, desc) should have the proper description +ok 289 - bag_ne(prepared, array, desc) should have the proper diagnostics +ok 290 - bag_ne(prepared, array) should pass +ok 291 - bag_ne(prepared, array) should have the proper description +ok 292 - bag_ne(prepared, array) should have the proper diagnostics +ok 293 - bag_ne(prepared, array) fail should fail +ok 294 - bag_ne(prepared, array) fail should have the proper description +ok 295 - bag_ne(prepared, array) fail should have the proper diagnostics +ok 296 - bag_ne(prepared, dupes array) should pass +ok 297 - bag_ne(prepared, dupes array) should have the proper description +ok 298 - bag_ne(prepared, dupes array) should have the proper diagnostics +ok 299 - bag_ne(prepared, array) incompatible types should fail +ok 300 - bag_ne(prepared, array) incompatible types should have the proper description +ok 301 - bag_ne(prepared, array) incompatible types should have the proper diagnostics +ok 302 - results_eq(prepared, array, desc) should pass +ok 303 - results_eq(prepared, array, desc) should have the proper description +ok 304 - results_eq(prepared, array, desc) should have the proper diagnostics +ok 305 - results_eq(prepared, array) should pass +ok 306 - results_eq(prepared, array) should have the proper description +ok 307 - results_eq(prepared, array) should have the proper diagnostics +ok 308 - results_eq(prepared, array) extra record should fail +ok 309 - results_eq(prepared, array) extra record should have the proper description +ok 310 - results_eq(prepared, array) extra record should have the proper diagnostics +ok 311 - results_ne(prepared, prepared, desc) should pass +ok 312 - results_ne(prepared, prepared, desc) should have the proper description +ok 313 - results_ne(prepared, prepared, desc) should have the proper diagnostics +ok 314 - results_ne(prepared, prepared) should pass +ok 315 - results_ne(prepared, prepared) should have the proper description +ok 316 - results_ne(prepared, prepared) should have the proper diagnostics +ok 317 - results_ne(execute, execute) should pass +ok 318 - results_ne(execute, execute) should have the proper description +ok 319 - results_ne(execute, execute) should have the proper diagnostics +ok 320 - results_ne(dupe values, dupe values) should pass +ok 321 - results_ne(dupe values, dupe values) should have the proper description +ok 322 - results_ne(dupe values, dupe values) should have the proper diagnostics +ok 323 - results_ne(values with null, values with null) should pass +ok 324 - results_ne(values with null, values with null) should have the proper description +ok 325 - results_ne(values with null, values with null) should have the proper diagnostics +ok 326 - results_ne(nulls, nulls) should pass +ok 327 - results_ne(nulls, nulls) should have the proper description +ok 328 - results_ne(nulls, nulls) should have the proper diagnostics +ok 329 - results_ne(values dupe, values) should pass +ok 330 - results_ne(values dupe, values) should have the proper description +ok 331 - results_ne(values dupe, values) should have the proper diagnostics +ok 332 - results_ne(values null, values) should pass +ok 333 - results_ne(values null, values) should have the proper description +ok 334 - results_ne(values null, values) should have the proper diagnostics +ok 335 - results_ne(values, values) mismatch should fail +ok 336 - results_ne(values, values) mismatch should have the proper description +ok 337 - results_ne(values, values) mismatch should have the proper diagnostics +ok 338 - results_ne(values, values) subtle mismatch should fail +ok 339 - results_ne(values, values) subtle mismatch should have the proper description +ok 340 - results_ne(values, values) subtle mismatch should have the proper diagnostics +ok 341 - results_ne(values, values) fail column count should fail +ok 342 - results_ne(values, values) fail column count should have the proper description +ok 343 - results_ne(values, values) fail column count should have the proper diagnostics +ok 344 - results_ne(cursor, prepared) should fail +ok 345 - results_ne(cursor, prepared) should have the proper description +ok 346 - results_ne(cursor, prepared) should have the proper diagnostics +ok 347 - results_ne(prepared, cursor) should fail +ok 348 - results_ne(prepared, cursor) should have the proper description +ok 349 - results_ne(prepared, cursor) should have the proper diagnostics diff --git a/sql/resultset.sql b/sql/resultset.sql index 4f40cb3262e8..26d29cb600fc 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -221,9 +221,15 @@ SELECT id, name FROM names WHERE name like 'An%'; -- We'll use these prepared statements. PREPARE anames AS SELECT id, name FROM names WHERE name like 'An%'; -PREPARE expect AS VALUES (11, 'Andrew'), ( 44, 'Anna'), (15, 'Anthony'), - (183, 'Antonio'), (86, 'Angelina'), (130, 'Andrea'), - (63, 'Angel'); +CREATE TABLE toexpect (id int, name text); +INSERT INTO toexpect (id, name) VALUES(11, 'Andrew'); +INSERT INTO toexpect (id, name) VALUES(44, 'Anna'); +INSERT INTO toexpect (id, name) VALUES(15, 'Anthony'); +INSERT INTO toexpect (id, name) VALUES(183, 'Antonio'); +INSERT INTO toexpect (id, name) VALUES(86, 'Angelina'); +INSERT INTO toexpect (id, name) VALUES(130, 'Andrea'); +INSERT INTO toexpect (id, name) VALUES(63, 'Angel'); +PREPARE expect AS SELECT id, name FROM toexpect; /****************************************************************************/ -- First, test _temptable. @@ -242,7 +248,7 @@ SELECT is( ); SELECT has_table('__somenames__' ); -PREPARE "something cool" AS VALUES (1, 2), (3, 4); +PREPARE "something cool" AS SELECT 1 AS a, 2 AS b; SELECT is( _temptable( '"something cool"', '__spacenames__' ), '__spacenames__', @@ -294,8 +300,8 @@ SELECT * FROM check_test( -- Make sure that dupes are disregarded. SELECT * FROM check_test( set_eq( - 'VALUES (1, ''Anna'')', - 'VALUES (1, ''Anna''), (1, ''Anna'')' + 'SELECT 1 AS a, ''Anna''::text AS b', + 'SELECT 1 AS a, ''Anna''::text AS b UNION ALL SELECT 1, ''Anna''' ), true, 'set_eq(values, dupe values)', @@ -389,7 +395,9 @@ SELECT * FROM check_test( -- Handle failure due to column mismatch. SELECT * FROM check_test( - set_eq( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + set_eq( + 'SELECT 1 AS a, ''foo''::text AS b UNION ALL SELECT 2, ''bar''', + 'SELECT ''foo''::text AS a, 1 AS b UNION ALL SELECT ''bar'', 2' ), false, 'set_eq(values, values) fail mismatch', '', @@ -400,7 +408,10 @@ SELECT * FROM check_test( -- Handle failure due to column count mismatch. SELECT * FROM check_test( - set_eq( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + set_eq( + 'SELECT 1 AS a UNION ALL SELECT 2 AS b', + 'SELECT ''foo''::text AS a, 1 AS b UNION ALL SELECT ''bar'', 2' + ), false, 'set_eq(values, values) fail column count', '', @@ -452,8 +463,8 @@ SELECT * FROM check_test( -- Compare with dupes. SELECT * FROM check_test( bag_eq( - 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', - 'VALUES (1, ''Anna''), (1, ''Anna''), (86, ''Angelina'')' + 'SELECT 1 AS a, ''Anna''::text AS b UNION ALL SELECT 86, ''Angelina'' UNION ALL SELECT 1, ''Anna''', + 'SELECT 1 AS a, ''Anna''::text AS b UNION ALL SELECT 1, ''Anna'' UNION ALL SELECT 86, ''Angelina''' ), true, 'bag_eq(dupe values, dupe values)', @@ -547,7 +558,10 @@ SELECT * FROM check_test( -- Handle failure due to column mismatch. SELECT * FROM check_test( - bag_eq( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + bag_eq( + 'SELECT 1 AS a, ''foo''::text AS b UNION ALL SELECT 2, ''bar''', + 'SELECT ''foo''::text AS a, 1 AS b UNION ALL SELECT ''bar'', 2' + ), false, 'bag_eq(values, values) fail mismatch', '', @@ -558,7 +572,10 @@ SELECT * FROM check_test( -- Handle failure due to column count mismatch. SELECT * FROM check_test( - bag_eq( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + bag_eq( + 'SELECT 1 AS a UNION ALL SELECT 2 AS b', + 'SELECT ''foo''::text AS a, 1 AS b UNION ALL SELECT ''bar'', 2' + ), false, 'bag_eq(values, values) fail column count', '', @@ -570,8 +587,8 @@ SELECT * FROM check_test( -- Handle failure due to missing dupe. SELECT * FROM check_test( bag_eq( - 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', - 'VALUES (1, ''Anna''), (86, ''Angelina'')' + 'SELECT 1 AS a, ''Anna''::text AS b UNION ALL SELECT 86, ''Angelina'' UNION ALL SELECT 1, ''Anna''', + 'SELECT 1 AS a, ''Anna''::TEXT AS b UNION ALL SELECT 86, ''Angelina''' ), false, 'bag_eq(values, values) fail missing dupe', @@ -616,7 +633,10 @@ SELECT * FROM check_test( -- Handle fail with column mismatch. SELECT * FROM check_test( - set_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + set_ne( + 'SELECT 1 AS a, ''foo''::text AS b UNION ALL SELECT 2, ''bar''', + 'SELECT ''foo''::text AS a, 1 AS b UNION ALL SELECT ''bar'', 2' + ), false, 'set_ne fail with column mismatch', '', @@ -627,7 +647,10 @@ SELECT * FROM check_test( -- Handle failure due to column count mismatch. SELECT * FROM check_test( - set_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + set_ne( + 'SELECT 1 UNION ALL SELECT 2', + 'SELECT ''foo''::text AS a, 1 UNION ALL SELECT ''bar'', 2' + ), false, 'set_ne fail with different col counts', '', @@ -639,8 +662,8 @@ SELECT * FROM check_test( -- Handle fail with a dupe. SELECT * FROM check_test( set_ne( - 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', - 'VALUES (1, ''Anna''), (86, ''Angelina'')' + 'SELECT 1 AS a, ''Anna''::text UNION ALL SELECT 86, ''Angelina'' UNION ALL SELECT 1, ''Anna''', + 'SELECT 1 AS a, ''Anna''::text UNION ALL SELECT 86, ''Angelina''' ), false, 'set_ne fail with dupe', @@ -683,7 +706,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - bag_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + bag_ne( + 'SELECT 1 AS a, ''foo''::text UNION ALL SELECT 2, ''bar''', + 'SELECT ''foo'' AS a, 1 UNION ALL SELECT ''bar'', 2' + ), false, 'bag_ne fail with column mismatch', '', @@ -695,8 +721,8 @@ SELECT * FROM check_test( -- Handle pass with a dupe. SELECT * FROM check_test( bag_ne( - 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', - 'VALUES (1, ''Anna''), (86, ''Angelina'')' + 'SELECT 1 AS a, ''Anna''::text UNION ALL SELECT 86, ''Angelina'' UNION ALL SELECT 1, ''Anna''', + 'SELECT 1 AS a, ''Anna''::text UNION ALL SELECT 86, ''Angelina''' ), true, 'set_ne pass with dupe', @@ -706,7 +732,10 @@ SELECT * FROM check_test( -- Handle fail with column mismatch. SELECT * FROM check_test( - bag_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + bag_ne( + 'SELECT 1 AS a, ''foo''::text UNION ALL SELECT 2, ''bar''', + 'SELECT ''foo'' AS a, 1 UNION ALL SELECT ''bar'', 2' + ), false, 'bag_ne fail with column mismatch', '', @@ -717,7 +746,10 @@ SELECT * FROM check_test( -- Handle failure due to column count mismatch. SELECT * FROM check_test( - bag_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + bag_ne( + 'SELECT 1 UNION SELECT 2', + 'SELECT ''foo''::text AS a, 1 UNION SELECT ''bar'', 2' + ), false, 'bag_ne fail with different col counts', '', @@ -730,9 +762,7 @@ SELECT * FROM check_test( -- Now test results_eq(). PREPARE anames_ord AS SELECT id, name FROM names WHERE name like 'An%' ORDER BY id; -PREPARE expect_ord AS VALUES (11, 'Andrew'), (15, 'Anthony'), ( 44, 'Anna'), - (63, 'Angel'), (86, 'Angelina'), (130, 'Andrea'), - (183, 'Antonio'); +PREPARE expect_ord AS SELECT id, name FROM toexpect ORDER BY id; SELECT * FROM check_test( results_eq( 'anames_ord', 'expect_ord', 'whatever' ), @@ -772,10 +802,17 @@ SELECT * FROM check_test( ); -- Compare with dupes. +SET client_min_messages = warning; +CREATE table dupes (pk SERIAL PRIMARY KEY, id int, name text); +RESET client_min_messages; +INSERT INTO dupes (id, name) VALUES(1, 'Anna'); +INSERT INTO dupes (id, name) VALUES(86, 'Angelina'); +INSERT INTO dupes (id, name) VALUES(1, 'Anna'); + SELECT * FROM check_test( results_eq( - 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', - 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')' + 'SELECT id, name FROM dupes ORDER BY pk', + 'SELECT id, name FROM dupes ORDER BY pk' ), true, 'results_eq(dupe values, dupe values)', @@ -783,11 +820,12 @@ SELECT * FROM check_test( '' ); +UPDATE dupes SET name = NULL WHERE pk = 1; -- Compare with nulls. SELECT * FROM check_test( results_eq( - 'VALUES (4, NULL), (86, ''Angelina''), (1, ''Anna'')', - 'VALUES (4, NULL), (86, ''Angelina''), (1, ''Anna'')' + 'SELECT id, name FROM dupes ORDER BY pk', + 'SELECT id, name FROM dupes ORDER BY pk' ), true, 'results_eq(values with null, values with null)', @@ -795,11 +833,12 @@ SELECT * FROM check_test( '' ); +UPDATE dupes SET id = NULL, name = NULL; -- Compare only NULLs SELECT * FROM check_test( results_eq( - 'VALUES (NULL, NULL), (NULL, NULL)', - 'VALUES (NULL, NULL), (NULL, NULL)' + 'SELECT id, name FROM dupes LIMIT 2', + 'SELECT id, name FROM dupes LIMIT 2' ), true, 'results_eq(nulls, nulls)', @@ -807,11 +846,11 @@ SELECT * FROM check_test( '' ); --- Compare only NULLs +-- Compare differnt rows of NULLs SELECT * FROM check_test( results_eq( - 'VALUES (NULL, NULL), (NULL, NULL)', - 'VALUES (NULL, NULL)' + 'SELECT id, name FROM dupes LIMIT 2', + 'SELECT id, name FROM dupes LIMIT 1' ), false, 'results_eq(nulls, nulls) fail', @@ -864,10 +903,17 @@ SELECT * FROM check_test( ); -- Compare with missing dupe. +SET client_min_messages = warning; +CREATE table dubs (pk SERIAL PRIMARY KEY, id int, name text); +RESET client_min_messages; +INSERT INTO dubs (id, name) VALUES(1, 'Anna'); +INSERT INTO dubs (id, name) VALUES(86, 'Angelina'); +INSERT INTO dubs (id, name) VALUES(1, 'Anna'); + SELECT * FROM check_test( results_eq( - 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', - 'VALUES (1, ''Anna''), (86, ''Angelina'')' + 'SELECT id, name from dubs ORDER BY pk', + 'SELECT id, name from dubs ORDER BY pk LIMIT 2' ), false, 'results_eq(values dupe, values)', @@ -877,11 +923,12 @@ SELECT * FROM check_test( want: NULL' ); +UPDATE dubs SET name = NULL WHERE pk = 1; -- Handle failure with null. SELECT * FROM check_test( results_eq( - 'VALUES (1, NULL), (86, ''Angelina'')', - 'VALUES (1, ''Anna''), (86, ''Angelina'')' + 'SELECT id, name from dubs ORDER BY pk LIMIT 2', + 'SELECT id, name from dubs ORDER BY pk DESC LIMIT 2' ), false, 'results_eq(values null, values)', @@ -891,9 +938,15 @@ SELECT * FROM check_test( want: (1,Anna)' ); +UPDATE dubs SET name = 'foo' WHERE pk = 1; +UPDATE dubs SET name = 'bar' WHERE pk = 2; + -- Handle failure due to column mismatch. SELECT * FROM check_test( - results_eq( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + results_eq( + 'SELECT pk, name from dubs ORDER BY pk LIMIT 2', + 'SELECT name, pk from dubs ORDER BY pk LIMIT 2' + ), false, 'results_eq(values, values) mismatch', '', @@ -935,7 +988,10 @@ SELECT * FROM subtlefail(); -- Handle failure due to column count mismatch. SELECT * FROM check_test( - results_eq( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + results_eq( + 'SELECT pk from dubs ORDER BY pk LIMIT 2', + 'SELECT name, pk from dubs ORDER BY pk LIMIT 2' + ), false, 'results_eq(values, values) fail column count', '', @@ -1006,7 +1062,7 @@ SELECT * FROM check_test( '' ); -PREPARE subset AS VALUES (11, 'Andrew'), ( 44, 'Anna'), (63, 'Angel'); +PREPARE subset AS SELECT id, name FROM toexpect WHERE id IN (11, 44, 63); SELECT * FROM check_test( set_has( 'anames', 'subset' ), @@ -1047,7 +1103,7 @@ SELECT * FROM check_test( -- Make sure that dupes are ignored. SELECT * FROM check_test( - set_has( 'anames', 'VALUES (44, ''Anna''), (44, ''Anna'')' ), + set_has( 'anames', 'SELECT 44 AS a, ''Anna''::text UNION ALL SELECT 44, ''Anna''' ), true, 'set_has( prepared, dupes )', '', @@ -1055,7 +1111,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - set_has( 'VALUES (44, ''Anna''), (44, ''Anna'')', 'VALUES(44, ''Anna'')' ), + set_has( + 'SELECT 44 AS a, ''Anna''::text UNION ALL SELECT 44, ''Anna''', + 'SELECT 44 AS a, ''Anna''::text' + ), true, 'set_has( dupes, values )', '', @@ -1091,7 +1150,9 @@ SELECT * FROM check_test( -- Handle failure due to column mismatch. SELECT * FROM check_test( - set_has( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + set_has( + 'SELECT 1 AS a, ''foo''::text UNION ALL SELECT 2, ''bar''', + 'SELECT ''foo''::text AS a, 1 UNION ALL SELECT ''bar'', 2' ), false, 'set_has((int,text), (text,int))', '', @@ -1102,7 +1163,10 @@ SELECT * FROM check_test( -- Handle failure due to column count mismatch. SELECT * FROM check_test( - set_has( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + set_has( + 'SELECT 1 UNION SELECT 2', + 'SELECT ''foo''::text AS a, 1 UNION SELECT ''bar'', 2' + ), false, 'set_has((int), (text,int))', '', @@ -1160,7 +1224,10 @@ SELECT * FROM check_test( -- Make sure that dupes are not ignored. SELECT * FROM check_test( - bag_has( 'anames', 'VALUES (44, ''Anna''), (44, ''Anna'')' ), + bag_has( + 'anames', + 'SELECT 44 AS a, ''Anna''::text UNION ALL SELECT 44, ''Anna''' + ), false, 'bag_has( prepared, dupes )', '', @@ -1169,7 +1236,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - bag_has( 'VALUES (44, ''Anna''), (44, ''Anna'')', 'VALUES(44, ''Anna'')' ), + bag_has( + 'SELECT 44 AS a, ''Anna''::text UNION ALL SELECT 44, ''Anna''', + 'SELECT 44 AS a, ''Anna''::text' + ), true, 'bag_has( dupes, values )', '', @@ -1204,7 +1274,10 @@ SELECT * FROM check_test( -- Handle failure due to column mismatch. SELECT * FROM check_test( - bag_has( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + bag_has( + 'SELECT 1 AS a, ''foo''::text UNION SELECT 2, ''bar''', + 'SELECT ''foo''::text AS a, 1 UNION SELECT ''bar'', 2' + ), false, 'bag_has((int,text), (text,int))', '', @@ -1215,7 +1288,10 @@ SELECT * FROM check_test( -- Handle failure due to column count mismatch. SELECT * FROM check_test( - bag_has( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + bag_has( + 'SELECT 1 UNION SELECT 2', + 'SELECT ''foo''::text AS a, 1 UNION SELECT ''bar'', 2' + ), false, 'bag_has((int), (text,int))', '', @@ -1227,7 +1303,11 @@ SELECT * FROM check_test( /****************************************************************************/ -- Now test set_hasnt(). -PREPARE others AS VALUES ( 44, 'Larry' ), (52, 'Tom'), (23, 'Damian' ); +CREATE TABLE folk (id int, name text); +INSERT INTO folk (id, name) VALUES ( 44, 'Larry' ); +INSERT INTO folk (id, name) VALUES (52, 'Tom'); +INSERT INTO folk (id, name) VALUES (23, 'Damian' ); +PREPARE others AS SELECT id, name FROM folk; SELECT * FROM check_test( set_hasnt( 'anames', 'others', 'whatever' ), @@ -1276,17 +1356,15 @@ SELECT * FROM check_test( -- Make sure that dupes are ignored. SELECT * FROM check_test( - set_hasnt( 'anames', 'VALUES (44, ''Bob''), (44, ''Bob'')' ), + set_hasnt( 'anames', 'SELECT 44 AS a, ''Bob''::text UNION ALL SELECT 44, ''Bob''' ), true, 'set_hasnt( prepared, dupes )', '', '' ); -PREPARE overlap AS VALUES ( 44, 'Larry' ), (52, 'Tom'), (23, 'Damian' ); - SELECT * FROM check_test( - set_hasnt( 'anames', 'VALUES (44,''Anna'')' ), + set_hasnt( 'anames', 'SELECT 44 AS a, ''Anna''::text' ), false, 'set_hasnt( prepared, value )', '', @@ -1295,7 +1373,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - set_hasnt( 'anames', 'VALUES (44, ''Anna''), (86, ''Angelina'')' ), + set_hasnt( 'anames', 'SELECT 44 AS a, ''Anna''::text UNION SELECT 86, ''Angelina''' ), false, 'set_hasnt( prepared, values )', '', @@ -1307,7 +1385,10 @@ SELECT * FROM check_test( -- Handle failure due to column mismatch. SELECT * FROM check_test( - set_hasnt( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + set_hasnt( + 'SELECT 1 AS a, ''foo''::text UNION SELECT 2, ''bar''', + 'SELECT ''foo''::text AS a, 1 UNION SELECT ''bar'', 2' + ), false, 'set_hasnt((int,text), (text,int))', '', @@ -1318,7 +1399,10 @@ SELECT * FROM check_test( -- Handle failure due to column count mismatch. SELECT * FROM check_test( - set_hasnt( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + set_hasnt( + 'SELECT 1 UNION SELECT 2', + 'SELECT ''foo''::text AS a, 1 UNION SELECT ''bar'', 2' + ), false, 'set_hasnt((int), (text,int))', '', @@ -1376,7 +1460,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - bag_hasnt( 'anames', 'VALUES (44,''Anna'')' ), + bag_hasnt( 'anames', 'SELECT 44 AS a, ''Anna''::text' ), false, 'bag_hasnt( prepared, value )', '', @@ -1385,7 +1469,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - bag_hasnt( 'anames', 'VALUES (44, ''Anna''), (86, ''Angelina'')' ), + bag_hasnt( 'anames', 'SELECT 44 AS a, ''Anna''::text UNION SELECT 86, ''Angelina''' ), false, 'bag_hasnt( prepared, values )', '', @@ -1397,7 +1481,10 @@ SELECT * FROM check_test( -- Handle failure due to column mismatch. SELECT * FROM check_test( - bag_hasnt( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + bag_hasnt( + 'SELECT 1 AS a, ''foo''::text UNION SELECT 2, ''bar''', + 'SELECT ''foo''::text AS a, 1 UNION SELECT ''bar'', 2' + ), false, 'bag_hasnt((int,text), (text,int))', '', @@ -1408,7 +1495,7 @@ SELECT * FROM check_test( -- Handle failure due to column count mismatch. SELECT * FROM check_test( - bag_hasnt( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + bag_hasnt( 'SELECT 1 UNION SELECT 2', 'SELECT ''foo''::text AS a, 1 UNION SELECT ''bar'', 2' ), false, 'bag_hasnt((int), (text,int))', '', @@ -1420,8 +1507,8 @@ SELECT * FROM check_test( -- Make sure that dupes are not ignored. SELECT * FROM check_test( bag_hasnt( - 'VALUES (44, ''Anna''), (44, ''Anna'')', - 'VALUES (44, ''Anna''), (44, ''Anna'')' + 'SELECT 44 AS a, ''Anna''::text UNION ALL SELECT 44, ''Anna''', + 'SELECT 44 AS a, ''Anna''::text UNION ALL SELECT 44, ''Anna''' ), false, 'bag_hasnt( dupes, dupes )', @@ -1434,8 +1521,8 @@ SELECT * FROM check_test( -- But a dupe that appears only once should be in the list only once. SELECT * FROM check_test( bag_hasnt( - 'VALUES (44, ''Anna'')', - 'VALUES (44, ''Anna''), (44, ''Anna'')' + 'SELECT 44 AS a, ''Anna''::text', + 'SELECT 44 AS a, ''Anna''::text UNION ALL SELECT 44, ''Anna''' ), false, 'bag_hasnt( value, dupes )', @@ -1493,7 +1580,7 @@ SELECT * FROM check_test( (Anthony)' ); --- Fail with an extra record. +-- Fail with a missing record. SELECT * FROM check_test( set_eq( 'SELECT name FROM names WHERE name like ''An%''', @@ -1584,7 +1671,7 @@ SELECT * FROM check_test( (Anthony)' ); --- Fail with an extra record. +-- Fail with a missing record. SELECT * FROM check_test( bag_eq( 'SELECT name FROM names WHERE name like ''An%''', @@ -1858,9 +1945,17 @@ SELECT * FROM check_test( -- Now test results_eq(). PREPARE nenames_ord AS SELECT id, name FROM names WHERE name like 'An%' ORDER BY id; -PREPARE nexpect_ord AS VALUES (15, 'Anthony'), ( 44, 'Anna'), (11, 'Andrew'), - (63, 'Angel'), (86, 'Angelina'), (130, 'Andrea'), - (183, 'Antonio'); +SET client_min_messages = warning; +CREATE TEMPORARY TABLE nord (pk SERIAL PRIMARY KEY, id int, name text); +RESET client_min_messages; +INSERT INTO nord (id, name ) VALUES(15, 'Anthony'); +INSERT INTO nord (id, name ) VALUES(44, 'Anna'); +INSERT INTO nord (id, name ) VALUES(11, 'Andrew'); +INSERT INTO nord (id, name ) VALUES(63, 'Angel'); +INSERT INTO nord (id, name ) VALUES(86, 'Angelina'); +INSERT INTO nord (id, name ) VALUES(130, 'Andrea'); +INSERT INTO nord (id, name ) VALUES(183, 'Antonio'); +PREPARE nexpect_ord AS SELECT id, name FROM nord ORDER BY pk; SELECT * FROM check_test( results_ne( 'nenames_ord', 'nexpect_ord', 'whatever' ), @@ -1899,11 +1994,21 @@ SELECT * FROM check_test( '' ); +UPDATE dubs SET name = 'Anna' WHERE pk = 1; +UPDATE dubs SET name = 'Angelina', id = 86 WHERE pk = 2; + +SET client_min_messages = warning; +CREATE table buds (pk SERIAL PRIMARY KEY, id int, name text); +RESET client_min_messages; +INSERT INTO buds (id, name) VALUES(2, 'Anna'); +INSERT INTO buds (id, name) VALUES(86, 'Angelina'); +INSERT INTO buds (id, name) VALUES(2, 'Anna'); + -- Compare with dupes. SELECT * FROM check_test( results_ne( - 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', - 'VALUES (2, ''Anna''), (86, ''Angelina''), (2, ''Anna'')' + 'SELECT id, name FROM dubs ORDER BY pk', + 'SELECT id, name FROM buds ORDER BY pk' ), true, 'results_ne(dupe values, dupe values)', @@ -1911,11 +2016,15 @@ SELECT * FROM check_test( '' ); +UPDATE dubs SET id = 4, name = NULL WHERE pk = 1; +UPDATE dubs SET name = NULL WHERE pk = 3; +UPDATE buds SET id = 4, name = NULL WHERE pk = 1; + -- Compare with nulls. SELECT * FROM check_test( results_ne( - 'VALUES (4, NULL), (86, ''Angelina''), (1, NULL)', - 'VALUES (4, NULL), (86, ''Angelina''), (1, ''Anna'')' + 'SELECT id, name FROM dubs ORDER BY pk', + 'SELECT id, name FROM buds ORDER BY pk' ), true, 'results_ne(values with null, values with null)', @@ -1926,8 +2035,8 @@ SELECT * FROM check_test( -- Compare only NULLs SELECT * FROM check_test( results_ne( - 'VALUES (NULL, NULL), (NULL, NULL), (NULL, NULL)', - 'VALUES (NULL, NULL), (NULL, NULL)' + 'SELECT id, name FROM dupes LIMIT 3', + 'SELECT id, name FROM dupes LIMIT 2' ), true, 'results_ne(nulls, nulls)', @@ -1947,7 +2056,7 @@ SELECT * FROM check_test( '' ); --- -- Now when the last row is missing. +-- Now when the last row is missing. SELECT * FROM check_test( results_ne( 'SELECT id, name FROM annames WHERE name <> ''Antonio''', @@ -1971,11 +2080,13 @@ SELECT * FROM check_test( '' ); +UPDATE dubs SET id = 1, name = 'Anna' WHERE pk IN (1, 3); + -- Compare with missing dupe. SELECT * FROM check_test( results_ne( - 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', - 'VALUES (1, ''Anna''), (86, ''Angelina'')' + 'SELECT id, name FROM dubs ORDER BY pk', + 'SELECT id, name FROM dubs ORDER BY pk LIMIT 2' ), true, 'results_ne(values dupe, values)', @@ -1983,11 +2094,12 @@ SELECT * FROM check_test( '' ); +UPDATE dubs SET name = NULL where PK = 2; -- Handle pass with null. SELECT * FROM check_test( results_ne( - 'VALUES (1, NULL), (86, ''Angelina'')', - 'VALUES (1, ''Anna''), (86, ''Angelina'')' + 'SELECT id, name FROM dubs ORDER BY pk LIMIT 2', + 'SELECT id, name FROM buds ORDER BY pk LIMIT 2' ), true, 'results_ne(values null, values)', diff --git a/sql/valueset.sql b/sql/valueset.sql new file mode 100644 index 000000000000..30dd9233587a --- /dev/null +++ b/sql/valueset.sql @@ -0,0 +1,1626 @@ +\unset ECHO +\i test_setup.sql + +SELECT plan(349); +--SELECT * FROM no_plan(); + +-- This will be rolled back. :-) +SET client_min_messages = warning; + +CREATE TABLE names ( + id SERIAL NOT NULL PRIMARY KEY, + name TEXT DEFAULT '' +); + +RESET client_min_messages; + +-- Top 100 boy an 100 girl names in 2005. http://www.ssa.gov/OACT/babynames/ +INSERT INTO names (name) VALUES ('Jacob'); +INSERT INTO names (name) VALUES ('Emily'); +INSERT INTO names (name) VALUES ('Michael'); +INSERT INTO names (name) VALUES ('Emma'); +INSERT INTO names (name) VALUES ('Joshua'); +INSERT INTO names (name) VALUES ('Madison'); +INSERT INTO names (name) VALUES ('Matthew'); +INSERT INTO names (name) VALUES ('Abigail'); +INSERT INTO names (name) VALUES ('Ethan'); +INSERT INTO names (name) VALUES ('Olivia'); +INSERT INTO names (name) VALUES ('Andrew'); +INSERT INTO names (name) VALUES ('Isabella'); +INSERT INTO names (name) VALUES ('Daniel'); +INSERT INTO names (name) VALUES ('Hannah'); +INSERT INTO names (name) VALUES ('Anthony'); +INSERT INTO names (name) VALUES ('Samantha'); +INSERT INTO names (name) VALUES ('Christopher'); +INSERT INTO names (name) VALUES ('Ava'); +INSERT INTO names (name) VALUES ('Joseph'); +INSERT INTO names (name) VALUES ('Ashley'); +INSERT INTO names (name) VALUES ('William'); +INSERT INTO names (name) VALUES ('Elizabeth'); +INSERT INTO names (name) VALUES ('Alexander'); +INSERT INTO names (name) VALUES ('Sophia'); +INSERT INTO names (name) VALUES ('David'); +INSERT INTO names (name) VALUES ('Alexis'); +INSERT INTO names (name) VALUES ('Ryan'); +INSERT INTO names (name) VALUES ('Grace'); +INSERT INTO names (name) VALUES ('Nicholas'); +INSERT INTO names (name) VALUES ('Sarah'); +INSERT INTO names (name) VALUES ('Tyler'); +INSERT INTO names (name) VALUES ('Alyssa'); +INSERT INTO names (name) VALUES ('James'); +INSERT INTO names (name) VALUES ('Mia'); +INSERT INTO names (name) VALUES ('John'); +INSERT INTO names (name) VALUES ('Natalie'); +INSERT INTO names (name) VALUES ('Jonathan'); +INSERT INTO names (name) VALUES ('Chloe'); +INSERT INTO names (name) VALUES ('Nathan'); +INSERT INTO names (name) VALUES ('Brianna'); +INSERT INTO names (name) VALUES ('Samuel'); +INSERT INTO names (name) VALUES ('Lauren'); +INSERT INTO names (name) VALUES ('Christian'); +INSERT INTO names (name) VALUES ('Anna'); +INSERT INTO names (name) VALUES ('Noah'); +INSERT INTO names (name) VALUES ('Ella'); +INSERT INTO names (name) VALUES ('Dylan'); +INSERT INTO names (name) VALUES ('Taylor'); +INSERT INTO names (name) VALUES ('Benjamin'); +INSERT INTO names (name) VALUES ('Kayla'); +INSERT INTO names (name) VALUES ('Logan'); +INSERT INTO names (name) VALUES ('Hailey'); +INSERT INTO names (name) VALUES ('Brandon'); +INSERT INTO names (name) VALUES ('Jessica'); +INSERT INTO names (name) VALUES ('Gabriel'); +INSERT INTO names (name) VALUES ('Victoria'); +INSERT INTO names (name) VALUES ('Zachary'); +INSERT INTO names (name) VALUES ('Jasmine'); +INSERT INTO names (name) VALUES ('Jose'); +INSERT INTO names (name) VALUES ('Sydney'); +INSERT INTO names (name) VALUES ('Elijah'); +INSERT INTO names (name) VALUES ('Julia'); +INSERT INTO names (name) VALUES ('Angel'); +INSERT INTO names (name) VALUES ('Destiny'); +INSERT INTO names (name) VALUES ('Kevin'); +INSERT INTO names (name) VALUES ('Morgan'); +INSERT INTO names (name) VALUES ('Jack'); +INSERT INTO names (name) VALUES ('Kaitlyn'); +INSERT INTO names (name) VALUES ('Caleb'); +INSERT INTO names (name) VALUES ('Savannah'); +INSERT INTO names (name) VALUES ('Justin'); +INSERT INTO names (name) VALUES ('Katherine'); +INSERT INTO names (name) VALUES ('Robert'); +INSERT INTO names (name) VALUES ('Alexandra'); +INSERT INTO names (name) VALUES ('Austin'); +INSERT INTO names (name) VALUES ('Rachel'); +INSERT INTO names (name) VALUES ('Evan'); +INSERT INTO names (name) VALUES ('Lily'); +INSERT INTO names (name) VALUES ('Thomas'); +INSERT INTO names (name) VALUES ('Kaylee'); +INSERT INTO names (name) VALUES ('Luke'); +INSERT INTO names (name) VALUES ('Megan'); +INSERT INTO names (name) VALUES ('Mason'); +INSERT INTO names (name) VALUES ('Jennifer'); +INSERT INTO names (name) VALUES ('Aidan'); +INSERT INTO names (name) VALUES ('Angelina'); +INSERT INTO names (name) VALUES ('Jackson'); +INSERT INTO names (name) VALUES ('Makayla'); +INSERT INTO names (name) VALUES ('Isaiah'); +INSERT INTO names (name) VALUES ('Allison'); +INSERT INTO names (name) VALUES ('Jordan'); +INSERT INTO names (name) VALUES ('Maria'); +INSERT INTO names (name) VALUES ('Gavin'); +INSERT INTO names (name) VALUES ('Brooke'); +INSERT INTO names (name) VALUES ('Connor'); +INSERT INTO names (name) VALUES ('Trinity'); +INSERT INTO names (name) VALUES ('Isaac'); +INSERT INTO names (name) VALUES ('Faith'); +INSERT INTO names (name) VALUES ('Aiden'); +INSERT INTO names (name) VALUES ('Lillian'); +INSERT INTO names (name) VALUES ('Jason'); +INSERT INTO names (name) VALUES ('Mackenzie'); +INSERT INTO names (name) VALUES ('Cameron'); +INSERT INTO names (name) VALUES ('Sofia'); +INSERT INTO names (name) VALUES ('Hunter'); +INSERT INTO names (name) VALUES ('Riley'); +INSERT INTO names (name) VALUES ('Jayden'); +INSERT INTO names (name) VALUES ('Haley'); +INSERT INTO names (name) VALUES ('Juan'); +INSERT INTO names (name) VALUES ('Gabrielle'); +INSERT INTO names (name) VALUES ('Charles'); +INSERT INTO names (name) VALUES ('Nicole'); +INSERT INTO names (name) VALUES ('Aaron'); +INSERT INTO names (name) VALUES ('Kylie'); +INSERT INTO names (name) VALUES ('Lucas'); +INSERT INTO names (name) VALUES ('Zoe'); +INSERT INTO names (name) VALUES ('Luis'); +INSERT INTO names (name) VALUES ('Katelyn'); +INSERT INTO names (name) VALUES ('Owen'); +INSERT INTO names (name) VALUES ('Paige'); +INSERT INTO names (name) VALUES ('Landon'); +INSERT INTO names (name) VALUES ('Gabriella'); +INSERT INTO names (name) VALUES ('Diego'); +INSERT INTO names (name) VALUES ('Jenna'); +INSERT INTO names (name) VALUES ('Brian'); +INSERT INTO names (name) VALUES ('Kimberly'); +INSERT INTO names (name) VALUES ('Adam'); +INSERT INTO names (name) VALUES ('Stephanie'); +INSERT INTO names (name) VALUES ('Adrian'); +INSERT INTO names (name) VALUES ('Andrea'); +INSERT INTO names (name) VALUES ('Eric'); +INSERT INTO names (name) VALUES ('Alexa'); +INSERT INTO names (name) VALUES ('Kyle'); +INSERT INTO names (name) VALUES ('Avery'); +INSERT INTO names (name) VALUES ('Ian'); +INSERT INTO names (name) VALUES ('Leah'); +INSERT INTO names (name) VALUES ('Nathaniel'); +INSERT INTO names (name) VALUES ('Nevaeh'); +INSERT INTO names (name) VALUES ('Carlos'); +INSERT INTO names (name) VALUES ('Madeline'); +INSERT INTO names (name) VALUES ('Alex'); +INSERT INTO names (name) VALUES ('Evelyn'); +INSERT INTO names (name) VALUES ('Bryan'); +INSERT INTO names (name) VALUES ('Mary'); +INSERT INTO names (name) VALUES ('Jesus'); +INSERT INTO names (name) VALUES ('Maya'); +INSERT INTO names (name) VALUES ('Julian'); +INSERT INTO names (name) VALUES ('Michelle'); +INSERT INTO names (name) VALUES ('Sean'); +INSERT INTO names (name) VALUES ('Sara'); +INSERT INTO names (name) VALUES ('Hayden'); +INSERT INTO names (name) VALUES ('Jada'); +INSERT INTO names (name) VALUES ('Carter'); +INSERT INTO names (name) VALUES ('Audrey'); +INSERT INTO names (name) VALUES ('Jeremiah'); +INSERT INTO names (name) VALUES ('Brooklyn'); +INSERT INTO names (name) VALUES ('Cole'); +INSERT INTO names (name) VALUES ('Vanessa'); +INSERT INTO names (name) VALUES ('Brayden'); +INSERT INTO names (name) VALUES ('Amanda'); +INSERT INTO names (name) VALUES ('Wyatt'); +INSERT INTO names (name) VALUES ('Rebecca'); +INSERT INTO names (name) VALUES ('Chase'); +INSERT INTO names (name) VALUES ('Caroline'); +INSERT INTO names (name) VALUES ('Steven'); +INSERT INTO names (name) VALUES ('Ariana'); +INSERT INTO names (name) VALUES ('Timothy'); +INSERT INTO names (name) VALUES ('Amelia'); +INSERT INTO names (name) VALUES ('Dominic'); +INSERT INTO names (name) VALUES ('Mariah'); +INSERT INTO names (name) VALUES ('Sebastian'); +INSERT INTO names (name) VALUES ('Jordan'); +INSERT INTO names (name) VALUES ('Xavier'); +INSERT INTO names (name) VALUES ('Jocelyn'); +INSERT INTO names (name) VALUES ('Jaden'); +INSERT INTO names (name) VALUES ('Arianna'); +INSERT INTO names (name) VALUES ('Jesse'); +INSERT INTO names (name) VALUES ('Isabel'); +INSERT INTO names (name) VALUES ('Seth'); +INSERT INTO names (name) VALUES ('Marissa'); +INSERT INTO names (name) VALUES ('Devin'); +INSERT INTO names (name) VALUES ('Autumn'); +INSERT INTO names (name) VALUES ('Antonio'); +INSERT INTO names (name) VALUES ('Melanie'); +INSERT INTO names (name) VALUES ('Miguel'); +INSERT INTO names (name) VALUES ('Aaliyah'); +INSERT INTO names (name) VALUES ('Richard'); +INSERT INTO names (name) VALUES ('Gracie'); +INSERT INTO names (name) VALUES ('Colin'); +INSERT INTO names (name) VALUES ('Claire'); +INSERT INTO names (name) VALUES ('Cody'); +INSERT INTO names (name) VALUES ('Isabelle'); +INSERT INTO names (name) VALUES ('Alejandro'); +INSERT INTO names (name) VALUES ('Molly'); +INSERT INTO names (name) VALUES ('Caden'); +INSERT INTO names (name) VALUES ('Mya'); +INSERT INTO names (name) VALUES ('Blake'); +INSERT INTO names (name) VALUES ('Diana'); +INSERT INTO names (name) VALUES ('Kaden'); +INSERT INTO names (name) VALUES ('Katie'); + +CREATE TABLE annames AS +SELECT id, name FROM names WHERE name like 'An%'; + +-- We'll use these prepared statements. +PREPARE anames AS SELECT id, name FROM names WHERE name like 'An%'; +PREPARE expect AS VALUES (11, 'Andrew'), ( 44, 'Anna'), (15, 'Anthony'), + (183, 'Antonio'), (86, 'Angelina'), (130, 'Andrea'), + (63, 'Angel'); + +/****************************************************************************/ +-- First, test _temptable. + +PREPARE "something cool" AS VALUES (1, 2), (3, 4); +SELECT is( + _temptable( '"something cool"', '__spacenames__' ), + '__spacenames__', + 'Should create a temp table for a prepared statement with space and values' +); +SELECT has_table('__spacenames__' ); + +SELECT is( + _temptable('VALUES (1, 2), (3, 5)', '__somevals__'), + '__somevals__', + 'Should create a temp table for a values statement' +); +SELECT has_table('__somevals__'); + +/****************************************************************************/ +-- Now test set_eq(). + +SELECT * FROM check_test( + set_eq( 'anames', 'expect', 'whatever' ), + true, + 'set_eq(prepared, prepared, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + set_eq( 'anames', 'expect' ), + true, + 'set_eq(prepared, prepared)', + '', + '' +); + +-- Pass a full SQL statement for the prepared statements. +SELECT * FROM check_test( + set_eq( 'EXECUTE anames', 'EXECUTE expect' ), + true, + 'set_eq(execute, execute, desc)', + '', + '' +); + +-- Make sure that dupes are disregarded. +SELECT * FROM check_test( + set_eq( + 'VALUES (1, ''Anna'')', + 'VALUES (1, ''Anna''), (1, ''Anna'')' + ), + true, + 'set_eq(values, dupe values)', + '', + '' +); + +-- Try some failures. +SELECT * FROM check_test( + set_eq( + 'SELECT id, name FROM annames WHERE name <> ''Anna''', + 'expect' + ), + false, + 'set_eq(select, prepared) fail missing', + '', + ' Missing records: + (44,Anna)' +); + +SELECT * FROM check_test( + set_eq( + 'SELECT id, name FROM annames WHERE name NOT IN (''Anna'', ''Angelina'')', + 'expect' + ), + false, + 'set_eq(select, prepared) fail missings', + '', + E' Missing records: + \\((44,Anna|86,Angelina)\\) + \\((44,Anna|86,Angelina)\\)', + true +); + +-- Handle failure due to column mismatch. +SELECT * FROM check_test( + set_eq( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'set_eq(values, values) fail mismatch', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' +); + +-- Handle failure due to column count mismatch. +SELECT * FROM check_test( + set_eq( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'set_eq(values, values) fail column count', + '', + ' Columns differ between queries: + have: (integer) + want: (text,integer)' +); + +/****************************************************************************/ +-- Now test bag_eq(). + +SELECT * FROM check_test( + bag_eq( 'anames', 'expect', 'whatever' ), + true, + 'bag_eq(prepared, prepared, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + bag_eq( 'anames', 'expect' ), + true, + 'bag_eq(prepared, prepared)', + '', + '' +); + +-- Pass a full SQL statement for the prepared statements. +SELECT * FROM check_test( + bag_eq( 'EXECUTE anames', 'EXECUTE expect' ), + true, + 'bag_eq(execute, execute)', + '', + '' +); + +-- Compare with dupes. +SELECT * FROM check_test( + bag_eq( + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (1, ''Anna''), (1, ''Anna''), (86, ''Angelina'')' + ), + true, + 'bag_eq(dupe values, dupe values)', + '', + '' +); + +-- And now some failures. +SELECT * FROM check_test( + bag_eq( + 'SELECT id, name FROM annames WHERE name <> ''Anna''', + 'expect' + ), + false, + 'bag_eq(select, prepared) fail missing', + '', + ' Missing records: + (44,Anna)' +); + +SELECT * FROM check_test( + bag_eq( + 'SELECT id, name FROM annames WHERE name NOT IN (''Anna'', ''Angelina'')', + 'expect' + ), + false, + 'bag_eq(select, prepared) fail missings', + '', + E' Missing records: + \\((44,Anna|86,Angelina)\\) + \\((44,Anna|86,Angelina)\\)', + true +); + +-- Handle failure due to column mismatch. +SELECT * FROM check_test( + bag_eq( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'bag_eq(values, values) fail mismatch', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' +); + +-- Handle failure due to column count mismatch. +SELECT * FROM check_test( + bag_eq( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'bag_eq(values, values) fail column count', + '', + ' Columns differ between queries: + have: (integer) + want: (text,integer)' +); + +-- Handle failure due to missing dupe. +SELECT * FROM check_test( + bag_eq( + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (1, ''Anna''), (86, ''Angelina'')' + ), + false, + 'bag_eq(values, values) fail missing dupe', + '', + ' Extra records: + (1,Anna)' +); + +/****************************************************************************/ +-- Now test set_eq(). + +SELECT * FROM check_test( + set_ne( 'anames', 'expect' ), + false, + 'set_ne(prepared, prepared) fail', + '', + '' +); + +-- Handle fail with column mismatch. +SELECT * FROM check_test( + set_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'set_ne fail with column mismatch', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' +); + +-- Handle failure due to column count mismatch. +SELECT * FROM check_test( + set_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'set_ne fail with different col counts', + '', + ' Columns differ between queries: + have: (integer) + want: (text,integer)' +); + +-- Handle fail with a dupe. +SELECT * FROM check_test( + set_ne( + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (1, ''Anna''), (86, ''Angelina'')' + ), + false, + 'set_ne fail with dupe', + '', + '' +); + +/****************************************************************************/ +-- Now test bag_ne(). + +SELECT * FROM check_test( + bag_ne( 'anames', 'expect' ), + false, + 'bag_ne(prepared, prepared) fail', + '', + '' +); + +SELECT * FROM check_test( + bag_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'bag_ne fail with column mismatch', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' +); + +-- Handle pass with a dupe. +SELECT * FROM check_test( + bag_ne( + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (1, ''Anna''), (86, ''Angelina'')' + ), + true, + 'set_ne pass with dupe', + '', + '' +); + +-- Handle fail with column mismatch. +SELECT * FROM check_test( + bag_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'bag_ne fail with column mismatch', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' +); + +-- Handle failure due to column count mismatch. +SELECT * FROM check_test( + bag_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'bag_ne fail with different col counts', + '', + ' Columns differ between queries: + have: (integer) + want: (text,integer)' +); + +/****************************************************************************/ +-- Now test results_eq(). + +PREPARE anames_ord AS SELECT id, name FROM names WHERE name like 'An%' ORDER BY id; +PREPARE expect_ord AS VALUES (11, 'Andrew'), (15, 'Anthony'), ( 44, 'Anna'), + (63, 'Angel'), (86, 'Angelina'), (130, 'Andrea'), + (183, 'Antonio'); + +SELECT * FROM check_test( + results_eq( 'anames_ord', 'expect_ord', 'whatever' ), + true, + 'results_eq(prepared, prepared, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + results_eq( 'anames_ord', 'expect_ord' ), + true, + 'results_eq(prepared, prepared)', + '', + '' +); + +-- Pass a full SQL statement for the prepared statements. +SELECT * FROM check_test( + results_eq( 'EXECUTE anames_ord', 'EXECUTE expect_ord' ), + true, + 'results_eq(execute, execute)', + '', + '' +); + +-- Compare with dupes. +SELECT * FROM check_test( + results_eq( + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')' + ), + true, + 'results_eq(dupe values, dupe values)', + '', + '' +); + +-- Compare with nulls. +SELECT * FROM check_test( + results_eq( + 'VALUES (4, NULL), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (4, NULL), (86, ''Angelina''), (1, ''Anna'')' + ), + true, + 'results_eq(values with null, values with null)', + '', + '' +); + +-- Compare only NULLs +SELECT * FROM check_test( + results_eq( + 'VALUES (NULL, NULL), (NULL, NULL)', + 'VALUES (NULL, NULL), (NULL, NULL)' + ), + true, + 'results_eq(nulls, nulls)', + '', + '' +); + +-- Compare only NULLs +SELECT * FROM check_test( + results_eq( + 'VALUES (NULL, NULL), (NULL, NULL)', + 'VALUES (NULL, NULL)' + ), + false, + 'results_eq(nulls, nulls) fail', + '', + ' Results differ beginning at row 2: + have: (,) + want: NULL' +); + +-- Now when the last row is missing. +SELECT * FROM check_test( + results_eq( + 'SELECT id, name FROM annames WHERE name <> ''Antonio''', + 'anames_ord' + ), + false, + 'results_eq(select, prepared) fail missing last row', + '', + ' Results differ beginning at row 7: + have: NULL + want: (183,Antonio)' +); + +-- Invert that. +SELECT * FROM check_test( + results_eq( + 'anames_ord', + 'SELECT id, name FROM annames WHERE name <> ''Antonio''' + ), + false, + 'results_eq(prepared, select) fail missing first row', + '', + ' Results differ beginning at row 7: + have: (183,Antonio) + want: NULL' +); + +-- Compare with missing dupe. +SELECT * FROM check_test( + results_eq( + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (1, ''Anna''), (86, ''Angelina'')' + ), + false, + 'results_eq(values dupe, values)', + '', + ' Results differ beginning at row 3: + have: (1,Anna) + want: NULL' +); + +-- Handle failure with null. +SELECT * FROM check_test( + results_eq( + 'VALUES (1, NULL), (86, ''Angelina'')', + 'VALUES (1, ''Anna''), (86, ''Angelina'')' + ), + false, + 'results_eq(values null, values)', + '', + ' Results differ beginning at row 1: + have: (1,) + want: (1,Anna)' +); + +-- Handle failure due to column mismatch. +SELECT * FROM check_test( + results_eq( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'results_eq(values, values) mismatch', + '', + CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Columns differ between queries:' END || ' + have: (1,foo) + want: (foo,1)' +); + +-- Handle failure due to more subtle column mismatch, valid only on 8.4. +CREATE OR REPLACE FUNCTION subtlefail() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() < 80400 THEN + -- 8.3 and earlier cast records to text, so subtlety is out. + RETURN NEXT pass('results_eq(values, values) subtle mismatch should fail'); + RETURN NEXT pass('results_eq(values, values) subtle mismatch should have the proper description'); + RETURN NEXT pass('results_eq(values, values) subtle mismatch should have the proper diagnostics'); + ELSE + -- 8.4 does true record comparisions, yay! + FOR tap IN SELECT * FROM check_test( + results_eq( + 'VALUES (1, ''foo''::varchar), (2, ''bar''::varchar)', + 'VALUES (1, ''foo''), (2, ''bar'')' + ), + false, + 'results_eq(values, values) subtle mismatch', + '', + ' Columns differ between queries: + have: (1,foo) + want: (1,foo)' ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + END IF; + RETURN; +END; +$$ LANGUAGE plpgsql; +SELECT * FROM subtlefail(); + +-- Handle failure due to column count mismatch. +SELECT * FROM check_test( + results_eq( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'results_eq(values, values) fail column count', + '', + CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Columns differ between queries:' END || ' + have: (1) + want: (foo,1)' +); + +-- Compare with cursors. +DECLARE cwant CURSOR FOR SELECT id, name FROM names WHERE name like 'An%' ORDER BY id; +DECLARE chave CURSOR FOR SELECT id, name from annames ORDER BY id; + +-- Mix cursors and prepared statements +PREPARE annames_ord AS VALUES (11, 'Andrew'), (15, 'Anthony'), ( 44, 'Anna'), + (63, 'Angel'), (86, 'Angelina'), (130, 'Andrea'), + (183, 'Antonio'); +MOVE BACKWARD ALL IN cwant; + +SELECT * FROM check_test( + results_eq( 'cwant'::refcursor, 'annames_ord' ), + true, + 'results_eq(cursor, prepared)', + '', + '' +); + +SELECT * FROM check_test( + results_eq( 'annames_ord', 'chave'::refcursor ), + true, + 'results_eq(prepared, cursor)', + '', + '' +); + +/****************************************************************************/ +-- Now test set_has(). +SELECT * FROM check_test( + set_has( 'anames', 'expect', 'whatever' ), + true, + 'set_has( prepared, prepared, description )', + 'whatever', + '' +); + +PREPARE subset AS VALUES (11, 'Andrew'), ( 44, 'Anna'), (63, 'Angel'); + +SELECT * FROM check_test( + set_has( 'anames', 'subset' ), + true, + 'set_has( prepared, subprepared )', + '', + '' +); + +SELECT * FROM check_test( + set_has( 'EXECUTE anames', 'EXECUTE subset' ), + true, + 'set_has( execute, execute )', + '', + '' +); + +-- Make sure that dupes are ignored. +SELECT * FROM check_test( + set_has( 'anames', 'VALUES (44, ''Anna''), (44, ''Anna'')' ), + true, + 'set_has( prepared, dupes )', + '', + '' +); + +SELECT * FROM check_test( + set_has( 'VALUES (44, ''Anna''), (44, ''Anna'')', 'VALUES(44, ''Anna'')' ), + true, + 'set_has( dupes, values )', + '', + '' +); + +-- Check failures. +SELECT * FROM check_test( + set_has( + 'SELECT id, name FROM annames WHERE name <> ''Anna''', + 'expect' + ), + false, + 'set_has( missing1, expect )', + '', + ' Missing records: + (44,Anna)' +); + +SELECT * FROM check_test( + set_has( + 'SELECT id, name FROM annames WHERE name NOT IN (''Anna'', ''Angelina'')', + 'expect' + ), + false, + 'set_has(missing2, expect )', + '', + E' Missing records: + \\((44,Anna|86,Angelina)\\) + \\((44,Anna|86,Angelina)\\)', + true +); + +-- Handle failure due to column mismatch. +SELECT * FROM check_test( + set_has( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'set_has((int,text), (text,int))', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' +); + +-- Handle failure due to column count mismatch. +SELECT * FROM check_test( + set_has( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'set_has((int), (text,int))', + '', + ' Columns differ between queries: + have: (integer) + want: (text,integer)' +); + +/****************************************************************************/ +-- Now test bag_has(). +SELECT * FROM check_test( + bag_has( 'anames', 'expect', 'whatever' ), + true, + 'bag_has( prepared, prepared, description )', + 'whatever', + '' +); + +SELECT * FROM check_test( + bag_has( 'anames', 'subset' ), + true, + 'bag_has( prepared, subprepared )', + '', + '' +); + +SELECT * FROM check_test( + bag_has( 'EXECUTE anames', 'EXECUTE subset' ), + true, + 'bag_has( execute, execute )', + '', + '' +); + +-- Make sure that dupes are not ignored. +SELECT * FROM check_test( + bag_has( 'anames', 'VALUES (44, ''Anna''), (44, ''Anna'')' ), + false, + 'bag_has( prepared, dupes )', + '', + ' Missing records: + (44,Anna)' +); + +SELECT * FROM check_test( + bag_has( 'VALUES (44, ''Anna''), (44, ''Anna'')', 'VALUES(44, ''Anna'')' ), + true, + 'bag_has( dupes, values )', + '', + '' +); + +SELECT * FROM check_test( + bag_has( + 'SELECT id, name FROM annames WHERE name <> ''Anna''', + 'expect' + ), + false, + 'bag_has( missing1, expect )', + '', + ' Missing records: + (44,Anna)' +); + +SELECT * FROM check_test( + bag_has( + 'SELECT id, name FROM annames WHERE name NOT IN (''Anna'', ''Angelina'')', + 'expect' + ), + false, + 'bag_has(missing2, expect )', + '', + E' Missing records: + \\((44,Anna|86,Angelina)\\) + \\((44,Anna|86,Angelina)\\)', + true +); + +-- Handle failure due to column mismatch. +SELECT * FROM check_test( + bag_has( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'bag_has((int,text), (text,int))', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' +); + +-- Handle failure due to column count mismatch. +SELECT * FROM check_test( + bag_has( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'bag_has((int), (text,int))', + '', + ' Columns differ between queries: + have: (integer) + want: (text,integer)' +); + +/****************************************************************************/ +-- Now test set_hasnt(). + +PREPARE others AS VALUES ( 44, 'Larry' ), (52, 'Tom'), (23, 'Damian' ); + +SELECT * FROM check_test( + set_hasnt( 'anames', 'others', 'whatever' ), + true, + 'set_hasnt( prepared, prepared, description )', + 'whatever', + '' +); + +SELECT * FROM check_test( + set_hasnt( 'anames', 'others' ), + true, + 'set_hasnt( prepared, prepared, description )', + '', + '' +); + +SELECT * FROM check_test( + set_hasnt( 'EXECUTE anames', 'EXECUTE others' ), + true, + 'set_hasnt( execute, execute )', + '', + '' +); + +-- Make sure that dupes are ignored. +SELECT * FROM check_test( + set_hasnt( 'anames', 'VALUES (44, ''Bob''), (44, ''Bob'')' ), + true, + 'set_hasnt( prepared, dupes )', + '', + '' +); + +SELECT * FROM check_test( + set_hasnt( 'anames', 'VALUES (44,''Anna'')' ), + false, + 'set_hasnt( prepared, value )', + '', + ' Extra records: + (44,Anna)' +); + +SELECT * FROM check_test( + set_hasnt( 'anames', 'VALUES (44, ''Anna''), (86, ''Angelina'')' ), + false, + 'set_hasnt( prepared, values )', + '', + E' Extra records: + \\((44,Anna|86,Angelina)\\) + \\((44,Anna|86,Angelina)\\)', + true +); + +-- Handle failure due to column mismatch. +SELECT * FROM check_test( + set_hasnt( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'set_hasnt((int,text), (text,int))', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' +); + +-- Handle failure due to column count mismatch. +SELECT * FROM check_test( + set_hasnt( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'set_hasnt((int), (text,int))', + '', + ' Columns differ between queries: + have: (integer) + want: (text,integer)' +); + +/****************************************************************************/ +-- Now test bag_hasnt(). + +SELECT * FROM check_test( + bag_hasnt( 'anames', 'others', 'whatever' ), + true, + 'bag_hasnt( prepared, prepared, description )', + 'whatever', + '' +); + +SELECT * FROM check_test( + bag_hasnt( 'anames', 'others' ), + true, + 'bag_hasnt( prepared, prepared, description )', + '', + '' +); + +SELECT * FROM check_test( + bag_hasnt( 'EXECUTE anames', 'EXECUTE others' ), + true, + 'bag_hasnt( execute, execute )', + '', + '' +); + +SELECT * FROM check_test( + bag_hasnt( 'anames', 'VALUES (44,''Anna'')' ), + false, + 'bag_hasnt( prepared, value )', + '', + ' Extra records: + (44,Anna)' +); + +SELECT * FROM check_test( + bag_hasnt( 'anames', 'VALUES (44, ''Anna''), (86, ''Angelina'')' ), + false, + 'bag_hasnt( prepared, values )', + '', + E' Extra records: + \\((44,Anna|86,Angelina)\\) + \\((44,Anna|86,Angelina)\\)', + true +); + +-- Handle failure due to column mismatch. +SELECT * FROM check_test( + bag_hasnt( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'bag_hasnt((int,text), (text,int))', + '', + ' Columns differ between queries: + have: (integer,text) + want: (text,integer)' +); + +-- Handle failure due to column count mismatch. +SELECT * FROM check_test( + bag_hasnt( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'bag_hasnt((int), (text,int))', + '', + ' Columns differ between queries: + have: (integer) + want: (text,integer)' +); + +-- Make sure that dupes are not ignored. +SELECT * FROM check_test( + bag_hasnt( + 'VALUES (44, ''Anna''), (44, ''Anna'')', + 'VALUES (44, ''Anna''), (44, ''Anna'')' + ), + false, + 'bag_hasnt( dupes, dupes )', + '', + ' Extra records: + (44,Anna) + (44,Anna)' +); + +-- But a dupe that appears only once should be in the list only once. +SELECT * FROM check_test( + bag_hasnt( + 'VALUES (44, ''Anna'')', + 'VALUES (44, ''Anna''), (44, ''Anna'')' + ), + false, + 'bag_hasnt( value, dupes )', + '', + ' Extra records: + (44,Anna)' +); + +/****************************************************************************/ +-- Test set_eq() with an array argument. +PREPARE justnames AS + VALUES ('Andrew'), ('Antonio'), ('Angelina'), ('Anna'), ('Anthony'), ('Andrea'), ('Angel'); +SELECT * FROM check_test( + set_eq( + 'justnames', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel' ], + 'whatever' + ), + true, + 'set_eq(prepared, array, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + set_eq( + 'justnames', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + true, + 'set_eq(prepared, array)', + '', + '' +); + +SELECT * FROM check_test( + set_eq( + 'justnames', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel', 'Andrew', 'Anna' ] + ), + true, + 'set_eq(prepared, dupe array)', + '', + '' +); + +-- Fail with an extra record. +SELECT * FROM check_test( + set_eq( + 'justnames', + ARRAY['Andrew', 'Anna', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + false, + 'set_eq(prepared, array) extra record', + '', + ' Extra records: + (Anthony)' +); + +-- Fail with a missing record. +SELECT * FROM check_test( + set_eq( + 'justnames', + ARRAY['Andrew', 'Anna', 'Anthony', 'Alan', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + false, + 'set_eq(prepared, array) missing record', + '', + ' Missing records: + (Alan)' +); + +-- Fail with incompatible columns. +SELECT * FROM check_test( + set_eq( + 'justnames', + ARRAY[1, 2, 3] + ), + false, + 'set_eq(sql, array) incompatible types', + '', + ' Columns differ between queries: + have: (text) + want: (integer)' +); + +/****************************************************************************/ +-- Test bag_eq() with an array argument. +SELECT * FROM check_test( + bag_eq( + 'justnames', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel' ], + 'whatever' + ), + true, + 'bag_eq(prepared, array, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + bag_eq( + 'justnames', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + true, + 'bag_eq(prepared, array)', + '', + '' +); + +SELECT * FROM check_test( + bag_eq( + 'justnames', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel', 'Anna' ] + ), + false, + 'bag_eq(prepared, dupe array) fail', + '', + ' Missing records: + (Anna)' +); + +-- Fail with an extra record. +SELECT * FROM check_test( + bag_eq( + 'justnames', + ARRAY['Andrew', 'Anna', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + false, + 'bag_eq(prepared, array) extra record', + '', + ' Extra records: + (Anthony)' +); + +-- Fail with a missing record. +SELECT * FROM check_test( + bag_eq( + 'justnames', + ARRAY['Andrew', 'Anna', 'Anthony', 'Alan', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + false, + 'bag_eq(prepared, array) missing record', + '', + ' Missing records: + (Alan)' +); + +-- Fail with incompatible columns. +SELECT * FROM check_test( + bag_eq( + 'justnames', + ARRAY[1, 2, 3] + ), + false, + 'bag_eq(prepared, array) incompatible types', + '', + ' Columns differ between queries: + have: (text) + want: (integer)' +); + +/****************************************************************************/ +-- Test set_ne() with an array argument. +SELECT * FROM check_test( + set_ne( + 'justnames', + ARRAY['Andrew', 'Anna' ], + 'whatever' + ), + true, + 'set_ne(prepared, array, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + set_ne( + 'justnames', + ARRAY['Andrew', 'Anna' ] + ), + true, + 'set_ne(prepared, array)', + '', + '' +); + +SELECT * FROM check_test( + set_ne( + 'justnames', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + false, + 'set_ne(prepared, array) fail', + '', + '' +); + +-- Fail with dupes. +SELECT * FROM check_test( + set_ne( + 'justnames', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel', 'Anna' ] + ), + false, + 'set_ne(prepared, dupes array) fail', + '', + '' +); + +-- Fail with incompatible columns. +SELECT * FROM check_test( + set_ne( + 'justnames', + ARRAY[1, 2, 3] + ), + false, + 'set_ne(sql, array) incompatible types', + '', + ' Columns differ between queries: + have: (text) + want: (integer)' +); + +/****************************************************************************/ +-- Test bag_ne() with an array argument. +SELECT * FROM check_test( + bag_ne( + 'justnames', + ARRAY['Andrew', 'Anna' ], + 'whatever' + ), + true, + 'bag_ne(prepared, array, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + bag_ne( + 'justnames', + ARRAY['Andrew', 'Anna' ] + ), + true, + 'bag_ne(prepared, array)', + '', + '' +); + +SELECT * FROM check_test( + bag_ne( + 'justnames', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel' ] + ), + false, + 'bag_ne(prepared, array) fail', + '', + '' +); + +-- Pass with dupes. +SELECT * FROM check_test( + bag_ne( + 'justnames', + ARRAY['Andrew', 'Anna', 'Anthony', 'Antonio', 'Angelina', 'Andrea', 'Angel', 'Anna' ] + ), + true, + 'bag_ne(prepared, dupes array)', + '', + '' +); + +-- Fail with incompatible columns. +SELECT * FROM check_test( + bag_ne( + 'justnames', + ARRAY[1, 2, 3] + ), + false, + 'bag_ne(prepared, array) incompatible types', + '', + ' Columns differ between queries: + have: (text) + want: (integer)' +); + +/****************************************************************************/ +-- Now test results_eq() with an array argument. + +PREPARE ordnames AS + VALUES ('Andrea'), ('Andrew'), ('Angel'), ('Angelina'), ('Anna'), ('Anthony'), ('Antonio'); + +SELECT * FROM check_test( + results_eq( + 'ordnames', + ARRAY['Andrea', 'Andrew', 'Angel', 'Angelina', 'Anna', 'Anthony', 'Antonio' ], + 'whatever' + ), + true, + 'results_eq(prepared, array, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + results_eq( + 'ordnames', + ARRAY['Andrea', 'Andrew', 'Angel', 'Angelina', 'Anna', 'Anthony', 'Antonio' ] + ), + true, + 'results_eq(prepared, array)', + '', + '' +); + +SELECT * FROM check_test( + results_eq( + 'ordnames', + ARRAY['Andrea', 'Andrew', 'Angel', 'Angelina', 'Anna', 'Anthony' ] + ), + false, + 'results_eq(prepared, array) extra record', + '', + ' Results differ beginning at row 7: + have: (Antonio) + want: NULL' +); + +/****************************************************************************/ +-- Now test results_eq(). + +PREPARE nenames_ord AS SELECT id, name FROM names WHERE name like 'An%' ORDER BY id; +PREPARE nexpect_ord AS VALUES (15, 'Anthony'), ( 44, 'Anna'), (11, 'Andrew'), + (63, 'Angel'), (86, 'Angelina'), (130, 'Andrea'), + (183, 'Antonio'); + +SELECT * FROM check_test( + results_ne( 'nenames_ord', 'nexpect_ord', 'whatever' ), + true, + 'results_ne(prepared, prepared, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + results_ne( 'nenames_ord', 'nexpect_ord' ), + true, + 'results_ne(prepared, prepared)', + '', + '' +); + +-- Pass a full SQL statement for the prepared statements. +SELECT * FROM check_test( + results_ne( 'EXECUTE nenames_ord', 'EXECUTE nexpect_ord' ), + true, + 'results_ne(execute, execute)', + '', + '' +); + +-- Compare with dupes. +SELECT * FROM check_test( + results_ne( + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (2, ''Anna''), (86, ''Angelina''), (2, ''Anna'')' + ), + true, + 'results_ne(dupe values, dupe values)', + '', + '' +); + +-- Compare with nulls. +SELECT * FROM check_test( + results_ne( + 'VALUES (4, NULL), (86, ''Angelina''), (1, NULL)', + 'VALUES (4, NULL), (86, ''Angelina''), (1, ''Anna'')' + ), + true, + 'results_ne(values with null, values with null)', + '', + '' +); + +-- Compare only NULLs +SELECT * FROM check_test( + results_ne( + 'VALUES (NULL, NULL), (NULL, NULL), (NULL, NULL)', + 'VALUES (NULL, NULL), (NULL, NULL)' + ), + true, + 'results_ne(nulls, nulls)', + '', + '' +); + +-- Compare with missing dupe. +SELECT * FROM check_test( + results_ne( + 'VALUES (1, ''Anna''), (86, ''Angelina''), (1, ''Anna'')', + 'VALUES (1, ''Anna''), (86, ''Angelina'')' + ), + true, + 'results_ne(values dupe, values)', + '', + '' +); + +-- Handle pass with null. +SELECT * FROM check_test( + results_ne( + 'VALUES (1, NULL), (86, ''Angelina'')', + 'VALUES (1, ''Anna''), (86, ''Angelina'')' + ), + true, + 'results_ne(values null, values)', + '', + '' +); + +-- Handle failure due to more subtle column mismatch, valid only on 8.4. +CREATE OR REPLACE FUNCTION subtlefail() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() < 80400 THEN + -- 8.3 and earlier cast records to text, so subtlety is out. + RETURN NEXT pass('results_ne(values, values) mismatch should fail'); + RETURN NEXT pass('results_ne(values, values) mismatch should have the proper description'); + RETURN NEXT pass('results_ne(values, values) mismatch should have the proper diagnostics'); + RETURN NEXT pass('results_ne(values, values) subtle mismatch should fail'); + RETURN NEXT pass('results_ne(values, values) subtle mismatch should have the proper description'); + RETURN NEXT pass('results_ne(values, values) subtle mismatch should have the proper diagnostics'); + RETURN NEXT pass('results_ne(values, values) fail column count should fail'); + RETURN NEXT pass('results_ne(values, values) fail column count should have the proper description'); + RETURN NEXT pass('results_ne(values, values) fail column count should have the proper diagnostics'); + ELSE + -- 8.4 does true record comparisions, yay! + -- Handle failure due to column mismatch. + FOR tap IN SELECT * FROM check_test( + results_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'results_ne(values, values) mismatch', + '', + ' Columns differ between queries: + have: (1,foo) + want: (foo,1)' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + -- Handle failure due to subtle column mismatch. + FOR tap IN SELECT * FROM check_test( + results_ne( + 'VALUES (1, ''foo''::varchar), (2, ''bar''::varchar)', + 'VALUES (1, ''foo''), (2, ''bar'')' + ), + false, + 'results_ne(values, values) subtle mismatch', + '', + ' Columns differ between queries: + have: (1,foo) + want: (1,foo)' ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + -- Handle failure due to column count mismatch. + FOR tap IN SELECT * FROM check_test( + results_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'results_ne(values, values) fail column count', + '', + ' Columns differ between queries: + have: (1) + want: (foo,1)' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + END IF; + RETURN; +END; +$$ LANGUAGE plpgsql; +SELECT * FROM subtlefail(); + + +-- Compare with cursors. +CLOSE cwant; +CLOSE chave; +DECLARE cwant CURSOR FOR SELECT id, name FROM names WHERE name like 'An%' ORDER BY id; +DECLARE chave CURSOR FOR SELECT id, name from annames ORDER BY id; + +-- Mix cursors and prepared statements +MOVE BACKWARD ALL IN cwant; + +SELECT * FROM check_test( + results_ne( 'cwant'::refcursor, 'annames_ord' ), + false, + 'results_ne(cursor, prepared)', + '', + '' +); + +MOVE BACKWARD ALL IN chave; +SELECT * FROM check_test( + results_ne( 'annames_ord', 'chave'::refcursor ), + false, + 'results_ne(prepared, cursor)', + '', + '' +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From 64faa0b319073f5c9e8325cfaa28f76361c5fbbf Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 30 Jul 2009 13:39:47 -0700 Subject: [PATCH 0426/1195] Updated for 8.0 Looks like I will have to figure out some way to avoid E'' in the resultset tests. In higher versions of PostgreSQL, that is. Not sure how I'll do it, but that's coming next. --- .gitignore | 4 + Makefile | 8 +- compat/install-8.0.patch | 237 +++++++++++++++++++++++++++++++++---- compat/uninstall-8.0.patch | 6 +- pgtap.sql.in | 1 - sql/resultset.sql | 20 ++-- 6 files changed, 232 insertions(+), 44 deletions(-) diff --git a/.gitignore b/.gitignore index 1b97b5799837..7b7ca1a2ebfd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ pgtap.sql +pgtap.sql.reg +pgtap.sql.orig uninstall_pgtap.sql +uninstall_pgtap.sql.rej +uninstall_pgtap.sql.orig test_setup.sql results pgtap.so diff --git a/Makefile b/Makefile index 5c9e9e9f5e95..9ed0726583c2 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ EXTRA_SUBS := -e "s/ E'/ '/g" # Throw, runtests, enums, and roles aren't supported in 8.0. TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql sql/enumtap.sql sql/roletap.sql,$(TESTS)) REGRESS := $(filter-out throwtap runtests enumtap roletap,$(REGRESS)) -else +endif ifeq ($(PGVER_MINOR), 4) # Do nothing. else @@ -75,7 +75,6 @@ REGRESS := $(filter-out valueset,$(REGRESS)) endif endif endif -endif # Determine the OS. Borrowed from Perl's Configure. ifeq ($(wildcard /irix), /irix) @@ -146,7 +145,9 @@ else endif ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 4) +ifneq ($(PGVER_MINOR), 0) patch -p0 < compat/install-8.3.patch +endif ifneq ($(PGVER_MINOR), 3) cat compat/install-8.2.sql >> pgtap.sql ifeq ($(PGVER_MINOR), 2) @@ -156,7 +157,6 @@ ifeq ($(PGVER_MINOR), 1) patch -p0 < compat/install-8.2.patch patch -p0 < compat/install-8.1.patch else - patch -p0 < compat/install-8.2.patch patch -p0 < compat/install-8.0.patch endif endif @@ -180,7 +180,7 @@ ifneq ($(PGVER_MINOR), 3) rm uninstall_pgtap.tmp endif ifeq ($(PGVER_MINOR), 0) - patch -p0 < compat/uninstall-8.0.patch +# patch -p0 < compat/uninstall-8.0.patch mv uninstall_pgtap.sql uninstall_pgtap.tmp cat compat/uninstall-8.0.sql uninstall_pgtap.tmp >> uninstall_pgtap.sql rm uninstall_pgtap.tmp diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index ba11679504a6..2ab8b1697ca7 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,8 +1,8 @@ ---- pgtap.sql.orig 2009-05-28 15:45:03.000000000 -0700 -+++ pgtap.sql 2009-05-28 15:41:05.000000000 -0700 -@@ -11,6 +11,22 @@ - -- ## CREATE SCHEMA TAPSCHEMA; - -- ## SET search_path TO TAPSCHEMA, public; +--- pgtap.sql.orig 2009-07-30 13:35:07.000000000 -0700 ++++ pgtap.sql 2009-07-30 13:35:24.000000000 -0700 +@@ -15,6 +15,27 @@ + RETURNS text AS 'SELECT current_setting(''server_version'')' + LANGUAGE SQL IMMUTABLE; +-- Cast oidvector to regtype[] like 8.1 does. +CREATE OR REPLACE FUNCTION oidvregtype(oidvector) @@ -20,10 +20,15 @@ + +CREATE CAST (int2vector AS int[]) WITH FUNCTION int2vint(int2vector) AS ASSIGNMENT; + - CREATE OR REPLACE FUNCTION pg_typeof("any") - RETURNS regtype - AS '$libdir/pgtap' -@@ -96,53 +112,63 @@ ++CREATE OR REPLACE FUNCTION pg_typeof("any") ++RETURNS regtype ++AS '$libdir/pgtap' ++LANGUAGE C STABLE; ++ + CREATE OR REPLACE FUNCTION pg_version_num() + RETURNS integer AS $$ + SELECT s.a[1]::int * 10000 +@@ -91,53 +112,63 @@ CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ DECLARE @@ -104,7 +109,24 @@ END; $$ LANGUAGE plpgsql strict; -@@ -206,10 +232,12 @@ +@@ -189,11 +220,11 @@ + RETURNS integer AS $$ + BEGIN + EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) +- VALUES( ' || $1 || ', ' +- || $2 || ', ' +- || quote_literal(COALESCE($3, '')) || ', ' +- || quote_literal($4) || ', ' +- || quote_literal($5) || ' )'; ++ VALUES( ' || $1::text || ', ' ++ || $2::text || ', ' ++ || quote_literal(COALESCE($3, '')::text) || ', ' ++ || quote_literal($4::text) || ', ' ++ || quote_literal($5::text) || ' )'; + RETURN currval('__tresults___numb_seq'); + END; + $$ LANGUAGE plpgsql; +@@ -201,10 +232,12 @@ CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ DECLARE @@ -120,7 +142,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -483,13 +511,16 @@ +@@ -478,13 +511,16 @@ want ALIAS FOR $3; descr ALIAS FOR $4; result BOOLEAN; @@ -128,19 +150,20 @@ output TEXT; BEGIN - EXECUTE 'SELECT ' || -+ FOR rec IN EXECUTE 'SELECT ' || - COALESCE(quote_literal( have ), 'NULL') || '::' || pg_typeof(have) || ' ' +- COALESCE(quote_literal( have ), 'NULL') || '::' || pg_typeof(have) || ' ' ++ FOR rec IN EXECUTE 'SELECT ' || ++ COALESCE(quote_literal( have ), 'NULL') || '::' || pg_typeof(have)::text || ' ' || op || ' ' || - COALESCE(quote_literal( want ), 'NULL') || '::' || pg_typeof(want) - INTO result; -+ COALESCE(quote_literal( want ), 'NULL') || '::' || pg_typeof(want) || ' AS res' ++ COALESCE(quote_literal( want ), 'NULL') || '::' || pg_typeof(want)::text || ' AS res' + LOOP + result := rec.res; + END LOOP; output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE '\n' || diag( ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -1227,19 +1258,21 @@ +@@ -1232,19 +1268,21 @@ CREATE OR REPLACE FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -152,7 +175,7 @@ RETURN is( $1, $3, $4 ); END IF; -- EXECUTE 'SELECT is(' +- EXECUTE 'SELECT is(' + FOR ret IN EXECUTE 'SELECT is(' || COALESCE($1, 'NULL' || '::' || $2) || '::' || $2 || ', ' || COALESCE(quote_literal($3), 'NULL') || '::' || $2 || ', ' @@ -166,10 +189,67 @@ END; $$ LANGUAGE plpgsql; -@@ -3288,39 +3321,6 @@ +@@ -3293,96 +3331,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( @@ -206,7 +286,7 @@ CREATE OR REPLACE FUNCTION _is_super( NAME ) RETURNS BOOLEAN AS $$ SELECT usesuper -@@ -3424,7 +3424,7 @@ +@@ -3486,7 +3434,7 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _grolist ( NAME ) @@ -215,7 +295,7 @@ SELECT grolist FROM pg_catalog.pg_group WHERE groname = $1; $$ LANGUAGE sql; -@@ -5385,6 +5385,7 @@ +@@ -5447,6 +5395,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -223,7 +303,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -5396,8 +5397,10 @@ +@@ -5458,8 +5407,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. @@ -236,7 +316,7 @@ -- Now delete those results. EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; -@@ -5520,7 +5523,7 @@ +@@ -5582,7 +5533,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -245,7 +325,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -5528,8 +5531,8 @@ +@@ -5590,8 +5541,8 @@ -- 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. @@ -256,7 +336,7 @@ END LOOP; END LOOP; RETURN; -@@ -5589,116 +5592,6 @@ +@@ -5651,116 +5602,6 @@ END $$ LANGUAGE plpgsql; @@ -370,6 +450,111 @@ - SELECT * FROM runtests( '^test' ); -$$ LANGUAGE sql; - - -- 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;' + CREATE OR REPLACE FUNCTION _temptable ( TEXT, TEXT ) + RETURNS TEXT AS $$ + BEGIN +@@ -5786,8 +5627,9 @@ + 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 ++ JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid + WHERE c.relname = $1 +- AND c.relistemp ++ AND n.nspname LIKE 'pg_temp%' + AND attnum > 0 + AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END + ORDER BY attnum +@@ -5809,13 +5651,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, 2249))); + 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, 2249))); + END LOOP; + + -- Drop the temporary tables. +@@ -5929,16 +5771,20 @@ + missing TEXT[] := '{}'; + res BOOLEAN := TRUE; + msg TEXT := ''; ++ rec RECORD; + BEGIN + BEGIN + -- Find extra records. +- EXECUTE 'SELECT EXISTS ( ' ++ FOR rec IN EXECUTE 'SELECT EXISTS ( ' + || '( SELECT * FROM ' || have || ' EXCEPT ' || $4 + || ' SELECT * FROM ' || want + || ' ) UNION ( ' + || ' SELECT * FROM ' || want || ' EXCEPT ' || $4 + || ' SELECT * FROM ' || have +- || ' ) LIMIT 1 )' INTO res; ++ || ' ) LIMIT 1 ) AS a' ++ LOOP ++ res := rec.a; ++ END LOOP; + + -- Drop the temporary tables. + EXECUTE 'DROP TABLE ' || have; +@@ -6039,7 +5885,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, 2249))); + END LOOP; + + -- Drop the temporary tables. +@@ -6134,11 +5980,11 @@ + 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 ++ IF textin(record_out(have_rec, 2249)) IS DISTINCT FROM textin(record_out(want_rec, 2249)) OR have_found <> want_found THEN + RETURN ok( false, $3 ) || '\n' || diag( + ' Results differ beginning at row ' || rownum || ':\n' || +- ' have: ' || CASE WHEN have_found THEN have_rec::text ELSE 'NULL' END || '\n' || +- ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE 'NULL' END ++ ' have: ' || CASE WHEN have_found THEN textin(record_out(have_rec, 2249)) ELSE 'NULL' END || '\n' || ++ ' want: ' || CASE WHEN want_found THEN textin(record_out(want_rec, 2249)) ELSE 'NULL' END + ); + END IF; + rownum = rownum + 1; +@@ -6153,8 +5999,8 @@ + WHEN datatype_mismatch THEN + RETURN ok( false, $3 ) || '\n' || diag( + ' Columns differ between queries:\n' || +- ' have: ' || CASE WHEN have_found THEN have_rec::text ELSE 'NULL' END || '\n' || +- ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE 'NULL' END ++ ' have: ' || CASE WHEN have_found THEN textin(record_out(have_rec, 2249)) ELSE 'NULL' END || '\n' || ++ ' want: ' || CASE WHEN want_found THEN textin(record_out(want_rec, 2249)) ELSE 'NULL' END + ); + END; + $$ LANGUAGE plpgsql; +@@ -6289,7 +6135,7 @@ + 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 ++ IF textin(record_out(have_rec, 2249)) IS DISTINCT FROM textin(record_out(want_rec, 2249)) OR have_found <> want_found THEN + RETURN ok( true, $3 ); + ELSE + FETCH have INTO have_rec; +@@ -6303,8 +6149,8 @@ + WHEN datatype_mismatch THEN + RETURN ok( false, $3 ) || '\n' || diag( + ' Columns differ between queries:\n' || +- ' have: ' || CASE WHEN have_found THEN have_rec::text ELSE 'NULL' END || '\n' || +- ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE 'NULL' END ++ ' have: ' || CASE WHEN have_found THEN textin(record_out(have_rec, 2249)) ELSE 'NULL' END || '\n' || ++ ' want: ' || CASE WHEN want_found THEN textin(record_out(want_rec, 2249)) ELSE 'NULL' END + ); + END; + $$ LANGUAGE plpgsql; diff --git a/compat/uninstall-8.0.patch b/compat/uninstall-8.0.patch index b4f1d10d2102..41bafc6e2a44 100644 --- a/compat/uninstall-8.0.patch +++ b/compat/uninstall-8.0.patch @@ -1,6 +1,6 @@ ---- uninstall_pgtap.sql.orig 2009-05-28 15:35:12.000000000 -0700 -+++ uninstall_pgtap.sql 2009-05-28 15:35:12.000000000 -0700 -@@ -250,11 +250,6 @@ +--- uninstall_pgtap.sql.orig 2009-07-30 13:36:36.000000000 -0700 ++++ uninstall_pgtap.sql 2009-07-30 13:36:49.000000000 -0700 +@@ -312,11 +312,6 @@ DROP FUNCTION has_user( NAME ); DROP FUNCTION has_user( NAME, TEXT ); DROP FUNCTION _is_super( NAME ); diff --git a/pgtap.sql.in b/pgtap.sql.in index efc36f92d070..6f0450dfbba1 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -5929,7 +5929,6 @@ DECLARE missing TEXT[] := '{}'; res BOOLEAN := TRUE; msg TEXT := ''; - rec RECORD; BEGIN BEGIN -- Find extra records. diff --git a/sql/resultset.sql b/sql/resultset.sql index 26d29cb600fc..1c1e5edc6938 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -330,7 +330,7 @@ SELECT * FROM check_test( false, 'set_eq(prepared, select) fail extras', '', - E' Extra records: + ' Extra records: \\((44,Anna|86,Angelina)\\) \\((44,Anna|86,Angelina)\\)', true @@ -356,7 +356,7 @@ SELECT * FROM check_test( false, 'set_eq(select, prepared) fail missings', '', - E' Missing records: + ' Missing records: \\((44,Anna|86,Angelina)\\) \\((44,Anna|86,Angelina)\\)', true @@ -384,7 +384,7 @@ SELECT * FROM check_test( false, 'set_eq(select, select) fail extras & missings', '', - E' Extra records: + ' Extra records: \\((1,Jacob|87,Jackson)\\) \\((1,Jacob|87,Jackson)\\) Missing records: @@ -493,7 +493,7 @@ SELECT * FROM check_test( false, 'bag_eq(prepared, select) fail extras', '', - E' Extra records: + ' Extra records: \\((44,Anna|86,Angelina)\\) \\((44,Anna|86,Angelina)\\)', true @@ -519,7 +519,7 @@ SELECT * FROM check_test( false, 'bag_eq(select, prepared) fail missings', '', - E' Missing records: + ' Missing records: \\((44,Anna|86,Angelina)\\) \\((44,Anna|86,Angelina)\\)', true @@ -547,7 +547,7 @@ SELECT * FROM check_test( false, 'bag_eq(select, select) fail extras & missings', '', - E' Extra records: + ' Extra records: \\((1,Jacob|87,Jackson)\\) \\((1,Jacob|87,Jackson)\\) Missing records: @@ -1142,7 +1142,7 @@ SELECT * FROM check_test( false, 'set_has(missing2, expect )', '', - E' Missing records: + ' Missing records: \\((44,Anna|86,Angelina)\\) \\((44,Anna|86,Angelina)\\)', true @@ -1266,7 +1266,7 @@ SELECT * FROM check_test( false, 'bag_has(missing2, expect )', '', - E' Missing records: + ' Missing records: \\((44,Anna|86,Angelina)\\) \\((44,Anna|86,Angelina)\\)', true @@ -1377,7 +1377,7 @@ SELECT * FROM check_test( false, 'set_hasnt( prepared, values )', '', - E' Extra records: + ' Extra records: \\((44,Anna|86,Angelina)\\) \\((44,Anna|86,Angelina)\\)', true @@ -1473,7 +1473,7 @@ SELECT * FROM check_test( false, 'bag_hasnt( prepared, values )', '', - E' Extra records: + ' Extra records: \\((44,Anna|86,Angelina)\\) \\((44,Anna|86,Angelina)\\)', true From 954141919a6c2469385372bb0e73928417c22534 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 30 Jul 2009 13:43:53 -0700 Subject: [PATCH 0427/1195] Forgot to properly update the 8.0 uninstall patch. --- Makefile | 2 +- compat/uninstall-8.0.patch | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 9ed0726583c2..7555128b2e69 100644 --- a/Makefile +++ b/Makefile @@ -180,7 +180,7 @@ ifneq ($(PGVER_MINOR), 3) rm uninstall_pgtap.tmp endif ifeq ($(PGVER_MINOR), 0) -# patch -p0 < compat/uninstall-8.0.patch + patch -p0 < compat/uninstall-8.0.patch mv uninstall_pgtap.sql uninstall_pgtap.tmp cat compat/uninstall-8.0.sql uninstall_pgtap.tmp >> uninstall_pgtap.sql rm uninstall_pgtap.tmp diff --git a/compat/uninstall-8.0.patch b/compat/uninstall-8.0.patch index 41bafc6e2a44..6a2ec9119987 100644 --- a/compat/uninstall-8.0.patch +++ b/compat/uninstall-8.0.patch @@ -1,6 +1,6 @@ ---- uninstall_pgtap.sql.orig 2009-07-30 13:36:36.000000000 -0700 -+++ uninstall_pgtap.sql 2009-07-30 13:36:49.000000000 -0700 -@@ -312,11 +312,6 @@ +--- uninstall_pgtap.sql.orig 2009-07-30 13:41:19.000000000 -0700 ++++ uninstall_pgtap.sql 2009-07-30 13:42:52.000000000 -0700 +@@ -308,11 +308,6 @@ DROP FUNCTION has_user( NAME ); DROP FUNCTION has_user( NAME, TEXT ); DROP FUNCTION _is_super( NAME ); From 828eca32503ac1224bea9d03b08c507aa8bcda2b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 30 Jul 2009 14:04:30 -0700 Subject: [PATCH 0428/1195] Updated for 8.3 again. In updating for 8.0, I got rid of all the `E''`s in `sql/resultset.sql` and `sql/valueset.sql`, but since they had backslashes in them, they broke in later versions. Fortunately, I was able to replaces those backslashes (in regular expressions) with character classes (`[(]` instead of `\\(`), thus eliminating the problem in all versions of PostgreSQL. Also updated the 8.3 install patch, since I deleted a line while working on 8.0. Also got the 8.3 patch to apply again! --- Makefile | 2 +- compat/install-8.3.patch | 8 +++---- sql/resultset.sql | 48 ++++++++++++++++++++-------------------- sql/valueset.sql | 24 ++++++++++---------- 4 files changed, 41 insertions(+), 41 deletions(-) diff --git a/Makefile b/Makefile index 7555128b2e69..2c060a46f02a 100644 --- a/Makefile +++ b/Makefile @@ -67,7 +67,6 @@ ifneq ($(PGVER_MINOR), 3) # Enum tests not supported by 8.2 and earlier. TESTS := $(filter-out sql/enumtap.sql,$(TESTS)) REGRESS := $(filter-out enumtap,$(REGRESS)) -endif ifneq ($(PGVER_MINOR), 2) # Values tests not supported by 8.1 and earlier. TESTS := $(filter-out sql/valueset.sql,$(TESTS)) @@ -75,6 +74,7 @@ REGRESS := $(filter-out valueset,$(REGRESS)) endif endif endif +endif # Determine the OS. Borrowed from Perl's Configure. ifeq ($(wildcard /irix), /irix) diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index 190d71ea0d89..bf205960217c 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.orig 2009-07-29 17:31:13.000000000 -0700 -+++ pgtap.sql 2009-07-29 17:32:02.000000000 -0700 +--- pgtap.sql.orig 2009-07-30 13:51:56.000000000 -0700 ++++ pgtap.sql 2009-07-30 13:52:15.000000000 -0700 @@ -15,6 +15,11 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -23,7 +23,7 @@ AND attnum > 0 AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END ORDER BY attnum -@@ -6135,7 +6141,7 @@ +@@ -6134,7 +6140,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -32,7 +32,7 @@ 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' || -@@ -6290,7 +6296,7 @@ +@@ -6289,7 +6295,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP diff --git a/sql/resultset.sql b/sql/resultset.sql index 1c1e5edc6938..c19b462b2e22 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -331,8 +331,8 @@ SELECT * FROM check_test( 'set_eq(prepared, select) fail extras', '', ' Extra records: - \\((44,Anna|86,Angelina)\\) - \\((44,Anna|86,Angelina)\\)', + [(](44,Anna|86,Angelina)[)] + [(](44,Anna|86,Angelina)[)]', true ); @@ -357,8 +357,8 @@ SELECT * FROM check_test( 'set_eq(select, prepared) fail missings', '', ' Missing records: - \\((44,Anna|86,Angelina)\\) - \\((44,Anna|86,Angelina)\\)', + [(](44,Anna|86,Angelina)[)] + [(](44,Anna|86,Angelina)[)]', true ); @@ -385,11 +385,11 @@ SELECT * FROM check_test( 'set_eq(select, select) fail extras & missings', '', ' Extra records: - \\((1,Jacob|87,Jackson)\\) - \\((1,Jacob|87,Jackson)\\) + [(](1,Jacob|87,Jackson)[)] + [(](1,Jacob|87,Jackson)[)] Missing records: - \\((44,Anna|86,Angelina)\\) - \\((44,Anna|86,Angelina)\\)', + [(](44,Anna|86,Angelina)[)] + [(](44,Anna|86,Angelina)[)]', true ); @@ -494,8 +494,8 @@ SELECT * FROM check_test( 'bag_eq(prepared, select) fail extras', '', ' Extra records: - \\((44,Anna|86,Angelina)\\) - \\((44,Anna|86,Angelina)\\)', + [(](44,Anna|86,Angelina)[)] + [(](44,Anna|86,Angelina)[)]', true ); @@ -520,8 +520,8 @@ SELECT * FROM check_test( 'bag_eq(select, prepared) fail missings', '', ' Missing records: - \\((44,Anna|86,Angelina)\\) - \\((44,Anna|86,Angelina)\\)', + [(](44,Anna|86,Angelina)[)] + [(](44,Anna|86,Angelina)[)]', true ); @@ -548,11 +548,11 @@ SELECT * FROM check_test( 'bag_eq(select, select) fail extras & missings', '', ' Extra records: - \\((1,Jacob|87,Jackson)\\) - \\((1,Jacob|87,Jackson)\\) + [(](1,Jacob|87,Jackson)[)] + [(](1,Jacob|87,Jackson)[)] Missing records: - \\((44,Anna|86,Angelina)\\) - \\((44,Anna|86,Angelina)\\)', + [(](44,Anna|86,Angelina)[)] + [(](44,Anna|86,Angelina)[)]', true ); @@ -1143,8 +1143,8 @@ SELECT * FROM check_test( 'set_has(missing2, expect )', '', ' Missing records: - \\((44,Anna|86,Angelina)\\) - \\((44,Anna|86,Angelina)\\)', + [(](44,Anna|86,Angelina)[)] + [(](44,Anna|86,Angelina)[)]', true ); @@ -1267,8 +1267,8 @@ SELECT * FROM check_test( 'bag_has(missing2, expect )', '', ' Missing records: - \\((44,Anna|86,Angelina)\\) - \\((44,Anna|86,Angelina)\\)', + [(](44,Anna|86,Angelina)[)] + [(](44,Anna|86,Angelina)[)]', true ); @@ -1378,8 +1378,8 @@ SELECT * FROM check_test( 'set_hasnt( prepared, values )', '', ' Extra records: - \\((44,Anna|86,Angelina)\\) - \\((44,Anna|86,Angelina)\\)', + [(](44,Anna|86,Angelina)[)] + [(](44,Anna|86,Angelina)[)]', true ); @@ -1474,8 +1474,8 @@ SELECT * FROM check_test( 'bag_hasnt( prepared, values )', '', ' Extra records: - \\((44,Anna|86,Angelina)\\) - \\((44,Anna|86,Angelina)\\)', + [(](44,Anna|86,Angelina)[)] + [(](44,Anna|86,Angelina)[)]', true ); diff --git a/sql/valueset.sql b/sql/valueset.sql index 30dd9233587a..cbb1878c0c68 100644 --- a/sql/valueset.sql +++ b/sql/valueset.sql @@ -305,8 +305,8 @@ SELECT * FROM check_test( 'set_eq(select, prepared) fail missings', '', E' Missing records: - \\((44,Anna|86,Angelina)\\) - \\((44,Anna|86,Angelina)\\)', + [(](44,Anna|86,Angelina)[)] + [(](44,Anna|86,Angelina)[)]', true ); @@ -394,8 +394,8 @@ SELECT * FROM check_test( 'bag_eq(select, prepared) fail missings', '', E' Missing records: - \\((44,Anna|86,Angelina)\\) - \\((44,Anna|86,Angelina)\\)', + [(](44,Anna|86,Angelina)[)] + [(](44,Anna|86,Angelina)[)]', true ); @@ -819,8 +819,8 @@ SELECT * FROM check_test( 'set_has(missing2, expect )', '', E' Missing records: - \\((44,Anna|86,Angelina)\\) - \\((44,Anna|86,Angelina)\\)', + [(](44,Anna|86,Angelina)[)] + [(](44,Anna|86,Angelina)[)]', true ); @@ -911,8 +911,8 @@ SELECT * FROM check_test( 'bag_has(missing2, expect )', '', E' Missing records: - \\((44,Anna|86,Angelina)\\) - \\((44,Anna|86,Angelina)\\)', + [(](44,Anna|86,Angelina)[)] + [(](44,Anna|86,Angelina)[)]', true ); @@ -991,8 +991,8 @@ SELECT * FROM check_test( 'set_hasnt( prepared, values )', '', E' Extra records: - \\((44,Anna|86,Angelina)\\) - \\((44,Anna|86,Angelina)\\)', + [(](44,Anna|86,Angelina)[)] + [(](44,Anna|86,Angelina)[)]', true ); @@ -1060,8 +1060,8 @@ SELECT * FROM check_test( 'bag_hasnt( prepared, values )', '', E' Extra records: - \\((44,Anna|86,Angelina)\\) - \\((44,Anna|86,Angelina)\\)', + [(](44,Anna|86,Angelina)[)] + [(](44,Anna|86,Angelina)[)]', true ); From 4b98bd8e2e2f08386f4faa415f876ed2e3a6966f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 30 Jul 2009 14:16:40 -0700 Subject: [PATCH 0429/1195] Updated 8.2 patch --- compat/install-8.2.patch | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 737a89fc004b..fedb29eb256d 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.orig 2009-07-29 21:23:40.000000000 -0700 -+++ pgtap.sql 2009-07-29 21:23:57.000000000 -0700 +--- pgtap.sql.orig 2009-07-30 14:14:11.000000000 -0700 ++++ pgtap.sql 2009-07-30 14:14:20.000000000 -0700 @@ -194,11 +194,11 @@ RETURNS integer AS $$ BEGIN @@ -109,7 +109,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6046,7 +5989,7 @@ +@@ -6045,7 +5988,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -118,7 +118,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6141,11 +6084,11 @@ +@@ -6140,11 +6083,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -133,7 +133,7 @@ ); END IF; rownum = rownum + 1; -@@ -6160,8 +6103,8 @@ +@@ -6159,8 +6102,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -144,7 +144,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6296,7 +6239,7 @@ +@@ -6295,7 +6238,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -153,7 +153,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6310,8 +6253,8 @@ +@@ -6309,8 +6252,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || From f77b8165902867c7a683dcc641dcb10bc0c579a7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 30 Jul 2009 15:23:04 -0700 Subject: [PATCH 0430/1195] Proof-read README.pgtap and Changes --- Changes | 30 ++++-- README.pgtap | 288 ++++++++++++++++++++++++++++----------------------- 2 files changed, 181 insertions(+), 137 deletions(-) diff --git a/Changes b/Changes index c4910fda7a48..499fee9c5f12 100644 --- a/Changes +++ b/Changes @@ -4,17 +4,27 @@ Revision history for pgTAP 0.22 ------------------------- * Fixed failing test on 8.4rc2. -* Added `results_eq(), `results_ne()`, `set_eq()`, `bag_eq()`, `set_ne()`, - `bag_ne()`, `set_has()`, `bag_has()`, `set_hasnt()`, and `bag_hasnt()` - functions to test query results. +* Added result set testing functions. These allow testers to write queries in + pure SQL and check that their results are as expected. The new functions + are: + + `results_eq()` + + `results_ne()` + + `set_eq()` + + `bag_eq()` + + `set_ne()` + + `bag_ne()` + + `set_has()` + + `bag_has()` + + `set_hasnt()` + + `bag_hasnt()` * Changed some internal queries to use explicit `JOIN`s. * Fixed capitalization of `TAP::Harness` in the `Makefile`. Thanks to Quinn Weaver. -* Fixed a syntax error that caused uninstall_pgtap.sql to fail on older +* Fixed a syntax error that caused `uninstall_pgtap.sql` to fail on older PostgreSQL versions (fix by Quinn Weaver). * Modified the summary documentation for the `--schema` and `--match` options - to better reflect that they are for finding xUnit tests, rather than for - finding pgTAP functions. + to `pg_prove` to better reflect that they are for finding xUnit tests, + not for finding pgTAP functions. 0.21 2009-05-29T00:04:31 ------------------------- @@ -53,13 +63,13 @@ Revision history for pgTAP * Added `has_cast()`, `hasnt_cast()`, and `cast_context_is()`. * Fixed a borked function signature in `has_trigger()`. * Added `has_operator()`, `has_leftop()`, and `has_rightop()`. -* Fixed a bug where the order of columns found for multicolum indexes by +* Fixed a bug where the order of columns found for multicolumn indexes by `has_index()` could be wrong. Reported by Jeff Wartes. Thanks to Andrew Gierth for help fixing the query. 0.19 2009-02-21T02:09:26 ------------------------- -* Added a alernate versions of `col_default_is()` to better handle the common +* Added an alternate version of `col_default_is()` to better handle the common case when a default is specified as a string, such as a text or expression default. This means that you have to do a lot less casting of default values specified in the arguments to `col_default_is()`. It also restores the @@ -76,7 +86,7 @@ Revision history for pgTAP that it can be used to run xUnit-style test functions without an SQL script. * Fixed a bug in `can()` where it could sometimes report that functions were available in a schema when in fact they were not. -* In the schema testing functions, removed the use ofa `quote_ident()` to +* In the schema testing functions, removed the use of `quote_ident()` to compare all identifiers, such as table names, schema names, column names, etc, added in 0.17. This is because `quote_ident(a) = quote_ident(b)` can't give a different result than `a = b`, and besides, made things slower and @@ -130,7 +140,7 @@ Revision history for pgTAP case-insensitive matching. This means that tests should use only lowercase strings to specify object names, unless mixed case objects were created with double quote characters. -* Added version of `todo()` with the `why` and `how_many` arugments +* Added version of `todo()` with the `why` and `how_many` arguments reversed, so that I don't have to remember a specific order. 0.16 2009-02-03T17:37:03 diff --git a/README.pgtap b/README.pgtap index 29efd32018fd..c431a55c5d43 100644 --- a/README.pgtap +++ b/README.pgtap @@ -70,7 +70,7 @@ schema you'd like to be created, for example: Testing pgTAP with pgTAP ------------------------ -In addition to the PostgreSQL-standared `installcheck` target, the `test` +In addition to the PostgreSQL-standard `installcheck` target, the `test` target uses the included `pg_prove` Perl program to do its testing, which requires that TAP::Harness, included in [Test::Harness](http://search.cpan.org/dist/Test-Harness/ "Test::Harness on @@ -534,23 +534,26 @@ you've got some complicated condition that is difficult to wedge into an Use these functions very, very, very sparingly. -Submit Your Query -================= +Pursuing Your Query +=================== -Sometimes, you've just gotta run tests on a query. I mean a full blown query, -not just the scalar assertion functions we've seen so far. pgTAP provides a -number of functions to help you test your queries, each of which takes one or -two SQL statements as arguments. Yes, as strings: +Sometimes, you've just gotta test a query. I mean the results of a full blown +query, not just the scalar assertion functions we've seen so far. pgTAP +provides a number of functions to help you test your queries, each of which +takes one or two SQL statements as arguments. For example: SELECT throws_ok('SELECT divide_by(0)'); -Of course, you'll often need to do something complex in your SQL, and quoting -SQL in strings in what is, after all, an SQL application, is an unnecessary -PITA. Each of the query-executing functions in this section thus support an -alternative to make your tests more SQLish: using prepared statements. +Yes, as strings. Of course, you'll often need to do something complex in your +SQL, and quoting SQL in strings in what is, after all, an SQL application, is +an unnecessary PITA. Each of the query-executing functions in this section +thus support an alternative to make your tests more SQLish: using prepared +statements. -Prepared statements allow you to just write SQL and simply pass the prepared -statement names. For example, the above example can be rewritten as: +[Prepared statements](http://www.postgresql.org/docs/current/static/sql-prepare.html +"PostgreSQL Documentation: `PREPARE`") allow you to just write SQL and simply +pass the prepared statement names to test functions. For example, the above +example can be rewritten as: PREPARE mythrow AS SELECT divide_by(0); SELECT throws_ok('mythrow'); @@ -558,9 +561,9 @@ statement names. For example, the above example can be rewritten as: pgTAP assumes that an SQL argument without space characters or starting with a double quote character is a prepared statement and simply `EXECUTE`s it. If you need to pass arguments to a prepared statement, perhaps because you plan -to use it in multiple tests to return different values, just include `EXECUTE` -in the SQL string. Here's an example with a prepared statement with a space in -its name, and one where arguments need to be passed: +to use it in multiple tests to return different values, just `EXECUTE` it +yourself. Here's an example with a prepared statement with a space in its +name, and one where arguments need to be passed: PREPARE "my test" AS SELECT * FROM active_users() WHERE name LIKE 'A%'; PREPARE expect AS SELECT * FROM users WHERE active = $1 AND name LIKE $2; @@ -574,7 +577,44 @@ Since "my test" was declared with double quotes, it must be passed with double quotes. And since the call to "expect" included spaces (to keep it legible), the `EXECUTE` keyword was required. -So keeping these techniques in mind, read on for all of the query-testing +In PostgreSQL 8.2 and up, you can also use a `VALUES` statement, both in +the query string or in a prepared statement. A useless example: + + PREPARE myvals AS VALUES (1, 2), (3, 4); + SELECT set_eq( + 'myvals', + 'VALUES (1, 2), (3, 4)' + ); + +On PostgreSQL 8.1 and down, you'll have to use `UNION` queries (if order +doesn't matter) or temporary tables to pass arbitrary values: + + SET client_min_messages = warning; + CREATE TEMPORARY TABLE stuff (a int, b int); + INSERT INTO stuff VALUES(1, 2); + INSERT INTO stuff VALUES(3, 4); + RESET client_min_messages; + PREPARE myvals AS SELECT a, b FROM stuff; + + SELECT set_eq( + 'myvals', + 'SELECT 1, 2 UNION SELECT 3, 4' + ); + +Here's a bonus if you need to check the results from a query that returns a +single column: for those functions that take two query arguments, the second +can be an array. Check it out: + + SELECT results_eq( + 'SELECT * FROM active_user_ids()', + ARRAY[ 2, 3, 4, 5] + ); + +The first query *must* return only one column of the same type as the values +in the array. If you need to test more columns, you'll need to use two +queries. + +Keeping these techniques in mind, read on for all of the query-testing goodness. To Error is Human @@ -600,14 +640,13 @@ error-prone as you think they should be. 'We should get a unique violation for a duplicate PK' ); -When you want to make sure that an exception is thrown by PostgreSQL under -certain circumstances, use `throws_ok()` to test for those circumstances. -Supported by 8.1 or higher. +When you want to make sure that an exception is thrown by PostgreSQL, use +`throws_ok()` to test for it. Supported by 8.1 and up. The first argument should be the name of a prepared statement or else a string representing the query to be executed (see the [summary](#Submit+Your+Query) for query argument details). `throws_ok()` will use the PL/pgSQL `EXECUTE` -statement to execute it and catch any exception. +statement to execute the query and catch any exception. The second argument should be an exception error code, which is a five-character string (if it happens to consist only of numbers and you pass @@ -625,17 +664,17 @@ careful of localized error messages. The fourth argument is of course a brief test description. -For the three- and two-argument forms of `throws_ok()`, if the second argument +For the two- and three-argument forms of `throws_ok()`, if the second argument is exactly five bytes long, it is assumed to be an error code and the optional third argument is the error message. Otherwise, the second argument is assumed to be an error message and the third argument is a description. If for some reason you need to test an error message that is five bytes long, use the four-argument form. -Should a `throws_ok()` test fail it produces appropriate diagnostic messages. -For example: +A failing `throws_ok()` test produces an appropriate diagnostic message. For +example: - # Failed test 81: "This should not die" + # Failed test 81: "This should die a glorious death" # caught: 23505: duplicate key value violates unique constraint "try_pkey" # wanted: 23502: null value in column "id" violates not-null constraint @@ -649,16 +688,16 @@ Idea borrowed from the Test::Exception Perl module. 'We should not get a unique violation for a new PK' ); -The inverse of `throws_ok()`, these functions test to ensure that an SQL -statement does *not* throw an exception. Supported by 8.1 or higher. Pass in -the name of a prepared statement or string of SQL code (see the +The inverse of `throws_ok()`, `lives_ok()` ensures that an SQL statement does +*not* throw an exception. Supported by 8.1 and up. Pass in the name of a +prepared statement or string of SQL code (see the [summary](#Submit+Your+Query) for query argument details). The optional second argument is the test description. -Should a `lives_ok()` test fail, it produces appropriate diagnostic messages. -For example: +A failing `lives_ok()` test produces an appropriate diagnostic message. For +example: - # Failed test 85: "simple success" + # Failed test 85: "don't die, little buddy!" # died: 23505: duplicate key value violates unique constraint "try_pkey" Idea borrowed from the Test::Exception Perl module. @@ -674,22 +713,22 @@ Idea borrowed from the Test::Exception Perl module. ); This function makes sure that an SQL statement performs well. It does so by -timing its execution, and failing if execution takes longer than the specified -amount of time. +timing its execution and failing if execution takes longer than the specified +number of milliseconds. The first argument should be the name of a prepared statement or a string representing the query to be executed (see the [summary](#Submit+Your+Query) for query argument details). `throws_ok()` will use the PL/pgSQL `EXECUTE` -statement to execute it and catch any exception. +statement to execute the query. The second argument is the maximum number of milliseconds it should take for the SQL statement to execute. This argument is numeric, so you can even use fractions of milliseconds if it floats your boat. The third argument is the usual description. If not provided, `performs_ok()` -will generate a placeholder description "Should run in less than $milliseconds -ms". You'll likely want to provide your own description if you have more than -a couple of these in a test script or function. +will generate the description "Should run in less than $milliseconds ms". +You'll likely want to provide your own description if you have more than a +couple of these in a test script or function. Should a `performs_ok()` test fail it produces appropriate diagnostic messages. For example: @@ -707,8 +746,8 @@ to ensure that a query is not *really* slow (think seconds). Can You Relate? --------------- -So you've got your basic scalar comparison funtions, what about relations? -Mabye you have some pretty hairy `SELECT` statements in views or functions to +So you've got your basic scalar comparison functions, what about relations? +Maybe you have some pretty hairy `SELECT` statements in views or functions to test? We've got your relation-testing functions right here. ### `results_eq( sql, sql, description )` ### @@ -730,11 +769,11 @@ test? We've got your relation-testing functions right here. SELECT results_eq( 'users_test', 'users_expect', 'We should have users' ); -There are three ways to test result sets in pgTAP. The most intuitive is to do -a direct row-by-row comparison of results to ensure that they are exactly what -you expect, in the order you expect. Coincidentally, this is exactly how -`results_eq()` behaves. Here's how you use it: simply pass in two SQL -statements or prepared statement names (or some combination; (see the +There are three ways to test result sets in pgTAP. Perhaps the most intuitive +is to do a direct row-by-row comparison of results to ensure that they are +exactly what you expect, in the order you expect. Coincidentally, this is +exactly how `results_eq()` behaves. Here's how you use it: simply pass in two +SQL statements or prepared statement names (or some combination; (see the [summary](#Submit+Your+Query) for query argument details) and an optional description. Yep, that's it. It will do the rest. @@ -748,8 +787,8 @@ expect, you might do something like this: 'active_users() should return active users' ); -Tip: If you want to hard-code the values to compare, use a `VALUES` statement -instead of a query, like so: +Tip: If you're using PostgreSQL 8.2 and up and want to hard-code the values to +compare, use a `VALUES` statement instead of a query, like so: SELECT results_eq( 'SELECT * FROM active_users()', @@ -766,9 +805,9 @@ second argument may be an array: ); In general, the use of prepared statements is highly recommended to keep your -test code SQLish (you can even use `VALUES` statements in prepared statements -in PostgreSQL 8.2 and up!). But note that, because `results_eq()` does a -row-by-row comparision, the results of the two query arguments must be in +test code SQLish (you can even use `VALUES` in prepared statements in +PostgreSQL 8.2 and up!). But note that, because `results_eq()` does a +row-by-row comparison, the results of the two query arguments must be in exactly the same order, with exactly the same data types, in order to pass. In practical terms, it means that you must make sure that your results are never unambiguously ordered. @@ -796,17 +835,16 @@ But a different run of the same query could have the rows in different order: Tom | 44 (4 rows) -Notice how the two "Tom" rows are reversed. The upshot is that you want to -make sure that your rows are always fully ordered. In a case like the above, -it means sorting on both the `name` column and the `age` column. If the sort -order of your results isn't important, consider using `set_eq()` or `bag_eq()` -instead. +Notice how the two "Tom" rows are reversed. The upshot is that you must ensure +that your queries are always fully ordered. In a case like the above, it means +sorting on both the `name` column and the `age` column. If the sort order of +your results isn't important, consider using `set_eq()` or `bag_eq()` instead. Internally, `results_eq()` turns your SQL statements into cursors so that it can iterate over them one row at a time. Conveniently, this behavior is directly available to you, too. Rather than pass in some arbitrary SQL -statement or the name of a prepared statement, simply create a cursor and just -pass *it* in, like so: +statement or the name of a prepared statement, simply create a cursor and pass +*it* in, like so: DECLARE cwant CURSOR FOR SELECT * FROM active_users(); DECLARE chave CURSOR FOR SELECT * FROM users WHERE active ORDER BY name; @@ -833,15 +871,15 @@ for the expected results: ); Regardless of which types of arguments you pass, in the event of a test -failure due to different results, `results_eq()` will offer a nice diagnostic -message to tell you at what row the results differ, something like: +failure, `results_eq()` will offer a nice diagnostic message to tell you at +what row the results differ, something like: # Failed test 146 # Results differ beginning at row 3: # have: (1,Anna) # want: (22,Betty) -If there are different numbers of rows in each result set, an non-existent row +If there are different numbers of rows in each result set, a non-existent row will be represented as "NULL": # Failed test 147 @@ -858,12 +896,13 @@ so: # have: (1) # want: (foo,1) -On PostgreSQL 8.3 and lower, the rows are cast to text for comparison, rather -than compared as `record` objects, so the test cannot detect incompatibilities -in column numbers or types, or differences in columns that convert to the same -text representation. For example, a `NULL` column will be equivalent to an -empty string. The upshot: read failure diagnostics carefully and pay attention -to data types on 8.3. +On PostgreSQL 8.3 and down, the rows are cast to text for comparison, rather +than compared as `record` objects. The downside to this necessity is that the +test cannot detect incompatibilities in column numbers or types, or +differences in columns that convert to the same text representation. For +example, a `NULL` column will be equivalent to an empty string. The upshot: +read failure diagnostics carefully and pay attention to data types on 8.3 and +down. ### `results_ne( sql, sql, description )` ### ### `results_ne( sql, sql )` ### @@ -890,9 +929,11 @@ have the same sets of results in the two query arguments and the test will pass if they're merely in a different order. More than likely what you really want is `results_eq()` or `set_ne()`. But this function is included for completeness and is kind of cute, so enjoy. If a `results_ne()` test fails, -however, there will be no diagnostics, becaus, well, the results will be the +however, there will be no diagnostics, because, well, the results will be the same! +Note that the caveats for `results_ne()` on PostgreSQL 8.3 and down apply to +`results_ne()` as well. ### `set_eq( sql, sql, description )` ### ### `set_eq( sql, sql )` ### @@ -954,10 +995,10 @@ This of course extends to sets with different numbers of columns: SELECT bag_eq( 'testq', 'expect', 'gotta have the A listers' ); The `bag_eq()` function is just like `set_eq()`, except that it considers the -results as bags rather than as sets. A bag is a set with duplicates. What this -means, effectively, is that you can use `bag_eq()` to test result sets where -order doesn't matter, but duplication does. In other words, if a two rows are -the same in the first result set, the same row must appear twice in the second +results as bags rather than as sets. A bag is a set that allows duplicates. In +practice, it mean that you can use `bag_eq()` to test result sets where order +doesn't matter, but duplication does. In other words, if a two rows are the +same in the first result set, the same row must appear twice in the second result set. Otherwise, this function behaves exactly like `set_eq()`, including the @@ -973,12 +1014,13 @@ utility of its diagnostics. SELECT set_ne( 'testq', 'expect', 'gotta have the A listers' ); The inverse of `set_eq()`, this function tests that the results of two queries -are *not* the same. The two queries, which The can of course be the names of -prepared statements or strings containing an SQL query (see the +are *not* the same. The two queries can as usual be the names of prepared +statements or strings containing an SQL query (see the [summary](#Submit+Your+Query) for query argument details), or even one of each. The two queries, however, must return results that are directly comparable -- that is, with the same number and types of columns in the same -orders. +orders. If it happens that the query you're testing returns a single column, +the second argument may be an array. ### `bag_ne( sql, sql, description )` ### ### `bag_ne( sql, sql )` ### @@ -990,12 +1032,13 @@ orders. SELECT bag_ne( 'testq', 'expect', 'gotta have the A listers' ); The inverse of `bag_eq()`, this function tests that the results of two queries -are *not* the same, including duplicates. The two queries, which The can of -course be the names of prepared statements or strings containing an SQL query -(see the [summary](#Submit+Your+Query) for query argument details), or even -one of each. The two queries, however, must return results that are directly +are *not* the same, including duplicates. The two queries can as usual be the +names of prepared statements or strings containing an SQL query (see the +[summary](#Submit+Your+Query) for query argument details), or even one of +each. The two queries, however, must return results that are directly comparable -- that is, with the same number and types of columns in the same -orders. +orders. If it happens that the query you're testing returns a single column, +the second argument may be an array. ### `set_has( sql, sql, description )` ### ### `set_has( sql, sql )` ### @@ -1014,16 +1057,18 @@ useful perhaps, but set-theoretically correct. As with `set_eq()`. the SQL arguments can be the names of prepared statements or strings containing an SQL query (see the [summary](#Submit+Your+Query) for -query argument details), or one of each. In whatever case, a failing test will -yield useful diagnostics just like: +query argument details), or one of each. If it happens that the query you're +testing returns a single column, the second argument may be an array. + +In whatever case, a failing test will yield useful diagnostics just like: # Failed test 122 # Missing records: # (44,Anna) # (86,Angelina) -As with `set_eq()`, it will also provide useful diagnostics when the queries -return incompatible columns. Internally, it uses an `EXCEPT` query to +As with `set_eq()`, `set_has()` will also provide useful diagnostics when the +queries return incompatible columns. Internally, it uses an `EXCEPT` query to determine if there any any unexpectedly missing results. ### `bag_has( sql, sql, description )` ### @@ -1035,9 +1080,9 @@ determine if there any any unexpectedly missing results. The `bag_has()` function is just like `set_has()`, except that it considers the results as bags rather than as sets. A bag is a set with duplicates. What -this means, effectively, is that you can use `bag_has()` to test result sets -where order doesn't matter, but duplication does. Internally, it uses an -`EXCEPT ALL` query to determine if there any any unexpectedly missing results. +practice this means that you can use `bag_has()` to test result sets where +order doesn't matter, but duplication does. Internally, it uses an `EXCEPT +ALL` query to determine if there any any unexpectedly missing results. ### `set_hasnt( sql, sql, description )` ### ### `set_hasnt( sql, sql )` ### @@ -2898,7 +2943,7 @@ But then you check with `has_function()` first, right? SELECT volatility_is( 'do_something', ARRAY['integer'], 'stable' ); SELECT volatility_is( 'do_something', ARRAY['numeric'], 'volatile' ); -Tests the volatility of a function. Supported volailities are "volatile", +Tests the volatility of a function. Supported volatilities are "volatile", "stable", and "immutable". Consult the [`CREATE FUNCTION` documentation A.](http://www.postgresql.org/docs/current/static/sql-createfunction.html) for details. The function name is required. If the `:schema` argument is omitted, @@ -3705,53 +3750,42 @@ instead: To see the specifics for each version of PostgreSQL, consult the files in the `compat/` directory in the pgTAP distribution. -8.4 and Higher --------------- - -The `pg_typeof()` function will not be built, as it is included in PostgreSQL -8.4. - -8.4 and Higher --------------- +8.4 and Up +---------- No changes. Everything should just work. -8.3 and Lower -------------- -A patch is applied to modify `results_eq()` to cast records to text before -comparing them. This means that things will mainly be correct, but it also -means that two queries with incopatible types that convert to the same text -string may be considered incorrectly equivalent. - - -8.2 and Lower -------------- - -A patch is applied that removes the `enum_has_labels()` function, and -`col_has_default()` cannot be used to test for columns specified with `DEFAULT -NULL` (even though that's the implied default default). Also, a number of -casts are added to increase compatibility. The casts are: - -* `boolean` to `text` -* `text[]` to `text` -* `name[]` to `text` -* `regtype` to `text` - -An `=` operator is also added that compares `name[]` values. +8.3 and Down +------------ +* A patch is applied to modify `results_eq()` to cast records to text before + comparing them. This means that things will mainly be correct, but it also + means that two queries with incompatible types that convert to the same text + string may be considered incorrectly equivalent. +* A C function, `pg_typeof()`, is built and installed in a DSO. This is for + compatibility with the same function that ships in 8.4 core, and is required + for `cmp_ok()` to work. + +8.2 and Down +------------ +* A patch is applied that removes the `enum_has_labels()` function, and + `col_has_default()` cannot be used to test for columns specified with + `DEFAULT NULL` (even though that's the implied default default). Also, a + number of assignments casts are added to increase compatibility. The casts + are: + + `boolean` to `text` + + `text[]` to `text` + + `name[]` to `text` + + `regtype` to `text` +* Two operators, `=` and `<>`, are added to compare `name[]` values. 8.0 and Lower ------------- - -A patch is applied that changes how some of the test functions are written and -removes the `runtests()` function. Also, a few casts are added for -compatibility: - -* `oidvector` to `regtypep[]`. -* `int2vector` to `integer[]`. - -Otherwise, all is the same as for 8.2 Do note, however, that the -`throws_ok()`, `lives_ok()`, `runtests()`, `has_role()`, and `hasnt_role()` -functions do not work under 8.0. Don't even use them there. +* A patch is applied that changes how some of the test functions are written. +* A few casts are added for compatibility: + + `oidvector` to `regtypep[]`. + + `int2vector` to `integer[]`. +* The `throws_ok()`, `lives_ok()`, `runtests()`, `has_role()`, and + `hasnt_role()` functions do not work under 8.0. Don't even use them there. To Do ----- From ba0d877e61a71806cf7bcbb2ea319e4d3d40454b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 30 Jul 2009 17:10:22 -0700 Subject: [PATCH 0431/1195] Reorganized bag and set function documentation. As I was writing a blog entry for 0.22, I realized that it made more sense to keep the set and bag functions grouped spearately: sets with sets and bags with bags. I also updated the link to the summary of result set function arguments to the new name of the section. --- README.pgtap | 124 +++++++++++++++++++++++++-------------------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/README.pgtap b/README.pgtap index c431a55c5d43..2148a107deeb 100644 --- a/README.pgtap +++ b/README.pgtap @@ -644,8 +644,8 @@ When you want to make sure that an exception is thrown by PostgreSQL, use `throws_ok()` to test for it. Supported by 8.1 and up. The first argument should be the name of a prepared statement or else a string -representing the query to be executed (see the [summary](#Submit+Your+Query) for -query argument details). `throws_ok()` will use the PL/pgSQL `EXECUTE` +representing the query to be executed (see the [summary](#Pursuing+Your+Query) +for query argument details). `throws_ok()` will use the PL/pgSQL `EXECUTE` statement to execute the query and catch any exception. The second argument should be an exception error code, which is a @@ -691,8 +691,8 @@ Idea borrowed from the Test::Exception Perl module. The inverse of `throws_ok()`, `lives_ok()` ensures that an SQL statement does *not* throw an exception. Supported by 8.1 and up. Pass in the name of a prepared statement or string of SQL code (see the -[summary](#Submit+Your+Query) for query argument details). The optional second -argument is the test description. +[summary](#Pursuing+Your+Query) for query argument details). The optional +second argument is the test description. A failing `lives_ok()` test produces an appropriate diagnostic message. For example: @@ -717,8 +717,8 @@ timing its execution and failing if execution takes longer than the specified number of milliseconds. The first argument should be the name of a prepared statement or a string -representing the query to be executed (see the [summary](#Submit+Your+Query) for -query argument details). `throws_ok()` will use the PL/pgSQL `EXECUTE` +representing the query to be executed (see the [summary](#Pursuing+Your+Query) +for query argument details). `throws_ok()` will use the PL/pgSQL `EXECUTE` statement to execute the query. The second argument is the maximum number of milliseconds it should take for @@ -774,7 +774,7 @@ is to do a direct row-by-row comparison of results to ensure that they are exactly what you expect, in the order you expect. Coincidentally, this is exactly how `results_eq()` behaves. Here's how you use it: simply pass in two SQL statements or prepared statement names (or some combination; (see the -[summary](#Submit+Your+Query) for query argument details) and an optional +[summary](#Pursuing+Your+Query) for query argument details) and an optional description. Yep, that's it. It will do the rest. For example, say that you have a function, `active_users()`, that returns a @@ -950,7 +950,7 @@ your result sets. As long as both queries return the same records, regardless of duplicates or ordering, a `set_eq()` test will pass. The SQL arguments can be the names of prepared statements or strings -containing an SQL query (see the [summary](#Submit+Your+Query) for query +containing an SQL query (see the [summary](#Pursuing+Your+Query) for query argument details), or even one of each. If the results returned by the first argument consist of a single column, the second argument may be an array: @@ -985,25 +985,6 @@ This of course extends to sets with different numbers of columns: # have: (integer) # want: (text,integer) -### `bag_eq( sql, sql, description )` ### -### `bag_eq( sql, sql )` ### -### `bag_eq( sql, array, description )` ### -### `bag_eq( sql, array )` ### - - PREPARE testq AS SELECT * FROM users('a%'); - PREPARE expect AS SELECT * FROM USERS where name LIKE 'a%'; - SELECT bag_eq( 'testq', 'expect', 'gotta have the A listers' ); - -The `bag_eq()` function is just like `set_eq()`, except that it considers the -results as bags rather than as sets. A bag is a set that allows duplicates. In -practice, it mean that you can use `bag_eq()` to test result sets where order -doesn't matter, but duplication does. In other words, if a two rows are the -same in the first result set, the same row must appear twice in the second -result set. - -Otherwise, this function behaves exactly like `set_eq()`, including the -utility of its diagnostics. - ### `set_ne( sql, sql, description )` ### ### `set_ne( sql, sql )` ### ### `set_ne( sql, array, description )` ### @@ -1016,25 +997,7 @@ utility of its diagnostics. The inverse of `set_eq()`, this function tests that the results of two queries are *not* the same. The two queries can as usual be the names of prepared statements or strings containing an SQL query (see the -[summary](#Submit+Your+Query) for query argument details), or even one of -each. The two queries, however, must return results that are directly -comparable -- that is, with the same number and types of columns in the same -orders. If it happens that the query you're testing returns a single column, -the second argument may be an array. - -### `bag_ne( sql, sql, description )` ### -### `bag_ne( sql, sql )` ### -### `bag_ne( sql, array, description )` ### -### `bag_ne( sql, array )` ### - - PREPARE testq AS SELECT * FROM users('a%'); - PREPARE expect AS SELECT * FROM USERS where name LIKE 'b%'; - SELECT bag_ne( 'testq', 'expect', 'gotta have the A listers' ); - -The inverse of `bag_eq()`, this function tests that the results of two queries -are *not* the same, including duplicates. The two queries can as usual be the -names of prepared statements or strings containing an SQL query (see the -[summary](#Submit+Your+Query) for query argument details), or even one of +[summary](#Pursuing+Your+Query) for query argument details), or even one of each. The two queries, however, must return results that are directly comparable -- that is, with the same number and types of columns in the same orders. If it happens that the query you're testing returns a single column, @@ -1056,9 +1019,9 @@ case the test will pass no matter what the first query returns. Not very useful perhaps, but set-theoretically correct. As with `set_eq()`. the SQL arguments can be the names of prepared statements -or strings containing an SQL query (see the [summary](#Submit+Your+Query) for -query argument details), or one of each. If it happens that the query you're -testing returns a single column, the second argument may be an array. +or strings containing an SQL query (see the [summary](#Pursuing+Your+Query) +for query argument details), or one of each. If it happens that the query +you're testing returns a single column, the second argument may be an array. In whatever case, a failing test will yield useful diagnostics just like: @@ -1071,19 +1034,6 @@ As with `set_eq()`, `set_has()` will also provide useful diagnostics when the queries return incompatible columns. Internally, it uses an `EXCEPT` query to determine if there any any unexpectedly missing results. -### `bag_has( sql, sql, description )` ### -### `bag_has( sql, sql )` ### - - PREPARE testq AS SELECT * FROM users('a%'); - PREPARE subset AS SELECT * FROM USERS where name LIKE 'a%'; - SELECT bag_has( 'testq', 'subset', 'gotta have at least the A listers' ); - -The `bag_has()` function is just like `set_has()`, except that it considers -the results as bags rather than as sets. A bag is a set with duplicates. What -practice this means that you can use `bag_has()` to test result sets where -order doesn't matter, but duplication does. Internally, it uses an `EXCEPT -ALL` query to determine if there any any unexpectedly missing results. - ### `set_hasnt( sql, sql, description )` ### ### `set_hasnt( sql, sql )` ### @@ -1103,6 +1053,56 @@ Diagnostics are similarly useful: Internally, the function uses an `INTERSECT` query to determine if there is any unexpected overlap between the query results. +### `bag_eq( sql, sql, description )` ### +### `bag_eq( sql, sql )` ### +### `bag_eq( sql, array, description )` ### +### `bag_eq( sql, array )` ### + + PREPARE testq AS SELECT * FROM users('a%'); + PREPARE expect AS SELECT * FROM USERS where name LIKE 'a%'; + SELECT bag_eq( 'testq', 'expect', 'gotta have the A listers' ); + +The `bag_eq()` function is just like `set_eq()`, except that it considers the +results as bags rather than as sets. A bag is a set that allows duplicates. In +practice, it mean that you can use `bag_eq()` to test result sets where order +doesn't matter, but duplication does. In other words, if a two rows are the +same in the first result set, the same row must appear twice in the second +result set. + +Otherwise, this function behaves exactly like `set_eq()`, including the +utility of its diagnostics. + +### `bag_ne( sql, sql, description )` ### +### `bag_ne( sql, sql )` ### +### `bag_ne( sql, array, description )` ### +### `bag_ne( sql, array )` ### + + PREPARE testq AS SELECT * FROM users('a%'); + PREPARE expect AS SELECT * FROM USERS where name LIKE 'b%'; + SELECT bag_ne( 'testq', 'expect', 'gotta have the A listers' ); + +The inverse of `bag_eq()`, this function tests that the results of two queries +are *not* the same, including duplicates. The two queries can as usual be the +names of prepared statements or strings containing an SQL query (see the +[summary](#Pursuing+Your+Query) for query argument details), or even one of +each. The two queries, however, must return results that are directly +comparable -- that is, with the same number and types of columns in the same +orders. If it happens that the query you're testing returns a single column, +the second argument may be an array. + +### `bag_has( sql, sql, description )` ### +### `bag_has( sql, sql )` ### + + PREPARE testq AS SELECT * FROM users('a%'); + PREPARE subset AS SELECT * FROM USERS where name LIKE 'a%'; + SELECT bag_has( 'testq', 'subset', 'gotta have at least the A listers' ); + +The `bag_has()` function is just like `set_has()`, except that it considers +the results as bags rather than as sets. A bag is a set with duplicates. What +practice this means that you can use `bag_has()` to test result sets where +order doesn't matter, but duplication does. Internally, it uses an `EXCEPT +ALL` query to determine if there any any unexpectedly missing results. + ### `bag_hasnt( sql, sql, description )` ### ### `bag_hasnt( sql, sql )` ### From 7db9b10111d19be324fa384ac96ca3cccc257598 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 30 Jul 2009 17:25:36 -0700 Subject: [PATCH 0432/1195] Timestamped for 0.22 release. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 499fee9c5f12..85174836e5c6 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.22 +0.22 2009-07-31T00:26:16 ------------------------- * Fixed failing test on 8.4rc2. * Added result set testing functions. These allow testers to write queries in From bcb71f40b076e01fe1009a6fc254e079c582463b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 30 Jul 2009 17:39:26 -0700 Subject: [PATCH 0433/1195] Incremented version number to 0.22. --- Changes | 4 ++++ Makefile | 2 +- README.pgtap | 2 +- bin/pg_prove | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 85174836e5c6..e3c9b4aa4f2b 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,10 @@ Revision history for pgTAP ========================== +0.23 +------------------------- + + 0.22 2009-07-31T00:26:16 ------------------------- * Fixed failing test on 8.4rc2. diff --git a/Makefile b/Makefile index 2c060a46f02a..dc42adcb5a41 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') PGVER_MAJOR = $(shell echo $(VERSION) | awk -F. '{ print ($$1 + 0) }') PGVER_MINOR = $(shell echo $(VERSION) | awk -F. '{ print ($$2 + 0) }') PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') -PGTAP_VERSION = 0.22 +PGTAP_VERSION = 0.23 # Compile the C code only if we're on 8.3 or older. ifneq ($(PGVER_MINOR), 4) diff --git a/README.pgtap b/README.pgtap index 2148a107deeb..ad3bc224f271 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.22 +pgTAP 0.23 ========== pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/bin/pg_prove b/bin/pg_prove index 71a2c189a16d..627e09136098 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -4,7 +4,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.22'; +our $VERSION = '0.23'; Getopt::Long::Configure (qw(bundling)); From 92301da01d70a650054bddb16d4bc0d722412409 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 30 Jul 2009 17:40:35 -0700 Subject: [PATCH 0434/1195] Output pg_prove docs to pg_prove.pod.html --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index dc42adcb5a41..d6b355b48d0d 100644 --- a/Makefile +++ b/Makefile @@ -222,4 +222,4 @@ test: test_setup.sql bbin/pg_prove html: markdown -F 0x1000 README.pgtap > readme.html perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$4; $$lab = $$4; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$4 ? "$$4()" : $$3) . "" } END { print "
  • \n
\n" }' readme.html > toc.html - PERL5LIB=/Users/david/dev/perl/Pod-Simple/lib perldoc -MPod::Simple::XHTML -d pg_prove.html -w html_header_tags:'' bin/pg_prove + PERL5LIB=/Users/david/dev/perl/Pod-Simple/lib perldoc -MPod::Simple::XHTML -d pg_prove.pod.html -w html_header_tags:'' bin/pg_prove From a8ac65df971c474b5edce86f29120437bf4a881e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 31 Jul 2009 15:11:12 -0700 Subject: [PATCH 0435/1195] Fixed broken Perl detection and Cygwin detection --- Changes | 2 ++ Makefile | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index e3c9b4aa4f2b..48f3b31a7033 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,8 @@ Revision history for pgTAP 0.23 ------------------------- +* Fixed broken Perl detection in `Makefile`. +* Fixed Cygwin detection. 0.22 2009-07-31T00:26:16 diff --git a/Makefile b/Makefile index d6b355b48d0d..ae1bb1b9f67d 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ endif # We need Perl. ifndef PERL -PERL := $(shell which foo) +PERL := $(shell which perl) endif # Is TAP::Harness installed? @@ -126,6 +126,12 @@ endif # Fallback on uname, if it's available. ifndef OSNAME OSNAME = $(shell uname | awk '{print tolower($$1)}') + +# Clean up the OS name. +ifeq ($(findstring cygwin,$(OSNAME)), cygwin) +OSNAME=cygwin +endif + endif # Override how .sql targets are processed to add the schema info, if From b1c9bd915b9ed817d93bfac2d44af366f7567d1e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 2 Aug 2009 15:03:41 -0700 Subject: [PATCH 0436/1195] Copied comlete OS detection from Perl. Copied OS detection from Perl's `Configure` script to new script, `getos.sh`. OS detection should now be much more accurate. --- Changes | 3 +- Makefile | 57 +------- getos.sh | 392 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 395 insertions(+), 57 deletions(-) create mode 100755 getos.sh diff --git a/Changes b/Changes index 48f3b31a7033..0c9b2cecf016 100644 --- a/Changes +++ b/Changes @@ -4,7 +4,8 @@ Revision history for pgTAP 0.23 ------------------------- * Fixed broken Perl detection in `Makefile`. -* Fixed Cygwin detection. +* Copied OS detection from Perl's `Configure` script to new script, + `getos.sh`. OS detection should now be much more accurate. 0.22 2009-07-31T00:26:16 diff --git a/Makefile b/Makefile index ae1bb1b9f67d..47fa41100127 100644 --- a/Makefile +++ b/Makefile @@ -77,62 +77,7 @@ endif endif # Determine the OS. Borrowed from Perl's Configure. -ifeq ($(wildcard /irix), /irix) -OSNAME=irix -endif -ifeq ($(wildcard /xynix), /xynix) -OSNAME=sco_xenix -endif -ifeq ($(wildcard /dynix), /dynix) -OSNAME=dynix -endif -ifeq ($(wildcard /dnix), /dnix) -OSNAME=dnxi -endif -ifeq ($(wildcard /lynx.os), /lynx.os) -OSNAME=lynxos -endif -ifeq ($(wildcard /unicos), /unicox) -OSNAME=unicos -endif -ifeq ($(wildcard /unicosmk), /unicosmk) -OSNAME=unicosmk -endif -ifeq ($(wildcard /unicosmk.ar), /unicosmk.ar) -OSNAME=unicosmk -endif -ifeq ($(wildcard /bin/mips), /bin/mips) -OSNAME=mips -endif -ifeq ($(wildcard /usr/apollo/bin), /usr/apollo/bin) -OSNAME=apollo -endif -ifeq ($(wildcard /etc/saf/_sactab), /etc/saf/_sactab) -OSNAME=svr4 -endif -ifeq ($(wildcard /usr/include/minix), /usr/include/minix) -OSNAME=minix -endif -ifeq ($(wildcard /system/gnu_library/bin/ar.pm), /system/gnu_library/bin/ar.pm) -OSNAME=vos -endif -ifeq ($(wildcard /MachTen), /MachTen) -OSNAME=machten -endif -ifeq ($(wildcard /sys/posix.dll), /sys/posix.dll) -OSNAME=uwin -endif - -# Fallback on uname, if it's available. -ifndef OSNAME -OSNAME = $(shell uname | awk '{print tolower($$1)}') - -# Clean up the OS name. -ifeq ($(findstring cygwin,$(OSNAME)), cygwin) -OSNAME=cygwin -endif - -endif +OSNAME := $(shell ./getos.sh) # Override how .sql targets are processed to add the schema info, if # necessary. Otherwise just copy the files. diff --git a/getos.sh b/getos.sh new file mode 100755 index 000000000000..67e25d5b3e64 --- /dev/null +++ b/getos.sh @@ -0,0 +1,392 @@ +#!/bin/sh + +uname=`which uname` +sed=`which sed` +tr=`which tr` +myuname='' +newmyuname='' +trnl='' + +case "$test" in +test) + echo "Hopefully test is built into your sh." + ;; +*) + if `sh -c "PATH= test true" >/dev/null 2>&1`; then +# echo "Using the test built into your sh." + test=test + _test=test + fi + ;; +esac + +: Find the appropriate value for a newline for tr +if test -n "$DJGPP"; then + trnl='\012' +fi +if test X"$trnl" = X; then + case "`echo foo|tr '\n' x 2>/dev/null`" in + foox) trnl='\n' ;; + esac +fi +if test X"$trnl" = X; then + case "`echo foo|tr '\012' x 2>/dev/null`" in + foox) trnl='\012' ;; + esac +fi +if test X"$trnl" = X; then + case "`echo foo|tr '\r\n' xy 2>/dev/null`" in + fooxy) trnl='\n\r' ;; + esac +fi +if test X"$trnl" = X; then + cat <&2 + +$me: Fatal Error: cannot figure out how to translate newlines with 'tr'. + +EOM + exit 1 +fi + +myuname=`$uname -a 2>/dev/null` +$test -z "$myuname" && myuname=`hostname 2>/dev/null` +# tr '[A-Z]' '[a-z]' would not work in EBCDIC +# because the A-Z/a-z are not consecutive. +myuname=`echo $myuname | $sed -e 's/^[^=]*=//' -e "s,['/],,g" | \ + $tr '[A-Z]' '[a-z]' | $tr $trnl ' '` +newmyuname="$myuname" + +: Half the following guesses are probably wrong... If you have better +: tests or hints, please send them to perlbug@perl.org +: The metaconfig authors would also appreciate a copy... +$test -f /irix && osname=irix +$test -f /xenix && osname=sco_xenix +$test -f /dynix && osname=dynix +$test -f /dnix && osname=dnix +$test -f /lynx.os && osname=lynxos +$test -f /unicos && osname=unicos && osvers=`$uname -r` +$test -f /unicosmk && osname=unicosmk && osvers=`$uname -r` +$test -f /unicosmk.ar && osname=unicosmk && osvers=`$uname -r` +$test -f /bin/mips && /bin/mips && osname=mips +$test -d /NextApps && set X `hostinfo | grep 'NeXT Mach.*:' | \ + $sed -e 's/://' -e 's/\./_/'` && osname=next && osvers=$4 +$test -d /usr/apollo/bin && osname=apollo +$test -f /etc/saf/_sactab && osname=svr4 +$test -d /usr/include/minix && osname=minix +$test -f /system/gnu_library/bin/ar.pm && osname=vos +if $test -d /MachTen -o -d /MachTen_Folder; then + osname=machten + if $test -x /sbin/version; then + osvers=`/sbin/version | $awk '{print $2}' | + $sed -e 's/[A-Za-z]$//'` + elif $test -x /usr/etc/version; then + osvers=`/usr/etc/version | $awk '{print $2}' | + $sed -e 's/[A-Za-z]$//'` + else + osvers="$2.$3" + fi +fi + +$test -f /sys/posix.dll && + $test -f /usr/bin/what && + set X `/usr/bin/what /sys/posix.dll` && + $test "$3" = UWIN && + osname=uwin && + osvers="$5" + +if $test -f $uname; then + set X $myuname + shift + + case "$5" in + fps*) osname=fps ;; + mips*) + case "$4" in + umips) osname=umips ;; + *) osname=mips ;; + esac;; + [23]100) osname=mips ;; + next*) osname=next ;; + i386*) + tmp=`/bin/uname -X 2>/dev/null|awk '/3\.2v[45]/{ print $(NF) }'` + if $test "$tmp" != "" -a "$3" = "3.2" -a -f '/etc/systemid'; then + osname='sco' + osvers=$tmp + elif $test -f /etc/kconfig; then + osname=isc + if test "$lns" = "$ln -s"; then + osvers=4 + elif $contains _SYSV3 /usr/include/stdio.h > /dev/null 2>&1 ; then + osvers=3 + elif $contains _POSIX_SOURCE /usr/include/stdio.h > /dev/null 2>&1 ; then + osvers=2 + fi + fi + tmp='' + ;; + pc*) + if test -n "$DJGPP"; then + osname=dos + osvers=djgpp + fi + ;; + esac + + case "$1" in + aix) osname=aix + tmp=`( (oslevel) 2>/dev/null || echo "not found") 2>&1` + case "$tmp" in + # oslevel can fail with: + # oslevel: Unable to acquire lock. + *not\ found) osvers="$4"."$3" ;; + '<3240'|'<>3240') osvers=3.2.0 ;; + '=3240'|'>3240'|'<3250'|'<>3250') osvers=3.2.4 ;; + '=3250'|'>3250') osvers=3.2.5 ;; + *) osvers=$tmp;; + esac + ;; + bsd386) osname=bsd386 + osvers=`$uname -r` + ;; + cygwin*) osname=cygwin + osvers="$3" + ;; + *dc.osx) osname=dcosx + osvers="$3" + ;; + dnix) osname=dnix + osvers="$3" + ;; + domainos) osname=apollo + osvers="$3" + ;; + dgux) osname=dgux + osvers="$3" + ;; + dragonfly) osname=dragonfly + osvers="$3" + ;; + dynixptx*) osname=dynixptx + osvers=`echo "$4"|sed 's/^v//'` + ;; + freebsd) osname=freebsd + osvers="$3" ;; + genix) osname=genix ;; + gnu) osname=gnu + osvers="$3" ;; + hp*) osname=hpux + osvers=`echo "$3" | $sed 's,.*\.\([0-9]*\.[0-9]*\),\1,'` + ;; + irix*) osname=irix + case "$3" in + 4*) osvers=4 ;; + 5*) osvers=5 ;; + *) osvers="$3" ;; + esac + ;; + linux) osname=linux + case "$3" in + *) osvers="$3" ;; + esac + ;; + MiNT) osname=mint + ;; + netbsd*) osname=netbsd + osvers="$3" + ;; + news-os) osvers="$3" + case "$3" in + 4*) osname=newsos4 ;; + *) osname=newsos ;; + esac + ;; + next*) osname=next ;; + nonstop-ux) osname=nonstopux ;; + openbsd) osname=openbsd + osvers="$3" + ;; + os2) osname=os2 + osvers="$4" + ;; + POSIX-BC | posix-bc ) osname=posix-bc + osvers="$3" + ;; + powerux | power_ux | powermax_os | powermaxos | \ + powerunix | power_unix) osname=powerux + osvers="$3" + ;; + qnx) osname=qnx + osvers="$4" + ;; + solaris) osname=solaris + case "$3" in + 5*) osvers=`echo $3 | $sed 's/^5/2/g'` ;; + *) osvers="$3" ;; + esac + ;; + sunos) osname=sunos + case "$3" in + 5*) osname=solaris + osvers=`echo $3 | $sed 's/^5/2/g'` ;; + *) osvers="$3" ;; + esac + ;; + titanos) osname=titanos + case "$3" in + 1*) osvers=1 ;; + 2*) osvers=2 ;; + 3*) osvers=3 ;; + 4*) osvers=4 ;; + *) osvers="$3" ;; + esac + ;; + ultrix) osname=ultrix + osvers="$3" + ;; + osf1|mls+) case "$5" in + alpha) + osname=dec_osf + osvers=`sizer -v | awk -FUNIX '{print $2}' | awk '{print $1}' | tr '[A-Z]' '[a-z]' | sed 's/^[xvt]//'` + case "$osvers" in + [1-9].[0-9]*) ;; + *) osvers=`echo "$3" | sed 's/^[xvt]//'` ;; + esac + ;; + hp*) osname=hp_osf1 ;; + mips) osname=mips_osf1 ;; + esac + ;; + # UnixWare 7.1.2 is known as Open UNIX 8 + openunix|unixware) osname=svr5 + osvers="$4" + ;; + uts) osname=uts + osvers="$3" + ;; + vos) osvers="$3" + ;; + $2) case "$osname" in + *isc*) ;; + *freebsd*) ;; + svr*) + : svr4.x or possibly later + case "svr$3" in + ${osname}*) + osname=svr$3 + osvers=$4 + ;; + esac + case "$osname" in + svr4.0) + : Check for ESIX + if test -f /stand/boot ; then + eval `grep '^INITPROG=[a-z/0-9]*$' /stand/boot` + if test -n "$INITPROG" -a -f "$INITPROG"; then + isesix=`strings -a $INITPROG|grep 'ESIX SYSTEM V/386 Release 4.0'` + if test -n "$isesix"; then + osname=esix4 + fi + fi + fi + ;; + esac + ;; + *) if test -f /etc/systemid; then + osname=sco + set `echo $3 | $sed 's/\./ /g'` $4 + if $test -f $src/hints/sco_$1_$2_$3.sh; then + osvers=$1.$2.$3 + elif $test -f $src/hints/sco_$1_$2.sh; then + osvers=$1.$2 + elif $test -f $src/hints/sco_$1.sh; then + osvers=$1 + fi + else + case "$osname" in + '') : Still unknown. Probably a generic Sys V. + osname="sysv" + osvers="$3" + ;; + esac + fi + ;; + esac + ;; + *) case "$osname" in + '') : Still unknown. Probably a generic BSD. + osname="$1" + osvers="$3" + ;; + esac + ;; + esac +else + if test -f /vmunix -a -f $src/hints/news_os.sh; then + (what /vmunix | UU/tr '[A-Z]' '[a-z]') > UU/kernel.what 2>&1 + if $contains news-os UU/kernel.what >/dev/null 2>&1; then + osname=news_os + fi + $rm -f UU/kernel.what + elif test -d c:/. -o -n "$is_os2" ; then + set X $myuname + osname=os2 + osvers="$5" + fi +fi + + case "$targetarch" in + '') ;; + *) hostarch=$osname + osname=`echo $targetarch|sed 's,^[^-]*-,,'` + osvers='' + ;; + esac + +: Now look for a hint file osname_osvers, unless one has been +: specified already. +case "$hintfile" in +''|' ') + file=`echo "${osname}_${osvers}" | $sed -e 's%\.%_%g' -e 's%_$%%'` + : Also try without trailing minor version numbers. + xfile=`echo $file | $sed -e 's%_[^_]*$%%'` + xxfile=`echo $xfile | $sed -e 's%_[^_]*$%%'` + xxxfile=`echo $xxfile | $sed -e 's%_[^_]*$%%'` + xxxxfile=`echo $xxxfile | $sed -e 's%_[^_]*$%%'` + case "$file" in + '') dflt=none ;; + *) case "$osvers" in + '') dflt=$file + ;; + *) if $test -f $src/hints/$file.sh ; then + dflt=$file + elif $test -f $src/hints/$xfile.sh ; then + dflt=$xfile + elif $test -f $src/hints/$xxfile.sh ; then + dflt=$xxfile + elif $test -f $src/hints/$xxxfile.sh ; then + dflt=$xxxfile + elif $test -f $src/hints/$xxxxfile.sh ; then + dflt=$xxxxfile + elif $test -f "$src/hints/${osname}.sh" ; then + dflt="${osname}" + else + dflt=none + fi + ;; + esac + ;; + esac + if $test -f Policy.sh ; then + case "$dflt" in + *Policy*) ;; + none) dflt="Policy" ;; + *) dflt="Policy $dflt" ;; + esac + fi + ;; +*) + dflt=`echo $hintfile | $sed 's/\.sh$//'` + ;; +esac + +echo $osname From 044831ffa33c5fb56c2a4ab267e57b25b9a48372 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 2 Aug 2009 15:10:41 -0700 Subject: [PATCH 0437/1195] Space. The final frontier. --- Changes | 1 - 1 file changed, 1 deletion(-) diff --git a/Changes b/Changes index 0c9b2cecf016..4d8c3e024979 100644 --- a/Changes +++ b/Changes @@ -7,7 +7,6 @@ Revision history for pgTAP * Copied OS detection from Perl's `Configure` script to new script, `getos.sh`. OS detection should now be much more accurate. - 0.22 2009-07-31T00:26:16 ------------------------- * Fixed failing test on 8.4rc2. From a32b6a4819d9ef67772f28e6e3697f46bbc230e2 Mon Sep 17 00:00:00 2001 From: Darrell Fuhriman Date: Fri, 21 Aug 2009 09:56:44 -0700 Subject: [PATCH 0438/1195] exit with non-zero status if tests failed. (works better in automated testing environments and consistent with "prove" command) --- bin/pg_prove | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bin/pg_prove b/bin/pg_prove index 627e09136098..1916a968f976 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -116,7 +116,7 @@ if ( $opts->{archive} ) { my $verbosity = $opts->{verbose} || $ENV{TEST_VERBOSE}; # Make it so! -$harness_class->new({ +my $return = $harness_class->new({ verbosity => $verbosity, failures => !$verbosity, ( TAP::Harness->VERSION ge '3.17' ? (comments => !$verbosity) : ()), @@ -127,6 +127,13 @@ $harness_class->new({ exec => \@command, })->runtests( @{ $tests } ); +if ( $return->{'failed'} > 0 ) { + exit 1; +} else { + exit 0; +} + + __END__ =encoding utf8 From 5fd9a54c3469abb880373a82856209cb02cf3334 Mon Sep 17 00:00:00 2001 From: Darrell Fuhriman Date: Fri, 21 Aug 2009 10:31:04 -0700 Subject: [PATCH 0439/1195] RPM spec file --- contrib/pgtap.spec | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 contrib/pgtap.spec diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec new file mode 100644 index 000000000000..bc75c1554bed --- /dev/null +++ b/contrib/pgtap.spec @@ -0,0 +1,42 @@ +Summary: pgtap is a unit testing suite for PostgreSQL +Name: pgtap +Version: 0.22 +Release: 3.%{?dist} +Group: Applications/Databases +License: Free use, with credit +URL: http://pgtap.projects.postgresql.org +Source0: http://pgfoundry.org/frs/download.php/2316/pgtap-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root +BuildRequires: postgresql-devel +Requires: postgresql-server, perl-Test-Harness >= 3.0 + +%description +pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and +PL/SQL. It includes a comprehensive collection of TAP-emitting assertion +functions, as well as the ability to integrate with other TAP-emitting +test frameworks. It can also be used in the xUnit testing style. + +%prep +%setup -q + + +%build +make USE_PGXS=1 TAPSCHEMA=pgtap + +%install +make install USE_PGXS=1 DESTDIR=%{buildroot} + +%clean +%{__rm} -rf %{buildroot} + + +%files +%defattr(-,root,root,-) +%{_bindir}/pg_prove +%{_libdir}/pgsql/pgtap.so +%{_datadir}/pgsql/contrib/* +%{_docdir}/pgsql/contrib/README.pgtap + +%changelog +* Wed Aug 19 2009 Darrell Fuhriman 0.22-1 +- initial RPM From 74b96e2137fcd930819c8905e36aa96042474b30 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 21 Aug 2009 10:32:57 -0700 Subject: [PATCH 0440/1195] Tweak style of non-zero exit and document in Changes --- Changes | 3 +++ bin/pg_prove | 8 ++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index 4d8c3e024979..f70b058489e2 100644 --- a/Changes +++ b/Changes @@ -6,6 +6,9 @@ Revision history for pgTAP * Fixed broken Perl detection in `Makefile`. * Copied OS detection from Perl's `Configure` script to new script, `getos.sh`. OS detection should now be much more accurate. +* Fixed `pg_prove` to exit with non-zero status if tests failed, as it works + better in automated testing environments and consistent with `prove` (fix by + darrell). 0.22 2009-07-31T00:26:16 ------------------------- diff --git a/bin/pg_prove b/bin/pg_prove index 1916a968f976..47a31daded8f 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -127,12 +127,8 @@ my $return = $harness_class->new({ exec => \@command, })->runtests( @{ $tests } ); -if ( $return->{'failed'} > 0 ) { - exit 1; -} else { - exit 0; -} - +# Exit with non-zero status if tests failed. +exit 1 if $return->{failed}; __END__ From 200021fe6e37897b88e75bedeb5967ba43396e0e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 21 Aug 2009 21:54:52 -0700 Subject: [PATCH 0441/1195] Typo. --- README.pgtap | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.pgtap b/README.pgtap index ad3bc224f271..e2785dca1908 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2944,14 +2944,14 @@ But then you check with `has_function()` first, right? SELECT volatility_is( 'do_something', ARRAY['numeric'], 'volatile' ); Tests the volatility of a function. Supported volatilities are "volatile", -"stable", and "immutable". Consult the [`CREATE FUNCTION` documentation -A.](http://www.postgresql.org/docs/current/static/sql-createfunction.html) for -details. The function name is required. If the `:schema` argument is omitted, -then the function must be visible in the search path. If the `:args[]` -argument is passed, then the function with that argument signature will be the -one tested; otherwise, a function with any signature will be checked (pass an -empty array to specify a function with an empty signature). If the -`:description` is omitted, a reasonable substitute will be created. +"stable", and "immutable". Consult the [`CREATE FUNCTION` +documentation](http://www.postgresql.org/docs/current/static/sql-createfunction.html) +for details. The function name is required. If the `:schema` argument is +omitted, then the function must be visible in the search path. If the +`:args[]` argument is passed, then the function with that argument signature +will be the one tested; otherwise, a function with any signature will be +checked (pass an empty array to specify a function with an empty signature). +If the `:description` is omitted, a reasonable substitute will be created. In the event of a failure, you'll useful diagnostics will tell you what went wrong, for example: From bb4a56469d5f625516962ba5f9cd987b946a3f1e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 24 Aug 2009 11:51:28 -0700 Subject: [PATCH 0442/1195] Give credit where credit is due --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index f70b058489e2..0f95d9b75e7d 100644 --- a/Changes +++ b/Changes @@ -8,7 +8,7 @@ Revision history for pgTAP `getos.sh`. OS detection should now be much more accurate. * Fixed `pg_prove` to exit with non-zero status if tests failed, as it works better in automated testing environments and consistent with `prove` (fix by - darrell). + Darrell Fuhriman). 0.22 2009-07-31T00:26:16 ------------------------- From 76f4410720c13815f2db836c33473d740511e895 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 24 Aug 2009 13:25:30 -0700 Subject: [PATCH 0443/1195] Add a TODO item --- README.pgtap | 1 + 1 file changed, 1 insertion(+) diff --git a/README.pgtap b/README.pgtap index e2785dca1908..7402402d11f6 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3789,6 +3789,7 @@ No changes. Everything should just work. To Do ----- +* Have `has_function()` manage OUT, INOUT, and VARIATIC arguments. * Useful schema testing functions to consider adding: + `throws_like()` + `sequence_has_range()` From 628aca91d51b8f607c7340f2ddd02bdee7efea24 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 24 Aug 2009 20:02:24 -0700 Subject: [PATCH 0444/1195] `pgtap.spec` cleanup from Devrim Gunduz and David Fetter --- contrib/pgtap.spec | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index bc75c1554bed..47e1a084b2b4 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,42 +1,46 @@ -Summary: pgtap is a unit testing suite for PostgreSQL -Name: pgtap -Version: 0.22 -Release: 3.%{?dist} -Group: Applications/Databases -License: Free use, with credit -URL: http://pgtap.projects.postgresql.org -Source0: http://pgfoundry.org/frs/download.php/2316/pgtap-%{version}.tar.gz -BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root -BuildRequires: postgresql-devel -Requires: postgresql-server, perl-Test-Harness >= 3.0 - +Summary: Unit testing suite for PostgreSQL +Name: pgtap +Version: 0.23 +Release: 1%{?dist} +Group: Applications/Databases +License: BSD +URL: http://pgtap.projects.postgresql.org +Source0: http://pgfoundry.org/frs/download.php/2316/pgtap-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root +BuildRequires: postgresql-devel +Requires: postgresql-server, perl-Test-Harness >= 3.0 +BuildArch: noarch + %description pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and PL/SQL. It includes a comprehensive collection of TAP-emitting assertion functions, as well as the ability to integrate with other TAP-emitting test frameworks. It can also be used in the xUnit testing style. - + %prep %setup -q - %build -make USE_PGXS=1 TAPSCHEMA=pgtap +make USE_PGXS=1 TAPSCHEMA=tap %install -make install USE_PGXS=1 DESTDIR=%{buildroot} +%{__rm} -rf %{buildroot} +make install USE_PGXS=1 DESTDIR=%{buildroot} %clean -%{__rm} -rf %{buildroot} - +%{__rm} -rf %{buildroot} %files %defattr(-,root,root,-) %{_bindir}/pg_prove -%{_libdir}/pgsql/pgtap.so %{_datadir}/pgsql/contrib/* %{_docdir}/pgsql/contrib/README.pgtap %changelog +* Mon Aug 24 2009 David Fetter 0.23-1 +- Got corrected .spec from Devrim GUNDUZ +- Bumped version to 0.23. + * Wed Aug 19 2009 Darrell Fuhriman 0.22-1 - initial RPM + From 45e6c691cf99c1865d687bae5eb83eb30cd92a64 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 4 Sep 2009 16:35:36 -0700 Subject: [PATCH 0445/1195] Updated documentation of supported environment variables in `bin/pg_prove`. --- Changes | 1 + bin/pg_prove | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 0f95d9b75e7d..783ef241bbab 100644 --- a/Changes +++ b/Changes @@ -9,6 +9,7 @@ Revision history for pgTAP * Fixed `pg_prove` to exit with non-zero status if tests failed, as it works better in automated testing environments and consistent with `prove` (fix by Darrell Fuhriman). +* Updated documentation of supported environment variables in `bin/pg_prove`. 0.22 2009-07-31T00:26:16 ------------------------- diff --git a/bin/pg_prove b/bin/pg_prove index 47a31daded8f..d0af5198bc6e 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -257,8 +257,8 @@ Defaults to F. pg_prove --dbname try pg_prove -d postgres -The name of database to which to connect. Defaults to be the same as the user -name. +The name of database to which to connect. Defaults to the value of the +C<$PGDATABASE> environment variable or to the system username. =item C<-U> @@ -267,8 +267,9 @@ name. pg_prove --username foo pg_prove -U postgres -PostgreSQL user name to connect as. Defaults to be the same as the operating -system name of the user running the application. +PostgreSQL user name to connect as. Defaults to the value of the C<$PGUSER> +environment variable or to the operating system name of the user running the +application. =item C<-h> @@ -279,7 +280,8 @@ system name of the user running the application. Specifies the host name of the machine on which the server is running. If the value begins with a slash, it is used as the directory for the Unix-domain -socket. +socket. Defaults to the value of the C<$PGHOST> environment variable or +localhost. =item C<-p> From ffb190b08189646f4231afac83935cc24048bf5b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 12 Sep 2009 21:46:55 -0700 Subject: [PATCH 0446/1195] Fixed date type formatting bug. In the function tests, the return value of a function was not always consistently formatted. For example, `function_returns()` would find "bool" instead of "boolean". --- Changes | 3 +++ README.pgtap | 23 +++++++++------- pgtap.sql.in | 4 +-- sql/functap.sql | 72 ++++++++++++++++++++++++------------------------- 4 files changed, 55 insertions(+), 47 deletions(-) diff --git a/Changes b/Changes index 783ef241bbab..b69e18ebd1a6 100644 --- a/Changes +++ b/Changes @@ -10,6 +10,9 @@ Revision history for pgTAP better in automated testing environments and consistent with `prove` (fix by Darrell Fuhriman). * Updated documentation of supported environment variables in `bin/pg_prove`. +* Fixed a bug in the function tests where the return value of a function was + not always consistently formatted. For example, `function_returns()` would + find "bool" instead of "boolean". 0.22 2009-07-31T00:26:16 ------------------------- diff --git a/README.pgtap b/README.pgtap index 7402402d11f6..39493d665311 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2791,17 +2791,22 @@ But then you check with `has_function()` first, right? ); SELECT function_returns( 'do_something', 'setof bool' ); - SELECT function_returns( 'do_something', ARRAY['integer'], 'bool' ); + SELECT function_returns( 'do_something', ARRAY['integer'], 'boolean' ); SELECT function_returns( 'do_something', ARRAY['numeric'], 'numeric' ); -Tests that a particular function returns a particular data type. For set -returning functions, the `:type` argument should start with "setof " (yes, -lowercase). If the `:schema` argument is omitted, then the function must be -visible in the search path. If the `:args[]` argument is passed, then the -function with that argument signature will be the one tested; otherwise, a -function with any signature will be checked (pass an empty array to specify a -function with an empty signature). If the `:description` is omitted, a -reasonable substitute will be created. +Tests that a particular function returns a particular data type. The `:args[]` +and `:type` arguments should be formatted as they would be displayed in the +view of a function using the `\df` command in `psql`. For example, use +"character varying" rather than "varchar", and "boolean" rather than "bool". +For set returning functions, the `:type` argument should start with "setof " +(yes, lowercase). + +If the `:schema` argument is omitted, then the function must be visible in the +search path. If the `:args[]` argument is passed, then the function with that +argument signature will be the one tested; otherwise, a function with any +signature will be checked (pass an empty array to specify a function with an +empty signature). If the `:description` is omitted, a reasonable substitute +will be created. In the event of a failure, you'll useful diagnostics will tell you what went wrong, for example: diff --git a/pgtap.sql.in b/pgtap.sql.in index 6f0450dfbba1..45fcccc5d3ef 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2021,7 +2021,8 @@ CREATE OR REPLACE VIEW tap_funky AS n.nspname AS schema, p.proname AS name, array_to_string(p.proargtypes::regtype[], ',') AS args, - CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END || t.typname AS returns, + CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END + || p.prorettype::regtype AS returns, p.prolang AS langoid, p.proisstrict AS is_strict, p.proisagg AS is_agg, @@ -2031,7 +2032,6 @@ CREATE OR REPLACE VIEW tap_funky AS 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 - JOIN pg_catalog.pg_type t ON p.prorettype = t.oid ; CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME, NAME[] ) diff --git a/sql/functap.sql b/sql/functap.sql index 235312ab9faf..fc542dd52457 100644 --- a/sql/functap.sql +++ b/sql/functap.sql @@ -617,7 +617,7 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test function_returns(). SELECT * FROM check_test( - function_returns( 'someschema', 'huh', '{}'::name[], 'bool', 'whatever' ), + function_returns( 'someschema', 'huh', '{}'::name[], 'boolean', 'whatever' ), true, 'function_returns(schema, func, 0 args, bool, desc)', 'whatever', @@ -625,15 +625,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'someschema', 'huh', '{}'::name[], 'bool' ), + function_returns( 'someschema', 'huh', '{}'::name[], 'boolean' ), true, 'function_returns(schema, func, 0 args, bool)', - 'Function someschema.huh() should return bool', + 'Function someschema.huh() should return boolean', '' ); SELECT * FROM check_test( - function_returns( 'someschema', 'bah', ARRAY['integer', 'text'], 'bool', 'whatever' ), + function_returns( 'someschema', 'bah', ARRAY['integer', 'text'], 'boolean', 'whatever' ), true, 'function_returns(schema, func, args, bool, false)', 'whatever', @@ -641,15 +641,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'someschema', 'bah', ARRAY['integer', 'text'], 'bool' ), + function_returns( 'someschema', 'bah', ARRAY['integer', 'text'], 'boolean' ), true, 'function_returns(schema, func, args, bool)', - 'Function someschema.bah(integer, text) should return bool', + 'Function someschema.bah(integer, text) should return boolean', '' ); SELECT * FROM check_test( - function_returns( 'public', 'pet', '{}'::name[], 'setof bool', 'whatever' ), + function_returns( 'public', 'pet', '{}'::name[], 'setof boolean', 'whatever' ), true, 'function_returns(schema, func, 0 args, setof bool, desc)', 'whatever', @@ -657,15 +657,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'public', 'pet', '{}'::name[], 'setof bool' ), + function_returns( 'public', 'pet', '{}'::name[], 'setof boolean' ), true, 'function_returns(schema, func, 0 args, setof bool)', - 'Function public.pet() should return setof bool', + 'Function public.pet() should return setof boolean', '' ); SELECT * FROM check_test( - function_returns( 'someschema', 'huh', 'bool', 'whatever' ), + function_returns( 'someschema', 'huh', 'boolean', 'whatever' ), true, 'function_returns(schema, func, bool, desc)', 'whatever', @@ -673,15 +673,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'someschema', 'huh'::name, 'bool' ), + function_returns( 'someschema', 'huh'::name, 'boolean' ), true, 'function_returns(schema, func, bool)', - 'Function someschema.huh() should return bool', + 'Function someschema.huh() should return boolean', '' ); SELECT * FROM check_test( - function_returns( 'someschema', 'bah', 'bool', 'whatever' ), + function_returns( 'someschema', 'bah', 'boolean', 'whatever' ), true, 'function_returns(schema, other func, bool, false)', 'whatever', @@ -689,15 +689,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'someschema', 'bah'::name, 'bool' ), + function_returns( 'someschema', 'bah'::name, 'boolean' ), true, 'function_returns(schema, other func, bool)', - 'Function someschema.bah() should return bool', + 'Function someschema.bah() should return boolean', '' ); SELECT * FROM check_test( - function_returns( 'public', 'pet', 'setof bool', 'whatever' ), + function_returns( 'public', 'pet', 'setof boolean', 'whatever' ), true, 'function_returns(schema, func, setof bool, desc)', 'whatever', @@ -705,15 +705,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'public', 'pet'::name, 'setof bool' ), + function_returns( 'public', 'pet'::name, 'setof boolean' ), true, 'function_returns(schema, func, setof bool)', - 'Function public.pet() should return setof bool', + 'Function public.pet() should return setof boolean', '' ); SELECT * FROM check_test( - function_returns( 'yay', '{}'::name[], 'bool', 'whatever' ), + function_returns( 'yay', '{}'::name[], 'boolean', 'whatever' ), true, 'function_returns(func, 0 args, bool, desc)', 'whatever', @@ -721,15 +721,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'yay', '{}'::name[], 'bool' ), + function_returns( 'yay', '{}'::name[], 'boolean' ), true, 'function_returns(func, 0 args, bool)', - 'Function yay() should return bool', + 'Function yay() should return boolean', '' ); SELECT * FROM check_test( - function_returns( 'oww', ARRAY['integer', 'text'], 'bool', 'whatever' ), + function_returns( 'oww', ARRAY['integer', 'text'], 'boolean', 'whatever' ), true, 'function_returns(func, args, bool, false)', 'whatever', @@ -737,15 +737,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'oww', ARRAY['integer', 'text'], 'bool' ), + function_returns( 'oww', ARRAY['integer', 'text'], 'boolean' ), true, 'function_returns(func, args, bool)', - 'Function oww(integer, text) should return bool', + 'Function oww(integer, text) should return boolean', '' ); SELECT * FROM check_test( - function_returns( 'pet', '{}'::name[], 'setof bool', 'whatever' ), + function_returns( 'pet', '{}'::name[], 'setof boolean', 'whatever' ), true, 'function_returns(func, 0 args, setof bool, desc)', 'whatever', @@ -753,15 +753,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'pet', '{}'::name[], 'setof bool' ), + function_returns( 'pet', '{}'::name[], 'setof boolean' ), true, 'function_returns(func, 0 args, setof bool)', - 'Function pet() should return setof bool', + 'Function pet() should return setof boolean', '' ); SELECT * FROM check_test( - function_returns( 'yay', 'bool', 'whatever' ), + function_returns( 'yay', 'boolean', 'whatever' ), true, 'function_returns(func, bool, desc)', 'whatever', @@ -769,15 +769,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'yay', 'bool' ), + function_returns( 'yay', 'boolean' ), true, 'function_returns(func, bool)', - 'Function yay() should return bool', + 'Function yay() should return boolean', '' ); SELECT * FROM check_test( - function_returns( 'oww', 'bool', 'whatever' ), + function_returns( 'oww', 'boolean', 'whatever' ), true, 'function_returns(other func, bool, false)', 'whatever', @@ -785,15 +785,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'oww', 'bool' ), + function_returns( 'oww', 'boolean' ), true, 'function_returns(other func, bool)', - 'Function oww() should return bool', + 'Function oww() should return boolean', '' ); SELECT * FROM check_test( - function_returns( 'pet', 'setof bool', 'whatever' ), + function_returns( 'pet', 'setof boolean', 'whatever' ), true, 'function_returns(func, setof bool, desc)', 'whatever', @@ -801,10 +801,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'pet', 'setof bool' ), + function_returns( 'pet', 'setof boolean' ), true, 'function_returns(func, setof bool)', - 'Function pet() should return setof bool', + 'Function pet() should return setof boolean', '' ); From 69742e7dc193787a44781a80808ec4678b288871 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 16 Oct 2009 23:15:26 -0700 Subject: [PATCH 0447/1195] Add `isa_ok()`. --- Changes | 1 + README.pgtap | 21 ++++++++++++++++ expected/cmpok.out | 20 ++++++++++++++- pgtap.sql.in | 17 +++++++++++++ sql/cmpok.sql | 56 +++++++++++++++++++++++++++++++++++++++--- uninstall_pgtap.sql.in | 2 ++ 6 files changed, 112 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index b69e18ebd1a6..6d3d9071dc04 100644 --- a/Changes +++ b/Changes @@ -13,6 +13,7 @@ Revision history for pgTAP * Fixed a bug in the function tests where the return value of a function was not always consistently formatted. For example, `function_returns()` would find "bool" instead of "boolean". +* Added `isa_ok()`. 0.22 2009-07-31T00:26:16 ------------------------- diff --git a/README.pgtap b/README.pgtap index 39493d665311..27ef9423ce2a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -534,6 +534,27 @@ you've got some complicated condition that is difficult to wedge into an Use these functions very, very, very sparingly. +### `isa_ok( value, regtype, name )` ### +### `isa_ok( value, regtype )` ### + + SELECT isa_ok( :value, :regtype, name ); + +Checks to see if the given value is of a particular type. The description and +diagnostics of this test normally just refer to "the value". If you'd like +them to be more specific, you can supply a `:name`. For example you might say +"the return value" when yo're examing the result of a function call: + + SELECT isa_ok( length('foo'), 'integer', 'The return value from length()' ); + +In which case the description will be "The return value from length() isa +integer". + +In the event of a failure, the diagnostic message will tell you what the type +of the value actually is: + + not ok 12 - the value isa integer[] + # the value isn't a "integer[]" it's a "boolean" + Pursuing Your Query =================== diff --git a/expected/cmpok.out b/expected/cmpok.out index 5aeff495b898..7ee13efea36b 100644 --- a/expected/cmpok.out +++ b/expected/cmpok.out @@ -1,5 +1,5 @@ \unset ECHO -1..20 +1..38 ok 1 - cmp_ok( int, =, int ) should pass ok 2 - cmp_ok( int, =, int ) should have the proper description ok 3 - cmp_ok( int, =, int ) should have the proper diagnostics @@ -20,3 +20,21 @@ ok 17 - cmp_ok() fail should have the proper diagnostics ok 18 - cmp_ok() NULL fail should fail ok 19 - cmp_ok() NULL fail should have the proper description ok 20 - cmp_ok() NULL fail should have the proper diagnostics +ok 21 - isa_ok("", text, desc) should pass +ok 22 - isa_ok("", text, desc) should have the proper description +ok 23 - isa_ok("", text, desc) should have the proper diagnostics +ok 24 - isa_ok("", text, desc) should pass +ok 25 - isa_ok("", text, desc) should have the proper description +ok 26 - isa_ok("", text, desc) should have the proper diagnostics +ok 27 - isa_ok(false, boolean) should pass +ok 28 - isa_ok(false, boolean) should have the proper description +ok 29 - isa_ok(false, boolean) should have the proper diagnostics +ok 30 - isa_ok(NULL, boolean) should pass +ok 31 - isa_ok(NULL, boolean) should have the proper description +ok 32 - isa_ok(NULL, boolean) should have the proper diagnostics +ok 33 - isa_ok(ARRAY, boolean[]) should pass +ok 34 - isa_ok(ARRAY, boolean[]) should have the proper description +ok 35 - isa_ok(ARRAY, boolean[]) should have the proper diagnostics +ok 36 - isa_ok(bool, int[]) should fail +ok 37 - isa_ok(bool, int[]) should have the proper description +ok 38 - isa_ok(bool, int[]) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 45fcccc5d3ef..9fa6e51142c1 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -6423,3 +6423,20 @@ 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; diff --git a/sql/cmpok.sql b/sql/cmpok.sql index 2d59cdf18e79..357559fc6bc3 100644 --- a/sql/cmpok.sql +++ b/sql/cmpok.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(20); +SELECT plan(38); /****************************************************************************/ @@ -82,10 +82,58 @@ SELECT * FROM check_test( NULL' ); + +/****************************************************************************/ +-- Test isa_ok(). +SELECT * FROM check_test( + isa_ok( ''::text, 'text', 'an empty string' ), + true, + 'isa_ok("", text, desc)', + 'an empty string isa text', + '' +); + +SELECT * FROM check_test( + isa_ok( ''::text, 'text', 'an empty string' ), + true, + 'isa_ok("", text, desc)', + 'an empty string isa text', + '' +); + +SELECT * FROM check_test( + isa_ok( false, 'bool' ), + true, + 'isa_ok(false, boolean)', + 'the value isa boolean', + '' +); + +SELECT * FROM check_test( + isa_ok( NULL::boolean, 'bool' ), + true, + 'isa_ok(NULL, boolean)', + 'the value isa boolean', + '' +); + +SELECT * FROM check_test( + isa_ok( ARRAY[false], 'bool[]' ), + true, + 'isa_ok(ARRAY, boolean[])', + 'the value isa boolean[]', + '' +); + +SELECT * FROM check_test( + isa_ok( true, 'int[]' ), + false, + 'isa_ok(bool, int[])', + 'the value isa integer[]', + ' the value isn''t a "integer[]" it''s a "boolean"' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); ROLLBACK; - --- Spam fingerprints: Contains an exact font color, and the words in the title are the same as in the body. --- rule that extracts the existing google ad ID, a string, get from original special features script. diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 5bf09552c1c8..7e43b75f8309 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,4 +1,6 @@ -- ## SET search_path TO TAPSCHEMA, public; +DROP FUNCTION isa_ok( anyelement, regtype ); +DROP FUNCTION isa_ok( anyelement, regtype, TEXT ); DROP FUNCTION results_ne( refcursor, anyarray ); DROP FUNCTION results_ne( refcursor, anyarray, TEXT ); DROP FUNCTION results_ne( refcursor, TEXT ); From 2b29ec9462285d359ff6f43bc8d91d6ca8e68bf8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 17 Oct 2009 09:06:55 -0700 Subject: [PATCH 0448/1195] Updated notes on `pg_typeof()`. --- README.pgtap | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.pgtap b/README.pgtap index 27ef9423ce2a..9412483fe467 100644 --- a/README.pgtap +++ b/README.pgtap @@ -159,11 +159,11 @@ Of course, if you already have the pgTAP functions in your testing database, you should skip `\i pgtap.sql` at the beginning of the script. The only other limitation is that the `pg_typoeof()` function, which is -written in C, will not be available. You'll want to comment-out its -declaration in the bundled copy of `pgtap.sql` and then avoid using -`cmp_ok()`, since that function relies on `pg_typeof()`. Note that -`pg_typeof()` will be included in PostgreSQL 8.4, in which case you wouldn't -need to avoid it. +written in C, will not be available in 8.3 and lower. You'll want to +comment-out its declaration in the bundled copy of `pgtap.sql` and then avoid +using `cmp_ok()`, since that function relies on `pg_typeof()`. Note that +`pg_typeof()` is included in PostgreSQL 8.4, so ou won't need to avoid it on +that version or higher. Now you're ready to run your test script! @@ -3789,7 +3789,7 @@ No changes. Everything should just work. string may be considered incorrectly equivalent. * A C function, `pg_typeof()`, is built and installed in a DSO. This is for compatibility with the same function that ships in 8.4 core, and is required - for `cmp_ok()` to work. + for `cmp_ok()` and `isa_ok()` to work. 8.2 and Down ------------ From 76a7ec0510f92b46e11656f1cea47268954cb103 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 17 Oct 2009 12:12:12 -0700 Subject: [PATCH 0449/1195] New todo: `collect_tests()`. --- README.pgtap | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.pgtap b/README.pgtap index 9412483fe467..2e98c525d08a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3825,6 +3825,19 @@ To Do * Useful result testing functions to consider adding (but might require C code): + `row_eq()` + `rowtype_is()` +* Add a function to collect multiple tests in one call, someting like + `collect_tests()`. Will be useful when used in combination with `skip()`: + + SELECT CASE os_name() WHEN 'darwin' THEN + collect_tests( + cmp_ok( 'Bjørn'::text, '>', 'Bjorn', 'ø > o' ), + cmp_ok( 'Pınar'::text, '>', 'Pinar', 'ı > i' ), + cmp_ok( 'José'::text, '>', 'Jose', 'é > e' ), + cmp_ok( 'Täp'::text, '>', 'Tap', 'ä > a' ) + ) + ELSE + skip('Collation-specific test', 4) + END; Supported Versions ----------------- From 5d6d49ca872a16e29aaaa9f2198446115b3ad389 Mon Sep 17 00:00:00 2001 From: Dan Date: Sun, 18 Oct 2009 09:44:43 -0700 Subject: [PATCH 0450/1195] updated makefile for 8.5 --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index 47fa41100127..9dd749bd0c6d 100644 --- a/Makefile +++ b/Makefile @@ -60,6 +60,7 @@ EXTRA_SUBS := -e "s/ E'/ '/g" TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql sql/enumtap.sql sql/roletap.sql,$(TESTS)) REGRESS := $(filter-out throwtap runtests enumtap roletap,$(REGRESS)) endif +ifeq ($(PGVER_MINOR), 5) ifeq ($(PGVER_MINOR), 4) # Do nothing. else @@ -75,6 +76,7 @@ endif endif endif endif +endif # Determine the OS. Borrowed from Perl's Configure. OSNAME := $(shell ./getos.sh) @@ -95,6 +97,7 @@ else sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' $(EXTRA_SUBS) pgtap.sql.in > pgtap.sql endif ifeq ($(PGVER_MAJOR), 8) +ifneq ($(PGVER_MINOR), 5) ifneq ($(PGVER_MINOR), 4) ifneq ($(PGVER_MINOR), 0) patch -p0 < compat/install-8.3.patch @@ -114,6 +117,7 @@ endif endif endif endif +endif uninstall_pgtap.sql: uninstall_pgtap.sql.in test_setup.sql ifdef TAPSCHEMA @@ -122,6 +126,7 @@ else cp uninstall_pgtap.sql.in uninstall_pgtap.sql endif ifeq ($(PGVER_MAJOR), 8) +ifneq ($(PGVER_MINOR), 5) ifneq ($(PGVER_MINOR), 4) patch -p0 < compat/uninstall-8.3.patch ifneq ($(PGVER_MINOR), 3) @@ -138,6 +143,7 @@ ifeq ($(PGVER_MINOR), 0) endif endif endif +endif # Build pg_prove and holler if there's no Perl or TAP::Harness. bbin/pg_prove: From 73f3fd3eb644b5303909952fe1880abcafbcdf93 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 18 Oct 2009 11:31:05 -0700 Subject: [PATCH 0451/1195] Added missing closing quotes to docs. Also made the `tablespaces_are()` examples a bit more realistic. --- README.pgtap | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.pgtap b/README.pgtap index 2e98c525d08a..bd3b2fff5ec5 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1202,8 +1202,8 @@ the next section. ### `tablespaces_are( tablespaces )` ### SELECT tablespaces_are( - ARRAY[ 'public, 'contrib, 'tap' ], - 'Should have the correct tablespaces + ARRAY[ 'dbspace, 'indexspace' ], + 'Should have the correct tablespaces' ); This function tests that all of the tablespaces in the database only the @@ -1212,16 +1212,16 @@ diagnostics listing the extra and/or missing tablespaces, like so: # Failed test 121: "There should be the correct tablespaces" # Extra tablespaces: - # pg_default + # trigspace # Missing tablespaces: - # __booya__ + # indexspace ### `schemas_are( schemas, description )` ### ### `schemas_are( schemas )` ### SELECT schemas_are( ARRAY[ 'public, 'contrib, 'tap' ], - 'Should have the correct schemas + 'Should have the correct schemas' ); This function tests that all of the schemas in the database only the schemas @@ -1377,7 +1377,7 @@ missing functions, like so: SELECT users_are( ARRAY[ 'postgres', 'someone', 'root' ], - 'Should have the correct users + 'Should have the correct users' ); This function tests that all of the users in the database only the users that @@ -1395,7 +1395,7 @@ the extra and/or missing users, like so: SELECT groups_are( ARRAY[ 'postgres', 'admins, 'l0s3rs' ], - 'Should have the correct groups + 'Should have the correct groups' ); This function tests that all of the groups in the database only the groups @@ -1413,7 +1413,7 @@ listing the extra and/or missing groups, like so: SELECT languages_are( ARRAY[ 'plpgsql', 'plperl', 'pllolcode' ], - 'Should have the correct procedural languages + 'Should have the correct procedural languages' ); This function tests that all of the procedural languages in the database only From 1f4df8bd68f30977d190193ef38d9d62d0d2f99e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 18 Oct 2009 11:31:55 -0700 Subject: [PATCH 0452/1195] More mising closoing quotation marks. --- README.pgtap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index bd3b2fff5ec5..f3886d33f820 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1220,7 +1220,7 @@ diagnostics listing the extra and/or missing tablespaces, like so: ### `schemas_are( schemas )` ### SELECT schemas_are( - ARRAY[ 'public, 'contrib, 'tap' ], + ARRAY[ 'public', 'contrib', 'tap' ], 'Should have the correct schemas' ); From 4f102b95e44ee4ece1cbcef0d42ec12505e02e04 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 19 Oct 2009 09:09:00 -0700 Subject: [PATCH 0453/1195] A few more functions to add. --- README.pgtap | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index f3886d33f820..a1005366fe22 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1202,7 +1202,7 @@ the next section. ### `tablespaces_are( tablespaces )` ### SELECT tablespaces_are( - ARRAY[ 'dbspace, 'indexspace' ], + ARRAY[ 'dbspace', 'indexspace' ], 'Should have the correct tablespaces' ); @@ -3822,6 +3822,14 @@ To Do + `sequence_increments_by()` + `sequence_starts_at()` + `sequence_cycles()` + + `roles_are()` + + `types_are()` + + `domains_are()` + + `enums_are()` + + `triggers_are()` + + `casts_are()` + + `operators_are()` + + `columns_are()` * Useful result testing functions to consider adding (but might require C code): + `row_eq()` + `rowtype_is()` From f6da63e47ba44d3ffad3387d14d07bf31d212995 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 19 Oct 2009 09:16:11 -0700 Subject: [PATCH 0454/1195] What Dan did. --- Changes | 1 + 1 file changed, 1 insertion(+) diff --git a/Changes b/Changes index 6d3d9071dc04..ca6d417f3593 100644 --- a/Changes +++ b/Changes @@ -14,6 +14,7 @@ Revision history for pgTAP not always consistently formatted. For example, `function_returns()` would find "bool" instead of "boolean". * Added `isa_ok()`. +* Added support for building against 8.5 [Dan Colish]. 0.22 2009-07-31T00:26:16 ------------------------- From 88195c5207cd0486606d71ac5a0547db92756ec0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Oct 2009 17:01:44 -0700 Subject: [PATCH 0455/1195] Add `pg_tapgen` `pg_tapgen` is a start and automating pgTAP schema validation tests. --- Changes | 2 + Makefile | 13 ++- bin/pg_prove | 2 +- bin/pg_tapgen | 287 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 301 insertions(+), 3 deletions(-) create mode 100755 bin/pg_tapgen diff --git a/Changes b/Changes index ca6d417f3593..8c89909f21b5 100644 --- a/Changes +++ b/Changes @@ -15,6 +15,8 @@ Revision history for pgTAP find "bool" instead of "boolean". * Added `isa_ok()`. * Added support for building against 8.5 [Dan Colish]. +* Added `pg_tapgen`, which is a start and automating pgTAP schema validation + tests. 0.22 2009-07-31T00:26:16 ------------------------- diff --git a/Makefile b/Makefile index 9dd749bd0c6d..96bbe0397cf8 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ TESTS = $(wildcard sql/*.sql) EXTRA_CLEAN = test_setup.sql *.html DATA_built = pgtap.sql uninstall_pgtap.sql DOCS = README.pgtap -SCRIPTS = bbin/pg_prove +SCRIPTS = bbin/pg_prove bbin/pg_tapgen REGRESS = $(patsubst sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --load-language=plpgsql @@ -160,11 +160,20 @@ else endif chmod +x bbin/pg_prove +bbin/pg_tapgen: + cp bin/pg_tapgen bbin +ifdef PERL + $(PERL) '-MExtUtils::MY' -e 'MY->fixin(shift)' bbin/pg_tapgen +else + $(warning Could not find perl (required by pg_tapgen). Install it or set the PERL variable.) +endif + chmod +x bbin/pg_tapgen + # Make sure that we build the regression tests. installcheck: test_setup.sql # Make sure we build pg_prove. -all: bbin/pg_prove +all: bbin/pg_prove bbin/pg_tapgen # Make sure we remove bbin. clean: extraclean diff --git a/bin/pg_prove b/bin/pg_prove index d0af5198bc6e..4fff7dd79351 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -53,7 +53,7 @@ unless ($opts->{runtests} or @ARGV) { '-message' => "\nOops! You didn't say what test scripts to run or specify --runtests.\n", '-sections' => '(?i:(Usage|Options))', '-verbose' => 99, - '-exitval' => 1, + '-exitval' => 1, ) } diff --git a/bin/pg_tapgen b/bin/pg_tapgen new file mode 100755 index 000000000000..92554c454137 --- /dev/null +++ b/bin/pg_tapgen @@ -0,0 +1,287 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; +use DBI; +use DBD::Pg; +use Getopt::Long; +our $VERSION = '0.23'; + +Getopt::Long::Configure (qw(bundling)); + +my $opts = { psql => 'psql', color => 1 }; + +Getopt::Long::GetOptions( + 'dbname|d=s' => \$opts->{dbname}, + 'username|U=s' => \$opts->{username}, + 'host|h=s' => \$opts->{host}, + 'port|p=s' => \$opts->{port}, + 'exclude-schema|N=s@' => \$opts->{exclude_schema}, + 'verbose|v+' => \$opts->{verbose}, + 'help|H' => \$opts->{help}, + 'man|m' => \$opts->{man}, + 'version|V' => \$opts->{version}, +) or require Pod::Usage && Pod::Usage::pod2usage(2); + +if ( $opts->{help} or $opts->{man} ) { + require Pod::Usage; + Pod::Usage::pod2usage( + '-sections' => $opts->{man} ? '.+' : '(?i:(Usage|Options))', + '-verbose' => 99, + '-exitval' => 0, + ) +} + +if ($opts->{version}) { + print 'pg_prove ', main->VERSION, $/; + exit; +} + +my @conn; +for (qw(host port dbname)) { + push @conn, "$_=$opts->{$_}" if defined $opts->{$_}; +} +my $dsn = 'dbi:Pg'; +$dsn .= ':' . join ';', @conn if @conn; + +my $dbh = DBI->connect($dsn, $opts->{username}, undef, { + RaiseError => 1, + PrintError => 0, + AutoCommit => 1, +}); + +print "SELECT * FROM no_plan();\n\n"; +if (my @schemas = get_schemas($opts->{exclude_schema})) { + schemas_are(\@schemas); + for my $schema (@schemas) { + tables_are($schema); + views_are($schema); + sequences_are($schema); + functions_are($schema); + } +} + +print "SELECT * FROM finish();\n"; + +############################################################################## + +sub get_schemas { + my @exclude = ('information_schema'); + push @exclude, @{ $_[0] } if $_[0] && @{ $_[0] }; + + my $sth = $dbh->prepare_cached(q{ + SELECT nspname + FROM pg_catalog.pg_namespace + WHERE nspname NOT LIKE 'pg_%' + AND nspname <> ALL(?) + ORDER BY nspname + }); + + my $schemas = $dbh->selectcol_arrayref($sth, undef, \@exclude) or return; + return @$schemas; +} + +sub schemas_are { + my $schemas = shift; + print "SELECT schemas_are( ARRAY[\n '", + join("',\n '", @$schemas), + "'\n] );\n\n" if @$schemas; +} + +sub get_rels { + my $sth = $dbh->prepare_cached(q{ + SELECT c.relname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace + WHERE c.relkind = ? + AND n.nspname = ? + ORDER BY c.relname + }); + return $dbh->selectcol_arrayref($sth, undef, @_); +} + +sub tables_are { + my $schema = shift; + my $tables = get_rels(r => $schema); + return unless $tables && @$tables; + print "SELECT tables_are( '$schema', ARRAY[\n '", + join("',\n '", @$tables), + "'\n] );\n\n"; +} + +sub views_are { + my $schema = shift; + my $tables = get_rels(v => $schema); + return unless $tables && @$tables; + print "SELECT views_are( '$schema', ARRAY[\n '", + join("',\n '", @$tables), + "'\n] );\n\n"; +} + +sub sequences_are { + my $schema = shift; + my $tables = get_rels(S => $schema); + return unless $tables && @$tables; + print "SELECT sequences_are( '$schema', ARRAY[\n '", + join("',\n '", @$tables), + "'\n] );\n\n"; +} + +sub functions_are { + my $schema = shift; + my $sth = $dbh->prepare(q{ + SELECT p.proname + FROM pg_catalog.pg_proc p + JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid + WHERE n.nspname = ? + }); + my $funcs = $dbh->selectcol_arrayref($sth, undef, $schema); + return unless $funcs && @$funcs; + print "SELECT functions_are( '$schema', ARRAY[\n '", + join("',\n '", @$funcs), + "'\n] );\n\n"; +} + + +__END__ + +=encoding utf8 + +=head1 Name + +pg_tapgen - Generate schema TAP tests from an existing database + +=head1 Usage + + pg_tapgen -d template1 > schema_test.sql + +=head1 Description + +C is a command-line utility to generate pgTAP tests to validate a +database schema by reading an existing database and generating the tests to +match. + +=head1 Options + + -d --dbname DBNAME Database to which to connect. + -U --username USERNAME Username with which to connect. + -h --host HOST Host to which to connect. + -p --port PORT Port to which to connect. + -v --verbose Display output of test scripts while running them. + -N --exclude-schema Exclude a schema from the generated tests. + -H --help Print a usage statement and exit. + -m --man Print the complete documentation and exit. + -V --version Print the version number and exit. + +=head1 Options Details + +=over + +=item C<-d> + +=item C<--dbname> + + pg_tapgen --dbname try + pg_tapgen -d postgres + +The name of database to which to connect. Defaults to the value of the +C<$PGDATABASE> environment variable or to the system username. + +=item C<-U> + +=item C<--username> + + pg_tapgen --username foo + pg_tapgen -U postgres + +PostgreSQL user name to connect as. Defaults to the value of the C<$PGUSER> +environment variable or to the operating system name of the user running the +application. + +=item C<-h> + +=item C<--host> + + pg_tapgen --host pg.example.com + pg_tapgen -h dev.local + +Specifies the host name of the machine on which the server is running. If the +value begins with a slash, it is used as the directory for the Unix-domain +socket. Defaults to the value of the C<$PGHOST> environment variable or +localhost. + +=item C<-p> + +=item C<--port> + + pg_tapgen --port 1234 + pg_tapgen -p 666 + +Specifies the TCP port or the local Unix-domain socket file extension on which +the server is listening for connections. Defaults to the value of the +C<$PGPORT> environment variable or, if not set, to the port specified at +compile time, usually 5432. + +=item C<-v> + +=item C<--verbose> + + pg_tapgen --verbose + pg_tapgen -v + +Display standard output of test scripts while running them. This behavior can +also be triggered by setting the C<$TEST_VERBOSE> environment variable to a +true value. + +=item C<-N> + +=item C<--exclude-schema> + + pg_tapgen --exclude-schema contrib + pg_tapgen -N testing -N temporary + +Exclude a schema from the test generation. C always ignores +C, as it is also ingored by pgTAP. But if there are other +schemas in the database that you don't need or want to test for in the +database (because you run the tests on another database without those schemas, +for example), use C<--exclude-schema> to omit them. May be used more than once +to exclude more than one schema. + +=item C<-H> + +=item C<--help> + + pg_tapgen --help + pg_tapgen -H + +Outputs a brief description of the options supported by C and exits. + +=item C<-m> + +=item C<--man> + + pg_tapgen --man + pg_tapgen -m + +Outputs this documentation and exits. + +=item C<-V> + +=item C<--version> + + pg_tapgen --version + pg_tapgen -V + +Outputs the program name and version and exits. + +=back + +=head1 Author + +David E. Wheeler + +=head1 Copyright + +Copyright (c) 2009 Kineticode, Inc. Some Rights Reserved. + +=cut From bc0b9b4859ff1e63fd5c42a803d1df43405fba5e Mon Sep 17 00:00:00 2001 From: Duke Leto Date: Mon, 16 Nov 2009 00:06:10 -0800 Subject: [PATCH 0456/1195] Fix small bug where util test would fail when the string 'alpha' was in a version 'number' --- sql/util.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/util.sql b/sql/util.sql index 744f5916a9d0..51bac44c27a7 100644 --- a/sql/util.sql +++ b/sql/util.sql @@ -16,7 +16,7 @@ SELECT is( ); SELECT matches( pg_version(), - '^8[.][[:digit:]]{1,2}([.][[:digit:]]{1,2}|devel|(beta|rc)[[:digit:]]+)$', + '^8[.][[:digit:]]{1,2}([.][[:digit:]]{1,2}|devel|(alpha|beta|rc)[[:digit:]]+)$', 'pg_version() should work' ); From 188a2c20889e4669eb2a2f896c50001653bd0510 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Nov 2009 13:32:08 +0900 Subject: [PATCH 0457/1195] Add `is_empty()`. Tests that a query returns an empty set (no results). --- Changes | 1 + README.pgtap | 16 ++++++++++++ expected/resultset.out | 20 ++++++++++++++- pgtap.sql.in | 33 ++++++++++++++++++++++++ sql/resultset.sql | 58 +++++++++++++++++++++++++++++++++++++++++- uninstall_pgtap.sql.in | 2 ++ 6 files changed, 128 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 8c89909f21b5..533fbf8772b9 100644 --- a/Changes +++ b/Changes @@ -17,6 +17,7 @@ Revision history for pgTAP * Added support for building against 8.5 [Dan Colish]. * Added `pg_tapgen`, which is a start and automating pgTAP schema validation tests. +* Added `is_empty()` to test that a query returns an empty set (no results). 0.22 2009-07-31T00:26:16 ------------------------- diff --git a/README.pgtap b/README.pgtap index a1005366fe22..e81644d1b478 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1145,6 +1145,22 @@ is any unexpected overlap between the query results. This means that a duplicate row in the first query will appear twice in the diagnostics if it is also duplicated in the second query. +### `is_empty( sql, description )` ### +### `is_empty( sql )` ### + + PREPARE emptyset AS SELECT * FROM users(FALSE); + SELECT is_empty( 'emptyset', 'Should have no inactive users' ); + +The `is_empty()` function takes a single query string or prepared statement +name as its first argument, and tests that said query returns no records. +Internally it simply executes the query and if there are any results, the test +fails and the results are displayed in the failure diagnostics, like so: + + # Failed test 494: "Should have no inactive users" + # Records returned: + # (1,Jacob,false) + # (2,Emily,false) + The Schema Things ================= diff --git a/expected/resultset.out b/expected/resultset.out index 0f7b66fde428..2a66b207e0a7 100644 --- a/expected/resultset.out +++ b/expected/resultset.out @@ -1,5 +1,5 @@ \unset ECHO -1..475 +1..493 ok 1 - Should create temp table with simple query ok 2 - Table __foonames__ should exist ok 3 - Should create a temp table for a prepared statement @@ -475,3 +475,21 @@ ok 472 - results_ne(cursor, sql) should have the proper diagnostics ok 473 - results_ne(sql, cursor) should fail ok 474 - results_ne(sql, cursor) should have the proper description ok 475 - results_ne(sql, cursor) should have the proper diagnostics +ok 476 - is_empty(sql, desc) should pass +ok 477 - is_empty(sql, desc) should have the proper description +ok 478 - is_empty(sql, desc) should have the proper diagnostics +ok 479 - is_empty(sql) should pass +ok 480 - is_empty(sql) should have the proper description +ok 481 - is_empty(sql) should have the proper diagnostics +ok 482 - is_empty(prepared, desc) should pass +ok 483 - is_empty(prepared, desc) should have the proper description +ok 484 - is_empty(prepared, desc) should have the proper diagnostics +ok 485 - is_empty(prepared) should pass +ok 486 - is_empty(prepared) should have the proper description +ok 487 - is_empty(prepared) should have the proper diagnostics +ok 488 - is_empty(prepared, desc) fail should fail +ok 489 - is_empty(prepared, desc) fail should have the proper description +ok 490 - is_empty(prepared, desc) fail should have the proper diagnostics +ok 491 - is_empty(prepared) fail should fail +ok 492 - is_empty(prepared) fail should have the proper description +ok 493 - is_empty(prepared) fail should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 9fa6e51142c1..dc3e56180e0a 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -6440,3 +6440,36 @@ 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' Records returned:\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; diff --git a/sql/resultset.sql b/sql/resultset.sql index c19b462b2e22..931695773193 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(475); +SELECT plan(493); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -2227,6 +2227,62 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Now test is_empty(). +SELECT * FROM check_test( + is_empty( 'SELECT 1 WHERE FALSE', 'whatever' ), + true, + 'is_empty(sql, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_empty( 'SELECT 1 WHERE FALSE' ), + true, + 'is_empty(sql)', + '', + '' +); + +PREPARE emptyset AS SELECT * FROM names WHERE FALSE; +SELECT * FROM check_test( + is_empty( 'emptyset', 'whatever' ), + true, + 'is_empty(prepared, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_empty( 'emptyset' ), + true, + 'is_empty(prepared)', + '', + '' +); + +PREPARE notempty AS SELECT id, name FROM names WHERE name IN ('Jacob', 'Emily') ORDER BY ID; +SELECT * FROM check_test( + is_empty( 'notempty', 'whatever' ), + false, + 'is_empty(prepared, desc) fail', + 'whatever', + ' Records returned: + (1,Jacob) + (2,Emily)' +); + +SELECT * FROM check_test( + is_empty( 'notempty' ), + false, + 'is_empty(prepared) fail', + '', + ' Records returned: + (1,Jacob) + (2,Emily)' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 7e43b75f8309..dcae4a10b8ba 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,4 +1,6 @@ -- ## SET search_path TO TAPSCHEMA, public; +DROP FUNCTION is_empty( TEXT ); +DROP FUNCTION is_empty( TEXT, TEXT ); DROP FUNCTION isa_ok( anyelement, regtype ); DROP FUNCTION isa_ok( anyelement, regtype, TEXT ); DROP FUNCTION results_ne( refcursor, anyarray ); From ec2b13639636926ed44149e2fa43b10af34b8a41 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Nov 2009 13:35:35 +0900 Subject: [PATCH 0458/1195] Better diagnostic message, I think. --- pgtap.sql.in | 2 +- sql/resultset.sql | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index dc3e56180e0a..99008c9840f7 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -6459,7 +6459,7 @@ BEGIN IF extras[1] IS NOT NULL THEN res := FALSE; msg := E'\n' || diag( - E' Records returned:\n ' + E' Unexpected records:\n ' || array_to_string( extras, E'\n ' ) ); END IF; diff --git a/sql/resultset.sql b/sql/resultset.sql index 931695773193..42dc295112e5 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -2268,7 +2268,7 @@ SELECT * FROM check_test( false, 'is_empty(prepared, desc) fail', 'whatever', - ' Records returned: + ' Unexpected records: (1,Jacob) (2,Emily)' ); @@ -2278,7 +2278,7 @@ SELECT * FROM check_test( false, 'is_empty(prepared) fail', '', - ' Records returned: + ' Unexpected records: (1,Jacob) (2,Emily)' ); From 5998f6c918b95697503fc144a87e8fdeeb175b0b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Nov 2009 14:01:48 +0900 Subject: [PATCH 0459/1195] * Add `collect_tap()` Makes it easier to run multiple tests in a single `SELECT` statement in combination with `skip()`. --- Changes | 2 ++ README.pgtap | 58 +++++++++++++++++++++++++++--------------- expected/util.out | 3 ++- pgtap.sql.in | 7 +++++ sql/util.sql | 15 +++++++++-- uninstall_pgtap.sql.in | 1 + 6 files changed, 62 insertions(+), 24 deletions(-) diff --git a/Changes b/Changes index 533fbf8772b9..c4ec9d4906af 100644 --- a/Changes +++ b/Changes @@ -18,6 +18,8 @@ Revision history for pgTAP * Added `pg_tapgen`, which is a start and automating pgTAP schema validation tests. * Added `is_empty()` to test that a query returns an empty set (no results). +* Added `collect_tap()`, which makes it easier to run multiple tests in a + single `SELECT` statement in combination with `skip()`. 0.22 2009-07-31T00:26:16 ------------------------- diff --git a/README.pgtap b/README.pgtap index e81644d1b478..474a5ad6e539 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3266,16 +3266,16 @@ have run. SELECT CASE WHEN pg_version_num() < 80100 THEN skip('throws_ok() not supported before 8.1', 2 ) - ELSE throws_ok( 'SELECT 1/0', 22012, 'division by zero' ) - || E'\n' - || throws_ok( 'INSERT INTO try (id) VALUES (1)', '23505' ) - END; + ELSE collect_tap( + throws_ok( 'SELECT 1/0', 22012, 'division by zero' ), + throws_ok( 'INSERT INTO try (id) VALUES (1)', '23505' ) + ) END; Note how use of the conditional `CASE` statement has been used to determine -whether or not to run a couple of tests. If they are to be run, they are -concatenated with newlines, so that we can run a few tests in the same query. -If we don't want to run them, we call `skip()` and tell it how many tests -we're skipping. +whether or not to run a couple of tests. If they are to be run, they are run +through `collect_tap()`, so that we can run a few tests in the same query. If +we don't want to run them, we call `skip()` and tell it how many tests we're +skipping. If you don't specify how many tests to skip, `skip()` will assume that you're skipping only one. This is useful for the simple case, of course: @@ -3445,6 +3445,35 @@ appreciated. **NOTE:** The values returned by this function may change in the future, depending on how good the pgTAP build process gets at detecting a OS. +### `collect_tap(tap[])` ### + + SELECT collect_tap( + ok(true, 'This should pass'), + ok(false, 'This should fail) + ); + +Collects the results of one or more pgTAP tests and returns them all. Useful +when used in combination with `skip()`: + + SELECT CASE os_name() WHEN 'darwin' THEN + collect_tap( + cmp_ok( 'Bjørn'::text, '>', 'Bjorn', 'ø > o' ), + cmp_ok( 'Pınar'::text, '>', 'Pinar', 'ı > i' ), + cmp_ok( 'José'::text, '>', 'Jose', 'é > e' ), + cmp_ok( 'Täp'::text, '>', 'Tap', 'ä > a' ) + ) + ELSE + skip('Collation-specific test', 4) + END; + +On PostgreSQL 8.4 and higher, it can take any number of arguments. Lower than +8.4 requires the explicit use of an array: + + SELECT collect_tap(ARRAY[ + ok(true, 'This should pass'), + ok(false, 'This should fail) + ]); + ### `pg_typeof(any)` ### SELECT pg_typeof(:value); @@ -3849,19 +3878,6 @@ To Do * Useful result testing functions to consider adding (but might require C code): + `row_eq()` + `rowtype_is()` -* Add a function to collect multiple tests in one call, someting like - `collect_tests()`. Will be useful when used in combination with `skip()`: - - SELECT CASE os_name() WHEN 'darwin' THEN - collect_tests( - cmp_ok( 'Bjørn'::text, '>', 'Bjorn', 'ø > o' ), - cmp_ok( 'Pınar'::text, '>', 'Pinar', 'ı > i' ), - cmp_ok( 'José'::text, '>', 'Jose', 'é > e' ), - cmp_ok( 'Täp'::text, '>', 'Tap', 'ä > a' ) - ) - ELSE - skip('Collation-specific test', 4) - END; Supported Versions ----------------- diff --git a/expected/util.out b/expected/util.out index 219766842a46..03a56faecf30 100644 --- a/expected/util.out +++ b/expected/util.out @@ -1,5 +1,5 @@ \unset ECHO -1..12 +1..13 ok 1 - pg_type(int) should work ok 2 - pg_type(numeric) should work ok 3 - pg_type(text) should work @@ -12,3 +12,4 @@ ok 9 - pg_version_num() should be correct ok 10 - os_name() should output something like an OS name ok 11 - findfincs() should return distinct values ok 12 - pgtap_version() should work +ok 13 - collect_tap() should simply collect tap diff --git a/pgtap.sql.in b/pgtap.sql.in index 99008c9840f7..ada15fedeb32 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -6473,3 +6473,10 @@ CREATE OR REPLACE FUNCTION is_empty( TEXT ) RETURNS TEXT AS $$ SELECT is_empty( $1, NULL ); $$ LANGUAGE sql; + +-- collect_tap( tap, tap, tap ) +CREATE OR REPLACE FUNCTION collect_tap( + VARIADIC tap text[] +) RETURNS TEXT AS $$ + SELECT array_to_string($1, E'\n'); +$$ LANGUAGE sql; diff --git a/sql/util.sql b/sql/util.sql index 51bac44c27a7..26fca4c89453 100644 --- a/sql/util.sql +++ b/sql/util.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(12); +SELECT plan(13); --SELECT * FROM no_plan(); SELECT is( pg_typeof(42), 'integer', 'pg_type(int) should work' ); @@ -58,8 +58,19 @@ SELECT matches( 'pgtap_version() should work' ); - /****************************************************************************/ +-- Test collect_tap(). +SELECT is( + CASE WHEN pg_version_num() >= 80400 + THEN collect_tap('foo', 'bar', 'baz') + ELSE collect_tap('{foo, bar, baz}') + END, + 'foo +bar +baz', + 'collect_tap() should simply collect tap' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index dcae4a10b8ba..5a70a3e39935 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,4 +1,5 @@ -- ## SET search_path TO TAPSCHEMA, public; +DROP FUNCTION collect_tap( TEXT[] ); DROP FUNCTION is_empty( TEXT ); DROP FUNCTION is_empty( TEXT, TEXT ); DROP FUNCTION isa_ok( anyelement, regtype ); From 988ec863c7584b54885ec50da706bbc390c29f8e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Nov 2009 15:23:00 +0900 Subject: [PATCH 0460/1195] Add `throws_like()` and friends. * `throws_like()` * `throws_ilike()` * `throws_matching()` * `throws_imatching()` Use these functions to test that an SQL statement throws an error message matching a `LIKE` pattern or a regular expression. --- Changes | 3 + README.pgtap | 69 ++++++++++++++++- expected/throwtap.out | 50 +++++++++++- pgtap.sql.in | 80 ++++++++++++++++++- sql/throwtap.sql | 172 ++++++++++++++++++++++++++++++++++++++++- uninstall_pgtap.sql.in | 11 ++- 6 files changed, 379 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index c4ec9d4906af..7935d382f41d 100644 --- a/Changes +++ b/Changes @@ -20,6 +20,9 @@ Revision history for pgTAP * Added `is_empty()` to test that a query returns an empty set (no results). * Added `collect_tap()`, which makes it easier to run multiple tests in a single `SELECT` statement in combination with `skip()`. +* Added `throws_like()`, `throws_ilike()`, `throws_matching()`, and + `throws_imatching()` to test that an SQL statement throws an error message + matching a `LIKE` pattern or a regular expression. 0.22 2009-07-31T00:26:16 ------------------------- diff --git a/README.pgtap b/README.pgtap index 474a5ad6e539..ef1b84bd13e7 100644 --- a/README.pgtap +++ b/README.pgtap @@ -701,6 +701,72 @@ example: Idea borrowed from the Test::Exception Perl module. +### `throws_like( query, pattern, description )` ### +### `throws_like( query, pattern )` ### + + PREPARE my_thrower AS INSERT INTO try (tz) VALUES ('America/Moscow'); + SELECT throws_like( + 'my_thrower', + '%"timezone_check"', + 'We should error for invalid time zone' + ); + +Like `throws_ok()`, but tests that an exception error message matches an SQL +`LIKE` pattern. Supported by 8.1 and up. + +A failing `throws_like()` test produces an appropriate diagnostic message. For +example: + + # Failed test 85: "We should error for invalid time zone" + # error message: 'value for domain timezone violates check constraint "tz_check"' + # doesn't match: '%"timezone_check"' + +### `throws_ilike( query, pattern, description )` ### +### `throws_ilike( query, pattern )` ### + + PREPARE my_thrower AS INSERT INTO try (tz) VALUES ('America/Moscow'); + SELECT throws_ilike( + 'my_thrower', + '%"TZ_check"', + 'We should error for invalid time zone' + ); + +Like `throws_like()`, but case-insensitively compares the exception message to +the SQL `LIKE` pattern. + +### `throws_matching( query, regex, description )` ### +### `throws_matching( query, regex )` ### + + PREPARE my_thrower AS INSERT INTO try (tz) VALUES ('America/Moscow'); + SELECT throws_matching( + 'my_thrower', + '.+"timezone_check"', + 'We should error for invalid time zone' + ); + +Like `throws_ok()`, but tests that an exception error message matches a +regular expression. Supported by 8.1 and up. + +A failing `throws_matching()` test produces an appropriate diagnostic message. For +example: + + # Failed test 85: "We should error for invalid time zone" + # error message: 'value for domain timezone violates check constraint "tz_check"' + # doesn't match: '.+"timezone_check"' + +### `throws_imatching( query, regex, description )` ### +### `throws_imatching( query, regex )` ### + + PREPARE my_thrower AS INSERT INTO try (tz) VALUES ('America/Moscow'); + SELECT throws_imatching( + 'my_thrower', + '.+"TZ_check"', + 'We should error for invalid time zone' + ); + +Like `throws_matching()`, but case-insensitively compares the exception +message to the regular expression. + ### `lives_ok( query, description )` ### ### `lives_ok( query )` ### @@ -3860,9 +3926,8 @@ No changes. Everything should just work. To Do ----- -* Have `has_function()` manage OUT, INOUT, and VARIATIC arguments. +* Have `has_function()` manage OUT, INOUT, and VARIADIC arguments. * Useful schema testing functions to consider adding: - + `throws_like()` + `sequence_has_range()` + `sequence_increments_by()` + `sequence_starts_at()` diff --git a/expected/throwtap.out b/expected/throwtap.out index c38035aa32c5..67a4933a637f 100644 --- a/expected/throwtap.out +++ b/expected/throwtap.out @@ -1,5 +1,5 @@ \unset ECHO -1..36 +1..84 ok 1 - four-argument form should pass ok 2 - four-argument form should have the proper description ok 3 - four-argument form should have the proper diagnostics @@ -36,3 +36,51 @@ ok 33 - lives_ok(execute) should have the proper description ok 34 - lives_ok failure diagnostics should fail ok 35 - lives_ok failure diagnostics should have the proper description ok 36 - lives_ok failure diagnostics should have the proper diagnostics +ok 37 - throws_like(sql, pattern, desc) should pass +ok 38 - throws_like(sql, pattern, desc) should have the proper description +ok 39 - throws_like(sql, pattern, desc) should have the proper diagnostics +ok 40 - throws_like(sql, pattern) should pass +ok 41 - throws_like(sql, pattern) should have the proper description +ok 42 - throws_like(sql, pattern) should have the proper diagnostics +ok 43 - throws_like(sql, pattern, desc) fail should fail +ok 44 - throws_like(sql, pattern, desc) fail should have the proper description +ok 45 - throws_like(sql, pattern, desc) fail should have the proper diagnostics +ok 46 - throws_like(valid sql, pattern, desc) should fail +ok 47 - throws_like(valid sql, pattern, desc) should have the proper description +ok 48 - throws_like(valid sql, pattern, desc) should have the proper diagnostics +ok 49 - throws_ilike(sql, pattern, desc) should pass +ok 50 - throws_ilike(sql, pattern, desc) should have the proper description +ok 51 - throws_ilike(sql, pattern, desc) should have the proper diagnostics +ok 52 - throws_ilike(sql, pattern) should pass +ok 53 - throws_ilike(sql, pattern) should have the proper description +ok 54 - throws_ilike(sql, pattern) should have the proper diagnostics +ok 55 - throws_ilike(sql, pattern, desc) fail should fail +ok 56 - throws_ilike(sql, pattern, desc) fail should have the proper description +ok 57 - throws_ilike(sql, pattern, desc) fail should have the proper diagnostics +ok 58 - throws_ilike(valid sql, pattern, desc) should fail +ok 59 - throws_ilike(valid sql, pattern, desc) should have the proper description +ok 60 - throws_ilike(valid sql, pattern, desc) should have the proper diagnostics +ok 61 - throws_matching(sql, regex, desc) should pass +ok 62 - throws_matching(sql, regex, desc) should have the proper description +ok 63 - throws_matching(sql, regex, desc) should have the proper diagnostics +ok 64 - throws_matching(sql, regex, desc) should pass +ok 65 - throws_matching(sql, regex, desc) should have the proper description +ok 66 - throws_matching(sql, regex, desc) should have the proper diagnostics +ok 67 - throws_matching(sql, regex, desc) should fail +ok 68 - throws_matching(sql, regex, desc) should have the proper description +ok 69 - throws_matching(sql, regex, desc) should have the proper diagnostics +ok 70 - throws_matching(valid sql, regex, desc) should fail +ok 71 - throws_matching(valid sql, regex, desc) should have the proper description +ok 72 - throws_matching(valid sql, regex, desc) should have the proper diagnostics +ok 73 - throws_imatching(sql, regex, desc) should pass +ok 74 - throws_imatching(sql, regex, desc) should have the proper description +ok 75 - throws_imatching(sql, regex, desc) should have the proper diagnostics +ok 76 - throws_imatching(sql, regex, desc) should pass +ok 77 - throws_imatching(sql, regex, desc) should have the proper description +ok 78 - throws_imatching(sql, regex, desc) should have the proper diagnostics +ok 79 - throws_imatching(sql, regex, desc) should fail +ok 80 - throws_imatching(sql, regex, desc) should have the proper description +ok 81 - throws_imatching(sql, regex, desc) should have the proper diagnostics +ok 82 - throws_imatching(valid sql, regex, desc) should fail +ok 83 - throws_imatching(valid sql, regex, desc) should have the proper description +ok 84 - throws_imatching(valid sql, regex, desc) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index ada15fedeb32..a8d20a49ad26 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -688,7 +688,7 @@ EXCEPTION WHEN OTHERS THEN -- The expected errcode and/or message was thrown. RETURN ok( TRUE, descr ); ELSE - -- This was not the expected errcodeor. + -- 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' ) || @@ -6480,3 +6480,81 @@ CREATE OR REPLACE FUNCTION collect_tap( ) 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; + diff --git a/sql/throwtap.sql b/sql/throwtap.sql index 0db145704f3d..ff7dd3330b6b 100644 --- a/sql/throwtap.sql +++ b/sql/throwtap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(36); +SELECT plan(84); --SELECT * FROM no_plan(); /****************************************************************************/ @@ -125,6 +125,176 @@ SELECT * FROM check_test( ' died: P0001: todo_end() called without todo_start()' ); +/****************************************************************************/ +-- test throws_like(). +SELECT * FROM check_test( + throws_like( 'SELECT * FROM todo_end()', '%end() called without todo%', 'whatever' ), + true, + 'throws_like(sql, pattern, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + throws_like( 'SELECT * FROM todo_end()', '%end() called without todo%' ), + true, + 'throws_like(sql, pattern)', + 'Should throw exception like ''%end() called without todo%''', + '' +); + +SELECT * FROM check_test( + throws_like( 'SELECT * FROM todo_end()', '%huh%', 'whatever' ), + false, + 'throws_like(sql, pattern, desc) fail', + 'whatever', + ' error message: ''todo_end() called without todo_start()'' + doesn''t match: ''%huh%''' +); + +SELECT * FROM check_test( + throws_like( 'SELECT 1', '%huh%', 'whatever' ), + false, + 'throws_like(valid sql, pattern, desc)', + 'whatever', + ' no exception thrown' +); + +/****************************************************************************/ +-- test throws_ilike(). +SELECT * FROM check_test( + throws_ilike( 'SELECT * FROM todo_end()', '%END() called without todo%', 'whatever' ), + true, + 'throws_ilike(sql, pattern, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + throws_ilike( 'SELECT * FROM todo_end()', '%END() called without todo%' ), + true, + 'throws_ilike(sql, pattern)', + 'Should throw exception like ''%END() called without todo%''', + '' +); + +SELECT * FROM check_test( + throws_ilike( 'SELECT * FROM todo_end()', '%HUH%', 'whatever' ), + false, + 'throws_ilike(sql, pattern, desc) fail', + 'whatever', + ' error message: ''todo_end() called without todo_start()'' + doesn''t match: ''%HUH%''' +); + +SELECT * FROM check_test( + throws_ilike( 'SELECT 1', '%HUH%', 'whatever' ), + false, + 'throws_ilike(valid sql, pattern, desc)', + 'whatever', + ' no exception thrown' +); + +/****************************************************************************/ +-- test throws_matching(). +SELECT * FROM check_test( + throws_matching( + 'SELECT * FROM todo_end()', + '.*end[(][)] called without todo.+', + 'whatever' + ), + true, + 'throws_matching(sql, regex, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + throws_matching( + 'SELECT * FROM todo_end()', + '.*end[(][)] called without todo.+' + ), + true, + 'throws_matching(sql, regex, desc)', + 'Should throw exception matching ''.*end[(][)] called without todo.+''', + '' +); + +SELECT * FROM check_test( + throws_matching( + 'SELECT * FROM todo_end()', + 'huh.+', + 'whatever' + ), + false, + 'throws_matching(sql, regex, desc)', + 'whatever', + ' error message: ''todo_end() called without todo_start()'' + doesn''t match: ''huh.+''' +); + +SELECT * FROM check_test( + throws_matching( + 'SELECT 1', + 'huh.+', + 'whatever' + ), + false, + 'throws_matching(valid sql, regex, desc)', + 'whatever', + ' no exception thrown' +); + +/****************************************************************************/ +-- test throws_imatching(). +SELECT * FROM check_test( + throws_imatching( + 'SELECT * FROM todo_end()', + '.*end[(][)] CALLED without todo.+', + 'whatever' + ), + true, + 'throws_imatching(sql, regex, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + throws_imatching( + 'SELECT * FROM todo_end()', + '.*end[(][)] CALLED without todo.+' + ), + true, + 'throws_imatching(sql, regex, desc)', + 'Should throw exception matching ''.*end[(][)] CALLED without todo.+''', + '' +); + +SELECT * FROM check_test( + throws_imatching( + 'SELECT * FROM todo_end()', + 'HUH.+', + 'whatever' + ), + false, + 'throws_imatching(sql, regex, desc)', + 'whatever', + ' error message: ''todo_end() called without todo_start()'' + doesn''t match: ''HUH.+''' +); + +SELECT * FROM check_test( + throws_imatching( + 'SELECT 1', + 'HUH.+', + 'whatever' + ), + false, + 'throws_imatching(valid sql, regex, desc)', + 'whatever', + ' no exception thrown' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 5a70a3e39935..dcbd6a102670 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,5 +1,14 @@ -- ## SET search_path TO TAPSCHEMA, public; -DROP FUNCTION collect_tap( TEXT[] ); +DROP FUNCTION throws_imatching ( TEXT, TEXT ); +DROP FUNCTION throws_imatching ( TEXT, TEXT, TEXT ); +DROP FUNCTION throws_matching ( TEXT, TEXT ); +DROP FUNCTION throws_matching ( TEXT, TEXT, TEXT ); +DROP FUNCTION throws_ilike ( TEXT, TEXT ); +DROP FUNCTION throws_ilike ( TEXT, TEXT, TEXT ); +DROP FUNCTION throws_like ( TEXT, TEXT ); +DROP FUNCTION throws_like ( TEXT, TEXT, TEXT ); +DROP FUNCTION _tlike ( BOOLEAN, TEXT, TEXT, TEXT ); +DROP FUNCTION collect_tap(; DROP FUNCTION is_empty( TEXT ); DROP FUNCTION is_empty( TEXT, TEXT ); DROP FUNCTION isa_ok( anyelement, regtype ); From 54f5904860534090f7d96d56a9e0c6f541100f65 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Nov 2009 15:45:28 +0900 Subject: [PATCH 0461/1195] Add `roles_are()`. --- Changes | 1 + README.pgtap | 36 ++++++++++++++++++++++++---- expected/roletap.out | 17 ++++++++++++- pgtap.sql.in | 29 +++++++++++++++++++++++ sql/roletap.sql | 54 +++++++++++++++++++++++++++++++++++++++++- uninstall_pgtap.sql.in | 2 ++ 6 files changed, 132 insertions(+), 7 deletions(-) diff --git a/Changes b/Changes index 7935d382f41d..98f819b218aa 100644 --- a/Changes +++ b/Changes @@ -23,6 +23,7 @@ Revision history for pgTAP * Added `throws_like()`, `throws_ilike()`, `throws_matching()`, and `throws_imatching()` to test that an SQL statement throws an error message matching a `LIKE` pattern or a regular expression. +* Added `roles_are()`. 0.22 2009-07-31T00:26:16 ------------------------- diff --git a/README.pgtap b/README.pgtap index ef1b84bd13e7..e2f01b6c732d 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1454,6 +1454,25 @@ missing functions, like so: # Missing functions: # frobnitz +### `roles_are( roles[], description )` ### +### `roles_are( roles[] )` ### + + SELECT roles_are( + ARRAY[ 'postgres', 'someone', 'root' ], + 'Should have the correct roles' + ); + +This function tests that all of the roles in the database only the roles that +*should* be there. Supported in PostgreSQL 8.1 and higher. In the event of a +failure, you'll see diagnostics listing the extra and/or missing roles, like +so: + + # Failed test 195: "There should be the correct roles" + # Extra roles: + # root + # Missing roles: + # bobby + ### `users_are( users[], description )` ### ### `users_are( users[] )` ### @@ -3915,14 +3934,22 @@ No changes. Everything should just work. + `regtype` to `text` * Two operators, `=` and `<>`, are added to compare `name[]` values. -8.0 and Lower -------------- +8.0 +--- * A patch is applied that changes how some of the test functions are written. * A few casts are added for compatibility: + `oidvector` to `regtypep[]`. + `int2vector` to `integer[]`. -* The `throws_ok()`, `lives_ok()`, `runtests()`, `has_role()`, and - `hasnt_role()` functions do not work under 8.0. Don't even use them there. +* The following functions do not work under 8.0. Don't even use them there: +* `throws_ok()` +* `throws_like()` +* `throws_ilike()` +* `throws_matching()` +* `throws_imatching()` +* `lives_ok()` +* `runtests()` +* `has_role()` +* `hasnt_role()` To Do ----- @@ -3932,7 +3959,6 @@ To Do + `sequence_increments_by()` + `sequence_starts_at()` + `sequence_cycles()` - + `roles_are()` + `types_are()` + `domains_are()` + `enums_are()` diff --git a/expected/roletap.out b/expected/roletap.out index 52afede853a1..69c2ed9189dd 100644 --- a/expected/roletap.out +++ b/expected/roletap.out @@ -1,5 +1,5 @@ \unset ECHO -1..24 +1..39 ok 1 - has_role(current role) should pass ok 2 - has_role(current role) should have the proper description ok 3 - has_role(current role) should have the proper diagnostics @@ -24,3 +24,18 @@ ok 21 - hasnt_role(nonexistent role) should have the proper diagnostics ok 22 - hasnt_role(nonexistent role, desc) should pass ok 23 - hasnt_role(nonexistent role, desc) should have the proper description ok 24 - hasnt_role(nonexistent role, desc) should have the proper diagnostics +ok 25 - roles_are(roles, desc) should pass +ok 26 - roles_are(roles, desc) should have the proper description +ok 27 - roles_are(roles, desc) should have the proper diagnostics +ok 28 - roles_are(roles) should pass +ok 29 - roles_are(roles) should have the proper description +ok 30 - roles_are(roles) should have the proper diagnostics +ok 31 - roles_are(roles, desc) missing should fail +ok 32 - roles_are(roles, desc) missing should have the proper description +ok 33 - roles_are(roles, desc) missing should have the proper diagnostics +ok 34 - roles_are(roles, desc) extras should fail +ok 35 - roles_are(roles, desc) extras should have the proper description +ok 36 - roles_are(roles, desc) extras should have the proper diagnostics +ok 37 - roles_are(roles, desc) missing and extras should fail +ok 38 - roles_are(roles, desc) missing and extras should have the proper description +ok 39 - roles_are(roles, desc) missing and extras should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index a8d20a49ad26..01b0b760de3e 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -6558,3 +6558,32 @@ 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; + diff --git a/sql/roletap.sql b/sql/roletap.sql index e71a5fffaa6a..960457d28047 100644 --- a/sql/roletap.sql +++ b/sql/roletap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(24); +SELECT plan(39); --SELECT * FROM no_plan(); /****************************************************************************/ @@ -65,6 +65,58 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test roles_are(). + +CREATE FUNCTION ___myroles(ex text) RETURNS NAME[] AS $$ + SELECT COALESCE(ARRAY( SELECT rolname FROM pg_catalog.pg_roles WHERE rolname <> $1 ), '{}'::name[]);; +$$ LANGUAGE SQL; + +SELECT * FROM check_test( + roles_are( ___myroles(''), 'whatever' ), + true, + 'roles_are(roles, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + roles_are( ___myroles('') ), + true, + 'roles_are(roles)', + 'There should be the correct roles', + '' +); + +SELECT * FROM check_test( + roles_are( array_append(___myroles(''), '__howdy__'), 'whatever' ), + false, + 'roles_are(roles, desc) missing', + 'whatever', + ' Missing roles: + __howdy__' +); + +SELECT * FROM check_test( + roles_are( ___myroles(current_role), 'whatever' ), + false, + 'roles_are(roles, desc) extras', + 'whatever', + ' Extra roles: + ' || current_role +); + +SELECT * FROM check_test( + roles_are( array_append(___myroles(current_role), '__howdy__'), 'whatever' ), + false, + 'roles_are(roles, desc) missing and extras', + 'whatever', + ' Extra roles: + ' || current_role || ' + Missing roles: + __howdy__' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index dcbd6a102670..6ec24bb7a020 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,4 +1,6 @@ -- ## SET search_path TO TAPSCHEMA, public; +DROP FUNCTION roles_are( NAME[] ); +DROP FUNCTION roles_are( NAME[], TEXT ); DROP FUNCTION throws_imatching ( TEXT, TEXT ); DROP FUNCTION throws_imatching ( TEXT, TEXT, TEXT ); DROP FUNCTION throws_matching ( TEXT, TEXT ); From 2edd599b5469c880161fb7e107ff45ac2aac81c8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Nov 2009 17:27:45 +0900 Subject: [PATCH 0462/1195] Add `types_are()`. --- Changes | 2 +- README.pgtap | 33 +++++++++++-- expected/aretap.out | 28 ++++++++++- pgtap.sql.in | 88 +++++++++++++++++++++++++++++++++ sql/aretap.sql | 108 ++++++++++++++++++++++++++++++++++++++++- uninstall_pgtap.sql.in | 4 ++ 6 files changed, 256 insertions(+), 7 deletions(-) diff --git a/Changes b/Changes index 98f819b218aa..bac6e73839ae 100644 --- a/Changes +++ b/Changes @@ -23,7 +23,7 @@ Revision history for pgTAP * Added `throws_like()`, `throws_ilike()`, `throws_matching()`, and `throws_imatching()` to test that an SQL statement throws an error message matching a `LIKE` pattern or a regular expression. -* Added `roles_are()`. +* Added `roles_are()` and `types_are()`. 0.22 2009-07-31T00:26:16 ------------------------- diff --git a/README.pgtap b/README.pgtap index e2f01b6c732d..d1e99e4d4fa0 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1567,9 +1567,9 @@ missing opclasses, like so: This function tests that all of the rules on the named relation are only the rules that *should* be on that relation (a table or a view). If the `:schema` -argument is omitted, the relation must be visible in the search path, -excluding `pg_catalog`. If the description is omitted, a generally useful -default description will be generated. +argument is omitted, the rules must be visible in the search path, excluding +`pg_catalog`. If the description is omitted, a generally useful default +description will be generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing rules, like so: @@ -1580,6 +1580,32 @@ missing rules, like so: # Missing rules: # on_delete +### `types_are( schema, types[], description )` ### +### `types_are( schema, types[] )` ### +### `types_are( types[], description )` ### +### `types_are( types[] )` ### + + SELECT types_are( + 'myschema', + ARRAY[ 'timezone', 'state' ], + 'Should have the correct types in myschema' + ); + +Tests that all of the types in the named schema are the only types in that +schema. If the `:schema` argument is omitted, the types must be visible in the +search path, excluding `pg_catalog` and `information_schema`. If the +description is omitted, a generally useful default description will be +generated. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing types, like so: + + # Failed test 307: "Schema someschema should have the correct types" + # Extra types: + # sometype + # Missing types: + # timezone + To Have or Have Not ------------------- @@ -3959,7 +3985,6 @@ To Do + `sequence_increments_by()` + `sequence_starts_at()` + `sequence_cycles()` - + `types_are()` + `domains_are()` + `enums_are()` + `triggers_are()` diff --git a/expected/aretap.out b/expected/aretap.out index e8ab27a97ec6..304384c793cf 100644 --- a/expected/aretap.out +++ b/expected/aretap.out @@ -1,5 +1,5 @@ \unset ECHO -1..280 +1..306 ok 1 - tablespaces_are(schemas, desc) should pass ok 2 - tablespaces_are(schemas, desc) should have the proper description ok 3 - tablespaces_are(schemas, desc) should have the proper diagnostics @@ -280,3 +280,29 @@ ok 277 - rules_are(table, rules) + missing should have the proper diagnostics ok 278 - rules_are(table, rules) + extra & missing should fail ok 279 - rules_are(table, rules) + extra & missing should have the proper description ok 280 - rules_are(table, rules) + extra & missing should have the proper diagnostics +ok 281 - types_are(schema, types, desc) should pass +ok 282 - types_are(schema, types, desc) should have the proper description +ok 283 - types_are(schema, types, desc) should have the proper diagnostics +ok 284 - types_are(schema, types) should pass +ok 285 - types_are(schema, types) should have the proper description +ok 286 - types_are(schema, types, desc) + missing should fail +ok 287 - types_are(schema, types, desc) + missing should have the proper description +ok 288 - types_are(schema, types, desc) + missing should have the proper diagnostics +ok 289 - types_are(schema, types, desc) + extra should fail +ok 290 - types_are(schema, types, desc) + extra should have the proper description +ok 291 - types_are(schema, types, desc) + extra should have the proper diagnostics +ok 292 - types_are(schema, types, desc) + extra & missing should fail +ok 293 - types_are(schema, types, desc) + extra & missing should have the proper description +ok 294 - types_are(schema, types, desc) + extra & missing should have the proper diagnostics +ok 295 - types_are(types) should pass +ok 296 - types_are(types) should have the proper description +ok 297 - types_are(types) should have the proper diagnostics +ok 298 - types_are(types, desc) + missing should fail +ok 299 - types_are(types, desc) + missing should have the proper description +ok 300 - types_are(types, desc) + missing should have the proper diagnostics +ok 301 - types_are(types, desc) + extra should fail +ok 302 - types_are(types, desc) + extra should have the proper description +ok 303 - types_are(types, desc) + extra should have the proper diagnostics +ok 304 - types_are(types, desc) + extra & missing should fail +ok 305 - types_are(types, desc) + extra & missing should have the proper description +ok 306 - types_are(types, desc) + extra & missing should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 01b0b760de3e..4acb7fc9c5ab 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -6587,3 +6587,91 @@ RETURNS TEXT AS $$ SELECT roles_are( $1, 'There should be the correct roles' ); $$ LANGUAGE SQL; +-- types_are( schema, types[], description ) +CREATE OR REPLACE FUNCTION types_are ( NAME, NAME[], TEXT ) +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 + 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.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 + ), + $3 + ); +$$ 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' ); +$$ LANGUAGE SQL; + +-- types_are( types[], description ) +CREATE OR REPLACE FUNCTION types_are ( NAME[], TEXT ) +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) + 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 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) + ), + $2 + ); +$$ 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' ); +$$ LANGUAGE SQL; + diff --git a/sql/aretap.sql b/sql/aretap.sql index 46f6611ea7d1..d11b87cfbc77 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(280); +SELECT plan(306); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -55,6 +55,11 @@ DEFAULT FOR TYPE goofy USING BTREE AS FUNCTION 1 goofy_cmp(goofy,goofy) ; +CREATE TYPE someschema.sometype AS ( + id INT, + name TEXT +); + RESET client_min_messages; /****************************************************************************/ @@ -1008,6 +1013,107 @@ SELECT * FROM check_test( del_me' ); +/****************************************************************************/ +-- Test types_are(). + +SELECT * FROM check_test( + types_are( 'someschema', ARRAY['sometype'], 'whatever' ), + true, + 'types_are(schema, types, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + types_are( 'someschema', ARRAY['sometype'] ), + true, + 'types_are(schema, types)', + 'Schema someschema should have the correct types' + '' +); + +SELECT * FROM check_test( + types_are( 'someschema', ARRAY['sometype', 'typo'], 'whatever' ), + false, + 'types_are(schema, types, desc) + missing', + 'whatever', + ' Missing types: + typo' +); + +SELECT * FROM check_test( + types_are( 'someschema', '{}'::name[], 'whatever' ), + false, + 'types_are(schema, types, desc) + extra', + 'whatever', + ' Extra types: + sometype' +); + +SELECT * FROM check_test( + types_are( 'someschema', ARRAY['typo'], 'whatever' ), + false, + 'types_are(schema, types, desc) + extra & missing', + 'whatever', + ' Extra types: + sometype + Missing types: + typo' +); + +CREATE FUNCTION ___mytype(ex text) RETURNS NAME[] AS $$ + SELECT COALESCE(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 t.typname <> $1 + AND pg_catalog.pg_type_is_visible(t.oid) + ), '{}'::name[]);; +$$ LANGUAGE SQL; + +SELECT * FROM check_test( + types_are( ___mytype('') ), + true, + 'types_are(types)', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct types', + '' +); + +SELECT * FROM check_test( + types_are( array_append(___mytype(''), 'silltypo'), 'whatever' ), + false, + 'types_are(types, desc) + missing', + 'whatever', + ' Missing types: + silltypo' +); + +SELECT * FROM check_test( + types_are( ___mytype('goofy'), 'whatever' ), + false, + 'types_are(types, desc) + extra', + 'whatever', + ' Extra types: + goofy' +); + +SELECT * FROM check_test( + types_are( array_append(___mytype('goofy'), 'silltypo'), 'whatever' ), + false, + 'types_are(types, desc) + extra & missing', + 'whatever', + ' Extra types: + goofy + Missing types: + silltypo' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 6ec24bb7a020..a0b63e191d52 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,4 +1,8 @@ -- ## SET search_path TO TAPSCHEMA, public; +DROP FUNCTION types_are ( NAME[] ); +DROP FUNCTION types_are ( NAME[], TEXT ); +DROP FUNCTION types_are ( NAME, NAME[] ); +DROP FUNCTION types_are ( NAME, NAME[], TEXT ); DROP FUNCTION roles_are( NAME[] ); DROP FUNCTION roles_are( NAME[], TEXT ); DROP FUNCTION throws_imatching ( TEXT, TEXT ); From af27dbf85637931ead71517fc04668d1a018ff4d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Nov 2009 22:14:38 +0900 Subject: [PATCH 0463/1195] Add `domains_are()` and `enums_are()`. --- Changes | 2 +- README.pgtap | 65 +++++++++++++++++++++++++--- expected/aretap.out | 26 ++++++++++- expected/enumtap.out | 26 ++++++++++- pgtap.sql.in | 74 ++++++++++++++++++++++++++++--- sql/aretap.sql | 98 +++++++++++++++++++++++++++++++++++++++++- sql/enumtap.sql | 98 +++++++++++++++++++++++++++++++++++++++++- uninstall_pgtap.sql.in | 10 +++++ 8 files changed, 382 insertions(+), 17 deletions(-) diff --git a/Changes b/Changes index bac6e73839ae..1bcfed3a9f6e 100644 --- a/Changes +++ b/Changes @@ -23,7 +23,7 @@ Revision history for pgTAP * Added `throws_like()`, `throws_ilike()`, `throws_matching()`, and `throws_imatching()` to test that an SQL statement throws an error message matching a `LIKE` pattern or a regular expression. -* Added `roles_are()` and `types_are()`. +* Added `roles_are()`, `types_are()`, `domains_are()`, and `enums_are()`. 0.22 2009-07-31T00:26:16 ------------------------- diff --git a/README.pgtap b/README.pgtap index d1e99e4d4fa0..3fb52cb91a3a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1592,8 +1592,9 @@ missing rules, like so: ); Tests that all of the types in the named schema are the only types in that -schema. If the `:schema` argument is omitted, the types must be visible in the -search path, excluding `pg_catalog` and `information_schema`. If the +schema, including base types, composite types, domains, enums, and +pseudo-types. If the `:schema` argument is omitted, the types must be visible +in the search path, excluding `pg_catalog` and `information_schema`. If the description is omitted, a generally useful default description will be generated. @@ -1606,6 +1607,58 @@ missing types, like so: # Missing types: # timezone +### `domains_are( schema, domains[], description )` ### +### `domains_are( schema, domains[] )` ### +### `domains_are( domains[], description )` ### +### `domains_are( domains[] )` ### + + SELECT domains_are( + 'myschema', + ARRAY[ 'timezone', 'state' ], + 'Should have the correct domains in myschema' + ); + +Tests that all of the domains in the named schema are the only domains in that +schema. If the `:schema` argument is omitted, the domains must be visible in +the search path, excluding `pg_catalog` and `information_schema`. If the +description is omitted, a generally useful default description will be +generated. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing domains, like so: + + # Failed test 327: "Schema someschema should have the correct domains" + # Extra domains: + # somedomain + # Missing domains: + # timezone + +### `enums_are( schema, enums[], description )` ### +### `enums_are( schema, enums[] )` ### +### `enums_are( enums[], description )` ### +### `enums_are( enums[] )` ### + + SELECT enums_are( + 'myschema', + ARRAY[ 'timezone', 'state' ], + 'Should have the correct enums in myschema' + ); + +Tests that all of the enums in the named schema are the only enums in that +schema. Enums are supported in PostgreSQL 8.3 and up. If the `:schema` +argument is omitted, the enums must be visible in the search path, excluding +`pg_catalog` and `information_schema`. If the description is omitted, a +generally useful default description will be generated. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing enums, like so: + + # Failed test 333: "Schema someschema should have the correct enums" + # Extra enums: + # someenum + # Missing enums: + # bug_status + To Have or Have Not ------------------- @@ -3951,9 +4004,9 @@ No changes. Everything should just work. ------------ * A patch is applied that removes the `enum_has_labels()` function, and `col_has_default()` cannot be used to test for columns specified with - `DEFAULT NULL` (even though that's the implied default default). Also, a - number of assignments casts are added to increase compatibility. The casts - are: + `DEFAULT NULL` (even though that's the implied default default). The + `has_enums()` function won't work. Also, a number of assignments casts are + added to increase compatibility. The casts are: + `boolean` to `text` + `text[]` to `text` + `name[]` to `text` @@ -3985,8 +4038,6 @@ To Do + `sequence_increments_by()` + `sequence_starts_at()` + `sequence_cycles()` - + `domains_are()` - + `enums_are()` + `triggers_are()` + `casts_are()` + `operators_are()` diff --git a/expected/aretap.out b/expected/aretap.out index 304384c793cf..49e0bf5a4d5e 100644 --- a/expected/aretap.out +++ b/expected/aretap.out @@ -1,5 +1,5 @@ \unset ECHO -1..306 +1..330 ok 1 - tablespaces_are(schemas, desc) should pass ok 2 - tablespaces_are(schemas, desc) should have the proper description ok 3 - tablespaces_are(schemas, desc) should have the proper diagnostics @@ -306,3 +306,27 @@ ok 303 - types_are(types, desc) + extra should have the proper diagnostics ok 304 - types_are(types, desc) + extra & missing should fail ok 305 - types_are(types, desc) + extra & missing should have the proper description ok 306 - types_are(types, desc) + extra & missing should have the proper diagnostics +ok 307 - domains_are(schema, domains, desc) should pass +ok 308 - domains_are(schema, domains, desc) should have the proper description +ok 309 - domains_are(schema, domains, desc) should have the proper diagnostics +ok 310 - domains_are(schema, domains) should pass +ok 311 - domains_are(schema, domains) should have the proper description +ok 312 - domains_are(schema, domains) should have the proper diagnostics +ok 313 - domains_are(schema, domains, desc) fail should fail +ok 314 - domains_are(schema, domains, desc) fail should have the proper description +ok 315 - domains_are(schema, domains, desc) fail should have the proper diagnostics +ok 316 - domains_are(schema, domains) fail should fail +ok 317 - domains_are(schema, domains) fail should have the proper description +ok 318 - domains_are(schema, domains) fail should have the proper diagnostics +ok 319 - domains_are(domains, desc) should pass +ok 320 - domains_are(domains, desc) should have the proper description +ok 321 - domains_are(domains, desc) should have the proper diagnostics +ok 322 - domains_are(domains) should pass +ok 323 - domains_are(domains) should have the proper description +ok 324 - domains_are(domains) should have the proper diagnostics +ok 325 - domains_are(domains, desc) fail should fail +ok 326 - domains_are(domains, desc) fail should have the proper description +ok 327 - domains_are(domains, desc) fail should have the proper diagnostics +ok 328 - domains_are(domains) fail should fail +ok 329 - domains_are(domains) fail should have the proper description +ok 330 - domains_are(domains) fail should have the proper diagnostics diff --git a/expected/enumtap.out b/expected/enumtap.out index 31ee0de3917f..084604a0a8ba 100644 --- a/expected/enumtap.out +++ b/expected/enumtap.out @@ -1,5 +1,5 @@ \unset ECHO -1..72 +1..96 ok 1 - has_type(enum) should pass ok 2 - has_type(enum) should have the proper description ok 3 - has_type(enum) should have the proper diagnostics @@ -72,3 +72,27 @@ ok 69 - enum_has_labels(schema, enum, labels, desc) fail should have the proper ok 70 - enum_has_labels(enum, labels, desc) fail should fail ok 71 - enum_has_labels(enum, labels, desc) fail should have the proper description ok 72 - enum_has_labels(enum, labels, desc) fail should have the proper diagnostics +ok 73 - enums_are(schema, enums, desc) should pass +ok 74 - enums_are(schema, enums, desc) should have the proper description +ok 75 - enums_are(schema, enums, desc) should have the proper diagnostics +ok 76 - enums_are(schema, enums) should pass +ok 77 - enums_are(schema, enums) should have the proper description +ok 78 - enums_are(schema, enums) should have the proper diagnostics +ok 79 - enums_are(schema, enums, desc) fail should fail +ok 80 - enums_are(schema, enums, desc) fail should have the proper description +ok 81 - enums_are(schema, enums, desc) fail should have the proper diagnostics +ok 82 - enums_are(schema, enums) fail should fail +ok 83 - enums_are(schema, enums) fail should have the proper description +ok 84 - enums_are(schema, enums) fail should have the proper diagnostics +ok 85 - enums_are(enums, desc) should pass +ok 86 - enums_are(enums, desc) should have the proper description +ok 87 - enums_are(enums, desc) should have the proper diagnostics +ok 88 - enums_are(enums) should pass +ok 89 - enums_are(enums) should have the proper description +ok 90 - enums_are(enums) should have the proper diagnostics +ok 91 - enums_are(enums, desc) fail should fail +ok 92 - enums_are(enums, desc) fail should have the proper description +ok 93 - enums_are(enums, desc) fail should have the proper diagnostics +ok 94 - enums_are(enums) fail should fail +ok 95 - enums_are(enums) fail should have the proper description +ok 96 - enums_are(enums) fail should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 4acb7fc9c5ab..0b7147e136b2 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -6587,8 +6587,7 @@ RETURNS TEXT AS $$ SELECT roles_are( $1, 'There should be the correct roles' ); $$ LANGUAGE SQL; --- types_are( schema, types[], description ) -CREATE OR REPLACE FUNCTION types_are ( NAME, NAME[], TEXT ) +CREATE OR REPLACE FUNCTION _types_are ( NAME, NAME[], TEXT, CHAR[] ) RETURNS TEXT AS $$ SELECT _are( 'types', @@ -6602,6 +6601,7 @@ RETURNS TEXT AS $$ ) 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 $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) @@ -6619,19 +6619,26 @@ RETURNS TEXT AS $$ ) 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' ); + 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 ) +CREATE OR REPLACE FUNCTION _types_are ( NAME[], TEXT, CHAR[] ) RETURNS TEXT AS $$ SELECT _are( 'types', @@ -6646,6 +6653,7 @@ RETURNS TEXT AS $$ 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 $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) @@ -6664,14 +6672,70 @@ RETURNS TEXT AS $$ 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' ); + 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; diff --git a/sql/aretap.sql b/sql/aretap.sql index d11b87cfbc77..30686ab02514 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(306); +SELECT plan(330); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -1114,6 +1114,102 @@ SELECT * FROM check_test( silltypo' ); +/****************************************************************************/ +-- Test domains_are(). + +SELECT * FROM check_test( + domains_are( 'public', ARRAY['goofy'], 'whatever' ), + true, + 'domains_are(schema, domains, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + domains_are( 'public', ARRAY['goofy'] ), + true, + 'domains_are(schema, domains)', + 'Schema public should have the correct domains', + '' +); + +SELECT * FROM check_test( + domains_are( 'public', ARRAY['freddy'], 'whatever' ), + false, + 'domains_are(schema, domains, desc) fail', + 'whatever', + ' Extra types: + goofy + Missing types: + freddy' +); + +SELECT * FROM check_test( + domains_are( 'public', ARRAY['freddy'] ), + false, + 'domains_are(schema, domains) fail', + 'Schema public should have the correct domains', + ' Extra types: + goofy + Missing types: + freddy' +); + +CREATE FUNCTION ___mydo(ex text) RETURNS NAME[] AS $$ + SELECT COALESCE(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 t.typname <> $1 + AND pg_catalog.pg_type_is_visible(t.oid) + AND t.typtype = 'd' + ), '{}'::name[]); +$$ LANGUAGE SQL; + +SELECT * FROM check_test( + domains_are( ___mydo(''), 'whatever' ), + true, + 'domains_are(domains, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + domains_are( ___mydo('') ), + true, + 'domains_are(domains)', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct domains', + '' +); + +SELECT * FROM check_test( + domains_are( array_append(___mydo('goofy'), 'fredy'), 'whatever' ), + false, + 'domains_are(domains, desc) fail', + 'whatever', + ' Extra types: + goofy + Missing types: + fredy' +); + +SELECT * FROM check_test( + domains_are( array_append(___mydo('goofy'), 'fredy') ), + false, + 'domains_are(domains) fail', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct domains', + ' Extra types: + goofy + Missing types: + fredy' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/sql/enumtap.sql b/sql/enumtap.sql index f21ea850b57b..158098fc536d 100644 --- a/sql/enumtap.sql +++ b/sql/enumtap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(72); +SELECT plan(96); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -199,6 +199,102 @@ SELECT * FROM check_test( want: {new,closed,open}' ); +/****************************************************************************/ +-- Test enums_are(). + +SELECT * FROM check_test( + enums_are( 'public', ARRAY['bug_status'], 'whatever' ), + true, + 'enums_are(schema, enums, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + enums_are( 'public', ARRAY['bug_status'] ), + true, + 'enums_are(schema, enums)', + 'Schema public should have the correct enums', + '' +); + +SELECT * FROM check_test( + enums_are( 'public', ARRAY['freddy'], 'whatever' ), + false, + 'enums_are(schema, enums, desc) fail', + 'whatever', + ' Extra types: + bug_status + Missing types: + freddy' +); + +SELECT * FROM check_test( + enums_are( 'public', ARRAY['freddy'] ), + false, + 'enums_are(schema, enums) fail', + 'Schema public should have the correct enums', + ' Extra types: + bug_status + Missing types: + freddy' +); + +CREATE FUNCTION ___myenum(ex text) RETURNS NAME[] AS $$ + SELECT COALESCE(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 t.typname <> $1 + AND pg_catalog.pg_type_is_visible(t.oid) + AND t.typtype = 'e' + ), '{}'::name[]); +$$ LANGUAGE SQL; + +SELECT * FROM check_test( + enums_are( ___myenum(''), 'whatever' ), + true, + 'enums_are(enums, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + enums_are( ___myenum('') ), + true, + 'enums_are(enums)', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct enums', + '' +); + +SELECT * FROM check_test( + enums_are( array_append(___myenum('bug_status'), 'fredy'), 'whatever' ), + false, + 'enums_are(enums, desc) fail', + 'whatever', + ' Extra types: + bug_status + Missing types: + fredy' +); + +SELECT * FROM check_test( + enums_are( array_append(___myenum('bug_status'), 'fredy') ), + false, + 'enums_are(enums) fail', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct enums', + ' Extra types: + bug_status + Missing types: + fredy' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index a0b63e191d52..5fb7b382469b 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,8 +1,18 @@ -- ## SET search_path TO TAPSCHEMA, public; +DROP FUNCTION enums_are ( NAME[] ); +DROP FUNCTION enums_are ( NAME[], TEXT ); +DROP FUNCTION enums_are ( NAME, NAME[] ); +DROP FUNCTION enums_are ( NAME, NAME[], TEXT ); +DROP FUNCTION domains_are ( NAME[] ); +DROP FUNCTION domains_are ( NAME[], TEXT ); +DROP FUNCTION domains_are ( NAME, NAME[] ); +DROP FUNCTION domains_are ( NAME, NAME[], TEXT ); DROP FUNCTION types_are ( NAME[] ); DROP FUNCTION types_are ( NAME[], TEXT ); +DROP FUNCTION _types_are ( NAME[], TEXT, CHAR[] ); DROP FUNCTION types_are ( NAME, NAME[] ); DROP FUNCTION types_are ( NAME, NAME[], TEXT ); +DROP FUNCTION _types_are ( NAME, NAME[], TEXT, CHAR[] ); DROP FUNCTION roles_are( NAME[] ); DROP FUNCTION roles_are( NAME[], TEXT ); DROP FUNCTION throws_imatching ( TEXT, TEXT ); From b7cb2d32d8afb67bb474dd5c035e1b905733038c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 21 Nov 2009 13:07:49 +0900 Subject: [PATCH 0464/1195] Note bugfix from dukeleto. --- Changes | 1 + 1 file changed, 1 insertion(+) diff --git a/Changes b/Changes index 1bcfed3a9f6e..576a98075596 100644 --- a/Changes +++ b/Changes @@ -17,6 +17,7 @@ Revision history for pgTAP * Added support for building against 8.5 [Dan Colish]. * Added `pg_tapgen`, which is a start and automating pgTAP schema validation tests. +* Fixed failing test on alpha releases [Jonathan Leto] * Added `is_empty()` to test that a query returns an empty set (no results). * Added `collect_tap()`, which makes it easier to run multiple tests in a single `SELECT` statement in combination with `skip()`. From 7728b643d001f2fba7686a6ee3901ded65795ce8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 2 Dec 2009 15:43:44 -0800 Subject: [PATCH 0465/1195] Add `domain_type_is()` and `domain_type_isnt()`. Also fixed up the diagnostic output from `isnt()` while I was at it. It was just wrong. --- Changes | 5 + README.pgtap | 52 ++++++++++ expected/hastap.out | 74 +++++++++++++- pgtap.sql.in | 216 ++++++++++++++++++++++++++++++++++++++++- sql/hastap.sql | 202 +++++++++++++++++++++++++++++++++++++- sql/istap.sql | 5 +- sql/pktap.sql | 15 ++- uninstall_pgtap.sql.in | 16 +++ 8 files changed, 568 insertions(+), 17 deletions(-) diff --git a/Changes b/Changes index 576a98075596..834eb28e837e 100644 --- a/Changes +++ b/Changes @@ -25,6 +25,11 @@ Revision history for pgTAP `throws_imatching()` to test that an SQL statement throws an error message matching a `LIKE` pattern or a regular expression. * Added `roles_are()`, `types_are()`, `domains_are()`, and `enums_are()`. +* Fixed the diagnostic output from `isnt()` and `col_isnt_pk()` when they + fail. They now more closely matches what Test::More's `isnt()` outputs and + makes much more sense. +* Added `domain_type_is()` and `domain_type_isnt()`. Based on an + implementation by Bob Lunney. 0.22 2009-07-31T00:26:16 ------------------------- diff --git a/README.pgtap b/README.pgtap index 3fb52cb91a3a..b74c65ed7fcc 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3241,6 +3241,58 @@ fourth a description. If you omit the schema, the enum must be visible in the search path. If you omit the test description, it will be set to "Enum `:enum` should have labels (`:labels`)". +### `domain_type_is( schema, domain, schema, type, description )` ### +### `domain_type_is( schema, domain, schema, type )` ### +### `domain_type_is( schema, domain, type, description )` ### +### `domain_type_is( schema, domain, type )` ### +### `domain_type_is( domain, type, description )` ### +### `domain_type_is( domain, type )` ### + + SELECT domain_type_is( + 'public', 'us_postal_code', + 'public', 'text', + 'The us_postal_code domain should extend the text type' + ); + +Tests the data type underlying a domain. The first two are arguments are the +schema and name of the domain. The second two are the schema and name of the +type that the domain should extend. The fifth argument is a description. If +there is no description, a reasonable default description will be created. The +schema arguments are also optional (though if there is no schema for the +domain then there cannot be one for the type). For the 3- and 4-argument +forms with schemas, cast the schemas to `NAME` to avoid ambiguities. + +If the data type does not match the type that the domain extends, the test +will fail and output diagnostics like so: + + # Failed test 631: "Domain public.us_postal_code should extend type public.integer" + # have: public.text + # want: public.integer + +If the domain in question does not actually exist, the test will fail with +diagnostics that tell you so: + + # Failed test 632: "Domain public.zip_code should extend type public.text" + # Domain public.zip_code does not exist + +### `domain_type_isnt( schema, domain, schema, type, description )` ### +### `domain_type_isnt( schema, domain, schema, type )` ### +### `domain_type_isnt( schema, domain, type, description )` ### +### `domain_type_isnt( schema, domain, type )` ### +### `domain_type_isnt( domain, type, description )` ### +### `domain_type_isnt( domain, type )` ### + + SELECT domain_type_isnt( + 'public', 'us_postal_code', + 'public', 'integer', + 'The us_postal_code domain should not extend the integer type' + ); + +The inverse of `domain_type_is()`, this function tests that a domain does +*not* extend a particular data type. For example, a US postal code domain +should probably extned the `text` type, not `integer`, since leading 0s are +valid and required. The arguments are the same as for `domain_type_is()`. + ### `cast_context_is( source_type, target_type, context, desc )` ### ### `cast_context_is( source_type, target_type, context )` ### diff --git a/expected/hastap.out b/expected/hastap.out index f81f2873568b..bdda11008855 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..558 +1..630 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -558,3 +558,75 @@ ok 555 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnos ok 556 - hasnt_opclass( name, desc ) fail should pass ok 557 - hasnt_opclass( name, desc ) fail should have the proper description ok 558 - hasnt_opclass( name, desc ) fail should have the proper diagnostics +ok 559 - domain_type_is(schema, domain, schema, type, desc) should pass +ok 560 - domain_type_is(schema, domain, schema, type, desc) should have the proper description +ok 561 - domain_type_is(schema, domain, schema, type, desc) should have the proper diagnostics +ok 562 - domain_type_is(schema, domain, schema, type) should pass +ok 563 - domain_type_is(schema, domain, schema, type) should have the proper description +ok 564 - domain_type_is(schema, domain, schema, type) should have the proper diagnostics +ok 565 - domain_type_is(schema, domain, schema, type, desc) fail should fail +ok 566 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper description +ok 567 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 568 - domain_type_is(schema, nondomain, schema, type, desc) should fail +ok 569 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper description +ok 570 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 571 - domain_type_is(schema, domain, type, desc) should pass +ok 572 - domain_type_is(schema, domain, type, desc) should have the proper description +ok 573 - domain_type_is(schema, domain, type, desc) should have the proper diagnostics +ok 574 - domain_type_is(schema, domain, type) should pass +ok 575 - domain_type_is(schema, domain, type) should have the proper description +ok 576 - domain_type_is(schema, domain, type) should have the proper diagnostics +ok 577 - domain_type_is(schema, domain, type, desc) fail should fail +ok 578 - domain_type_is(schema, domain, type, desc) fail should have the proper description +ok 579 - domain_type_is(schema, domain, type, desc) fail should have the proper diagnostics +ok 580 - domain_type_is(schema, nondomain, type, desc) should fail +ok 581 - domain_type_is(schema, nondomain, type, desc) should have the proper description +ok 582 - domain_type_is(schema, nondomain, type, desc) should have the proper diagnostics +ok 583 - domain_type_is(domain, type, desc) should pass +ok 584 - domain_type_is(domain, type, desc) should have the proper description +ok 585 - domain_type_is(domain, type, desc) should have the proper diagnostics +ok 586 - domain_type_is(domain, type) should pass +ok 587 - domain_type_is(domain, type) should have the proper description +ok 588 - domain_type_is(domain, type) should have the proper diagnostics +ok 589 - domain_type_is(domain, type, desc) fail should fail +ok 590 - domain_type_is(domain, type, desc) fail should have the proper description +ok 591 - domain_type_is(domain, type, desc) fail should have the proper diagnostics +ok 592 - domain_type_is(nondomain, type, desc) should fail +ok 593 - domain_type_is(nondomain, type, desc) should have the proper description +ok 594 - domain_type_is(nondomain, type, desc) should have the proper diagnostics +ok 595 - domain_type_isnt(schema, domain, schema, type, desc) should pass +ok 596 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper description +ok 597 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper diagnostics +ok 598 - domain_type_isnt(schema, domain, schema, type) should pass +ok 599 - domain_type_isnt(schema, domain, schema, type) should have the proper description +ok 600 - domain_type_isnt(schema, domain, schema, type) should have the proper diagnostics +ok 601 - domain_type_isnt(schema, domain, schema, type, desc) fail should fail +ok 602 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper description +ok 603 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 604 - domain_type_isnt(schema, nondomain, schema, type, desc) should fail +ok 605 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper description +ok 606 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 607 - domain_type_isnt(schema, domain, type, desc) should pass +ok 608 - domain_type_isnt(schema, domain, type, desc) should have the proper description +ok 609 - domain_type_isnt(schema, domain, type, desc) should have the proper diagnostics +ok 610 - domain_type_isnt(schema, domain, type) should pass +ok 611 - domain_type_isnt(schema, domain, type) should have the proper description +ok 612 - domain_type_isnt(schema, domain, type) should have the proper diagnostics +ok 613 - domain_type_isnt(schema, domain, type, desc) fail should fail +ok 614 - domain_type_isnt(schema, domain, type, desc) fail should have the proper description +ok 615 - domain_type_isnt(schema, domain, type, desc) fail should have the proper diagnostics +ok 616 - domain_type_isnt(schema, nondomain, type, desc) should fail +ok 617 - domain_type_isnt(schema, nondomain, type, desc) should have the proper description +ok 618 - domain_type_isnt(schema, nondomain, type, desc) should have the proper diagnostics +ok 619 - domain_type_isnt(domain, type, desc) should pass +ok 620 - domain_type_isnt(domain, type, desc) should have the proper description +ok 621 - domain_type_isnt(domain, type, desc) should have the proper diagnostics +ok 622 - domain_type_isnt(domain, type) should pass +ok 623 - domain_type_isnt(domain, type) should have the proper description +ok 624 - domain_type_isnt(domain, type) should have the proper diagnostics +ok 625 - domain_type_isnt(domain, type, desc) fail should fail +ok 626 - domain_type_isnt(domain, type, desc) fail should have the proper description +ok 627 - domain_type_isnt(domain, type, desc) fail should have the proper diagnostics +ok 628 - domain_type_isnt(nondomain, type, desc) should fail +ok 629 - domain_type_isnt(nondomain, type, desc) should have the proper description +ok 630 - domain_type_isnt(nondomain, type, desc) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 0b7147e136b2..e6cfea5baa92 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -344,9 +344,8 @@ BEGIN result := $1 IS DISTINCT FROM $2; output := ok( result, $3 ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( - ' ' || COALESCE( $1::text, 'NULL' ) || - E'\n <>' || - E'\n ' || COALESCE( $2::text, 'NULL' ) + ' have: ' || COALESCE( $1::text, 'NULL' ) || + E'\n want: anything else' ) END; END; $$ LANGUAGE plpgsql; @@ -6739,3 +6738,214 @@ 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_type( NAME, NAME, CHAR[] ) +RETURNS TEXT[] AS $$ + SELECT ARRAY[quote_ident(tn.nspname), 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 d.typnamespace = tn.oid + WHERE d.typisdefined + AND dn.nspname = $1 + AND d.typname = $2 + AND t.typtype = ANY( COALESCE($3, ARRAY['b', 'c', 'd', 'p', 'e']) ) +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _get_type( NAME, CHAR[] ) +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 d.typname = $1 + AND t.typtype = ANY( COALESCE($2, ARRAY['b', 'c', 'd', 'p', 'e']) ) +$$ 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_type($1, $2, ARRAY['b']); +BEGIN + IF actual_type[1] IS NULL THEN + RETURN fail( $5 ) || E'\n' || diag ( + ' Domain ' || quote_ident($1) || '.' || $2 + || ' does not exist' + ); + END IF; + + RETURN is( + actual_type[1] || '.' || actual_type[2], + 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_type($1, $2, ARRAY['b']); +BEGIN + IF actual_type[1] IS NULL THEN + RETURN fail( $4 ) || E'\n' || diag ( + ' Domain ' || quote_ident($1) || '.' || $2 + || ' does not exist' + ); + END IF; + + RETURN is( actual_type[2], $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_type($1, ARRAY['b']); +BEGIN + IF actual_type IS NULL THEN + RETURN fail( $3 ) || E'\n' || diag ( + ' Domain ' || $1 || ' does not exist' + ); + END IF; + + RETURN is( actual_type, $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_type($1, $2, ARRAY['b']); +BEGIN + IF actual_type[1] IS NULL THEN + RETURN fail( $5 ) || E'\n' || diag ( + ' Domain ' || quote_ident($1) || '.' || $2 + || ' does not exist' + ); + END IF; + + RETURN isnt( + actual_type[1] || '.' || actual_type[2], + 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_type($1, $2, ARRAY['b']); +BEGIN + IF actual_type[1] IS NULL THEN + RETURN fail( $4 ) || E'\n' || diag ( + ' Domain ' || quote_ident($1) || '.' || $2 + || ' does not exist' + ); + END IF; + + RETURN isnt( actual_type[2], $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_type($1, ARRAY['b']); +BEGIN + IF actual_type IS NULL THEN + RETURN fail( $3 ) || E'\n' || diag ( + ' Domain ' || $1 || ' does not exist' + ); + END IF; + + RETURN isnt( actual_type, $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; diff --git a/sql/hastap.sql b/sql/hastap.sql index 2443497e193a..30dcae1a8196 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(558); +SELECT plan(630); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -1561,6 +1561,206 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test domain_type_is() and domain_type_isnt(). +SELECT * FROM check_test( + domain_type_is( 'public', 'us_postal_code', 'public', 'text', 'whatever'), + true, + 'domain_type_is(schema, domain, schema, type, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + domain_type_is( 'public', 'us_postal_code', 'public'::name, 'text'), + true, + 'domain_type_is(schema, domain, schema, type)', + 'Domain public.us_postal_code should extend type public.text', + '' +); + +SELECT * FROM check_test( + domain_type_is( 'public', 'us_postal_code', 'public', 'integer', 'whatever'), + false, + 'domain_type_is(schema, domain, schema, type, desc) fail', + 'whatever', + ' have: public.text + want: public.integer' +); + +SELECT * FROM check_test( + domain_type_is( 'public', 'zip_code', 'public', 'integer', 'whatever'), + false, + 'domain_type_is(schema, nondomain, schema, type, desc)', + 'whatever', + ' Domain public.zip_code does not exist' +); + +SELECT * FROM check_test( + domain_type_is( 'public', 'us_postal_code', 'text', 'whatever'), + true, + 'domain_type_is(schema, domain, type, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + domain_type_is( 'public'::name, 'us_postal_code', 'text'), + true, + 'domain_type_is(schema, domain, type)', + 'Domain public.us_postal_code should extend type text', + '' +); + +SELECT * FROM check_test( + domain_type_is( 'public', 'us_postal_code', 'integer', 'whatever'), + false, + 'domain_type_is(schema, domain, type, desc) fail', + 'whatever', + ' have: text + want: integer' +); + +SELECT * FROM check_test( + domain_type_is( 'public', 'zip_code', 'integer', 'whatever'), + false, + 'domain_type_is(schema, nondomain, type, desc)', + 'whatever', + ' Domain public.zip_code does not exist' +); + +SELECT * FROM check_test( + domain_type_is( 'us_postal_code', 'text', 'whatever'), + true, + 'domain_type_is(domain, type, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + domain_type_is( 'us_postal_code', 'text'), + true, + 'domain_type_is(domain, type)', + 'Domain us_postal_code should extend type text', + '' +); + +SELECT * FROM check_test( + domain_type_is( 'us_postal_code', 'integer', 'whatever'), + false, + 'domain_type_is(domain, type, desc) fail', + 'whatever', + ' have: text + want: integer' +); + +SELECT * FROM check_test( + domain_type_is( 'zip_code', 'integer', 'whatever'), + false, + 'domain_type_is(nondomain, type, desc)', + 'whatever', + ' Domain zip_code does not exist' +); + +SELECT * FROM check_test( + domain_type_isnt( 'public', 'us_postal_code', 'public', 'integer', 'whatever'), + true, + 'domain_type_isnt(schema, domain, schema, type, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + domain_type_isnt( 'public', 'us_postal_code', 'public'::name, 'integer'), + true, + 'domain_type_isnt(schema, domain, schema, type)', + 'Domain public.us_postal_code should not extend type public.integer', + '' +); + +SELECT * FROM check_test( + domain_type_isnt( 'public', 'us_postal_code', 'public', 'text', 'whatever'), + false, + 'domain_type_isnt(schema, domain, schema, type, desc) fail', + 'whatever', + ' have: public.text + want: anything else' +); + +SELECT * FROM check_test( + domain_type_isnt( 'public', 'zip_code', 'public', 'text', 'whatever'), + false, + 'domain_type_isnt(schema, nondomain, schema, type, desc)', + 'whatever', + ' Domain public.zip_code does not exist' +); + +SELECT * FROM check_test( + domain_type_isnt( 'public', 'us_postal_code', 'integer', 'whatever'), + true, + 'domain_type_isnt(schema, domain, type, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + domain_type_isnt( 'public'::name, 'us_postal_code', 'integer'), + true, + 'domain_type_isnt(schema, domain, type)', + 'Domain public.us_postal_code should not extend type integer', + '' +); + +SELECT * FROM check_test( + domain_type_isnt( 'public', 'us_postal_code', 'text', 'whatever'), + false, + 'domain_type_isnt(schema, domain, type, desc) fail', + 'whatever', + ' have: text + want: anything else' +); + +SELECT * FROM check_test( + domain_type_isnt( 'public', 'zip_code', 'text', 'whatever'), + false, + 'domain_type_isnt(schema, nondomain, type, desc)', + 'whatever', + ' Domain public.zip_code does not exist' +); + +SELECT * FROM check_test( + domain_type_isnt( 'us_postal_code', 'integer', 'whatever'), + true, + 'domain_type_isnt(domain, type, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + domain_type_isnt( 'us_postal_code', 'integer'), + true, + 'domain_type_isnt(domain, type)', + 'Domain us_postal_code should not extend type integer', + '' +); + +SELECT * FROM check_test( + domain_type_isnt( 'us_postal_code', 'text', 'whatever'), + false, + 'domain_type_isnt(domain, type, desc) fail', + 'whatever', + ' have: text + want: anything else' +); + +SELECT * FROM check_test( + domain_type_isnt( 'zip_code', 'text', 'whatever'), + false, + 'domain_type_isnt(nondomain, type, desc)', + 'whatever', + ' Domain zip_code does not exist' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/sql/istap.sql b/sql/istap.sql index fcf4e7861f4d..ecabe776c2da 100644 --- a/sql/istap.sql +++ b/sql/istap.sql @@ -17,9 +17,8 @@ SELECT * FROM check_test( is( 1, 2 ), false, 'is(1, 2)', '', ' have: 1 /****************************************************************************/ -- Test isnt(). SELECT * FROM check_test( isnt(1, 2), true, 'isnt(1, 2)', '', '' ); -SELECT * FROM check_test( isnt( 1, 1 ), false, 'isnt(1, 1)', '', ' 1 - <> - 1' ); +SELECT * FROM check_test( isnt( 1, 1 ), false, 'isnt(1, 1)', '', ' have: 1 + want: anything else' ); /****************************************************************************/ -- Try using variables. diff --git a/sql/pktap.sql b/sql/pktap.sql index 04d996ea66f3..c441df3102ac 100644 --- a/sql/pktap.sql +++ b/sql/pktap.sql @@ -188,9 +188,8 @@ SELECT * FROM check_test( false, 'col_isnt_pk( schema, table, column, description )', 'public.sometab.id should not be a pk', - ' {id} - <> - {id}' + ' have: {id} + want: anything else' ); SELECT * FROM check_test( @@ -198,9 +197,8 @@ SELECT * FROM check_test( false, 'col_isnt_pk( table, column, description )', 'sometab.id should not be a pk', - ' {id} - <> - {id}' + ' have: {id} + want: anything else' ); SELECT * FROM check_test( @@ -208,9 +206,8 @@ SELECT * FROM check_test( false, 'col_isnt_pk( table, column )', 'Column sometab(id) should not be a primary key', - ' {id} - <> - {id}' + ' have: {id} + want: anything else' ); SELECT * FROM check_test( diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 5fb7b382469b..d571ede1e076 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,4 +1,20 @@ -- ## SET search_path TO TAPSCHEMA, public; +DROP FUNCTION domain_type_isnt( TEXT, TEXT ); +DROP FUNCTION domain_type_isnt( TEXT, TEXT, TEXT ); +DROP FUNCTION domain_type_isnt( NAME, TEXT, TEXT ); +DROP FUNCTION domain_type_isnt( NAME, TEXT, TEXT, TEXT ); +DROP FUNCTION domain_type_isnt( NAME, TEXT, NAME, TEXT ); +DROP FUNCTION domain_type_isnt( NAME, TEXT, NAME, TEXT, TEXT ); +DROP FUNCTION domain_type_is( TEXT, TEXT ); +DROP FUNCTION domain_type_is( TEXT, TEXT, TEXT ); +DROP FUNCTION domain_type_is( NAME, TEXT, TEXT ); +DROP FUNCTION domain_type_is( NAME, TEXT, TEXT, TEXT ); +DROP FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT ); +DROP FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT, TEXT ); +DROP FUNCTION _get_type( NAME, CHAR[] ); +DROP FUNCTION _get_type( NAME, NAME, CHAR[] ); +DROP FUNCTION _dexists ( NAME ); +DROP FUNCTION _dexists ( NAME, NAME ); DROP FUNCTION enums_are ( NAME[] ); DROP FUNCTION enums_are ( NAME[], TEXT ); DROP FUNCTION enums_are ( NAME, NAME[] ); From 2e657a7dc12c996f307458381d3faf0f9eb7f245 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 3 Dec 2009 16:15:15 -0800 Subject: [PATCH 0466/1195] Add type schema variants of `col_is_type()` Also: * Add `col_type_is( schema, table, column, type )` * Fix `domain_type_is()` to compare domain names case-insensitvely and to restrict the search to domains. --- Changes | 2 + README.pgtap | 13 +- expected/coltap.out | 293 ++++++++++++++++++++++------------------- expected/hastap.out | 179 +++++++++++++++---------- pgtap.sql.in | 155 +++++++++++++++------- sql/coltap.sql | 61 ++++++++- sql/hastap.sql | 125 +++++++++++++++++- uninstall_pgtap.sql.in | 10 +- 8 files changed, 580 insertions(+), 258 deletions(-) diff --git a/Changes b/Changes index 834eb28e837e..652709548daa 100644 --- a/Changes +++ b/Changes @@ -30,6 +30,8 @@ Revision history for pgTAP makes much more sense. * Added `domain_type_is()` and `domain_type_isnt()`. Based on an implementation by Bob Lunney. +* Added variants of `col_is_type()` to allow a schema name to be specified for + the type. 0.22 2009-07-31T00:26:16 ------------------------- diff --git a/README.pgtap b/README.pgtap index b74c65ed7fcc..8ad377b706cc 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2415,7 +2415,10 @@ This function is the inverse of `col_has_default()`. The test passes if the specified column does *not* have a default. It will still fail if the column does not exist, and emit useful diagnostics to let you know. +### `col_type_is( schema, table, column, schema, type, description )` ### +### `col_type_is( schema, table, column, schema, type )` ### ### `col_type_is( schema, table, column, type, description )` ### +### `col_type_is( schema, table, column, type )` ### ### `col_type_is( table, column, type, description )` ### ### `col_type_is( table, column, type )` ### @@ -2430,11 +2433,11 @@ does not exist, and emit useful diagnostics to let you know. This function tests that the specified column is of a particular type. If it fails, it will emit diagnostics naming the actual type. The first argument is the schema name, the second the table name, the third the column name, the -fourth the type, and the fifth is the test description. If the schema is -omitted, the table and the type must be visible in the search path. If the -test description is omitted, it will be set to "Column -`:schema.:table.:column` should be type `:type`". Note that this test will -fail if the table or column in question does not exist. +fourth the type's schem, the fifth the type, and the sixth is the test +description. If the table schema is omitted, the table and the type must be +visible in the search path. If the test description is omitted, it will be set +to "Column `:schema.:table.:column` should be type `:schema.:type`". Note that +this test will fail if the table or column in question does not exist. The type argument should be formatted as it would be displayed in the view of a table using the `\d` command in `psql`. For example, if you have a numeric diff --git a/expected/coltap.out b/expected/coltap.out index 3e7f5859baaf..92c4fc26d717 100644 --- a/expected/coltap.out +++ b/expected/coltap.out @@ -1,5 +1,5 @@ \unset ECHO -1..171 +1..192 ok 1 - col_not_null( sch, tab, col, desc ) should pass ok 2 - col_not_null( sch, tab, col, desc ) should have the proper description ok 3 - col_not_null( sch, tab, col, desc ) should have the proper diagnostics @@ -36,138 +36,159 @@ ok 33 - col_is_null( sch, tab, noncol, desc ) should have the proper diagnostics ok 34 - col_is_null( table, noncolumn ) fail should fail ok 35 - col_is_null( table, noncolumn ) fail should have the proper description ok 36 - col_is_null( table, noncolumn ) fail should have the proper diagnostics -ok 37 - col_type_is( sch, tab, col, type, desc ) should pass -ok 38 - col_type_is( sch, tab, col, type, desc ) should have the proper description -ok 39 - col_type_is( sch, tab, col, type, desc ) should have the proper diagnostics -ok 40 - col_type_is( tab, col, type, desc ) should pass -ok 41 - col_type_is( tab, col, type, desc ) should have the proper description -ok 42 - col_type_is( tab, col, type, desc ) should have the proper diagnostics -ok 43 - col_type_is( tab, col, type ) should pass -ok 44 - col_type_is( tab, col, type ) should have the proper description -ok 45 - col_type_is( tab, col, type ) should have the proper diagnostics -ok 46 - col_type_is( tab, col, type ) insensitive should pass -ok 47 - col_type_is( tab, col, type ) insensitive should have the proper description -ok 48 - col_type_is( tab, col, type ) insensitive should have the proper diagnostics -ok 49 - col_type_is( tab, col, type ) fail should fail -ok 50 - col_type_is( tab, col, type ) fail should have the proper description -ok 51 - col_type_is( tab, col, type ) fail should have the proper diagnostics -ok 52 - col_type_is( tab, noncol, type ) fail should fail -ok 53 - col_type_is( tab, noncol, type ) fail should have the proper description -ok 54 - col_type_is( tab, noncol, type ) fail should have the proper diagnostics -ok 55 - col_type_is( sch, tab, noncol, type, desc ) fail should fail -ok 56 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper description -ok 57 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper diagnostics -ok 58 - col_type_is with precision should pass -ok 59 - col_type_is with precision should have the proper description -ok 60 - col_type_is with precision should have the proper diagnostics -ok 61 - col_type_is precision fail should fail -ok 62 - col_type_is precision fail should have the proper description -ok 63 - col_type_is precision fail should have the proper diagnostics -ok 64 - col_has_default( sch, tab, col, desc ) should pass -ok 65 - col_has_default( sch, tab, col, desc ) should have the proper description -ok 66 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics -ok 67 - col_has_default( tab, col, desc ) should pass -ok 68 - col_has_default( tab, col, desc ) should have the proper description -ok 69 - col_has_default( tab, col, desc ) should have the proper diagnostics -ok 70 - col_has_default( tab, col ) should pass -ok 71 - col_has_default( tab, col ) should have the proper description -ok 72 - col_has_default( tab, col ) should have the proper diagnostics -ok 73 - col_has_default( sch, tab, col, desc ) should fail -ok 74 - col_has_default( sch, tab, col, desc ) should have the proper description -ok 75 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics -ok 76 - col_has_default( tab, col, desc ) should fail -ok 77 - col_has_default( tab, col, desc ) should have the proper description -ok 78 - col_has_default( tab, col, desc ) should have the proper diagnostics -ok 79 - col_has_default( tab, col ) should fail -ok 80 - col_has_default( tab, col ) should have the proper description -ok 81 - col_has_default( tab, col ) should have the proper diagnostics -ok 82 - col_has_default( sch, tab, col, desc ) should fail -ok 83 - col_has_default( sch, tab, col, desc ) should have the proper description -ok 84 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics -ok 85 - col_has_default( tab, col, desc ) should fail -ok 86 - col_has_default( tab, col, desc ) should have the proper description -ok 87 - col_has_default( tab, col, desc ) should have the proper diagnostics -ok 88 - col_has_default( tab, col ) should fail -ok 89 - col_has_default( tab, col ) should have the proper description -ok 90 - col_has_default( tab, col ) should have the proper diagnostics -ok 91 - col_hasnt_default( sch, tab, col, desc ) should fail -ok 92 - col_hasnt_default( sch, tab, col, desc ) should have the proper description -ok 93 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics -ok 94 - col_hasnt_default( tab, col, desc ) should fail -ok 95 - col_hasnt_default( tab, col, desc ) should have the proper description -ok 96 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics -ok 97 - col_hasnt_default( tab, col ) should fail -ok 98 - col_hasnt_default( tab, col ) should have the proper description -ok 99 - col_hasnt_default( tab, col ) should have the proper diagnostics -ok 100 - col_hasnt_default( sch, tab, col, desc ) should pass -ok 101 - col_hasnt_default( sch, tab, col, desc ) should have the proper description -ok 102 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics -ok 103 - col_hasnt_default( tab, col, desc ) should pass -ok 104 - col_hasnt_default( tab, col, desc ) should have the proper description -ok 105 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics -ok 106 - col_hasnt_default( tab, col ) should pass -ok 107 - col_hasnt_default( tab, col ) should have the proper description -ok 108 - col_hasnt_default( tab, col ) should have the proper diagnostics -ok 109 - col_hasnt_default( sch, tab, col, desc ) should fail -ok 110 - col_hasnt_default( sch, tab, col, desc ) should have the proper description -ok 111 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics -ok 112 - col_hasnt_default( tab, col, desc ) should fail -ok 113 - col_hasnt_default( tab, col, desc ) should have the proper description -ok 114 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics -ok 115 - col_hasnt_default( tab, col ) should fail -ok 116 - col_hasnt_default( tab, col ) should have the proper description -ok 117 - col_hasnt_default( tab, col ) should have the proper diagnostics -ok 118 - col_default_is( sch, tab, col, def, desc ) should pass -ok 119 - col_default_is( sch, tab, col, def, desc ) should have the proper description -ok 120 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics -ok 121 - col_default_is() fail should fail -ok 122 - col_default_is() fail should have the proper description -ok 123 - col_default_is() fail should have the proper diagnostics -ok 124 - col_default_is( tab, col, def, desc ) should pass -ok 125 - col_default_is( tab, col, def, desc ) should have the proper description -ok 126 - col_default_is( tab, col, def, desc ) should have the proper diagnostics -ok 127 - col_default_is( tab, col, def ) should pass -ok 128 - col_default_is( tab, col, def ) should have the proper description -ok 129 - col_default_is( tab, col, def ) should have the proper diagnostics -ok 130 - col_default_is( tab, col, int ) should pass -ok 131 - col_default_is( tab, col, int ) should have the proper description -ok 132 - col_default_is( tab, col, int ) should have the proper diagnostics -ok 133 - col_default_is( tab, col, NULL, desc ) should pass -ok 134 - col_default_is( tab, col, NULL, desc ) should have the proper description -ok 135 - col_default_is( tab, col, NULL, desc ) should have the proper diagnostics -ok 136 - col_default_is( tab, col, NULL ) should pass -ok 137 - col_default_is( tab, col, NULL ) should have the proper description -ok 138 - col_default_is( tab, col, NULL ) should have the proper diagnostics -ok 139 - col_default_is( tab, col, bogus, desc ) should fail -ok 140 - col_default_is( tab, col, bogus, desc ) should have the proper description -ok 141 - col_default_is( tab, col, bogus, desc ) should have the proper diagnostics -ok 142 - col_default_is( tab, col, bogus ) should fail -ok 143 - col_default_is( tab, col, bogus ) should have the proper description -ok 144 - col_default_is( tab, col, bogus ) should have the proper diagnostics -ok 145 - col_default_is( tab, col, expression ) should pass -ok 146 - col_default_is( tab, col, expression ) should have the proper description -ok 147 - col_default_is( tab, col, expression ) should have the proper diagnostics -ok 148 - col_default_is( tab, col, expression::text ) should pass -ok 149 - col_default_is( tab, col, expression::text ) should have the proper description -ok 150 - col_default_is( tab, col, expression::text ) should have the proper diagnostics -ok 151 - col_default_is( tab, col, expression, desc ) should pass -ok 152 - col_default_is( tab, col, expression, desc ) should have the proper description -ok 153 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics -ok 154 - col_default_is( tab, col, expression, desc ) should pass -ok 155 - col_default_is( tab, col, expression, desc ) should have the proper description -ok 156 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics -ok 157 - col_default_is( schema, tab, col, expression, desc ) should pass -ok 158 - col_default_is( schema, tab, col, expression, desc ) should have the proper description -ok 159 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics -ok 160 - col_default_is( schema, tab, col, expression, desc ) should pass -ok 161 - col_default_is( schema, tab, col, expression, desc ) should have the proper description -ok 162 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics -ok 163 - col_default_is( sch, tab, col, def, desc ) should fail -ok 164 - col_default_is( sch, tab, col, def, desc ) should have the proper description -ok 165 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics -ok 166 - col_default_is( tab, col, def, desc ) should fail -ok 167 - col_default_is( tab, col, def, desc ) should have the proper description -ok 168 - col_default_is( tab, col, def, desc ) should have the proper diagnostics -ok 169 - col_default_is( tab, col, def ) should fail -ok 170 - col_default_is( tab, col, def ) should have the proper description -ok 171 - col_default_is( tab, col, def ) should have the proper diagnostics +ok 37 - col_type_is( sch, tab, col, sch, type, desc ) should pass +ok 38 - col_type_is( sch, tab, col, sch, type, desc ) should have the proper description +ok 39 - col_type_is( sch, tab, col, sch, type, desc ) should have the proper diagnostics +ok 40 - col_type_is( sch, tab, col, sch, type, desc ) should pass +ok 41 - col_type_is( sch, tab, col, sch, type, desc ) should have the proper description +ok 42 - col_type_is( sch, tab, col, sch, type, desc ) should have the proper diagnostics +ok 43 - col_type_is( sch, tab, col, sch, type, desc ) fail should fail +ok 44 - col_type_is( sch, tab, col, sch, type, desc ) fail should have the proper description +ok 45 - col_type_is( sch, tab, col, sch, type, desc ) fail should have the proper diagnostics +ok 46 - col_type_is( sch, tab, col, sch, non-type, desc ) should fail +ok 47 - col_type_is( sch, tab, col, sch, non-type, desc ) should have the proper description +ok 48 - col_type_is( sch, tab, col, sch, non-type, desc ) should have the proper diagnostics +ok 49 - col_type_is( sch, tab, col, non-sch, type, desc ) should fail +ok 50 - col_type_is( sch, tab, col, non-sch, type, desc ) should have the proper description +ok 51 - col_type_is( sch, tab, col, non-sch, type, desc ) should have the proper diagnostics +ok 52 - col_type_is( sch, tab, non-col, sch, type, desc ) should fail +ok 53 - col_type_is( sch, tab, non-col, sch, type, desc ) should have the proper description +ok 54 - col_type_is( sch, tab, non-col, sch, type, desc ) should have the proper diagnostics +ok 55 - col_type_is( sch, tab, col, type, desc ) should pass +ok 56 - col_type_is( sch, tab, col, type, desc ) should have the proper description +ok 57 - col_type_is( sch, tab, col, type, desc ) should have the proper diagnostics +ok 58 - col_type_is( sch, tab, col, type ) should pass +ok 59 - col_type_is( sch, tab, col, type ) should have the proper description +ok 60 - col_type_is( sch, tab, col, type ) should have the proper diagnostics +ok 61 - col_type_is( tab, col, type, desc ) should pass +ok 62 - col_type_is( tab, col, type, desc ) should have the proper description +ok 63 - col_type_is( tab, col, type, desc ) should have the proper diagnostics +ok 64 - col_type_is( tab, col, type ) should pass +ok 65 - col_type_is( tab, col, type ) should have the proper description +ok 66 - col_type_is( tab, col, type ) should have the proper diagnostics +ok 67 - col_type_is( tab, col, type ) insensitive should pass +ok 68 - col_type_is( tab, col, type ) insensitive should have the proper description +ok 69 - col_type_is( tab, col, type ) insensitive should have the proper diagnostics +ok 70 - col_type_is( tab, col, type ) fail should fail +ok 71 - col_type_is( tab, col, type ) fail should have the proper description +ok 72 - col_type_is( tab, col, type ) fail should have the proper diagnostics +ok 73 - col_type_is( tab, noncol, type ) fail should fail +ok 74 - col_type_is( tab, noncol, type ) fail should have the proper description +ok 75 - col_type_is( tab, noncol, type ) fail should have the proper diagnostics +ok 76 - col_type_is( sch, tab, noncol, type, desc ) fail should fail +ok 77 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper description +ok 78 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper diagnostics +ok 79 - col_type_is with precision should pass +ok 80 - col_type_is with precision should have the proper description +ok 81 - col_type_is with precision should have the proper diagnostics +ok 82 - col_type_is precision fail should fail +ok 83 - col_type_is precision fail should have the proper description +ok 84 - col_type_is precision fail should have the proper diagnostics +ok 85 - col_has_default( sch, tab, col, desc ) should pass +ok 86 - col_has_default( sch, tab, col, desc ) should have the proper description +ok 87 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics +ok 88 - col_has_default( tab, col, desc ) should pass +ok 89 - col_has_default( tab, col, desc ) should have the proper description +ok 90 - col_has_default( tab, col, desc ) should have the proper diagnostics +ok 91 - col_has_default( tab, col ) should pass +ok 92 - col_has_default( tab, col ) should have the proper description +ok 93 - col_has_default( tab, col ) should have the proper diagnostics +ok 94 - col_has_default( sch, tab, col, desc ) should fail +ok 95 - col_has_default( sch, tab, col, desc ) should have the proper description +ok 96 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics +ok 97 - col_has_default( tab, col, desc ) should fail +ok 98 - col_has_default( tab, col, desc ) should have the proper description +ok 99 - col_has_default( tab, col, desc ) should have the proper diagnostics +ok 100 - col_has_default( tab, col ) should fail +ok 101 - col_has_default( tab, col ) should have the proper description +ok 102 - col_has_default( tab, col ) should have the proper diagnostics +ok 103 - col_has_default( sch, tab, col, desc ) should fail +ok 104 - col_has_default( sch, tab, col, desc ) should have the proper description +ok 105 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics +ok 106 - col_has_default( tab, col, desc ) should fail +ok 107 - col_has_default( tab, col, desc ) should have the proper description +ok 108 - col_has_default( tab, col, desc ) should have the proper diagnostics +ok 109 - col_has_default( tab, col ) should fail +ok 110 - col_has_default( tab, col ) should have the proper description +ok 111 - col_has_default( tab, col ) should have the proper diagnostics +ok 112 - col_hasnt_default( sch, tab, col, desc ) should fail +ok 113 - col_hasnt_default( sch, tab, col, desc ) should have the proper description +ok 114 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics +ok 115 - col_hasnt_default( tab, col, desc ) should fail +ok 116 - col_hasnt_default( tab, col, desc ) should have the proper description +ok 117 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics +ok 118 - col_hasnt_default( tab, col ) should fail +ok 119 - col_hasnt_default( tab, col ) should have the proper description +ok 120 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 121 - col_hasnt_default( sch, tab, col, desc ) should pass +ok 122 - col_hasnt_default( sch, tab, col, desc ) should have the proper description +ok 123 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics +ok 124 - col_hasnt_default( tab, col, desc ) should pass +ok 125 - col_hasnt_default( tab, col, desc ) should have the proper description +ok 126 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics +ok 127 - col_hasnt_default( tab, col ) should pass +ok 128 - col_hasnt_default( tab, col ) should have the proper description +ok 129 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 130 - col_hasnt_default( sch, tab, col, desc ) should fail +ok 131 - col_hasnt_default( sch, tab, col, desc ) should have the proper description +ok 132 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics +ok 133 - col_hasnt_default( tab, col, desc ) should fail +ok 134 - col_hasnt_default( tab, col, desc ) should have the proper description +ok 135 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics +ok 136 - col_hasnt_default( tab, col ) should fail +ok 137 - col_hasnt_default( tab, col ) should have the proper description +ok 138 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 139 - col_default_is( sch, tab, col, def, desc ) should pass +ok 140 - col_default_is( sch, tab, col, def, desc ) should have the proper description +ok 141 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics +ok 142 - col_default_is() fail should fail +ok 143 - col_default_is() fail should have the proper description +ok 144 - col_default_is() fail should have the proper diagnostics +ok 145 - col_default_is( tab, col, def, desc ) should pass +ok 146 - col_default_is( tab, col, def, desc ) should have the proper description +ok 147 - col_default_is( tab, col, def, desc ) should have the proper diagnostics +ok 148 - col_default_is( tab, col, def ) should pass +ok 149 - col_default_is( tab, col, def ) should have the proper description +ok 150 - col_default_is( tab, col, def ) should have the proper diagnostics +ok 151 - col_default_is( tab, col, int ) should pass +ok 152 - col_default_is( tab, col, int ) should have the proper description +ok 153 - col_default_is( tab, col, int ) should have the proper diagnostics +ok 154 - col_default_is( tab, col, NULL, desc ) should pass +ok 155 - col_default_is( tab, col, NULL, desc ) should have the proper description +ok 156 - col_default_is( tab, col, NULL, desc ) should have the proper diagnostics +ok 157 - col_default_is( tab, col, NULL ) should pass +ok 158 - col_default_is( tab, col, NULL ) should have the proper description +ok 159 - col_default_is( tab, col, NULL ) should have the proper diagnostics +ok 160 - col_default_is( tab, col, bogus, desc ) should fail +ok 161 - col_default_is( tab, col, bogus, desc ) should have the proper description +ok 162 - col_default_is( tab, col, bogus, desc ) should have the proper diagnostics +ok 163 - col_default_is( tab, col, bogus ) should fail +ok 164 - col_default_is( tab, col, bogus ) should have the proper description +ok 165 - col_default_is( tab, col, bogus ) should have the proper diagnostics +ok 166 - col_default_is( tab, col, expression ) should pass +ok 167 - col_default_is( tab, col, expression ) should have the proper description +ok 168 - col_default_is( tab, col, expression ) should have the proper diagnostics +ok 169 - col_default_is( tab, col, expression::text ) should pass +ok 170 - col_default_is( tab, col, expression::text ) should have the proper description +ok 171 - col_default_is( tab, col, expression::text ) should have the proper diagnostics +ok 172 - col_default_is( tab, col, expression, desc ) should pass +ok 173 - col_default_is( tab, col, expression, desc ) should have the proper description +ok 174 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics +ok 175 - col_default_is( tab, col, expression, desc ) should pass +ok 176 - col_default_is( tab, col, expression, desc ) should have the proper description +ok 177 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics +ok 178 - col_default_is( schema, tab, col, expression, desc ) should pass +ok 179 - col_default_is( schema, tab, col, expression, desc ) should have the proper description +ok 180 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics +ok 181 - col_default_is( schema, tab, col, expression, desc ) should pass +ok 182 - col_default_is( schema, tab, col, expression, desc ) should have the proper description +ok 183 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics +ok 184 - col_default_is( sch, tab, col, def, desc ) should fail +ok 185 - col_default_is( sch, tab, col, def, desc ) should have the proper description +ok 186 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics +ok 187 - col_default_is( tab, col, def, desc ) should fail +ok 188 - col_default_is( tab, col, def, desc ) should have the proper description +ok 189 - col_default_is( tab, col, def, desc ) should have the proper diagnostics +ok 190 - col_default_is( tab, col, def ) should fail +ok 191 - col_default_is( tab, col, def ) should have the proper description +ok 192 - col_default_is( tab, col, def ) should have the proper diagnostics diff --git a/expected/hastap.out b/expected/hastap.out index bdda11008855..431bd3059aa4 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..630 +1..675 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -564,69 +564,114 @@ ok 561 - domain_type_is(schema, domain, schema, type, desc) should have the prop ok 562 - domain_type_is(schema, domain, schema, type) should pass ok 563 - domain_type_is(schema, domain, schema, type) should have the proper description ok 564 - domain_type_is(schema, domain, schema, type) should have the proper diagnostics -ok 565 - domain_type_is(schema, domain, schema, type, desc) fail should fail -ok 566 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper description -ok 567 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper diagnostics -ok 568 - domain_type_is(schema, nondomain, schema, type, desc) should fail -ok 569 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper description -ok 570 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper diagnostics -ok 571 - domain_type_is(schema, domain, type, desc) should pass -ok 572 - domain_type_is(schema, domain, type, desc) should have the proper description -ok 573 - domain_type_is(schema, domain, type, desc) should have the proper diagnostics -ok 574 - domain_type_is(schema, domain, type) should pass -ok 575 - domain_type_is(schema, domain, type) should have the proper description -ok 576 - domain_type_is(schema, domain, type) should have the proper diagnostics -ok 577 - domain_type_is(schema, domain, type, desc) fail should fail -ok 578 - domain_type_is(schema, domain, type, desc) fail should have the proper description -ok 579 - domain_type_is(schema, domain, type, desc) fail should have the proper diagnostics -ok 580 - domain_type_is(schema, nondomain, type, desc) should fail -ok 581 - domain_type_is(schema, nondomain, type, desc) should have the proper description -ok 582 - domain_type_is(schema, nondomain, type, desc) should have the proper diagnostics -ok 583 - domain_type_is(domain, type, desc) should pass -ok 584 - domain_type_is(domain, type, desc) should have the proper description -ok 585 - domain_type_is(domain, type, desc) should have the proper diagnostics -ok 586 - domain_type_is(domain, type) should pass -ok 587 - domain_type_is(domain, type) should have the proper description -ok 588 - domain_type_is(domain, type) should have the proper diagnostics -ok 589 - domain_type_is(domain, type, desc) fail should fail -ok 590 - domain_type_is(domain, type, desc) fail should have the proper description -ok 591 - domain_type_is(domain, type, desc) fail should have the proper diagnostics -ok 592 - domain_type_is(nondomain, type, desc) should fail -ok 593 - domain_type_is(nondomain, type, desc) should have the proper description -ok 594 - domain_type_is(nondomain, type, desc) should have the proper diagnostics -ok 595 - domain_type_isnt(schema, domain, schema, type, desc) should pass -ok 596 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper description -ok 597 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper diagnostics -ok 598 - domain_type_isnt(schema, domain, schema, type) should pass -ok 599 - domain_type_isnt(schema, domain, schema, type) should have the proper description -ok 600 - domain_type_isnt(schema, domain, schema, type) should have the proper diagnostics -ok 601 - domain_type_isnt(schema, domain, schema, type, desc) fail should fail -ok 602 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper description -ok 603 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper diagnostics -ok 604 - domain_type_isnt(schema, nondomain, schema, type, desc) should fail -ok 605 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper description -ok 606 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper diagnostics -ok 607 - domain_type_isnt(schema, domain, type, desc) should pass -ok 608 - domain_type_isnt(schema, domain, type, desc) should have the proper description -ok 609 - domain_type_isnt(schema, domain, type, desc) should have the proper diagnostics -ok 610 - domain_type_isnt(schema, domain, type) should pass -ok 611 - domain_type_isnt(schema, domain, type) should have the proper description -ok 612 - domain_type_isnt(schema, domain, type) should have the proper diagnostics -ok 613 - domain_type_isnt(schema, domain, type, desc) fail should fail -ok 614 - domain_type_isnt(schema, domain, type, desc) fail should have the proper description -ok 615 - domain_type_isnt(schema, domain, type, desc) fail should have the proper diagnostics -ok 616 - domain_type_isnt(schema, nondomain, type, desc) should fail -ok 617 - domain_type_isnt(schema, nondomain, type, desc) should have the proper description -ok 618 - domain_type_isnt(schema, nondomain, type, desc) should have the proper diagnostics -ok 619 - domain_type_isnt(domain, type, desc) should pass -ok 620 - domain_type_isnt(domain, type, desc) should have the proper description -ok 621 - domain_type_isnt(domain, type, desc) should have the proper diagnostics -ok 622 - domain_type_isnt(domain, type) should pass -ok 623 - domain_type_isnt(domain, type) should have the proper description -ok 624 - domain_type_isnt(domain, type) should have the proper diagnostics -ok 625 - domain_type_isnt(domain, type, desc) fail should fail -ok 626 - domain_type_isnt(domain, type, desc) fail should have the proper description -ok 627 - domain_type_isnt(domain, type, desc) fail should have the proper diagnostics -ok 628 - domain_type_isnt(nondomain, type, desc) should fail -ok 629 - domain_type_isnt(nondomain, type, desc) should have the proper description -ok 630 - domain_type_isnt(nondomain, type, desc) should have the proper diagnostics +ok 565 - domain_type_is(schema, DOMAIN, schema, TYPE, desc) should pass +ok 566 - domain_type_is(schema, DOMAIN, schema, TYPE, desc) should have the proper description +ok 567 - domain_type_is(schema, DOMAIN, schema, TYPE, desc) should have the proper diagnostics +ok 568 - domain_type_is(schema, domain, schema, type, desc) fail should fail +ok 569 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper description +ok 570 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 571 - domain_type_is(schema, nondomain, schema, type, desc) should fail +ok 572 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper description +ok 573 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 574 - domain_type_is(schema, type, schema, type, desc) fail should fail +ok 575 - domain_type_is(schema, type, schema, type, desc) fail should have the proper description +ok 576 - domain_type_is(schema, type, schema, type, desc) fail should have the proper diagnostics +ok 577 - domain_type_is(schema, domain, type, desc) should pass +ok 578 - domain_type_is(schema, domain, type, desc) should have the proper description +ok 579 - domain_type_is(schema, domain, type, desc) should have the proper diagnostics +ok 580 - domain_type_is(schema, domain, type) should pass +ok 581 - domain_type_is(schema, domain, type) should have the proper description +ok 582 - domain_type_is(schema, domain, type) should have the proper diagnostics +ok 583 - domain_type_is(schema, DOMAIN, TYPE, desc) should pass +ok 584 - domain_type_is(schema, DOMAIN, TYPE, desc) should have the proper description +ok 585 - domain_type_is(schema, DOMAIN, TYPE, desc) should have the proper diagnostics +ok 586 - domain_type_is(schema, domain, type, desc) fail should fail +ok 587 - domain_type_is(schema, domain, type, desc) fail should have the proper description +ok 588 - domain_type_is(schema, domain, type, desc) fail should have the proper diagnostics +ok 589 - domain_type_is(schema, nondomain, type, desc) should fail +ok 590 - domain_type_is(schema, nondomain, type, desc) should have the proper description +ok 591 - domain_type_is(schema, nondomain, type, desc) should have the proper diagnostics +ok 592 - domain_type_is(schema, type, type, desc) fail should fail +ok 593 - domain_type_is(schema, type, type, desc) fail should have the proper description +ok 594 - domain_type_is(schema, type, type, desc) fail should have the proper diagnostics +ok 595 - domain_type_is(domain, type, desc) should pass +ok 596 - domain_type_is(domain, type, desc) should have the proper description +ok 597 - domain_type_is(domain, type, desc) should have the proper diagnostics +ok 598 - domain_type_is(domain, type) should pass +ok 599 - domain_type_is(domain, type) should have the proper description +ok 600 - domain_type_is(domain, type) should have the proper diagnostics +ok 601 - domain_type_is(DOMAIN, TYPE, desc) should pass +ok 602 - domain_type_is(DOMAIN, TYPE, desc) should have the proper description +ok 603 - domain_type_is(DOMAIN, TYPE, desc) should have the proper diagnostics +ok 604 - domain_type_is(domain, type, desc) fail should fail +ok 605 - domain_type_is(domain, type, desc) fail should have the proper description +ok 606 - domain_type_is(domain, type, desc) fail should have the proper diagnostics +ok 607 - domain_type_is(nondomain, type, desc) should fail +ok 608 - domain_type_is(nondomain, type, desc) should have the proper description +ok 609 - domain_type_is(nondomain, type, desc) should have the proper diagnostics +ok 610 - domain_type_is(type, type, desc) fail should fail +ok 611 - domain_type_is(type, type, desc) fail should have the proper description +ok 612 - domain_type_is(type, type, desc) fail should have the proper diagnostics +ok 613 - domain_type_isnt(schema, domain, schema, type, desc) should pass +ok 614 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper description +ok 615 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper diagnostics +ok 616 - domain_type_isnt(schema, domain, schema, type) should pass +ok 617 - domain_type_isnt(schema, domain, schema, type) should have the proper description +ok 618 - domain_type_isnt(schema, domain, schema, type) should have the proper diagnostics +ok 619 - domain_type_isnt(schema, DOMAIN, schema, TYPE, desc) should pass +ok 620 - domain_type_isnt(schema, DOMAIN, schema, TYPE, desc) should have the proper description +ok 621 - domain_type_isnt(schema, DOMAIN, schema, TYPE, desc) should have the proper diagnostics +ok 622 - domain_type_isnt(schema, domain, schema, type, desc) fail should fail +ok 623 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper description +ok 624 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 625 - domain_type_isnt(schema, DOMAIN, schema, TYPE, desc) fail should fail +ok 626 - domain_type_isnt(schema, DOMAIN, schema, TYPE, desc) fail should have the proper description +ok 627 - domain_type_isnt(schema, DOMAIN, schema, TYPE, desc) fail should have the proper diagnostics +ok 628 - domain_type_isnt(schema, nondomain, schema, type, desc) should fail +ok 629 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper description +ok 630 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 631 - domain_type_isnt(schema, type, schema, type, desc) should fail +ok 632 - domain_type_isnt(schema, type, schema, type, desc) should have the proper description +ok 633 - domain_type_isnt(schema, type, schema, type, desc) should have the proper diagnostics +ok 634 - domain_type_isnt(schema, domain, type, desc) should pass +ok 635 - domain_type_isnt(schema, domain, type, desc) should have the proper description +ok 636 - domain_type_isnt(schema, domain, type, desc) should have the proper diagnostics +ok 637 - domain_type_isnt(schema, domain, type) should pass +ok 638 - domain_type_isnt(schema, domain, type) should have the proper description +ok 639 - domain_type_isnt(schema, domain, type) should have the proper diagnostics +ok 640 - domain_type_isnt(schema, DOMAIN, TYPE, desc) should pass +ok 641 - domain_type_isnt(schema, DOMAIN, TYPE, desc) should have the proper description +ok 642 - domain_type_isnt(schema, DOMAIN, TYPE, desc) should have the proper diagnostics +ok 643 - domain_type_isnt(schema, domain, type, desc) fail should fail +ok 644 - domain_type_isnt(schema, domain, type, desc) fail should have the proper description +ok 645 - domain_type_isnt(schema, domain, type, desc) fail should have the proper diagnostics +ok 646 - domain_type_isnt(schema, DOMAIN, TYPE, desc) fail should fail +ok 647 - domain_type_isnt(schema, DOMAIN, TYPE, desc) fail should have the proper description +ok 648 - domain_type_isnt(schema, DOMAIN, TYPE, desc) fail should have the proper diagnostics +ok 649 - domain_type_isnt(schema, nondomain, type, desc) should fail +ok 650 - domain_type_isnt(schema, nondomain, type, desc) should have the proper description +ok 651 - domain_type_isnt(schema, nondomain, type, desc) should have the proper diagnostics +ok 652 - domain_type_isnt(schema, type, type, desc) should fail +ok 653 - domain_type_isnt(schema, type, type, desc) should have the proper description +ok 654 - domain_type_isnt(schema, type, type, desc) should have the proper diagnostics +ok 655 - domain_type_isnt(domain, type, desc) should pass +ok 656 - domain_type_isnt(domain, type, desc) should have the proper description +ok 657 - domain_type_isnt(domain, type, desc) should have the proper diagnostics +ok 658 - domain_type_isnt(DOMAIN, TYPE, desc) should pass +ok 659 - domain_type_isnt(DOMAIN, TYPE, desc) should have the proper description +ok 660 - domain_type_isnt(DOMAIN, TYPE, desc) should have the proper diagnostics +ok 661 - domain_type_isnt(domain, type) should pass +ok 662 - domain_type_isnt(domain, type) should have the proper description +ok 663 - domain_type_isnt(domain, type) should have the proper diagnostics +ok 664 - domain_type_isnt(domain, type, desc) fail should fail +ok 665 - domain_type_isnt(domain, type, desc) fail should have the proper description +ok 666 - domain_type_isnt(domain, type, desc) fail should have the proper diagnostics +ok 667 - domain_type_isnt(DOMAIN, TYPE, desc) fail should fail +ok 668 - domain_type_isnt(DOMAIN, TYPE, desc) fail should have the proper description +ok 669 - domain_type_isnt(DOMAIN, TYPE, desc) fail should have the proper diagnostics +ok 670 - domain_type_isnt(nondomain, type, desc) should fail +ok 671 - domain_type_isnt(nondomain, type, desc) should have the proper description +ok 672 - domain_type_isnt(nondomain, type, desc) should have the proper diagnostics +ok 673 - domain_type_isnt(type, type, desc) should fail +ok 674 - domain_type_isnt(type, type, desc) should have the proper description +ok 675 - domain_type_isnt(type, type, desc) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index e6cfea5baa92..bcbd76aca58d 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1081,6 +1081,80 @@ RETURNS TEXT AS $$ SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should allow NULL', 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_table_is_visible(c.oid) + AND c.relname = $1 + AND a.attname = $2 + AND attnum > 0 + AND NOT a.attisdropped + AND pg_type_is_visible(a.atttypid) +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_col_ns_type ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT quote_ident(tn.nspname) || '.' || 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; + +-- 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 + actual_type TEXT := _get_col_ns_type($1, $2, $3); +BEGIN + IF actual_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 actual_type = quote_ident($4) || '.' || LOWER($5) 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: ' || actual_type || + E'\n want: ' || quote_ident($4) || '.' || LOWER($5) + ); +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) || '.' || quote_ident($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 $$ @@ -1089,34 +1163,16 @@ DECLARE BEGIN -- Get the data type. IF $1 IS NULL THEN - IF NOT _cexists( $2, $3 ) THEN - RETURN fail( $5 ) || E'\n' - || diag (' Column ' || quote_ident($2) || '.' || quote_ident($3) || ' does not exist' ); - END IF; - - SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) into actual_type - FROM pg_catalog.pg_attribute a - JOIN pg_catalog.pg_class c ON a.attrelid = c.oid - WHERE pg_table_is_visible(c.oid) - AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END - AND c.relname = $2 - AND attnum > 0 - AND a.attname = $3; + actual_type := _get_col_type($2, $3); ELSE - 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; + actual_type := _get_col_type($1, $2, $3); + END IF; - SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) into actual_type - 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 attnum > 0 - AND a.attname = $3 - AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END; + IF actual_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; IF actual_type = LOWER($4) THEN @@ -1126,12 +1182,18 @@ BEGIN -- Wrong data type. tell 'em what we really got. RETURN ok( false, $5 ) || E'\n' || diag( - ' have: ' || COALESCE( actual_type::text, 'NULL' ) || + ' have: ' || actual_type || E'\n want: ' || $4::text ); 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) || ' should be type ' || $3 ); +$$ LANGUAGE SQL; + -- col_type_is( table, column, type, description ) CREATE OR REPLACE FUNCTION col_type_is ( NAME, NAME, TEXT, TEXT ) RETURNS TEXT AS $$ @@ -6761,7 +6823,7 @@ RETURNS BOOLEAN AS $$ ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _get_type( NAME, NAME, CHAR[] ) +CREATE OR REPLACE FUNCTION _get_dtype( NAME, NAME ) RETURNS TEXT[] AS $$ SELECT ARRAY[quote_ident(tn.nspname), pg_catalog.format_type(t.oid, t.typtypmod)] FROM pg_catalog.pg_type d @@ -6770,25 +6832,25 @@ RETURNS TEXT[] AS $$ JOIN pg_catalog.pg_namespace tn ON d.typnamespace = tn.oid WHERE d.typisdefined AND dn.nspname = $1 - AND d.typname = $2 - AND t.typtype = ANY( COALESCE($3, ARRAY['b', 'c', 'd', 'p', 'e']) ) + AND d.typname = LOWER($2) + AND d.typtype = 'd' $$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION _get_type( NAME, CHAR[] ) +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 + JOIN pg_catalog.pg_type t ON d.typbasetype = t.oid WHERE d.typisdefined - AND d.typname = $1 - AND t.typtype = ANY( COALESCE($2, ARRAY['b', 'c', 'd', 'p', 'e']) ) + 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_type($1, $2, ARRAY['b']); + actual_type TEXT[] := _get_dtype($1, $2); BEGIN IF actual_type[1] IS NULL THEN RETURN fail( $5 ) || E'\n' || diag ( @@ -6799,7 +6861,7 @@ BEGIN RETURN is( actual_type[1] || '.' || actual_type[2], - quote_ident($3) || '.' || $4, + quote_ident($3) || '.' || LOWER($4), $5 ); END; @@ -6819,7 +6881,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION domain_type_is( NAME, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE - actual_type TEXT[] := _get_type($1, $2, ARRAY['b']); + actual_type TEXT[] := _get_dtype($1, $2); BEGIN IF actual_type[1] IS NULL THEN RETURN fail( $4 ) || E'\n' || diag ( @@ -6828,7 +6890,7 @@ BEGIN ); END IF; - RETURN is( actual_type[2], $3, $4 ); + RETURN is( actual_type[2], LOWER($3), $4 ); END; $$ LANGUAGE plpgsql; @@ -6846,7 +6908,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION domain_type_is( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE - actual_type TEXT := _get_type($1, ARRAY['b']); + actual_type TEXT := _get_dtype($1); BEGIN IF actual_type IS NULL THEN RETURN fail( $3 ) || E'\n' || diag ( @@ -6854,7 +6916,7 @@ BEGIN ); END IF; - RETURN is( actual_type, $2, $3 ); + RETURN is( actual_type, LOWER($2), $3 ); END; $$ LANGUAGE plpgsql; @@ -6871,7 +6933,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION domain_type_isnt( NAME, TEXT, NAME, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE - actual_type TEXT[] := _get_type($1, $2, ARRAY['b']); + actual_type TEXT[] := _get_dtype($1, $2); BEGIN IF actual_type[1] IS NULL THEN RETURN fail( $5 ) || E'\n' || diag ( @@ -6882,7 +6944,8 @@ BEGIN RETURN isnt( actual_type[1] || '.' || actual_type[2], - quote_ident($3) || '.' || $4, + quote_ident($3) || '.' || LOWER($4), + $5 ); END; @@ -6902,7 +6965,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION domain_type_isnt( NAME, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE - actual_type TEXT[] := _get_type($1, $2, ARRAY['b']); + actual_type TEXT[] := _get_dtype($1, $2); BEGIN IF actual_type[1] IS NULL THEN RETURN fail( $4 ) || E'\n' || diag ( @@ -6911,7 +6974,7 @@ BEGIN ); END IF; - RETURN isnt( actual_type[2], $3, $4 ); + RETURN isnt( actual_type[2], LOWER($3), $4 ); END; $$ LANGUAGE plpgsql; @@ -6929,7 +6992,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION domain_type_isnt( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE - actual_type TEXT := _get_type($1, ARRAY['b']); + actual_type TEXT := _get_dtype($1); BEGIN IF actual_type IS NULL THEN RETURN fail( $3 ) || E'\n' || diag ( @@ -6937,7 +7000,7 @@ BEGIN ); END IF; - RETURN isnt( actual_type, $2, $3 ); + RETURN isnt( actual_type, LOWER($2), $3 ); END; $$ LANGUAGE plpgsql; diff --git a/sql/coltap.sql b/sql/coltap.sql index 99b5edf4689d..726817afbd9e 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(171); +SELECT plan(192); --SELECT * from no_plan(); -- This will be rolled back. :-) @@ -134,6 +134,57 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test col_type_is(). +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'name', 'pg_catalog', 'text', 'name is text' ), + true, + 'col_type_is( sch, tab, col, sch, type, desc )', + 'name is text', + '' +); + +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'name', 'pg_catalog'::name, 'text' ), + true, + 'col_type_is( sch, tab, col, sch, type, desc )', + 'Column public.sometab.name should be type pg_catalog.text', + '' +); + +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'name', 'pg_catalog', 'integer', 'whatever' ), + false, + 'col_type_is( sch, tab, col, sch, type, desc ) fail', + 'whatever', + ' have: pg_catalog.text + want: pg_catalog.integer' +); + +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'name', 'pg_catalog', 'blech', 'whatever' ), + false, + 'col_type_is( sch, tab, col, sch, non-type, desc )', + 'whatever', + ' have: pg_catalog.text + want: pg_catalog.blech' +); + +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'name', 'fooey', 'text', 'whatever' ), + false, + 'col_type_is( sch, tab, col, non-sch, type, desc )', + 'whatever', + ' have: pg_catalog.text + want: fooey.text' +); + +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'nonesuch', 'pg_catalog', 'text', 'whatever' ), + false, + 'col_type_is( sch, tab, non-col, sch, type, desc )', + 'whatever', + ' Column public.sometab.nonesuch does not exist' +); + SELECT * FROM check_test( col_type_is( 'public', 'sometab', 'name', 'text', 'name is text' ), true, @@ -142,6 +193,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'name'::name, 'text' ), + true, + 'col_type_is( sch, tab, col, type )', + 'Column public.sometab should be type name', + '' +); + SELECT * FROM check_test( col_type_is( 'sometab', 'name', 'text', 'yadda yadda yadda' ), true, diff --git a/sql/hastap.sql b/sql/hastap.sql index 30dcae1a8196..ee78b79028c0 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(630); +SELECT plan(675); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -1579,6 +1579,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + domain_type_is( 'public', 'US_POSTAL_CODE', 'public', 'TEXT', 'whatever'), + true, + 'domain_type_is(schema, DOMAIN, schema, TYPE, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( domain_type_is( 'public', 'us_postal_code', 'public', 'integer', 'whatever'), false, @@ -1596,6 +1604,14 @@ SELECT * FROM check_test( ' Domain public.zip_code does not exist' ); +SELECT * FROM check_test( + domain_type_is( 'public', 'integer', 'public', 'integer', 'whatever'), + false, + 'domain_type_is(schema, type, schema, type, desc) fail', + 'whatever', + ' Domain public.integer does not exist' +); + SELECT * FROM check_test( domain_type_is( 'public', 'us_postal_code', 'text', 'whatever'), true, @@ -1612,6 +1628,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + domain_type_is( 'public', 'US_POSTAL_CODE', 'TEXT', 'whatever'), + true, + 'domain_type_is(schema, DOMAIN, TYPE, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( domain_type_is( 'public', 'us_postal_code', 'integer', 'whatever'), false, @@ -1629,6 +1653,14 @@ SELECT * FROM check_test( ' Domain public.zip_code does not exist' ); +SELECT * FROM check_test( + domain_type_is( 'public', 'integer', 'integer', 'whatever'), + false, + 'domain_type_is(schema, type, type, desc) fail', + 'whatever', + ' Domain public.integer does not exist' +); + SELECT * FROM check_test( domain_type_is( 'us_postal_code', 'text', 'whatever'), true, @@ -1645,6 +1677,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + domain_type_is( 'US_POSTAL_CODE', 'TEXT', 'whatever'), + true, + 'domain_type_is(DOMAIN, TYPE, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( domain_type_is( 'us_postal_code', 'integer', 'whatever'), false, @@ -1662,6 +1702,14 @@ SELECT * FROM check_test( ' Domain zip_code does not exist' ); +SELECT * FROM check_test( + domain_type_is( 'integer', 'integer', 'whatever'), + false, + 'domain_type_is(type, type, desc) fail', + 'whatever', + ' Domain integer does not exist' +); + SELECT * FROM check_test( domain_type_isnt( 'public', 'us_postal_code', 'public', 'integer', 'whatever'), true, @@ -1678,6 +1726,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + domain_type_isnt( 'public', 'US_POSTAL_CODE', 'public', 'INTEGER', 'whatever'), + true, + 'domain_type_isnt(schema, DOMAIN, schema, TYPE, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( domain_type_isnt( 'public', 'us_postal_code', 'public', 'text', 'whatever'), false, @@ -1687,6 +1743,15 @@ SELECT * FROM check_test( want: anything else' ); +SELECT * FROM check_test( + domain_type_isnt( 'public', 'US_POSTAL_CODE', 'public', 'TEXT', 'whatever'), + false, + 'domain_type_isnt(schema, DOMAIN, schema, TYPE, desc) fail', + 'whatever', + ' have: public.text + want: anything else' +); + SELECT * FROM check_test( domain_type_isnt( 'public', 'zip_code', 'public', 'text', 'whatever'), false, @@ -1695,6 +1760,14 @@ SELECT * FROM check_test( ' Domain public.zip_code does not exist' ); +SELECT * FROM check_test( + domain_type_isnt( 'public', 'integer', 'public', 'text', 'whatever'), + false, + 'domain_type_isnt(schema, type, schema, type, desc)', + 'whatever', + ' Domain public.integer does not exist' +); + SELECT * FROM check_test( domain_type_isnt( 'public', 'us_postal_code', 'integer', 'whatever'), true, @@ -1711,6 +1784,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + domain_type_isnt( 'public', 'US_POSTAL_CODE', 'INTEGER', 'whatever'), + true, + 'domain_type_isnt(schema, DOMAIN, TYPE, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( domain_type_isnt( 'public', 'us_postal_code', 'text', 'whatever'), false, @@ -1720,6 +1801,15 @@ SELECT * FROM check_test( want: anything else' ); +SELECT * FROM check_test( + domain_type_isnt( 'public', 'US_POSTAL_CODE', 'TEXT', 'whatever'), + false, + 'domain_type_isnt(schema, DOMAIN, TYPE, desc) fail', + 'whatever', + ' have: text + want: anything else' +); + SELECT * FROM check_test( domain_type_isnt( 'public', 'zip_code', 'text', 'whatever'), false, @@ -1728,6 +1818,14 @@ SELECT * FROM check_test( ' Domain public.zip_code does not exist' ); +SELECT * FROM check_test( + domain_type_isnt( 'public', 'integer', 'text', 'whatever'), + false, + 'domain_type_isnt(schema, type, type, desc)', + 'whatever', + ' Domain public.integer does not exist' +); + SELECT * FROM check_test( domain_type_isnt( 'us_postal_code', 'integer', 'whatever'), true, @@ -1736,6 +1834,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + domain_type_isnt( 'US_POSTAL_CODE', 'INTEGER', 'whatever'), + true, + 'domain_type_isnt(DOMAIN, TYPE, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( domain_type_isnt( 'us_postal_code', 'integer'), true, @@ -1753,6 +1859,15 @@ SELECT * FROM check_test( want: anything else' ); +SELECT * FROM check_test( + domain_type_isnt( 'US_POSTAL_CODE', 'TEXT', 'whatever'), + false, + 'domain_type_isnt(DOMAIN, TYPE, desc) fail', + 'whatever', + ' have: text + want: anything else' +); + SELECT * FROM check_test( domain_type_isnt( 'zip_code', 'text', 'whatever'), false, @@ -1761,6 +1876,14 @@ SELECT * FROM check_test( ' Domain zip_code does not exist' ); +SELECT * FROM check_test( + domain_type_isnt( 'integer', 'text', 'whatever'), + false, + 'domain_type_isnt(type, type, desc)', + 'whatever', + ' Domain integer does not exist' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index d571ede1e076..b9716f0a589b 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -11,8 +11,8 @@ DROP FUNCTION domain_type_is( NAME, TEXT, TEXT ); DROP FUNCTION domain_type_is( NAME, TEXT, TEXT, TEXT ); DROP FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT ); DROP FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT, TEXT ); -DROP FUNCTION _get_type( NAME, CHAR[] ); -DROP FUNCTION _get_type( NAME, NAME, CHAR[] ); +DROP FUNCTION _get_dtype( NAME ); +DROP FUNCTION _get_dtype( NAME, NAME ); DROP FUNCTION _dexists ( NAME ); DROP FUNCTION _dexists ( NAME, NAME ); DROP FUNCTION enums_are ( NAME[] ); @@ -563,7 +563,13 @@ DROP FUNCTION _has_def ( NAME, NAME ); DROP FUNCTION _has_def ( NAME, NAME, NAME ); DROP FUNCTION col_type_is ( NAME, NAME, TEXT ); DROP FUNCTION col_type_is ( NAME, NAME, TEXT, TEXT ); +DROP FUNCTION col_type_is ( NAME, NAME, NAME, TEXT ); DROP FUNCTION col_type_is ( NAME, NAME, NAME, TEXT, TEXT ); +DROP FUNCTION col_type_is ( NAME, NAME, NAME, NAME, TEXT ); +DROP FUNCTION col_type_is ( NAME, NAME, NAME, NAME, TEXT, TEXT ); +DROP FUNCTION _get_col_ns_type ( NAME, NAME, NAME ); +DROP FUNCTION _get_col_type ( NAME, NAME ); +DROP FUNCTION _get_col_type ( NAME, NAME, NAME ); DROP FUNCTION col_is_null ( NAME, NAME ); DROP FUNCTION col_is_null ( NAME, NAME, NAME ); DROP FUNCTION col_is_null ( NAME, NAME, NAME, TEXT ); From c28e71549a583a55978c56b8305d4c6715cea332 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 3 Dec 2009 17:35:56 -0800 Subject: [PATCH 0467/1195] * Add `display_type()`. Replaced all internal use of `format_type()` with it. This is so that schemas are always displayed properly. --- Changes | 2 + README.pgtap | 10 +++++ expected/util.out | 12 +++++- pgtap.sql.in | 98 +++++++++++++++++++++--------------------- sql/util.sql | 16 ++++++- uninstall_pgtap.sql.in | 4 +- 6 files changed, 91 insertions(+), 51 deletions(-) diff --git a/Changes b/Changes index 652709548daa..a1a9c755c0ab 100644 --- a/Changes +++ b/Changes @@ -32,6 +32,8 @@ Revision history for pgTAP implementation by Bob Lunney. * Added variants of `col_is_type()` to allow a schema name to be specified for the type. +* Added `display_type()` and replaced all internal use of `format_type()` with + it. This is so that schemas are always displayed properly. 0.22 2009-07-31T00:26:16 ------------------------- diff --git a/README.pgtap b/README.pgtap index 8ad377b706cc..fb0d05e2d6bd 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3693,6 +3693,16 @@ On PostgreSQL 8.4 and higher, it can take any number of arguments. Lower than ok(false, 'This should fail) ]); +### `display_type( schema, type_oid, typemod )` ### +### `display_type( type_oid, typemod )` ### + + SELECT display_type('public', 'varchar'::regtype, NULL ); + SELECT display_type('numeric'::regtype, 196612 ); + +Like `pg_catalot.format_type()`, except that the returned value is not +prepended with the schema name unless it is passed as the first argument. Used +internally by pgTAP to compare type names, but may be more generally useful. + ### `pg_typeof(any)` ### SELECT pg_typeof(:value); diff --git a/expected/util.out b/expected/util.out index 03a56faecf30..40893c767a5f 100644 --- a/expected/util.out +++ b/expected/util.out @@ -1,5 +1,5 @@ \unset ECHO -1..13 +1..23 ok 1 - pg_type(int) should work ok 2 - pg_type(numeric) should work ok 3 - pg_type(text) should work @@ -13,3 +13,13 @@ ok 10 - os_name() should output something like an OS name ok 11 - findfincs() should return distinct values ok 12 - pgtap_version() should work ok 13 - collect_tap() should simply collect tap +ok 14 - display_type(int4) +ok 15 - display_type(numeric) +ok 16 - display_type(numeric, typmod) +ok 17 - display_type("char") +ok 18 - display_type(char) +ok 19 - display_type(timestamp) +ok 20 - display_type(timestamptz) +ok 21 - display_type(foo, int4) +ok 22 - display_type(HEY, numeric) +ok 23 - display_type(t z, int4) diff --git a/pgtap.sql.in b/pgtap.sql.in index bcbd76aca58d..24fa3e80db0b 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1081,9 +1081,21 @@ RETURNS TEXT AS $$ SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should allow NULL', false ); $$ LANGUAGE SQL; +CREATE OR REPLACE FUNCTION display_type ( OID, INTEGER ) +RETURNS TEXT AS $$ + SELECT $1::regtype + || COALESCE(substring(pg_catalog.format_type($1, $2), '[(][^)]+[)]$'), '') +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION display_type ( NAME, OID, INTEGER ) +RETURNS TEXT AS $$ + SELECT CASE WHEN $1 IS NULL THEN '' ELSE quote_ident($1) || '.' END + || display_type($2, $3) +$$ LANGUAGE SQL; + CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME, NAME ) RETURNS TEXT AS $$ - SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) + SELECT display_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 @@ -1096,7 +1108,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) + SELECT display_type(a.atttypid, a.atttypmod) FROM pg_catalog.pg_attribute a JOIN pg_catalog.pg_class c ON a.attrelid = c.oid WHERE pg_table_is_visible(c.oid) @@ -1109,7 +1121,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _get_col_ns_type ( NAME, NAME, NAME ) RETURNS TEXT AS $$ - SELECT quote_ident(tn.nspname) || '.' || pg_catalog.format_type(a.atttypid, a.atttypmod) + SELECT display_type(tn.nspname, 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 @@ -1325,7 +1337,7 @@ BEGIN RETURN _def_is( pg_catalog.pg_get_expr(d.adbin, d.adrelid), - pg_catalog.format_type(a.atttypid, a.atttypmod), + display_type(a.atttypid, a.atttypmod), $4, $5 ) FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_attribute a, @@ -1359,7 +1371,7 @@ BEGIN RETURN _def_is( pg_catalog.pg_get_expr(d.adbin, d.adrelid), - pg_catalog.format_type(a.atttypid, a.atttypmod), + display_type(a.atttypid, a.atttypmod), $3, $4 ) FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a, pg_catalog.pg_attrdef d @@ -3606,8 +3618,8 @@ RETURNS BOOLEAN AS $$ 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 pg_catalog.format_type(castsource, NULL) = $1 - AND pg_catalog.format_type(casttarget, NULL) = $2 + WHERE display_type(castsource, NULL) = $1 + AND display_type(casttarget, NULL) = $2 AND n.nspname = $3 AND p.proname = $4 ); @@ -3619,8 +3631,8 @@ RETURNS BOOLEAN AS $$ SELECT TRUE FROM pg_catalog.pg_cast c JOIN pg_catalog.pg_proc p ON c.castfunc = p.oid - WHERE pg_catalog.format_type(castsource, NULL) = $1 - AND pg_catalog.format_type(casttarget, NULL) = $2 + WHERE display_type(castsource, NULL) = $1 + AND display_type(casttarget, NULL) = $2 AND p.proname = $3 ); $$ LANGUAGE SQL; @@ -3630,8 +3642,8 @@ RETURNS BOOLEAN AS $$ SELECT EXISTS ( SELECT TRUE FROM pg_catalog.pg_cast c - WHERE pg_catalog.format_type(castsource, NULL) = $1 - AND pg_catalog.format_type(casttarget, NULL) = $2 + WHERE display_type(castsource, NULL) = $1 + AND display_type(casttarget, NULL) = $2 ); $$ LANGUAGE SQL; @@ -3746,8 +3758,8 @@ CREATE OR REPLACE FUNCTION _get_context( NAME, NAME ) RETURNS "char" AS $$ SELECT c.castcontext FROM pg_catalog.pg_cast c - WHERE pg_catalog.format_type(castsource, NULL) = $1 - AND pg_catalog.format_type(casttarget, NULL) = $2 + WHERE display_type(castsource, NULL) = $1 + AND display_type(casttarget, NULL) = $2 $$ LANGUAGE SQL; -- cast_context_is( source_type, target_type, context, description ) @@ -3787,10 +3799,10 @@ RETURNS BOOLEAN AS $$ WHERE n.nspname = $2 AND o.oprname = $3 AND CASE o.oprkind WHEN 'l' THEN $1 IS NULL - ELSE pg_catalog.format_type(o.oprleft, NULL) = $1 END + ELSE display_type(o.oprleft, NULL) = $1 END AND CASE o.oprkind WHEN 'r' THEN $4 IS NULL - ELSE pg_catalog.format_type(o.oprright, NULL) = $4 END - AND pg_catalog.format_type(o.oprresult, NULL) = $5 + ELSE display_type(o.oprright, NULL) = $4 END + AND display_type(o.oprresult, NULL) = $5 ); $$ LANGUAGE SQL; @@ -3802,10 +3814,10 @@ RETURNS BOOLEAN AS $$ WHERE pg_catalog.pg_operator_is_visible(o.oid) AND o.oprname = $2 AND CASE o.oprkind WHEN 'l' THEN $1 IS NULL - ELSE pg_catalog.format_type(o.oprleft, NULL) = $1 END + ELSE display_type(o.oprleft, NULL) = $1 END AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL - ELSE pg_catalog.format_type(o.oprright, NULL) = $3 END - AND pg_catalog.format_type(o.oprresult, NULL) = $4 + ELSE display_type(o.oprright, NULL) = $3 END + AND display_type(o.oprresult, NULL) = $4 ); $$ LANGUAGE SQL; @@ -3817,9 +3829,9 @@ RETURNS BOOLEAN AS $$ WHERE pg_catalog.pg_operator_is_visible(o.oid) AND o.oprname = $2 AND CASE o.oprkind WHEN 'l' THEN $1 IS NULL - ELSE pg_catalog.format_type(o.oprleft, NULL) = $1 END + ELSE display_type(o.oprleft, NULL) = $1 END AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL - ELSE pg_catalog.format_type(o.oprright, NULL) = $3 END + ELSE display_type(o.oprright, NULL) = $3 END ); $$ LANGUAGE SQL; @@ -5844,7 +5856,7 @@ $$ 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) + SELECT display_type(a.atttypid, a.atttypmod) FROM pg_catalog.pg_attribute a JOIN pg_catalog.pg_class c ON a.attrelid = c.oid WHERE c.relname = $1 @@ -6800,7 +6812,6 @@ 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 $$ @@ -6823,9 +6834,9 @@ RETURNS BOOLEAN AS $$ ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _get_dtype( NAME, NAME ) -RETURNS TEXT[] AS $$ - SELECT ARRAY[quote_ident(tn.nspname), pg_catalog.format_type(t.oid, t.typtypmod)] +CREATE OR REPLACE FUNCTION _get_dtype( NAME, TEXT, BOOLEAN ) +RETURNS TEXT AS $$ + SELECT display_type(CASE WHEN $3 THEN tn.nspname ELSE NULL END, 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 @@ -6838,7 +6849,7 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _get_dtype( NAME ) RETURNS TEXT AS $$ - SELECT pg_catalog.format_type(t.oid, t.typtypmod) + SELECT display_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 @@ -6850,20 +6861,16 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE - actual_type TEXT[] := _get_dtype($1, $2); + actual_type TEXT := _get_dtype($1, $2, true); BEGIN - IF actual_type[1] IS NULL THEN + IF actual_type IS NULL THEN RETURN fail( $5 ) || E'\n' || diag ( ' Domain ' || quote_ident($1) || '.' || $2 || ' does not exist' ); END IF; - RETURN is( - actual_type[1] || '.' || actual_type[2], - quote_ident($3) || '.' || LOWER($4), - $5 - ); + RETURN is( actual_type, quote_ident($3) || '.' || LOWER($4), $5 ); END; $$ LANGUAGE plpgsql; @@ -6881,16 +6888,16 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION domain_type_is( NAME, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE - actual_type TEXT[] := _get_dtype($1, $2); + actual_type TEXT := _get_dtype($1, $2, false); BEGIN - IF actual_type[1] IS NULL THEN + 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[2], LOWER($3), $4 ); + RETURN is( actual_type, LOWER($3), $4 ); END; $$ LANGUAGE plpgsql; @@ -6933,21 +6940,16 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION domain_type_isnt( NAME, TEXT, NAME, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE - actual_type TEXT[] := _get_dtype($1, $2); + actual_type TEXT := _get_dtype($1, $2, true); BEGIN - IF actual_type[1] IS NULL THEN + IF actual_type IS NULL THEN RETURN fail( $5 ) || E'\n' || diag ( ' Domain ' || quote_ident($1) || '.' || $2 || ' does not exist' ); END IF; - RETURN isnt( - actual_type[1] || '.' || actual_type[2], - quote_ident($3) || '.' || LOWER($4), - - $5 - ); + RETURN isnt( actual_type, quote_ident($3) || '.' || LOWER($4), $5 ); END; $$ LANGUAGE plpgsql; @@ -6965,16 +6967,16 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION domain_type_isnt( NAME, TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE - actual_type TEXT[] := _get_dtype($1, $2); + actual_type TEXT := _get_dtype($1, $2, false); BEGIN - IF actual_type[1] IS NULL THEN + 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[2], LOWER($3), $4 ); + RETURN isnt( actual_type, LOWER($3), $4 ); END; $$ LANGUAGE plpgsql; diff --git a/sql/util.sql b/sql/util.sql index 26fca4c89453..886386e051e0 100644 --- a/sql/util.sql +++ b/sql/util.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(13); +SELECT plan(23); --SELECT * FROM no_plan(); SELECT is( pg_typeof(42), 'integer', 'pg_type(int) should work' ); @@ -71,6 +71,20 @@ baz', 'collect_tap() should simply collect tap' ); +/****************************************************************************/ +-- Test display_type(). +SELECT is( display_type('int4'::regtype, NULL), 'integer', 'display_type(int4)'); +SELECT is( display_type('numeric'::regtype, NULL), 'numeric', 'display_type(numeric)'); +SELECT is( display_type('numeric'::regtype, 196612), 'numeric(3,0)', 'display_type(numeric, typmod)'); +SELECT is( display_type('"char"'::regtype, NULL), '"char"', 'display_type("char")'); +SELECT is( display_type('char'::regtype, NULL), 'character', 'display_type(char)'); +SELECT is( display_type('timestamp'::regtype, NULL), 'timestamp without time zone', 'display_type(timestamp)'); +SELECT is( display_type('timestamptz'::regtype, NULL), 'timestamp with time zone', 'display_type(timestamptz)'); + +SELECT is( display_type('foo', 'int4'::regtype, NULL), 'foo.integer', 'display_type(foo, int4)'); +SELECT is( display_type('HEY', 'numeric'::regtype, NULL), '"HEY".numeric', 'display_type(HEY, numeric)'); +SELECT is( display_type('t z', 'int4'::regtype, NULL), '"t z".integer', 'display_type(t z, int4)'); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index b9716f0a589b..5517fa071140 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -12,7 +12,7 @@ DROP FUNCTION domain_type_is( NAME, TEXT, TEXT, TEXT ); DROP FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT ); DROP FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT, TEXT ); DROP FUNCTION _get_dtype( NAME ); -DROP FUNCTION _get_dtype( NAME, NAME ); +DROP FUNCTION _get_dtype( NAME, TEXT, BOOLEAN ); DROP FUNCTION _dexists ( NAME ); DROP FUNCTION _dexists ( NAME, NAME ); DROP FUNCTION enums_are ( NAME[] ); @@ -570,6 +570,8 @@ DROP FUNCTION col_type_is ( NAME, NAME, NAME, NAME, TEXT, TEXT ); DROP FUNCTION _get_col_ns_type ( NAME, NAME, NAME ); DROP FUNCTION _get_col_type ( NAME, NAME ); DROP FUNCTION _get_col_type ( NAME, NAME, NAME ); +DROP FUNCTION display_type ( NAME, OID, INTEGER ); +DROP FUNCTION display_type ( OID, INTEGER ); DROP FUNCTION col_is_null ( NAME, NAME ); DROP FUNCTION col_is_null ( NAME, NAME, NAME ); DROP FUNCTION col_is_null ( NAME, NAME, NAME, TEXT ); From d8e903447887242fe1e25af0829535f01b51fda0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 4 Dec 2009 00:09:21 -0800 Subject: [PATCH 0468/1195] Add `row_eq()`. --- Changes | 2 ++ README.pgtap | 57 +++++++++++++++++++++++++++-- expected/istap.out | 12 ++++++- expected/resultset.out | 21 ++++++++++- pgtap.sql.in | 27 ++++++++++++-- sql/istap.sql | 38 +++++++++++++++++++- sql/resultset.sql | 81 +++++++++++++++++++++++++++++++++++++++++- uninstall_pgtap.sql.in | 2 ++ 8 files changed, 231 insertions(+), 9 deletions(-) diff --git a/Changes b/Changes index a1a9c755c0ab..c9662e0cb9e5 100644 --- a/Changes +++ b/Changes @@ -34,6 +34,8 @@ Revision history for pgTAP the type. * Added `display_type()` and replaced all internal use of `format_type()` with it. This is so that schemas are always displayed properly. +* Added `row_eq()`. It's a 90% solution that requires that `record` values be + cast to existing composite values. But it should work well even at that.a 0.22 2009-07-31T00:26:16 ------------------------- diff --git a/README.pgtap b/README.pgtap index fb0d05e2d6bd..f1c759c35957 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1227,6 +1227,58 @@ fails and the results are displayed in the failure diagnostics, like so: # (1,Jacob,false) # (2,Emily,false) +### `row_eq( sql, record, description )` ### +### `row_eq( sql, record )` ### + + PREPARE testrow AS SELECT * FROM users where nick = 'theory'; + SELECT row_eq('testrow', ROW(1, 'theory', 'David Wheeler')::users); + +Compares the contents of a single row to a record. Due to the limitations of +non-C functions in PostgreSQL, a bar `RECORD` value cannot be passed to the +function. You must instead pass in a valid composite type value, and cast the +record argument (the second argument) to the same type. Both explicitly +created composite types and table types are supported. Thus, you can do this: + + CREATE TYPE sometype AS ( + id INT, + name TEXT + ); + + SELECT row_eq( ROW(1, 'foo')::sometype, ROW(1, 'foo')::sometype ); + +And, of course, thins: + + CREATE TABLE users ( + id INT, + name TEXT + ); + + INSERT INTO users VALUES (1, 'theory'); + + SELECT row_eq( id, name), ROW(1, 'theory')::users ) + FROM users; + +Compatible types can be compared, though. So if the `users` table actually +included an `active` column, for example, and you only wanted to test the +`id` and `name`, you could do this: + + SELECT row_eq( id, name), ROW(1, 'theory')::sometype ) + FROM users; + +Note the use of the `sometype` composite type for the second argument. The +upshot is that you can create composite types in your tests explicitly for +comparing the rerutn values of your queries, if such queries don't return an +existing valid type. + +Hopefully someday in the future we'll be able to support arbitrary `record` +arguments. In the meantime, this is the 90% solution. + +Diagnostics on failure are similar to those from `is()`: + + # Failed test 322 + # have: (1,Jacob) + # want: (1,Larry) + The Schema Things ================= @@ -4107,9 +4159,8 @@ To Do + `casts_are()` + `operators_are()` + `columns_are()` -* Useful result testing functions to consider adding (but might require C code): - + `row_eq()` - + `rowtype_is()` +* Useful result testing function to consider adding (but might require C + code): `rowtype_is()` Supported Versions ----------------- diff --git a/expected/istap.out b/expected/istap.out index 2001b9edeb47..ef68ccf315c7 100644 --- a/expected/istap.out +++ b/expected/istap.out @@ -1,5 +1,5 @@ \unset ECHO -1..37 +1..47 ok 1 - is(1, 1) should pass ok 2 - is(1, 1) should have the proper description ok 3 - is(1, 1) should have the proper diagnostics @@ -37,3 +37,13 @@ ok 34 - is(NULL, foo) should have the proper diagnostics ok 35 - is(foo, NULL) should fail ok 36 - is(foo, NULL) should have the proper description ok 37 - is(foo, NULL) should have the proper diagnostics +ok 38 - with records! +ok 39 - is(mumble, row) fail should fail +ok 40 - is(mumble, row) fail should have the proper description +ok 41 - is(mumble, row) fail should have the proper diagnostics +ok 42 - is(mumble, row) fail with NULL should fail +ok 43 - is(mumble, row) fail with NULL should have the proper description +ok 44 - is(mumble, row) fail with NULL should have the proper diagnostics +ok 45 - is(mumble, NULL) should fail +ok 46 - is(mumble, NULL) should have the proper description +ok 47 - is(mumble, NULL) should have the proper diagnostics diff --git a/expected/resultset.out b/expected/resultset.out index 2a66b207e0a7..b43bf96a13bc 100644 --- a/expected/resultset.out +++ b/expected/resultset.out @@ -1,5 +1,5 @@ \unset ECHO -1..493 +1..512 ok 1 - Should create temp table with simple query ok 2 - Table __foonames__ should exist ok 3 - Should create a temp table for a prepared statement @@ -493,3 +493,22 @@ ok 490 - is_empty(prepared, desc) fail should have the proper diagnostics ok 491 - is_empty(prepared) fail should fail ok 492 - is_empty(prepared) fail should have the proper description ok 493 - is_empty(prepared) fail should have the proper diagnostics +ok 494 - row_eq(prepared, record, desc) should pass +ok 495 - row_eq(prepared, record, desc) should have the proper description +ok 496 - row_eq(prepared, record, desc) should have the proper diagnostics +ok 497 - row_eq(sql, record, desc) should pass +ok 498 - row_eq(sql, record, desc) should have the proper description +ok 499 - row_eq(sql, record, desc) should have the proper diagnostics +ok 500 - row_eq(prepared, record, desc) should pass +ok 501 - row_eq(prepared, record, desc) should have the proper description +ok 502 - row_eq(prepared, record, desc) should have the proper diagnostics +ok 503 - row_eq(prepared, record, desc) should fail +ok 504 - row_eq(prepared, record, desc) should have the proper description +ok 505 - row_eq(prepared, record, desc) should have the proper diagnostics +ok 506 - row_eq(prepared, sometype, desc) should pass +ok 507 - row_eq(prepared, sometype, desc) should have the proper description +ok 508 - row_eq(prepared, sometype, desc) should have the proper diagnostics +ok 509 - row_eq(sqlrow, sometype, desc) should pass +ok 510 - row_eq(sqlrow, sometype, desc) should have the proper description +ok 511 - row_eq(sqlrow, sometype, desc) should have the proper diagnostics +ok 512 - threw 0A000 diff --git a/pgtap.sql.in b/pgtap.sql.in index 24fa3e80db0b..1e109ec6a200 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -324,8 +324,8 @@ BEGIN result := NOT $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: ' || COALESCE( $2::text, 'NULL' ) + ' 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; @@ -7014,3 +7014,26 @@ RETURNS TEXT AS $$ '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; + result BOOLEAN; +BEGIN + FOR rec in EXECUTE _query($1) LOOP + result := NOT rec IS DISTINCT FROM $2; + RETURN ok(result, $3 ) || CASE WHEN result THEN '' ELSE 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; + END LOOP; +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; diff --git a/sql/istap.sql b/sql/istap.sql index ecabe776c2da..828561813d79 100644 --- a/sql/istap.sql +++ b/sql/istap.sql @@ -1,7 +1,8 @@ \unset ECHO \i test_setup.sql -SELECT plan(37); +SELECT plan(47); +--SELECT * from no_plan(); /****************************************************************************/ -- Test is(). @@ -55,6 +56,41 @@ SELECT * FROM check_test( want: NULL' ); +SET client_min_messages = warning; +CREATE TABLE mumble ( id int, name text ); +RESET client_min_messages; +INSERT INTO mumble VALUES (1, 'hey'); + +SELECT is( mumble.*, ROW(1, 'hey')::mumble, 'with records!' ) + FROM mumble; + +SELECT check_test( + is( mumble.*, ROW(1, 'HEY')::mumble ), + false, + 'is(mumble, row) fail', + '', + ' have: (1,hey) + want: (1,HEY)' +) FROM mumble; + +SELECT check_test( + is( mumble.*, ROW(1, NULL)::mumble ), + false, + 'is(mumble, row) fail with NULL', + '', + ' have: (1,hey) + want: (1,)' +) FROM mumble; + +SELECT check_test( + is( mumble.*, NULL::mumble ), + false, + 'is(mumble, NULL)', + '', + ' have: (1,hey) + want: NULL' +) FROM mumble; + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/sql/resultset.sql b/sql/resultset.sql index 42dc295112e5..c34f55541d42 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(493); +SELECT plan(512); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -12,6 +12,12 @@ CREATE TABLE names ( name TEXT DEFAULT '' ); +CREATE TABLE someat ( + id SERIAL PRIMARY KEY, + ts timestamp DEFAULT NOW(), + active BOOLEAN DEFAULT TRUE +); + RESET client_min_messages; -- Top 100 boy an 100 girl names in 2005. http://www.ssa.gov/OACT/babynames/ @@ -2283,6 +2289,79 @@ SELECT * FROM check_test( (2,Emily)' ); +/****************************************************************************/ +-- Test row_eq(). +PREPARE arow AS SELECT id, name FROM names WHERE name = 'Jacob'; + +SELECT check_test( + row_eq('arow', ROW(1, 'Jacob')::names, 'whatever'), + true, + 'row_eq(prepared, record, desc)', + 'whatever', + '' +); + +SELECT check_test( + row_eq('SELECT id, name FROM names WHERE id = 1', ROW(1, 'Jacob')::names, 'whatever'), + true, + 'row_eq(sql, record, desc)', + 'whatever', + '' +); + +SELECT check_test( + row_eq('arow', ROW(1, 'Jacob')::names), + true, + 'row_eq(prepared, record, desc)', + '', + '' +); + +SELECT check_test( + row_eq('arow', ROW(1, 'Larry')::names), + false, + 'row_eq(prepared, record, desc)', + '', + ' have: (1,Jacob) + want: (1,Larry)' +); + +CREATE TYPE sometype AS ( + id INT, + name TEXT +); + +SELECT check_test( + row_eq('arow', ROW(1, 'Jacob')::sometype), + true, + 'row_eq(prepared, sometype, desc)', + '', + '' +); + +SELECT check_test( + row_eq('SELECT 1, ''Jacob''::text', ROW(1, 'Jacob')::sometype), + true, + 'row_eq(sqlrow, sometype, desc)', + '', + '' +); + +INSERT INTO someat (ts) values ('2009-12-04T07:22:52'); +SELECT throws_ok( + 'SELECT row_eq( ''SELECT id, ts FROM someat'', ROW(1, ''2009-12-04T07:22:52'') )', + '0A000' +-- 'PL/pgSQL functions cannot accept type record' +); + +-- SELECT check_test( +-- row_eq( 'SELECT id, ts FROM someat', ROW(1, '2009-12-04T07:22:52') ), +-- true, +-- 'row_eq(sql, rec)', +-- '', +-- '' +-- ); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 5517fa071140..37f0f83b65a4 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,4 +1,6 @@ -- ## SET search_path TO TAPSCHEMA, public; +DROP FUNCTION row_eq( TEXT, anyelement ); +DROP FUNCTION row_eq( TEXT, anyelement, TEXT ); DROP FUNCTION domain_type_isnt( TEXT, TEXT ); DROP FUNCTION domain_type_isnt( TEXT, TEXT, TEXT ); DROP FUNCTION domain_type_isnt( NAME, TEXT, TEXT ); From 4e0f858c7012b0ef5ec838c66f722691fed5fbaa Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 7 Dec 2009 11:50:10 -0800 Subject: [PATCH 0469/1195] Add `triggers_are()`. --- Changes | 3 +- README.pgtap | 28 +++++++++++- expected/trigger.out | 32 ++++++++++++- pgtap.sql.in | 70 +++++++++++++++++++++++++++++ sql/trigger.sql | 100 ++++++++++++++++++++++++++++++++++++++++- uninstall_pgtap.sql.in | 4 ++ 6 files changed, 232 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index c9662e0cb9e5..87048c1f3398 100644 --- a/Changes +++ b/Changes @@ -24,7 +24,8 @@ Revision history for pgTAP * Added `throws_like()`, `throws_ilike()`, `throws_matching()`, and `throws_imatching()` to test that an SQL statement throws an error message matching a `LIKE` pattern or a regular expression. -* Added `roles_are()`, `types_are()`, `domains_are()`, and `enums_are()`. +* Added `roles_are()`, `types_are()`, `domains_are()`, `enums_are()`, + and `triggers_are()`. * Fixed the diagnostic output from `isnt()` and `col_isnt_pk()` when they fail. They now more closely matches what Test::More's `isnt()` outputs and makes much more sense. diff --git a/README.pgtap b/README.pgtap index f1c759c35957..a4ac20dcc5bf 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1480,6 +1480,33 @@ missing indexes, like so: # Missing indexes: # idx_fou_name +### `triggers_are( schema, table, triggers[], description )` ### +### `triggers_are( schema, table, triggers[] )` ### +### `triggers_are( table, triggers[], description )` ### +### `triggers_are( table, triggers[] )` ### + + SELECT triggers_are( + 'myschema', + 'atable', + ARRAY[ 'atable_pkey', 'idx_atable_name' ], + 'Should have the correct triggers on myschema.atable' + ); + +This function tests that all of the triggers on the named table are only the +triggers that *should* be on that table. If the `:schema` argument is omitted, +the table must be visible in the search path, excluding `pg_catalog`. If the +description is omitted, a generally useful default description will be +generated. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing triggers, like so: + + # Failed test 180: "Table fou should have the correct triggers" + # Extra triggers: + # set_user_pass + # Missing triggers: + # set_users_pass + ### `functions_are( schema, functions[], description )` ### ### `functions_are( schema, functions[] )` ### ### `functions_are( functions[], description )` ### @@ -4155,7 +4182,6 @@ To Do + `sequence_increments_by()` + `sequence_starts_at()` + `sequence_cycles()` - + `triggers_are()` + `casts_are()` + `operators_are()` + `columns_are()` diff --git a/expected/trigger.out b/expected/trigger.out index 7014628eddec..52e6bc064c07 100644 --- a/expected/trigger.out +++ b/expected/trigger.out @@ -1,5 +1,5 @@ \unset ECHO -1..54 +1..84 ok 1 - has_trigger(schema, table, trigger, desc) should pass ok 2 - has_trigger(schema, table, trigger, desc) should have the proper description ok 3 - has_trigger(schema, table, trigger, desc) should have the proper diagnostics @@ -54,3 +54,33 @@ ok 51 - trigger_is() fail should have the proper diagnostics ok 52 - trigger_is() no schema fail should fail ok 53 - trigger_is() no schema fail should have the proper description ok 54 - trigger_is() no schema fail should have the proper diagnostics +ok 55 - triggers_are(schema, table, triggers, desc) should pass +ok 56 - triggers_are(schema, table, triggers, desc) should have the proper description +ok 57 - triggers_are(schema, table, triggers, desc) should have the proper diagnostics +ok 58 - triggers_are(schema, table, triggers) should pass +ok 59 - triggers_are(schema, table, triggers) should have the proper description +ok 60 - triggers_are(schema, table, triggers) should have the proper diagnostics +ok 61 - triggers_are(schema, table, triggers) + extra should fail +ok 62 - triggers_are(schema, table, triggers) + extra should have the proper description +ok 63 - triggers_are(schema, table, triggers) + extra should have the proper diagnostics +ok 64 - triggers_are(schema, table, triggers) + missing should fail +ok 65 - triggers_are(schema, table, triggers) + missing should have the proper description +ok 66 - triggers_are(schema, table, triggers) + missing should have the proper diagnostics +ok 67 - triggers_are(schema, table, triggers) + extra & missing should fail +ok 68 - triggers_are(schema, table, triggers) + extra & missing should have the proper description +ok 69 - triggers_are(schema, table, triggers) + extra & missing should have the proper diagnostics +ok 70 - triggers_are(table, triggers, desc) should pass +ok 71 - triggers_are(table, triggers, desc) should have the proper description +ok 72 - triggers_are(table, triggers, desc) should have the proper diagnostics +ok 73 - triggers_are(table, triggers) should pass +ok 74 - triggers_are(table, triggers) should have the proper description +ok 75 - triggers_are(table, triggers) should have the proper diagnostics +ok 76 - triggers_are(table, triggers) + extra should fail +ok 77 - triggers_are(table, triggers) + extra should have the proper description +ok 78 - triggers_are(table, triggers) + extra should have the proper diagnostics +ok 79 - triggers_are(table, triggers) + missing should fail +ok 80 - triggers_are(table, triggers) + missing should have the proper description +ok 81 - triggers_are(table, triggers) + missing should have the proper diagnostics +ok 82 - triggers_are(table, triggers) + extra & missing should fail +ok 83 - triggers_are(table, triggers) + extra & missing should have the proper description +ok 84 - triggers_are(table, triggers) + extra & missing should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 1e109ec6a200..37cb0a7336bb 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -7037,3 +7037,73 @@ 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 + 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 + ), + $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 + WHERE c.relname = $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 t.tgname + FROM pg_catalog.pg_trigger t + JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid + ), + $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; + diff --git a/sql/trigger.sql b/sql/trigger.sql index eadb479ef4f3..2eb6a4803ef1 100644 --- a/sql/trigger.sql +++ b/sql/trigger.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(54); +SELECT plan(84); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -18,7 +18,11 @@ END; ' LANGUAGE plpgsql; CREATE TRIGGER set_users_pass -BEFORE INSERT OR UPDATE ON public.users +BEFORE INSERT ON public.users +FOR EACH ROW EXECUTE PROCEDURE hash_pass(); + +CREATE TRIGGER upd_users_pass +BEFORE UPDATE ON public.users FOR EACH ROW EXECUTE PROCEDURE hash_pass(); RESET client_min_messages; @@ -174,6 +178,98 @@ SELECT * FROM check_test( want: oops' ); +/****************************************************************************/ +-- Test triggers_are(). +SELECT * FROM check_test( + triggers_are( 'public', 'users', ARRAY['set_users_pass', 'upd_users_pass'], 'whatever' ), + true, + 'triggers_are(schema, table, triggers, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + triggers_are( 'public', 'users', ARRAY['set_users_pass', 'upd_users_pass'] ), + true, + 'triggers_are(schema, table, triggers)', + 'Table public.users should have the correct triggers', + '' +); + +SELECT * FROM check_test( + triggers_are( 'public', 'users', ARRAY['set_users_pass'] ), + false, + 'triggers_are(schema, table, triggers) + extra', + 'Table public.users should have the correct triggers', + ' Extra triggers: + upd_users_pass' +); + +SELECT * FROM check_test( + triggers_are( 'public', 'users', ARRAY['set_users_pass', 'upd_users_pass', 'howdy'] ), + false, + 'triggers_are(schema, table, triggers) + missing', + 'Table public.users should have the correct triggers', + ' Missing triggers: + howdy' +); + +SELECT * FROM check_test( + triggers_are( 'public', 'users', ARRAY['set_users_pass', 'howdy'] ), + false, + 'triggers_are(schema, table, triggers) + extra & missing', + 'Table public.users should have the correct triggers', + ' Extra triggers: + upd_users_pass + Missing triggers: + howdy' +); + +SELECT * FROM check_test( + triggers_are( 'users', ARRAY['set_users_pass', 'upd_users_pass'], 'whatever' ), + true, + 'triggers_are(table, triggers, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + triggers_are( 'users', ARRAY['set_users_pass', 'upd_users_pass'] ), + true, + 'triggers_are(table, triggers)', + 'Table users should have the correct triggers', + '' +); + +SELECT * FROM check_test( + triggers_are( 'users', ARRAY['set_users_pass'] ), + false, + 'triggers_are(table, triggers) + extra', + 'Table users should have the correct triggers', + ' Extra triggers: + upd_users_pass' +); + +SELECT * FROM check_test( + triggers_are( 'users', ARRAY['set_users_pass', 'upd_users_pass', 'howdy'] ), + false, + 'triggers_are(table, triggers) + missing', + 'Table users should have the correct triggers', + ' Missing triggers: + howdy' +); + +SELECT * FROM check_test( + triggers_are( 'users', ARRAY['set_users_pass', 'howdy'] ), + false, + 'triggers_are(table, triggers) + extra & missing', + 'Table users should have the correct triggers', + ' Extra triggers: + upd_users_pass + Missing triggers: + howdy' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 37f0f83b65a4..67de42d87bc8 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,4 +1,8 @@ -- ## SET search_path TO TAPSCHEMA, public; +DROP FUNCTION triggers_are( NAME, NAME[] ); +DROP FUNCTION triggers_are( NAME, NAME[], TEXT ); +DROP FUNCTION triggers_are( NAME, NAME, NAME[] ); +DROP FUNCTION triggers_are( NAME, NAME, NAME[], TEXT ); DROP FUNCTION row_eq( TEXT, anyelement ); DROP FUNCTION row_eq( TEXT, anyelement, TEXT ); DROP FUNCTION domain_type_isnt( TEXT, TEXT ); From ab4d0723d290a6ff6f5ba2c17622d732c5a18a5c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 7 Dec 2009 19:38:46 -0800 Subject: [PATCH 0470/1195] Add `casts_are()`. --- Changes | 2 +- README.pgtap | 30 ++++++++++++++++- expected/aretap.out | 47 +++++++++++++++++--------- pgtap.sql.in | 49 +++++++++++++++++++++++++++ sql/aretap.sql | 76 ++++++++++++++++++++++++++++++++++++++---- uninstall_pgtap.sql.in | 2 ++ 6 files changed, 182 insertions(+), 24 deletions(-) diff --git a/Changes b/Changes index 87048c1f3398..66030999c320 100644 --- a/Changes +++ b/Changes @@ -25,7 +25,7 @@ Revision history for pgTAP `throws_imatching()` to test that an SQL statement throws an error message matching a `LIKE` pattern or a regular expression. * Added `roles_are()`, `types_are()`, `domains_are()`, `enums_are()`, - and `triggers_are()`. + `triggers_are()`, and `casts_are()`. * Fixed the diagnostic output from `isnt()` and `col_isnt_pk()` when they fail. They now more closely matches what Test::More's `isnt()` outputs and makes much more sense. diff --git a/README.pgtap b/README.pgtap index a4ac20dcc5bf..71f68af8fbc4 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1738,6 +1738,35 @@ missing enums, like so: # Missing enums: # bug_status +### `casts_are( casts[], description )` ### +### `casts_are( casts[] )` ### + + SELECT casts_are( + ARRAY[ + ARRAY[ 'integer', 'double precision' ], + ARRAY[ 'integer', 'reltime' ], + ARRAY[ 'integer', 'numeric' ], + -- ... + ] + 'Should have the correct casts' + ); + +This function tests that all of the casts in the database are only the casts +that *should* be in that database. Casts are specified as a two dimensional +array, where each element in the array is a two-item array containing first +the name of source data type and then the name of the target data type. If the +description is omitted, a generally useful default description will be +generated. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing casts, like so: + + # Failed test 302: "There should be the correct casts" + # Extra casts: + # lseg AS point + # Missing casts: + # lseg AS integer + To Have or Have Not ------------------- @@ -4182,7 +4211,6 @@ To Do + `sequence_increments_by()` + `sequence_starts_at()` + `sequence_cycles()` - + `casts_are()` + `operators_are()` + `columns_are()` * Useful result testing function to consider adding (but might require C diff --git a/expected/aretap.out b/expected/aretap.out index 49e0bf5a4d5e..8b98a1061021 100644 --- a/expected/aretap.out +++ b/expected/aretap.out @@ -1,20 +1,20 @@ \unset ECHO -1..330 -ok 1 - tablespaces_are(schemas, desc) should pass -ok 2 - tablespaces_are(schemas, desc) should have the proper description -ok 3 - tablespaces_are(schemas, desc) should have the proper diagnostics -ok 4 - tablespaces_are(schemas) should pass -ok 5 - tablespaces_are(schemas) should have the proper description -ok 6 - tablespaces_are(schemas) should have the proper diagnostics -ok 7 - tablespaces_are(schemas, desc) missing should fail -ok 8 - tablespaces_are(schemas, desc) missing should have the proper description -ok 9 - tablespaces_are(schemas, desc) missing should have the proper diagnostics -ok 10 - tablespaces_are(schemas, desc) extra should fail -ok 11 - tablespaces_are(schemas, desc) extra should have the proper description -ok 12 - tablespaces_are(schemas, desc) extra should have the proper diagnostics -ok 13 - tablespaces_are(schemas, desc) extras and missing should fail -ok 14 - tablespaces_are(schemas, desc) extras and missing should have the proper description -ok 15 - tablespaces_are(schemas, desc) extras and missing should have the proper diagnostics +1..345 +ok 1 - tablespaces_are(tablespaces, desc) should pass +ok 2 - tablespaces_are(tablespaces, desc) should have the proper description +ok 3 - tablespaces_are(tablespaces, desc) should have the proper diagnostics +ok 4 - tablespaces_are(tablespaces) should pass +ok 5 - tablespaces_are(tablespaces) should have the proper description +ok 6 - tablespaces_are(tablespaces) should have the proper diagnostics +ok 7 - tablespaces_are(tablespaces, desc) missing should fail +ok 8 - tablespaces_are(tablespaces, desc) missing should have the proper description +ok 9 - tablespaces_are(tablespaces, desc) missing should have the proper diagnostics +ok 10 - tablespaces_are(tablespaces, desc) extra should fail +ok 11 - tablespaces_are(tablespaces, desc) extra should have the proper description +ok 12 - tablespaces_are(tablespaces, desc) extra should have the proper diagnostics +ok 13 - tablespaces_are(tablespaces, desc) extras and missing should fail +ok 14 - tablespaces_are(tablespaces, desc) extras and missing should have the proper description +ok 15 - tablespaces_are(tablespaces, desc) extras and missing should have the proper diagnostics ok 16 - schemas_are(schemas, desc) should pass ok 17 - schemas_are(schemas, desc) should have the proper description ok 18 - schemas_are(schemas, desc) should have the proper diagnostics @@ -330,3 +330,18 @@ ok 327 - domains_are(domains, desc) fail should have the proper diagnostics ok 328 - domains_are(domains) fail should fail ok 329 - domains_are(domains) fail should have the proper description ok 330 - domains_are(domains) fail should have the proper diagnostics +ok 331 - casts_are(casts, desc) should pass +ok 332 - casts_are(casts, desc) should have the proper description +ok 333 - casts_are(casts, desc) should have the proper diagnostics +ok 334 - casts_are(casts) should pass +ok 335 - casts_are(casts) should have the proper description +ok 336 - casts_are(casts) should have the proper diagnostics +ok 337 - casts_are(casts, desc) missing should fail +ok 338 - casts_are(casts, desc) missing should have the proper description +ok 339 - casts_are(casts, desc) missing should have the proper diagnostics +ok 340 - casts_are(casts, desc) extra should fail +ok 341 - casts_are(casts, desc) extra should have the proper description +ok 342 - casts_are(casts, desc) extra should have the proper diagnostics +ok 343 - casts_are(casts, desc) extras and missing should fail +ok 344 - casts_are(casts, desc) extras and missing should have the proper description +ok 345 - casts_are(casts, desc) extras and missing should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 37cb0a7336bb..1cfd451581bb 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -7107,3 +7107,52 @@ RETURNS TEXT AS $$ SELECT triggers_are( $1, $2, 'Table ' || quote_ident($1) || ' should have the correct triggers' ); $$ LANGUAGE SQL; +-- casts_are( casts[], description ) +CREATE OR REPLACE FUNCTION casts_are ( TEXT[][], TEXT ) +RETURNS TEXT AS $$ +DECLARE + extras text[][]; + missing text[][]; + msg TEXT := ''; + res BOOLEAN := TRUE; +BEGIN + SELECT ARRAY( + SELECT display_type(castsource, NULL) || ' AS ' || display_type(casttarget, NULL) + FROM pg_catalog.pg_cast c + EXCEPT + SELECT $1[i][1] || ' AS ' || $1[i][2] + FROM generate_series(1, array_upper($1, 1)) s(i) + ) INTO extras; + + SELECT ARRAY( + SELECT $1[i][1] || ' AS ' || $1[i][2] + FROM generate_series(1, array_upper($1, 1)) s(i) + EXCEPT + SELECT display_type(castsource, NULL) || ' AS ' || display_type(casttarget, NULL) + FROM pg_catalog.pg_cast c + ) INTO missing; + + IF extras[1] IS NOT NULL THEN + res = FALSE; + msg := E'\n' || diag( + E' Extra casts:\n ' + || array_to_string( extras, E'\n ' ) + ); + END IF; + IF missing[1] IS NOT NULL THEN + res = FALSE; + msg := msg || E'\n' || diag( + E' Missing casts:\n ' + || array_to_string( missing, E'\n ' ) + ); + END IF; + + RETURN ok(res, $2) || msg; +END; +$$ LANGUAGE plpgsql; + +-- 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; diff --git a/sql/aretap.sql b/sql/aretap.sql index 30686ab02514..9c43576a9d3a 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(330); +SELECT plan(345); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -76,7 +76,7 @@ $$ LANGUAGE SQL; SELECT * FROM check_test( tablespaces_are( ___myts(''), 'whatever' ), true, - 'tablespaces_are(schemas, desc)', + 'tablespaces_are(tablespaces, desc)', 'whatever', '' ); @@ -84,7 +84,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( tablespaces_are( ___myts('') ), true, - 'tablespaces_are(schemas)', + 'tablespaces_are(tablespaces)', 'There should be the correct tablespaces', '' ); @@ -92,7 +92,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( tablespaces_are( array_append(___myts(''), '__booya__'), 'whatever' ), false, - 'tablespaces_are(schemas, desc) missing', + 'tablespaces_are(tablespaces, desc) missing', 'whatever', ' Missing tablespaces: __booya__' @@ -101,7 +101,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( tablespaces_are( ___myts('pg_default'), 'whatever' ), false, - 'tablespaces_are(schemas, desc) extra', + 'tablespaces_are(tablespaces, desc) extra', 'whatever', ' Extra tablespaces: pg_default' @@ -110,7 +110,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( tablespaces_are( array_append(___myts('pg_default'), '__booya__'), 'whatever' ), false, - 'tablespaces_are(schemas, desc) extras and missing', + 'tablespaces_are(tablespaces, desc) extras and missing', 'whatever', ' Extra tablespaces: pg_default @@ -1210,6 +1210,70 @@ SELECT * FROM check_test( fredy' ); +/****************************************************************************/ +-- Test domains_are(). + +CREATE OR REPLACE FUNCTION ___mycasts(ex text) RETURNS TEXT[][] AS $$ +DECLARE + casts TEXT[][]; + rec TEXT[]; +BEGIN + FOR rec IN + SELECT ARRAY[ display_type(castsource, NULL), display_type(casttarget, NULL) ] + FROM pg_catalog.pg_cast + WHERE castsource::regtype::text <> $1 + LOOP + casts := casts || ARRAY[rec]; + END LOOP; + return casts; +END; +$$ LANGUAGE plpgsql; + +SELECT * FROM check_test( + casts_are( ___mycasts(''), 'whatever' ), + true, + 'casts_are(casts, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + casts_are( ___mycasts('') ), + true, + 'casts_are(casts)', + 'There should be the correct casts', + '' +); + +SELECT * FROM check_test( + casts_are( ___mycasts('') || ARRAY[ARRAY['__booya__', 'integer']], 'whatever' ), + false, + 'casts_are(casts, desc) missing', + 'whatever', + ' Missing casts: + __booya__ AS integer' +); + +SELECT * FROM check_test( + casts_are( ___mycasts('lseg'), 'whatever' ), + false, + 'casts_are(casts, desc) extra', + 'whatever', + ' Extra casts: + lseg AS point' +); + +SELECT * FROM check_test( + casts_are( ___mycasts('lseg') || ARRAY[ARRAY['__booya__', 'integer']], 'whatever' ), + false, + 'casts_are(casts, desc) extras and missing', + 'whatever', + ' Extra casts: + lseg AS point + Missing casts: + __booya__ AS integer' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 67de42d87bc8..805097089390 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,4 +1,6 @@ -- ## SET search_path TO TAPSCHEMA, public; +DROP FUNCTION casts_are ( TEXT[][] ); +DROP FUNCTION casts_are ( TEXT[][], TEXT ); DROP FUNCTION triggers_are( NAME, NAME[] ); DROP FUNCTION triggers_are( NAME, NAME[], TEXT ); DROP FUNCTION triggers_are( NAME, NAME, NAME[] ); From 68b0f61d6ee5300a7c332d56e8c8d210f2df1151 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Dec 2009 12:13:10 -0800 Subject: [PATCH 0471/1195] Add `operators_are()`. Also changed `casts_are()` to accept an array of strings rather than a multidimensional array of types. It's just easier, and close enough to the syntax of `CREATE CAST` to make sense. Did this after I decided that a similar approach to `operators_are()` made much more sense, given that `::regoperator` nicely formats operators. In the process, added `_areni()`, which is the same as `_are()` except that it doesn't `quote_ident()` extra or missing things. Used for the string syntax of `casts_are()` and `operators_are()`. --- Changes | 2 +- README.pgtap | 57 ++++++++++++++++--- expected/aretap.out | 26 ++++++++- pgtap.sql.in | 126 +++++++++++++++++++++++++++++++++-------- sql/aretap.sql | 116 +++++++++++++++++++++++++++++++------ uninstall_pgtap.sql.in | 9 ++- 6 files changed, 282 insertions(+), 54 deletions(-) diff --git a/Changes b/Changes index 66030999c320..9d4be7377645 100644 --- a/Changes +++ b/Changes @@ -25,7 +25,7 @@ Revision history for pgTAP `throws_imatching()` to test that an SQL statement throws an error message matching a `LIKE` pattern or a regular expression. * Added `roles_are()`, `types_are()`, `domains_are()`, `enums_are()`, - `triggers_are()`, and `casts_are()`. + `triggers_are()`, `casts_are()`, and `operators_are()`. * Fixed the diagnostic output from `isnt()` and `col_isnt_pk()` when they fail. They now more closely matches what Test::More's `isnt()` outputs and makes much more sense. diff --git a/README.pgtap b/README.pgtap index 71f68af8fbc4..d17be41b335c 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1743,20 +1743,19 @@ missing enums, like so: SELECT casts_are( ARRAY[ - ARRAY[ 'integer', 'double precision' ], - ARRAY[ 'integer', 'reltime' ], - ARRAY[ 'integer', 'numeric' ], + 'integer AS double precision', + 'integer AS reltime', + 'integer AS numeric', -- ... ] 'Should have the correct casts' ); This function tests that all of the casts in the database are only the casts -that *should* be in that database. Casts are specified as a two dimensional -array, where each element in the array is a two-item array containing first -the name of source data type and then the name of the target data type. If the -description is omitted, a generally useful default description will be -generated. +that *should* be in that database. Casts are specified as strings in a syntax +similarly to how they're declared via `CREATE CAST`. The pattern is +`:source_type AS :target_type`. If the description is omitted, a generally +useful default description will be generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing casts, like so: @@ -1767,6 +1766,47 @@ missing casts, like so: # Missing casts: # lseg AS integer +### `operators_are( schema, operators[], description )` ### +### `operators_are( schema, operators[] )` ### +### `operators_are( operators[], description )` ### +### `operators_are( operators[] )` ### + + SELECT operators_are( + 'public', + ARRAY[ + '=(citext,citext) RETURNS boolean', + '-(NONE,bigint) RETURNS bigint', + '!(bigint,NONE) RETURNS numeric', + -- ... + ], + '' + ); + +Tests that all of the operators in the named schema are the only operators in +that schema. If the `:schema` argument is omitted, the operators must be +visible in the search path, excluding `pg_catalog` and `information_schema`. +If the description is omitted, a generally useful default description will be +generated. + +The `:operators` argument is specified as an array of strings in which +each operator is defined similarly to the display of the `:regoperator` type. +The format is `:op(:leftop,:rightop) RETURNS :return_type`. + +For left operators the left argument type should be `NONE`. For right +operators, the right argument type should be `NONE`. The example above shows +one one of each of the operator types. `=(citext,citext)` is an infix +operator, `-(bigint,NONE)` is a left operator, and `!(NONE,bigint)' is a right +operator. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing operators, like so: + + # Failed test 453: "Schema public should have the correct operators" + # Extra operators: + # +(integer,integer) RETURNS integer + # Missing enums: + # +(integer,text) RETURNS text + To Have or Have Not ------------------- @@ -4211,7 +4251,6 @@ To Do + `sequence_increments_by()` + `sequence_starts_at()` + `sequence_cycles()` - + `operators_are()` + `columns_are()` * Useful result testing function to consider adding (but might require C code): `rowtype_is()` diff --git a/expected/aretap.out b/expected/aretap.out index 8b98a1061021..2a49afab2ae4 100644 --- a/expected/aretap.out +++ b/expected/aretap.out @@ -1,5 +1,5 @@ \unset ECHO -1..345 +1..369 ok 1 - tablespaces_are(tablespaces, desc) should pass ok 2 - tablespaces_are(tablespaces, desc) should have the proper description ok 3 - tablespaces_are(tablespaces, desc) should have the proper diagnostics @@ -345,3 +345,27 @@ ok 342 - casts_are(casts, desc) extra should have the proper diagnostics ok 343 - casts_are(casts, desc) extras and missing should fail ok 344 - casts_are(casts, desc) extras and missing should have the proper description ok 345 - casts_are(casts, desc) extras and missing should have the proper diagnostics +ok 346 - operators_are(schema, operators, desc) should pass +ok 347 - operators_are(schema, operators, desc) should have the proper description +ok 348 - operators_are(schema, operators, desc) should have the proper diagnostics +ok 349 - operators_are(schema, operators) should pass +ok 350 - operators_are(schema, operators) should have the proper description +ok 351 - operators_are(schema, operators) should have the proper diagnostics +ok 352 - operators_are(schema, operators, desc) fail should fail +ok 353 - operators_are(schema, operators, desc) fail should have the proper description +ok 354 - operators_are(schema, operators, desc) fail should have the proper diagnostics +ok 355 - operators_are(schema, operators) fail should fail +ok 356 - operators_are(schema, operators) fail should have the proper description +ok 357 - operators_are(schema, operators) fail should have the proper diagnostics +ok 358 - operators_are(operators, desc) should pass +ok 359 - operators_are(operators, desc) should have the proper description +ok 360 - operators_are(operators, desc) should have the proper diagnostics +ok 361 - operators_are(operators) should pass +ok 362 - operators_are(operators) should have the proper description +ok 363 - operators_are(operators) should have the proper diagnostics +ok 364 - operators_are(operators, desc) fail should fail +ok 365 - operators_are(operators, desc) fail should have the proper description +ok 366 - operators_are(operators, desc) fail should have the proper diagnostics +ok 367 - operators_are(operators) fail should fail +ok 368 - operators_are(operators) fail should have the proper description +ok 369 - operators_are(operators) fail should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 1cfd451581bb..72c48d83f17c 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -7107,52 +7107,130 @@ RETURNS TEXT AS $$ SELECT triggers_are( $1, $2, 'Table ' || quote_ident($1) || ' should have the correct triggers' ); $$ LANGUAGE SQL; --- casts_are( casts[], description ) -CREATE OR REPLACE FUNCTION casts_are ( TEXT[][], TEXT ) +CREATE OR REPLACE FUNCTION _areni ( text, text[], text[], TEXT ) RETURNS TEXT AS $$ DECLARE - extras text[][]; - missing text[][]; + what ALIAS FOR $1; + extras ALIAS FOR $2; + missing ALIAS FOR $3; + descr ALIAS FOR $4; msg TEXT := ''; res BOOLEAN := TRUE; BEGIN - SELECT ARRAY( - SELECT display_type(castsource, NULL) || ' AS ' || display_type(casttarget, NULL) - FROM pg_catalog.pg_cast c - EXCEPT - SELECT $1[i][1] || ' AS ' || $1[i][2] - FROM generate_series(1, array_upper($1, 1)) s(i) - ) INTO extras; - - SELECT ARRAY( - SELECT $1[i][1] || ' AS ' || $1[i][2] - FROM generate_series(1, array_upper($1, 1)) s(i) - EXCEPT - SELECT display_type(castsource, NULL) || ' AS ' || display_type(casttarget, NULL) - FROM pg_catalog.pg_cast c - ) INTO missing; - IF extras[1] IS NOT NULL THEN res = FALSE; msg := E'\n' || diag( - E' Extra casts:\n ' + ' Extra ' || what || E':\n ' || array_to_string( extras, E'\n ' ) ); END IF; IF missing[1] IS NOT NULL THEN res = FALSE; msg := msg || E'\n' || diag( - E' Missing casts:\n ' + ' Missing ' || what || E':\n ' || array_to_string( missing, E'\n ' ) ); END IF; - RETURN ok(res, $2) || msg; + 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 display_type(castsource, NULL) || ' AS ' || display_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 display_type(castsource, NULL) || ' AS ' || display_type(casttarget, NULL) + FROM pg_catalog.pg_cast c + ), + $2 + ); +$$ LANGUAGE sql; + -- casts_are( casts[] ) -CREATE OR REPLACE FUNCTION casts_are ( TEXT[][] ) +CREATE OR REPLACE FUNCTION casts_are ( TEXT[] ) RETURNS TEXT AS $$ SELECT casts_are( $1, 'There should be the correct casts'); $$ LANGUAGE SQL; + +-- operators_are( schema, operators[], description ) +CREATE OR REPLACE FUNCTION operators_are( NAME, TEXT[], TEXT ) +RETURNS TEXT AS $$ + SELECT _areni( + 'operators', + ARRAY( + SELECT o.oid::regoperator || ' 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 o.oid::regoperator || ' 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 o.oid::regoperator || ' 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 o.oid::regoperator || ' 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; + diff --git a/sql/aretap.sql b/sql/aretap.sql index 9c43576a9d3a..8e98afaab949 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(345); +SELECT plan(369); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -1211,23 +1211,15 @@ SELECT * FROM check_test( ); /****************************************************************************/ --- Test domains_are(). +-- Test casts_are(). -CREATE OR REPLACE FUNCTION ___mycasts(ex text) RETURNS TEXT[][] AS $$ -DECLARE - casts TEXT[][]; - rec TEXT[]; -BEGIN - FOR rec IN - SELECT ARRAY[ display_type(castsource, NULL), display_type(casttarget, NULL) ] +CREATE OR REPLACE FUNCTION ___mycasts(ex text) RETURNS TEXT[] AS $$ + SELECT ARRAY( + SELECT display_type(castsource, NULL) || ' AS ' || display_type(casttarget, NULL) FROM pg_catalog.pg_cast WHERE castsource::regtype::text <> $1 - LOOP - casts := casts || ARRAY[rec]; - END LOOP; - return casts; -END; -$$ LANGUAGE plpgsql; + ); +$$ LANGUAGE SQL; SELECT * FROM check_test( casts_are( ___mycasts(''), 'whatever' ), @@ -1246,7 +1238,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - casts_are( ___mycasts('') || ARRAY[ARRAY['__booya__', 'integer']], 'whatever' ), + casts_are( array_append(___mycasts(''), '__booya__ AS integer'), 'whatever' ), false, 'casts_are(casts, desc) missing', 'whatever', @@ -1264,7 +1256,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - casts_are( ___mycasts('lseg') || ARRAY[ARRAY['__booya__', 'integer']], 'whatever' ), + casts_are( array_append(___mycasts('lseg'), '__booya__ AS integer'), 'whatever' ), false, 'casts_are(casts, desc) extras and missing', 'whatever', @@ -1274,6 +1266,96 @@ SELECT * FROM check_test( __booya__ AS integer' ); +/****************************************************************************/ +-- Test operators_are(). + +SELECT * FROM check_test( + operators_are( 'public', ARRAY['=(goofy,goofy) RETURNS boolean'], 'whatever' ), + true, + 'operators_are(schema, operators, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + operators_are( 'public', ARRAY['=(goofy,goofy) RETURNS boolean'] ), + true, + 'operators_are(schema, operators)', + 'Schema public should have the correct operators', + '' +); + +SELECT * FROM check_test( + operators_are( 'public', ARRAY['+(freddy,freddy) RETURNS barnie'], 'whatever' ), + false, + 'operators_are(schema, operators, desc) fail', + 'whatever', + ' Extra operators: + =(goofy,goofy) RETURNS boolean + Missing operators: + +(freddy,freddy) RETURNS barnie' +); + +SELECT * FROM check_test( + operators_are( 'public', ARRAY['+(freddy,freddy) RETURNS barnie'] ), + false, + 'operators_are(schema, operators) fail', + 'Schema public should have the correct operators', + ' Extra operators: + =(goofy,goofy) RETURNS boolean + Missing operators: + +(freddy,freddy) RETURNS barnie' +); + +CREATE OR REPLACE FUNCTION ___myops(ex text) RETURNS TEXT[] AS $$ + SELECT ARRAY( + SELECT o.oid::regoperator::text || ' 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 o.oprleft::regtype::text <> $1 + AND n.nspname NOT IN ('pg_catalog', 'information_schema') + ); +$$ LANGUAGE SQL; + +SELECT * FROM check_test( + operators_are( array_append( ___myops(''), '=(goofy,goofy) RETURNS boolean' ), 'whatever' ), + true, + 'operators_are(operators, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + operators_are( array_append( ___myops(''), '=(goofy,goofy) RETURNS boolean' ) ), + true, + 'operators_are(operators)', + 'There should be the correct operators', + '' +); + +SELECT * FROM check_test( + operators_are( array_append( ___myops('goofy'), '+(freddy,freddy) RETURNS barnie' ), 'whatever' ), + false, + 'operators_are(operators, desc) fail', + 'whatever', + ' Extra operators: + =(goofy,goofy) RETURNS boolean + Missing operators: + +(freddy,freddy) RETURNS barnie' +); + +SELECT * FROM check_test( + operators_are( array_append( ___myops('goofy'), '+(freddy,freddy) RETURNS barnie' ) ), + false, + 'operators_are(operators) fail', + 'There should be the correct operators', + ' Extra operators: + =(goofy,goofy) RETURNS boolean + Missing operators: + +(freddy,freddy) RETURNS barnie' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 805097089390..0fc16a7b4c7c 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,6 +1,11 @@ -- ## SET search_path TO TAPSCHEMA, public; -DROP FUNCTION casts_are ( TEXT[][] ); -DROP FUNCTION casts_are ( TEXT[][], TEXT ); +DROP FUNCTION operators_are ( TEXT[] ); +DROP FUNCTION operators_are( TEXT[], TEXT ); +DROP FUNCTION operators_are ( NAME, TEXT[] ); +DROP FUNCTION operators_are( NAME, TEXT[], TEXT ); +DROP FUNCTION casts_are ( TEXT[] ); +DROP FUNCTION casts_are ( TEXT[], TEXT ); +DROP FUNCTION _areni ( text, text[], text[], TEXT ); DROP FUNCTION triggers_are( NAME, NAME[] ); DROP FUNCTION triggers_are( NAME, NAME[], TEXT ); DROP FUNCTION triggers_are( NAME, NAME, NAME[] ); From b71d297b9556d012e43d21a5cd7ab9ec8b944a8d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Dec 2009 12:43:00 -0800 Subject: [PATCH 0472/1195] * Update `has_operator()` default descriptions. Changed the default descriptions output by `has_operator()`, `has_leftop()`, and `has_rightop()` to format the operator names more like the display of `regoperator` types. --- Changes | 5 ++++- pgtap.sql.in | 45 ++++++++++++++------------------------------- sql/hastap.sql | 36 ++++++++++++++++++------------------ 3 files changed, 36 insertions(+), 50 deletions(-) diff --git a/Changes b/Changes index 9d4be7377645..b7ace7456ed3 100644 --- a/Changes +++ b/Changes @@ -36,7 +36,10 @@ Revision history for pgTAP * Added `display_type()` and replaced all internal use of `format_type()` with it. This is so that schemas are always displayed properly. * Added `row_eq()`. It's a 90% solution that requires that `record` values be - cast to existing composite values. But it should work well even at that.a + cast to existing composite values. But it should work well even at that. +* Changed the default descriptions output by `has_operator()`, `has_leftop()`, + and `has_rightop()` to format the operator names more like the display of + `regoperator` types. 0.22 2009-07-31T00:26:16 ------------------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index 72c48d83f17c..30fd04c498dd 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3846,11 +3846,8 @@ 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($1) - || ' ' || quote_ident($2) || '.' || $3 || ' ' - || quote_ident($4) - || ' RETURNS ' || quote_ident($5) || ') should exist' + 'Operator ' || quote_ident($2) || '.' || $3 || '(' || $1 || ',' || $4 + || ') RETURNS ' || $5 || ' should exist' ); $$ LANGUAGE SQL; @@ -3865,11 +3862,8 @@ CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, NAME ) RETURNS TEXT AS $$ SELECT ok( _op_exists($1, $2, $3, $4 ), - 'Operator (' - || quote_ident($1) - || ' ' || $2 || ' ' - || quote_ident($3) - || ' RETURNS ' || quote_ident($4) || ') should exist' + 'Operator ' || $2 || '(' || $1 || ',' || $3 + || ') RETURNS ' || $4 || ' should exist' ); $$ LANGUAGE SQL; @@ -3884,10 +3878,7 @@ CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME ) RETURNS TEXT AS $$ SELECT ok( _op_exists($1, $2, $3 ), - 'Operator (' - || quote_ident($1) - || ' ' || $2 || ' ' - || quote_ident($3) + 'Operator ' || $2 || '(' || $1 || ',' || $3 || ') should exist' ); $$ LANGUAGE SQL; @@ -3903,10 +3894,8 @@ CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME, NAME ) RETURNS TEXT AS $$ SELECT ok( _op_exists(NULL, $1, $2, $3, $4 ), - 'Operator (' - || quote_ident($1) || '.' || $2 || ' ' - || quote_ident($3) - || ' RETURNS ' || quote_ident($4) || ') should exist' + 'Left operator ' || quote_ident($1) || '.' || $2 || '(NONE,' + || $3 || ') RETURNS ' || $4 || ' should exist' ); $$ LANGUAGE SQL; @@ -3921,9 +3910,7 @@ CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME ) RETURNS TEXT AS $$ SELECT ok( _op_exists(NULL, $1, $2, $3 ), - 'Operator (' || $1 || ' ' - || quote_ident($2) - || ' RETURNS ' || quote_ident($3) || ') should exist' + 'Left operator ' || $1 || '(NONE,' || $2 || ') RETURNS ' || $3 || ' should exist' ); $$ LANGUAGE SQL; @@ -3938,7 +3925,7 @@ CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME ) RETURNS TEXT AS $$ SELECT ok( _op_exists(NULL, $1, $2 ), - 'Operator (' || $1 || ' ' || quote_ident($2) || ') should exist' + 'Left operator ' || $1 || '(NONE,' || $2 || ') should exist' ); $$ LANGUAGE SQL; @@ -3953,10 +3940,8 @@ CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME, NAME ) RETURNS TEXT AS $$ SELECT ok( _op_exists($1, $2, $3, NULL, $4 ), - 'Operator (' - || quote_ident($1) || ' ' - || quote_ident($2) || '.' || $3 - || ' RETURNS ' || quote_ident($4) || ') should exist' + 'Right operator ' || quote_ident($2) || '.' || $3 || '(' + || $1 || ',NONE) RETURNS ' || $4 || ' should exist' ); $$ LANGUAGE SQL; @@ -3971,9 +3956,8 @@ CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME ) RETURNS TEXT AS $$ SELECT ok( _op_exists($1, $2, NULL, $3 ), - 'Operator (' - || quote_ident($1) || ' ' || $2 - || ' RETURNS ' || quote_ident($3) || ') should exist' + 'Right operator ' || $2 || '(' + || $1 || ',NONE) RETURNS ' || $3 || ' should exist' ); $$ LANGUAGE SQL; @@ -3988,8 +3972,7 @@ CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME ) RETURNS TEXT AS $$ SELECT ok( _op_exists($1, $2, NULL ), - 'Operator (' - || quote_ident($1) || ' ' || $2 || ') should exist' + 'Right operator ' || $2 || '(' || $1 || ',NONE) should exist' ); $$ LANGUAGE SQL; diff --git a/sql/hastap.sql b/sql/hastap.sql index ee78b79028c0..66f0f6da8ebb 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -1079,7 +1079,7 @@ SELECT * FROM check_test( has_operator( 'integer', 'pg_catalog', '<=', 'integer', 'boolean'::name ), true, 'has_operator( left, schema, name, right, result )', - 'Operator ("integer" pg_catalog.<= "integer" RETURNS "boolean") should exist', + 'Operator pg_catalog.<=(integer,integer) RETURNS boolean should exist', '' ); @@ -1095,7 +1095,7 @@ SELECT * FROM check_test( has_operator( 'integer', '<=', 'integer', 'boolean'::name ), true, 'has_operator( left, name, right, result )', - 'Operator ("integer" <= "integer" RETURNS "boolean") should exist', + 'Operator <=(integer,integer) RETURNS boolean should exist', '' ); @@ -1111,7 +1111,7 @@ SELECT * FROM check_test( has_operator( 'integer', '<=', 'integer'::name ), true, 'has_operator( left, name, right )', - 'Operator ("integer" <= "integer") should exist', + 'Operator <=(integer,integer) should exist', '' ); @@ -1127,7 +1127,7 @@ SELECT * FROM check_test( has_operator( 'integer', 'pg_catalog', '<=', 'text', 'boolean'::name ), false, 'has_operator( left, schema, name, right, result ) fail', - 'Operator ("integer" pg_catalog.<= text RETURNS "boolean") should exist', + 'Operator pg_catalog.<=(integer,text) RETURNS boolean should exist', '' ); @@ -1143,7 +1143,7 @@ SELECT * FROM check_test( has_operator( 'integer', '<=', 'text', 'boolean'::name ), false, 'has_operator( left, name, right, result ) fail', - 'Operator ("integer" <= text RETURNS "boolean") should exist', + 'Operator <=(integer,text) RETURNS boolean should exist', '' ); @@ -1159,7 +1159,7 @@ SELECT * FROM check_test( has_operator( 'integer', '<=', 'text'::name ), false, 'has_operator( left, name, right ) fail', - 'Operator ("integer" <= text) should exist', + 'Operator <=(integer,text) should exist', '' ); @@ -1178,7 +1178,7 @@ SELECT * FROM check_test( has_leftop( 'pg_catalog', '!!', 'bigint', 'numeric'::name ), true, 'has_leftop( schema, name, right, result )', - 'Operator (pg_catalog.!! "bigint" RETURNS "numeric") should exist', + 'Left operator pg_catalog.!!(NONE,bigint) RETURNS numeric should exist', '' ); @@ -1194,7 +1194,7 @@ SELECT * FROM check_test( has_leftop( '!!', 'bigint', 'numeric'::name ), true, 'has_leftop( name, right, result )', - 'Operator (!! "bigint" RETURNS "numeric") should exist', + 'Left operator !!(NONE,bigint) RETURNS numeric should exist', '' ); @@ -1210,7 +1210,7 @@ SELECT * FROM check_test( has_leftop( '!!', 'bigint' ), true, 'has_leftop( name, right )', - 'Operator (!! "bigint") should exist', + 'Left operator !!(NONE,bigint) should exist', '' ); @@ -1226,7 +1226,7 @@ SELECT * FROM check_test( has_leftop( 'pg_catalog', '!!', 'text', 'numeric'::name ), false, 'has_leftop( schema, name, right, result ) fail', - 'Operator (pg_catalog.!! text RETURNS "numeric") should exist', + 'Left operator pg_catalog.!!(NONE,text) RETURNS numeric should exist', '' ); @@ -1242,7 +1242,7 @@ SELECT * FROM check_test( has_leftop( '!!', 'text', 'numeric'::name ), false, 'has_leftop( name, right, result ) fail', - 'Operator (!! text RETURNS "numeric") should exist', + 'Left operator !!(NONE,text) RETURNS numeric should exist', '' ); @@ -1258,7 +1258,7 @@ SELECT * FROM check_test( has_leftop( '!!', 'text' ), false, 'has_leftop( name, right ) fail', - 'Operator (!! text) should exist', + 'Left operator !!(NONE,text) should exist', '' ); @@ -1277,7 +1277,7 @@ SELECT * FROM check_test( has_rightop( 'bigint', 'pg_catalog', '!', 'numeric'::name ), true, 'has_rightop( left, schema, name, result )', - 'Operator ("bigint" pg_catalog.! RETURNS "numeric") should exist', + 'Right operator pg_catalog.!(bigint,NONE) RETURNS numeric should exist', '' ); @@ -1293,7 +1293,7 @@ SELECT * FROM check_test( has_rightop( 'bigint', '!', 'numeric'::name ), true, 'has_rightop( left, name, result )', - 'Operator ("bigint" ! RETURNS "numeric") should exist', + 'Right operator !(bigint,NONE) RETURNS numeric should exist', '' ); @@ -1309,7 +1309,7 @@ SELECT * FROM check_test( has_rightop( 'bigint', '!' ), true, 'has_rightop( left, name )', - 'Operator ("bigint" !) should exist', + 'Right operator !(bigint,NONE) should exist', '' ); @@ -1325,7 +1325,7 @@ SELECT * FROM check_test( has_rightop( 'text', 'pg_catalog', '!', 'numeric'::name ), false, 'has_rightop( left, schema, name, result ) fail', - 'Operator (text pg_catalog.! RETURNS "numeric") should exist', + 'Right operator pg_catalog.!(text,NONE) RETURNS numeric should exist', '' ); @@ -1341,7 +1341,7 @@ SELECT * FROM check_test( has_rightop( 'text', '!', 'numeric'::name ), false, 'has_rightop( left, name, result ) fail', - 'Operator (text ! RETURNS "numeric") should exist', + 'Right operator !(text,NONE) RETURNS numeric should exist', '' ); @@ -1357,7 +1357,7 @@ SELECT * FROM check_test( has_rightop( 'text', '!' ), false, 'has_rightop( left, name ) fail', - 'Operator (text !) should exist', + 'Right operator !(text,NONE) should exist', '' ); From 354484c7a053f39f3f3791fa231ea951f581a633 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Dec 2009 13:01:08 -0800 Subject: [PATCH 0473/1195] Exclude `information_schema` as well as `pg_catalog`. This is for all the `*_are()` functions. --- README.pgtap | 40 ++++++++++++++++++++-------------------- pgtap.sql.in | 36 +++++++++++++++++++++++------------- sql/aretap.sql | 2 +- 3 files changed, 44 insertions(+), 34 deletions(-) diff --git a/README.pgtap b/README.pgtap index d17be41b335c..cf74e66e02c7 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1383,8 +1383,8 @@ missing schemas, like so: This function tests that all of the tables in the named schema, or that are visible in the search path, are only the tables that *should* be there. If the `:schema` argument is omitted, tables will be sought in the search path, -excluding `pg_catalog.` If the description is omitted, a generally useful -default description will be generated. +excluding `pg_catalog` and `information_schema` If the description is omitted, +a generally useful default description will be generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing tables, like so: @@ -1411,8 +1411,8 @@ missing tables, like so: This function tests that all of the views in the named schema, or that are visible in the search path, are only the views that *should* be there. If the `:schema` argument is omitted, views will be sought in the search path, -excluding `pg_catalog.` If the description is omitted, a generally useful -default description will be generated. +excluding `pg_catalog` and `information_schema` If the description is omitted, +a generally useful default description will be generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing views, like so: @@ -1439,8 +1439,8 @@ missing views, like so: This function tests that all of the sequences in the named schema, or that are visible in the search path, are only the sequences that *should* be there. If the `:schema` argument is omitted, sequences will be sought in the search -path, excluding `pg_catalog.` If the description is omitted, a generally -useful default description will be generated. +path, excluding `pg_catalog` and `information_schema`. If the description is +omitted, a generally useful default description will be generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing sequences, like so: @@ -1467,9 +1467,9 @@ missing sequences, like so: This function tests that all of the indexes on the named table are only the indexes that *should* be on that table. If the `:schema` argument is omitted, -the table must be visible in the search path, excluding `pg_catalog`. If the -description is omitted, a generally useful default description will be -generated. +the table must be visible in the search path, excluding `pg_catalog` and +`information_schema`. If the description is omitted, a generally useful +default description will be generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing indexes, like so: @@ -1494,9 +1494,9 @@ missing indexes, like so: This function tests that all of the triggers on the named table are only the triggers that *should* be on that table. If the `:schema` argument is omitted, -the table must be visible in the search path, excluding `pg_catalog`. If the -description is omitted, a generally useful default description will be -generated. +the table must be visible in the search path, excluding `pg_catalog` and +`information_schema`. If the description is omitted, a generally useful +default description will be generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing triggers, like so: @@ -1521,8 +1521,8 @@ missing triggers, like so: This function tests that all of the functions in the named schema, or that are visible in the search path, are only the functions that *should* be there. If the `:schema` argument is omitted, functions will be sought in the search -path, excluding `pg_catalog.` If the description is omitted, a generally -useful default description will be generated. +path, excluding `pg_catalog` and `information_schema` If the description is +omitted, a generally useful default description will be generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing functions, like so: @@ -1620,8 +1620,9 @@ diagnostics listing the extra and/or missing languages, like so: This function tests that all of the operator classes in the named schema, or that are visible in the search path, are only the opclasses that *should* be there. If the `:schema` argument is omitted, opclasses will be sought in the -search path, excluding `pg_catalog`. If the description is omitted, a -generally useful default description will be generated. +search path, excluding `pg_catalog` and `information_schema`. If the +description is omitted, a generally useful default description will be +generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing opclasses, like so: @@ -1647,8 +1648,8 @@ missing opclasses, like so: This function tests that all of the rules on the named relation are only the rules that *should* be on that relation (a table or a view). If the `:schema` argument is omitted, the rules must be visible in the search path, excluding -`pg_catalog`. If the description is omitted, a generally useful default -description will be generated. +`pg_catalog` and `information_schema`. If the description is omitted, a +generally useful default description will be generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing rules, like so: @@ -3847,7 +3848,7 @@ On PostgreSQL 8.4 and higher, it can take any number of arguments. Lower than SELECT display_type('public', 'varchar'::regtype, NULL ); SELECT display_type('numeric'::regtype, 196612 ); -Like `pg_catalot.format_type()`, except that the returned value is not +Like `pg_catalog.format_type()`, except that the returned value is not prepended with the schema name unless it is passed as the first argument. Used internally by pgTAP to compare type names, but may be more generally useful. @@ -4251,7 +4252,6 @@ To Do + `sequence_increments_by()` + `sequence_starts_at()` + `sequence_cycles()` - + `columns_are()` * Useful result testing function to consider adding (but might require C code): `rowtype_is()` diff --git a/pgtap.sql.in b/pgtap.sql.in index 30fd04c498dd..2c2f74c6c269 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -4122,7 +4122,7 @@ RETURNS NAME[] AS $$ 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 n.nspname NOT IN ('pg_catalog', 'information_schema') AND c.relkind = $1 ); $$ LANGUAGE SQL; @@ -4250,7 +4250,8 @@ RETURNS TEXT AS $$ SELECT _are( 'functions', ARRAY( - SELECT name FROM tap_funky WHERE is_visible AND schema <> 'pg_catalog' + 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) @@ -4259,7 +4260,8 @@ RETURNS TEXT AS $$ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT - SELECT name FROM tap_funky WHERE is_visible AND schema <> 'pg_catalog' + SELECT name FROM tap_funky WHERE is_visible + AND schema NOT IN ('pg_catalog', 'information_schema') ), $2 ); @@ -4318,10 +4320,12 @@ RETURNS TEXT AS $$ 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_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) @@ -4332,10 +4336,12 @@ RETURNS TEXT AS $$ 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_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 ); @@ -4585,7 +4591,7 @@ RETURNS TEXT AS $$ SELECT oc.opcname FROM pg_catalog.pg_opclass oc JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid - WHERE n.nspname <> 'pg_catalog' + AND n.nspname NOT IN ('pg_catalog', 'information_schema') AND pg_catalog.pg_opclass_is_visible(oc.oid) EXCEPT SELECT $1[i] @@ -4598,7 +4604,7 @@ RETURNS TEXT AS $$ SELECT oc.opcname FROM pg_catalog.pg_opclass oc JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid - WHERE n.nspname <> 'pg_catalog' + AND n.nspname NOT IN ('pg_catalog', 'information_schema') AND pg_catalog.pg_opclass_is_visible(oc.oid) ), $2 @@ -4659,7 +4665,7 @@ RETURNS TEXT AS $$ 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 <> 'pg_catalog' + AND n.nspname NOT IN ('pg_catalog', 'information_schema') AND pg_catalog.pg_table_is_visible(c.oid) EXCEPT SELECT $2[i] @@ -4674,7 +4680,7 @@ RETURNS TEXT AS $$ 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 - WHERE n.nspname <> 'pg_catalog' + AND n.nspname NOT IN ('pg_catalog', 'information_schema') AND pg_catalog.pg_table_is_visible(c.oid) ), $3 @@ -6707,7 +6713,7 @@ RETURNS TEXT AS $$ 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 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 @@ -6726,7 +6732,7 @@ RETURNS TEXT AS $$ 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 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']) ) ), @@ -7067,7 +7073,9 @@ RETURNS TEXT AS $$ 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') EXCEPT SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) @@ -7079,6 +7087,8 @@ RETURNS TEXT AS $$ 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') ), $3 ); diff --git a/sql/aretap.sql b/sql/aretap.sql index 8e98afaab949..e1f2cc9e85f3 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -525,7 +525,7 @@ CREATE FUNCTION ___myfunk(ex text) RETURNS NAME[] AS $$ FROM pg_catalog.pg_namespace n JOIN pg_catalog.pg_proc p ON n.oid = p.pronamespace WHERE pg_catalog.pg_function_is_visible(p.oid) - AND n.nspname <> 'pg_catalog' + AND n.nspname NOT IN ('pg_catalog', 'information_schema') AND p.proname <> $1 ); $$ LANGUAGE SQL; From df64d52754e82acedbb1e1e201632e8104dac782 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Dec 2009 13:11:41 -0800 Subject: [PATCH 0474/1195] Add `columns_are()`. --- Changes | 2 +- README.pgtap | 28 +++++++++++++ expected/aretap.out | 32 +++++++++++++- pgtap.sql.in | 84 +++++++++++++++++++++++++++++++++++++ sql/aretap.sql | 94 +++++++++++++++++++++++++++++++++++++++++- uninstall_pgtap.sql.in | 4 ++ 6 files changed, 241 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index b7ace7456ed3..20d8e256cab2 100644 --- a/Changes +++ b/Changes @@ -25,7 +25,7 @@ Revision history for pgTAP `throws_imatching()` to test that an SQL statement throws an error message matching a `LIKE` pattern or a regular expression. * Added `roles_are()`, `types_are()`, `domains_are()`, `enums_are()`, - `triggers_are()`, `casts_are()`, and `operators_are()`. + `triggers_are()`, `casts_are()`, `operators_are()`, and `columns_are()`. * Fixed the diagnostic output from `isnt()` and `col_isnt_pk()` when they fail. They now more closely matches what Test::More's `isnt()` outputs and makes much more sense. diff --git a/README.pgtap b/README.pgtap index cf74e66e02c7..250640e65035 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1453,6 +1453,34 @@ missing sequences, like so: # users_seq # widgets_seq +### `columns_are( schema, table, columns[], description )` ### +### `columns_are( schema, table, columns[] )` ### +### `columns_are( table, columns[], description )` ### +### `columns_are( table, columns[] )` ### + + SELECT columns_are( + 'myschema', + 'atable', + ARRAY[ 'id', 'name', 'rank', 'sn' ], + 'Should have the correct columns on myschema.atable' + ); + +This function tests that all of the columns on the named table are only the +columns that *should* be on that table. If the `:schema` argument is omitted, +the table must be visible in the search path, excluding `pg_catalog` and +`information_schema`. If the description is omitted, a generally useful +default description will be generated. + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing columns, like so: + + # Failed test 183: "Table users should have the correct columns" + # Extra columns: + # given_name + # surname + # Missing columns: + # name + ### `indexes_are( schema, table, indexes[], description )` ### ### `indexes_are( schema, table, indexes[] )` ### ### `indexes_are( table, indexes[], description )` ### diff --git a/expected/aretap.out b/expected/aretap.out index 2a49afab2ae4..b99f39f66cd8 100644 --- a/expected/aretap.out +++ b/expected/aretap.out @@ -1,5 +1,5 @@ \unset ECHO -1..369 +1..399 ok 1 - tablespaces_are(tablespaces, desc) should pass ok 2 - tablespaces_are(tablespaces, desc) should have the proper description ok 3 - tablespaces_are(tablespaces, desc) should have the proper diagnostics @@ -369,3 +369,33 @@ ok 366 - operators_are(operators, desc) fail should have the proper diagnostics ok 367 - operators_are(operators) fail should fail ok 368 - operators_are(operators) fail should have the proper description ok 369 - operators_are(operators) fail should have the proper diagnostics +ok 370 - columns_are(schema, table, columns, desc) should pass +ok 371 - columns_are(schema, table, columns, desc) should have the proper description +ok 372 - columns_are(schema, table, columns, desc) should have the proper diagnostics +ok 373 - columns_are(schema, table, columns) should pass +ok 374 - columns_are(schema, table, columns) should have the proper description +ok 375 - columns_are(schema, table, columns) should have the proper diagnostics +ok 376 - columns_are(schema, table, columns) + extra should fail +ok 377 - columns_are(schema, table, columns) + extra should have the proper description +ok 378 - columns_are(schema, table, columns) + extra should have the proper diagnostics +ok 379 - columns_are(schema, table, columns) + missing should fail +ok 380 - columns_are(schema, table, columns) + missing should have the proper description +ok 381 - columns_are(schema, table, columns) + missing should have the proper diagnostics +ok 382 - columns_are(schema, table, columns) + extra & missing should fail +ok 383 - columns_are(schema, table, columns) + extra & missing should have the proper description +ok 384 - columns_are(schema, table, columns) + extra & missing should have the proper diagnostics +ok 385 - columns_are(table, columns, desc) should pass +ok 386 - columns_are(table, columns, desc) should have the proper description +ok 387 - columns_are(table, columns, desc) should have the proper diagnostics +ok 388 - columns_are(table, columns) should pass +ok 389 - columns_are(table, columns) should have the proper description +ok 390 - columns_are(table, columns) should have the proper diagnostics +ok 391 - columns_are(table, columns) + extra should fail +ok 392 - columns_are(table, columns) + extra should have the proper description +ok 393 - columns_are(table, columns) + extra should have the proper diagnostics +ok 394 - columns_are(table, columns) + missing should fail +ok 395 - columns_are(table, columns) + missing should have the proper description +ok 396 - columns_are(table, columns) + missing should have the proper diagnostics +ok 397 - columns_are(table, columns) + extra & missing should fail +ok 398 - columns_are(table, columns) + extra & missing should have the proper description +ok 399 - columns_are(table, columns) + extra & missing should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index 2c2f74c6c269..13738af7640e 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -7227,3 +7227,87 @@ 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; + diff --git a/sql/aretap.sql b/sql/aretap.sql index e1f2cc9e85f3..74b412933f2a 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(369); +SELECT plan(399); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -1356,6 +1356,98 @@ SELECT * FROM check_test( +(freddy,freddy) RETURNS barnie' ); +/****************************************************************************/ +-- Test columns_are(). +SELECT * FROM check_test( + columns_are( 'public', 'fou', ARRAY['id', 'name', 'numb', 'myint'], 'whatever' ), + true, + 'columns_are(schema, table, columns, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + columns_are( 'public', 'fou', ARRAY['id', 'name', 'numb', 'myint'] ), + true, + 'columns_are(schema, table, columns)', + 'Table public.fou should have the correct columns', + '' +); + +SELECT * FROM check_test( + columns_are( 'public', 'fou', ARRAY['id', 'name', 'numb'] ), + false, + 'columns_are(schema, table, columns) + extra', + 'Table public.fou should have the correct columns', + ' Extra columns: + myint' +); + +SELECT * FROM check_test( + columns_are( 'public', 'fou', ARRAY['id', 'name', 'numb', 'myint', 'howdy'] ), + false, + 'columns_are(schema, table, columns) + missing', + 'Table public.fou should have the correct columns', + ' Missing columns: + howdy' +); + +SELECT * FROM check_test( + columns_are( 'public', 'fou', ARRAY['id', 'name', 'numb', 'howdy'] ), + false, + 'columns_are(schema, table, columns) + extra & missing', + 'Table public.fou should have the correct columns', + ' Extra columns: + myint + Missing columns: + howdy' +); + +SELECT * FROM check_test( + columns_are( 'fou', ARRAY['id', 'name', 'numb', 'myint'], 'whatever' ), + true, + 'columns_are(table, columns, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + columns_are( 'fou', ARRAY['id', 'name', 'numb', 'myint'] ), + true, + 'columns_are(table, columns)', + 'Table fou should have the correct columns', + '' +); + +SELECT * FROM check_test( + columns_are( 'fou', ARRAY['id', 'name', 'numb'] ), + false, + 'columns_are(table, columns) + extra', + 'Table fou should have the correct columns', + ' Extra columns: + myint' +); + +SELECT * FROM check_test( + columns_are( 'fou', ARRAY['id', 'name', 'numb', 'myint', 'howdy'] ), + false, + 'columns_are(table, columns) + missing', + 'Table fou should have the correct columns', + ' Missing columns: + howdy' +); + +SELECT * FROM check_test( + columns_are( 'fou', ARRAY['id', 'name', 'numb', 'howdy'] ), + false, + 'columns_are(table, columns) + extra & missing', + 'Table fou should have the correct columns', + ' Extra columns: + myint + Missing columns: + howdy' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 0fc16a7b4c7c..f6bb2eb51b45 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -1,4 +1,8 @@ -- ## SET search_path TO TAPSCHEMA, public; +DROP FUNCTION columns_are( NAME, NAME[] ); +DROP FUNCTION columns_are( NAME, NAME[], TEXT ); +DROP FUNCTION columns_are( NAME, NAME, NAME[] ); +DROP FUNCTION columns_are( NAME, NAME, NAME[], TEXT ); DROP FUNCTION operators_are ( TEXT[] ); DROP FUNCTION operators_are( TEXT[], TEXT ); DROP FUNCTION operators_are ( NAME, TEXT[] ); From 882d26c19bea2aa90fb322202520a3a61b1d8266 Mon Sep 17 00:00:00 2001 From: KevinField Date: Fri, 11 Dec 2009 00:04:56 +0800 Subject: [PATCH 0475/1195] Fixed issue with using SET SESSION AUTHORIZATION among tests --- Changes | 1 + 1 file changed, 1 insertion(+) diff --git a/Changes b/Changes index 20d8e256cab2..1e8f36a50da4 100644 --- a/Changes +++ b/Changes @@ -40,6 +40,7 @@ Revision history for pgTAP * Changed the default descriptions output by `has_operator()`, `has_leftop()`, and `has_rightop()` to format the operator names more like the display of `regoperator` types. +* Fixed issue with using SET SESSION AUTHORIZATION among tests [Kevin Field] 0.22 2009-07-31T00:26:16 ------------------------- From d22cd591887de4c2f55dd149fbf223ee93329e34 Mon Sep 17 00:00:00 2001 From: KevinField Date: Fri, 11 Dec 2009 00:07:00 +0800 Subject: [PATCH 0476/1195] Added a note about being about to use descriptions without error messages. --- README.pgtap | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index 250640e65035..6661b39cc847 100644 --- a/README.pgtap +++ b/README.pgtap @@ -681,7 +681,9 @@ documentation](http://www.postgresql.org/docs/current/static/). The third argument is an error message. This will be most useful for functions you've written that raise exceptions, so that you can test the exception message that you've thrown. Otherwise, for core errors, you'll need to be -careful of localized error messages. +careful of localized error messages. One trick to get around localized error +messages is to pass NULL as the third argument. This allows you to still pass +a description as the fourth argument. The fourth argument is of course a brief test description. From b195b893a8315ae54e811916208f9a3844b90422 Mon Sep 17 00:00:00 2001 From: KevinField Date: Fri, 11 Dec 2009 11:23:40 -0800 Subject: [PATCH 0477/1195] Fixed issue with using SET SESSION AUTHORIZATION among tests --- pgtap.sql.in | 1 + 1 file changed, 1 insertion(+) diff --git a/pgtap.sql.in b/pgtap.sql.in index 13738af7640e..bcd411ae7c29 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -49,6 +49,7 @@ BEGIN note TEXT NOT NULL DEFAULT '''' ); GRANT ALL ON TABLE __tcache__ TO PUBLIC; + GRANT ALL ON TABLE __tcache___id_seq TO PUBLIC; CREATE TEMP TABLE __tresults__ ( numb SERIAL PRIMARY KEY, From dde7d940647e3bf35a07c46380ae656621487748 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 16 Dec 2009 12:08:45 -0800 Subject: [PATCH 0478/1195] Fixes to run on 8.5. --- pgtap.sql.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index bcd411ae7c29..4759d7ad3ff0 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2971,7 +2971,7 @@ BEGIN return is( aname, - LOWER($3)::name, + LOWER($2)::name, 'Index ' || quote_ident($1) || ' should be a ' || quote_ident($2) || ' index' ); END; @@ -5723,7 +5723,7 @@ DECLARE teardown ALIAS FOR $4; tests ALIAS FOR $5; tap text; - verbose boolean := _is_verbose(); + verbos boolean := _is_verbose(); -- verbose is a reserved word in 8.5. num_faild INTEGER := 0; BEGIN BEGIN @@ -5741,7 +5741,7 @@ BEGIN FOR i IN 1..array_upper(tests, 1) LOOP BEGIN -- What test are we running? - IF verbose THEN RETURN NEXT diag(tests[i] || '()'); END IF; + IF verbos THEN RETURN NEXT diag(tests[i] || '()'); END IF; -- Run the setup functions. FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; From 6eb0308151c523618f3585a35c1687c5cc993489 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 16 Dec 2009 12:11:40 -0800 Subject: [PATCH 0479/1195] List 8.5 as supported. --- README.pgtap | 1 + 1 file changed, 1 insertion(+) diff --git a/README.pgtap b/README.pgtap index 6661b39cc847..a34935d70ad6 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4290,6 +4290,7 @@ Supported Versions pgTAP has been tested on the following builds of PostgreSQL: +* PostgreSQL 8.5devel on i386-apple-darwin10.2.0 * PostgreSQL 8.4.0 on i386-apple-darwin9.7.0 * PostgreSQL 8.3.7 on i386-apple-darwin9.6.0 * PostgreSQL 8.3.6 on i386-redhat-linux-gnu From aa17a8400be708a3457180a03d68810622ce48de Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 16 Dec 2009 15:37:23 -0800 Subject: [PATCH 0480/1195] Update patches for 8.3. --- README.pgtap | 2 +- compat/install-8.3.patch | 12 ++++++------ compat/uninstall-8.3.patch | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.pgtap b/README.pgtap index a34935d70ad6..c2fa2a922399 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4292,7 +4292,7 @@ pgTAP has been tested on the following builds of PostgreSQL: * PostgreSQL 8.5devel on i386-apple-darwin10.2.0 * PostgreSQL 8.4.0 on i386-apple-darwin9.7.0 -* PostgreSQL 8.3.7 on i386-apple-darwin9.6.0 +* PostgreSQL 8.3.9 on i386-apple-darwin10.2.0 * PostgreSQL 8.3.6 on i386-redhat-linux-gnu * PostgreSQL 8.2.13 on i386-apple-darwin9.6.0 * PostgreSQL 8.1.17 on i686-apple-darwin9.6.0 diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index bf205960217c..bd55846ba83c 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.orig 2009-07-30 13:51:56.000000000 -0700 -+++ pgtap.sql 2009-07-30 13:52:15.000000000 -0700 +--- pgtap.sql.orig 2009-12-16 15:34:43.000000000 -0800 ++++ pgtap.sql 2009-12-16 15:34:43.000000000 -0800 @@ -15,6 +15,11 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -12,8 +12,8 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT s.a[1]::int * 10000 -@@ -5786,8 +5791,9 @@ - SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) +@@ -5849,8 +5854,9 @@ + SELECT display_type(a.atttypid, a.atttypmod) FROM pg_catalog.pg_attribute a JOIN pg_catalog.pg_class c ON a.attrelid = c.oid + JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid @@ -23,7 +23,7 @@ AND attnum > 0 AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END ORDER BY attnum -@@ -6134,7 +6140,7 @@ +@@ -6197,7 +6203,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -32,7 +32,7 @@ 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' || -@@ -6289,7 +6295,7 @@ +@@ -6352,7 +6358,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP diff --git a/compat/uninstall-8.3.patch b/compat/uninstall-8.3.patch index 788d7bcbb21d..9a0d3abf20f3 100644 --- a/compat/uninstall-8.3.patch +++ b/compat/uninstall-8.3.patch @@ -1,6 +1,6 @@ ---- uninstall_pgtap.sql.orig 2009-07-29 21:51:55.000000000 -0700 -+++ uninstall_pgtap.sql 2009-07-29 21:52:53.000000000 -0700 -@@ -626,6 +626,7 @@ +--- uninstall_pgtap.sql.orig 2009-12-16 15:34:43.000000000 -0800 ++++ uninstall_pgtap.sql 2009-12-16 15:34:43.000000000 -0800 +@@ -697,6 +697,7 @@ DROP FUNCTION _get ( text ); DROP FUNCTION no_plan(); DROP FUNCTION plan( integer ); From 9505a517d8a2ff6a1f3b81c99b46aad3b3e2b45b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 16 Dec 2009 16:03:31 -0800 Subject: [PATCH 0481/1195] Fix variadic `collect_tap()` for 8.3. --- compat/install-8.3.patch | 13 +++++++++++-- compat/uninstall-8.3.patch | 13 +++++++++++-- pgtap.sql.in | 5 ++--- sql/util.sql | 6 ++++++ uninstall_pgtap.sql.in | 2 +- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index bd55846ba83c..4b52efb67e95 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.orig 2009-12-16 15:34:43.000000000 -0800 -+++ pgtap.sql 2009-12-16 15:34:43.000000000 -0800 +--- pgtap.sql.orig 2009-12-16 15:46:29.000000000 -0800 ++++ pgtap.sql 2009-12-16 15:46:51.000000000 -0800 @@ -15,6 +15,11 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -41,3 +41,12 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; +@@ -6538,7 +6544,7 @@ + $$ LANGUAGE sql; + + -- collect_tap( tap, tap, tap ) +-CREATE OR REPLACE FUNCTION collect_tap( VARIADIC text[] ) ++CREATE OR REPLACE FUNCTION collect_tap( text[] ) + RETURNS TEXT AS $$ + SELECT array_to_string($1, E'\n'); + $$ LANGUAGE sql; diff --git a/compat/uninstall-8.3.patch b/compat/uninstall-8.3.patch index 9a0d3abf20f3..95c855fce2ae 100644 --- a/compat/uninstall-8.3.patch +++ b/compat/uninstall-8.3.patch @@ -1,5 +1,14 @@ ---- uninstall_pgtap.sql.orig 2009-12-16 15:34:43.000000000 -0800 -+++ uninstall_pgtap.sql 2009-12-16 15:34:43.000000000 -0800 +--- uninstall_pgtap.sql.orig 2009-12-16 15:46:29.000000000 -0800 ++++ uninstall_pgtap.sql 2009-12-16 15:46:59.000000000 -0800 +@@ -57,7 +57,7 @@ + DROP FUNCTION throws_like ( TEXT, TEXT ); + DROP FUNCTION throws_like ( TEXT, TEXT, TEXT ); + DROP FUNCTION _tlike ( BOOLEAN, TEXT, TEXT, TEXT ); +-DROP FUNCTION collect_tap( VARIADIC text[] ); ++DROP FUNCTION collect_tap( text[] ); + DROP FUNCTION is_empty( TEXT ); + DROP FUNCTION is_empty( TEXT, TEXT ); + DROP FUNCTION isa_ok( anyelement, regtype ); @@ -697,6 +697,7 @@ DROP FUNCTION _get ( text ); DROP FUNCTION no_plan(); diff --git a/pgtap.sql.in b/pgtap.sql.in index 4759d7ad3ff0..fb410792e9d5 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -6538,9 +6538,8 @@ RETURNS TEXT AS $$ $$ LANGUAGE sql; -- collect_tap( tap, tap, tap ) -CREATE OR REPLACE FUNCTION collect_tap( - VARIADIC tap text[] -) RETURNS TEXT AS $$ +CREATE OR REPLACE FUNCTION collect_tap( VARIADIC text[] ) +RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); $$ LANGUAGE sql; diff --git a/sql/util.sql b/sql/util.sql index 886386e051e0..52afcb6c922a 100644 --- a/sql/util.sql +++ b/sql/util.sql @@ -60,6 +60,12 @@ SELECT matches( /****************************************************************************/ -- Test collect_tap(). + +-- Create fake collect_tap() to prevent an error on 8.3 and lower. +CREATE FUNCTION collect_tap(text, text, text ) RETURNS text AS $$ + SELECT fail('Fake collect_tap(text, text, text) was called!') +$$ LANGUAGE SQL; + SELECT is( CASE WHEN pg_version_num() >= 80400 THEN collect_tap('foo', 'bar', 'baz') diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index f6bb2eb51b45..51e07f9d7aa6 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -57,7 +57,7 @@ DROP FUNCTION throws_ilike ( TEXT, TEXT, TEXT ); DROP FUNCTION throws_like ( TEXT, TEXT ); DROP FUNCTION throws_like ( TEXT, TEXT, TEXT ); DROP FUNCTION _tlike ( BOOLEAN, TEXT, TEXT, TEXT ); -DROP FUNCTION collect_tap(; +DROP FUNCTION collect_tap( VARIADIC text[] ); DROP FUNCTION is_empty( TEXT ); DROP FUNCTION is_empty( TEXT, TEXT ); DROP FUNCTION isa_ok( anyelement, regtype ); From 7722b4f6a791a337c5531e2e6941547fc9e833e5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 16 Dec 2009 16:12:57 -0800 Subject: [PATCH 0482/1195] Add backward-compatible `collect_tap()`. --- expected/util.out | 25 +++++++++++++------------ pgtap.sql.in | 5 +++++ sql/util.sql | 29 +++++++++++++++++++---------- uninstall_pgtap.sql.in | 1 + 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/expected/util.out b/expected/util.out index 40893c767a5f..3bd66f37a0e6 100644 --- a/expected/util.out +++ b/expected/util.out @@ -1,5 +1,5 @@ \unset ECHO -1..23 +1..24 ok 1 - pg_type(int) should work ok 2 - pg_type(numeric) should work ok 3 - pg_type(text) should work @@ -12,14 +12,15 @@ ok 9 - pg_version_num() should be correct ok 10 - os_name() should output something like an OS name ok 11 - findfincs() should return distinct values ok 12 - pgtap_version() should work -ok 13 - collect_tap() should simply collect tap -ok 14 - display_type(int4) -ok 15 - display_type(numeric) -ok 16 - display_type(numeric, typmod) -ok 17 - display_type("char") -ok 18 - display_type(char) -ok 19 - display_type(timestamp) -ok 20 - display_type(timestamptz) -ok 21 - display_type(foo, int4) -ok 22 - display_type(HEY, numeric) -ok 23 - display_type(t z, int4) +ok 13 - collect_tap(text[]) should simply collect tap +ok 14 - variadic collect_tap() should simply collect tap +ok 15 - display_type(int4) +ok 16 - display_type(numeric) +ok 17 - display_type(numeric, typmod) +ok 18 - display_type("char") +ok 19 - display_type(char) +ok 20 - display_type(timestamp) +ok 21 - display_type(timestamptz) +ok 22 - display_type(foo, int4) +ok 23 - display_type(HEY, numeric) +ok 24 - display_type(t z, int4) diff --git a/pgtap.sql.in b/pgtap.sql.in index fb410792e9d5..71cba46c7d6e 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -6543,6 +6543,11 @@ 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 $$ diff --git a/sql/util.sql b/sql/util.sql index 52afcb6c922a..3d05c518a656 100644 --- a/sql/util.sql +++ b/sql/util.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(23); +SELECT plan(24); --SELECT * FROM no_plan(); SELECT is( pg_typeof(42), 'integer', 'pg_type(int) should work' ); @@ -60,21 +60,30 @@ SELECT matches( /****************************************************************************/ -- Test collect_tap(). +SELECT is( + collect_tap(ARRAY['foo', 'bar', 'baz']), + 'foo +bar +baz', + 'collect_tap(text[]) should simply collect tap' +); --- Create fake collect_tap() to prevent an error on 8.3 and lower. -CREATE FUNCTION collect_tap(text, text, text ) RETURNS text AS $$ - SELECT fail('Fake collect_tap(text, text, text) was called!') -$$ LANGUAGE SQL; +CREATE FUNCTION test_variadic() RETURNS TEXT AS $$ +BEGIN + IF pg_version_num() >= 80400 THEN + RETURN collect_tap('foo', 'bar', 'baz'); + ELSE + RETURN collect_tap(ARRAY['foo', 'bar', 'baz']); + END IF; +END; +$$ LANGUAGE plpgsql; SELECT is( - CASE WHEN pg_version_num() >= 80400 - THEN collect_tap('foo', 'bar', 'baz') - ELSE collect_tap('{foo, bar, baz}') - END, + test_variadic(), 'foo bar baz', - 'collect_tap() should simply collect tap' + 'variadic collect_tap() should simply collect tap' ); /****************************************************************************/ diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 51e07f9d7aa6..105e0cc6bf2b 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -57,6 +57,7 @@ DROP FUNCTION throws_ilike ( TEXT, TEXT, TEXT ); DROP FUNCTION throws_like ( TEXT, TEXT ); DROP FUNCTION throws_like ( TEXT, TEXT, TEXT ); DROP FUNCTION _tlike ( BOOLEAN, TEXT, TEXT, TEXT ); +DROP FUNCTION collect_tap( VARCHAR[] ); DROP FUNCTION collect_tap( VARIADIC text[] ); DROP FUNCTION is_empty( TEXT ); DROP FUNCTION is_empty( TEXT, TEXT ); From 7c3808ad64fab73eb4a70f958e999ca7871103cb Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 16 Dec 2009 16:28:04 -0800 Subject: [PATCH 0483/1195] Updated compat of `collect_tap()` to 8.3. --- README.pgtap | 2 ++ compat/install-8.3.patch | 12 +++++++++--- compat/uninstall-8.3.patch | 9 +++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/README.pgtap b/README.pgtap index c2fa2a922399..5441abb278f7 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4243,6 +4243,8 @@ No changes. Everything should just work. * A C function, `pg_typeof()`, is built and installed in a DSO. This is for compatibility with the same function that ships in 8.4 core, and is required for `cmp_ok()` and `isa_ok()` to work. +* The variadic form of `collect_tap()` is not available. Instead you must pass + an array of TAP to it. 8.2 and Down ------------ diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index 4b52efb67e95..7fdd40b89c70 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.orig 2009-12-16 15:46:29.000000000 -0800 -+++ pgtap.sql 2009-12-16 15:46:51.000000000 -0800 +--- pgtap.sql.orig 2009-12-16 16:16:23.000000000 -0800 ++++ pgtap.sql 2009-12-16 16:17:15.000000000 -0800 @@ -15,6 +15,11 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -41,11 +41,17 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6538,7 +6544,7 @@ +@@ -6538,13 +6544,7 @@ $$ 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[] ) +CREATE OR REPLACE FUNCTION collect_tap( text[] ) RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); diff --git a/compat/uninstall-8.3.patch b/compat/uninstall-8.3.patch index 95c855fce2ae..4a77ec289904 100644 --- a/compat/uninstall-8.3.patch +++ b/compat/uninstall-8.3.patch @@ -1,15 +1,16 @@ ---- uninstall_pgtap.sql.orig 2009-12-16 15:46:29.000000000 -0800 -+++ uninstall_pgtap.sql 2009-12-16 15:46:59.000000000 -0800 -@@ -57,7 +57,7 @@ +--- uninstall_pgtap.sql.orig 2009-12-16 16:16:49.000000000 -0800 ++++ uninstall_pgtap.sql 2009-12-16 16:20:53.000000000 -0800 +@@ -57,8 +57,7 @@ DROP FUNCTION throws_like ( TEXT, TEXT ); DROP FUNCTION throws_like ( TEXT, TEXT, TEXT ); DROP FUNCTION _tlike ( BOOLEAN, TEXT, TEXT, TEXT ); +-DROP FUNCTION collect_tap( VARCHAR[] ); -DROP FUNCTION collect_tap( VARIADIC text[] ); +DROP FUNCTION collect_tap( text[] ); DROP FUNCTION is_empty( TEXT ); DROP FUNCTION is_empty( TEXT, TEXT ); DROP FUNCTION isa_ok( anyelement, regtype ); -@@ -697,6 +697,7 @@ +@@ -698,6 +697,7 @@ DROP FUNCTION _get ( text ); DROP FUNCTION no_plan(); DROP FUNCTION plan( integer ); From 0a82bedf454c78ce056c2583ab87c5fbca6224e0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 16 Dec 2009 16:54:00 -0800 Subject: [PATCH 0484/1195] Fix `is(row, row)` on 8.3 and earlier. --- README.pgtap | 7 +++- sql/istap.sql | 100 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 83 insertions(+), 24 deletions(-) diff --git a/README.pgtap b/README.pgtap index 5441abb278f7..fd71d5fceaab 100644 --- a/README.pgtap +++ b/README.pgtap @@ -412,7 +412,12 @@ Will produce something like this: So you can figure out what went wrong without re-running the test. -You are encouraged to use `is()` and `isnt()` over `ok()` where possible. +You are encouraged to use `is()` and `isnt()` over `ok()` where possible. You +can even use them to compar records in PostgreSQL 8.4 and later: + + SELECT is( users.*, ROW(1, 'theory', true)::users ) + FROM users + WHERE nick = 'theory'; ### `matches( anyelement, regex, description )` ### ### `matches( anyelement, regex )` ### diff --git a/sql/istap.sql b/sql/istap.sql index 828561813d79..a717645c0bba 100644 --- a/sql/istap.sql +++ b/sql/istap.sql @@ -61,35 +61,89 @@ CREATE TABLE mumble ( id int, name text ); RESET client_min_messages; INSERT INTO mumble VALUES (1, 'hey'); -SELECT is( mumble.*, ROW(1, 'hey')::mumble, 'with records!' ) - FROM mumble; +CREATE FUNCTION test_records() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() >= 80400 THEN + RETURN NEXT is( mumble.*, ROW(1, 'hey')::mumble, 'with records!' ) + FROM mumble; -SELECT check_test( - is( mumble.*, ROW(1, 'HEY')::mumble ), - false, - 'is(mumble, row) fail', - '', - ' have: (1,hey) + -- Before 8.3, have to cast to text. + FOR tap IN SELECT check_test( + is( mumble.*, ROW(1, 'HEY')::mumble ), + false, + 'is(mumble, row) fail', + '', + ' have: (1,hey) want: (1,HEY)' -) FROM mumble; + ) AS b FROM mumble LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT check_test( - is( mumble.*, ROW(1, NULL)::mumble ), - false, - 'is(mumble, row) fail with NULL', - '', - ' have: (1,hey) + FOR tap IN SELECT check_test( + is( mumble.*, ROW(1, NULL)::mumble ), + false, + 'is(mumble, row) fail with NULL', + '', + ' have: (1,hey) want: (1,)' -) FROM mumble; + ) AS b FROM mumble LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT check_test( - is( mumble.*, NULL::mumble ), - false, - 'is(mumble, NULL)', - '', - ' have: (1,hey) + FOR tap IN SELECT check_test( + is( mumble.*, NULL::mumble ), + false, + 'is(mumble, NULL)', + '', + ' have: (1,hey) want: NULL' -) FROM mumble; + ) AS b FROM mumble LOOP + RETURN NEXT tap.b; + END LOOP; + ELSE + RETURN NEXT is( (mumble.*)::text, ROW(1, 'hey')::text, 'with records!' ) + FROM mumble; + + FOR tap IN SELECT check_test( + is( (mumble.*)::text, ROW(1, 'HEY')::text ), + false, + 'is(mumble, row) fail', + '', + ' have: (1,hey) + want: (1,HEY)' + ) AS b FROM mumble LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT check_test( + is( (mumble.*)::text, ROW(1, NULL)::text ), + false, + 'is(mumble, row) fail with NULL', + '', + ' have: (1,hey) + want: (1,)' + ) AS b FROM mumble LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT check_test( + is( (mumble.*)::text, NULL::text ), + false, + 'is(mumble, NULL)', + '', + ' have: (1,hey) + want: NULL' + ) AS b FROM mumble LOOP + RETURN NEXT tap.b; + END LOOP; + END IF; + RETURN; +END; +$$ LANGUAGE PLPGSQL; + +SELECT * FROM test_records(); /****************************************************************************/ -- Finish the tests and clean up. From 6eef8e6fc6cb8f71d0755bfe57a1bb1961ac2ff1 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 16 Dec 2009 17:08:36 -0800 Subject: [PATCH 0485/1195] Finally all tests pass on 8.3. Have to cast from records to text in `row_eq()` in 8.3 and earlier. --- README.pgtap | 8 ++++---- compat/install-8.3.patch | 13 +++++++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/README.pgtap b/README.pgtap index fd71d5fceaab..e7ed400d95fb 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4241,10 +4241,10 @@ No changes. Everything should just work. 8.3 and Down ------------ -* A patch is applied to modify `results_eq()` to cast records to text before - comparing them. This means that things will mainly be correct, but it also - means that two queries with incompatible types that convert to the same text - string may be considered incorrectly equivalent. +* A patch is applied to modify `results_eq()` and `row_eq()` to cast records + to text before comparing them. This means that things will mainly be + correct, but it also means that two queries with incompatible types that + convert to the same text string may be considered incorrectly equivalent. * A C function, `pg_typeof()`, is built and installed in a DSO. This is for compatibility with the same function that ships in 8.4 core, and is required for `cmp_ok()` and `isa_ok()` to work. diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index 7fdd40b89c70..f9b8be9fe55d 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.orig 2009-12-16 16:16:23.000000000 -0800 -+++ pgtap.sql 2009-12-16 16:17:15.000000000 -0800 +--- pgtap.sql.orig 2009-12-16 17:06:12.000000000 -0800 ++++ pgtap.sql 2009-12-16 17:06:38.000000000 -0800 @@ -15,6 +15,11 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -56,3 +56,12 @@ RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); $$ LANGUAGE sql; +@@ -7017,7 +7017,7 @@ + result BOOLEAN; + BEGIN + FOR rec in EXECUTE _query($1) LOOP +- result := NOT rec IS DISTINCT FROM $2; ++ result := NOT rec::text IS DISTINCT FROM $2::text; + RETURN ok(result, $3 ) || CASE WHEN result THEN '' ELSE 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 From d6c6258aa1ae237552c1e74bc77a604634633cb1 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 17 Dec 2009 16:10:10 -0800 Subject: [PATCH 0486/1195] Started updating for PostgreSQL 8.2. --- Makefile | 4 - compat/install-8.2.patch | 208 +++++++++++++++++++++++++++++++++++-- compat/install-8.2.sql | 53 ---------- compat/uninstall-8.2.patch | 24 ++++- compat/uninstall-8.2.sql | 12 --- sql/aretap.sql | 27 ++--- sql/istap.sql | 8 +- 7 files changed, 237 insertions(+), 99 deletions(-) delete mode 100644 compat/install-8.2.sql delete mode 100644 compat/uninstall-8.2.sql diff --git a/Makefile b/Makefile index 96bbe0397cf8..17c9988b91f6 100644 --- a/Makefile +++ b/Makefile @@ -103,7 +103,6 @@ ifneq ($(PGVER_MINOR), 0) patch -p0 < compat/install-8.3.patch endif ifneq ($(PGVER_MINOR), 3) - cat compat/install-8.2.sql >> pgtap.sql ifeq ($(PGVER_MINOR), 2) patch -p0 < compat/install-8.2.patch else @@ -131,9 +130,6 @@ ifneq ($(PGVER_MINOR), 4) patch -p0 < compat/uninstall-8.3.patch ifneq ($(PGVER_MINOR), 3) patch -p0 < compat/uninstall-8.2.patch - mv uninstall_pgtap.sql uninstall_pgtap.tmp - cat compat/uninstall-8.2.sql uninstall_pgtap.tmp >> uninstall_pgtap.sql - rm uninstall_pgtap.tmp endif ifeq ($(PGVER_MINOR), 0) patch -p0 < compat/uninstall-8.0.patch diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index fedb29eb256d..36d8f3c829f5 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,6 +1,66 @@ ---- pgtap.sql.orig 2009-07-30 14:14:11.000000000 -0700 -+++ pgtap.sql 2009-07-30 14:14:20.000000000 -0700 -@@ -194,11 +194,11 @@ +--- pgtap.sql.orig 2009-12-17 14:55:53.000000000 -0800 ++++ pgtap.sql 2009-12-17 16:05:04.000000000 -0800 +@@ -11,6 +11,59 @@ + -- ## CREATE SCHEMA TAPSCHEMA; + -- ## SET search_path TO TAPSCHEMA, public; + ++-- 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; ++ ++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; +@@ -195,11 +248,11 @@ RETURNS integer AS $$ BEGIN EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) @@ -17,7 +77,7 @@ RETURN currval('__tresults___numb_seq'); END; $$ LANGUAGE plpgsql; -@@ -486,9 +486,9 @@ +@@ -486,9 +539,9 @@ output TEXT; BEGIN EXECUTE 'SELECT ' || @@ -29,7 +89,25 @@ INTO result; output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( -@@ -3298,63 +3298,6 @@ +@@ -1089,7 +1142,7 @@ + + CREATE OR REPLACE FUNCTION display_type ( OID, INTEGER ) + RETURNS TEXT AS $$ +- SELECT $1::regtype ++ SELECT $1::regtype::text + || COALESCE(substring(pg_catalog.format_type($1, $2), '[(][^)]+[)]$'), '') + $$ LANGUAGE SQL; + +@@ -2101,7 +2154,7 @@ + p.proname AS name, + 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, +@@ -3372,63 +3425,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -93,7 +171,7 @@ CREATE OR REPLACE FUNCTION _has_role( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( -@@ -5815,13 +5758,13 @@ +@@ -5878,13 +5874,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -109,7 +187,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6045,7 +5988,7 @@ +@@ -6108,7 +6104,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -118,7 +196,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6140,11 +6083,11 @@ +@@ -6203,11 +6199,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -133,7 +211,7 @@ ); END IF; rownum = rownum + 1; -@@ -6159,8 +6102,8 @@ +@@ -6222,8 +6218,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -144,7 +222,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6295,7 +6238,7 @@ +@@ -6358,7 +6354,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -153,7 +231,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6309,8 +6252,8 @@ +@@ -6372,8 +6368,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -164,3 +242,111 @@ ); END; $$ LANGUAGE plpgsql; +@@ -6498,9 +6494,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; + +@@ -6521,7 +6517,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? +@@ -6666,7 +6662,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 +@@ -6684,7 +6680,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']) ) + ), +@@ -6717,7 +6713,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']) ) +@@ -6736,7 +6732,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']) ) +@@ -7017,10 +7013,10 @@ + result BOOLEAN; + BEGIN + FOR rec in EXECUTE _query($1) LOOP +- result := NOT rec::text IS DISTINCT FROM $2::text; ++ result := NOT textin(record_out(rec)) IS DISTINCT FROM textin(record_out($2)); + RETURN ok(result, $3 ) || CASE WHEN result THEN '' ELSE 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; + END LOOP; + END; +@@ -7170,7 +7166,7 @@ + SELECT _areni( + 'operators', + ARRAY( +- SELECT o.oid::regoperator || ' RETURNS ' || o.oprresult::regtype ++ SELECT o.oid::regoperator || ' 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 +@@ -7182,7 +7178,7 @@ + SELECT $2[i] + FROM generate_series(1, array_upper($2, 1)) s(i) + EXCEPT +- SELECT o.oid::regoperator || ' RETURNS ' || o.oprresult::regtype ++ SELECT o.oid::regoperator || ' 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 +@@ -7203,7 +7199,7 @@ + SELECT _areni( + 'operators', + ARRAY( +- SELECT o.oid::regoperator || ' RETURNS ' || o.oprresult::regtype ++ SELECT o.oid::regoperator || ' 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) +@@ -7216,7 +7212,7 @@ + SELECT $1[i] + FROM generate_series(1, array_upper($1, 1)) s(i) + EXCEPT +- SELECT o.oid::regoperator || ' RETURNS ' || o.oprresult::regtype ++ SELECT o.oid::regoperator || ' 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) diff --git a/compat/install-8.2.sql b/compat/install-8.2.sql deleted file mode 100644 index 064793476870..000000000000 --- a/compat/install-8.2.sql +++ /dev/null @@ -1,53 +0,0 @@ - --- 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; - -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; diff --git a/compat/uninstall-8.2.patch b/compat/uninstall-8.2.patch index e41830836f3d..642313541e6f 100644 --- a/compat/uninstall-8.2.patch +++ b/compat/uninstall-8.2.patch @@ -1,6 +1,6 @@ ---- uninstall_pgtap.sql.orig 2009-07-29 17:49:53.000000000 -0700 -+++ uninstall_pgtap.sql 2009-07-29 17:50:01.000000000 -0700 -@@ -301,10 +301,6 @@ +--- uninstall_pgtap.sql.orig 2009-12-17 16:07:04.000000000 -0800 ++++ uninstall_pgtap.sql 2009-12-17 16:06:55.000000000 -0800 +@@ -364,10 +364,6 @@ DROP FUNCTION has_role( NAME ); DROP FUNCTION has_role( NAME, TEXT ); DROP FUNCTION _has_role( NAME ); @@ -11,3 +11,21 @@ DROP FUNCTION hasnt_enum( NAME ); DROP FUNCTION hasnt_enum( NAME, TEXT ); DROP FUNCTION hasnt_enum( NAME, NAME ); +@@ -702,5 +698,17 @@ + DROP FUNCTION os_name(); + DROP FUNCTION pg_version_num(); + DROP FUNCTION pg_version(); ++DROP CAST (regtype AS text); ++DROP FUNCTION regtypetext(regtype); ++DROP OPERATOR <> ( name[], name[] ); ++DROP FUNCTION namearray_ne( name[], name[] ); ++DROP OPERATOR = ( name[], name[] ); ++DROP FUNCTION namearray_eq( name[], name[] ); ++DROP CAST (name[] AS text); ++DROP FUNCTION namearray_text(name[]); ++DROP CAST (text[] AS text); ++DROP FUNCTION textarray_text(text[]); ++DROP CAST (boolean AS char(1)); ++DROP FUNCTION booltext(boolean); + -- ## SET search_path TO public; + -- ## DROP SCHEMA TAPSCHEMA; diff --git a/compat/uninstall-8.2.sql b/compat/uninstall-8.2.sql deleted file mode 100644 index bf0bdb92ed21..000000000000 --- a/compat/uninstall-8.2.sql +++ /dev/null @@ -1,12 +0,0 @@ -DROP CAST (regtype AS text); -DROP FUNCTION regtypetext(regtype); -DROP OPERATOR <> ( name[], name[] ); -DROP FUNCTION namearray_ne( name[], name[] ); -DROP OPERATOR = ( name[], name[] ); -DROP FUNCTION namearray_eq( name[], name[] ); -DROP CAST (name[] AS text); -DROP FUNCTION namearray_text(name[]); -DROP CAST (text[] AS text); -DROP FUNCTION textarray_text(text[]); -DROP CAST (boolean AS char(1)); -DROP FUNCTION booltext(boolean); diff --git a/sql/aretap.sql b/sql/aretap.sql index 74b412933f2a..59a567e5ebe6 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -47,11 +47,13 @@ CREATE OR REPLACE FUNCTION public.goofy_eq (goofy, goofy) RETURNS boolean AS $$ SELECT $1 = $2; $$ LANGUAGE SQL IMMUTABLE STRICT; -CREATE OPERATOR public.= ( PROCEDURE = goofy_eq, LEFTARG = goofy, RIGHTARG = goofy); +CREATE SCHEMA disney; -CREATE OPERATOR CLASS public.goofy_ops +CREATE OPERATOR disney.= ( PROCEDURE = goofy_eq, LEFTARG = goofy, RIGHTARG = goofy); + +CREATE OPERATOR CLASS goofy_ops DEFAULT FOR TYPE goofy USING BTREE AS - OPERATOR 1 =, + OPERATOR 1 disney.=, FUNCTION 1 goofy_cmp(goofy,goofy) ; @@ -1070,7 +1072,7 @@ CREATE FUNCTION ___mytype(ex text) RETURNS NAME[] AS $$ 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 t.typname <> $1 AND pg_catalog.pg_type_is_visible(t.oid) @@ -1164,7 +1166,7 @@ CREATE FUNCTION ___mydo(ex text) RETURNS NAME[] AS $$ 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 t.typname <> $1 AND pg_catalog.pg_type_is_visible(t.oid) @@ -1268,9 +1270,10 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test operators_are(). +SET search_path = disney,public,pg_catalog; SELECT * FROM check_test( - operators_are( 'public', ARRAY['=(goofy,goofy) RETURNS boolean'], 'whatever' ), + operators_are( 'disney', ARRAY['=(goofy,goofy) RETURNS boolean'], 'whatever' ), true, 'operators_are(schema, operators, desc)', 'whatever', @@ -1278,15 +1281,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - operators_are( 'public', ARRAY['=(goofy,goofy) RETURNS boolean'] ), + operators_are( 'disney', ARRAY['=(goofy,goofy) RETURNS boolean'] ), true, 'operators_are(schema, operators)', - 'Schema public should have the correct operators', + 'Schema disney should have the correct operators', '' ); SELECT * FROM check_test( - operators_are( 'public', ARRAY['+(freddy,freddy) RETURNS barnie'], 'whatever' ), + operators_are( 'disney', ARRAY['+(freddy,freddy) RETURNS barnie'], 'whatever' ), false, 'operators_are(schema, operators, desc) fail', 'whatever', @@ -1297,10 +1300,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - operators_are( 'public', ARRAY['+(freddy,freddy) RETURNS barnie'] ), + operators_are( 'disney', ARRAY['+(freddy,freddy) RETURNS barnie'] ), false, 'operators_are(schema, operators) fail', - 'Schema public should have the correct operators', + 'Schema disney should have the correct operators', ' Extra operators: =(goofy,goofy) RETURNS boolean Missing operators: @@ -1309,7 +1312,7 @@ SELECT * FROM check_test( CREATE OR REPLACE FUNCTION ___myops(ex text) RETURNS TEXT[] AS $$ SELECT ARRAY( - SELECT o.oid::regoperator::text || ' RETURNS ' || o.oprresult::regtype + SELECT textin(regoperatorout(o.oid::regoperator)) || ' 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) diff --git a/sql/istap.sql b/sql/istap.sql index a717645c0bba..ba790c1cd949 100644 --- a/sql/istap.sql +++ b/sql/istap.sql @@ -103,11 +103,11 @@ BEGIN RETURN NEXT tap.b; END LOOP; ELSE - RETURN NEXT is( (mumble.*)::text, ROW(1, 'hey')::text, 'with records!' ) + RETURN NEXT is( textin(record_out(mumble.*)), textin(record_out(ROW(1, 'hey'))), 'with records!' ) FROM mumble; FOR tap IN SELECT check_test( - is( (mumble.*)::text, ROW(1, 'HEY')::text ), + is( textin(record_out(mumble.*)), textin(record_out(ROW(1, 'HEY')))), false, 'is(mumble, row) fail', '', @@ -118,7 +118,7 @@ BEGIN END LOOP; FOR tap IN SELECT check_test( - is( (mumble.*)::text, ROW(1, NULL)::text ), + is( textin(record_out(mumble.*)), textin(record_out(ROW(1, NULL))) ), false, 'is(mumble, row) fail with NULL', '', @@ -129,7 +129,7 @@ BEGIN END LOOP; FOR tap IN SELECT check_test( - is( (mumble.*)::text, NULL::text ), + is( textin(record_out(mumble.*)), NULL::text ), false, 'is(mumble, NULL)', '', From 6194e58531ef650e60d57419391acfb9080adbec Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 17 Dec 2009 16:24:24 -0800 Subject: [PATCH 0487/1195] Add display_oper()`. --- Changes | 2 ++ README.pgtap | 8 ++++++++ pgtap.sql.in | 13 +++++++++---- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 1e8f36a50da4..a72a31dc1522 100644 --- a/Changes +++ b/Changes @@ -41,6 +41,8 @@ Revision history for pgTAP and `has_rightop()` to format the operator names more like the display of `regoperator` types. * Fixed issue with using SET SESSION AUTHORIZATION among tests [Kevin Field] +* Added `display_oper()` so that schemas are always displayed properly when + comparing operators. 0.22 2009-07-31T00:26:16 ------------------------- diff --git a/README.pgtap b/README.pgtap index e7ed400d95fb..043226e79c09 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3887,6 +3887,14 @@ Like `pg_catalog.format_type()`, except that the returned value is not prepended with the schema name unless it is passed as the first argument. Used internally by pgTAP to compare type names, but may be more generally useful. +### `display_oper( oper_name, oper_oid )` ### + + SELECT display_type(oprname, oid ) FROM pg_operator; + +Similar to casting an operator OID to regoperator, only the schema is not +included in the display. Used internally by pgTAP to compare operators, but +may be more generally useful. + ### `pg_typeof(any)` ### SELECT pg_typeof(:value); diff --git a/pgtap.sql.in b/pgtap.sql.in index 71cba46c7d6e..a94f20d78c07 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -7164,13 +7164,18 @@ 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 o.oid::regoperator || ' RETURNS ' || o.oprresult::regtype + 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 @@ -7182,7 +7187,7 @@ RETURNS TEXT AS $$ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT - SELECT o.oid::regoperator || ' RETURNS ' || o.oprresult::regtype + 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 @@ -7203,7 +7208,7 @@ RETURNS TEXT AS $$ SELECT _areni( 'operators', ARRAY( - SELECT o.oid::regoperator || ' RETURNS ' || o.oprresult::regtype + 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) @@ -7216,7 +7221,7 @@ RETURNS TEXT AS $$ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT - SELECT o.oid::regoperator || ' RETURNS ' || o.oprresult::regtype + 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) From 9fcd38275fc2d48d445764ca9d871cf46f7f23a9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 18 Dec 2009 09:19:15 -0800 Subject: [PATCH 0488/1195] Build the .so only when < 8.4. --- Makefile | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 17c9988b91f6..718f2d655ead 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,16 @@ PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') PGTAP_VERSION = 0.23 # Compile the C code only if we're on 8.3 or older. -ifneq ($(PGVER_MINOR), 4) +ifeq ($(PGVER_MINOR), 3) +MODULES = pgtap +endif +ifeq ($(PGVER_MINOR), 2) +MODULES = pgtap +endif +ifeq ($(PGVER_MINOR), 1) +MODULES = pgtap +endif +ifeq ($(PGVER_MINOR), 0) MODULES = pgtap endif From 9c692591b3bea2190611674a30bbce466b207c04 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 18 Dec 2009 09:28:53 -0800 Subject: [PATCH 0489/1195] All tests oow pass on 8.2. --- Makefile | 2 ++ compat/install-8.2.patch | 37 +++++++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index 718f2d655ead..d8ed8c0de420 100644 --- a/Makefile +++ b/Makefile @@ -70,6 +70,8 @@ TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql sql/enumtap.sql sql/role REGRESS := $(filter-out throwtap runtests enumtap roletap,$(REGRESS)) endif ifeq ($(PGVER_MINOR), 5) +# Do nothing. +else ifeq ($(PGVER_MINOR), 4) # Do nothing. else diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 36d8f3c829f5..30531cc1ff64 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.orig 2009-12-17 14:55:53.000000000 -0800 -+++ pgtap.sql 2009-12-17 16:05:04.000000000 -0800 +--- pgtap.sql.orig 2009-12-18 09:23:38.000000000 -0800 ++++ pgtap.sql 2009-12-18 09:25:55.000000000 -0800 @@ -11,6 +11,59 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA, public; @@ -314,39 +314,48 @@ ) END; END LOOP; END; -@@ -7170,7 +7166,7 @@ +@@ -7166,7 +7162,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 ) +@@ -7175,7 +7171,7 @@ SELECT _areni( 'operators', ARRAY( -- SELECT o.oid::regoperator || ' RETURNS ' || o.oprresult::regtype -+ SELECT o.oid::regoperator || ' RETURNS ' || o.oprresult::regtype::text +- 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 -@@ -7182,7 +7178,7 @@ +@@ -7187,7 +7183,7 @@ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT -- SELECT o.oid::regoperator || ' RETURNS ' || o.oprresult::regtype -+ SELECT o.oid::regoperator || ' RETURNS ' || o.oprresult::regtype::text +- 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 -@@ -7203,7 +7199,7 @@ +@@ -7208,7 +7204,7 @@ SELECT _areni( 'operators', ARRAY( -- SELECT o.oid::regoperator || ' RETURNS ' || o.oprresult::regtype -+ SELECT o.oid::regoperator || ' RETURNS ' || o.oprresult::regtype::text +- 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) -@@ -7216,7 +7212,7 @@ +@@ -7221,7 +7217,7 @@ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT -- SELECT o.oid::regoperator || ' RETURNS ' || o.oprresult::regtype -+ SELECT o.oid::regoperator || ' RETURNS ' || o.oprresult::regtype::text +- 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) From cad2f766ea9061e4d12964c439b09c848a415f53 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 18 Dec 2009 09:35:51 -0800 Subject: [PATCH 0490/1195] Easier maintenence for new versions going forward. --- Makefile | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index d8ed8c0de420..0f29f3b5e188 100644 --- a/Makefile +++ b/Makefile @@ -62,6 +62,16 @@ endif # Set up extra substitutions based on version numbers. ifeq ($(PGVER_MAJOR), 8) +ifeq ($(PGVER_MINOR), 2) +# Enum tests not supported by 8.2 and earlier. +TESTS := $(filter-out sql/enumtap.sql,$(TESTS)) +REGRESS := $(filter-out enumtap,$(REGRESS)) +endif +ifeq ($(PGVER_MINOR), 1) +# Values tests not supported by 8.1 and earlier. +TESTS := $(filter-out sql/enumtap.sql sql/valueset.sql,$(TESTS)) +REGRESS := $(filter-out enumtap valueset,$(REGRESS)) +endif ifeq ($(PGVER_MINOR), 0) # Hack for E'' syntax (<= PG8.0) EXTRA_SUBS := -e "s/ E'/ '/g" @@ -69,24 +79,6 @@ EXTRA_SUBS := -e "s/ E'/ '/g" TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql sql/enumtap.sql sql/roletap.sql,$(TESTS)) REGRESS := $(filter-out throwtap runtests enumtap roletap,$(REGRESS)) endif -ifeq ($(PGVER_MINOR), 5) -# Do nothing. -else -ifeq ($(PGVER_MINOR), 4) -# Do nothing. -else -ifneq ($(PGVER_MINOR), 3) -# Enum tests not supported by 8.2 and earlier. -TESTS := $(filter-out sql/enumtap.sql,$(TESTS)) -REGRESS := $(filter-out enumtap,$(REGRESS)) -ifneq ($(PGVER_MINOR), 2) -# Values tests not supported by 8.1 and earlier. -TESTS := $(filter-out sql/valueset.sql,$(TESTS)) -REGRESS := $(filter-out valueset,$(REGRESS)) -endif -endif -endif -endif endif # Determine the OS. Borrowed from Perl's Configure. From 1a087470476f7b6c28410ae10ca6a708efa3a121 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 18 Dec 2009 09:43:09 -0800 Subject: [PATCH 0491/1195] All tests pass on 8.1. --- compat/install-8.1.patch | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index 1a90968200e1..7a4770374b72 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ ---- pgtap.sql.orig 2009-07-29 22:02:08.000000000 -0700 -+++ pgtap.sql 2009-07-29 22:02:23.000000000 -0700 -@@ -5530,7 +5530,7 @@ +--- pgtap.sql.orig 2009-12-18 09:41:17.000000000 -0800 ++++ pgtap.sql 2009-12-18 09:41:17.000000000 -0800 +@@ -5646,7 +5646,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -9,7 +9,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -5538,8 +5538,8 @@ +@@ -5654,8 +5654,8 @@ -- 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. @@ -20,13 +20,13 @@ END LOOP; END LOOP; RETURN; -@@ -5607,14 +5607,14 @@ +@@ -5723,14 +5723,14 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; - tap text; + rec record; - verbose boolean := _is_verbose(); + verbos boolean := _is_verbose(); -- verbose is a reserved word in 8.5. num_faild INTEGER := 0; BEGIN BEGIN @@ -37,8 +37,8 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -5629,15 +5629,15 @@ - IF verbose THEN RETURN NEXT diag(tests[i] || '()'); END IF; +@@ -5745,15 +5745,15 @@ + IF verbos THEN RETURN NEXT diag(tests[i] || '()'); END IF; -- Run the setup functions. - FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; @@ -57,7 +57,7 @@ -- Remember how many failed and then roll back. num_faild := num_faild + num_failed(); -@@ -5652,7 +5652,7 @@ +@@ -5768,7 +5768,7 @@ END LOOP; -- Run the shutdown functions. @@ -66,7 +66,7 @@ -- Raise an exception to rollback any changes. RAISE EXCEPTION '__TAP_ROLLBACK__'; -@@ -5663,8 +5663,8 @@ +@@ -5779,8 +5779,8 @@ END IF; END; -- Finish up. From 67f38e3b358def16a1f642e1497dbfcd40819c4b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 18 Dec 2009 09:45:56 -0800 Subject: [PATCH 0492/1195] Update testsed versions. --- README.pgtap | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.pgtap b/README.pgtap index 043226e79c09..954665f6db08 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4309,9 +4309,9 @@ pgTAP has been tested on the following builds of PostgreSQL: * PostgreSQL 8.4.0 on i386-apple-darwin9.7.0 * PostgreSQL 8.3.9 on i386-apple-darwin10.2.0 * PostgreSQL 8.3.6 on i386-redhat-linux-gnu -* PostgreSQL 8.2.13 on i386-apple-darwin9.6.0 -* PostgreSQL 8.1.17 on i686-apple-darwin9.6.0 -* PostgreSQL 8.0.21 on i686-apple-darwin9.6.0 +* PostgreSQL 8.2.15 on i386-apple-darwin10.2.0 +* PostgreSQL 8.1.19 on i686-apple-darwin10.2.0 +* PostgreSQL 8.0.23 on i686-apple-darwin10.2.0 If you know of others, please submit them! Use `psql -d template1 -c 'SELECT VERSION()'` to get the formal build version and OS. From 47a58060eec73ca16965c8133eb5793a3fbc7496 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 18 Dec 2009 14:12:56 -0800 Subject: [PATCH 0493/1195] All tests pass on 8.0. --- Makefile | 7 +- README.pgtap | 1 + compat/install-8.0.patch | 257 +++++++++++++++++++++++++++++++++---- compat/uninstall-8.0.patch | 16 ++- compat/uninstall-8.0.sql | 4 - sql/istap.sql | 14 +- sql/resultset.sql | 217 ++++++++++++++++++++----------- 7 files changed, 403 insertions(+), 113 deletions(-) delete mode 100644 compat/uninstall-8.0.sql diff --git a/Makefile b/Makefile index 0f29f3b5e188..e6ca236c92e5 100644 --- a/Makefile +++ b/Makefile @@ -76,8 +76,8 @@ ifeq ($(PGVER_MINOR), 0) # Hack for E'' syntax (<= PG8.0) EXTRA_SUBS := -e "s/ E'/ '/g" # Throw, runtests, enums, and roles aren't supported in 8.0. -TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql sql/enumtap.sql sql/roletap.sql,$(TESTS)) -REGRESS := $(filter-out throwtap runtests enumtap roletap,$(REGRESS)) +TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql sql/enumtap.sql sql/roletap.sql sql/valueset.sql,$(TESTS)) +REGRESS := $(filter-out throwtap runtests enumtap roletap valueset,$(REGRESS)) endif endif @@ -136,9 +136,6 @@ ifneq ($(PGVER_MINOR), 3) endif ifeq ($(PGVER_MINOR), 0) patch -p0 < compat/uninstall-8.0.patch - mv uninstall_pgtap.sql uninstall_pgtap.tmp - cat compat/uninstall-8.0.sql uninstall_pgtap.tmp >> uninstall_pgtap.sql - rm uninstall_pgtap.tmp endif endif endif diff --git a/README.pgtap b/README.pgtap index 954665f6db08..9f37ce0d54ad 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4288,6 +4288,7 @@ No changes. Everything should just work. * `runtests()` * `has_role()` * `hasnt_role()` +* `roles_are()` To Do ----- diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index 2ab8b1697ca7..f7c8b0e1be2c 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,6 +1,63 @@ ---- pgtap.sql.orig 2009-07-30 13:35:07.000000000 -0700 -+++ pgtap.sql 2009-07-30 13:35:24.000000000 -0700 -@@ -15,6 +15,27 @@ +--- pgtap.sql.orig 2009-12-18 10:01:20.000000000 -0800 ++++ pgtap.sql 2009-12-18 10:07:07.000000000 -0800 +@@ -11,10 +11,84 @@ + -- ## CREATE SCHEMA TAPSCHEMA; + -- ## SET search_path TO TAPSCHEMA, public; + ++-- 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; ++ ++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; @@ -28,7 +85,7 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT s.a[1]::int * 10000 -@@ -91,53 +112,63 @@ +@@ -92,53 +166,63 @@ CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ DECLARE @@ -109,7 +166,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -189,11 +220,11 @@ +@@ -190,11 +274,11 @@ RETURNS integer AS $$ BEGIN EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) @@ -126,7 +183,7 @@ RETURN currval('__tresults___numb_seq'); END; $$ LANGUAGE plpgsql; -@@ -201,10 +232,12 @@ +@@ -202,10 +286,12 @@ CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ DECLARE @@ -142,7 +199,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -478,13 +511,16 @@ +@@ -478,13 +564,16 @@ want ALIAS FOR $3; descr ALIAS FOR $4; result BOOLEAN; @@ -163,7 +220,16 @@ output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE '\n' || diag( ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -1232,19 +1268,21 @@ +@@ -1084,7 +1173,7 @@ + + CREATE OR REPLACE FUNCTION display_type ( OID, INTEGER ) + RETURNS TEXT AS $$ +- SELECT $1::regtype ++ SELECT $1::regtype::text + || COALESCE(substring(pg_catalog.format_type($1, $2), '[(][^)]+[)]$'), '') + $$ LANGUAGE SQL; + +@@ -1306,19 +1395,21 @@ CREATE OR REPLACE FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -189,7 +255,16 @@ END; $$ LANGUAGE plpgsql; -@@ -3293,96 +3331,6 @@ +@@ -2096,7 +2187,7 @@ + p.proname AS name, + 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, +@@ -3367,96 +3458,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -286,7 +361,7 @@ CREATE OR REPLACE FUNCTION _is_super( NAME ) RETURNS BOOLEAN AS $$ SELECT usesuper -@@ -3486,7 +3434,7 @@ +@@ -3560,7 +3561,7 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _grolist ( NAME ) @@ -295,7 +370,7 @@ SELECT grolist FROM pg_catalog.pg_group WHERE groname = $1; $$ LANGUAGE sql; -@@ -5447,6 +5395,7 @@ +@@ -5510,6 +5511,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -303,7 +378,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -5458,8 +5407,10 @@ +@@ -5521,8 +5523,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. @@ -316,7 +391,7 @@ -- Now delete those results. EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; -@@ -5582,7 +5533,7 @@ +@@ -5645,7 +5649,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -325,7 +400,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -5590,8 +5541,8 @@ +@@ -5653,8 +5657,8 @@ -- 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. @@ -336,7 +411,7 @@ END LOOP; END LOOP; RETURN; -@@ -5651,116 +5602,6 @@ +@@ -5714,116 +5718,6 @@ END $$ LANGUAGE plpgsql; @@ -349,7 +424,7 @@ - teardown ALIAS FOR $4; - tests ALIAS FOR $5; - tap text; -- verbose boolean := _is_verbose(); +- verbos boolean := _is_verbose(); -- verbose is a reserved word in 8.5. - num_faild INTEGER := 0; -BEGIN - BEGIN @@ -367,7 +442,7 @@ - FOR i IN 1..array_upper(tests, 1) LOOP - BEGIN - -- What test are we running? -- IF verbose THEN RETURN NEXT diag(tests[i] || '()'); END IF; +- IF verbos THEN RETURN NEXT diag(tests[i] || '()'); END IF; - - -- Run the setup functions. - FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; @@ -453,8 +528,8 @@ CREATE OR REPLACE FUNCTION _temptable ( TEXT, TEXT ) RETURNS TEXT AS $$ BEGIN -@@ -5786,8 +5627,9 @@ - SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) +@@ -5849,8 +5743,9 @@ + SELECT display_type(a.atttypid, a.atttypmod) FROM pg_catalog.pg_attribute a JOIN pg_catalog.pg_class c ON a.attrelid = c.oid + JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid @@ -464,7 +539,7 @@ AND attnum > 0 AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END ORDER BY attnum -@@ -5809,13 +5651,13 @@ +@@ -5872,13 +5767,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -480,7 +555,7 @@ END LOOP; -- Drop the temporary tables. -@@ -5929,16 +5771,20 @@ +@@ -5992,16 +5887,20 @@ missing TEXT[] := '{}'; res BOOLEAN := TRUE; msg TEXT := ''; @@ -503,7 +578,7 @@ -- Drop the temporary tables. EXECUTE 'DROP TABLE ' || have; -@@ -6039,7 +5885,7 @@ +@@ -6102,7 +6001,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -512,7 +587,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6134,11 +5980,11 @@ +@@ -6197,11 +6096,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -527,7 +602,7 @@ ); END IF; rownum = rownum + 1; -@@ -6153,8 +5999,8 @@ +@@ -6216,8 +6115,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || '\n' || diag( ' Columns differ between queries:\n' || @@ -538,7 +613,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6289,7 +6135,7 @@ +@@ -6352,7 +6251,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -547,7 +622,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6303,8 +6149,8 @@ +@@ -6366,8 +6265,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || '\n' || diag( ' Columns differ between queries:\n' || @@ -558,3 +633,133 @@ ); END; $$ LANGUAGE plpgsql; +@@ -6492,9 +6391,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 ) || '\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 ) || '\n' || ++ diag(' ' || $3 || ' isn''t a "' || $2::text || '" it''s a "' || typeof::text || '"'); + END; + $$ LANGUAGE plpgsql; + +@@ -6625,35 +6524,6 @@ + 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( +@@ -6666,7 +6536,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 +@@ -6684,7 +6554,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']) ) + ), +@@ -6717,7 +6587,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']) ) +@@ -6736,7 +6606,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']) ) +@@ -7166,7 +7036,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 ) +@@ -7175,7 +7045,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 +@@ -7187,7 +7057,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 +@@ -7208,7 +7078,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) +@@ -7221,7 +7091,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) diff --git a/compat/uninstall-8.0.patch b/compat/uninstall-8.0.patch index 6a2ec9119987..f6326657c6b6 100644 --- a/compat/uninstall-8.0.patch +++ b/compat/uninstall-8.0.patch @@ -1,6 +1,6 @@ ---- uninstall_pgtap.sql.orig 2009-07-30 13:41:19.000000000 -0700 -+++ uninstall_pgtap.sql 2009-07-30 13:42:52.000000000 -0700 -@@ -308,11 +308,6 @@ +--- uninstall_pgtap.sql.orig 2009-12-18 09:53:39.000000000 -0800 ++++ uninstall_pgtap.sql 2009-12-18 09:55:43.000000000 -0800 +@@ -359,11 +359,6 @@ DROP FUNCTION has_user( NAME ); DROP FUNCTION has_user( NAME, TEXT ); DROP FUNCTION _is_super( NAME ); @@ -12,3 +12,13 @@ DROP FUNCTION hasnt_enum( NAME ); DROP FUNCTION hasnt_enum( NAME, TEXT ); DROP FUNCTION hasnt_enum( NAME, NAME ); +@@ -710,5 +705,9 @@ + DROP FUNCTION textarray_text(text[]); + DROP CAST (boolean AS char(1)); + DROP FUNCTION booltext(boolean); ++DROP CAST (int2vector AS int[]); ++DROP FUNCTION int2vint(int2vector); ++DROP CAST (oidvector AS regtype[]); ++DROP FUNCTION oidvregtype(oidvector); + -- ## SET search_path TO public; + -- ## DROP SCHEMA TAPSCHEMA; diff --git a/compat/uninstall-8.0.sql b/compat/uninstall-8.0.sql deleted file mode 100644 index a998dae519b8..000000000000 --- a/compat/uninstall-8.0.sql +++ /dev/null @@ -1,4 +0,0 @@ -DROP CAST (int2vector AS int[]); -DROP FUNCTION int2vint(int2vector); -DROP CAST (oidvector AS regtype[]); -DROP FUNCTION oidvregtype(oidvector); diff --git a/sql/istap.sql b/sql/istap.sql index ba790c1cd949..adf1adfcaecb 100644 --- a/sql/istap.sql +++ b/sql/istap.sql @@ -65,7 +65,19 @@ CREATE FUNCTION test_records() RETURNS SETOF TEXT AS $$ DECLARE tap record; BEGIN - IF pg_version_num() >= 80400 THEN + IF pg_version_num() < 80100 THEN + -- Can't do shit with records on 8.0 + RETURN NEXT pass('with records!'); + RETURN NEXT pass( 'is(mumble, row) fail should fail'); + RETURN NEXT pass( 'is(mumble, row) fail should have the proper description'); + RETURN NEXT pass( 'is(mumble, row) fail should have the proper diagnostics'); + RETURN NEXT pass( 'is(mumble, row) fail with NULL should fail'); + RETURN NEXT pass( 'is(mumble, row) fail with NULL should have the proper description'); + RETURN NEXT pass( 'is(mumble, row) fail with NULL should have the proper diagnostics'); + RETURN NEXT pass( 'is(mumble, NULL) should fail'); + RETURN NEXT pass( 'is(mumble, NULL) should have the proper description'); + RETURN NEXT pass( 'is(mumble, NULL) should have the proper diagnostics'); + ELSIF pg_version_num() >= 80400 THEN RETURN NEXT is( mumble.*, ROW(1, 'hey')::mumble, 'with records!' ) FROM mumble; diff --git a/sql/resultset.sql b/sql/resultset.sql index c34f55541d42..6534f6dbe170 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -2268,99 +2268,168 @@ SELECT * FROM check_test( '' ); -PREPARE notempty AS SELECT id, name FROM names WHERE name IN ('Jacob', 'Emily') ORDER BY ID; -SELECT * FROM check_test( - is_empty( 'notempty', 'whatever' ), - false, - 'is_empty(prepared, desc) fail', - 'whatever', - ' Unexpected records: +CREATE FUNCTION test_empty_fail() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() < 80100 THEN + -- Can't do shit with records on 8.0 + RETURN NEXT pass('is_empty(prepared, desc) fail should fail'); + RETURN NEXT pass('is_empty(prepared, desc) fail should have the proper description'); + RETURN NEXT pass('is_empty(prepared, desc) fail should have the proper diagnostics'); + RETURN NEXT pass('is_empty(prepared) fail should fail'); + RETURN NEXT pass('is_empty(prepared) fail should have the proper description'); + RETURN NEXT pass('is_empty(prepared) fail should have the proper diagnostics'); + ELSE + PREPARE notempty AS SELECT id, name FROM names WHERE name IN ('Jacob', 'Emily') + ORDER BY ID; + FOR tap IN SELECT check_test( + is_empty( 'notempty', 'whatever' ), + false, + 'is_empty(prepared, desc) fail', + 'whatever', + ' Unexpected records: (1,Jacob) (2,Emily)' -); + ) AS b FROM mumble LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT * FROM check_test( - is_empty( 'notempty' ), - false, - 'is_empty(prepared) fail', - '', - ' Unexpected records: + FOR tap IN SELECT check_test( + is_empty( 'notempty' ), + false, + 'is_empty(prepared) fail', + '', + ' Unexpected records: (1,Jacob) (2,Emily)' -); + ) AS b FROM mumble LOOP + RETURN NEXT tap.b; + END LOOP; + END IF; + RETURN; +END; +$$ LANGUAGE PLPGSQL; +SELECT * FROM test_empty_fail(); /****************************************************************************/ -- Test row_eq(). PREPARE arow AS SELECT id, name FROM names WHERE name = 'Jacob'; - -SELECT check_test( - row_eq('arow', ROW(1, 'Jacob')::names, 'whatever'), - true, - 'row_eq(prepared, record, desc)', - 'whatever', - '' +CREATE TYPE sometype AS ( + id INT, + name TEXT ); -SELECT check_test( - row_eq('SELECT id, name FROM names WHERE id = 1', ROW(1, 'Jacob')::names, 'whatever'), - true, - 'row_eq(sql, record, desc)', - 'whatever', - '' -); +CREATE FUNCTION test_row_eq() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() < 80100 THEN + -- Can't do shit with records on 8.0 + RETURN NEXT pass('row_eq(prepared, record, desc) should pass'); + RETURN NEXT pass('row_eq(prepared, record, desc) should have the proper description'); + RETURN NEXT pass('row_eq(prepared, record, desc) should have the proper diagnostics'); + RETURN NEXT pass('row_eq(sql, record, desc) should pass'); + RETURN NEXT pass('row_eq(sql, record, desc) should have the proper description'); + RETURN NEXT pass('row_eq(sql, record, desc) should have the proper diagnostics'); + RETURN NEXT pass('row_eq(prepared, record, desc) should pass'); + RETURN NEXT pass('row_eq(prepared, record, desc) should have the proper description'); + RETURN NEXT pass('row_eq(prepared, record, desc) should have the proper diagnostics'); + RETURN NEXT pass('row_eq(prepared, record, desc) should fail'); + RETURN NEXT pass('row_eq(prepared, record, desc) should have the proper description'); + RETURN NEXT pass('row_eq(prepared, record, desc) should have the proper diagnostics'); + RETURN NEXT pass('row_eq(prepared, sometype, desc) should pass'); + RETURN NEXT pass('row_eq(prepared, sometype, desc) should have the proper description'); + RETURN NEXT pass('row_eq(prepared, sometype, desc) should have the proper diagnostics'); + RETURN NEXT pass('row_eq(sqlrow, sometype, desc) should pass'); + RETURN NEXT pass('row_eq(sqlrow, sometype, desc) should have the proper description'); + RETURN NEXT pass('row_eq(sqlrow, sometype, desc) should have the proper diagnostics'); + RETURN NEXT pass('threw 0A000'); + ELSE + FOR tap IN SELECT check_test( + row_eq('arow', ROW(1, 'Jacob')::names, 'whatever'), + true, + 'row_eq(prepared, record, desc)', + 'whatever', + '' + ) AS b FROM mumble LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT check_test( - row_eq('arow', ROW(1, 'Jacob')::names), - true, - 'row_eq(prepared, record, desc)', - '', - '' -); + FOR tap IN SELECT check_test( + row_eq('SELECT id, name FROM names WHERE id = 1', ROW(1, 'Jacob')::names, 'whatever'), + true, + 'row_eq(sql, record, desc)', + 'whatever', + '' + ) AS b FROM mumble LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT check_test( - row_eq('arow', ROW(1, 'Larry')::names), - false, - 'row_eq(prepared, record, desc)', - '', - ' have: (1,Jacob) + FOR tap IN SELECT check_test( + row_eq('arow', ROW(1, 'Jacob')::names), + true, + 'row_eq(prepared, record, desc)', + '', + '' + ) AS b FROM mumble LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT check_test( + row_eq('arow', ROW(1, 'Larry')::names), + false, + 'row_eq(prepared, record, desc)', + '', + ' have: (1,Jacob) want: (1,Larry)' -); + ) AS b FROM mumble LOOP + RETURN NEXT tap.b; + END LOOP; -CREATE TYPE sometype AS ( - id INT, - name TEXT -); + FOR tap IN SELECT check_test( + row_eq('arow', ROW(1, 'Jacob')::sometype), + true, + 'row_eq(prepared, sometype, desc)', + '', + '' + ) AS b FROM mumble LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT check_test( - row_eq('arow', ROW(1, 'Jacob')::sometype), - true, - 'row_eq(prepared, sometype, desc)', - '', - '' -); + FOR tap IN SELECT check_test( + row_eq('SELECT 1, ''Jacob''::text', ROW(1, 'Jacob')::sometype), + true, + 'row_eq(sqlrow, sometype, desc)', + '', + '' + ) AS b FROM mumble LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT check_test( - row_eq('SELECT 1, ''Jacob''::text', ROW(1, 'Jacob')::sometype), - true, - 'row_eq(sqlrow, sometype, desc)', - '', - '' -); + INSERT INTO someat (ts) values ('2009-12-04T07:22:52'); + RETURN NEXT throws_ok( + 'SELECT row_eq( ''SELECT id, ts FROM someat'', ROW(1, ''2009-12-04T07:22:52'') )', + '0A000' + -- 'PL/pgSQL functions cannot accept type record' + ); + + -- FOR tap IN SELECT check_test( + -- row_eq( 'SELECT id, ts FROM someat', ROW(1, '2009-12-04T07:22:52') ), + -- true, + -- 'row_eq(sql, rec)', + -- '', + -- '' + -- ) AS b FROM mumble LOOP + -- RETURN NEXT tap.b; + -- END LOOP; -INSERT INTO someat (ts) values ('2009-12-04T07:22:52'); -SELECT throws_ok( - 'SELECT row_eq( ''SELECT id, ts FROM someat'', ROW(1, ''2009-12-04T07:22:52'') )', - '0A000' --- 'PL/pgSQL functions cannot accept type record' -); + END IF; + RETURN; +END; +$$ LANGUAGE plpgsql; --- SELECT check_test( --- row_eq( 'SELECT id, ts FROM someat', ROW(1, '2009-12-04T07:22:52') ), --- true, --- 'row_eq(sql, rec)', --- '', --- '' --- ); +SELECT * FROM test_row_eq(); /****************************************************************************/ -- Finish the tests and clean up. From a58c090da524447ebe1aa0ccf773bb3d5d82ab35 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 18 Dec 2009 14:34:55 -0800 Subject: [PATCH 0494/1195] 8.0 fixes passing on 8.1 now. --- README.pgtap | 12 +++++++----- sql/resultset.sql | 36 ++++++++++++++++++------------------ 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/README.pgtap b/README.pgtap index 9f37ce0d54ad..babc30894129 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1240,11 +1240,12 @@ fails and the results are displayed in the failure diagnostics, like so: PREPARE testrow AS SELECT * FROM users where nick = 'theory'; SELECT row_eq('testrow', ROW(1, 'theory', 'David Wheeler')::users); -Compares the contents of a single row to a record. Due to the limitations of -non-C functions in PostgreSQL, a bar `RECORD` value cannot be passed to the -function. You must instead pass in a valid composite type value, and cast the -record argument (the second argument) to the same type. Both explicitly -created composite types and table types are supported. Thus, you can do this: +Compares the contents of a single row to a record. Works on PostgreSQL 8.1 and +higher. Due to the limitations of non-C functions in PostgreSQL, a bar +`RECORD` value cannot be passed to the function. You must instead pass in a +valid composite type value, and cast the record argument (the second argument) +to the same type. Both explicitly created composite types and table types are +supported. Thus, you can do this: CREATE TYPE sometype AS ( id INT, @@ -4289,6 +4290,7 @@ No changes. Everything should just work. * `has_role()` * `hasnt_role()` * `roles_are()` +* `row_eq()` To Do ----- diff --git a/sql/resultset.sql b/sql/resultset.sql index 6534f6dbe170..d29d928d7d01 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -2283,7 +2283,7 @@ BEGIN ELSE PREPARE notempty AS SELECT id, name FROM names WHERE name IN ('Jacob', 'Emily') ORDER BY ID; - FOR tap IN SELECT check_test( + FOR tap IN SELECT * FROM check_test( is_empty( 'notempty', 'whatever' ), false, 'is_empty(prepared, desc) fail', @@ -2291,11 +2291,11 @@ BEGIN ' Unexpected records: (1,Jacob) (2,Emily)' - ) AS b FROM mumble LOOP + ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; - FOR tap IN SELECT check_test( + FOR tap IN SELECT * FROM check_test( is_empty( 'notempty' ), false, 'is_empty(prepared) fail', @@ -2303,7 +2303,7 @@ BEGIN ' Unexpected records: (1,Jacob) (2,Emily)' - ) AS b FROM mumble LOOP + ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; END IF; @@ -2346,64 +2346,64 @@ BEGIN RETURN NEXT pass('row_eq(sqlrow, sometype, desc) should have the proper diagnostics'); RETURN NEXT pass('threw 0A000'); ELSE - FOR tap IN SELECT check_test( + FOR tap IN SELECT * FROM check_test( row_eq('arow', ROW(1, 'Jacob')::names, 'whatever'), true, 'row_eq(prepared, record, desc)', 'whatever', '' - ) AS b FROM mumble LOOP + ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; - FOR tap IN SELECT check_test( + FOR tap IN SELECT * FROM check_test( row_eq('SELECT id, name FROM names WHERE id = 1', ROW(1, 'Jacob')::names, 'whatever'), true, 'row_eq(sql, record, desc)', 'whatever', '' - ) AS b FROM mumble LOOP + ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; - FOR tap IN SELECT check_test( + FOR tap IN SELECT * FROM check_test( row_eq('arow', ROW(1, 'Jacob')::names), true, 'row_eq(prepared, record, desc)', '', '' - ) AS b FROM mumble LOOP + ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; - FOR tap IN SELECT check_test( + FOR tap IN SELECT * FROM check_test( row_eq('arow', ROW(1, 'Larry')::names), false, 'row_eq(prepared, record, desc)', '', ' have: (1,Jacob) want: (1,Larry)' - ) AS b FROM mumble LOOP + ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; - FOR tap IN SELECT check_test( + FOR tap IN SELECT * FROM check_test( row_eq('arow', ROW(1, 'Jacob')::sometype), true, 'row_eq(prepared, sometype, desc)', '', '' - ) AS b FROM mumble LOOP + ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; - FOR tap IN SELECT check_test( + FOR tap IN SELECT * FROM check_test( row_eq('SELECT 1, ''Jacob''::text', ROW(1, 'Jacob')::sometype), true, 'row_eq(sqlrow, sometype, desc)', '', '' - ) AS b FROM mumble LOOP + ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; @@ -2414,13 +2414,13 @@ BEGIN -- 'PL/pgSQL functions cannot accept type record' ); - -- FOR tap IN SELECT check_test( + -- FOR tap IN SELECT * FROM check_test( -- row_eq( 'SELECT id, ts FROM someat', ROW(1, '2009-12-04T07:22:52') ), -- true, -- 'row_eq(sql, rec)', -- '', -- '' - -- ) AS b FROM mumble LOOP + -- ) AS a(b) LOOP -- RETURN NEXT tap.b; -- END LOOP; From 3dc1595ab51cb470fd053ef62a8e977e39baeeee Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 18 Dec 2009 15:02:34 -0800 Subject: [PATCH 0495/1195] Not includeing pg_tapgen in the release. Not sure what to do with this script, but will likely move it to its own project. --- Changes | 2 -- 1 file changed, 2 deletions(-) diff --git a/Changes b/Changes index a72a31dc1522..3514e46f8625 100644 --- a/Changes +++ b/Changes @@ -15,8 +15,6 @@ Revision history for pgTAP find "bool" instead of "boolean". * Added `isa_ok()`. * Added support for building against 8.5 [Dan Colish]. -* Added `pg_tapgen`, which is a start and automating pgTAP schema validation - tests. * Fixed failing test on alpha releases [Jonathan Leto] * Added `is_empty()` to test that a query returns an empty set (no results). * Added `collect_tap()`, which makes it easier to run multiple tests in a From a790afb3033b3c99d8dd085d34eff97613506b84 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 18 Dec 2009 15:02:59 -0800 Subject: [PATCH 0496/1195] Timestamped for 0.23 release. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 3514e46f8625..62094e279570 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.23 +0.23 2009-12-18T23:03:39 ------------------------- * Fixed broken Perl detection in `Makefile`. * Copied OS detection from Perl's `Configure` script to new script, From 8684d32db2d0940d840b8ee043bd86a789646720 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 18 Dec 2009 15:16:04 -0800 Subject: [PATCH 0497/1195] Bump version number to 0.24. --- Changes | 3 +++ Makefile | 2 +- README.pgtap | 2 +- bin/pg_prove | 2 +- bin/pg_tapgen | 2 +- contrib/pgtap.spec | 2 +- 6 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 62094e279570..4da905b8f2a0 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Revision history for pgTAP ========================== +0.24 +------------------------- + 0.23 2009-12-18T23:03:39 ------------------------- * Fixed broken Perl detection in `Makefile`. diff --git a/Makefile b/Makefile index e6ca236c92e5..645ccd2fde6c 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') PGVER_MAJOR = $(shell echo $(VERSION) | awk -F. '{ print ($$1 + 0) }') PGVER_MINOR = $(shell echo $(VERSION) | awk -F. '{ print ($$2 + 0) }') PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') -PGTAP_VERSION = 0.23 +PGTAP_VERSION = 0.24 # Compile the C code only if we're on 8.3 or older. ifeq ($(PGVER_MINOR), 3) diff --git a/README.pgtap b/README.pgtap index babc30894129..272d1a12b041 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.23 +pgTAP 0.24 ========== pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/bin/pg_prove b/bin/pg_prove index 4fff7dd79351..ab22c22b6f45 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -4,7 +4,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.23'; +our $VERSION = '0.24'; Getopt::Long::Configure (qw(bundling)); diff --git a/bin/pg_tapgen b/bin/pg_tapgen index 92554c454137..310c7046eeff 100755 --- a/bin/pg_tapgen +++ b/bin/pg_tapgen @@ -5,7 +5,7 @@ use warnings; use DBI; use DBD::Pg; use Getopt::Long; -our $VERSION = '0.23'; +our $VERSION = '0.24'; Getopt::Long::Configure (qw(bundling)); diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 47e1a084b2b4..e0a4d5b6d46e 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 0.23 +Version: 0.24 Release: 1%{?dist} Group: Applications/Databases License: BSD From 5cc5c4631431f7e105e1a70200f8f77c8973f035 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 21 Dec 2009 11:21:33 -0800 Subject: [PATCH 0498/1195] Get tests passing with `$TAPSCHEMA` set. --- Changes | 1 + sql/aretap.sql | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 4da905b8f2a0..328a1fbbc94b 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,7 @@ Revision history for pgTAP 0.24 ------------------------- +* Got `sql/artap.sql` tests passing again when building with `$TAPSCHEMA` set. 0.23 2009-12-18T23:03:39 ------------------------- diff --git a/sql/aretap.sql b/sql/aretap.sql index 59a567e5ebe6..cf933afed255 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -50,8 +50,9 @@ $$ LANGUAGE SQL IMMUTABLE STRICT; CREATE SCHEMA disney; CREATE OPERATOR disney.= ( PROCEDURE = goofy_eq, LEFTARG = goofy, RIGHTARG = goofy); +CREATE OPERATOR = ( PROCEDURE = goofy_eq, LEFTARG = goofy, RIGHTARG = goofy); -CREATE OPERATOR CLASS goofy_ops +CREATE OPERATOR CLASS public.goofy_ops DEFAULT FOR TYPE goofy USING BTREE AS OPERATOR 1 disney.=, FUNCTION 1 goofy_cmp(goofy,goofy) @@ -1270,7 +1271,7 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test operators_are(). -SET search_path = disney,public,pg_catalog; +--SET search_path = disney,public,pg_catalog; SELECT * FROM check_test( operators_are( 'disney', ARRAY['=(goofy,goofy) RETURNS boolean'], 'whatever' ), From 53dd5371f8d91a0c0b24ce5c57409e3586c62371 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 27 Dec 2009 13:23:22 -0800 Subject: [PATCH 0499/1195] Saner URL in `pgtap.spec`. --- Changes | 1 + contrib/pgtap.spec | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 328a1fbbc94b..1b2ddf059a10 100644 --- a/Changes +++ b/Changes @@ -4,6 +4,7 @@ Revision history for pgTAP 0.24 ------------------------- * Got `sql/artap.sql` tests passing again when building with `$TAPSCHEMA` set. +* Changed to saner source URL in `contrib/pgtap.spec`. 0.23 2009-12-18T23:03:39 ------------------------- diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index e0a4d5b6d46e..367c0d8d75f3 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -5,7 +5,7 @@ Release: 1%{?dist} Group: Applications/Databases License: BSD URL: http://pgtap.projects.postgresql.org -Source0: http://pgfoundry.org/frs/download.php/2316/pgtap-%{version}.tar.gz +Source0: ftp://ftp.postgresql.org/pub/projects/pgFoundry/pgtap/pgtap-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root BuildRequires: postgresql-devel Requires: postgresql-server, perl-Test-Harness >= 3.0 @@ -37,6 +37,9 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog +* Sun Dec 27 2009 Davi dWheeler 0.24-1 +- Updated Source URL to a more predictable format. + * Mon Aug 24 2009 David Fetter 0.23-1 - Got corrected .spec from Devrim GUNDUZ - Bumped version to 0.23. From a5c5f8a8c4cbe53705a231cd08243c2e07686b8f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 31 Dec 2009 14:22:35 -0800 Subject: [PATCH 0500/1195] Typo. --- README.pgtap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.pgtap b/README.pgtap index 272d1a12b041..ab8357b726cb 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3535,8 +3535,8 @@ so. ### `is_member_of( group, users[], desc )` ### ### `is_member_of( group, users[] )` ### -### `is_member_of( group, users, desc )` ### -### `is_member_of( group, users )` ### +### `is_member_of( group, user, desc )` ### +### `is_member_of( group, user )` ### SELECT is_member_of( 'sweeties', 'anna' 'Anna should be a sweetie' ); SELECT is_member_of( 'meanies', ARRAY['dr_evil', 'dr_no' ] ); From 657050d00f62aacbc58906e030a1ab4c52fdb74a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 31 Dec 2009 14:48:47 -0800 Subject: [PATCH 0501/1195] Fix `member_of()`. Once again, `ANY()` bites me on the ass. --- Changes | 2 ++ pgtap.sql.in | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 1b2ddf059a10..0f3ee492722d 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,8 @@ Revision history for pgTAP ------------------------- * Got `sql/artap.sql` tests passing again when building with `$TAPSCHEMA` set. * Changed to saner source URL in `contrib/pgtap.spec`. +* Fixed a bug in `has_member()` where it failed to confirm that users were + actually members of a group. 0.23 2009-12-18T23:03:39 ------------------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index a94f20d78c07..f08a08247dec 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3581,7 +3581,7 @@ BEGIN FROM generate_series(1, array_upper($2, 1)) s(i) LEFT JOIN pg_catalog.pg_user ON usename = $2[i] WHERE usesysid IS NULL - OR usesysid <> ANY ( _grolist($1) ) + OR NOT usesysid = ANY ( _grolist($1) ) ORDER BY s.i ) INTO missing; IF missing[1] IS NULL THEN From 41c21cef78111c9a55fc60a997899d9adb958bcc Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 31 Dec 2009 15:13:21 -0800 Subject: [PATCH 0502/1195] Another typo. --- README.pgtap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index ab8357b726cb..72dca614e1bc 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3709,7 +3709,7 @@ had the `WHEN` condition been true. Declares a series of tests that you expect to fail and why. Perhaps it's because you haven't fixed a bug or haven't finished a new feature: - todo('URIGeller not finished', 2); + SELECT todo('URIGeller not finished', 2); \set card '\'' Eight of clubs '\'' SELECT is( URIGeller.yourCard(), :card, 'Is THIS your card?' ); From 6e36bbb02c04ee4f4a09e2a1e878a74e298c1079 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 31 Dec 2009 15:20:25 -0800 Subject: [PATCH 0503/1195] Add To-Do for ownership testing functions. --- README.pgtap | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.pgtap b/README.pgtap index 72dca614e1bc..13f0e49c06dd 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4294,6 +4294,13 @@ No changes. Everything should just work. To Do ----- +* Add functions to test for object ownership. + + `db_owner_is()` + + `table_owner_is()` + + `view_owner_is()` + + `sequence_owner_is()` + + `function_owner_is()` + + etc. * Have `has_function()` manage OUT, INOUT, and VARIADIC arguments. * Useful schema testing functions to consider adding: + `sequence_has_range()` From 1401f4af6a76023d1f803006ceaf0b1d5a00d9e0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 31 Dec 2009 16:01:49 -0800 Subject: [PATCH 0504/1195] Another To-Do: Add permissions validation functions. --- README.pgtap | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.pgtap b/README.pgtap index 13f0e49c06dd..5580099239fb 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4301,6 +4301,9 @@ To Do + `sequence_owner_is()` + `function_owner_is()` + etc. +* Add some sort of tests for permisions. Something like: + `table_privs_are(:table, :user, :privs[])`, and would have variations for + database, sequence, function, language, schema, and tablespace. * Have `has_function()` manage OUT, INOUT, and VARIADIC arguments. * Useful schema testing functions to consider adding: + `sequence_has_range()` From 568ed076cfd49190b106de015c3c6545c6ea55b5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 31 Dec 2009 17:19:13 -0800 Subject: [PATCH 0505/1195] Fix `is_member_of()` and improve 8.0 Compat. Turns out that the way that groups are represented in 8.1 and higher is just a simulation of how they looked before 8.1. This is because roles are different, of course. So I changed the query in `_grolist()` to use the proper role way of doing things, and then took the old query and added it to the 8.0 patch. Of course, that required a lot of work, so I went ahead and changed how the 8.0 patch works so that it doesn't replicate the work of other patches. To do this, I had to move the code that removes `E''` syntax to run *after* the patches have been applied. This is a lot cleaner, and should make maintenance of the 8.0 compat patch a lot simpler from here on in. Change tested with 8.3-8.4. --- Makefile | 19 +- compat/install-8.0.patch | 415 ++++++++------------------------------- pgtap.sql.in | 7 +- 3 files changed, 99 insertions(+), 342 deletions(-) diff --git a/Makefile b/Makefile index 645ccd2fde6c..a75a6a9685bd 100644 --- a/Makefile +++ b/Makefile @@ -73,8 +73,6 @@ TESTS := $(filter-out sql/enumtap.sql sql/valueset.sql,$(TESTS)) REGRESS := $(filter-out enumtap valueset,$(REGRESS)) endif ifeq ($(PGVER_MINOR), 0) -# Hack for E'' syntax (<= PG8.0) -EXTRA_SUBS := -e "s/ E'/ '/g" # Throw, runtests, enums, and roles aren't supported in 8.0. TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql sql/enumtap.sql sql/roletap.sql sql/valueset.sql,$(TESTS)) REGRESS := $(filter-out throwtap runtests enumtap roletap valueset,$(REGRESS)) @@ -95,25 +93,24 @@ endif pgtap.sql: pgtap.sql.in test_setup.sql ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' $(EXTRA_SUBS) pgtap.sql.in > pgtap.sql + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' pgtap.sql.in > pgtap.sql else - sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' $(EXTRA_SUBS) pgtap.sql.in > pgtap.sql + sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' pgtap.sql.in > pgtap.sql endif ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 5) ifneq ($(PGVER_MINOR), 4) -ifneq ($(PGVER_MINOR), 0) patch -p0 < compat/install-8.3.patch -endif ifneq ($(PGVER_MINOR), 3) -ifeq ($(PGVER_MINOR), 2) - patch -p0 < compat/install-8.2.patch -else -ifeq ($(PGVER_MINOR), 1) patch -p0 < compat/install-8.2.patch +ifneq ($(PGVER_MINOR), 2) patch -p0 < compat/install-8.1.patch -else +ifneq ($(PGVER_MINOR), 1) patch -p0 < compat/install-8.0.patch +# Hack for E'' syntax (<= PG8.0) + mv pgtap.sql pgtap.tmp + sed -e "s/ E'/ '/g" pgtap.tmp > pgtap.sql + rm pgtap.tmp endif endif endif diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index f7c8b0e1be2c..38b9652f2509 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,63 +1,6 @@ ---- pgtap.sql.orig 2009-12-18 10:01:20.000000000 -0800 -+++ pgtap.sql 2009-12-18 10:07:07.000000000 -0800 -@@ -11,10 +11,84 @@ - -- ## CREATE SCHEMA TAPSCHEMA; - -- ## SET search_path TO TAPSCHEMA, public; - -+-- 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; -+ -+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() +--- pgtap.sql.orig 2009-12-31 16:40:00.000000000 -0800 ++++ pgtap.sql 2009-12-31 17:13:43.000000000 -0800 +@@ -68,6 +68,27 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -82,10 +25,10 @@ +AS '$libdir/pgtap' +LANGUAGE C STABLE; + - CREATE OR REPLACE FUNCTION pg_version_num() - RETURNS integer AS $$ - SELECT s.a[1]::int * 10000 -@@ -92,53 +166,63 @@ + CREATE OR REPLACE FUNCTION pg_typeof("any") + RETURNS regtype + AS '$libdir/pgtap' +@@ -150,53 +171,63 @@ CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ DECLARE @@ -132,7 +75,7 @@ + quote_literal($1) || ' AND value = ' || $2 LOOP + RETURN rec.id; + END LOOP; -+ RETURN; ++ RETURN NULL; END; $$ LANGUAGE plpgsql strict; @@ -166,24 +109,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -190,11 +274,11 @@ - RETURNS integer AS $$ - BEGIN - EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) -- VALUES( ' || $1 || ', ' -- || $2 || ', ' -- || quote_literal(COALESCE($3, '')) || ', ' -- || quote_literal($4) || ', ' -- || quote_literal($5) || ' )'; -+ VALUES( ' || $1::text || ', ' -+ || $2::text || ', ' -+ || quote_literal(COALESCE($3, '')::text) || ', ' -+ || quote_literal($4::text) || ', ' -+ || quote_literal($5::text) || ' )'; - RETURN currval('__tresults___numb_seq'); - END; - $$ LANGUAGE plpgsql; -@@ -202,10 +286,12 @@ +@@ -260,10 +291,12 @@ CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ DECLARE @@ -199,7 +125,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -478,13 +564,16 @@ +@@ -536,13 +569,16 @@ want ALIAS FOR $3; descr ALIAS FOR $4; result BOOLEAN; @@ -207,29 +133,19 @@ output TEXT; BEGIN - EXECUTE 'SELECT ' || -- COALESCE(quote_literal( have ), 'NULL') || '::' || pg_typeof(have) || ' ' + FOR rec IN EXECUTE 'SELECT ' || -+ COALESCE(quote_literal( have ), 'NULL') || '::' || pg_typeof(have)::text || ' ' + 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; + COALESCE(quote_literal( want ), 'NULL') || '::' || pg_typeof(want)::text || ' AS res' + LOOP + result := rec.res; + END LOOP; output := ok( COALESCE(result, FALSE), descr ); - RETURN output || CASE result WHEN TRUE THEN '' ELSE '\n' || diag( + RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -1084,7 +1173,7 @@ - - CREATE OR REPLACE FUNCTION display_type ( OID, INTEGER ) - RETURNS TEXT AS $$ -- SELECT $1::regtype -+ SELECT $1::regtype::text - || COALESCE(substring(pg_catalog.format_type($1, $2), '[(][^)]+[)]$'), '') - $$ LANGUAGE SQL; - -@@ -1306,19 +1395,21 @@ +@@ -1364,19 +1400,21 @@ CREATE OR REPLACE FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -255,76 +171,10 @@ END; $$ LANGUAGE plpgsql; -@@ -2096,7 +2187,7 @@ - p.proname AS name, - 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, -@@ -3367,96 +3458,6 @@ +@@ -3425,39 +3463,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( @@ -361,16 +211,23 @@ CREATE OR REPLACE FUNCTION _is_super( NAME ) RETURNS BOOLEAN AS $$ SELECT usesuper -@@ -3560,7 +3561,7 @@ +@@ -3561,13 +3566,8 @@ $$ 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 +- ); +RETURNS integer[] AS $$ - SELECT grolist FROM pg_catalog.pg_group WHERE groname = $1; ++ SELECT grolist FROM pg_catalog.pg_group WHERE groname = $1; $$ LANGUAGE sql; -@@ -5510,6 +5511,7 @@ + -- is_member_of( group, user[], description ) +@@ -5516,6 +5516,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -378,7 +235,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -5521,8 +5523,10 @@ +@@ -5527,8 +5528,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. @@ -391,27 +248,7 @@ -- Now delete those results. EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; -@@ -5645,7 +5649,7 @@ - CREATE OR REPLACE FUNCTION _runem( text[], boolean ) - RETURNS SETOF TEXT AS $$ - DECLARE -- tap text; -+ rec record; - lbound int := array_lower($1, 1); - BEGIN - IF lbound IS NULL THEN RETURN; END IF; -@@ -5653,8 +5657,8 @@ - -- 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; -+ FOR rec IN EXECUTE 'SELECT * FROM ' || $1[i] || '() AS b(a)' LOOP -+ RETURN NEXT rec.a; - END LOOP; - END LOOP; - RETURN; -@@ -5714,116 +5718,6 @@ +@@ -5720,116 +5723,6 @@ END $$ LANGUAGE plpgsql; @@ -423,14 +260,14 @@ - setup ALIAS FOR $3; - teardown ALIAS FOR $4; - tests ALIAS FOR $5; -- tap text; +- rec record; - verbos boolean := _is_verbose(); -- verbose is a reserved word in 8.5. - num_faild INTEGER := 0; -BEGIN - BEGIN - -- No plan support. - PERFORM * FROM no_plan(); -- FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP; +- FOR rec IN SELECT * FROM _runem(startup, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; - EXCEPTION - -- Catch all exceptions and simply rethrow custom exceptions. This - -- will roll back everything in the above block. @@ -445,15 +282,15 @@ - IF verbos THEN RETURN NEXT diag(tests[i] || '()'); END IF; - - -- Run the setup functions. -- FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; +- FOR rec IN SELECT * FROM _runem(setup, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; - - -- Run the actual test function. -- FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP -- RETURN NEXT tap; +- FOR rec IN EXECUTE 'SELECT * FROM ' || tests[i] || '() AS b(a)' LOOP +- RETURN NEXT rec.a; - END LOOP; - - -- Run the teardown functions. -- FOR tap IN SELECT * FROM _runem(teardown, false) LOOP RETURN NEXT tap; END LOOP; +- FOR rec IN SELECT * FROM _runem(teardown, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; - - -- Remember how many failed and then roll back. - num_faild := num_faild + num_failed(); @@ -468,7 +305,7 @@ - END LOOP; - - -- Run the shutdown functions. -- FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; +- FOR rec IN SELECT * FROM _runem(shutdown, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; - - -- Raise an exception to rollback any changes. - RAISE EXCEPTION '__TAP_ROLLBACK__'; @@ -479,8 +316,8 @@ - END IF; - END; - -- Finish up. -- FOR tap IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) LOOP -- RETURN NEXT tap; +- FOR rec IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) AS b(a) LOOP +- RETURN NEXT rec.a; - END LOOP; - - -- Clean up and return. @@ -528,34 +365,23 @@ CREATE OR REPLACE FUNCTION _temptable ( TEXT, TEXT ) RETURNS TEXT AS $$ BEGIN -@@ -5849,8 +5743,9 @@ - SELECT display_type(a.atttypid, a.atttypmod) - FROM pg_catalog.pg_attribute a - JOIN pg_catalog.pg_class c ON a.attrelid = c.oid -+ JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid - WHERE c.relname = $1 -- AND c.relistemp -+ AND n.nspname LIKE 'pg_temp%' - AND attnum > 0 - AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END - ORDER BY attnum -@@ -5872,13 +5767,13 @@ +@@ -5879,13 +5772,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))); + extras := array_append(extras, textin(record_out(rec, 2249))); 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))); + missing := array_append(missing, textin(record_out(rec, 2249))); END LOOP; -- Drop the temporary tables. -@@ -5992,16 +5887,20 @@ +@@ -5999,16 +5892,20 @@ missing TEXT[] := '{}'; res BOOLEAN := TRUE; msg TEXT := ''; @@ -578,75 +404,71 @@ -- Drop the temporary tables. EXECUTE 'DROP TABLE ' || have; -@@ -6102,7 +6001,7 @@ +@@ -6109,7 +6006,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))); + results := array_append(results, textin(record_out(rec, 2249))); END LOOP; -- Drop the temporary tables. -@@ -6197,11 +6096,11 @@ +@@ -6204,11 +6101,11 @@ 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 +- IF textin(record_out(have_rec)) IS DISTINCT FROM textin(record_out(want_rec)) OR have_found <> want_found THEN + IF textin(record_out(have_rec, 2249)) IS DISTINCT FROM textin(record_out(want_rec, 2249)) OR have_found <> want_found THEN - RETURN ok( false, $3 ) || '\n' || diag( - ' Results differ beginning at row ' || rownum || ':\n' || -- ' have: ' || CASE WHEN have_found THEN have_rec::text ELSE 'NULL' END || '\n' || -- ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE 'NULL' END -+ ' have: ' || CASE WHEN have_found THEN textin(record_out(have_rec, 2249)) ELSE 'NULL' END || '\n' || + RETURN ok( false, $3 ) || E'\n' || diag( + ' Results differ beginning at row ' || rownum || 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 ++ ' have: ' || CASE WHEN have_found THEN textin(record_out(have_rec, 2249)) ELSE 'NULL' END || E'\n' || + ' want: ' || CASE WHEN want_found THEN textin(record_out(want_rec, 2249)) ELSE 'NULL' END ); END IF; rownum = rownum + 1; -@@ -6216,8 +6115,8 @@ +@@ -6223,8 +6120,8 @@ WHEN datatype_mismatch THEN - RETURN ok( false, $3 ) || '\n' || diag( - ' Columns differ between queries:\n' || -- ' have: ' || CASE WHEN have_found THEN have_rec::text ELSE 'NULL' END || '\n' || -- ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE 'NULL' END -+ ' have: ' || CASE WHEN have_found THEN textin(record_out(have_rec, 2249)) ELSE 'NULL' END || '\n' || + RETURN ok( false, $3 ) || E'\n' || diag( + E' Columns differ between queries:\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 ++ ' have: ' || CASE WHEN have_found THEN textin(record_out(have_rec, 2249)) ELSE 'NULL' END || E'\n' || + ' want: ' || CASE WHEN want_found THEN textin(record_out(want_rec, 2249)) ELSE 'NULL' END ); END; $$ LANGUAGE plpgsql; -@@ -6352,7 +6251,7 @@ +@@ -6359,7 +6256,7 @@ 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 +- IF textin(record_out(have_rec)) IS DISTINCT FROM textin(record_out(want_rec)) OR have_found <> want_found THEN + IF textin(record_out(have_rec, 2249)) IS DISTINCT FROM textin(record_out(want_rec, 2249)) OR have_found <> want_found THEN RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6366,8 +6265,8 @@ +@@ -6373,8 +6270,8 @@ WHEN datatype_mismatch THEN - RETURN ok( false, $3 ) || '\n' || diag( - ' Columns differ between queries:\n' || -- ' have: ' || CASE WHEN have_found THEN have_rec::text ELSE 'NULL' END || '\n' || -- ' want: ' || CASE WHEN want_found THEN want_rec::text ELSE 'NULL' END -+ ' have: ' || CASE WHEN have_found THEN textin(record_out(have_rec, 2249)) ELSE 'NULL' END || '\n' || + RETURN ok( false, $3 ) || E'\n' || diag( + E' Columns differ between queries:\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 ++ ' have: ' || CASE WHEN have_found THEN textin(record_out(have_rec, 2249)) ELSE 'NULL' END || E'\n' || + ' want: ' || CASE WHEN want_found THEN textin(record_out(want_rec, 2249)) ELSE 'NULL' END ); END; $$ LANGUAGE plpgsql; -@@ -6492,9 +6391,9 @@ - DECLARE - typeof regtype := pg_typeof($1); +@@ -6522,7 +6419,7 @@ BEGIN -- IF typeof = $2 THEN RETURN ok(true, $3 || ' isa ' || $2 ); END IF; -- RETURN ok(false, $3 || ' isa ' || $2 ) || '\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 ) || '\n' || -+ diag(' ' || $3 || ' isn''t a "' || $2::text || '" it''s a "' || typeof::text || '"'); - END; - $$ LANGUAGE plpgsql; + -- Find extra records. + FOR rec in EXECUTE _query($1) LOOP +- extras := extras || textin(record_out(rec)); ++ extras := extras || textin(record_out(rec, 2249)); + END LOOP; -@@ -6625,35 +6524,6 @@ + -- What extra records do we have? +@@ -6626,35 +6523,6 @@ SELECT throws_imatching($1, $2, 'Should throw exception matching ' || quote_literal($2) ); $$ LANGUAGE sql; @@ -682,84 +504,17 @@ CREATE OR REPLACE FUNCTION _types_are ( NAME, NAME[], TEXT, CHAR[] ) RETURNS TEXT AS $$ SELECT _are( -@@ -6666,7 +6536,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 -@@ -6684,7 +6554,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']) ) - ), -@@ -6717,7 +6587,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']) ) -@@ -6736,7 +6606,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']) ) -@@ -7166,7 +7036,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 ) -@@ -7175,7 +7045,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 -@@ -7187,7 +7057,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 -@@ -7208,7 +7078,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) -@@ -7221,7 +7091,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) +@@ -7018,10 +6886,10 @@ + result BOOLEAN; + BEGIN + FOR rec in EXECUTE _query($1) LOOP +- result := NOT textin(record_out(rec)) IS DISTINCT FROM textin(record_out($2)); ++ result := NOT textin(record_out(rec, 2249)) IS DISTINCT FROM textin(record_out($2, 2249)); + RETURN ok(result, $3 ) || CASE WHEN result THEN '' ELSE E'\n' || diag( +- ' 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 ++ ' have: ' || CASE WHEN rec IS NULL THEN 'NULL' ELSE textin(record_out(rec, 2249)) END || ++ E'\n want: ' || CASE WHEN $2 IS NULL THEN 'NULL' ELSE textin(record_out($2, 2249)) END + ) END; + END LOOP; + END; diff --git a/pgtap.sql.in b/pgtap.sql.in index f08a08247dec..b89c0faff8a0 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3561,7 +3561,12 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _grolist ( NAME ) RETURNS oid[] AS $$ - SELECT grolist FROM pg_catalog.pg_group WHERE groname = $1; + 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( group, user[], description ) From 87ac9eb4064f7e05f0e8ed5466085ca78003acb5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 31 Dec 2009 17:30:10 -0800 Subject: [PATCH 0506/1195] Test for role existence. In `is_member_of()`, instead of for gropu existence, which will be wrong in 8.1 and higher. --- pgtap.sql.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index b89c0faff8a0..5a2080f14716 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3575,9 +3575,9 @@ RETURNS TEXT AS $$ DECLARE missing text[]; BEGIN - IF NOT _has_group($1) THEN + IF NOT _has_role($1) THEN RETURN fail( $3 ) || E'\n' || diag ( - ' Group ' || quote_ident($1) || ' does not exist' + ' Role ' || quote_ident($1) || ' does not exist' ); END IF; From cf1bfdd352ffd968a62ed0be32bf7eded02e300b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 2 Jan 2010 16:29:41 -0800 Subject: [PATCH 0507/1195] Coupla type formatting bug fixes. * Fix bug where `display_type()` would include the schema name in its output for types not found in the current path, despite its assertion that it wouldn't. * Change the diagnostic output for row type mismatches from `set_eq()`, `set_ne()`, `bag_eq()`, and `bag_ne()` to include the schema name for types not found in the search path. --- Changes | 6 ++++++ expected/util.out | 7 ++++++- pgtap.sql.in | 8 +++++--- sql/util.sql | 26 +++++++++++++++++++++++++- 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 0f3ee492722d..f361c1ee18de 100644 --- a/Changes +++ b/Changes @@ -7,6 +7,12 @@ Revision history for pgTAP * Changed to saner source URL in `contrib/pgtap.spec`. * Fixed a bug in `has_member()` where it failed to confirm that users were actually members of a group. +* Fixed a bug where `display_type()` would include the schema name in its + output for types not found in the current path, despite its assertion that + it wouldn't. +* Changed the diagnostic output for row type mismatches from `set_eq()`, + `set_ne()`, `bag_eq()`, and `bag_ne()` to include the schema name for types + not found in the search path. 0.23 2009-12-18T23:03:39 ------------------------- diff --git a/expected/util.out b/expected/util.out index 3bd66f37a0e6..e539dc6852ed 100644 --- a/expected/util.out +++ b/expected/util.out @@ -1,5 +1,5 @@ \unset ECHO -1..24 +1..29 ok 1 - pg_type(int) should work ok 2 - pg_type(numeric) should work ok 3 - pg_type(text) should work @@ -24,3 +24,8 @@ ok 21 - display_type(timestamptz) ok 22 - display_type(foo, int4) ok 23 - display_type(HEY, numeric) ok 24 - display_type(t z, int4) +ok 25 - display_type(__foo.goofy) +ok 26 - display_type(__foo."this.that") +ok 27 - display_type(__foo."this"".that") +ok 28 - display_type(__foo."hey"".yoman", 13) +ok 29 - display_type("try.this""", 42) diff --git a/pgtap.sql.in b/pgtap.sql.in index 5a2080f14716..27b52c0588c0 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1084,8 +1084,10 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION display_type ( OID, INTEGER ) RETURNS TEXT AS $$ - SELECT $1::regtype - || COALESCE(substring(pg_catalog.format_type($1, $2), '[(][^)]+[)]$'), '') + SELECT COALESCE(substring( + pg_catalog.format_type($1, $2), + '(("(?!")([^"]|"")+"|[^.]+)([(][^)]+[)])?)$' + ), '') $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION display_type ( NAME, OID, INTEGER ) @@ -5851,7 +5853,7 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION _temptypes( TEXT ) RETURNS TEXT AS $$ SELECT array_to_string(ARRAY( - SELECT display_type(a.atttypid, a.atttypmod) + 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.relname = $1 diff --git a/sql/util.sql b/sql/util.sql index 3d05c518a656..28c0bd4a86cb 100644 --- a/sql/util.sql +++ b/sql/util.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(24); +SELECT plan(29); --SELECT * FROM no_plan(); SELECT is( pg_typeof(42), 'integer', 'pg_type(int) should work' ); @@ -100,6 +100,30 @@ SELECT is( display_type('foo', 'int4'::regtype, NULL), 'foo.integer', 'display_t SELECT is( display_type('HEY', 'numeric'::regtype, NULL), '"HEY".numeric', 'display_type(HEY, numeric)'); SELECT is( display_type('t z', 'int4'::regtype, NULL), '"t z".integer', 'display_type(t z, int4)'); +-- Look at a type not in the current schema. +CREATE SCHEMA __foo; +CREATE DOMAIN __foo.goofy AS text CHECK ( TRUE ); +SELECT is( display_type( oid, NULL ), 'goofy', 'display_type(__foo.goofy)' ) + FROM pg_type WHERE typname = 'goofy'; + +-- Look at types with funny names. +CREATE DOMAIN __foo."this.that" AS text CHECK (TRUE); +SELECT is( display_type( oid, NULL ), '"this.that"', 'display_type(__foo."this.that")' ) + FROM pg_type WHERE typname = 'this.that'; + +CREATE DOMAIN __foo."this"".that" AS text CHECK (TRUE); +SELECT is( display_type( oid, NULL ), '"this"".that"', 'display_type(__foo."this"".that")' ) + FROM pg_type WHERE typname = 'this".that'; + +-- Look at types with precision. +CREATE DOMAIN __foo."hey"".yoman" AS numeric CHECK (TRUE); +SELECT is( display_type( oid, 13 ), '"hey"".yoman"(13)', 'display_type(__foo."hey"".yoman", 13)' ) + FROM pg_type WHERE typname = 'hey".yoman'; + +CREATE DOMAIN "try.this""" AS numeric CHECK (TRUE); +SELECT is( display_type( oid, 42 ), '"try.this"""(42)', 'display_type("try.this""", 42)' ) + FROM pg_type WHERE typname = 'try.this"'; + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 242b1024fca846340728464fea7d492cdc8d0a85 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 2 Jan 2010 16:43:11 -0800 Subject: [PATCH 0508/1195] Fix missing column in types diagnostic output. Fix bug in the diagnostic output of `set_eq()`, `set_ne()`, `bag_eq()`, and `bag_ne()` where a column's type would not be displayed if the column was not in the search path. Thanks to Rod Taylor for the spot! --- Changes | 3 + expected/resultset.out | 891 +++++++++++++++++++++-------------------- pgtap.sql.in | 2 +- sql/resultset.sql | 27 +- 4 files changed, 477 insertions(+), 446 deletions(-) diff --git a/Changes b/Changes index f361c1ee18de..6c63ddbce464 100644 --- a/Changes +++ b/Changes @@ -13,6 +13,9 @@ Revision history for pgTAP * Changed the diagnostic output for row type mismatches from `set_eq()`, `set_ne()`, `bag_eq()`, and `bag_ne()` to include the schema name for types not found in the search path. +* Fixed bug in the diagnostic output of `set_eq()`, `set_ne()`, `bag_eq()`, + and `bag_ne()` where a column's type would not be displayed if the column + was not in the search path. Thanks to Rod Taylor for the spot! 0.23 2009-12-18T23:03:39 ------------------------- diff --git a/expected/resultset.out b/expected/resultset.out index b43bf96a13bc..9f8ccc09bc7d 100644 --- a/expected/resultset.out +++ b/expected/resultset.out @@ -1,5 +1,5 @@ \unset ECHO -1..512 +1..515 ok 1 - Should create temp table with simple query ok 2 - Table __foonames__ should exist ok 3 - Should create a temp table for a prepared statement @@ -46,469 +46,472 @@ ok 43 - set_eq(values, values) fail mismatch should have the proper diagnostics ok 44 - set_eq(values, values) fail column count should fail ok 45 - set_eq(values, values) fail column count should have the proper description ok 46 - set_eq(values, values) fail column count should have the proper diagnostics -ok 47 - bag_eq(prepared, prepared, desc) should pass -ok 48 - bag_eq(prepared, prepared, desc) should have the proper description -ok 49 - bag_eq(prepared, prepared, desc) should have the proper diagnostics -ok 50 - bag_eq(prepared, prepared) should pass -ok 51 - bag_eq(prepared, prepared) should have the proper description -ok 52 - bag_eq(prepared, prepared) should have the proper diagnostics -ok 53 - bag_eq(execute, execute) should pass -ok 54 - bag_eq(execute, execute) should have the proper description -ok 55 - bag_eq(execute, execute) should have the proper diagnostics -ok 56 - bag_eq(select, select) should pass -ok 57 - bag_eq(select, select) should have the proper description -ok 58 - bag_eq(select, select) should have the proper diagnostics -ok 59 - bag_eq(dupe values, dupe values) should pass -ok 60 - bag_eq(dupe values, dupe values) should have the proper description -ok 61 - bag_eq(dupe values, dupe values) should have the proper diagnostics -ok 62 - bag_eq(prepared, select) fail extra should fail -ok 63 - bag_eq(prepared, select) fail extra should have the proper description -ok 64 - bag_eq(prepared, select) fail extra should have the proper diagnostics -ok 65 - bag_eq(prepared, select) fail extras should fail -ok 66 - bag_eq(prepared, select) fail extras should have the proper description -ok 67 - bag_eq(prepared, select) fail extras should have the proper diagnostics -ok 68 - bag_eq(select, prepared) fail missing should fail -ok 69 - bag_eq(select, prepared) fail missing should have the proper description -ok 70 - bag_eq(select, prepared) fail missing should have the proper diagnostics -ok 71 - bag_eq(select, prepared) fail missings should fail -ok 72 - bag_eq(select, prepared) fail missings should have the proper description -ok 73 - bag_eq(select, prepared) fail missings should have the proper diagnostics -ok 74 - bag_eq(select, select) fail extra & missing should fail -ok 75 - bag_eq(select, select) fail extra & missing should have the proper description -ok 76 - bag_eq(select, select) fail extra & missing should have the proper diagnostics -ok 77 - bag_eq(select, select) fail extras & missings should fail -ok 78 - bag_eq(select, select) fail extras & missings should have the proper description -ok 79 - bag_eq(select, select) fail extras & missings should have the proper diagnostics -ok 80 - bag_eq(values, values) fail mismatch should fail -ok 81 - bag_eq(values, values) fail mismatch should have the proper description -ok 82 - bag_eq(values, values) fail mismatch should have the proper diagnostics -ok 83 - bag_eq(values, values) fail column count should fail -ok 84 - bag_eq(values, values) fail column count should have the proper description -ok 85 - bag_eq(values, values) fail column count should have the proper diagnostics -ok 86 - bag_eq(values, values) fail missing dupe should fail -ok 87 - bag_eq(values, values) fail missing dupe should have the proper description -ok 88 - bag_eq(values, values) fail missing dupe should have the proper diagnostics -ok 89 - set_ne(prepared, select, desc) should pass -ok 90 - set_ne(prepared, select, desc) should have the proper description -ok 91 - set_ne(prepared, select, desc) should have the proper diagnostics -ok 92 - set_ne(prepared, select) should pass -ok 93 - set_ne(prepared, select) should have the proper description -ok 94 - set_ne(prepared, select) should have the proper diagnostics -ok 95 - set_ne(prepared, prepared) fail should fail -ok 96 - set_ne(prepared, prepared) fail should have the proper description -ok 97 - set_ne(prepared, prepared) fail should have the proper diagnostics -ok 98 - set_ne fail with column mismatch should fail -ok 99 - set_ne fail with column mismatch should have the proper description -ok 100 - set_ne fail with column mismatch should have the proper diagnostics -ok 101 - set_ne fail with different col counts should fail -ok 102 - set_ne fail with different col counts should have the proper description -ok 103 - set_ne fail with different col counts should have the proper diagnostics -ok 104 - set_ne fail with dupe should fail -ok 105 - set_ne fail with dupe should have the proper description -ok 106 - set_ne fail with dupe should have the proper diagnostics -ok 107 - bag_ne(prepared, select, desc) should pass -ok 108 - bag_ne(prepared, select, desc) should have the proper description -ok 109 - bag_ne(prepared, select, desc) should have the proper diagnostics -ok 110 - bag_ne(prepared, select) should pass -ok 111 - bag_ne(prepared, select) should have the proper description -ok 112 - bag_ne(prepared, select) should have the proper diagnostics -ok 113 - bag_ne(prepared, prepared) fail should fail -ok 114 - bag_ne(prepared, prepared) fail should have the proper description -ok 115 - bag_ne(prepared, prepared) fail should have the proper diagnostics -ok 116 - bag_ne fail with column mismatch should fail -ok 117 - bag_ne fail with column mismatch should have the proper description -ok 118 - bag_ne fail with column mismatch should have the proper diagnostics -ok 119 - set_ne pass with dupe should pass -ok 120 - set_ne pass with dupe should have the proper description -ok 121 - set_ne pass with dupe should have the proper diagnostics -ok 122 - bag_ne fail with column mismatch should fail -ok 123 - bag_ne fail with column mismatch should have the proper description -ok 124 - bag_ne fail with column mismatch should have the proper diagnostics -ok 125 - bag_ne fail with different col counts should fail -ok 126 - bag_ne fail with different col counts should have the proper description -ok 127 - bag_ne fail with different col counts should have the proper diagnostics -ok 128 - results_eq(prepared, prepared, desc) should pass -ok 129 - results_eq(prepared, prepared, desc) should have the proper description -ok 130 - results_eq(prepared, prepared, desc) should have the proper diagnostics -ok 131 - results_eq(prepared, prepared) should pass -ok 132 - results_eq(prepared, prepared) should have the proper description -ok 133 - results_eq(prepared, prepared) should have the proper diagnostics -ok 134 - results_eq(execute, execute) should pass -ok 135 - results_eq(execute, execute) should have the proper description -ok 136 - results_eq(execute, execute) should have the proper diagnostics -ok 137 - results_eq(select, select) should pass -ok 138 - results_eq(select, select) should have the proper description -ok 139 - results_eq(select, select) should have the proper diagnostics -ok 140 - results_eq(dupe values, dupe values) should pass -ok 141 - results_eq(dupe values, dupe values) should have the proper description -ok 142 - results_eq(dupe values, dupe values) should have the proper diagnostics -ok 143 - results_eq(values with null, values with null) should pass -ok 144 - results_eq(values with null, values with null) should have the proper description -ok 145 - results_eq(values with null, values with null) should have the proper diagnostics -ok 146 - results_eq(nulls, nulls) should pass -ok 147 - results_eq(nulls, nulls) should have the proper description -ok 148 - results_eq(nulls, nulls) should have the proper diagnostics -ok 149 - results_eq(nulls, nulls) fail should fail -ok 150 - results_eq(nulls, nulls) fail should have the proper description -ok 151 - results_eq(nulls, nulls) fail should have the proper diagnostics -ok 152 - results_eq(prepared, select) fail should fail -ok 153 - results_eq(prepared, select) fail should have the proper description -ok 154 - results_eq(prepared, select) fail should have the proper diagnostics -ok 155 - results_eq(select, prepared) fail missing last row should fail -ok 156 - results_eq(select, prepared) fail missing last row should have the proper description -ok 157 - results_eq(select, prepared) fail missing last row should have the proper diagnostics -ok 158 - results_eq(prepared, select) fail missing first row should fail -ok 159 - results_eq(prepared, select) fail missing first row should have the proper description -ok 160 - results_eq(prepared, select) fail missing first row should have the proper diagnostics -ok 161 - results_eq(values dupe, values) should fail -ok 162 - results_eq(values dupe, values) should have the proper description -ok 163 - results_eq(values dupe, values) should have the proper diagnostics -ok 164 - results_eq(values null, values) should fail -ok 165 - results_eq(values null, values) should have the proper description -ok 166 - results_eq(values null, values) should have the proper diagnostics -ok 167 - results_eq(values, values) mismatch should fail -ok 168 - results_eq(values, values) mismatch should have the proper description -ok 169 - results_eq(values, values) mismatch should have the proper diagnostics -ok 170 - results_eq(values, values) subtle mismatch should fail -ok 171 - results_eq(values, values) subtle mismatch should have the proper description -ok 172 - results_eq(values, values) subtle mismatch should have the proper diagnostics -ok 173 - results_eq(values, values) fail column count should fail -ok 174 - results_eq(values, values) fail column count should have the proper description -ok 175 - results_eq(values, values) fail column count should have the proper diagnostics -ok 176 - results_eq(cursor, cursor) should pass -ok 177 - results_eq(cursor, cursor) should have the proper description -ok 178 - results_eq(cursor, cursor) should have the proper diagnostics -ok 179 - results_eq(cursor, prepared) should pass -ok 180 - results_eq(cursor, prepared) should have the proper description -ok 181 - results_eq(cursor, prepared) should have the proper diagnostics -ok 182 - results_eq(prepared, cursor) should pass -ok 183 - results_eq(prepared, cursor) should have the proper description -ok 184 - results_eq(prepared, cursor) should have the proper diagnostics -ok 185 - results_eq(cursor, sql) should pass -ok 186 - results_eq(cursor, sql) should have the proper description -ok 187 - results_eq(cursor, sql) should have the proper diagnostics -ok 188 - results_eq(sql, cursor) should pass -ok 189 - results_eq(sql, cursor) should have the proper description -ok 190 - results_eq(sql, cursor) should have the proper diagnostics -ok 191 - set_has( prepared, prepared, description ) should pass -ok 192 - set_has( prepared, prepared, description ) should have the proper description -ok 193 - set_has( prepared, prepared, description ) should have the proper diagnostics -ok 194 - set_has( prepared, subprepared ) should pass -ok 195 - set_has( prepared, subprepared ) should have the proper description -ok 196 - set_has( prepared, subprepared ) should have the proper diagnostics -ok 197 - set_has( execute, execute ) should pass -ok 198 - set_has( execute, execute ) should have the proper description -ok 199 - set_has( execute, execute ) should have the proper diagnostics -ok 200 - set_has( select, select ) should pass -ok 201 - set_has( select, select ) should have the proper description -ok 202 - set_has( select, select ) should have the proper diagnostics -ok 203 - set_has( prepared, empty ) should pass -ok 204 - set_has( prepared, empty ) should have the proper description -ok 205 - set_has( prepared, empty ) should have the proper diagnostics -ok 206 - set_has( prepared, dupes ) should pass -ok 207 - set_has( prepared, dupes ) should have the proper description -ok 208 - set_has( prepared, dupes ) should have the proper diagnostics -ok 209 - set_has( dupes, values ) should pass -ok 210 - set_has( dupes, values ) should have the proper description -ok 211 - set_has( dupes, values ) should have the proper diagnostics -ok 212 - set_has( missing1, expect ) should fail -ok 213 - set_has( missing1, expect ) should have the proper description -ok 214 - set_has( missing1, expect ) should have the proper diagnostics -ok 215 - set_has(missing2, expect ) should fail -ok 216 - set_has(missing2, expect ) should have the proper description -ok 217 - set_has(missing2, expect ) should have the proper diagnostics -ok 218 - set_has((int,text), (text,int)) should fail -ok 219 - set_has((int,text), (text,int)) should have the proper description -ok 220 - set_has((int,text), (text,int)) should have the proper diagnostics -ok 221 - set_has((int), (text,int)) should fail -ok 222 - set_has((int), (text,int)) should have the proper description -ok 223 - set_has((int), (text,int)) should have the proper diagnostics -ok 224 - bag_has( prepared, prepared, description ) should pass -ok 225 - bag_has( prepared, prepared, description ) should have the proper description -ok 226 - bag_has( prepared, prepared, description ) should have the proper diagnostics -ok 227 - bag_has( prepared, subprepared ) should pass -ok 228 - bag_has( prepared, subprepared ) should have the proper description -ok 229 - bag_has( prepared, subprepared ) should have the proper diagnostics -ok 230 - bag_has( execute, execute ) should pass -ok 231 - bag_has( execute, execute ) should have the proper description -ok 232 - bag_has( execute, execute ) should have the proper diagnostics -ok 233 - bag_has( select, select ) should pass -ok 234 - bag_has( select, select ) should have the proper description -ok 235 - bag_has( select, select ) should have the proper diagnostics -ok 236 - bag_has( prepared, empty ) should pass -ok 237 - bag_has( prepared, empty ) should have the proper description -ok 238 - bag_has( prepared, empty ) should have the proper diagnostics -ok 239 - bag_has( prepared, dupes ) should fail -ok 240 - bag_has( prepared, dupes ) should have the proper description -ok 241 - bag_has( prepared, dupes ) should have the proper diagnostics -ok 242 - bag_has( dupes, values ) should pass -ok 243 - bag_has( dupes, values ) should have the proper description -ok 244 - bag_has( dupes, values ) should have the proper diagnostics -ok 245 - bag_has( missing1, expect ) should fail -ok 246 - bag_has( missing1, expect ) should have the proper description -ok 247 - bag_has( missing1, expect ) should have the proper diagnostics -ok 248 - bag_has(missing2, expect ) should fail -ok 249 - bag_has(missing2, expect ) should have the proper description -ok 250 - bag_has(missing2, expect ) should have the proper diagnostics -ok 251 - bag_has((int,text), (text,int)) should fail -ok 252 - bag_has((int,text), (text,int)) should have the proper description -ok 253 - bag_has((int,text), (text,int)) should have the proper diagnostics -ok 254 - bag_has((int), (text,int)) should fail -ok 255 - bag_has((int), (text,int)) should have the proper description -ok 256 - bag_has((int), (text,int)) should have the proper diagnostics -ok 257 - set_hasnt( prepared, prepared, description ) should pass -ok 258 - set_hasnt( prepared, prepared, description ) should have the proper description -ok 259 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 47 - set_eq(sql, sql) fail type schema visibility should fail +ok 48 - set_eq(sql, sql) fail type schema visibility should have the proper description +ok 49 - set_eq(sql, sql) fail type schema visibility should have the proper diagnostics +ok 50 - bag_eq(prepared, prepared, desc) should pass +ok 51 - bag_eq(prepared, prepared, desc) should have the proper description +ok 52 - bag_eq(prepared, prepared, desc) should have the proper diagnostics +ok 53 - bag_eq(prepared, prepared) should pass +ok 54 - bag_eq(prepared, prepared) should have the proper description +ok 55 - bag_eq(prepared, prepared) should have the proper diagnostics +ok 56 - bag_eq(execute, execute) should pass +ok 57 - bag_eq(execute, execute) should have the proper description +ok 58 - bag_eq(execute, execute) should have the proper diagnostics +ok 59 - bag_eq(select, select) should pass +ok 60 - bag_eq(select, select) should have the proper description +ok 61 - bag_eq(select, select) should have the proper diagnostics +ok 62 - bag_eq(dupe values, dupe values) should pass +ok 63 - bag_eq(dupe values, dupe values) should have the proper description +ok 64 - bag_eq(dupe values, dupe values) should have the proper diagnostics +ok 65 - bag_eq(prepared, select) fail extra should fail +ok 66 - bag_eq(prepared, select) fail extra should have the proper description +ok 67 - bag_eq(prepared, select) fail extra should have the proper diagnostics +ok 68 - bag_eq(prepared, select) fail extras should fail +ok 69 - bag_eq(prepared, select) fail extras should have the proper description +ok 70 - bag_eq(prepared, select) fail extras should have the proper diagnostics +ok 71 - bag_eq(select, prepared) fail missing should fail +ok 72 - bag_eq(select, prepared) fail missing should have the proper description +ok 73 - bag_eq(select, prepared) fail missing should have the proper diagnostics +ok 74 - bag_eq(select, prepared) fail missings should fail +ok 75 - bag_eq(select, prepared) fail missings should have the proper description +ok 76 - bag_eq(select, prepared) fail missings should have the proper diagnostics +ok 77 - bag_eq(select, select) fail extra & missing should fail +ok 78 - bag_eq(select, select) fail extra & missing should have the proper description +ok 79 - bag_eq(select, select) fail extra & missing should have the proper diagnostics +ok 80 - bag_eq(select, select) fail extras & missings should fail +ok 81 - bag_eq(select, select) fail extras & missings should have the proper description +ok 82 - bag_eq(select, select) fail extras & missings should have the proper diagnostics +ok 83 - bag_eq(values, values) fail mismatch should fail +ok 84 - bag_eq(values, values) fail mismatch should have the proper description +ok 85 - bag_eq(values, values) fail mismatch should have the proper diagnostics +ok 86 - bag_eq(values, values) fail column count should fail +ok 87 - bag_eq(values, values) fail column count should have the proper description +ok 88 - bag_eq(values, values) fail column count should have the proper diagnostics +ok 89 - bag_eq(values, values) fail missing dupe should fail +ok 90 - bag_eq(values, values) fail missing dupe should have the proper description +ok 91 - bag_eq(values, values) fail missing dupe should have the proper diagnostics +ok 92 - set_ne(prepared, select, desc) should pass +ok 93 - set_ne(prepared, select, desc) should have the proper description +ok 94 - set_ne(prepared, select, desc) should have the proper diagnostics +ok 95 - set_ne(prepared, select) should pass +ok 96 - set_ne(prepared, select) should have the proper description +ok 97 - set_ne(prepared, select) should have the proper diagnostics +ok 98 - set_ne(prepared, prepared) fail should fail +ok 99 - set_ne(prepared, prepared) fail should have the proper description +ok 100 - set_ne(prepared, prepared) fail should have the proper diagnostics +ok 101 - set_ne fail with column mismatch should fail +ok 102 - set_ne fail with column mismatch should have the proper description +ok 103 - set_ne fail with column mismatch should have the proper diagnostics +ok 104 - set_ne fail with different col counts should fail +ok 105 - set_ne fail with different col counts should have the proper description +ok 106 - set_ne fail with different col counts should have the proper diagnostics +ok 107 - set_ne fail with dupe should fail +ok 108 - set_ne fail with dupe should have the proper description +ok 109 - set_ne fail with dupe should have the proper diagnostics +ok 110 - bag_ne(prepared, select, desc) should pass +ok 111 - bag_ne(prepared, select, desc) should have the proper description +ok 112 - bag_ne(prepared, select, desc) should have the proper diagnostics +ok 113 - bag_ne(prepared, select) should pass +ok 114 - bag_ne(prepared, select) should have the proper description +ok 115 - bag_ne(prepared, select) should have the proper diagnostics +ok 116 - bag_ne(prepared, prepared) fail should fail +ok 117 - bag_ne(prepared, prepared) fail should have the proper description +ok 118 - bag_ne(prepared, prepared) fail should have the proper diagnostics +ok 119 - bag_ne fail with column mismatch should fail +ok 120 - bag_ne fail with column mismatch should have the proper description +ok 121 - bag_ne fail with column mismatch should have the proper diagnostics +ok 122 - set_ne pass with dupe should pass +ok 123 - set_ne pass with dupe should have the proper description +ok 124 - set_ne pass with dupe should have the proper diagnostics +ok 125 - bag_ne fail with column mismatch should fail +ok 126 - bag_ne fail with column mismatch should have the proper description +ok 127 - bag_ne fail with column mismatch should have the proper diagnostics +ok 128 - bag_ne fail with different col counts should fail +ok 129 - bag_ne fail with different col counts should have the proper description +ok 130 - bag_ne fail with different col counts should have the proper diagnostics +ok 131 - results_eq(prepared, prepared, desc) should pass +ok 132 - results_eq(prepared, prepared, desc) should have the proper description +ok 133 - results_eq(prepared, prepared, desc) should have the proper diagnostics +ok 134 - results_eq(prepared, prepared) should pass +ok 135 - results_eq(prepared, prepared) should have the proper description +ok 136 - results_eq(prepared, prepared) should have the proper diagnostics +ok 137 - results_eq(execute, execute) should pass +ok 138 - results_eq(execute, execute) should have the proper description +ok 139 - results_eq(execute, execute) should have the proper diagnostics +ok 140 - results_eq(select, select) should pass +ok 141 - results_eq(select, select) should have the proper description +ok 142 - results_eq(select, select) should have the proper diagnostics +ok 143 - results_eq(dupe values, dupe values) should pass +ok 144 - results_eq(dupe values, dupe values) should have the proper description +ok 145 - results_eq(dupe values, dupe values) should have the proper diagnostics +ok 146 - results_eq(values with null, values with null) should pass +ok 147 - results_eq(values with null, values with null) should have the proper description +ok 148 - results_eq(values with null, values with null) should have the proper diagnostics +ok 149 - results_eq(nulls, nulls) should pass +ok 150 - results_eq(nulls, nulls) should have the proper description +ok 151 - results_eq(nulls, nulls) should have the proper diagnostics +ok 152 - results_eq(nulls, nulls) fail should fail +ok 153 - results_eq(nulls, nulls) fail should have the proper description +ok 154 - results_eq(nulls, nulls) fail should have the proper diagnostics +ok 155 - results_eq(prepared, select) fail should fail +ok 156 - results_eq(prepared, select) fail should have the proper description +ok 157 - results_eq(prepared, select) fail should have the proper diagnostics +ok 158 - results_eq(select, prepared) fail missing last row should fail +ok 159 - results_eq(select, prepared) fail missing last row should have the proper description +ok 160 - results_eq(select, prepared) fail missing last row should have the proper diagnostics +ok 161 - results_eq(prepared, select) fail missing first row should fail +ok 162 - results_eq(prepared, select) fail missing first row should have the proper description +ok 163 - results_eq(prepared, select) fail missing first row should have the proper diagnostics +ok 164 - results_eq(values dupe, values) should fail +ok 165 - results_eq(values dupe, values) should have the proper description +ok 166 - results_eq(values dupe, values) should have the proper diagnostics +ok 167 - results_eq(values null, values) should fail +ok 168 - results_eq(values null, values) should have the proper description +ok 169 - results_eq(values null, values) should have the proper diagnostics +ok 170 - results_eq(values, values) mismatch should fail +ok 171 - results_eq(values, values) mismatch should have the proper description +ok 172 - results_eq(values, values) mismatch should have the proper diagnostics +ok 173 - results_eq(values, values) subtle mismatch should fail +ok 174 - results_eq(values, values) subtle mismatch should have the proper description +ok 175 - results_eq(values, values) subtle mismatch should have the proper diagnostics +ok 176 - results_eq(values, values) fail column count should fail +ok 177 - results_eq(values, values) fail column count should have the proper description +ok 178 - results_eq(values, values) fail column count should have the proper diagnostics +ok 179 - results_eq(cursor, cursor) should pass +ok 180 - results_eq(cursor, cursor) should have the proper description +ok 181 - results_eq(cursor, cursor) should have the proper diagnostics +ok 182 - results_eq(cursor, prepared) should pass +ok 183 - results_eq(cursor, prepared) should have the proper description +ok 184 - results_eq(cursor, prepared) should have the proper diagnostics +ok 185 - results_eq(prepared, cursor) should pass +ok 186 - results_eq(prepared, cursor) should have the proper description +ok 187 - results_eq(prepared, cursor) should have the proper diagnostics +ok 188 - results_eq(cursor, sql) should pass +ok 189 - results_eq(cursor, sql) should have the proper description +ok 190 - results_eq(cursor, sql) should have the proper diagnostics +ok 191 - results_eq(sql, cursor) should pass +ok 192 - results_eq(sql, cursor) should have the proper description +ok 193 - results_eq(sql, cursor) should have the proper diagnostics +ok 194 - set_has( prepared, prepared, description ) should pass +ok 195 - set_has( prepared, prepared, description ) should have the proper description +ok 196 - set_has( prepared, prepared, description ) should have the proper diagnostics +ok 197 - set_has( prepared, subprepared ) should pass +ok 198 - set_has( prepared, subprepared ) should have the proper description +ok 199 - set_has( prepared, subprepared ) should have the proper diagnostics +ok 200 - set_has( execute, execute ) should pass +ok 201 - set_has( execute, execute ) should have the proper description +ok 202 - set_has( execute, execute ) should have the proper diagnostics +ok 203 - set_has( select, select ) should pass +ok 204 - set_has( select, select ) should have the proper description +ok 205 - set_has( select, select ) should have the proper diagnostics +ok 206 - set_has( prepared, empty ) should pass +ok 207 - set_has( prepared, empty ) should have the proper description +ok 208 - set_has( prepared, empty ) should have the proper diagnostics +ok 209 - set_has( prepared, dupes ) should pass +ok 210 - set_has( prepared, dupes ) should have the proper description +ok 211 - set_has( prepared, dupes ) should have the proper diagnostics +ok 212 - set_has( dupes, values ) should pass +ok 213 - set_has( dupes, values ) should have the proper description +ok 214 - set_has( dupes, values ) should have the proper diagnostics +ok 215 - set_has( missing1, expect ) should fail +ok 216 - set_has( missing1, expect ) should have the proper description +ok 217 - set_has( missing1, expect ) should have the proper diagnostics +ok 218 - set_has(missing2, expect ) should fail +ok 219 - set_has(missing2, expect ) should have the proper description +ok 220 - set_has(missing2, expect ) should have the proper diagnostics +ok 221 - set_has((int,text), (text,int)) should fail +ok 222 - set_has((int,text), (text,int)) should have the proper description +ok 223 - set_has((int,text), (text,int)) should have the proper diagnostics +ok 224 - set_has((int), (text,int)) should fail +ok 225 - set_has((int), (text,int)) should have the proper description +ok 226 - set_has((int), (text,int)) should have the proper diagnostics +ok 227 - bag_has( prepared, prepared, description ) should pass +ok 228 - bag_has( prepared, prepared, description ) should have the proper description +ok 229 - bag_has( prepared, prepared, description ) should have the proper diagnostics +ok 230 - bag_has( prepared, subprepared ) should pass +ok 231 - bag_has( prepared, subprepared ) should have the proper description +ok 232 - bag_has( prepared, subprepared ) should have the proper diagnostics +ok 233 - bag_has( execute, execute ) should pass +ok 234 - bag_has( execute, execute ) should have the proper description +ok 235 - bag_has( execute, execute ) should have the proper diagnostics +ok 236 - bag_has( select, select ) should pass +ok 237 - bag_has( select, select ) should have the proper description +ok 238 - bag_has( select, select ) should have the proper diagnostics +ok 239 - bag_has( prepared, empty ) should pass +ok 240 - bag_has( prepared, empty ) should have the proper description +ok 241 - bag_has( prepared, empty ) should have the proper diagnostics +ok 242 - bag_has( prepared, dupes ) should fail +ok 243 - bag_has( prepared, dupes ) should have the proper description +ok 244 - bag_has( prepared, dupes ) should have the proper diagnostics +ok 245 - bag_has( dupes, values ) should pass +ok 246 - bag_has( dupes, values ) should have the proper description +ok 247 - bag_has( dupes, values ) should have the proper diagnostics +ok 248 - bag_has( missing1, expect ) should fail +ok 249 - bag_has( missing1, expect ) should have the proper description +ok 250 - bag_has( missing1, expect ) should have the proper diagnostics +ok 251 - bag_has(missing2, expect ) should fail +ok 252 - bag_has(missing2, expect ) should have the proper description +ok 253 - bag_has(missing2, expect ) should have the proper diagnostics +ok 254 - bag_has((int,text), (text,int)) should fail +ok 255 - bag_has((int,text), (text,int)) should have the proper description +ok 256 - bag_has((int,text), (text,int)) should have the proper diagnostics +ok 257 - bag_has((int), (text,int)) should fail +ok 258 - bag_has((int), (text,int)) should have the proper description +ok 259 - bag_has((int), (text,int)) should have the proper diagnostics ok 260 - set_hasnt( prepared, prepared, description ) should pass ok 261 - set_hasnt( prepared, prepared, description ) should have the proper description ok 262 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics -ok 263 - set_hasnt( execute, execute ) should pass -ok 264 - set_hasnt( execute, execute ) should have the proper description -ok 265 - set_hasnt( execute, execute ) should have the proper diagnostics -ok 266 - set_hasnt( select, select ) should pass -ok 267 - set_hasnt( select, select ) should have the proper description -ok 268 - set_hasnt( select, select ) should have the proper diagnostics -ok 269 - set_hasnt( prepared, empty ) should pass -ok 270 - set_hasnt( prepared, empty ) should have the proper description -ok 271 - set_hasnt( prepared, empty ) should have the proper diagnostics -ok 272 - set_hasnt( prepared, dupes ) should pass -ok 273 - set_hasnt( prepared, dupes ) should have the proper description -ok 274 - set_hasnt( prepared, dupes ) should have the proper diagnostics -ok 275 - set_hasnt( prepared, value ) should fail -ok 276 - set_hasnt( prepared, value ) should have the proper description -ok 277 - set_hasnt( prepared, value ) should have the proper diagnostics -ok 278 - set_hasnt( prepared, values ) should fail -ok 279 - set_hasnt( prepared, values ) should have the proper description -ok 280 - set_hasnt( prepared, values ) should have the proper diagnostics -ok 281 - set_hasnt((int,text), (text,int)) should fail -ok 282 - set_hasnt((int,text), (text,int)) should have the proper description -ok 283 - set_hasnt((int,text), (text,int)) should have the proper diagnostics -ok 284 - set_hasnt((int), (text,int)) should fail -ok 285 - set_hasnt((int), (text,int)) should have the proper description -ok 286 - set_hasnt((int), (text,int)) should have the proper diagnostics -ok 287 - bag_hasnt( prepared, prepared, description ) should pass -ok 288 - bag_hasnt( prepared, prepared, description ) should have the proper description -ok 289 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 263 - set_hasnt( prepared, prepared, description ) should pass +ok 264 - set_hasnt( prepared, prepared, description ) should have the proper description +ok 265 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 266 - set_hasnt( execute, execute ) should pass +ok 267 - set_hasnt( execute, execute ) should have the proper description +ok 268 - set_hasnt( execute, execute ) should have the proper diagnostics +ok 269 - set_hasnt( select, select ) should pass +ok 270 - set_hasnt( select, select ) should have the proper description +ok 271 - set_hasnt( select, select ) should have the proper diagnostics +ok 272 - set_hasnt( prepared, empty ) should pass +ok 273 - set_hasnt( prepared, empty ) should have the proper description +ok 274 - set_hasnt( prepared, empty ) should have the proper diagnostics +ok 275 - set_hasnt( prepared, dupes ) should pass +ok 276 - set_hasnt( prepared, dupes ) should have the proper description +ok 277 - set_hasnt( prepared, dupes ) should have the proper diagnostics +ok 278 - set_hasnt( prepared, value ) should fail +ok 279 - set_hasnt( prepared, value ) should have the proper description +ok 280 - set_hasnt( prepared, value ) should have the proper diagnostics +ok 281 - set_hasnt( prepared, values ) should fail +ok 282 - set_hasnt( prepared, values ) should have the proper description +ok 283 - set_hasnt( prepared, values ) should have the proper diagnostics +ok 284 - set_hasnt((int,text), (text,int)) should fail +ok 285 - set_hasnt((int,text), (text,int)) should have the proper description +ok 286 - set_hasnt((int,text), (text,int)) should have the proper diagnostics +ok 287 - set_hasnt((int), (text,int)) should fail +ok 288 - set_hasnt((int), (text,int)) should have the proper description +ok 289 - set_hasnt((int), (text,int)) should have the proper diagnostics ok 290 - bag_hasnt( prepared, prepared, description ) should pass ok 291 - bag_hasnt( prepared, prepared, description ) should have the proper description ok 292 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics -ok 293 - bag_hasnt( execute, execute ) should pass -ok 294 - bag_hasnt( execute, execute ) should have the proper description -ok 295 - bag_hasnt( execute, execute ) should have the proper diagnostics -ok 296 - bag_hasnt( select, select ) should pass -ok 297 - bag_hasnt( select, select ) should have the proper description -ok 298 - bag_hasnt( select, select ) should have the proper diagnostics -ok 299 - bag_hasnt( prepared, empty ) should pass -ok 300 - bag_hasnt( prepared, empty ) should have the proper description -ok 301 - bag_hasnt( prepared, empty ) should have the proper diagnostics -ok 302 - bag_hasnt( prepared, value ) should fail -ok 303 - bag_hasnt( prepared, value ) should have the proper description -ok 304 - bag_hasnt( prepared, value ) should have the proper diagnostics -ok 305 - bag_hasnt( prepared, values ) should fail -ok 306 - bag_hasnt( prepared, values ) should have the proper description -ok 307 - bag_hasnt( prepared, values ) should have the proper diagnostics -ok 308 - bag_hasnt((int,text), (text,int)) should fail -ok 309 - bag_hasnt((int,text), (text,int)) should have the proper description -ok 310 - bag_hasnt((int,text), (text,int)) should have the proper diagnostics -ok 311 - bag_hasnt((int), (text,int)) should fail -ok 312 - bag_hasnt((int), (text,int)) should have the proper description -ok 313 - bag_hasnt((int), (text,int)) should have the proper diagnostics -ok 314 - bag_hasnt( dupes, dupes ) should fail -ok 315 - bag_hasnt( dupes, dupes ) should have the proper description -ok 316 - bag_hasnt( dupes, dupes ) should have the proper diagnostics -ok 317 - bag_hasnt( value, dupes ) should fail -ok 318 - bag_hasnt( value, dupes ) should have the proper description -ok 319 - bag_hasnt( value, dupes ) should have the proper diagnostics -ok 320 - set_eq(sql, array, desc) should pass -ok 321 - set_eq(sql, array, desc) should have the proper description -ok 322 - set_eq(sql, array, desc) should have the proper diagnostics -ok 323 - set_eq(sql, array) should pass -ok 324 - set_eq(sql, array) should have the proper description -ok 325 - set_eq(sql, array) should have the proper diagnostics -ok 326 - set_eq(sql, dupe array) should pass -ok 327 - set_eq(sql, dupe array) should have the proper description -ok 328 - set_eq(sql, dupe array) should have the proper diagnostics -ok 329 - set_eq(sql, array) extra record should fail -ok 330 - set_eq(sql, array) extra record should have the proper description -ok 331 - set_eq(sql, array) extra record should have the proper diagnostics -ok 332 - set_eq(sql, array) missing record should fail -ok 333 - set_eq(sql, array) missing record should have the proper description -ok 334 - set_eq(sql, array) missing record should have the proper diagnostics -ok 335 - set_eq(sql, array) incompatible types should fail -ok 336 - set_eq(sql, array) incompatible types should have the proper description -ok 337 - set_eq(sql, array) incompatible types should have the proper diagnostics +ok 293 - bag_hasnt( prepared, prepared, description ) should pass +ok 294 - bag_hasnt( prepared, prepared, description ) should have the proper description +ok 295 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 296 - bag_hasnt( execute, execute ) should pass +ok 297 - bag_hasnt( execute, execute ) should have the proper description +ok 298 - bag_hasnt( execute, execute ) should have the proper diagnostics +ok 299 - bag_hasnt( select, select ) should pass +ok 300 - bag_hasnt( select, select ) should have the proper description +ok 301 - bag_hasnt( select, select ) should have the proper diagnostics +ok 302 - bag_hasnt( prepared, empty ) should pass +ok 303 - bag_hasnt( prepared, empty ) should have the proper description +ok 304 - bag_hasnt( prepared, empty ) should have the proper diagnostics +ok 305 - bag_hasnt( prepared, value ) should fail +ok 306 - bag_hasnt( prepared, value ) should have the proper description +ok 307 - bag_hasnt( prepared, value ) should have the proper diagnostics +ok 308 - bag_hasnt( prepared, values ) should fail +ok 309 - bag_hasnt( prepared, values ) should have the proper description +ok 310 - bag_hasnt( prepared, values ) should have the proper diagnostics +ok 311 - bag_hasnt((int,text), (text,int)) should fail +ok 312 - bag_hasnt((int,text), (text,int)) should have the proper description +ok 313 - bag_hasnt((int,text), (text,int)) should have the proper diagnostics +ok 314 - bag_hasnt((int), (text,int)) should fail +ok 315 - bag_hasnt((int), (text,int)) should have the proper description +ok 316 - bag_hasnt((int), (text,int)) should have the proper diagnostics +ok 317 - bag_hasnt( dupes, dupes ) should fail +ok 318 - bag_hasnt( dupes, dupes ) should have the proper description +ok 319 - bag_hasnt( dupes, dupes ) should have the proper diagnostics +ok 320 - bag_hasnt( value, dupes ) should fail +ok 321 - bag_hasnt( value, dupes ) should have the proper description +ok 322 - bag_hasnt( value, dupes ) should have the proper diagnostics +ok 323 - set_eq(sql, array, desc) should pass +ok 324 - set_eq(sql, array, desc) should have the proper description +ok 325 - set_eq(sql, array, desc) should have the proper diagnostics +ok 326 - set_eq(sql, array) should pass +ok 327 - set_eq(sql, array) should have the proper description +ok 328 - set_eq(sql, array) should have the proper diagnostics +ok 329 - set_eq(sql, dupe array) should pass +ok 330 - set_eq(sql, dupe array) should have the proper description +ok 331 - set_eq(sql, dupe array) should have the proper diagnostics +ok 332 - set_eq(sql, array) extra record should fail +ok 333 - set_eq(sql, array) extra record should have the proper description +ok 334 - set_eq(sql, array) extra record should have the proper diagnostics +ok 335 - set_eq(sql, array) missing record should fail +ok 336 - set_eq(sql, array) missing record should have the proper description +ok 337 - set_eq(sql, array) missing record should have the proper diagnostics ok 338 - set_eq(sql, array) incompatible types should fail ok 339 - set_eq(sql, array) incompatible types should have the proper description ok 340 - set_eq(sql, array) incompatible types should have the proper diagnostics -ok 341 - bag_eq(sql, array, desc) should pass -ok 342 - bag_eq(sql, array, desc) should have the proper description -ok 343 - bag_eq(sql, array, desc) should have the proper diagnostics -ok 344 - bag_eq(sql, array) should pass -ok 345 - bag_eq(sql, array) should have the proper description -ok 346 - bag_eq(sql, array) should have the proper diagnostics -ok 347 - bag_eq(sql, dupe array) fail should fail -ok 348 - bag_eq(sql, dupe array) fail should have the proper description -ok 349 - bag_eq(sql, dupe array) fail should have the proper diagnostics -ok 350 - bag_eq(sql, array) extra record should fail -ok 351 - bag_eq(sql, array) extra record should have the proper description -ok 352 - bag_eq(sql, array) extra record should have the proper diagnostics -ok 353 - bag_eq(sql, array) missing record should fail -ok 354 - bag_eq(sql, array) missing record should have the proper description -ok 355 - bag_eq(sql, array) missing record should have the proper diagnostics -ok 356 - bag_eq(sql, array) incompatible types should fail -ok 357 - bag_eq(sql, array) incompatible types should have the proper description -ok 358 - bag_eq(sql, array) incompatible types should have the proper diagnostics +ok 341 - set_eq(sql, array) incompatible types should fail +ok 342 - set_eq(sql, array) incompatible types should have the proper description +ok 343 - set_eq(sql, array) incompatible types should have the proper diagnostics +ok 344 - bag_eq(sql, array, desc) should pass +ok 345 - bag_eq(sql, array, desc) should have the proper description +ok 346 - bag_eq(sql, array, desc) should have the proper diagnostics +ok 347 - bag_eq(sql, array) should pass +ok 348 - bag_eq(sql, array) should have the proper description +ok 349 - bag_eq(sql, array) should have the proper diagnostics +ok 350 - bag_eq(sql, dupe array) fail should fail +ok 351 - bag_eq(sql, dupe array) fail should have the proper description +ok 352 - bag_eq(sql, dupe array) fail should have the proper diagnostics +ok 353 - bag_eq(sql, array) extra record should fail +ok 354 - bag_eq(sql, array) extra record should have the proper description +ok 355 - bag_eq(sql, array) extra record should have the proper diagnostics +ok 356 - bag_eq(sql, array) missing record should fail +ok 357 - bag_eq(sql, array) missing record should have the proper description +ok 358 - bag_eq(sql, array) missing record should have the proper diagnostics ok 359 - bag_eq(sql, array) incompatible types should fail ok 360 - bag_eq(sql, array) incompatible types should have the proper description ok 361 - bag_eq(sql, array) incompatible types should have the proper diagnostics -ok 362 - set_ne(sql, array, desc) should pass -ok 363 - set_ne(sql, array, desc) should have the proper description -ok 364 - set_ne(sql, array, desc) should have the proper diagnostics -ok 365 - set_ne(sql, array) should pass -ok 366 - set_ne(sql, array) should have the proper description -ok 367 - set_ne(sql, array) should have the proper diagnostics -ok 368 - set_ne(sql, array) fail should fail -ok 369 - set_ne(sql, array) fail should have the proper description -ok 370 - set_ne(sql, array) fail should have the proper diagnostics -ok 371 - set_ne(sql, dupes array) fail should fail -ok 372 - set_ne(sql, dupes array) fail should have the proper description -ok 373 - set_ne(sql, dupes array) fail should have the proper diagnostics -ok 374 - set_ne(sql, array) incompatible types should fail -ok 375 - set_ne(sql, array) incompatible types should have the proper description -ok 376 - set_ne(sql, array) incompatible types should have the proper diagnostics +ok 362 - bag_eq(sql, array) incompatible types should fail +ok 363 - bag_eq(sql, array) incompatible types should have the proper description +ok 364 - bag_eq(sql, array) incompatible types should have the proper diagnostics +ok 365 - set_ne(sql, array, desc) should pass +ok 366 - set_ne(sql, array, desc) should have the proper description +ok 367 - set_ne(sql, array, desc) should have the proper diagnostics +ok 368 - set_ne(sql, array) should pass +ok 369 - set_ne(sql, array) should have the proper description +ok 370 - set_ne(sql, array) should have the proper diagnostics +ok 371 - set_ne(sql, array) fail should fail +ok 372 - set_ne(sql, array) fail should have the proper description +ok 373 - set_ne(sql, array) fail should have the proper diagnostics +ok 374 - set_ne(sql, dupes array) fail should fail +ok 375 - set_ne(sql, dupes array) fail should have the proper description +ok 376 - set_ne(sql, dupes array) fail should have the proper diagnostics ok 377 - set_ne(sql, array) incompatible types should fail ok 378 - set_ne(sql, array) incompatible types should have the proper description ok 379 - set_ne(sql, array) incompatible types should have the proper diagnostics -ok 380 - bag_ne(sql, array, desc) should pass -ok 381 - bag_ne(sql, array, desc) should have the proper description -ok 382 - bag_ne(sql, array, desc) should have the proper diagnostics -ok 383 - bag_ne(sql, array) should pass -ok 384 - bag_ne(sql, array) should have the proper description -ok 385 - bag_ne(sql, array) should have the proper diagnostics -ok 386 - bag_ne(sql, array) fail should fail -ok 387 - bag_ne(sql, array) fail should have the proper description -ok 388 - bag_ne(sql, array) fail should have the proper diagnostics -ok 389 - bag_ne(sql, dupes array) should pass -ok 390 - bag_ne(sql, dupes array) should have the proper description -ok 391 - bag_ne(sql, dupes array) should have the proper diagnostics -ok 392 - bag_ne(sql, array) incompatible types should fail -ok 393 - bag_ne(sql, array) incompatible types should have the proper description -ok 394 - bag_ne(sql, array) incompatible types should have the proper diagnostics +ok 380 - set_ne(sql, array) incompatible types should fail +ok 381 - set_ne(sql, array) incompatible types should have the proper description +ok 382 - set_ne(sql, array) incompatible types should have the proper diagnostics +ok 383 - bag_ne(sql, array, desc) should pass +ok 384 - bag_ne(sql, array, desc) should have the proper description +ok 385 - bag_ne(sql, array, desc) should have the proper diagnostics +ok 386 - bag_ne(sql, array) should pass +ok 387 - bag_ne(sql, array) should have the proper description +ok 388 - bag_ne(sql, array) should have the proper diagnostics +ok 389 - bag_ne(sql, array) fail should fail +ok 390 - bag_ne(sql, array) fail should have the proper description +ok 391 - bag_ne(sql, array) fail should have the proper diagnostics +ok 392 - bag_ne(sql, dupes array) should pass +ok 393 - bag_ne(sql, dupes array) should have the proper description +ok 394 - bag_ne(sql, dupes array) should have the proper diagnostics ok 395 - bag_ne(sql, array) incompatible types should fail ok 396 - bag_ne(sql, array) incompatible types should have the proper description ok 397 - bag_ne(sql, array) incompatible types should have the proper diagnostics -ok 398 - results_eq(prepared, array, desc) should pass -ok 399 - results_eq(prepared, array, desc) should have the proper description -ok 400 - results_eq(prepared, array, desc) should have the proper diagnostics -ok 401 - results_eq(prepared, array) should pass -ok 402 - results_eq(prepared, array) should have the proper description -ok 403 - results_eq(prepared, array) should have the proper diagnostics -ok 404 - results_eq(sql, array, desc) should pass -ok 405 - results_eq(sql, array, desc) should have the proper description -ok 406 - results_eq(sql, array, desc) should have the proper diagnostics +ok 398 - bag_ne(sql, array) incompatible types should fail +ok 399 - bag_ne(sql, array) incompatible types should have the proper description +ok 400 - bag_ne(sql, array) incompatible types should have the proper diagnostics +ok 401 - results_eq(prepared, array, desc) should pass +ok 402 - results_eq(prepared, array, desc) should have the proper description +ok 403 - results_eq(prepared, array, desc) should have the proper diagnostics +ok 404 - results_eq(prepared, array) should pass +ok 405 - results_eq(prepared, array) should have the proper description +ok 406 - results_eq(prepared, array) should have the proper diagnostics ok 407 - results_eq(sql, array, desc) should pass ok 408 - results_eq(sql, array, desc) should have the proper description ok 409 - results_eq(sql, array, desc) should have the proper diagnostics -ok 410 - results_eq(prepared, array) extra record should fail -ok 411 - results_eq(prepared, array) extra record should have the proper description -ok 412 - results_eq(prepared, array) extra record should have the proper diagnostics -ok 413 - results_eq(select, array) missing record should fail -ok 414 - results_eq(select, array) missing record should have the proper description -ok 415 - results_eq(select, array) missing record should have the proper diagnostics -ok 416 - results_ne(prepared, prepared, desc) should pass -ok 417 - results_ne(prepared, prepared, desc) should have the proper description -ok 418 - results_ne(prepared, prepared, desc) should have the proper diagnostics -ok 419 - results_ne(prepared, prepared) should pass -ok 420 - results_ne(prepared, prepared) should have the proper description -ok 421 - results_ne(prepared, prepared) should have the proper diagnostics -ok 422 - results_ne(execute, execute) should pass -ok 423 - results_ne(execute, execute) should have the proper description -ok 424 - results_ne(execute, execute) should have the proper diagnostics -ok 425 - results_ne(select, select) should pass -ok 426 - results_ne(select, select) should have the proper description -ok 427 - results_ne(select, select) should have the proper diagnostics -ok 428 - results_ne(dupe values, dupe values) should pass -ok 429 - results_ne(dupe values, dupe values) should have the proper description -ok 430 - results_ne(dupe values, dupe values) should have the proper diagnostics -ok 431 - results_ne(values with null, values with null) should pass -ok 432 - results_ne(values with null, values with null) should have the proper description -ok 433 - results_ne(values with null, values with null) should have the proper diagnostics -ok 434 - results_ne(nulls, nulls) should pass -ok 435 - results_ne(nulls, nulls) should have the proper description -ok 436 - results_ne(nulls, nulls) should have the proper diagnostics -ok 437 - results_ne(prepared, select) fail should fail -ok 438 - results_ne(prepared, select) fail should have the proper description -ok 439 - results_ne(prepared, select) fail should have the proper diagnostics -ok 440 - results_ne(select, prepared) missing last row should pass -ok 441 - results_ne(select, prepared) missing last row should have the proper description -ok 442 - results_ne(select, prepared) missing last row should have the proper diagnostics -ok 443 - results_ne(prepared, select) missing first row should pass -ok 444 - results_ne(prepared, select) missing first row should have the proper description -ok 445 - results_ne(prepared, select) missing first row should have the proper diagnostics -ok 446 - results_ne(values dupe, values) should pass -ok 447 - results_ne(values dupe, values) should have the proper description -ok 448 - results_ne(values dupe, values) should have the proper diagnostics -ok 449 - results_ne(values null, values) should pass -ok 450 - results_ne(values null, values) should have the proper description -ok 451 - results_ne(values null, values) should have the proper diagnostics -ok 452 - results_ne(values, values) mismatch should fail -ok 453 - results_ne(values, values) mismatch should have the proper description -ok 454 - results_ne(values, values) mismatch should have the proper diagnostics -ok 455 - results_ne(values, values) subtle mismatch should fail -ok 456 - results_ne(values, values) subtle mismatch should have the proper description -ok 457 - results_ne(values, values) subtle mismatch should have the proper diagnostics -ok 458 - results_ne(values, values) fail column count should fail -ok 459 - results_ne(values, values) fail column count should have the proper description -ok 460 - results_ne(values, values) fail column count should have the proper diagnostics -ok 461 - results_ne(cursor, cursor) should fail -ok 462 - results_ne(cursor, cursor) should have the proper description -ok 463 - results_ne(cursor, cursor) should have the proper diagnostics -ok 464 - results_ne(cursor, prepared) should fail -ok 465 - results_ne(cursor, prepared) should have the proper description -ok 466 - results_ne(cursor, prepared) should have the proper diagnostics -ok 467 - results_ne(prepared, cursor) should fail -ok 468 - results_ne(prepared, cursor) should have the proper description -ok 469 - results_ne(prepared, cursor) should have the proper diagnostics -ok 470 - results_ne(cursor, sql) should pass -ok 471 - results_ne(cursor, sql) should have the proper description -ok 472 - results_ne(cursor, sql) should have the proper diagnostics -ok 473 - results_ne(sql, cursor) should fail -ok 474 - results_ne(sql, cursor) should have the proper description -ok 475 - results_ne(sql, cursor) should have the proper diagnostics -ok 476 - is_empty(sql, desc) should pass -ok 477 - is_empty(sql, desc) should have the proper description -ok 478 - is_empty(sql, desc) should have the proper diagnostics -ok 479 - is_empty(sql) should pass -ok 480 - is_empty(sql) should have the proper description -ok 481 - is_empty(sql) should have the proper diagnostics -ok 482 - is_empty(prepared, desc) should pass -ok 483 - is_empty(prepared, desc) should have the proper description -ok 484 - is_empty(prepared, desc) should have the proper diagnostics -ok 485 - is_empty(prepared) should pass -ok 486 - is_empty(prepared) should have the proper description -ok 487 - is_empty(prepared) should have the proper diagnostics -ok 488 - is_empty(prepared, desc) fail should fail -ok 489 - is_empty(prepared, desc) fail should have the proper description -ok 490 - is_empty(prepared, desc) fail should have the proper diagnostics -ok 491 - is_empty(prepared) fail should fail -ok 492 - is_empty(prepared) fail should have the proper description -ok 493 - is_empty(prepared) fail should have the proper diagnostics -ok 494 - row_eq(prepared, record, desc) should pass -ok 495 - row_eq(prepared, record, desc) should have the proper description -ok 496 - row_eq(prepared, record, desc) should have the proper diagnostics -ok 497 - row_eq(sql, record, desc) should pass -ok 498 - row_eq(sql, record, desc) should have the proper description -ok 499 - row_eq(sql, record, desc) should have the proper diagnostics -ok 500 - row_eq(prepared, record, desc) should pass -ok 501 - row_eq(prepared, record, desc) should have the proper description -ok 502 - row_eq(prepared, record, desc) should have the proper diagnostics -ok 503 - row_eq(prepared, record, desc) should fail +ok 410 - results_eq(sql, array, desc) should pass +ok 411 - results_eq(sql, array, desc) should have the proper description +ok 412 - results_eq(sql, array, desc) should have the proper diagnostics +ok 413 - results_eq(prepared, array) extra record should fail +ok 414 - results_eq(prepared, array) extra record should have the proper description +ok 415 - results_eq(prepared, array) extra record should have the proper diagnostics +ok 416 - results_eq(select, array) missing record should fail +ok 417 - results_eq(select, array) missing record should have the proper description +ok 418 - results_eq(select, array) missing record should have the proper diagnostics +ok 419 - results_ne(prepared, prepared, desc) should pass +ok 420 - results_ne(prepared, prepared, desc) should have the proper description +ok 421 - results_ne(prepared, prepared, desc) should have the proper diagnostics +ok 422 - results_ne(prepared, prepared) should pass +ok 423 - results_ne(prepared, prepared) should have the proper description +ok 424 - results_ne(prepared, prepared) should have the proper diagnostics +ok 425 - results_ne(execute, execute) should pass +ok 426 - results_ne(execute, execute) should have the proper description +ok 427 - results_ne(execute, execute) should have the proper diagnostics +ok 428 - results_ne(select, select) should pass +ok 429 - results_ne(select, select) should have the proper description +ok 430 - results_ne(select, select) should have the proper diagnostics +ok 431 - results_ne(dupe values, dupe values) should pass +ok 432 - results_ne(dupe values, dupe values) should have the proper description +ok 433 - results_ne(dupe values, dupe values) should have the proper diagnostics +ok 434 - results_ne(values with null, values with null) should pass +ok 435 - results_ne(values with null, values with null) should have the proper description +ok 436 - results_ne(values with null, values with null) should have the proper diagnostics +ok 437 - results_ne(nulls, nulls) should pass +ok 438 - results_ne(nulls, nulls) should have the proper description +ok 439 - results_ne(nulls, nulls) should have the proper diagnostics +ok 440 - results_ne(prepared, select) fail should fail +ok 441 - results_ne(prepared, select) fail should have the proper description +ok 442 - results_ne(prepared, select) fail should have the proper diagnostics +ok 443 - results_ne(select, prepared) missing last row should pass +ok 444 - results_ne(select, prepared) missing last row should have the proper description +ok 445 - results_ne(select, prepared) missing last row should have the proper diagnostics +ok 446 - results_ne(prepared, select) missing first row should pass +ok 447 - results_ne(prepared, select) missing first row should have the proper description +ok 448 - results_ne(prepared, select) missing first row should have the proper diagnostics +ok 449 - results_ne(values dupe, values) should pass +ok 450 - results_ne(values dupe, values) should have the proper description +ok 451 - results_ne(values dupe, values) should have the proper diagnostics +ok 452 - results_ne(values null, values) should pass +ok 453 - results_ne(values null, values) should have the proper description +ok 454 - results_ne(values null, values) should have the proper diagnostics +ok 455 - results_ne(values, values) mismatch should fail +ok 456 - results_ne(values, values) mismatch should have the proper description +ok 457 - results_ne(values, values) mismatch should have the proper diagnostics +ok 458 - results_ne(values, values) subtle mismatch should fail +ok 459 - results_ne(values, values) subtle mismatch should have the proper description +ok 460 - results_ne(values, values) subtle mismatch should have the proper diagnostics +ok 461 - results_ne(values, values) fail column count should fail +ok 462 - results_ne(values, values) fail column count should have the proper description +ok 463 - results_ne(values, values) fail column count should have the proper diagnostics +ok 464 - results_ne(cursor, cursor) should fail +ok 465 - results_ne(cursor, cursor) should have the proper description +ok 466 - results_ne(cursor, cursor) should have the proper diagnostics +ok 467 - results_ne(cursor, prepared) should fail +ok 468 - results_ne(cursor, prepared) should have the proper description +ok 469 - results_ne(cursor, prepared) should have the proper diagnostics +ok 470 - results_ne(prepared, cursor) should fail +ok 471 - results_ne(prepared, cursor) should have the proper description +ok 472 - results_ne(prepared, cursor) should have the proper diagnostics +ok 473 - results_ne(cursor, sql) should pass +ok 474 - results_ne(cursor, sql) should have the proper description +ok 475 - results_ne(cursor, sql) should have the proper diagnostics +ok 476 - results_ne(sql, cursor) should fail +ok 477 - results_ne(sql, cursor) should have the proper description +ok 478 - results_ne(sql, cursor) should have the proper diagnostics +ok 479 - is_empty(sql, desc) should pass +ok 480 - is_empty(sql, desc) should have the proper description +ok 481 - is_empty(sql, desc) should have the proper diagnostics +ok 482 - is_empty(sql) should pass +ok 483 - is_empty(sql) should have the proper description +ok 484 - is_empty(sql) should have the proper diagnostics +ok 485 - is_empty(prepared, desc) should pass +ok 486 - is_empty(prepared, desc) should have the proper description +ok 487 - is_empty(prepared, desc) should have the proper diagnostics +ok 488 - is_empty(prepared) should pass +ok 489 - is_empty(prepared) should have the proper description +ok 490 - is_empty(prepared) should have the proper diagnostics +ok 491 - is_empty(prepared, desc) fail should fail +ok 492 - is_empty(prepared, desc) fail should have the proper description +ok 493 - is_empty(prepared, desc) fail should have the proper diagnostics +ok 494 - is_empty(prepared) fail should fail +ok 495 - is_empty(prepared) fail should have the proper description +ok 496 - is_empty(prepared) fail should have the proper diagnostics +ok 497 - row_eq(prepared, record, desc) should pass +ok 498 - row_eq(prepared, record, desc) should have the proper description +ok 499 - row_eq(prepared, record, desc) should have the proper diagnostics +ok 500 - row_eq(sql, record, desc) should pass +ok 501 - row_eq(sql, record, desc) should have the proper description +ok 502 - row_eq(sql, record, desc) should have the proper diagnostics +ok 503 - row_eq(prepared, record, desc) should pass ok 504 - row_eq(prepared, record, desc) should have the proper description ok 505 - row_eq(prepared, record, desc) should have the proper diagnostics -ok 506 - row_eq(prepared, sometype, desc) should pass -ok 507 - row_eq(prepared, sometype, desc) should have the proper description -ok 508 - row_eq(prepared, sometype, desc) should have the proper diagnostics -ok 509 - row_eq(sqlrow, sometype, desc) should pass -ok 510 - row_eq(sqlrow, sometype, desc) should have the proper description -ok 511 - row_eq(sqlrow, sometype, desc) should have the proper diagnostics -ok 512 - threw 0A000 +ok 506 - row_eq(prepared, record, desc) should fail +ok 507 - row_eq(prepared, record, desc) should have the proper description +ok 508 - row_eq(prepared, record, desc) should have the proper diagnostics +ok 509 - row_eq(prepared, sometype, desc) should pass +ok 510 - row_eq(prepared, sometype, desc) should have the proper description +ok 511 - row_eq(prepared, sometype, desc) should have the proper diagnostics +ok 512 - row_eq(sqlrow, sometype, desc) should pass +ok 513 - row_eq(sqlrow, sometype, desc) should have the proper description +ok 514 - row_eq(sqlrow, sometype, desc) should have the proper diagnostics +ok 515 - threw 0A000 diff --git a/pgtap.sql.in b/pgtap.sql.in index 27b52c0588c0..52e0fa96b52d 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -5859,7 +5859,7 @@ RETURNS TEXT AS $$ WHERE c.relname = $1 AND c.relistemp AND attnum > 0 - AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END + AND NOT attisdropped ORDER BY attnum ), ','); $$ LANGUAGE sql; diff --git a/sql/resultset.sql b/sql/resultset.sql index d29d928d7d01..d02f042ce04e 100644 --- a/sql/resultset.sql +++ b/sql/resultset.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(512); +SELECT plan(515); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -426,6 +426,31 @@ SELECT * FROM check_test( want: (text,integer)' ); +-- Handle failure with column mismatch with a column type in a schema not in +-- the search path. This is a regression. +CREATE SCHEMA __myfoo; +CREATE DOMAIN __myfoo.text AS TEXT CHECK(TRUE); +CREATE TABLE __yowza( + foo text, + bar __myfoo.text, + baz integer +); +INSERT INTO __yowza VALUES ('abc', 'xyz', 1); +INSERT INTO __yowza VALUES ('def', 'utf', 2); + +SELECT * FROM check_test( + set_eq( + 'SELECT foo, bar from __yowza', + 'SELECT foo, bar, baz from __yowza' + ), + false, + 'set_eq(sql, sql) fail type schema visibility', + '', + ' Columns differ between queries: + have: (text,__myfoo.text) + want: (text,__myfoo.text,integer)' +); + /****************************************************************************/ -- Now test bag_eq(). From c85926cd519110b2063b78472fe7474b7d912de7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 2 Feb 2010 12:15:26 -0800 Subject: [PATCH 0509/1195] Make sure `row_eq()` always returns a row. --- Changes | 2 ++ pgtap.sql.in | 14 ++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Changes b/Changes index 6c63ddbce464..4d3e1e51175a 100644 --- a/Changes +++ b/Changes @@ -16,6 +16,8 @@ Revision history for pgTAP * Fixed bug in the diagnostic output of `set_eq()`, `set_ne()`, `bag_eq()`, and `bag_ne()` where a column's type would not be displayed if the column was not in the search path. Thanks to Rod Taylor for the spot! +* Made the implementation of `row_eq()` always return a value, even if the + query it executes returns no rows. 0.23 2009-12-18T23:03:39 ------------------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index 52e0fa96b52d..83173f209c2a 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -7021,15 +7021,13 @@ CREATE OR REPLACE FUNCTION row_eq( TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE rec RECORD; - result BOOLEAN; BEGIN - FOR rec in EXECUTE _query($1) LOOP - result := NOT rec IS DISTINCT FROM $2; - RETURN ok(result, $3 ) || CASE WHEN result THEN '' ELSE 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; - END LOOP; + 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; From c9c49caa0868a0118dc46b972c4445d945645ce3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 16 Feb 2010 16:15:23 -0800 Subject: [PATCH 0510/1195] Add todo. --- README.pgtap | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.pgtap b/README.pgtap index 5580099239fb..24a6c8b50616 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4294,6 +4294,9 @@ No changes. Everything should just work. To Do ----- +* Add `schema, table, colname` variations of the table-checking functions + (`table_has_column()`, col_type_is()`, etc.). That is, allow the description + to be optional if the schema is included in the function call. * Add functions to test for object ownership. + `db_owner_is()` + `table_owner_is()` From c42defed9608156cbce2145dc11776051d902850 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Feb 2010 17:01:48 -0800 Subject: [PATCH 0511/1195] The column name isn't the type. Fix bug in the default description for `col_type_is()` where the column name was listed as the type! --- Changes | 2 ++ pgtap.sql.in | 2 +- sql/coltap.sql | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 4d3e1e51175a..75c95cd46dd6 100644 --- a/Changes +++ b/Changes @@ -18,6 +18,8 @@ Revision history for pgTAP was not in the search path. Thanks to Rod Taylor for the spot! * Made the implementation of `row_eq()` always return a value, even if the query it executes returns no rows. +* Fixed bug in the default description for `col_type_is()` where the column + name was listed as the type! 0.23 2009-12-18T23:03:39 ------------------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index 83173f209c2a..882511676958 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1206,7 +1206,7 @@ $$ 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) || ' should be type ' || $3 ); + 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 ) diff --git a/sql/coltap.sql b/sql/coltap.sql index 726817afbd9e..f3a14dd2fba3 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -197,7 +197,7 @@ SELECT * FROM check_test( col_type_is( 'public', 'sometab', 'name'::name, 'text' ), true, 'col_type_is( sch, tab, col, type )', - 'Column public.sometab should be type name', + 'Column public.sometab.name should be type text', '' ); From 92f0a48cc09152721921e01e3cd73853cba6e6d4 Mon Sep 17 00:00:00 2001 From: Darrell Fuhriman Date: Tue, 2 Mar 2010 10:04:59 -0800 Subject: [PATCH 0512/1195] fix spec to work with 8.3, which requires pgtap.so to be installed --- contrib/pgtap.spec | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 367c0d8d75f3..e6097a73eabc 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,21 +1,26 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap Version: 0.24 -Release: 1%{?dist} +Release: 2%{?dist} Group: Applications/Databases License: BSD URL: http://pgtap.projects.postgresql.org Source0: ftp://ftp.postgresql.org/pub/projects/pgFoundry/pgtap/pgtap-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root -BuildRequires: postgresql-devel -Requires: postgresql-server, perl-Test-Harness >= 3.0 -BuildArch: noarch %description pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and PL/SQL. It includes a comprehensive collection of TAP-emitting assertion functions, as well as the ability to integrate with other TAP-emitting test frameworks. It can also be used in the xUnit testing style. + +%define postgresver %(pg_config --version|awk '{print $2}'| cut -d. -f1,2) +Requires: postgresql-server = %{postgresver}, perl-Test-Harness >= 3.0 +BuildRequires: postgresql-devel = %{postgresver} + +%if "%{postgresver}" != "8.4" +BuildArch: noarch +%endif %prep %setup -q @@ -33,10 +38,17 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %files %defattr(-,root,root,-) %{_bindir}/pg_prove +%{_bindir}/pg_tapgen +%if "%{postgresver}" == "8.3" +%{_libdir}/pgsql/pgtap.so +%endif %{_datadir}/pgsql/contrib/* %{_docdir}/pgsql/contrib/README.pgtap %changelog +* Sun Mar 01 2010 Darrell Fuhriman 0.24-2 +- Make install work where the pgtap.so library is needed. + * Sun Dec 27 2009 Davi dWheeler 0.24-1 - Updated Source URL to a more predictable format. From 91ccb58b5976ddbc4739b7144753f9da83ba36d7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 2 Mar 2010 10:11:26 -0800 Subject: [PATCH 0513/1195] Typo. --- contrib/pgtap.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index e6097a73eabc..1b33ddb651bf 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -49,7 +49,7 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} * Sun Mar 01 2010 Darrell Fuhriman 0.24-2 - Make install work where the pgtap.so library is needed. -* Sun Dec 27 2009 Davi dWheeler 0.24-1 +* Sun Dec 27 2009 David Wheeler 0.24-1 - Updated Source URL to a more predictable format. * Mon Aug 24 2009 David Fetter 0.23-1 From 31e4b7822e5440720fc052ef13f2907e94fe4c16 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 3 Mar 2010 09:02:32 -0800 Subject: [PATCH 0514/1195] Fix Makefile for PostgreSQL 9. --- Changes | 1 + Makefile | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Changes b/Changes index 75c95cd46dd6..a36c7df7c1c0 100644 --- a/Changes +++ b/Changes @@ -20,6 +20,7 @@ Revision history for pgTAP query it executes returns no rows. * Fixed bug in the default description for `col_type_is()` where the column name was listed as the type! +* Updated the `Makefile` to support PostgreSQL 9. 0.23 2009-12-18T23:03:39 ------------------------- diff --git a/Makefile b/Makefile index a75a6a9685bd..1474c9a476a2 100644 --- a/Makefile +++ b/Makefile @@ -57,8 +57,10 @@ endif # We support 8.0 and later. ifneq ($(PGVER_MAJOR), 8) +ifneq ($(PGVER_MAJOR), 9) $(error pgTAP requires PostgreSQL 8.0 or later. This is $(VERSION)) endif +endif # Set up extra substitutions based on version numbers. ifeq ($(PGVER_MAJOR), 8) From bfa908f380025e52cfa32cc7428cf6ce3b42b3e3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 3 Mar 2010 09:08:21 -0800 Subject: [PATCH 0515/1195] Don't build .so on 9.0. Also, move detection of < 8 earlier in the Makefile. --- Makefile | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 1474c9a476a2..02794dfa8064 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,15 @@ PGVER_MINOR = $(shell echo $(VERSION) | awk -F. '{ print ($$2 + 0) }') PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') PGTAP_VERSION = 0.24 +# We support 8.0 and later. +ifneq ($(PGVER_MAJOR), 8) +ifneq ($(PGVER_MAJOR), 9) +$(error pgTAP requires PostgreSQL 8.0 or later. This is $(VERSION)) +endif +endif + # Compile the C code only if we're on 8.3 or older. +ifeq ($(PGVER_MAJOR), 8) ifeq ($(PGVER_MINOR), 3) MODULES = pgtap endif @@ -36,6 +44,7 @@ endif ifeq ($(PGVER_MINOR), 0) MODULES = pgtap endif +endif # Load up the PostgreSQL makefiles. ifdef PGXS @@ -55,13 +64,6 @@ ifdef PERL HAVE_HARNESS := $(shell $(PERL) -le 'eval { require TAP::Harness }; print 1 unless $$@' ) endif -# We support 8.0 and later. -ifneq ($(PGVER_MAJOR), 8) -ifneq ($(PGVER_MAJOR), 9) -$(error pgTAP requires PostgreSQL 8.0 or later. This is $(VERSION)) -endif -endif - # Set up extra substitutions based on version numbers. ifeq ($(PGVER_MAJOR), 8) ifeq ($(PGVER_MINOR), 2) From 62790fe39e813cb1682427fa215db2842da25e2d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 15 Mar 2010 09:33:03 -0700 Subject: [PATCH 0516/1195] No more PKs on internal tables. There was code in `plan()` and `_cleanup()` to temporarily change `client_min_messages` to "warning" as a way to suppress NOTICEs when creating and dropping the tables that pgTAP uses internally. By eliminating the primary keys in those tables in favor of `NOT NULL` columns and `UNIQUE INDEX`es, those workarounds are no longer necessary. --- Changes | 3 +++ pgtap.sql.in | 25 +++++++++---------------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Changes b/Changes index a36c7df7c1c0..c2b46dee4db3 100644 --- a/Changes +++ b/Changes @@ -21,6 +21,9 @@ Revision history for pgTAP * Fixed bug in the default description for `col_type_is()` where the column name was listed as the type! * Updated the `Makefile` to support PostgreSQL 9. +* Removed the primary keys from the temporary tables used internally by pgTAP + and thus eliminated the need for pgTAP to suppress NOTICEs when creating + those tables. 0.23 2009-12-18T23:03:39 ------------------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index 882511676958..2973b9b7ef89 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -37,34 +37,35 @@ CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ DECLARE rcount INTEGER; - cmm text := current_setting('client_min_messages'); BEGIN BEGIN - PERFORM set_config('client_min_messages', 'warning', true); EXECUTE ' + CREATE SEQUENCE __tcache___id_seq; CREATE TEMP TABLE __tcache__ ( - id SERIAL PRIMARY KEY, + 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 SEQUENCE __tresults___numb_seq; CREATE TEMP TABLE __tresults__ ( - numb SERIAL PRIMARY KEY, + numb INTEGER NOT NULL DEFAULT nextval(''__tresults___numb_seq''), ok BOOLEAN NOT NULL DEFAULT TRUE, aok BOOLEAN NOT NULL DEFAULT TRUE, descr TEXT NOT NULL DEFAULT '''', type TEXT NOT NULL DEFAULT '''', reason TEXT NOT NULL DEFAULT '''' ); + CREATE UNIQUE INDEX __tresults___key ON __tresults__(numb); GRANT ALL ON TABLE __tresults__ TO PUBLIC; GRANT ALL ON TABLE __tresults___numb_seq TO PUBLIC; '; EXCEPTION WHEN duplicate_table THEN - PERFORM set_config('client_min_messages', cmm, true); -- Raise an exception if there's already a plan. EXECUTE 'SELECT TRUE FROM __tcache__ WHERE label = ''plan'''; GET DIAGNOSTICS rcount = ROW_COUNT; @@ -73,8 +74,6 @@ BEGIN END IF; END; - PERFORM set_config('client_min_messages', cmm, true); - -- Save the plan and return. PERFORM _set('plan', $1 ); RETURN '1..' || $1; @@ -4083,7 +4082,7 @@ RETURNS NAME[] AS $$ JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace WHERE c.relkind = $1 AND n.nspname = $2 - AND c.relname NOT IN('pg_all_foreign_keys', 'tap_funky') + 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) @@ -5710,16 +5709,10 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION _cleanup() RETURNS boolean AS $$ -DECLARE - cmm text := current_setting('client_min_messages'); -BEGIN - PERFORM set_config('client_min_messages', 'warning', true); DROP TABLE __tresults__; DROP TABLE __tcache__; - PERFORM set_config('client_min_messages', cmm, true); - RETURN TRUE; -END -$$ LANGUAGE plpgsql; + SELECT TRUE; +$$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _runner( text[], text[], text[], text[], text[] ) RETURNS SETOF TEXT AS $$ From 8c0ee6a72f34778cbaa7ebb0582a83daed6c4a7d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 15 Mar 2010 10:36:12 -0700 Subject: [PATCH 0517/1195] Better description of why lack of NOTICEs is good. --- Changes | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index c2b46dee4db3..724e07840bb4 100644 --- a/Changes +++ b/Changes @@ -22,8 +22,8 @@ Revision history for pgTAP name was listed as the type! * Updated the `Makefile` to support PostgreSQL 9. * Removed the primary keys from the temporary tables used internally by pgTAP - and thus eliminated the need for pgTAP to suppress NOTICEs when creating - those tables. + and thus eliminated the logging of NOTICEs to the PostgreSQL log when pgTAP + creates those tables. 0.23 2009-12-18T23:03:39 ------------------------- From 400db6d2db7ebabb90fbc528100bb9e518f7fbc3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 15 Mar 2010 11:19:52 -0700 Subject: [PATCH 0518/1195] No longer require `USE_PGXS=1` when built outside contrib. --- Changes | 3 +++ Makefile | 6 ++++++ README.pgtap | 22 +++++++++------------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Changes b/Changes index 724e07840bb4..5b1f26fe359d 100644 --- a/Changes +++ b/Changes @@ -24,6 +24,9 @@ Revision history for pgTAP * Removed the primary keys from the temporary tables used internally by pgTAP and thus eliminated the logging of NOTICEs to the PostgreSQL log when pgTAP creates those tables. +* Eliminated the need to set `USE_PGXS=1` when building outsde the contrib + directory of the PostgreSQL distribution, though it will still be respected + if it is set. 0.23 2009-12-18T23:03:39 ------------------------- diff --git a/Makefile b/Makefile index 02794dfa8064..bf96cd0c7702 100644 --- a/Makefile +++ b/Makefile @@ -8,12 +8,18 @@ REGRESS_OPTS = --load-language=plpgsql # Figure out where pg_config is, and set other vars we'll need depending on # whether or not we're in the source tree. + ifdef USE_PGXS PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) else +ifeq (exists, $(shell [ -e ../../src/bin/pg_config/pg_config ] && echo exists) ) top_builddir = ../.. PG_CONFIG := $(top_builddir)/src/bin/pg_config/pg_config +else +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +endif endif # We need to do various things with various versions of PostgreSQL. diff --git a/README.pgtap b/README.pgtap index 24a6c8b50616..6cb2b0dadad3 100644 --- a/README.pgtap +++ b/README.pgtap @@ -11,9 +11,9 @@ Installation For the impatient, to install pgTAP into a PostgreSQL database, just do this: - make USE_PGXS=1 - make install USE_PGXS=1 - make installcheck USE_PGXS=1 + make + make install + make installcheck If you encounter an error such as: @@ -22,9 +22,9 @@ If you encounter an error such as: You need to use GNU make, which may well be installed on your system as 'gmake': - gmake USE_PGXS=1 - gmake install USE_PGXS=1 - gmake installcheck USE_PGXS=1 + gmake + gmake install + gmake installcheck If you encounter an error such as: @@ -39,12 +39,11 @@ package management system such as RPM to install PostgreSQL, be sure that the `-devel` package is also installed. If necessary, add the path to `pg_config` to your `$PATH` environment variable: - env PATH=$PATH:/path/to/pgsql/bin USE_PGXS=1 \ + env PATH=$PATH:/path/to/pgsql/bin \ make && make install && make installcheck And finally, if all that fails, copy the entire distribution directory to the -`contrib/` subdirectory of the PostgreSQL source tree and try it there without -the `$USE_PGXS` variable: +`contrib/` subdirectory of the PostgreSQL source tree and try it there: make make install @@ -86,10 +85,7 @@ PostgreSQL client environment variables: You can use it to run the test suite as a database super user like so: - make test USE_PGXS=1 PGUSER=postgres - -Of course, if you're running the tests from the `contrib/` directory, you -should omit the `USE_PGXS` variable. + make test PGUSER=postgres Adding pgTAP to a Database -------------------------- From 4687f84bb813187a51417745f12662dfb38c36de Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 16 Mar 2010 10:29:47 -0700 Subject: [PATCH 0519/1195] Remove all support for `USE_PGXS`. See the comments on my [blog post](http://justatheory.com/computers/databases/postgresql/no_more_use_pgxs.html) for details. It's just superfluous, and one can still build inside the `contrib` directory by seting `PG_CONFIG`. Very nice and clean, frankly. While at it, and since I'm building against 9.0devel, I've fixed the util tests to pass on 9.x. --- Changes | 6 +++--- Makefile | 14 +------------- README.pgtap | 12 +++++------- sql/util.sql | 4 ++-- 4 files changed, 11 insertions(+), 25 deletions(-) diff --git a/Changes b/Changes index 5b1f26fe359d..edc25b62d3d5 100644 --- a/Changes +++ b/Changes @@ -20,13 +20,13 @@ Revision history for pgTAP query it executes returns no rows. * Fixed bug in the default description for `col_type_is()` where the column name was listed as the type! -* Updated the `Makefile` to support PostgreSQL 9. +* Updated the `Makefile` and the test suite to support PostgreSQL 9. * Removed the primary keys from the temporary tables used internally by pgTAP and thus eliminated the logging of NOTICEs to the PostgreSQL log when pgTAP creates those tables. * Eliminated the need to set `USE_PGXS=1` when building outsde the contrib - directory of the PostgreSQL distribution, though it will still be respected - if it is set. + directory of the PostgreSQL distribution. If `pg_config` isn't in the + `$PATH` environment variable, one can simply set `PG_CONFIG` to point to it. 0.23 2009-12-18T23:03:39 ------------------------- diff --git a/Makefile b/Makefile index bf96cd0c7702..5560c4ea94d1 100644 --- a/Makefile +++ b/Makefile @@ -6,21 +6,9 @@ SCRIPTS = bbin/pg_prove bbin/pg_tapgen REGRESS = $(patsubst sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --load-language=plpgsql -# Figure out where pg_config is, and set other vars we'll need depending on -# whether or not we're in the source tree. - -ifdef USE_PGXS +# Run pg_config to get the PGXS Makefiles PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) -else -ifeq (exists, $(shell [ -e ../../src/bin/pg_config/pg_config ] && echo exists) ) -top_builddir = ../.. -PG_CONFIG := $(top_builddir)/src/bin/pg_config/pg_config -else -PG_CONFIG = pg_config -PGXS := $(shell $(PG_CONFIG) --pgxs) -endif -endif # We need to do various things with various versions of PostgreSQL. VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') diff --git a/README.pgtap b/README.pgtap index 6cb2b0dadad3..529b75a63729 100644 --- a/README.pgtap +++ b/README.pgtap @@ -36,18 +36,16 @@ Or: Be sure that you have `pg_config` installed and in your path. If you used a package management system such as RPM to install PostgreSQL, be sure that the -`-devel` package is also installed. If necessary, add the path to `pg_config` -to your `$PATH` environment variable: +`-devel` package is also installed. If necessary tell the build process where +to find it: - env PATH=$PATH:/path/to/pgsql/bin \ - make && make install && make installcheck + env PG_CONFIG=/path/to/pg_config make && make install && make installcheck And finally, if all that fails, copy the entire distribution directory to the `contrib/` subdirectory of the PostgreSQL source tree and try it there: - make - make install - make installcheck + env PG_CONFIG=../../src/bin/pg_config/pg_config \ + make && make install && make installcheck If you encounter an error such as: diff --git a/sql/util.sql b/sql/util.sql index 28c0bd4a86cb..8a6857221e1f 100644 --- a/sql/util.sql +++ b/sql/util.sql @@ -16,7 +16,7 @@ SELECT is( ); SELECT matches( pg_version(), - '^8[.][[:digit:]]{1,2}([.][[:digit:]]{1,2}|devel|(alpha|beta|rc)[[:digit:]]+)$', + '^[89][.][[:digit:]]{1,2}([.][[:digit:]]{1,2}|devel|(alpha|beta|rc)[[:digit:]]+)$', 'pg_version() should work' ); @@ -36,7 +36,7 @@ SELECT is( ); SELECT matches( pg_version_num()::text, - '^8[[:digit:]]{4}$', + '^[89][[:digit:]]{4}$', 'pg_version_num() should be correct' ); From c2e5ce1738032d6a1971afe99fc64dd88546d946 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 16 Mar 2010 10:45:23 -0700 Subject: [PATCH 0520/1195] Remove more stuff related to `USE_PGXS`. --- Makefile | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 5560c4ea94d1..9549f0996abc 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ REGRESS_OPTS = --load-language=plpgsql # Run pg_config to get the PGXS Makefiles PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) # We need to do various things with various versions of PostgreSQL. VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') @@ -40,14 +41,6 @@ MODULES = pgtap endif endif -# Load up the PostgreSQL makefiles. -ifdef PGXS -include $(PGXS) -else -include $(top_builddir)/src/Makefile.global -include $(top_srcdir)/contrib/contrib-global.mk -endif - # We need Perl. ifndef PERL PERL := $(shell which perl) From 39fde6d3548b37f8250d1af7a89620d616fd7cef Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 15 May 2010 15:10:33 -0700 Subject: [PATCH 0521/1195] Fix DROP statement. --- Changes | 2 ++ pgtap.sql.in | 4 ++-- uninstall_pgtap.sql.in | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index edc25b62d3d5..3d7a53f6d32c 100644 --- a/Changes +++ b/Changes @@ -27,6 +27,8 @@ Revision history for pgTAP * Eliminated the need to set `USE_PGXS=1` when building outsde the contrib directory of the PostgreSQL distribution. If `pg_config` isn't in the `$PATH` environment variable, one can simply set `PG_CONFIG` to point to it. +* Fixed typo in the statement to drop the `tap_funky` view in + `uninstall_pgtap.sql`. Thanks to Erik Rijkers. 0.23 2009-12-18T23:03:39 ------------------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index 2973b9b7ef89..e9ce23044c79 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2091,8 +2091,8 @@ RETURNS TEXT AS $$ SELECT fk_ok( $1, ARRAY[$2], $3, ARRAY[$4] ); $$ LANGUAGE sql; -CREATE OR REPLACE VIEW tap_funky AS - SELECT p.oid AS oid, +CREATE OR REPLACE VIEW tap_funky + AS SELECT p.oid AS oid, n.nspname AS schema, p.proname AS name, array_to_string(p.proargtypes::regtype[], ',') AS args, diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index 105e0cc6bf2b..fea4ab36e9cf 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -488,7 +488,7 @@ DROP FUNCTION _got_func ( NAME ); DROP FUNCTION _got_func ( NAME, NAME[] ); DROP FUNCTION _got_func ( NAME, NAME ); DROP FUNCTION _got_func ( NAME, NAME, NAME[] ); -DROP VIEW tap_funky AS; +DROP VIEW tap_funky; DROP FUNCTION fk_ok ( NAME, NAME, NAME, NAME ); DROP FUNCTION fk_ok ( NAME, NAME, NAME, NAME, TEXT ); DROP FUNCTION fk_ok ( NAME, NAME, NAME, NAME, NAME, TEXT ); From 2128b71660bd55986fb080fb8850828af50ece02 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 21 May 2010 15:39:30 -0400 Subject: [PATCH 0522/1195] Note need to split up the functions. --- README.pgtap | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.pgtap b/README.pgtap index 529b75a63729..4f55fe4a8740 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4309,6 +4309,10 @@ To Do + `sequence_cycles()` * Useful result testing function to consider adding (but might require C code): `rowtype_is()` +* Split test functions into separate files (and maybe distributions?): + + One with scalar comparison functions: `ok()`, `is()`, `like()`, etc. + + One with relation comparison functions: `set_eq()`, `results_eq()`, etc. + + One with schema testing functions: `has_table()`, `tables_are()`, etc. Supported Versions ----------------- From fa811f51682f3a0af26075144bf7712f1b4dd9fa Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 21 May 2010 15:57:41 -0400 Subject: [PATCH 0523/1195] Remove deprecated `can_ok()` functions. --- Changes | 1 + expected/functap.out | 814 ++++++++++++++++++++----------------------- pgtap.sql.in | 64 ---- sql/functap.sql | 120 +------ 4 files changed, 388 insertions(+), 611 deletions(-) diff --git a/Changes b/Changes index 3d7a53f6d32c..98a034bf4518 100644 --- a/Changes +++ b/Changes @@ -29,6 +29,7 @@ Revision history for pgTAP `$PATH` environment variable, one can simply set `PG_CONFIG` to point to it. * Fixed typo in the statement to drop the `tap_funky` view in `uninstall_pgtap.sql`. Thanks to Erik Rijkers. +* Removed deprecated `can_ok()` functions. Use `has_function()`, instead. 0.23 2009-12-18T23:03:39 ------------------------- diff --git a/expected/functap.out b/expected/functap.out index 7f43402069a2..678092930890 100644 --- a/expected/functap.out +++ b/expected/functap.out @@ -1,5 +1,5 @@ \unset ECHO -1..508 +1..466 ok 1 - simple function should pass ok 2 - simple function should have the proper description ok 3 - simple function should have the proper diagnostics @@ -81,430 +81,388 @@ ok 78 - custom array function should have the proper diagnostics ok 79 - custom numeric function should fail ok 80 - custom numeric function should have the proper description ok 81 - custom numeric function should have the proper diagnostics -ok 82 - simple function should pass -ok 83 - simple function should have the proper description -ok 84 - simple function should have the proper diagnostics -ok 85 - simple schema.function should pass -ok 86 - simple schema.function should have the proper description -ok 87 - simple schema.function should have the proper diagnostics -ok 88 - simple function desc should pass -ok 89 - simple function desc should have the proper description -ok 90 - simple function desc should have the proper diagnostics -ok 91 - simple with 0 args should pass -ok 92 - simple with 0 args should have the proper description -ok 93 - simple with 0 args should have the proper diagnostics -ok 94 - simple with 0 args desc should pass -ok 95 - simple with 0 args desc should have the proper description -ok 96 - simple with 0 args desc should have the proper diagnostics -ok 97 - simple schema.func with 0 args should pass -ok 98 - simple schema.func with 0 args should have the proper description -ok 99 - simple schema.func with 0 args should have the proper diagnostics -ok 100 - simple schema.func with desc should pass -ok 101 - simple schema.func with desc should have the proper description -ok 102 - simple schema.func with desc should have the proper diagnostics -ok 103 - simple scchma.func with 0 args, desc should pass -ok 104 - simple scchma.func with 0 args, desc should have the proper description -ok 105 - simple scchma.func with 0 args, desc should have the proper diagnostics -ok 106 - simple function with 1 arg should pass -ok 107 - simple function with 1 arg should have the proper description -ok 108 - simple function with 1 arg should have the proper diagnostics -ok 109 - simple function with 2 args should pass -ok 110 - simple function with 2 args should have the proper description -ok 111 - simple function with 2 args should have the proper diagnostics -ok 112 - simple array function should pass -ok 113 - simple array function should have the proper description -ok 114 - simple array function should have the proper diagnostics -ok 115 - custom array function should pass -ok 116 - custom array function should have the proper description -ok 117 - custom array function should have the proper diagnostics -ok 118 - custom numeric function should pass -ok 119 - custom numeric function should have the proper description -ok 120 - custom numeric function should have the proper diagnostics -ok 121 - failure output should fail -ok 122 - failure output should have the proper description -ok 123 - failure output should have the proper diagnostics -ok 124 - can(schema) with desc should pass -ok 125 - can(schema) with desc should have the proper description -ok 126 - can(schema) with desc should have the proper diagnostics -ok 127 - can(schema) should pass -ok 128 - can(schema) should have the proper description -ok 129 - can(schema) should have the proper diagnostics -ok 130 - fail can(schema) with desc should fail -ok 131 - fail can(schema) with desc should have the proper description -ok 132 - fail can(schema) with desc should have the proper diagnostics -ok 133 - fail can(someschema) with desc should fail -ok 134 - fail can(someschema) with desc should have the proper description -ok 135 - fail can(someschema) with desc should have the proper diagnostics -ok 136 - can() with desc should pass -ok 137 - can() with desc should have the proper description -ok 138 - can() with desc should have the proper diagnostics -ok 139 - can(schema) should pass -ok 140 - can(schema) should have the proper description -ok 141 - can(schema) should have the proper diagnostics -ok 142 - fail can() with desc should fail -ok 143 - fail can() with desc should have the proper description -ok 144 - fail can() with desc should have the proper diagnostics -ok 145 - function_lang_is(schema, func, 0 args, sql, desc) should pass -ok 146 - function_lang_is(schema, func, 0 args, sql, desc) should have the proper description -ok 147 - function_lang_is(schema, func, 0 args, sql, desc) should have the proper diagnostics -ok 148 - function_lang_is(schema, func, 0 args, sql) should pass -ok 149 - function_lang_is(schema, func, 0 args, sql) should have the proper description -ok 150 - function_lang_is(schema, func, 0 args, sql) should have the proper diagnostics -ok 151 - function_lang_is(schema, func, args, plpgsql, desc) should pass -ok 152 - function_lang_is(schema, func, args, plpgsql, desc) should have the proper description -ok 153 - function_lang_is(schema, func, args, plpgsql, desc) should have the proper diagnostics -ok 154 - function_lang_is(schema, func, args, plpgsql) should pass -ok 155 - function_lang_is(schema, func, args, plpgsql) should have the proper description -ok 156 - function_lang_is(schema, func, args, plpgsql) should have the proper diagnostics -ok 157 - function_lang_is(schema, func, 0 args, perl, desc) should fail -ok 158 - function_lang_is(schema, func, 0 args, perl, desc) should have the proper description -ok 159 - function_lang_is(schema, func, 0 args, perl, desc) should have the proper diagnostics -ok 160 - function_lang_is(schema, non-func, 0 args, sql, desc) should fail -ok 161 - function_lang_is(schema, non-func, 0 args, sql, desc) should have the proper description -ok 162 - function_lang_is(schema, non-func, 0 args, sql, desc) should have the proper diagnostics -ok 163 - function_lang_is(schema, func, args, plpgsql) should fail -ok 164 - function_lang_is(schema, func, args, plpgsql) should have the proper description -ok 165 - function_lang_is(schema, func, args, plpgsql) should have the proper diagnostics -ok 166 - function_lang_is(schema, func, sql, desc) should pass -ok 167 - function_lang_is(schema, func, sql, desc) should have the proper description -ok 168 - function_lang_is(schema, func, sql, desc) should have the proper diagnostics -ok 169 - function_lang_is(schema, func, sql) should pass -ok 170 - function_lang_is(schema, func, sql) should have the proper description -ok 171 - function_lang_is(schema, func, sql) should have the proper diagnostics -ok 172 - function_lang_is(schema, func, perl, desc) should fail -ok 173 - function_lang_is(schema, func, perl, desc) should have the proper description -ok 174 - function_lang_is(schema, func, perl, desc) should have the proper diagnostics -ok 175 - function_lang_is(schema, non-func, sql, desc) should fail -ok 176 - function_lang_is(schema, non-func, sql, desc) should have the proper description -ok 177 - function_lang_is(schema, non-func, sql, desc) should have the proper diagnostics -ok 178 - function_lang_is(func, 0 args, sql, desc) should pass -ok 179 - function_lang_is(func, 0 args, sql, desc) should have the proper description -ok 180 - function_lang_is(func, 0 args, sql, desc) should have the proper diagnostics -ok 181 - function_lang_is(func, 0 args, sql) should pass -ok 182 - function_lang_is(func, 0 args, sql) should have the proper description -ok 183 - function_lang_is(func, 0 args, sql) should have the proper diagnostics -ok 184 - function_lang_is(func, args, plpgsql, desc) should pass -ok 185 - function_lang_is(func, args, plpgsql, desc) should have the proper description -ok 186 - function_lang_is(func, args, plpgsql, desc) should have the proper diagnostics -ok 187 - function_lang_is(func, args, plpgsql) should pass -ok 188 - function_lang_is(func, args, plpgsql) should have the proper description -ok 189 - function_lang_is(func, args, plpgsql) should have the proper diagnostics -ok 190 - function_lang_is(func, 0 args, perl, desc) should fail -ok 191 - function_lang_is(func, 0 args, perl, desc) should have the proper description -ok 192 - function_lang_is(func, 0 args, perl, desc) should have the proper diagnostics -ok 193 - function_lang_is(non-func, 0 args, sql, desc) should fail -ok 194 - function_lang_is(non-func, 0 args, sql, desc) should have the proper description -ok 195 - function_lang_is(non-func, 0 args, sql, desc) should have the proper diagnostics -ok 196 - function_lang_is(func, args, plpgsql) should fail -ok 197 - function_lang_is(func, args, plpgsql) should have the proper description -ok 198 - function_lang_is(func, args, plpgsql) should have the proper diagnostics -ok 199 - function_lang_is(func, sql, desc) should pass -ok 200 - function_lang_is(func, sql, desc) should have the proper description -ok 201 - function_lang_is(func, sql, desc) should have the proper diagnostics -ok 202 - function_lang_is(func, sql) should pass -ok 203 - function_lang_is(func, sql) should have the proper description -ok 204 - function_lang_is(func, sql) should have the proper diagnostics -ok 205 - function_lang_is(func, perl, desc) should fail -ok 206 - function_lang_is(func, perl, desc) should have the proper description -ok 207 - function_lang_is(func, perl, desc) should have the proper diagnostics -ok 208 - function_lang_is(non-func, sql, desc) should fail -ok 209 - function_lang_is(non-func, sql, desc) should have the proper description -ok 210 - function_lang_is(non-func, sql, desc) should have the proper diagnostics -ok 211 - function_returns(schema, func, 0 args, bool, desc) should pass -ok 212 - function_returns(schema, func, 0 args, bool, desc) should have the proper description -ok 213 - function_returns(schema, func, 0 args, bool, desc) should have the proper diagnostics -ok 214 - function_returns(schema, func, 0 args, bool) should pass -ok 215 - function_returns(schema, func, 0 args, bool) should have the proper description -ok 216 - function_returns(schema, func, 0 args, bool) should have the proper diagnostics -ok 217 - function_returns(schema, func, args, bool, false) should pass -ok 218 - function_returns(schema, func, args, bool, false) should have the proper description -ok 219 - function_returns(schema, func, args, bool, false) should have the proper diagnostics -ok 220 - function_returns(schema, func, args, bool) should pass -ok 221 - function_returns(schema, func, args, bool) should have the proper description -ok 222 - function_returns(schema, func, args, bool) should have the proper diagnostics -ok 223 - function_returns(schema, func, 0 args, setof bool, desc) should pass -ok 224 - function_returns(schema, func, 0 args, setof bool, desc) should have the proper description -ok 225 - function_returns(schema, func, 0 args, setof bool, desc) should have the proper diagnostics -ok 226 - function_returns(schema, func, 0 args, setof bool) should pass -ok 227 - function_returns(schema, func, 0 args, setof bool) should have the proper description -ok 228 - function_returns(schema, func, 0 args, setof bool) should have the proper diagnostics -ok 229 - function_returns(schema, func, bool, desc) should pass -ok 230 - function_returns(schema, func, bool, desc) should have the proper description -ok 231 - function_returns(schema, func, bool, desc) should have the proper diagnostics -ok 232 - function_returns(schema, func, bool) should pass -ok 233 - function_returns(schema, func, bool) should have the proper description -ok 234 - function_returns(schema, func, bool) should have the proper diagnostics -ok 235 - function_returns(schema, other func, bool, false) should pass -ok 236 - function_returns(schema, other func, bool, false) should have the proper description -ok 237 - function_returns(schema, other func, bool, false) should have the proper diagnostics -ok 238 - function_returns(schema, other func, bool) should pass -ok 239 - function_returns(schema, other func, bool) should have the proper description -ok 240 - function_returns(schema, other func, bool) should have the proper diagnostics -ok 241 - function_returns(schema, func, setof bool, desc) should pass -ok 242 - function_returns(schema, func, setof bool, desc) should have the proper description -ok 243 - function_returns(schema, func, setof bool, desc) should have the proper diagnostics -ok 244 - function_returns(schema, func, setof bool) should pass -ok 245 - function_returns(schema, func, setof bool) should have the proper description -ok 246 - function_returns(schema, func, setof bool) should have the proper diagnostics -ok 247 - function_returns(func, 0 args, bool, desc) should pass -ok 248 - function_returns(func, 0 args, bool, desc) should have the proper description -ok 249 - function_returns(func, 0 args, bool, desc) should have the proper diagnostics -ok 250 - function_returns(func, 0 args, bool) should pass -ok 251 - function_returns(func, 0 args, bool) should have the proper description -ok 252 - function_returns(func, 0 args, bool) should have the proper diagnostics -ok 253 - function_returns(func, args, bool, false) should pass -ok 254 - function_returns(func, args, bool, false) should have the proper description -ok 255 - function_returns(func, args, bool, false) should have the proper diagnostics -ok 256 - function_returns(func, args, bool) should pass -ok 257 - function_returns(func, args, bool) should have the proper description -ok 258 - function_returns(func, args, bool) should have the proper diagnostics -ok 259 - function_returns(func, 0 args, setof bool, desc) should pass -ok 260 - function_returns(func, 0 args, setof bool, desc) should have the proper description -ok 261 - function_returns(func, 0 args, setof bool, desc) should have the proper diagnostics -ok 262 - function_returns(func, 0 args, setof bool) should pass -ok 263 - function_returns(func, 0 args, setof bool) should have the proper description -ok 264 - function_returns(func, 0 args, setof bool) should have the proper diagnostics -ok 265 - function_returns(func, bool, desc) should pass -ok 266 - function_returns(func, bool, desc) should have the proper description -ok 267 - function_returns(func, bool, desc) should have the proper diagnostics -ok 268 - function_returns(func, bool) should pass -ok 269 - function_returns(func, bool) should have the proper description -ok 270 - function_returns(func, bool) should have the proper diagnostics -ok 271 - function_returns(other func, bool, false) should pass -ok 272 - function_returns(other func, bool, false) should have the proper description -ok 273 - function_returns(other func, bool, false) should have the proper diagnostics -ok 274 - function_returns(other func, bool) should pass -ok 275 - function_returns(other func, bool) should have the proper description -ok 276 - function_returns(other func, bool) should have the proper diagnostics -ok 277 - function_returns(func, setof bool, desc) should pass -ok 278 - function_returns(func, setof bool, desc) should have the proper description -ok 279 - function_returns(func, setof bool, desc) should have the proper diagnostics -ok 280 - function_returns(func, setof bool) should pass -ok 281 - function_returns(func, setof bool) should have the proper description -ok 282 - function_returns(func, setof bool) should have the proper diagnostics -ok 283 - is_definer(schema, func, 0 args, desc) should pass -ok 284 - is_definer(schema, func, 0 args, desc) should have the proper description -ok 285 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics -ok 286 - is_definer(schema, func, 0 args) should pass -ok 287 - is_definer(schema, func, 0 args) should have the proper description -ok 288 - is_definer(schema, func, 0 args) should have the proper diagnostics -ok 289 - is_definer(schema, func, args, desc) should fail -ok 290 - is_definer(schema, func, args, desc) should have the proper description -ok 291 - is_definer(schema, func, args, desc) should have the proper diagnostics -ok 292 - is_definer(schema, func, args) should fail -ok 293 - is_definer(schema, func, args) should have the proper description -ok 294 - is_definer(schema, func, args) should have the proper diagnostics -ok 295 - is_definer(schema, func, desc) should pass -ok 296 - is_definer(schema, func, desc) should have the proper description -ok 297 - is_definer(schema, func, desc) should have the proper diagnostics -ok 298 - is_definer(schema, func) should pass -ok 299 - is_definer(schema, func) should have the proper description -ok 300 - is_definer(schema, func) should have the proper diagnostics -ok 301 - is_definer(schema, func, 0 args, desc) should pass -ok 302 - is_definer(schema, func, 0 args, desc) should have the proper description -ok 303 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics -ok 304 - is_definer(schema, func, 0 args) should pass -ok 305 - is_definer(schema, func, 0 args) should have the proper description -ok 306 - is_definer(schema, func, 0 args) should have the proper diagnostics -ok 307 - is_definer(schema, func, args, desc) should fail -ok 308 - is_definer(schema, func, args, desc) should have the proper description -ok 309 - is_definer(schema, func, args, desc) should have the proper diagnostics -ok 310 - is_definer(schema, func, args) should fail -ok 311 - is_definer(schema, func, args) should have the proper description -ok 312 - is_definer(schema, func, args) should have the proper diagnostics -ok 313 - is_definer(schema, func, desc) should pass -ok 314 - is_definer(schema, func, desc) should have the proper description -ok 315 - is_definer(schema, func, desc) should have the proper diagnostics -ok 316 - is_definer(schema, func) should pass -ok 317 - is_definer(schema, func) should have the proper description -ok 318 - is_definer(schema, func) should have the proper diagnostics -ok 319 - is_definer(func, 0 args, desc) should pass -ok 320 - is_definer(func, 0 args, desc) should have the proper description -ok 321 - is_definer(func, 0 args, desc) should have the proper diagnostics -ok 322 - is_definer(func, 0 args) should pass -ok 323 - is_definer(func, 0 args) should have the proper description -ok 324 - is_definer(func, 0 args) should have the proper diagnostics -ok 325 - is_definer(func, args, desc) should fail -ok 326 - is_definer(func, args, desc) should have the proper description -ok 327 - is_definer(func, args, desc) should have the proper diagnostics -ok 328 - is_definer(func, args) should fail -ok 329 - is_definer(func, args) should have the proper description -ok 330 - is_definer(func, args) should have the proper diagnostics -ok 331 - is_definer(func, desc) should pass -ok 332 - is_definer(func, desc) should have the proper description -ok 333 - is_definer(func, desc) should have the proper diagnostics -ok 334 - is_definer(func) should pass -ok 335 - is_definer(func) should have the proper description -ok 336 - is_definer(func) should have the proper diagnostics -ok 337 - is_aggregate(schema, func, arg, desc) should pass -ok 338 - is_aggregate(schema, func, arg, desc) should have the proper description -ok 339 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics -ok 340 - is_aggregate(schema, func, arg) should pass -ok 341 - is_aggregate(schema, func, arg) should have the proper description -ok 342 - is_aggregate(schema, func, arg) should have the proper diagnostics -ok 343 - is_aggregate(schema, func, args, desc) should fail -ok 344 - is_aggregate(schema, func, args, desc) should have the proper description -ok 345 - is_aggregate(schema, func, args, desc) should have the proper diagnostics -ok 346 - is_aggregate(schema, func, args) should fail -ok 347 - is_aggregate(schema, func, args) should have the proper description -ok 348 - is_aggregate(schema, func, args) should have the proper diagnostics -ok 349 - is_aggregate(schema, func, desc) should pass -ok 350 - is_aggregate(schema, func, desc) should have the proper description -ok 351 - is_aggregate(schema, func, desc) should have the proper diagnostics -ok 352 - is_aggregate(schema, func) should pass -ok 353 - is_aggregate(schema, func) should have the proper description -ok 354 - is_aggregate(schema, func) should have the proper diagnostics -ok 355 - is_aggregate(schema, func, arg, desc) should pass -ok 356 - is_aggregate(schema, func, arg, desc) should have the proper description -ok 357 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics -ok 358 - is_aggregate(schema, func, arg) should pass -ok 359 - is_aggregate(schema, func, arg) should have the proper description -ok 360 - is_aggregate(schema, func, arg) should have the proper diagnostics -ok 361 - is_aggregate(schema, func, args, desc) should fail -ok 362 - is_aggregate(schema, func, args, desc) should have the proper description -ok 363 - is_aggregate(schema, func, args, desc) should have the proper diagnostics -ok 364 - is_aggregate(schema, func, args) should fail -ok 365 - is_aggregate(schema, func, args) should have the proper description -ok 366 - is_aggregate(schema, func, args) should have the proper diagnostics -ok 367 - is_aggregate(schema, func, desc) should pass -ok 368 - is_aggregate(schema, func, desc) should have the proper description -ok 369 - is_aggregate(schema, func, desc) should have the proper diagnostics -ok 370 - is_aggregate(schema, func) should pass -ok 371 - is_aggregate(schema, func) should have the proper description -ok 372 - is_aggregate(schema, func) should have the proper diagnostics -ok 373 - is_aggregate(func, arg, desc) should pass -ok 374 - is_aggregate(func, arg, desc) should have the proper description -ok 375 - is_aggregate(func, arg, desc) should have the proper diagnostics -ok 376 - is_aggregate(func, arg) should pass -ok 377 - is_aggregate(func, arg) should have the proper description -ok 378 - is_aggregate(func, arg) should have the proper diagnostics -ok 379 - is_aggregate(func, args, desc) should fail -ok 380 - is_aggregate(func, args, desc) should have the proper description -ok 381 - is_aggregate(func, args, desc) should have the proper diagnostics -ok 382 - is_aggregate(func, args) should fail -ok 383 - is_aggregate(func, args) should have the proper description -ok 384 - is_aggregate(func, args) should have the proper diagnostics -ok 385 - is_aggregate(func, desc) should pass -ok 386 - is_aggregate(func, desc) should have the proper description -ok 387 - is_aggregate(func, desc) should have the proper diagnostics -ok 388 - is_aggregate(func) should pass -ok 389 - is_aggregate(func) should have the proper description -ok 390 - is_aggregate(func) should have the proper diagnostics -ok 391 - is_strict(schema, func, 0 args, desc) should pass -ok 392 - is_strict(schema, func, 0 args, desc) should have the proper description -ok 393 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 394 - is_strict(schema, func, 0 args) should pass -ok 395 - is_strict(schema, func, 0 args) should have the proper description -ok 396 - is_strict(schema, func, 0 args) should have the proper diagnostics -ok 397 - is_strict(schema, func, args, desc) should fail -ok 398 - is_strict(schema, func, args, desc) should have the proper description -ok 399 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 400 - is_strict(schema, func, args) should fail -ok 401 - is_strict(schema, func, args) should have the proper description -ok 402 - is_strict(schema, func, args) should have the proper diagnostics -ok 403 - is_strict(schema, func, desc) should pass -ok 404 - is_strict(schema, func, desc) should have the proper description -ok 405 - is_strict(schema, func, desc) should have the proper diagnostics -ok 406 - is_strict(schema, func) should pass -ok 407 - is_strict(schema, func) should have the proper description -ok 408 - is_strict(schema, func) should have the proper diagnostics -ok 409 - is_strict(schema, func, 0 args, desc) should pass -ok 410 - is_strict(schema, func, 0 args, desc) should have the proper description -ok 411 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 412 - is_strict(schema, func, 0 args) should pass -ok 413 - is_strict(schema, func, 0 args) should have the proper description -ok 414 - is_strict(schema, func, 0 args) should have the proper diagnostics -ok 415 - is_strict(schema, func, args, desc) should fail -ok 416 - is_strict(schema, func, args, desc) should have the proper description -ok 417 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 418 - is_strict(schema, func, args) should fail -ok 419 - is_strict(schema, func, args) should have the proper description -ok 420 - is_strict(schema, func, args) should have the proper diagnostics -ok 421 - is_strict(schema, func, desc) should pass -ok 422 - is_strict(schema, func, desc) should have the proper description -ok 423 - is_strict(schema, func, desc) should have the proper diagnostics -ok 424 - is_strict(schema, func) should pass -ok 425 - is_strict(schema, func) should have the proper description -ok 426 - is_strict(schema, func) should have the proper diagnostics -ok 427 - is_strict(func, 0 args, desc) should pass -ok 428 - is_strict(func, 0 args, desc) should have the proper description -ok 429 - is_strict(func, 0 args, desc) should have the proper diagnostics -ok 430 - is_strict(func, 0 args) should pass -ok 431 - is_strict(func, 0 args) should have the proper description -ok 432 - is_strict(func, 0 args) should have the proper diagnostics -ok 433 - is_strict(func, args, desc) should fail -ok 434 - is_strict(func, args, desc) should have the proper description -ok 435 - is_strict(func, args, desc) should have the proper diagnostics -ok 436 - is_strict(func, args) should fail -ok 437 - is_strict(func, args) should have the proper description -ok 438 - is_strict(func, args) should have the proper diagnostics -ok 439 - is_strict(func, desc) should pass -ok 440 - is_strict(func, desc) should have the proper description -ok 441 - is_strict(func, desc) should have the proper diagnostics -ok 442 - is_strict(func) should pass -ok 443 - is_strict(func) should have the proper description -ok 444 - is_strict(func) should have the proper diagnostics -ok 445 - function_volatility(schema, func, 0 args, volatile, desc) should pass -ok 446 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper description -ok 447 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper diagnostics -ok 448 - function_volatility(schema, func, 0 args, VOLATILE, desc) should pass -ok 449 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper description -ok 450 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper diagnostics -ok 451 - function_volatility(schema, func, 0 args, v, desc) should pass -ok 452 - function_volatility(schema, func, 0 args, v, desc) should have the proper description -ok 453 - function_volatility(schema, func, 0 args, v, desc) should have the proper diagnostics -ok 454 - function_volatility(schema, func, args, immutable, desc) should pass -ok 455 - function_volatility(schema, func, args, immutable, desc) should have the proper description -ok 456 - function_volatility(schema, func, args, immutable, desc) should have the proper diagnostics -ok 457 - function_volatility(schema, func, 0 args, stable, desc) should pass -ok 458 - function_volatility(schema, func, 0 args, stable, desc) should have the proper description -ok 459 - function_volatility(schema, func, 0 args, stable, desc) should have the proper diagnostics -ok 460 - function_volatility(schema, func, 0 args, volatile) should pass -ok 461 - function_volatility(schema, func, 0 args, volatile) should have the proper description -ok 462 - function_volatility(schema, func, 0 args, volatile) should have the proper diagnostics -ok 463 - function_volatility(schema, func, args, immutable) should pass -ok 464 - function_volatility(schema, func, args, immutable) should have the proper description -ok 465 - function_volatility(schema, func, volatile, desc) should pass -ok 466 - function_volatility(schema, func, volatile, desc) should have the proper description -ok 467 - function_volatility(schema, func, volatile, desc) should have the proper diagnostics -ok 468 - function_volatility(schema, func, volatile) should pass -ok 469 - function_volatility(schema, func, volatile) should have the proper description -ok 470 - function_volatility(schema, func, volatile) should have the proper diagnostics -ok 471 - function_volatility(schema, func, immutable, desc) should pass -ok 472 - function_volatility(schema, func, immutable, desc) should have the proper description -ok 473 - function_volatility(schema, func, immutable, desc) should have the proper diagnostics -ok 474 - function_volatility(schema, func, stable, desc) should pass -ok 475 - function_volatility(schema, func, stable, desc) should have the proper description -ok 476 - function_volatility(schema, func, stable, desc) should have the proper diagnostics -ok 477 - function_volatility(func, 0 args, volatile, desc) should pass -ok 478 - function_volatility(func, 0 args, volatile, desc) should have the proper description -ok 479 - function_volatility(func, 0 args, volatile, desc) should have the proper diagnostics -ok 480 - function_volatility(func, 0 args, VOLATILE, desc) should pass -ok 481 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper description -ok 482 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper diagnostics -ok 483 - function_volatility(func, 0 args, v, desc) should pass -ok 484 - function_volatility(func, 0 args, v, desc) should have the proper description -ok 485 - function_volatility(func, 0 args, v, desc) should have the proper diagnostics -ok 486 - function_volatility(func, args, immutable, desc) should pass -ok 487 - function_volatility(func, args, immutable, desc) should have the proper description -ok 488 - function_volatility(func, args, immutable, desc) should have the proper diagnostics -ok 489 - function_volatility(func, 0 args, stable, desc) should pass -ok 490 - function_volatility(func, 0 args, stable, desc) should have the proper description -ok 491 - function_volatility(func, 0 args, stable, desc) should have the proper diagnostics -ok 492 - function_volatility(func, 0 args, volatile) should pass -ok 493 - function_volatility(func, 0 args, volatile) should have the proper description -ok 494 - function_volatility(func, 0 args, volatile) should have the proper diagnostics -ok 495 - function_volatility(func, args, immutable) should pass -ok 496 - function_volatility(func, args, immutable) should have the proper description -ok 497 - function_volatility(func, volatile, desc) should pass -ok 498 - function_volatility(func, volatile, desc) should have the proper description -ok 499 - function_volatility(func, volatile, desc) should have the proper diagnostics -ok 500 - function_volatility(func, volatile) should pass -ok 501 - function_volatility(func, volatile) should have the proper description -ok 502 - function_volatility(func, volatile) should have the proper diagnostics -ok 503 - function_volatility(func, immutable, desc) should pass -ok 504 - function_volatility(func, immutable, desc) should have the proper description -ok 505 - function_volatility(func, immutable, desc) should have the proper diagnostics -ok 506 - function_volatility(func, stable, desc) should pass -ok 507 - function_volatility(func, stable, desc) should have the proper description -ok 508 - function_volatility(func, stable, desc) should have the proper diagnostics +ok 82 - can(schema) with desc should pass +ok 83 - can(schema) with desc should have the proper description +ok 84 - can(schema) with desc should have the proper diagnostics +ok 85 - can(schema) should pass +ok 86 - can(schema) should have the proper description +ok 87 - can(schema) should have the proper diagnostics +ok 88 - fail can(schema) with desc should fail +ok 89 - fail can(schema) with desc should have the proper description +ok 90 - fail can(schema) with desc should have the proper diagnostics +ok 91 - fail can(someschema) with desc should fail +ok 92 - fail can(someschema) with desc should have the proper description +ok 93 - fail can(someschema) with desc should have the proper diagnostics +ok 94 - can() with desc should pass +ok 95 - can() with desc should have the proper description +ok 96 - can() with desc should have the proper diagnostics +ok 97 - can(schema) should pass +ok 98 - can(schema) should have the proper description +ok 99 - can(schema) should have the proper diagnostics +ok 100 - fail can() with desc should fail +ok 101 - fail can() with desc should have the proper description +ok 102 - fail can() with desc should have the proper diagnostics +ok 103 - function_lang_is(schema, func, 0 args, sql, desc) should pass +ok 104 - function_lang_is(schema, func, 0 args, sql, desc) should have the proper description +ok 105 - function_lang_is(schema, func, 0 args, sql, desc) should have the proper diagnostics +ok 106 - function_lang_is(schema, func, 0 args, sql) should pass +ok 107 - function_lang_is(schema, func, 0 args, sql) should have the proper description +ok 108 - function_lang_is(schema, func, 0 args, sql) should have the proper diagnostics +ok 109 - function_lang_is(schema, func, args, plpgsql, desc) should pass +ok 110 - function_lang_is(schema, func, args, plpgsql, desc) should have the proper description +ok 111 - function_lang_is(schema, func, args, plpgsql, desc) should have the proper diagnostics +ok 112 - function_lang_is(schema, func, args, plpgsql) should pass +ok 113 - function_lang_is(schema, func, args, plpgsql) should have the proper description +ok 114 - function_lang_is(schema, func, args, plpgsql) should have the proper diagnostics +ok 115 - function_lang_is(schema, func, 0 args, perl, desc) should fail +ok 116 - function_lang_is(schema, func, 0 args, perl, desc) should have the proper description +ok 117 - function_lang_is(schema, func, 0 args, perl, desc) should have the proper diagnostics +ok 118 - function_lang_is(schema, non-func, 0 args, sql, desc) should fail +ok 119 - function_lang_is(schema, non-func, 0 args, sql, desc) should have the proper description +ok 120 - function_lang_is(schema, non-func, 0 args, sql, desc) should have the proper diagnostics +ok 121 - function_lang_is(schema, func, args, plpgsql) should fail +ok 122 - function_lang_is(schema, func, args, plpgsql) should have the proper description +ok 123 - function_lang_is(schema, func, args, plpgsql) should have the proper diagnostics +ok 124 - function_lang_is(schema, func, sql, desc) should pass +ok 125 - function_lang_is(schema, func, sql, desc) should have the proper description +ok 126 - function_lang_is(schema, func, sql, desc) should have the proper diagnostics +ok 127 - function_lang_is(schema, func, sql) should pass +ok 128 - function_lang_is(schema, func, sql) should have the proper description +ok 129 - function_lang_is(schema, func, sql) should have the proper diagnostics +ok 130 - function_lang_is(schema, func, perl, desc) should fail +ok 131 - function_lang_is(schema, func, perl, desc) should have the proper description +ok 132 - function_lang_is(schema, func, perl, desc) should have the proper diagnostics +ok 133 - function_lang_is(schema, non-func, sql, desc) should fail +ok 134 - function_lang_is(schema, non-func, sql, desc) should have the proper description +ok 135 - function_lang_is(schema, non-func, sql, desc) should have the proper diagnostics +ok 136 - function_lang_is(func, 0 args, sql, desc) should pass +ok 137 - function_lang_is(func, 0 args, sql, desc) should have the proper description +ok 138 - function_lang_is(func, 0 args, sql, desc) should have the proper diagnostics +ok 139 - function_lang_is(func, 0 args, sql) should pass +ok 140 - function_lang_is(func, 0 args, sql) should have the proper description +ok 141 - function_lang_is(func, 0 args, sql) should have the proper diagnostics +ok 142 - function_lang_is(func, args, plpgsql, desc) should pass +ok 143 - function_lang_is(func, args, plpgsql, desc) should have the proper description +ok 144 - function_lang_is(func, args, plpgsql, desc) should have the proper diagnostics +ok 145 - function_lang_is(func, args, plpgsql) should pass +ok 146 - function_lang_is(func, args, plpgsql) should have the proper description +ok 147 - function_lang_is(func, args, plpgsql) should have the proper diagnostics +ok 148 - function_lang_is(func, 0 args, perl, desc) should fail +ok 149 - function_lang_is(func, 0 args, perl, desc) should have the proper description +ok 150 - function_lang_is(func, 0 args, perl, desc) should have the proper diagnostics +ok 151 - function_lang_is(non-func, 0 args, sql, desc) should fail +ok 152 - function_lang_is(non-func, 0 args, sql, desc) should have the proper description +ok 153 - function_lang_is(non-func, 0 args, sql, desc) should have the proper diagnostics +ok 154 - function_lang_is(func, args, plpgsql) should fail +ok 155 - function_lang_is(func, args, plpgsql) should have the proper description +ok 156 - function_lang_is(func, args, plpgsql) should have the proper diagnostics +ok 157 - function_lang_is(func, sql, desc) should pass +ok 158 - function_lang_is(func, sql, desc) should have the proper description +ok 159 - function_lang_is(func, sql, desc) should have the proper diagnostics +ok 160 - function_lang_is(func, sql) should pass +ok 161 - function_lang_is(func, sql) should have the proper description +ok 162 - function_lang_is(func, sql) should have the proper diagnostics +ok 163 - function_lang_is(func, perl, desc) should fail +ok 164 - function_lang_is(func, perl, desc) should have the proper description +ok 165 - function_lang_is(func, perl, desc) should have the proper diagnostics +ok 166 - function_lang_is(non-func, sql, desc) should fail +ok 167 - function_lang_is(non-func, sql, desc) should have the proper description +ok 168 - function_lang_is(non-func, sql, desc) should have the proper diagnostics +ok 169 - function_returns(schema, func, 0 args, bool, desc) should pass +ok 170 - function_returns(schema, func, 0 args, bool, desc) should have the proper description +ok 171 - function_returns(schema, func, 0 args, bool, desc) should have the proper diagnostics +ok 172 - function_returns(schema, func, 0 args, bool) should pass +ok 173 - function_returns(schema, func, 0 args, bool) should have the proper description +ok 174 - function_returns(schema, func, 0 args, bool) should have the proper diagnostics +ok 175 - function_returns(schema, func, args, bool, false) should pass +ok 176 - function_returns(schema, func, args, bool, false) should have the proper description +ok 177 - function_returns(schema, func, args, bool, false) should have the proper diagnostics +ok 178 - function_returns(schema, func, args, bool) should pass +ok 179 - function_returns(schema, func, args, bool) should have the proper description +ok 180 - function_returns(schema, func, args, bool) should have the proper diagnostics +ok 181 - function_returns(schema, func, 0 args, setof bool, desc) should pass +ok 182 - function_returns(schema, func, 0 args, setof bool, desc) should have the proper description +ok 183 - function_returns(schema, func, 0 args, setof bool, desc) should have the proper diagnostics +ok 184 - function_returns(schema, func, 0 args, setof bool) should pass +ok 185 - function_returns(schema, func, 0 args, setof bool) should have the proper description +ok 186 - function_returns(schema, func, 0 args, setof bool) should have the proper diagnostics +ok 187 - function_returns(schema, func, bool, desc) should pass +ok 188 - function_returns(schema, func, bool, desc) should have the proper description +ok 189 - function_returns(schema, func, bool, desc) should have the proper diagnostics +ok 190 - function_returns(schema, func, bool) should pass +ok 191 - function_returns(schema, func, bool) should have the proper description +ok 192 - function_returns(schema, func, bool) should have the proper diagnostics +ok 193 - function_returns(schema, other func, bool, false) should pass +ok 194 - function_returns(schema, other func, bool, false) should have the proper description +ok 195 - function_returns(schema, other func, bool, false) should have the proper diagnostics +ok 196 - function_returns(schema, other func, bool) should pass +ok 197 - function_returns(schema, other func, bool) should have the proper description +ok 198 - function_returns(schema, other func, bool) should have the proper diagnostics +ok 199 - function_returns(schema, func, setof bool, desc) should pass +ok 200 - function_returns(schema, func, setof bool, desc) should have the proper description +ok 201 - function_returns(schema, func, setof bool, desc) should have the proper diagnostics +ok 202 - function_returns(schema, func, setof bool) should pass +ok 203 - function_returns(schema, func, setof bool) should have the proper description +ok 204 - function_returns(schema, func, setof bool) should have the proper diagnostics +ok 205 - function_returns(func, 0 args, bool, desc) should pass +ok 206 - function_returns(func, 0 args, bool, desc) should have the proper description +ok 207 - function_returns(func, 0 args, bool, desc) should have the proper diagnostics +ok 208 - function_returns(func, 0 args, bool) should pass +ok 209 - function_returns(func, 0 args, bool) should have the proper description +ok 210 - function_returns(func, 0 args, bool) should have the proper diagnostics +ok 211 - function_returns(func, args, bool, false) should pass +ok 212 - function_returns(func, args, bool, false) should have the proper description +ok 213 - function_returns(func, args, bool, false) should have the proper diagnostics +ok 214 - function_returns(func, args, bool) should pass +ok 215 - function_returns(func, args, bool) should have the proper description +ok 216 - function_returns(func, args, bool) should have the proper diagnostics +ok 217 - function_returns(func, 0 args, setof bool, desc) should pass +ok 218 - function_returns(func, 0 args, setof bool, desc) should have the proper description +ok 219 - function_returns(func, 0 args, setof bool, desc) should have the proper diagnostics +ok 220 - function_returns(func, 0 args, setof bool) should pass +ok 221 - function_returns(func, 0 args, setof bool) should have the proper description +ok 222 - function_returns(func, 0 args, setof bool) should have the proper diagnostics +ok 223 - function_returns(func, bool, desc) should pass +ok 224 - function_returns(func, bool, desc) should have the proper description +ok 225 - function_returns(func, bool, desc) should have the proper diagnostics +ok 226 - function_returns(func, bool) should pass +ok 227 - function_returns(func, bool) should have the proper description +ok 228 - function_returns(func, bool) should have the proper diagnostics +ok 229 - function_returns(other func, bool, false) should pass +ok 230 - function_returns(other func, bool, false) should have the proper description +ok 231 - function_returns(other func, bool, false) should have the proper diagnostics +ok 232 - function_returns(other func, bool) should pass +ok 233 - function_returns(other func, bool) should have the proper description +ok 234 - function_returns(other func, bool) should have the proper diagnostics +ok 235 - function_returns(func, setof bool, desc) should pass +ok 236 - function_returns(func, setof bool, desc) should have the proper description +ok 237 - function_returns(func, setof bool, desc) should have the proper diagnostics +ok 238 - function_returns(func, setof bool) should pass +ok 239 - function_returns(func, setof bool) should have the proper description +ok 240 - function_returns(func, setof bool) should have the proper diagnostics +ok 241 - is_definer(schema, func, 0 args, desc) should pass +ok 242 - is_definer(schema, func, 0 args, desc) should have the proper description +ok 243 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 244 - is_definer(schema, func, 0 args) should pass +ok 245 - is_definer(schema, func, 0 args) should have the proper description +ok 246 - is_definer(schema, func, 0 args) should have the proper diagnostics +ok 247 - is_definer(schema, func, args, desc) should fail +ok 248 - is_definer(schema, func, args, desc) should have the proper description +ok 249 - is_definer(schema, func, args, desc) should have the proper diagnostics +ok 250 - is_definer(schema, func, args) should fail +ok 251 - is_definer(schema, func, args) should have the proper description +ok 252 - is_definer(schema, func, args) should have the proper diagnostics +ok 253 - is_definer(schema, func, desc) should pass +ok 254 - is_definer(schema, func, desc) should have the proper description +ok 255 - is_definer(schema, func, desc) should have the proper diagnostics +ok 256 - is_definer(schema, func) should pass +ok 257 - is_definer(schema, func) should have the proper description +ok 258 - is_definer(schema, func) should have the proper diagnostics +ok 259 - is_definer(schema, func, 0 args, desc) should pass +ok 260 - is_definer(schema, func, 0 args, desc) should have the proper description +ok 261 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 262 - is_definer(schema, func, 0 args) should pass +ok 263 - is_definer(schema, func, 0 args) should have the proper description +ok 264 - is_definer(schema, func, 0 args) should have the proper diagnostics +ok 265 - is_definer(schema, func, args, desc) should fail +ok 266 - is_definer(schema, func, args, desc) should have the proper description +ok 267 - is_definer(schema, func, args, desc) should have the proper diagnostics +ok 268 - is_definer(schema, func, args) should fail +ok 269 - is_definer(schema, func, args) should have the proper description +ok 270 - is_definer(schema, func, args) should have the proper diagnostics +ok 271 - is_definer(schema, func, desc) should pass +ok 272 - is_definer(schema, func, desc) should have the proper description +ok 273 - is_definer(schema, func, desc) should have the proper diagnostics +ok 274 - is_definer(schema, func) should pass +ok 275 - is_definer(schema, func) should have the proper description +ok 276 - is_definer(schema, func) should have the proper diagnostics +ok 277 - is_definer(func, 0 args, desc) should pass +ok 278 - is_definer(func, 0 args, desc) should have the proper description +ok 279 - is_definer(func, 0 args, desc) should have the proper diagnostics +ok 280 - is_definer(func, 0 args) should pass +ok 281 - is_definer(func, 0 args) should have the proper description +ok 282 - is_definer(func, 0 args) should have the proper diagnostics +ok 283 - is_definer(func, args, desc) should fail +ok 284 - is_definer(func, args, desc) should have the proper description +ok 285 - is_definer(func, args, desc) should have the proper diagnostics +ok 286 - is_definer(func, args) should fail +ok 287 - is_definer(func, args) should have the proper description +ok 288 - is_definer(func, args) should have the proper diagnostics +ok 289 - is_definer(func, desc) should pass +ok 290 - is_definer(func, desc) should have the proper description +ok 291 - is_definer(func, desc) should have the proper diagnostics +ok 292 - is_definer(func) should pass +ok 293 - is_definer(func) should have the proper description +ok 294 - is_definer(func) should have the proper diagnostics +ok 295 - is_aggregate(schema, func, arg, desc) should pass +ok 296 - is_aggregate(schema, func, arg, desc) should have the proper description +ok 297 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 298 - is_aggregate(schema, func, arg) should pass +ok 299 - is_aggregate(schema, func, arg) should have the proper description +ok 300 - is_aggregate(schema, func, arg) should have the proper diagnostics +ok 301 - is_aggregate(schema, func, args, desc) should fail +ok 302 - is_aggregate(schema, func, args, desc) should have the proper description +ok 303 - is_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 304 - is_aggregate(schema, func, args) should fail +ok 305 - is_aggregate(schema, func, args) should have the proper description +ok 306 - is_aggregate(schema, func, args) should have the proper diagnostics +ok 307 - is_aggregate(schema, func, desc) should pass +ok 308 - is_aggregate(schema, func, desc) should have the proper description +ok 309 - is_aggregate(schema, func, desc) should have the proper diagnostics +ok 310 - is_aggregate(schema, func) should pass +ok 311 - is_aggregate(schema, func) should have the proper description +ok 312 - is_aggregate(schema, func) should have the proper diagnostics +ok 313 - is_aggregate(schema, func, arg, desc) should pass +ok 314 - is_aggregate(schema, func, arg, desc) should have the proper description +ok 315 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 316 - is_aggregate(schema, func, arg) should pass +ok 317 - is_aggregate(schema, func, arg) should have the proper description +ok 318 - is_aggregate(schema, func, arg) should have the proper diagnostics +ok 319 - is_aggregate(schema, func, args, desc) should fail +ok 320 - is_aggregate(schema, func, args, desc) should have the proper description +ok 321 - is_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 322 - is_aggregate(schema, func, args) should fail +ok 323 - is_aggregate(schema, func, args) should have the proper description +ok 324 - is_aggregate(schema, func, args) should have the proper diagnostics +ok 325 - is_aggregate(schema, func, desc) should pass +ok 326 - is_aggregate(schema, func, desc) should have the proper description +ok 327 - is_aggregate(schema, func, desc) should have the proper diagnostics +ok 328 - is_aggregate(schema, func) should pass +ok 329 - is_aggregate(schema, func) should have the proper description +ok 330 - is_aggregate(schema, func) should have the proper diagnostics +ok 331 - is_aggregate(func, arg, desc) should pass +ok 332 - is_aggregate(func, arg, desc) should have the proper description +ok 333 - is_aggregate(func, arg, desc) should have the proper diagnostics +ok 334 - is_aggregate(func, arg) should pass +ok 335 - is_aggregate(func, arg) should have the proper description +ok 336 - is_aggregate(func, arg) should have the proper diagnostics +ok 337 - is_aggregate(func, args, desc) should fail +ok 338 - is_aggregate(func, args, desc) should have the proper description +ok 339 - is_aggregate(func, args, desc) should have the proper diagnostics +ok 340 - is_aggregate(func, args) should fail +ok 341 - is_aggregate(func, args) should have the proper description +ok 342 - is_aggregate(func, args) should have the proper diagnostics +ok 343 - is_aggregate(func, desc) should pass +ok 344 - is_aggregate(func, desc) should have the proper description +ok 345 - is_aggregate(func, desc) should have the proper diagnostics +ok 346 - is_aggregate(func) should pass +ok 347 - is_aggregate(func) should have the proper description +ok 348 - is_aggregate(func) should have the proper diagnostics +ok 349 - is_strict(schema, func, 0 args, desc) should pass +ok 350 - is_strict(schema, func, 0 args, desc) should have the proper description +ok 351 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 352 - is_strict(schema, func, 0 args) should pass +ok 353 - is_strict(schema, func, 0 args) should have the proper description +ok 354 - is_strict(schema, func, 0 args) should have the proper diagnostics +ok 355 - is_strict(schema, func, args, desc) should fail +ok 356 - is_strict(schema, func, args, desc) should have the proper description +ok 357 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 358 - is_strict(schema, func, args) should fail +ok 359 - is_strict(schema, func, args) should have the proper description +ok 360 - is_strict(schema, func, args) should have the proper diagnostics +ok 361 - is_strict(schema, func, desc) should pass +ok 362 - is_strict(schema, func, desc) should have the proper description +ok 363 - is_strict(schema, func, desc) should have the proper diagnostics +ok 364 - is_strict(schema, func) should pass +ok 365 - is_strict(schema, func) should have the proper description +ok 366 - is_strict(schema, func) should have the proper diagnostics +ok 367 - is_strict(schema, func, 0 args, desc) should pass +ok 368 - is_strict(schema, func, 0 args, desc) should have the proper description +ok 369 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 370 - is_strict(schema, func, 0 args) should pass +ok 371 - is_strict(schema, func, 0 args) should have the proper description +ok 372 - is_strict(schema, func, 0 args) should have the proper diagnostics +ok 373 - is_strict(schema, func, args, desc) should fail +ok 374 - is_strict(schema, func, args, desc) should have the proper description +ok 375 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 376 - is_strict(schema, func, args) should fail +ok 377 - is_strict(schema, func, args) should have the proper description +ok 378 - is_strict(schema, func, args) should have the proper diagnostics +ok 379 - is_strict(schema, func, desc) should pass +ok 380 - is_strict(schema, func, desc) should have the proper description +ok 381 - is_strict(schema, func, desc) should have the proper diagnostics +ok 382 - is_strict(schema, func) should pass +ok 383 - is_strict(schema, func) should have the proper description +ok 384 - is_strict(schema, func) should have the proper diagnostics +ok 385 - is_strict(func, 0 args, desc) should pass +ok 386 - is_strict(func, 0 args, desc) should have the proper description +ok 387 - is_strict(func, 0 args, desc) should have the proper diagnostics +ok 388 - is_strict(func, 0 args) should pass +ok 389 - is_strict(func, 0 args) should have the proper description +ok 390 - is_strict(func, 0 args) should have the proper diagnostics +ok 391 - is_strict(func, args, desc) should fail +ok 392 - is_strict(func, args, desc) should have the proper description +ok 393 - is_strict(func, args, desc) should have the proper diagnostics +ok 394 - is_strict(func, args) should fail +ok 395 - is_strict(func, args) should have the proper description +ok 396 - is_strict(func, args) should have the proper diagnostics +ok 397 - is_strict(func, desc) should pass +ok 398 - is_strict(func, desc) should have the proper description +ok 399 - is_strict(func, desc) should have the proper diagnostics +ok 400 - is_strict(func) should pass +ok 401 - is_strict(func) should have the proper description +ok 402 - is_strict(func) should have the proper diagnostics +ok 403 - function_volatility(schema, func, 0 args, volatile, desc) should pass +ok 404 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper description +ok 405 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper diagnostics +ok 406 - function_volatility(schema, func, 0 args, VOLATILE, desc) should pass +ok 407 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper description +ok 408 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper diagnostics +ok 409 - function_volatility(schema, func, 0 args, v, desc) should pass +ok 410 - function_volatility(schema, func, 0 args, v, desc) should have the proper description +ok 411 - function_volatility(schema, func, 0 args, v, desc) should have the proper diagnostics +ok 412 - function_volatility(schema, func, args, immutable, desc) should pass +ok 413 - function_volatility(schema, func, args, immutable, desc) should have the proper description +ok 414 - function_volatility(schema, func, args, immutable, desc) should have the proper diagnostics +ok 415 - function_volatility(schema, func, 0 args, stable, desc) should pass +ok 416 - function_volatility(schema, func, 0 args, stable, desc) should have the proper description +ok 417 - function_volatility(schema, func, 0 args, stable, desc) should have the proper diagnostics +ok 418 - function_volatility(schema, func, 0 args, volatile) should pass +ok 419 - function_volatility(schema, func, 0 args, volatile) should have the proper description +ok 420 - function_volatility(schema, func, 0 args, volatile) should have the proper diagnostics +ok 421 - function_volatility(schema, func, args, immutable) should pass +ok 422 - function_volatility(schema, func, args, immutable) should have the proper description +ok 423 - function_volatility(schema, func, volatile, desc) should pass +ok 424 - function_volatility(schema, func, volatile, desc) should have the proper description +ok 425 - function_volatility(schema, func, volatile, desc) should have the proper diagnostics +ok 426 - function_volatility(schema, func, volatile) should pass +ok 427 - function_volatility(schema, func, volatile) should have the proper description +ok 428 - function_volatility(schema, func, volatile) should have the proper diagnostics +ok 429 - function_volatility(schema, func, immutable, desc) should pass +ok 430 - function_volatility(schema, func, immutable, desc) should have the proper description +ok 431 - function_volatility(schema, func, immutable, desc) should have the proper diagnostics +ok 432 - function_volatility(schema, func, stable, desc) should pass +ok 433 - function_volatility(schema, func, stable, desc) should have the proper description +ok 434 - function_volatility(schema, func, stable, desc) should have the proper diagnostics +ok 435 - function_volatility(func, 0 args, volatile, desc) should pass +ok 436 - function_volatility(func, 0 args, volatile, desc) should have the proper description +ok 437 - function_volatility(func, 0 args, volatile, desc) should have the proper diagnostics +ok 438 - function_volatility(func, 0 args, VOLATILE, desc) should pass +ok 439 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper description +ok 440 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper diagnostics +ok 441 - function_volatility(func, 0 args, v, desc) should pass +ok 442 - function_volatility(func, 0 args, v, desc) should have the proper description +ok 443 - function_volatility(func, 0 args, v, desc) should have the proper diagnostics +ok 444 - function_volatility(func, args, immutable, desc) should pass +ok 445 - function_volatility(func, args, immutable, desc) should have the proper description +ok 446 - function_volatility(func, args, immutable, desc) should have the proper diagnostics +ok 447 - function_volatility(func, 0 args, stable, desc) should pass +ok 448 - function_volatility(func, 0 args, stable, desc) should have the proper description +ok 449 - function_volatility(func, 0 args, stable, desc) should have the proper diagnostics +ok 450 - function_volatility(func, 0 args, volatile) should pass +ok 451 - function_volatility(func, 0 args, volatile) should have the proper description +ok 452 - function_volatility(func, 0 args, volatile) should have the proper diagnostics +ok 453 - function_volatility(func, args, immutable) should pass +ok 454 - function_volatility(func, args, immutable) should have the proper description +ok 455 - function_volatility(func, volatile, desc) should pass +ok 456 - function_volatility(func, volatile, desc) should have the proper description +ok 457 - function_volatility(func, volatile, desc) should have the proper diagnostics +ok 458 - function_volatility(func, volatile) should pass +ok 459 - function_volatility(func, volatile) should have the proper description +ok 460 - function_volatility(func, volatile) should have the proper diagnostics +ok 461 - function_volatility(func, immutable, desc) should pass +ok 462 - function_volatility(func, immutable, desc) should have the proper description +ok 463 - function_volatility(func, immutable, desc) should have the proper diagnostics +ok 464 - function_volatility(func, stable, desc) should pass +ok 465 - function_volatility(func, stable, desc) should have the proper description +ok 466 - function_volatility(func, stable, desc) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index e9ce23044c79..f7a7ae8c891f 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -2259,70 +2259,6 @@ RETURNS TEXT AS $$ SELECT ok( NOT _got_func($1), 'Function ' || quote_ident($1) || '() should not exist' ); $$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION can_ok ( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -BEGIN - RAISE WARNING 'can_ok() is deprecated; use has_function() instead'; - RETURN has_function($1, $2, $3, $4); -END; -$$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION can_ok( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ -BEGIN - RAISE WARNING 'can_ok() is deprecated; use has_function() instead'; - RETURN has_function($1, $2, $3); -END; -$$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION can_ok ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ -BEGIN - RAISE WARNING 'can_ok() is deprecated; use has_function() instead'; - RETURN has_function($1, $2, $3); -END; -$$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION can_ok( NAME, NAME ) -RETURNS TEXT AS $$ -BEGIN - RAISE WARNING 'can_ok() is deprecated; use has_function() instead'; - RETURN has_function($1, $2); -END; -$$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION can_ok ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ -BEGIN - RAISE WARNING 'can_ok() is deprecated; use has_function() instead'; - RETURN has_function($1, $2, $3); -END; -$$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION can_ok( NAME, NAME[] ) -RETURNS TEXT AS $$ -BEGIN - RAISE WARNING 'can_ok() is deprecated; use has_function() instead'; - RETURN has_function($1, $2); -END; -$$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION can_ok( NAME, TEXT ) -RETURNS TEXT AS $$ -BEGIN - RAISE WARNING 'can_ok() is deprecated; use has_function() instead'; - RETURN has_function($1, $2); -END; -$$ LANGUAGE PLPGSQL; - -CREATE OR REPLACE FUNCTION can_ok( NAME ) -RETURNS TEXT AS $$ -BEGIN - RAISE WARNING 'can_ok() is deprecated; use has_function() instead'; - RETURN has_function($1); -END; -$$ LANGUAGE PLPGSQL; - CREATE OR REPLACE FUNCTION _pg_sv_type_array( OID[] ) RETURNS NAME[] AS $$ SELECT ARRAY( diff --git a/sql/functap.sql b/sql/functap.sql index fc542dd52457..dce66cafe8f5 100644 --- a/sql/functap.sql +++ b/sql/functap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(508); +SELECT plan(466); --SELECT * FROM no_plan(); CREATE SCHEMA someschema; @@ -22,9 +22,6 @@ CREATE AGGREGATE public.tap_accum ( initcond = '{}' ); --- XXX Delete when can_ok() is removed. -SET client_min_messages = error; - /****************************************************************************/ -- Test has_function(). SELECT * FROM check_test( @@ -256,121 +253,6 @@ SELECT * FROM check_test( '' ); -/****************************************************************************/ --- Test can_ok(). -SELECT * FROM check_test( - can_ok( 'now' ), - true, - 'simple function', - 'Function now() should exist', - '' -); - -SELECT * FROM check_test( - can_ok( 'pg_catalog', 'now'::name ), - true, - 'simple schema.function', - 'Function pg_catalog.now() should exist', - '' -); - -SELECT * FROM check_test( - can_ok( 'now', 'whatever' ), - true, - 'simple function desc', - 'whatever', - '' -); - -SELECT * FROM check_test( - can_ok( 'now', '{}'::name[] ), - true, - 'simple with 0 args', - 'Function now() should exist', - '' -); - -SELECT * FROM check_test( - can_ok( 'now', '{}'::name[], 'whatever' ), - true, - 'simple with 0 args desc', - 'whatever', - '' -); - -SELECT * FROM check_test( - can_ok( 'pg_catalog', 'now', '{}'::name[] ), - true, - 'simple schema.func with 0 args', - 'Function pg_catalog.now() should exist', - '' -); - -SELECT * FROM check_test( - can_ok( 'pg_catalog', 'now', 'whatever' ), - true, - 'simple schema.func with desc', - 'whatever', - '' -); - -SELECT * FROM check_test( - can_ok( 'pg_catalog', 'now', '{}'::name[], 'whatever' ), - true, - 'simple scchma.func with 0 args, desc', - 'whatever', - '' -); - -SELECT * FROM check_test( - can_ok( 'lower', '{text}'::name[] ), - true, - 'simple function with 1 arg', - 'Function lower(text) should exist', - '' -); - -SELECT * FROM check_test( - can_ok( 'decode', '{text,text}'::name[] ), - true, - 'simple function with 2 args', - 'Function decode(text, text) should exist', - '' -); - -SELECT * FROM check_test( - can_ok( 'array_cat', ARRAY['anyarray','anyarray'] ), - true, - 'simple array function', - 'Function array_cat(anyarray, anyarray) should exist', - '' -); - -SELECT * FROM check_test( - can_ok( '__cat__', '{text[]}'::name[] ), - true, - 'custom array function', - 'Function __cat__(text[]) should exist', - '' -); - -SELECT * FROM check_test( - can_ok( '__cat__', '{numeric}'::name[] ), - true, - 'custom numeric function', - 'Function __cat__(numeric) should exist', - '' -); - --- Check failure output. -SELECT * FROM check_test( - can_ok( '__cat__', '{varchar[]}'::name[] ), - false, - 'failure output', - 'Function __cat__(varchar[]) should exist', - '' -- No diagnostics. -); - /****************************************************************************/ -- Try can() function names. SELECT * FROM check_test( From 49231f28498aa3662203ecc8c18b42fd5f5f158e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 21 May 2010 16:50:28 -0400 Subject: [PATCH 0524/1195] Update 8.3 build. --- Makefile | 15 ++++----------- README.pgtap | 6 +++--- compat/install-8.3.patch | 32 ++++++++++++++++---------------- 3 files changed, 23 insertions(+), 30 deletions(-) diff --git a/Makefile b/Makefile index 9549f0996abc..945a78f022e4 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,6 @@ REGRESS_OPTS = --load-language=plpgsql # Run pg_config to get the PGXS Makefiles PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) -include $(PGXS) # We need to do various things with various versions of PostgreSQL. VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') @@ -27,16 +26,7 @@ endif # Compile the C code only if we're on 8.3 or older. ifeq ($(PGVER_MAJOR), 8) -ifeq ($(PGVER_MINOR), 3) -MODULES = pgtap -endif -ifeq ($(PGVER_MINOR), 2) -MODULES = pgtap -endif -ifeq ($(PGVER_MINOR), 1) -MODULES = pgtap -endif -ifeq ($(PGVER_MINOR), 0) +ifneq ($(PGVER_MINOR), 4) MODULES = pgtap endif endif @@ -46,6 +36,9 @@ ifndef PERL PERL := $(shell which perl) endif +# Load PGXS now that we've set all the variables it might need. +include $(PGXS) + # Is TAP::Harness installed? ifdef PERL HAVE_HARNESS := $(shell $(PERL) -le 'eval { require TAP::Harness }; print 1 unless $$@' ) diff --git a/README.pgtap b/README.pgtap index 4f55fe4a8740..32eba0ad77c4 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4319,9 +4319,9 @@ Supported Versions pgTAP has been tested on the following builds of PostgreSQL: -* PostgreSQL 8.5devel on i386-apple-darwin10.2.0 -* PostgreSQL 8.4.0 on i386-apple-darwin9.7.0 -* PostgreSQL 8.3.9 on i386-apple-darwin10.2.0 +* PostgreSQL 9.0beta1 on x86_64-apple-darwin10.3.0 +* PostgreSQL 8.4.4 on i386-apple-darwin10.3.0 +* PostgreSQL 8.3.11 on i386-apple-darwin10.3.0 * PostgreSQL 8.3.6 on i386-redhat-linux-gnu * PostgreSQL 8.2.15 on i386-apple-darwin10.2.0 * PostgreSQL 8.1.19 on i686-apple-darwin10.2.0 diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index f9b8be9fe55d..a7daea5dcea1 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.orig 2009-12-16 17:06:12.000000000 -0800 -+++ pgtap.sql 2009-12-16 17:06:38.000000000 -0800 +--- pgtap.sql 2010-05-21 16:48:21.000000000 -0400 ++++ pgtap.sql.new 2010-05-21 16:47:26.000000000 -0400 @@ -15,6 +15,11 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -12,8 +12,8 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT s.a[1]::int * 10000 -@@ -5849,8 +5854,9 @@ - SELECT display_type(a.atttypid, a.atttypmod) +@@ -5785,8 +5790,9 @@ + 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 + JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid @@ -21,9 +21,9 @@ - AND c.relistemp + AND n.nspname LIKE 'pg_temp%' AND attnum > 0 - AND CASE WHEN attisdropped THEN false ELSE pg_type_is_visible(a.atttypid) END + AND NOT attisdropped ORDER BY attnum -@@ -6197,7 +6203,7 @@ +@@ -6133,7 +6139,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -32,7 +32,7 @@ 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' || -@@ -6352,7 +6358,7 @@ +@@ -6288,7 +6294,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -41,7 +41,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6538,13 +6544,7 @@ +@@ -6474,13 +6480,7 @@ $$ LANGUAGE sql; -- collect_tap( tap, tap, tap ) @@ -56,12 +56,12 @@ RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); $$ LANGUAGE sql; -@@ -7017,7 +7017,7 @@ - result BOOLEAN; +@@ -6952,7 +6952,7 @@ + rec RECORD; BEGIN - FOR rec in EXECUTE _query($1) LOOP -- result := NOT rec IS DISTINCT FROM $2; -+ result := NOT rec::text IS DISTINCT FROM $2::text; - RETURN ok(result, $3 ) || CASE WHEN result THEN '' ELSE 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 + EXECUTE _query($1) INTO rec; +- IF NOT rec IS DISTINCT FROM $2 THEN RETURN ok(true, $3); END IF; ++ IF NOT rec::text IS DISTINCT FROM $2::text 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 From 22947647f11c7bccc2333d502ca14d45d1a550e6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 21 May 2010 17:21:48 -0400 Subject: [PATCH 0525/1195] Update 8.2 build. --- README.pgtap | 2 +- compat/install-8.2.patch | 86 ++++++++++++++++++++-------------------- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/README.pgtap b/README.pgtap index 32eba0ad77c4..4c84a7c40778 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4323,7 +4323,7 @@ pgTAP has been tested on the following builds of PostgreSQL: * PostgreSQL 8.4.4 on i386-apple-darwin10.3.0 * PostgreSQL 8.3.11 on i386-apple-darwin10.3.0 * PostgreSQL 8.3.6 on i386-redhat-linux-gnu -* PostgreSQL 8.2.15 on i386-apple-darwin10.2.0 +* PostgreSQL 8.2.17 on i386-apple-darwin10.3.0 * PostgreSQL 8.1.19 on i686-apple-darwin10.2.0 * PostgreSQL 8.0.23 on i686-apple-darwin10.2.0 diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 30531cc1ff64..25227d4997f5 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.orig 2009-12-18 09:23:38.000000000 -0800 -+++ pgtap.sql 2009-12-18 09:25:55.000000000 -0800 +--- pgtap.sql 2010-05-21 17:18:26.000000000 -0400 ++++ pgtap.sql.new 2010-05-21 17:17:21.000000000 -0400 @@ -11,6 +11,59 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA, public; @@ -60,7 +60,7 @@ CREATE OR REPLACE FUNCTION pg_version() RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; -@@ -195,11 +248,11 @@ +@@ -194,11 +247,11 @@ RETURNS integer AS $$ BEGIN EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) @@ -77,7 +77,7 @@ RETURN currval('__tresults___numb_seq'); END; $$ LANGUAGE plpgsql; -@@ -486,9 +539,9 @@ +@@ -485,9 +538,9 @@ output TEXT; BEGIN EXECUTE 'SELECT ' || @@ -89,16 +89,16 @@ INTO result; output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( -@@ -1089,7 +1142,7 @@ - - CREATE OR REPLACE FUNCTION display_type ( OID, INTEGER ) - RETURNS TEXT AS $$ -- SELECT $1::regtype -+ SELECT $1::regtype::text - || COALESCE(substring(pg_catalog.format_type($1, $2), '[(][^)]+[)]$'), '') +@@ -1091,7 +1144,7 @@ + SELECT COALESCE(substring( + pg_catalog.format_type($1, $2), + '(("(?!")([^"]|"")+"|[^.]+)([(][^)]+[)])?)$' +- ), '') ++ ) || CASE WHEN $2 IS NULL OR $2 < 0 OR pg_catalog.format_type($1, $2) LIKE '%)' THEN '' ELSE '(' || $2 || ')' END, '') $$ LANGUAGE SQL; -@@ -2101,7 +2154,7 @@ + CREATE OR REPLACE FUNCTION display_type ( NAME, OID, INTEGER ) +@@ -2102,7 +2155,7 @@ p.proname AS name, array_to_string(p.proargtypes::regtype[], ',') AS args, CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END @@ -107,7 +107,7 @@ p.prolang AS langoid, p.proisstrict AS is_strict, p.proisagg AS is_agg, -@@ -3372,63 +3425,6 @@ +@@ -3309,63 +3362,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -171,7 +171,7 @@ CREATE OR REPLACE FUNCTION _has_role( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( -@@ -5878,13 +5874,13 @@ +@@ -5814,13 +5810,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -187,7 +187,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6108,7 +6104,7 @@ +@@ -6044,7 +6040,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -196,7 +196,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6203,11 +6199,11 @@ +@@ -6139,11 +6135,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -211,7 +211,7 @@ ); END IF; rownum = rownum + 1; -@@ -6222,8 +6218,8 @@ +@@ -6158,8 +6154,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -222,7 +222,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6358,7 +6354,7 @@ +@@ -6294,7 +6290,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -231,7 +231,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6372,8 +6368,8 @@ +@@ -6308,8 +6304,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -242,7 +242,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6498,9 +6494,9 @@ +@@ -6434,9 +6430,9 @@ DECLARE typeof regtype := pg_typeof($1); BEGIN @@ -255,7 +255,7 @@ END; $$ LANGUAGE plpgsql; -@@ -6521,7 +6517,7 @@ +@@ -6457,7 +6453,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -264,7 +264,7 @@ END LOOP; -- What extra records do we have? -@@ -6666,7 +6662,7 @@ +@@ -6602,7 +6598,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -273,7 +273,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT -@@ -6684,7 +6680,7 @@ +@@ -6620,7 +6616,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -282,7 +282,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) ), -@@ -6717,7 +6713,7 @@ +@@ -6653,7 +6649,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -291,7 +291,7 @@ 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']) ) -@@ -6736,7 +6732,7 @@ +@@ -6672,7 +6668,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -300,21 +300,23 @@ 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']) ) -@@ -7017,10 +7013,10 @@ - result BOOLEAN; +@@ -6952,10 +6948,12 @@ + rec RECORD; BEGIN - FOR rec in EXECUTE _query($1) LOOP -- result := NOT rec::text IS DISTINCT FROM $2::text; -+ result := NOT textin(record_out(rec)) IS DISTINCT FROM textin(record_out($2)); - RETURN ok(result, $3 ) || CASE WHEN result THEN '' ELSE 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; - END LOOP; + 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; -@@ -7166,7 +7162,7 @@ + $$ LANGUAGE plpgsql; +@@ -7100,7 +7098,7 @@ CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) RETURNS TEXT AS $$ @@ -323,7 +325,7 @@ $$ LANGUAGE SQL; -- operators_are( schema, operators[], description ) -@@ -7175,7 +7171,7 @@ +@@ -7109,7 +7107,7 @@ SELECT _areni( 'operators', ARRAY( @@ -332,7 +334,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7187,7 +7183,7 @@ +@@ -7121,7 +7119,7 @@ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT @@ -341,7 +343,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7208,7 +7204,7 @@ +@@ -7142,7 +7140,7 @@ SELECT _areni( 'operators', ARRAY( @@ -350,7 +352,7 @@ 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) -@@ -7221,7 +7217,7 @@ +@@ -7155,7 +7153,7 @@ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT From bf6f2a9efc58e99ade9f9a38521368715d9f20e5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 21 May 2010 18:01:40 -0400 Subject: [PATCH 0526/1195] Update 8.1 build. Had to add `NO_PGXS=1` to support building in the `contrib` directory. --- Changes | 1 + Makefile | 10 ++++++++++ README.pgtap | 9 +++++---- compat/install-8.1.patch | 25 +++++++++++++++++-------- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/Changes b/Changes index 98a034bf4518..7ca83a2da71e 100644 --- a/Changes +++ b/Changes @@ -27,6 +27,7 @@ Revision history for pgTAP * Eliminated the need to set `USE_PGXS=1` when building outsde the contrib directory of the PostgreSQL distribution. If `pg_config` isn't in the `$PATH` environment variable, one can simply set `PG_CONFIG` to point to it. + If building inside the contrib directory, set `NO_PGXS=1`. * Fixed typo in the statement to drop the `tap_funky` view in `uninstall_pgtap.sql`. Thanks to Erik Rijkers. * Removed deprecated `can_ok()` functions. Use `has_function()`, instead. diff --git a/Makefile b/Makefile index 945a78f022e4..9f4316b2300f 100644 --- a/Makefile +++ b/Makefile @@ -6,9 +6,14 @@ SCRIPTS = bbin/pg_prove bbin/pg_tapgen REGRESS = $(patsubst sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --load-language=plpgsql +ifdef NO_PGXS +top_builddir = ../.. +PG_CONFIG := $(top_builddir)/src/bin/pg_config/pg_config +else # Run pg_config to get the PGXS Makefiles PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) +endif # We need to do various things with various versions of PostgreSQL. VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') @@ -37,7 +42,12 @@ PERL := $(shell which perl) endif # Load PGXS now that we've set all the variables it might need. +ifdef NO_PGXS +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +else include $(PGXS) +endif # Is TAP::Harness installed? ifdef PERL diff --git a/README.pgtap b/README.pgtap index 4c84a7c40778..0bf884aeebd3 100644 --- a/README.pgtap +++ b/README.pgtap @@ -41,11 +41,12 @@ to find it: env PG_CONFIG=/path/to/pg_config make && make install && make installcheck -And finally, if all that fails, copy the entire distribution directory to the -`contrib/` subdirectory of the PostgreSQL source tree and try it there: +And finally, if all that fails (and if you're on PostgreSQL 8.1 or lower, it +likely will), copy the entire distribution directory to the `contrib/` +subdirectory of the PostgreSQL source tree and try it there without +`pg_config`: - env PG_CONFIG=../../src/bin/pg_config/pg_config \ - make && make install && make installcheck + env NO_PGXS=1 make && make install && make installcheck If you encounter an error such as: diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index 7a4770374b72..e6719ac89fc4 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ ---- pgtap.sql.orig 2009-12-18 09:41:17.000000000 -0800 -+++ pgtap.sql 2009-12-18 09:41:17.000000000 -0800 -@@ -5646,7 +5646,7 @@ +--- pgtap.sql 2010-05-21 17:56:54.000000000 -0400 ++++ pgtap.sql.new 2010-05-21 17:56:36.000000000 -0400 +@@ -5588,7 +5588,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -9,7 +9,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -5654,8 +5654,8 @@ +@@ -5596,8 +5596,8 @@ -- 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. @@ -20,7 +20,7 @@ END LOOP; END LOOP; RETURN; -@@ -5723,14 +5723,14 @@ +@@ -5659,14 +5659,14 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -37,7 +37,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -5745,15 +5745,15 @@ +@@ -5681,15 +5681,15 @@ IF verbos THEN RETURN NEXT diag(tests[i] || '()'); END IF; -- Run the setup functions. @@ -57,7 +57,7 @@ -- Remember how many failed and then roll back. num_faild := num_faild + num_failed(); -@@ -5768,7 +5768,7 @@ +@@ -5704,7 +5704,7 @@ END LOOP; -- Run the shutdown functions. @@ -66,7 +66,7 @@ -- Raise an exception to rollback any changes. RAISE EXCEPTION '__TAP_ROLLBACK__'; -@@ -5779,8 +5779,8 @@ +@@ -5715,8 +5715,8 @@ END IF; END; -- Finish up. @@ -77,3 +77,12 @@ END LOOP; -- Clean up and return. +@@ -6947,7 +6947,7 @@ + DECLARE + rec RECORD; + BEGIN +- EXECUTE _query($1) INTO rec; ++ FOR rec in EXECUTE _query($1) LOOP END LOOP; + IF NOT textin(record_out(rec)) IS DISTINCT FROM textin(record_out($2)) + THEN RETURN ok(true, $3); + END IF; From 9720d117da94fa54400490e8f77aa3a96372c174 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 21 May 2010 18:41:57 -0400 Subject: [PATCH 0527/1195] Update 8.0 build. --- README.pgtap | 5 +-- compat/install-8.0.patch | 82 +++++++++++++++++++++++----------------- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/README.pgtap b/README.pgtap index 0bf884aeebd3..f92f625a2813 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4323,10 +4323,9 @@ pgTAP has been tested on the following builds of PostgreSQL: * PostgreSQL 9.0beta1 on x86_64-apple-darwin10.3.0 * PostgreSQL 8.4.4 on i386-apple-darwin10.3.0 * PostgreSQL 8.3.11 on i386-apple-darwin10.3.0 -* PostgreSQL 8.3.6 on i386-redhat-linux-gnu * PostgreSQL 8.2.17 on i386-apple-darwin10.3.0 -* PostgreSQL 8.1.19 on i686-apple-darwin10.2.0 -* PostgreSQL 8.0.23 on i686-apple-darwin10.2.0 +* PostgreSQL 8.1.21 on i686-apple-darwin10.3.0 +* PostgreSQL 8.0.25 on i686-apple-darwin10.3.0 If you know of others, please submit them! Use `psql -d template1 -c 'SELECT VERSION()'` to get the formal build version and OS. diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index 38b9652f2509..3ca0db7029b6 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.orig 2009-12-31 16:40:00.000000000 -0800 -+++ pgtap.sql 2009-12-31 17:13:43.000000000 -0800 +--- pgtap.sql 2010-05-21 18:40:22.000000000 -0400 ++++ pgtap.sql.new 2010-05-21 18:40:08.000000000 -0400 @@ -68,6 +68,27 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -28,7 +28,7 @@ CREATE OR REPLACE FUNCTION pg_typeof("any") RETURNS regtype AS '$libdir/pgtap' -@@ -150,53 +171,63 @@ +@@ -149,53 +170,63 @@ CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ DECLARE @@ -109,7 +109,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -260,10 +291,12 @@ +@@ -259,10 +290,12 @@ CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ DECLARE @@ -125,7 +125,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -536,13 +569,16 @@ +@@ -535,13 +568,16 @@ want ALIAS FOR $3; descr ALIAS FOR $4; result BOOLEAN; @@ -145,7 +145,7 @@ output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -1364,19 +1400,21 @@ +@@ -1365,19 +1401,21 @@ CREATE OR REPLACE FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -171,7 +171,7 @@ END; $$ LANGUAGE plpgsql; -@@ -3425,39 +3463,6 @@ +@@ -3362,39 +3400,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -211,7 +211,7 @@ CREATE OR REPLACE FUNCTION _is_super( NAME ) RETURNS BOOLEAN AS $$ SELECT usesuper -@@ -3561,13 +3566,8 @@ +@@ -3498,13 +3503,8 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _grolist ( NAME ) @@ -227,7 +227,19 @@ $$ LANGUAGE sql; -- is_member_of( group, user[], description ) -@@ -5516,6 +5516,7 @@ +@@ -3513,9 +3513,9 @@ + DECLARE + missing text[]; + BEGIN +- IF NOT _has_role($1) THEN ++ IF NOT _has_group($1) THEN + RETURN fail( $3 ) || E'\n' || diag ( +- ' Role ' || quote_ident($1) || ' does not exist' ++ ' Group ' || quote_ident($1) || ' does not exist' + ); + END IF; + +@@ -5453,6 +5453,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -235,7 +247,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -5527,8 +5528,10 @@ +@@ -5464,8 +5465,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. @@ -248,9 +260,9 @@ -- Now delete those results. EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; -@@ -5720,116 +5723,6 @@ - END - $$ LANGUAGE plpgsql; +@@ -5651,116 +5654,6 @@ + SELECT TRUE; + $$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION _runner( text[], text[], text[], text[], text[] ) -RETURNS SETOF TEXT AS $$ @@ -365,7 +377,7 @@ CREATE OR REPLACE FUNCTION _temptable ( TEXT, TEXT ) RETURNS TEXT AS $$ BEGIN -@@ -5879,13 +5772,13 @@ +@@ -5810,13 +5703,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -381,7 +393,7 @@ END LOOP; -- Drop the temporary tables. -@@ -5999,16 +5892,20 @@ +@@ -5930,16 +5823,20 @@ missing TEXT[] := '{}'; res BOOLEAN := TRUE; msg TEXT := ''; @@ -404,7 +416,7 @@ -- Drop the temporary tables. EXECUTE 'DROP TABLE ' || have; -@@ -6109,7 +6006,7 @@ +@@ -6040,7 +5937,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -413,7 +425,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6204,11 +6101,11 @@ +@@ -6135,11 +6032,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -428,7 +440,7 @@ ); END IF; rownum = rownum + 1; -@@ -6223,8 +6120,8 @@ +@@ -6154,8 +6051,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -439,7 +451,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6359,7 +6256,7 @@ +@@ -6290,7 +6187,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -448,7 +460,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6373,8 +6270,8 @@ +@@ -6304,8 +6201,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -459,7 +471,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6522,7 +6419,7 @@ +@@ -6453,7 +6350,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -468,7 +480,7 @@ END LOOP; -- What extra records do we have? -@@ -6626,35 +6523,6 @@ +@@ -6557,35 +6454,6 @@ SELECT throws_imatching($1, $2, 'Should throw exception matching ' || quote_literal($2) ); $$ LANGUAGE sql; @@ -504,17 +516,19 @@ CREATE OR REPLACE FUNCTION _types_are ( NAME, NAME[], TEXT, CHAR[] ) RETURNS TEXT AS $$ SELECT _are( -@@ -7018,10 +6886,10 @@ - result BOOLEAN; +@@ -6948,12 +6816,12 @@ + rec RECORD; BEGIN - FOR rec in EXECUTE _query($1) LOOP -- result := NOT textin(record_out(rec)) IS DISTINCT FROM textin(record_out($2)); -+ result := NOT textin(record_out(rec, 2249)) IS DISTINCT FROM textin(record_out($2, 2249)); - RETURN ok(result, $3 ) || CASE WHEN result THEN '' ELSE E'\n' || diag( -- ' 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 -+ ' have: ' || CASE WHEN rec IS NULL THEN 'NULL' ELSE textin(record_out(rec, 2249)) END || -+ E'\n want: ' || CASE WHEN $2 IS NULL THEN 'NULL' ELSE textin(record_out($2, 2249)) END - ) END; - END LOOP; + FOR rec in EXECUTE _query($1) LOOP END LOOP; +- IF NOT textin(record_out(rec)) IS DISTINCT FROM textin(record_out($2)) ++ IF NOT textin(record_out(rec, 2249)) IS DISTINCT FROM textin(record_out($2, 2249)) + THEN RETURN ok(true, $3); + END IF; + RETURN ok(false, $3 ) || E'\n' || diag( +- ' 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 ++ ' have: ' || CASE WHEN rec IS NULL THEN 'NULL' ELSE textin(record_out(rec, 2249)) END || ++ E'\n want: ' || CASE WHEN $2 IS NULL THEN 'NULL' ELSE textin(record_out($2, 2249)) END + ); END; + $$ LANGUAGE plpgsql; From f3bfa263ca2d2690e87aedce1ac0507444855ed6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 22 May 2010 12:13:51 -0400 Subject: [PATCH 0528/1195] Fix to support $TAPSCHEMA again. The patch management is such a PITA. Anyway, needed to not change the files until after the patches are applied, as doing so can break the patches. While at it, remove the extra diff files from `.gitignore`, as I actually want to know when they're there. One was mis-spelled anyway. Go figure. --- .gitignore | 4 ---- Makefile | 20 +++++++++++--------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 7b7ca1a2ebfd..1b97b5799837 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,5 @@ pgtap.sql -pgtap.sql.reg -pgtap.sql.orig uninstall_pgtap.sql -uninstall_pgtap.sql.rej -uninstall_pgtap.sql.orig test_setup.sql results pgtap.so diff --git a/Makefile b/Makefile index 9f4316b2300f..54076f81b8da 100644 --- a/Makefile +++ b/Makefile @@ -86,11 +86,7 @@ else endif pgtap.sql: pgtap.sql.in test_setup.sql -ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' pgtap.sql.in > pgtap.sql -else - sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' pgtap.sql.in > pgtap.sql -endif + cp $< $@ ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 5) ifneq ($(PGVER_MINOR), 4) @@ -111,13 +107,15 @@ endif endif endif endif - -uninstall_pgtap.sql: uninstall_pgtap.sql.in test_setup.sql ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' uninstall_pgtap.sql.in > uninstall_pgtap.sql + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' pgtap.sql > pgtap.tmp else - cp uninstall_pgtap.sql.in uninstall_pgtap.sql + sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' pgtap.sql > pgtap.tmp endif + mv pgtap.tmp pgtap.sql + +uninstall_pgtap.sql: uninstall_pgtap.sql.in test_setup.sql + cp uninstall_pgtap.sql.in uninstall_pgtap.sql ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 5) ifneq ($(PGVER_MINOR), 4) @@ -131,6 +129,10 @@ endif endif endif endif +ifdef TAPSCHEMA + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' uninstall_pgtap.sql > uninstall.tmp + mv uninstall.tmp uninstall_pgtap.sql +endif # Build pg_prove and holler if there's no Perl or TAP::Harness. bbin/pg_prove: From c581c89f0b23ebf5fd7356124f05b12229293278 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 24 May 2010 16:18:38 -0700 Subject: [PATCH 0529/1195] Nicer handling of IDs in HTML documtation. Update `Makefile` to create nicer IDs for function headers. So instead of an ID of "`isnt(+anyelement,+anyelement,+description+)`" we'll get an ID of "isnt". This will make for much nicer links into the documentation. This required that I change the IDs generated by Discount. I think this is a good thing. While at it, fix some broken backticks in `README.pgtap` that were messing up the HTML output a bit. --- Makefile | 3 ++- README.pgtap | 14 +++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 54076f81b8da..8bb732194902 100644 --- a/Makefile +++ b/Makefile @@ -176,5 +176,6 @@ test: test_setup.sql bbin/pg_prove html: markdown -F 0x1000 README.pgtap > readme.html - perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$4; $$lab = $$4; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$4 ? "$$4()" : $$3) . "" } END { print "
  • \n
\n" }' readme.html > toc.html + perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$4; $$lab = $$4; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$4 ? "$$4()" : $$3) . "" } END { print "
  • \n
\n" }' readme.html > toc.html + perl -pi -e 'BEGIN { my %seen }; s{(' bin/pg_prove diff --git a/README.pgtap b/README.pgtap index f92f625a2813..32426a12b6f8 100644 --- a/README.pgtap +++ b/README.pgtap @@ -572,7 +572,7 @@ thus support an alternative to make your tests more SQLish: using prepared statements. [Prepared statements](http://www.postgresql.org/docs/current/static/sql-prepare.html -"PostgreSQL Documentation: `PREPARE`") allow you to just write SQL and simply +"PostgreSQL Documentation: PREPARE") allow you to just write SQL and simply pass the prepared statement names to test functions. For example, the above example can be rewritten as: @@ -1827,7 +1827,7 @@ The format is `:op(:leftop,:rightop) RETURNS :return_type`. For left operators the left argument type should be `NONE`. For right operators, the right argument type should be `NONE`. The example above shows one one of each of the operator types. `=(citext,citext)` is an infix -operator, `-(bigint,NONE)` is a left operator, and `!(NONE,bigint)' is a right +operator, `-(bigint,NONE)` is a left operator, and `!(NONE,bigint)` is a right operator. In the event of a failure, you'll see diagnostics listing the extra and/or @@ -2245,10 +2245,10 @@ have a numeric column with a precision of 8, you should specify `ARRAY['numeric']`". If you created a `varchar(64)` column, you should pass the `:args[]` argument as `ARRAY['character varying']`. -If you wish to use the two-argument form of `has_function()`, specifying only the -schema and the function name, you must cast the `:function` argument to +If you wish to use the two-argument form of `has_function()`, specifying only +the schema and the function name, you must cast the `:function` argument to `:name` in order to disambiguate it from from the -`has_function(`:function`, `:description)` form. If you neglect to do so, your +`has_function(:function, :description)` form. If you neglect to do so, your results will be unexpected. Also, if you use the string form to specify the `:args[]` array, be sure to @@ -4290,8 +4290,8 @@ No changes. Everything should just work. To Do ----- * Add `schema, table, colname` variations of the table-checking functions - (`table_has_column()`, col_type_is()`, etc.). That is, allow the description - to be optional if the schema is included in the function call. + (`table_has_column()`, `col_type_is()`, etc.). That is, allow the + description to be optional if the schema is included in the function call. * Add functions to test for object ownership. + `db_owner_is()` + `table_owner_is()` From 47825bfa51f79ee31b35544ac8f2bd143dd4d3a2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 24 May 2010 16:23:32 -0700 Subject: [PATCH 0530/1195] Update a couple of copyright dates. --- bin/pg_prove | 2 +- bin/pg_tapgen | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/pg_prove b/bin/pg_prove index ab22c22b6f45..58f31f463cd8 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -454,6 +454,6 @@ David E. Wheeler =head1 Copyright -Copyright (c) 2008-2009 Kineticode, Inc. Some Rights Reserved. +Copyright (c) 2008-2010 Kineticode, Inc. Some Rights Reserved. =cut diff --git a/bin/pg_tapgen b/bin/pg_tapgen index 310c7046eeff..f5ecabf27d09 100755 --- a/bin/pg_tapgen +++ b/bin/pg_tapgen @@ -282,6 +282,6 @@ David E. Wheeler =head1 Copyright -Copyright (c) 2009 Kineticode, Inc. Some Rights Reserved. +Copyright (c) 2009-2010 Kineticode, Inc. Some Rights Reserved. =cut From 8f18b7080834b76e4149e002ec07dc88d6733927 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 24 May 2010 16:27:16 -0700 Subject: [PATCH 0531/1195] Note `pg_tapgen` prereqs. Also note that it may be moved out of the pgTAP distribution in the future. --- bin/pg_tapgen | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bin/pg_tapgen b/bin/pg_tapgen index f5ecabf27d09..634f644ed43d 100755 --- a/bin/pg_tapgen +++ b/bin/pg_tapgen @@ -142,7 +142,6 @@ sub functions_are { "'\n] );\n\n"; } - __END__ =encoding utf8 @@ -159,7 +158,13 @@ pg_tapgen - Generate schema TAP tests from an existing database C is a command-line utility to generate pgTAP tests to validate a database schema by reading an existing database and generating the tests to -match. +match. Its use requires the installation of the L and L from +CPAN or via a package distribution. + +B These prerequisites are not validatd by the pgTAP C, so +you'll need to instal them youself. As a result, inclusion of this script in +the pgTAP distribution is experimental. It may be moved to its own +distribution in the future. =head1 Options From 5ed017540fdb9e1b859f9d808e3026f1996633b7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 24 May 2010 16:33:08 -0700 Subject: [PATCH 0532/1195] Timestamp for 0.24. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 7ca83a2da71e..ca791b23328d 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.24 +0.24 2010-05-24T23:33:22 ------------------------- * Got `sql/artap.sql` tests passing again when building with `$TAPSCHEMA` set. * Changed to saner source URL in `contrib/pgtap.spec`. From c536ad4fd4365b6543c780ac06f5bcb04f7b4b4a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 24 May 2010 18:01:08 -0700 Subject: [PATCH 0533/1195] Restore some missing TOC headers. Seems the changes I made in c581c89f0b23ebf5fd7356124f05b12229293278 made it so only function headers were found. So change so that all headers work properly. --- Makefile | 2 +- README.pgtap | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 8bb732194902..92cb64e1157a 100644 --- a/Makefile +++ b/Makefile @@ -176,6 +176,6 @@ test: test_setup.sql bbin/pg_prove html: markdown -F 0x1000 README.pgtap > readme.html - perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$4; $$lab = $$4; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$4 ? "$$4()" : $$3) . "" } END { print "
  • \n
\n" }' readme.html > toc.html + perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$5; $$lab = $$5; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$5 ? "$$5()" : $$4) . "" } END { print "
  • \n
\n" }' readme.html > toc.html perl -pi -e 'BEGIN { my %seen }; s{(' bin/pg_prove diff --git a/README.pgtap b/README.pgtap index 32426a12b6f8..370ba681a05d 100644 --- a/README.pgtap +++ b/README.pgtap @@ -791,8 +791,8 @@ example: Idea borrowed from the Test::Exception Perl module. -### `performs_ok ( sql, milliseconds, description )` ### -### `performs_ok ( sql, milliseconds )` ### +### `performs_ok( sql, milliseconds, description )` ### +### `performs_ok( sql, milliseconds )` ### PREPARE fast_query AS SELECT id FROM try WHERE name = 'Larry'; SELECT performs_ok( From d58cc5d845207137ddcc902314e52f24ec7c76ba Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 24 May 2010 18:06:16 -0700 Subject: [PATCH 0534/1195] Increment version number to 0.25. --- Makefile | 2 +- README.pgtap | 2 +- bin/pg_prove | 2 +- bin/pg_tapgen | 2 +- contrib/pgtap.spec | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 92cb64e1157a..97de7a63dbbb 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') PGVER_MAJOR = $(shell echo $(VERSION) | awk -F. '{ print ($$1 + 0) }') PGVER_MINOR = $(shell echo $(VERSION) | awk -F. '{ print ($$2 + 0) }') PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') -PGTAP_VERSION = 0.24 +PGTAP_VERSION = 0.25 # We support 8.0 and later. ifneq ($(PGVER_MAJOR), 8) diff --git a/README.pgtap b/README.pgtap index 370ba681a05d..dbdf5aa81983 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,4 +1,4 @@ -pgTAP 0.24 +pgTAP 0.25 ========== pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/bin/pg_prove b/bin/pg_prove index 58f31f463cd8..342c944a838f 100755 --- a/bin/pg_prove +++ b/bin/pg_prove @@ -4,7 +4,7 @@ use strict; use warnings; use TAP::Harness; use Getopt::Long; -our $VERSION = '0.24'; +our $VERSION = '0.25'; Getopt::Long::Configure (qw(bundling)); diff --git a/bin/pg_tapgen b/bin/pg_tapgen index 634f644ed43d..03d79e02af77 100755 --- a/bin/pg_tapgen +++ b/bin/pg_tapgen @@ -5,7 +5,7 @@ use warnings; use DBI; use DBD::Pg; use Getopt::Long; -our $VERSION = '0.24'; +our $VERSION = '0.25'; Getopt::Long::Configure (qw(bundling)); diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 1b33ddb651bf..ea47b46cfadc 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 0.24 +Version: 0.25 Release: 2%{?dist} Group: Applications/Databases License: BSD From a8eea87360b3806fef5a5425dca45a0ed26f3cdf Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 23 Jun 2010 11:44:21 -0700 Subject: [PATCH 0535/1195] Don't refer to rules as triggers in the docs. A pasto, most likely. --- Changes | 3 +++ README.pgtap | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index ca791b23328d..ffdc2e9714bf 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Revision history for pgTAP ========================== +0.25 +* Minor documentation tweaks. + 0.24 2010-05-24T23:33:22 ------------------------- * Got `sql/artap.sql` tests passing again when building with `$TAPSCHEMA` set. diff --git a/README.pgtap b/README.pgtap index dbdf5aa81983..707ef501499f 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2194,13 +2194,13 @@ specified trigger does *not* exist. 'myschema', 'sometable', 'somerule, - 'Trigger "somerule" should exist' + 'Rule "somerule" should exist' ); SELECT has_rule( 'sometable', 'somerule' ); Tests to see if the specified table has the named rule. The `:description` is -optional, and if the schema is omitted, the table with which the trigger is +optional, and if the schema is omitted, the table with which the rule is associated must be visible in the search path. ### `hasnt_rule( schema, table, rule, description )` ### From 9b3044924843838ae8f89efd2cc57512d288153e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 30 Jun 2010 08:43:37 -0700 Subject: [PATCH 0536/1195] Make the sequence temporary. Hat tip to Richard Huxton. --- Changes | 4 ++++ pgtap.sql.in | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index ffdc2e9714bf..d79b523fc250 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,10 @@ Revision history for pgTAP 0.25 * Minor documentation tweaks. +* The sequence created to keep track of test numbers is now a temporary + sequence. This will prevent it from persisting beyond the current + connection, which could lead to erorrs in future connections. Thanks to + Richard Huxton for the report. 0.24 2010-05-24T23:33:22 ------------------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index f7a7ae8c891f..39058f039036 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -40,7 +40,7 @@ DECLARE BEGIN BEGIN EXECUTE ' - CREATE SEQUENCE __tcache___id_seq; + CREATE TEMP SEQUENCE __tcache___id_seq; CREATE TEMP TABLE __tcache__ ( id INTEGER NOT NULL DEFAULT nextval(''__tcache___id_seq''), label TEXT NOT NULL, From 495abbcb9c7b717606ae613efd703c62baf8f3bc Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 12 Jul 2010 17:02:17 -0700 Subject: [PATCH 0537/1195] Check pg_roles for superuser, not pg_user. --- Changes | 2 ++ pgtap.sql.in | 21 +++++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Changes b/Changes index d79b523fc250..543879cef457 100644 --- a/Changes +++ b/Changes @@ -7,6 +7,8 @@ Revision history for pgTAP sequence. This will prevent it from persisting beyond the current connection, which could lead to erorrs in future connections. Thanks to Richard Huxton for the report. +* Fixed `is_superuser()` and `isnt_superuser()` so that they properly detect + superuser roles as well as users. 0.24 2010-05-24T23:33:22 ------------------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index 39058f039036..e0a7785b0a4e 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3394,37 +3394,42 @@ RETURNS TEXT AS $$ SELECT ok( NOT _has_role($1), 'Role ' || quote_ident($1) || ' should not exist' ); $$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION _is_super( NAME ) +CREATE OR REPLACE FUNCTION _haS_user( NAME ) RETURNS BOOLEAN AS $$ - SELECT usesuper - FROM pg_catalog.pg_user - WHERE usename = $1 + 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( _is_super($1) IS NOT NULL, $2 ); + SELECT ok( _has_user($1), $2 ); $$ LANGUAGE sql; -- has_user( user ) CREATE OR REPLACE FUNCTION has_user( NAME ) RETURNS TEXT AS $$ - SELECT ok( _is_super( $1 ) IS NOT NULL, 'User ' || quote_ident($1) || ' should exist'); + 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( _is_super($1) IS NULL, $2 ); + SELECT ok( NOT _has_user($1), $2 ); $$ LANGUAGE sql; -- hasnt_user( user ) CREATE OR REPLACE FUNCTION hasnt_user( NAME ) RETURNS TEXT AS $$ - SELECT ok( _is_super( $1 ) IS NULL, 'User ' || quote_ident($1) || ' should not exist'); + 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 $$ From 0fed146c649041e47bafa7d1f3737e8d79dc1903 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 13 Jul 2010 22:32:01 -0700 Subject: [PATCH 0538/1195] Some new To-Dos. --- README.pgtap | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.pgtap b/README.pgtap index 707ef501499f..da184e068345 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4314,6 +4314,14 @@ To Do + One with scalar comparison functions: `ok()`, `is()`, `like()`, etc. + One with relation comparison functions: `set_eq()`, `results_eq()`, etc. + One with schema testing functions: `has_table()`, `tables_are()`, etc. +* Add useful negation function tests: + + `isnt_definer()` + + `isnt_strict()` + + `isnt_aggregate()` +* Add `has_columns(:schema, :table, :columns[])` +* Modify function testing assertions so that functions can be specified with + full signatures, so that a polymorphic functions can be independently tested + for language, volatility, etc. Supported Versions ----------------- From 572687cb8d97d1aa8d146f9c871c688cf695bf1a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 15 Jul 2010 20:13:10 -0700 Subject: [PATCH 0539/1195] Fix \set variable string values. --- Changes | 2 ++ README.pgtap | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 543879cef457..aa00cecd6d10 100644 --- a/Changes +++ b/Changes @@ -9,6 +9,8 @@ Revision history for pgTAP Richard Huxton for the report. * Fixed `is_superuser()` and `isnt_superuser()` so that they properly detect superuser roles as well as users. +* Fixed examples of single-quoted string values stored in `psql` variables in + `README.pgtap`. 0.24 2010-05-24T23:33:22 ------------------------- diff --git a/README.pgtap b/README.pgtap index da184e068345..9f03fa63bbe1 100644 --- a/README.pgtap +++ b/README.pgtap @@ -395,8 +395,8 @@ So why use these test functions? They produce better diagnostics on failure. `is()` and `isnt()` know what the test was and why it failed. For example this test: - \set foo '\'' waffle '\'' - \set bar '\'' yarblokos '\'' + \set foo '\'waffle\'' + \set bar '\'yarblokos\'' SELECT is( :foo::text, :bar::text, 'Is foo the same as bar?' ); Will produce something like this: @@ -3706,7 +3706,7 @@ because you haven't fixed a bug or haven't finished a new feature: SELECT todo('URIGeller not finished', 2); - \set card '\'' Eight of clubs '\'' + \set card '\'Eight of clubs\'' SELECT is( URIGeller.yourCard(), :card, 'Is THIS your card?' ); SELECT is( URIGeller.bendSpoon(), 'bent', 'Spoon bending, how original' ); From d136c9b0a91cd07a72eefff1e5f8545929feb6a4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 18 Jul 2010 16:51:26 -0700 Subject: [PATCH 0540/1195] Another To Do. --- README.pgtap | 1 + 1 file changed, 1 insertion(+) diff --git a/README.pgtap b/README.pgtap index 9f03fa63bbe1..fd0eb3194efb 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4289,6 +4289,7 @@ No changes. Everything should just work. To Do ----- +* Add `diag(anyelement)`. * Add `schema, table, colname` variations of the table-checking functions (`table_has_column()`, `col_type_is()`, etc.). That is, allow the description to be optional if the schema is included in the function call. From 29d237cbbcdba35d77419ea8b8db8ef06a25483b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 22 Jul 2010 13:50:53 -0700 Subject: [PATCH 0541/1195] Typos. --- README.pgtap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.pgtap b/README.pgtap index fd0eb3194efb..28e0b8a73ac3 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4048,7 +4048,7 @@ values always compare case-insensitively. Sure you could do this with `is()` and the `LOWER()` function, but if you're doing this all the time, you might want to simplify things. Here's how to go about it: - CREATE OR REPLACE FUNCTION is (text, text, text) + CREATE OR REPLACE FUNCTION lc_is (text, text, text) RETURNS TEXT AS $$ DECLARE result BOOLEAN; @@ -4073,7 +4073,7 @@ is instructive, this version is easier on the eyes: CREATE OR REPLACE FUNCTION lc_is ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT is( LOWER($1), LOWER(2), $3); + SELECT is( LOWER($1), LOWER($2), $3); $$ LANGUAGE sql; But either way, let pgTAP handle recording the test results and formatting the From bd04452a40e25b2bc4b841c7337781d13dd7795f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 22 Jul 2010 13:55:47 -0700 Subject: [PATCH 0542/1195] Update copyright date. --- README.pgtap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index 28e0b8a73ac3..2416080c574b 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4367,7 +4367,7 @@ Credits Copyright and License --------------------- -Copyright (c) 2008-2009 Kineticode, Inc. Some rights reserved. +Copyright (c) 2008-2010 Kineticode, Inc. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is From 66f797094c6c6cd30b29ba4420a2a91cd491d084 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 26 Jul 2010 18:58:36 -0700 Subject: [PATCH 0543/1195] Make sure temporary objets are really temporary. --- Changes | 3 +++ pgtap.sql.in | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index aa00cecd6d10..88b214c2bb6d 100644 --- a/Changes +++ b/Changes @@ -11,6 +11,9 @@ Revision history for pgTAP superuser roles as well as users. * Fixed examples of single-quoted string values stored in `psql` variables in `README.pgtap`. +* Fixed a bug in `plan()` where a sequence wasn't getting created as a + temporary sequence. This wasn't a problem if all testin was done inside a + transaction, but if not, you could only run tests once without an error. 0.24 2010-05-24T23:33:22 ------------------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index e0a7785b0a4e..2f1082a4f348 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -51,7 +51,7 @@ BEGIN GRANT ALL ON TABLE __tcache__ TO PUBLIC; GRANT ALL ON TABLE __tcache___id_seq TO PUBLIC; - CREATE SEQUENCE __tresults___numb_seq; + CREATE TEMP SEQUENCE __tresults___numb_seq; CREATE TEMP TABLE __tresults__ ( numb INTEGER NOT NULL DEFAULT nextval(''__tresults___numb_seq''), ok BOOLEAN NOT NULL DEFAULT TRUE, From 5df2809b6092f20c5c8b77190f27423846d448fc Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 27 Jul 2010 21:32:42 -0700 Subject: [PATCH 0544/1195] Couple of documentation tweaks. --- README.pgtap | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.pgtap b/README.pgtap index 2416080c574b..ef3b400cc9a2 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4114,18 +4114,19 @@ function. For the impatient, the arguments are: * `:name` - A brief name for your test, to make it easier to find failures in your test script. Optional. * `:want_description` - Expected test description to be output by the test. - Optional. + Optional. Use an empty string to test that no description is output. * `:want_diag` - Expected diagnostic message output during the execution of a test. Must always follow whatever is output by the call to `ok()`. + Optional. Use an empty string to test that no description is output. * `:match_diag` - Use `matches()` to compare the diagnostics rather than `:is()`. Useful for those situations where you're not sure what will be in the output, but you can match it with a regular expression. -Now, on with the detailed documentation. At its simplest, you just pass it the -output of your test (and it must be one and **only one** test function's -output, or you'll screw up the count, so don't do that!) and a boolean value -indicating whether or not you expect the test to have passed. That looks -something like the second example above. +Now, on with the detailed documentation. At its simplest, you just pass in the +output of your test function (and it must be one and **only one** test +function's output, or you'll screw up the count, so don't do that!) and a +boolean value indicating whether or not you expect the test to have passed. +That looks something like the second example above. All other arguments are optional, but I recommend that you *always* include a short test name to make it easier to track down failures in your test script. @@ -4187,9 +4188,9 @@ diagnostics generated during the test to an expected string. Such diagnostics test function should not call `diag()` until after it calls `ok()` or things will get truly funky. -Assuming you've followed that rule in your `lc_eq()` test function, to see -what happens when a `lc_eq()` fails. Write your test to test the diagnostics -like so: +Assuming you've followed that rule in your `lc_eq()` test function, see what +happens when a `lc_eq()` fails. Write your test to test the diagnostics like +so: SELECT * FROM check_test( lc_eq( ''this'', ''THat'' ), From df771a1e453b2231cbc8fa7ab77451a42038f43e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 28 Jul 2010 13:00:49 -0700 Subject: [PATCH 0545/1195] Doc tweak. --- README.pgtap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index ef3b400cc9a2..d00261fa6331 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3722,7 +3722,7 @@ tests, is that they're like a programmatic todo list. You know how much work is left to be done, you're aware of what bugs there are, and you'll know immediately when they're fixed. -### `todo_start( :why )` ### +### `todo_start( why )` ### ### `todo_start( )` ### This function allows you declare all subsequent tests as TODO tests, up until From 7e26c14c7df41f9c46473684e579119db753e1b7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Aug 2010 15:57:18 -0700 Subject: [PATCH 0546/1195] Fix column case-sensitivity bug. Let users pass column names in the case in which they were created, and compare accordingly. Don't force to lowercase. --- Changes | 3 +++ pgtap.sql.in | 8 ++++---- sql/aretap.sql | 28 ++++++++++++++-------------- sql/coltap.sql | 22 +++++++++++----------- sql/hastap.sql | 19 ++++++++++++++----- 5 files changed, 46 insertions(+), 34 deletions(-) diff --git a/Changes b/Changes index 88b214c2bb6d..b0762e8e2344 100644 --- a/Changes +++ b/Changes @@ -14,6 +14,9 @@ Revision history for pgTAP * Fixed a bug in `plan()` where a sequence wasn't getting created as a temporary sequence. This wasn't a problem if all testin was done inside a transaction, but if not, you could only run tests once without an error. +* Fixed but where some object names were inproperly converted to to lowercase. + This caused `has_column('foo', 'myInt')`, for example, to fail, because the + test was changing "myInt" to "myint". Thanks to Robert White for the report. 0.24 2010-05-24T23:33:22 ------------------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index 2f1082a4f348..17678f3e1f21 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -942,7 +942,7 @@ RETURNS BOOLEAN AS $$ AND c.relname = $2 AND a.attnum > 0 AND NOT a.attisdropped - AND a.attname = LOWER($3) + AND a.attname = $3 ); $$ LANGUAGE SQL; @@ -956,7 +956,7 @@ RETURNS BOOLEAN AS $$ AND pg_catalog.pg_table_is_visible(c.oid) AND a.attnum > 0 AND NOT a.attisdropped - AND a.attname = LOWER($2) + AND a.attname = $2 ); $$ LANGUAGE SQL; @@ -1230,7 +1230,7 @@ RETURNS boolean AS $$ AND c.relname = $2 AND a.attnum > 0 AND NOT a.attisdropped - AND a.attname = LOWER($3) + AND a.attname = $3 $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _has_def ( NAME, NAME ) @@ -1241,7 +1241,7 @@ RETURNS boolean AS $$ WHERE c.relname = $1 AND a.attnum > 0 AND NOT a.attisdropped - AND a.attname = LOWER($2) + AND a.attname = $2 $$ LANGUAGE sql; -- col_has_default( schema, table, column, description ) diff --git a/sql/aretap.sql b/sql/aretap.sql index cf933afed255..1aa160d9a651 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -8,10 +8,10 @@ SELECT plan(399); SET client_min_messages = warning; CREATE TABLE public.fou( - id INT NOT NULL PRIMARY KEY, - name TEXT DEFAULT '', - numb NUMERIC(10, 2), - myint NUMERIC(8) + id INT NOT NULL PRIMARY KEY, + name TEXT DEFAULT '', + numb NUMERIC(10, 2), + "myInt" NUMERIC(8) ); CREATE TABLE public.foo( id INT NOT NULL PRIMARY KEY @@ -1363,7 +1363,7 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test columns_are(). SELECT * FROM check_test( - columns_are( 'public', 'fou', ARRAY['id', 'name', 'numb', 'myint'], 'whatever' ), + columns_are( 'public', 'fou', ARRAY['id', 'name', 'numb', 'myInt'], 'whatever' ), true, 'columns_are(schema, table, columns, desc)', 'whatever', @@ -1371,7 +1371,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - columns_are( 'public', 'fou', ARRAY['id', 'name', 'numb', 'myint'] ), + columns_are( 'public', 'fou', ARRAY['id', 'name', 'numb', 'myInt'] ), true, 'columns_are(schema, table, columns)', 'Table public.fou should have the correct columns', @@ -1384,11 +1384,11 @@ SELECT * FROM check_test( 'columns_are(schema, table, columns) + extra', 'Table public.fou should have the correct columns', ' Extra columns: - myint' + "myInt"' ); SELECT * FROM check_test( - columns_are( 'public', 'fou', ARRAY['id', 'name', 'numb', 'myint', 'howdy'] ), + columns_are( 'public', 'fou', ARRAY['id', 'name', 'numb', 'myInt', 'howdy'] ), false, 'columns_are(schema, table, columns) + missing', 'Table public.fou should have the correct columns', @@ -1402,13 +1402,13 @@ SELECT * FROM check_test( 'columns_are(schema, table, columns) + extra & missing', 'Table public.fou should have the correct columns', ' Extra columns: - myint + "myInt" Missing columns: howdy' ); SELECT * FROM check_test( - columns_are( 'fou', ARRAY['id', 'name', 'numb', 'myint'], 'whatever' ), + columns_are( 'fou', ARRAY['id', 'name', 'numb', 'myInt'], 'whatever' ), true, 'columns_are(table, columns, desc)', 'whatever', @@ -1416,7 +1416,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - columns_are( 'fou', ARRAY['id', 'name', 'numb', 'myint'] ), + columns_are( 'fou', ARRAY['id', 'name', 'numb', 'myInt'] ), true, 'columns_are(table, columns)', 'Table fou should have the correct columns', @@ -1429,11 +1429,11 @@ SELECT * FROM check_test( 'columns_are(table, columns) + extra', 'Table fou should have the correct columns', ' Extra columns: - myint' + "myInt"' ); SELECT * FROM check_test( - columns_are( 'fou', ARRAY['id', 'name', 'numb', 'myint', 'howdy'] ), + columns_are( 'fou', ARRAY['id', 'name', 'numb', 'myInt', 'howdy'] ), false, 'columns_are(table, columns) + missing', 'Table fou should have the correct columns', @@ -1447,7 +1447,7 @@ SELECT * FROM check_test( 'columns_are(table, columns) + extra & missing', 'Table fou should have the correct columns', ' Extra columns: - myint + "myInt" Missing columns: howdy' ); diff --git a/sql/coltap.sql b/sql/coltap.sql index f3a14dd2fba3..efe0f00b65b2 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -7,12 +7,12 @@ SELECT plan(192); -- This will be rolled back. :-) SET client_min_messages = warning; CREATE TABLE public.sometab( - id INT NOT NULL PRIMARY KEY, - name TEXT DEFAULT '', - numb NUMERIC(10, 2) DEFAULT NULL, - myint NUMERIC(8) DEFAULT 24, - myat TIMESTAMP DEFAULT NOW(), - plain INTEGER + id INT NOT NULL PRIMARY KEY, + name TEXT DEFAULT '', + numb NUMERIC(10, 2) DEFAULT NULL, + "myInt" NUMERIC(8) DEFAULT 24, + myat TIMESTAMP DEFAULT NOW(), + plain INTEGER ); CREATE OR REPLACE FUNCTION fakeout( eok boolean, name text ) @@ -264,7 +264,7 @@ SELECT * FROM check_test( -- Check its diagnostics. SELECT * FROM check_test( - col_type_is( 'sometab', 'myint', 'numeric(7)', 'should be numeric(7)' ), + col_type_is( 'sometab', 'myInt', 'numeric(7)', 'should be numeric(7)' ), false, 'col_type_is precision fail', 'should be numeric(7)', @@ -456,10 +456,10 @@ BEGIN IF pg_version_num() < 80300 THEN -- Before 8.3, have to cast to text. FOR tap IN SELECT * FROM check_test( - col_default_is( 'sometab', 'myint', 24::text ), + col_default_is( 'sometab', 'myInt', 24::text ), true, 'col_default_is( tab, col, int )', - 'Column sometab.myint should default to ''24''', + 'Column sometab."myInt" should default to ''24''', '' ) AS a(b) LOOP RETURN NEXT tap.b; @@ -503,10 +503,10 @@ BEGIN ELSE -- In 8.3 and later, can just use the raw value. FOR tap IN SELECT * FROM check_test( - col_default_is( 'sometab', 'myint', 24 ), + col_default_is( 'sometab', 'myInt', 24 ), true, 'col_default_is( tab, col, int )', - 'Column sometab.myint should default to ''24''', + 'Column sometab."myInt" should default to ''24''', '' ) AS a(b) LOOP RETURN NEXT tap.b; diff --git a/sql/hastap.sql b/sql/hastap.sql index 66f0f6da8ebb..8eec2658b427 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -1,16 +1,16 @@ \unset ECHO \i test_setup.sql -SELECT plan(675); +SELECT plan(678); --SELECT * FROM no_plan(); -- This will be rolled back. :-) SET client_min_messages = warning; CREATE TABLE public.sometab( - id INT NOT NULL PRIMARY KEY, - name TEXT DEFAULT '', - numb NUMERIC(10, 2), - myint NUMERIC(8) + id INT NOT NULL PRIMARY KEY, + name TEXT DEFAULT '', + numb NUMERIC(10, 2), + "myInt" NUMERIC(8) ); CREATE TYPE public.sometype AS ( @@ -51,6 +51,7 @@ SELECT * FROM check_test( 'Tablespace pg_default should exist', '' ); + SELECT * FROM check_test( has_tablespace( 'pg_default', 'lol' ), true, @@ -726,6 +727,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + has_column( 'sometab', 'myInt' ), + true, + 'has_column(table, camleCase column)', + 'Column sometab."myInt" should exist', + '' +); + -- Make sure it works with views. SELECT * FROM check_test( has_column( 'pg_tables', 'schemaname' ), From d71a3b69aa39938755003d78692d4b0c0c1ab8b3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Aug 2010 19:44:41 -0700 Subject: [PATCH 0547/1195] Tweak `col_type_is()` to beter deal with mixed-case type names. The trick is to try to quote the wanted type name like the actual type name is quoted, as much as possible. That means calling `quote_ident()` only if the actual type is quoted. The new function `_quote_ident_like()` handles this. There are a bunch of other places that likely will need to be tweaked to handle this. Might actually get rid of `display_type()` and fetch the information from the catalog, instead, so as to avoid quoting issues altogether. Still poking at it. --- Changes | 3 ++ pgtap.sql.in | 58 +++++++++++++++++++++++++------------ sql/aretap.sql | 24 ++++++++++------ sql/coltap.sql | 73 +++++++++++++++++++++++++++++++++++----------- sql/hastap.sql | 78 ++++++++++++++++++++++++++++++++++++++++++++++++-- sql/util.sql | 3 +- 6 files changed, 193 insertions(+), 46 deletions(-) diff --git a/Changes b/Changes index b0762e8e2344..9e92931ee275 100644 --- a/Changes +++ b/Changes @@ -17,6 +17,9 @@ Revision history for pgTAP * Fixed but where some object names were inproperly converted to to lowercase. This caused `has_column('foo', 'myInt')`, for example, to fail, because the test was changing "myInt" to "myint". Thanks to Robert White for the report. +* Fixed bug where `col_type_is()` was forcing the type to lowercase, which + isn't correct when the type is actually mixed case or uppercase (that is, + named in double quotes when it was created). 0.24 2010-05-24T23:33:22 ------------------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index 17678f3e1f21..da4bebc415b5 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1136,28 +1136,50 @@ RETURNS TEXT AS $$ AND NOT a.attisdropped $$ LANGUAGE SQL; +CREATE OR REPLACE FUNCTION _quote_ident_like(TEXT, TEXT) +RETURNS TEXT AS $$ +DECLARE + have TEXT; + pcision TEXT; +BEGIN + -- Just return it if rhs isn't quoted. + IF $2 !~ '"' THEN RETURN $1; END IF; + + pcision := substring($1 FROM '[(][^")]+[)]$'); + + -- Just quote it if thre is no precision. + if pcision IS NULL THEN RETURN quote_ident($1); END IF; + + -- Quote the non-precision part and concatenate with precision. + RETURN quote_ident(substring($1 FROM char_length($1) - char_length(pcision))) + || pcision; +END; +$$ LANGUAGE plpgsql; + -- 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 - actual_type TEXT := _get_col_ns_type($1, $2, $3); + have_type TEXT := _get_col_ns_type($1, $2, $3); + want_type TEXT; BEGIN - IF actual_type IS NULL THEN + 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 actual_type = quote_ident($4) || '.' || LOWER($5) THEN + want_type := quote_ident($4) || '.' || _quote_ident_like($5, have_type); + 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: ' || actual_type || - E'\n want: ' || quote_ident($4) || '.' || LOWER($5) + ' have: ' || have_type || + E'\n want: ' || want_type ); END; $$ LANGUAGE plpgsql; @@ -1166,38 +1188,40 @@ $$ LANGUAGE plpgsql; 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) || '.' || quote_ident($5)); + || '.' || 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 - actual_type TEXT; + have_type TEXT; + want_type TEXT; BEGIN -- Get the data type. IF $1 IS NULL THEN - actual_type := _get_col_type($2, $3); + have_type := _get_col_type($2, $3); ELSE - actual_type := _get_col_type($1, $2, $3); + have_type := _get_col_type($1, $2, $3); END IF; - IF actual_type IS NULL THEN + 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; - IF actual_type = LOWER($4) THEN + want_type := _quote_ident_like($4, have_type); + 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: ' || actual_type || - E'\n want: ' || $4::text + ' have: ' || have_type || + E'\n want: ' || want_type ); END; $$ LANGUAGE plpgsql; @@ -2857,7 +2881,7 @@ BEGIN AND n.nspname = $1 INTO aname; - return is( aname, LOWER($4)::name, $5 ); + return is( aname, $4, $5 ); END; $$ LANGUAGE plpgsql; @@ -2886,8 +2910,7 @@ BEGIN INTO aname; return is( - aname, - LOWER($3)::name, + aname, $3, 'Index ' || quote_ident($2) || ' should be a ' || quote_ident($3) || ' index' ); END; @@ -2907,8 +2930,7 @@ BEGIN INTO aname; return is( - aname, - LOWER($2)::name, + aname, $2, 'Index ' || quote_ident($1) || ' should be a ' || quote_ident($2) || ' index' ); END; diff --git a/sql/aretap.sql b/sql/aretap.sql index 1aa160d9a651..8ab56f4d07c0 100644 --- a/sql/aretap.sql +++ b/sql/aretap.sql @@ -35,6 +35,7 @@ CREATE FUNCTION someschema.yip() returns boolean as 'SELECT TRUE' language SQL; CREATE FUNCTION someschema.yap() returns boolean as 'SELECT TRUE' language SQL; CREATE DOMAIN public.goofy AS text CHECK ( TRUE ); +CREATE DOMAIN public."myDomain" AS text CHECK ( TRUE ); CREATE OR REPLACE FUNCTION public.goofy_cmp(goofy,goofy) RETURNS INTEGER AS $$ SELECT CASE WHEN $1 = $2 THEN 0 @@ -63,6 +64,11 @@ CREATE TYPE someschema.sometype AS ( name TEXT ); +CREATE TYPE someschema."myType" AS ( + id INT, + foo INT +); + RESET client_min_messages; /****************************************************************************/ @@ -1020,7 +1026,7 @@ SELECT * FROM check_test( -- Test types_are(). SELECT * FROM check_test( - types_are( 'someschema', ARRAY['sometype'], 'whatever' ), + types_are( 'someschema', ARRAY['sometype', 'myType'], 'whatever' ), true, 'types_are(schema, types, desc)', 'whatever', @@ -1028,7 +1034,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - types_are( 'someschema', ARRAY['sometype'] ), + types_are( 'someschema', ARRAY['sometype', 'myType'] ), true, 'types_are(schema, types)', 'Schema someschema should have the correct types' @@ -1036,7 +1042,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - types_are( 'someschema', ARRAY['sometype', 'typo'], 'whatever' ), + types_are( 'someschema', ARRAY['sometype', 'myType', 'typo'], 'whatever' ), false, 'types_are(schema, types, desc) + missing', 'whatever', @@ -1045,7 +1051,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - types_are( 'someschema', '{}'::name[], 'whatever' ), + types_are( 'someschema', ARRAY['myType'], 'whatever' ), false, 'types_are(schema, types, desc) + extra', 'whatever', @@ -1054,7 +1060,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - types_are( 'someschema', ARRAY['typo'], 'whatever' ), + types_are( 'someschema', ARRAY['typo', 'myType'], 'whatever' ), false, 'types_are(schema, types, desc) + extra & missing', 'whatever', @@ -1121,7 +1127,7 @@ SELECT * FROM check_test( -- Test domains_are(). SELECT * FROM check_test( - domains_are( 'public', ARRAY['goofy'], 'whatever' ), + domains_are( 'public', ARRAY['goofy', 'myDomain'], 'whatever' ), true, 'domains_are(schema, domains, desc)', 'whatever', @@ -1129,7 +1135,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domains_are( 'public', ARRAY['goofy'] ), + domains_are( 'public', ARRAY['goofy', 'myDomain'] ), true, 'domains_are(schema, domains)', 'Schema public should have the correct domains', @@ -1137,7 +1143,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domains_are( 'public', ARRAY['freddy'], 'whatever' ), + domains_are( 'public', ARRAY['freddy', 'myDomain'], 'whatever' ), false, 'domains_are(schema, domains, desc) fail', 'whatever', @@ -1148,7 +1154,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domains_are( 'public', ARRAY['freddy'] ), + domains_are( 'public', ARRAY['freddy', 'myDomain'] ), false, 'domains_are(schema, domains) fail', 'Schema public should have the correct domains', diff --git a/sql/coltap.sql b/sql/coltap.sql index efe0f00b65b2..96253793c5ae 100644 --- a/sql/coltap.sql +++ b/sql/coltap.sql @@ -1,18 +1,24 @@ \unset ECHO \i test_setup.sql -SELECT plan(192); +SELECT plan(204); --SELECT * from no_plan(); +CREATE TYPE public."myType" AS ( + id INT, + foo INT +); + -- This will be rolled back. :-) SET client_min_messages = warning; CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '', numb NUMERIC(10, 2) DEFAULT NULL, - "myInt" NUMERIC(8) DEFAULT 24, + "myNum" NUMERIC(8) DEFAULT 24, myat TIMESTAMP DEFAULT NOW(), - plain INTEGER + plain INTEGER, + camel "myType" ); CREATE OR REPLACE FUNCTION fakeout( eok boolean, name text ) @@ -150,6 +156,49 @@ SELECT * FROM check_test( '' ); +-- Try case-sensitive column name. +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'myNum', 'pg_catalog', 'numeric(8,0)', 'myNum is numeric' ), + true, + 'col_type_is( sch, tab, myNum, sch, type, desc )', + 'myNum is numeric', + '' +); + +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'myNum', 'pg_catalog'::name, 'numeric(8,0)' ), + true, + 'col_type_is( sch, tab, myNum, sch, type, desc )', + 'Column public.sometab."myNum" should be type pg_catalog.numeric(8,0)', + '' +); + +-- Try case-sensitive type name. +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'camel', 'public', 'myType', 'camel is myType' ), + true, + 'col_type_is( sch, tab, camel, sch, type, desc )', + 'camel is myType', + '' +); + +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'camel', 'public'::name, 'myType' ), + true, + 'col_type_is( sch, tab, camel, sch, type, desc )', + 'Column public.sometab.camel should be type public.myType', + '' +); + +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'camel', 'myType', 'whatever' ), + true, + 'col_type_is( sch, tab, camel, type, desc )', + 'whatever', + '' +); + +-- Try failures. SELECT * FROM check_test( col_type_is( 'public', 'sometab', 'name', 'pg_catalog', 'integer', 'whatever' ), false, @@ -217,14 +266,6 @@ SELECT * FROM check_test( '' ); -SELECT * FROM check_test( - col_type_is( 'sometab', 'name', 'TEXT' ), - true, - 'col_type_is( tab, col, type ) insensitive', - 'Column sometab.name should be type TEXT', - '' -); - -- Make sure failure is correct. SELECT * FROM check_test( col_type_is( 'sometab', 'name', 'int4' ), @@ -264,7 +305,7 @@ SELECT * FROM check_test( -- Check its diagnostics. SELECT * FROM check_test( - col_type_is( 'sometab', 'myInt', 'numeric(7)', 'should be numeric(7)' ), + col_type_is( 'sometab', 'myNum', 'numeric(7)', 'should be numeric(7)' ), false, 'col_type_is precision fail', 'should be numeric(7)', @@ -456,10 +497,10 @@ BEGIN IF pg_version_num() < 80300 THEN -- Before 8.3, have to cast to text. FOR tap IN SELECT * FROM check_test( - col_default_is( 'sometab', 'myInt', 24::text ), + col_default_is( 'sometab', 'myNum', 24::text ), true, 'col_default_is( tab, col, int )', - 'Column sometab."myInt" should default to ''24''', + 'Column sometab."myNum" should default to ''24''', '' ) AS a(b) LOOP RETURN NEXT tap.b; @@ -503,10 +544,10 @@ BEGIN ELSE -- In 8.3 and later, can just use the raw value. FOR tap IN SELECT * FROM check_test( - col_default_is( 'sometab', 'myInt', 24 ), + col_default_is( 'sometab', 'myNum', 24 ), true, 'col_default_is( tab, col, int )', - 'Column sometab."myInt" should default to ''24''', + 'Column sometab."myNum" should default to ''24''', '' ) AS a(b) LOOP RETURN NEXT tap.b; diff --git a/sql/hastap.sql b/sql/hastap.sql index 8eec2658b427..817a58efc713 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(678); +SELECT plan(705); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -18,10 +18,17 @@ CREATE TYPE public.sometype AS ( name TEXT ); +CREATE TYPE public."myType" AS ( + id INT, + foo INT +); + CREATE DOMAIN public.us_postal_code AS TEXT CHECK( VALUE ~ '^[[:digit:]]{5}$' OR VALUE ~ '^[[:digit:]]{5}-[[:digit:]]{4}$' ); +CREATE DOMAIN public."myDomain" AS TEXT CHECK(TRUE); + CREATE SEQUENCE public.someseq; CREATE SCHEMA someschema; @@ -461,6 +468,36 @@ SELECT * FROM check_test( '' ); +-- Try case-sensitive. +SELECT * FROM check_test( + has_type( 'myType' ), + true, + 'has_type(myType)', + 'Type "myType" should exist', + '' +); +SELECT * FROM check_test( + has_type( 'myType', 'mydesc' ), + true, + 'has_type(myType, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + has_type( 'public'::name, 'myType'::name ), + true, + 'has_type(scheam, myType)', + 'Type public."myType" should exist', + '' +); +SELECT * FROM check_test( + has_type( 'public', 'myType', 'mydesc' ), + true, + 'has_type(schema, myType, desc)', + 'mydesc', + '' +); + -- Try failures. SELECT * FROM check_test( has_type( '__foobarbaz__' ), @@ -500,6 +537,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + has_type( 'myDomain' ), + true, + 'has_type(myDomain)', + 'Type "myDomain" should exist', + '' +); + /****************************************************************************/ -- Test hasnt_type(). SELECT * FROM check_test( @@ -570,7 +615,6 @@ SELECT * FROM check_test( 'Domain us_postal_code should exist', '' ); - SELECT * FROM check_test( has_domain( 'us_postal_code', 'mydesc' ), true, @@ -593,6 +637,36 @@ SELECT * FROM check_test( '' ); +-- Try case-sensitive. +SELECT * FROM check_test( + has_domain( 'myDomain' ), + true, + 'has_domain(myDomain)', + 'Domain "myDomain" should exist', + '' +); +SELECT * FROM check_test( + has_domain( 'myDomain', 'mydesc' ), + true, + 'has_domain(myDomain, desc)', + 'mydesc', + '' +); +SELECT * FROM check_test( + has_domain( 'public'::name, 'myDomain'::name ), + true, + 'has_domain(scheam, myDomain)', + 'Domain public."myDomain" should exist', + '' +); +SELECT * FROM check_test( + has_domain( 'public', 'myDomain', 'mydesc' ), + true, + 'has_domain(schema, myDomain, desc)', + 'mydesc', + '' +); + -- Try failures. SELECT * FROM check_test( has_domain( '__foobarbaz__' ), diff --git a/sql/util.sql b/sql/util.sql index 8a6857221e1f..269a12b571fa 100644 --- a/sql/util.sql +++ b/sql/util.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(29); +SELECT plan(30); --SELECT * FROM no_plan(); SELECT is( pg_typeof(42), 'integer', 'pg_type(int) should work' ); @@ -99,6 +99,7 @@ SELECT is( display_type('timestamptz'::regtype, NULL), 'timestamp with time zone SELECT is( display_type('foo', 'int4'::regtype, NULL), 'foo.integer', 'display_type(foo, int4)'); SELECT is( display_type('HEY', 'numeric'::regtype, NULL), '"HEY".numeric', 'display_type(HEY, numeric)'); SELECT is( display_type('t z', 'int4'::regtype, NULL), '"t z".integer', 'display_type(t z, int4)'); +SELECT is( display_type('text'::regtype, NULL), 'text', 'display type_type(text)'); -- Look at a type not in the current schema. CREATE SCHEMA __foo; From 3f2fa040da9d3f5ac860978641e3dfd701fc92da Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Aug 2010 20:42:55 -0700 Subject: [PATCH 0548/1195] Fix type cmoparisons in more assertion functions. No longer force the type name to lowercase in these functions, since the type name might actually be mixed case. * `col_type_is()` * `domain_type_is()` * `cast_exists()` * `cast_context_is()` * `has_operator()` --- Changes | 14 +++++---- pgtap.sql.in | 53 +++++++++++++++++++--------------- sql/hastap.sql | 77 +------------------------------------------------- sql/index.sql | 10 +------ 4 files changed, 41 insertions(+), 113 deletions(-) diff --git a/Changes b/Changes index 9e92931ee275..dbfaa4e921b6 100644 --- a/Changes +++ b/Changes @@ -14,12 +14,14 @@ Revision history for pgTAP * Fixed a bug in `plan()` where a sequence wasn't getting created as a temporary sequence. This wasn't a problem if all testin was done inside a transaction, but if not, you could only run tests once without an error. -* Fixed but where some object names were inproperly converted to to lowercase. - This caused `has_column('foo', 'myInt')`, for example, to fail, because the - test was changing "myInt" to "myint". Thanks to Robert White for the report. -* Fixed bug where `col_type_is()` was forcing the type to lowercase, which - isn't correct when the type is actually mixed case or uppercase (that is, - named in double quotes when it was created). +* Fixed bug where `has_column()`, `col_type_is()`, `domain_type_is()`, + `cast_exists()`, `cast_context_is()`, and `has_operator()`, were forcing the + type argument to lowercase, which wont't correct when the type is actually + mixed case or uppercase (that is, named in double quotes when it was + created). +* The expected type passed to `index_is_type()` is no longer compared + case-insensitively, since it might be possible in the future to create a + type with mixed case by using double quotes when naming it. 0.24 2010-05-24T23:33:22 ------------------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index da4bebc415b5..bf9caae79847 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3581,6 +3581,15 @@ RETURNS TEXT AS $$ SELECT is_member_of( $1, ARRAY[$2] ); $$ LANGUAGE SQL; +CREATE OR REPLACE FUNCTION _cmp_types(oid, name) +RETURNS BOOLEAN AS $$ +DECLARE + dtype TEXT := display_type($1, NULL); +BEGIN + RETURN dtype = _quote_ident_like($2, dtype); +END; +$$ LANGUAGE plpgsql; + CREATE OR REPLACE FUNCTION _cast_exists ( NAME, NAME, NAME, NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS ( @@ -3588,8 +3597,8 @@ RETURNS BOOLEAN AS $$ 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 display_type(castsource, NULL) = $1 - AND display_type(casttarget, NULL) = $2 + WHERE _cmp_types(castsource, $1) + AND _cmp_types(casttarget, $2) AND n.nspname = $3 AND p.proname = $4 ); @@ -3601,8 +3610,8 @@ RETURNS BOOLEAN AS $$ SELECT TRUE FROM pg_catalog.pg_cast c JOIN pg_catalog.pg_proc p ON c.castfunc = p.oid - WHERE display_type(castsource, NULL) = $1 - AND display_type(casttarget, NULL) = $2 + WHERE _cmp_types(castsource, $1) + AND _cmp_types(casttarget, $2) AND p.proname = $3 ); $$ LANGUAGE SQL; @@ -3612,8 +3621,8 @@ RETURNS BOOLEAN AS $$ SELECT EXISTS ( SELECT TRUE FROM pg_catalog.pg_cast c - WHERE display_type(castsource, NULL) = $1 - AND display_type(casttarget, NULL) = $2 + WHERE _cmp_types(castsource, $1) + AND _cmp_types(casttarget, $2) ); $$ LANGUAGE SQL; @@ -3728,8 +3737,8 @@ CREATE OR REPLACE FUNCTION _get_context( NAME, NAME ) RETURNS "char" AS $$ SELECT c.castcontext FROM pg_catalog.pg_cast c - WHERE display_type(castsource, NULL) = $1 - AND display_type(casttarget, NULL) = $2 + WHERE _cmp_types(castsource, $1) + AND _cmp_types(casttarget, $2) $$ LANGUAGE SQL; -- cast_context_is( source_type, target_type, context, description ) @@ -3769,10 +3778,10 @@ RETURNS BOOLEAN AS $$ WHERE n.nspname = $2 AND o.oprname = $3 AND CASE o.oprkind WHEN 'l' THEN $1 IS NULL - ELSE display_type(o.oprleft, NULL) = $1 END + ELSE _cmp_types(o.oprleft, $1) END AND CASE o.oprkind WHEN 'r' THEN $4 IS NULL - ELSE display_type(o.oprright, NULL) = $4 END - AND display_type(o.oprresult, NULL) = $5 + ELSE _cmp_types(o.oprright, $4) END + AND _cmp_types(o.oprresult, $5) ); $$ LANGUAGE SQL; @@ -3784,10 +3793,10 @@ RETURNS BOOLEAN AS $$ WHERE pg_catalog.pg_operator_is_visible(o.oid) AND o.oprname = $2 AND CASE o.oprkind WHEN 'l' THEN $1 IS NULL - ELSE display_type(o.oprleft, NULL) = $1 END + ELSE _cmp_types(o.oprleft, $1) END AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL - ELSE display_type(o.oprright, NULL) = $3 END - AND display_type(o.oprresult, NULL) = $4 + ELSE _cmp_types(o.oprright, $3) END + AND _cmp_types(o.oprresult, $4) ); $$ LANGUAGE SQL; @@ -3799,9 +3808,9 @@ RETURNS BOOLEAN AS $$ WHERE pg_catalog.pg_operator_is_visible(o.oid) AND o.oprname = $2 AND CASE o.oprkind WHEN 'l' THEN $1 IS NULL - ELSE display_type(o.oprleft, NULL) = $1 END + ELSE _cmp_types(o.oprleft, $1) END AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL - ELSE display_type(o.oprright, NULL) = $3 END + ELSE _cmp_types(o.oprright, $3) END ); $$ LANGUAGE SQL; @@ -6827,7 +6836,7 @@ BEGIN ); END IF; - RETURN is( actual_type, quote_ident($3) || '.' || LOWER($4), $5 ); + RETURN is( actual_type, quote_ident($3) || '.' || _quote_ident_like($4, actual_type), $5 ); END; $$ LANGUAGE plpgsql; @@ -6854,7 +6863,7 @@ BEGIN ); END IF; - RETURN is( actual_type, LOWER($3), $4 ); + RETURN is( actual_type, _quote_ident_like($3, actual_type), $4 ); END; $$ LANGUAGE plpgsql; @@ -6880,7 +6889,7 @@ BEGIN ); END IF; - RETURN is( actual_type, LOWER($2), $3 ); + RETURN is( actual_type, _quote_ident_like($2, actual_type), $3 ); END; $$ LANGUAGE plpgsql; @@ -6906,7 +6915,7 @@ BEGIN ); END IF; - RETURN isnt( actual_type, quote_ident($3) || '.' || LOWER($4), $5 ); + RETURN isnt( actual_type, quote_ident($3) || '.' || _quote_ident_like($4, actual_type), $5 ); END; $$ LANGUAGE plpgsql; @@ -6933,7 +6942,7 @@ BEGIN ); END IF; - RETURN isnt( actual_type, LOWER($3), $4 ); + RETURN isnt( actual_type, _quote_ident_like($3, actual_type), $4 ); END; $$ LANGUAGE plpgsql; @@ -6959,7 +6968,7 @@ BEGIN ); END IF; - RETURN isnt( actual_type, LOWER($2), $3 ); + RETURN isnt( actual_type, _quote_ident_like($2, actual_type), $3 ); END; $$ LANGUAGE plpgsql; diff --git a/sql/hastap.sql b/sql/hastap.sql index 817a58efc713..bec30dc35703 100644 --- a/sql/hastap.sql +++ b/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(705); +SELECT plan(678); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -1662,14 +1662,6 @@ SELECT * FROM check_test( '' ); -SELECT * FROM check_test( - domain_type_is( 'public', 'US_POSTAL_CODE', 'public', 'TEXT', 'whatever'), - true, - 'domain_type_is(schema, DOMAIN, schema, TYPE, desc)', - 'whatever', - '' -); - SELECT * FROM check_test( domain_type_is( 'public', 'us_postal_code', 'public', 'integer', 'whatever'), false, @@ -1711,14 +1703,6 @@ SELECT * FROM check_test( '' ); -SELECT * FROM check_test( - domain_type_is( 'public', 'US_POSTAL_CODE', 'TEXT', 'whatever'), - true, - 'domain_type_is(schema, DOMAIN, TYPE, desc)', - 'whatever', - '' -); - SELECT * FROM check_test( domain_type_is( 'public', 'us_postal_code', 'integer', 'whatever'), false, @@ -1760,14 +1744,6 @@ SELECT * FROM check_test( '' ); -SELECT * FROM check_test( - domain_type_is( 'US_POSTAL_CODE', 'TEXT', 'whatever'), - true, - 'domain_type_is(DOMAIN, TYPE, desc)', - 'whatever', - '' -); - SELECT * FROM check_test( domain_type_is( 'us_postal_code', 'integer', 'whatever'), false, @@ -1809,14 +1785,6 @@ SELECT * FROM check_test( '' ); -SELECT * FROM check_test( - domain_type_isnt( 'public', 'US_POSTAL_CODE', 'public', 'INTEGER', 'whatever'), - true, - 'domain_type_isnt(schema, DOMAIN, schema, TYPE, desc)', - 'whatever', - '' -); - SELECT * FROM check_test( domain_type_isnt( 'public', 'us_postal_code', 'public', 'text', 'whatever'), false, @@ -1826,15 +1794,6 @@ SELECT * FROM check_test( want: anything else' ); -SELECT * FROM check_test( - domain_type_isnt( 'public', 'US_POSTAL_CODE', 'public', 'TEXT', 'whatever'), - false, - 'domain_type_isnt(schema, DOMAIN, schema, TYPE, desc) fail', - 'whatever', - ' have: public.text - want: anything else' -); - SELECT * FROM check_test( domain_type_isnt( 'public', 'zip_code', 'public', 'text', 'whatever'), false, @@ -1867,14 +1826,6 @@ SELECT * FROM check_test( '' ); -SELECT * FROM check_test( - domain_type_isnt( 'public', 'US_POSTAL_CODE', 'INTEGER', 'whatever'), - true, - 'domain_type_isnt(schema, DOMAIN, TYPE, desc)', - 'whatever', - '' -); - SELECT * FROM check_test( domain_type_isnt( 'public', 'us_postal_code', 'text', 'whatever'), false, @@ -1884,15 +1835,6 @@ SELECT * FROM check_test( want: anything else' ); -SELECT * FROM check_test( - domain_type_isnt( 'public', 'US_POSTAL_CODE', 'TEXT', 'whatever'), - false, - 'domain_type_isnt(schema, DOMAIN, TYPE, desc) fail', - 'whatever', - ' have: text - want: anything else' -); - SELECT * FROM check_test( domain_type_isnt( 'public', 'zip_code', 'text', 'whatever'), false, @@ -1917,14 +1859,6 @@ SELECT * FROM check_test( '' ); -SELECT * FROM check_test( - domain_type_isnt( 'US_POSTAL_CODE', 'INTEGER', 'whatever'), - true, - 'domain_type_isnt(DOMAIN, TYPE, desc)', - 'whatever', - '' -); - SELECT * FROM check_test( domain_type_isnt( 'us_postal_code', 'integer'), true, @@ -1942,15 +1876,6 @@ SELECT * FROM check_test( want: anything else' ); -SELECT * FROM check_test( - domain_type_isnt( 'US_POSTAL_CODE', 'TEXT', 'whatever'), - false, - 'domain_type_isnt(DOMAIN, TYPE, desc) fail', - 'whatever', - ' have: text - want: anything else' -); - SELECT * FROM check_test( domain_type_isnt( 'zip_code', 'text', 'whatever'), false, diff --git a/sql/index.sql b/sql/index.sql index f79141893270..b91a4df2f226 100644 --- a/sql/index.sql +++ b/sql/index.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(228); +SELECT plan(225); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -583,14 +583,6 @@ SELECT * FROM check_test( '' ); -SELECT * FROM check_test( - index_is_type( 'public', 'sometab', 'idx_bar', 'BTREE', 'whatever' ), - true, - 'index_is_type() ci', - 'whatever', - '' -); - SELECT * FROM check_test( index_is_type( 'public', 'sometab', 'idx_bar', 'btree' ), true, From 8b77b8eda9aa7529f0738bf21e794abc8aea75d7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Aug 2010 20:55:05 -0700 Subject: [PATCH 0549/1195] Document need for double-quotes in `casts_are()`. --- Changes | 3 +++ README.pgtap | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index dbfaa4e921b6..4d34e3e6b94d 100644 --- a/Changes +++ b/Changes @@ -22,6 +22,9 @@ Revision history for pgTAP * The expected type passed to `index_is_type()` is no longer compared case-insensitively, since it might be possible in the future to create a type with mixed case by using double quotes when naming it. +* Documented that double-quotes must be used for types created with + double-quoted names in the strings passed to `casts_are()`. This should be + the only part of the API that does require this sort of quoting. 0.24 2010-05-24T23:33:22 ------------------------- diff --git a/README.pgtap b/README.pgtap index d00261fa6331..813b2b75a19d 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1779,15 +1779,27 @@ missing enums, like so: 'integer AS reltime', 'integer AS numeric', -- ... - ] + ], 'Should have the correct casts' ); This function tests that all of the casts in the database are only the casts that *should* be in that database. Casts are specified as strings in a syntax similarly to how they're declared via `CREATE CAST`. The pattern is -`:source_type AS :target_type`. If the description is omitted, a generally -useful default description will be generated. +`:source_type AS :target_type`. If either type was created with double-quotes +to force mixed case or special characers, then you must use double quotes in +the cast strings: + + SELECT casts_are( + ARRAY[ + 'integer AS "myInteger"', + -- ... + ] + ); + + +If the description is omitted, a generally useful default description will be +generated. In the event of a failure, you'll see diagnostics listing the extra and/or missing casts, like so: From afd0e97ef1b8d9c8f5a2dc799c02d24eb94302cd Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Aug 2010 20:57:44 -0700 Subject: [PATCH 0550/1195] Update `pg_regress` expected files. --- expected/coltap.out | 314 ++++++------- expected/hastap.out | 1043 ++++++++++++++++++++++--------------------- expected/index.out | 47 +- expected/util.out | 13 +- 4 files changed, 715 insertions(+), 702 deletions(-) diff --git a/expected/coltap.out b/expected/coltap.out index 92c4fc26d717..7727ff2a1f4c 100644 --- a/expected/coltap.out +++ b/expected/coltap.out @@ -1,5 +1,5 @@ \unset ECHO -1..192 +1..204 ok 1 - col_not_null( sch, tab, col, desc ) should pass ok 2 - col_not_null( sch, tab, col, desc ) should have the proper description ok 3 - col_not_null( sch, tab, col, desc ) should have the proper diagnostics @@ -42,153 +42,165 @@ ok 39 - col_type_is( sch, tab, col, sch, type, desc ) should have the proper dia ok 40 - col_type_is( sch, tab, col, sch, type, desc ) should pass ok 41 - col_type_is( sch, tab, col, sch, type, desc ) should have the proper description ok 42 - col_type_is( sch, tab, col, sch, type, desc ) should have the proper diagnostics -ok 43 - col_type_is( sch, tab, col, sch, type, desc ) fail should fail -ok 44 - col_type_is( sch, tab, col, sch, type, desc ) fail should have the proper description -ok 45 - col_type_is( sch, tab, col, sch, type, desc ) fail should have the proper diagnostics -ok 46 - col_type_is( sch, tab, col, sch, non-type, desc ) should fail -ok 47 - col_type_is( sch, tab, col, sch, non-type, desc ) should have the proper description -ok 48 - col_type_is( sch, tab, col, sch, non-type, desc ) should have the proper diagnostics -ok 49 - col_type_is( sch, tab, col, non-sch, type, desc ) should fail -ok 50 - col_type_is( sch, tab, col, non-sch, type, desc ) should have the proper description -ok 51 - col_type_is( sch, tab, col, non-sch, type, desc ) should have the proper diagnostics -ok 52 - col_type_is( sch, tab, non-col, sch, type, desc ) should fail -ok 53 - col_type_is( sch, tab, non-col, sch, type, desc ) should have the proper description -ok 54 - col_type_is( sch, tab, non-col, sch, type, desc ) should have the proper diagnostics -ok 55 - col_type_is( sch, tab, col, type, desc ) should pass -ok 56 - col_type_is( sch, tab, col, type, desc ) should have the proper description -ok 57 - col_type_is( sch, tab, col, type, desc ) should have the proper diagnostics -ok 58 - col_type_is( sch, tab, col, type ) should pass -ok 59 - col_type_is( sch, tab, col, type ) should have the proper description -ok 60 - col_type_is( sch, tab, col, type ) should have the proper diagnostics -ok 61 - col_type_is( tab, col, type, desc ) should pass -ok 62 - col_type_is( tab, col, type, desc ) should have the proper description -ok 63 - col_type_is( tab, col, type, desc ) should have the proper diagnostics -ok 64 - col_type_is( tab, col, type ) should pass -ok 65 - col_type_is( tab, col, type ) should have the proper description -ok 66 - col_type_is( tab, col, type ) should have the proper diagnostics -ok 67 - col_type_is( tab, col, type ) insensitive should pass -ok 68 - col_type_is( tab, col, type ) insensitive should have the proper description -ok 69 - col_type_is( tab, col, type ) insensitive should have the proper diagnostics -ok 70 - col_type_is( tab, col, type ) fail should fail -ok 71 - col_type_is( tab, col, type ) fail should have the proper description -ok 72 - col_type_is( tab, col, type ) fail should have the proper diagnostics -ok 73 - col_type_is( tab, noncol, type ) fail should fail -ok 74 - col_type_is( tab, noncol, type ) fail should have the proper description -ok 75 - col_type_is( tab, noncol, type ) fail should have the proper diagnostics -ok 76 - col_type_is( sch, tab, noncol, type, desc ) fail should fail -ok 77 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper description -ok 78 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper diagnostics -ok 79 - col_type_is with precision should pass -ok 80 - col_type_is with precision should have the proper description -ok 81 - col_type_is with precision should have the proper diagnostics -ok 82 - col_type_is precision fail should fail -ok 83 - col_type_is precision fail should have the proper description -ok 84 - col_type_is precision fail should have the proper diagnostics -ok 85 - col_has_default( sch, tab, col, desc ) should pass -ok 86 - col_has_default( sch, tab, col, desc ) should have the proper description -ok 87 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics -ok 88 - col_has_default( tab, col, desc ) should pass -ok 89 - col_has_default( tab, col, desc ) should have the proper description -ok 90 - col_has_default( tab, col, desc ) should have the proper diagnostics -ok 91 - col_has_default( tab, col ) should pass -ok 92 - col_has_default( tab, col ) should have the proper description -ok 93 - col_has_default( tab, col ) should have the proper diagnostics -ok 94 - col_has_default( sch, tab, col, desc ) should fail -ok 95 - col_has_default( sch, tab, col, desc ) should have the proper description -ok 96 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics -ok 97 - col_has_default( tab, col, desc ) should fail -ok 98 - col_has_default( tab, col, desc ) should have the proper description -ok 99 - col_has_default( tab, col, desc ) should have the proper diagnostics -ok 100 - col_has_default( tab, col ) should fail -ok 101 - col_has_default( tab, col ) should have the proper description -ok 102 - col_has_default( tab, col ) should have the proper diagnostics -ok 103 - col_has_default( sch, tab, col, desc ) should fail -ok 104 - col_has_default( sch, tab, col, desc ) should have the proper description -ok 105 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics -ok 106 - col_has_default( tab, col, desc ) should fail -ok 107 - col_has_default( tab, col, desc ) should have the proper description -ok 108 - col_has_default( tab, col, desc ) should have the proper diagnostics -ok 109 - col_has_default( tab, col ) should fail -ok 110 - col_has_default( tab, col ) should have the proper description -ok 111 - col_has_default( tab, col ) should have the proper diagnostics -ok 112 - col_hasnt_default( sch, tab, col, desc ) should fail -ok 113 - col_hasnt_default( sch, tab, col, desc ) should have the proper description -ok 114 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics -ok 115 - col_hasnt_default( tab, col, desc ) should fail -ok 116 - col_hasnt_default( tab, col, desc ) should have the proper description -ok 117 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics -ok 118 - col_hasnt_default( tab, col ) should fail -ok 119 - col_hasnt_default( tab, col ) should have the proper description -ok 120 - col_hasnt_default( tab, col ) should have the proper diagnostics -ok 121 - col_hasnt_default( sch, tab, col, desc ) should pass -ok 122 - col_hasnt_default( sch, tab, col, desc ) should have the proper description -ok 123 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics -ok 124 - col_hasnt_default( tab, col, desc ) should pass -ok 125 - col_hasnt_default( tab, col, desc ) should have the proper description -ok 126 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics -ok 127 - col_hasnt_default( tab, col ) should pass -ok 128 - col_hasnt_default( tab, col ) should have the proper description -ok 129 - col_hasnt_default( tab, col ) should have the proper diagnostics -ok 130 - col_hasnt_default( sch, tab, col, desc ) should fail -ok 131 - col_hasnt_default( sch, tab, col, desc ) should have the proper description -ok 132 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics -ok 133 - col_hasnt_default( tab, col, desc ) should fail -ok 134 - col_hasnt_default( tab, col, desc ) should have the proper description -ok 135 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics -ok 136 - col_hasnt_default( tab, col ) should fail -ok 137 - col_hasnt_default( tab, col ) should have the proper description -ok 138 - col_hasnt_default( tab, col ) should have the proper diagnostics -ok 139 - col_default_is( sch, tab, col, def, desc ) should pass -ok 140 - col_default_is( sch, tab, col, def, desc ) should have the proper description -ok 141 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics -ok 142 - col_default_is() fail should fail -ok 143 - col_default_is() fail should have the proper description -ok 144 - col_default_is() fail should have the proper diagnostics -ok 145 - col_default_is( tab, col, def, desc ) should pass -ok 146 - col_default_is( tab, col, def, desc ) should have the proper description -ok 147 - col_default_is( tab, col, def, desc ) should have the proper diagnostics -ok 148 - col_default_is( tab, col, def ) should pass -ok 149 - col_default_is( tab, col, def ) should have the proper description -ok 150 - col_default_is( tab, col, def ) should have the proper diagnostics -ok 151 - col_default_is( tab, col, int ) should pass -ok 152 - col_default_is( tab, col, int ) should have the proper description -ok 153 - col_default_is( tab, col, int ) should have the proper diagnostics -ok 154 - col_default_is( tab, col, NULL, desc ) should pass -ok 155 - col_default_is( tab, col, NULL, desc ) should have the proper description -ok 156 - col_default_is( tab, col, NULL, desc ) should have the proper diagnostics -ok 157 - col_default_is( tab, col, NULL ) should pass -ok 158 - col_default_is( tab, col, NULL ) should have the proper description -ok 159 - col_default_is( tab, col, NULL ) should have the proper diagnostics -ok 160 - col_default_is( tab, col, bogus, desc ) should fail -ok 161 - col_default_is( tab, col, bogus, desc ) should have the proper description -ok 162 - col_default_is( tab, col, bogus, desc ) should have the proper diagnostics -ok 163 - col_default_is( tab, col, bogus ) should fail -ok 164 - col_default_is( tab, col, bogus ) should have the proper description -ok 165 - col_default_is( tab, col, bogus ) should have the proper diagnostics -ok 166 - col_default_is( tab, col, expression ) should pass -ok 167 - col_default_is( tab, col, expression ) should have the proper description -ok 168 - col_default_is( tab, col, expression ) should have the proper diagnostics -ok 169 - col_default_is( tab, col, expression::text ) should pass -ok 170 - col_default_is( tab, col, expression::text ) should have the proper description -ok 171 - col_default_is( tab, col, expression::text ) should have the proper diagnostics -ok 172 - col_default_is( tab, col, expression, desc ) should pass -ok 173 - col_default_is( tab, col, expression, desc ) should have the proper description -ok 174 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics -ok 175 - col_default_is( tab, col, expression, desc ) should pass -ok 176 - col_default_is( tab, col, expression, desc ) should have the proper description -ok 177 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics -ok 178 - col_default_is( schema, tab, col, expression, desc ) should pass -ok 179 - col_default_is( schema, tab, col, expression, desc ) should have the proper description -ok 180 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics -ok 181 - col_default_is( schema, tab, col, expression, desc ) should pass -ok 182 - col_default_is( schema, tab, col, expression, desc ) should have the proper description -ok 183 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics -ok 184 - col_default_is( sch, tab, col, def, desc ) should fail -ok 185 - col_default_is( sch, tab, col, def, desc ) should have the proper description -ok 186 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics -ok 187 - col_default_is( tab, col, def, desc ) should fail -ok 188 - col_default_is( tab, col, def, desc ) should have the proper description -ok 189 - col_default_is( tab, col, def, desc ) should have the proper diagnostics -ok 190 - col_default_is( tab, col, def ) should fail -ok 191 - col_default_is( tab, col, def ) should have the proper description -ok 192 - col_default_is( tab, col, def ) should have the proper diagnostics +ok 43 - col_type_is( sch, tab, myNum, sch, type, desc ) should pass +ok 44 - col_type_is( sch, tab, myNum, sch, type, desc ) should have the proper description +ok 45 - col_type_is( sch, tab, myNum, sch, type, desc ) should have the proper diagnostics +ok 46 - col_type_is( sch, tab, myNum, sch, type, desc ) should pass +ok 47 - col_type_is( sch, tab, myNum, sch, type, desc ) should have the proper description +ok 48 - col_type_is( sch, tab, myNum, sch, type, desc ) should have the proper diagnostics +ok 49 - col_type_is( sch, tab, camel, sch, type, desc ) should pass +ok 50 - col_type_is( sch, tab, camel, sch, type, desc ) should have the proper description +ok 51 - col_type_is( sch, tab, camel, sch, type, desc ) should have the proper diagnostics +ok 52 - col_type_is( sch, tab, camel, sch, type, desc ) should pass +ok 53 - col_type_is( sch, tab, camel, sch, type, desc ) should have the proper description +ok 54 - col_type_is( sch, tab, camel, sch, type, desc ) should have the proper diagnostics +ok 55 - col_type_is( sch, tab, camel, type, desc ) should pass +ok 56 - col_type_is( sch, tab, camel, type, desc ) should have the proper description +ok 57 - col_type_is( sch, tab, camel, type, desc ) should have the proper diagnostics +ok 58 - col_type_is( sch, tab, col, sch, type, desc ) fail should fail +ok 59 - col_type_is( sch, tab, col, sch, type, desc ) fail should have the proper description +ok 60 - col_type_is( sch, tab, col, sch, type, desc ) fail should have the proper diagnostics +ok 61 - col_type_is( sch, tab, col, sch, non-type, desc ) should fail +ok 62 - col_type_is( sch, tab, col, sch, non-type, desc ) should have the proper description +ok 63 - col_type_is( sch, tab, col, sch, non-type, desc ) should have the proper diagnostics +ok 64 - col_type_is( sch, tab, col, non-sch, type, desc ) should fail +ok 65 - col_type_is( sch, tab, col, non-sch, type, desc ) should have the proper description +ok 66 - col_type_is( sch, tab, col, non-sch, type, desc ) should have the proper diagnostics +ok 67 - col_type_is( sch, tab, non-col, sch, type, desc ) should fail +ok 68 - col_type_is( sch, tab, non-col, sch, type, desc ) should have the proper description +ok 69 - col_type_is( sch, tab, non-col, sch, type, desc ) should have the proper diagnostics +ok 70 - col_type_is( sch, tab, col, type, desc ) should pass +ok 71 - col_type_is( sch, tab, col, type, desc ) should have the proper description +ok 72 - col_type_is( sch, tab, col, type, desc ) should have the proper diagnostics +ok 73 - col_type_is( sch, tab, col, type ) should pass +ok 74 - col_type_is( sch, tab, col, type ) should have the proper description +ok 75 - col_type_is( sch, tab, col, type ) should have the proper diagnostics +ok 76 - col_type_is( tab, col, type, desc ) should pass +ok 77 - col_type_is( tab, col, type, desc ) should have the proper description +ok 78 - col_type_is( tab, col, type, desc ) should have the proper diagnostics +ok 79 - col_type_is( tab, col, type ) should pass +ok 80 - col_type_is( tab, col, type ) should have the proper description +ok 81 - col_type_is( tab, col, type ) should have the proper diagnostics +ok 82 - col_type_is( tab, col, type ) fail should fail +ok 83 - col_type_is( tab, col, type ) fail should have the proper description +ok 84 - col_type_is( tab, col, type ) fail should have the proper diagnostics +ok 85 - col_type_is( tab, noncol, type ) fail should fail +ok 86 - col_type_is( tab, noncol, type ) fail should have the proper description +ok 87 - col_type_is( tab, noncol, type ) fail should have the proper diagnostics +ok 88 - col_type_is( sch, tab, noncol, type, desc ) fail should fail +ok 89 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper description +ok 90 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper diagnostics +ok 91 - col_type_is with precision should pass +ok 92 - col_type_is with precision should have the proper description +ok 93 - col_type_is with precision should have the proper diagnostics +ok 94 - col_type_is precision fail should fail +ok 95 - col_type_is precision fail should have the proper description +ok 96 - col_type_is precision fail should have the proper diagnostics +ok 97 - col_has_default( sch, tab, col, desc ) should pass +ok 98 - col_has_default( sch, tab, col, desc ) should have the proper description +ok 99 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics +ok 100 - col_has_default( tab, col, desc ) should pass +ok 101 - col_has_default( tab, col, desc ) should have the proper description +ok 102 - col_has_default( tab, col, desc ) should have the proper diagnostics +ok 103 - col_has_default( tab, col ) should pass +ok 104 - col_has_default( tab, col ) should have the proper description +ok 105 - col_has_default( tab, col ) should have the proper diagnostics +ok 106 - col_has_default( sch, tab, col, desc ) should fail +ok 107 - col_has_default( sch, tab, col, desc ) should have the proper description +ok 108 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics +ok 109 - col_has_default( tab, col, desc ) should fail +ok 110 - col_has_default( tab, col, desc ) should have the proper description +ok 111 - col_has_default( tab, col, desc ) should have the proper diagnostics +ok 112 - col_has_default( tab, col ) should fail +ok 113 - col_has_default( tab, col ) should have the proper description +ok 114 - col_has_default( tab, col ) should have the proper diagnostics +ok 115 - col_has_default( sch, tab, col, desc ) should fail +ok 116 - col_has_default( sch, tab, col, desc ) should have the proper description +ok 117 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics +ok 118 - col_has_default( tab, col, desc ) should fail +ok 119 - col_has_default( tab, col, desc ) should have the proper description +ok 120 - col_has_default( tab, col, desc ) should have the proper diagnostics +ok 121 - col_has_default( tab, col ) should fail +ok 122 - col_has_default( tab, col ) should have the proper description +ok 123 - col_has_default( tab, col ) should have the proper diagnostics +ok 124 - col_hasnt_default( sch, tab, col, desc ) should fail +ok 125 - col_hasnt_default( sch, tab, col, desc ) should have the proper description +ok 126 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics +ok 127 - col_hasnt_default( tab, col, desc ) should fail +ok 128 - col_hasnt_default( tab, col, desc ) should have the proper description +ok 129 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics +ok 130 - col_hasnt_default( tab, col ) should fail +ok 131 - col_hasnt_default( tab, col ) should have the proper description +ok 132 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 133 - col_hasnt_default( sch, tab, col, desc ) should pass +ok 134 - col_hasnt_default( sch, tab, col, desc ) should have the proper description +ok 135 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics +ok 136 - col_hasnt_default( tab, col, desc ) should pass +ok 137 - col_hasnt_default( tab, col, desc ) should have the proper description +ok 138 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics +ok 139 - col_hasnt_default( tab, col ) should pass +ok 140 - col_hasnt_default( tab, col ) should have the proper description +ok 141 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 142 - col_hasnt_default( sch, tab, col, desc ) should fail +ok 143 - col_hasnt_default( sch, tab, col, desc ) should have the proper description +ok 144 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics +ok 145 - col_hasnt_default( tab, col, desc ) should fail +ok 146 - col_hasnt_default( tab, col, desc ) should have the proper description +ok 147 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics +ok 148 - col_hasnt_default( tab, col ) should fail +ok 149 - col_hasnt_default( tab, col ) should have the proper description +ok 150 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 151 - col_default_is( sch, tab, col, def, desc ) should pass +ok 152 - col_default_is( sch, tab, col, def, desc ) should have the proper description +ok 153 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics +ok 154 - col_default_is() fail should fail +ok 155 - col_default_is() fail should have the proper description +ok 156 - col_default_is() fail should have the proper diagnostics +ok 157 - col_default_is( tab, col, def, desc ) should pass +ok 158 - col_default_is( tab, col, def, desc ) should have the proper description +ok 159 - col_default_is( tab, col, def, desc ) should have the proper diagnostics +ok 160 - col_default_is( tab, col, def ) should pass +ok 161 - col_default_is( tab, col, def ) should have the proper description +ok 162 - col_default_is( tab, col, def ) should have the proper diagnostics +ok 163 - col_default_is( tab, col, int ) should pass +ok 164 - col_default_is( tab, col, int ) should have the proper description +ok 165 - col_default_is( tab, col, int ) should have the proper diagnostics +ok 166 - col_default_is( tab, col, NULL, desc ) should pass +ok 167 - col_default_is( tab, col, NULL, desc ) should have the proper description +ok 168 - col_default_is( tab, col, NULL, desc ) should have the proper diagnostics +ok 169 - col_default_is( tab, col, NULL ) should pass +ok 170 - col_default_is( tab, col, NULL ) should have the proper description +ok 171 - col_default_is( tab, col, NULL ) should have the proper diagnostics +ok 172 - col_default_is( tab, col, bogus, desc ) should fail +ok 173 - col_default_is( tab, col, bogus, desc ) should have the proper description +ok 174 - col_default_is( tab, col, bogus, desc ) should have the proper diagnostics +ok 175 - col_default_is( tab, col, bogus ) should fail +ok 176 - col_default_is( tab, col, bogus ) should have the proper description +ok 177 - col_default_is( tab, col, bogus ) should have the proper diagnostics +ok 178 - col_default_is( tab, col, expression ) should pass +ok 179 - col_default_is( tab, col, expression ) should have the proper description +ok 180 - col_default_is( tab, col, expression ) should have the proper diagnostics +ok 181 - col_default_is( tab, col, expression::text ) should pass +ok 182 - col_default_is( tab, col, expression::text ) should have the proper description +ok 183 - col_default_is( tab, col, expression::text ) should have the proper diagnostics +ok 184 - col_default_is( tab, col, expression, desc ) should pass +ok 185 - col_default_is( tab, col, expression, desc ) should have the proper description +ok 186 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics +ok 187 - col_default_is( tab, col, expression, desc ) should pass +ok 188 - col_default_is( tab, col, expression, desc ) should have the proper description +ok 189 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics +ok 190 - col_default_is( schema, tab, col, expression, desc ) should pass +ok 191 - col_default_is( schema, tab, col, expression, desc ) should have the proper description +ok 192 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics +ok 193 - col_default_is( schema, tab, col, expression, desc ) should pass +ok 194 - col_default_is( schema, tab, col, expression, desc ) should have the proper description +ok 195 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics +ok 196 - col_default_is( sch, tab, col, def, desc ) should fail +ok 197 - col_default_is( sch, tab, col, def, desc ) should have the proper description +ok 198 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics +ok 199 - col_default_is( tab, col, def, desc ) should fail +ok 200 - col_default_is( tab, col, def, desc ) should have the proper description +ok 201 - col_default_is( tab, col, def, desc ) should have the proper diagnostics +ok 202 - col_default_is( tab, col, def ) should fail +ok 203 - col_default_is( tab, col, def ) should have the proper description +ok 204 - col_default_is( tab, col, def ) should have the proper diagnostics diff --git a/expected/hastap.out b/expected/hastap.out index 431bd3059aa4..2a6c046f2614 100644 --- a/expected/hastap.out +++ b/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..675 +1..678 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -156,522 +156,525 @@ ok 153 - has_type(scheam, type) should have the proper diagnostics ok 154 - has_type(schema, type, desc) should pass ok 155 - has_type(schema, type, desc) should have the proper description ok 156 - has_type(schema, type, desc) should have the proper diagnostics -ok 157 - has_type(type) should fail -ok 158 - has_type(type) should have the proper description -ok 159 - has_type(type) should have the proper diagnostics -ok 160 - has_type(type, desc) should fail -ok 161 - has_type(type, desc) should have the proper description -ok 162 - has_type(type, desc) should have the proper diagnostics -ok 163 - has_type(scheam, type) should fail -ok 164 - has_type(scheam, type) should have the proper description -ok 165 - has_type(scheam, type) should have the proper diagnostics -ok 166 - has_type(schema, type, desc) should fail -ok 167 - has_type(schema, type, desc) should have the proper description -ok 168 - has_type(schema, type, desc) should have the proper diagnostics -ok 169 - has_type(domain) should pass -ok 170 - has_type(domain) should have the proper description -ok 171 - has_type(domain) should have the proper diagnostics -ok 172 - hasnt_type(type) should pass -ok 173 - hasnt_type(type) should have the proper description -ok 174 - hasnt_type(type) should have the proper diagnostics -ok 175 - hasnt_type(type, desc) should pass -ok 176 - hasnt_type(type, desc) should have the proper description -ok 177 - hasnt_type(type, desc) should have the proper diagnostics -ok 178 - hasnt_type(scheam, type) should pass -ok 179 - hasnt_type(scheam, type) should have the proper description -ok 180 - hasnt_type(scheam, type) should have the proper diagnostics -ok 181 - hasnt_type(schema, type, desc) should pass -ok 182 - hasnt_type(schema, type, desc) should have the proper description -ok 183 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 184 - hasnt_type(type) should fail -ok 185 - hasnt_type(type) should have the proper description -ok 186 - hasnt_type(type) should have the proper diagnostics -ok 187 - hasnt_type(type, desc) should fail -ok 188 - hasnt_type(type, desc) should have the proper description -ok 189 - hasnt_type(type, desc) should have the proper diagnostics -ok 190 - hasnt_type(scheam, type) should fail -ok 191 - hasnt_type(scheam, type) should have the proper description -ok 192 - hasnt_type(scheam, type) should have the proper diagnostics -ok 193 - hasnt_type(schema, type, desc) should fail -ok 194 - hasnt_type(schema, type, desc) should have the proper description -ok 195 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 196 - has_domain(domain) should pass -ok 197 - has_domain(domain) should have the proper description -ok 198 - has_domain(domain) should have the proper diagnostics -ok 199 - has_domain(domain, desc) should pass -ok 200 - has_domain(domain, desc) should have the proper description -ok 201 - has_domain(domain, desc) should have the proper diagnostics -ok 202 - has_domain(scheam, domain) should pass -ok 203 - has_domain(scheam, domain) should have the proper description -ok 204 - has_domain(scheam, domain) should have the proper diagnostics -ok 205 - has_domain(schema, domain, desc) should pass -ok 206 - has_domain(schema, domain, desc) should have the proper description -ok 207 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 208 - has_domain(domain) should fail -ok 209 - has_domain(domain) should have the proper description -ok 210 - has_domain(domain) should have the proper diagnostics -ok 211 - has_domain(domain, desc) should fail -ok 212 - has_domain(domain, desc) should have the proper description -ok 213 - has_domain(domain, desc) should have the proper diagnostics -ok 214 - has_domain(scheam, domain) should fail -ok 215 - has_domain(scheam, domain) should have the proper description -ok 216 - has_domain(scheam, domain) should have the proper diagnostics -ok 217 - has_domain(schema, domain, desc) should fail -ok 218 - has_domain(schema, domain, desc) should have the proper description -ok 219 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 220 - hasnt_domain(domain) should pass -ok 221 - hasnt_domain(domain) should have the proper description -ok 222 - hasnt_domain(domain) should have the proper diagnostics -ok 223 - hasnt_domain(domain, desc) should pass -ok 224 - hasnt_domain(domain, desc) should have the proper description -ok 225 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 226 - hasnt_domain(scheam, domain) should pass -ok 227 - hasnt_domain(scheam, domain) should have the proper description -ok 228 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 229 - hasnt_domain(schema, domain, desc) should pass -ok 230 - hasnt_domain(schema, domain, desc) should have the proper description -ok 231 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 232 - hasnt_domain(domain) should fail -ok 233 - hasnt_domain(domain) should have the proper description -ok 234 - hasnt_domain(domain) should have the proper diagnostics -ok 235 - hasnt_domain(domain, desc) should fail -ok 236 - hasnt_domain(domain, desc) should have the proper description -ok 237 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 238 - hasnt_domain(scheam, domain) should fail -ok 239 - hasnt_domain(scheam, domain) should have the proper description -ok 240 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 241 - hasnt_domain(schema, domain, desc) should fail -ok 242 - hasnt_domain(schema, domain, desc) should have the proper description -ok 243 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 244 - has_column(non-existent tab, col) should fail -ok 245 - has_column(non-existent tab, col) should have the proper description -ok 246 - has_column(non-existent tab, col) should have the proper diagnostics -ok 247 - has_column(non-existent tab, col, desc) should fail -ok 248 - has_column(non-existent tab, col, desc) should have the proper description -ok 249 - has_column(non-existent tab, col, desc) should have the proper diagnostics -ok 250 - has_column(non-existent sch, tab, col, desc) should fail -ok 251 - has_column(non-existent sch, tab, col, desc) should have the proper description -ok 252 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 253 - has_column(table, column) should pass -ok 254 - has_column(table, column) should have the proper description -ok 255 - has_column(table, column) should have the proper diagnostics -ok 256 - has_column(sch, tab, col, desc) should pass -ok 257 - has_column(sch, tab, col, desc) should have the proper description -ok 258 - has_column(sch, tab, col, desc) should have the proper diagnostics -ok 259 - has_column(view, column) should pass -ok 260 - has_column(view, column) should have the proper description -ok 261 - has_column(view, column) should have the proper diagnostics -ok 262 - has_column(type, column) should pass -ok 263 - has_column(type, column) should have the proper description -ok 264 - has_column(type, column) should have the proper diagnostics -ok 265 - hasnt_column(non-existent tab, col) should pass -ok 266 - hasnt_column(non-existent tab, col) should have the proper description -ok 267 - hasnt_column(non-existent tab, col) should have the proper diagnostics -ok 268 - hasnt_column(non-existent tab, col, desc) should pass -ok 269 - hasnt_column(non-existent tab, col, desc) should have the proper description -ok 270 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics -ok 271 - hasnt_column(non-existent sch, tab, col, desc) should pass -ok 272 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description -ok 273 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 274 - hasnt_column(table, column) should fail -ok 275 - hasnt_column(table, column) should have the proper description -ok 276 - hasnt_column(table, column) should have the proper diagnostics -ok 277 - hasnt_column(sch, tab, col, desc) should fail -ok 278 - hasnt_column(sch, tab, col, desc) should have the proper description -ok 279 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics -ok 280 - hasnt_column(view, column) should pass -ok 281 - hasnt_column(view, column) should have the proper description -ok 282 - hasnt_column(view, column) should have the proper diagnostics -ok 283 - hasnt_column(type, column) should pass -ok 284 - hasnt_column(type, column) should have the proper description -ok 285 - hasnt_column(type, column) should have the proper diagnostics -ok 286 - has_cast( src, targ, schema, func, desc) should pass -ok 287 - has_cast( src, targ, schema, func, desc) should have the proper description -ok 288 - has_cast( src, targ, schema, func, desc) should have the proper diagnostics -ok 289 - has_cast( src, targ, schema, func ) should pass -ok 290 - has_cast( src, targ, schema, func ) should have the proper description -ok 291 - has_cast( src, targ, schema, func ) should have the proper diagnostics -ok 292 - has_cast( src, targ, func, desc ) should pass -ok 293 - has_cast( src, targ, func, desc ) should have the proper description -ok 294 - has_cast( src, targ, func, desc ) should have the proper diagnostics -ok 295 - has_cast( src, targ, func) should pass -ok 296 - has_cast( src, targ, func) should have the proper description -ok 297 - has_cast( src, targ, func) should have the proper diagnostics -ok 298 - has_cast( src, targ, desc ) should pass -ok 299 - has_cast( src, targ, desc ) should have the proper description -ok 300 - has_cast( src, targ, desc ) should have the proper diagnostics -ok 301 - has_cast( src, targ ) should pass -ok 302 - has_cast( src, targ ) should have the proper description -ok 303 - has_cast( src, targ ) should have the proper diagnostics -ok 304 - has_cast( src, targ, schema, func, desc) fail should fail -ok 305 - has_cast( src, targ, schema, func, desc) fail should have the proper description -ok 306 - has_cast( src, targ, schema, func, desc) fail should have the proper diagnostics -ok 307 - has_cast( src, targ, func, desc ) fail should fail -ok 308 - has_cast( src, targ, func, desc ) fail should have the proper description -ok 309 - has_cast( src, targ, func, desc ) fail should have the proper diagnostics -ok 310 - has_cast( src, targ, desc ) fail should fail -ok 311 - has_cast( src, targ, desc ) fail should have the proper description -ok 312 - has_cast( src, targ, desc ) fail should have the proper diagnostics -ok 313 - hasnt_cast( src, targ, schema, func, desc) should fail -ok 314 - hasnt_cast( src, targ, schema, func, desc) should have the proper description -ok 315 - hasnt_cast( src, targ, schema, func, desc) should have the proper diagnostics -ok 316 - hasnt_cast( src, targ, schema, func ) should fail -ok 317 - hasnt_cast( src, targ, schema, func ) should have the proper description -ok 318 - hasnt_cast( src, targ, schema, func ) should have the proper diagnostics -ok 319 - hasnt_cast( src, targ, func, desc ) should fail -ok 320 - hasnt_cast( src, targ, func, desc ) should have the proper description -ok 321 - hasnt_cast( src, targ, func, desc ) should have the proper diagnostics -ok 322 - hasnt_cast( src, targ, func) should fail -ok 323 - hasnt_cast( src, targ, func) should have the proper description -ok 324 - hasnt_cast( src, targ, func) should have the proper diagnostics -ok 325 - hasnt_cast( src, targ, desc ) should fail -ok 326 - hasnt_cast( src, targ, desc ) should have the proper description -ok 327 - hasnt_cast( src, targ, desc ) should have the proper diagnostics -ok 328 - hasnt_cast( src, targ ) should fail -ok 329 - hasnt_cast( src, targ ) should have the proper description -ok 330 - hasnt_cast( src, targ ) should have the proper diagnostics -ok 331 - hasnt_cast( src, targ, schema, func, desc) fail should pass -ok 332 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper description -ok 333 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper diagnostics -ok 334 - hasnt_cast( src, targ, func, desc ) fail should pass -ok 335 - hasnt_cast( src, targ, func, desc ) fail should have the proper description -ok 336 - hasnt_cast( src, targ, func, desc ) fail should have the proper diagnostics -ok 337 - hasnt_cast( src, targ, desc ) fail should pass -ok 338 - hasnt_cast( src, targ, desc ) fail should have the proper description -ok 339 - hasnt_cast( src, targ, desc ) fail should have the proper diagnostics -ok 340 - cast_context_is( src, targ, context, desc ) should pass -ok 341 - cast_context_is( src, targ, context, desc ) should have the proper description -ok 342 - cast_context_is( src, targ, context, desc ) should have the proper diagnostics -ok 343 - cast_context_is( src, targ, context ) should pass -ok 344 - cast_context_is( src, targ, context ) should have the proper description -ok 345 - cast_context_is( src, targ, context ) should have the proper diagnostics -ok 346 - cast_context_is( src, targ, i, desc ) should pass -ok 347 - cast_context_is( src, targ, i, desc ) should have the proper description -ok 348 - cast_context_is( src, targ, i, desc ) should have the proper diagnostics -ok 349 - cast_context_is( src, targ, IMPL, desc ) should pass -ok 350 - cast_context_is( src, targ, IMPL, desc ) should have the proper description -ok 351 - cast_context_is( src, targ, IMPL, desc ) should have the proper diagnostics -ok 352 - cast_context_is( src, targ, assignment, desc ) should pass -ok 353 - cast_context_is( src, targ, assignment, desc ) should have the proper description -ok 354 - cast_context_is( src, targ, assignment, desc ) should have the proper diagnostics -ok 355 - cast_context_is( src, targ, a, desc ) should pass -ok 356 - cast_context_is( src, targ, a, desc ) should have the proper description -ok 357 - cast_context_is( src, targ, a, desc ) should have the proper diagnostics -ok 358 - cast_context_is( src, targ, ASS, desc ) should pass -ok 359 - cast_context_is( src, targ, ASS, desc ) should have the proper description -ok 360 - cast_context_is( src, targ, ASS, desc ) should have the proper diagnostics -ok 361 - cast_context_is( src, targ, explicit, desc ) should pass -ok 362 - cast_context_is( src, targ, explicit, desc ) should have the proper description -ok 363 - cast_context_is( src, targ, explicit, desc ) should have the proper diagnostics -ok 364 - cast_context_is( src, targ, e, desc ) should pass -ok 365 - cast_context_is( src, targ, e, desc ) should have the proper description -ok 366 - cast_context_is( src, targ, e, desc ) should have the proper diagnostics -ok 367 - cast_context_is( src, targ, EX, desc ) should pass -ok 368 - cast_context_is( src, targ, EX, desc ) should have the proper description -ok 369 - cast_context_is( src, targ, EX, desc ) should have the proper diagnostics -ok 370 - cast_context_is( src, targ, context, desc ) fail should fail -ok 371 - cast_context_is( src, targ, context, desc ) fail should have the proper description -ok 372 - cast_context_is( src, targ, context, desc ) fail should have the proper diagnostics -ok 373 - cast_context_is( src, targ, context ) fail should fail -ok 374 - cast_context_is( src, targ, context ) fail should have the proper description -ok 375 - cast_context_is( src, targ, context ) fail should have the proper diagnostics -ok 376 - cast_context_is( src, targ, context, desc ) noexist should fail -ok 377 - cast_context_is( src, targ, context, desc ) noexist should have the proper description -ok 378 - cast_context_is( src, targ, context, desc ) noexist should have the proper diagnostics -ok 379 - has_operator( left, schema, name, right, result, desc ) should pass -ok 380 - has_operator( left, schema, name, right, result, desc ) should have the proper description -ok 381 - has_operator( left, schema, name, right, result, desc ) should have the proper diagnostics -ok 382 - has_operator( left, schema, name, right, result ) should pass -ok 383 - has_operator( left, schema, name, right, result ) should have the proper description -ok 384 - has_operator( left, schema, name, right, result ) should have the proper diagnostics -ok 385 - has_operator( left, name, right, result, desc ) should pass -ok 386 - has_operator( left, name, right, result, desc ) should have the proper description -ok 387 - has_operator( left, name, right, result, desc ) should have the proper diagnostics -ok 388 - has_operator( left, name, right, result ) should pass -ok 389 - has_operator( left, name, right, result ) should have the proper description -ok 390 - has_operator( left, name, right, result ) should have the proper diagnostics -ok 391 - has_operator( left, name, right, desc ) should pass -ok 392 - has_operator( left, name, right, desc ) should have the proper description -ok 393 - has_operator( left, name, right, desc ) should have the proper diagnostics -ok 394 - has_operator( left, name, right ) should pass -ok 395 - has_operator( left, name, right ) should have the proper description -ok 396 - has_operator( left, name, right ) should have the proper diagnostics -ok 397 - has_operator( left, schema, name, right, result, desc ) fail should fail -ok 398 - has_operator( left, schema, name, right, result, desc ) fail should have the proper description -ok 399 - has_operator( left, schema, name, right, result, desc ) fail should have the proper diagnostics -ok 400 - has_operator( left, schema, name, right, result ) fail should fail -ok 401 - has_operator( left, schema, name, right, result ) fail should have the proper description -ok 402 - has_operator( left, schema, name, right, result ) fail should have the proper diagnostics -ok 403 - has_operator( left, name, right, result, desc ) fail should fail -ok 404 - has_operator( left, name, right, result, desc ) fail should have the proper description -ok 405 - has_operator( left, name, right, result, desc ) fail should have the proper diagnostics -ok 406 - has_operator( left, name, right, result ) fail should fail -ok 407 - has_operator( left, name, right, result ) fail should have the proper description -ok 408 - has_operator( left, name, right, result ) fail should have the proper diagnostics -ok 409 - has_operator( left, name, right, desc ) fail should fail -ok 410 - has_operator( left, name, right, desc ) fail should have the proper description -ok 411 - has_operator( left, name, right, desc ) fail should have the proper diagnostics -ok 412 - has_operator( left, name, right ) fail should fail -ok 413 - has_operator( left, name, right ) fail should have the proper description -ok 414 - has_operator( left, name, right ) fail should have the proper diagnostics -ok 415 - has_leftop( schema, name, right, result, desc ) should pass -ok 416 - has_leftop( schema, name, right, result, desc ) should have the proper description -ok 417 - has_leftop( schema, name, right, result, desc ) should have the proper diagnostics -ok 418 - has_leftop( schema, name, right, result ) should pass -ok 419 - has_leftop( schema, name, right, result ) should have the proper description -ok 420 - has_leftop( schema, name, right, result ) should have the proper diagnostics -ok 421 - has_leftop( name, right, result, desc ) should pass -ok 422 - has_leftop( name, right, result, desc ) should have the proper description -ok 423 - has_leftop( name, right, result, desc ) should have the proper diagnostics -ok 424 - has_leftop( name, right, result ) should pass -ok 425 - has_leftop( name, right, result ) should have the proper description -ok 426 - has_leftop( name, right, result ) should have the proper diagnostics -ok 427 - has_leftop( name, right, desc ) should pass -ok 428 - has_leftop( name, right, desc ) should have the proper description -ok 429 - has_leftop( name, right, desc ) should have the proper diagnostics -ok 430 - has_leftop( name, right ) should pass -ok 431 - has_leftop( name, right ) should have the proper description -ok 432 - has_leftop( name, right ) should have the proper diagnostics -ok 433 - has_leftop( schema, name, right, result, desc ) fail should fail -ok 434 - has_leftop( schema, name, right, result, desc ) fail should have the proper description -ok 435 - has_leftop( schema, name, right, result, desc ) fail should have the proper diagnostics -ok 436 - has_leftop( schema, name, right, result ) fail should fail -ok 437 - has_leftop( schema, name, right, result ) fail should have the proper description -ok 438 - has_leftop( schema, name, right, result ) fail should have the proper diagnostics -ok 439 - has_leftop( name, right, result, desc ) fail should fail -ok 440 - has_leftop( name, right, result, desc ) fail should have the proper description -ok 441 - has_leftop( name, right, result, desc ) fail should have the proper diagnostics -ok 442 - has_leftop( name, right, result ) fail should fail -ok 443 - has_leftop( name, right, result ) fail should have the proper description -ok 444 - has_leftop( name, right, result ) fail should have the proper diagnostics -ok 445 - has_leftop( name, right, desc ) fail should fail -ok 446 - has_leftop( name, right, desc ) fail should have the proper description -ok 447 - has_leftop( name, right, desc ) fail should have the proper diagnostics -ok 448 - has_leftop( name, right ) fail should fail -ok 449 - has_leftop( name, right ) fail should have the proper description -ok 450 - has_leftop( name, right ) fail should have the proper diagnostics -ok 451 - has_rightop( left, schema, name, result, desc ) should pass -ok 452 - has_rightop( left, schema, name, result, desc ) should have the proper description -ok 453 - has_rightop( left, schema, name, result, desc ) should have the proper diagnostics -ok 454 - has_rightop( left, schema, name, result ) should pass -ok 455 - has_rightop( left, schema, name, result ) should have the proper description -ok 456 - has_rightop( left, schema, name, result ) should have the proper diagnostics -ok 457 - has_rightop( left, name, result, desc ) should pass -ok 458 - has_rightop( left, name, result, desc ) should have the proper description -ok 459 - has_rightop( left, name, result, desc ) should have the proper diagnostics -ok 460 - has_rightop( left, name, result ) should pass -ok 461 - has_rightop( left, name, result ) should have the proper description -ok 462 - has_rightop( left, name, result ) should have the proper diagnostics -ok 463 - has_rightop( left, name, desc ) should pass -ok 464 - has_rightop( left, name, desc ) should have the proper description -ok 465 - has_rightop( left, name, desc ) should have the proper diagnostics -ok 466 - has_rightop( left, name ) should pass -ok 467 - has_rightop( left, name ) should have the proper description -ok 468 - has_rightop( left, name ) should have the proper diagnostics -ok 469 - has_rightop( left, schema, name, result, desc ) fail should fail -ok 470 - has_rightop( left, schema, name, result, desc ) fail should have the proper description -ok 471 - has_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics -ok 472 - has_rightop( left, schema, name, result ) fail should fail -ok 473 - has_rightop( left, schema, name, result ) fail should have the proper description -ok 474 - has_rightop( left, schema, name, result ) fail should have the proper diagnostics -ok 475 - has_rightop( left, name, result, desc ) fail should fail -ok 476 - has_rightop( left, name, result, desc ) fail should have the proper description -ok 477 - has_rightop( left, name, result, desc ) fail should have the proper diagnostics -ok 478 - has_rightop( left, name, result ) fail should fail -ok 479 - has_rightop( left, name, result ) fail should have the proper description -ok 480 - has_rightop( left, name, result ) fail should have the proper diagnostics -ok 481 - has_rightop( left, name, desc ) fail should fail -ok 482 - has_rightop( left, name, desc ) fail should have the proper description -ok 483 - has_rightop( left, name, desc ) fail should have the proper diagnostics -ok 484 - has_rightop( left, name ) fail should fail -ok 485 - has_rightop( left, name ) fail should have the proper description -ok 486 - has_rightop( left, name ) fail should have the proper diagnostics -ok 487 - has_language(language) should pass -ok 488 - has_language(language) should have the proper description -ok 489 - has_language(language) should have the proper diagnostics -ok 490 - has_language(language, desc) should pass -ok 491 - has_language(language, desc) should have the proper description -ok 492 - has_language(language, desc) should have the proper diagnostics -ok 493 - has_language(nonexistent language) should fail -ok 494 - has_language(nonexistent language) should have the proper description -ok 495 - has_language(nonexistent language) should have the proper diagnostics -ok 496 - has_language(nonexistent language, desc) should fail -ok 497 - has_language(nonexistent language, desc) should have the proper description -ok 498 - has_language(nonexistent language, desc) should have the proper diagnostics -ok 499 - hasnt_language(language) should fail -ok 500 - hasnt_language(language) should have the proper description -ok 501 - hasnt_language(language) should have the proper diagnostics -ok 502 - hasnt_language(language, desc) should fail -ok 503 - hasnt_language(language, desc) should have the proper description -ok 504 - hasnt_language(language, desc) should have the proper diagnostics -ok 505 - hasnt_language(nonexistent language) should pass -ok 506 - hasnt_language(nonexistent language) should have the proper description -ok 507 - hasnt_language(nonexistent language) should have the proper diagnostics -ok 508 - hasnt_language(nonexistent language, desc) should pass -ok 509 - hasnt_language(nonexistent language, desc) should have the proper description -ok 510 - hasnt_language(nonexistent language, desc) should have the proper diagnostics -ok 511 - language_is_trusted(language, desc) should pass -ok 512 - language_is_trusted(language, desc) should have the proper description -ok 513 - language_is_trusted(language, desc) should have the proper diagnostics -ok 514 - language_is_trusted(language) should pass -ok 515 - language_is_trusted(language) should have the proper description -ok 516 - language_is_trusted(language) should have the proper diagnostics -ok 517 - language_is_trusted(language, desc) fail should fail -ok 518 - language_is_trusted(language, desc) fail should have the proper description -ok 519 - language_is_trusted(language, desc) fail should have the proper diagnostics -ok 520 - language_is_trusted(language, desc) non-existent should fail -ok 521 - language_is_trusted(language, desc) non-existent should have the proper description -ok 522 - language_is_trusted(language, desc) non-existent should have the proper diagnostics -ok 523 - has_opclass( schema, name, desc ) should pass -ok 524 - has_opclass( schema, name, desc ) should have the proper description -ok 525 - has_opclass( schema, name, desc ) should have the proper diagnostics -ok 526 - has_opclass( schema, name ) should pass -ok 527 - has_opclass( schema, name ) should have the proper description -ok 528 - has_opclass( schema, name ) should have the proper diagnostics -ok 529 - has_opclass( name, desc ) should pass -ok 530 - has_opclass( name, desc ) should have the proper description -ok 531 - has_opclass( name, desc ) should have the proper diagnostics -ok 532 - has_opclass( name ) should pass -ok 533 - has_opclass( name ) should have the proper description -ok 534 - has_opclass( name ) should have the proper diagnostics -ok 535 - has_opclass( schema, name, desc ) fail should fail -ok 536 - has_opclass( schema, name, desc ) fail should have the proper description -ok 537 - has_opclass( schema, name, desc ) fail should have the proper diagnostics -ok 538 - has_opclass( name, desc ) fail should fail -ok 539 - has_opclass( name, desc ) fail should have the proper description -ok 540 - has_opclass( name, desc ) fail should have the proper diagnostics -ok 541 - hasnt_opclass( schema, name, desc ) should fail -ok 542 - hasnt_opclass( schema, name, desc ) should have the proper description -ok 543 - hasnt_opclass( schema, name, desc ) should have the proper diagnostics -ok 544 - hasnt_opclass( schema, name ) should fail -ok 545 - hasnt_opclass( schema, name ) should have the proper description -ok 546 - hasnt_opclass( schema, name ) should have the proper diagnostics -ok 547 - hasnt_opclass( name, desc ) should fail -ok 548 - hasnt_opclass( name, desc ) should have the proper description -ok 549 - hasnt_opclass( name, desc ) should have the proper diagnostics -ok 550 - hasnt_opclass( name ) should fail -ok 551 - hasnt_opclass( name ) should have the proper description -ok 552 - hasnt_opclass( name ) should have the proper diagnostics -ok 553 - hasnt_opclass( schema, name, desc ) fail should pass -ok 554 - hasnt_opclass( schema, name, desc ) fail should have the proper description -ok 555 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnostics -ok 556 - hasnt_opclass( name, desc ) fail should pass -ok 557 - hasnt_opclass( name, desc ) fail should have the proper description -ok 558 - hasnt_opclass( name, desc ) fail should have the proper diagnostics -ok 559 - domain_type_is(schema, domain, schema, type, desc) should pass -ok 560 - domain_type_is(schema, domain, schema, type, desc) should have the proper description -ok 561 - domain_type_is(schema, domain, schema, type, desc) should have the proper diagnostics -ok 562 - domain_type_is(schema, domain, schema, type) should pass -ok 563 - domain_type_is(schema, domain, schema, type) should have the proper description -ok 564 - domain_type_is(schema, domain, schema, type) should have the proper diagnostics -ok 565 - domain_type_is(schema, DOMAIN, schema, TYPE, desc) should pass -ok 566 - domain_type_is(schema, DOMAIN, schema, TYPE, desc) should have the proper description -ok 567 - domain_type_is(schema, DOMAIN, schema, TYPE, desc) should have the proper diagnostics -ok 568 - domain_type_is(schema, domain, schema, type, desc) fail should fail -ok 569 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper description -ok 570 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper diagnostics -ok 571 - domain_type_is(schema, nondomain, schema, type, desc) should fail -ok 572 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper description -ok 573 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper diagnostics -ok 574 - domain_type_is(schema, type, schema, type, desc) fail should fail -ok 575 - domain_type_is(schema, type, schema, type, desc) fail should have the proper description -ok 576 - domain_type_is(schema, type, schema, type, desc) fail should have the proper diagnostics -ok 577 - domain_type_is(schema, domain, type, desc) should pass -ok 578 - domain_type_is(schema, domain, type, desc) should have the proper description -ok 579 - domain_type_is(schema, domain, type, desc) should have the proper diagnostics -ok 580 - domain_type_is(schema, domain, type) should pass -ok 581 - domain_type_is(schema, domain, type) should have the proper description -ok 582 - domain_type_is(schema, domain, type) should have the proper diagnostics -ok 583 - domain_type_is(schema, DOMAIN, TYPE, desc) should pass -ok 584 - domain_type_is(schema, DOMAIN, TYPE, desc) should have the proper description -ok 585 - domain_type_is(schema, DOMAIN, TYPE, desc) should have the proper diagnostics -ok 586 - domain_type_is(schema, domain, type, desc) fail should fail -ok 587 - domain_type_is(schema, domain, type, desc) fail should have the proper description -ok 588 - domain_type_is(schema, domain, type, desc) fail should have the proper diagnostics -ok 589 - domain_type_is(schema, nondomain, type, desc) should fail -ok 590 - domain_type_is(schema, nondomain, type, desc) should have the proper description -ok 591 - domain_type_is(schema, nondomain, type, desc) should have the proper diagnostics -ok 592 - domain_type_is(schema, type, type, desc) fail should fail -ok 593 - domain_type_is(schema, type, type, desc) fail should have the proper description -ok 594 - domain_type_is(schema, type, type, desc) fail should have the proper diagnostics -ok 595 - domain_type_is(domain, type, desc) should pass -ok 596 - domain_type_is(domain, type, desc) should have the proper description -ok 597 - domain_type_is(domain, type, desc) should have the proper diagnostics -ok 598 - domain_type_is(domain, type) should pass -ok 599 - domain_type_is(domain, type) should have the proper description -ok 600 - domain_type_is(domain, type) should have the proper diagnostics -ok 601 - domain_type_is(DOMAIN, TYPE, desc) should pass -ok 602 - domain_type_is(DOMAIN, TYPE, desc) should have the proper description -ok 603 - domain_type_is(DOMAIN, TYPE, desc) should have the proper diagnostics -ok 604 - domain_type_is(domain, type, desc) fail should fail -ok 605 - domain_type_is(domain, type, desc) fail should have the proper description -ok 606 - domain_type_is(domain, type, desc) fail should have the proper diagnostics -ok 607 - domain_type_is(nondomain, type, desc) should fail -ok 608 - domain_type_is(nondomain, type, desc) should have the proper description -ok 609 - domain_type_is(nondomain, type, desc) should have the proper diagnostics -ok 610 - domain_type_is(type, type, desc) fail should fail -ok 611 - domain_type_is(type, type, desc) fail should have the proper description -ok 612 - domain_type_is(type, type, desc) fail should have the proper diagnostics -ok 613 - domain_type_isnt(schema, domain, schema, type, desc) should pass -ok 614 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper description -ok 615 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper diagnostics -ok 616 - domain_type_isnt(schema, domain, schema, type) should pass -ok 617 - domain_type_isnt(schema, domain, schema, type) should have the proper description -ok 618 - domain_type_isnt(schema, domain, schema, type) should have the proper diagnostics -ok 619 - domain_type_isnt(schema, DOMAIN, schema, TYPE, desc) should pass -ok 620 - domain_type_isnt(schema, DOMAIN, schema, TYPE, desc) should have the proper description -ok 621 - domain_type_isnt(schema, DOMAIN, schema, TYPE, desc) should have the proper diagnostics -ok 622 - domain_type_isnt(schema, domain, schema, type, desc) fail should fail -ok 623 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper description -ok 624 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper diagnostics -ok 625 - domain_type_isnt(schema, DOMAIN, schema, TYPE, desc) fail should fail -ok 626 - domain_type_isnt(schema, DOMAIN, schema, TYPE, desc) fail should have the proper description -ok 627 - domain_type_isnt(schema, DOMAIN, schema, TYPE, desc) fail should have the proper diagnostics -ok 628 - domain_type_isnt(schema, nondomain, schema, type, desc) should fail -ok 629 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper description -ok 630 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper diagnostics -ok 631 - domain_type_isnt(schema, type, schema, type, desc) should fail -ok 632 - domain_type_isnt(schema, type, schema, type, desc) should have the proper description -ok 633 - domain_type_isnt(schema, type, schema, type, desc) should have the proper diagnostics -ok 634 - domain_type_isnt(schema, domain, type, desc) should pass -ok 635 - domain_type_isnt(schema, domain, type, desc) should have the proper description -ok 636 - domain_type_isnt(schema, domain, type, desc) should have the proper diagnostics -ok 637 - domain_type_isnt(schema, domain, type) should pass -ok 638 - domain_type_isnt(schema, domain, type) should have the proper description -ok 639 - domain_type_isnt(schema, domain, type) should have the proper diagnostics -ok 640 - domain_type_isnt(schema, DOMAIN, TYPE, desc) should pass -ok 641 - domain_type_isnt(schema, DOMAIN, TYPE, desc) should have the proper description -ok 642 - domain_type_isnt(schema, DOMAIN, TYPE, desc) should have the proper diagnostics -ok 643 - domain_type_isnt(schema, domain, type, desc) fail should fail -ok 644 - domain_type_isnt(schema, domain, type, desc) fail should have the proper description -ok 645 - domain_type_isnt(schema, domain, type, desc) fail should have the proper diagnostics -ok 646 - domain_type_isnt(schema, DOMAIN, TYPE, desc) fail should fail -ok 647 - domain_type_isnt(schema, DOMAIN, TYPE, desc) fail should have the proper description -ok 648 - domain_type_isnt(schema, DOMAIN, TYPE, desc) fail should have the proper diagnostics -ok 649 - domain_type_isnt(schema, nondomain, type, desc) should fail -ok 650 - domain_type_isnt(schema, nondomain, type, desc) should have the proper description -ok 651 - domain_type_isnt(schema, nondomain, type, desc) should have the proper diagnostics -ok 652 - domain_type_isnt(schema, type, type, desc) should fail -ok 653 - domain_type_isnt(schema, type, type, desc) should have the proper description -ok 654 - domain_type_isnt(schema, type, type, desc) should have the proper diagnostics -ok 655 - domain_type_isnt(domain, type, desc) should pass -ok 656 - domain_type_isnt(domain, type, desc) should have the proper description -ok 657 - domain_type_isnt(domain, type, desc) should have the proper diagnostics -ok 658 - domain_type_isnt(DOMAIN, TYPE, desc) should pass -ok 659 - domain_type_isnt(DOMAIN, TYPE, desc) should have the proper description -ok 660 - domain_type_isnt(DOMAIN, TYPE, desc) should have the proper diagnostics -ok 661 - domain_type_isnt(domain, type) should pass -ok 662 - domain_type_isnt(domain, type) should have the proper description -ok 663 - domain_type_isnt(domain, type) should have the proper diagnostics -ok 664 - domain_type_isnt(domain, type, desc) fail should fail -ok 665 - domain_type_isnt(domain, type, desc) fail should have the proper description -ok 666 - domain_type_isnt(domain, type, desc) fail should have the proper diagnostics -ok 667 - domain_type_isnt(DOMAIN, TYPE, desc) fail should fail -ok 668 - domain_type_isnt(DOMAIN, TYPE, desc) fail should have the proper description -ok 669 - domain_type_isnt(DOMAIN, TYPE, desc) fail should have the proper diagnostics -ok 670 - domain_type_isnt(nondomain, type, desc) should fail -ok 671 - domain_type_isnt(nondomain, type, desc) should have the proper description -ok 672 - domain_type_isnt(nondomain, type, desc) should have the proper diagnostics -ok 673 - domain_type_isnt(type, type, desc) should fail -ok 674 - domain_type_isnt(type, type, desc) should have the proper description -ok 675 - domain_type_isnt(type, type, desc) should have the proper diagnostics +ok 157 - has_type(myType) should pass +ok 158 - has_type(myType) should have the proper description +ok 159 - has_type(myType) should have the proper diagnostics +ok 160 - has_type(myType, desc) should pass +ok 161 - has_type(myType, desc) should have the proper description +ok 162 - has_type(myType, desc) should have the proper diagnostics +ok 163 - has_type(scheam, myType) should pass +ok 164 - has_type(scheam, myType) should have the proper description +ok 165 - has_type(scheam, myType) should have the proper diagnostics +ok 166 - has_type(schema, myType, desc) should pass +ok 167 - has_type(schema, myType, desc) should have the proper description +ok 168 - has_type(schema, myType, desc) should have the proper diagnostics +ok 169 - has_type(type) should fail +ok 170 - has_type(type) should have the proper description +ok 171 - has_type(type) should have the proper diagnostics +ok 172 - has_type(type, desc) should fail +ok 173 - has_type(type, desc) should have the proper description +ok 174 - has_type(type, desc) should have the proper diagnostics +ok 175 - has_type(scheam, type) should fail +ok 176 - has_type(scheam, type) should have the proper description +ok 177 - has_type(scheam, type) should have the proper diagnostics +ok 178 - has_type(schema, type, desc) should fail +ok 179 - has_type(schema, type, desc) should have the proper description +ok 180 - has_type(schema, type, desc) should have the proper diagnostics +ok 181 - has_type(domain) should pass +ok 182 - has_type(domain) should have the proper description +ok 183 - has_type(domain) should have the proper diagnostics +ok 184 - has_type(myDomain) should pass +ok 185 - has_type(myDomain) should have the proper description +ok 186 - has_type(myDomain) should have the proper diagnostics +ok 187 - hasnt_type(type) should pass +ok 188 - hasnt_type(type) should have the proper description +ok 189 - hasnt_type(type) should have the proper diagnostics +ok 190 - hasnt_type(type, desc) should pass +ok 191 - hasnt_type(type, desc) should have the proper description +ok 192 - hasnt_type(type, desc) should have the proper diagnostics +ok 193 - hasnt_type(scheam, type) should pass +ok 194 - hasnt_type(scheam, type) should have the proper description +ok 195 - hasnt_type(scheam, type) should have the proper diagnostics +ok 196 - hasnt_type(schema, type, desc) should pass +ok 197 - hasnt_type(schema, type, desc) should have the proper description +ok 198 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 199 - hasnt_type(type) should fail +ok 200 - hasnt_type(type) should have the proper description +ok 201 - hasnt_type(type) should have the proper diagnostics +ok 202 - hasnt_type(type, desc) should fail +ok 203 - hasnt_type(type, desc) should have the proper description +ok 204 - hasnt_type(type, desc) should have the proper diagnostics +ok 205 - hasnt_type(scheam, type) should fail +ok 206 - hasnt_type(scheam, type) should have the proper description +ok 207 - hasnt_type(scheam, type) should have the proper diagnostics +ok 208 - hasnt_type(schema, type, desc) should fail +ok 209 - hasnt_type(schema, type, desc) should have the proper description +ok 210 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 211 - has_domain(domain) should pass +ok 212 - has_domain(domain) should have the proper description +ok 213 - has_domain(domain) should have the proper diagnostics +ok 214 - has_domain(domain, desc) should pass +ok 215 - has_domain(domain, desc) should have the proper description +ok 216 - has_domain(domain, desc) should have the proper diagnostics +ok 217 - has_domain(scheam, domain) should pass +ok 218 - has_domain(scheam, domain) should have the proper description +ok 219 - has_domain(scheam, domain) should have the proper diagnostics +ok 220 - has_domain(schema, domain, desc) should pass +ok 221 - has_domain(schema, domain, desc) should have the proper description +ok 222 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 223 - has_domain(myDomain) should pass +ok 224 - has_domain(myDomain) should have the proper description +ok 225 - has_domain(myDomain) should have the proper diagnostics +ok 226 - has_domain(myDomain, desc) should pass +ok 227 - has_domain(myDomain, desc) should have the proper description +ok 228 - has_domain(myDomain, desc) should have the proper diagnostics +ok 229 - has_domain(scheam, myDomain) should pass +ok 230 - has_domain(scheam, myDomain) should have the proper description +ok 231 - has_domain(scheam, myDomain) should have the proper diagnostics +ok 232 - has_domain(schema, myDomain, desc) should pass +ok 233 - has_domain(schema, myDomain, desc) should have the proper description +ok 234 - has_domain(schema, myDomain, desc) should have the proper diagnostics +ok 235 - has_domain(domain) should fail +ok 236 - has_domain(domain) should have the proper description +ok 237 - has_domain(domain) should have the proper diagnostics +ok 238 - has_domain(domain, desc) should fail +ok 239 - has_domain(domain, desc) should have the proper description +ok 240 - has_domain(domain, desc) should have the proper diagnostics +ok 241 - has_domain(scheam, domain) should fail +ok 242 - has_domain(scheam, domain) should have the proper description +ok 243 - has_domain(scheam, domain) should have the proper diagnostics +ok 244 - has_domain(schema, domain, desc) should fail +ok 245 - has_domain(schema, domain, desc) should have the proper description +ok 246 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 247 - hasnt_domain(domain) should pass +ok 248 - hasnt_domain(domain) should have the proper description +ok 249 - hasnt_domain(domain) should have the proper diagnostics +ok 250 - hasnt_domain(domain, desc) should pass +ok 251 - hasnt_domain(domain, desc) should have the proper description +ok 252 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 253 - hasnt_domain(scheam, domain) should pass +ok 254 - hasnt_domain(scheam, domain) should have the proper description +ok 255 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 256 - hasnt_domain(schema, domain, desc) should pass +ok 257 - hasnt_domain(schema, domain, desc) should have the proper description +ok 258 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 259 - hasnt_domain(domain) should fail +ok 260 - hasnt_domain(domain) should have the proper description +ok 261 - hasnt_domain(domain) should have the proper diagnostics +ok 262 - hasnt_domain(domain, desc) should fail +ok 263 - hasnt_domain(domain, desc) should have the proper description +ok 264 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 265 - hasnt_domain(scheam, domain) should fail +ok 266 - hasnt_domain(scheam, domain) should have the proper description +ok 267 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 268 - hasnt_domain(schema, domain, desc) should fail +ok 269 - hasnt_domain(schema, domain, desc) should have the proper description +ok 270 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 271 - has_column(non-existent tab, col) should fail +ok 272 - has_column(non-existent tab, col) should have the proper description +ok 273 - has_column(non-existent tab, col) should have the proper diagnostics +ok 274 - has_column(non-existent tab, col, desc) should fail +ok 275 - has_column(non-existent tab, col, desc) should have the proper description +ok 276 - has_column(non-existent tab, col, desc) should have the proper diagnostics +ok 277 - has_column(non-existent sch, tab, col, desc) should fail +ok 278 - has_column(non-existent sch, tab, col, desc) should have the proper description +ok 279 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 280 - has_column(table, column) should pass +ok 281 - has_column(table, column) should have the proper description +ok 282 - has_column(table, column) should have the proper diagnostics +ok 283 - has_column(sch, tab, col, desc) should pass +ok 284 - has_column(sch, tab, col, desc) should have the proper description +ok 285 - has_column(sch, tab, col, desc) should have the proper diagnostics +ok 286 - has_column(table, camleCase column) should pass +ok 287 - has_column(table, camleCase column) should have the proper description +ok 288 - has_column(table, camleCase column) should have the proper diagnostics +ok 289 - has_column(view, column) should pass +ok 290 - has_column(view, column) should have the proper description +ok 291 - has_column(view, column) should have the proper diagnostics +ok 292 - has_column(type, column) should pass +ok 293 - has_column(type, column) should have the proper description +ok 294 - has_column(type, column) should have the proper diagnostics +ok 295 - hasnt_column(non-existent tab, col) should pass +ok 296 - hasnt_column(non-existent tab, col) should have the proper description +ok 297 - hasnt_column(non-existent tab, col) should have the proper diagnostics +ok 298 - hasnt_column(non-existent tab, col, desc) should pass +ok 299 - hasnt_column(non-existent tab, col, desc) should have the proper description +ok 300 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics +ok 301 - hasnt_column(non-existent sch, tab, col, desc) should pass +ok 302 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description +ok 303 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 304 - hasnt_column(table, column) should fail +ok 305 - hasnt_column(table, column) should have the proper description +ok 306 - hasnt_column(table, column) should have the proper diagnostics +ok 307 - hasnt_column(sch, tab, col, desc) should fail +ok 308 - hasnt_column(sch, tab, col, desc) should have the proper description +ok 309 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics +ok 310 - hasnt_column(view, column) should pass +ok 311 - hasnt_column(view, column) should have the proper description +ok 312 - hasnt_column(view, column) should have the proper diagnostics +ok 313 - hasnt_column(type, column) should pass +ok 314 - hasnt_column(type, column) should have the proper description +ok 315 - hasnt_column(type, column) should have the proper diagnostics +ok 316 - has_cast( src, targ, schema, func, desc) should pass +ok 317 - has_cast( src, targ, schema, func, desc) should have the proper description +ok 318 - has_cast( src, targ, schema, func, desc) should have the proper diagnostics +ok 319 - has_cast( src, targ, schema, func ) should pass +ok 320 - has_cast( src, targ, schema, func ) should have the proper description +ok 321 - has_cast( src, targ, schema, func ) should have the proper diagnostics +ok 322 - has_cast( src, targ, func, desc ) should pass +ok 323 - has_cast( src, targ, func, desc ) should have the proper description +ok 324 - has_cast( src, targ, func, desc ) should have the proper diagnostics +ok 325 - has_cast( src, targ, func) should pass +ok 326 - has_cast( src, targ, func) should have the proper description +ok 327 - has_cast( src, targ, func) should have the proper diagnostics +ok 328 - has_cast( src, targ, desc ) should pass +ok 329 - has_cast( src, targ, desc ) should have the proper description +ok 330 - has_cast( src, targ, desc ) should have the proper diagnostics +ok 331 - has_cast( src, targ ) should pass +ok 332 - has_cast( src, targ ) should have the proper description +ok 333 - has_cast( src, targ ) should have the proper diagnostics +ok 334 - has_cast( src, targ, schema, func, desc) fail should fail +ok 335 - has_cast( src, targ, schema, func, desc) fail should have the proper description +ok 336 - has_cast( src, targ, schema, func, desc) fail should have the proper diagnostics +ok 337 - has_cast( src, targ, func, desc ) fail should fail +ok 338 - has_cast( src, targ, func, desc ) fail should have the proper description +ok 339 - has_cast( src, targ, func, desc ) fail should have the proper diagnostics +ok 340 - has_cast( src, targ, desc ) fail should fail +ok 341 - has_cast( src, targ, desc ) fail should have the proper description +ok 342 - has_cast( src, targ, desc ) fail should have the proper diagnostics +ok 343 - hasnt_cast( src, targ, schema, func, desc) should fail +ok 344 - hasnt_cast( src, targ, schema, func, desc) should have the proper description +ok 345 - hasnt_cast( src, targ, schema, func, desc) should have the proper diagnostics +ok 346 - hasnt_cast( src, targ, schema, func ) should fail +ok 347 - hasnt_cast( src, targ, schema, func ) should have the proper description +ok 348 - hasnt_cast( src, targ, schema, func ) should have the proper diagnostics +ok 349 - hasnt_cast( src, targ, func, desc ) should fail +ok 350 - hasnt_cast( src, targ, func, desc ) should have the proper description +ok 351 - hasnt_cast( src, targ, func, desc ) should have the proper diagnostics +ok 352 - hasnt_cast( src, targ, func) should fail +ok 353 - hasnt_cast( src, targ, func) should have the proper description +ok 354 - hasnt_cast( src, targ, func) should have the proper diagnostics +ok 355 - hasnt_cast( src, targ, desc ) should fail +ok 356 - hasnt_cast( src, targ, desc ) should have the proper description +ok 357 - hasnt_cast( src, targ, desc ) should have the proper diagnostics +ok 358 - hasnt_cast( src, targ ) should fail +ok 359 - hasnt_cast( src, targ ) should have the proper description +ok 360 - hasnt_cast( src, targ ) should have the proper diagnostics +ok 361 - hasnt_cast( src, targ, schema, func, desc) fail should pass +ok 362 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper description +ok 363 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper diagnostics +ok 364 - hasnt_cast( src, targ, func, desc ) fail should pass +ok 365 - hasnt_cast( src, targ, func, desc ) fail should have the proper description +ok 366 - hasnt_cast( src, targ, func, desc ) fail should have the proper diagnostics +ok 367 - hasnt_cast( src, targ, desc ) fail should pass +ok 368 - hasnt_cast( src, targ, desc ) fail should have the proper description +ok 369 - hasnt_cast( src, targ, desc ) fail should have the proper diagnostics +ok 370 - cast_context_is( src, targ, context, desc ) should pass +ok 371 - cast_context_is( src, targ, context, desc ) should have the proper description +ok 372 - cast_context_is( src, targ, context, desc ) should have the proper diagnostics +ok 373 - cast_context_is( src, targ, context ) should pass +ok 374 - cast_context_is( src, targ, context ) should have the proper description +ok 375 - cast_context_is( src, targ, context ) should have the proper diagnostics +ok 376 - cast_context_is( src, targ, i, desc ) should pass +ok 377 - cast_context_is( src, targ, i, desc ) should have the proper description +ok 378 - cast_context_is( src, targ, i, desc ) should have the proper diagnostics +ok 379 - cast_context_is( src, targ, IMPL, desc ) should pass +ok 380 - cast_context_is( src, targ, IMPL, desc ) should have the proper description +ok 381 - cast_context_is( src, targ, IMPL, desc ) should have the proper diagnostics +ok 382 - cast_context_is( src, targ, assignment, desc ) should pass +ok 383 - cast_context_is( src, targ, assignment, desc ) should have the proper description +ok 384 - cast_context_is( src, targ, assignment, desc ) should have the proper diagnostics +ok 385 - cast_context_is( src, targ, a, desc ) should pass +ok 386 - cast_context_is( src, targ, a, desc ) should have the proper description +ok 387 - cast_context_is( src, targ, a, desc ) should have the proper diagnostics +ok 388 - cast_context_is( src, targ, ASS, desc ) should pass +ok 389 - cast_context_is( src, targ, ASS, desc ) should have the proper description +ok 390 - cast_context_is( src, targ, ASS, desc ) should have the proper diagnostics +ok 391 - cast_context_is( src, targ, explicit, desc ) should pass +ok 392 - cast_context_is( src, targ, explicit, desc ) should have the proper description +ok 393 - cast_context_is( src, targ, explicit, desc ) should have the proper diagnostics +ok 394 - cast_context_is( src, targ, e, desc ) should pass +ok 395 - cast_context_is( src, targ, e, desc ) should have the proper description +ok 396 - cast_context_is( src, targ, e, desc ) should have the proper diagnostics +ok 397 - cast_context_is( src, targ, EX, desc ) should pass +ok 398 - cast_context_is( src, targ, EX, desc ) should have the proper description +ok 399 - cast_context_is( src, targ, EX, desc ) should have the proper diagnostics +ok 400 - cast_context_is( src, targ, context, desc ) fail should fail +ok 401 - cast_context_is( src, targ, context, desc ) fail should have the proper description +ok 402 - cast_context_is( src, targ, context, desc ) fail should have the proper diagnostics +ok 403 - cast_context_is( src, targ, context ) fail should fail +ok 404 - cast_context_is( src, targ, context ) fail should have the proper description +ok 405 - cast_context_is( src, targ, context ) fail should have the proper diagnostics +ok 406 - cast_context_is( src, targ, context, desc ) noexist should fail +ok 407 - cast_context_is( src, targ, context, desc ) noexist should have the proper description +ok 408 - cast_context_is( src, targ, context, desc ) noexist should have the proper diagnostics +ok 409 - has_operator( left, schema, name, right, result, desc ) should pass +ok 410 - has_operator( left, schema, name, right, result, desc ) should have the proper description +ok 411 - has_operator( left, schema, name, right, result, desc ) should have the proper diagnostics +ok 412 - has_operator( left, schema, name, right, result ) should pass +ok 413 - has_operator( left, schema, name, right, result ) should have the proper description +ok 414 - has_operator( left, schema, name, right, result ) should have the proper diagnostics +ok 415 - has_operator( left, name, right, result, desc ) should pass +ok 416 - has_operator( left, name, right, result, desc ) should have the proper description +ok 417 - has_operator( left, name, right, result, desc ) should have the proper diagnostics +ok 418 - has_operator( left, name, right, result ) should pass +ok 419 - has_operator( left, name, right, result ) should have the proper description +ok 420 - has_operator( left, name, right, result ) should have the proper diagnostics +ok 421 - has_operator( left, name, right, desc ) should pass +ok 422 - has_operator( left, name, right, desc ) should have the proper description +ok 423 - has_operator( left, name, right, desc ) should have the proper diagnostics +ok 424 - has_operator( left, name, right ) should pass +ok 425 - has_operator( left, name, right ) should have the proper description +ok 426 - has_operator( left, name, right ) should have the proper diagnostics +ok 427 - has_operator( left, schema, name, right, result, desc ) fail should fail +ok 428 - has_operator( left, schema, name, right, result, desc ) fail should have the proper description +ok 429 - has_operator( left, schema, name, right, result, desc ) fail should have the proper diagnostics +ok 430 - has_operator( left, schema, name, right, result ) fail should fail +ok 431 - has_operator( left, schema, name, right, result ) fail should have the proper description +ok 432 - has_operator( left, schema, name, right, result ) fail should have the proper diagnostics +ok 433 - has_operator( left, name, right, result, desc ) fail should fail +ok 434 - has_operator( left, name, right, result, desc ) fail should have the proper description +ok 435 - has_operator( left, name, right, result, desc ) fail should have the proper diagnostics +ok 436 - has_operator( left, name, right, result ) fail should fail +ok 437 - has_operator( left, name, right, result ) fail should have the proper description +ok 438 - has_operator( left, name, right, result ) fail should have the proper diagnostics +ok 439 - has_operator( left, name, right, desc ) fail should fail +ok 440 - has_operator( left, name, right, desc ) fail should have the proper description +ok 441 - has_operator( left, name, right, desc ) fail should have the proper diagnostics +ok 442 - has_operator( left, name, right ) fail should fail +ok 443 - has_operator( left, name, right ) fail should have the proper description +ok 444 - has_operator( left, name, right ) fail should have the proper diagnostics +ok 445 - has_leftop( schema, name, right, result, desc ) should pass +ok 446 - has_leftop( schema, name, right, result, desc ) should have the proper description +ok 447 - has_leftop( schema, name, right, result, desc ) should have the proper diagnostics +ok 448 - has_leftop( schema, name, right, result ) should pass +ok 449 - has_leftop( schema, name, right, result ) should have the proper description +ok 450 - has_leftop( schema, name, right, result ) should have the proper diagnostics +ok 451 - has_leftop( name, right, result, desc ) should pass +ok 452 - has_leftop( name, right, result, desc ) should have the proper description +ok 453 - has_leftop( name, right, result, desc ) should have the proper diagnostics +ok 454 - has_leftop( name, right, result ) should pass +ok 455 - has_leftop( name, right, result ) should have the proper description +ok 456 - has_leftop( name, right, result ) should have the proper diagnostics +ok 457 - has_leftop( name, right, desc ) should pass +ok 458 - has_leftop( name, right, desc ) should have the proper description +ok 459 - has_leftop( name, right, desc ) should have the proper diagnostics +ok 460 - has_leftop( name, right ) should pass +ok 461 - has_leftop( name, right ) should have the proper description +ok 462 - has_leftop( name, right ) should have the proper diagnostics +ok 463 - has_leftop( schema, name, right, result, desc ) fail should fail +ok 464 - has_leftop( schema, name, right, result, desc ) fail should have the proper description +ok 465 - has_leftop( schema, name, right, result, desc ) fail should have the proper diagnostics +ok 466 - has_leftop( schema, name, right, result ) fail should fail +ok 467 - has_leftop( schema, name, right, result ) fail should have the proper description +ok 468 - has_leftop( schema, name, right, result ) fail should have the proper diagnostics +ok 469 - has_leftop( name, right, result, desc ) fail should fail +ok 470 - has_leftop( name, right, result, desc ) fail should have the proper description +ok 471 - has_leftop( name, right, result, desc ) fail should have the proper diagnostics +ok 472 - has_leftop( name, right, result ) fail should fail +ok 473 - has_leftop( name, right, result ) fail should have the proper description +ok 474 - has_leftop( name, right, result ) fail should have the proper diagnostics +ok 475 - has_leftop( name, right, desc ) fail should fail +ok 476 - has_leftop( name, right, desc ) fail should have the proper description +ok 477 - has_leftop( name, right, desc ) fail should have the proper diagnostics +ok 478 - has_leftop( name, right ) fail should fail +ok 479 - has_leftop( name, right ) fail should have the proper description +ok 480 - has_leftop( name, right ) fail should have the proper diagnostics +ok 481 - has_rightop( left, schema, name, result, desc ) should pass +ok 482 - has_rightop( left, schema, name, result, desc ) should have the proper description +ok 483 - has_rightop( left, schema, name, result, desc ) should have the proper diagnostics +ok 484 - has_rightop( left, schema, name, result ) should pass +ok 485 - has_rightop( left, schema, name, result ) should have the proper description +ok 486 - has_rightop( left, schema, name, result ) should have the proper diagnostics +ok 487 - has_rightop( left, name, result, desc ) should pass +ok 488 - has_rightop( left, name, result, desc ) should have the proper description +ok 489 - has_rightop( left, name, result, desc ) should have the proper diagnostics +ok 490 - has_rightop( left, name, result ) should pass +ok 491 - has_rightop( left, name, result ) should have the proper description +ok 492 - has_rightop( left, name, result ) should have the proper diagnostics +ok 493 - has_rightop( left, name, desc ) should pass +ok 494 - has_rightop( left, name, desc ) should have the proper description +ok 495 - has_rightop( left, name, desc ) should have the proper diagnostics +ok 496 - has_rightop( left, name ) should pass +ok 497 - has_rightop( left, name ) should have the proper description +ok 498 - has_rightop( left, name ) should have the proper diagnostics +ok 499 - has_rightop( left, schema, name, result, desc ) fail should fail +ok 500 - has_rightop( left, schema, name, result, desc ) fail should have the proper description +ok 501 - has_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics +ok 502 - has_rightop( left, schema, name, result ) fail should fail +ok 503 - has_rightop( left, schema, name, result ) fail should have the proper description +ok 504 - has_rightop( left, schema, name, result ) fail should have the proper diagnostics +ok 505 - has_rightop( left, name, result, desc ) fail should fail +ok 506 - has_rightop( left, name, result, desc ) fail should have the proper description +ok 507 - has_rightop( left, name, result, desc ) fail should have the proper diagnostics +ok 508 - has_rightop( left, name, result ) fail should fail +ok 509 - has_rightop( left, name, result ) fail should have the proper description +ok 510 - has_rightop( left, name, result ) fail should have the proper diagnostics +ok 511 - has_rightop( left, name, desc ) fail should fail +ok 512 - has_rightop( left, name, desc ) fail should have the proper description +ok 513 - has_rightop( left, name, desc ) fail should have the proper diagnostics +ok 514 - has_rightop( left, name ) fail should fail +ok 515 - has_rightop( left, name ) fail should have the proper description +ok 516 - has_rightop( left, name ) fail should have the proper diagnostics +ok 517 - has_language(language) should pass +ok 518 - has_language(language) should have the proper description +ok 519 - has_language(language) should have the proper diagnostics +ok 520 - has_language(language, desc) should pass +ok 521 - has_language(language, desc) should have the proper description +ok 522 - has_language(language, desc) should have the proper diagnostics +ok 523 - has_language(nonexistent language) should fail +ok 524 - has_language(nonexistent language) should have the proper description +ok 525 - has_language(nonexistent language) should have the proper diagnostics +ok 526 - has_language(nonexistent language, desc) should fail +ok 527 - has_language(nonexistent language, desc) should have the proper description +ok 528 - has_language(nonexistent language, desc) should have the proper diagnostics +ok 529 - hasnt_language(language) should fail +ok 530 - hasnt_language(language) should have the proper description +ok 531 - hasnt_language(language) should have the proper diagnostics +ok 532 - hasnt_language(language, desc) should fail +ok 533 - hasnt_language(language, desc) should have the proper description +ok 534 - hasnt_language(language, desc) should have the proper diagnostics +ok 535 - hasnt_language(nonexistent language) should pass +ok 536 - hasnt_language(nonexistent language) should have the proper description +ok 537 - hasnt_language(nonexistent language) should have the proper diagnostics +ok 538 - hasnt_language(nonexistent language, desc) should pass +ok 539 - hasnt_language(nonexistent language, desc) should have the proper description +ok 540 - hasnt_language(nonexistent language, desc) should have the proper diagnostics +ok 541 - language_is_trusted(language, desc) should pass +ok 542 - language_is_trusted(language, desc) should have the proper description +ok 543 - language_is_trusted(language, desc) should have the proper diagnostics +ok 544 - language_is_trusted(language) should pass +ok 545 - language_is_trusted(language) should have the proper description +ok 546 - language_is_trusted(language) should have the proper diagnostics +ok 547 - language_is_trusted(language, desc) fail should fail +ok 548 - language_is_trusted(language, desc) fail should have the proper description +ok 549 - language_is_trusted(language, desc) fail should have the proper diagnostics +ok 550 - language_is_trusted(language, desc) non-existent should fail +ok 551 - language_is_trusted(language, desc) non-existent should have the proper description +ok 552 - language_is_trusted(language, desc) non-existent should have the proper diagnostics +ok 553 - has_opclass( schema, name, desc ) should pass +ok 554 - has_opclass( schema, name, desc ) should have the proper description +ok 555 - has_opclass( schema, name, desc ) should have the proper diagnostics +ok 556 - has_opclass( schema, name ) should pass +ok 557 - has_opclass( schema, name ) should have the proper description +ok 558 - has_opclass( schema, name ) should have the proper diagnostics +ok 559 - has_opclass( name, desc ) should pass +ok 560 - has_opclass( name, desc ) should have the proper description +ok 561 - has_opclass( name, desc ) should have the proper diagnostics +ok 562 - has_opclass( name ) should pass +ok 563 - has_opclass( name ) should have the proper description +ok 564 - has_opclass( name ) should have the proper diagnostics +ok 565 - has_opclass( schema, name, desc ) fail should fail +ok 566 - has_opclass( schema, name, desc ) fail should have the proper description +ok 567 - has_opclass( schema, name, desc ) fail should have the proper diagnostics +ok 568 - has_opclass( name, desc ) fail should fail +ok 569 - has_opclass( name, desc ) fail should have the proper description +ok 570 - has_opclass( name, desc ) fail should have the proper diagnostics +ok 571 - hasnt_opclass( schema, name, desc ) should fail +ok 572 - hasnt_opclass( schema, name, desc ) should have the proper description +ok 573 - hasnt_opclass( schema, name, desc ) should have the proper diagnostics +ok 574 - hasnt_opclass( schema, name ) should fail +ok 575 - hasnt_opclass( schema, name ) should have the proper description +ok 576 - hasnt_opclass( schema, name ) should have the proper diagnostics +ok 577 - hasnt_opclass( name, desc ) should fail +ok 578 - hasnt_opclass( name, desc ) should have the proper description +ok 579 - hasnt_opclass( name, desc ) should have the proper diagnostics +ok 580 - hasnt_opclass( name ) should fail +ok 581 - hasnt_opclass( name ) should have the proper description +ok 582 - hasnt_opclass( name ) should have the proper diagnostics +ok 583 - hasnt_opclass( schema, name, desc ) fail should pass +ok 584 - hasnt_opclass( schema, name, desc ) fail should have the proper description +ok 585 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnostics +ok 586 - hasnt_opclass( name, desc ) fail should pass +ok 587 - hasnt_opclass( name, desc ) fail should have the proper description +ok 588 - hasnt_opclass( name, desc ) fail should have the proper diagnostics +ok 589 - domain_type_is(schema, domain, schema, type, desc) should pass +ok 590 - domain_type_is(schema, domain, schema, type, desc) should have the proper description +ok 591 - domain_type_is(schema, domain, schema, type, desc) should have the proper diagnostics +ok 592 - domain_type_is(schema, domain, schema, type) should pass +ok 593 - domain_type_is(schema, domain, schema, type) should have the proper description +ok 594 - domain_type_is(schema, domain, schema, type) should have the proper diagnostics +ok 595 - domain_type_is(schema, domain, schema, type, desc) fail should fail +ok 596 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper description +ok 597 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 598 - domain_type_is(schema, nondomain, schema, type, desc) should fail +ok 599 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper description +ok 600 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 601 - domain_type_is(schema, type, schema, type, desc) fail should fail +ok 602 - domain_type_is(schema, type, schema, type, desc) fail should have the proper description +ok 603 - domain_type_is(schema, type, schema, type, desc) fail should have the proper diagnostics +ok 604 - domain_type_is(schema, domain, type, desc) should pass +ok 605 - domain_type_is(schema, domain, type, desc) should have the proper description +ok 606 - domain_type_is(schema, domain, type, desc) should have the proper diagnostics +ok 607 - domain_type_is(schema, domain, type) should pass +ok 608 - domain_type_is(schema, domain, type) should have the proper description +ok 609 - domain_type_is(schema, domain, type) should have the proper diagnostics +ok 610 - domain_type_is(schema, domain, type, desc) fail should fail +ok 611 - domain_type_is(schema, domain, type, desc) fail should have the proper description +ok 612 - domain_type_is(schema, domain, type, desc) fail should have the proper diagnostics +ok 613 - domain_type_is(schema, nondomain, type, desc) should fail +ok 614 - domain_type_is(schema, nondomain, type, desc) should have the proper description +ok 615 - domain_type_is(schema, nondomain, type, desc) should have the proper diagnostics +ok 616 - domain_type_is(schema, type, type, desc) fail should fail +ok 617 - domain_type_is(schema, type, type, desc) fail should have the proper description +ok 618 - domain_type_is(schema, type, type, desc) fail should have the proper diagnostics +ok 619 - domain_type_is(domain, type, desc) should pass +ok 620 - domain_type_is(domain, type, desc) should have the proper description +ok 621 - domain_type_is(domain, type, desc) should have the proper diagnostics +ok 622 - domain_type_is(domain, type) should pass +ok 623 - domain_type_is(domain, type) should have the proper description +ok 624 - domain_type_is(domain, type) should have the proper diagnostics +ok 625 - domain_type_is(domain, type, desc) fail should fail +ok 626 - domain_type_is(domain, type, desc) fail should have the proper description +ok 627 - domain_type_is(domain, type, desc) fail should have the proper diagnostics +ok 628 - domain_type_is(nondomain, type, desc) should fail +ok 629 - domain_type_is(nondomain, type, desc) should have the proper description +ok 630 - domain_type_is(nondomain, type, desc) should have the proper diagnostics +ok 631 - domain_type_is(type, type, desc) fail should fail +ok 632 - domain_type_is(type, type, desc) fail should have the proper description +ok 633 - domain_type_is(type, type, desc) fail should have the proper diagnostics +ok 634 - domain_type_isnt(schema, domain, schema, type, desc) should pass +ok 635 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper description +ok 636 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper diagnostics +ok 637 - domain_type_isnt(schema, domain, schema, type) should pass +ok 638 - domain_type_isnt(schema, domain, schema, type) should have the proper description +ok 639 - domain_type_isnt(schema, domain, schema, type) should have the proper diagnostics +ok 640 - domain_type_isnt(schema, domain, schema, type, desc) fail should fail +ok 641 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper description +ok 642 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 643 - domain_type_isnt(schema, nondomain, schema, type, desc) should fail +ok 644 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper description +ok 645 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 646 - domain_type_isnt(schema, type, schema, type, desc) should fail +ok 647 - domain_type_isnt(schema, type, schema, type, desc) should have the proper description +ok 648 - domain_type_isnt(schema, type, schema, type, desc) should have the proper diagnostics +ok 649 - domain_type_isnt(schema, domain, type, desc) should pass +ok 650 - domain_type_isnt(schema, domain, type, desc) should have the proper description +ok 651 - domain_type_isnt(schema, domain, type, desc) should have the proper diagnostics +ok 652 - domain_type_isnt(schema, domain, type) should pass +ok 653 - domain_type_isnt(schema, domain, type) should have the proper description +ok 654 - domain_type_isnt(schema, domain, type) should have the proper diagnostics +ok 655 - domain_type_isnt(schema, domain, type, desc) fail should fail +ok 656 - domain_type_isnt(schema, domain, type, desc) fail should have the proper description +ok 657 - domain_type_isnt(schema, domain, type, desc) fail should have the proper diagnostics +ok 658 - domain_type_isnt(schema, nondomain, type, desc) should fail +ok 659 - domain_type_isnt(schema, nondomain, type, desc) should have the proper description +ok 660 - domain_type_isnt(schema, nondomain, type, desc) should have the proper diagnostics +ok 661 - domain_type_isnt(schema, type, type, desc) should fail +ok 662 - domain_type_isnt(schema, type, type, desc) should have the proper description +ok 663 - domain_type_isnt(schema, type, type, desc) should have the proper diagnostics +ok 664 - domain_type_isnt(domain, type, desc) should pass +ok 665 - domain_type_isnt(domain, type, desc) should have the proper description +ok 666 - domain_type_isnt(domain, type, desc) should have the proper diagnostics +ok 667 - domain_type_isnt(domain, type) should pass +ok 668 - domain_type_isnt(domain, type) should have the proper description +ok 669 - domain_type_isnt(domain, type) should have the proper diagnostics +ok 670 - domain_type_isnt(domain, type, desc) fail should fail +ok 671 - domain_type_isnt(domain, type, desc) fail should have the proper description +ok 672 - domain_type_isnt(domain, type, desc) fail should have the proper diagnostics +ok 673 - domain_type_isnt(nondomain, type, desc) should fail +ok 674 - domain_type_isnt(nondomain, type, desc) should have the proper description +ok 675 - domain_type_isnt(nondomain, type, desc) should have the proper diagnostics +ok 676 - domain_type_isnt(type, type, desc) should fail +ok 677 - domain_type_isnt(type, type, desc) should have the proper description +ok 678 - domain_type_isnt(type, type, desc) should have the proper diagnostics diff --git a/expected/index.out b/expected/index.out index d3101ec43181..6a0086df0662 100644 --- a/expected/index.out +++ b/expected/index.out @@ -1,5 +1,5 @@ \unset ECHO -1..228 +1..225 ok 1 - has_index() single column should pass ok 2 - has_index() single column should have the proper description ok 3 - has_index() single column should have the proper diagnostics @@ -204,27 +204,24 @@ ok 201 - is_clustered() index only should have the proper diagnostics ok 202 - index_is_type() should pass ok 203 - index_is_type() should have the proper description ok 204 - index_is_type() should have the proper diagnostics -ok 205 - index_is_type() ci should pass -ok 206 - index_is_type() ci should have the proper description -ok 207 - index_is_type() ci should have the proper diagnostics -ok 208 - index_is_type() no desc should pass -ok 209 - index_is_type() no desc should have the proper description -ok 210 - index_is_type() no desc should have the proper diagnostics -ok 211 - index_is_type() fail should fail -ok 212 - index_is_type() fail should have the proper description -ok 213 - index_is_type() fail should have the proper diagnostics -ok 214 - index_is_type() no schema should pass -ok 215 - index_is_type() no schema should have the proper description -ok 216 - index_is_type() no schema should have the proper diagnostics -ok 217 - index_is_type() no schema fail should fail -ok 218 - index_is_type() no schema fail should have the proper description -ok 219 - index_is_type() no schema fail should have the proper diagnostics -ok 220 - index_is_type() no table should pass -ok 221 - index_is_type() no table should have the proper description -ok 222 - index_is_type() no table should have the proper diagnostics -ok 223 - index_is_type() no table fail should fail -ok 224 - index_is_type() no table fail should have the proper description -ok 225 - index_is_type() no table fail should have the proper diagnostics -ok 226 - index_is_type() hash should pass -ok 227 - index_is_type() hash should have the proper description -ok 228 - index_is_type() hash should have the proper diagnostics +ok 205 - index_is_type() no desc should pass +ok 206 - index_is_type() no desc should have the proper description +ok 207 - index_is_type() no desc should have the proper diagnostics +ok 208 - index_is_type() fail should fail +ok 209 - index_is_type() fail should have the proper description +ok 210 - index_is_type() fail should have the proper diagnostics +ok 211 - index_is_type() no schema should pass +ok 212 - index_is_type() no schema should have the proper description +ok 213 - index_is_type() no schema should have the proper diagnostics +ok 214 - index_is_type() no schema fail should fail +ok 215 - index_is_type() no schema fail should have the proper description +ok 216 - index_is_type() no schema fail should have the proper diagnostics +ok 217 - index_is_type() no table should pass +ok 218 - index_is_type() no table should have the proper description +ok 219 - index_is_type() no table should have the proper diagnostics +ok 220 - index_is_type() no table fail should fail +ok 221 - index_is_type() no table fail should have the proper description +ok 222 - index_is_type() no table fail should have the proper diagnostics +ok 223 - index_is_type() hash should pass +ok 224 - index_is_type() hash should have the proper description +ok 225 - index_is_type() hash should have the proper diagnostics diff --git a/expected/util.out b/expected/util.out index e539dc6852ed..47af45b547fe 100644 --- a/expected/util.out +++ b/expected/util.out @@ -1,5 +1,5 @@ \unset ECHO -1..29 +1..30 ok 1 - pg_type(int) should work ok 2 - pg_type(numeric) should work ok 3 - pg_type(text) should work @@ -24,8 +24,9 @@ ok 21 - display_type(timestamptz) ok 22 - display_type(foo, int4) ok 23 - display_type(HEY, numeric) ok 24 - display_type(t z, int4) -ok 25 - display_type(__foo.goofy) -ok 26 - display_type(__foo."this.that") -ok 27 - display_type(__foo."this"".that") -ok 28 - display_type(__foo."hey"".yoman", 13) -ok 29 - display_type("try.this""", 42) +ok 25 - display type_type(text) +ok 26 - display_type(__foo.goofy) +ok 27 - display_type(__foo."this.that") +ok 28 - display_type(__foo."this"".that") +ok 29 - display_type(__foo."hey"".yoman", 13) +ok 30 - display_type("try.this""", 42) From 96c34dfefc5173c732b63abb34fa5f611b12d71b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Aug 2010 21:02:07 -0700 Subject: [PATCH 0551/1195] Update uninstall file. Also, fix typo in `pgtap.sql.in`. --- pgtap.sql.in | 2 +- uninstall_pgtap.sql.in | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index bf9caae79847..cac55a82f9ec 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -3416,7 +3416,7 @@ 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 ) +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; diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index fea4ab36e9cf..ba01b4b68fca 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -7,6 +7,7 @@ DROP FUNCTION operators_are ( TEXT[] ); DROP FUNCTION operators_are( TEXT[], TEXT ); DROP FUNCTION operators_are ( NAME, TEXT[] ); DROP FUNCTION operators_are( NAME, TEXT[], TEXT ); +DROP FUNCTION display_oper ( NAME, OID ); DROP FUNCTION casts_are ( TEXT[] ); DROP FUNCTION casts_are ( TEXT[], TEXT ); DROP FUNCTION _areni ( text, text[], text[], TEXT ); @@ -341,6 +342,7 @@ DROP FUNCTION has_cast ( NAME, NAME, NAME, NAME, TEXT ); DROP FUNCTION _cast_exists ( NAME, NAME ); DROP FUNCTION _cast_exists ( NAME, NAME, NAME ); DROP FUNCTION _cast_exists ( NAME, NAME, NAME, NAME ); +DROP FUNCTION _cmp_types(oid, name); DROP FUNCTION is_member_of( NAME, NAME ); DROP FUNCTION is_member_of( NAME, NAME[] ); DROP FUNCTION is_member_of( NAME, NAME, TEXT ); @@ -355,11 +357,12 @@ DROP FUNCTION isnt_superuser( NAME ); DROP FUNCTION isnt_superuser( NAME, TEXT ); DROP FUNCTION is_superuser( NAME ); DROP FUNCTION is_superuser( NAME, TEXT ); +DROP FUNCTION _is_super( NAME ); DROP FUNCTION hasnt_user( NAME ); DROP FUNCTION hasnt_user( NAME, TEXT ); DROP FUNCTION has_user( NAME ); DROP FUNCTION has_user( NAME, TEXT ); -DROP FUNCTION _is_super( NAME ); +DROP FUNCTION _has_user( NAME ); DROP FUNCTION hasnt_role( NAME ); DROP FUNCTION hasnt_role( NAME, TEXT ); DROP FUNCTION has_role( NAME ); @@ -460,14 +463,6 @@ DROP FUNCTION can ( NAME[], TEXT ); DROP FUNCTION can ( NAME, NAME[] ); DROP FUNCTION can ( NAME, NAME[], TEXT ); DROP FUNCTION _pg_sv_type_array( OID[] ); -DROP FUNCTION can_ok( NAME ); -DROP FUNCTION can_ok( NAME, TEXT ); -DROP FUNCTION can_ok( NAME, NAME[] ); -DROP FUNCTION can_ok ( NAME, NAME[], TEXT ); -DROP FUNCTION can_ok( NAME, NAME ); -DROP FUNCTION can_ok ( NAME, NAME, TEXT ); -DROP FUNCTION can_ok( NAME, NAME, NAME[] ); -DROP FUNCTION can_ok ( NAME, NAME, NAME[], TEXT ); DROP FUNCTION hasnt_function( NAME ); DROP FUNCTION hasnt_function( NAME, TEXT ); DROP FUNCTION hasnt_function( NAME, NAME[] ); @@ -585,6 +580,7 @@ DROP FUNCTION col_type_is ( NAME, NAME, NAME, TEXT ); DROP FUNCTION col_type_is ( NAME, NAME, NAME, TEXT, TEXT ); DROP FUNCTION col_type_is ( NAME, NAME, NAME, NAME, TEXT ); DROP FUNCTION col_type_is ( NAME, NAME, NAME, NAME, TEXT, TEXT ); +DROP FUNCTION _quote_ident_like(TEXT, TEXT); DROP FUNCTION _get_col_ns_type ( NAME, NAME, NAME ); DROP FUNCTION _get_col_type ( NAME, NAME ); DROP FUNCTION _get_col_type ( NAME, NAME, NAME ); From 644563f6927d689ad5a671e1e6927236233e290b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 3 Aug 2010 11:33:42 -0700 Subject: [PATCH 0552/1195] Add some `diag()` variants. --- Changes | 2 ++ README.pgtap | 19 ++++++++---- expected/moretap.out | 70 +++++++++++++++++++++++------------------- pgtap.sql.in | 15 +++++++++ sql/moretap.sql | 28 +++++++++++++++-- uninstall_pgtap.sql.in | 3 ++ 6 files changed, 96 insertions(+), 41 deletions(-) diff --git a/Changes b/Changes index 4d34e3e6b94d..b5aafc66776f 100644 --- a/Changes +++ b/Changes @@ -25,6 +25,8 @@ Revision history for pgTAP * Documented that double-quotes must be used for types created with double-quoted names in the strings passed to `casts_are()`. This should be the only part of the API that does require this sort of quoting. +* Added `diag(anyelement)` and `diag(VARIADIC anyarray)` (for 8.4 and higher) + to make it easier to emit diagnostics for whatever data you have at hand. 0.24 2010-05-24T23:33:22 ------------------------- diff --git a/README.pgtap b/README.pgtap index 813b2b75a19d..4e44bd4ebf80 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3636,15 +3636,18 @@ we have ways for you to write your own diagnostic messages which are safer than just `\echo` or `SELECT foo`. ### `diag( text )` ### +### `diag( anyelement )` ### +### `diag( variadic anyarray )` ### +### `diag( variadic text[] )` ### -Returns a diagnostic message which is guaranteed not to interfere with -test output. Handy for this sort of thing: +Returns a diagnostic message which is guaranteed not to interfere with test +output. Handy for this sort of thing: -- Output a diagnostic message if the collation is not en_US.UTF-8. SELECT diag( - E'These tests expect LC_COLLATE to be en_US.UTF-8,\n' - || 'but yours is set to ' || setting || E'.\n' - || 'As a result, some tests may fail. YMMV.' + E'These tests expect LC_COLLATE to be en_US.UTF-8,\n', + 'but yours is set to ', setting, E'.\n', + 'As a result, some tests may fail. YMMV.' ) FROM pg_settings WHERE name = 'lc_collate' @@ -3656,6 +3659,11 @@ Which would produce: # but yours is set to en_US.ISO8859-1. # As a result, some tests may fail. YMMV. +You can pass data of any type to `diag()` and it will all be converted to text +for the diagnostics. On PostgreSQL 8.4 and higher, you can pass any number of +arguments (as long as they are all the same data type) and they will be +concatenated together. + Conditional Tests ----------------- @@ -4302,7 +4310,6 @@ No changes. Everything should just work. To Do ----- -* Add `diag(anyelement)`. * Add `schema, table, colname` variations of the table-checking functions (`table_has_column()`, `col_type_is()`, etc.). That is, allow the description to be optional if the schema is included in the function call. diff --git a/expected/moretap.out b/expected/moretap.out index 3f32ead49203..3f07bf7beeb9 100644 --- a/expected/moretap.out +++ b/expected/moretap.out @@ -1,5 +1,5 @@ \unset ECHO -1..40 +1..46 ok 1 - My pass() passed, w00t! ok 2 - Testing fail() ok 3 - We should get the proper output from fail() @@ -9,34 +9,40 @@ ok 6 - We should now have no failures ok 7 - diag() should work properly ok 8 - multiline diag() should work properly ok 9 - multiline diag() should work properly with existing comments -ok 10 - no_plan() should have stored a plan of 0 -ok 11 - Set the plan to 4000 -ok 12 - The output of finish() should reflect a high test plan -ok 13 - Set the plan to 4 -ok 14 - The output of finish() should reflect a low test plan -ok 15 - Reset the plan -ok 16 - plan() should have stored the test count -ok 17 - ok(true) should pass -ok 18 - ok(true) should have the proper description -ok 19 - ok(true) should have the proper diagnostics -ok 20 - ok(true, '') should pass -ok 21 - ok(true, '') should have the proper description -ok 22 - ok(true, '') should have the proper diagnostics -ok 23 - ok(true, 'foo') should pass -ok 24 - ok(true, 'foo') should have the proper description -ok 25 - ok(true, 'foo') should have the proper diagnostics -ok 26 - ok(false) should fail -ok 27 - ok(false) should have the proper description -ok 28 - ok(false) should have the proper diagnostics -ok 29 - ok(false, '') should fail -ok 30 - ok(false, '') should have the proper description -ok 31 - ok(false, '') should have the proper diagnostics -ok 32 - ok(false, 'foo') should fail -ok 33 - ok(false, 'foo') should have the proper description -ok 34 - ok(false, 'foo') should have the proper diagnostics -ok 35 - ok(NULL, 'null') should fail -ok 36 - ok(NULL, 'null') should have the proper description -ok 37 - ok(NULL, 'null') should have the proper diagnostics -ok 38 - multiline desc should pass -ok 39 - multiline desc should have the proper description -ok 40 - multiline desc should have the proper diagnostics +ok 10 - diag(int) +ok 11 - diag(numeric) +ok 12 - diag(timestamptz) +ok 13 - variadic text +ok 14 - variadic int +ok 15 - variadic unknown +ok 16 - no_plan() should have stored a plan of 0 +ok 17 - Set the plan to 4000 +ok 18 - The output of finish() should reflect a high test plan +ok 19 - Set the plan to 4 +ok 20 - The output of finish() should reflect a low test plan +ok 21 - Reset the plan +ok 22 - plan() should have stored the test count +ok 23 - ok(true) should pass +ok 24 - ok(true) should have the proper description +ok 25 - ok(true) should have the proper diagnostics +ok 26 - ok(true, '') should pass +ok 27 - ok(true, '') should have the proper description +ok 28 - ok(true, '') should have the proper diagnostics +ok 29 - ok(true, 'foo') should pass +ok 30 - ok(true, 'foo') should have the proper description +ok 31 - ok(true, 'foo') should have the proper diagnostics +ok 32 - ok(false) should fail +ok 33 - ok(false) should have the proper description +ok 34 - ok(false) should have the proper diagnostics +ok 35 - ok(false, '') should fail +ok 36 - ok(false, '') should have the proper description +ok 37 - ok(false, '') should have the proper diagnostics +ok 38 - ok(false, 'foo') should fail +ok 39 - ok(false, 'foo') should have the proper description +ok 40 - ok(false, 'foo') should have the proper diagnostics +ok 41 - ok(NULL, 'null') should fail +ok 42 - ok(NULL, 'null') should have the proper description +ok 43 - ok(NULL, 'null') should have the proper diagnostics +ok 44 - multiline desc should pass +ok 45 - multiline desc should have the proper description +ok 46 - multiline desc should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index cac55a82f9ec..b52cfc8a2165 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -268,6 +268,21 @@ RETURNS TEXT AS $$ ); $$ 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 diff --git a/sql/moretap.sql b/sql/moretap.sql index caae6e74e0f3..4023258d221d 100644 --- a/sql/moretap.sql +++ b/sql/moretap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -\set numb_tests 40 +\set numb_tests 46 SELECT plan(:numb_tests); -- Replace the internal record of the plan for a few tests. @@ -42,6 +42,28 @@ SELECT is( diag( 'foo # bar'), '# foo # # bar', 'multiline diag() should work properly with existing comments' ); +-- Try anyelement form. +SELECT is(diag(6), '# 6', 'diag(int)'); +SELECT is(diag(11.2), '# 11.2', 'diag(numeric)'); +SELECT is(diag(NOW()), '# ' || NOW(), 'diag(timestamptz)'); + +-- Try variadic anyarray +CREATE FUNCTION test_variadic() RETURNS SETOF TEXT AS $$ +BEGIN + IF pg_version_num() >= 80400 THEN + RETURN NEXT is(diag('foo'::text, 'bar', 'baz'), '# foobarbaz', 'variadic text'); + RETURN NEXT is(diag(1::int, 3, 4), '# 134', 'variadic int'); + RETURN NEXT is(diag('foo', 'bar', 'baz'), '# foobarbaz', 'variadic unknown'); + ELSE + RETURN NEXT pass('variadic text'); + RETURN NEXT pass('variadic int'); + RETURN NEXT pass('variadic unknown'); + END IF; +END; +$$ LANGUAGE plpgsql; + +SELECT * FROM test_variadic(); + /****************************************************************************/ -- Check no_plan. DELETE FROM __tcache__ WHERE label = 'plan'; @@ -55,7 +77,7 @@ DELETE FROM __tcache__ WHERE label = 'plan'; SELECT is( plan(4000), '1..4000', 'Set the plan to 4000' ); SELECT is( (SELECT * FROM finish() LIMIT 1), - '# Looks like you planned 4000 tests but ran 11', + '# Looks like you planned 4000 tests but ran 17', 'The output of finish() should reflect a high test plan' ); @@ -64,7 +86,7 @@ DELETE FROM __tcache__ WHERE label = 'plan'; SELECT is( plan(4), '1..4', 'Set the plan to 4' ); SELECT is( (SELECT * FROM finish() LIMIT 1), - '# Looks like you planned 4 tests but ran 13', + '# Looks like you planned 4 tests but ran 19', 'The output of finish() should reflect a low test plan' ); diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index ba01b4b68fca..b389d33e50f6 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -677,6 +677,9 @@ DROP FUNCTION is (anyelement, anyelement); DROP FUNCTION is (anyelement, anyelement, text); DROP FUNCTION ok ( boolean ); DROP FUNCTION ok ( boolean, text ); +DROP FUNCTION diag( VARIADIC text[] ); +DROP FUNCTION diag( VARIADIC anyarray ); +DROP FUNCTION diag ( msg anyelement ); DROP FUNCTION diag ( msg text ); DROP FUNCTION finish (); DROP FUNCTION _finish ( INTEGER, INTEGER, INTEGER); From cc9a0472d25d5aa65d88991db678ce100548ac75 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 3 Aug 2010 12:53:26 -0700 Subject: [PATCH 0553/1195] That to-do was done a while ago as `columns_are()`. --- README.pgtap | 1 - 1 file changed, 1 deletion(-) diff --git a/README.pgtap b/README.pgtap index 4e44bd4ebf80..9e5ce1c92c52 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4339,7 +4339,6 @@ To Do + `isnt_definer()` + `isnt_strict()` + `isnt_aggregate()` -* Add `has_columns(:schema, :table, :columns[])` * Modify function testing assertions so that functions can be specified with full signatures, so that a polymorphic functions can be independently tested for language, volatility, etc. From 6004a3923cdecf6042928f440dbcbe51e9119578 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 4 Aug 2010 10:20:32 -0700 Subject: [PATCH 0554/1195] Drop sequences in cleanup. Closes #4. --- Changes | 4 ++++ pgtap.sql.in | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index b5aafc66776f..ee6507b918c2 100644 --- a/Changes +++ b/Changes @@ -27,6 +27,10 @@ Revision history for pgTAP the only part of the API that does require this sort of quoting. * Added `diag(anyelement)` and `diag(VARIADIC anyarray)` (for 8.4 and higher) to make it easier to emit diagnostics for whatever data you have at hand. +* The `_cleanup()` function now properly drops the temporary sequences as well + as the temporary tables. These are usually automatically dropped when the + tests finish and the client disconnects, but it's best to try to keep things + tidy ourselves, too. Spotted by Peter Eisentraut. 0.24 2010-05-24T23:33:22 ------------------------- diff --git a/pgtap.sql.in b/pgtap.sql.in index b52cfc8a2165..75f26d664016 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -5694,10 +5694,11 @@ EXCEPTION END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION _cleanup() -RETURNS boolean AS $$ +CREATE OR REPLACE FUNCTION _cleanup boolean AS $$ DROP TABLE __tresults__; + DROP SEQUENCE __tresults___numb_seq; DROP TABLE __tcache__; + DROP SEQUENCE __tcache___id_seq; SELECT TRUE; $$ LANGUAGE sql; From 1440b397e3e3cfdfe1062629b07b25453a2c00a6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 5 Aug 2010 13:30:49 -0700 Subject: [PATCH 0555/1195] Note to add `isnt_empty()`. --- README.pgtap | 1 + 1 file changed, 1 insertion(+) diff --git a/README.pgtap b/README.pgtap index 9e5ce1c92c52..fdb17f9fdcb6 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4310,6 +4310,7 @@ No changes. Everything should just work. To Do ----- +* Add `isnt_empty()` to complement `is_empty()`. * Add `schema, table, colname` variations of the table-checking functions (`table_has_column()`, `col_type_is()`, etc.). That is, allow the description to be optional if the schema is included in the function call. From ef50dece540114dd92adb2812c6f37f5bef92dda Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 17 Aug 2010 17:27:42 -0700 Subject: [PATCH 0556/1195] Fix bogus signature. --- pgtap.sql.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 75f26d664016..a6214ed3d2b5 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -5694,7 +5694,7 @@ EXCEPTION END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION _cleanup boolean AS $$ +CREATE OR REPLACE FUNCTION _cleanup() RETURNS boolean AS $$ DROP TABLE __tresults__; DROP SEQUENCE __tresults___numb_seq; DROP TABLE __tcache__; From 3644c0693bacd72062ddc63c35a65e4221b9d568 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 28 Aug 2010 15:44:54 -0700 Subject: [PATCH 0557/1195] Another feature to add. --- README.pgtap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.pgtap b/README.pgtap index fdb17f9fdcb6..5f6aa70445d0 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4311,6 +4311,8 @@ No changes. Everything should just work. To Do ----- * Add `isnt_empty()` to complement `is_empty()`. +* Add variants of `set_eq()`, `bag_eq()`, and `results_eq()` that take an + array of records as the second argument. * Add `schema, table, colname` variations of the table-checking functions (`table_has_column()`, `col_type_is()`, etc.). That is, allow the description to be optional if the schema is included in the function call. From 55ef08b2a2261a598e86b2b26d36e88640513f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Villemain?= Date: Wed, 20 Oct 2010 21:19:15 +0200 Subject: [PATCH 0558/1195] Fix _hasc : is_visible was set in the wrong function src --- pgtap.sql.in | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index a6214ed3d2b5..b23cf9a698c0 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1479,12 +1479,10 @@ CREATE OR REPLACE FUNCTION _hasc ( NAME, NAME, CHAR ) RETURNS BOOLEAN AS $$ SELECT EXISTS( SELECT true - FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, - pg_catalog.pg_constraint x - WHERE n.oid = c.relnamespace - AND c.oid = x.conrelid - AND pg_table_is_visible(c.oid) - AND c.relhaspkey = 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 c.relhaspkey = true AND n.nspname = $1 AND c.relname = $2 AND x.contype = $3 @@ -1499,6 +1497,7 @@ RETURNS BOOLEAN AS $$ FROM pg_catalog.pg_class c JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid WHERE c.relhaspkey = true + AND pg_table_is_visible(c.oid) AND c.relname = $1 AND x.contype = $2 ); From 9f87ef2e2c424889b1371f913b74f85a349e2ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Villemain?= Date: Thu, 21 Oct 2010 00:43:41 +0200 Subject: [PATCH 0559/1195] Fixed a bug in _hasc The bug occurs when checking PK of a table in a schema not defined in the search_path --- Changes | 2 + expected/pktap.out | 158 +++++++++++++++++++++++---------------------- sql/pktap.sql | 26 +++++++- 3 files changed, 109 insertions(+), 77 deletions(-) diff --git a/Changes b/Changes index ee6507b918c2..ffb4b2031af7 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,8 @@ Revision history for pgTAP ========================== +* Fixed a bug in _hasc occurying when checking PK of a table in a schema not defined in the search_path + 0.25 * Minor documentation tweaks. * The sequence created to keep track of test numbers is now a temporary diff --git a/expected/pktap.out b/expected/pktap.out index e6fcba6cd027..2441c71f6f53 100644 --- a/expected/pktap.out +++ b/expected/pktap.out @@ -1,80 +1,86 @@ \unset ECHO -1..78 +1..84 ok 1 - has_pk( schema, table, description ) should pass ok 2 - has_pk( schema, table, description ) should have the proper description ok 3 - has_pk( schema, table, description ) should have the proper diagnostics -ok 4 - has_pk( table, description ) should pass -ok 5 - has_pk( table, description ) should have the proper description -ok 6 - has_pk( table, description ) should have the proper diagnostics -ok 7 - has_pk( table ) should pass -ok 8 - has_pk( table ) should have the proper description -ok 9 - has_pk( table ) should have the proper diagnostics -ok 10 - has_pk( schema, table, description ) fail should fail -ok 11 - has_pk( schema, table, description ) fail should have the proper description -ok 12 - has_pk( schema, table, description ) fail should have the proper diagnostics -ok 13 - has_pk( table, description ) fail should fail -ok 14 - has_pk( table, description ) fail should have the proper description -ok 15 - has_pk( table, description ) fail should have the proper diagnostics -ok 16 - hasnt_pk( schema, table, description ) should fail -ok 17 - hasnt_pk( schema, table, description ) should have the proper description -ok 18 - hasnt_pk( schema, table, description ) should have the proper diagnostics -ok 19 - hasnt_pk( table, description ) should fail -ok 20 - hasnt_pk( table, description ) should have the proper description -ok 21 - hasnt_pk( table, description ) should have the proper diagnostics -ok 22 - hasnt_pk( table ) should fail -ok 23 - hasnt_pk( table ) should have the proper description -ok 24 - hasnt_pk( table ) should have the proper diagnostics -ok 25 - hasnt_pk( schema, table, description ) pass should pass -ok 26 - hasnt_pk( schema, table, description ) pass should have the proper description -ok 27 - hasnt_pk( schema, table, description ) pass should have the proper diagnostics -ok 28 - hasnt_pk( table, description ) pass should pass -ok 29 - hasnt_pk( table, description ) pass should have the proper description -ok 30 - hasnt_pk( table, description ) pass should have the proper diagnostics -ok 31 - col_is_pk( schema, table, column, description ) should pass -ok 32 - col_is_pk( schema, table, column, description ) should have the proper description -ok 33 - col_is_pk( schema, table, column, description ) should have the proper diagnostics -ok 34 - col_is_pk( table, column, description ) should pass -ok 35 - col_is_pk( table, column, description ) should have the proper description -ok 36 - col_is_pk( table, column, description ) should have the proper diagnostics -ok 37 - col_is_pk( table, column ) should pass -ok 38 - col_is_pk( table, column ) should have the proper description -ok 39 - col_is_pk( table, column ) should have the proper diagnostics -ok 40 - col_is_pk( schema, table, column, description ) fail should fail -ok 41 - col_is_pk( schema, table, column, description ) fail should have the proper description -ok 42 - col_is_pk( schema, table, column, description ) fail should have the proper diagnostics -ok 43 - col_is_pk( table, column, description ) fail should fail -ok 44 - col_is_pk( table, column, description ) fail should have the proper description -ok 45 - col_is_pk( table, column, description ) fail should have the proper diagnostics -ok 46 - col_is_pk( schema, table, column[], description ) should pass -ok 47 - col_is_pk( schema, table, column[], description ) should have the proper description -ok 48 - col_is_pk( schema, table, column[], description ) should have the proper diagnostics -ok 49 - col_is_pk( table, column[], description ) should pass -ok 50 - col_is_pk( table, column[], description ) should have the proper description -ok 51 - col_is_pk( table, column[], description ) should have the proper diagnostics -ok 52 - col_is_pk( table, column[] ) should pass -ok 53 - col_is_pk( table, column[] ) should have the proper description -ok 54 - col_is_pk( table, column[] ) should have the proper diagnostics -ok 55 - col_isnt_pk( schema, table, column, description ) should fail -ok 56 - col_isnt_pk( schema, table, column, description ) should have the proper description -ok 57 - col_isnt_pk( schema, table, column, description ) should have the proper diagnostics -ok 58 - col_isnt_pk( table, column, description ) should fail -ok 59 - col_isnt_pk( table, column, description ) should have the proper description -ok 60 - col_isnt_pk( table, column, description ) should have the proper diagnostics -ok 61 - col_isnt_pk( table, column ) should fail -ok 62 - col_isnt_pk( table, column ) should have the proper description -ok 63 - col_isnt_pk( table, column ) should have the proper diagnostics -ok 64 - col_isnt_pk( schema, table, column, description ) pass should pass -ok 65 - col_isnt_pk( schema, table, column, description ) pass should have the proper description -ok 66 - col_isnt_pk( schema, table, column, description ) pass should have the proper diagnostics -ok 67 - col_isnt_pk( table, column, description ) pass should pass -ok 68 - col_isnt_pk( table, column, description ) pass should have the proper description -ok 69 - col_isnt_pk( table, column, description ) pass should have the proper diagnostics -ok 70 - col_isnt_pk( schema, table, column[], description ) should pass -ok 71 - col_isnt_pk( schema, table, column[], description ) should have the proper description -ok 72 - col_isnt_pk( schema, table, column[], description ) should have the proper diagnostics -ok 73 - col_isnt_pk( table, column[], description ) should pass -ok 74 - col_isnt_pk( table, column[], description ) should have the proper description -ok 75 - col_isnt_pk( table, column[], description ) should have the proper diagnostics -ok 76 - col_isnt_pk( table, column[] ) should pass -ok 77 - col_isnt_pk( table, column[] ) should have the proper description -ok 78 - col_isnt_pk( table, column[] ) should have the proper diagnostics +ok 4 - has_pk( hideschema, hidetable, description ) should pass +ok 5 - has_pk( hideschema, hidetable, description ) should have the proper description +ok 6 - has_pk( hideschema, hidetable, description ) should have the proper diagnostics +ok 7 - has_pk( table, description ) should pass +ok 8 - has_pk( table, description ) should have the proper description +ok 9 - has_pk( table, description ) should have the proper diagnostics +ok 10 - has_pk( hidetable, description ) fail should fail +ok 11 - has_pk( hidetable, description ) fail should have the proper description +ok 12 - has_pk( hidetable, description ) fail should have the proper diagnostics +ok 13 - has_pk( table ) should pass +ok 14 - has_pk( table ) should have the proper description +ok 15 - has_pk( table ) should have the proper diagnostics +ok 16 - has_pk( schema, table, description ) fail should fail +ok 17 - has_pk( schema, table, description ) fail should have the proper description +ok 18 - has_pk( schema, table, description ) fail should have the proper diagnostics +ok 19 - has_pk( table, description ) fail should fail +ok 20 - has_pk( table, description ) fail should have the proper description +ok 21 - has_pk( table, description ) fail should have the proper diagnostics +ok 22 - hasnt_pk( schema, table, description ) should fail +ok 23 - hasnt_pk( schema, table, description ) should have the proper description +ok 24 - hasnt_pk( schema, table, description ) should have the proper diagnostics +ok 25 - hasnt_pk( table, description ) should fail +ok 26 - hasnt_pk( table, description ) should have the proper description +ok 27 - hasnt_pk( table, description ) should have the proper diagnostics +ok 28 - hasnt_pk( table ) should fail +ok 29 - hasnt_pk( table ) should have the proper description +ok 30 - hasnt_pk( table ) should have the proper diagnostics +ok 31 - hasnt_pk( schema, table, description ) pass should pass +ok 32 - hasnt_pk( schema, table, description ) pass should have the proper description +ok 33 - hasnt_pk( schema, table, description ) pass should have the proper diagnostics +ok 34 - hasnt_pk( table, description ) pass should pass +ok 35 - hasnt_pk( table, description ) pass should have the proper description +ok 36 - hasnt_pk( table, description ) pass should have the proper diagnostics +ok 37 - col_is_pk( schema, table, column, description ) should pass +ok 38 - col_is_pk( schema, table, column, description ) should have the proper description +ok 39 - col_is_pk( schema, table, column, description ) should have the proper diagnostics +ok 40 - col_is_pk( table, column, description ) should pass +ok 41 - col_is_pk( table, column, description ) should have the proper description +ok 42 - col_is_pk( table, column, description ) should have the proper diagnostics +ok 43 - col_is_pk( table, column ) should pass +ok 44 - col_is_pk( table, column ) should have the proper description +ok 45 - col_is_pk( table, column ) should have the proper diagnostics +ok 46 - col_is_pk( schema, table, column, description ) fail should fail +ok 47 - col_is_pk( schema, table, column, description ) fail should have the proper description +ok 48 - col_is_pk( schema, table, column, description ) fail should have the proper diagnostics +ok 49 - col_is_pk( table, column, description ) fail should fail +ok 50 - col_is_pk( table, column, description ) fail should have the proper description +ok 51 - col_is_pk( table, column, description ) fail should have the proper diagnostics +ok 52 - col_is_pk( schema, table, column[], description ) should pass +ok 53 - col_is_pk( schema, table, column[], description ) should have the proper description +ok 54 - col_is_pk( schema, table, column[], description ) should have the proper diagnostics +ok 55 - col_is_pk( table, column[], description ) should pass +ok 56 - col_is_pk( table, column[], description ) should have the proper description +ok 57 - col_is_pk( table, column[], description ) should have the proper diagnostics +ok 58 - col_is_pk( table, column[] ) should pass +ok 59 - col_is_pk( table, column[] ) should have the proper description +ok 60 - col_is_pk( table, column[] ) should have the proper diagnostics +ok 61 - col_isnt_pk( schema, table, column, description ) should fail +ok 62 - col_isnt_pk( schema, table, column, description ) should have the proper description +ok 63 - col_isnt_pk( schema, table, column, description ) should have the proper diagnostics +ok 64 - col_isnt_pk( table, column, description ) should fail +ok 65 - col_isnt_pk( table, column, description ) should have the proper description +ok 66 - col_isnt_pk( table, column, description ) should have the proper diagnostics +ok 67 - col_isnt_pk( table, column ) should fail +ok 68 - col_isnt_pk( table, column ) should have the proper description +ok 69 - col_isnt_pk( table, column ) should have the proper diagnostics +ok 70 - col_isnt_pk( schema, table, column, description ) pass should pass +ok 71 - col_isnt_pk( schema, table, column, description ) pass should have the proper description +ok 72 - col_isnt_pk( schema, table, column, description ) pass should have the proper diagnostics +ok 73 - col_isnt_pk( table, column, description ) pass should pass +ok 74 - col_isnt_pk( table, column, description ) pass should have the proper description +ok 75 - col_isnt_pk( table, column, description ) pass should have the proper diagnostics +ok 76 - col_isnt_pk( schema, table, column[], description ) should pass +ok 77 - col_isnt_pk( schema, table, column[], description ) should have the proper description +ok 78 - col_isnt_pk( schema, table, column[], description ) should have the proper diagnostics +ok 79 - col_isnt_pk( table, column[], description ) should pass +ok 80 - col_isnt_pk( table, column[], description ) should have the proper description +ok 81 - col_isnt_pk( table, column[], description ) should have the proper diagnostics +ok 82 - col_isnt_pk( table, column[] ) should pass +ok 83 - col_isnt_pk( table, column[] ) should have the proper description +ok 84 - col_isnt_pk( table, column[] ) should have the proper diagnostics diff --git a/sql/pktap.sql b/sql/pktap.sql index c441df3102ac..d5db622ee8d3 100644 --- a/sql/pktap.sql +++ b/sql/pktap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(78); +SELECT plan(84); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -12,6 +12,14 @@ CREATE TABLE public.sometab( numb NUMERIC(10, 2), myint NUMERIC(8) ); +CREATE SCHEMA hide; +CREATE TABLE hide.hidesometab( + id INT NOT NULL PRIMARY KEY, + name TEXT DEFAULT '', + numb NUMERIC(10, 2), + myint NUMERIC(8) +); + RESET client_min_messages; /****************************************************************************/ @@ -25,6 +33,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + has_pk( 'hide', 'hidesometab', 'hide.sometab should have a pk' ), + true, + 'has_pk( hideschema, hidetable, description )', + 'hide.sometab should have a pk', + '' +); + SELECT * FROM check_test( has_pk( 'sometab', 'sometab should have a pk' ), true, @@ -33,6 +49,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + has_pk( 'hidesometab', 'hidesometab should have a pk' ), + false, + 'has_pk( hidetable, description ) fail', + 'hidesometab should have a pk', + '' +); + SELECT * FROM check_test( has_pk( 'sometab' ), true, From 24981793a266f796b7a4dbce49f04c5a16d17271 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 20 Oct 2010 16:33:04 -0700 Subject: [PATCH 0560/1195] =?UTF-8?q?Update=20change=20log=20item=20from?= =?UTF-8?q?=20C=C3=A9dric=20Villemain.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Changes | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index ffb4b2031af7..b4a3d3fa8e18 100644 --- a/Changes +++ b/Changes @@ -1,8 +1,6 @@ Revision history for pgTAP ========================== -* Fixed a bug in _hasc occurying when checking PK of a table in a schema not defined in the search_path - 0.25 * Minor documentation tweaks. * The sequence created to keep track of test numbers is now a temporary @@ -33,6 +31,10 @@ Revision history for pgTAP as the temporary tables. These are usually automatically dropped when the tests finish and the client disconnects, but it's best to try to keep things tidy ourselves, too. Spotted by Peter Eisentraut. +* Fixed a bug in `has_pk()` so that it properly checks that a table is visible + in the search path when a schema is not specified, and doesn't check that + when the schema *is* specified. Patch from Cédric Villemain. + 0.24 2010-05-24T23:33:22 ------------------------- From 8375d84732d0390e9f679738e88dd95b7ff65e74 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 20 Oct 2010 16:51:01 -0700 Subject: [PATCH 0561/1195] Add failing tests for `col_is_unique()` and `col_has_check()`. --- Changes | 1 - sql/check.sql | 53 +++++++++++++++++++++++++++++++++++++------------- sql/unique.sql | 53 +++++++++++++++++++++++++++++++++++++------------- 3 files changed, 78 insertions(+), 29 deletions(-) diff --git a/Changes b/Changes index b4a3d3fa8e18..19803f3c6828 100644 --- a/Changes +++ b/Changes @@ -35,7 +35,6 @@ Revision history for pgTAP in the search path when a schema is not specified, and doesn't check that when the schema *is* specified. Patch from Cédric Villemain. - 0.24 2010-05-24T23:33:22 ------------------------- * Got `sql/artap.sql` tests passing again when building with `$TAPSCHEMA` set. diff --git a/sql/check.sql b/sql/check.sql index a83b4674dabc..fc360c2ee843 100644 --- a/sql/check.sql +++ b/sql/check.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(39); +SELECT plan(48); -- This will be rolled back. :-) SET client_min_messages = warning; @@ -9,7 +9,8 @@ CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '' CHECK ( name IN ('foo', 'bar', 'baz') ), numb NUMERIC(10, 2), - myint NUMERIC(8) + myint NUMERIC(8), + CHECK (numb > 1.0 AND myint < 10) ); RESET client_min_messages; @@ -60,18 +61,34 @@ SELECT * FROM check_test( -- Test col_has_check(). SELECT * FROM check_test( - col_has_check( 'public', 'sometab', 'name', 'public.sometab.name should be a pk' ), + col_has_check( 'public', 'sometab', 'name', 'public.sometab.name should have a check' ), true, 'col_has_check( sch, tab, col, desc )', - 'public.sometab.name should be a pk', + 'public.sometab.name should have a check', '' ); SELECT * FROM check_test( - col_has_check( 'sometab', 'name', 'sometab.name should be a pk' ), + col_has_check( 'public', 'sometab', ARRAY['numb', 'myint'], 'public.sometab.numb+myint should have a check' ), + true, + 'col_has_check( sch, tab, cols, desc )', + 'public.sometab.numb+myint should have a check', + '' +); + +SELECT * FROM check_test( + col_has_check( 'sometab', 'name', 'sometab.name should have a check' ), true, 'col_has_check( tab, col, desc )', - 'sometab.name should be a pk', + 'sometab.name should have a check', + '' +); + +SELECT * FROM check_test( + col_has_check( 'sometab', ARRAY['numb', 'myint'], 'sometab.numb+myint should have a check' ), + true, + 'col_has_check( tab, cols, desc )', + 'sometab.numb+myint should have a check', '' ); @@ -84,19 +101,27 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - col_has_check( 'public', 'sometab', 'id', 'public.sometab.id should be a pk' ), + col_has_check( 'sometab', ARRAY['numb', 'myint'] ), + true, + 'col_has_check( table, columns )', + 'Column sometab(numb, myint) should have a check constraint', + '' +); + +SELECT * FROM check_test( + col_has_check( 'public', 'sometab', 'id', 'public.sometab.id should have a check' ), false, 'col_has_check( sch, tab, col, desc ) fail', - 'public.sometab.id should be a pk', + 'public.sometab.id should have a check', ' have: {name} want: {id}' ); SELECT * FROM check_test( - col_has_check( 'sometab', 'id', 'sometab.id should be a pk' ), + col_has_check( 'sometab', 'id', 'sometab.id should have a check' ), false, 'col_has_check( tab, col, desc ) fail', - 'sometab.id should be a pk', + 'sometab.id should have a check', ' have: {name} want: {id}' ); @@ -113,18 +138,18 @@ CREATE TABLE public.argh ( RESET client_min_messages; SELECT * FROM check_test( - col_has_check( 'public', 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), + col_has_check( 'public', 'argh', ARRAY['id', 'name'], 'id + name should have a check' ), true, 'col_has_check( sch, tab, col[], desc )', - 'id + name should be a pk', + 'id + name should have a check', '' ); SELECT * FROM check_test( - col_has_check( 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), + col_has_check( 'argh', ARRAY['id', 'name'], 'id + name should have a check' ), true, 'col_has_check( tab, col[], desc )', - 'id + name should be a pk', + 'id + name should have a check', '' ); diff --git a/sql/unique.sql b/sql/unique.sql index 99867cc5bcc5..e6d8cad80080 100644 --- a/sql/unique.sql +++ b/sql/unique.sql @@ -1,7 +1,7 @@ \unset ECHO \i test_setup.sql -SELECT plan(39); +SELECT plan(48); -- This will be rolled back. :-) SET client_min_messages = warning; @@ -9,7 +9,8 @@ CREATE TABLE public.sometab( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '' UNIQUE, numb NUMERIC(10, 2), - myint NUMERIC(8) + myint NUMERIC(8), + UNIQUE (numb, myint) ); RESET client_min_messages; @@ -60,18 +61,34 @@ SELECT * FROM check_test( -- Test col_is_unique(). SELECT * FROM check_test( - col_is_unique( 'public', 'sometab', 'name', 'public.sometab.name should be a pk' ), + col_is_unique( 'public', 'sometab', 'name', 'public.sometab.name should be unique' ), true, 'col_is_unique( schema, table, column, description )', - 'public.sometab.name should be a pk', + 'public.sometab.name should be unique', '' ); SELECT * FROM check_test( - col_is_unique( 'sometab', 'name', 'sometab.name should be a pk' ), + col_is_unique( 'public', 'sometab', ARRAY['numb', 'myint'], 'public.sometab.numb+myint should be unique' ), + true, + 'col_is_unique( schema, table, columns, description )', + 'public.sometab.numb+myint should be unique', + '' +); + +SELECT * FROM check_test( + col_is_unique( 'sometab', 'name', 'sometab.name should be unique' ), true, 'col_is_unique( table, column, description )', - 'sometab.name should be a pk', + 'sometab.name should be unique', + '' +); + +SELECT * FROM check_test( + col_is_unique( 'sometab', ARRAY['numb', 'myint'], 'sometab.numb+myint should be unique' ), + true, + 'col_is_unique( table, columns, description )', + 'sometab.numb+myint should be unique', '' ); @@ -84,19 +101,27 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - col_is_unique( 'public', 'sometab', 'id', 'public.sometab.id should be a pk' ), + col_is_unique( 'sometab', ARRAY['numb', 'myint'] ), + true, + 'col_is_unique( table, columns )', + 'Column sometab(numb, myint) should have a unique constraint', + '' +); + +SELECT * FROM check_test( + col_is_unique( 'public', 'sometab', 'id', 'public.sometab.id should be unique' ), false, 'col_is_unique( schema, table, column, description ) fail', - 'public.sometab.id should be a pk', + 'public.sometab.id should be unique', ' have: {name} want: {id}' ); SELECT * FROM check_test( - col_is_unique( 'sometab', 'id', 'sometab.id should be a pk' ), + col_is_unique( 'sometab', 'id', 'sometab.id should be unique' ), false, 'col_is_unique( table, column, description ) fail', - 'sometab.id should be a pk', + 'sometab.id should be unique', ' have: {name} want: {id}' ); @@ -113,18 +138,18 @@ CREATE TABLE public.argh ( RESET client_min_messages; SELECT * FROM check_test( - col_is_unique( 'public', 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), + col_is_unique( 'public', 'argh', ARRAY['id', 'name'], 'id + name should be unique' ), true, 'col_is_unique( schema, table, column[], description )', - 'id + name should be a pk', + 'id + name should be unique', '' ); SELECT * FROM check_test( - col_is_unique( 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), + col_is_unique( 'argh', ARRAY['id', 'name'], 'id + name should be unique' ), true, 'col_is_unique( table, column[], description )', - 'id + name should be a pk', + 'id + name should be unique', '' ); From 9e2109811d4796fb19bb6d20936d091ede1bf35a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Oct 2010 09:28:32 -0700 Subject: [PATCH 0562/1195] Fix column inclusion bugs in constraint test functions. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turns out that `col_is_unique()` and `col_has_constraint()` were including *all* columns in a unique or check constraint in a table as if they were all in one constraint. Of course, different columns can be in different constraints, so this wasn't right. Thanks to Cédric Villemain for the spot! --- Changes | 5 ++ README.pgtap | 15 +++++- expected/check.out | 53 +++++++++++--------- expected/unique.out | 53 +++++++++++--------- pgtap.sql.in | 118 ++++++++++++++++++++++++++++++++------------ sql/check.sql | 4 +- sql/unique.sql | 4 +- 7 files changed, 173 insertions(+), 79 deletions(-) diff --git a/Changes b/Changes index 19803f3c6828..ad872b3fc4bc 100644 --- a/Changes +++ b/Changes @@ -34,6 +34,11 @@ Revision history for pgTAP * Fixed a bug in `has_pk()` so that it properly checks that a table is visible in the search path when a schema is not specified, and doesn't check that when the schema *is* specified. Patch from Cédric Villemain. +* Fixed a bug in `col_is_unique()` and `col_has_check()` where they considered + *all* columns in a table that were unique or involved in a check constraint + were assocated with only one constraint. Since one can have multiple unique + or check constraints on a table, each using different columns, this was + sub-optimal. Thanks to Cédric Villemain for the spot! 0.24 2010-05-24T23:33:22 ------------------------- diff --git a/README.pgtap b/README.pgtap index 5f6aa70445d0..f475fcc81467 100644 --- a/README.pgtap +++ b/README.pgtap @@ -2969,7 +2969,13 @@ in question does not exist. ); Just like `col_is_pk()`, except that it test that the column or array of -columns have a unique constraint on them. +columns have a unique constraint on them. In the event of failure, the +diagnostics will list the unique constraints that were actually found, if any: + + Failed test 40: "users.email should be unique" + have: {username} + {first_name,last_name} + want: {email} ### `has_check( schema, table, description )` ### ### `has_check( table, description )` ### @@ -2981,6 +2987,10 @@ columns have a unique constraint on them. 'Table myschema.sometable should have a check constraint' ); + Failed test 41: "users.email should have a check constraint" + have: {username} + want: {email} + Tests whether or not a table has a check constraint. The first argument is the schema name, the second the table name, the the third is the test description. If the schema is omitted, the table must be visible in the search path. If the @@ -2988,6 +2998,9 @@ test description is omitted, it will be set to "Table `:table` should have a check constraint". Note that this test will fail if the table in question does not exist. +In the event of failure, the diagnostics will list the columns on the table +that do have check constraints, if any: + ### `col_has_check( schema, table, column, description )` ### ### `col_has_check( schema, table, column[], description )` ### ### `col_has_check( table, column, description )` ### diff --git a/expected/check.out b/expected/check.out index 063411781e61..e672d06c510f 100644 --- a/expected/check.out +++ b/expected/check.out @@ -1,5 +1,5 @@ \unset ECHO -1..39 +1..48 ok 1 - has_check( schema, table, desc ) should pass ok 2 - has_check( schema, table, desc ) should have the proper description ok 3 - has_check( schema, table, desc ) should have the proper diagnostics @@ -18,24 +18,33 @@ ok 15 - has_check( table, desc ) fail should have the proper diagnostics ok 16 - col_has_check( sch, tab, col, desc ) should pass ok 17 - col_has_check( sch, tab, col, desc ) should have the proper description ok 18 - col_has_check( sch, tab, col, desc ) should have the proper diagnostics -ok 19 - col_has_check( tab, col, desc ) should pass -ok 20 - col_has_check( tab, col, desc ) should have the proper description -ok 21 - col_has_check( tab, col, desc ) should have the proper diagnostics -ok 22 - col_has_check( table, column ) should pass -ok 23 - col_has_check( table, column ) should have the proper description -ok 24 - col_has_check( table, column ) should have the proper diagnostics -ok 25 - col_has_check( sch, tab, col, desc ) fail should fail -ok 26 - col_has_check( sch, tab, col, desc ) fail should have the proper description -ok 27 - col_has_check( sch, tab, col, desc ) fail should have the proper diagnostics -ok 28 - col_has_check( tab, col, desc ) fail should fail -ok 29 - col_has_check( tab, col, desc ) fail should have the proper description -ok 30 - col_has_check( tab, col, desc ) fail should have the proper diagnostics -ok 31 - col_has_check( sch, tab, col[], desc ) should pass -ok 32 - col_has_check( sch, tab, col[], desc ) should have the proper description -ok 33 - col_has_check( sch, tab, col[], desc ) should have the proper diagnostics -ok 34 - col_has_check( tab, col[], desc ) should pass -ok 35 - col_has_check( tab, col[], desc ) should have the proper description -ok 36 - col_has_check( tab, col[], desc ) should have the proper diagnostics -ok 37 - col_has_check( tab, col[] ) should pass -ok 38 - col_has_check( tab, col[] ) should have the proper description -ok 39 - col_has_check( tab, col[] ) should have the proper diagnostics +ok 19 - col_has_check( sch, tab, cols, desc ) should pass +ok 20 - col_has_check( sch, tab, cols, desc ) should have the proper description +ok 21 - col_has_check( sch, tab, cols, desc ) should have the proper diagnostics +ok 22 - col_has_check( tab, col, desc ) should pass +ok 23 - col_has_check( tab, col, desc ) should have the proper description +ok 24 - col_has_check( tab, col, desc ) should have the proper diagnostics +ok 25 - col_has_check( tab, cols, desc ) should pass +ok 26 - col_has_check( tab, cols, desc ) should have the proper description +ok 27 - col_has_check( tab, cols, desc ) should have the proper diagnostics +ok 28 - col_has_check( table, column ) should pass +ok 29 - col_has_check( table, column ) should have the proper description +ok 30 - col_has_check( table, column ) should have the proper diagnostics +ok 31 - col_has_check( table, columns ) should pass +ok 32 - col_has_check( table, columns ) should have the proper description +ok 33 - col_has_check( table, columns ) should have the proper diagnostics +ok 34 - col_has_check( sch, tab, col, desc ) fail should fail +ok 35 - col_has_check( sch, tab, col, desc ) fail should have the proper description +ok 36 - col_has_check( sch, tab, col, desc ) fail should have the proper diagnostics +ok 37 - col_has_check( tab, col, desc ) fail should fail +ok 38 - col_has_check( tab, col, desc ) fail should have the proper description +ok 39 - col_has_check( tab, col, desc ) fail should have the proper diagnostics +ok 40 - col_has_check( sch, tab, col[], desc ) should pass +ok 41 - col_has_check( sch, tab, col[], desc ) should have the proper description +ok 42 - col_has_check( sch, tab, col[], desc ) should have the proper diagnostics +ok 43 - col_has_check( tab, col[], desc ) should pass +ok 44 - col_has_check( tab, col[], desc ) should have the proper description +ok 45 - col_has_check( tab, col[], desc ) should have the proper diagnostics +ok 46 - col_has_check( tab, col[] ) should pass +ok 47 - col_has_check( tab, col[] ) should have the proper description +ok 48 - col_has_check( tab, col[] ) should have the proper diagnostics diff --git a/expected/unique.out b/expected/unique.out index 9c3dd3f86fac..2f191e51a665 100644 --- a/expected/unique.out +++ b/expected/unique.out @@ -1,5 +1,5 @@ \unset ECHO -1..39 +1..48 ok 1 - has_unique( schema, table, description ) should pass ok 2 - has_unique( schema, table, description ) should have the proper description ok 3 - has_unique( schema, table, description ) should have the proper diagnostics @@ -18,24 +18,33 @@ ok 15 - has_unique( table, description ) fail should have the proper diagnostics ok 16 - col_is_unique( schema, table, column, description ) should pass ok 17 - col_is_unique( schema, table, column, description ) should have the proper description ok 18 - col_is_unique( schema, table, column, description ) should have the proper diagnostics -ok 19 - col_is_unique( table, column, description ) should pass -ok 20 - col_is_unique( table, column, description ) should have the proper description -ok 21 - col_is_unique( table, column, description ) should have the proper diagnostics -ok 22 - col_is_unique( table, column ) should pass -ok 23 - col_is_unique( table, column ) should have the proper description -ok 24 - col_is_unique( table, column ) should have the proper diagnostics -ok 25 - col_is_unique( schema, table, column, description ) fail should fail -ok 26 - col_is_unique( schema, table, column, description ) fail should have the proper description -ok 27 - col_is_unique( schema, table, column, description ) fail should have the proper diagnostics -ok 28 - col_is_unique( table, column, description ) fail should fail -ok 29 - col_is_unique( table, column, description ) fail should have the proper description -ok 30 - col_is_unique( table, column, description ) fail should have the proper diagnostics -ok 31 - col_is_unique( schema, table, column[], description ) should pass -ok 32 - col_is_unique( schema, table, column[], description ) should have the proper description -ok 33 - col_is_unique( schema, table, column[], description ) should have the proper diagnostics -ok 34 - col_is_unique( table, column[], description ) should pass -ok 35 - col_is_unique( table, column[], description ) should have the proper description -ok 36 - col_is_unique( table, column[], description ) should have the proper diagnostics -ok 37 - col_is_unique( table, column[] ) should pass -ok 38 - col_is_unique( table, column[] ) should have the proper description -ok 39 - col_is_unique( table, column[] ) should have the proper diagnostics +ok 19 - col_is_unique( schema, table, columns, description ) should pass +ok 20 - col_is_unique( schema, table, columns, description ) should have the proper description +ok 21 - col_is_unique( schema, table, columns, description ) should have the proper diagnostics +ok 22 - col_is_unique( table, column, description ) should pass +ok 23 - col_is_unique( table, column, description ) should have the proper description +ok 24 - col_is_unique( table, column, description ) should have the proper diagnostics +ok 25 - col_is_unique( table, columns, description ) should pass +ok 26 - col_is_unique( table, columns, description ) should have the proper description +ok 27 - col_is_unique( table, columns, description ) should have the proper diagnostics +ok 28 - col_is_unique( table, column ) should pass +ok 29 - col_is_unique( table, column ) should have the proper description +ok 30 - col_is_unique( table, column ) should have the proper diagnostics +ok 31 - col_is_unique( table, columns ) should pass +ok 32 - col_is_unique( table, columns ) should have the proper description +ok 33 - col_is_unique( table, columns ) should have the proper diagnostics +ok 34 - col_is_unique( schema, table, column, description ) fail should fail +ok 35 - col_is_unique( schema, table, column, description ) fail should have the proper description +ok 36 - col_is_unique( schema, table, column, description ) fail should have the proper diagnostics +ok 37 - col_is_unique( table, column, description ) fail should fail +ok 38 - col_is_unique( table, column, description ) fail should have the proper description +ok 39 - col_is_unique( table, column, description ) fail should have the proper diagnostics +ok 40 - col_is_unique( schema, table, column[], description ) should pass +ok 41 - col_is_unique( schema, table, column[], description ) should have the proper description +ok 42 - col_is_unique( schema, table, column[], description ) should have the proper diagnostics +ok 43 - col_is_unique( table, column[], description ) should pass +ok 44 - col_is_unique( table, column[], description ) should have the proper description +ok 45 - col_is_unique( table, column[], description ) should have the proper diagnostics +ok 46 - col_is_unique( table, column[] ) should pass +ok 47 - col_is_unique( table, column[] ) should have the proper description +ok 48 - col_is_unique( table, column[] ) should have the proper diagnostics diff --git a/pgtap.sql.in b/pgtap.sql.in index b23cf9a698c0..1ac286ee0f74 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -1539,34 +1539,6 @@ RETURNS TEXT AS $$ SELECT hasnt_pk( $1, 'Table ' || quote_ident($1) || ' should not have a primary key' ); $$ LANGUAGE sql; --- _ckeys( schema, table, constraint_type ) -CREATE OR REPLACE FUNCTION _ckeys ( NAME, NAME, CHAR ) -RETURNS NAME[] AS $$ - SELECT 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 - JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid AND a.attnum = ANY( x.conkey ) - WHERE n.nspname = $1 - AND c.relname = $2 - AND x.contype = $3 - ); -$$ LANGUAGE sql; - --- _ckeys( table, constraint_type ) -CREATE OR REPLACE FUNCTION _ckeys ( NAME, CHAR ) -RETURNS NAME[] AS $$ - SELECT ARRAY ( - SELECT a.attname - FROM pg_catalog.pg_class c - JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid - JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid AND a.attnum = ANY( x.conkey ) - AND c.relname = $1 - AND x.contype = $2 - ); -$$ LANGUAGE sql; - CREATE OR REPLACE FUNCTION _ident_array_to_string( name[], text ) RETURNS text AS $$ SELECT array_to_string(ARRAY( @@ -1669,6 +1641,40 @@ AS 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) + 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 +$$ 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) + 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 +$$ 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 $$ @@ -1941,16 +1947,64 @@ 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 || ' constriants'; + 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 || ' constriants'; + 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 is( _ckeys( $1, $2, 'u' ), $3, $4 ); + SELECT _constraint( $1, $2, 'u', $3, $4, 'unique' ); $$ LANGUAGE sql; -- col_is_unique( table, column, description ) CREATE OR REPLACE FUNCTION col_is_unique ( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ - SELECT is( _ckeys( $1, 'u' ), $2, $3 ); + SELECT _constraint( $1, 'u', $2, $3, 'unique' ); $$ LANGUAGE sql; -- col_is_unique( table, column[] ) @@ -1998,13 +2052,13 @@ $$ LANGUAGE sql; -- col_has_check( schema, table, column, description ) CREATE OR REPLACE FUNCTION col_has_check ( NAME, NAME, NAME[], TEXT ) RETURNS TEXT AS $$ - SELECT is( _ckeys( $1, $2, 'c' ), $3, $4 ); + 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 is( _ckeys( $1, 'c' ), $2, $3 ); + SELECT _constraint( $1, 'c', $2, $3, 'check' ); $$ LANGUAGE sql; -- col_has_check( table, column[] ) diff --git a/sql/check.sql b/sql/check.sql index fc360c2ee843..4b707ed49d68 100644 --- a/sql/check.sql +++ b/sql/check.sql @@ -104,7 +104,7 @@ SELECT * FROM check_test( col_has_check( 'sometab', ARRAY['numb', 'myint'] ), true, 'col_has_check( table, columns )', - 'Column sometab(numb, myint) should have a check constraint', + 'Columns sometab(numb, myint) should have a check constraint', '' ); @@ -114,6 +114,7 @@ SELECT * FROM check_test( 'col_has_check( sch, tab, col, desc ) fail', 'public.sometab.id should have a check', ' have: {name} + {numb,myint} want: {id}' ); @@ -123,6 +124,7 @@ SELECT * FROM check_test( 'col_has_check( tab, col, desc ) fail', 'sometab.id should have a check', ' have: {name} + {numb,myint} want: {id}' ); diff --git a/sql/unique.sql b/sql/unique.sql index e6d8cad80080..70e742c3dcc7 100644 --- a/sql/unique.sql +++ b/sql/unique.sql @@ -104,7 +104,7 @@ SELECT * FROM check_test( col_is_unique( 'sometab', ARRAY['numb', 'myint'] ), true, 'col_is_unique( table, columns )', - 'Column sometab(numb, myint) should have a unique constraint', + 'Columns sometab(numb, myint) should have a unique constraint', '' ); @@ -114,6 +114,7 @@ SELECT * FROM check_test( 'col_is_unique( schema, table, column, description ) fail', 'public.sometab.id should be unique', ' have: {name} + {numb,myint} want: {id}' ); @@ -123,6 +124,7 @@ SELECT * FROM check_test( 'col_is_unique( table, column, description ) fail', 'sometab.id should be unique', ' have: {name} + {numb,myint} want: {id}' ); From e24ee3b07c9d2ec7b7b93f229c4d57acb677bb59 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Oct 2010 09:33:21 -0700 Subject: [PATCH 0563/1195] Update uninstall script. --- pgtap.sql.in | 3 ++- uninstall_pgtap.sql.in | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 1ac286ee0f74..83b89d9344af 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -5747,7 +5747,8 @@ EXCEPTION END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION _cleanup() RETURNS boolean AS $$ +CREATE OR REPLACE FUNCTION _cleanup() +RETURNS boolean AS $$ DROP TABLE __tresults__; DROP SEQUENCE __tresults___numb_seq; DROP TABLE __tcache__; diff --git a/uninstall_pgtap.sql.in b/uninstall_pgtap.sql.in index b389d33e50f6..a14683d85f4d 100644 --- a/uninstall_pgtap.sql.in +++ b/uninstall_pgtap.sql.in @@ -507,6 +507,8 @@ DROP FUNCTION col_is_unique ( NAME, NAME, NAME, TEXT ); DROP FUNCTION col_is_unique ( NAME, NAME[] ); DROP FUNCTION col_is_unique ( NAME, NAME[], TEXT ); DROP FUNCTION col_is_unique ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ); +DROP FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ); DROP FUNCTION has_unique ( TEXT ); DROP FUNCTION has_unique ( TEXT, TEXT ); DROP FUNCTION has_unique ( TEXT, TEXT, TEXT ); @@ -542,12 +544,14 @@ DROP FUNCTION col_is_pk ( NAME, NAME, NAME, TEXT ); DROP FUNCTION col_is_pk ( NAME, NAME[] ); DROP FUNCTION col_is_pk ( NAME, NAME[], TEXT ); DROP FUNCTION col_is_pk ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _ckeys ( NAME, CHAR ); +DROP FUNCTION _ckeys ( NAME, NAME, CHAR ); +DROP FUNCTION _keys ( NAME, CHAR ); +DROP FUNCTION _keys ( NAME, NAME, CHAR ); DROP VIEW pg_all_foreign_keys; DROP FUNCTION _pg_sv_table_accessible( OID, OID ); DROP FUNCTION _pg_sv_column_array( OID, SMALLINT[] ); DROP FUNCTION _ident_array_to_string( name[], text ); -DROP FUNCTION _ckeys ( NAME, CHAR ); -DROP FUNCTION _ckeys ( NAME, NAME, CHAR ); DROP FUNCTION hasnt_pk ( NAME ); DROP FUNCTION hasnt_pk ( NAME, TEXT ); DROP FUNCTION hasnt_pk ( NAME, NAME, TEXT ); @@ -677,8 +681,8 @@ DROP FUNCTION is (anyelement, anyelement); DROP FUNCTION is (anyelement, anyelement, text); DROP FUNCTION ok ( boolean ); DROP FUNCTION ok ( boolean, text ); -DROP FUNCTION diag( VARIADIC text[] ); DROP FUNCTION diag( VARIADIC anyarray ); +DROP FUNCTION diag( VARIADIC text[] ); DROP FUNCTION diag ( msg anyelement ); DROP FUNCTION diag ( msg text ); DROP FUNCTION finish (); From c647f4491ff867b9012ff3807309425f9597ca2c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 27 Oct 2010 11:08:16 -0700 Subject: [PATCH 0564/1195] Add missing header. --- README.pgtap | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.pgtap b/README.pgtap index f475fcc81467..645e24c935bc 100644 --- a/README.pgtap +++ b/README.pgtap @@ -4374,6 +4374,9 @@ pgTAP has been tested on the following builds of PostgreSQL: If you know of others, please submit them! Use `psql -d template1 -c 'SELECT VERSION()'` to get the formal build version and OS. +Metadata +======== + Public Repository ----------------- From be33026d7246b59768ec8c5035703710fa77aae3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 7 Jan 2011 15:07:07 -0800 Subject: [PATCH 0565/1195] Add Synopsis to the README. --- README.pgtap | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/README.pgtap b/README.pgtap index 645e24c935bc..727eade0acf8 100644 --- a/README.pgtap +++ b/README.pgtap @@ -6,6 +6,45 @@ PL/SQL. It includes a comprehensive collection of TAP-emitting assertion functions, as well as the ability to integrate with other TAP-emitting test frameworks. It can also be used in the xUnit testing style. +Synopsis +======== + + SELECT plan( 23 ); + -- or SELECT * from no_plan(); + + -- Various ways to say "ok" + SELECT ok( :have = :want, :test_description ); + + SELECT is( :have, :want, $test_description ); + SELECT isnt( :have, :want, $test_description ); + + -- Rather than \echo # here's what went wrong + SELECT diag( 'here''s what went wrong' ); + + -- Compare values with LIKE or regular expressions. + SELECT alike( :have, :like_expression, $test_description ); + SELECT unalike( :have, :like_expression, $test_description ); + + SELECT matches( :have, :regex, $test_description ); + SELECT doesnt_match( :have, :regex, $test_description ); + + SELECT cmp_ok(:have, '=', :want, $test_description ); + + -- Skip tests based on runtime conditions. + SELECT CASE WHEN :some_feature THEN collect_tap( + ok( foo(), :test_description), + is( foo(42), 23, :test_description) + ) ELSE skip(:why, :how_many ) END; + + -- Mark some tests as to-do tests. + SELECT todo(:why, :how_many); + SELECT ok( foo(), :test_description); + SELECT is( foo(42), 23, :test_description); + + -- Simple pass/fail. + SELECT pass(:test_description); + SELECT fail(:test_description); + Installation ============ From a33595b32ad6b040e6f453ab2567ad8b19c24134 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 13 Jan 2011 14:12:55 -0800 Subject: [PATCH 0566/1195] Typo. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index ad872b3fc4bc..2f8b67651c8d 100644 --- a/Changes +++ b/Changes @@ -12,7 +12,7 @@ Revision history for pgTAP * Fixed examples of single-quoted string values stored in `psql` variables in `README.pgtap`. * Fixed a bug in `plan()` where a sequence wasn't getting created as a - temporary sequence. This wasn't a problem if all testin was done inside a + temporary sequence. This wasn't a problem if all testing was done inside a transaction, but if not, you could only run tests once without an error. * Fixed bug where `has_column()`, `col_type_is()`, `domain_type_is()`, `cast_exists()`, `cast_context_is()`, and `has_operator()`, were forcing the From 237efa43ae756dc42687ee953063a0a5381b1b76 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 13 Jan 2011 14:18:38 -0800 Subject: [PATCH 0567/1195] Typo. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 2f8b67651c8d..69f20ebc4354 100644 --- a/Changes +++ b/Changes @@ -16,7 +16,7 @@ Revision history for pgTAP transaction, but if not, you could only run tests once without an error. * Fixed bug where `has_column()`, `col_type_is()`, `domain_type_is()`, `cast_exists()`, `cast_context_is()`, and `has_operator()`, were forcing the - type argument to lowercase, which wont't correct when the type is actually + type argument to lowercase, which wasn't correct when the type is actually mixed case or uppercase (that is, named in double quotes when it was created). * The expected type passed to `index_is_type()` is no longer compared From 02d5a87c6703176b9643bf72fc3f5a0a7eebe2d9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 14:34:28 -0800 Subject: [PATCH 0568/1195] Update mail list link. --- pgtap.sql.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgtap.sql.in b/pgtap.sql.in index 83b89d9344af..41a2f3f5551d 100644 --- a/pgtap.sql.in +++ b/pgtap.sql.in @@ -6,7 +6,7 @@ -- -- The home page for the pgTAP project is: -- --- http://pgtap.projects.postgresql.org/ +-- http://pgtap.org/ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA, public; From f9b14da81d3735a69340ab88b6f150d7e01fe317 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 14:59:53 -0800 Subject: [PATCH 0569/1195] Update 8.3 compatibility patches. --- compat/install-8.3.patch | 14 +++++++------- compat/uninstall-8.3.patch | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index a7daea5dcea1..4647a9af6952 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -1,5 +1,5 @@ ---- pgtap.sql 2010-05-21 16:48:21.000000000 -0400 -+++ pgtap.sql.new 2010-05-21 16:47:26.000000000 -0400 +--- pgtap.sql.orig 2011-02-01 14:54:33.000000000 -0800 ++++ pgtap.sql 2011-02-01 14:54:33.000000000 -0800 @@ -15,6 +15,11 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -12,7 +12,7 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT s.a[1]::int * 10000 -@@ -5785,8 +5790,9 @@ +@@ -5891,8 +5896,9 @@ 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 @@ -23,7 +23,7 @@ AND attnum > 0 AND NOT attisdropped ORDER BY attnum -@@ -6133,7 +6139,7 @@ +@@ -6239,7 +6245,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -32,7 +32,7 @@ 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' || -@@ -6288,7 +6294,7 @@ +@@ -6394,7 +6400,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -41,7 +41,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6474,13 +6480,7 @@ +@@ -6580,13 +6586,7 @@ $$ LANGUAGE sql; -- collect_tap( tap, tap, tap ) @@ -56,7 +56,7 @@ RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); $$ LANGUAGE sql; -@@ -6952,7 +6952,7 @@ +@@ -7058,7 +7058,7 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; diff --git a/compat/uninstall-8.3.patch b/compat/uninstall-8.3.patch index 4a77ec289904..1c91b36bc7bc 100644 --- a/compat/uninstall-8.3.patch +++ b/compat/uninstall-8.3.patch @@ -1,6 +1,6 @@ ---- uninstall_pgtap.sql.orig 2009-12-16 16:16:49.000000000 -0800 -+++ uninstall_pgtap.sql 2009-12-16 16:20:53.000000000 -0800 -@@ -57,8 +57,7 @@ +--- uninstall_pgtap.sql.orig 2011-02-01 14:58:51.000000000 -0800 ++++ uninstall_pgtap.sql 2011-02-01 14:58:51.000000000 -0800 +@@ -58,8 +58,7 @@ DROP FUNCTION throws_like ( TEXT, TEXT ); DROP FUNCTION throws_like ( TEXT, TEXT, TEXT ); DROP FUNCTION _tlike ( BOOLEAN, TEXT, TEXT, TEXT ); @@ -10,7 +10,7 @@ DROP FUNCTION is_empty( TEXT ); DROP FUNCTION is_empty( TEXT, TEXT ); DROP FUNCTION isa_ok( anyelement, regtype ); -@@ -698,6 +697,7 @@ +@@ -701,6 +700,7 @@ DROP FUNCTION _get ( text ); DROP FUNCTION no_plan(); DROP FUNCTION plan( integer ); From d638532ebda38f67e74cc42fb450c6ff7c2a9fc8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 15:22:01 -0800 Subject: [PATCH 0570/1195] Update 8.2 compatibility patches. --- Changes | 5 +-- README.pgtap | 23 +++++++------ compat/install-8.2.patch | 70 +++++++++++++++++++++++++------------- compat/uninstall-8.2.patch | 18 +++++++--- 4 files changed, 75 insertions(+), 41 deletions(-) diff --git a/Changes b/Changes index 69f20ebc4354..03d81f3c50c0 100644 --- a/Changes +++ b/Changes @@ -25,8 +25,9 @@ Revision history for pgTAP * Documented that double-quotes must be used for types created with double-quoted names in the strings passed to `casts_are()`. This should be the only part of the API that does require this sort of quoting. -* Added `diag(anyelement)` and `diag(VARIADIC anyarray)` (for 8.4 and higher) - to make it easier to emit diagnostics for whatever data you have at hand. +* Added `diag(anyelement)` (for 8.3 and higher) and `diag(VARIADIC anyarray)` + (for 8.4 and higher) to make it easier to emit diagnostics for whatever data + you have at hand. * The `_cleanup()` function now properly drops the temporary sequences as well as the temporary tables. These are usually automatically dropped when the tests finish and the client disconnects, but it's best to try to keep things diff --git a/README.pgtap b/README.pgtap index 727eade0acf8..6b649036ac63 100644 --- a/README.pgtap +++ b/README.pgtap @@ -3711,10 +3711,10 @@ Which would produce: # but yours is set to en_US.ISO8859-1. # As a result, some tests may fail. YMMV. -You can pass data of any type to `diag()` and it will all be converted to text -for the diagnostics. On PostgreSQL 8.4 and higher, you can pass any number of -arguments (as long as they are all the same data type) and they will be -concatenated together. +You can pass data of any type to `diag()` on PostgreSQL 8.3 and higher and it +will all be converted to text for the diagnostics. On PostgreSQL 8.4 and +higher, you can pass any number of arguments (as long as they are all the same +data type) and they will be concatenated together. Conditional Tests ----------------- @@ -4325,16 +4325,17 @@ No changes. Everything should just work. * A C function, `pg_typeof()`, is built and installed in a DSO. This is for compatibility with the same function that ships in 8.4 core, and is required for `cmp_ok()` and `isa_ok()` to work. -* The variadic form of `collect_tap()` is not available. Instead you must pass - an array of TAP to it. +* The variadic forms of `diag()` and `collect_tap()` are not available. + You can pass an array of TAP to `collect_tap()`, however. 8.2 and Down ------------ -* A patch is applied that removes the `enum_has_labels()` function, and - `col_has_default()` cannot be used to test for columns specified with - `DEFAULT NULL` (even though that's the implied default default). The - `has_enums()` function won't work. Also, a number of assignments casts are - added to increase compatibility. The casts are: +* A patch is applied that removes the `enum_has_labels()`, the + `diag(anyelement)` function and `col_has_default()` cannot be used to test + for columns specified with `DEFAULT NULL` (even though that's the implied + default default). The `has_enums()` function won't work. Also, a number of + assignments casts are added to increase compatibility. The casts are: + + `boolean` to `text` + `text[]` to `text` + `name[]` to `text` diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 25227d4997f5..9a96e40bfa85 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,5 +1,5 @@ ---- pgtap.sql 2010-05-21 17:18:26.000000000 -0400 -+++ pgtap.sql.new 2010-05-21 17:17:21.000000000 -0400 +--- pgtap.sql 2011-02-01 15:15:23.000000000 -0800 ++++ pgtap.sql.orig 2011-02-01 15:14:35.000000000 -0800 @@ -11,6 +11,59 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA, public; @@ -77,7 +77,29 @@ RETURN currval('__tresults___numb_seq'); END; $$ LANGUAGE plpgsql; -@@ -485,9 +538,9 @@ +@@ -273,21 +326,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 +@@ -500,9 +538,9 @@ output TEXT; BEGIN EXECUTE 'SELECT ' || @@ -89,7 +111,7 @@ INTO result; output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( -@@ -1091,7 +1144,7 @@ +@@ -1106,7 +1144,7 @@ SELECT COALESCE(substring( pg_catalog.format_type($1, $2), '(("(?!")([^"]|"")+"|[^.]+)([(][^)]+[)])?)$' @@ -98,7 +120,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION display_type ( NAME, OID, INTEGER ) -@@ -2102,7 +2155,7 @@ +@@ -2194,7 +2232,7 @@ p.proname AS name, array_to_string(p.proargtypes::regtype[], ',') AS args, CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END @@ -107,7 +129,7 @@ p.prolang AS langoid, p.proisstrict AS is_strict, p.proisagg AS is_agg, -@@ -3309,63 +3362,6 @@ +@@ -3399,63 +3437,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -171,7 +193,7 @@ CREATE OR REPLACE FUNCTION _has_role( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( -@@ -5814,13 +5810,13 @@ +@@ -5920,13 +5901,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -187,7 +209,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6044,7 +6040,7 @@ +@@ -6150,7 +6131,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -196,7 +218,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6139,11 +6135,11 @@ +@@ -6245,11 +6226,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -211,7 +233,7 @@ ); END IF; rownum = rownum + 1; -@@ -6158,8 +6154,8 @@ +@@ -6264,8 +6245,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -222,7 +244,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6294,7 +6290,7 @@ +@@ -6400,7 +6381,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -231,7 +253,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6308,8 +6304,8 @@ +@@ -6414,8 +6395,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -242,7 +264,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6434,9 +6430,9 @@ +@@ -6540,9 +6521,9 @@ DECLARE typeof regtype := pg_typeof($1); BEGIN @@ -255,7 +277,7 @@ END; $$ LANGUAGE plpgsql; -@@ -6457,7 +6453,7 @@ +@@ -6563,7 +6544,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -264,7 +286,7 @@ END LOOP; -- What extra records do we have? -@@ -6602,7 +6598,7 @@ +@@ -6708,7 +6689,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -273,7 +295,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT -@@ -6620,7 +6616,7 @@ +@@ -6726,7 +6707,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -282,7 +304,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) ), -@@ -6653,7 +6649,7 @@ +@@ -6759,7 +6740,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -291,7 +313,7 @@ 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']) ) -@@ -6672,7 +6668,7 @@ +@@ -6778,7 +6759,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -300,7 +322,7 @@ 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']) ) -@@ -6952,10 +6948,12 @@ +@@ -7058,10 +7039,12 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; @@ -316,7 +338,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7100,7 +7098,7 @@ +@@ -7206,7 +7189,7 @@ CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) RETURNS TEXT AS $$ @@ -325,7 +347,7 @@ $$ LANGUAGE SQL; -- operators_are( schema, operators[], description ) -@@ -7109,7 +7107,7 @@ +@@ -7215,7 +7198,7 @@ SELECT _areni( 'operators', ARRAY( @@ -334,7 +356,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7121,7 +7119,7 @@ +@@ -7227,7 +7210,7 @@ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT @@ -343,7 +365,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7142,7 +7140,7 @@ +@@ -7248,7 +7231,7 @@ SELECT _areni( 'operators', ARRAY( @@ -352,7 +374,7 @@ 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) -@@ -7155,7 +7153,7 @@ +@@ -7261,7 +7244,7 @@ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT diff --git a/compat/uninstall-8.2.patch b/compat/uninstall-8.2.patch index 642313541e6f..cc5a22f66a1b 100644 --- a/compat/uninstall-8.2.patch +++ b/compat/uninstall-8.2.patch @@ -1,6 +1,6 @@ ---- uninstall_pgtap.sql.orig 2009-12-17 16:07:04.000000000 -0800 -+++ uninstall_pgtap.sql 2009-12-17 16:06:55.000000000 -0800 -@@ -364,10 +364,6 @@ +--- uninstall_pgtap.sql 2011-02-01 15:15:23.000000000 -0800 ++++ uninstall_pgtap.sql.orig 2011-02-01 15:14:54.000000000 -0800 +@@ -367,10 +367,6 @@ DROP FUNCTION has_role( NAME ); DROP FUNCTION has_role( NAME, TEXT ); DROP FUNCTION _has_role( NAME ); @@ -11,7 +11,17 @@ DROP FUNCTION hasnt_enum( NAME ); DROP FUNCTION hasnt_enum( NAME, TEXT ); DROP FUNCTION hasnt_enum( NAME, NAME ); -@@ -702,5 +698,17 @@ +@@ -680,9 +676,6 @@ + DROP FUNCTION is (anyelement, anyelement, text); + DROP FUNCTION ok ( boolean ); + DROP FUNCTION ok ( boolean, text ); +-DROP FUNCTION diag( VARIADIC anyarray ); +-DROP FUNCTION diag( VARIADIC text[] ); +-DROP FUNCTION diag ( msg anyelement ); + DROP FUNCTION diag ( msg text ); + DROP FUNCTION finish (); + DROP FUNCTION _finish ( INTEGER, INTEGER, INTEGER); +@@ -705,5 +698,17 @@ DROP FUNCTION os_name(); DROP FUNCTION pg_version_num(); DROP FUNCTION pg_version(); From 95e4f993e96f192f71cb3046df37489e65d704b3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 15:35:03 -0800 Subject: [PATCH 0571/1195] Update 8.1 compatibility patches. --- compat/install-8.1.patch | 54 +++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index e6719ac89fc4..773532a2c34a 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,42 @@ ---- pgtap.sql 2010-05-21 17:56:54.000000000 -0400 -+++ pgtap.sql.new 2010-05-21 17:56:36.000000000 -0400 -@@ -5588,7 +5588,7 @@ +--- pgtap.sql 2011-02-01 15:32:02.000000000 -0800 ++++ pgtap.sql.orig 2011-02-01 15:30:37.000000000 -0800 +@@ -1993,13 +1993,13 @@ + CREATE OR REPLACE FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ) + RETURNS TEXT AS $$ + DECLARE +- akey NAME[]; ++ rec record; + 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; ++ FOR rec IN SELECT * FROM _keys($1, $2, $3) AS b(a) LOOP ++ IF rec.a = $4 THEN RETURN pass($5); END IF; ++ keys = keys || rec.a::text; + END LOOP; + IF array_upper(keys, 0) = 1 THEN + have := 'No ' || $6 || ' constriants'; +@@ -2017,13 +2017,13 @@ + CREATE OR REPLACE FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ) + RETURNS TEXT AS $$ + DECLARE +- akey NAME[]; ++ rec record; + 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; ++ FOR rec IN SELECT * FROM _keys($1, $2) AS b(a) LOOP ++ IF rec.a = $3 THEN RETURN pass($4); END IF; ++ keys = keys || rec.a::text; + END LOOP; + IF array_upper(keys, 0) = 1 THEN + have := 'No ' || $5 || ' constriants'; +@@ -5677,7 +5677,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -9,7 +45,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -5596,8 +5596,8 @@ +@@ -5685,8 +5685,8 @@ -- 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. @@ -20,7 +56,7 @@ END LOOP; END LOOP; RETURN; -@@ -5659,14 +5659,14 @@ +@@ -5750,14 +5750,14 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -37,7 +73,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -5681,15 +5681,15 @@ +@@ -5772,15 +5772,15 @@ IF verbos THEN RETURN NEXT diag(tests[i] || '()'); END IF; -- Run the setup functions. @@ -57,7 +93,7 @@ -- Remember how many failed and then roll back. num_faild := num_faild + num_failed(); -@@ -5704,7 +5704,7 @@ +@@ -5795,7 +5795,7 @@ END LOOP; -- Run the shutdown functions. @@ -66,7 +102,7 @@ -- Raise an exception to rollback any changes. RAISE EXCEPTION '__TAP_ROLLBACK__'; -@@ -5715,8 +5715,8 @@ +@@ -5806,8 +5806,8 @@ END IF; END; -- Finish up. @@ -77,7 +113,7 @@ END LOOP; -- Clean up and return. -@@ -6947,7 +6947,7 @@ +@@ -7038,7 +7038,7 @@ DECLARE rec RECORD; BEGIN From c23f07425d0498138f7972359bc60f4184286e44 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 16:03:11 -0800 Subject: [PATCH 0572/1195] Update 8.0 compatibility patches. --- compat/install-8.0.patch | 53 ++++++++++++++++++++++++-------------- compat/uninstall-8.0.patch | 8 +++--- sql/moretap.sql | 1 + 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index 3ca0db7029b6..cc31d6c854d3 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,5 +1,5 @@ ---- pgtap.sql 2010-05-21 18:40:22.000000000 -0400 -+++ pgtap.sql.new 2010-05-21 18:40:08.000000000 -0400 +--- pgtap.sql 2011-02-01 16:00:45.000000000 -0800 ++++ pgtap.sql.orig 2011-02-01 16:00:54.000000000 -0800 @@ -68,6 +68,27 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -145,7 +145,7 @@ output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -1365,19 +1401,21 @@ +@@ -1389,19 +1425,21 @@ CREATE OR REPLACE FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -171,7 +171,7 @@ END; $$ LANGUAGE plpgsql; -@@ -3362,39 +3400,6 @@ +@@ -3437,39 +3475,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -208,10 +208,23 @@ - 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); +@@ -3501,9 +3506,9 @@ + CREATE OR REPLACE FUNCTION _is_super( NAME ) RETURNS BOOLEAN AS $$ - SELECT usesuper -@@ -3498,13 +3503,8 @@ +- SELECT rolsuper +- FROM pg_catalog.pg_roles +- WHERE rolname = $1 ++ SELECT usesuper ++ FROM pg_catalog.pg_user ++ WHERE usename = $1 + $$ LANGUAGE sql STRICT; + + -- is_superuser( user, description ) +@@ -3578,13 +3583,8 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _grolist ( NAME ) @@ -227,7 +240,7 @@ $$ LANGUAGE sql; -- is_member_of( group, user[], description ) -@@ -3513,9 +3513,9 @@ +@@ -3593,9 +3593,9 @@ DECLARE missing text[]; BEGIN @@ -239,7 +252,7 @@ ); END IF; -@@ -5453,6 +5453,7 @@ +@@ -5542,6 +5542,7 @@ res BOOLEAN; descr TEXT; adiag TEXT; @@ -247,7 +260,7 @@ have ALIAS FOR $1; eok ALIAS FOR $2; name ALIAS FOR $3; -@@ -5464,8 +5465,10 @@ +@@ -5553,8 +5554,10 @@ tnumb := currval('__tresults___numb_seq'); -- Fetch the results. @@ -260,7 +273,7 @@ -- Now delete those results. EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; -@@ -5651,116 +5654,6 @@ +@@ -5742,116 +5745,6 @@ SELECT TRUE; $$ LANGUAGE sql; @@ -377,7 +390,7 @@ CREATE OR REPLACE FUNCTION _temptable ( TEXT, TEXT ) RETURNS TEXT AS $$ BEGIN -@@ -5810,13 +5703,13 @@ +@@ -5901,13 +5794,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -393,7 +406,7 @@ END LOOP; -- Drop the temporary tables. -@@ -5930,16 +5823,20 @@ +@@ -6021,16 +5914,20 @@ missing TEXT[] := '{}'; res BOOLEAN := TRUE; msg TEXT := ''; @@ -416,7 +429,7 @@ -- Drop the temporary tables. EXECUTE 'DROP TABLE ' || have; -@@ -6040,7 +5937,7 @@ +@@ -6131,7 +6028,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -425,7 +438,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6135,11 +6032,11 @@ +@@ -6226,11 +6123,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -440,7 +453,7 @@ ); END IF; rownum = rownum + 1; -@@ -6154,8 +6051,8 @@ +@@ -6245,8 +6142,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -451,7 +464,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6290,7 +6187,7 @@ +@@ -6381,7 +6278,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -460,7 +473,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6304,8 +6201,8 @@ +@@ -6395,8 +6292,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -471,7 +484,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6453,7 +6350,7 @@ +@@ -6544,7 +6441,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -480,7 +493,7 @@ END LOOP; -- What extra records do we have? -@@ -6557,35 +6454,6 @@ +@@ -6648,35 +6545,6 @@ SELECT throws_imatching($1, $2, 'Should throw exception matching ' || quote_literal($2) ); $$ LANGUAGE sql; @@ -516,7 +529,7 @@ CREATE OR REPLACE FUNCTION _types_are ( NAME, NAME[], TEXT, CHAR[] ) RETURNS TEXT AS $$ SELECT _are( -@@ -6948,12 +6816,12 @@ +@@ -7039,12 +6907,12 @@ rec RECORD; BEGIN FOR rec in EXECUTE _query($1) LOOP END LOOP; diff --git a/compat/uninstall-8.0.patch b/compat/uninstall-8.0.patch index f6326657c6b6..53e1b2c63d8c 100644 --- a/compat/uninstall-8.0.patch +++ b/compat/uninstall-8.0.patch @@ -1,9 +1,9 @@ ---- uninstall_pgtap.sql.orig 2009-12-18 09:53:39.000000000 -0800 -+++ uninstall_pgtap.sql 2009-12-18 09:55:43.000000000 -0800 -@@ -359,11 +359,6 @@ +--- uninstall_pgtap.sql 2011-02-01 15:54:35.000000000 -0800 ++++ uninstall_pgtap.sql.orig 2011-02-01 15:55:28.000000000 -0800 +@@ -362,11 +362,6 @@ DROP FUNCTION has_user( NAME ); DROP FUNCTION has_user( NAME, TEXT ); - DROP FUNCTION _is_super( NAME ); + DROP FUNCTION _has_user( NAME ); -DROP FUNCTION hasnt_role( NAME ); -DROP FUNCTION hasnt_role( NAME, TEXT ); -DROP FUNCTION has_role( NAME ); diff --git a/sql/moretap.sql b/sql/moretap.sql index 4023258d221d..4e382491ec84 100644 --- a/sql/moretap.sql +++ b/sql/moretap.sql @@ -58,6 +58,7 @@ BEGIN RETURN NEXT pass('variadic text'); RETURN NEXT pass('variadic int'); RETURN NEXT pass('variadic unknown'); + RETURN; END IF; END; $$ LANGUAGE plpgsql; From 5e209a60904d883ffe487d195fc55a1b85c3ab10 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 16:54:41 -0800 Subject: [PATCH 0573/1195] Remove pg_prove and pg_tapgen. They are now on CPAN. --- Changes | 3 + Makefile | 51 ++--- README.pgtap | 19 +- bin/pg_prove | 459 --------------------------------------------- bin/pg_tapgen | 292 ---------------------------- contrib/pgtap.spec | 2 - 6 files changed, 24 insertions(+), 802 deletions(-) delete mode 100755 bin/pg_prove delete mode 100755 bin/pg_tapgen diff --git a/Changes b/Changes index 03d81f3c50c0..4b4b9871bb5d 100644 --- a/Changes +++ b/Changes @@ -40,6 +40,9 @@ Revision history for pgTAP were assocated with only one constraint. Since one can have multiple unique or check constraints on a table, each using different columns, this was sub-optimal. Thanks to Cédric Villemain for the spot! +* Removed `pg_prove` and `pg_tapgen` from the distribution. They must now be + installed separately from CPAN. If it's not installed, the intaller will + issue a warning. 0.24 2010-05-24T23:33:22 ------------------------- diff --git a/Makefile b/Makefile index 97de7a63dbbb..3fe6c803b6af 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,7 @@ -TESTS = $(wildcard sql/*.sql) +ESTS = $(wildcard sql/*.sql) EXTRA_CLEAN = test_setup.sql *.html DATA_built = pgtap.sql uninstall_pgtap.sql DOCS = README.pgtap -SCRIPTS = bbin/pg_prove bbin/pg_tapgen REGRESS = $(patsubst sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --load-language=plpgsql @@ -49,9 +48,15 @@ else include $(PGXS) endif -# Is TAP::Harness installed? +# Is TAP::Parser::SourceHandler::pgTAP installed? ifdef PERL -HAVE_HARNESS := $(shell $(PERL) -le 'eval { require TAP::Harness }; print 1 unless $$@' ) +HAVE_HARNESS := $(shell $(PERL) -le 'eval { require TAP::Parser::SourceHandler::pgTAP }; print 1 unless $$@' ) +endif + +ifndef HAVE_HARNESS + $(warning To use pg_prove, TAP::Parser::SourceHandler::pgTAP Perl module) + $(warning must be installed from CPAN. To do so, simply run:) + $(warning cpan TAP::Parser::SourceHandler::pgTAP) endif # Set up extra substitutions based on version numbers. @@ -134,48 +139,14 @@ ifdef TAPSCHEMA mv uninstall.tmp uninstall_pgtap.sql endif -# Build pg_prove and holler if there's no Perl or TAP::Harness. -bbin/pg_prove: - mkdir bbin -# sed -e "s,\\('\\|F<\\)psql\\(['>]\\),\\1$(bindir)/psql\\2,g" bin/pg_prove > bbin/pg_prove - sed -e "s,\\'psql\\',\\'$(bindir)/psql\\'," -e 's,F,F<$(bindir)/psql>,' bin/pg_prove > bbin/pg_prove -ifdef PERL - $(PERL) '-MExtUtils::MY' -e 'MY->fixin(shift)' bbin/pg_prove -ifndef HAVE_HARNESS - $(warning To use pg_prove, TAP::Harness Perl module must be installed from CPAN.) -endif -else - $(warning Could not find perl (required by pg_prove). Install it or set the PERL variable.) -endif - chmod +x bbin/pg_prove - -bbin/pg_tapgen: - cp bin/pg_tapgen bbin -ifdef PERL - $(PERL) '-MExtUtils::MY' -e 'MY->fixin(shift)' bbin/pg_tapgen -else - $(warning Could not find perl (required by pg_tapgen). Install it or set the PERL variable.) -endif - chmod +x bbin/pg_tapgen - # Make sure that we build the regression tests. installcheck: test_setup.sql -# Make sure we build pg_prove. -all: bbin/pg_prove bbin/pg_tapgen - -# Make sure we remove bbin. -clean: extraclean - -extraclean: - rm -rf bbin - # In addition to installcheck, one can also run the tests through pg_prove. -test: test_setup.sql bbin/pg_prove - ./bbin/pg_prove --pset tuples_only=1 $(TESTS) +test: test_setup.sql + pg_prove --pset tuples_only=1 $(TESTS) html: markdown -F 0x1000 README.pgtap > readme.html perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$5; $$lab = $$5; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$5 ? "$$5()" : $$4) . "" } END { print "
  • \n
\n" }' readme.html > toc.html perl -pi -e 'BEGIN { my %seen }; s{(' bin/pg_prove diff --git a/README.pgtap b/README.pgtap index 6b649036ac63..dd79b33454fa 100644 --- a/README.pgtap +++ b/README.pgtap @@ -108,11 +108,11 @@ Testing pgTAP with pgTAP ------------------------ In addition to the PostgreSQL-standard `installcheck` target, the `test` -target uses the included `pg_prove` Perl program to do its testing, which -requires that TAP::Harness, included in -[Test::Harness](http://search.cpan.org/dist/Test-Harness/ "Test::Harness on -CPAN") 3.x. You'll need to make sure that you use a database with PL/pgSQL -loaded, or else the tests won't work. `pg_prove` supports a number of +target uses the `pg_prove` Perl program to do its testing, which will be +installed with the +[TAP::Parser::SourceHandler::pgTAP](http://search.cpan.org/dist/TAP-Parser-SourceHandler-pgTAP) +CPAN distribution. You'll need to make sure that you use a database with +PL/pgSQL loaded, or else the tests won't work. `pg_prove` supports a number of environment variables that you might need to use, including all the usual PostgreSQL client environment variables: @@ -213,10 +213,11 @@ Using `pg_prove` ---------------- Or save yourself some effort -- and run a batch of tests scripts or all of -your xUnit test functions at once -- by using `pg_prove`. If you're not -relying on `installcheck`, your test scripts can be a lot less verbose; you -don't need to set all the extra variables, because `pg_prove` takes care of -that for you: +your xUnit test functions at once -- by using `pg_prove`, available in the +[TAP::Parser::SourceHandler::pgTAP](http://search.cpan.org/dist/TAP-Parser-SourceHandler-pgTAP) +CPAN distribution . If you're not relying on `installcheck`, your test scripts +can be a lot less verbose; you don't need to set all the extra variables, +because `pg_prove` takes care of that for you: -- Start transaction and plan the tests. BEGIN; diff --git a/bin/pg_prove b/bin/pg_prove deleted file mode 100755 index 342c944a838f..000000000000 --- a/bin/pg_prove +++ /dev/null @@ -1,459 +0,0 @@ -#!/usr/bin/perl -w - -use strict; -use warnings; -use TAP::Harness; -use Getopt::Long; -our $VERSION = '0.25'; - -Getopt::Long::Configure (qw(bundling)); - -my $opts = { psql => 'psql', color => 1 }; - -Getopt::Long::GetOptions( - 'psql-bin|b=s' => \$opts->{psql}, - 'dbname|d=s' => \$opts->{dbname}, - 'username|U=s' => \$opts->{username}, - 'host|h=s' => \$opts->{host}, - 'port|p=s' => \$opts->{port}, - 'pset|P=s%' => \$opts->{pset}, - 'runtests|r' => \$opts->{runtests}, - 'schema|s=s' => \$opts->{schema}, - 'match|x=s' => \$opts->{match}, - 'timer|t' => \$opts->{timer}, - 'color|c!' => \$opts->{color}, - 'formatter|f=s' => \$opts->{formatter}, - 'archive|a=s' => \$opts->{archive}, - 'verbose|v+' => \$opts->{verbose}, - 'help|H' => \$opts->{help}, - 'man|m' => \$opts->{man}, - 'version|V' => \$opts->{version}, -) or require Pod::Usage && Pod::Usage::pod2usage(2); - -if ( $opts->{help} or $opts->{man} ) { - require Pod::Usage; - Pod::Usage::pod2usage( - '-sections' => $opts->{man} ? '.+' : '(?i:(Usage|Options))', - '-verbose' => 99, - '-exitval' => 0, - ) -} - -if ($opts->{version}) { - print 'pg_prove ', main->VERSION, $/; - exit; -} - -# --schema and --match assume --runtests. -$opts->{runtests} ||= !!($opts->{schema} || $opts->{match}); - -unless ($opts->{runtests} or @ARGV) { - require Pod::Usage; - Pod::Usage::pod2usage( - '-message' => "\nOops! You didn't say what test scripts to run or specify --runtests.\n", - '-sections' => '(?i:(Usage|Options))', - '-verbose' => 99, - '-exitval' => 1, - ) -} - -my @command = ($opts->{psql}); - -for (qw(username host port dbname)) { - push @command, "--$_" => $opts->{$_} if defined $opts->{$_} -} - -# We can't use --tuples-only because then it doesn't allow --pset to toggle it -# properly. :-( -push @command, qw( - --no-psqlrc - --no-align - --quiet - --pset pager= - --pset tuples_only=true - --set ON_ERROR_ROLLBACK=1 - --set ON_ERROR_STOP=1 -); - -if (my $pset = $opts->{pset}) { - while (my ($k, $v) = each %{ $pset }) { - push @command, '--pset', "$k=$v"; - } -} - -# Figure out what tests to run. -my $tests; -if ($opts->{runtests}) { - # We're just going to call `runtests()`. - push @command, '--command'; - - my @args; - for my $key qw(schema match) { - next unless $opts->{$key}; - (my $arg = $opts->{$key}) =~ s/'/\\'/g; - # Gotta cast the arguments. - push @args, "'$arg'::" . ($key eq 'schema' ? 'name' : 'text'); - } - - # This is the command we'll run. - $tests = [[ - "SELECT * FROM runtests(" . join( ', ', @args ) . ');', - 'runtests(' . join(', ', @args) . ')', - ]]; -} else { - # Each item in @ARGV is a file name. - $tests = \@ARGV; - push @command, '--file'; -} - -# Are we using TAP::Harness or TAP::Harness::Archive? -my $harness_class = 'TAP::Harness'; -if ( $opts->{archive} ) { - require TAP::Harness::Archive; - $harness_class = 'TAP::Harness::Archive'; -} - -my $verbosity = $opts->{verbose} || $ENV{TEST_VERBOSE}; - -# Make it so! -my $return = $harness_class->new({ - verbosity => $verbosity, - failures => !$verbosity, - ( TAP::Harness->VERSION ge '3.17' ? (comments => !$verbosity) : ()), - timer => $opts->{timer}, - color => $opts->{color}, - formatter_class => $opts->{formatter}, - ($opts->{archive} ? (archive => $opts->{archive}) : ()), - exec => \@command, -})->runtests( @{ $tests } ); - -# Exit with non-zero status if tests failed. -exit 1 if $return->{failed}; - -__END__ - -=encoding utf8 - -=head1 Name - -pg_prove - A command-line tool for running and harnessing pgTAP tests - -=head1 Usage - - pg_prove -d template1 test*.sql - pg_prove -d testdb -s testschema - -=head1 Description - -C is a command-line application to run one or more pgTAP tests in a -PostgreSQL database. The output of the tests is harvested and processed by -L in order to summarize the results of the test. - -Tests can be written and run in one of two ways, as SQL scripts or as database -functions. - -=head2 Test Scripts - -pgTAP test scripts should consist of a series of SQL statements that output -TAP. Here’s a simple example that assumes that the pgTAP functions have been -installed in the database: - - -- Start transaction and plan the tests. - BEGIN; - SELECT plan(1); - - -- Run the tests. - SELECT pass( 'My test passed, w00t!' ); - - -- Finish the tests and clean up. - SELECT * FROM finish(); - ROLLBACK; - -Now run the tests by passing the list of SQL script names to C. -Here’s what it looks like when the pgTAP tests are run with C - - % pg_prove -U postgres sql/*.sql - sql/coltap.....ok - sql/hastap.....ok - sql/moretap....ok - sql/pg73.......ok - sql/pktap......ok - All tests successful. - Files=5, Tests=216, 1 wallclock secs ( 0.06 usr 0.02 sys + 0.08 cusr 0.07 csys = 0.23 CPU) - Result: PASS - -=head2 xUnit Test Functions - -pgTAP test functions should return a set of text, and then simply return the -values returned by pgTAP functions, like so: - - CREATE OR REPLACE FUNCTION setup_insert( - ) RETURNS SETOF TEXT AS $$ - RETURN NEXT is( MAX(nick), NULL, 'Should have no users') FROM users; - INSERT INTO users (nick) VALUES ('theory'); - $$ LANGUAGE plpgsql; - - CREATE OR REPLACE FUNCTION test_user( - ) RETURNS SETOF TEXT AS $$ - SELECT is( nick, 'theory', 'Should have nick') FROM users; - END; - $$ LANGUAGE sql; - -Once you have these functions defined in your database, you can run them with -C by using the C<--runtests> option. - - % pg_prove -d myapp -r - runtests()....ok - All tests successful. - Files=1, Tests=32, 0 wallclock secs ( 0.02 usr 0.01 sys + 0.01 cusr 0.00 csys = 0.04 CPU) - Result: PASS - -Be sure to pass the C<--schema> option if your test functions are all in one -schema, and the C<--match> option if they have names that don’t start with -“test”. For example, if you have all of your test functions in “test” schema -and I with “test,” run the tests like so: - - pg_prove -d myapp --schema test --match 'test$' - -=head1 Options - - -b --psql-bin PSQL Location of the psql program. - -d --dbname DBNAME Database to which to connect. - -U --username USERNAME Username with which to connect. - -h --host HOST Host to which to connect. - -p --port PORT Port to which to connect. - -P --pset OPTION=VALUE Set psql printing option. - -v --verbose Display output of test scripts while running them. - -r --runtests Run xUnit test using C. - -s --schema Schema in which to find xUnit tests. - -x --match Regular expression to find xUnit tests. - -t --timer Print elapsed time after each test file. - -c --color Display colored test ouput. - --nocolor Do not display colored test ouput. - -f --formatter FORMATTER TAP::Formatter class to format output. - -a --archive FILENAME Store the resulting TAP in an archive file. - -H --help Print a usage statement and exit. - -m --man Print the complete documentation and exit. - -V --version Print the version number and exit. - -=head1 Options Details - -=over - -=item C<-b> - -=item C<--psql-bin> - - pg_prove --psql-bin /usr/local/pgsql/bin/psql - pg_prove -b /usr/local/bin/psql - -Path to the C program, which will be used to actually run the tests. -Defaults to F. - -=item C<-d> - -=item C<--dbname> - - pg_prove --dbname try - pg_prove -d postgres - -The name of database to which to connect. Defaults to the value of the -C<$PGDATABASE> environment variable or to the system username. - -=item C<-U> - -=item C<--username> - - pg_prove --username foo - pg_prove -U postgres - -PostgreSQL user name to connect as. Defaults to the value of the C<$PGUSER> -environment variable or to the operating system name of the user running the -application. - -=item C<-h> - -=item C<--host> - - pg_prove --host pg.example.com - pg_prove -h dev.local - -Specifies the host name of the machine on which the server is running. If the -value begins with a slash, it is used as the directory for the Unix-domain -socket. Defaults to the value of the C<$PGHOST> environment variable or -localhost. - -=item C<-p> - -=item C<--port> - - pg_prove --port 1234 - pg_prove -p 666 - -Specifies the TCP port or the local Unix-domain socket file extension on which -the server is listening for connections. Defaults to the value of the -C<$PGPORT> environment variable or, if not set, to the port specified at -compile time, usually 5432. - -=item C<-P> - -=item C<--pset> - - pg_prove --pset tuples_only=0 - pg_prove -P null=[NULL] - -Specifies printing options in the style of C<\pset> in the C program. -See L for details -on the supported options. - -=item C<-v> - -=item C<--verbose> - - pg_prove --verbose - pg_prove -v - -Display standard output of test scripts while running them. This behavior can -also be triggered by setting the C<$TEST_VERBOSE> environment variable to a -true value. - -=item C<-r> - -=item C<--runtests> - - pg_prove --runtests - pg_prove -r - -Don’t run any test scripts, but just use the C pgTAP function to -run xUnit tests. This ends up looking like a single test script has been run, -when in fact no test scripts have been run. Instead, C tells C -to run something like: - - psql --command 'SELECT * FROM runtests()' - -You should use this option when you’ve written your tests in xUnit style, -where they’re all defined in test functions already loaded in the database. - -=item C<-s> - -=item C<--schema> - - pg_prove --schema test - pg_prove -s mytest - -Used with C<--runtests>, and, in fact, implicitly forces C<--runtests> to be -true. This option can be used to specify the name of a schema in which to find -xUnit functions to run. Basically, it tells C to run something like: - - psql --command "SELECT * FROM runtests('test'::name)" - -=item C<-x> - -=item C<--match> - - pg_prove --match 'test$' - pg_prove -x _test_ - -Used with C<--runtests>, and, in fact, implicitly forces C<--runtests> to be -true. This option can be used to specify a POSIX regular expression that will -be used to search for xUnit functions to run. Basically, it tells C to -run something like: - - psql --command "SELECT * FROM runtests('_test_'::text)" - -This will run any visible functions with the string “_test_” in their names. -This can be especially useful if you just want to run a single test in a -given schema. For example, this: - - pg_prove --schema testing --match '^test_widgets$' - -Will have C execute the C function like so: - - SELECT * FROM runtests('testing'::name, '^test_widgets$'::text); - -=item C<-t> - -=item C<--timer> - - pg_prove --timer - pg_prove -t - -Print elapsed time after each test file. - -=item C<-t> - -=item C<--color> - - pg_prove --color - pg_prove -c - -Display test results in color. Colored test output is the default, but if -output is not to a terminal, color is disabled. - -Requires L on Unix-like platforms and -L on Windows. If the necessary module is not -installed colored output will not be available. - -=item C<--nocolor> - -Do not display test results in color. - -=item C<-f> - -=item C<--formatter> - - pg_prove --formatter TAP::Formatter::File - pg_prove -f TAP::Formatter::Console - -The name of the class to use to format output. The default is -L, or -L if the output isn’t a TTY. - -=item C<-a> - -=item C<--archive> - - pg_prove --archive tap.tar.gz - pg_prove -a test_output.tar - -Send the TAP output to a TAP archive file as wel as to the normal output -destination. The archive formats supported are F<.tar> and F<.tar.gz>. - -=item C<-H> - -=item C<--help> - - pg_prove --help - pg_prove -H - -Outputs a brief description of the options supported by C and exits. - -=item C<-m> - -=item C<--man> - - pg_prove --man - pg_prove -m - -Outputs this documentation and exits. - -=item C<-V> - -=item C<--version> - - pg_prove --version - pg_prove -V - -Outputs the program name and version and exits. - -=back - -=head1 Author - -David E. Wheeler - -=head1 Copyright - -Copyright (c) 2008-2010 Kineticode, Inc. Some Rights Reserved. - -=cut diff --git a/bin/pg_tapgen b/bin/pg_tapgen deleted file mode 100755 index 03d79e02af77..000000000000 --- a/bin/pg_tapgen +++ /dev/null @@ -1,292 +0,0 @@ -#!/usr/bin/perl -w - -use strict; -use warnings; -use DBI; -use DBD::Pg; -use Getopt::Long; -our $VERSION = '0.25'; - -Getopt::Long::Configure (qw(bundling)); - -my $opts = { psql => 'psql', color => 1 }; - -Getopt::Long::GetOptions( - 'dbname|d=s' => \$opts->{dbname}, - 'username|U=s' => \$opts->{username}, - 'host|h=s' => \$opts->{host}, - 'port|p=s' => \$opts->{port}, - 'exclude-schema|N=s@' => \$opts->{exclude_schema}, - 'verbose|v+' => \$opts->{verbose}, - 'help|H' => \$opts->{help}, - 'man|m' => \$opts->{man}, - 'version|V' => \$opts->{version}, -) or require Pod::Usage && Pod::Usage::pod2usage(2); - -if ( $opts->{help} or $opts->{man} ) { - require Pod::Usage; - Pod::Usage::pod2usage( - '-sections' => $opts->{man} ? '.+' : '(?i:(Usage|Options))', - '-verbose' => 99, - '-exitval' => 0, - ) -} - -if ($opts->{version}) { - print 'pg_prove ', main->VERSION, $/; - exit; -} - -my @conn; -for (qw(host port dbname)) { - push @conn, "$_=$opts->{$_}" if defined $opts->{$_}; -} -my $dsn = 'dbi:Pg'; -$dsn .= ':' . join ';', @conn if @conn; - -my $dbh = DBI->connect($dsn, $opts->{username}, undef, { - RaiseError => 1, - PrintError => 0, - AutoCommit => 1, -}); - -print "SELECT * FROM no_plan();\n\n"; -if (my @schemas = get_schemas($opts->{exclude_schema})) { - schemas_are(\@schemas); - for my $schema (@schemas) { - tables_are($schema); - views_are($schema); - sequences_are($schema); - functions_are($schema); - } -} - -print "SELECT * FROM finish();\n"; - -############################################################################## - -sub get_schemas { - my @exclude = ('information_schema'); - push @exclude, @{ $_[0] } if $_[0] && @{ $_[0] }; - - my $sth = $dbh->prepare_cached(q{ - SELECT nspname - FROM pg_catalog.pg_namespace - WHERE nspname NOT LIKE 'pg_%' - AND nspname <> ALL(?) - ORDER BY nspname - }); - - my $schemas = $dbh->selectcol_arrayref($sth, undef, \@exclude) or return; - return @$schemas; -} - -sub schemas_are { - my $schemas = shift; - print "SELECT schemas_are( ARRAY[\n '", - join("',\n '", @$schemas), - "'\n] );\n\n" if @$schemas; -} - -sub get_rels { - my $sth = $dbh->prepare_cached(q{ - SELECT c.relname - FROM pg_catalog.pg_namespace n - JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - WHERE c.relkind = ? - AND n.nspname = ? - ORDER BY c.relname - }); - return $dbh->selectcol_arrayref($sth, undef, @_); -} - -sub tables_are { - my $schema = shift; - my $tables = get_rels(r => $schema); - return unless $tables && @$tables; - print "SELECT tables_are( '$schema', ARRAY[\n '", - join("',\n '", @$tables), - "'\n] );\n\n"; -} - -sub views_are { - my $schema = shift; - my $tables = get_rels(v => $schema); - return unless $tables && @$tables; - print "SELECT views_are( '$schema', ARRAY[\n '", - join("',\n '", @$tables), - "'\n] );\n\n"; -} - -sub sequences_are { - my $schema = shift; - my $tables = get_rels(S => $schema); - return unless $tables && @$tables; - print "SELECT sequences_are( '$schema', ARRAY[\n '", - join("',\n '", @$tables), - "'\n] );\n\n"; -} - -sub functions_are { - my $schema = shift; - my $sth = $dbh->prepare(q{ - SELECT p.proname - FROM pg_catalog.pg_proc p - JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid - WHERE n.nspname = ? - }); - my $funcs = $dbh->selectcol_arrayref($sth, undef, $schema); - return unless $funcs && @$funcs; - print "SELECT functions_are( '$schema', ARRAY[\n '", - join("',\n '", @$funcs), - "'\n] );\n\n"; -} - -__END__ - -=encoding utf8 - -=head1 Name - -pg_tapgen - Generate schema TAP tests from an existing database - -=head1 Usage - - pg_tapgen -d template1 > schema_test.sql - -=head1 Description - -C is a command-line utility to generate pgTAP tests to validate a -database schema by reading an existing database and generating the tests to -match. Its use requires the installation of the L and L from -CPAN or via a package distribution. - -B These prerequisites are not validatd by the pgTAP C, so -you'll need to instal them youself. As a result, inclusion of this script in -the pgTAP distribution is experimental. It may be moved to its own -distribution in the future. - -=head1 Options - - -d --dbname DBNAME Database to which to connect. - -U --username USERNAME Username with which to connect. - -h --host HOST Host to which to connect. - -p --port PORT Port to which to connect. - -v --verbose Display output of test scripts while running them. - -N --exclude-schema Exclude a schema from the generated tests. - -H --help Print a usage statement and exit. - -m --man Print the complete documentation and exit. - -V --version Print the version number and exit. - -=head1 Options Details - -=over - -=item C<-d> - -=item C<--dbname> - - pg_tapgen --dbname try - pg_tapgen -d postgres - -The name of database to which to connect. Defaults to the value of the -C<$PGDATABASE> environment variable or to the system username. - -=item C<-U> - -=item C<--username> - - pg_tapgen --username foo - pg_tapgen -U postgres - -PostgreSQL user name to connect as. Defaults to the value of the C<$PGUSER> -environment variable or to the operating system name of the user running the -application. - -=item C<-h> - -=item C<--host> - - pg_tapgen --host pg.example.com - pg_tapgen -h dev.local - -Specifies the host name of the machine on which the server is running. If the -value begins with a slash, it is used as the directory for the Unix-domain -socket. Defaults to the value of the C<$PGHOST> environment variable or -localhost. - -=item C<-p> - -=item C<--port> - - pg_tapgen --port 1234 - pg_tapgen -p 666 - -Specifies the TCP port or the local Unix-domain socket file extension on which -the server is listening for connections. Defaults to the value of the -C<$PGPORT> environment variable or, if not set, to the port specified at -compile time, usually 5432. - -=item C<-v> - -=item C<--verbose> - - pg_tapgen --verbose - pg_tapgen -v - -Display standard output of test scripts while running them. This behavior can -also be triggered by setting the C<$TEST_VERBOSE> environment variable to a -true value. - -=item C<-N> - -=item C<--exclude-schema> - - pg_tapgen --exclude-schema contrib - pg_tapgen -N testing -N temporary - -Exclude a schema from the test generation. C always ignores -C, as it is also ingored by pgTAP. But if there are other -schemas in the database that you don't need or want to test for in the -database (because you run the tests on another database without those schemas, -for example), use C<--exclude-schema> to omit them. May be used more than once -to exclude more than one schema. - -=item C<-H> - -=item C<--help> - - pg_tapgen --help - pg_tapgen -H - -Outputs a brief description of the options supported by C and exits. - -=item C<-m> - -=item C<--man> - - pg_tapgen --man - pg_tapgen -m - -Outputs this documentation and exits. - -=item C<-V> - -=item C<--version> - - pg_tapgen --version - pg_tapgen -V - -Outputs the program name and version and exits. - -=back - -=head1 Author - -David E. Wheeler - -=head1 Copyright - -Copyright (c) 2009-2010 Kineticode, Inc. Some Rights Reserved. - -=cut diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index ea47b46cfadc..4fa12013430a 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -37,8 +37,6 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %files %defattr(-,root,root,-) -%{_bindir}/pg_prove -%{_bindir}/pg_tapgen %if "%{postgresver}" == "8.3" %{_libdir}/pgsql/pgtap.so %endif From 43542f1976bc90f65520b23a871f8e58792ad9d4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 16:59:48 -0800 Subject: [PATCH 0574/1195] Add META.json and change version to 0.25.0. --- Changes | 4 +++- META.json | 53 ++++++++++++++++++++++++++++++++++++++++++++++ README.pgtap | 4 ++-- contrib/pgtap.spec | 2 +- 4 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 META.json diff --git a/Changes b/Changes index 4b4b9871bb5d..5598375c4eda 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,8 @@ Revision history for pgTAP ========================== -0.25 +0.25.0 +----------------------------- * Minor documentation tweaks. * The sequence created to keep track of test numbers is now a temporary sequence. This will prevent it from persisting beyond the current @@ -43,6 +44,7 @@ Revision history for pgTAP * Removed `pg_prove` and `pg_tapgen` from the distribution. They must now be installed separately from CPAN. If it's not installed, the intaller will issue a warning. +* Added `META.json` and distributed on [PGXN](http://pgxn.org/). 0.24 2010-05-24T23:33:22 ------------------------- diff --git a/META.json b/META.json new file mode 100644 index 000000000000..47fa768739ca --- /dev/null +++ b/META.json @@ -0,0 +1,53 @@ +{ + "name": "pgTAP", + "abstract": "Unit testing for PostgreSQL", + "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", + "version": "0.25.0", + "maintainer": [ + "David E. Wheeler ", + "pgTAP List " + ], + "license": { + "PostgreSQL": "http://www.postgresql.org/about/licence" + }, + "prereqs": { + "runtime": { + "requires": { + "plpgsql": 0, + "PostgreSQL": "8.0.0" + }, + "recommends": { + "PostgreSQL": "8.4.0" + } + } + }, + "provides": { + "pgtap": { + "file": "pgtap.sql", + "version": "0.25.0" + } + }, + "resources": { + "homepage": "http://pgtap.org/", + "bugtracker": { + "web": "https://github.com/theory/pgtap/issues" + }, + "repository": { + "url": "https://github.com/theory/pgtap.git", + "web": "https://github.com/theory/pgtap", + "type": "git" + } + }, + "generated_by": "David E. Wheeler", + "meta-spec": { + "version": "1.0.0", + "url": "http://pgxn.org/meta/spec.txt" + }, + "tags": [ + "testing", + "unit testing", + "tap", + "tddd", + "test driven database development" + ] +} diff --git a/README.pgtap b/README.pgtap index dd79b33454fa..545d284ed98a 100644 --- a/README.pgtap +++ b/README.pgtap @@ -1,5 +1,5 @@ -pgTAP 0.25 -========== +pgTAP 0.25.0 +============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and PL/SQL. It includes a comprehensive collection of TAP-emitting assertion diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 4fa12013430a..4c2384234876 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 0.25 +Version: 0.25.0 Release: 2%{?dist} Group: Applications/Databases License: BSD From b6db3cd6910c3493ec68d5d862ce8690790a0a8e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 17:04:26 -0800 Subject: [PATCH 0575/1195] Move tests to `test` directory. --- Makefile | 6 +++--- {expected => test/expected}/aretap.out | 0 {expected => test/expected}/check.out | 0 {expected => test/expected}/cmpok.out | 0 {expected => test/expected}/coltap.out | 0 {expected => test/expected}/do_tap.out | 0 {expected => test/expected}/enumtap.out | 0 {expected => test/expected}/fktap.out | 0 {expected => test/expected}/functap.out | 0 {expected => test/expected}/hastap.out | 0 {expected => test/expected}/index.out | 0 {expected => test/expected}/istap.out | 0 {expected => test/expected}/matching.out | 0 {expected => test/expected}/moretap.out | 0 {expected => test/expected}/perform.out | 0 {expected => test/expected}/pg73.out | 0 {expected => test/expected}/pktap.out | 0 {expected => test/expected}/resultset.out | 0 {expected => test/expected}/roletap.out | 0 {expected => test/expected}/ruletap.out | 0 {expected => test/expected}/runtests.out | 0 {expected => test/expected}/throwtap.out | 0 {expected => test/expected}/todotap.out | 0 {expected => test/expected}/trigger.out | 0 {expected => test/expected}/unique.out | 0 {expected => test/expected}/usergroup.out | 0 {expected => test/expected}/util.out | 0 {expected => test/expected}/valueset.out | 0 {sql => test/sql}/aretap.sql | 0 {sql => test/sql}/check.sql | 0 {sql => test/sql}/cmpok.sql | 0 {sql => test/sql}/coltap.sql | 0 {sql => test/sql}/do_tap.sql | 0 {sql => test/sql}/enumtap.sql | 0 {sql => test/sql}/fktap.sql | 0 {sql => test/sql}/functap.sql | 0 {sql => test/sql}/hastap.sql | 0 {sql => test/sql}/index.sql | 0 {sql => test/sql}/istap.sql | 0 {sql => test/sql}/matching.sql | 0 {sql => test/sql}/moretap.sql | 0 {sql => test/sql}/perform.sql | 0 {sql => test/sql}/pg73.sql | 0 {sql => test/sql}/pktap.sql | 0 {sql => test/sql}/resultset.sql | 0 {sql => test/sql}/roletap.sql | 0 {sql => test/sql}/ruletap.sql | 0 {sql => test/sql}/runtests.sql | 0 {sql => test/sql}/throwtap.sql | 0 {sql => test/sql}/todotap.sql | 0 {sql => test/sql}/trigger.sql | 0 {sql => test/sql}/unique.sql | 0 {sql => test/sql}/usergroup.sql | 0 {sql => test/sql}/util.sql | 0 {sql => test/sql}/valueset.sql | 0 55 files changed, 3 insertions(+), 3 deletions(-) rename {expected => test/expected}/aretap.out (100%) rename {expected => test/expected}/check.out (100%) rename {expected => test/expected}/cmpok.out (100%) rename {expected => test/expected}/coltap.out (100%) rename {expected => test/expected}/do_tap.out (100%) rename {expected => test/expected}/enumtap.out (100%) rename {expected => test/expected}/fktap.out (100%) rename {expected => test/expected}/functap.out (100%) rename {expected => test/expected}/hastap.out (100%) rename {expected => test/expected}/index.out (100%) rename {expected => test/expected}/istap.out (100%) rename {expected => test/expected}/matching.out (100%) rename {expected => test/expected}/moretap.out (100%) rename {expected => test/expected}/perform.out (100%) rename {expected => test/expected}/pg73.out (100%) rename {expected => test/expected}/pktap.out (100%) rename {expected => test/expected}/resultset.out (100%) rename {expected => test/expected}/roletap.out (100%) rename {expected => test/expected}/ruletap.out (100%) rename {expected => test/expected}/runtests.out (100%) rename {expected => test/expected}/throwtap.out (100%) rename {expected => test/expected}/todotap.out (100%) rename {expected => test/expected}/trigger.out (100%) rename {expected => test/expected}/unique.out (100%) rename {expected => test/expected}/usergroup.out (100%) rename {expected => test/expected}/util.out (100%) rename {expected => test/expected}/valueset.out (100%) rename {sql => test/sql}/aretap.sql (100%) rename {sql => test/sql}/check.sql (100%) rename {sql => test/sql}/cmpok.sql (100%) rename {sql => test/sql}/coltap.sql (100%) rename {sql => test/sql}/do_tap.sql (100%) rename {sql => test/sql}/enumtap.sql (100%) rename {sql => test/sql}/fktap.sql (100%) rename {sql => test/sql}/functap.sql (100%) rename {sql => test/sql}/hastap.sql (100%) rename {sql => test/sql}/index.sql (100%) rename {sql => test/sql}/istap.sql (100%) rename {sql => test/sql}/matching.sql (100%) rename {sql => test/sql}/moretap.sql (100%) rename {sql => test/sql}/perform.sql (100%) rename {sql => test/sql}/pg73.sql (100%) rename {sql => test/sql}/pktap.sql (100%) rename {sql => test/sql}/resultset.sql (100%) rename {sql => test/sql}/roletap.sql (100%) rename {sql => test/sql}/ruletap.sql (100%) rename {sql => test/sql}/runtests.sql (100%) rename {sql => test/sql}/throwtap.sql (100%) rename {sql => test/sql}/todotap.sql (100%) rename {sql => test/sql}/trigger.sql (100%) rename {sql => test/sql}/unique.sql (100%) rename {sql => test/sql}/usergroup.sql (100%) rename {sql => test/sql}/util.sql (100%) rename {sql => test/sql}/valueset.sql (100%) diff --git a/Makefile b/Makefile index 3fe6c803b6af..cbd329039ffa 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ -ESTS = $(wildcard sql/*.sql) +TESTS = $(wildcard test/sql/*.sql) EXTRA_CLEAN = test_setup.sql *.html DATA_built = pgtap.sql uninstall_pgtap.sql DOCS = README.pgtap -REGRESS = $(patsubst sql/%.sql,%,$(TESTS)) -REGRESS_OPTS = --load-language=plpgsql +REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) +REGRESS_OPTS = --inputdir=test --load-language=plpgsql ifdef NO_PGXS top_builddir = ../.. diff --git a/expected/aretap.out b/test/expected/aretap.out similarity index 100% rename from expected/aretap.out rename to test/expected/aretap.out diff --git a/expected/check.out b/test/expected/check.out similarity index 100% rename from expected/check.out rename to test/expected/check.out diff --git a/expected/cmpok.out b/test/expected/cmpok.out similarity index 100% rename from expected/cmpok.out rename to test/expected/cmpok.out diff --git a/expected/coltap.out b/test/expected/coltap.out similarity index 100% rename from expected/coltap.out rename to test/expected/coltap.out diff --git a/expected/do_tap.out b/test/expected/do_tap.out similarity index 100% rename from expected/do_tap.out rename to test/expected/do_tap.out diff --git a/expected/enumtap.out b/test/expected/enumtap.out similarity index 100% rename from expected/enumtap.out rename to test/expected/enumtap.out diff --git a/expected/fktap.out b/test/expected/fktap.out similarity index 100% rename from expected/fktap.out rename to test/expected/fktap.out diff --git a/expected/functap.out b/test/expected/functap.out similarity index 100% rename from expected/functap.out rename to test/expected/functap.out diff --git a/expected/hastap.out b/test/expected/hastap.out similarity index 100% rename from expected/hastap.out rename to test/expected/hastap.out diff --git a/expected/index.out b/test/expected/index.out similarity index 100% rename from expected/index.out rename to test/expected/index.out diff --git a/expected/istap.out b/test/expected/istap.out similarity index 100% rename from expected/istap.out rename to test/expected/istap.out diff --git a/expected/matching.out b/test/expected/matching.out similarity index 100% rename from expected/matching.out rename to test/expected/matching.out diff --git a/expected/moretap.out b/test/expected/moretap.out similarity index 100% rename from expected/moretap.out rename to test/expected/moretap.out diff --git a/expected/perform.out b/test/expected/perform.out similarity index 100% rename from expected/perform.out rename to test/expected/perform.out diff --git a/expected/pg73.out b/test/expected/pg73.out similarity index 100% rename from expected/pg73.out rename to test/expected/pg73.out diff --git a/expected/pktap.out b/test/expected/pktap.out similarity index 100% rename from expected/pktap.out rename to test/expected/pktap.out diff --git a/expected/resultset.out b/test/expected/resultset.out similarity index 100% rename from expected/resultset.out rename to test/expected/resultset.out diff --git a/expected/roletap.out b/test/expected/roletap.out similarity index 100% rename from expected/roletap.out rename to test/expected/roletap.out diff --git a/expected/ruletap.out b/test/expected/ruletap.out similarity index 100% rename from expected/ruletap.out rename to test/expected/ruletap.out diff --git a/expected/runtests.out b/test/expected/runtests.out similarity index 100% rename from expected/runtests.out rename to test/expected/runtests.out diff --git a/expected/throwtap.out b/test/expected/throwtap.out similarity index 100% rename from expected/throwtap.out rename to test/expected/throwtap.out diff --git a/expected/todotap.out b/test/expected/todotap.out similarity index 100% rename from expected/todotap.out rename to test/expected/todotap.out diff --git a/expected/trigger.out b/test/expected/trigger.out similarity index 100% rename from expected/trigger.out rename to test/expected/trigger.out diff --git a/expected/unique.out b/test/expected/unique.out similarity index 100% rename from expected/unique.out rename to test/expected/unique.out diff --git a/expected/usergroup.out b/test/expected/usergroup.out similarity index 100% rename from expected/usergroup.out rename to test/expected/usergroup.out diff --git a/expected/util.out b/test/expected/util.out similarity index 100% rename from expected/util.out rename to test/expected/util.out diff --git a/expected/valueset.out b/test/expected/valueset.out similarity index 100% rename from expected/valueset.out rename to test/expected/valueset.out diff --git a/sql/aretap.sql b/test/sql/aretap.sql similarity index 100% rename from sql/aretap.sql rename to test/sql/aretap.sql diff --git a/sql/check.sql b/test/sql/check.sql similarity index 100% rename from sql/check.sql rename to test/sql/check.sql diff --git a/sql/cmpok.sql b/test/sql/cmpok.sql similarity index 100% rename from sql/cmpok.sql rename to test/sql/cmpok.sql diff --git a/sql/coltap.sql b/test/sql/coltap.sql similarity index 100% rename from sql/coltap.sql rename to test/sql/coltap.sql diff --git a/sql/do_tap.sql b/test/sql/do_tap.sql similarity index 100% rename from sql/do_tap.sql rename to test/sql/do_tap.sql diff --git a/sql/enumtap.sql b/test/sql/enumtap.sql similarity index 100% rename from sql/enumtap.sql rename to test/sql/enumtap.sql diff --git a/sql/fktap.sql b/test/sql/fktap.sql similarity index 100% rename from sql/fktap.sql rename to test/sql/fktap.sql diff --git a/sql/functap.sql b/test/sql/functap.sql similarity index 100% rename from sql/functap.sql rename to test/sql/functap.sql diff --git a/sql/hastap.sql b/test/sql/hastap.sql similarity index 100% rename from sql/hastap.sql rename to test/sql/hastap.sql diff --git a/sql/index.sql b/test/sql/index.sql similarity index 100% rename from sql/index.sql rename to test/sql/index.sql diff --git a/sql/istap.sql b/test/sql/istap.sql similarity index 100% rename from sql/istap.sql rename to test/sql/istap.sql diff --git a/sql/matching.sql b/test/sql/matching.sql similarity index 100% rename from sql/matching.sql rename to test/sql/matching.sql diff --git a/sql/moretap.sql b/test/sql/moretap.sql similarity index 100% rename from sql/moretap.sql rename to test/sql/moretap.sql diff --git a/sql/perform.sql b/test/sql/perform.sql similarity index 100% rename from sql/perform.sql rename to test/sql/perform.sql diff --git a/sql/pg73.sql b/test/sql/pg73.sql similarity index 100% rename from sql/pg73.sql rename to test/sql/pg73.sql diff --git a/sql/pktap.sql b/test/sql/pktap.sql similarity index 100% rename from sql/pktap.sql rename to test/sql/pktap.sql diff --git a/sql/resultset.sql b/test/sql/resultset.sql similarity index 100% rename from sql/resultset.sql rename to test/sql/resultset.sql diff --git a/sql/roletap.sql b/test/sql/roletap.sql similarity index 100% rename from sql/roletap.sql rename to test/sql/roletap.sql diff --git a/sql/ruletap.sql b/test/sql/ruletap.sql similarity index 100% rename from sql/ruletap.sql rename to test/sql/ruletap.sql diff --git a/sql/runtests.sql b/test/sql/runtests.sql similarity index 100% rename from sql/runtests.sql rename to test/sql/runtests.sql diff --git a/sql/throwtap.sql b/test/sql/throwtap.sql similarity index 100% rename from sql/throwtap.sql rename to test/sql/throwtap.sql diff --git a/sql/todotap.sql b/test/sql/todotap.sql similarity index 100% rename from sql/todotap.sql rename to test/sql/todotap.sql diff --git a/sql/trigger.sql b/test/sql/trigger.sql similarity index 100% rename from sql/trigger.sql rename to test/sql/trigger.sql diff --git a/sql/unique.sql b/test/sql/unique.sql similarity index 100% rename from sql/unique.sql rename to test/sql/unique.sql diff --git a/sql/usergroup.sql b/test/sql/usergroup.sql similarity index 100% rename from sql/usergroup.sql rename to test/sql/usergroup.sql diff --git a/sql/util.sql b/test/sql/util.sql similarity index 100% rename from sql/util.sql rename to test/sql/util.sql diff --git a/sql/valueset.sql b/test/sql/valueset.sql similarity index 100% rename from sql/valueset.sql rename to test/sql/valueset.sql From 5e32c7750e90cf63560694b3fbba3365db992b25 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 17:13:35 -0800 Subject: [PATCH 0576/1195] Move SQL files to `sql/`. --- Makefile | 24 +++++++++---------- compat/install-8.0.patch | 4 ++-- compat/install-8.1.patch | 4 ++-- compat/install-8.2.patch | 4 ++-- compat/install-8.3.patch | 4 ++-- compat/uninstall-8.0.patch | 4 ++-- compat/uninstall-8.2.patch | 4 ++-- compat/uninstall-8.3.patch | 4 ++-- pgtap.sql.in => sql/pgtap.sql.in | 0 .../uninstall_pgtap.sql.in | 0 test_setup.sql.in | 2 +- 11 files changed, 27 insertions(+), 27 deletions(-) rename pgtap.sql.in => sql/pgtap.sql.in (100%) rename uninstall_pgtap.sql.in => sql/uninstall_pgtap.sql.in (100%) diff --git a/Makefile b/Makefile index cbd329039ffa..8da276fdda57 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ TESTS = $(wildcard test/sql/*.sql) EXTRA_CLEAN = test_setup.sql *.html -DATA_built = pgtap.sql uninstall_pgtap.sql +DATA_built = sql/pgtap.sql sql/uninstall_pgtap.sql DOCS = README.pgtap REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --inputdir=test --load-language=plpgsql @@ -90,7 +90,7 @@ else cp $< $@ endif -pgtap.sql: pgtap.sql.in test_setup.sql +sql/pgtap.sql: sql/pgtap.sql.in test_setup.sql cp $< $@ ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 5) @@ -103,9 +103,9 @@ ifneq ($(PGVER_MINOR), 2) ifneq ($(PGVER_MINOR), 1) patch -p0 < compat/install-8.0.patch # Hack for E'' syntax (<= PG8.0) - mv pgtap.sql pgtap.tmp - sed -e "s/ E'/ '/g" pgtap.tmp > pgtap.sql - rm pgtap.tmp + mv sql/pgtap.sql sql/pgtap.tmp + sed -e "s/ E'/ '/g" sql/pgtap.tmp > sql/pgtap.sql + rm sql/pgtap.tmp endif endif endif @@ -113,14 +113,14 @@ endif endif endif ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' pgtap.sql > pgtap.tmp + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' sql/pgtap.sql > sql/pgtap.tmp else - sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' pgtap.sql > pgtap.tmp + sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' sql/pgtap.sql > sql/pgtap.tmp endif - mv pgtap.tmp pgtap.sql + mv sql/pgtap.tmp sql/pgtap.sql -uninstall_pgtap.sql: uninstall_pgtap.sql.in test_setup.sql - cp uninstall_pgtap.sql.in uninstall_pgtap.sql +sql/uninstall_pgtap.sql: sql/uninstall_pgtap.sql.in test_setup.sql + cp sql/uninstall_pgtap.sql.in sql/uninstall_pgtap.sql ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 5) ifneq ($(PGVER_MINOR), 4) @@ -135,8 +135,8 @@ endif endif endif ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' uninstall_pgtap.sql > uninstall.tmp - mv uninstall.tmp uninstall_pgtap.sql + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' sql/uninstall_pgtap.sql > sql/uninstall.tmp + mv sql/uninstall.tmp sql/uninstall_pgtap.sql endif # Make sure that we build the regression tests. diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index cc31d6c854d3..7cb69bc88e22 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,5 +1,5 @@ ---- pgtap.sql 2011-02-01 16:00:45.000000000 -0800 -+++ pgtap.sql.orig 2011-02-01 16:00:54.000000000 -0800 +--- sql/pgtap.sql 2011-02-01 16:00:45.000000000 -0800 ++++ sql/pgtap.sql.orig 2011-02-01 16:00:54.000000000 -0800 @@ -68,6 +68,27 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index 773532a2c34a..dc1b230391a6 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,5 +1,5 @@ ---- pgtap.sql 2011-02-01 15:32:02.000000000 -0800 -+++ pgtap.sql.orig 2011-02-01 15:30:37.000000000 -0800 +--- sql/pgtap.sql 2011-02-01 15:32:02.000000000 -0800 ++++ sql/pgtap.sql.orig 2011-02-01 15:30:37.000000000 -0800 @@ -1993,13 +1993,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 9a96e40bfa85..25e879fdc4a3 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,5 +1,5 @@ ---- pgtap.sql 2011-02-01 15:15:23.000000000 -0800 -+++ pgtap.sql.orig 2011-02-01 15:14:35.000000000 -0800 +--- sql/pgtap.sql 2011-02-01 15:15:23.000000000 -0800 ++++ sql/pgtap.sql.orig 2011-02-01 15:14:35.000000000 -0800 @@ -11,6 +11,59 @@ -- ## CREATE SCHEMA TAPSCHEMA; -- ## SET search_path TO TAPSCHEMA, public; diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index 4647a9af6952..24de6b004f4c 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -1,5 +1,5 @@ ---- pgtap.sql.orig 2011-02-01 14:54:33.000000000 -0800 -+++ pgtap.sql 2011-02-01 14:54:33.000000000 -0800 +--- sql/pgtap.sql.orig 2011-02-01 14:54:33.000000000 -0800 ++++ sql/pgtap.sql 2011-02-01 14:54:33.000000000 -0800 @@ -15,6 +15,11 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; diff --git a/compat/uninstall-8.0.patch b/compat/uninstall-8.0.patch index 53e1b2c63d8c..fac58f9581c2 100644 --- a/compat/uninstall-8.0.patch +++ b/compat/uninstall-8.0.patch @@ -1,5 +1,5 @@ ---- uninstall_pgtap.sql 2011-02-01 15:54:35.000000000 -0800 -+++ uninstall_pgtap.sql.orig 2011-02-01 15:55:28.000000000 -0800 +--- sql/uninstall_pgtap.sql 2011-02-01 15:54:35.000000000 -0800 ++++ sql/uninstall_pgtap.sql.orig 2011-02-01 15:55:28.000000000 -0800 @@ -362,11 +362,6 @@ DROP FUNCTION has_user( NAME ); DROP FUNCTION has_user( NAME, TEXT ); diff --git a/compat/uninstall-8.2.patch b/compat/uninstall-8.2.patch index cc5a22f66a1b..77a2ba9c31f7 100644 --- a/compat/uninstall-8.2.patch +++ b/compat/uninstall-8.2.patch @@ -1,5 +1,5 @@ ---- uninstall_pgtap.sql 2011-02-01 15:15:23.000000000 -0800 -+++ uninstall_pgtap.sql.orig 2011-02-01 15:14:54.000000000 -0800 +--- sql/uninstall_pgtap.sql 2011-02-01 15:15:23.000000000 -0800 ++++ sql/uninstall_pgtap.sql.orig 2011-02-01 15:14:54.000000000 -0800 @@ -367,10 +367,6 @@ DROP FUNCTION has_role( NAME ); DROP FUNCTION has_role( NAME, TEXT ); diff --git a/compat/uninstall-8.3.patch b/compat/uninstall-8.3.patch index 1c91b36bc7bc..3970bad65bf8 100644 --- a/compat/uninstall-8.3.patch +++ b/compat/uninstall-8.3.patch @@ -1,5 +1,5 @@ ---- uninstall_pgtap.sql.orig 2011-02-01 14:58:51.000000000 -0800 -+++ uninstall_pgtap.sql 2011-02-01 14:58:51.000000000 -0800 +--- sql/uninstall_pgtap.sql.orig 2011-02-01 14:58:51.000000000 -0800 ++++ sql/uninstall_pgtap.sql 2011-02-01 14:58:51.000000000 -0800 @@ -58,8 +58,7 @@ DROP FUNCTION throws_like ( TEXT, TEXT ); DROP FUNCTION throws_like ( TEXT, TEXT, TEXT ); diff --git a/pgtap.sql.in b/sql/pgtap.sql.in similarity index 100% rename from pgtap.sql.in rename to sql/pgtap.sql.in diff --git a/uninstall_pgtap.sql.in b/sql/uninstall_pgtap.sql.in similarity index 100% rename from uninstall_pgtap.sql.in rename to sql/uninstall_pgtap.sql.in diff --git a/test_setup.sql.in b/test_setup.sql.in index 3218b8dc36b1..f2ea7ae7d53a 100644 --- a/test_setup.sql.in +++ b/test_setup.sql.in @@ -15,6 +15,6 @@ -- Load the TAP functions. BEGIN; -\i pgtap.sql +\i sql/pgtap.sql -- ## SET search_path TO TAPSCHEMA,public; From ce13f8e2bdfa5675c34bb39d4e583989a08a19d3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 17:17:04 -0800 Subject: [PATCH 0577/1195] Move C source file to `src/`. --- Makefile | 2 +- pgtap.c => src/pgtap.c | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename pgtap.c => src/pgtap.c (100%) diff --git a/Makefile b/Makefile index 8da276fdda57..0aafe019e3c2 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ endif # Compile the C code only if we're on 8.3 or older. ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 4) -MODULES = pgtap +MODULES = src/pgtap endif endif diff --git a/pgtap.c b/src/pgtap.c similarity index 100% rename from pgtap.c rename to src/pgtap.c From 23ec733d1dad56932434e2151be23b83b570b961 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 17:29:59 -0800 Subject: [PATCH 0578/1195] Rename `test_setup.sql.in` to `test/setup.sql.in`. --- .gitignore | 2 +- Changes | 2 ++ Makefile | 12 ++++++------ test_setup.sql.in => test/setup.sql.in | 0 test/sql/aretap.sql | 2 +- test/sql/check.sql | 2 +- test/sql/cmpok.sql | 2 +- test/sql/coltap.sql | 2 +- test/sql/do_tap.sql | 2 +- test/sql/enumtap.sql | 2 +- test/sql/fktap.sql | 2 +- test/sql/functap.sql | 2 +- test/sql/hastap.sql | 2 +- test/sql/index.sql | 2 +- test/sql/istap.sql | 2 +- test/sql/matching.sql | 2 +- test/sql/moretap.sql | 2 +- test/sql/perform.sql | 2 +- test/sql/pg73.sql | 2 +- test/sql/pktap.sql | 2 +- test/sql/resultset.sql | 2 +- test/sql/roletap.sql | 2 +- test/sql/ruletap.sql | 2 +- test/sql/runtests.sql | 2 +- test/sql/throwtap.sql | 2 +- test/sql/todotap.sql | 2 +- test/sql/trigger.sql | 2 +- test/sql/unique.sql | 2 +- test/sql/usergroup.sql | 2 +- test/sql/util.sql | 2 +- test/sql/valueset.sql | 2 +- 31 files changed, 36 insertions(+), 34 deletions(-) rename test_setup.sql.in => test/setup.sql.in (100%) diff --git a/.gitignore b/.gitignore index 1b97b5799837..0e478b8cd6ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ pgtap.sql uninstall_pgtap.sql -test_setup.sql +test/setup.sql results pgtap.so regression.* diff --git a/Changes b/Changes index 5598375c4eda..99685707b92d 100644 --- a/Changes +++ b/Changes @@ -45,6 +45,8 @@ Revision history for pgTAP installed separately from CPAN. If it's not installed, the intaller will issue a warning. * Added `META.json` and distributed on [PGXN](http://pgxn.org/). +* Rearranged the source directory layout to more closely match the [preferred + PGXN layout](http://manager.pgxn.org/howto#new-order). 0.24 2010-05-24T23:33:22 ------------------------- diff --git a/Makefile b/Makefile index 0aafe019e3c2..2e5f4da2b2b9 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TESTS = $(wildcard test/sql/*.sql) -EXTRA_CLEAN = test_setup.sql *.html +EXTRA_CLEAN = test/setup.sql *.html DATA_built = sql/pgtap.sql sql/uninstall_pgtap.sql DOCS = README.pgtap REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) @@ -83,14 +83,14 @@ OSNAME := $(shell ./getos.sh) # Override how .sql targets are processed to add the schema info, if # necessary. Otherwise just copy the files. -test_setup.sql: test_setup.sql.in +test/setup.sql: test/setup.sql.in ifdef TAPSCHEMA sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' $< >$@ else cp $< $@ endif -sql/pgtap.sql: sql/pgtap.sql.in test_setup.sql +sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql cp $< $@ ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 5) @@ -119,7 +119,7 @@ else endif mv sql/pgtap.tmp sql/pgtap.sql -sql/uninstall_pgtap.sql: sql/uninstall_pgtap.sql.in test_setup.sql +sql/uninstall_pgtap.sql: sql/uninstall_pgtap.sql.in test/setup.sql cp sql/uninstall_pgtap.sql.in sql/uninstall_pgtap.sql ifeq ($(PGVER_MAJOR), 8) ifneq ($(PGVER_MINOR), 5) @@ -140,10 +140,10 @@ ifdef TAPSCHEMA endif # Make sure that we build the regression tests. -installcheck: test_setup.sql +installcheck: test/setup.sql # In addition to installcheck, one can also run the tests through pg_prove. -test: test_setup.sql +test: test/setup.sql pg_prove --pset tuples_only=1 $(TESTS) html: diff --git a/test_setup.sql.in b/test/setup.sql.in similarity index 100% rename from test_setup.sql.in rename to test/setup.sql.in diff --git a/test/sql/aretap.sql b/test/sql/aretap.sql index 8ab56f4d07c0..ee578faa3bfb 100644 --- a/test/sql/aretap.sql +++ b/test/sql/aretap.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(399); --SELECT * FROM no_plan(); diff --git a/test/sql/check.sql b/test/sql/check.sql index 4b707ed49d68..134d54b6a318 100644 --- a/test/sql/check.sql +++ b/test/sql/check.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(48); diff --git a/test/sql/cmpok.sql b/test/sql/cmpok.sql index 357559fc6bc3..7522e6cb1392 100644 --- a/test/sql/cmpok.sql +++ b/test/sql/cmpok.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(38); diff --git a/test/sql/coltap.sql b/test/sql/coltap.sql index 96253793c5ae..39430a7ab153 100644 --- a/test/sql/coltap.sql +++ b/test/sql/coltap.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(204); --SELECT * from no_plan(); diff --git a/test/sql/do_tap.sql b/test/sql/do_tap.sql index 90d0ffa94946..fc741f8bd719 100644 --- a/test/sql/do_tap.sql +++ b/test/sql/do_tap.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SET client_min_messages = notice; SELECT plan(26); diff --git a/test/sql/enumtap.sql b/test/sql/enumtap.sql index 158098fc536d..8620a77fd2d3 100644 --- a/test/sql/enumtap.sql +++ b/test/sql/enumtap.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(96); --SELECT * FROM no_plan(); diff --git a/test/sql/fktap.sql b/test/sql/fktap.sql index dfa7dff51440..0722a0f8d877 100644 --- a/test/sql/fktap.sql +++ b/test/sql/fktap.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(128); --SELECT * from no_plan(); diff --git a/test/sql/functap.sql b/test/sql/functap.sql index dce66cafe8f5..1f6fc11e7286 100644 --- a/test/sql/functap.sql +++ b/test/sql/functap.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(466); --SELECT * FROM no_plan(); diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index bec30dc35703..a1db46434459 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(678); --SELECT * FROM no_plan(); diff --git a/test/sql/index.sql b/test/sql/index.sql index b91a4df2f226..ab34b3b540c9 100644 --- a/test/sql/index.sql +++ b/test/sql/index.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(225); --SELECT * FROM no_plan(); diff --git a/test/sql/istap.sql b/test/sql/istap.sql index adf1adfcaecb..885a49ac612d 100644 --- a/test/sql/istap.sql +++ b/test/sql/istap.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(47); --SELECT * from no_plan(); diff --git a/test/sql/matching.sql b/test/sql/matching.sql index c2d6039974c2..29bd3fd4084b 100644 --- a/test/sql/matching.sql +++ b/test/sql/matching.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(24); diff --git a/test/sql/moretap.sql b/test/sql/moretap.sql index 4e382491ec84..b6986bef01d8 100644 --- a/test/sql/moretap.sql +++ b/test/sql/moretap.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql \set numb_tests 46 SELECT plan(:numb_tests); diff --git a/test/sql/perform.sql b/test/sql/perform.sql index 914ff04b7a06..5f9c8df5ae60 100644 --- a/test/sql/perform.sql +++ b/test/sql/perform.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(24); --SELECT * FROM no_plan(); diff --git a/test/sql/pg73.sql b/test/sql/pg73.sql index 98abb49a9671..3eddfd591995 100644 --- a/test/sql/pg73.sql +++ b/test/sql/pg73.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql select plan(39); diff --git a/test/sql/pktap.sql b/test/sql/pktap.sql index d5db622ee8d3..f7577c361d5c 100644 --- a/test/sql/pktap.sql +++ b/test/sql/pktap.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(84); --SELECT * FROM no_plan(); diff --git a/test/sql/resultset.sql b/test/sql/resultset.sql index d02f042ce04e..2e30e4d8711c 100644 --- a/test/sql/resultset.sql +++ b/test/sql/resultset.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(515); --SELECT * FROM no_plan(); diff --git a/test/sql/roletap.sql b/test/sql/roletap.sql index 960457d28047..30628ba6d033 100644 --- a/test/sql/roletap.sql +++ b/test/sql/roletap.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(39); --SELECT * FROM no_plan(); diff --git a/test/sql/ruletap.sql b/test/sql/ruletap.sql index c431982c6aeb..df93b125a430 100644 --- a/test/sql/ruletap.sql +++ b/test/sql/ruletap.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(132); --SELECT * FROM no_plan(); diff --git a/test/sql/runtests.sql b/test/sql/runtests.sql index a7343ae17da5..df87e03f9b3b 100644 --- a/test/sql/runtests.sql +++ b/test/sql/runtests.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SET client_min_messages = warning; CREATE SCHEMA whatever; diff --git a/test/sql/throwtap.sql b/test/sql/throwtap.sql index ff7dd3330b6b..cef2eb2358cf 100644 --- a/test/sql/throwtap.sql +++ b/test/sql/throwtap.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(84); --SELECT * FROM no_plan(); diff --git a/test/sql/todotap.sql b/test/sql/todotap.sql index ac1220f8872a..dbd14d43b99c 100644 --- a/test/sql/todotap.sql +++ b/test/sql/todotap.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(36); --SELECT * FROM no_plan(); diff --git a/test/sql/trigger.sql b/test/sql/trigger.sql index 2eb6a4803ef1..906dfeb8f6d3 100644 --- a/test/sql/trigger.sql +++ b/test/sql/trigger.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(84); --SELECT * FROM no_plan(); diff --git a/test/sql/unique.sql b/test/sql/unique.sql index 70e742c3dcc7..80c0cae90a73 100644 --- a/test/sql/unique.sql +++ b/test/sql/unique.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(48); diff --git a/test/sql/usergroup.sql b/test/sql/usergroup.sql index f39100b657e2..577d6a1b2f6f 100644 --- a/test/sql/usergroup.sql +++ b/test/sql/usergroup.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(78); --SELECT * FROM no_plan(); diff --git a/test/sql/util.sql b/test/sql/util.sql index 269a12b571fa..175819682253 100644 --- a/test/sql/util.sql +++ b/test/sql/util.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(30); --SELECT * FROM no_plan(); diff --git a/test/sql/valueset.sql b/test/sql/valueset.sql index cbb1878c0c68..7eb0aaab2f01 100644 --- a/test/sql/valueset.sql +++ b/test/sql/valueset.sql @@ -1,5 +1,5 @@ \unset ECHO -\i test_setup.sql +\i test/setup.sql SELECT plan(349); --SELECT * FROM no_plan(); From 5058549e494128607698ed8ec1cf11381cd73529 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 17:33:06 -0800 Subject: [PATCH 0579/1195] Timestamp for the 0.25.0 release. --- Changes | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 99685707b92d..5db8ae89444f 100644 --- a/Changes +++ b/Changes @@ -1,8 +1,8 @@ Revision history for pgTAP ========================== -0.25.0 ------------------------------ +0.25.0 2011-02-02T01:33:36 +-------------------------- * Minor documentation tweaks. * The sequence created to keep track of test numbers is now a temporary sequence. This will prevent it from persisting beyond the current From 7d128a1bbb236a2175522aad342e021c0e298560 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 17:35:40 -0800 Subject: [PATCH 0580/1195] Add .gitattributes. --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..de2f3167aa9a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +.gitignore export-ignore +.gitattributes export-ignore From 2eb7420a978ba5e0db1e2806724ed7502a661e9f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 19:11:13 -0800 Subject: [PATCH 0581/1195] Rename `README.pgtap` to `doc/pgtap.md`. --- Makefile | 6 +++--- README.pgtap => doc/pgtap.md | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename README.pgtap => doc/pgtap.md (100%) diff --git a/Makefile b/Makefile index 2e5f4da2b2b9..73d1255ec43c 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ TESTS = $(wildcard test/sql/*.sql) EXTRA_CLEAN = test/setup.sql *.html DATA_built = sql/pgtap.sql sql/uninstall_pgtap.sql -DOCS = README.pgtap +DOCS = doc/pgtap.md REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --inputdir=test --load-language=plpgsql @@ -147,6 +147,6 @@ test: test/setup.sql pg_prove --pset tuples_only=1 $(TESTS) html: - markdown -F 0x1000 README.pgtap > readme.html + markdown -F 0x1000 doc/pgtap.md > doc/pgtap.html perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$5; $$lab = $$5; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$5 ? "$$5()" : $$4) . "" } END { print "
  • \n
\n" }' readme.html > toc.html - perl -pi -e 'BEGIN { my %seen }; s{( Date: Tue, 1 Feb 2011 19:20:50 -0800 Subject: [PATCH 0582/1195] Add `README.md` and update Copyright year. Also add a changelog entry to `contrib/pgtap.spec`. --- META.json | 2 +- README.md | 93 ++++++++++++++++++++++++++++++++++++++++++++++ contrib/pgtap.spec | 3 ++ doc/pgtap.md | 9 +++-- 4 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 README.md diff --git a/META.json b/META.json index 47fa768739ca..a19d8a3c8a18 100644 --- a/META.json +++ b/META.json @@ -4,7 +4,7 @@ "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", "version": "0.25.0", "maintainer": [ - "David E. Wheeler ", + "David E. Wheeler ", "pgTAP List " ], "license": { diff --git a/README.md b/README.md new file mode 100644 index 000000000000..fa239db44433 --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +pgTAP 0.25.0 +============ + +[pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written +in PL/pgSQL and PL/SQL. It includes a comprehensive collection of +[TAP](http://testanything.org)-emitting assertion functions, as well as the +ability to integrate with other TAP-emitting test frameworks. It can also be +used in the xUnit testing style. For detailed documentation, see the +documentation in `doc/pgtap.md` or +[online](http://pgtap.org/documentation.html "Complete pgTAP Documentation"). + +To build it, just do this: + + make + make installcheck + make install + +If you encounter an error such as: + + "Makefile", line 8: Need an operator + +You need to use GNU make, which may well be installed on your system as +`gmake`: + + gmake + gmake install + gmake installcheck + +If you encounter an error such as: + + make: pg_config: Command not found + +Or: + + Makefile:52: *** pgTAP requires PostgreSQL 8.0 or later. This is . Stop. + +Be sure that you have `pg_config` installed and in your path. If you used a +package management system such as RPM to install PostgreSQL, be sure that the +`-devel` package is also installed. If necessary tell the build process where +to find it: + + env PG_CONFIG=/path/to/pg_config make && make install && make installcheck + +And finally, if all that fails (and if you're on PostgreSQL 8.1 or lower, it +likely will), copy the entire distribution directory to the `contrib/` +subdirectory of the PostgreSQL source tree and try it there without +`pg_config`: + + env NO_PGXS=1 make && make install && make installcheck + +If you encounter an error such as: + + ERROR: must be owner of database regression + +You need to run the test suite using a super user, such as the default +"postgres" super user: + + make installcheck PGUSER=postgres + +If you want to schema-qualify pgTAP (that is, install all of its functions +into their own schema), set the `$TAPSCHEMA` variable to the name of the +schema you'd like to be created, for example: + + make TAPSCHEMA=tap + make install + make installcheck + +Dependencies +------------ + +pgTAP requires PostgreSQL 8.0 or higher, with 8.4 or higher recommended for +full use of its API. It also requires PL/pgSQL. + +Copyright and License +--------------------- + +Copyright (c) 2008-2011 Kineticode, Inc. Some rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose, without fee, and without a written agreement is +hereby granted, provided that the above copyright notice and this paragraph +and the following two paragraphs appear in all copies. + +IN NO EVENT SHALL KINETICODE BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING +OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF KINETICODE HAS +BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +KINETICODE SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND +KINETICODE HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, +ENHANCEMENTS, OR MODIFICATIONS. diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 4c2384234876..cc554ea17acd 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -44,6 +44,9 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog +* Tue Feb 01 2011 David Wheeler 0.25.0 +- Removed pg_prove and pg_tapgen, which are now distributed via CPAN. + * Sun Mar 01 2010 Darrell Fuhriman 0.24-2 - Make install work where the pgtap.so library is needed. diff --git a/doc/pgtap.md b/doc/pgtap.md index 545d284ed98a..738e71dfcfc7 100644 --- a/doc/pgtap.md +++ b/doc/pgtap.md @@ -2,9 +2,10 @@ pgTAP 0.25.0 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and -PL/SQL. It includes a comprehensive collection of TAP-emitting assertion -functions, as well as the ability to integrate with other TAP-emitting test -frameworks. It can also be used in the xUnit testing style. +PL/SQL. It includes a comprehensive collection of +[TAP](http://testanything.org)-emitting assertion functions, as well as the +ability to integrate with other TAP-emitting test frameworks. It can also be +used in the xUnit testing style. Synopsis ======== @@ -4446,7 +4447,7 @@ Credits Copyright and License --------------------- -Copyright (c) 2008-2010 Kineticode, Inc. Some rights reserved. +Copyright (c) 2008-2011 Kineticode, Inc. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is From 0d04cec8a601eb5ec48c1a504809c3c5feeb64c0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 19:21:13 -0800 Subject: [PATCH 0583/1195] Update timestamp for 0.25.0. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 5db8ae89444f..89dc706fbaad 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.25.0 2011-02-02T01:33:36 +0.25.0 2011-02-02T03:21:55 -------------------------- * Minor documentation tweaks. * The sequence created to keep track of test numbers is now a temporary From d5a7e0e6d25bf7a3d2fe7ec00300692178ae33fc Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 19:34:11 -0800 Subject: [PATCH 0584/1195] Increment version to 0.26.0. --- Changes | 2 ++ META.json | 4 ++-- Makefile | 2 +- README.md | 2 +- contrib/pgtap.spec | 2 +- doc/pgtap.md | 2 +- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index 89dc706fbaad..14df16c97c51 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,8 @@ Revision history for pgTAP ========================== +0.26.0 + 0.25.0 2011-02-02T03:21:55 -------------------------- * Minor documentation tweaks. diff --git a/META.json b/META.json index a19d8a3c8a18..718075ebdb4e 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "0.25.0", + "version": "0.26.0", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -24,7 +24,7 @@ "provides": { "pgtap": { "file": "pgtap.sql", - "version": "0.25.0" + "version": "0.26.0" } }, "resources": { diff --git a/Makefile b/Makefile index 73d1255ec43c..2a93d9183fd3 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') PGVER_MAJOR = $(shell echo $(VERSION) | awk -F. '{ print ($$1 + 0) }') PGVER_MINOR = $(shell echo $(VERSION) | awk -F. '{ print ($$2 + 0) }') PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') -PGTAP_VERSION = 0.25 +PGTAP_VERSION = 0.26 # We support 8.0 and later. ifneq ($(PGVER_MAJOR), 8) diff --git a/README.md b/README.md index fa239db44433..32acc5e7ebb7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 0.25.0 +pgTAP 0.26.0 ============ [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index cc554ea17acd..e2d486403925 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 0.25.0 +Version: 0.26.0 Release: 2%{?dist} Group: Applications/Databases License: BSD diff --git a/doc/pgtap.md b/doc/pgtap.md index 738e71dfcfc7..3a6c81140cb3 100644 --- a/doc/pgtap.md +++ b/doc/pgtap.md @@ -1,4 +1,4 @@ -pgTAP 0.25.0 +pgTAP 0.26.0 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and From 462f4dac19f45619bff9e34d6e477796eb43d145 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 19:36:21 -0800 Subject: [PATCH 0585/1195] Fix HTML generation. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2a93d9183fd3..50eeed23f752 100644 --- a/Makefile +++ b/Makefile @@ -148,5 +148,5 @@ test: test/setup.sql html: markdown -F 0x1000 doc/pgtap.md > doc/pgtap.html - perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$5; $$lab = $$5; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$5 ? "$$5()" : $$4) . "" } END { print "
  • \n
\n" }' readme.html > toc.html + perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$5; $$lab = $$5; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$5 ? "$$5()" : $$4) . "" } END { print "
  • \n
\n" }' doc/pgtap.html > doc/toc.html perl -pi -e 'BEGIN { my %seen }; s{( Date: Tue, 1 Feb 2011 19:38:20 -0800 Subject: [PATCH 0586/1195] Removed "Supported Versions" section of the docs. Suffice it to say that pgTAP supports PostgreSQL 8.0 or higher as covered in the README. --- Changes | 3 +++ doc/pgtap.md | 15 --------------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/Changes b/Changes index 14df16c97c51..817347eefe01 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,9 @@ Revision history for pgTAP ========================== 0.26.0 +-------------------------- +* Removed the "Supported Versions" section of the documentation. Suffice it to + say that pgTAP supports PostgreSQL 8.0 or higher. 0.25.0 2011-02-02T03:21:55 -------------------------- diff --git a/doc/pgtap.md b/doc/pgtap.md index 3a6c81140cb3..0743c3991323 100644 --- a/doc/pgtap.md +++ b/doc/pgtap.md @@ -4401,21 +4401,6 @@ To Do full signatures, so that a polymorphic functions can be independently tested for language, volatility, etc. -Supported Versions ------------------ - -pgTAP has been tested on the following builds of PostgreSQL: - -* PostgreSQL 9.0beta1 on x86_64-apple-darwin10.3.0 -* PostgreSQL 8.4.4 on i386-apple-darwin10.3.0 -* PostgreSQL 8.3.11 on i386-apple-darwin10.3.0 -* PostgreSQL 8.2.17 on i386-apple-darwin10.3.0 -* PostgreSQL 8.1.21 on i686-apple-darwin10.3.0 -* PostgreSQL 8.0.25 on i686-apple-darwin10.3.0 - -If you know of others, please submit them! Use -`psql -d template1 -c 'SELECT VERSION()'` to get the formal build version and OS. - Metadata ======== From 6cbf33ad5f08d845514edb8627c6fc293b1c808d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 20:02:45 -0800 Subject: [PATCH 0587/1195] Use an older version of Discount to build the docs. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 50eeed23f752..c54d59ff8a28 100644 --- a/Makefile +++ b/Makefile @@ -147,6 +147,6 @@ test: test/setup.sql pg_prove --pset tuples_only=1 $(TESTS) html: - markdown -F 0x1000 doc/pgtap.md > doc/pgtap.html + /usr/local/discount-1.6.7/bin/markdown -F 0x1000 doc/pgtap.md > doc/pgtap.html perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$5; $$lab = $$5; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$5 ? "$$5()" : $$4) . "" } END { print "
  • \n
\n" }' doc/pgtap.html > doc/toc.html perl -pi -e 'BEGIN { my %seen }; s{( Date: Tue, 1 Feb 2011 20:13:23 -0800 Subject: [PATCH 0588/1195] Restore line to generate pg_prove docs But generate them from the CPAN-installed version. --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c54d59ff8a28..73800c581c95 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TESTS = $(wildcard test/sql/*.sql) -EXTRA_CLEAN = test/setup.sql *.html +EXTRA_CLEAN = test/setup.sql doc/*.html DATA_built = sql/pgtap.sql sql/uninstall_pgtap.sql DOCS = doc/pgtap.md REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) @@ -150,3 +150,4 @@ html: /usr/local/discount-1.6.7/bin/markdown -F 0x1000 doc/pgtap.md > doc/pgtap.html perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$5; $$lab = $$5; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$5 ? "$$5()" : $$4) . "" } END { print "
  • \n
\n" }' doc/pgtap.html > doc/toc.html perl -pi -e 'BEGIN { my %seen }; s{(' pg_prove From 3d68c5a2ff4dc4896e007013a27730ab8fdd19a5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 20:33:45 -0800 Subject: [PATCH 0589/1195] Strip verbatim whitespace from the pg_prove html. --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 73800c581c95..c87ccecad501 100644 --- a/Makefile +++ b/Makefile @@ -150,4 +150,5 @@ html: /usr/local/discount-1.6.7/bin/markdown -F 0x1000 doc/pgtap.md > doc/pgtap.html perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$5; $$lab = $$5; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$5 ? "$$5()" : $$4) . "" } END { print "
  • \n
\n" }' doc/pgtap.html > doc/toc.html perl -pi -e 'BEGIN { my %seen }; s{(' pg_prove + perl -MPod::Simple::XHTML -E "my \$$p = Pod::Simple::XHTML->new; \$$p->html_header_tags(''); \$$p->strip_verbatim_indent(sub { (my \$$i = \$$_[0]->[0]) =~ s/\\S.*//; \$$i }); \$$p->parse_from_file('`perldoc -l pg_prove`')" > doc/pg_prove.html + From 46bf5da97a95def5a51039b4db19e862258a81fe Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Feb 2011 20:39:24 -0800 Subject: [PATCH 0590/1195] Make To-Do a top-level section of the docs. --- doc/pgtap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pgtap.md b/doc/pgtap.md index 0743c3991323..d02249c6fa79 100644 --- a/doc/pgtap.md +++ b/doc/pgtap.md @@ -4364,7 +4364,7 @@ No changes. Everything should just work. * `row_eq()` To Do ------ +===== * Add `isnt_empty()` to complement `is_empty()`. * Add variants of `set_eq()`, `bag_eq()`, and `results_eq()` that take an array of records as the second argument. From e49f4839c1ada15a234d6792d78bb886323b91de Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 2 Feb 2011 17:42:13 -0800 Subject: [PATCH 0591/1195] Add target to build portable core pgTAP. It will just be built, and can then be taken and embedded in other distributions for testing on PostgreSQL 8.3+. Still trying to create its complement with the schema-testing stuff in it. Once I get that figured out, I'll document it. --- .gitignore | 2 + Changes | 3 + Makefile | 12 +- compat/pgtap-core.patch | 4645 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 4661 insertions(+), 1 deletion(-) create mode 100644 compat/pgtap-core.patch diff --git a/.gitignore b/.gitignore index 0e478b8cd6ac..81d90377b71c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ pgtap.sql +pgtap-core.sql +pgtap-schema.sql uninstall_pgtap.sql test/setup.sql results diff --git a/Changes b/Changes index 817347eefe01..ccc3ff8c1bf7 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,9 @@ Revision history for pgTAP -------------------------- * Removed the "Supported Versions" section of the documentation. Suffice it to say that pgTAP supports PostgreSQL 8.0 or higher. +* Added a build target to create a portable version of pgTAP that can be + included in any distribution and should just work for tesing on PostgreSQL + 8.3+. 0.25.0 2011-02-02T03:21:55 -------------------------- diff --git a/Makefile b/Makefile index c87ccecad501..dedcf1b4cbb8 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ TESTS = $(wildcard test/sql/*.sql) EXTRA_CLEAN = test/setup.sql doc/*.html -DATA_built = sql/pgtap.sql sql/uninstall_pgtap.sql +DATA_built = sql/pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql sql/uninstall_pgtap.sql DOCS = doc/pgtap.md REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --inputdir=test --load-language=plpgsql @@ -139,6 +139,16 @@ ifdef TAPSCHEMA mv sql/uninstall.tmp sql/uninstall_pgtap.sql endif +sql/pgtap-core.sql: sql/pgtap.sql + cp $< $@ + sed -e 's,sql/pgtap,sql/pgtap-core,g' compat/install-8.3.patch | patch -p0 + patch -p0 < compat/pgtap-core.patch + +sql/pgtap-schema.sql: sql/pgtap.sql + cp $< $@ + sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-8.3.patch | patch -p0 +# diff -u sql/pgtap-core.sql sql/pgtap-schema.sql | patch -R -p0 + # Make sure that we build the regression tests. installcheck: test/setup.sql diff --git a/compat/pgtap-core.patch b/compat/pgtap-core.patch new file mode 100644 index 000000000000..1bb244e62581 --- /dev/null +++ b/compat/pgtap-core.patch @@ -0,0 +1,4645 @@ +--- sql/pgtap-core.sql 2011-02-02 15:31:35.000000000 -0800 ++++ sql/pgtap-core.sql.orig 2011-02-02 15:25:38.000000000 -0800 +@@ -1,25 +1,23 @@ +--- This file defines pgTAP, a collection of functions for TAP-based unit +--- testing. It is distributed under the revised FreeBSD license. You can +--- find the original here: +--- +--- http://github.com/theory/pgtap/raw/master/pgtap.sql.in +--- +--- The home page for the pgTAP project is: ++\set ECHO 0 ++-- This file defines pgTAP Lite, a portable collection of functions for ++-- TAP-based unit testing on PostgreSQL 8.3 or higher. It is distributed under ++-- the revised FreeBSD license. The home page for the pgTAP project is: + -- + -- http://pgtap.org/ ++-- + +--- ## CREATE SCHEMA TAPSCHEMA; +--- ## SET search_path TO TAPSCHEMA, public; ++\pset format unaligned ++\pset tuples_only true ++\pset pager ++ ++-- Revert all changes on failure. ++\set ON_ERROR_ROLLBACK 1 ++\set ON_ERROR_STOP true + + CREATE OR REPLACE FUNCTION pg_version() + RETURNS text AS 'SELECT current_setting(''server_version'')' + LANGUAGE SQL IMMUTABLE; + +-CREATE OR REPLACE FUNCTION pg_typeof("any") +-RETURNS regtype +-AS '$libdir/pgtap' +-LANGUAGE C STABLE; +- + CREATE OR REPLACE FUNCTION pg_version_num() + RETURNS integer AS $$ + SELECT s.a[1]::int * 10000 +@@ -30,10 +28,6 @@ + ) AS s; + $$ LANGUAGE SQL IMMUTABLE; + +-CREATE OR REPLACE FUNCTION os_name() +-RETURNS TEXT AS 'SELECT ''darwin''::text;' +-LANGUAGE SQL IMMUTABLE; +- + CREATE OR REPLACE FUNCTION pgtap_version() + RETURNS NUMERIC AS 'SELECT 0.26;' + LANGUAGE SQL IMMUTABLE; +@@ -518,6 +512,35 @@ + SELECT cmp_ok( $1, $2, $3, 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 ); +@@ -812,2422 +835,254 @@ + 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; ++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; + +-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 = $1 +- AND n.nspname = $2 +- AND c.relname = $3 +- ); +-$$ LANGUAGE SQL; ++CREATE OR REPLACE VIEW tap_funky ++ AS SELECT p.oid AS oid, ++ n.nspname AS schema, ++ p.proname AS name, ++ 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, ++ p.proisagg AS is_agg, ++ 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 _rexists ( CHAR, NAME ) ++CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME, NAME[] ) + RETURNS BOOLEAN AS $$ + SELECT EXISTS( +- SELECT true +- FROM pg_catalog.pg_class c +- WHERE c.relkind = $1 +- AND pg_catalog.pg_table_is_visible(c.oid) +- AND c.relname = $2 ++ SELECT TRUE ++ FROM tap_funky ++ WHERE schema = $1 ++ AND name = $2 ++ AND args = array_to_string($3, ',') + ); + $$ LANGUAGE SQL; + +--- has_table( schema, table, description ) +-CREATE OR REPLACE FUNCTION has_table ( NAME, NAME, TEXT ) +-RETURNS TEXT AS $$ +- SELECT ok( _rexists( 'r', $1, $2 ), $3 ); +-$$ LANGUAGE SQL; +- +--- has_table( table, description ) +-CREATE OR REPLACE FUNCTION has_table ( NAME, TEXT ) +-RETURNS TEXT AS $$ +- SELECT ok( _rexists( 'r', $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', $1, $2 ), $3 ); ++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; + +--- hasnt_table( table, description ) +-CREATE OR REPLACE FUNCTION hasnt_table ( NAME, TEXT ) +-RETURNS TEXT AS $$ +- SELECT ok( NOT _rexists( 'r', $1 ), $2 ); ++CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME[] ) ++RETURNS BOOLEAN AS $$ ++ SELECT EXISTS( ++ SELECT TRUE ++ FROM tap_funky ++ WHERE name = $1 ++ AND args = array_to_string($2, ',') ++ AND is_visible ++ ); + $$ 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' ); ++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_view( schema, view, description ) +-CREATE OR REPLACE FUNCTION has_view ( NAME, NAME, TEXT ) ++-- has_function( schema, function, args[], description ) ++CREATE OR REPLACE FUNCTION has_function ( NAME, NAME, NAME[], TEXT ) + RETURNS TEXT AS $$ +- SELECT ok( _rexists( 'v', $1, $2 ), $3 ); ++ SELECT ok( _got_func($1, $2, $3), $4 ); + $$ LANGUAGE SQL; + +--- has_view( view, description ) +-CREATE OR REPLACE FUNCTION has_view ( NAME, TEXT ) ++-- has_function( schema, function, args[] ) ++CREATE OR REPLACE FUNCTION has_function( NAME, NAME, NAME[] ) + RETURNS TEXT AS $$ +- SELECT ok( _rexists( 'v', $1 ), $2 ); +-$$ LANGUAGE SQL; ++ SELECT ok( ++ _got_func($1, $2, $3), ++ 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || ++ array_to_string($3, ', ') || ') should exist' ++ ); ++$$ LANGUAGE sql; + +--- has_view( view ) +-CREATE OR REPLACE FUNCTION has_view ( NAME ) ++-- has_function( schema, function, description ) ++CREATE OR REPLACE FUNCTION has_function ( NAME, NAME, TEXT ) + RETURNS TEXT AS $$ +- SELECT has_view( $1, 'View ' || quote_ident($1) || ' should exist' ); ++ SELECT ok( _got_func($1, $2), $3 ); + $$ LANGUAGE SQL; + +--- hasnt_view( schema, view, description ) +-CREATE OR REPLACE FUNCTION hasnt_view ( NAME, NAME, TEXT ) ++-- has_function( schema, function ) ++CREATE OR REPLACE FUNCTION has_function( NAME, NAME ) + RETURNS TEXT AS $$ +- SELECT ok( NOT _rexists( 'v', $1, $2 ), $3 ); +-$$ LANGUAGE SQL; ++ SELECT ok( ++ _got_func($1, $2), ++ 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should exist' ++ ); ++$$ LANGUAGE sql; + +--- hasnt_view( view, description ) +-CREATE OR REPLACE FUNCTION hasnt_view ( NAME, TEXT ) ++-- has_function( function, args[], description ) ++CREATE OR REPLACE FUNCTION has_function ( NAME, NAME[], TEXT ) + RETURNS TEXT AS $$ +- SELECT ok( NOT _rexists( 'v', $1 ), $2 ); ++ SELECT ok( _got_func($1, $2), $3 ); + $$ LANGUAGE SQL; + +--- hasnt_view( view ) +-CREATE OR REPLACE FUNCTION hasnt_view ( NAME ) ++-- has_function( function, args[] ) ++CREATE OR REPLACE FUNCTION has_function( NAME, NAME[] ) + RETURNS TEXT AS $$ +- SELECT hasnt_view( $1, 'View ' || quote_ident($1) || ' should not exist' ); +-$$ LANGUAGE SQL; ++ SELECT ok( ++ _got_func($1, $2), ++ 'Function ' || quote_ident($1) || '(' || ++ array_to_string($2, ', ') || ') should exist' ++ ); ++$$ LANGUAGE sql; + +--- has_sequence( schema, sequence, description ) +-CREATE OR REPLACE FUNCTION has_sequence ( NAME, NAME, TEXT ) ++-- has_function( function, description ) ++CREATE OR REPLACE FUNCTION has_function( NAME, TEXT ) + RETURNS TEXT AS $$ +- SELECT ok( _rexists( 'S', $1, $2 ), $3 ); +-$$ LANGUAGE SQL; ++ SELECT ok( _got_func($1), $2 ); ++$$ LANGUAGE sql; + +--- has_sequence( sequence, description ) +-CREATE OR REPLACE FUNCTION has_sequence ( NAME, TEXT ) ++-- has_function( function ) ++CREATE OR REPLACE FUNCTION has_function( NAME ) + RETURNS TEXT AS $$ +- SELECT ok( _rexists( 'S', $1 ), $2 ); +-$$ LANGUAGE SQL; ++ SELECT ok( _got_func($1), 'Function ' || quote_ident($1) || '() should exist' ); ++$$ LANGUAGE sql; + +--- has_sequence( sequence ) +-CREATE OR REPLACE FUNCTION has_sequence ( NAME ) ++-- hasnt_function( schema, function, args[], description ) ++CREATE OR REPLACE FUNCTION hasnt_function ( NAME, NAME, NAME[], TEXT ) + RETURNS TEXT AS $$ +- SELECT has_sequence( $1, 'Sequence ' || quote_ident($1) || ' should exist' ); ++ SELECT ok( NOT _got_func($1, $2, $3), $4 ); + $$ LANGUAGE SQL; + +--- hasnt_sequence( schema, sequence, description ) +-CREATE OR REPLACE FUNCTION hasnt_sequence ( NAME, NAME, TEXT ) ++-- hasnt_function( schema, function, args[] ) ++CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME, NAME[] ) + RETURNS TEXT AS $$ +- SELECT ok( NOT _rexists( 'S', $1, $2 ), $3 ); +-$$ LANGUAGE SQL; ++ 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_sequence( sequence, description ) +-CREATE OR REPLACE FUNCTION hasnt_sequence ( NAME, TEXT ) ++-- hasnt_function( schema, function, description ) ++CREATE OR REPLACE FUNCTION hasnt_function ( NAME, NAME, TEXT ) + RETURNS TEXT AS $$ +- SELECT ok( NOT _rexists( 'S', $1 ), $2 ); ++ SELECT ok( NOT _got_func($1, $2), $3 ); + $$ LANGUAGE SQL; + +--- hasnt_sequence( sequence ) +-CREATE OR REPLACE FUNCTION hasnt_sequence ( NAME ) ++-- hasnt_function( schema, function ) ++CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME ) + RETURNS TEXT AS $$ +- SELECT hasnt_sequence( $1, 'Sequence ' || 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 ++ SELECT ok( ++ NOT _got_func($1, $2), ++ 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not exist' + ); +-$$ 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; ++$$ LANGUAGE sql; + +--- has_column( table, column, description ) +-CREATE OR REPLACE FUNCTION has_column ( NAME, NAME, TEXT ) ++-- hasnt_function( function, args[], description ) ++CREATE OR REPLACE FUNCTION hasnt_function ( NAME, NAME[], TEXT ) + RETURNS TEXT AS $$ +- SELECT ok( _cexists( $1, $2 ), $3 ); ++ SELECT ok( NOT _got_func($1, $2), $3 ); + $$ LANGUAGE SQL; + +--- has_column( table, column ) +-CREATE OR REPLACE FUNCTION has_column ( NAME, NAME ) ++-- hasnt_function( function, args[] ) ++CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME[] ) + RETURNS TEXT AS $$ +- SELECT has_column( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); +-$$ LANGUAGE SQL; ++ SELECT ok( ++ NOT _got_func($1, $2), ++ 'Function ' || quote_ident($1) || '(' || ++ array_to_string($2, ', ') || ') should not exist' ++ ); ++$$ LANGUAGE sql; + +--- hasnt_column( schema, table, column, description ) +-CREATE OR REPLACE FUNCTION hasnt_column ( NAME, NAME, NAME, TEXT ) ++-- hasnt_function( function, description ) ++CREATE OR REPLACE FUNCTION hasnt_function( NAME, TEXT ) + RETURNS TEXT AS $$ +- SELECT ok( NOT _cexists( $1, $2, $3 ), $4 ); +-$$ LANGUAGE SQL; ++ SELECT ok( NOT _got_func($1), $2 ); ++$$ LANGUAGE sql; + +--- hasnt_column( table, column, description ) +-CREATE OR REPLACE FUNCTION hasnt_column ( NAME, NAME, TEXT ) ++-- hasnt_function( function ) ++CREATE OR REPLACE FUNCTION hasnt_function( NAME ) + RETURNS TEXT AS $$ +- SELECT ok( NOT _cexists( $1, $2 ), $3 ); +-$$ LANGUAGE SQL; ++ SELECT ok( NOT _got_func($1), 'Function ' || quote_ident($1) || '() should not exist' ); ++$$ 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; ++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; + +--- _col_is_null( schema, table, column, desc, null ) +-CREATE OR REPLACE FUNCTION _col_is_null ( NAME, NAME, NAME, TEXT, bool ) ++-- can( schema, functions[], description ) ++CREATE OR REPLACE FUNCTION can ( NAME, NAME[], TEXT ) + RETURNS TEXT AS $$ ++DECLARE ++ missing text[]; + 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( +- 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 +- ), $4 +- ); +-END; +-$$ LANGUAGE plpgsql; +- +--- _col_is_null( table, column, desc, null ) +-CREATE OR REPLACE FUNCTION _col_is_null ( NAME, NAME, TEXT, bool ) +-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' ); ++ 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( +- 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 +- ), $3 ++ RETURN ok( false, $3 ) || E'\n' || diag( ++ ' ' || quote_ident($1) || '.' || ++ array_to_string( missing, E'() missing\n ' || quote_ident($1) || '.') || ++ '() missing' + ); + END; + $$ LANGUAGE plpgsql; + +--- col_not_null( schema, table, column, description ) +-CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, NAME, TEXT ) +-RETURNS TEXT AS $$ +- SELECT _col_is_null( $1, $2, $3, $4, true ); +-$$ LANGUAGE SQL; +- +--- col_not_null( table, column, description ) +-CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, TEXT ) +-RETURNS TEXT AS $$ +- SELECT _col_is_null( $1, $2, $3, true ); +-$$ LANGUAGE SQL; +- +--- col_not_null( table, column ) +-CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME ) ++-- can( schema, functions[] ) ++CREATE OR REPLACE FUNCTION can ( NAME, NAME[] ) + RETURNS TEXT AS $$ +- SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should be NOT NULL', true ); +-$$ LANGUAGE SQL; ++ SELECT can( $1, $2, 'Schema ' || quote_ident($1) || ' can' ); ++$$ LANGUAGE sql; + +--- col_is_null( schema, table, column, description ) +-CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME, TEXT ) +-RETURNS TEXT AS $$ +- SELECT _col_is_null( $1, $2, $3, $4, false ); +-$$ LANGUAGE SQL; +- +--- col_is_null( schema, table, column ) +-CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME ) +-RETURNS TEXT AS $$ +- SELECT _col_is_null( $1, $2, $3, false ); +-$$ LANGUAGE SQL; +- +--- col_is_null( table, column ) +-CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME ) +-RETURNS TEXT AS $$ +- SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should allow NULL', false ); +-$$ LANGUAGE SQL; +- +-CREATE OR REPLACE FUNCTION display_type ( OID, INTEGER ) +-RETURNS TEXT AS $$ +- SELECT COALESCE(substring( +- pg_catalog.format_type($1, $2), +- '(("(?!")([^"]|"")+"|[^.]+)([(][^)]+[)])?)$' +- ), '') +-$$ LANGUAGE SQL; +- +-CREATE OR REPLACE FUNCTION display_type ( NAME, OID, INTEGER ) +-RETURNS TEXT AS $$ +- SELECT CASE WHEN $1 IS NULL THEN '' ELSE quote_ident($1) || '.' END +- || display_type($2, $3) +-$$ LANGUAGE SQL; +- +-CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME, NAME ) +-RETURNS TEXT AS $$ +- SELECT display_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 display_type(a.atttypid, a.atttypmod) +- FROM pg_catalog.pg_attribute a +- JOIN pg_catalog.pg_class c ON a.attrelid = c.oid +- WHERE pg_table_is_visible(c.oid) +- AND c.relname = $1 +- AND a.attname = $2 +- AND attnum > 0 +- AND NOT a.attisdropped +- AND pg_type_is_visible(a.atttypid) +-$$ LANGUAGE SQL; +- +-CREATE OR REPLACE FUNCTION _get_col_ns_type ( NAME, NAME, NAME ) +-RETURNS TEXT AS $$ +- SELECT display_type(tn.nspname, 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 _quote_ident_like(TEXT, TEXT) +-RETURNS TEXT AS $$ +-DECLARE +- have TEXT; +- pcision TEXT; +-BEGIN +- -- Just return it if rhs isn't quoted. +- IF $2 !~ '"' THEN RETURN $1; END IF; +- +- pcision := substring($1 FROM '[(][^")]+[)]$'); +- +- -- Just quote it if thre is no precision. +- if pcision IS NULL THEN RETURN quote_ident($1); END IF; +- +- -- Quote the non-precision part and concatenate with precision. +- RETURN quote_ident(substring($1 FROM char_length($1) - char_length(pcision))) +- || pcision; +-END; +-$$ LANGUAGE plpgsql; +- +--- 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; +- +- want_type := quote_ident($4) || '.' || _quote_ident_like($5, have_type); +- 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 := _quote_ident_like($4, have_type); +- 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 +-$$ 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 +- IF $1 ~ '^[^'']+[(]' THEN +- -- It's a functional default. +- 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), +- display_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), +- display_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 c.relhaspkey = true +- AND 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 c.relhaspkey = true +- AND 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( 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: http://pgfoundry.org/projects/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: http://pgfoundry.org/projects/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: http://pgfoundry.org/projects/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) +- 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 +-$$ 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) +- 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 +-$$ 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( 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( 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 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 || ' constriants'; +- 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 || ' constriants'; +- 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( 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 +- 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; +- +-CREATE OR REPLACE VIEW tap_funky +- AS SELECT p.oid AS oid, +- n.nspname AS schema, +- p.proname AS name, +- 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, +- p.proisagg AS is_agg, +- 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 _got_func ( NAME, NAME, NAME[] ) +-RETURNS BOOLEAN AS $$ +- SELECT EXISTS( +- SELECT TRUE +- FROM tap_funky +- WHERE schema = $1 +- AND name = $2 +- AND args = array_to_string($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 = array_to_string($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 NAME[] AS $$ +- SELECT ARRAY( +- SELECT a.attname +- 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_attribute a ON ct.oid = a.attrelid +- JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) +- ON a.attnum = x.indkey[s.i] +- 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 NAME[] AS $$ +- SELECT ARRAY( +- SELECT a.attname +- 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_attribute a ON ct.oid = a.attrelid +- JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) +- ON a.attnum = x.indkey[s.i] +- 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 ct.relname = $2 +- AND ci.relname = $3 +- AND n.nspname = $1 +- ); +-$$ 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 +- ); +-$$ LANGUAGE sql; +- +-CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME, NAME) +-RETURNS TEXT AS $$ +- SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) +- 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 +-$$ LANGUAGE sql; +- +-CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME) +-RETURNS TEXT AS $$ +- SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) +- 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) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')', +- quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_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/expression, description ) +-CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME, text ) +-RETURNS TEXT AS $$ +-DECLARE +- expr text; +-BEGIN +- IF $4 NOT LIKE '%(%' THEN +- -- Not a functional index. +- RETURN has_index( $1, $2, $3, ARRAY[$4], $5 ); +- END IF; +- +- -- Get the functional expression. +- expr := _iexpr($1, $2, $3); +- +- IF expr IS NULL 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) || '(' || expr || ')', +- quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || $4 || ')', +- $5 +- ); +-END; +-$$ LANGUAGE plpgsql; +- +--- has_index( schema, table, index, columns/expression ) +-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) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')', +- quote_ident($2) || ' ON ' || quote_ident($1) || '(' || _ident_array_to_string( $3, ', ' ) || ')', +- $4 +- ); +-END; +-$$ LANGUAGE plpgsql; +- +--- has_index( table, index, columns[], description ) +-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 $$ +-DECLARE +- want_expr text; +- descr text; +- have_expr text; +- idx name; +- tab text; +-BEGIN +- IF $3 NOT LIKE '%(%' THEN +- -- Not a functional index. +- IF _is_schema( $1 ) THEN +- -- Looking for schema.table index. +- RETURN ok ( _have_index( $1, $2, $3 ), $4); +- END IF; +- -- Looking for particular columns. +- RETURN has_index( $1, $2, ARRAY[$3], $4 ); +- END IF; +- +- -- Get the functional expression. +- IF _is_schema( $1 ) THEN +- -- Looking for an index within a schema. +- have_expr := _iexpr($1, $2, $3); +- want_expr := $4; +- descr := 'Index ' || quote_ident($3) || ' should exist'; +- idx := $3; +- tab := quote_ident($1) || '.' || quote_ident($2); +- ELSE +- -- Looking for an index without a schema spec. +- have_expr := _iexpr($1, $2); +- want_expr := $3; +- descr := $4; +- idx := $2; +- tab := quote_ident($1); +- END IF; +- +- IF have_expr IS NULL THEN +- RETURN ok( false, descr ) || E'\n' +- || diag( 'Index ' || idx || ' ON ' || tab || ' not found'); +- END IF; +- +- RETURN is( +- quote_ident(idx) || ' ON ' || tab || '(' || have_expr || ')', +- quote_ident(idx) || ' ON ' || tab || '(' || want_expr || ')', +- descr +- ); +-END; +-$$ LANGUAGE plpgsql; +- +--- 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; +- +--- 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 +- ); +-$$ 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 ) ++-- can( functions[], description ) ++CREATE OR REPLACE FUNCTION can ( NAME[], TEXT ) + RETURNS TEXT AS $$ + DECLARE +- pname text; ++ missing 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) || '()' ++ 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' + ); +-$$ 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 $$ +- SELECT ok( +- EXISTS( +- SELECT true +- FROM pg_catalog.pg_tablespace +- WHERE spcname = $1 +- AND spclocation = $2 +- ), $3 +- ); +-$$ LANGUAGE sql; +- +--- 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 ) ++-- can( functions[] ) ++CREATE OR REPLACE FUNCTION can ( NAME[] ) + RETURNS TEXT AS $$ +- SELECT hasnt_tablespace( $1, 'Tablespace ' || quote_ident($1) || ' should not exist' ); ++ SELECT can( $1, 'Schema ' || _ident_array_to_string(current_schemas(true), ' or ') || ' can' ); + $$ LANGUAGE sql; + + CREATE OR REPLACE FUNCTION _has_type( NAME, NAME, CHAR[] ) +@@ -3433,225 +1288,41 @@ + 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( +- 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( group, user[], 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_user ON usename = $2[i] +- WHERE usesysid IS NULL +- OR NOT usesysid = 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( +- ' Users missing from the ' || quote_ident($1) || E' group:\n ' || +- array_to_string( missing, E'\n ') ++ 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 + ); +-END; +-$$ LANGUAGE plpgsql; ++$$ LANGUAGE sql; + +--- is_member_of( group, user, description ) +-CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME, TEXT ) ++-- enum_has_labels( enum, labels ) ++CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME[] ) + RETURNS TEXT AS $$ +- SELECT is_member_of( $1, ARRAY[$2], $3 ); +-$$ LANGUAGE SQL; ++ SELECT enum_has_labels( ++ $1, $2, ++ 'Enum ' || quote_ident($1) || ' should have labels (' || array_to_string( $2, ', ' ) || ')' ++ ); ++$$ LANGUAGE sql; + +--- is_member_of( group, user[] ) +-CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[] ) ++CREATE OR REPLACE FUNCTION display_type ( OID, INTEGER ) + RETURNS TEXT AS $$ +- SELECT is_member_of( $1, $2, 'Should have members of group ' || quote_ident($1) ); ++ SELECT COALESCE(substring( ++ pg_catalog.format_type($1, $2), ++ '(("(?!")([^"]|"")+"|[^.]+)([(][^)]+[)])?)$' ++ ), '') + $$ LANGUAGE SQL; + +--- is_member_of( group, user ) +-CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME ) ++CREATE OR REPLACE FUNCTION display_type ( NAME, OID, INTEGER ) + RETURNS TEXT AS $$ +- SELECT is_member_of( $1, ARRAY[$2] ); ++ SELECT CASE WHEN $1 IS NULL THEN '' ELSE quote_ident($1) || '.' END ++ || display_type($2, $3) + $$ LANGUAGE SQL; + + CREATE OR REPLACE FUNCTION _cmp_types(oid, name) +@@ -3887,613 +1558,147 @@ + ); + $$ 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; +- +--- 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; +- +--- 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; +- +-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_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_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 = $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 = $1 +- AND c.relname NOT IN ('__tcache__', '__tresults__', '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 _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 = $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 = $1 +- ); +-$$ LANGUAGE SQL; +- +--- tables_are( schema, tables, description ) +-CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[], TEXT ) +-RETURNS TEXT AS $$ +- SELECT _are( 'tables', _extras('r', $1, $2), _missing('r', $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', $1), _missing('r', $1), $2); +-$$ LANGUAGE SQL; +- +--- tables_are( schema, tables ) +-CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[] ) +-RETURNS TEXT AS $$ +- SELECT _are( +- 'tables', _extras('r', $1, $2), _missing('r', $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', $1), _missing('r', $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[] ) ++-- 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 _are( +- 'views', _extras('v', $1), _missing('v', $1), +- 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views' +- ); ++ SELECT ok( _op_exists($1, $2, $3, $4, $5 ), $6 ); + $$ LANGUAGE SQL; + +--- sequences_are( schema, sequences, description ) +-CREATE OR REPLACE FUNCTION sequences_are ( NAME, NAME[], TEXT ) ++-- 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 _are( 'sequences', _extras('S', $1, $2), _missing('S', $1, $2), $3); ++ SELECT ok( ++ _op_exists($1, $2, $3, $4, $5 ), ++ 'Operator ' || quote_ident($2) || '.' || $3 || '(' || $1 || ',' || $4 ++ || ') RETURNS ' || $5 || ' should exist' ++ ); + $$ LANGUAGE SQL; + +--- sequences_are( sequences, description ) +-CREATE OR REPLACE FUNCTION sequences_are ( NAME[], TEXT ) ++-- 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 _are( 'sequences', _extras('S', $1), _missing('S', $1), $2); ++ SELECT ok( _op_exists($1, $2, $3, $4 ), $5 ); + $$ LANGUAGE SQL; + +--- sequences_are( schema, sequences ) +-CREATE OR REPLACE FUNCTION sequences_are ( NAME, NAME[] ) ++-- has_operator( left_type, name, right_type, return_type ) ++CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, 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' ++ SELECT ok( ++ _op_exists($1, $2, $3, $4 ), ++ 'Operator ' || $2 || '(' || $1 || ',' || $3 ++ || ') RETURNS ' || $4 || ' should exist' + ); + $$ LANGUAGE SQL; + +--- sequences_are( sequences ) +-CREATE OR REPLACE FUNCTION sequences_are ( NAME[] ) ++-- has_operator( left_type, name, right_type, description ) ++CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, TEXT ) + 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' +- ); ++ SELECT ok( _op_exists($1, $2, $3 ), $4 ); + $$ LANGUAGE SQL; + +--- functions_are( schema, functions[], description ) +-CREATE OR REPLACE FUNCTION functions_are ( NAME, NAME[], TEXT ) ++-- has_operator( left_type, name, right_type ) ++CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME ) + 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 ++ SELECT ok( ++ _op_exists($1, $2, $3 ), ++ 'Operator ' || $2 || '(' || $1 || ',' || $3 ++ || ') should exist' + ); + $$ LANGUAGE SQL; + +--- functions_are( schema, functions[] ) +-CREATE OR REPLACE FUNCTION functions_are ( NAME, NAME[] ) ++-- has_leftop( schema, name, right_type, return_type, description ) ++CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME, NAME, TEXT ) + RETURNS TEXT AS $$ +- SELECT functions_are( $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct functions' ); ++ SELECT ok( _op_exists(NULL, $1, $2, $3, $4), $5 ); + $$ LANGUAGE SQL; + +--- functions_are( functions[], description ) +-CREATE OR REPLACE FUNCTION functions_are ( NAME[], TEXT ) ++-- has_leftop( schema, name, right_type, return_type ) ++CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME, NAME ) + 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 ++ SELECT ok( ++ _op_exists(NULL, $1, $2, $3, $4 ), ++ 'Left operator ' || quote_ident($1) || '.' || $2 || '(NONE,' ++ || $3 || ') RETURNS ' || $4 || ' should exist' + ); + $$ LANGUAGE SQL; + +--- functions_are( functions[] ) +-CREATE OR REPLACE FUNCTION functions_are ( NAME[] ) ++-- has_leftop( name, right_type, return_type, description ) ++CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME, TEXT ) + RETURNS TEXT AS $$ +- SELECT functions_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct functions' ); ++ SELECT ok( _op_exists(NULL, $1, $2, $3), $4 ); + $$ LANGUAGE SQL; + +--- indexes_are( schema, table, indexes[], description ) +-CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME, NAME[], TEXT ) ++-- has_leftop( name, right_type, return_type ) ++CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME ) + 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 ++ SELECT ok( ++ _op_exists(NULL, $1, $2, $3 ), ++ 'Left operator ' || $1 || '(NONE,' || $2 || ') RETURNS ' || $3 || ' should exist' + ); + $$ LANGUAGE SQL; + +--- indexes_are( schema, table, indexes[] ) +-CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME, NAME[] ) ++-- has_leftop( name, right_type, description ) ++CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, TEXT ) + RETURNS TEXT AS $$ +- SELECT indexes_are( $1, $2, $3, 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct indexes' ); ++ SELECT ok( _op_exists(NULL, $1, $2), $3 ); + $$ LANGUAGE SQL; + +--- indexes_are( table, indexes[], description ) +-CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME[], TEXT ) ++-- has_leftop( name, right_type ) ++CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME ) + 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 ++ SELECT ok( ++ _op_exists(NULL, $1, $2 ), ++ 'Left operator ' || $1 || '(NONE,' || $2 || ') should exist' + ); + $$ LANGUAGE SQL; + +--- indexes_are( table, indexes[] ) +-CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME[] ) ++-- has_rightop( left_type, schema, name, return_type, description ) ++CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME, NAME, TEXT ) + RETURNS TEXT AS $$ +- SELECT indexes_are( $1, $2, 'Table ' || quote_ident($1) || ' should have the correct indexes' ); ++ SELECT ok( _op_exists( $1, $2, $3, NULL, $4), $5 ); + $$ LANGUAGE SQL; + +--- users_are( users[], description ) +-CREATE OR REPLACE FUNCTION users_are( NAME[], TEXT ) ++-- has_rightop( left_type, schema, name, return_type ) ++CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME, NAME ) + 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 ++ SELECT ok( ++ _op_exists($1, $2, $3, NULL, $4 ), ++ 'Right operator ' || quote_ident($2) || '.' || $3 || '(' ++ || $1 || ',NONE) RETURNS ' || $4 || ' should exist' + ); + $$ LANGUAGE SQL; + +--- users_are( users[] ) +-CREATE OR REPLACE FUNCTION users_are( NAME[] ) ++-- has_rightop( left_type, name, return_type, description ) ++CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME, TEXT ) + RETURNS TEXT AS $$ +- SELECT users_are( $1, 'There should be the correct users' ); ++ SELECT ok( _op_exists( $1, $2, NULL, $3), $4 ); + $$ LANGUAGE SQL; + +--- groups_are( groups[], description ) +-CREATE OR REPLACE FUNCTION groups_are( NAME[], TEXT ) ++-- has_rightop( left_type, name, return_type ) ++CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME ) + 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 ++ SELECT ok( ++ _op_exists($1, $2, NULL, $3 ), ++ 'Right operator ' || $2 || '(' ++ || $1 || ',NONE) RETURNS ' || $3 || ' should exist' + ); + $$ LANGUAGE SQL; + +--- groups_are( groups[] ) +-CREATE OR REPLACE FUNCTION groups_are( NAME[] ) ++-- has_rightop( left_type, name, description ) ++CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, TEXT ) + RETURNS TEXT AS $$ +- SELECT groups_are( $1, 'There should be the correct groups' ); ++ SELECT ok( _op_exists( $1, $2, NULL), $3 ); + $$ LANGUAGE SQL; + +--- languages_are( languages[], description ) +-CREATE OR REPLACE FUNCTION languages_are( NAME[], TEXT ) ++-- has_rightop( left_type, name ) ++CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME ) + 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 ++ SELECT ok( ++ _op_exists($1, $2, NULL ), ++ 'Right operator ' || $2 || '(' || $1 || ',NONE) should exist' + ); + $$ 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; +@@ -4568,384 +1773,37 @@ + -- has_opclass( name, description ) + CREATE OR REPLACE FUNCTION has_opclass( NAME, TEXT ) + RETURNS TEXT AS $$ +- SELECT ok( _opc_exists( NULL, $1 ), $2) +-$$ LANGUAGE SQL; +- +--- has_opclass( name ) +-CREATE OR REPLACE FUNCTION has_opclass( NAME ) +-RETURNS TEXT AS $$ +- SELECT ok( _opc_exists( NULL, $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 exist' ); +-$$ LANGUAGE SQL; +- +--- hasnt_opclass( name, description ) +-CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, TEXT ) +-RETURNS TEXT AS $$ +- SELECT ok( NOT _opc_exists( NULL, $1 ), $2) +-$$ LANGUAGE SQL; +- +--- hasnt_opclass( name ) +-CREATE OR REPLACE FUNCTION hasnt_opclass( NAME ) +-RETURNS TEXT AS $$ +- SELECT ok( NOT _opc_exists( NULL, $1 ), 'Operator class ' || quote_ident($1) || ' should 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 ++ SELECT ok( _opc_exists( NULL, $1 ), $2) + $$ 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 ++-- has_opclass( name ) ++CREATE OR REPLACE FUNCTION has_opclass( NAME ) ++RETURNS TEXT AS $$ ++ SELECT ok( _opc_exists( NULL, $1 ), 'Operator class ' || quote_ident($1) || ' should exist' ); + $$ LANGUAGE SQL; + +--- rule_is_on( schema, table, rule, event, description ) +-CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, NAME, TEXT, TEXT ) ++-- hasnt_opclass( schema, name, description ) ++CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, NAME, 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; ++ SELECT ok( NOT _opc_exists( $1, $2 ), $3 ); ++$$ LANGUAGE SQL; + +--- rule_is_on( schema, table, rule, event ) +-CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, NAME, TEXT ) ++-- hasnt_opclass( schema, name ) ++CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, NAME ) + 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) +- ); ++ SELECT ok( NOT _opc_exists( $1, $2 ), 'Operator class ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); + $$ LANGUAGE SQL; + +--- rule_is_on( table, rule, event, description ) +-CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, TEXT, TEXT ) ++-- hasnt_opclass( name, description ) ++CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, 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; ++ SELECT ok( NOT _opc_exists( NULL, $1 ), $2) ++$$ LANGUAGE SQL; + +--- rule_is_on( table, rule, event ) +-CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, TEXT ) ++-- hasnt_opclass( name ) ++CREATE OR REPLACE FUNCTION hasnt_opclass( NAME ) + 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) +- ); ++ SELECT ok( NOT _opc_exists( NULL, $1 ), 'Operator class ' || quote_ident($1) || ' should exist' ); + $$ LANGUAGE SQL; + + CREATE OR REPLACE FUNCTION _nosuch( NAME, NAME, NAME[]) +@@ -5551,124 +2409,6 @@ + ); + $$ 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. +- EXECUTE 'SELECT aok, descr FROM __tresults__ WHERE numb = ' || tnumb +- INTO aok, adescr; +- +- -- Now delete those results. +- EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; +- EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb; +- +- -- 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 3 + char_length( diag( adescr ) ) ); +- END IF; +- +- -- Remove failure message from ok(). +- IF NOT aok THEN +- adiag := substring( +- adiag +- FROM 14 + char_length(tnumb::text) +- + CASE adescr WHEN '' THEN 3 ELSE 3 + char_length( diag( adescr ) ) END +- ); +- 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 ) + RETURNS TEXT[] AS $$ + SELECT ARRAY( +@@ -6667,187 +3407,6 @@ + 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 $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.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 $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 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 $$ +@@ -7071,293 +3630,3 @@ + 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 +- 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 +- ), +- $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') +- 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') +- ), +- $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_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_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 display_type(castsource, NULL) || ' AS ' || display_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 display_type(castsource, NULL) || ' AS ' || display_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; +- From 60ac7d7219c6bfeb732ca9db0575f94e9898c1f1 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 2 Feb 2011 22:43:47 -0800 Subject: [PATCH 0592/1195] Generate the compat files. Instead of a patch, just use a simple Perl script with a list of core functions. It can also be inverted to generate a script with the schema functions. --- Makefile | 12 +- compat/gencore | 433 ++++ compat/pgtap-core.patch | 4645 --------------------------------------- 3 files changed, 441 insertions(+), 4649 deletions(-) create mode 100644 compat/gencore delete mode 100644 compat/pgtap-core.patch diff --git a/Makefile b/Makefile index dedcf1b4cbb8..7e6fbba7f965 100644 --- a/Makefile +++ b/Makefile @@ -139,15 +139,19 @@ ifdef TAPSCHEMA mv sql/uninstall.tmp sql/uninstall_pgtap.sql endif -sql/pgtap-core.sql: sql/pgtap.sql +sql/pgtap-core.sql: sql/pgtap.sql.in cp $< $@ sed -e 's,sql/pgtap,sql/pgtap-core,g' compat/install-8.3.patch | patch -p0 - patch -p0 < compat/pgtap-core.patch + sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' sql/pgtap-core.sql > sql/pgtap-core.tmp + $(PERL) compat/gencore 0 sql/pgtap-core.tmp > sql/pgtap-core.sql + rm sql/pgtap-core.tmp -sql/pgtap-schema.sql: sql/pgtap.sql +sql/pgtap-schema.sql: sql/pgtap.sql.in cp $< $@ sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-8.3.patch | patch -p0 -# diff -u sql/pgtap-core.sql sql/pgtap-schema.sql | patch -R -p0 + sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' sql/pgtap-schema.sql > sql/pgtap-schema.tmp + $(PERL) compat/gencore 1 sql/pgtap-schema.tmp > sql/pgtap-schema.sql + rm sql/pgtap-schema.tmp # Make sure that we build the regression tests. installcheck: test/setup.sql diff --git a/compat/gencore b/compat/gencore new file mode 100644 index 000000000000..3456caba02bc --- /dev/null +++ b/compat/gencore @@ -0,0 +1,433 @@ +#!/usr/bin/env perl -w + +use strict; +use warnings; + +my $invert = shift; +my %keep = map { chomp; $_ => 1 } ; + + +print $invert ? '\\i pgtap-core.sql' : '\\set ECHO 0'; + +my ($name, $type) = $invert ? ('Schema', 'schema-testing') : ('Core', 'assertion'); +print qq{ +-- This file defines pgTAP $name, a portable collection of $type +-- functions for TAP-based unit testing on PostgreSQL 8.3 or higher. It is +-- distributed under the revised FreeBSD license. The home page for the pgTAP +-- project is: + +-- +-- http://pgtap.org/ +-- + +}; + +print $invert ? qq{-- Requires pgtap-core.sql +-- +} : qq{\\pset format unaligned +\\pset tuples_only true +\\pset pager + +-- Revert all changes on failure. +\\set ON_ERROR_ROLLBACK 1 +\\set ON_ERROR_STOP true + +}; + +my $print = 0; +while (<>) { + if (/^CREATE OR REPLACE \w+ (\w+)/) { + if ($1 eq 'os_name' || $1 eq 'pg_typeof') { + # Never keep this one. + $print = 0; + } elsif ($invert ? !$keep{$1} : $keep{$1}) { + $print = 1; + print; + } else { + $print = 0; + } + } else { + print if $print; + } +} + +__DATA__ +pg_version +pg_version_num +pgtap_version +plan +no_plan +_get +_get_latest +_get_latest +_get_note +_get_note +_set +_set +_set +_add +_add +add_result +num_failed +_finish +finish +diag +diag +diag +diag +ok +ok +is +is +isnt +isnt +_alike +matches +matches +imatches +imatches +alike +alike +ialike +ialike +_unalike +doesnt_match +doesnt_match +doesnt_imatch +doesnt_imatch +unalike +unalike +unialike +unialike +cmp_ok +cmp_ok +cmp_ok +cmp_ok +pass +pass +fail +fail +todo +todo +todo +todo +todo_start +todo_start +in_todo +todo_end +_todo +skip +skip +skip +skip +_query +throws_ok +throws_ok +throws_ok +throws_ok +throws_ok +throws_ok +throws_ok +lives_ok +lives_ok +performs_ok +_ident_array_to_string +tap_funky +_got_func +_got_func +_got_func +_got_func +has_function +has_function +has_function +has_function +has_function +has_function +has_function +has_function +hasnt_function +hasnt_function +hasnt_function +hasnt_function +hasnt_function +hasnt_function +hasnt_function +hasnt_function +_pg_sv_type_array +can +can +can +can +_has_type +_has_type +has_type +has_type +has_type +has_type +hasnt_type +hasnt_type +hasnt_type +hasnt_type +has_domain +has_domain +has_domain +has_domain +hasnt_domain +hasnt_domain +hasnt_domain +hasnt_domain +has_enum +has_enum +has_enum +has_enum +hasnt_enum +hasnt_enum +hasnt_enum +hasnt_enum +enum_has_labels +enum_has_labels +enum_has_labels +enum_has_labels +display_type +display_type +_cmp_types +_cast_exists +_cast_exists +_cast_exists +has_cast +has_cast +has_cast +has_cast +has_cast +has_cast +hasnt_cast +hasnt_cast +hasnt_cast +hasnt_cast +hasnt_cast +hasnt_cast +_expand_context +_get_context +cast_context_is +cast_context_is +_op_exists +_op_exists +_op_exists +has_operator +has_operator +has_operator +has_operator +has_operator +has_operator +has_leftop +has_leftop +has_leftop +has_leftop +has_leftop +has_leftop +has_rightop +has_rightop +has_rightop +has_rightop +has_rightop +has_rightop +_is_trusted +has_language +has_language +hasnt_language +hasnt_language +language_is_trusted +language_is_trusted +_opc_exists +has_opclass +has_opclass +has_opclass +has_opclass +hasnt_opclass +hasnt_opclass +hasnt_opclass +hasnt_opclass +_nosuch +_func_compare +_func_compare +_func_compare +_func_compare +_lang +_lang +_lang +_lang +function_lang_is +function_lang_is +function_lang_is +function_lang_is +function_lang_is +function_lang_is +function_lang_is +function_lang_is +_returns +_returns +_returns +_returns +function_returns +function_returns +function_returns +function_returns +function_returns +function_returns +function_returns +function_returns +_definer +_definer +_definer +_definer +is_definer +is_definer +is_definer +is_definer +is_definer +is_definer +is_definer +is_definer +_agg +_agg +_agg +_agg +is_aggregate +is_aggregate +is_aggregate +is_aggregate +is_aggregate +is_aggregate +is_aggregate +is_aggregate +_strict +_strict +_strict +_strict +is_strict +is_strict +is_strict +is_strict +is_strict +is_strict +is_strict +is_strict +_expand_vol +_refine_vol +_vol +_vol +_vol +_vol +volatility_is +volatility_is +volatility_is +volatility_is +volatility_is +volatility_is +volatility_is +volatility_is +findfuncs +findfuncs +_runem +_is_verbose +do_tap +do_tap +do_tap +do_tap +_currtest +_cleanup +_runner +runtests +runtests +runtests +runtests +_temptable +_temptable +_temptypes +_docomp +_relcomp +_relcomp +set_eq +set_eq +set_eq +set_eq +bag_eq +bag_eq +bag_eq +bag_eq +_do_ne +_relne +_relne +set_ne +set_ne +set_ne +set_ne +bag_ne +bag_ne +bag_ne +bag_ne +_relcomp +set_has +set_has +bag_has +bag_has +set_hasnt +set_hasnt +bag_hasnt +bag_hasnt +results_eq +results_eq +results_eq +results_eq +results_eq +results_eq +results_eq +results_eq +results_eq +results_eq +results_eq +results_eq +results_ne +results_ne +results_ne +results_ne +results_ne +results_ne +results_ne +results_ne +results_ne +results_ne +results_ne +results_ne +isa_ok +isa_ok +is_empty +is_empty +collect_tap +_tlike +throws_like +throws_like +throws_ilike +throws_ilike +throws_matching +throws_matching +throws_imatching +throws_imatching +_dexists +_dexists +_get_dtype +_get_dtype +domain_type_is +domain_type_is +domain_type_is +domain_type_is +domain_type_is +domain_type_is +domain_type_isnt +domain_type_isnt +domain_type_isnt +domain_type_isnt +domain_type_isnt +domain_type_isnt +row_eq +row_eq diff --git a/compat/pgtap-core.patch b/compat/pgtap-core.patch deleted file mode 100644 index 1bb244e62581..000000000000 --- a/compat/pgtap-core.patch +++ /dev/null @@ -1,4645 +0,0 @@ ---- sql/pgtap-core.sql 2011-02-02 15:31:35.000000000 -0800 -+++ sql/pgtap-core.sql.orig 2011-02-02 15:25:38.000000000 -0800 -@@ -1,25 +1,23 @@ ---- This file defines pgTAP, a collection of functions for TAP-based unit ---- testing. It is distributed under the revised FreeBSD license. You can ---- find the original here: ---- ---- http://github.com/theory/pgtap/raw/master/pgtap.sql.in ---- ---- The home page for the pgTAP project is: -+\set ECHO 0 -+-- This file defines pgTAP Lite, a portable collection of functions for -+-- TAP-based unit testing on PostgreSQL 8.3 or higher. It is distributed under -+-- the revised FreeBSD license. The home page for the pgTAP project is: - -- - -- http://pgtap.org/ -+-- - ---- ## CREATE SCHEMA TAPSCHEMA; ---- ## SET search_path TO TAPSCHEMA, public; -+\pset format unaligned -+\pset tuples_only true -+\pset pager -+ -+-- Revert all changes on failure. -+\set ON_ERROR_ROLLBACK 1 -+\set ON_ERROR_STOP true - - CREATE OR REPLACE FUNCTION pg_version() - RETURNS text AS 'SELECT current_setting(''server_version'')' - LANGUAGE SQL IMMUTABLE; - --CREATE OR REPLACE FUNCTION pg_typeof("any") --RETURNS regtype --AS '$libdir/pgtap' --LANGUAGE C STABLE; -- - CREATE OR REPLACE FUNCTION pg_version_num() - RETURNS integer AS $$ - SELECT s.a[1]::int * 10000 -@@ -30,10 +28,6 @@ - ) AS s; - $$ LANGUAGE SQL IMMUTABLE; - --CREATE OR REPLACE FUNCTION os_name() --RETURNS TEXT AS 'SELECT ''darwin''::text;' --LANGUAGE SQL IMMUTABLE; -- - CREATE OR REPLACE FUNCTION pgtap_version() - RETURNS NUMERIC AS 'SELECT 0.26;' - LANGUAGE SQL IMMUTABLE; -@@ -518,6 +512,35 @@ - SELECT cmp_ok( $1, $2, $3, 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 ); -@@ -812,2422 +835,254 @@ - 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; -+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; - --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 = $1 -- AND n.nspname = $2 -- AND c.relname = $3 -- ); --$$ LANGUAGE SQL; -+CREATE OR REPLACE VIEW tap_funky -+ AS SELECT p.oid AS oid, -+ n.nspname AS schema, -+ p.proname AS name, -+ 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, -+ p.proisagg AS is_agg, -+ 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 _rexists ( CHAR, NAME ) -+CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME, NAME[] ) - RETURNS BOOLEAN AS $$ - SELECT EXISTS( -- SELECT true -- FROM pg_catalog.pg_class c -- WHERE c.relkind = $1 -- AND pg_catalog.pg_table_is_visible(c.oid) -- AND c.relname = $2 -+ SELECT TRUE -+ FROM tap_funky -+ WHERE schema = $1 -+ AND name = $2 -+ AND args = array_to_string($3, ',') - ); - $$ LANGUAGE SQL; - ---- has_table( schema, table, description ) --CREATE OR REPLACE FUNCTION has_table ( NAME, NAME, TEXT ) --RETURNS TEXT AS $$ -- SELECT ok( _rexists( 'r', $1, $2 ), $3 ); --$$ LANGUAGE SQL; -- ---- has_table( table, description ) --CREATE OR REPLACE FUNCTION has_table ( NAME, TEXT ) --RETURNS TEXT AS $$ -- SELECT ok( _rexists( 'r', $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', $1, $2 ), $3 ); -+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; - ---- hasnt_table( table, description ) --CREATE OR REPLACE FUNCTION hasnt_table ( NAME, TEXT ) --RETURNS TEXT AS $$ -- SELECT ok( NOT _rexists( 'r', $1 ), $2 ); -+CREATE OR REPLACE FUNCTION _got_func ( NAME, NAME[] ) -+RETURNS BOOLEAN AS $$ -+ SELECT EXISTS( -+ SELECT TRUE -+ FROM tap_funky -+ WHERE name = $1 -+ AND args = array_to_string($2, ',') -+ AND is_visible -+ ); - $$ 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' ); -+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_view( schema, view, description ) --CREATE OR REPLACE FUNCTION has_view ( NAME, NAME, TEXT ) -+-- has_function( schema, function, args[], description ) -+CREATE OR REPLACE FUNCTION has_function ( NAME, NAME, NAME[], TEXT ) - RETURNS TEXT AS $$ -- SELECT ok( _rexists( 'v', $1, $2 ), $3 ); -+ SELECT ok( _got_func($1, $2, $3), $4 ); - $$ LANGUAGE SQL; - ---- has_view( view, description ) --CREATE OR REPLACE FUNCTION has_view ( NAME, TEXT ) -+-- has_function( schema, function, args[] ) -+CREATE OR REPLACE FUNCTION has_function( NAME, NAME, NAME[] ) - RETURNS TEXT AS $$ -- SELECT ok( _rexists( 'v', $1 ), $2 ); --$$ LANGUAGE SQL; -+ SELECT ok( -+ _got_func($1, $2, $3), -+ 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || -+ array_to_string($3, ', ') || ') should exist' -+ ); -+$$ LANGUAGE sql; - ---- has_view( view ) --CREATE OR REPLACE FUNCTION has_view ( NAME ) -+-- has_function( schema, function, description ) -+CREATE OR REPLACE FUNCTION has_function ( NAME, NAME, TEXT ) - RETURNS TEXT AS $$ -- SELECT has_view( $1, 'View ' || quote_ident($1) || ' should exist' ); -+ SELECT ok( _got_func($1, $2), $3 ); - $$ LANGUAGE SQL; - ---- hasnt_view( schema, view, description ) --CREATE OR REPLACE FUNCTION hasnt_view ( NAME, NAME, TEXT ) -+-- has_function( schema, function ) -+CREATE OR REPLACE FUNCTION has_function( NAME, NAME ) - RETURNS TEXT AS $$ -- SELECT ok( NOT _rexists( 'v', $1, $2 ), $3 ); --$$ LANGUAGE SQL; -+ SELECT ok( -+ _got_func($1, $2), -+ 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should exist' -+ ); -+$$ LANGUAGE sql; - ---- hasnt_view( view, description ) --CREATE OR REPLACE FUNCTION hasnt_view ( NAME, TEXT ) -+-- has_function( function, args[], description ) -+CREATE OR REPLACE FUNCTION has_function ( NAME, NAME[], TEXT ) - RETURNS TEXT AS $$ -- SELECT ok( NOT _rexists( 'v', $1 ), $2 ); -+ SELECT ok( _got_func($1, $2), $3 ); - $$ LANGUAGE SQL; - ---- hasnt_view( view ) --CREATE OR REPLACE FUNCTION hasnt_view ( NAME ) -+-- has_function( function, args[] ) -+CREATE OR REPLACE FUNCTION has_function( NAME, NAME[] ) - RETURNS TEXT AS $$ -- SELECT hasnt_view( $1, 'View ' || quote_ident($1) || ' should not exist' ); --$$ LANGUAGE SQL; -+ SELECT ok( -+ _got_func($1, $2), -+ 'Function ' || quote_ident($1) || '(' || -+ array_to_string($2, ', ') || ') should exist' -+ ); -+$$ LANGUAGE sql; - ---- has_sequence( schema, sequence, description ) --CREATE OR REPLACE FUNCTION has_sequence ( NAME, NAME, TEXT ) -+-- has_function( function, description ) -+CREATE OR REPLACE FUNCTION has_function( NAME, TEXT ) - RETURNS TEXT AS $$ -- SELECT ok( _rexists( 'S', $1, $2 ), $3 ); --$$ LANGUAGE SQL; -+ SELECT ok( _got_func($1), $2 ); -+$$ LANGUAGE sql; - ---- has_sequence( sequence, description ) --CREATE OR REPLACE FUNCTION has_sequence ( NAME, TEXT ) -+-- has_function( function ) -+CREATE OR REPLACE FUNCTION has_function( NAME ) - RETURNS TEXT AS $$ -- SELECT ok( _rexists( 'S', $1 ), $2 ); --$$ LANGUAGE SQL; -+ SELECT ok( _got_func($1), 'Function ' || quote_ident($1) || '() should exist' ); -+$$ LANGUAGE sql; - ---- has_sequence( sequence ) --CREATE OR REPLACE FUNCTION has_sequence ( NAME ) -+-- hasnt_function( schema, function, args[], description ) -+CREATE OR REPLACE FUNCTION hasnt_function ( NAME, NAME, NAME[], TEXT ) - RETURNS TEXT AS $$ -- SELECT has_sequence( $1, 'Sequence ' || quote_ident($1) || ' should exist' ); -+ SELECT ok( NOT _got_func($1, $2, $3), $4 ); - $$ LANGUAGE SQL; - ---- hasnt_sequence( schema, sequence, description ) --CREATE OR REPLACE FUNCTION hasnt_sequence ( NAME, NAME, TEXT ) -+-- hasnt_function( schema, function, args[] ) -+CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME, NAME[] ) - RETURNS TEXT AS $$ -- SELECT ok( NOT _rexists( 'S', $1, $2 ), $3 ); --$$ LANGUAGE SQL; -+ 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_sequence( sequence, description ) --CREATE OR REPLACE FUNCTION hasnt_sequence ( NAME, TEXT ) -+-- hasnt_function( schema, function, description ) -+CREATE OR REPLACE FUNCTION hasnt_function ( NAME, NAME, TEXT ) - RETURNS TEXT AS $$ -- SELECT ok( NOT _rexists( 'S', $1 ), $2 ); -+ SELECT ok( NOT _got_func($1, $2), $3 ); - $$ LANGUAGE SQL; - ---- hasnt_sequence( sequence ) --CREATE OR REPLACE FUNCTION hasnt_sequence ( NAME ) -+-- hasnt_function( schema, function ) -+CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME ) - RETURNS TEXT AS $$ -- SELECT hasnt_sequence( $1, 'Sequence ' || 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 -+ SELECT ok( -+ NOT _got_func($1, $2), -+ 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not exist' - ); --$$ 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; -+$$ LANGUAGE sql; - ---- has_column( table, column, description ) --CREATE OR REPLACE FUNCTION has_column ( NAME, NAME, TEXT ) -+-- hasnt_function( function, args[], description ) -+CREATE OR REPLACE FUNCTION hasnt_function ( NAME, NAME[], TEXT ) - RETURNS TEXT AS $$ -- SELECT ok( _cexists( $1, $2 ), $3 ); -+ SELECT ok( NOT _got_func($1, $2), $3 ); - $$ LANGUAGE SQL; - ---- has_column( table, column ) --CREATE OR REPLACE FUNCTION has_column ( NAME, NAME ) -+-- hasnt_function( function, args[] ) -+CREATE OR REPLACE FUNCTION hasnt_function( NAME, NAME[] ) - RETURNS TEXT AS $$ -- SELECT has_column( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); --$$ LANGUAGE SQL; -+ SELECT ok( -+ NOT _got_func($1, $2), -+ 'Function ' || quote_ident($1) || '(' || -+ array_to_string($2, ', ') || ') should not exist' -+ ); -+$$ LANGUAGE sql; - ---- hasnt_column( schema, table, column, description ) --CREATE OR REPLACE FUNCTION hasnt_column ( NAME, NAME, NAME, TEXT ) -+-- hasnt_function( function, description ) -+CREATE OR REPLACE FUNCTION hasnt_function( NAME, TEXT ) - RETURNS TEXT AS $$ -- SELECT ok( NOT _cexists( $1, $2, $3 ), $4 ); --$$ LANGUAGE SQL; -+ SELECT ok( NOT _got_func($1), $2 ); -+$$ LANGUAGE sql; - ---- hasnt_column( table, column, description ) --CREATE OR REPLACE FUNCTION hasnt_column ( NAME, NAME, TEXT ) -+-- hasnt_function( function ) -+CREATE OR REPLACE FUNCTION hasnt_function( NAME ) - RETURNS TEXT AS $$ -- SELECT ok( NOT _cexists( $1, $2 ), $3 ); --$$ LANGUAGE SQL; -+ SELECT ok( NOT _got_func($1), 'Function ' || quote_ident($1) || '() should not exist' ); -+$$ 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; -+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; - ---- _col_is_null( schema, table, column, desc, null ) --CREATE OR REPLACE FUNCTION _col_is_null ( NAME, NAME, NAME, TEXT, bool ) -+-- can( schema, functions[], description ) -+CREATE OR REPLACE FUNCTION can ( NAME, NAME[], TEXT ) - RETURNS TEXT AS $$ -+DECLARE -+ missing text[]; - 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( -- 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 -- ), $4 -- ); --END; --$$ LANGUAGE plpgsql; -- ---- _col_is_null( table, column, desc, null ) --CREATE OR REPLACE FUNCTION _col_is_null ( NAME, NAME, TEXT, bool ) --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' ); -+ 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( -- 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 -- ), $3 -+ RETURN ok( false, $3 ) || E'\n' || diag( -+ ' ' || quote_ident($1) || '.' || -+ array_to_string( missing, E'() missing\n ' || quote_ident($1) || '.') || -+ '() missing' - ); - END; - $$ LANGUAGE plpgsql; - ---- col_not_null( schema, table, column, description ) --CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, NAME, TEXT ) --RETURNS TEXT AS $$ -- SELECT _col_is_null( $1, $2, $3, $4, true ); --$$ LANGUAGE SQL; -- ---- col_not_null( table, column, description ) --CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, TEXT ) --RETURNS TEXT AS $$ -- SELECT _col_is_null( $1, $2, $3, true ); --$$ LANGUAGE SQL; -- ---- col_not_null( table, column ) --CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME ) -+-- can( schema, functions[] ) -+CREATE OR REPLACE FUNCTION can ( NAME, NAME[] ) - RETURNS TEXT AS $$ -- SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should be NOT NULL', true ); --$$ LANGUAGE SQL; -+ SELECT can( $1, $2, 'Schema ' || quote_ident($1) || ' can' ); -+$$ LANGUAGE sql; - ---- col_is_null( schema, table, column, description ) --CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME, TEXT ) --RETURNS TEXT AS $$ -- SELECT _col_is_null( $1, $2, $3, $4, false ); --$$ LANGUAGE SQL; -- ---- col_is_null( schema, table, column ) --CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME ) --RETURNS TEXT AS $$ -- SELECT _col_is_null( $1, $2, $3, false ); --$$ LANGUAGE SQL; -- ---- col_is_null( table, column ) --CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME ) --RETURNS TEXT AS $$ -- SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should allow NULL', false ); --$$ LANGUAGE SQL; -- --CREATE OR REPLACE FUNCTION display_type ( OID, INTEGER ) --RETURNS TEXT AS $$ -- SELECT COALESCE(substring( -- pg_catalog.format_type($1, $2), -- '(("(?!")([^"]|"")+"|[^.]+)([(][^)]+[)])?)$' -- ), '') --$$ LANGUAGE SQL; -- --CREATE OR REPLACE FUNCTION display_type ( NAME, OID, INTEGER ) --RETURNS TEXT AS $$ -- SELECT CASE WHEN $1 IS NULL THEN '' ELSE quote_ident($1) || '.' END -- || display_type($2, $3) --$$ LANGUAGE SQL; -- --CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME, NAME ) --RETURNS TEXT AS $$ -- SELECT display_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 display_type(a.atttypid, a.atttypmod) -- FROM pg_catalog.pg_attribute a -- JOIN pg_catalog.pg_class c ON a.attrelid = c.oid -- WHERE pg_table_is_visible(c.oid) -- AND c.relname = $1 -- AND a.attname = $2 -- AND attnum > 0 -- AND NOT a.attisdropped -- AND pg_type_is_visible(a.atttypid) --$$ LANGUAGE SQL; -- --CREATE OR REPLACE FUNCTION _get_col_ns_type ( NAME, NAME, NAME ) --RETURNS TEXT AS $$ -- SELECT display_type(tn.nspname, 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 _quote_ident_like(TEXT, TEXT) --RETURNS TEXT AS $$ --DECLARE -- have TEXT; -- pcision TEXT; --BEGIN -- -- Just return it if rhs isn't quoted. -- IF $2 !~ '"' THEN RETURN $1; END IF; -- -- pcision := substring($1 FROM '[(][^")]+[)]$'); -- -- -- Just quote it if thre is no precision. -- if pcision IS NULL THEN RETURN quote_ident($1); END IF; -- -- -- Quote the non-precision part and concatenate with precision. -- RETURN quote_ident(substring($1 FROM char_length($1) - char_length(pcision))) -- || pcision; --END; --$$ LANGUAGE plpgsql; -- ---- 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; -- -- want_type := quote_ident($4) || '.' || _quote_ident_like($5, have_type); -- 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 := _quote_ident_like($4, have_type); -- 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 --$$ 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 -- IF $1 ~ '^[^'']+[(]' THEN -- -- It's a functional default. -- 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), -- display_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), -- display_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 c.relhaspkey = true -- AND 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 c.relhaspkey = true -- AND 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( 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: http://pgfoundry.org/projects/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: http://pgfoundry.org/projects/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: http://pgfoundry.org/projects/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) -- 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 --$$ 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) -- 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 --$$ 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( 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( 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 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 || ' constriants'; -- 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 || ' constriants'; -- 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( 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 -- 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; -- --CREATE OR REPLACE VIEW tap_funky -- AS SELECT p.oid AS oid, -- n.nspname AS schema, -- p.proname AS name, -- 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, -- p.proisagg AS is_agg, -- 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 _got_func ( NAME, NAME, NAME[] ) --RETURNS BOOLEAN AS $$ -- SELECT EXISTS( -- SELECT TRUE -- FROM tap_funky -- WHERE schema = $1 -- AND name = $2 -- AND args = array_to_string($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 = array_to_string($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 NAME[] AS $$ -- SELECT ARRAY( -- SELECT a.attname -- 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_attribute a ON ct.oid = a.attrelid -- JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) -- ON a.attnum = x.indkey[s.i] -- 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 NAME[] AS $$ -- SELECT ARRAY( -- SELECT a.attname -- 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_attribute a ON ct.oid = a.attrelid -- JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) -- ON a.attnum = x.indkey[s.i] -- 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 ct.relname = $2 -- AND ci.relname = $3 -- AND n.nspname = $1 -- ); --$$ 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 -- ); --$$ LANGUAGE sql; -- --CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME, NAME) --RETURNS TEXT AS $$ -- SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) -- 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 --$$ LANGUAGE sql; -- --CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME) --RETURNS TEXT AS $$ -- SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) -- 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) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')', -- quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_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/expression, description ) --CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME, text ) --RETURNS TEXT AS $$ --DECLARE -- expr text; --BEGIN -- IF $4 NOT LIKE '%(%' THEN -- -- Not a functional index. -- RETURN has_index( $1, $2, $3, ARRAY[$4], $5 ); -- END IF; -- -- -- Get the functional expression. -- expr := _iexpr($1, $2, $3); -- -- IF expr IS NULL 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) || '(' || expr || ')', -- quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || $4 || ')', -- $5 -- ); --END; --$$ LANGUAGE plpgsql; -- ---- has_index( schema, table, index, columns/expression ) --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) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')', -- quote_ident($2) || ' ON ' || quote_ident($1) || '(' || _ident_array_to_string( $3, ', ' ) || ')', -- $4 -- ); --END; --$$ LANGUAGE plpgsql; -- ---- has_index( table, index, columns[], description ) --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 $$ --DECLARE -- want_expr text; -- descr text; -- have_expr text; -- idx name; -- tab text; --BEGIN -- IF $3 NOT LIKE '%(%' THEN -- -- Not a functional index. -- IF _is_schema( $1 ) THEN -- -- Looking for schema.table index. -- RETURN ok ( _have_index( $1, $2, $3 ), $4); -- END IF; -- -- Looking for particular columns. -- RETURN has_index( $1, $2, ARRAY[$3], $4 ); -- END IF; -- -- -- Get the functional expression. -- IF _is_schema( $1 ) THEN -- -- Looking for an index within a schema. -- have_expr := _iexpr($1, $2, $3); -- want_expr := $4; -- descr := 'Index ' || quote_ident($3) || ' should exist'; -- idx := $3; -- tab := quote_ident($1) || '.' || quote_ident($2); -- ELSE -- -- Looking for an index without a schema spec. -- have_expr := _iexpr($1, $2); -- want_expr := $3; -- descr := $4; -- idx := $2; -- tab := quote_ident($1); -- END IF; -- -- IF have_expr IS NULL THEN -- RETURN ok( false, descr ) || E'\n' -- || diag( 'Index ' || idx || ' ON ' || tab || ' not found'); -- END IF; -- -- RETURN is( -- quote_ident(idx) || ' ON ' || tab || '(' || have_expr || ')', -- quote_ident(idx) || ' ON ' || tab || '(' || want_expr || ')', -- descr -- ); --END; --$$ LANGUAGE plpgsql; -- ---- 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; -- ---- 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 -- ); --$$ 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 ) -+-- can( functions[], description ) -+CREATE OR REPLACE FUNCTION can ( NAME[], TEXT ) - RETURNS TEXT AS $$ - DECLARE -- pname text; -+ missing 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) || '()' -+ 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' - ); --$$ 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 $$ -- SELECT ok( -- EXISTS( -- SELECT true -- FROM pg_catalog.pg_tablespace -- WHERE spcname = $1 -- AND spclocation = $2 -- ), $3 -- ); --$$ LANGUAGE sql; -- ---- 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 ) -+-- can( functions[] ) -+CREATE OR REPLACE FUNCTION can ( NAME[] ) - RETURNS TEXT AS $$ -- SELECT hasnt_tablespace( $1, 'Tablespace ' || quote_ident($1) || ' should not exist' ); -+ SELECT can( $1, 'Schema ' || _ident_array_to_string(current_schemas(true), ' or ') || ' can' ); - $$ LANGUAGE sql; - - CREATE OR REPLACE FUNCTION _has_type( NAME, NAME, CHAR[] ) -@@ -3433,225 +1288,41 @@ - 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( -- 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( group, user[], 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_user ON usename = $2[i] -- WHERE usesysid IS NULL -- OR NOT usesysid = 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( -- ' Users missing from the ' || quote_ident($1) || E' group:\n ' || -- array_to_string( missing, E'\n ') -+ 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 - ); --END; --$$ LANGUAGE plpgsql; -+$$ LANGUAGE sql; - ---- is_member_of( group, user, description ) --CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME, TEXT ) -+-- enum_has_labels( enum, labels ) -+CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME[] ) - RETURNS TEXT AS $$ -- SELECT is_member_of( $1, ARRAY[$2], $3 ); --$$ LANGUAGE SQL; -+ SELECT enum_has_labels( -+ $1, $2, -+ 'Enum ' || quote_ident($1) || ' should have labels (' || array_to_string( $2, ', ' ) || ')' -+ ); -+$$ LANGUAGE sql; - ---- is_member_of( group, user[] ) --CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[] ) -+CREATE OR REPLACE FUNCTION display_type ( OID, INTEGER ) - RETURNS TEXT AS $$ -- SELECT is_member_of( $1, $2, 'Should have members of group ' || quote_ident($1) ); -+ SELECT COALESCE(substring( -+ pg_catalog.format_type($1, $2), -+ '(("(?!")([^"]|"")+"|[^.]+)([(][^)]+[)])?)$' -+ ), '') - $$ LANGUAGE SQL; - ---- is_member_of( group, user ) --CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME ) -+CREATE OR REPLACE FUNCTION display_type ( NAME, OID, INTEGER ) - RETURNS TEXT AS $$ -- SELECT is_member_of( $1, ARRAY[$2] ); -+ SELECT CASE WHEN $1 IS NULL THEN '' ELSE quote_ident($1) || '.' END -+ || display_type($2, $3) - $$ LANGUAGE SQL; - - CREATE OR REPLACE FUNCTION _cmp_types(oid, name) -@@ -3887,613 +1558,147 @@ - ); - $$ 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; -- ---- 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; -- ---- 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; -- --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_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_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 = $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 = $1 -- AND c.relname NOT IN ('__tcache__', '__tresults__', '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 _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 = $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 = $1 -- ); --$$ LANGUAGE SQL; -- ---- tables_are( schema, tables, description ) --CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[], TEXT ) --RETURNS TEXT AS $$ -- SELECT _are( 'tables', _extras('r', $1, $2), _missing('r', $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', $1), _missing('r', $1), $2); --$$ LANGUAGE SQL; -- ---- tables_are( schema, tables ) --CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[] ) --RETURNS TEXT AS $$ -- SELECT _are( -- 'tables', _extras('r', $1, $2), _missing('r', $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', $1), _missing('r', $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[] ) -+-- 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 _are( -- 'views', _extras('v', $1), _missing('v', $1), -- 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views' -- ); -+ SELECT ok( _op_exists($1, $2, $3, $4, $5 ), $6 ); - $$ LANGUAGE SQL; - ---- sequences_are( schema, sequences, description ) --CREATE OR REPLACE FUNCTION sequences_are ( NAME, NAME[], TEXT ) -+-- 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 _are( 'sequences', _extras('S', $1, $2), _missing('S', $1, $2), $3); -+ SELECT ok( -+ _op_exists($1, $2, $3, $4, $5 ), -+ 'Operator ' || quote_ident($2) || '.' || $3 || '(' || $1 || ',' || $4 -+ || ') RETURNS ' || $5 || ' should exist' -+ ); - $$ LANGUAGE SQL; - ---- sequences_are( sequences, description ) --CREATE OR REPLACE FUNCTION sequences_are ( NAME[], TEXT ) -+-- 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 _are( 'sequences', _extras('S', $1), _missing('S', $1), $2); -+ SELECT ok( _op_exists($1, $2, $3, $4 ), $5 ); - $$ LANGUAGE SQL; - ---- sequences_are( schema, sequences ) --CREATE OR REPLACE FUNCTION sequences_are ( NAME, NAME[] ) -+-- has_operator( left_type, name, right_type, return_type ) -+CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, 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' -+ SELECT ok( -+ _op_exists($1, $2, $3, $4 ), -+ 'Operator ' || $2 || '(' || $1 || ',' || $3 -+ || ') RETURNS ' || $4 || ' should exist' - ); - $$ LANGUAGE SQL; - ---- sequences_are( sequences ) --CREATE OR REPLACE FUNCTION sequences_are ( NAME[] ) -+-- has_operator( left_type, name, right_type, description ) -+CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME, TEXT ) - 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' -- ); -+ SELECT ok( _op_exists($1, $2, $3 ), $4 ); - $$ LANGUAGE SQL; - ---- functions_are( schema, functions[], description ) --CREATE OR REPLACE FUNCTION functions_are ( NAME, NAME[], TEXT ) -+-- has_operator( left_type, name, right_type ) -+CREATE OR REPLACE FUNCTION has_operator ( NAME, NAME, NAME ) - 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 -+ SELECT ok( -+ _op_exists($1, $2, $3 ), -+ 'Operator ' || $2 || '(' || $1 || ',' || $3 -+ || ') should exist' - ); - $$ LANGUAGE SQL; - ---- functions_are( schema, functions[] ) --CREATE OR REPLACE FUNCTION functions_are ( NAME, NAME[] ) -+-- has_leftop( schema, name, right_type, return_type, description ) -+CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME, NAME, TEXT ) - RETURNS TEXT AS $$ -- SELECT functions_are( $1, $2, 'Schema ' || quote_ident($1) || ' should have the correct functions' ); -+ SELECT ok( _op_exists(NULL, $1, $2, $3, $4), $5 ); - $$ LANGUAGE SQL; - ---- functions_are( functions[], description ) --CREATE OR REPLACE FUNCTION functions_are ( NAME[], TEXT ) -+-- has_leftop( schema, name, right_type, return_type ) -+CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME, NAME ) - 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 -+ SELECT ok( -+ _op_exists(NULL, $1, $2, $3, $4 ), -+ 'Left operator ' || quote_ident($1) || '.' || $2 || '(NONE,' -+ || $3 || ') RETURNS ' || $4 || ' should exist' - ); - $$ LANGUAGE SQL; - ---- functions_are( functions[] ) --CREATE OR REPLACE FUNCTION functions_are ( NAME[] ) -+-- has_leftop( name, right_type, return_type, description ) -+CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME, TEXT ) - RETURNS TEXT AS $$ -- SELECT functions_are( $1, 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct functions' ); -+ SELECT ok( _op_exists(NULL, $1, $2, $3), $4 ); - $$ LANGUAGE SQL; - ---- indexes_are( schema, table, indexes[], description ) --CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME, NAME[], TEXT ) -+-- has_leftop( name, right_type, return_type ) -+CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, NAME ) - 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 -+ SELECT ok( -+ _op_exists(NULL, $1, $2, $3 ), -+ 'Left operator ' || $1 || '(NONE,' || $2 || ') RETURNS ' || $3 || ' should exist' - ); - $$ LANGUAGE SQL; - ---- indexes_are( schema, table, indexes[] ) --CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME, NAME[] ) -+-- has_leftop( name, right_type, description ) -+CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME, TEXT ) - RETURNS TEXT AS $$ -- SELECT indexes_are( $1, $2, $3, 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should have the correct indexes' ); -+ SELECT ok( _op_exists(NULL, $1, $2), $3 ); - $$ LANGUAGE SQL; - ---- indexes_are( table, indexes[], description ) --CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME[], TEXT ) -+-- has_leftop( name, right_type ) -+CREATE OR REPLACE FUNCTION has_leftop ( NAME, NAME ) - 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 -+ SELECT ok( -+ _op_exists(NULL, $1, $2 ), -+ 'Left operator ' || $1 || '(NONE,' || $2 || ') should exist' - ); - $$ LANGUAGE SQL; - ---- indexes_are( table, indexes[] ) --CREATE OR REPLACE FUNCTION indexes_are( NAME, NAME[] ) -+-- has_rightop( left_type, schema, name, return_type, description ) -+CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME, NAME, TEXT ) - RETURNS TEXT AS $$ -- SELECT indexes_are( $1, $2, 'Table ' || quote_ident($1) || ' should have the correct indexes' ); -+ SELECT ok( _op_exists( $1, $2, $3, NULL, $4), $5 ); - $$ LANGUAGE SQL; - ---- users_are( users[], description ) --CREATE OR REPLACE FUNCTION users_are( NAME[], TEXT ) -+-- has_rightop( left_type, schema, name, return_type ) -+CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME, NAME ) - 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 -+ SELECT ok( -+ _op_exists($1, $2, $3, NULL, $4 ), -+ 'Right operator ' || quote_ident($2) || '.' || $3 || '(' -+ || $1 || ',NONE) RETURNS ' || $4 || ' should exist' - ); - $$ LANGUAGE SQL; - ---- users_are( users[] ) --CREATE OR REPLACE FUNCTION users_are( NAME[] ) -+-- has_rightop( left_type, name, return_type, description ) -+CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME, TEXT ) - RETURNS TEXT AS $$ -- SELECT users_are( $1, 'There should be the correct users' ); -+ SELECT ok( _op_exists( $1, $2, NULL, $3), $4 ); - $$ LANGUAGE SQL; - ---- groups_are( groups[], description ) --CREATE OR REPLACE FUNCTION groups_are( NAME[], TEXT ) -+-- has_rightop( left_type, name, return_type ) -+CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, NAME ) - 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 -+ SELECT ok( -+ _op_exists($1, $2, NULL, $3 ), -+ 'Right operator ' || $2 || '(' -+ || $1 || ',NONE) RETURNS ' || $3 || ' should exist' - ); - $$ LANGUAGE SQL; - ---- groups_are( groups[] ) --CREATE OR REPLACE FUNCTION groups_are( NAME[] ) -+-- has_rightop( left_type, name, description ) -+CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME, TEXT ) - RETURNS TEXT AS $$ -- SELECT groups_are( $1, 'There should be the correct groups' ); -+ SELECT ok( _op_exists( $1, $2, NULL), $3 ); - $$ LANGUAGE SQL; - ---- languages_are( languages[], description ) --CREATE OR REPLACE FUNCTION languages_are( NAME[], TEXT ) -+-- has_rightop( left_type, name ) -+CREATE OR REPLACE FUNCTION has_rightop ( NAME, NAME ) - 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 -+ SELECT ok( -+ _op_exists($1, $2, NULL ), -+ 'Right operator ' || $2 || '(' || $1 || ',NONE) should exist' - ); - $$ 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; -@@ -4568,384 +1773,37 @@ - -- has_opclass( name, description ) - CREATE OR REPLACE FUNCTION has_opclass( NAME, TEXT ) - RETURNS TEXT AS $$ -- SELECT ok( _opc_exists( NULL, $1 ), $2) --$$ LANGUAGE SQL; -- ---- has_opclass( name ) --CREATE OR REPLACE FUNCTION has_opclass( NAME ) --RETURNS TEXT AS $$ -- SELECT ok( _opc_exists( NULL, $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 exist' ); --$$ LANGUAGE SQL; -- ---- hasnt_opclass( name, description ) --CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, TEXT ) --RETURNS TEXT AS $$ -- SELECT ok( NOT _opc_exists( NULL, $1 ), $2) --$$ LANGUAGE SQL; -- ---- hasnt_opclass( name ) --CREATE OR REPLACE FUNCTION hasnt_opclass( NAME ) --RETURNS TEXT AS $$ -- SELECT ok( NOT _opc_exists( NULL, $1 ), 'Operator class ' || quote_ident($1) || ' should 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 -+ SELECT ok( _opc_exists( NULL, $1 ), $2) - $$ 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 -+-- has_opclass( name ) -+CREATE OR REPLACE FUNCTION has_opclass( NAME ) -+RETURNS TEXT AS $$ -+ SELECT ok( _opc_exists( NULL, $1 ), 'Operator class ' || quote_ident($1) || ' should exist' ); - $$ LANGUAGE SQL; - ---- rule_is_on( schema, table, rule, event, description ) --CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, NAME, TEXT, TEXT ) -+-- hasnt_opclass( schema, name, description ) -+CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, NAME, 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; -+ SELECT ok( NOT _opc_exists( $1, $2 ), $3 ); -+$$ LANGUAGE SQL; - ---- rule_is_on( schema, table, rule, event ) --CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, NAME, TEXT ) -+-- hasnt_opclass( schema, name ) -+CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, NAME ) - 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) -- ); -+ SELECT ok( NOT _opc_exists( $1, $2 ), 'Operator class ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); - $$ LANGUAGE SQL; - ---- rule_is_on( table, rule, event, description ) --CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, TEXT, TEXT ) -+-- hasnt_opclass( name, description ) -+CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, 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; -+ SELECT ok( NOT _opc_exists( NULL, $1 ), $2) -+$$ LANGUAGE SQL; - ---- rule_is_on( table, rule, event ) --CREATE OR REPLACE FUNCTION rule_is_on( NAME, NAME, TEXT ) -+-- hasnt_opclass( name ) -+CREATE OR REPLACE FUNCTION hasnt_opclass( NAME ) - 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) -- ); -+ SELECT ok( NOT _opc_exists( NULL, $1 ), 'Operator class ' || quote_ident($1) || ' should exist' ); - $$ LANGUAGE SQL; - - CREATE OR REPLACE FUNCTION _nosuch( NAME, NAME, NAME[]) -@@ -5551,124 +2409,6 @@ - ); - $$ 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. -- EXECUTE 'SELECT aok, descr FROM __tresults__ WHERE numb = ' || tnumb -- INTO aok, adescr; -- -- -- Now delete those results. -- EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; -- EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb; -- -- -- 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 3 + char_length( diag( adescr ) ) ); -- END IF; -- -- -- Remove failure message from ok(). -- IF NOT aok THEN -- adiag := substring( -- adiag -- FROM 14 + char_length(tnumb::text) -- + CASE adescr WHEN '' THEN 3 ELSE 3 + char_length( diag( adescr ) ) END -- ); -- 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 ) - RETURNS TEXT[] AS $$ - SELECT ARRAY( -@@ -6667,187 +3407,6 @@ - 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 $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.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 $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 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 $$ -@@ -7071,293 +3630,3 @@ - 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 -- 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 -- ), -- $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') -- 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') -- ), -- $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_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_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 display_type(castsource, NULL) || ' AS ' || display_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 display_type(castsource, NULL) || ' AS ' || display_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; -- From ce9057c67ea77f4bf372f4d25d279ff0302875fa Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 25 Feb 2011 16:21:30 -0800 Subject: [PATCH 0593/1195] Add abstract to provides section of META.json. --- Changes | 1 + META.json | 1 + 2 files changed, 2 insertions(+) diff --git a/Changes b/Changes index ccc3ff8c1bf7..3452147357b9 100644 --- a/Changes +++ b/Changes @@ -8,6 +8,7 @@ Revision history for pgTAP * Added a build target to create a portable version of pgTAP that can be included in any distribution and should just work for tesing on PostgreSQL 8.3+. +* Added abstract to the `provides` section of `META.json`. 0.25.0 2011-02-02T03:21:55 -------------------------- diff --git a/META.json b/META.json index 718075ebdb4e..e56d9c0d1385 100644 --- a/META.json +++ b/META.json @@ -23,6 +23,7 @@ }, "provides": { "pgtap": { + "abstract": "Unit testing for PostgreSQL", "file": "pgtap.sql", "version": "0.26.0" } From e9e6b6c62d0110eec55cd74c55b913ae33ba7330 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 26 Feb 2011 14:37:02 -0800 Subject: [PATCH 0594/1195] Update HTML generation for new verson of dicsount. Move TOC generation and cleanup to an external script that's easier to maintain. --- .gitattributes | 1 + Makefile | 5 ++--- doc/pgtap.md | 4 ++-- tocgen | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 5 deletions(-) create mode 100755 tocgen diff --git a/.gitattributes b/.gitattributes index de2f3167aa9a..c496927a6cbc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ .gitignore export-ignore .gitattributes export-ignore +tocgen export-ignore diff --git a/Makefile b/Makefile index 7e6fbba7f965..dcfc0def6f96 100644 --- a/Makefile +++ b/Makefile @@ -161,8 +161,7 @@ test: test/setup.sql pg_prove --pset tuples_only=1 $(TESTS) html: - /usr/local/discount-1.6.7/bin/markdown -F 0x1000 doc/pgtap.md > doc/pgtap.html - perl -ne 'BEGIN { $$prev = 0; $$lab = ""; print "

Contents

\n
    \n" } if (m{(([^(]+)?.+?)}) { next if $$lab && $$lab eq $$5; $$lab = $$5; if ($$prev) { if ($$1 != $$prev) { print $$1 > $$prev ? $$1 - $$prev > 1 ? "
      • " : "
          \n" : $$prev - $$1 > 1 ? "
    • \n" : "
    \n"; $$prev = $$1; } else { print "\n" } } else { $$prev = $$1; } print qq{
  • } . ($$5 ? "$$5()" : $$4) . "" } END { print "
  • \n
\n" }' doc/pgtap.html > doc/toc.html - perl -pi -e 'BEGIN { my %seen }; s{( doc/pgtap.html + ./tocgen doc/pgtap.html 2> doc/toc.html perl -MPod::Simple::XHTML -E "my \$$p = Pod::Simple::XHTML->new; \$$p->html_header_tags(''); \$$p->strip_verbatim_indent(sub { (my \$$i = \$$_[0]->[0]) =~ s/\\S.*//; \$$i }); \$$p->parse_from_file('`perldoc -l pg_prove`')" > doc/pg_prove.html diff --git a/doc/pgtap.md b/doc/pgtap.md index d02249c6fa79..2c2d98cb429f 100644 --- a/doc/pgtap.md +++ b/doc/pgtap.md @@ -3835,12 +3835,12 @@ discouraged: We recommend that you pick one style or another of TODO to be on the safe side. -### todo_end() ### +### `todo_end()` ### Stops running tests as TODO tests. This function is fatal if called without a preceding `todo_start()` method call. -### in_todo() ### +### `in_todo()` ### Returns true if the test is currently inside a TODO block. diff --git a/tocgen b/tocgen new file mode 100755 index 000000000000..4fa6f382cad1 --- /dev/null +++ b/tocgen @@ -0,0 +1,53 @@ +#!/usr/bin/env perl -n -pi + +our $prevn; +our $previd; +our %seen; + +BEGIN { + $prevn = 0; + $previd = ''; + print STDERR "

Contents

\n
    \n"; +} + +if (m{ + + ( # 4. header + ([^(]+)?.+? # 5. label + ) + +}x) { + # Clean up the ID a bit. + my ($hn, $func, $id, $val, $label) = ($1, $2, $3, $4, $5); + $id = $func || $id; + if ($id) { + $id =~ s{L?[.]code[.]}{}g; + $id =~ s{[.]{2,}}{.}g; + } + if ($previd ne $id) { + $previd = $id; + if ($prevn) { + if ($hn != $prevn) { + print STDERR $hn > $prevn + ? $hn - $prevn > 1 + ? "
      • " : "
          \n" : $prevn - $hn > 1 + ? "
    • \n" : "
    \n"; + $prevn = $hn; + } else { + print STDERR "\n" + } + } else { + $prevn = $hn; + } + print STDERR qq{
  • } . ($5 ? "$5()" : $val) . ""; + } + + $_ = qq{$val\n}; +} + +END { print STDERR "
  • \n
\n" } From 48e22bc4c4fdb9b027ff558ba94bec5035275270 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 26 Feb 2011 17:17:38 -0800 Subject: [PATCH 0595/1195] Simplify a bit. --- tocgen | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tocgen b/tocgen index 4fa6f382cad1..0ed3ee0f98fd 100755 --- a/tocgen +++ b/tocgen @@ -25,10 +25,10 @@ if (m{ # Clean up the ID a bit. my ($hn, $func, $id, $val, $label) = ($1, $2, $3, $4, $5); $id = $func || $id; - if ($id) { - $id =~ s{L?[.]code[.]}{}g; - $id =~ s{[.]{2,}}{.}g; - } + $id =~ s{L?[.]code[.]}{}g; + $id =~ s{[.]{2,}}{.}g; + my $num = $seen{$id}++ || ''; + if ($previd ne $id) { $previd = $id; if ($prevn) { @@ -44,10 +44,10 @@ if (m{ } else { $prevn = $hn; } - print STDERR qq{
  • } . ($5 ? "$5()" : $val) . ""; + print STDERR qq{
  • } . ($5 ? "$5()" : $val) . ""; } - $_ = qq{$val\n}; + $_ = qq{$val\n}; } END { print STDERR "
  • \n\n" } From 522a0d3cbce439a42b2d6af5d8a74f773d22ca88 Mon Sep 17 00:00:00 2001 From: Vladimir Protasov Date: Tue, 26 Apr 2011 17:10:55 +0400 Subject: [PATCH 0596/1195] Custom function to say that test is started --- sql/pgtap.sql.in | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 41a2f3f5551d..49c23217cc53 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -5756,6 +5756,12 @@ RETURNS boolean AS $$ SELECT TRUE; $$ LANGUAGE sql; +-- _test_started ( test_name ) +CREATE OR REPLACE FUNCTION _test_started(TEXT) +RETURNS TEXT AS $$ + SELECT diag($1 || '()'); +$$ LANGUAGE SQL; + CREATE OR REPLACE FUNCTION _runner( text[], text[], text[], text[], text[] ) RETURNS SETOF TEXT AS $$ DECLARE @@ -5783,7 +5789,7 @@ BEGIN FOR i IN 1..array_upper(tests, 1) LOOP BEGIN -- What test are we running? - IF verbos THEN RETURN NEXT diag(tests[i] || '()'); END IF; + IF verbos THEN RETURN NEXT _test_started(tests[i]); END IF; -- Run the setup functions. FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; From f586c9e37faa8339b3058546823b7e237c21f0d1 Mon Sep 17 00:00:00 2001 From: Vladimir Protasov Date: Wed, 27 Apr 2011 17:47:15 +0400 Subject: [PATCH 0597/1195] Small fixes in docs: typo and writing style --- doc/pgtap.md | 51 +++++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/doc/pgtap.md b/doc/pgtap.md index 2c2d98cb429f..8f1c80efafb1 100644 --- a/doc/pgtap.md +++ b/doc/pgtap.md @@ -1941,7 +1941,6 @@ argument is a schema name and the second is the test description. If you omit the schema, the schema must be visible in the search path. If you omit the test description, it will be set to "Schema `:schema` should exist". -### `hasnt_schema( schema, schema, description )` ### ### `hasnt_schema( schema, description )` ### ### `hasnt_schema( schema )` ### @@ -2373,11 +2372,11 @@ pgTAP will generate a useful description if you don't provide one. This function is the inverse of `has_cast()`. The test passes if the specified cast does *not* exist. -### `has_operator( left_type, schema, name, right_type, return_type, desc )` ### +### `has_operator( left_type, schema, name, right_type, return_type, description )` ### ### `has_operator( left_type, schema, name, right_type, return_type )` ### -### `has_operator( left_type, name, right_type, return_type, desc )` ### +### `has_operator( left_type, name, right_type, return_type, description )` ### ### `has_operator( left_type, name, right_type, return_type )` ### -### `has_operator( left_type, name, right_type, desc )` ### +### `has_operator( left_type, name, right_type, description )` ### ### `has_operator( left_type, name, right_type )` ### SELECT has_operator( @@ -2396,11 +2395,11 @@ the test description, pgTAP will generate a reasonable one for you. The return value is also optional. If you need to test for a left or right unary operator, use `has_leftop()` or `has_rightop()` instead. -### `has_leftop( schema, name, right_type, return_type, desc )` ### +### `has_leftop( schema, name, right_type, return_type, description )` ### ### `has_leftop( schema, name, right_type, return_type )` ### -### `has_leftop( name, right_type, return_type, desc )` ### +### `has_leftop( name, right_type, return_type, description )` ### ### `has_leftop( name, right_type, return_type )` ### -### `has_leftop( name, right_type, desc )` ### +### `has_leftop( name, right_type, description )` ### ### `has_leftop( name, right_type )` ### SELECT has_leftop( @@ -2417,11 +2416,11 @@ name, then the operator must be visible in the search path. If you omit the test description, pgTAP will generate a reasonable one for you. The return value is also optional. -### `has_rightop( left_type, schema, name, return_type, desc )` ### +### `has_rightop( left_type, schema, name, return_type, description )` ### ### `has_rightop( left_type, schema, name, return_type )` ### -### `has_rightop( left_type, name, return_type, desc )` ### +### `has_rightop( left_type, name, return_type, description )` ### ### `has_rightop( left_type, name, return_type )` ### -### `has_rightop( left_type, name, desc )` ### +### `has_rightop( left_type, name, description )` ### ### `has_rightop( left_type, name )` ### SELECT has_rightop( @@ -2468,7 +2467,7 @@ also optional. This function is the inverse of `has_opclass()`. The test passes if the specified operator class does *not* exist. -### `has_role( role, desc )` ### +### `has_role( role, description )` ### ### `has_role( role )` ### SELECT has_role( 'theory', 'Role "theory" should exist' ); @@ -2476,7 +2475,7 @@ specified operator class does *not* exist. Checks to ensure that a database role exists. If the description is omitted, it will default to "Role `:role` should exist". -### `hasnt_role( role, desc )` ### +### `hasnt_role( role, description )` ### ### `hasnt_role( role )` ### SELECT hasnt_role( 'theory', 'Role "theory" should not exist' ); @@ -2484,7 +2483,7 @@ it will default to "Role `:role` should exist". The inverse of `has_role()`, this function tests for the *absence* of a database role. -### `has_user( user, desc )` ### +### `has_user( user, description )` ### ### `has_user( user )` ### SELECT has_user( 'theory', 'User "theory" should exist' ); @@ -2492,7 +2491,7 @@ database role. Checks to ensure that a database user exists. If the description is omitted, it will default to "User `:user` should exist". -### `hasnt_user( user, desc )` ### +### `hasnt_user( user, description )` ### ### `hasnt_user( user )` ### SELECT hasnt_user( 'theory', 'User "theory" should not exist' ); @@ -2500,7 +2499,7 @@ it will default to "User `:user` should exist". The inverse of `has_user()`, this function tests for the *absence* of a database user. -### `has_group( group, desc )` ### +### `has_group( group, description )` ### ### `has_group( group )` ### SELECT has_group( 'sweeties, 'Group "sweeties" should exist' ); @@ -2508,7 +2507,7 @@ database user. Checks to ensure that a database group exists. If the description is omitted, it will default to "Group `:group` should exist". -### `hasnt_group( group, desc )` ### +### `hasnt_group( group, description )` ### ### `hasnt_group( group )` ### SELECT hasnt_group( 'meanies, 'Group meaines should not exist' ); @@ -2516,7 +2515,7 @@ it will default to "Group `:group` should exist". The inverse of `has_group()`, this function tests for the *absence* of a database group. -### `has_language( language, desc )` ### +### `has_language( language, description )` ### ### `has_language( language )` ### SELECT has_language( 'plpgsql', 'Language "plpgsql" should exist' ); @@ -2524,7 +2523,7 @@ database group. Checks to ensure that a procedural language exists. If the description is omitted, it will default to "Procedural language `:language` should exist". -### `hasnt_language( language, desc )` ### +### `hasnt_language( language, description )` ### ### `hasnt_language( language )` ### SELECT hasnt_language( 'plpgsql', 'Language "plpgsql" should not exist' ); @@ -2694,7 +2693,7 @@ first, eh? ### `col_default_is( schema, table, column, default, description )` ### ### `col_default_is( table, column, default, description )` ### -### `col_default_is( table, column, type )` ### +### `col_default_is( table, column, default )` ### SELECT col_default_is( 'myschema', @@ -3471,9 +3470,9 @@ fact, like so: But you really ought to call `has_language()` first so that you never get that far. -### `enum_has_labels( schema, enum, labels, desc )` ### +### `enum_has_labels( schema, enum, labels, description )` ### ### `enum_has_labels( schema, enum, labels )` ### -### `enum_has_labels( enum, labels, desc )` ### +### `enum_has_labels( enum, labels, description )` ### ### `enum_has_labels( enum, labels )` ### SELECT enum_has_labels( @@ -3542,7 +3541,7 @@ The inverse of `domain_type_is()`, this function tests that a domain does should probably extned the `text` type, not `integer`, since leading 0s are valid and required. The arguments are the same as for `domain_type_is()`. -### `cast_context_is( source_type, target_type, context, desc )` ### +### `cast_context_is( source_type, target_type, context, description )` ### ### `cast_context_is( source_type, target_type, context )` ### SELECT cast_context_is( 'integer', 'bigint', 'implicit' ); @@ -3572,7 +3571,7 @@ If the cast doesn't exist, you'll be told that, too: But you've already used `has_cast()` to make sure of that, right? -### `is_superuser( user, desc )` ### +### `is_superuser( user, description )` ### ### `is_superuser( user )` ### SELECT is_superuser( 'theory', 'User "theory" should be a super user' ); @@ -3581,7 +3580,7 @@ Tests that a database user is a super user. If the description is omitted, it will default to "User `:user` should be a super user". If the user does not exist in the database, the diagnostics will say so. -### `isnt_superuser( user, desc )` ### +### `isnt_superuser( user, description )` ### ### `isnt_superuser( user )` ### SELECT is_superuser( @@ -3594,9 +3593,9 @@ The inverse of `is_superuser()`, this function tests that a database user is database, the test is still considered a failure, and the diagnostics will say so. -### `is_member_of( group, users[], desc )` ### +### `is_member_of( group, users[], description )` ### ### `is_member_of( group, users[] )` ### -### `is_member_of( group, user, desc )` ### +### `is_member_of( group, user, description )` ### ### `is_member_of( group, user )` ### SELECT is_member_of( 'sweeties', 'anna' 'Anna should be a sweetie' ); From 99ef96411aad12e69a70fea0904b25e964633db6 Mon Sep 17 00:00:00 2001 From: Vladimir Protasov Date: Fri, 29 Apr 2011 17:15:50 +0400 Subject: [PATCH 0598/1195] Small fixes in docs: typo and writing style --- doc/pgtap.md | 51 +++++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/doc/pgtap.md b/doc/pgtap.md index 2c2d98cb429f..8f1c80efafb1 100644 --- a/doc/pgtap.md +++ b/doc/pgtap.md @@ -1941,7 +1941,6 @@ argument is a schema name and the second is the test description. If you omit the schema, the schema must be visible in the search path. If you omit the test description, it will be set to "Schema `:schema` should exist". -### `hasnt_schema( schema, schema, description )` ### ### `hasnt_schema( schema, description )` ### ### `hasnt_schema( schema )` ### @@ -2373,11 +2372,11 @@ pgTAP will generate a useful description if you don't provide one. This function is the inverse of `has_cast()`. The test passes if the specified cast does *not* exist. -### `has_operator( left_type, schema, name, right_type, return_type, desc )` ### +### `has_operator( left_type, schema, name, right_type, return_type, description )` ### ### `has_operator( left_type, schema, name, right_type, return_type )` ### -### `has_operator( left_type, name, right_type, return_type, desc )` ### +### `has_operator( left_type, name, right_type, return_type, description )` ### ### `has_operator( left_type, name, right_type, return_type )` ### -### `has_operator( left_type, name, right_type, desc )` ### +### `has_operator( left_type, name, right_type, description )` ### ### `has_operator( left_type, name, right_type )` ### SELECT has_operator( @@ -2396,11 +2395,11 @@ the test description, pgTAP will generate a reasonable one for you. The return value is also optional. If you need to test for a left or right unary operator, use `has_leftop()` or `has_rightop()` instead. -### `has_leftop( schema, name, right_type, return_type, desc )` ### +### `has_leftop( schema, name, right_type, return_type, description )` ### ### `has_leftop( schema, name, right_type, return_type )` ### -### `has_leftop( name, right_type, return_type, desc )` ### +### `has_leftop( name, right_type, return_type, description )` ### ### `has_leftop( name, right_type, return_type )` ### -### `has_leftop( name, right_type, desc )` ### +### `has_leftop( name, right_type, description )` ### ### `has_leftop( name, right_type )` ### SELECT has_leftop( @@ -2417,11 +2416,11 @@ name, then the operator must be visible in the search path. If you omit the test description, pgTAP will generate a reasonable one for you. The return value is also optional. -### `has_rightop( left_type, schema, name, return_type, desc )` ### +### `has_rightop( left_type, schema, name, return_type, description )` ### ### `has_rightop( left_type, schema, name, return_type )` ### -### `has_rightop( left_type, name, return_type, desc )` ### +### `has_rightop( left_type, name, return_type, description )` ### ### `has_rightop( left_type, name, return_type )` ### -### `has_rightop( left_type, name, desc )` ### +### `has_rightop( left_type, name, description )` ### ### `has_rightop( left_type, name )` ### SELECT has_rightop( @@ -2468,7 +2467,7 @@ also optional. This function is the inverse of `has_opclass()`. The test passes if the specified operator class does *not* exist. -### `has_role( role, desc )` ### +### `has_role( role, description )` ### ### `has_role( role )` ### SELECT has_role( 'theory', 'Role "theory" should exist' ); @@ -2476,7 +2475,7 @@ specified operator class does *not* exist. Checks to ensure that a database role exists. If the description is omitted, it will default to "Role `:role` should exist". -### `hasnt_role( role, desc )` ### +### `hasnt_role( role, description )` ### ### `hasnt_role( role )` ### SELECT hasnt_role( 'theory', 'Role "theory" should not exist' ); @@ -2484,7 +2483,7 @@ it will default to "Role `:role` should exist". The inverse of `has_role()`, this function tests for the *absence* of a database role. -### `has_user( user, desc )` ### +### `has_user( user, description )` ### ### `has_user( user )` ### SELECT has_user( 'theory', 'User "theory" should exist' ); @@ -2492,7 +2491,7 @@ database role. Checks to ensure that a database user exists. If the description is omitted, it will default to "User `:user` should exist". -### `hasnt_user( user, desc )` ### +### `hasnt_user( user, description )` ### ### `hasnt_user( user )` ### SELECT hasnt_user( 'theory', 'User "theory" should not exist' ); @@ -2500,7 +2499,7 @@ it will default to "User `:user` should exist". The inverse of `has_user()`, this function tests for the *absence* of a database user. -### `has_group( group, desc )` ### +### `has_group( group, description )` ### ### `has_group( group )` ### SELECT has_group( 'sweeties, 'Group "sweeties" should exist' ); @@ -2508,7 +2507,7 @@ database user. Checks to ensure that a database group exists. If the description is omitted, it will default to "Group `:group` should exist". -### `hasnt_group( group, desc )` ### +### `hasnt_group( group, description )` ### ### `hasnt_group( group )` ### SELECT hasnt_group( 'meanies, 'Group meaines should not exist' ); @@ -2516,7 +2515,7 @@ it will default to "Group `:group` should exist". The inverse of `has_group()`, this function tests for the *absence* of a database group. -### `has_language( language, desc )` ### +### `has_language( language, description )` ### ### `has_language( language )` ### SELECT has_language( 'plpgsql', 'Language "plpgsql" should exist' ); @@ -2524,7 +2523,7 @@ database group. Checks to ensure that a procedural language exists. If the description is omitted, it will default to "Procedural language `:language` should exist". -### `hasnt_language( language, desc )` ### +### `hasnt_language( language, description )` ### ### `hasnt_language( language )` ### SELECT hasnt_language( 'plpgsql', 'Language "plpgsql" should not exist' ); @@ -2694,7 +2693,7 @@ first, eh? ### `col_default_is( schema, table, column, default, description )` ### ### `col_default_is( table, column, default, description )` ### -### `col_default_is( table, column, type )` ### +### `col_default_is( table, column, default )` ### SELECT col_default_is( 'myschema', @@ -3471,9 +3470,9 @@ fact, like so: But you really ought to call `has_language()` first so that you never get that far. -### `enum_has_labels( schema, enum, labels, desc )` ### +### `enum_has_labels( schema, enum, labels, description )` ### ### `enum_has_labels( schema, enum, labels )` ### -### `enum_has_labels( enum, labels, desc )` ### +### `enum_has_labels( enum, labels, description )` ### ### `enum_has_labels( enum, labels )` ### SELECT enum_has_labels( @@ -3542,7 +3541,7 @@ The inverse of `domain_type_is()`, this function tests that a domain does should probably extned the `text` type, not `integer`, since leading 0s are valid and required. The arguments are the same as for `domain_type_is()`. -### `cast_context_is( source_type, target_type, context, desc )` ### +### `cast_context_is( source_type, target_type, context, description )` ### ### `cast_context_is( source_type, target_type, context )` ### SELECT cast_context_is( 'integer', 'bigint', 'implicit' ); @@ -3572,7 +3571,7 @@ If the cast doesn't exist, you'll be told that, too: But you've already used `has_cast()` to make sure of that, right? -### `is_superuser( user, desc )` ### +### `is_superuser( user, description )` ### ### `is_superuser( user )` ### SELECT is_superuser( 'theory', 'User "theory" should be a super user' ); @@ -3581,7 +3580,7 @@ Tests that a database user is a super user. If the description is omitted, it will default to "User `:user` should be a super user". If the user does not exist in the database, the diagnostics will say so. -### `isnt_superuser( user, desc )` ### +### `isnt_superuser( user, description )` ### ### `isnt_superuser( user )` ### SELECT is_superuser( @@ -3594,9 +3593,9 @@ The inverse of `is_superuser()`, this function tests that a database user is database, the test is still considered a failure, and the diagnostics will say so. -### `is_member_of( group, users[], desc )` ### +### `is_member_of( group, users[], description )` ### ### `is_member_of( group, users[] )` ### -### `is_member_of( group, user, desc )` ### +### `is_member_of( group, user, description )` ### ### `is_member_of( group, user )` ### SELECT is_member_of( 'sweeties', 'anna' 'Anna should be a sweetie' ); From 1934e88e6473b78df75b6a476d4e8a9e6d07c94e Mon Sep 17 00:00:00 2001 From: Vladimir Protasov Date: Fri, 29 Apr 2011 17:23:03 +0400 Subject: [PATCH 0599/1195] Function test_started(TEXT) was renamed from _test_started(TEXT) and documented. --- doc/pgtap.md | 16 ++++++++++++++++ sql/pgtap.sql.in | 6 +++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/doc/pgtap.md b/doc/pgtap.md index 8f1c80efafb1..b32de6f3bdb9 100644 --- a/doc/pgtap.md +++ b/doc/pgtap.md @@ -353,6 +353,22 @@ the test in your script, simply search for "simple exponential". All test functions take a name argument. It's optional, but highly suggested that you use it. + +Sometimes it's useful to extract test function names from pgtap output, especially when using xUnit style with Continuous Integration Server like Hudson or TeamCity. +By default pgTAP displays this names as "comment", but you're able to change this behavior by overriding function `test_started`: + +### `test_started( test_name )` ### + + CREATE OR REPLACE FUNCTION test_started(TEXT) + RETURNS TEXT AS $$ + SELECT 'test ' || $1 || '()'; + $$ LANGUAGE SQL; + +This will show + test my_example_test_function_name() +instead of + # my_example_test_function_name() +This makes easy handling test name and differing test names from comments. I'm ok, you're not ok --------------------- diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 49c23217cc53..63af5979337b 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -5756,8 +5756,8 @@ RETURNS boolean AS $$ SELECT TRUE; $$ LANGUAGE sql; --- _test_started ( test_name ) -CREATE OR REPLACE FUNCTION _test_started(TEXT) +-- test_started ( test_name ) +CREATE OR REPLACE FUNCTION test_started(TEXT) RETURNS TEXT AS $$ SELECT diag($1 || '()'); $$ LANGUAGE SQL; @@ -5789,7 +5789,7 @@ BEGIN FOR i IN 1..array_upper(tests, 1) LOOP BEGIN -- What test are we running? - IF verbos THEN RETURN NEXT _test_started(tests[i]); END IF; + IF verbos THEN RETURN NEXT test_started(tests[i]); END IF; -- Run the setup functions. FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; From 5030dc3a3e00f433ee6797360ce1fe1bb32041a9 Mon Sep 17 00:00:00 2001 From: eoranged Date: Tue, 3 May 2011 21:34:17 +0400 Subject: [PATCH 0600/1195] Function test_started() renamed to diag_test_name() --- doc/pgtap.md | 10 +++++----- sql/pgtap.sql.in | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/pgtap.md b/doc/pgtap.md index b32de6f3bdb9..0a1d315d9307 100644 --- a/doc/pgtap.md +++ b/doc/pgtap.md @@ -355,17 +355,17 @@ All test functions take a name argument. It's optional, but highly suggested that you use it. Sometimes it's useful to extract test function names from pgtap output, especially when using xUnit style with Continuous Integration Server like Hudson or TeamCity. -By default pgTAP displays this names as "comment", but you're able to change this behavior by overriding function `test_started`: +By default pgTAP displays this names as "comment", but you're able to change this behavior by overriding function `diag_test_name`: -### `test_started( test_name )` ### +### `diag_test_name( test_name )` ### - CREATE OR REPLACE FUNCTION test_started(TEXT) + CREATE OR REPLACE FUNCTION diag_test_name(TEXT) RETURNS TEXT AS $$ - SELECT 'test ' || $1 || '()'; + SELECT diag('test: ' || $1 ); $$ LANGUAGE SQL; This will show - test my_example_test_function_name() + # test: my_example_test_function_name instead of # my_example_test_function_name() This makes easy handling test name and differing test names from comments. diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 63af5979337b..56e44949b673 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -5756,8 +5756,8 @@ RETURNS boolean AS $$ SELECT TRUE; $$ LANGUAGE sql; --- test_started ( test_name ) -CREATE OR REPLACE FUNCTION test_started(TEXT) +-- diag_test_name ( test_name ) +CREATE OR REPLACE FUNCTION diag_test_name(TEXT) RETURNS TEXT AS $$ SELECT diag($1 || '()'); $$ LANGUAGE SQL; @@ -5789,7 +5789,7 @@ BEGIN FOR i IN 1..array_upper(tests, 1) LOOP BEGIN -- What test are we running? - IF verbos THEN RETURN NEXT test_started(tests[i]); END IF; + IF verbos THEN RETURN NEXT diag_test_name(tests[i]); END IF; -- Run the setup functions. FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; From 0f7676c24ea1f34f3d267204a558dfa74fed48a6 Mon Sep 17 00:00:00 2001 From: eoranged Date: Tue, 3 May 2011 21:35:46 +0400 Subject: [PATCH 0601/1195] Add function diag_test_name() to sql/uninstall_pgtap.sql.in --- sql/uninstall_pgtap.sql.in | 1 + 1 file changed, 1 insertion(+) diff --git a/sql/uninstall_pgtap.sql.in b/sql/uninstall_pgtap.sql.in index a14683d85f4d..fb72d22b457f 100644 --- a/sql/uninstall_pgtap.sql.in +++ b/sql/uninstall_pgtap.sql.in @@ -705,5 +705,6 @@ DROP FUNCTION pgtap_version(); DROP FUNCTION os_name(); DROP FUNCTION pg_version_num(); DROP FUNCTION pg_version(); +DROP FUNCTION diag_test_name(TEXT) -- ## SET search_path TO public; -- ## DROP SCHEMA TAPSCHEMA; From 05b45e28eb5078b197e4bd2008482ec8b72eb919 Mon Sep 17 00:00:00 2001 From: Vladimir Protasov Date: Mon, 23 May 2011 16:09:19 +0400 Subject: [PATCH 0602/1195] Fixed issue #6: wrong quoting for types with precision Fixed function _quote_ident_like(text,text) --- sql/pgtap.sql.in | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 56e44949b673..ed4b40f0b188 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1160,13 +1160,17 @@ BEGIN -- Just return it if rhs isn't quoted. IF $2 !~ '"' THEN RETURN $1; END IF; + -- If it's quoted ident without precision, return it. + -- We need to care about, because "test type(123)" is distinct from "test type"(123) + IF substring($2 FROM char_length($2) FOR 1) = '"' THEN RETURN quote_ident($1); END IF; + pcision := substring($1 FROM '[(][^")]+[)]$'); -- Just quote it if thre is no precision. if pcision IS NULL THEN RETURN quote_ident($1); END IF; -- Quote the non-precision part and concatenate with precision. - RETURN quote_ident(substring($1 FROM char_length($1) - char_length(pcision))) + RETURN quote_ident(substring($1 FOR char_length($1) - char_length(pcision))) || pcision; END; $$ LANGUAGE plpgsql; From dc5dc1109e54f9c9a2b6ca3a8f8b05ae2ed4b0c6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 26 May 2011 16:53:12 -0700 Subject: [PATCH 0603/1195] Fix URL. --- sql/pgtap.sql.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 56e44949b673..3700dca24191 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2,7 +2,7 @@ -- testing. It is distributed under the revised FreeBSD license. You can -- find the original here: -- --- http://github.com/theory/pgtap/raw/master/pgtap.sql.in +-- http://github.com/theory/pgtap/raw/master/sql/pgtap.sql.in -- -- The home page for the pgTAP project is: -- From 0578eacc80e3474b4051a2c03851cf4d43173e34 Mon Sep 17 00:00:00 2001 From: Vladimir Protasov Date: Wed, 1 Jun 2011 15:50:07 +0400 Subject: [PATCH 0604/1195] Add test suite for _quote_ident_like(text, text) --- test/sql/util.sql | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/sql/util.sql b/test/sql/util.sql index 175819682253..568fc3d8538a 100644 --- a/test/sql/util.sql +++ b/test/sql/util.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(30); +SELECT plan(35); --SELECT * FROM no_plan(); SELECT is( pg_typeof(42), 'integer', 'pg_type(int) should work' ); @@ -125,6 +125,14 @@ CREATE DOMAIN "try.this""" AS numeric CHECK (TRUE); SELECT is( display_type( oid, 42 ), '"try.this"""(42)', 'display_type("try.this""", 42)' ) FROM pg_type WHERE typname = 'try.this"'; +-- Take care about quoting with/without precision +SELECT is(_quote_ident_like('test','public.test'), 'test', 'No quoting is required'); +SELECT is(_quote_ident_like('test type','public."test type"'), '"test type"', 'Just quote'); +SELECT is(_quote_ident_like('varchar(12)', 'varchar(12)'), 'varchar(12)', 'No quoting is required (with precision)'); +SELECT is(_quote_ident_like('test type(123)','myschema."test type"(234)'), '"test type"(123)', 'Quote as type with precision'); +SELECT is(_quote_ident_like('test table (123)','public."test table (123)"'), '"test table (123)"', 'Quote as ident without precision'); + + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 461fa22cb2971f86f5987cc744468612340731bb Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Jul 2011 11:32:26 -0700 Subject: [PATCH 0605/1195] Remove use of `relistemp`. --- Changes | 2 ++ sql/pgtap.sql.in | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 3452147357b9..f7c92807b6eb 100644 --- a/Changes +++ b/Changes @@ -9,6 +9,8 @@ Revision history for pgTAP included in any distribution and should just work for tesing on PostgreSQL 8.3+. * Added abstract to the `provides` section of `META.json`. +* Removed use of `relistemp` column in an internal function, as it has been + removed in PostgreSQL 9.1. Thanks to Tom Lane for the alternate syntax. 0.25.0 2011-02-02T03:21:55 -------------------------- diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 3700dca24191..75010a3c184a 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -5897,8 +5897,7 @@ 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 c.relname = $1 - AND c.relistemp + WHERE c.oid = ('pg_temp.' || $1)::pg_catalog.regclass AND attnum > 0 AND NOT attisdropped ORDER BY attnum From d5d90b3e077007fe15fce29639b00f2892044538 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 21 Jul 2011 11:33:15 -0700 Subject: [PATCH 0606/1195] Add `pgtap.control`. --- pgtap.control | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 pgtap.control diff --git a/pgtap.control b/pgtap.control new file mode 100644 index 000000000000..2abf3c54e082 --- /dev/null +++ b/pgtap.control @@ -0,0 +1,6 @@ +# pgTAP extension +comment = 'Unit testing for PostgreSQL' +default_version = '0.26.0' +module_pathname = '$libdir/semver' +relocatable = true +superuser = false From 9f48ec166ffa57d8d56b026581c54c8e7925117e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 23 Aug 2011 20:01:05 -0700 Subject: [PATCH 0607/1195] Add PostgreSQL 9.1 CREATE EXTENSION support. While at it, rewrite the Makefile to be a bit easier to understand, with much better version determination (more future-proof, too). Might run into some issues when building for other versions; will have to test that soon. --- .gitignore | 1 + Changes | 1 + Makefile | 101 +++-- README.md | 26 +- sql/pgtap--unpackaged--0.26.0.sql | 707 ++++++++++++++++++++++++++++++ sql/uninstall_pgtap.sql.in | 2 +- 6 files changed, 780 insertions(+), 58 deletions(-) create mode 100644 sql/pgtap--unpackaged--0.26.0.sql diff --git a/.gitignore b/.gitignore index 81d90377b71c..189df09f6651 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ pgtap.so regression.* *.html bbin +/sql/pgtap--0.* \ No newline at end of file diff --git a/Changes b/Changes index f7c92807b6eb..417a0e6f1bee 100644 --- a/Changes +++ b/Changes @@ -11,6 +11,7 @@ Revision history for pgTAP * Added abstract to the `provides` section of `META.json`. * Removed use of `relistemp` column in an internal function, as it has been removed in PostgreSQL 9.1. Thanks to Tom Lane for the alternate syntax. +* Added PostreSQL 9.1 `CREATE EXTENSION` support. 0.25.0 2011-02-02T03:21:55 -------------------------- diff --git a/Makefile b/Makefile index dcfc0def6f96..df76c6f858c5 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,14 @@ -TESTS = $(wildcard test/sql/*.sql) -EXTRA_CLEAN = test/setup.sql doc/*.html -DATA_built = sql/pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql sql/uninstall_pgtap.sql -DOCS = doc/pgtap.md -REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) +EXTENSION = pgtap +EXTVERSION = $(shell grep default_version $(EXTENSION).control | \ + sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") +NUMVERSION = $(shell echo $(EXTVERSION) | sed -e 's/\([[:digit:]]*[.][[:digit:]]*\).*/\1/') +DATA = $(filter-out $(wildcard sql/*--*.sql),$(wildcard sql/*.sql)) +TESTS = $(wildcard test/sql/*.sql) +EXTRA_CLEAN = test/setup.sql doc/*.html +DOCS = doc/pgtap.md +REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --inputdir=test --load-language=plpgsql +PG_CONFIG = pg_config ifdef NO_PGXS top_builddir = ../.. @@ -14,26 +19,18 @@ PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) endif -# We need to do various things with various versions of PostgreSQL. -VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') -PGVER_MAJOR = $(shell echo $(VERSION) | awk -F. '{ print ($$1 + 0) }') -PGVER_MINOR = $(shell echo $(VERSION) | awk -F. '{ print ($$2 + 0) }') -PGVER_PATCH = $(shell echo $(VERSION) | awk -F. '{ print ($$3 + 0) }') -PGTAP_VERSION = 0.26 +# We need to do various things with the PostgreSQLl version. +VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') # We support 8.0 and later. -ifneq ($(PGVER_MAJOR), 8) -ifneq ($(PGVER_MAJOR), 9) +ifeq ($(shell echo $(VERSION) | grep -qE " 7\." && echo yes || echo no),yes) $(error pgTAP requires PostgreSQL 8.0 or later. This is $(VERSION)) endif -endif # Compile the C code only if we're on 8.3 or older. -ifeq ($(PGVER_MAJOR), 8) -ifneq ($(PGVER_MINOR), 4) +ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[0123]" && echo yes || echo no),yes) MODULES = src/pgtap endif -endif # We need Perl. ifndef PERL @@ -59,28 +56,38 @@ ifndef HAVE_HARNESS $(warning cpan TAP::Parser::SourceHandler::pgTAP) endif -# Set up extra substitutions based on version numbers. -ifeq ($(PGVER_MAJOR), 8) -ifeq ($(PGVER_MINOR), 2) # Enum tests not supported by 8.2 and earlier. -TESTS := $(filter-out sql/enumtap.sql,$(TESTS)) +ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[012]" && echo yes || echo no),yes) +TESTS := $(filter-out sql/enumtap.sql,$(TESTS)) REGRESS := $(filter-out enumtap,$(REGRESS)) endif -ifeq ($(PGVER_MINOR), 1) + # Values tests not supported by 8.1 and earlier. -TESTS := $(filter-out sql/enumtap.sql sql/valueset.sql,$(TESTS)) +ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[01]" && echo yes || echo no),yes) +TESTS := $(filter-out sql/enumtap.sql sql/valueset.sql,$(TESTS)) REGRESS := $(filter-out enumtap valueset,$(REGRESS)) endif -ifeq ($(PGVER_MINOR), 0) -# Throw, runtests, enums, and roles aren't supported in 8.0. -TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql sql/enumtap.sql sql/roletap.sql sql/valueset.sql,$(TESTS)) -REGRESS := $(filter-out throwtap runtests enumtap roletap valueset,$(REGRESS)) -endif + +# Throw, runtests, and roles aren't supported in 8.0. +ifeq ($(shell echo $(VERSION) | grep -qE " 8\.0" && echo yes || echo no),yes) +TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql sql/roletap.sql,$(TESTS)) +REGRESS := $(filter-out throwtap runtests roletap,$(REGRESS)) endif # Determine the OS. Borrowed from Perl's Configure. OSNAME := $(shell ./getos.sh) +# Add extension build targets on 9.1 and up. +ifeq ($(shell $(PG_CONFIG) --version | grep -qE " 8\.| 9\.0" && echo no || echo yes),yes) +all: sql/$(EXTENSION)--$(EXTVERSION).sql + +sql/$(EXTENSION)--$(EXTVERSION).sql: sql/$(EXTENSION).sql + cp $< $@ + +DATA = $(wildcard sql/*--*.sql) sql/$(EXTENSION)--$(EXTVERSION).sql +EXTRA_CLEAN += sql/$(EXTENSION)--$(EXTVERSION).sql +endif + # Override how .sql targets are processed to add the schema info, if # necessary. Otherwise just copy the files. test/setup.sql: test/setup.sql.in @@ -92,48 +99,40 @@ endif sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql cp $< $@ -ifeq ($(PGVER_MAJOR), 8) -ifneq ($(PGVER_MINOR), 5) -ifneq ($(PGVER_MINOR), 4) +ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[0123]" && echo yes || echo no),yes) patch -p0 < compat/install-8.3.patch -ifneq ($(PGVER_MINOR), 3) +endif +ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[012]" && echo yes || echo no),yes) patch -p0 < compat/install-8.2.patch -ifneq ($(PGVER_MINOR), 2) +endif +ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[01]" && echo yes || echo no),yes) patch -p0 < compat/install-8.1.patch -ifneq ($(PGVER_MINOR), 1) +endif +ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[0]" && echo yes || echo no),yes) patch -p0 < compat/install-8.0.patch # Hack for E'' syntax (<= PG8.0) mv sql/pgtap.sql sql/pgtap.tmp sed -e "s/ E'/ '/g" sql/pgtap.tmp > sql/pgtap.sql rm sql/pgtap.tmp endif -endif -endif -endif -endif -endif ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' sql/pgtap.sql > sql/pgtap.tmp + sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap.sql > sql/pgtap.tmp else - sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' sql/pgtap.sql > sql/pgtap.tmp + sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap.sql > sql/pgtap.tmp endif mv sql/pgtap.tmp sql/pgtap.sql sql/uninstall_pgtap.sql: sql/uninstall_pgtap.sql.in test/setup.sql cp sql/uninstall_pgtap.sql.in sql/uninstall_pgtap.sql -ifeq ($(PGVER_MAJOR), 8) -ifneq ($(PGVER_MINOR), 5) -ifneq ($(PGVER_MINOR), 4) +ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[0123]" && echo yes || echo no),yes) patch -p0 < compat/uninstall-8.3.patch -ifneq ($(PGVER_MINOR), 3) +endif +ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[012]" && echo yes || echo no),yes) patch -p0 < compat/uninstall-8.2.patch endif -ifeq ($(PGVER_MINOR), 0) +ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[0]" && echo yes || echo no),yes) patch -p0 < compat/uninstall-8.0.patch endif -endif -endif -endif ifdef TAPSCHEMA sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' sql/uninstall_pgtap.sql > sql/uninstall.tmp mv sql/uninstall.tmp sql/uninstall_pgtap.sql @@ -142,14 +141,14 @@ endif sql/pgtap-core.sql: sql/pgtap.sql.in cp $< $@ sed -e 's,sql/pgtap,sql/pgtap-core,g' compat/install-8.3.patch | patch -p0 - sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' sql/pgtap-core.sql > sql/pgtap-core.tmp + sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap-core.sql > sql/pgtap-core.tmp $(PERL) compat/gencore 0 sql/pgtap-core.tmp > sql/pgtap-core.sql rm sql/pgtap-core.tmp sql/pgtap-schema.sql: sql/pgtap.sql.in cp $< $@ sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-8.3.patch | patch -p0 - sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(PGTAP_VERSION),g' sql/pgtap-schema.sql > sql/pgtap-schema.tmp + sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap-schema.sql > sql/pgtap-schema.tmp $(PERL) compat/gencore 1 sql/pgtap-schema.tmp > sql/pgtap-schema.sql rm sql/pgtap-schema.tmp diff --git a/README.md b/README.md index 32acc5e7ebb7..dfe1c352d4f8 100644 --- a/README.md +++ b/README.md @@ -57,13 +57,27 @@ You need to run the test suite using a super user, such as the default make installcheck PGUSER=postgres -If you want to schema-qualify pgTAP (that is, install all of its functions -into their own schema), set the `$TAPSCHEMA` variable to the name of the -schema you'd like to be created, for example: +Once pgTAP is installed, you can add it to a database. If you're running +PostgreSQL 9.1.0 or greater, it's a simple as connecting to a database as a +super user and running: - make TAPSCHEMA=tap - make install - make installcheck + CREATE EXTENSION pgtap; + +If you've upgraded your cluster to PostgreSQL 9.1 and already had pgTAP +installed, you can upgrade it to a properly packaged extension with: + + CREATE EXTENSION pgtap FROM unpackaged; + +For versions of PostgreSQL less than 9.1.0, you'll need to run the +installation script: + + psql -d mydb -f /path/to/pgsql/share/contrib/pgtap.sql + +If you want to install pgTAP and all of its supporting objects into a +specific schema, use the `PGOPTIONS` environment variable to specify the +schema, like so: + + PGOPTIONS=--search_path=extensions psql -d mydb -f pgTAP.sql Dependencies ------------ diff --git a/sql/pgtap--unpackaged--0.26.0.sql b/sql/pgtap--unpackaged--0.26.0.sql new file mode 100644 index 000000000000..d2762cac1a67 --- /dev/null +++ b/sql/pgtap--unpackaged--0.26.0.sql @@ -0,0 +1,707 @@ +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 _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[] ); diff --git a/sql/uninstall_pgtap.sql.in b/sql/uninstall_pgtap.sql.in index fb72d22b457f..1959c02b9860 100644 --- a/sql/uninstall_pgtap.sql.in +++ b/sql/uninstall_pgtap.sql.in @@ -127,6 +127,7 @@ DROP FUNCTION runtests( TEXT ); DROP FUNCTION runtests( NAME ); DROP FUNCTION runtests( NAME, TEXT ); DROP FUNCTION _runner( text[], text[], text[], text[], text[] ); +DROP FUNCTION diag_test_name(TEXT); DROP FUNCTION _cleanup(); DROP FUNCTION _currtest(); DROP FUNCTION do_tap( ); @@ -705,6 +706,5 @@ DROP FUNCTION pgtap_version(); DROP FUNCTION os_name(); DROP FUNCTION pg_version_num(); DROP FUNCTION pg_version(); -DROP FUNCTION diag_test_name(TEXT) -- ## SET search_path TO public; -- ## DROP SCHEMA TAPSCHEMA; From b8c7cb2c1f0432a354ff84fa5064eeae0c1942cf Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 23 Aug 2011 20:12:33 -0700 Subject: [PATCH 0608/1195] Remove TAPSCHEMA option. --- Changes | 2 ++ Makefile | 17 ----------------- README.md | 2 +- compat/install-8.2.patch | 4 ++-- compat/uninstall-8.0.patch | 4 +--- compat/uninstall-8.2.patch | 4 +--- contrib/pgtap.spec | 9 +++++++-- doc/pgtap.md | 26 ++++++++++++++++++++------ sql/pgtap.sql.in | 3 --- sql/uninstall_pgtap.sql.in | 3 --- test/{setup.sql.in => setup.sql} | 2 -- 11 files changed, 34 insertions(+), 42 deletions(-) rename test/{setup.sql.in => setup.sql} (86%) diff --git a/Changes b/Changes index 417a0e6f1bee..3bd6355f6cc4 100644 --- a/Changes +++ b/Changes @@ -12,6 +12,8 @@ Revision history for pgTAP * Removed use of `relistemp` column in an internal function, as it has been removed in PostgreSQL 9.1. Thanks to Tom Lane for the alternate syntax. * Added PostreSQL 9.1 `CREATE EXTENSION` support. +* Removed `TAPSCHEMA` option to `make`. Use `PGOPTIONS=--search_path=tap` with + `psql`, instead. 0.25.0 2011-02-02T03:21:55 -------------------------- diff --git a/Makefile b/Makefile index df76c6f858c5..3755071acd5f 100644 --- a/Makefile +++ b/Makefile @@ -88,15 +88,6 @@ DATA = $(wildcard sql/*--*.sql) sql/$(EXTENSION)--$(EXTVERSION).sql EXTRA_CLEAN += sql/$(EXTENSION)--$(EXTVERSION).sql endif -# Override how .sql targets are processed to add the schema info, if -# necessary. Otherwise just copy the files. -test/setup.sql: test/setup.sql.in -ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' $< >$@ -else - cp $< $@ -endif - sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql cp $< $@ ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[0123]" && echo yes || echo no),yes) @@ -115,11 +106,7 @@ ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[0]" && echo yes || echo no),yes) sed -e "s/ E'/ '/g" sql/pgtap.tmp > sql/pgtap.sql rm sql/pgtap.tmp endif -ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap.sql > sql/pgtap.tmp -else sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap.sql > sql/pgtap.tmp -endif mv sql/pgtap.tmp sql/pgtap.sql sql/uninstall_pgtap.sql: sql/uninstall_pgtap.sql.in test/setup.sql @@ -133,10 +120,6 @@ endif ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[0]" && echo yes || echo no),yes) patch -p0 < compat/uninstall-8.0.patch endif -ifdef TAPSCHEMA - sed -e 's,TAPSCHEMA,$(TAPSCHEMA),g' -e 's/^-- ## //g' sql/uninstall_pgtap.sql > sql/uninstall.tmp - mv sql/uninstall.tmp sql/uninstall_pgtap.sql -endif sql/pgtap-core.sql: sql/pgtap.sql.in cp $< $@ diff --git a/README.md b/README.md index dfe1c352d4f8..3a2e71fb604b 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ If you want to install pgTAP and all of its supporting objects into a specific schema, use the `PGOPTIONS` environment variable to specify the schema, like so: - PGOPTIONS=--search_path=extensions psql -d mydb -f pgTAP.sql + PGOPTIONS=--search_path=tap psql -d mydb -f pgTAP.sql Dependencies ------------ diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 25e879fdc4a3..b2e662b62e1d 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,8 +1,8 @@ --- sql/pgtap.sql 2011-02-01 15:15:23.000000000 -0800 +++ sql/pgtap.sql.orig 2011-02-01 15:14:35.000000000 -0800 @@ -11,6 +11,59 @@ - -- ## CREATE SCHEMA TAPSCHEMA; - -- ## SET search_path TO TAPSCHEMA, public; + -- + -- http://pgtap.org/ +-- Cast booleans to text like 8.3 does. +CREATE OR REPLACE FUNCTION booltext(boolean) diff --git a/compat/uninstall-8.0.patch b/compat/uninstall-8.0.patch index fac58f9581c2..47eda963e2fa 100644 --- a/compat/uninstall-8.0.patch +++ b/compat/uninstall-8.0.patch @@ -12,7 +12,7 @@ DROP FUNCTION hasnt_enum( NAME ); DROP FUNCTION hasnt_enum( NAME, TEXT ); DROP FUNCTION hasnt_enum( NAME, NAME ); -@@ -710,5 +705,9 @@ +@@ -710,3 +705,7 @@ DROP FUNCTION textarray_text(text[]); DROP CAST (boolean AS char(1)); DROP FUNCTION booltext(boolean); @@ -20,5 +20,3 @@ +DROP FUNCTION int2vint(int2vector); +DROP CAST (oidvector AS regtype[]); +DROP FUNCTION oidvregtype(oidvector); - -- ## SET search_path TO public; - -- ## DROP SCHEMA TAPSCHEMA; diff --git a/compat/uninstall-8.2.patch b/compat/uninstall-8.2.patch index 77a2ba9c31f7..1288fac03d51 100644 --- a/compat/uninstall-8.2.patch +++ b/compat/uninstall-8.2.patch @@ -21,7 +21,7 @@ DROP FUNCTION diag ( msg text ); DROP FUNCTION finish (); DROP FUNCTION _finish ( INTEGER, INTEGER, INTEGER); -@@ -705,5 +698,17 @@ +@@ -705,3 +698,15 @@ DROP FUNCTION os_name(); DROP FUNCTION pg_version_num(); DROP FUNCTION pg_version(); @@ -37,5 +37,3 @@ +DROP FUNCTION textarray_text(text[]); +DROP CAST (boolean AS char(1)); +DROP FUNCTION booltext(boolean); - -- ## SET search_path TO public; - -- ## DROP SCHEMA TAPSCHEMA; diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index e2d486403925..6faf90169d2d 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -26,7 +26,7 @@ BuildArch: noarch %setup -q %build -make USE_PGXS=1 TAPSCHEMA=tap +make %install %{__rm} -rf %{buildroot} @@ -59,4 +59,9 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} * Wed Aug 19 2009 Darrell Fuhriman 0.22-1 - initial RPM - + +* Tue Aug 23 2011 David Wheeler 0.26.0 +- Removed USE_PGXS from Makefile; it has not been supported in some time. +- Removed TAPSCHEMA from Makefile; use PGOPTIONS=--search_path=tap with + psql instead. + diff --git a/doc/pgtap.md b/doc/pgtap.md index 0a1d315d9307..55fcc431ad47 100644 --- a/doc/pgtap.md +++ b/doc/pgtap.md @@ -97,13 +97,27 @@ You need to run the test suite using a super user, such as the default make installcheck PGUSER=postgres -If you want to schema-qualify pgTAP (that is, install all of its functions -into their own schema), set the `$TAPSCHEMA` variable to the name of the -schema you'd like to be created, for example: +Once pgTAP is installed, you can add it to a database. If you're running +PostgreSQL 9.1.0 or greater, it's a simple as connecting to a database as a +super user and running: - make TAPSCHEMA=tap - make install - make installcheck + CREATE EXTENSION pgtap; + +If you've upgraded your cluster to PostgreSQL 9.1 and already had pgTAP +installed, you can upgrade it to a properly packaged extension with: + + CREATE EXTENSION pgtap FROM unpackaged; + +For versions of PostgreSQL less than 9.1.0, you'll need to run the +installation script: + + psql -d mydb -f /path/to/pgsql/share/contrib/pgtap.sql + +If you want to install pgTAP and all of its supporting objects into a +specific schema, use the `PGOPTIONS` environment variable to specify the +schema, like so: + + PGOPTIONS=--search_path=tap psql -d mydb -f pgTAP.sql Testing pgTAP with pgTAP ------------------------ diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 75010a3c184a..685396e59ab1 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -8,9 +8,6 @@ -- -- http://pgtap.org/ --- ## CREATE SCHEMA TAPSCHEMA; --- ## SET search_path TO TAPSCHEMA, public; - CREATE OR REPLACE FUNCTION pg_version() RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; diff --git a/sql/uninstall_pgtap.sql.in b/sql/uninstall_pgtap.sql.in index 1959c02b9860..b00ea6000771 100644 --- a/sql/uninstall_pgtap.sql.in +++ b/sql/uninstall_pgtap.sql.in @@ -1,4 +1,3 @@ --- ## SET search_path TO TAPSCHEMA, public; DROP FUNCTION columns_are( NAME, NAME[] ); DROP FUNCTION columns_are( NAME, NAME[], TEXT ); DROP FUNCTION columns_are( NAME, NAME, NAME[] ); @@ -706,5 +705,3 @@ DROP FUNCTION pgtap_version(); DROP FUNCTION os_name(); DROP FUNCTION pg_version_num(); DROP FUNCTION pg_version(); --- ## SET search_path TO public; --- ## DROP SCHEMA TAPSCHEMA; diff --git a/test/setup.sql.in b/test/setup.sql similarity index 86% rename from test/setup.sql.in rename to test/setup.sql index f2ea7ae7d53a..aa231f054007 100644 --- a/test/setup.sql.in +++ b/test/setup.sql @@ -16,5 +16,3 @@ -- Load the TAP functions. BEGIN; \i sql/pgtap.sql - --- ## SET search_path TO TAPSCHEMA,public; From 33a20742b1080caf369b2a2ed53b8c86d7d833aa Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 24 Aug 2011 16:30:13 -0700 Subject: [PATCH 0609/1195] Get working on 9.0 again. Also, don't remove `test/setup.sql` during `make clean`, but do remove `sql/pgtap.sql` and `sql/uninstall_pgtap.sql`. --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3755071acd5f..87d99ac63137 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ EXTVERSION = $(shell grep default_version $(EXTENSION).control | \ NUMVERSION = $(shell echo $(EXTVERSION) | sed -e 's/\([[:digit:]]*[.][[:digit:]]*\).*/\1/') DATA = $(filter-out $(wildcard sql/*--*.sql),$(wildcard sql/*.sql)) TESTS = $(wildcard test/sql/*.sql) -EXTRA_CLEAN = test/setup.sql doc/*.html +EXTRA_CLEAN = sql/pgtap.sql sql/uninstall_pgtap.sql doc/*.html DOCS = doc/pgtap.md REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --inputdir=test --load-language=plpgsql @@ -77,6 +77,9 @@ endif # Determine the OS. Borrowed from Perl's Configure. OSNAME := $(shell ./getos.sh) +# Make sure we build these. +all: sql/pgtap.sql sql/uninstall_pgtap.sql + # Add extension build targets on 9.1 and up. ifeq ($(shell $(PG_CONFIG) --version | grep -qE " 8\.| 9\.0" && echo no || echo yes),yes) all: sql/$(EXTENSION)--$(EXTVERSION).sql From 634700eafca3d68587dcfaf400ed95f1501504c7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 24 Aug 2011 21:51:34 -0700 Subject: [PATCH 0610/1195] Add `db_owner_is()`. Closes Issue #5. --- Changes | 1 + doc/pgtap.md | 28 ++++++++++++++++++- sql/pgtap--unpackaged--0.26.0.sql | 3 +++ sql/pgtap.sql.in | 34 +++++++++++++++++++++++ sql/uninstall_pgtap.sql.in | 3 +++ test/expected/ownership.out | 14 ++++++++++ test/sql/ownership.sql | 45 +++++++++++++++++++++++++++++++ 7 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 test/expected/ownership.out create mode 100644 test/sql/ownership.sql diff --git a/Changes b/Changes index 3bd6355f6cc4..3e723b06c078 100644 --- a/Changes +++ b/Changes @@ -14,6 +14,7 @@ Revision history for pgTAP * Added PostreSQL 9.1 `CREATE EXTENSION` support. * Removed `TAPSCHEMA` option to `make`. Use `PGOPTIONS=--search_path=tap` with `psql`, instead. +* Added `db_owner_is()`. Based on a patch by Gerd Koenig. 0.25.0 2011-02-02T03:21:55 -------------------------- diff --git a/doc/pgtap.md b/doc/pgtap.md index 55fcc431ad47..86f4f3727b22 100644 --- a/doc/pgtap.md +++ b/doc/pgtap.md @@ -3701,6 +3701,33 @@ If the rule in question does not exist, you'll be told that, too: But then you run `has_rule()` first, don't you? +Who owns me? +------------ + +After testing the availability of several objects, we often need to know who +owns an object. + +### `db_owner_is (dbname, user, description)` ### +### `db_owner_is (dbname, user)` ### + + SELECT db_owner_is( 'mydb', 'someuser', 'mydb should be owned by someuser'); + SELECT db_owner_is( current_database(), current_user); + +Tests the ownership of the database. If the `:description` argument is +omitted, an appropriate description will be created. In the event that the +test fails because the database in question does not actually exist, you will +see an appropriate diagnostic such as: + + # Failed test 16: "Database foo should be owned by www" + # Database foo does not exist + +If the test fails because the database is not owned by the specified user, the +diagnostics will look something like: + + # Failed test 17: "Database bar should be owned by root" + # have: postgres + # want: root + No Test for the Wicked ====================== @@ -4401,7 +4428,6 @@ To Do (`table_has_column()`, `col_type_is()`, etc.). That is, allow the description to be optional if the schema is included in the function call. * Add functions to test for object ownership. - + `db_owner_is()` + `table_owner_is()` + `view_owner_is()` + `sequence_owner_is()` diff --git a/sql/pgtap--unpackaged--0.26.0.sql b/sql/pgtap--unpackaged--0.26.0.sql index d2762cac1a67..fc2a7e62fc08 100644 --- a/sql/pgtap--unpackaged--0.26.0.sql +++ b/sql/pgtap--unpackaged--0.26.0.sql @@ -705,3 +705,6 @@ 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 ); diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 685396e59ab1..0c4b8f2cebee 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7363,3 +7363,37 @@ 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; + diff --git a/sql/uninstall_pgtap.sql.in b/sql/uninstall_pgtap.sql.in index b00ea6000771..36a8b5c40606 100644 --- a/sql/uninstall_pgtap.sql.in +++ b/sql/uninstall_pgtap.sql.in @@ -1,3 +1,6 @@ +DROP FUNCTION db_owner_is ( NAME, NAME ); +DROP FUNCTION db_owner_is ( NAME, NAME, TEXT ); +DROP FUNCTION _get_db_owner( NAME ); DROP FUNCTION columns_are( NAME, NAME[] ); DROP FUNCTION columns_are( NAME, NAME[], TEXT ); DROP FUNCTION columns_are( NAME, NAME, NAME[] ); diff --git a/test/expected/ownership.out b/test/expected/ownership.out new file mode 100644 index 000000000000..188b35699222 --- /dev/null +++ b/test/expected/ownership.out @@ -0,0 +1,14 @@ +\unset ECHO +1..12 +ok 1 - db_owner_is(db, user, desc) should pass +ok 2 - db_owner_is(db, user, desc) should have the proper description +ok 3 - db_owner_is(db, user, desc) should have the proper diagnostics +ok 4 - db_owner_is(db, user) should pass +ok 5 - db_owner_is(db, user) should have the proper description +ok 6 - db_owner_is(db, user) should have the proper diagnostics +ok 7 - db_owner_is(non-db, user) should fail +ok 8 - db_owner_is(non-db, user) should have the proper description +ok 9 - db_owner_is(non-db, user) should have the proper diagnostics +ok 10 - db_owner_is(db, non-user) should fail +ok 11 - db_owner_is(db, non-user) should have the proper description +ok 12 - db_owner_is(db, non-user) should have the proper diagnostics diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql new file mode 100644 index 000000000000..15d515bb7434 --- /dev/null +++ b/test/sql/ownership.sql @@ -0,0 +1,45 @@ +\unset ECHO +\i test/setup.sql + +SELECT plan(12); +--SELECT * FROM no_plan(); + +/****************************************************************************/ +SELECT * From check_test( + db_owner_is(current_database(), current_user, 'mumble'), + true, + 'db_owner_is(db, user, desc)', + 'mumble', + '' +); + +SELECT * From check_test( + db_owner_is(current_database(), current_user), + true, + 'db_owner_is(db, user)', + 'Database ' || quote_ident(current_database()) || ' should be owned by ' || current_user, + '' +); + +SELECT * From check_test( + db_owner_is('__not__' || current_database(), current_user, 'mumble'), + false, + 'db_owner_is(non-db, user)', + 'mumble', + ' Database __not__' || current_database() || ' does not exist' +); + +SELECT * From check_test( + db_owner_is(current_database(), '__not__' || current_user, 'mumble'), + false, + 'db_owner_is(db, non-user)', + 'mumble', + ' have: ' || current_user || ' + want: __not__' || current_user +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; + From 989e28d16116e77ca3f58471bf979f46bf8efd74 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 24 Aug 2011 22:07:25 -0700 Subject: [PATCH 0611/1195] Simplify check for quoted string. Also update expected test output for new tests. --- sql/pgtap.sql.in | 5 ++--- test/expected/util.out | 7 ++++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 3f9ab89383ec..492b20e7825d 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1157,9 +1157,8 @@ BEGIN -- Just return it if rhs isn't quoted. IF $2 !~ '"' THEN RETURN $1; END IF; - -- If it's quoted ident without precision, return it. - -- We need to care about, because "test type(123)" is distinct from "test type"(123) - IF substring($2 FROM char_length($2) FOR 1) = '"' THEN RETURN quote_ident($1); END IF; + -- If it's quoted ident without precision, return it quoted. + IF $2 ~ '"$' THEN RETURN quote_ident($1); END IF; pcision := substring($1 FROM '[(][^")]+[)]$'); diff --git a/test/expected/util.out b/test/expected/util.out index 47af45b547fe..03a39fcff008 100644 --- a/test/expected/util.out +++ b/test/expected/util.out @@ -1,5 +1,5 @@ \unset ECHO -1..30 +1..35 ok 1 - pg_type(int) should work ok 2 - pg_type(numeric) should work ok 3 - pg_type(text) should work @@ -30,3 +30,8 @@ ok 27 - display_type(__foo."this.that") ok 28 - display_type(__foo."this"".that") ok 29 - display_type(__foo."hey"".yoman", 13) ok 30 - display_type("try.this""", 42) +ok 31 - No quoting is required +ok 32 - Just quote +ok 33 - No quoting is required (with precision) +ok 34 - Quote as type with precision +ok 35 - Quote as ident without precision From c20a0adec18f8b7ef982be405bf427df42d6bb3a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 24 Aug 2011 22:46:09 -0700 Subject: [PATCH 0612/1195] Start simplifiying document headers. There are way too many `h3` headers. Some functions have many different signaures. Instead, have one header for each function name, and then list the parameters as a definition list after the synopsis. This keeps the signatures explicit while minimizing the number (and length!) of the `h3` headers. To make this work, the documentation must be Multimarkdown, as Markdown does not support definition lists. --- Makefile | 4 +- README.md | 2 +- doc/{pgtap.md => pgtap.mmd} | 205 ++++++++++++++++++++++++++---------- 3 files changed, 153 insertions(+), 58 deletions(-) rename doc/{pgtap.md => pgtap.mmd} (98%) diff --git a/Makefile b/Makefile index 87d99ac63137..21e13d120039 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ NUMVERSION = $(shell echo $(EXTVERSION) | sed -e 's/\([[:digit:]]*[.][[:digit: DATA = $(filter-out $(wildcard sql/*--*.sql),$(wildcard sql/*.sql)) TESTS = $(wildcard test/sql/*.sql) EXTRA_CLEAN = sql/pgtap.sql sql/uninstall_pgtap.sql doc/*.html -DOCS = doc/pgtap.md +DOCS = doc/pgtap.mmd REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --inputdir=test --load-language=plpgsql PG_CONFIG = pg_config @@ -146,7 +146,7 @@ test: test/setup.sql pg_prove --pset tuples_only=1 $(TESTS) html: - markdown -ftoc doc/pgtap.md > doc/pgtap.html + MultiMarkdown.pl doc/pgtap.mmd > doc/pgtap.html ./tocgen doc/pgtap.html 2> doc/toc.html perl -MPod::Simple::XHTML -E "my \$$p = Pod::Simple::XHTML->new; \$$p->html_header_tags(''); \$$p->strip_verbatim_indent(sub { (my \$$i = \$$_[0]->[0]) =~ s/\\S.*//; \$$i }); \$$p->parse_from_file('`perldoc -l pg_prove`')" > doc/pg_prove.html diff --git a/README.md b/README.md index 3a2e71fb604b..47761cf59c27 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ in PL/pgSQL and PL/SQL. It includes a comprehensive collection of [TAP](http://testanything.org)-emitting assertion functions, as well as the ability to integrate with other TAP-emitting test frameworks. It can also be used in the xUnit testing style. For detailed documentation, see the -documentation in `doc/pgtap.md` or +documentation in `doc/pgtap.mmd` or [online](http://pgtap.org/documentation.html "Complete pgTAP Documentation"). To build it, just do this: diff --git a/doc/pgtap.md b/doc/pgtap.mmd similarity index 98% rename from doc/pgtap.md rename to doc/pgtap.mmd index 86f4f3727b22..7e0fa6cb8ba5 100644 --- a/doc/pgtap.md +++ b/doc/pgtap.mmd @@ -371,13 +371,18 @@ that you use it. Sometimes it's useful to extract test function names from pgtap output, especially when using xUnit style with Continuous Integration Server like Hudson or TeamCity. By default pgTAP displays this names as "comment", but you're able to change this behavior by overriding function `diag_test_name`: -### `diag_test_name( test_name )` ### +### `diag_test_name()` ### CREATE OR REPLACE FUNCTION diag_test_name(TEXT) RETURNS TEXT AS $$ SELECT diag('test: ' || $1 ); $$ LANGUAGE SQL; +**Parameters** + +`:test_name` +: A test name. + This will show # test: my_example_test_function_name instead of @@ -394,11 +399,18 @@ given test succeeded or failed. Everything else is just gravy. All of the following functions return "ok" or "not ok" depending on whether the test succeeded or failed. -### `ok( boolean, description )` ### -### `ok( boolean )` ### +### `ok()` ### SELECT ok( :this = :that, :description ); +**Parameters** + +`:boolean` +: A boolean value indicating success or failure. + +`:description` +: A short description of the test. + This function simply evaluates any expression (`:this = :that` is just a simple example) and uses that to determine if the test succeeded or failed. A true expression passes, a false one fails. Very simple. @@ -431,13 +443,22 @@ additional diagnostic: # Failed test 18: "sufficient mucus" # (test result was NULL) -### `is( anyelement, anyelement, description )` ### -### `is( anyelement, anyelement )` ### -### `isnt( anyelement, anyelement, description )` ### -### `isnt( anyelement, anyelement )` ### +### `is()` ### +### `isnt()` ### - SELECT is( :this, :that, :description ); - SELECT isnt( :this, :that, :description ); + SELECT is( :have, :want, :description ); + SELECT isnt( :have, :want, :description ); + +**Parameters** + +`:have` +: Value to test. + +`:want` +: Value that `:have` is expcted to be. Must be the same data type. + +`:description` +: A short description of the test. Similar to `ok()`, `is()` and `isnt()` compare their two arguments with `IS NOT DISTINCT FROM` (`=`) AND `IS DISTINCT FROM` (`<>`) respectively and use @@ -457,8 +478,8 @@ are similar to these: (Mnemonic: "This is that." "This isn't that.") *Note:* Thanks to the use of the `IS [ NOT ] DISTINCT FROM` construct, `NULL`s -are not treated as unknowns by `is()` or `isnt()`. That is, if `:this` and -`:that` are both `NULL`, the test will pass, and if only one of them is +are not treated as unknowns by `is()` or `isnt()`. That is, if `:have` and +`:want` are both `NULL`, the test will pass, and if only one of them is `NULL`, the test will fail. So why use these test functions? They produce better diagnostics on failure. @@ -485,12 +506,22 @@ can even use them to compar records in PostgreSQL 8.4 and later: FROM users WHERE nick = 'theory'; -### `matches( anyelement, regex, description )` ### -### `matches( anyelement, regex )` ### +### `matches()` ### + + SELECT matches( :have, :regex, :description ); - SELECT matches( :this, '^that', :description ); +**Parameters** -Similar to `ok()`, `matches()` matches `:this` against the regex `/^that/`. +`:have` +: Value to match. + +`:regex` +: A regular expression. + +`:description` +: A short description of the test. + +Similar to `ok()`, `matches()` matches `:have` against the regex `:regex`. So this: @@ -505,76 +536,127 @@ is similar to: Its advantages over `ok()` are similar to that of `is()` and `isnt()`: Better diagnostics on failure. -### `imatches( anyelement, regex, description )` ### -### `imatches( anyelement, regex )` ### +### `imatches()` ### + + SELECT imatches( :have, :regex, :description ); + +**Parameters** + +`:have` +: Value to match. + +`:regex` +: A regular expression. + +`:description` +: A short description of the test. + +Just like `matches()` except that the regular expression is compared to +`:have` case-insensitively. + +### `doesnt_match()` ### +### `doesnt_imatch()` ### - SELECT imatches( :this, '^that', :description ); + SELECT doesnt_match( :have, :regex, :description ); -These are just like `matches()` except that the regular expression is compared -to `:this` case-insensitively. +**Parameters** -### `doesnt_match( anyelement, regex, description )` ### -### `doesnt_match( anyelement, regex )` ### -### `doesnt_imatch( anyelement, regex, description )` ### -### `doesnt_imatch( anyelement, regex )` ### +`:have` +: Value to match. - SELECT doesnt_match( :this, '^that', :description ); +`:regex` +: A regular expression. + +`:description` +: A short description of the test. These functions work exactly as `matches()` and `imatches()` do, only they -check if `:this` *does not* match the given pattern. +check if `:have` *does not* match the given pattern. + +### `alike()` ### +### `ialike()` ### + + SELECT alike( :this, :like, :description ); + +**Parameters** + +`:have` +: Value to match. -### `alike( anyelement, pattern, description )` ### -### `alike( anyelement, pattern )` ### -### `ialike( anyelement, pattern, description )` ### -### `ialike( anyelement, pattern )` ### +`:like` +: A SQL `LIKE` pattern. - SELECT alike( :this, 'that%', :description ); +`:description` +: A short description of the test. -Similar to `matches()`, `alike()` matches `:this` against the SQL `LIKE` -pattern 'that%'. `ialike()` matches case-insensitively. +Similar to `matches()`, `alike()` matches `:hve` against the SQL `LIKE` +pattern `:like`. `ialike()` matches case-insensitively. So this: - SELECT ialike( :this, 'that%', 'this is alike that' ); + SELECT ialike( :have, 'that%', 'this is alike that' ); is similar to: - SELECT ok( :this ILIKE 'that%', 'this is like that' ); + SELECT ok( :have ILIKE 'that%', 'this is like that' ); (Mnemonic "This is like that".) Its advantages over `ok()` are similar to that of `is()` and `isnt()`: Better diagnostics on failure. -### `unalike( anyelement, pattern, description )` ### -### `unalike( anyelement, pattern )` ### -### `unialike( anyelement, pattern, description )` ### -### `unialike( anyelement, pattern )` ### +### `unalike()` ### +### `unialike()` ### - SELECT unalike( :this, 'that%', :description ); + SELECT unalike( :this, :like, :description ); -Works exactly as `alike()`, only it checks if `:this` *does not* match the +**Parameters** + +`:have` +: Value to match. + +`:like` +: A SQL `LIKE` pattern. + +`:description` +: A short description of the test. + +Works exactly as `alike()`, only it checks if `:have` *does not* match the given pattern. ### `cmp_ok( anyelement, operator, anyelement, description )` ### ### `cmp_ok( anyelement, operator, anyelement )` ### - SELECT cmp_ok( :this, :op, :that, :description ); + SELECT cmp_ok( :have, :op, :want, :description ); + +**Parameters** + +`:have` +: Value to compare. + +`:op` +: An SQL operator specified as a string. + +`:want` +: Value to compare to `:have` using the `:op` operator. + +`:description` +: A short description of the test. Halfway between `ok()` and `is()` lies `cmp_ok()`. This function allows you to compare two arguments using any binary operator. - -- ok( :this = :that ); - SELECT cmp_ok( :this, '=', :that, 'this = that' ); + -- ok( :have = :want ); + SELECT cmp_ok( :have, '=', :want, 'this = that' ); - -- ok( :this >= :that ); - SELECT cmp_ok( :this, '>=, 'this >= that' ); + -- ok( :have >= :want ); + SELECT cmp_ok( :have, '>=', :want, 'this >= that' ); - -- ok( :this && :that ); - SELECT cmp_ok( :this, '&&', :that, 'this && that' ); + -- ok( :have && :want ); + SELECT cmp_ok( :have, '&&', :want, 'this && that' ); -Its advantage over `ok()` is that when the test fails you'll know what `:this` -and `:that` were: +Its advantage over `ok()` is that when the test fails you'll know what `:have` +and `:want` were: not ok 1 # Failed test 1: @@ -590,14 +672,17 @@ was, for example: But in that case, you should probably use `is()`, instead. -### `pass( description )` ### ### `pass()` ### -### `fail( description )` ### ### `fail()` ### SELECT pass( :description ); SELECT fail( :description ); +**Parameters** + +`:description` +: A short description of the test. + Sometimes you just want to say that the tests have passed. Usually the case is you've got some complicated condition that is difficult to wedge into an `ok()`. In this case, you can simply use `pass()` (to declare the test ok) or @@ -605,10 +690,20 @@ you've got some complicated condition that is difficult to wedge into an Use these functions very, very, very sparingly. -### `isa_ok( value, regtype, name )` ### -### `isa_ok( value, regtype )` ### +### `isa_ok()` ### + + SELECT isa_ok( :have, :regtype, :name ); + +**Parameters** + +`:have` +: Value to check the type of. + +`:regtype` +: Name of an SQL data type. - SELECT isa_ok( :value, :regtype, name ); +`:name` +: A name for the value being compared. Checks to see if the given value is of a particular type. The description and diagnostics of this test normally just refer to "the value". If you'd like From bedd6204348f6a2ab69cf39e0f761039a17a7890 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 24 Aug 2011 23:11:56 -0700 Subject: [PATCH 0613/1195] Simplified "To Error is Human" headers. --- doc/pgtap.mmd | 151 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 96 insertions(+), 55 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 7e0fa6cb8ba5..8b87d8fff435 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -812,20 +812,26 @@ Or maybe you want to make sure a query *does not* trigger an error. For such cases, we provide a couple of test functions to make sure your queries are as error-prone as you think they should be. -### `throws_ok( sql, errcode, errmsg, description )` ### -### `throws_ok( sql, errcode, errmsg )` ### -### `throws_ok( sql, errmsg, description )` ### -### `throws_ok( sql, errcode )` ### -### `throws_ok( sql, errmsg )` ### -### `throws_ok( sql )` ### +### `throws_ok()` ### - PREPARE my_thrower AS INSERT INTO try (id) VALUES (1); - SELECT throws_ok( - 'my_thrower', - '23505', - 'duplicate key value violates unique constraint "try_pkey"', - 'We should get a unique violation for a duplicate PK' - ); + SELECT throws_ok( :sql, :errcode, :ermsg, :description); + SELECT throws_ok( :sql, :errcode, :description); + SELECT throws_ok( :sql, :errmsg, :description); + +**Parameters** + +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. + +`:errcode` +: A [PostgreSQL error code](http://www.postgresql.org/docs/current/static/errcodes-appendix.html +"Appendix A. PostgreSQL Error Codes") + +`:errmsg` +: An error message. + +`:description` +: A short description of the test. When you want to make sure that an exception is thrown by PostgreSQL, use `throws_ok()` to test for it. Supported by 8.1 and up. @@ -851,7 +857,16 @@ careful of localized error messages. One trick to get around localized error messages is to pass NULL as the third argument. This allows you to still pass a description as the fourth argument. -The fourth argument is of course a brief test description. +The fourth argument is of course a brief test description. Here's a useful +example: + + PREPARE my_thrower AS INSERT INTO try (id) VALUES (1); + SELECT throws_ok( + 'my_thrower', + '23505', + 'duplicate key value violates unique constraint "try_pkey"', + 'We should get a unique violation for a duplicate PK' + ); For the two- and three-argument forms of `throws_ok()`, if the second argument is exactly five bytes long, it is assumed to be an error code and the optional @@ -869,8 +884,26 @@ example: Idea borrowed from the Test::Exception Perl module. -### `throws_like( query, pattern, description )` ### -### `throws_like( query, pattern )` ### +### `throws_like()` ### +### `throws_ilike()` ### + + SELECT throws_like(:sql, :like, :description); + SELECT throws_ilike(:sql, :like, :description); + +**Parameters** + +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. + +`:like` +: An SQL `LIKE` pattern. + +`:description` +: A short description of the test. + +Like `throws_ok()`, but tests that an exception error message matches an SQL +`LIKE` pattern. Supported by 8.1 and up. The `throws_ilike()` variant matches +case-insensitively. An example: PREPARE my_thrower AS INSERT INTO try (tz) VALUES ('America/Moscow'); SELECT throws_like( @@ -879,9 +912,6 @@ Idea borrowed from the Test::Exception Perl module. 'We should error for invalid time zone' ); -Like `throws_ok()`, but tests that an exception error message matches an SQL -`LIKE` pattern. Supported by 8.1 and up. - A failing `throws_like()` test produces an appropriate diagnostic message. For example: @@ -889,21 +919,26 @@ example: # error message: 'value for domain timezone violates check constraint "tz_check"' # doesn't match: '%"timezone_check"' -### `throws_ilike( query, pattern, description )` ### -### `throws_ilike( query, pattern )` ### - PREPARE my_thrower AS INSERT INTO try (tz) VALUES ('America/Moscow'); - SELECT throws_ilike( - 'my_thrower', - '%"TZ_check"', - 'We should error for invalid time zone' - ); +### `throws_matching()` ### +### `throws_imatching()` ### -Like `throws_like()`, but case-insensitively compares the exception message to -the SQL `LIKE` pattern. + SELECT throws_matching(:sql, :regex, :description); -### `throws_matching( query, regex, description )` ### -### `throws_matching( query, regex )` ### +**Parameters** + +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. + +`:regex` +: A regular expression. + +`:description` +: A short description of the test. + +Like `throws_ok()`, but tests that an exception error message matches a +regular expression. The `throws_imatching()` variant matches +case-insensitively. Supported by 8.1 and up. An example: PREPARE my_thrower AS INSERT INTO try (tz) VALUES ('America/Moscow'); SELECT throws_matching( @@ -912,9 +947,6 @@ the SQL `LIKE` pattern. 'We should error for invalid time zone' ); -Like `throws_ok()`, but tests that an exception error message matches a -regular expression. Supported by 8.1 and up. - A failing `throws_matching()` test produces an appropriate diagnostic message. For example: @@ -922,32 +954,28 @@ example: # error message: 'value for domain timezone violates check constraint "tz_check"' # doesn't match: '.+"timezone_check"' -### `throws_imatching( query, regex, description )` ### -### `throws_imatching( query, regex )` ### +### `lives_ok()` ### - PREPARE my_thrower AS INSERT INTO try (tz) VALUES ('America/Moscow'); - SELECT throws_imatching( - 'my_thrower', - '.+"TZ_check"', - 'We should error for invalid time zone' - ); + SELECT lives_ok(:sql, :description); -Like `throws_matching()`, but case-insensitively compares the exception -message to the regular expression. +**Parameters** -### `lives_ok( query, description )` ### -### `lives_ok( query )` ### +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. - SELECT lives_ok( - 'INSERT INTO try (id) VALUES (1)', - 'We should not get a unique violation for a new PK' - ); +`:description` +: A short description of the test. The inverse of `throws_ok()`, `lives_ok()` ensures that an SQL statement does *not* throw an exception. Supported by 8.1 and up. Pass in the name of a prepared statement or string of SQL code (see the [summary](#Pursuing+Your+Query) for query argument details). The optional -second argument is the test description. +second argument is the test description. An example: + + SELECT lives_ok( + 'INSERT INTO try (id) VALUES (1)', + 'We should not get a unique violation for a new PK' + ); A failing `lives_ok()` test produces an appropriate diagnostic message. For example: @@ -960,6 +988,23 @@ Idea borrowed from the Test::Exception Perl module. ### `performs_ok( sql, milliseconds, description )` ### ### `performs_ok( sql, milliseconds )` ### + SELECT performs_ok( :sql, :milliseconds, :description ); + +**Parameters** + +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. + +`:milliseconds` +: Number of milliseconds. + +`:description` +: A short description of the test. + +This function makes sure that an SQL statement performs well. It does so by +timing its execution and failing if execution takes longer than the specified +number of milliseconds. An example: + PREPARE fast_query AS SELECT id FROM try WHERE name = 'Larry'; SELECT performs_ok( 'fast_query', @@ -967,10 +1012,6 @@ Idea borrowed from the Test::Exception Perl module. 'A select by name should be fast' ); -This function makes sure that an SQL statement performs well. It does so by -timing its execution and failing if execution takes longer than the specified -number of milliseconds. - The first argument should be the name of a prepared statement or a string representing the query to be executed (see the [summary](#Pursuing+Your+Query) for query argument details). `throws_ok()` will use the PL/pgSQL `EXECUTE` From b310a09571be76b7405982f1223d1956a70bbeb7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 8 Sep 2011 17:02:41 -0700 Subject: [PATCH 0614/1195] Remove trailing white space. --- doc/pgtap.mmd | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 8b87d8fff435..6d6c6f9ff72a 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -367,7 +367,7 @@ the test in your script, simply search for "simple exponential". All test functions take a name argument. It's optional, but highly suggested that you use it. - + Sometimes it's useful to extract test function names from pgtap output, especially when using xUnit style with Continuous Integration Server like Hudson or TeamCity. By default pgTAP displays this names as "comment", but you're able to change this behavior by overriding function `diag_test_name`: @@ -504,7 +504,7 @@ can even use them to compar records in PostgreSQL 8.4 and later: SELECT is( users.*, ROW(1, 'theory', true)::users ) FROM users - WHERE nick = 'theory'; + WHERE nick = 'theory'; ### `matches()` ### @@ -814,9 +814,9 @@ error-prone as you think they should be. ### `throws_ok()` ### - SELECT throws_ok( :sql, :errcode, :ermsg, :description); - SELECT throws_ok( :sql, :errcode, :description); - SELECT throws_ok( :sql, :errmsg, :description); + SELECT throws_ok( :sql, :errcode, :ermsg, :description); + SELECT throws_ok( :sql, :errcode, :description); + SELECT throws_ok( :sql, :errmsg, :description); **Parameters** @@ -1112,7 +1112,7 @@ For example, say that you want to compare queries against a `persons` table. The simplest way to sort is by `name`, as in: try=# select * from people order by name; - name | age + name | age --------+----- Damian | 19 Larry | 53 @@ -1123,7 +1123,7 @@ The simplest way to sort is by `name`, as in: But a different run of the same query could have the rows in different order: try=# select * from people order by name; - name | age + name | age --------+----- Damian | 19 Larry | 53 From 09594b383e01ece758c3b57bfc225893667a1716 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 8 Sep 2011 17:22:47 -0700 Subject: [PATCH 0615/1195] Restore examples of all signatures. --- doc/pgtap.mmd | 64 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 6d6c6f9ff72a..7dcbb8b5700a 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -401,7 +401,8 @@ the test succeeded or failed. ### `ok()` ### - SELECT ok( :this = :that, :description ); + SELECT ok( :boolean, :description ); + SELECT ok( :boolean ); **Parameters** @@ -411,9 +412,9 @@ the test succeeded or failed. `:description` : A short description of the test. -This function simply evaluates any expression (`:this = :that` is just a -simple example) and uses that to determine if the test succeeded or failed. A -true expression passes, a false one fails. Very simple. +This function simply evaluates any boolean expression and uses it to determine +if the test succeeded or failed. A true expression passes, a false one fails. +Very simple. For example: @@ -425,7 +426,7 @@ For example: (Mnemonic: "This is ok.") -The `description` is a very short description of the test that will be printed +The `:description` is a very short description of the test that will be printed out. It makes it very easy to find a test in your script when it fails and gives others an idea of your intentions. The description is optional, but we *very* strongly encourage its use. @@ -447,7 +448,9 @@ additional diagnostic: ### `isnt()` ### SELECT is( :have, :want, :description ); + SELECT is( :have, :want ); SELECT isnt( :have, :want, :description ); + SELECT isnt( :have, :want ); **Parameters** @@ -509,6 +512,7 @@ can even use them to compar records in PostgreSQL 8.4 and later: ### `matches()` ### SELECT matches( :have, :regex, :description ); + SELECT matches( :have, :regex ); **Parameters** @@ -539,6 +543,7 @@ diagnostics on failure. ### `imatches()` ### SELECT imatches( :have, :regex, :description ); + SELECT imatches( :have, :regex ); **Parameters** @@ -557,7 +562,10 @@ Just like `matches()` except that the regular expression is compared to ### `doesnt_match()` ### ### `doesnt_imatch()` ### - SELECT doesnt_match( :have, :regex, :description ); + SELECT doesnt_match( :have, :regex, :description ); + SELECT doesnt_match( :have, :regex ); + SELECT doesnt_imatch( :have, :regex, :description ); + SELECT doesnt_imatch( :have, :regex ); **Parameters** @@ -576,7 +584,10 @@ check if `:have` *does not* match the given pattern. ### `alike()` ### ### `ialike()` ### - SELECT alike( :this, :like, :description ); + SELECT alike( :this, :like, :description ); + SELECT alike( :this, :like ); + SELECT ialike( :this, :like, :description ); + SELECT ialike( :this, :like ); **Parameters** @@ -608,7 +619,10 @@ diagnostics on failure. ### `unalike()` ### ### `unialike()` ### - SELECT unalike( :this, :like, :description ); + SELECT unalike( :this, :like, :description ); + SELECT unalike( :this, :like ); + SELECT unialike( :this, :like, :description ); + SELECT unialike( :this, :like ); **Parameters** @@ -624,10 +638,10 @@ diagnostics on failure. Works exactly as `alike()`, only it checks if `:have` *does not* match the given pattern. -### `cmp_ok( anyelement, operator, anyelement, description )` ### -### `cmp_ok( anyelement, operator, anyelement )` ### +### `cmp_ok()` ### SELECT cmp_ok( :have, :op, :want, :description ); + SELECT cmp_ok( :have, :op, :want ); **Parameters** @@ -676,7 +690,9 @@ But in that case, you should probably use `is()`, instead. ### `fail()` ### SELECT pass( :description ); + SELECT pass( ); SELECT fail( :description ); + SELECT fail( ); **Parameters** @@ -693,6 +709,7 @@ Use these functions very, very, very sparingly. ### `isa_ok()` ### SELECT isa_ok( :have, :regtype, :name ); + SELECT isa_ok( :have, :regtype ); **Parameters** @@ -814,9 +831,12 @@ error-prone as you think they should be. ### `throws_ok()` ### - SELECT throws_ok( :sql, :errcode, :ermsg, :description); - SELECT throws_ok( :sql, :errcode, :description); - SELECT throws_ok( :sql, :errmsg, :description); + SELECT throws_ok( :sql, :errcode, :ermsg, :description ); + SELECT throws_ok( :sql, :errcode, :ermsg ); + SELECT throws_ok( :sql, :errcode ); + SELECT throws_ok( :sql, :errmsg, :description ); + SELECT throws_ok( :sql, :errmsg ); + SELECT throws_ok( :sql ); **Parameters** @@ -887,8 +907,10 @@ Idea borrowed from the Test::Exception Perl module. ### `throws_like()` ### ### `throws_ilike()` ### - SELECT throws_like(:sql, :like, :description); - SELECT throws_ilike(:sql, :like, :description); + SELECT throws_like( :sql, :like, :description ); + SELECT throws_like( :sql, :like ); + SELECT throws_ilike( :sql, :like, :description ); + SELECT throws_ilike( :sql, :like ); **Parameters** @@ -923,7 +945,10 @@ example: ### `throws_matching()` ### ### `throws_imatching()` ### - SELECT throws_matching(:sql, :regex, :description); + SELECT throws_matching( :sql, :regex, :description ); + SELECT throws_matching( :sql, :regex ); + SELECT throws_imatching( :sql, :regex, :description ); + SELECT throws_imatching( :sql, :regex ); **Parameters** @@ -956,7 +981,8 @@ example: ### `lives_ok()` ### - SELECT lives_ok(:sql, :description); + SELECT lives_ok( :sql, :description ); + SELECT lives_ok( :sql ); **Parameters** @@ -985,10 +1011,10 @@ example: Idea borrowed from the Test::Exception Perl module. -### `performs_ok( sql, milliseconds, description )` ### -### `performs_ok( sql, milliseconds )` ### +### `performs_ok()` ### SELECT performs_ok( :sql, :milliseconds, :description ); + SELECT performs_ok( :sql, :milliseconds ); **Parameters** From 61f1184fea40f2a0d0049e0fb987507212871adf Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 9 Sep 2011 15:37:51 -0700 Subject: [PATCH 0616/1195] Simplified "Pursuing Your Query" headers. --- doc/pgtap.mmd | 275 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 186 insertions(+), 89 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 7dcbb8b5700a..7e9abca52f0b 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1072,24 +1072,34 @@ So you've got your basic scalar comparison functions, what about relations? Maybe you have some pretty hairy `SELECT` statements in views or functions to test? We've got your relation-testing functions right here. -### `results_eq( sql, sql, description )` ### -### `results_eq( sql, sql )` ### -### `results_eq( sql, array, description )` ### -### `results_eq( sql, array )` ### -### `results_eq( cursor, cursor, description )` ### -### `results_eq( cursor, cursor )` ### -### `results_eq( sql, cursor, description )` ### -### `results_eq( sql, cursor )` ### -### `results_eq( cursor, sql, description )` ### -### `results_eq( cursor, sql )` ### -### `results_eq( cursor, array, description )` ### -### `results_eq( cursor, array )` ### +### `results_eq()` ### + + SELECT results_eq( :sql, :sql, :description ); + SELECT results_eq( :sql, :sql ); + SELECT results_eq( :sql, :array, :description ); + SELECT results_eq( :sql, :array ); + SELECT results_eq( :cursor, :cursor, :description ); + SELECT results_eq( :cursor, :cursor ); + SELECT results_eq( :sql, :cursor, :description ); + SELECT results_eq( :sql, :cursor ); + SELECT results_eq( :cursor, :sql, :description ); + SELECT results_eq( :cursor, :sql ); + SELECT results_eq( :cursor, :array, :description ); + SELECT results_eq( :cursor, :array ); - PREPARE users_test AS SELECT * FROM active_users(); - PREPARE users_expect AS - VALUES ( 42, 'Anna'), (19, 'Strongrrl'), (39, 'Theory'); +**Parameters** - SELECT results_eq( 'users_test', 'users_expect', 'We should have users' ); +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. + +`:array` +: An array of values representing a single-column row values. + +`:cursor` +: A PostgreSQL `refcursor` value representing a named cursor. + +`:description` +: A short description of the test. There are three ways to test result sets in pgTAP. Perhaps the most intuitive is to do a direct row-by-row comparison of results to ensure that they are @@ -1226,24 +1236,34 @@ example, a `NULL` column will be equivalent to an empty string. The upshot: read failure diagnostics carefully and pay attention to data types on 8.3 and down. -### `results_ne( sql, sql, description )` ### -### `results_ne( sql, sql )` ### -### `results_ne( sql, array, description )` ### -### `results_ne( sql, array )` ### -### `results_ne( cursor, cursor, description )` ### -### `results_ne( cursor, cursor )` ### -### `results_ne( sql, cursor, description )` ### -### `results_ne( sql, cursor )` ### -### `results_ne( cursor, sql, description )` ### -### `results_ne( cursor, sql )` ### -### `results_ne( cursor, array, description )` ### -### `results_ne( cursor, array )` ### +### `results_ne()` ### + + SELECT results_ne( :sql, :sql, :description ); + SELECT results_ne( :sql, :sql ); + SELECT results_ne( :sql, :array, :description ); + SELECT results_ne( :sql, :array ); + SELECT results_ne( :cursor, :cursor, :description ); + SELECT results_ne( :cursor, :cursor ); + SELECT results_ne( :sql, :cursor, :description ); + SELECT results_ne( :sql, :cursor ); + SELECT results_ne( :cursor, :sql, :description ); + SELECT results_ne( :cursor, :sql ); + SELECT results_ne( :cursor, :array, :description ); + SELECT results_ne( :cursor, :array ); - PREPARE users_test AS SELECT * FROM active_users(); - PREPARE not_users AS - VALUES ( 42, 'Anna'), (19, 'Strongrrl'), (39, 'Theory'); +**Parameters** + +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. + +`:array` +: An array of values representing a single-column row values. - SELECT results_ne( 'users_test', 'not_users', 'We should get only users' ); +`:cursor` +: A PostgreSQL `refcursor` value representing a named cursor. + +`:description` +: A short description of the test. The inverse of `results_eq()`, this function tests that query results are not equivalent. Note that, like `results_ne()`, order matters, so you can actually @@ -1257,14 +1277,23 @@ same! Note that the caveats for `results_ne()` on PostgreSQL 8.3 and down apply to `results_ne()` as well. -### `set_eq( sql, sql, description )` ### -### `set_eq( sql, sql )` ### -### `set_eq( sql, array, description )` ### -### `set_eq( sql, array )` ### +### `set_eq()` ### + + SELECT set_eq( :sql, :sql, :description ); + SELECT set_eq( :sql, :sql ); + SELECT set_eq( :sql, :array, :description ); + SELECT set_eq( :sql, :array ); - PREPARE testq AS SELECT * FROM users('a%'); - PREPARE expect AS SELECT * FROM USERS where name LIKE 'a%'; - SELECT set_eq( 'testq', 'expect', 'gotta have the A listers' ); +**Parameters** + +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. + +`:array` +: An array of values representing a single-column row values. + +`:description` +: A short description of the test. Sometimes you don't care what order query results are in, or if there are duplicates. In those cases, use `set_eq()` to do a simple set comparison of @@ -1307,14 +1336,23 @@ This of course extends to sets with different numbers of columns: # have: (integer) # want: (text,integer) -### `set_ne( sql, sql, description )` ### -### `set_ne( sql, sql )` ### -### `set_ne( sql, array, description )` ### -### `set_ne( sql, array )` ### +### `set_ne()` ### + + SELECT set_ne( :sql, :sql, :description ); + SELECT set_ne( :sql, :sql ); + SELECT set_ne( :sql, :array, :description ); + SELECT set_ne( :sql, :array ); + +**Parameters** + +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. - PREPARE testq AS SELECT * FROM users('a%'); - PREPARE expect AS SELECT * FROM USERS where name LIKE 'b%'; - SELECT set_ne( 'testq', 'expect', 'gotta have the A listers' ); +`:array` +: An array of values representing a single-column row values. + +`:description` +: A short description of the test. The inverse of `set_eq()`, this function tests that the results of two queries are *not* the same. The two queries can as usual be the names of prepared @@ -1325,12 +1363,18 @@ comparable -- that is, with the same number and types of columns in the same orders. If it happens that the query you're testing returns a single column, the second argument may be an array. -### `set_has( sql, sql, description )` ### -### `set_has( sql, sql )` ### +### `set_has()` ### + + SELECT set_has( :sql, :sql, :description ); + SELECT set_has( :sql, :sql ); + +**Parameters** + +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. - PREPARE testq AS SELECT * FROM users('a%'); - PREPARE subset AS SELECT * FROM USERS where name LIKE 'a%'; - SELECT set_has( 'testq', 'subset', 'gotta have at least the A listers' ); +`:description` +: A short description of the test. When you need to test that a query returns at least some subset of records, `set_has()` is the hammer you're looking for. It tests that the the results of @@ -1356,12 +1400,18 @@ As with `set_eq()`, `set_has()` will also provide useful diagnostics when the queries return incompatible columns. Internally, it uses an `EXCEPT` query to determine if there any any unexpectedly missing results. -### `set_hasnt( sql, sql, description )` ### -### `set_hasnt( sql, sql )` ### +### `set_hasnt()` ### + + SELECT set_hasnt( :sql, :sql, :description ); + SELECT set_hasnt( :sql, :sql ); - PREPARE testq AS SELECT * FROM users('a%'); - PREPARE exclude AS SELECT * FROM USERS where name LIKE 'b%'; - SELECT set_has( 'testq', 'exclude', 'Must not have the Bs' ); +**Parameters** + +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. + +`:description` +: A short description of the test. This test function is the inverse of `set_has()`: the test passes when the results of the first query have none of the results of the second query. @@ -1375,14 +1425,23 @@ Diagnostics are similarly useful: Internally, the function uses an `INTERSECT` query to determine if there is any unexpected overlap between the query results. -### `bag_eq( sql, sql, description )` ### -### `bag_eq( sql, sql )` ### -### `bag_eq( sql, array, description )` ### -### `bag_eq( sql, array )` ### +### `bag_eq()` ### - PREPARE testq AS SELECT * FROM users('a%'); - PREPARE expect AS SELECT * FROM USERS where name LIKE 'a%'; - SELECT bag_eq( 'testq', 'expect', 'gotta have the A listers' ); + SELECT bag_eq( :sql, :sql, :description ); + SELECT bag_eq( :sql, :sql ); + SELECT bag_eq( :sql, :array, :description ); + SELECT bag_eq( :sql, :array ); + +**Parameters** + +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. + +`:array` +: An array of values representing a single-column row values. + +`:description` +: A short description of the test. The `bag_eq()` function is just like `set_eq()`, except that it considers the results as bags rather than as sets. A bag is a set that allows duplicates. In @@ -1394,14 +1453,23 @@ result set. Otherwise, this function behaves exactly like `set_eq()`, including the utility of its diagnostics. -### `bag_ne( sql, sql, description )` ### -### `bag_ne( sql, sql )` ### -### `bag_ne( sql, array, description )` ### -### `bag_ne( sql, array )` ### +### `bag_ne()` ### + + SELECT bag_ne( :sql, :sql, :description ); + SELECT bag_ne( :sql, :sql ); + SELECT bag_ne( :sql, :array, :description ); + SELECT bag_ne( :sql, :array ); + +**Parameters** + +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. - PREPARE testq AS SELECT * FROM users('a%'); - PREPARE expect AS SELECT * FROM USERS where name LIKE 'b%'; - SELECT bag_ne( 'testq', 'expect', 'gotta have the A listers' ); +`:array` +: An array of values representing a single-column row values. + +`:description` +: A short description of the test. The inverse of `bag_eq()`, this function tests that the results of two queries are *not* the same, including duplicates. The two queries can as usual be the @@ -1412,12 +1480,18 @@ comparable -- that is, with the same number and types of columns in the same orders. If it happens that the query you're testing returns a single column, the second argument may be an array. -### `bag_has( sql, sql, description )` ### -### `bag_has( sql, sql )` ### +### `bag_has()` ### + + SELECT bag_has( :sql, :sql, :description ); + SELECT bag_has( :sql, :sql ); - PREPARE testq AS SELECT * FROM users('a%'); - PREPARE subset AS SELECT * FROM USERS where name LIKE 'a%'; - SELECT bag_has( 'testq', 'subset', 'gotta have at least the A listers' ); +**Parameters** + +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. + +`:description` +: A short description of the test. The `bag_has()` function is just like `set_has()`, except that it considers the results as bags rather than as sets. A bag is a set with duplicates. What @@ -1425,12 +1499,18 @@ practice this means that you can use `bag_has()` to test result sets where order doesn't matter, but duplication does. Internally, it uses an `EXCEPT ALL` query to determine if there any any unexpectedly missing results. -### `bag_hasnt( sql, sql, description )` ### -### `bag_hasnt( sql, sql )` ### +### `bag_hasnt()` ### - PREPARE testq AS SELECT * FROM users('a%'); - PREPARE exclude AS SELECT * FROM USERS where name LIKE 'b%'; - SELECT bag_has( 'testq', 'exclude', 'Must not have the Bs' ); + SELECT bag_hasnt( :sql, :sql, :description ); + SELECT bag_hasnt( :sql, :sql ); + +**Parameters** + +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. + +`:description` +: A short description of the test. This test function is the inverse of `bag_hasnt()`: the test passes when the results of the first query have none of the results of the second query. @@ -1446,11 +1526,18 @@ is any unexpected overlap between the query results. This means that a duplicate row in the first query will appear twice in the diagnostics if it is also duplicated in the second query. -### `is_empty( sql, description )` ### -### `is_empty( sql )` ### +### `is_empty()` ### + + SELECT is_empty( :sql, :description ); + SELECT is_empty( :sql ); + +**Parameters** - PREPARE emptyset AS SELECT * FROM users(FALSE); - SELECT is_empty( 'emptyset', 'Should have no inactive users' ); +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. + +`:description` +: A short description of the test. The `is_empty()` function takes a single query string or prepared statement name as its first argument, and tests that said query returns no records. @@ -1462,11 +1549,21 @@ fails and the results are displayed in the failure diagnostics, like so: # (1,Jacob,false) # (2,Emily,false) -### `row_eq( sql, record, description )` ### -### `row_eq( sql, record )` ### +### `row_eq()` ### + + SELECT row_eq( :sql, :record, :description ); + SELECT row_eq( :sql, :record ); + +**Parameters** + +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. + +`:record` +: A row or value, also known as a [composite type](http://www.postgresql.org/docs/current/static/rowtypes.html). - PREPARE testrow AS SELECT * FROM users where nick = 'theory'; - SELECT row_eq('testrow', ROW(1, 'theory', 'David Wheeler')::users); +`:description` +: A short description of the test. Compares the contents of a single row to a record. Works on PostgreSQL 8.1 and higher. Due to the limitations of non-C functions in PostgreSQL, a bar @@ -1482,7 +1579,7 @@ supported. Thus, you can do this: SELECT row_eq( ROW(1, 'foo')::sometype, ROW(1, 'foo')::sometype ); -And, of course, thins: +And, of course, this: CREATE TABLE users ( id INT, From d7428864b4c5b50193ee3f54bd0e618c6e30ebc7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 9 Sep 2011 16:28:27 -0700 Subject: [PATCH 0617/1195] Simplify "I Object!" section headers. --- doc/pgtap.mmd | 630 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 420 insertions(+), 210 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 7e9abca52f0b..6a78353c3a7b 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1665,17 +1665,26 @@ failure of schema change deployments. If you're more interested in the specifics of particular objects, skip to the next section. -### `tablespaces_are( tablespaces, description )` ### -### `tablespaces_are( tablespaces )` ### +### `tablespaces_are()` ### - SELECT tablespaces_are( - ARRAY[ 'dbspace', 'indexspace' ], - 'Should have the correct tablespaces' - ); + SELECT tablespaces_are( :tablespaces, :description ); + SELECT tablespaces_are( :tablespaces ); + +**Parameters** + +`:tablespaces` +: An array of tablespace names. + +`:description` +: A short description of the test. This function tests that all of the tablespaces in the database only the -tablespaces that *should* be there. In the event of a failure, you'll see -diagnostics listing the extra and/or missing tablespaces, like so: +tablespaces that *should* be there. Example: + + SELECT tablespaces_are(ARRAY[ 'dbspace', 'indexspace' ]); + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing tablespaces, like so: # Failed test 121: "There should be the correct tablespaces" # Extra tablespaces: @@ -1683,17 +1692,26 @@ diagnostics listing the extra and/or missing tablespaces, like so: # Missing tablespaces: # indexspace -### `schemas_are( schemas, description )` ### -### `schemas_are( schemas )` ### +### `schemas_are()` ### - SELECT schemas_are( - ARRAY[ 'public', 'contrib', 'tap' ], - 'Should have the correct schemas' - ); + SELECT schemas_are( :schemas, :description ); + SELECT schemas_are( :schemas ); + +**Parameters** + +`:schemas` +: An array of schema names. + +`:description` +: A short description of the test. This function tests that all of the schemas in the database only the schemas -that *should* be there, excluding system schemas and `information_schema`. In -the event of a failure, you'll see diagnostics listing the extra and/or +that *should* be there, excluding system schemas and `information_schema`. +Example: + + SELECT schemas_are(ARRAY[ 'public', 'contrib', 'tap' ]); + +In the event of a failure, you'll see diagnostics listing the extra and/or missing schemas, like so: # Failed test 106: "There should be the correct schemas" @@ -1702,22 +1720,34 @@ missing schemas, like so: # Missing schemas: # someschema -### `tables_are( schema, tables, description )` ### -### `tables_are( tables, description )` ### -### `tables_are( schema, tables )` ### -### `tables_are( tables )` ### +### `tables_are()` ### - SELECT tables_are( - 'myschema', - ARRAY[ 'users', 'widgets', 'gadgets', 'session' ], - 'Should have the correct tables in myschema' - ); + SELECT tables_are( :schema, :tables, :description ); + SELECT tables_are( :schema, :tables ); + SELECT tables_are( :tables, :description ); + SELECT tables_are( :tables ); + +**Parameters** + +`:schema` +: Name of a schema in which to find tables. + +`:tables` +: An array of table names. + +`:description` +: A short description of the test. This function tests that all of the tables in the named schema, or that are visible in the search path, are only the tables that *should* be there. If the `:schema` argument is omitted, tables will be sought in the search path, excluding `pg_catalog` and `information_schema` If the description is omitted, -a generally useful default description will be generated. +a generally useful default description will be generated. Example: + + SELECT tables_are( + 'myschema', + ARRAY[ 'users', 'widgets', 'gadgets', 'session' ] + ); In the event of a failure, you'll see diagnostics listing the extra and/or missing tables, like so: @@ -1730,22 +1760,34 @@ missing tables, like so: # users # widgets -### `views_are( schema, views, description )` ### -### `views_are( views, description )` ### -### `views_are( schema, views )` ### -### `views_are( views )` ### +### `views_are()` ### - SELECT views_are( - 'myschema', - ARRAY[ 'users', 'widgets', 'gadgets', 'session' ], - 'Should have the correct views in myschema' - ); + SELECT views_are( :schema, :views, :description ); + SELECT views_are( :schema, :views ); + SELECT views_are( :views, :description ); + SELECT views_are( :views ); + +**Parameters** + +`:schema` +: Name of a schema in which to find views. + +`:views` +: An array of view names. + +`:description` +: A short description of the test. This function tests that all of the views in the named schema, or that are visible in the search path, are only the views that *should* be there. If the `:schema` argument is omitted, views will be sought in the search path, excluding `pg_catalog` and `information_schema` If the description is omitted, -a generally useful default description will be generated. +a generally useful default description will be generated. Example: + + SELECT views_are( + 'myschema', + ARRAY[ 'users', 'widgets', 'gadgets', 'session' ] + ); In the event of a failure, you'll see diagnostics listing the extra and/or missing views, like so: @@ -1758,22 +1800,34 @@ missing views, like so: # v_userlog # eated -### `sequences_are( schema, sequences, description )` ### -### `sequences_are( sequences, description )` ### -### `sequences_are( schema, sequences )` ### -### `sequences_are( sequences )` ### +### `sequences_are()` ### - SELECT sequences_are( - 'myschema', - ARRAY[ 'users', 'widgets', 'gadgets', 'session' ], - 'Should have the correct sequences in myschema' - ); + SELECT sequences_are( :schema, :sequences, :description ); + SELECT sequences_are( :schema, :sequences ); + SELECT sequences_are( :sequences, :description ); + SELECT sequences_are( :sequences ); + +**Parameters** + +`:schema` +: Name of a schema in which to find sequences. + +`:sequences` +: An array of sequence names. + +`:description` +: A short description of the test. This function tests that all of the sequences in the named schema, or that are visible in the search path, are only the sequences that *should* be there. If the `:schema` argument is omitted, sequences will be sought in the search path, excluding `pg_catalog` and `information_schema`. If the description is -omitted, a generally useful default description will be generated. +omitted, a generally useful default description will be generated. Example: + + SELECT sequences_are( + 'myschema', + ARRAY[ 'users', 'widgets', 'gadgets', 'session' ] + ); In the event of a failure, you'll see diagnostics listing the extra and/or missing sequences, like so: @@ -1786,23 +1840,38 @@ missing sequences, like so: # users_seq # widgets_seq -### `columns_are( schema, table, columns[], description )` ### -### `columns_are( schema, table, columns[] )` ### -### `columns_are( table, columns[], description )` ### -### `columns_are( table, columns[] )` ### +### `columns_are()` ### - SELECT columns_are( - 'myschema', - 'atable', - ARRAY[ 'id', 'name', 'rank', 'sn' ], - 'Should have the correct columns on myschema.atable' - ); + SELECT columns_are( :schema, :table, :columns[], :description ); + SELECT columns_are( :schema, :table, :columns[] ); + SELECT columns_are( :table, :columns[], :description ); + SELECT columns_are( :table, :columns[] ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the `:table`. + +`:table` +: Name of a table in which to find columns. + +`:columns` +: An array of column names. + +`:description` +: A short description of the test. This function tests that all of the columns on the named table are only the columns that *should* be on that table. If the `:schema` argument is omitted, the table must be visible in the search path, excluding `pg_catalog` and `information_schema`. If the description is omitted, a generally useful -default description will be generated. +default description will be generated. Example: + + SELECT columns_are( + 'myschema', + 'atable', + ARRAY[ 'id', 'name', 'rank', 'sn' ] + ); In the event of a failure, you'll see diagnostics listing the extra and/or missing columns, like so: @@ -1814,23 +1883,38 @@ missing columns, like so: # Missing columns: # name -### `indexes_are( schema, table, indexes[], description )` ### -### `indexes_are( schema, table, indexes[] )` ### -### `indexes_are( table, indexes[], description )` ### -### `indexes_are( table, indexes[] )` ### +### `indexes_are()` ### - SELECT indexes_are( - 'myschema', - 'atable', - ARRAY[ 'atable_pkey', 'idx_atable_name' ], - 'Should have the correct indexes on myschema.atable' - ); + SELECT indexes_are( :schema, :table, :indexes[], :description ); + SELECT indexes_are( :schema, :table, :indexes[] ); + SELECT indexes_are( :table, :indexes[], :description ); + SELECT indexes_are( :table, :indexes[] ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the `:table`. + +`:table` +: Name of a table in which to find indexes. + +`:indexes` +: An array of index names. + +`:description` +: A short description of the test. This function tests that all of the indexes on the named table are only the indexes that *should* be on that table. If the `:schema` argument is omitted, the table must be visible in the search path, excluding `pg_catalog` and `information_schema`. If the description is omitted, a generally useful -default description will be generated. +default description will be generated. Example: + + SELECT indexes_are( + 'myschema', + 'atable', + ARRAY[ 'atable_pkey', 'idx_atable_name' ] + ); In the event of a failure, you'll see diagnostics listing the extra and/or missing indexes, like so: @@ -1841,23 +1925,38 @@ missing indexes, like so: # Missing indexes: # idx_fou_name -### `triggers_are( schema, table, triggers[], description )` ### -### `triggers_are( schema, table, triggers[] )` ### -### `triggers_are( table, triggers[], description )` ### -### `triggers_are( table, triggers[] )` ### +### `triggers_are()` ### - SELECT triggers_are( - 'myschema', - 'atable', - ARRAY[ 'atable_pkey', 'idx_atable_name' ], - 'Should have the correct triggers on myschema.atable' - ); + SELECT triggers_are( :schema, :table, :triggers[], :description ); + SELECT triggers_are( :schema, :table, :triggers[] ); + SELECT triggers_are( :table, :triggers[], :description ); + SELECT triggers_are( :table, :triggers[] ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the `:table`. + +`:table` +: Name of a table in which to find triggers. + +`:triggers` +: An array of trigger names. + +`:description` +: A short description of the test. This function tests that all of the triggers on the named table are only the triggers that *should* be on that table. If the `:schema` argument is omitted, the table must be visible in the search path, excluding `pg_catalog` and `information_schema`. If the description is omitted, a generally useful -default description will be generated. +default description will be generated. Example: + + SELECT triggers_are( + 'myschema', + 'atable', + ARRAY[ 'atable_pkey', 'idx_atable_name' ] + ); In the event of a failure, you'll see diagnostics listing the extra and/or missing triggers, like so: @@ -1868,22 +1967,34 @@ missing triggers, like so: # Missing triggers: # set_users_pass -### `functions_are( schema, functions[], description )` ### -### `functions_are( schema, functions[] )` ### -### `functions_are( functions[], description )` ### -### `functions_are( functions[] )` ### +### `functions_are()` ### - SELECT functions_are( - 'myschema', - ARRAY[ 'foo', 'bar', 'frobnitz' ], - 'Should have the correct functions in myschema' - ); + SELECT functions_are( :schema, :functions, :description ); + SELECT functions_are( :schema, :functions ); + SELECT functions_are( :functions, :description ); + SELECT functions_are( :functions ); + +**Parameters** + +`:schema` +: Name of a schema in which to find functions. + +`:functions` +: An array of function names. + +`:description` +: A short description of the test. This function tests that all of the functions in the named schema, or that are visible in the search path, are only the functions that *should* be there. If the `:schema` argument is omitted, functions will be sought in the search path, excluding `pg_catalog` and `information_schema` If the description is -omitted, a generally useful default description will be generated. +omitted, a generally useful default description will be generated. Example: + + SELECT functions_are( + 'myschema', + ARRAY[ 'foo', 'bar', 'frobnitz' ] + ); In the event of a failure, you'll see diagnostics listing the extra and/or missing functions, like so: @@ -1894,18 +2005,26 @@ missing functions, like so: # Missing functions: # frobnitz -### `roles_are( roles[], description )` ### -### `roles_are( roles[] )` ### +### `roles_are()` ### - SELECT roles_are( - ARRAY[ 'postgres', 'someone', 'root' ], - 'Should have the correct roles' - ); + SELECT roles_are( :roles, :description ); + SELECT roles_are( :roles ); + +**Parameters** + +`:roles` +: An array of role names. + +`:description` +: A short description of the test. This function tests that all of the roles in the database only the roles that -*should* be there. Supported in PostgreSQL 8.1 and higher. In the event of a -failure, you'll see diagnostics listing the extra and/or missing roles, like -so: +*should* be there. Supported in PostgreSQL 8.1 and higher. Example: + + SELECT roles_are(ARRAY[ 'postgres', 'someone', 'root' ]); + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing roles, like so: # Failed test 195: "There should be the correct roles" # Extra roles: @@ -1913,17 +2032,26 @@ so: # Missing roles: # bobby -### `users_are( users[], description )` ### -### `users_are( users[] )` ### +### `users_are()` ### - SELECT users_are( - ARRAY[ 'postgres', 'someone', 'root' ], - 'Should have the correct users' - ); + SELECT users_are( :users, :description ); + SELECT users_are( :users ); + +**Parameters** + +`:users` +: An array of user names. + +`:description` +: A short description of the test. This function tests that all of the users in the database only the users that -*should* be there. In the event of a failure, you'll see diagnostics listing -the extra and/or missing users, like so: +*should* be there. Example: + + SELECT users_are(ARRAY[ 'postgres', 'someone', 'root' ]); + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing users, like so: # Failed test 195: "There should be the correct users" # Extra users: @@ -1931,17 +2059,26 @@ the extra and/or missing users, like so: # Missing users: # bobby -### `groups_are( groups[], description )` ### -### `groups_are( groups[] )` ### +### `groups_are()` ### - SELECT groups_are( - ARRAY[ 'postgres', 'admins, 'l0s3rs' ], - 'Should have the correct groups' - ); + SELECT groups_are( :groups, :description ); + SELECT groups_are( :groups ); + +**Parameters** + +`:groups` +: An array of group names. + +`:description` +: A short description of the test. -This function tests that all of the groups in the database only the groups -that *should* be there. In the event of a failure, you'll see diagnostics -listing the extra and/or missing groups, like so: +This function tests that all of the groups in the database only the groups that +*should* be there. Example: + + SELECT groups_are(ARRAY[ 'postgres', 'admins, 'l0s3rs' ]); + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing groups, like so: # Failed test 210: "There should be the correct groups" # Extra groups: @@ -1949,17 +2086,26 @@ listing the extra and/or missing groups, like so: # Missing groups: # __howdy__ -### `languages_are( languages[], description )` ### -### `languages_are( languages[] )` ### +### `languages_are()` ### - SELECT languages_are( - ARRAY[ 'plpgsql', 'plperl', 'pllolcode' ], - 'Should have the correct procedural languages' - ); + SELECT languages_are( :languages, :description ); + SELECT languages_are( :languages ); -This function tests that all of the procedural languages in the database only -the languages that *should* be there. In the event of a failure, you'll see -diagnostics listing the extra and/or missing languages, like so: +**Parameters** + +`:languages` +: An array of language names. + +`:description` +: A short description of the test. + +This function tests that all of the languages in the database only the languages that +*should* be there. Example: + + SELECT languages_are(ARRAY[ 'plpgsql', 'plperl', 'pllolcode' ]); + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing languages, like so: # Failed test 225: "There should be the correct procedural languages" # Extra languages: @@ -1967,23 +2113,35 @@ diagnostics listing the extra and/or missing languages, like so: # Missing languages: # plpgsql -### `opclasses_are( schema, opclasses[], description )` ### -### `opclasses_are( schema, opclasses[] )` ### -### `opclasses_are( opclasses[], description )` ### -### `opclasses_are( opclasses[] )` ### +### `opclasses_are()` ### - SELECT opclasses_are( - 'myschema', - ARRAY[ 'foo', 'bar', 'frobnitz' ], - 'Should have the correct opclasses in myschema' - ); + SELECT opclasses_are( :schema, :opclasses, :description ); + SELECT opclasses_are( :schema, :opclasses ); + SELECT opclasses_are( :opclasses, :description ); + SELECT opclasses_are( :opclasses ); + +**Parameters** + +`:schema` +: Name of a schema in which to find opclasses. + +`:opclasses` +: An array of opclass names. + +`:description` +: A short description of the test. This function tests that all of the operator classes in the named schema, or that are visible in the search path, are only the opclasses that *should* be there. If the `:schema` argument is omitted, opclasses will be sought in the search path, excluding `pg_catalog` and `information_schema`. If the description is omitted, a generally useful default description will be -generated. +generated. Example: + + SELECT opclasses_are( + 'myschema', + ARRAY[ 'foo', 'bar', 'frobnitz' ] + ); In the event of a failure, you'll see diagnostics listing the extra and/or missing opclasses, like so: @@ -1994,23 +2152,38 @@ missing opclasses, like so: # Missing operator classes: # custom_ops -### `rules_are( schema, table, rules[], description )` ### -### `rules_are( schema, table, rules[] )` ### -### `rules_are( table, rules[], description )` ### -### `rules_are( table, rules[] )` ### +### `rules_are()` ### - SELECT rules_are( - 'myschema', - 'atable', - ARRAY[ 'on_insert', 'on_update', 'on_delete' ], - 'Should have the correct rules on myschema.atable' - ); + SELECT rules_are( :schema, :table, :rules[], :description ); + SELECT rules_are( :schema, :table, :rules[] ); + SELECT rules_are( :table, :rules[], :description ); + SELECT rules_are( :table, :rules[] ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the `:table`. + +`:table` +: Name of a table in which to find rules. + +`:rules` +: An array of rule names. + +`:description` +: A short description of the test. This function tests that all of the rules on the named relation are only the rules that *should* be on that relation (a table or a view). If the `:schema` argument is omitted, the rules must be visible in the search path, excluding `pg_catalog` and `information_schema`. If the description is omitted, a -generally useful default description will be generated. +generally useful default description will be generated. Example: + + SELECT rules_are( + 'myschema', + 'atable', + ARRAY[ 'on_insert', 'on_update', 'on_delete' ] + ); In the event of a failure, you'll see diagnostics listing the extra and/or missing rules, like so: @@ -2021,23 +2194,32 @@ missing rules, like so: # Missing rules: # on_delete -### `types_are( schema, types[], description )` ### -### `types_are( schema, types[] )` ### -### `types_are( types[], description )` ### -### `types_are( types[] )` ### +### `types_are()` ### - SELECT types_are( - 'myschema', - ARRAY[ 'timezone', 'state' ], - 'Should have the correct types in myschema' - ); + SELECT types_are( :schema, :types, :description ); + SELECT types_are( :schema, :types ); + SELECT types_are( :types, :description ); + SELECT types_are( :types ); + +**Parameters** + +`:schema` +: Name of a schema in which to find types. + +`:types` +: An array of data type names. + +`:description` +: A short description of the test. Tests that all of the types in the named schema are the only types in that schema, including base types, composite types, domains, enums, and pseudo-types. If the `:schema` argument is omitted, the types must be visible in the search path, excluding `pg_catalog` and `information_schema`. If the description is omitted, a generally useful default description will be -generated. +generated. Example: + + SELECT types_are('myschema', ARRAY[ 'timezone', 'state' ]); In the event of a failure, you'll see diagnostics listing the extra and/or missing types, like so: @@ -2048,22 +2230,31 @@ missing types, like so: # Missing types: # timezone -### `domains_are( schema, domains[], description )` ### -### `domains_are( schema, domains[] )` ### -### `domains_are( domains[], description )` ### -### `domains_are( domains[] )` ### +### `domains_are()` ### - SELECT domains_are( - 'myschema', - ARRAY[ 'timezone', 'state' ], - 'Should have the correct domains in myschema' - ); + SELECT domains_are( :schema, :domains, :description ); + SELECT domains_are( :schema, :domains ); + SELECT domains_are( :domains, :description ); + SELECT domains_are( :domains ); + +**Parameters** + +`:schema` +: Name of a schema in which to find domains. + +`:domains` +: An array of data domain names. + +`:description` +: A short description of the test. Tests that all of the domains in the named schema are the only domains in that schema. If the `:schema` argument is omitted, the domains must be visible in the search path, excluding `pg_catalog` and `information_schema`. If the description is omitted, a generally useful default description will be -generated. +generated. Example: + + SELECT domains_are('myschema', ARRAY[ 'timezone', 'state' ]); In the event of a failure, you'll see diagnostics listing the extra and/or missing domains, like so: @@ -2074,22 +2265,31 @@ missing domains, like so: # Missing domains: # timezone -### `enums_are( schema, enums[], description )` ### -### `enums_are( schema, enums[] )` ### -### `enums_are( enums[], description )` ### -### `enums_are( enums[] )` ### +### `enums_are()` ### - SELECT enums_are( - 'myschema', - ARRAY[ 'timezone', 'state' ], - 'Should have the correct enums in myschema' - ); + SELECT enums_are( :schema, :enums, :description ); + SELECT enums_are( :schema, :enums ); + SELECT enums_are( :enums, :description ); + SELECT enums_are( :enums ); + +**Parameters** + +`:schema` +: Name of a schema in which to find enums. + +`:enums` +: An array of enum data type names. + +`:description` +: A short description of the test. Tests that all of the enums in the named schema are the only enums in that schema. Enums are supported in PostgreSQL 8.3 and up. If the `:schema` argument is omitted, the enums must be visible in the search path, excluding `pg_catalog` and `information_schema`. If the description is omitted, a -generally useful default description will be generated. +generally useful default description will be generated. Example: + + SELECT enums_are('myschema', ARRAY[ 'timezone', 'state' ]); In the event of a failure, you'll see diagnostics listing the extra and/or missing enums, like so: @@ -2100,34 +2300,33 @@ missing enums, like so: # Missing enums: # bug_status -### `casts_are( casts[], description )` ### -### `casts_are( casts[] )` ### +### `casts_are()` ### - SELECT casts_are( - ARRAY[ - 'integer AS double precision', - 'integer AS reltime', - 'integer AS numeric', - -- ... - ], - 'Should have the correct casts' - ); + SELECT casts_are( :casts, :description ); + SELECT casts_are( :casts ); + +**Parameters** + +`:casts` +: An array of cast names. + +`:description` +: A short description of the test. This function tests that all of the casts in the database are only the casts that *should* be in that database. Casts are specified as strings in a syntax similarly to how they're declared via `CREATE CAST`. The pattern is `:source_type AS :target_type`. If either type was created with double-quotes to force mixed case or special characers, then you must use double quotes in -the cast strings: - - SELECT casts_are( - ARRAY[ - 'integer AS "myInteger"', - -- ... - ] - ); - +the cast strings. Example: + SELECT casts_are(ARRAY[ + 'integer AS "myInteger"', + 'integer AS double precision', + 'integer AS reltime', + 'integer AS numeric', + ]); + If the description is omitted, a generally useful default description will be generated. @@ -2140,21 +2339,23 @@ missing casts, like so: # Missing casts: # lseg AS integer -### `operators_are( schema, operators[], description )` ### -### `operators_are( schema, operators[] )` ### -### `operators_are( operators[], description )` ### -### `operators_are( operators[] )` ### +### `operators_are()` ### - SELECT operators_are( - 'public', - ARRAY[ - '=(citext,citext) RETURNS boolean', - '-(NONE,bigint) RETURNS bigint', - '!(bigint,NONE) RETURNS numeric', - -- ... - ], - '' - ); + SELECT operators_are( :schema, :operators, :description ); + SELECT operators_are( :schema, :operators ); + SELECT operators_are( :operators, :description ); + SELECT operators_are( :operators ); + +**Parameters** + +`:schema` +: Name of a schema in which to find operators. + +`:operators` +: An array of operators. + +`:description` +: A short description of the test. Tests that all of the operators in the named schema are the only operators in that schema. If the `:schema` argument is omitted, the operators must be @@ -2170,7 +2371,16 @@ For left operators the left argument type should be `NONE`. For right operators, the right argument type should be `NONE`. The example above shows one one of each of the operator types. `=(citext,citext)` is an infix operator, `-(bigint,NONE)` is a left operator, and `!(NONE,bigint)` is a right -operator. +operator. Example: + + SELECT operators_are( + 'public', + ARRAY[ + '=(citext,citext) RETURNS boolean', + '-(NONE,bigint) RETURNS bigint', + '!(bigint,NONE) RETURNS numeric' + ] + ); In the event of a failure, you'll see diagnostics listing the extra and/or missing operators, like so: From 232b9db903e7f3c9dc8f93ce9f2876cf43c1010a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 9 Sep 2011 17:06:54 -0700 Subject: [PATCH 0618/1195] Start converting doc headers under "To Have or Have Not." --- doc/pgtap.mmd | 415 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 273 insertions(+), 142 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 6a78353c3a7b..48b3db1a6a08 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2399,46 +2399,64 @@ database objects](#I+Object! "I Object!"). Perhaps you just need to make sure that certain objects exist (or that certain objects *don't* exist). You've come to the right place. -### `has_tablespace( tablespace, location, description )` ### -### `has_tablespace( tablespace, description )` ### -### `has_tablespace( tablespace )` ### - - SELECT has_tablespace( - 'sometablespace', - '/data/dbs', - 'I got sometablespace in /data/dbs' - ); +### `has_tablespace()` ### + + SELECT has_tablespace( :tablespace, :location, :description ); + SELECT has_tablespace( :tablespace, :description ); + SELECT has_tablespace( :tablespace ); + +**Parameters** + +`:tablespace` +: Name of a tablespace. + +`:location` +: The tablespace's Location on disk. + +`:description` +: A short description of the test. This function tests whether or not a tablespace exists in the database. The first argument is a tablespace name. The second is either the a file system path for the database or a test description. If you specify a location path, you must pass a description as the third argument; otherwise, if you omit the test description, it will be set to "Tablespace `:tablespace` should exist". +Example: -### `hasnt_tablespace( tablespace, tablespace, description )` ### -### `hasnt_tablespace( tablespace, description )` ### -### `hasnt_tablespace( tablespace )` ### + SELECT has_tablespace('sometablespace', '/data/dbs'); - SELECT hasnt_tablespace( - 'sometablespace', - 'There should be no tablespace sometablespace' - ); +### `hasnt_tablespace()` ### + + SELECT hasnt_tablespace( :tablespace, :description ); + SELECT hasnt_tablespace( :tablespace ); + +**Parameters** + +`:tablespace` +: Name of a tablespace. + +`:description` +: A short description of the test. This function is the inverse of `has_tablespace()`. The test passes if the specified tablespace does *not* exist. -### `has_schema( schema, description )` ### -### `has_schema( schema )` ### +### `has_schema()` ### - SELECT has_schema( - 'someschema', - 'I got someschema' - ); + SELECT has_schema( :schema, :description ); + SELECT has_schema( :schema ); + +**Parameters** + +`:schema` +: Name of a schema. + +`:description` +: A short description of the test. This function tests whether or not a schema exists in the database. The first argument is a schema name and the second is the test description. If you omit -the schema, the schema must be visible in the search path. If you omit the -test description, it will be set to "Schema `:schema` should exist". +the test description, it will be set to "Schema `:schema` should exist". ### `hasnt_schema( schema, description )` ### ### `hasnt_schema( schema )` ### @@ -2451,95 +2469,164 @@ test description, it will be set to "Schema `:schema` should exist". This function is the inverse of `has_schema()`. The test passes if the specified schema does *not* exist. -### `has_table( schema, table, description )` ### -### `has_table( table, description )` ### -### `has_table( table )` ### +### `has_table()` ### - SELECT has_table( - 'myschema', - 'sometable', - 'I got myschema.sometable' - ); + SELECT has_table( :schema, :table, :description ); + SELECT has_table( :table, :description ); + SELECT has_table( :table ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the table. + +`:table` +: Name of a table. + +`:description` +: A short description of the test. This function tests whether or not a table exists in the database. The first argument is a schema name, the second is a table name, and the third is the test description. If you omit the schema, the table must be visible in the -search path. If you omit the test description, it will be set to "Table -`:table` should exist". +search path. Example: -### `hasnt_table( schema, table, description )` ### -### `hasnt_table( table, description )` ### -### `hasnt_table( table )` ### + SELECT has_table('myschema', 'sometable'); - SELECT hasnt_table( - 'myschema', - 'sometable', - 'There should be no table myschema.sometable' - ); +If you omit the test description, it will be set to "Table `:table` should +exist". + +### `hasnt_table()` ### + + SELECT hasnt_table( :schema, :table, :description ); + SELECT hasnt_table( :table, :description ); + SELECT hasnt_table( :table ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the table. + +`:table` +: Name of a table. + +`:description` +: A short description of the test. This function is the inverse of `has_table()`. The test passes if the specified table does *not* exist. -### `has_view( schema, view, description )` ### -### `has_view( view, description )` ### -### `has_view( view )` ### +### `has_view()` ### - SELECT has_view( - 'myschema', - 'someview', - 'I got myschema.someview' - ); + SELECT has_view( :schema, :view, :description ); + SELECT has_view( :view, :description ); + SELECT has_view( :view ); -Just like `has_table()`, only it tests for the existence of a view. +**Parameters** -### `hasnt_view( schema, view, description )` ### -### `hasnt_view( view, description )` ### -### `hasnt_view( view )` ### +`:schema` +: Name of a schema in which to find the view. - SELECT hasnt_view( - 'myschema', - 'someview', - 'There should be no myschema.someview' - ); +`:view` +: Name of a view. -This function is the inverse of `has_view()`. The test passes if the specified -view does *not* exist. +`:description` +: A short description of the test. -### `has_sequence( schema, sequence, description )` ### -### `has_sequence( sequence, description )` ### -### `has_sequence( sequence )` ### +This function tests whether or not a view exists in the database. The first +argument is a schema name, the second is a view name, and the third is the +test description. If you omit the schema, the view must be visible in the +search path. Example: - SELECT has_sequence( - 'myschema', - 'somesequence', - 'I got myschema.somesequence' - ); + SELECT has_view('myschema', 'someview'); -Just like `has_table()`, only it tests for the existence of a sequence. +If you omit the test description, it will be set to "View `:view` should +exist". -### `hasnt_sequence( schema, sequence, description )` ### -### `hasnt_sequence( sequence, description )` ### -### `hasnt_sequence( sequence )` ### +### `hasnt_view()` ### - SELECT hasnt_sequence( - 'myschema', - 'somesequence', - 'There should be no myschema.somesequence' - ); + SELECT hasnt_view( :schema, :view, :description ); + SELECT hasnt_view( :view, :description ); + SELECT hasnt_view( :view ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the view. + +`:view` +: Name of a view. + +`:description` +: A short description of the test. + +This function is the inverse of `has_view()`. The test passes if the +specified view does *not* exist. + +### `has_sequence()` ### + + SELECT has_sequence( :schema, :sequence, :description ); + SELECT has_sequence( :sequence, :description ); + SELECT has_sequence( :sequence ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the sequence. + +`:sequence` +: Name of a sequence. + +`:description` +: A short description of the test. + +This function tests whether or not a sequence exists in the database. The first +argument is a schema name, the second is a sequence name, and the third is the +test description. If you omit the schema, the sequence must be visible in the +search path. Example: + + SELECT has_sequence('myschema', 'somesequence'); + +If you omit the test description, it will be set to "Sequence `:sequence` should +exist". + +### `hasnt_sequence()` ### + + SELECT hasnt_sequence( :schema, :sequence, :description ); + SELECT hasnt_sequence( :sequence, :description ); + SELECT hasnt_sequence( :sequence ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the sequence. + +`:sequence` +: Name of a sequence. + +`:description` +: A short description of the test. This function is the inverse of `has_sequence()`. The test passes if the specified sequence does *not* exist. -### `has_type( schema, type, description )` ### -### `has_type( schema, type )` ### -### `has_type( type, description )` ### -### `has_type( type )` ### +### `has_type()` ### - SELECT has_type( - 'myschema', - 'sometype', - 'I got myschema.sometype' - ); + SLEECT has_type( schema, type, description ); + SELECT has_type( schema, type ); + SELECT has_type( type, description ); + SELECT has_type( type ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the sequence. + +`:type` +: Name of a data type. + +`:description` +: A short description of the test. This function tests whether or not a type exists in the database. Detects all types of types, including base types, composite types, domains, enums, and @@ -2548,7 +2635,9 @@ and the third is the test description. If you omit the schema, the type must be visible in the search path. If you omit the test description, it will be set to "Type `:type` should exist". If you're passing a schema and type rather than type and description, be sure to cast the arguments to `name` values so -that your type name doesn't get treated as a description. +that your type name doesn't get treated as a description. Example: + + SELECT has_type( 'myschema', 'sometype' ); If you've created a composite type and want to test that the composed types are a part of it, use the column testing functions to verify them, like so: @@ -2558,83 +2647,125 @@ are a part of it, use the column testing functions to verify them, like so: SELECT has_column( 'foo', 'id' ); SELECT col_type_is( 'foo', 'id', 'integer' ); -### `hasnt_type( schema, type, description )` ### -### `hasnt_type( schema, type )` ### -### `hasnt_type( type, description )` ### -### `hasnt_type( type )` ### +### `hasnt_type()` ### - SELECT hasnt_type( - 'myschema', - 'sometype', - 'There should be no type myschema.sometype' - ); + SLEECT hasnt_type( schema, type, description ); + SELECT hasnt_type( schema, type ); + SELECT hasnt_type( type, description ); + SELECT hasnt_type( type ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the sequence. + +`:type` +: Name of a data type. + +`:description` +: A short description of the test. This function is the inverse of `has_type()`. The test passes if the specified type does *not* exist. -### `has_domain( schema, domain, description )` ### -### `has_domain( schema, domain )` ### -### `has_domain( domain, description )` ### -### `has_domain( domain )` ### +### `has_domain()` ### - SELECT has_domain( - 'myschema', - 'somedomain', - 'I got myschema.somedomain' - ); + SLEECT has_domain( schema, domain, description ); + SELECT has_domain( schema, domain ); + SELECT has_domain( domain, description ); + SELECT has_domain( domain ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the sequence. + +`:domain` +: Name of a domain. + +`:description` +: A short description of the test. This function tests whether or not a domain exists in the database. The first argument is a schema name, the second is the name of a domain, and the third is the test description. If you omit the schema, the domain must be visible in the search path. If you omit the test description, it will be set to "Domain -`:domain` should exist". If you're passing a schema and domain rather than -domain and description, be sure to cast the arguments to `name` values so that -your domain name doesn't get treated as a description. +`:domain` should exist". Example: -### `hasnt_domain( schema, domain, description )` ### -### `hasnt_domain( schema, domain )` ### -### `hasnt_domain( domain, description )` ### -### `hasnt_domain( domain )` ### + SELECT has_domain( 'myschema', 'somedomain' ); - SELECT hasnt_domain( - 'myschema', - 'somedomain', - 'There should be no domain myschema.somedomain' - ); +If you're passing a schema and domain rather than domain and description, be +sure to cast the arguments to `name` values so that your domain name doesn't +get treated as a description. -This function is the inverse of `has_domain()`. The test passes if the -specified domain does *not* exist. +### `hasnt_domain()` ### -### `has_enum( schema, enum, description )` ### -### `has_enum( schema, enum )` ### -### `has_enum( enum, description )` ### -### `has_enum( enum )` ### + SLEECT hasnt_domain( schema, domain, description ); + SELECT hasnt_domain( schema, domain ); + SELECT hasnt_domain( domain, description ); + SELECT hasnt_domain( domain ); - SELECT has_enum( - 'myschema', - 'someenum', - 'I got myschema.someenum' - ); +**Parameters** + +`:schema` +: Name of a schema in which to find the sequence. + +`:domain` +: Name of a domain. + +`:description` +: A short description of the test. + +This function is the inverse of `has_domain()`. The test passes if the specified +domain does *not* exist. + +### `has_enum()` ### + + SLEECT has_enum( schema, enum, description ); + SELECT has_enum( schema, enum ); + SELECT has_enum( enum, description ); + SELECT has_enum( enum ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the sequence. + +`:enum` +: Name of a enum. + +`:description` +: A short description of the test. This function tests whether or not a enum exists in the database. Enums are supported in PostgreSQL 8.3 or higher. The first argument is a schema name, the second is the an enum name, and the third is the test description. If you omit the schema, the enum must be visible in the search path. If you omit the -test description, it will be set to "Enum `:enum` should exist". If you're -passing a schema and enum rather than enum and description, be sure to cast -the arguments to `name` values so that your enum name doesn't get treated as a -description. +test description, it will be set to "Enum `:enum` should exist". Example: -### `hasnt_enum( schema, enum, description )` ### -### `hasnt_enum( schema, enum )` ### -### `hasnt_enum( enum, description )` ### -### `hasnt_enum( enum )` ### + SELECT has_enum( 'myschema', 'someenum' ); - SELECT hasnt_enum( - 'myschema', - 'someenum', - 'I don''t got myschema.someenum' - ); +If you're passing a schema and enum rather than enum and description, be sure +to cast the arguments to `name` values so that your enum name doesn't get +treated as a description. + +### `hasnt_enum()` ### + + SLEECT hasnt_enum( schema, enum, description ); + SELECT hasnt_enum( schema, enum ); + SELECT hasnt_enum( enum, description ); + SELECT hasnt_enum( enum ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the sequence. + +`:enum` +: Name of a enum. + +`:description` +: A short description of the test. This function is the inverse of `has_enum()`. The test passes if the specified enum does *not* exist. From 115090041a6a05ab629c664d003264b282d557a7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 9 Sep 2011 19:51:31 -0700 Subject: [PATCH 0619/1195] Update headers for `has_index()` and `hasnt_index()`. --- doc/pgtap.mmd | 91 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 61 insertions(+), 30 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 48b3db1a6a08..1f07a7f09ce0 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2770,18 +2770,48 @@ treated as a description. This function is the inverse of `has_enum()`. The test passes if the specified enum does *not* exist. -### `has_index( schema, table, index, columns[], description )` ### -### `has_index( schema, table, index, columns[] )` ### -### `has_index( schema, table, index, column/expression, description )` ### -### `has_index( schema, table, index, columns/expression )` ### -### `has_index( table, index, columns[], description )` ### -### `has_index( table, index, columns[], description )` ### -### `has_index( table, index, column/expression, description )` ### -### `has_index( schema, table, index, column/expression )` ### -### `has_index( table, index, column/expression )` ### -### `has_index( schema, table, index )` ### -### `has_index( table, index, description )` ### -### `has_index( table, index )` ### +### `has_index()` ### + + SELECT has_index( :schema, :table, :index, :columns, :description ); + SELECT has_index( :schema, :table, :index, :columns ); + SELECT has_index( :schema, :table, :index, :column, :description ); + SELECT has_index( :schema, :table, :index, :column ); + SELECT has_index( :table, :index, :columns, :description ); + SELECT has_index( :table, :index, :columns, :description ); + SELECT has_index( :table, :index, :column, :description ); + SELECT has_index( :schema, :table, :index, :column ); + SELECT has_index( :table, :index, :column ); + SELECT has_index( :schema, :table, :index ); + SELECT has_index( :table, :index, :description ); + SELECT has_index( :table, :index ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the index. + +`:table` +: Name of a table in which to find index. + +`:index` +: Name of an index. + +`:columns` +: Array of the columns in the index. + +`:column` +: Idexed column name or expression. + +`:description` +: A short description of the test. + +Checks for the existence of an index associated with the named table. The +`:schema` argument is optional, as is the column name or names or expression, +and the description. The columns argument may be a string naming one column or +an array of column names. It may also be a string representing an expression, +such as `lower(foo)`. For expressions, you must use lowercase for all SQL +keywords and functions to properly compare to PostgreSQL's internal form of +the expression. A few examples: SELECT has_index( 'myschema', @@ -2795,14 +2825,6 @@ enum does *not* exist. SELECT has_index('myschema', 'sometable', 'loweridx', 'LOWER(somecolumn)'); SELECT has_index('sometable', 'someindex'); -Checks for the existence of an index associated with the named table. The -`:schema` argument is optional, as is the column name or names or expression, -and the description. The columns argument may be a string naming one column or -an array of column names. It may also be a string representing an expression, -such as `lower(foo)`. For expressions, you must use lowercase for all SQL -keywords and functions to properly compare to PostgreSQL's internal form of -the expression. - If you find that the function call seems to be getting confused, cast the index name to the `NAME` type: @@ -2819,17 +2841,26 @@ incorrect, the diagnostics will look more like this: # have: "idx_baz" ON public.sometab(lower(name)) # want: "idx_baz" ON public.sometab(lower(lname)) -### `hasnt_index( schema, table, index, description )` ### -### `hasnt_index( schema, table, index )` ### -### `hasnt_index( table, index, description )` ### -### `hasnt_index( table, index )` ### +### `hasnt_index()` ### - SELECT hasnt_index( - 'myschema', - 'sometable', - 'someindex', - 'Index "someindex" should not exist' - ); + SELECT hasnt_index( schema, table, index, description ); + SELECT hasnt_index( schema, table, index ); + SELECT hasnt_index( table, index, description ); + SELECT hasnt_index( table, index ); + +**Parameters** + +`:schema` +: Name of a schema in which to not find the index. + +`:table` +: Name of a table in which to not find the index. + +`:index` +: Name of an index. + +`:description` +: A short description of the test. This function is the inverse of `has_index()`. The test passes if the specified index does *not* exist. From 888af8d52ebf8e477acdf12db0878d89d6617a3b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 9 Sep 2011 20:10:00 -0700 Subject: [PATCH 0620/1195] Update headers for `has_trigger` and `hasnt_rule`. --- doc/pgtap.mmd | 111 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 37 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 1f07a7f09ce0..6b8ef9365e09 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2865,63 +2865,100 @@ incorrect, the diagnostics will look more like this: This function is the inverse of `has_index()`. The test passes if the specified index does *not* exist. -### `has_trigger( schema, table, trigger, description )` ### -### `has_trigger( schema, table, trigger )` ### -### `has_trigger( table, trigger, description )` ### -### `has_trigger( table, trigger )` ### +### `has_trigger()` ### - SELECT has_trigger( - 'myschema', - 'sometable', - 'sometrigger', - 'Trigger "sometrigger" should exist' - ); + SELECT has_trigger( :schema, :table, :trigger, :description ); + SELECT has_trigger( :schema, :table, :trigger ); + SELECT has_trigger( :table, :trigger, :description ); + SELECT has_trigger( :table, :trigger )` ### - SELECT has_trigger( 'sometable', 'sometrigger' ); +**Parameters** + +`:schema` +: Name of a schema in which to find the trigger. + +`:table` +: Name of a table in which to find the trigger. + +`:trigger` +: Name of an trigger. + +`:description` +: A short description of the test. Tests to see if the specified table has the named trigger. The `:description` is optional, and if the schema is omitted, the table with which the trigger is associated must be visible in the search path. -### `hasnt_trigger( schema, table, trigger, description )` ### -### `hasnt_trigger( schema, table, trigger )` ### -### `hasnt_trigger( table, trigger, description )` ### -### `hasnt_trigger( table, trigger )` ### +### `hasnt_trigger()` ### - SELECT hasnt_trigger( - 'myschema', - 'sometable', - 'sometrigger', - 'Trigger "sometrigger" should not exist' - ); + SELECT hasnt_trigger( :schema, :table, :trigger, :description ); + SELECT hasnt_trigger( :schema, :table, :trigger ); + SELECT hasnt_trigger( :table, :trigger, :description ); + SELECT hasnt_trigger( :table, :trigger )` ### + +**Parameters** + +`:schema` +: Name of a schema in which to not find the trigger. + +`:table` +: Name of a table in which to not find the trigger. + +`:trigger` +: Name of an trigger. + +`:description` +: A short description of the test. This function is the inverse of `has_trigger()`. The test passes if the specified trigger does *not* exist. -### `has_rule( schema, table, rule, description )` ### -### `has_rule( schema, table, rule )` ### -### `has_rule( table, rule, description )` ### -### `has_rule( table, rule )` ### +### `has_rule()` ### - SELECT has_rule( - 'myschema', - 'sometable', - 'somerule, - 'Rule "somerule" should exist' - ); + SELECT has_rule( :schema, :table, :rule, :description ); + SELECT has_rule( :schema, :table, :rule ); + SELECT has_rule( :table, :rule, :description ); + SELECT has_rule( :table, :rule )` ### + +**Parameters** + +`:schema` +: Name of a schema in which to find the rule. + +`:table` +: Name of a table in which to find the rule. + +`:rule` +: Name of an rule. - SELECT has_rule( 'sometable', 'somerule' ); +`:description` +: A short description of the test. Tests to see if the specified table has the named rule. The `:description` is optional, and if the schema is omitted, the table with which the rule is associated must be visible in the search path. -### `hasnt_rule( schema, table, rule, description )` ### -### `hasnt_rule( schema, table, rule )` ### -### `hasnt_rule( table, rule, description )` ### -### `hasnt_rule( table, rule )` ### +### `hasnt_rule()` ### + + SELECT hasnt_rule( :schema, :table, :rule, :description ); + SELECT hasnt_rule( :schema, :table, :rule ); + SELECT hasnt_rule( :table, :rule, :description ); + SELECT hasnt_rule( :table, :rule )` ### + +**Parameters** + +`:schema` +: Name of a schema in which to not find the rule. + +`:table` +: Name of a table in which to not find the rule. - SELECT hasnt_rule( 'sometable', 'somerule' ); +`:rule` +: Name of an rule. + +`:description` +: A short description of the test. This function is the inverse of `has_rule()`. The test passes if the specified rule does *not* exist. From af901545ffbb1035a8ce0f3a30fc2aebdf3dc20a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 13 Sep 2011 13:18:05 -0700 Subject: [PATCH 0621/1195] Finish updating `has_*` docs. --- doc/pgtap.mmd | 479 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 327 insertions(+), 152 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 6b8ef9365e09..d2b499ebbf1b 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2963,14 +2963,36 @@ associated must be visible in the search path. This function is the inverse of `has_rule()`. The test passes if the specified rule does *not* exist. -### `has_function( schema, function, args[], description )` ### -### `has_function( schema, function, args[] )` ### -### `has_function( schema, function, description )` ### -### `has_function( schema, function )` ### -### `has_function( function, args[], description )` ### -### `has_function( function, args[] )` ### -### `has_function( function, description )` ### -### `has_function( function )` ### +### `has_function()` ### + + SELECT has_function( :schema, :function, :args, :description ); + SELECT has_function( :schema, :function, :args ); + SELECT has_function( :schema, :function, :description ); + SELECT has_function( :schema, :function ); + SELECT has_function( :function, :args, :description ); + SELECT has_function( :function, :args ); + SELECT has_function( :function, :description ); + SELECT has_function( :function ); + +**Parameters** + +`:schema` +: Name of a schema in which to not find the function. + +`:function` +: Name of a function. + +`:args` +: Array of data types of the function arguments. + +`:description` +: A short description of the test. + +Checks to be sure that the given function exists in the named schema and with +the specified argument data types. If `:schema` is omitted, `has_function()` will +search for the function in the schemas defined in the search path. If +`:args[]` is omitted, `has_function()` will see if the function exists without +regard to its arguments. Some examples: SELECT has_function( 'pg_catalog', @@ -2983,12 +3005,6 @@ rule does *not* exist. SELECT has_function( 'do_something', ARRAY['integer'] ); SELECT has_function( 'do_something', ARRAY['numeric'] ); -Checks to be sure that the given function exists in the named schema and with -the specified argument data types. If `:schema` is omitted, `has_function()` will -search for the function in the schemas defined in the search path. If -`:args[]` is omitted, `has_function()` will see if the function exists without -regard to its arguments. - The `:args[]` argument should be formatted as it would be displayed in the view of a function using the `\df` command in `psql`. For example, even if you have a numeric column with a precision of 8, you should specify @@ -3010,221 +3026,380 @@ cast it to `name[]` to disambiguate it from a text string: still available, but emits a warning when called. It will be removed in a future version of pgTAP. -### `hasnt_function( schema, function, args[], description )` ### -### `hasnt_function( schema, function, args[] )` ### -### `hasnt_function( schema, function, description )` ### -### `hasnt_function( schema, function )` ### -### `hasnt_function( function, args[], description )` ### -### `hasnt_function( function, args[] )` ### -### `hasnt_function( function, description )` ### -### `hasnt_function( function )` ### +### `hasnt_function()` ### - SELECT hasnt_function( - 'pg_catalog', - 'alamode', - ARRAY[ 'text', 'text' ], - 'Function alamode(text, text) should not exist' - ); + SELECT hasnt_function( :schema, :function, :args, :description ); + SELECT hasnt_function( :schema, :function, :args ); + SELECT hasnt_function( :schema, :function, :description ); + SELECT hasnt_function( :schema, :function ); + SELECT hasnt_function( :function, :args, :description ); + SELECT hasnt_function( :function, :args ); + SELECT hasnt_function( :function, :description ); + SELECT hasnt_function( :function ); + +**Parameters** + +`:schema` +: Name of a schema in which not to find the function. + +`:function` +: Name of a function. - SELECT hasnt_function( 'bogus' ); - SELECT hasnt_function( 'bogus', ARRAY['integer'] ); - SELECT hasnt_function( 'bogus', ARRAY['numeric'] ); +`:args` +: Array of data types of the function arguments. + +`:description` +: A short description of the test. This function is the inverse of `has_function()`. The test passes if the specified function (optionally with the specified signature) does *not* exist. -### `has_cast( source_type, target_type, schema, function, description )` ### -### `has_cast( source_type, target_type, schema, function )` ### -### `has_cast( source_type, target_type, function, description )` ### -### `has_cast( source_type, target_type, function )` ### -### `has_cast( source_type, target_type, description )` ### -### `has_cast( source_type, target_type )` ### +### `has_cast()` ### - SELECT has_cast( - 'integer', - 'bigint', - 'pg_catalog', - 'int8' - 'We should have a cast from integer to bigint' - ); + SELECT has_cast( :source_type, :target_type, :schema, :function, :description ); + SELECT has_cast( :source_type, :target_type, :schema, :function ); + SELECT has_cast( :source_type, :target_type, :function, :description ); + SELECT has_cast( :source_type, :target_type, :function ); + SELECT has_cast( :source_type, :target_type, :description ); + SELECT has_cast( :source_type, :target_type ); + +**Parameters** + +`:source_type` +: Data type of the source value. + +`:target_type` +: Data type of the target value. + +`:schema` +: Schema in which to find the operator function. + +`:function` +: Name of the operator function. + +`:description` +: A short description of the test. Tests for the existence of a cast. A cast consists of a source data type, a -target data type, and perhaps a (possibly schema-qualified) function. If you -omit the description four the 3- or 4-argument version, you'll need to cast -the function name to the `NAME` data type so that PostgreSQL doesn't resolve -the function name as a description. For example: +target data type, and perhaps a (possibly schema-qualified) function. An example: + + SELECT has_cast( 'integer', 'bigint', 'pg_catalog', 'int8' ); + +If you omit the description four the 3- or 4-argument version, you'll need to +cast the function name to the `NAME` data type so that PostgreSQL doesn't +resolve the function name as a description. For example: SELECT has_cast( 'integer', 'bigint', 'int8'::NAME ); pgTAP will generate a useful description if you don't provide one. -### `hasnt_cast( source_type, target_type, schema, function, description )` ### -### `hasnt_cast( source_type, target_type, schema, function )` ### -### `hasnt_cast( source_type, target_type, function, description )` ### -### `hasnt_cast( source_type, target_type, function )` ### -### `hasnt_cast( source_type, target_type, description )` ### -### `hasnt_cast( source_type, target_type )` ### +### `hasnt_cast()` ### + + SELECT hasnt_cast( :source_type, :target_type, :schema, :function, :description ); + SELECT hasnt_cast( :source_type, :target_type, :schema, :function ); + SELECT hasnt_cast( :source_type, :target_type, :function, :description ); + SELECT hasnt_cast( :source_type, :target_type, :function ); + SELECT hasnt_cast( :source_type, :target_type, :description ); + SELECT hasnt_cast( :source_type, :target_type ); + +**Parameters** + +`:source_type` +: Data type of the source value. - SELECT hasnt_cast( 'integer', 'circle' ); +`:target_type` +: Data type of the target value. + +`:schema` +: Schema in which not to find the operator function. + +`:function` +: Name of the operator function. + +`:description` +: A short description of the test. -This function is the inverse of `has_cast()`. The test passes if the specified +This function is the inverse of `has_cast()`: the test passes if the specified cast does *not* exist. -### `has_operator( left_type, schema, name, right_type, return_type, description )` ### -### `has_operator( left_type, schema, name, right_type, return_type )` ### -### `has_operator( left_type, name, right_type, return_type, description )` ### -### `has_operator( left_type, name, right_type, return_type )` ### -### `has_operator( left_type, name, right_type, description )` ### -### `has_operator( left_type, name, right_type )` ### + SELECT has_operator( :left_type, :schema, :name, :right_type, :return_type, :description ); + SELECT has_operator( :left_type, :schema, :name, :right_type, :return_type ); + SELECT has_operator( :left_type, :name, :right_type, :return_type, :description ); + SELECT has_operator( :left_type, :name, :right_type, :return_type ); + SELECT has_operator( :left_type, :name, :right_type, :description ); + SELECT has_operator( :left_type, :name, :right_type ); - SELECT has_operator( - 'integer', - 'pg_catalog', '<=', - 'integer', - 'boolean', - 'Operator (integer <= integer RETURNS boolean) should exist' - ); +**Parameters** + +`:left_type` +: Data type of the left operand. + +`:schema` +: Schema in which to find the operator. + +`:name` +: Name of the operator. + +`:right_type` +: Data type of the right operand. + +`:return_type` +: Data type of the return value. + +`:description` +: A short description of the test. Tests for the presence of a binary operator. If the operator exists with the given schema, name, left and right arguments, and return value, the test will -fail. If the operator does not exist, the test will fail. If you omit the -schema name, then the operator must be visible in the search path. If you omit -the test description, pgTAP will generate a reasonable one for you. The return -value is also optional. If you need to test for a left or right unary -operator, use `has_leftop()` or `has_rightop()` instead. - -### `has_leftop( schema, name, right_type, return_type, description )` ### -### `has_leftop( schema, name, right_type, return_type )` ### -### `has_leftop( name, right_type, return_type, description )` ### -### `has_leftop( name, right_type, return_type )` ### -### `has_leftop( name, right_type, description )` ### -### `has_leftop( name, right_type )` ### - - SELECT has_leftop( - 'pg_catalog', '!!', - 'bigint', - 'numeric', - 'Operator (!! bigint RETURNS numeric) should exist' - ); +fail. If the operator does not exist, the test will fail. Example: + + SELECT has_operator( 'integer', 'pg_catalog', '<=', 'integer', 'boolean' ); + +If you omit the schema name, then the operator must be visible in the search +path. If you omit the test description, pgTAP will generate a reasonable one +for you. The return value is also optional. If you need to test for a left or +right unary operator, use `has_leftop()` or `has_rightop()` instead. + +### `has_leftop()` ### + + SELECT has_leftop( :schema, :name, :type, :return_type, :description ); + SELECT has_leftop( :schema, :name, :type, :return_type ); + SELECT has_leftop( :name, :type, :return_type, :description ); + SELECT has_leftop( :name, :type, :return_type ); + SELECT has_leftop( :name, :type, :description ); + SELECT has_leftop( :name, :type ); + +**Parameters** + +`:schema` +: Schema in which to find the operator. + +`:name` +: Name of the operator. + +`:type` +: Data type of the operand. + +`:return_type` +: Data type of the return value. + +`:description` +: A short description of the test. Tests for the presence of a left-unary operator. If the operator exists with the given schema, name, right argument, and return value, the test will fail. -If the operator does not exist, the test will fail. If you omit the schema -name, then the operator must be visible in the search path. If you omit the -test description, pgTAP will generate a reasonable one for you. The return -value is also optional. - -### `has_rightop( left_type, schema, name, return_type, description )` ### -### `has_rightop( left_type, schema, name, return_type )` ### -### `has_rightop( left_type, name, return_type, description )` ### -### `has_rightop( left_type, name, return_type )` ### -### `has_rightop( left_type, name, description )` ### -### `has_rightop( left_type, name )` ### - - SELECT has_rightop( - 'bigint', - 'pg_catalog', '!', - 'numeric', - 'Operator (bigint ! RETURNS numeric) should exist' - ); +If the operator does not exist, the test will fail. Example: + + SELECT has_leftop( 'pg_catalog', '!!', 'bigint', 'numeric' ); + +If you omit the schema name, then the operator must be visible in the search +path. If you omit the test description, pgTAP will generate a reasonable one +for you. The return type is also optional. + +### `has_rightop()` ### + + SELECT has_rightop( :schema, :name, :type, :return_type, :description ); + SELECT has_rightop( :schema, :name, :type, :return_type ); + SELECT has_rightop( :name, :type, :return_type, :description ); + SELECT has_rightop( :name, :type, :return_type ); + SELECT has_rightop( :name, :type, :description ); + SELECT has_rightop( :name, :type ); + +**Parameters** + +`:schema` +: Schema in which to find the operator. + +`:name` +: Name of the operator. + +`:type` +: Data type of the operand. + +`:return_type` +: Data type of the return value. + +`:description` +: A short description of the test. Tests for the presence of a right-unary operator. If the operator exists with the given left argument, schema, name, and return value, the test will fail. -If the operator does not exist, the test will fail. If you omit the schema -name, then the operator must be visible in the search path. If you omit the -test description, pgTAP will generate a reasonable one for you. The return -value is also optional. +If the operator does not exist, the test will fail. Example: -### `has_opclass( schema, name, description )` ### -### `has_opclass( schema, name )` ### -### `has_opclass( name, description )` ### -### `has_opclass( name )` ### + SELECT has_rightop( 'bigint', 'pg_catalog', '!', 'numeric' ); - SELECT has_opclass( - 'myschema', - 'my_ops', - 'We should have the "my_ops" operator class' - ); +If you omit the schema name, then the operator must be visible in the search +path. If you omit the test description, pgTAP will generate a reasonable one +for you. The return type is also optional. + +### `has_opclass()` ### + + SELECT has_opclass( :schema, :name, :description ); + SELECT has_opclass( :schema, :name ); + SELECT has_opclass( :name, :description ); + SELECT has_opclass( :name ); + +**Parameters** + +`:schema` +: Schema in which to find the operator class. + +`:name` +: Name of the operator class. + +`:description` +: A short description of the test. Tests for the presence of an operator class. If you omit the schema name, then the operator must be visible in the search path. If you omit the test description, pgTAP will generate a reasonable one for you. The return value is also optional. -### `hasnt_opclass( schema, name, description )` ### -### `hasnt_opclass( schema, name )` ### -### `hasnt_opclass( name, description )` ### -### `hasnt_opclass( name )` ### +### `hasnt_opclass()` ### - SELECT hasnt_opclass( - 'myschema', - 'your_ops', - 'We should not have the "your_ops" operator class' - ); + SELECT hasnt_opclass( :schema, :name, :description ); + SELECT hasnt_opclass( :schema, :name ); + SELECT hasnt_opclass( :name, :description ); + SELECT hasnt_opclass( :name ); + +**Parameters** + +`:schema` +: Schema in which not to find the operator class. + +`:name` +: Name of the operator class. + +`:description` +: A short description of the test. This function is the inverse of `has_opclass()`. The test passes if the specified operator class does *not* exist. -### `has_role( role, description )` ### -### `has_role( role )` ### +### `has_role()` ### - SELECT has_role( 'theory', 'Role "theory" should exist' ); + SELECT has_role( :role, :description ); + SELECT has_role( :role ); + +**Parameters** + +`:role` +: Name of the role. + +`:description` +: A short description of the test. Checks to ensure that a database role exists. If the description is omitted, it will default to "Role `:role` should exist". -### `hasnt_role( role, description )` ### -### `hasnt_role( role )` ### +### `hasnt_role()` ### + + SELECT hasnt_role( :role, :description ); + SELECT hasnt_role( :role ); + +**Parameters** - SELECT hasnt_role( 'theory', 'Role "theory" should not exist' ); +`:role` +: Name of the role. + +`:description` +: A short description of the test. The inverse of `has_role()`, this function tests for the *absence* of a database role. -### `has_user( user, description )` ### -### `has_user( user )` ### +### `has_user()` ### + + SELECT has_user( :user, :description ); + SELECT has_user( :user ); + +**Parameters** + +`:user` +: Name of the user. - SELECT has_user( 'theory', 'User "theory" should exist' ); +`:description` +: A short description of the test. Checks to ensure that a database user exists. If the description is omitted, it will default to "User `:user` should exist". -### `hasnt_user( user, description )` ### -### `hasnt_user( user )` ### +### `hasnt_user()` ### + + SELECT hasnt_user( :user, :description ); + SELECT hasnt_user( :user ); - SELECT hasnt_user( 'theory', 'User "theory" should not exist' ); +**Parameters** + +`:user` +: Name of the user. + +`:description` +: A short description of the test. The inverse of `has_user()`, this function tests for the *absence* of a database user. -### `has_group( group, description )` ### -### `has_group( group )` ### +### `has_group()` ### + + SELECT has_group( :group, :description ); + SELECT has_group( :group ); + +**Parameters** - SELECT has_group( 'sweeties, 'Group "sweeties" should exist' ); +`:group` +: Name of the group. + +`:description` +: A short description of the test. Checks to ensure that a database group exists. If the description is omitted, it will default to "Group `:group` should exist". -### `hasnt_group( group, description )` ### -### `hasnt_group( group )` ### +### `hasnt_group()` ### + + SELECT hasnt_group( :group, :description ); + SELECT hasnt_group( :group ); + +**Parameters** - SELECT hasnt_group( 'meanies, 'Group meaines should not exist' ); +`:group` +: Name of the group. + +`:description` +: A short description of the test. The inverse of `has_group()`, this function tests for the *absence* of a database group. -### `has_language( language, description )` ### -### `has_language( language )` ### +### `has_language()` ### - SELECT has_language( 'plpgsql', 'Language "plpgsql" should exist' ); + SELECT has_language( :language, :description ); + SELECT has_language( :language ); + +**Parameters** + +`:language` +: Name of the language. + +`:description` +: A short description of the test. Checks to ensure that a procedural language exists. If the description is omitted, it will default to "Procedural language `:language` should exist". -### `hasnt_language( language, description )` ### -### `hasnt_language( language )` ### +### `hasnt_language()` ### + + SELECT hasnt_language( :language, :description ); + SELECT hasnt_language( :language ); - SELECT hasnt_language( 'plpgsql', 'Language "plpgsql" should not exist' ); +**Parameters** + +`:language` +: Name of the language. + +`:description` +: A short description of the test. The inverse of `has_language()`, this function tests for the *absence* of a procedural language. From bd07786d150211cc9838e95f4826d8af2ce3ce69 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 13 Sep 2011 14:06:48 -0700 Subject: [PATCH 0622/1195] Update "Table for One" doc section. --- doc/pgtap.mmd | 888 +++++++++++++++++++++++++++++------------------ sql/pgtap.sql.in | 2 +- 2 files changed, 559 insertions(+), 331 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index d2b499ebbf1b..d49b07128bde 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1842,10 +1842,10 @@ missing sequences, like so: ### `columns_are()` ### - SELECT columns_are( :schema, :table, :columns[], :description ); - SELECT columns_are( :schema, :table, :columns[] ); - SELECT columns_are( :table, :columns[], :description ); - SELECT columns_are( :table, :columns[] ); + SELECT columns_are( :schema, :table, :columns, :description ); + SELECT columns_are( :schema, :table, :columns ); + SELECT columns_are( :table, :columns, :description ); + SELECT columns_are( :table, :columns ); **Parameters** @@ -1885,10 +1885,10 @@ missing columns, like so: ### `indexes_are()` ### - SELECT indexes_are( :schema, :table, :indexes[], :description ); - SELECT indexes_are( :schema, :table, :indexes[] ); - SELECT indexes_are( :table, :indexes[], :description ); - SELECT indexes_are( :table, :indexes[] ); + SELECT indexes_are( :schema, :table, :indexes, :description ); + SELECT indexes_are( :schema, :table, :indexes ); + SELECT indexes_are( :table, :indexes, :description ); + SELECT indexes_are( :table, :indexes ); **Parameters** @@ -1927,10 +1927,10 @@ missing indexes, like so: ### `triggers_are()` ### - SELECT triggers_are( :schema, :table, :triggers[], :description ); - SELECT triggers_are( :schema, :table, :triggers[] ); - SELECT triggers_are( :table, :triggers[], :description ); - SELECT triggers_are( :table, :triggers[] ); + SELECT triggers_are( :schema, :table, :triggers, :description ); + SELECT triggers_are( :schema, :table, :triggers ); + SELECT triggers_are( :table, :triggers, :description ); + SELECT triggers_are( :table, :triggers ); **Parameters** @@ -2154,10 +2154,10 @@ missing opclasses, like so: ### `rules_are()` ### - SELECT rules_are( :schema, :table, :rules[], :description ); - SELECT rules_are( :schema, :table, :rules[] ); - SELECT rules_are( :table, :rules[], :description ); - SELECT rules_are( :table, :rules[] ); + SELECT rules_are( :schema, :table, :rules, :description ); + SELECT rules_are( :schema, :table, :rules ); + SELECT rules_are( :table, :rules, :description ); + SELECT rules_are( :table, :rules ); **Parameters** @@ -2989,9 +2989,9 @@ rule does *not* exist. : A short description of the test. Checks to be sure that the given function exists in the named schema and with -the specified argument data types. If `:schema` is omitted, `has_function()` will -search for the function in the schemas defined in the search path. If -`:args[]` is omitted, `has_function()` will see if the function exists without +the specified argument data types. If `:schema` is omitted, `has_function()` +will search for the function in the schemas defined in the search path. If +`:args` is omitted, `has_function()` will see if the function exists without regard to its arguments. Some examples: SELECT has_function( @@ -3005,11 +3005,11 @@ regard to its arguments. Some examples: SELECT has_function( 'do_something', ARRAY['integer'] ); SELECT has_function( 'do_something', ARRAY['numeric'] ); -The `:args[]` argument should be formatted as it would be displayed in the -view of a function using the `\df` command in `psql`. For example, even if you -have a numeric column with a precision of 8, you should specify +The `:args` argument should be formatted as it would be displayed in the view +of a function using the `\df` command in `psql`. For example, even if you have +a numeric column with a precision of 8, you should specify `ARRAY['numeric']`". If you created a `varchar(64)` column, you should pass -the `:args[]` argument as `ARRAY['character varying']`. +the `:args` argument as `ARRAY['character varying']`. If you wish to use the two-argument form of `has_function()`, specifying only the schema and the function name, you must cast the `:function` argument to @@ -3017,8 +3017,8 @@ the schema and the function name, you must cast the `:function` argument to `has_function(:function, :description)` form. If you neglect to do so, your results will be unexpected. -Also, if you use the string form to specify the `:args[]` array, be sure to -cast it to `name[]` to disambiguate it from a text string: +Also, if you use the string form to specify the `:args` array, be sure to cast +it to `name` to disambiguate it from a text string: SELECT has_function( 'lower', '{text}'::name[] ); @@ -3414,16 +3414,25 @@ look at tables. There are a lot of ways to look at tables, to make sure that they have all the columns, indexes, constraints, keys, and indexes they need. So we have the assertions to validate 'em. -### `has_column( schema, table, column, description )` ### -### `has_column( table, column, description )` ### -### `has_column( table, column )` ### +### `has_column()` ### - SELECT has_column( - 'myschema', - 'sometable', - 'somecolumn', - 'I got myschema.sometable.somecolumn' - ); + SELECT has_column( :schema, :table, :column, :description ); + SELECT has_column( :table, :column, :description ); + SELECT has_column( :table, :column ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table. + +`:column` +: Name of the column. + +`:description` +: A short description of the test. Tests whether or not a column exists in a given table, view, or composite type. The first argument is the schema name, the second the table name, the @@ -3432,31 +3441,49 @@ is omitted, the table must be visible in the search path. If the test description is omitted, it will be set to "Column `:table.:column` should exist". -### `hasnt_column( schema, table, column, description )` ### -### `hasnt_column( table, column, description )` ### -### `hasnt_column( table, column )` ### +### `hasnt_column()` ### - SELECT hasnt_column( - 'myschema', - 'sometable', - 'somecolumn', - 'There should be no myschema.sometable.somecolumn column' - ); + SELECT hasnt_column( :schema, :table, :column, :description ); + SELECT hasnt_column( :table, :column, :description ); + SELECT hasnt_column( :table, :column ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table. + +`:column` +: Name of the column. + +`:description` +: A short description of the test. This function is the inverse of `has_column()`. The test passes if the specified column does *not* exist in the specified table, view, or composite type. -### `col_not_null( schema, table, column, description )` ### -### `col_not_null( table, column, description )` ### -### `col_not_null( table, column )` ### +### `col_not_null()` ### - SELECT col_not_null( - 'myschema', - 'sometable', - 'somecolumn', - 'Column myschema.sometable.somecolumn should be NOT NULL' - ); + SELECT col_not_null( :schema, :table, :column, :description ); + SELECT col_not_null( :table, :column, :description ); + SELECT col_not_null( :table, :column ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table. + +`:column` +: Name of the column. + +`:description` +: A short description of the test. Tests whether the specified column has a `NOT NULL` constraint. The first argument is the schema name, the second the table name, the third the column @@ -3467,16 +3494,25 @@ test will fail with a useful diagnostic message if the table or column in question does not exist. But use `has_column()` to make sure the column exists first, eh? -### `col_is_null( schema, table, column, description )` ### -### `col_is_null( table, column, description )` ### -### `col_is_null( table, column )` ### +### `col_is_null()` ### - SELECT col_is_null( - 'myschema', - 'sometable', - 'somecolumn', - 'Column myschema.sometable.somecolumn should allow NULL' - ); + SELECT col_is_null( :schema, :table, :column, :description ); + SELECT col_is_null( :table, :column, :description ); + SELECT col_is_null( :table, :column ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table. + +`:column` +: Name of the column. + +`:description` +: A short description of the test. This function is the inverse of `col_not_null()`: the test passes if the column does not have a `NOT NULL` constraint. The first argument is the schema @@ -3487,16 +3523,25 @@ the search path. If the test description is omitted, it will be set to "Column useful diagnostic message if the table or column in question does not exist. But use `has_column()` to make sure the column exists first, eh? -### `col_has_default( schema, table, column, description )` ### -### `col_has_default( table, column, description )` ### -### `col_has_default( table, column )` ### +### `col_has_default()` ### - SELECT col_has_default( - 'myschema', - 'sometable', - 'somecolumn', - 'Column myschema.sometable.somecolumn has a default' - ); + SELECT col_has_default( :schema, :table, :column, :description ); + SELECT col_has_default( :table, :column, :description ); + SELECT col_has_default( :table, :column ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table. + +`:column` +: Name of the column. + +`:description` +: A short description of the test. Tests whether or not a column has a default value. Fails if the column doesn't have a default value. It will also fail if the column doesn't exist, and emit @@ -3505,50 +3550,74 @@ useful diagnostics to let you know: # Failed test 136: "desc" # Column public.sometab.__asdfasdfs__ does not exist -### `col_hasnt_default( schema, table, column, description )` ### -### `col_hasnt_default( table, column, description )` ### -### `col_hasnt_default( table, column )` ### +### `col_hasnt_default()` ### - SELECT col_hasnt_default( - 'myschema', - 'sometable', - 'somecolumn', - 'There should be no default on myschema.sometable.somecolumn' - ); + SELECT col_hasnt_default( :schema, :table, :column, :description ); + SELECT col_hasnt_default( :table, :column, :description ); + SELECT col_hasnt_default( :table, :column ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table. + +`:column` +: Name of the column. + +`:description` +: A short description of the test. This function is the inverse of `col_has_default()`. The test passes if the specified column does *not* have a default. It will still fail if the column does not exist, and emit useful diagnostics to let you know. -### `col_type_is( schema, table, column, schema, type, description )` ### -### `col_type_is( schema, table, column, schema, type )` ### -### `col_type_is( schema, table, column, type, description )` ### -### `col_type_is( schema, table, column, type )` ### -### `col_type_is( table, column, type, description )` ### -### `col_type_is( table, column, type )` ### + SELECT col_type_is( :schema, :table, :column, :type_schema, :type, :description ); + SELECT col_type_is( :schema, :table, :column, :type_schema, :type ); + SELECT col_type_is( :schema, :table, :column, :type, :description ); + SELECT col_type_is( :schema, :table, :column, :type ); + SELECT col_type_is( :table, :column, :type, :description ); + SELECT col_type_is( :table, :column, :type ); - SELECT col_type_is( - 'myschema', - 'sometable', - 'somecolumn', - 'numeric(10,2)', - 'Column myschema.sometable.somecolumn should be type text' - ); +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table. + +`:column` +: Name of the column. + +`:type_schema` +: Schema in which to find the data type. + +`:type` +: Name of a data type. + +`:description` +: A short description of the test. This function tests that the specified column is of a particular type. If it fails, it will emit diagnostics naming the actual type. The first argument is the schema name, the second the table name, the third the column name, the -fourth the type's schem, the fifth the type, and the sixth is the test -description. If the table schema is omitted, the table and the type must be -visible in the search path. If the test description is omitted, it will be set -to "Column `:schema.:table.:column` should be type `:schema.:type`". Note that -this test will fail if the table or column in question does not exist. +fourth the type's schema, the fifth the type, and the sixth is the test +description. Example: If the table schema is omitted, the table and the type +must be visible in the search path. If the test description is omitted, it +will be set to "Column `:schema.:table.:column` should be type +`:schema.:type`". Note that this test will fail if the table or column in +question does not exist. The type argument should be formatted as it would be displayed in the view of a table using the `\d` command in `psql`. For example, if you have a numeric column with a precision of 8, you should specify "numeric(8,0)". If you created a `varchar(64)` column, you should pass the type as "character -varying(64)". +varying(64)". Example: + + SELECT col_type_is( 'myschema', 'sometable', 'somecolumn', 'numeric(10,2)' ); If the test fails, it will output useful diagnostics. For example this test: @@ -3564,17 +3633,28 @@ It will even tell you if the test fails because a column doesn't exist or actually has no default. But use `has_column()` to make sure the column exists first, eh? -### `col_default_is( schema, table, column, default, description )` ### -### `col_default_is( table, column, default, description )` ### -### `col_default_is( table, column, default )` ### +### `col_default_is()` ### - SELECT col_default_is( - 'myschema', - 'sometable', - 'somecolumn', - 'howdy'::text, - 'Column myschema.sometable.somecolumn should default to ''howdy''' - ); + SELECT col_default_is( :schema, :table, :column, :default, :description ); + SELECT col_default_is( :table, :column, :default, :description ); + SELECT col_default_is( :table, :column, :default ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table. + +`:column` +: Name of the column. + +`:default` +: Default value expressed as a string. + +`:description` +: A short description of the test. Tests the default value of a column. If it fails, it will emit diagnostics showing the actual default value. The first argument is the schema name, the @@ -3623,15 +3703,22 @@ the diagnostics will tell you that, too. But you use `has_column()` and `col_has_default()` to test those conditions before you call `col_default_is()`, right? *Right???* Yeah, good, I thought so. -### `has_pk( schema, table, description )` ### -### `has_pk( table, description )` ### -### `has_pk( table )` ### +### `has_pk()` ### - SELECT has_pk( - 'myschema', - 'sometable', - 'Table myschema.sometable should have a primary key' - ); + SELECT has_pk( :schema, :table, :description ); + SELECT has_pk( :table, :description ); + SELECT has_pk( :table ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table. + +`:description` +: A short description of the test. Tests whether or not a table has a primary key. The first argument is the schema name, the second the table name, the the third is the test description. @@ -3640,28 +3727,42 @@ test description is omitted, it will be set to "Table `:table` should have a primary key". Note that this test will fail if the table in question does not exist. -### `hasnt_pk( schema, table, description )` ### -### `hasnt_pk( table, description )` ### -### `hasnt_pk( table )` ### +### `hasnt_pk()` ### - SELECT hasnt_pk( - 'myschema', - 'sometable', - 'Table myschema.sometable should not have a primary key' - ); + SELECT hasnt_pk( :schema, :table, :description ); + SELECT hasnt_pk( :table, :description ); + SELECT hasnt_pk( :table ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table. + +`:description` +: A short description of the test. This function is the inverse of `has_pk()`. The test passes if the specified primary key does *not* exist. -### `has_fk( schema, table, description )` ### -### `has_fk( table, description )` ### -### `has_fk( table )` ### +### `has_fk()` ### - SELECT has_fk( - 'myschema', - 'sometable', - 'Table myschema.sometable should have a foreign key constraint' - ); + SELECT has_fk( :schema, :table, :description ); + SELECT has_fk( :table, :description ); + SELECT has_fk( :table ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table. + +`:description` +: A short description of the test. Tests whether or not a table has a foreign key constraint. The first argument is the schema name, the second the table name, the the third is the test @@ -3670,46 +3771,65 @@ path. If the test description is omitted, it will be set to "Table `:table` should have a foreign key constraint". Note that this test will fail if the table in question does not exist. -### `hasnt_fk( schema, table, description )` ### -### `hasnt_fk( table, description )` ### -### `hasnt_fk( table )` ### +### `hasnt_fk()` ### - SELECT hasnt_fk( - 'myschema', - 'sometable', - 'Table myschema.sometable should not have a foreign key constraint' - ); + SELECT hasnt_fk( :schema, :table, :description ); + SELECT hasnt_fk( :table, :description ); + SELECT hasnt_fk( :table ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table. + +`:description` +: A short description of the test. This function is the inverse of `has_fk()`. The test passes if the specified foreign key does *not* exist. -### `col_is_pk( schema, table, column, description )` ### -### `col_is_pk( schema, table, column[], description )` ### -### `col_is_pk( table, column, description )` ### -### `col_is_pk( table, column[], description )` ### -### `col_is_pk( table, column )` ### -### `col_is_pk( table, column[] )` ### +### `col_is_pk()` ### - SELECT col_is_pk( - 'myschema', - 'sometable', - 'id', - 'Column myschema.sometable.id should be a primary key' - ); + SELECT col_is_pk( :schema, :table, :columns, :description ); + SELECT col_is_pk( :schema, :table, :column, :description ); + SELECT col_is_pk( :table, :columns, :description ); + SELECT col_is_pk( :table, :column, :description ); + SELECT col_is_pk( :table, :columns ); + SELECT col_is_pk( :table, :column ); - SELECT col_is_pk( - 'persons', - ARRAY['given_name', 'surname'], - ); +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table containing the primary key. + +`:columns` +: Array of the names of the primary key columns. + +`:column` +: Name of the primary key column. + +`:description` +: A short description of the test. Tests whether the specified column or columns in a table is/are the primary key for that table. If it fails, it will emit diagnostics showing the actual primary key columns, if any. The first argument is the schema name, the second the table name, the third the column name or an array of column names, and the -fourth is the test description. If the schema is omitted, the table must be -visible in the search path. If the test description is omitted, it will be set -to "Column `:table(:column)` should be a primary key". Note that this test -will fail if the table or column in question does not exist. +fourth is the test description. Examples: + + SELECT col_is_pk( 'myschema', 'sometable', 'id' ); + SELECT col_is_pk( 'persons', ARRAY['given_name', 'surname'] ); + +If the schema is omitted, the table must be visible in the search path. If the +test description is omitted, it will be set to "Column `:table(:column)` +should be a primary key". Note that this test will fail if the table or column +in question does not exist. If the test fails, it will output useful diagnostics. For example this test: @@ -3721,46 +3841,60 @@ Will produce something like this: # have: {} # want: {id} -### `col_isnt_pk( schema, table, column, description )` ### -### `col_isnt_pk( schema, table, column[], description )` ### -### `col_isnt_pk( table, column, description )` ### -### `col_isnt_pk( table, column[], description )` ### -### `col_isnt_pk( table, column )` ### -### `col_isnt_pk( table, column[] )` ### +### `col_isnt_pk()` ### - SELECT col_isnt_pk( - 'myschema', - 'sometable', - 'id', - 'Column myschema.sometable.id should not be a primary key' - ); + SELECT col_isnt_pk( :schema, :table, :columns, :description ); + SELECT col_isnt_pk( :schema, :table, :column, :description ); + SELECT col_isnt_pk( :table, :columns, :description ); + SELECT col_isnt_pk( :table, :column, :description ); + SELECT col_isnt_pk( :table, :columns ); + SELECT col_isnt_pk( :table, :column ); - SELECT col_isnt_pk( - 'persons', - ARRAY['given_name', 'surname'], - ); +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table not containing the primary key. + +`:columns` +: Array of the names of the primary key columns. + +`:column` +: Name of the primary key column. + +`:description` +: A short description of the test. This function is the inverse of `col_is_pk()`. The test passes if the specified column or columns are not a primary key. -### `col_is_fk( schema, table, column, description )` ### -### `col_is_fk( schema, table, column[], description )` ### -### `col_is_fk( table, column, description )` ### -### `col_is_fk( table, column[], description )` ### -### `col_is_fk( table, column )` ### -### `col_is_fk( table, column[] )` ### +### `col_is_fk()` ### - SELECT col_is_fk( - 'myschema', - 'sometable', - 'other_id', - 'Column myschema.sometable.other_id should be a foreign key' - ); + SELECT col_is_fk( :schema, :table, :columns, :description ); + SELECT col_is_fk( :schema, :table, :column, :description ); + SELECT col_is_fk( :table, :columns, :description ); + SELECT col_is_fk( :table, :column, :description ); + SELECT col_is_fk( :table, :columns ); + SELECT col_is_fk( :table, :column ); - SELECT col_is_fk( - 'contacts', - ARRAY['given_name', 'surname'], - ); +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table containing the foreign key constraint. + +`:columns` +: Array of the names of the foreign key columns. + +`:column` +: Name of the foreign key column. + +`:description` +: A short description of the test. Just like `col_is_fk()`, except that it test that the column or array of columns are a primary key. The diagnostics on failure are a bit different, @@ -3771,53 +3905,74 @@ simply list all of the foreign key constraint columns, like so: # {thingy_id} # {surname,given_name} -### `col_isnt_fk( schema, table, column, description )` ### -### `col_isnt_fk( schema, table, column[], description )` ### -### `col_isnt_fk( table, column, description )` ### -### `col_isnt_fk( table, column[], description )` ### -### `col_isnt_fk( table, column )` ### -### `col_isnt_fk( table, column[] )` ### +### `col_isnt_fk()` ### - SELECT col_isnt_fk( - 'myschema', - 'sometable', - 'other_id', - 'Column myschema.sometable.other_id should not be a foreign key' - ); + SELECT col_isnt_fk( :schema, :table, :columns, :description ); + SELECT col_isnt_fk( :schema, :table, :column, :description ); + SELECT col_isnt_fk( :table, :columns, :description ); + SELECT col_isnt_fk( :table, :column, :description ); + SELECT col_isnt_fk( :table, :columns ); + SELECT col_isnt_fk( :table, :column ); - SELECT col_isnt_fk( - 'contacts', - ARRAY['given_name', 'surname'], - ); +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table not containing the foreign key constraint. + +`:columns` +: Array of the names of the foreign key columns. + +`:column` +: Name of the foreign key column. + +`:description` +: A short description of the test. This function is the inverse of `col_is_fk()`. The test passes if the specified column or columns are not a foreign key. -### `fk_ok( fk_schema, fk_table, fk_column[], pk_schema, pk_table, pk_column[], description )` ### -### `fk_ok( fk_schema, fk_table, fk_column[], fk_schema, pk_table, pk_column[] )` ### -### `fk_ok( fk_table, fk_column[], pk_table, pk_column[], description )` ### -### `fk_ok( fk_table, fk_column[], pk_table, pk_column[] )` ### -### `fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column, description )` ### -### `fk_ok( fk_schema, fk_table, fk_column, pk_schema, pk_table, pk_column )` ### -### `fk_ok( fk_table, fk_column, pk_table, pk_column, description )` ### -### `fk_ok( fk_table, fk_column, pk_table, pk_column )` ### +### `fk_ok()` ### - SELECT fk_ok( - 'myschema', - 'sometable', - 'big_id', - 'myschema', - 'bigtable', - 'id', - 'myschema.sometable(big_id) should reference myschema.bigtable(id)' - ); + SELECT fk_ok( :fk_schema, :fk_table, :fk_columns, :pk_schema, :pk_table, :pk_columns, :description ); + SELECT fk_ok( :fk_schema, :fk_table, :fk_columns, :fk_schema, :pk_table, :pk_columns ); + SELECT fk_ok( :fk_table, :fk_columns, :pk_table, :pk_columns, :description ); + SELECT fk_ok( :fk_table, :fk_columns, :pk_table, :pk_columns ); + SELECT fk_ok( :fk_schema, :fk_table, :fk_column, :pk_schema, :pk_table, :pk_column, :description ); + SELECT fk_ok( :fk_schema, :fk_table, :fk_column, :pk_schema, :pk_table, :pk_column ); + SELECT fk_ok( :fk_table, :fk_column, :pk_table, :pk_column, :description ); + SELECT fk_ok( :fk_table, :fk_column, :pk_table, :pk_column ); - SELECT fk_ok( - 'contacts', - ARRAY['person_given_name', 'person_surname'], - 'persons', - ARRAY['given_name', 'surname'], - ); +**Parameters** + +`:fk_schema` +: Schema in which to find the table with the foreign key + +`:fk_table` +: Name of a table containing the foreign key. + +`:fk_columns` +: Array of the names of the foreign key columns. + +`:fk_column` +: Name of the foreign key column. + +`:pk_schema` +: Schema in which to find the table with the primary key + +`:pk_table` +: Name of a table containing the primary key. + +`:pk_columns` +: Array of the names of the primary key columns. + +`:pk_column` +: Name of the primary key column. + +`:description` +: A short description of the test. This function combines `col_is_fk()` and `col_is_pk()` into a single test that also happens to determine that there is in fact a foreign key relationship @@ -3833,7 +3988,15 @@ key. Again, the schema is optional and the columns may be a string or array of strings (though of course it should have the same number of elements as the foreign key column argument). The seventh argument is an optional description If it's not included, it will be set to `:fk_schema.:fk_table(:fk_column)` -should reference `:pk_column.pk_table(:pk_column)`. +should reference `:pk_column.pk_table(:pk_column)`. Some examples: + + SELECT fk_ok( 'myschema', 'sometable', 'big_id', 'myschema', 'bigtable', 'id' ); + SELECT fk_ok( + 'contacts', + ARRAY['person_given_name', 'person_surname'], + 'persons', + ARRAY['given_name', 'surname'], + ); If the test fails, it will output useful diagnostics. For example this test: @@ -3845,15 +4008,22 @@ Will produce something like this: # have: contacts(person_id) REFERENCES persons(id)" # want: contacts(person_nick) REFERENCES persons(nick)" -### `has_unique( schema, table, description )` ### -### `has_unique( table, description )` ### -### `has_unique( table )` ### +### `has_unique()` ### - SELECT has_unique( - 'myschema', - 'sometable', - 'Table myschema.sometable should have a unique constraint' - ); + SELECT has_unique( :schema, :table, :description ); + SELECT has_unique( :table, :description ); + SELECT has_unique( :table ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table containing the unique constraint. + +`:description` +: A short description of the test. Tests whether or not a table has a unique constraint. The first argument is the schema name, the second the table name, the the third is the test @@ -3862,47 +4032,62 @@ path. If the test description is omitted, it will be set to "Table `:table` should have a unique constraint". Note that this test will fail if the table in question does not exist. -### `col_is_unique( schema, table, column, description )` ### -### `col_is_unique( schema, table, column[], description )` ### -### `col_is_unique( table, column, description )` ### -### `col_is_unique( table, column[], description )` ### -### `col_is_unique( table, column )` ### -### `col_is_unique( table, column[] )` ### +### `col_is_unique()` ### - SELECT col_is_unique( - 'myschema', - 'sometable', - 'other_id', - 'Column myschema.sometable.other_id should have a unique constraint' - ); + SELECT col_is_unique( schema, table, columns, description ); + SELECT col_is_unique( schema, table, column, description ); + SELECT col_is_unique( table, columns, description ); + SELECT col_is_unique( table, column, description ); + SELECT col_is_unique( table, columns ); + SELECT col_is_unique( table, column ); - SELECT col_is_unique( - 'contacts', - ARRAY['given_name', 'surname'], - ); +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table containing the unique constraint. + +`:columns` +: Array of the names of the unique columns. + +`:column` +: Name of the unique column. + +`:description` +: A short description of the test. Just like `col_is_pk()`, except that it test that the column or array of -columns have a unique constraint on them. In the event of failure, the -diagnostics will list the unique constraints that were actually found, if any: +columns have a unique constraint on them. Examples: + + SELECT col_is_unique( 'myschema', 'sometable', 'other_id' ); + SELECT col_is_unique( 'contacts', ARRAY['given_name', 'surname'] ); + +In the event of failure, the diagnostics will list the unique constraints that +were actually found, if any: Failed test 40: "users.email should be unique" have: {username} {first_name,last_name} want: {email} -### `has_check( schema, table, description )` ### -### `has_check( table, description )` ### -### `has_check( table )` ### +### `has_check()` ### - SELECT has_check( - 'myschema', - 'sometable', - 'Table myschema.sometable should have a check constraint' - ); + SELECT has_check( :schema, :table, :description ); + SELECT has_check( :table, :description ); + SELECT has_check( :table ); - Failed test 41: "users.email should have a check constraint" - have: {username} - want: {email} +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table containing the check constraint. + +`:description` +: A short description of the test. Tests whether or not a table has a check constraint. The first argument is the schema name, the second the table name, the the third is the test description. @@ -3914,93 +4099,134 @@ not exist. In the event of failure, the diagnostics will list the columns on the table that do have check constraints, if any: -### `col_has_check( schema, table, column, description )` ### -### `col_has_check( schema, table, column[], description )` ### -### `col_has_check( table, column, description )` ### -### `col_has_check( table, column[], description )` ### -### `col_has_check( table, column )` ### -### `col_has_check( table, column[] )` ### + Failed test 41: "users.email should have a check constraint" + have: {username} + want: {email} - SELECT col_has_check( - 'myschema', - 'sometable', - 'other_id', - 'Column myschema.sometable.other_id should have a check constraint' - ); +### `col_has_check()` ### - SELECT col_has_check( - 'contacts', - ARRAY['given_name', 'surname'], - ); + SELECT col_has_check( :schema, :table, :columns, :description ); + SELECT col_has_check( :schema, :table, :column, :description ); + SELECT col_has_check( :table, :columns, :description ); + SELECT col_has_check( :table, :column, :description ); + SELECT col_has_check( :table, :columns ); + SELECT col_has_check( :table, :column ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table containing the check constraint. + +`:columns` +: Array of the names of the check constraint columns. + +`:column` +: Name of the check constraint column. + +`:description` +: A short description of the test. Just like `col_is_pk()`, except that it test that the column or array of columns have a check constraint on them. -### `index_is_unique( schema, table, index, description )` ### -### `index_is_unique( schema, table, index )` ### -### `index_is_unique( table, index )` ### -### `index_is_unique( index )` ### +### `index_is_unique()` ### - SELECT index_is_unique( - 'myschema', - 'sometable', - 'myindex', - 'Index "myindex" should be unique' - ); + SELECT index_is_unique( :schema, :table, :index, :description ); + SELECT index_is_unique( :schema, :table, :index ); + SELECT index_is_unique( :table, :index ); + SELECT index_is_unique( :index ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table containing the index. - SELECT index_is_unique( 'sometable', 'myindex' ); +`:index` +: Name of the index. + +`:description` +: A short description of the test. Tests whether an index is unique. -### `index_is_primary( schema, table, index, description )` ### -### `index_is_primary( schema, table, index )` ### -### `index_is_primary( table, index )` ### -### `index_is_primary( index )` ### +### `index_is_primary()` ### - SELECT index_is_primary( - 'myschema', - 'sometable', - 'myindex', - 'Index "myindex" should be on a primary key' - ); + SELECT index_is_primary( :schema, :table, :index, :description ); + SELECT index_is_primary( :schema, :table, :index ); + SELECT index_is_primary( :table, :index ); + SELECT index_is_primary( :index ); + +**Parameters** - SELECT index_is_primary( 'sometable', 'myindex' ); +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table containing the index. + +`:index` +: Name of the index. + +`:description` +: A short description of the test. Tests whether an index is on a primary key. -### `is_clustered( schema, table, index, description )` ### -### `is_clustered( schema, table, index )` ### -### `is_clustered( table, index )` ### -### `is_clustered( index )` ### +### `is_clustered()` ### - SELECT is_clustered( - 'myschema', - 'sometable', - 'myindex', - 'Table sometable should be clustered on "myindex"' - ); + SELECT is_clustered( :schema, :table, :index, :description ); + SELECT is_clustered( :schema, :table, :index ); + SELECT is_clustered( :table, :index ); + SELECT is_clustered( :index ); - SELECT is_clustered( 'sometable', 'myindex' ); +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table containing the index. + +`:index` +: Name of the index. + +`:description` +: A short description of the test. Tests whether a table is clustered on the given index. A table is clustered on an index when the SQL command `CLUSTER TABLE INDEXNAME` has been executed. Clustering reorganizes the table tuples so that they are stored on disk in the order defined by the index. -### `index_is_type( schema, table, index, type, description )` ### -### `index_is_type( schema, table, index, type )` ### -### `index_is_type( table, index, type )` ### -### `index_is_type( index, type )` ### +### `index_is_type()` ### - SELECT index_is_type( - 'myschema', - 'sometable', - 'myindex', - 'gist', - 'Index "myindex" should be a GIST index' - ); + SELECT index_is_type( :schema, :table, :index, :type, :description ); + SELECT index_is_type( :schema, :table, :index, :type ); + SELECT index_is_type( :table, :index, :type ); + SELECT index_is_type( :index, :type ); - SELECT index_is_type( 'myindex', 'gin' ); +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table containing the index. + +`:index` +: Name of the index. + +`:type` +: The index Type. + +`:description` +: A short description of the test. Tests to ensure that an index is of a particular type. At the time of this writing, the supported types are: @@ -5264,6 +5490,8 @@ No changes. Everything should just work. To Do ===== +* Add parameter names matching the docs so parameters can be passed by name on + 9.0 and higher. * Add `isnt_empty()` to complement `is_empty()`. * Add variants of `set_eq()`, `bag_eq()`, and `results_eq()` that take an array of records as the second argument. diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 492b20e7825d..de0cf00f09ec 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -30,7 +30,7 @@ CREATE OR REPLACE FUNCTION pgtap_version() RETURNS NUMERIC AS 'SELECT __VERSION__;' LANGUAGE SQL IMMUTABLE; -CREATE OR REPLACE FUNCTION plan( integer ) +CREATE OR REPLACE FUNCTION plan( tesinteger ) RETURNS TEXT AS $$ DECLARE rcount INTEGER; From fa600fbe53f8538f08a31c684c9d18f23b5a8dd6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 13 Sep 2011 14:37:45 -0700 Subject: [PATCH 0623/1195] Update "Feeling Funky" doc section. --- doc/pgtap.mmd | 358 +++++++++++++++++++++++++++++++------------------- 1 file changed, 225 insertions(+), 133 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index d49b07128bde..9566a45a0fce 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4252,23 +4252,36 @@ clients to interact with the database, making sure that they work will save you time in the long run. So check out these assertions to maintain your sanity. -### `can( schema, functions[], description )` ### -### `can( schema, functions[] )` ### -### `can( functions[], description )` ### -### `can( functions[] )` ### +### `can()` ### - SELECT can( 'pg_catalog', ARRAY['upper', 'lower'] ); + SELECT can( :schema, :functions, :description ); + SELECT can( :schema, :functions ); + SELECT can( :functions, :description ); + SELECT can( :functions ); + +**Parameters** + +`:schema` +: Schema in which to find the functions. + +`:functions` +: Array of function names. + +`:description` +: A short description of the test. -Checks to be sure that `:schema` has `:functions[]` defined. This is subtly +Checks to be sure that `:schema` has `:functions` defined. This is subtly different from `functions_are()`. `functions_are()` fails if the functions -defined in `:schema` are not exactly the functions defined in `:functions[]`. -`can()`, on the other hand, just makes sure that `functions[]` exist. +defined in `:schema` are not exactly the functions defined in `:functions`. +`can()`, on the other hand, just makes sure that `:functions` exist. If `:schema` is omitted, then `can()` will look for functions defined in schemas defined in the search path. No matter how many functions are listed in -`:functions[]`, a single call to `can()` counts as one test. If you want +`:functions`, a single call to `can()` counts as one test. If you want otherwise, call `can()` once for each function -- or better yet, use -`has_function()`. +`has_function()`. Example: + + SELECT can( 'pg_catalog', ARRAY['upper', 'lower'] ); If any of the functions are not defined, the test will fail and the diagnostics will output a list of the functions that are missing, like so: @@ -4277,27 +4290,33 @@ diagnostics will output a list of the functions that are missing, like so: # pg_catalog.foo() missing # pg_catalog.bar() missing -### `function_lang_is( schema, function, args[], language, description )` ### -### `function_lang_is( schema, function, args[], language )` ### -### `function_lang_is( schema, function, language, description )` ### -### `function_lang_is( schema, function, language )` ### -### `function_lang_is( function, args[], language, description )` ### -### `function_lang_is( function, args[], language )` ### -### `function_lang_is( function, language, description )` ### -### `function_lang_is( function, language )` ### +### `function_lang_is()` ### - SELECT function_lang_is( - 'myschema', - 'foo', - ARRAY['integer', 'text'], - 'perl', - 'The myschema.foo() function should be written in Perl' - ); + SELECT function_lang_is( :schema, :function, :args, :language, :description ); + SELECT function_lang_is( :schema, :function, :args, :language ); + SELECT function_lang_is( :schema, :function, :language, :description ); + SELECT function_lang_is( :schema, :function, :language ); + SELECT function_lang_is( :function, :args, :language, :description ); + SELECT function_lang_is( :function, :args, :language ); + SELECT function_lang_is( :function, :language, :description ); + SELECT function_lang_is( :function, :language ); +**Parameters** - SELECT function_lang_is( 'do_something', 'sql' ); - SELECT function_lang_is( 'do_something', ARRAY['integer'], 'plpgsql' ); - SELECT function_lang_is( 'do_something', ARRAY['numeric'], 'plpgsql' ); +`:schema` +: Schema in which to find the function. + +`:function` +: Function name. + +`:args` +: Array of data types for the function arguments. + +`:language` +: Name of the procedural language. + +`:description` +: A short description of the test. Tests that a particular function is implemented in a particular procedural language. The function name is required. If the `:schema` argument is omitted, @@ -4305,7 +4324,12 @@ then the function must be visible in the search path. If the `:args[]` argument is passed, then the function with that argument signature will be the one tested; otherwise, a function with any signature will be checked (pass an empty array to specify a function with an empty signature). If the -`:description` is omitted, a reasonable substitute will be created. +`:description` is omitted, a reasonable substitute will be created. Examples: + + SELECT function_lang_is( 'myschema', 'foo', ARRAY['integer', 'text'], 'plperl' ); + SELECT function_lang_is( 'do_something', 'sql' ); + SELECT function_lang_is( 'do_something', ARRAY['integer'], 'plpgsql' ); + SELECT function_lang_is( 'do_something', ARRAY['numeric'], 'plpgsql' ); In the event of a failure, you'll useful diagnostics will tell you what went wrong, for example: @@ -4321,33 +4345,45 @@ If the function does not exist, you'll be told that, too. But then you check with `has_function()` first, right? -### `function_returns( schema, function, args[], type, description )` ### -### `function_returns( schema, function, args[], type )` ### -### `function_returns( schema, function, type, description )` ### -### `function_returns( schema, function, type )` ### -### `function_returns( function, args[], type, description )` ### -### `function_returns( function, args[], type )` ### -### `function_returns( function, type, description )` ### -### `function_returns( function, type )` ### +### `function_returns()` ### - SELECT function_returns( - 'myschema', - 'foo', - ARRAY['integer', 'text'], - 'integer', - 'The myschema.foo() function should return an integer' - ); + SELECT function_returns( :schema, :function, :args, :type, :description ); + SELECT function_returns( :schema, :function, :args, :type ); + SELECT function_returns( :schema, :function, :type, :description ); + SELECT function_returns( :schema, :function, :type ); + SELECT function_returns( :function, :args, :type, :description ); + SELECT function_returns( :function, :args, :type ); + SELECT function_returns( :function, :type, :description ); + SELECT function_returns( :function, :type ); - SELECT function_returns( 'do_something', 'setof bool' ); - SELECT function_returns( 'do_something', ARRAY['integer'], 'boolean' ); - SELECT function_returns( 'do_something', ARRAY['numeric'], 'numeric' ); +**Parameters** + +`:schema` +: Schema in which to find the function. + +`:function` +: Function name. + +`:args` +: Array of data types for the function arguments. + +`:Type` +: Return value data type. + +`:description` +: A short description of the test. Tests that a particular function returns a particular data type. The `:args[]` and `:type` arguments should be formatted as they would be displayed in the view of a function using the `\df` command in `psql`. For example, use "character varying" rather than "varchar", and "boolean" rather than "bool". For set returning functions, the `:type` argument should start with "setof " -(yes, lowercase). +(yes, lowercase). Examples: + + SELECT function_returns( 'myschema', 'foo', ARRAY['integer', 'text'], 'integer' ); + SELECT function_returns( 'do_something', 'setof bool' ); + SELECT function_returns( 'do_something', ARRAY['integer'], 'boolean' ); + SELECT function_returns( 'do_something', ARRAY['numeric'], 'numeric' ); If the `:schema` argument is omitted, then the function must be visible in the search path. If the `:args[]` argument is passed, then the function with that @@ -4370,33 +4406,43 @@ If the function does not exist, you'll be told that, too. But then you check with `has_function()` first, right? -### `is_definer( schema, function, args[], description )` ### -### `is_definer( schema, function, args[] )` ### -### `is_definer( schema, function, description )` ### -### `is_definer( schema, function )` ### -### `is_definer( function, args[], description )` ### -### `is_definer( function, args[] )` ### -### `is_definer( function, description )` ### -### `is_definer( function )` ### +### `is_definer()` ### - SELECT is_definer( - 'myschema', - 'foo', - ARRAY['integer', 'text'], - 'The myschema.foo() function should be security definer' - ); + SELECT is_definer( :schema, :function, :args, :description ); + SELECT is_definer( :schema, :function, :args ); + SELECT is_definer( :schema, :function, :description ); + SELECT is_definer( :schema, :function ); + SELECT is_definer( :function, :args, :description ); + SELECT is_definer( :function, :args ); + SELECT is_definer( :function, :description ); + SELECT is_definer( :function ); - SELECT is_definer( 'do_something' ); - SELECT is_definer( 'do_something', ARRAY['integer'] ); - SELECT is_definer( 'do_something', ARRAY['numeric'] ); +**Parameters** + +`:schema` +: Schema in which to find the function. + +`:function` +: Function name. + +`:args` +: Array of data types for the function arguments. + +`:description` +: A short description of the test. Tests that a function is a security definer (i.e., a "setuid" function). If the `:schema` argument is omitted, then the function must be visible in the -search path. If the `:args[]` argument is passed, then the function with that +search path. If the `:args` argument is passed, then the function with that argument signature will be the one tested; otherwise, a function with any signature will be checked (pass an empty array to specify a function with an empty signature). If the `:description` is omitted, a reasonable substitute -will be created. +will be created. Examples: + + SELECT is_definer( 'myschema', 'foo', ARRAY['integer', 'text'] ); + SELECT is_definer( 'do_something' ); + SELECT is_definer( 'do_something', ARRAY['integer'] ); + SELECT is_definer( 'do_something', ARRAY['numeric'] ); If the function does not exist, a handy diagnostic message will let you know: @@ -4405,33 +4451,43 @@ If the function does not exist, a handy diagnostic message will let you know: But then you check with `has_function()` first, right? -### `is_strict( schema, function, args[], description )` ### -### `is_strict( schema, function, args[] )` ### -### `is_strict( schema, function, description )` ### -### `is_strict( schema, function )` ### -### `is_strict( function, args[], description )` ### -### `is_strict( function, args[] )` ### -### `is_strict( function, description )` ### -### `is_strict( function )` ### +### `is_strict()` ### - SELECT is_strict( - 'myschema', - 'foo', - ARRAY['integer', 'text'], - 'The myschema.foo() function should be strict - ); + SELECT is_strict( :schema, :function, :args, :description ); + SELECT is_strict( :schema, :function, :args ); + SELECT is_strict( :schema, :function, :description ); + SELECT is_strict( :schema, :function ); + SELECT is_strict( :function, :args, :description ); + SELECT is_strict( :function, :args ); + SELECT is_strict( :function, :description ); + SELECT is_strict( :function ); - SELECT is_strict( 'do_something' ); - SELECT is_strict( 'do_something', ARRAY['integer'] ); - SELECT is_strict( 'do_something', ARRAY['numeric'] ); +**Parameters** + +`:schema` +: Schema in which to find the function. + +`:function` +: Function name. + +`:args` +: Array of data types for the function arguments. + +`:description` +: A short description of the test. Tests that a function is a strict, meaning that the function returns null if any argument is null. If the `:schema` argument is omitted, then the function -must be visible in the search path. If the `:args[]` argument is passed, then +must be visible in the search path. If the `:args` argument is passed, then the function with that argument signature will be the one tested; otherwise, a function with any signature will be checked (pass an empty array to specify a function with an empty signature). If the `:description` is omitted, a -reasonable substitute will be created. +reasonable substitute will be created. Examples: + + SELECT is_strict( 'myschema', 'foo', ARRAY['integer', 'text'] ); + SELECT is_strict( 'do_something' ); + SELECT is_strict( 'do_something', ARRAY['integer'] ); + SELECT is_strict( 'do_something', ARRAY['numeric'] ); If the function does not exist, a handy diagnostic message will let you know: @@ -4440,25 +4496,30 @@ If the function does not exist, a handy diagnostic message will let you know: But then you check with `has_function()` first, right? -### `is_aggregate( schema, function, args[], description )` ### -### `is_aggregate( schema, function, args[] )` ### -### `is_aggregate( schema, function, description )` ### -### `is_aggregate( schema, function )` ### -### `is_aggregate( function, args[], description )` ### -### `is_aggregate( function, args[] )` ### -### `is_aggregate( function, description )` ### -### `is_aggregate( function )` ### +### `is_aggregate()` ### - SELECT is_aggregate( - 'myschema', - 'foo', - ARRAY['integer', 'text'], - 'The myschema.foo() function should be strict - ); + SELECT is_aggregate( :schema, :function, :args, :description ); + SELECT is_aggregate( :schema, :function, :args ); + SELECT is_aggregate( :schema, :function, :description ); + SELECT is_aggregate( :schema, :function ); + SELECT is_aggregate( :function, :args, :description ); + SELECT is_aggregate( :function, :args ); + SELECT is_aggregate( :function, :description ); + SELECT is_aggregate( :function ); - SELECT is_aggregate( 'do_something' ); - SELECT is_aggregate( 'do_something', ARRAY['integer'] ); - SELECT is_aggregate( 'do_something', ARRAY['numeric'] ); +**Parameters** + +`:schema` +: Schema in which to find the function. + +`:function` +: Function name. + +`:args` +: Array of data types for the function arguments. + +`:description` +: A short description of the test. Tests that a function is an aggregate function. If the `:schema` argument is omitted, then the function must be visible in the search path. If the @@ -4466,6 +4527,12 @@ omitted, then the function must be visible in the search path. If the will be the one tested; otherwise, a function with any signature will be checked (pass an empty array to specify a function with an empty signature). If the `:description` is omitted, a reasonable substitute will be created. +Examples: + + SELECT is_aggregate( 'myschema', 'foo', ARRAY['integer', 'text'] ); + SELECT is_aggregate( 'do_something' ); + SELECT is_aggregate( 'do_something', ARRAY['integer'] ); + SELECT is_aggregate( 'do_something', ARRAY['numeric'] ); If the function does not exist, a handy diagnostic message will let you know: @@ -4474,27 +4541,33 @@ If the function does not exist, a handy diagnostic message will let you know: But then you check with `has_function()` first, right? -### `volatility_is( schema, function, args[], volatility, description )` ### -### `volatility_is( schema, function, args[], volatility )` ### -### `volatility_is( schema, function, volatility, description )` ### -### `volatility_is( schema, function, volatility )` ### -### `volatility_is( function, args[], volatility, description )` ### -### `volatility_is( function, args[], volatility )` ### -### `volatility_is( function, volatility, description )` ### -### `volatility_is( function, volatility )` ### +### `volatility_is()` ### - SELECT volatility_is( - 'myschema', - 'foo', - ARRAY['integer', 'text'], - 'stable', - 'The myschema.foo() function should be stable - ); + SELECT volatility_is( :schema, :function, :args, :volatility, :description ); + SELECT volatility_is( :schema, :function, :args, :volatility ); + SELECT volatility_is( :schema, :function, :volatility, :description ); + SELECT volatility_is( :schema, :function, :volatility ); + SELECT volatility_is( :function, :args, :volatility, :description ); + SELECT volatility_is( :function, :args, :volatility ); + SELECT volatility_is( :function, :volatility, :description ); + SELECT volatility_is( :function, :volatility ); +**Parameters** - SELECT volatility_is( 'do_something', 'immutable' ); - SELECT volatility_is( 'do_something', ARRAY['integer'], 'stable' ); - SELECT volatility_is( 'do_something', ARRAY['numeric'], 'volatile' ); +`:schema` +: Schema in which to find the function. + +`:function` +: Function name. + +`:args` +: Array of data types for the function arguments. + +`:volatility` +: Volatility level. + +`:description` +: A short description of the test. Tests the volatility of a function. Supported volatilities are "volatile", "stable", and "immutable". Consult the [`CREATE FUNCTION` @@ -4505,6 +4578,12 @@ omitted, then the function must be visible in the search path. If the will be the one tested; otherwise, a function with any signature will be checked (pass an empty array to specify a function with an empty signature). If the `:description` is omitted, a reasonable substitute will be created. +Examples: + + SELECT volatility_is( 'myschema', 'foo', ARRAY['integer', 'text'], 'stable' ); + SELECT volatility_is( 'do_something', 'immutable' ); + SELECT volatility_is( 'do_something', ARRAY['integer'], 'stable' ); + SELECT volatility_is( 'do_something', ARRAY['numeric'], 'volatile' ); In the event of a failure, you'll useful diagnostics will tell you what went wrong, for example: @@ -4520,19 +4599,32 @@ If the function does not exist, you'll be told that, too. But then you check with `has_function()` first, right? -### `trigger_is( schema, table, trigger, schema, function, description )` ### -### `trigger_is( schema, table, trigger, schema, function )` ### -### `trigger_is( table, trigger, function, description )` ### -### `trigger_is( table, trigger, function )` ### +### `trigger_is()` ### - SELECT trigger_is( - 'myschema', - 'sometable', - 'sometrigger', - 'myschema', - 'somefunction', - 'Trigger "sometrigger" should call somefunction()' - ); + SELECT trigger_is( :schema, :table, :trigger, :func_schema, :function, :description ); + SELECT trigger_is( :schema, :table, :trigger, :func_schema, :function ); + SELECT trigger_is( :table, :trigger, :function, :description ); + SELECT trigger_is( :table, :trigger, :function ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Table in which to find the trigger. + +`:trigger` +: Trigger name. + +`:func_schema` +: Schema in which to find the trigger function. + +`:function` +: Function name. + +`:description` +: A short description of the test. Tests that the specified trigger calls the named function. If not, it outputs a useful diagnostic: From 79fc80ab62273b933f85af1fedc348eb1c30ddd8 Mon Sep 17 00:00:00 2001 From: Taras Kopets Date: Tue, 20 Sep 2011 11:35:49 +0300 Subject: [PATCH 0624/1195] fix possibly incorect typo in integer datatype of plan() function --- sql/pgtap.sql.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index de0cf00f09ec..492b20e7825d 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -30,7 +30,7 @@ CREATE OR REPLACE FUNCTION pgtap_version() RETURNS NUMERIC AS 'SELECT __VERSION__;' LANGUAGE SQL IMMUTABLE; -CREATE OR REPLACE FUNCTION plan( tesinteger ) +CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ DECLARE rcount INTEGER; From cdf308059915a07a5b00af69a5ea2499b5efaa25 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 19 Oct 2011 17:02:02 -0700 Subject: [PATCH 0625/1195] Update "Database Deets" doc section. --- doc/pgtap.mmd | 280 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 204 insertions(+), 76 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 9566a45a0fce..511cbb4f06e3 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4640,10 +4640,18 @@ Tables and functions aren't the only objects in the database, as you well know. These assertions close the gap by letting you test the attributes of other database objects. -### `language_is_trusted( language, description )` ### -### `language_is_trusted( language )` ### +### `language_is_trusted()` ### - SELECT language_is_trusted( 'plperl', 'PL/Perl should be trusted' ); + SELECT language_is_trusted( language, description ); + SELECT language_is_trusted( language ); + +**Parameters** + +`:language` +: Name of a procedural language. + +`:description` +: A short description of the test. Tests that the specified procedural language is trusted. See the [CREATE LANGUAGE](http://www.postgresql.org/docs/current/static/sql-createlanguage.html @@ -4661,37 +4669,63 @@ fact, like so: But you really ought to call `has_language()` first so that you never get that far. -### `enum_has_labels( schema, enum, labels, description )` ### -### `enum_has_labels( schema, enum, labels )` ### -### `enum_has_labels( enum, labels, description )` ### -### `enum_has_labels( enum, labels )` ### +### `enum_has_labels()` ### - SELECT enum_has_labels( - 'myschema', - 'someenum', - ARRAY['foo', 'bar'], - 'Enum someenum should have labels foo, bar' - ); + SELECT enum_has_labels( :schema, :enum, :labels, :description ); + SELECT enum_has_labels( :schema, :enum, :labels ); + SELECT enum_has_labels( :enum, :labels, :description ); + SELECT enum_has_labels( :enum, :labels ); + +**Parameters** + +`:schema` +: Schema in which to find the enum. + +`:enum` +: Enum name. + +`:labels` +: An array of the enum labels. + +`:description` +: A short description of the test. This function tests that an enum consists of an expected list of labels. Enums are supported in PostgreSQL 8.3 or higher. The first argument is a schema name, the second an enum name, the third an array of enum labels, and the -fourth a description. If you omit the schema, the enum must be visible in the -search path. If you omit the test description, it will be set to "Enum `:enum` -should have labels (`:labels`)". +fourth a description. Example: -### `domain_type_is( schema, domain, schema, type, description )` ### -### `domain_type_is( schema, domain, schema, type )` ### -### `domain_type_is( schema, domain, type, description )` ### -### `domain_type_is( schema, domain, type )` ### -### `domain_type_is( domain, type, description )` ### -### `domain_type_is( domain, type )` ### + SELECT enum_has_labels( 'myschema', 'someenum', ARRAY['foo', 'bar'] ); - SELECT domain_type_is( - 'public', 'us_postal_code', - 'public', 'text', - 'The us_postal_code domain should extend the text type' - ); +If you omit the schema, the enum must be visible in the search path. If you +omit the test description, it will be set to "Enum `:enum` should have labels +(`:labels`)". + +### `domain_type_is()` ### + + SELECT domain_type_is( :schema, :domain, :type_schema, :type, :description ); + SELECT domain_type_is( :schema, :domain, :type_schema, :type ); + SELECT domain_type_is( :schema, :domain, :type, :description ); + SELECT domain_type_is( :schema, :domain, :type ); + SELECT domain_type_is( :domain, :type, :description ); + SELECT domain_type_is( :domain, :type ); + +**Parameters** + +`:schema` +: Schema in which to find the domain. + +`:domain` +: Domain name. + +`:type_schema` +: Schema in which to find the data type. + +`:type` +: Domain data type. + +`:description` +: A short description of the test. Tests the data type underlying a domain. The first two are arguments are the schema and name of the domain. The second two are the schema and name of the @@ -4699,7 +4733,12 @@ type that the domain should extend. The fifth argument is a description. If there is no description, a reasonable default description will be created. The schema arguments are also optional (though if there is no schema for the domain then there cannot be one for the type). For the 3- and 4-argument -forms with schemas, cast the schemas to `NAME` to avoid ambiguities. +forms with schemas, cast the schemas to `NAME` to avoid ambiguities. Example: + + SELECT domain_type_is( + 'public'::name, 'us_postal_code', + 'public'::name, 'text' + ); If the data type does not match the type that the domain extends, the test will fail and output diagnostics like so: @@ -4714,12 +4753,36 @@ diagnostics that tell you so: # Failed test 632: "Domain public.zip_code should extend type public.text" # Domain public.zip_code does not exist -### `domain_type_isnt( schema, domain, schema, type, description )` ### -### `domain_type_isnt( schema, domain, schema, type )` ### -### `domain_type_isnt( schema, domain, type, description )` ### -### `domain_type_isnt( schema, domain, type )` ### -### `domain_type_isnt( domain, type, description )` ### -### `domain_type_isnt( domain, type )` ### +### `domain_type_isnt()` ### + + SELECT domain_type_isnt( :schema, :domain, :type_schema, :type, :description ); + SELECT domain_type_isnt( :schema, :domain, :type_schema, :type ); + SELECT domain_type_isnt( :schema, :domain, :type, :description ); + SELECT domain_type_isnt( :schema, :domain, :type ); + SELECT domain_type_isnt( :domain, :type, :description ); + SELECT domain_type_isnt( :domain, :type ); + +**Parameters** + +`:schema` +: Schema in which to find the domain. + +`:domain` +: Domain name. + +`:type_schema` +: Schema in which to find the data type. + +`:type` +: Domain data type. + +`:description` +: A short description of the test. + +The inverse of `domain_type_is()`, this function tests that a domain does +*not* extend a particular data type. For example, a US postal code domain +should probably extned the `text` type, not `integer`, since leading 0s are +valid and required. Example: SELECT domain_type_isnt( 'public', 'us_postal_code', @@ -4727,17 +4790,29 @@ diagnostics that tell you so: 'The us_postal_code domain should not extend the integer type' ); -The inverse of `domain_type_is()`, this function tests that a domain does -*not* extend a particular data type. For example, a US postal code domain -should probably extned the `text` type, not `integer`, since leading 0s are -valid and required. The arguments are the same as for `domain_type_is()`. +The arguments are the same as for `domain_type_is()`. -### `cast_context_is( source_type, target_type, context, description )` ### -### `cast_context_is( source_type, target_type, context )` ### +### `cast_context_is()` ### - SELECT cast_context_is( 'integer', 'bigint', 'implicit' ); + SELECT cast_context_is( :source_type, :target_type, :context, :description ); + SELECT cast_context_is( :source_type, :target_type, :context ); + +**Parameters** + +`:source_type` +: The type cast from. + +`:target_type` +: The type cast to. + +`:context` +: The context for the cast, one of "implicit", "assignment", or "explicit". Test that a cast from a source to a target data type has a particular context. +Example: + + SELECT cast_context_is( 'integer', 'bigint', 'implicit' ); + The data types should be passed as they are displayed by `pg_catalog.format_type()`. For example, you would need to pass "character varying", and not "VARCHAR". @@ -4762,14 +4837,25 @@ If the cast doesn't exist, you'll be told that, too: But you've already used `has_cast()` to make sure of that, right? -### `is_superuser( user, description )` ### -### `is_superuser( user )` ### +### `is_superuser()` ### + + SELECT is_superuser( :user, :description ); + SELECT is_superuser( :user ); + +**Parameters** + +`:user` +: Name of a PostgreSQL user. - SELECT is_superuser( 'theory', 'User "theory" should be a super user' ); +`:description` +: A short description of the test. Tests that a database user is a super user. If the description is omitted, it -will default to "User `:user` should be a super user". If the user does not -exist in the database, the diagnostics will say so. +will default to "User `:user` should be a super user". Example: + + SELECT is_superuser('theory' ; + +If the user does not exist in the database, the diagnostics will say so. ### `isnt_superuser( user, description )` ### ### `isnt_superuser( user )` ### @@ -4784,10 +4870,26 @@ The inverse of `is_superuser()`, this function tests that a database user is database, the test is still considered a failure, and the diagnostics will say so. -### `is_member_of( group, users[], description )` ### -### `is_member_of( group, users[] )` ### -### `is_member_of( group, user, description )` ### -### `is_member_of( group, user )` ### +### `is_member_of()` ### + + SELECT is_member_of( :group, :users, :description ); + SELECT is_member_of( :group, :users ); + SELECT is_member_of( :group, :user, :description ); + SELECT is_member_of( :group, :user ); + +**Parameters** + +`:group` +: Name of a PostgreSQL group. + +`:users` +: Array of names of users that should be members of the group. + +`:user` +: Name of a user that should be a member of the group. + +`:description` +: A short description of the test. SELECT is_member_of( 'sweeties', 'anna' 'Anna should be a sweetie' ); SELECT is_member_of( 'meanies', ARRAY['dr_evil', 'dr_no' ] ); @@ -4806,48 +4908,74 @@ If the group does not exist, the diagnostics will tell you that, instead. But you use `has_group()` to make sure the group exists before you check its members, don't you? Of course you do. -### `rule_is_instead( schema, table, rule, description )` ### -### `rule_is_instead( schema, table, rule )` ### -### `rule_is_instead( table, rule, description )` ### -### `rule_is_instead( table, rule )` ### +### `rule_is_instead()` ### - SELECT rule_is_instead( - 'public', - 'users', - 'on_insert', - 'Rule "on_insert" should be on on relation public.users' - ); + SELECT rule_is_instead( :schema, :table, :rule, :description ); + SELECT rule_is_instead( :schema, :table, :rule ); + SELECT rule_is_instead( :table, :rule, :description ); + SELECT rule_is_instead( :table, :rule ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the table. + +`:table` +: Name of the table to which the rule is applied. + +`:rule` +: A rule name. + +`:description` +: A short description of the test. Checks whether a rule on the specified relation is an `INSTEAD` rule. See the [`CREATE RULE` Documentation](http://www.postgresql.org/docs/current/static/sql-createrule.html) for details. If the `:schema` argument is omitted, the relation must be visible in the search path. If the `:description` argument is omitted, an -appropriate description will be created. In the event that the test fails -because the rule in question does not actually exist, you will see an -appropriate diagnostic such as: +appropriate description will be created. An example: + + SELECT rule_is_instead('public', 'users', 'on_insert'); + +In the event that the test fails because the rule in question does not +actually exist, you will see an appropriate diagnostic such as: # Failed test 625: "Rule on_insert on relation public.users should be an INSTEAD rule" # Rule on_insert does not exist -### `rule_is_on( schema, table, rule, event, description )` ### -### `rule_is_on( schema, table, rule, event )` ### -### `rule_is_on( table, rule, event, description )` ### -### `rule_is_on( table, rule, event )` ### +### `rule_is_on()` ### - SELECT rule_is_on( - 'public', - 'users', - 'on_insert', - 'INSERT', - 'Rule "on_insert" be on insert to on relation public.users' - ); + SELECT rule_is_on( :schema, :table, :rule, :event, :description ); + SELECT rule_is_on( :schema, :table, :rule, :event ); + SELECT rule_is_on( :table, :rule, :event, :description ); + SELECT rule_is_on( :table, :rule, :event ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the table. + +`:table` +: Name of the table to which the rule is applied. -Tests the event for a rule, which may be one of `SELECT`, `INSERT`, `UPDATE`, -or `DELETE`. For the `:event` argument, you can specify the name of the event +`:rule` +: A rule name. + +`:event` +: Name of a rule event, one of "SELECT", "INSERT", "UPDATE", or "DELETE". + +`:description` +: A short description of the test. + +Tests the event for a rule, which may be one of "SELECT", "INSERT", "UPDATE", +or "DELETE". For the `:event` argument, you can specify the name of the event in any case, or even with a single letter ("s", "i", "u", or "d"). If the `:schema` argument is omitted, then the table must be visible in the search path. If the `:description` is omitted, a reasonable default will be created. +Example: + + SELECT rule_is_on('public', 'users', 'on_insert', 'INSERT'); If the test fails, you'll see useful diagnostics, such as: From 83774fd264f75595d90879c6fc3216f0868b2d14 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 19 Oct 2011 17:10:17 -0700 Subject: [PATCH 0626/1195] Update "Who owns me?" doc section. --- doc/pgtap.mmd | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 511cbb4f06e3..58108ff47978 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4996,16 +4996,30 @@ Who owns me? After testing the availability of several objects, we often need to know who owns an object. -### `db_owner_is (dbname, user, description)` ### -### `db_owner_is (dbname, user)` ### +### `db_owner_is ()` ### - SELECT db_owner_is( 'mydb', 'someuser', 'mydb should be owned by someuser'); - SELECT db_owner_is( current_database(), current_user); + SELECT db_owner_is ( :dbname, :user, :description ); + SELECT db_owner_is ( :dbname, :user ); + +**Parameters** + +`:dbname` +: Name of a database. + +`:user` +: Name of a user. + +`:description` +: A short description of the test. Tests the ownership of the database. If the `:description` argument is -omitted, an appropriate description will be created. In the event that the -test fails because the database in question does not actually exist, you will -see an appropriate diagnostic such as: +omitted, an appropriate description will be created. Examples: + + SELECT db_owner_is( 'mydb', 'someuser', 'mydb should be owned by someuser' ); + SELECT db_owner_is( current_database(), current_user ); + +In the event that the test fails because the database in question does not +actually exist, you will see an appropriate diagnostic such as: # Failed test 16: "Database foo should be owned by www" # Database foo does not exist From cf42d5eb5e4e0aef34b79650cf594458bf113df2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 20 Oct 2011 18:28:39 -0700 Subject: [PATCH 0627/1195] More doc sections updated. Only a few left, starting with "Tap that Batch". --- doc/pgtap.mmd | 129 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 98 insertions(+), 31 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 58108ff47978..370eb568a6df 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5048,10 +5048,14 @@ went wrong when it failed. But sometimes it doesn't work out that way. So here we have ways for you to write your own diagnostic messages which are safer than just `\echo` or `SELECT foo`. -### `diag( text )` ### -### `diag( anyelement )` ### -### `diag( variadic anyarray )` ### -### `diag( variadic text[] )` ### +### `diag()` ### + + SELECT diag( :lines ); + +**Parameters** + +`:lines` +: A list of one or more SQL values of the same type. Returns a diagnostic message which is guaranteed not to interfere with test output. Handy for this sort of thing: @@ -5087,10 +5091,20 @@ procedural language), or a contrib module isn't available. In these cases it's necessary to skip tests, or declare that they are supposed to fail but will work in the future (a todo test). -### `skip( why, how_many )` ### -### `skip( how_many, why )` ### -### `skip( why )` ### -### `skip( how_many )` ### +### `skip()` ### + + SELECT skip( :why, :how_many ); + SELECT skip( :how_many, :why ); + SELECT skip( :why ); + SELECT skip( :how_many ); + +**Parameters** + +`:why` +: Reason for skipping the tests. + +`:how_many` +: Number of tests to skip Outputs SKIP test results. Use it in a conditional expression within a `SELECT` statement to replace the output of a test that you otherwise would @@ -5129,10 +5143,20 @@ multiple rows: This will cause it to skip the same number of rows as would have been tested had the `WHEN` condition been true. -### `todo( why, how_many )` ### -### `todo( how_many, why )` ### -### `todo( how_many )` ### -### `todo( why )` ### +### `todo()` ### + + SELECT todo( :why, :how_many ); + SELECT todo( :how_many, :why ); + SELECT todo( :why ); + SELECT todo( :how_many ); + +**Parameters** + +`:why` +: Reason for marking tests as to dos. + +`:how_many` +: Number of tests to mark as to dos. Declares a series of tests that you expect to fail and why. Perhaps it's because you haven't fixed a bug or haven't finished a new feature: @@ -5277,12 +5301,14 @@ appreciated. **NOTE:** The values returned by this function may change in the future, depending on how good the pgTAP build process gets at detecting a OS. -### `collect_tap(tap[])` ### +### `collect_tap()` ### - SELECT collect_tap( - ok(true, 'This should pass'), - ok(false, 'This should fail) - ); + SELECT collect_tap(:lines); + +**Parameters** + +`:lines` +: A list of one or more lines of TAP. Collects the results of one or more pgTAP tests and returns them all. Useful when used in combination with `skip()`: @@ -5306,27 +5332,60 @@ On PostgreSQL 8.4 and higher, it can take any number of arguments. Lower than ok(false, 'This should fail) ]); -### `display_type( schema, type_oid, typemod )` ### -### `display_type( type_oid, typemod )` ### +### `display_type()` ### + + SELECT display_type( :schema, :typeoid, typemod ); + SELECT display_type( :typeoid, :typemod ); + +**Parameters** + +`:schema` +: Schema in which to find the type. + +`:typeoid` +: OID of the type. + +`:typemod` +: Typemode for the type. + +Like `pg_catalog.format_type()`, except that the returned value is not +prepended with the schema name unless it is passed as the first argument. Some +examples: SELECT display_type('public', 'varchar'::regtype, NULL ); SELECT display_type('numeric'::regtype, 196612 ); -Like `pg_catalog.format_type()`, except that the returned value is not -prepended with the schema name unless it is passed as the first argument. Used -internally by pgTAP to compare type names, but may be more generally useful. +Used internally by pgTAP to compare type names, but may be more generally +useful. -### `display_oper( oper_name, oper_oid )` ### +### `display_oper()` ### - SELECT display_type(oprname, oid ) FROM pg_operator; + SELECT display_oper( :opername, :operoid ); + +**Parameters** + +`:opername` +: Operator name. + +`:operoid` +: Operator OID. Similar to casting an operator OID to regoperator, only the schema is not -included in the display. Used internally by pgTAP to compare operators, but -may be more generally useful. +included in the display. For example: -### `pg_typeof(any)` ### + SELECT display_type(oprname, oid ) FROM pg_operator; + +Used internally by pgTAP to compare operators, but may be more generally +useful. + +### `pg_typeof()` ### + + SELECT pg_typeof(:any); + +**Parameters** - SELECT pg_typeof(:value); +`:any` +: Any SQL value. Returns a `regtype` identifying the type of value passed to the function. This function is used internally by `cmp_ok()` to properly construct types when @@ -5342,10 +5401,18 @@ because it's in core in 8.4. You only need to worry about this if you depend on the function being in particular schema. It will always be in `pg_catalog` in 8.4 and higher. -### `findfuncs( schema, pattern )` ### -### `findfuncs( pattern )` ### +### `findfuncs()` ### + + SELECT findfuncs( :schema, :pattern ); + SELECT findfuncs( :pattern ); + +**Parameters** + +`:schema` +: Schema to search for functions. - SELECT findfuncs('myschema', '^test' ); +`:pattern` +: Regular expression pattern against which to match function names. This function searches the named schema or, if no schema is passed, the search patch, for all functions that match the regular expression pattern. The From 39d1878f37fd7f1c205c110abc512a0cc67ac600 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 21 Oct 2011 16:12:25 -0700 Subject: [PATCH 0628/1195] Finally finish rejiggering function headers. --- doc/pgtap.mmd | 123 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 75 insertions(+), 48 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 370eb568a6df..50fda8c9ec11 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5446,14 +5446,20 @@ Then you can just call the function to run all of your TAP tests at once: SELECT * FROM my_tests(); SELECT * FROM finish(); -### `do_tap( schema, pattern )` ### -### `do_tap( schema )` ### -### `do_tap( pattern )` ### ### `do_tap()` ### - SELECT plan(32); - SELECT * FROM do_tap('testschema'::name); - SELECT * FROM finish(); + SELECT do_tap( :schema, :pattern ); + SELECT do_tap( :schema ); + SELECT do_tap( :pattern ); + SELECT do_tap(); + +**Parameters** + +`:schema` +: Name of a schema containing pgTAP test functions. + +`:pattern` +: Regular expression pattern against which to match function names. If you like you can create a whole slew of these batched tap functions, and then use the `do_tap()` function to run them all at once. If passed no @@ -5467,7 +5473,11 @@ schema. This can be very useful if you prefer to keep all of your TAP tests in functions defined in the database. Simply call `plan()`, use `do_tap()` to -execute all of your tests, and then call `finish()`. +execute all of your tests, and then call `finish()`. A dead simple example: + + SELECT plan(32); + SELECT * FROM do_tap('testschema'::name); + SELECT * FROM finish(); As a bonus, if `client_min_messages` is set to "warning", "error", "fatal", or "panic", the name of each function will be emitted as a diagnostic message @@ -5485,18 +5495,29 @@ look something like this: Which will make it much easier to tell what functions need to be examined for failing tests. -### `runtests( schema, match )` ### -### `runtests( schema )` ### -### `runtests( match )` ### -### `runtests( )` ### +### `runtests()` ### - SELECT * FROM runtests( 'testschema', '^test' ); + SELECT runtests( :schema, :pattern ); + SELECT runtests( :schema ); + SELECT runtests( :pattern ); + SELECT runtests( ); + +**Parameters** + +`:schema` +: Name of a schema containing pgTAP test functions. + +`:pattern` +: Regular expression pattern against which to match function names. If you'd like pgTAP to plan, run all of your tests functions, and finish all in one fell swoop, use `runtests()`. This most closely emulates the xUnit testing environment, similar to the functionality of [PGUnit](http://en.dklab.ru/lib/dklab_pgunit/) and [Epic](http://www.epictest.org/). It requires PostgreSQL 8.1 or higher. +Example: + + SELECT * FROM runtests( 'testschema', '^test' ); As with `do_tap()`, you can pass in a schema argument and/or a pattern that the names of the tests functions can match. If you pass in only the schema @@ -5585,49 +5606,55 @@ Testing Test Functions Now you've written your test function. So how do you test it? Why, with this handy-dandy test function! -### `check_test( test_output, is_ok, name, want_description, want_diag, match_diag )` ### -### `check_test( test_output, is_ok, name, want_description, want_diag )` ### -### `check_test( test_output, is_ok, name, want_description )` ### -### `check_test( test_output, is_ok, name )` ### -### `check_test( test_output, is_ok )` ### +### `check_test()` ### - SELECT * FROM check_test( - lc_eq('This', 'THAT', 'not eq'), - false, - 'lc_eq fail', - 'not eq', - E' Want: this\n Have: that' - ); + SELECT check_test( :test_output, :is_ok, :name, :want_description, :want_diag, :match_diag ); + SELECT check_test( :test_output, :is_ok, :name, :want_description, :want_diag ); + SELECT check_test( :test_output, :is_ok, :name, :want_description ); + SELECT check_test( :test_output, :is_ok, :name ); + SELECT check_test( :test_output, :is_ok ); + +**Parameters** + +`:schema` +: Name of a schema containing pgTAP test functions. + +`:test_output` +: The output from your test. Usually it's just returned by a call to the test + function itself. Required. + +`:is_ok` +: Boolean indicating whether or not the test is expected to pass. Required. + +`:name` +: A brief name for your test, to make it easier to find failures in your test + script. Optional. + +`:want_description` +: Expected test description to be output by the test. Optional. Use an empty + string to test that no description is output. + +`:want_diag` +: Expected diagnostic message output during the execution of a test. Must + always follow whatever is output by the call to `ok()`. Optional. Use an + empty string to test that no description is output. + +`:match_diag` +: Use `matches()` to compare the diagnostics rather than `:is()`. Useful for + those situations where you're not sure what will be in the output, but you + can match it with a regular expression. + +This function runs anywhere between one and three tests against a test +function. At its simplest, you just pass in the output of your test function +(and it must be one and **only one** test function's output, or you'll screw +up the count, so don't do that!) and a boolean value indicating whether or not +you expect the test to have passed. That looks something like this: SELECT * FROM check_test( lc_eq('This', 'THIS', 'eq'), true ); -This function runs anywhere between one and three tests against a test -function. For the impatient, the arguments are: - -* `:test_output` - The output from your test. Usually it's just returned by a - call to the test function itself. Required. -* `:is_ok` - Boolean indicating whether or not the test is expected to pass. - Required. -* `:name` - A brief name for your test, to make it easier to find failures in - your test script. Optional. -* `:want_description` - Expected test description to be output by the test. - Optional. Use an empty string to test that no description is output. -* `:want_diag` - Expected diagnostic message output during the execution of - a test. Must always follow whatever is output by the call to `ok()`. - Optional. Use an empty string to test that no description is output. -* `:match_diag` - Use `matches()` to compare the diagnostics rather than - `:is()`. Useful for those situations where you're not sure what will be in - the output, but you can match it with a regular expression. - -Now, on with the detailed documentation. At its simplest, you just pass in the -output of your test function (and it must be one and **only one** test -function's output, or you'll screw up the count, so don't do that!) and a -boolean value indicating whether or not you expect the test to have passed. -That looks something like the second example above. - All other arguments are optional, but I recommend that you *always* include a short test name to make it easier to track down failures in your test script. `check_test()` uses this name to construct descriptions of all of the tests it From f6aafa5aa7f7946952fbac471859f6eee1679d71 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 21 Oct 2011 16:24:33 -0700 Subject: [PATCH 0629/1195] Simplify tocgen. Now that the headers are all sane, there is no noeed for so much jiggery pokery. --- tocgen | 48 +++++++++++++++++------------------------------- 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/tocgen b/tocgen index 0ed3ee0f98fd..f769562adea0 100755 --- a/tocgen +++ b/tocgen @@ -1,53 +1,39 @@ #!/usr/bin/env perl -n -pi our $prevn; -our $previd; -our %seen; BEGIN { $prevn = 0; - $previd = ''; print STDERR "

    Contents

    \n
      \n"; } if (m{ - - ( # 4. header - ([^(]+)?.+? # 5. label + ( # 4. header + ([^(]+)?.+? # 5. label ) }x) { - # Clean up the ID a bit. - my ($hn, $func, $id, $val, $label) = ($1, $2, $3, $4, $5); - $id = $func || $id; - $id =~ s{L?[.]code[.]}{}g; - $id =~ s{[.]{2,}}{.}g; - my $num = $seen{$id}++ || ''; + my ($hn, $id, $val, $label) = ($1, $2, $3, $4); - if ($previd ne $id) { - $previd = $id; - if ($prevn) { - if ($hn != $prevn) { - print STDERR $hn > $prevn - ? $hn - $prevn > 1 - ? "
        • " : "
            \n" : $prevn - $hn > 1 - ? "
      • \n" : "
      \n"; - $prevn = $hn; - } else { - print STDERR "\n" - } - } else { + if ($prevn) { + if ($hn != $prevn) { + print STDERR $hn > $prevn + ? $hn - $prevn > 1 + ? "
        • " : "
            \n" : $prevn - $hn > 1 + ? "
      • \n" : "
      \n"; $prevn = $hn; + } else { + print STDERR "\n" } - print STDERR qq{
    • } . ($5 ? "$5()" : $val) . ""; + } else { + $prevn = $hn; } + print STDERR qq{
    • $val}; - $_ = qq{$val\n}; + $_ = qq{$val\n}; } END { print STDERR "
    • \n
    \n" } From b5186344033209f37126849d59d993db79221f70 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Nov 2011 14:53:50 -0800 Subject: [PATCH 0630/1195] Fix version checks. --- Makefile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 21e13d120039..af29f41e87da 100644 --- a/Makefile +++ b/Makefile @@ -93,16 +93,16 @@ endif sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql cp $< $@ -ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[0123]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][0123]" && echo yes || echo no),yes) patch -p0 < compat/install-8.3.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[012]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][012]" && echo yes || echo no),yes) patch -p0 < compat/install-8.2.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[01]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][01]" && echo yes || echo no),yes) patch -p0 < compat/install-8.1.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[0]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][0]" && echo yes || echo no),yes) patch -p0 < compat/install-8.0.patch # Hack for E'' syntax (<= PG8.0) mv sql/pgtap.sql sql/pgtap.tmp @@ -114,13 +114,13 @@ endif sql/uninstall_pgtap.sql: sql/uninstall_pgtap.sql.in test/setup.sql cp sql/uninstall_pgtap.sql.in sql/uninstall_pgtap.sql -ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[0123]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][0123]" && echo yes || echo no),yes) patch -p0 < compat/uninstall-8.3.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[012]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][012]" && echo yes || echo no),yes) patch -p0 < compat/uninstall-8.2.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[0]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][0]" && echo yes || echo no),yes) patch -p0 < compat/uninstall-8.0.patch endif From b4829599eb8403651343fadcc6cd971f60aea739 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Nov 2011 15:02:47 -0800 Subject: [PATCH 0631/1195] Fix patches for 8.3. --- compat/install-8.3.patch | 25 +++++++------------------ compat/uninstall-8.3.patch | 8 ++++---- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index 24de6b004f4c..90aa6e235992 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -1,6 +1,6 @@ ---- sql/pgtap.sql.orig 2011-02-01 14:54:33.000000000 -0800 -+++ sql/pgtap.sql 2011-02-01 14:54:33.000000000 -0800 -@@ -15,6 +15,11 @@ +--- sql/pgtap.sql.saf 2011-11-10 14:58:17.000000000 -0800 ++++ sql/pgtap.sql 2011-11-10 14:59:43.000000000 -0800 +@@ -12,6 +12,11 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -12,18 +12,7 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT s.a[1]::int * 10000 -@@ -5891,8 +5896,9 @@ - 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 -+ JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid - WHERE c.relname = $1 -- AND c.relistemp -+ AND n.nspname LIKE 'pg_temp%' - AND attnum > 0 - AND NOT attisdropped - ORDER BY attnum -@@ -6239,7 +6245,7 @@ +@@ -6244,7 +6249,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -32,7 +21,7 @@ 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' || -@@ -6394,7 +6400,7 @@ +@@ -6399,7 +6404,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -41,7 +30,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6580,13 +6586,7 @@ +@@ -6585,13 +6590,7 @@ $$ LANGUAGE sql; -- collect_tap( tap, tap, tap ) @@ -56,7 +45,7 @@ RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); $$ LANGUAGE sql; -@@ -7058,7 +7058,7 @@ +@@ -7063,7 +7062,7 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; diff --git a/compat/uninstall-8.3.patch b/compat/uninstall-8.3.patch index 3970bad65bf8..dbede62b4c04 100644 --- a/compat/uninstall-8.3.patch +++ b/compat/uninstall-8.3.patch @@ -1,6 +1,6 @@ ---- sql/uninstall_pgtap.sql.orig 2011-02-01 14:58:51.000000000 -0800 -+++ sql/uninstall_pgtap.sql 2011-02-01 14:58:51.000000000 -0800 -@@ -58,8 +58,7 @@ +--- sql/uninstall_pgtap.sql.saf 2011-11-10 15:00:44.000000000 -0800 ++++ sql/uninstall_pgtap.sql 2011-11-10 15:00:53.000000000 -0800 +@@ -60,8 +60,7 @@ DROP FUNCTION throws_like ( TEXT, TEXT ); DROP FUNCTION throws_like ( TEXT, TEXT, TEXT ); DROP FUNCTION _tlike ( BOOLEAN, TEXT, TEXT, TEXT ); @@ -10,7 +10,7 @@ DROP FUNCTION is_empty( TEXT ); DROP FUNCTION is_empty( TEXT, TEXT ); DROP FUNCTION isa_ok( anyelement, regtype ); -@@ -701,6 +700,7 @@ +@@ -704,6 +703,7 @@ DROP FUNCTION _get ( text ); DROP FUNCTION no_plan(); DROP FUNCTION plan( integer ); From bd9e5a89c5a6bf52a60f28ab725f5c79ef3875c7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Nov 2011 15:05:28 -0800 Subject: [PATCH 0632/1195] Change more backslashes to char classes. --- Makefile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index af29f41e87da..45624a2b0e56 100644 --- a/Makefile +++ b/Makefile @@ -23,12 +23,12 @@ endif VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') # We support 8.0 and later. -ifeq ($(shell echo $(VERSION) | grep -qE " 7\." && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE " 7[.]" && echo yes || echo no),yes) $(error pgTAP requires PostgreSQL 8.0 or later. This is $(VERSION)) endif # Compile the C code only if we're on 8.3 or older. -ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[0123]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE " 8[.][0123]" && echo yes || echo no),yes) MODULES = src/pgtap endif @@ -57,19 +57,19 @@ ifndef HAVE_HARNESS endif # Enum tests not supported by 8.2 and earlier. -ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[012]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE " 8[.][012]" && echo yes || echo no),yes) TESTS := $(filter-out sql/enumtap.sql,$(TESTS)) REGRESS := $(filter-out enumtap,$(REGRESS)) endif # Values tests not supported by 8.1 and earlier. -ifeq ($(shell echo $(VERSION) | grep -qE " 8\.[01]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE " 8[.][01]" && echo yes || echo no),yes) TESTS := $(filter-out sql/enumtap.sql sql/valueset.sql,$(TESTS)) REGRESS := $(filter-out enumtap valueset,$(REGRESS)) endif # Throw, runtests, and roles aren't supported in 8.0. -ifeq ($(shell echo $(VERSION) | grep -qE " 8\.0" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE " 8[.]0" && echo yes || echo no),yes) TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql sql/roletap.sql,$(TESTS)) REGRESS := $(filter-out throwtap runtests roletap,$(REGRESS)) endif @@ -81,7 +81,7 @@ OSNAME := $(shell ./getos.sh) all: sql/pgtap.sql sql/uninstall_pgtap.sql # Add extension build targets on 9.1 and up. -ifeq ($(shell $(PG_CONFIG) --version | grep -qE " 8\.| 9\.0" && echo no || echo yes),yes) +ifeq ($(shell $(PG_CONFIG) --version | grep -qE " 8[.]| 9[.]0" && echo no || echo yes),yes) all: sql/$(EXTENSION)--$(EXTVERSION).sql sql/$(EXTENSION)--$(EXTVERSION).sql: sql/$(EXTENSION).sql From 1984db7bb041e57a696e2b77bbef993d782116cc Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Nov 2011 15:37:55 -0800 Subject: [PATCH 0633/1195] Build pgtap-core and pgtap-schema. --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 45624a2b0e56..9794e5583bfd 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ EXTVERSION = $(shell grep default_version $(EXTENSION).control | \ NUMVERSION = $(shell echo $(EXTVERSION) | sed -e 's/\([[:digit:]]*[.][[:digit:]]*\).*/\1/') DATA = $(filter-out $(wildcard sql/*--*.sql),$(wildcard sql/*.sql)) TESTS = $(wildcard test/sql/*.sql) -EXTRA_CLEAN = sql/pgtap.sql sql/uninstall_pgtap.sql doc/*.html +EXTRA_CLEAN = sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql doc/*.html DOCS = doc/pgtap.mmd REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --inputdir=test --load-language=plpgsql @@ -78,7 +78,7 @@ endif OSNAME := $(shell ./getos.sh) # Make sure we build these. -all: sql/pgtap.sql sql/uninstall_pgtap.sql +all: sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql # Add extension build targets on 9.1 and up. ifeq ($(shell $(PG_CONFIG) --version | grep -qE " 8[.]| 9[.]0" && echo no || echo yes),yes) From 092ede26dcbf821e457eaa34d052f39ab6b66914 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Nov 2011 15:45:07 -0800 Subject: [PATCH 0634/1195] Update build for 8.2. --- Makefile | 16 ++++++------ compat/install-8.2.patch | 50 +++++++++++++++++++------------------- compat/uninstall-8.2.patch | 10 ++++---- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/Makefile b/Makefile index 9794e5583bfd..f14ce41e8b9c 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ $(error pgTAP requires PostgreSQL 8.0 or later. This is $(VERSION)) endif # Compile the C code only if we're on 8.3 or older. -ifeq ($(shell echo $(VERSION) | grep -qE " 8[.][0123]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][0123]" && echo yes || echo no),yes) MODULES = src/pgtap endif @@ -57,20 +57,20 @@ ifndef HAVE_HARNESS endif # Enum tests not supported by 8.2 and earlier. -ifeq ($(shell echo $(VERSION) | grep -qE " 8[.][012]" && echo yes || echo no),yes) -TESTS := $(filter-out sql/enumtap.sql,$(TESTS)) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][012]" && echo yes || echo no),yes) +TESTS := $(filter-out test/sql/enumtap.sql,$(TESTS)) REGRESS := $(filter-out enumtap,$(REGRESS)) endif # Values tests not supported by 8.1 and earlier. -ifeq ($(shell echo $(VERSION) | grep -qE " 8[.][01]" && echo yes || echo no),yes) -TESTS := $(filter-out sql/enumtap.sql sql/valueset.sql,$(TESTS)) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][01]" && echo yes || echo no),yes) +TESTS := $(filter-out test/sql/enumtap.sql sql/valueset.sql,$(TESTS)) REGRESS := $(filter-out enumtap valueset,$(REGRESS)) endif # Throw, runtests, and roles aren't supported in 8.0. -ifeq ($(shell echo $(VERSION) | grep -qE " 8[.]0" && echo yes || echo no),yes) -TESTS := $(filter-out sql/throwtap.sql sql/runtests.sql sql/roletap.sql,$(TESTS)) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.]0" && echo yes || echo no),yes) +TESTS := $(filter-out test/sql/throwtap.sql sql/runtests.sql sql/roletap.sql,$(TESTS)) REGRESS := $(filter-out throwtap runtests roletap,$(REGRESS)) endif @@ -81,7 +81,7 @@ OSNAME := $(shell ./getos.sh) all: sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql # Add extension build targets on 9.1 and up. -ifeq ($(shell $(PG_CONFIG) --version | grep -qE " 8[.]| 9[.]0" && echo no || echo yes),yes) +ifeq ($(shell $(PG_CONFIG) --version | grep -qE "8[.]| 9[.]0" && echo no || echo yes),yes) all: sql/$(EXTENSION)--$(EXTVERSION).sql sql/$(EXTENSION)--$(EXTVERSION).sql: sql/$(EXTENSION).sql diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index b2e662b62e1d..762788c89cc0 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,6 +1,6 @@ ---- sql/pgtap.sql 2011-02-01 15:15:23.000000000 -0800 -+++ sql/pgtap.sql.orig 2011-02-01 15:14:35.000000000 -0800 -@@ -11,6 +11,59 @@ +--- sql/pgtap.sql.saf 2011-11-10 15:40:08.000000000 -0800 ++++ sql/pgtap.sql 2011-11-10 15:40:43.000000000 -0800 +@@ -8,6 +8,59 @@ -- -- http://pgtap.org/ @@ -60,7 +60,7 @@ CREATE OR REPLACE FUNCTION pg_version() RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; -@@ -194,11 +247,11 @@ +@@ -191,11 +244,11 @@ RETURNS integer AS $$ BEGIN EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) @@ -77,7 +77,7 @@ RETURN currval('__tresults___numb_seq'); END; $$ LANGUAGE plpgsql; -@@ -273,21 +326,6 @@ +@@ -270,21 +323,6 @@ ); $$ LANGUAGE sql strict; @@ -99,7 +99,7 @@ CREATE OR REPLACE FUNCTION ok ( boolean, text ) RETURNS TEXT AS $$ DECLARE -@@ -500,9 +538,9 @@ +@@ -497,9 +535,9 @@ output TEXT; BEGIN EXECUTE 'SELECT ' || @@ -111,7 +111,7 @@ INTO result; output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( -@@ -1106,7 +1144,7 @@ +@@ -1103,7 +1141,7 @@ SELECT COALESCE(substring( pg_catalog.format_type($1, $2), '(("(?!")([^"]|"")+"|[^.]+)([(][^)]+[)])?)$' @@ -193,7 +193,7 @@ CREATE OR REPLACE FUNCTION _has_role( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( -@@ -5920,13 +5901,13 @@ +@@ -5924,13 +5905,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -209,7 +209,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6150,7 +6131,7 @@ +@@ -6154,7 +6135,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -218,7 +218,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6245,11 +6226,11 @@ +@@ -6249,11 +6230,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -233,7 +233,7 @@ ); END IF; rownum = rownum + 1; -@@ -6264,8 +6245,8 @@ +@@ -6268,8 +6249,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -244,7 +244,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6400,7 +6381,7 @@ +@@ -6404,7 +6385,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -253,7 +253,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6414,8 +6395,8 @@ +@@ -6418,8 +6399,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -264,7 +264,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6540,9 +6521,9 @@ +@@ -6544,9 +6525,9 @@ DECLARE typeof regtype := pg_typeof($1); BEGIN @@ -277,7 +277,7 @@ END; $$ LANGUAGE plpgsql; -@@ -6563,7 +6544,7 @@ +@@ -6567,7 +6548,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -286,7 +286,7 @@ END LOOP; -- What extra records do we have? -@@ -6708,7 +6689,7 @@ +@@ -6712,7 +6693,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -295,7 +295,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT -@@ -6726,7 +6707,7 @@ +@@ -6730,7 +6711,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -304,7 +304,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) ), -@@ -6759,7 +6740,7 @@ +@@ -6763,7 +6744,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -313,7 +313,7 @@ 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']) ) -@@ -6778,7 +6759,7 @@ +@@ -6782,7 +6763,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -322,7 +322,7 @@ 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']) ) -@@ -7058,10 +7039,12 @@ +@@ -7062,10 +7043,12 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; @@ -338,7 +338,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7206,7 +7189,7 @@ +@@ -7210,7 +7193,7 @@ CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) RETURNS TEXT AS $$ @@ -347,7 +347,7 @@ $$ LANGUAGE SQL; -- operators_are( schema, operators[], description ) -@@ -7215,7 +7198,7 @@ +@@ -7219,7 +7202,7 @@ SELECT _areni( 'operators', ARRAY( @@ -356,7 +356,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7227,7 +7210,7 @@ +@@ -7231,7 +7214,7 @@ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT @@ -365,7 +365,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7248,7 +7231,7 @@ +@@ -7252,7 +7235,7 @@ SELECT _areni( 'operators', ARRAY( @@ -374,7 +374,7 @@ 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) -@@ -7261,7 +7244,7 @@ +@@ -7265,7 +7248,7 @@ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT diff --git a/compat/uninstall-8.2.patch b/compat/uninstall-8.2.patch index 1288fac03d51..db67c883ca81 100644 --- a/compat/uninstall-8.2.patch +++ b/compat/uninstall-8.2.patch @@ -1,6 +1,6 @@ ---- sql/uninstall_pgtap.sql 2011-02-01 15:15:23.000000000 -0800 -+++ sql/uninstall_pgtap.sql.orig 2011-02-01 15:14:54.000000000 -0800 -@@ -367,10 +367,6 @@ +--- sql/uninstall_pgtap.sql.saf 2011-11-10 15:40:19.000000000 -0800 ++++ sql/uninstall_pgtap.sql 2011-11-10 15:40:43.000000000 -0800 +@@ -370,10 +370,6 @@ DROP FUNCTION has_role( NAME ); DROP FUNCTION has_role( NAME, TEXT ); DROP FUNCTION _has_role( NAME ); @@ -11,7 +11,7 @@ DROP FUNCTION hasnt_enum( NAME ); DROP FUNCTION hasnt_enum( NAME, TEXT ); DROP FUNCTION hasnt_enum( NAME, NAME ); -@@ -680,9 +676,6 @@ +@@ -683,9 +679,6 @@ DROP FUNCTION is (anyelement, anyelement, text); DROP FUNCTION ok ( boolean ); DROP FUNCTION ok ( boolean, text ); @@ -21,7 +21,7 @@ DROP FUNCTION diag ( msg text ); DROP FUNCTION finish (); DROP FUNCTION _finish ( INTEGER, INTEGER, INTEGER); -@@ -705,3 +698,15 @@ +@@ -708,3 +701,15 @@ DROP FUNCTION os_name(); DROP FUNCTION pg_version_num(); DROP FUNCTION pg_version(); From 80dcf43ed563676abd0b45ce92ba422ffee6025f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Nov 2011 15:52:23 -0800 Subject: [PATCH 0635/1195] Update patch file for 8.1. --- compat/install-8.1.patch | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index dc1b230391a6..07742067ffc4 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,5 +1,5 @@ ---- sql/pgtap.sql 2011-02-01 15:32:02.000000000 -0800 -+++ sql/pgtap.sql.orig 2011-02-01 15:30:37.000000000 -0800 +--- sql/pgtap.sql.saf 2011-11-10 15:49:06.000000000 -0800 ++++ sql/pgtap.sql 2011-11-10 15:49:12.000000000 -0800 @@ -1993,13 +1993,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ @@ -56,7 +56,7 @@ END LOOP; END LOOP; RETURN; -@@ -5750,14 +5750,14 @@ +@@ -5756,14 +5756,14 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -73,8 +73,8 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -5772,15 +5772,15 @@ - IF verbos THEN RETURN NEXT diag(tests[i] || '()'); END IF; +@@ -5778,15 +5778,15 @@ + IF verbos THEN RETURN NEXT diag_test_name(tests[i]); END IF; -- Run the setup functions. - FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; @@ -93,7 +93,7 @@ -- Remember how many failed and then roll back. num_faild := num_faild + num_failed(); -@@ -5795,7 +5795,7 @@ +@@ -5801,7 +5801,7 @@ END LOOP; -- Run the shutdown functions. @@ -102,7 +102,7 @@ -- Raise an exception to rollback any changes. RAISE EXCEPTION '__TAP_ROLLBACK__'; -@@ -5806,8 +5806,8 @@ +@@ -5812,8 +5812,8 @@ END IF; END; -- Finish up. @@ -113,7 +113,7 @@ END LOOP; -- Clean up and return. -@@ -7038,7 +7038,7 @@ +@@ -7042,7 +7042,7 @@ DECLARE rec RECORD; BEGIN From 08e3895a7727509b0d7f17a4fe142e8633227459 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Nov 2011 16:51:08 -0800 Subject: [PATCH 0636/1195] Update patch files for 8.0. --- compat/install-8.0.patch | 40 +++++++++++++++++++------------------- compat/uninstall-8.0.patch | 8 ++++---- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch index 7cb69bc88e22..f44832e261e3 100644 --- a/compat/install-8.0.patch +++ b/compat/install-8.0.patch @@ -1,6 +1,6 @@ ---- sql/pgtap.sql 2011-02-01 16:00:45.000000000 -0800 -+++ sql/pgtap.sql.orig 2011-02-01 16:00:54.000000000 -0800 -@@ -68,6 +68,27 @@ +--- sql/pgtap.sql.saf 2011-11-10 16:30:29.000000000 -0800 ++++ sql/pgtap.sql 2011-11-10 16:35:53.000000000 -0800 +@@ -65,6 +65,27 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -28,7 +28,7 @@ CREATE OR REPLACE FUNCTION pg_typeof("any") RETURNS regtype AS '$libdir/pgtap' -@@ -149,53 +170,63 @@ +@@ -146,53 +167,63 @@ CREATE OR REPLACE FUNCTION _get ( text ) RETURNS integer AS $$ DECLARE @@ -109,7 +109,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -259,10 +290,12 @@ +@@ -256,10 +287,12 @@ CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ DECLARE @@ -125,7 +125,7 @@ END; $$ LANGUAGE plpgsql strict; -@@ -535,13 +568,16 @@ +@@ -532,13 +565,16 @@ want ALIAS FOR $3; descr ALIAS FOR $4; result BOOLEAN; @@ -273,9 +273,9 @@ -- Now delete those results. EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; -@@ -5742,116 +5745,6 @@ - SELECT TRUE; - $$ LANGUAGE sql; +@@ -5748,116 +5751,6 @@ + SELECT diag($1 || '()'); + $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _runner( text[], text[], text[], text[], text[] ) -RETURNS SETOF TEXT AS $$ @@ -304,7 +304,7 @@ - FOR i IN 1..array_upper(tests, 1) LOOP - BEGIN - -- What test are we running? -- IF verbos THEN RETURN NEXT diag(tests[i] || '()'); END IF; +- IF verbos THEN RETURN NEXT diag_test_name(tests[i]); END IF; - - -- Run the setup functions. - FOR rec IN SELECT * FROM _runem(setup, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; @@ -390,7 +390,7 @@ CREATE OR REPLACE FUNCTION _temptable ( TEXT, TEXT ) RETURNS TEXT AS $$ BEGIN -@@ -5901,13 +5794,13 @@ +@@ -5905,13 +5798,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -406,7 +406,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6021,16 +5914,20 @@ +@@ -6025,16 +5918,20 @@ missing TEXT[] := '{}'; res BOOLEAN := TRUE; msg TEXT := ''; @@ -429,7 +429,7 @@ -- Drop the temporary tables. EXECUTE 'DROP TABLE ' || have; -@@ -6131,7 +6028,7 @@ +@@ -6135,7 +6032,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -438,7 +438,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6226,11 +6123,11 @@ +@@ -6230,11 +6127,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -453,7 +453,7 @@ ); END IF; rownum = rownum + 1; -@@ -6245,8 +6142,8 @@ +@@ -6249,8 +6146,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -464,7 +464,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6381,7 +6278,7 @@ +@@ -6385,7 +6282,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -473,7 +473,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6395,8 +6292,8 @@ +@@ -6399,8 +6296,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -484,7 +484,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6544,7 +6441,7 @@ +@@ -6548,7 +6445,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -493,7 +493,7 @@ END LOOP; -- What extra records do we have? -@@ -6648,35 +6545,6 @@ +@@ -6652,35 +6549,6 @@ SELECT throws_imatching($1, $2, 'Should throw exception matching ' || quote_literal($2) ); $$ LANGUAGE sql; @@ -529,7 +529,7 @@ CREATE OR REPLACE FUNCTION _types_are ( NAME, NAME[], TEXT, CHAR[] ) RETURNS TEXT AS $$ SELECT _are( -@@ -7039,12 +6907,12 @@ +@@ -7043,12 +6911,12 @@ rec RECORD; BEGIN FOR rec in EXECUTE _query($1) LOOP END LOOP; diff --git a/compat/uninstall-8.0.patch b/compat/uninstall-8.0.patch index 47eda963e2fa..9883e581c310 100644 --- a/compat/uninstall-8.0.patch +++ b/compat/uninstall-8.0.patch @@ -1,6 +1,6 @@ ---- sql/uninstall_pgtap.sql 2011-02-01 15:54:35.000000000 -0800 -+++ sql/uninstall_pgtap.sql.orig 2011-02-01 15:55:28.000000000 -0800 -@@ -362,11 +362,6 @@ +--- sql/uninstall_pgtap.sql.saf 2011-11-10 16:30:54.000000000 -0800 ++++ sql/uninstall_pgtap.sql 2011-11-10 16:35:13.000000000 -0800 +@@ -365,11 +365,6 @@ DROP FUNCTION has_user( NAME ); DROP FUNCTION has_user( NAME, TEXT ); DROP FUNCTION _has_user( NAME ); @@ -12,7 +12,7 @@ DROP FUNCTION hasnt_enum( NAME ); DROP FUNCTION hasnt_enum( NAME, TEXT ); DROP FUNCTION hasnt_enum( NAME, NAME ); -@@ -710,3 +705,7 @@ +@@ -713,3 +708,7 @@ DROP FUNCTION textarray_text(text[]); DROP CAST (boolean AS char(1)); DROP FUNCTION booltext(boolean); From ee53a3af0e01786c6e84b69ed158548b3b174bc7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Nov 2011 17:02:22 -0800 Subject: [PATCH 0637/1195] Drop support for PostgreSQL 8.0. The version of pg_regress in 8.0 doesn't support the --inputdir option, so there is no longer any way to adequately test it. Besides, 8.0 has been EOLed for a long time now. --- Changes | 1 + Makefile | 36 +-- README.md | 11 +- compat/install-8.0.patch | 547 ------------------------------------- compat/uninstall-8.0.patch | 22 -- doc/pgtap.mmd | 95 ++----- 6 files changed, 44 insertions(+), 668 deletions(-) delete mode 100644 compat/install-8.0.patch delete mode 100644 compat/uninstall-8.0.patch diff --git a/Changes b/Changes index 3e723b06c078..7b69f85f062b 100644 --- a/Changes +++ b/Changes @@ -15,6 +15,7 @@ Revision history for pgTAP * Removed `TAPSCHEMA` option to `make`. Use `PGOPTIONS=--search_path=tap` with `psql`, instead. * Added `db_owner_is()`. Based on a patch by Gerd Koenig. +* Dropped support for PostgreSQL 8.0. 0.25.0 2011-02-02T03:21:55 -------------------------- diff --git a/Makefile b/Makefile index f14ce41e8b9c..12c826b35fa9 100644 --- a/Makefile +++ b/Makefile @@ -22,13 +22,13 @@ endif # We need to do various things with the PostgreSQLl version. VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') -# We support 8.0 and later. -ifeq ($(shell echo $(VERSION) | grep -qE " 7[.]" && echo yes || echo no),yes) +# We support 8.1 and later. +ifeq ($(shell echo $(VERSION) | grep -qE " 7[.]|8[.]0" && echo yes || echo no),yes) $(error pgTAP requires PostgreSQL 8.0 or later. This is $(VERSION)) endif # Compile the C code only if we're on 8.3 or older. -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][0123]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][123]" && echo yes || echo no),yes) MODULES = src/pgtap endif @@ -57,23 +57,17 @@ ifndef HAVE_HARNESS endif # Enum tests not supported by 8.2 and earlier. -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][012]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][12]" && echo yes || echo no),yes) TESTS := $(filter-out test/sql/enumtap.sql,$(TESTS)) REGRESS := $(filter-out enumtap,$(REGRESS)) endif # Values tests not supported by 8.1 and earlier. -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][01]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][1]" && echo yes || echo no),yes) TESTS := $(filter-out test/sql/enumtap.sql sql/valueset.sql,$(TESTS)) REGRESS := $(filter-out enumtap valueset,$(REGRESS)) endif -# Throw, runtests, and roles aren't supported in 8.0. -ifeq ($(shell echo $(VERSION) | grep -qE "8[.]0" && echo yes || echo no),yes) -TESTS := $(filter-out test/sql/throwtap.sql sql/runtests.sql sql/roletap.sql,$(TESTS)) -REGRESS := $(filter-out throwtap runtests roletap,$(REGRESS)) -endif - # Determine the OS. Borrowed from Perl's Configure. OSNAME := $(shell ./getos.sh) @@ -93,36 +87,26 @@ endif sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql cp $< $@ -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][0123]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][123]" && echo yes || echo no),yes) patch -p0 < compat/install-8.3.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][012]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][12]" && echo yes || echo no),yes) patch -p0 < compat/install-8.2.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][01]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][1]" && echo yes || echo no),yes) patch -p0 < compat/install-8.1.patch -endif -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][0]" && echo yes || echo no),yes) - patch -p0 < compat/install-8.0.patch -# Hack for E'' syntax (<= PG8.0) - mv sql/pgtap.sql sql/pgtap.tmp - sed -e "s/ E'/ '/g" sql/pgtap.tmp > sql/pgtap.sql - rm sql/pgtap.tmp endif sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap.sql > sql/pgtap.tmp mv sql/pgtap.tmp sql/pgtap.sql sql/uninstall_pgtap.sql: sql/uninstall_pgtap.sql.in test/setup.sql cp sql/uninstall_pgtap.sql.in sql/uninstall_pgtap.sql -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][0123]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][123]" && echo yes || echo no),yes) patch -p0 < compat/uninstall-8.3.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][012]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][12]" && echo yes || echo no),yes) patch -p0 < compat/uninstall-8.2.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][0]" && echo yes || echo no),yes) - patch -p0 < compat/uninstall-8.0.patch -endif sql/pgtap-core.sql: sql/pgtap.sql.in cp $< $@ diff --git a/README.md b/README.md index 47761cf59c27..0a9c92467dca 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ If you encounter an error such as: Or: - Makefile:52: *** pgTAP requires PostgreSQL 8.0 or later. This is . Stop. + Makefile:52: *** pgTAP requires PostgreSQL 8.1 or later. This is . Stop. Be sure that you have `pg_config` installed and in your path. If you used a package management system such as RPM to install PostgreSQL, be sure that the @@ -41,10 +41,9 @@ to find it: env PG_CONFIG=/path/to/pg_config make && make install && make installcheck -And finally, if all that fails (and if you're on PostgreSQL 8.1 or lower, it -likely will), copy the entire distribution directory to the `contrib/` -subdirectory of the PostgreSQL source tree and try it there without -`pg_config`: +And finally, if all that fails (and if you're on PostgreSQL 8.1, it likely +will), copy the entire distribution directory to the `contrib/` subdirectory +of the PostgreSQL source tree and try it there without `pg_config`: env NO_PGXS=1 make && make install && make installcheck @@ -82,7 +81,7 @@ schema, like so: Dependencies ------------ -pgTAP requires PostgreSQL 8.0 or higher, with 8.4 or higher recommended for +pgTAP requires PostgreSQL 8.1 or higher, with 8.4 or higher recommended for full use of its API. It also requires PL/pgSQL. Copyright and License diff --git a/compat/install-8.0.patch b/compat/install-8.0.patch deleted file mode 100644 index f44832e261e3..000000000000 --- a/compat/install-8.0.patch +++ /dev/null @@ -1,547 +0,0 @@ ---- sql/pgtap.sql.saf 2011-11-10 16:30:29.000000000 -0800 -+++ sql/pgtap.sql 2011-11-10 16:35:53.000000000 -0800 -@@ -65,6 +65,27 @@ - RETURNS text AS 'SELECT current_setting(''server_version'')' - LANGUAGE SQL IMMUTABLE; - -+-- Cast oidvector to regtype[] like 8.1 does. -+CREATE OR REPLACE FUNCTION oidvregtype(oidvector) -+RETURNS regtype[] AS -+'SELECT COALESCE(string_to_array(textin(oidvectorout($1::oidvector)), '' '')::oid[]::regtype[], ''{}''::regtype[]);' -+LANGUAGE sql IMMUTABLE STRICT; -+ -+CREATE CAST (oidvector AS regtype[]) WITH FUNCTION oidvregtype(oidvector) AS ASSIGNMENT; -+ -+-- Cast int2vector to int[] like 8.1 does. -+CREATE OR REPLACE FUNCTION int2vint(int2vector) -+RETURNS int[] AS -+'SELECT COALESCE(string_to_array(textin(int2vectorout($1::int2vector)), '' '')::int[], ''{}''::int[]);' -+LANGUAGE sql IMMUTABLE STRICT; -+ -+CREATE CAST (int2vector AS int[]) WITH FUNCTION int2vint(int2vector) AS ASSIGNMENT; -+ -+CREATE OR REPLACE FUNCTION pg_typeof("any") -+RETURNS regtype -+AS '$libdir/pgtap' -+LANGUAGE C STABLE; -+ - CREATE OR REPLACE FUNCTION pg_typeof("any") - RETURNS regtype - AS '$libdir/pgtap' -@@ -146,53 +167,63 @@ - CREATE OR REPLACE FUNCTION _get ( text ) - RETURNS integer AS $$ - DECLARE -- ret integer; -+ rec RECORD; - BEGIN -- EXECUTE 'SELECT value FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' INTO ret; -- RETURN ret; -+ FOR rec IN EXECUTE 'SELECT value FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' LOOP -+ RETURN rec.value; -+ END LOOP; -+ RETURN NULL; - END; - $$ LANGUAGE plpgsql strict; - - CREATE OR REPLACE FUNCTION _get_latest ( text ) - RETURNS integer[] AS $$ - DECLARE -- ret integer[]; -+ rec RECORD; - BEGIN -- EXECUTE 'SELECT ARRAY[ id, value] FROM __tcache__ WHERE label = ' || -+ FOR rec IN EXECUTE 'SELECT ARRAY[ id, value] AS a 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; -+ quote_literal($1) || ') LIMIT 1' LOOP -+ RETURN rec.a; -+ END LOOP; -+ RETURN NULL; - END; - $$ LANGUAGE plpgsql strict; - - CREATE OR REPLACE FUNCTION _get_latest ( text, integer ) - RETURNS integer AS $$ - DECLARE -- ret integer; -+ rec RECORD; - BEGIN -- EXECUTE 'SELECT MAX(id) FROM __tcache__ WHERE label = ' || -- quote_literal($1) || ' AND value = ' || $2 INTO ret; -- RETURN ret; -+ FOR rec IN EXECUTE 'SELECT MAX(id) AS id FROM __tcache__ WHERE label = ' || -+ quote_literal($1) || ' AND value = ' || $2 LOOP -+ RETURN rec.id; -+ END LOOP; -+ RETURN NULL; - END; - $$ LANGUAGE plpgsql strict; - - CREATE OR REPLACE FUNCTION _get_note ( text ) - RETURNS text AS $$ - DECLARE -- ret text; -+ rec RECORD; - BEGIN -- EXECUTE 'SELECT note FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' INTO ret; -- RETURN ret; -+ FOR rec IN EXECUTE 'SELECT note FROM __tcache__ WHERE label = ' || quote_literal($1) || ' LIMIT 1' LOOP -+ RETURN rec.note; -+ END LOOP; -+ RETURN; - END; - $$ LANGUAGE plpgsql strict; - - CREATE OR REPLACE FUNCTION _get_note ( integer ) - RETURNS text AS $$ - DECLARE -- ret text; -+ rec RECORD; - BEGIN -- EXECUTE 'SELECT note FROM __tcache__ WHERE id = ' || $1 || ' LIMIT 1' INTO ret; -- RETURN ret; -+ FOR rec IN EXECUTE 'SELECT note FROM __tcache__ WHERE id = ' || $1 || ' LIMIT 1' LOOP -+ RETURN rec.note; -+ END LOOP; -+ RETURN; - END; - $$ LANGUAGE plpgsql strict; - -@@ -256,10 +287,12 @@ - CREATE OR REPLACE FUNCTION num_failed () - RETURNS INTEGER AS $$ - DECLARE -- ret integer; -+ rec RECORD; - BEGIN -- EXECUTE 'SELECT COUNT(*)::INTEGER FROM __tresults__ WHERE ok = FALSE' INTO ret; -- RETURN ret; -+ FOR rec IN EXECUTE 'SELECT COUNT(*)::INTEGER AS cnt FROM __tresults__ WHERE ok = FALSE' LOOP -+ RETURN rec.cnt; -+ END LOOP; -+ RETURN; - END; - $$ LANGUAGE plpgsql strict; - -@@ -532,13 +565,16 @@ - want ALIAS FOR $3; - descr ALIAS FOR $4; - result BOOLEAN; -+ rec RECORD; - output TEXT; - BEGIN -- EXECUTE 'SELECT ' || -+ FOR rec IN EXECUTE 'SELECT ' || - COALESCE(quote_literal( have ), 'NULL') || '::' || pg_typeof(have)::text || ' ' - || op || ' ' || -- COALESCE(quote_literal( want ), 'NULL') || '::' || pg_typeof(want)::text -- INTO result; -+ COALESCE(quote_literal( want ), 'NULL') || '::' || pg_typeof(want)::text || ' AS res' -+ LOOP -+ result := rec.res; -+ END LOOP; - output := ok( COALESCE(result, FALSE), descr ); - RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( - ' ' || COALESCE( quote_literal(have), 'NULL' ) || -@@ -1389,19 +1425,21 @@ - CREATE OR REPLACE FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT ) - RETURNS TEXT AS $$ - DECLARE -- thing text; -+ ret RECORD; - BEGIN - IF $1 ~ '^[^'']+[(]' THEN - -- It's a functional default. - RETURN is( $1, $3, $4 ); - END IF; - -- EXECUTE 'SELECT is(' -+ FOR ret IN EXECUTE 'SELECT is(' - || COALESCE($1, 'NULL' || '::' || $2) || '::' || $2 || ', ' - || COALESCE(quote_literal($3), 'NULL') || '::' || $2 || ', ' - || COALESCE(quote_literal($4), 'NULL') -- || ')' INTO thing; -- RETURN thing; -+ || ') AS a' LOOP -+ RETURN ret.a; -+ END LOOP; -+ RETURN; - END; - $$ LANGUAGE plpgsql; - -@@ -3437,39 +3475,6 @@ - SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); - $$ 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); -@@ -3501,9 +3506,9 @@ - - CREATE OR REPLACE FUNCTION _is_super( NAME ) - RETURNS BOOLEAN AS $$ -- SELECT rolsuper -- FROM pg_catalog.pg_roles -- WHERE rolname = $1 -+ SELECT usesuper -+ FROM pg_catalog.pg_user -+ WHERE usename = $1 - $$ LANGUAGE sql STRICT; - - -- is_superuser( user, description ) -@@ -3578,13 +3583,8 @@ - $$ 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 -- ); -+RETURNS integer[] AS $$ -+ SELECT grolist FROM pg_catalog.pg_group WHERE groname = $1; - $$ LANGUAGE sql; - - -- is_member_of( group, user[], description ) -@@ -3593,9 +3593,9 @@ - DECLARE - missing text[]; - BEGIN -- IF NOT _has_role($1) THEN -+ IF NOT _has_group($1) THEN - RETURN fail( $3 ) || E'\n' || diag ( -- ' Role ' || quote_ident($1) || ' does not exist' -+ ' Group ' || quote_ident($1) || ' does not exist' - ); - END IF; - -@@ -5542,6 +5542,7 @@ - res BOOLEAN; - descr TEXT; - adiag TEXT; -+ rec RECORD; - have ALIAS FOR $1; - eok ALIAS FOR $2; - name ALIAS FOR $3; -@@ -5553,8 +5554,10 @@ - tnumb := currval('__tresults___numb_seq'); - - -- Fetch the results. -- EXECUTE 'SELECT aok, descr FROM __tresults__ WHERE numb = ' || tnumb -- INTO aok, adescr; -+ FOR rec IN EXECUTE 'SELECT aok, descr FROM __tresults__ WHERE numb = ' || tnumb LOOP -+ aok := rec.aok; -+ adescr := rec.descr; -+ END LOOP; - - -- Now delete those results. - EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; -@@ -5748,116 +5751,6 @@ - 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; -- rec record; -- verbos boolean := _is_verbose(); -- verbose is a reserved word in 8.5. -- num_faild INTEGER := 0; --BEGIN -- BEGIN -- -- No plan support. -- PERFORM * FROM no_plan(); -- FOR rec IN SELECT * FROM _runem(startup, false) AS b(a) LOOP RETURN NEXT rec.a; 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; -- -- BEGIN -- FOR i IN 1..array_upper(tests, 1) LOOP -- BEGIN -- -- What test are we running? -- IF verbos THEN RETURN NEXT diag_test_name(tests[i]); END IF; -- -- -- Run the setup functions. -- FOR rec IN SELECT * FROM _runem(setup, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; -- -- -- Run the actual test function. -- FOR rec IN EXECUTE 'SELECT * FROM ' || tests[i] || '() AS b(a)' LOOP -- RETURN NEXT rec.a; -- END LOOP; -- -- -- Run the teardown functions. -- FOR rec IN SELECT * FROM _runem(teardown, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; -- -- -- Remember how many failed and then roll back. -- num_faild := num_faild + num_failed(); -- RAISE EXCEPTION '__TAP_ROLLBACK__'; -- -- EXCEPTION WHEN raise_exception THEN -- IF SQLERRM <> '__TAP_ROLLBACK__' THEN -- -- We didn't raise it, so propagate it. -- RAISE EXCEPTION '%', SQLERRM; -- END IF; -- END; -- END LOOP; -- -- -- Run the shutdown functions. -- FOR rec IN SELECT * FROM _runem(shutdown, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; -- -- -- Raise an exception to rollback any changes. -- RAISE EXCEPTION '__TAP_ROLLBACK__'; -- EXCEPTION WHEN raise_exception THEN -- IF SQLERRM <> '__TAP_ROLLBACK__' THEN -- -- We didn't raise it, so propagate it. -- RAISE EXCEPTION '%', SQLERRM; -- END IF; -- END; -- -- Finish up. -- FOR rec IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) AS b(a) LOOP -- RETURN NEXT rec.a; -- 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 ) -- ); --$$ 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 ) -- ); --$$ 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 -@@ -5905,13 +5798,13 @@ - -- Find extra records. - FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 - || 'SELECT * FROM ' || want LOOP -- extras := array_append(extras, textin(record_out(rec))); -+ extras := array_append(extras, textin(record_out(rec, 2249))); - END LOOP; - - -- Find missing records. - FOR rec in EXECUTE 'SELECT * FROM ' || want || ' EXCEPT ' || $4 - || 'SELECT * FROM ' || have LOOP -- missing := array_append(missing, textin(record_out(rec))); -+ missing := array_append(missing, textin(record_out(rec, 2249))); - END LOOP; - - -- Drop the temporary tables. -@@ -6025,16 +5918,20 @@ - missing TEXT[] := '{}'; - res BOOLEAN := TRUE; - msg TEXT := ''; -+ rec RECORD; - BEGIN - BEGIN - -- Find extra records. -- EXECUTE 'SELECT EXISTS ( ' -+ FOR rec IN EXECUTE 'SELECT EXISTS ( ' - || '( SELECT * FROM ' || have || ' EXCEPT ' || $4 - || ' SELECT * FROM ' || want - || ' ) UNION ( ' - || ' SELECT * FROM ' || want || ' EXCEPT ' || $4 - || ' SELECT * FROM ' || have -- || ' ) LIMIT 1 )' INTO res; -+ || ' ) LIMIT 1 ) AS a' -+ LOOP -+ res := rec.a; -+ END LOOP; - - -- Drop the temporary tables. - EXECUTE 'DROP TABLE ' || have; -@@ -6135,7 +6032,7 @@ - -- Find relevant records. - FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 - || ' SELECT * FROM ' || have LOOP -- results := array_append(results, textin(record_out(rec))); -+ results := array_append(results, textin(record_out(rec, 2249))); - END LOOP; - - -- Drop the temporary tables. -@@ -6230,11 +6127,11 @@ - FETCH want INTO want_rec; - want_found := FOUND; - WHILE have_found OR want_found LOOP -- IF textin(record_out(have_rec)) IS DISTINCT FROM textin(record_out(want_rec)) OR have_found <> want_found THEN -+ IF textin(record_out(have_rec, 2249)) IS DISTINCT FROM textin(record_out(want_rec, 2249)) 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 textin(record_out(have_rec)) ELSE 'NULL' END || E'\n' || -- ' want: ' || CASE WHEN want_found THEN textin(record_out(want_rec)) ELSE 'NULL' END -+ ' have: ' || CASE WHEN have_found THEN textin(record_out(have_rec, 2249)) ELSE 'NULL' END || E'\n' || -+ ' want: ' || CASE WHEN want_found THEN textin(record_out(want_rec, 2249)) ELSE 'NULL' END - ); - END IF; - rownum = rownum + 1; -@@ -6249,8 +6146,8 @@ - WHEN datatype_mismatch THEN - RETURN ok( false, $3 ) || E'\n' || diag( - E' Columns differ between queries:\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 -+ ' have: ' || CASE WHEN have_found THEN textin(record_out(have_rec, 2249)) ELSE 'NULL' END || E'\n' || -+ ' want: ' || CASE WHEN want_found THEN textin(record_out(want_rec, 2249)) ELSE 'NULL' END - ); - END; - $$ LANGUAGE plpgsql; -@@ -6385,7 +6282,7 @@ - FETCH want INTO want_rec; - want_found := FOUND; - WHILE have_found OR want_found LOOP -- IF textin(record_out(have_rec)) IS DISTINCT FROM textin(record_out(want_rec)) OR have_found <> want_found THEN -+ IF textin(record_out(have_rec, 2249)) IS DISTINCT FROM textin(record_out(want_rec, 2249)) OR have_found <> want_found THEN - RETURN ok( true, $3 ); - ELSE - FETCH have INTO have_rec; -@@ -6399,8 +6296,8 @@ - WHEN datatype_mismatch THEN - RETURN ok( false, $3 ) || E'\n' || diag( - E' Columns differ between queries:\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 -+ ' have: ' || CASE WHEN have_found THEN textin(record_out(have_rec, 2249)) ELSE 'NULL' END || E'\n' || -+ ' want: ' || CASE WHEN want_found THEN textin(record_out(want_rec, 2249)) ELSE 'NULL' END - ); - END; - $$ LANGUAGE plpgsql; -@@ -6548,7 +6445,7 @@ - BEGIN - -- Find extra records. - FOR rec in EXECUTE _query($1) LOOP -- extras := extras || textin(record_out(rec)); -+ extras := extras || textin(record_out(rec, 2249)); - END LOOP; - - -- What extra records do we have? -@@ -6652,35 +6549,6 @@ - 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( -@@ -7043,12 +6911,12 @@ - rec RECORD; - BEGIN - FOR rec in EXECUTE _query($1) LOOP END LOOP; -- IF NOT textin(record_out(rec)) IS DISTINCT FROM textin(record_out($2)) -+ IF NOT textin(record_out(rec, 2249)) IS DISTINCT FROM textin(record_out($2, 2249)) - THEN RETURN ok(true, $3); - END IF; - RETURN ok(false, $3 ) || E'\n' || diag( -- ' 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 -+ ' have: ' || CASE WHEN rec IS NULL THEN 'NULL' ELSE textin(record_out(rec, 2249)) END || -+ E'\n want: ' || CASE WHEN $2 IS NULL THEN 'NULL' ELSE textin(record_out($2, 2249)) END - ); - END; - $$ LANGUAGE plpgsql; diff --git a/compat/uninstall-8.0.patch b/compat/uninstall-8.0.patch deleted file mode 100644 index 9883e581c310..000000000000 --- a/compat/uninstall-8.0.patch +++ /dev/null @@ -1,22 +0,0 @@ ---- sql/uninstall_pgtap.sql.saf 2011-11-10 16:30:54.000000000 -0800 -+++ sql/uninstall_pgtap.sql 2011-11-10 16:35:13.000000000 -0800 -@@ -365,11 +365,6 @@ - DROP FUNCTION has_user( NAME ); - DROP FUNCTION has_user( NAME, TEXT ); - DROP FUNCTION _has_user( NAME ); --DROP FUNCTION hasnt_role( NAME ); --DROP FUNCTION hasnt_role( NAME, TEXT ); --DROP FUNCTION has_role( NAME ); --DROP FUNCTION has_role( NAME, TEXT ); --DROP FUNCTION _has_role( NAME ); - DROP FUNCTION hasnt_enum( NAME ); - DROP FUNCTION hasnt_enum( NAME, TEXT ); - DROP FUNCTION hasnt_enum( NAME, NAME ); -@@ -713,3 +708,7 @@ - DROP FUNCTION textarray_text(text[]); - DROP CAST (boolean AS char(1)); - DROP FUNCTION booltext(boolean); -+DROP CAST (int2vector AS int[]); -+DROP FUNCTION int2vint(int2vector); -+DROP CAST (oidvector AS regtype[]); -+DROP FUNCTION oidvregtype(oidvector); diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 50fda8c9ec11..4c46bd16d783 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -72,7 +72,7 @@ If you encounter an error such as: Or: - Makefile:52: *** pgTAP requires PostgreSQL 8.0 or later. This is . Stop. + Makefile:52: *** pgTAP requires PostgreSQL 8.1 or later. This is . Stop. Be sure that you have `pg_config` installed and in your path. If you used a package management system such as RPM to install PostgreSQL, be sure that the @@ -81,10 +81,9 @@ to find it: env PG_CONFIG=/path/to/pg_config make && make install && make installcheck -And finally, if all that fails (and if you're on PostgreSQL 8.1 or lower, it -likely will), copy the entire distribution directory to the `contrib/` -subdirectory of the PostgreSQL source tree and try it there without -`pg_config`: +And finally, if all that fails (and if you're on PostgreSQL 8.1, it likely +will), copy the entire distribution directory to the `contrib/` subdirectory +of the PostgreSQL source tree and try it there without `pg_config`: env NO_PGXS=1 make && make install && make installcheck @@ -313,10 +312,9 @@ discrepancy between the planned number of tests and the number actually run: What a sweet unit! ------------------ -If you're used to xUnit testing frameworks and using PostgreSQL 8.1 or higher, -you can collect all of your tests into database functions and run them all at -once with `runtests()`. This is similar to how -[PGUnit](http://en.dklab.ru/lib/dklab_pgunit/) and +If you're used to xUnit testing frameworks, you can collect all of your tests +into database functions and run them all at once with `runtests()`. This is +similar to how [PGUnit](http://en.dklab.ru/lib/dklab_pgunit/) and [Epic](http://www.epictest.org/) work. The `runtests()` function does all the work of finding and running your test functions in individual transactions. It even supports setup and teardown functions. To use it, write your unit test @@ -790,21 +788,6 @@ the query string or in a prepared statement. A useless example: 'VALUES (1, 2), (3, 4)' ); -On PostgreSQL 8.1 and down, you'll have to use `UNION` queries (if order -doesn't matter) or temporary tables to pass arbitrary values: - - SET client_min_messages = warning; - CREATE TEMPORARY TABLE stuff (a int, b int); - INSERT INTO stuff VALUES(1, 2); - INSERT INTO stuff VALUES(3, 4); - RESET client_min_messages; - PREPARE myvals AS SELECT a, b FROM stuff; - - SELECT set_eq( - 'myvals', - 'SELECT 1, 2 UNION SELECT 3, 4' - ); - Here's a bonus if you need to check the results from a query that returns a single column: for those functions that take two query arguments, the second can be an array. Check it out: @@ -854,7 +837,7 @@ error-prone as you think they should be. : A short description of the test. When you want to make sure that an exception is thrown by PostgreSQL, use -`throws_ok()` to test for it. Supported by 8.1 and up. +`throws_ok()` to test for it. The first argument should be the name of a prepared statement or else a string representing the query to be executed (see the [summary](#Pursuing+Your+Query) @@ -924,8 +907,8 @@ Idea borrowed from the Test::Exception Perl module. : A short description of the test. Like `throws_ok()`, but tests that an exception error message matches an SQL -`LIKE` pattern. Supported by 8.1 and up. The `throws_ilike()` variant matches -case-insensitively. An example: +`LIKE` pattern. The `throws_ilike()` variant matches case-insensitively. An +example: PREPARE my_thrower AS INSERT INTO try (tz) VALUES ('America/Moscow'); SELECT throws_like( @@ -963,7 +946,7 @@ example: Like `throws_ok()`, but tests that an exception error message matches a regular expression. The `throws_imatching()` variant matches -case-insensitively. Supported by 8.1 and up. An example: +case-insensitively. An example: PREPARE my_thrower AS INSERT INTO try (tz) VALUES ('America/Moscow'); SELECT throws_matching( @@ -993,10 +976,9 @@ example: : A short description of the test. The inverse of `throws_ok()`, `lives_ok()` ensures that an SQL statement does -*not* throw an exception. Supported by 8.1 and up. Pass in the name of a -prepared statement or string of SQL code (see the -[summary](#Pursuing+Your+Query) for query argument details). The optional -second argument is the test description. An example: +*not* throw an exception. Pass in the name of a prepared statement or string +of SQL code (see the [summary](#Pursuing+Your+Query) for query argument +details). The optional second argument is the test description. An example: SELECT lives_ok( 'INSERT INTO try (id) VALUES (1)', @@ -1565,12 +1547,11 @@ fails and the results are displayed in the failure diagnostics, like so: `:description` : A short description of the test. -Compares the contents of a single row to a record. Works on PostgreSQL 8.1 and -higher. Due to the limitations of non-C functions in PostgreSQL, a bar -`RECORD` value cannot be passed to the function. You must instead pass in a -valid composite type value, and cast the record argument (the second argument) -to the same type. Both explicitly created composite types and table types are -supported. Thus, you can do this: +Compares the contents of a single row to a record. Due to the limitations of +non-C functions in PostgreSQL, a bar `RECORD` value cannot be passed to the +function. You must instead pass in a valid composite type value, and cast the +record argument (the second argument) to the same type. Both explicitly +created composite types and table types are supported. Thus, you can do this: CREATE TYPE sometype AS ( id INT, @@ -2019,7 +2000,7 @@ missing functions, like so: : A short description of the test. This function tests that all of the roles in the database only the roles that -*should* be there. Supported in PostgreSQL 8.1 and higher. Example: +*should* be there. Example: SELECT roles_are(ARRAY[ 'postgres', 'someone', 'root' ]); @@ -5110,11 +5091,11 @@ Outputs SKIP test results. Use it in a conditional expression within a `SELECT` statement to replace the output of a test that you otherwise would have run. - SELECT CASE WHEN pg_version_num() < 80100 - THEN skip('throws_ok() not supported before 8.1', 2 ) + SELECT CASE WHEN pg_version_num() < 80300 + THEN skip('has_enum() not supported before 8.3', 2 ) ELSE collect_tap( - throws_ok( 'SELECT 1/0', 22012, 'division by zero' ), - throws_ok( 'INSERT INTO try (id) VALUES (1)', '23505' ) + has_enum( 'bug_status' ), + has_enum( 'bug_status', 'mydesc' ) ) END; Note how use of the conditional `CASE` statement has been used to determine @@ -5269,8 +5250,8 @@ certain tests should be run or skipped (using `skip()`) depending on the version of PostgreSQL. For example: SELECT CASE WHEN pg_version_num() < 80100 - THEN skip('throws_ok() not supported before 8.1' ) - ELSE throws_ok( 'SELECT 1/0', 22012, 'division by zero' ) + THEN skip('has_enum() not supported before 8.3' ) + ELSE has_enum( 'bug_status', 'mydesc' ) END; The revision level is in the tens position, the minor version in the thousands @@ -5278,7 +5259,7 @@ position, and the major version in the ten thousands position and above (assuming PostgreSQL 10 is ever released, it will be in the hundred thousands position). This value is the same as the `server_version_num` setting available in PostgreSQL 8.2 and higher, but supported by this function back to -PostgreSQL 8.0: +PostgreSQL 8.1: try=% select current_setting( 'server_version_num'), pg_version_num(); current_setting | pg_version_num @@ -5514,8 +5495,7 @@ If you'd like pgTAP to plan, run all of your tests functions, and finish all in one fell swoop, use `runtests()`. This most closely emulates the xUnit testing environment, similar to the functionality of [PGUnit](http://en.dklab.ru/lib/dklab_pgunit/) and -[Epic](http://www.epictest.org/). It requires PostgreSQL 8.1 or higher. -Example: +[Epic](http://www.epictest.org/). Example: SELECT * FROM runtests( 'testschema', '^test' ); @@ -5797,25 +5777,6 @@ No changes. Everything should just work. + `regtype` to `text` * Two operators, `=` and `<>`, are added to compare `name[]` values. -8.0 ---- -* A patch is applied that changes how some of the test functions are written. -* A few casts are added for compatibility: - + `oidvector` to `regtypep[]`. - + `int2vector` to `integer[]`. -* The following functions do not work under 8.0. Don't even use them there: -* `throws_ok()` -* `throws_like()` -* `throws_ilike()` -* `throws_matching()` -* `throws_imatching()` -* `lives_ok()` -* `runtests()` -* `has_role()` -* `hasnt_role()` -* `roles_are()` -* `row_eq()` - To Do ===== * Add parameter names matching the docs so parameters can be passed by name on From 326315ca933799dfa5354d64e40771f56720f320 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Nov 2011 17:13:49 -0800 Subject: [PATCH 0638/1195] Remove mentions of Kineticode. --- META.json | 2 +- README.md | 20 ++++++++++---------- contrib/pgtap.spec | 6 +++--- doc/pgtap.mmd | 20 ++++++++++---------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/META.json b/META.json index e56d9c0d1385..e31d46c2eee2 100644 --- a/META.json +++ b/META.json @@ -4,7 +4,7 @@ "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", "version": "0.26.0", "maintainer": [ - "David E. Wheeler ", + "David E. Wheeler ", "pgTAP List " ], "license": { diff --git a/README.md b/README.md index 0a9c92467dca..eb48dedf592e 100644 --- a/README.md +++ b/README.md @@ -87,20 +87,20 @@ full use of its API. It also requires PL/pgSQL. Copyright and License --------------------- -Copyright (c) 2008-2011 Kineticode, Inc. Some rights reserved. +Copyright (c) 2008-2011 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. -IN NO EVENT SHALL KINETICODE BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, -SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING -OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF KINETICODE HAS -BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +IN NO EVENT SHALL DAVID E. WHEELER BE LIABLE TO ANY PARTY FOR DIRECT, +INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST +PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN +IF DAVID E. WHEELER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -KINETICODE SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND -KINETICODE HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, -ENHANCEMENTS, OR MODIFICATIONS. +DAVID E. WHEELER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, +AND DAVID E. WHEELER HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, +UPDATES, ENHANCEMENTS, OR MODIFICATIONS. diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 6faf90169d2d..9e508bdb609c 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -44,13 +44,13 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog -* Tue Feb 01 2011 David Wheeler 0.25.0 +* Tue Feb 01 2011 David Wheeler 0.25.0 - Removed pg_prove and pg_tapgen, which are now distributed via CPAN. * Sun Mar 01 2010 Darrell Fuhriman 0.24-2 - Make install work where the pgtap.so library is needed. -* Sun Dec 27 2009 David Wheeler 0.24-1 +* Sun Dec 27 2009 David Wheeler 0.24-1 - Updated Source URL to a more predictable format. * Mon Aug 24 2009 David Fetter 0.23-1 @@ -60,7 +60,7 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} * Wed Aug 19 2009 Darrell Fuhriman 0.22-1 - initial RPM -* Tue Aug 23 2011 David Wheeler 0.26.0 +* Tue Aug 23 2011 David Wheeler 0.26.0 - Removed USE_PGXS from Makefile; it has not been supported in some time. - Removed TAPSCHEMA from Makefile; use PGOPTIONS=--search_path=tap with psql instead. diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 4c46bd16d783..c200f730ea77 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5847,20 +5847,20 @@ Credits Copyright and License --------------------- -Copyright (c) 2008-2011 Kineticode, Inc. Some rights reserved. +Copyright (c) 2008-2011 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. -IN NO EVENT SHALL KINETICODE BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, -SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING -OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF KINETICODE HAS -BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +IN NO EVENT SHALL DAVID E. WHEELER BE LIABLE TO ANY PARTY FOR DIRECT, +INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST +PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN +IF DAVID E. WHEELER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -KINETICODE SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND -KINETICODE HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, -ENHANCEMENTS, OR MODIFICATIONS. +DAVID E. WHEELER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, +AND DAVID E. WHEELER HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, +UPDATES, ENHANCEMENTS, OR MODIFICATIONS. From 68d185ebf86971a0b4f0c3fda7b3d113f949de9e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Nov 2011 17:17:18 -0800 Subject: [PATCH 0639/1195] Increment to 1.0.0. --- Changes | 2 +- META.json | 6 +++--- README.md | 4 ++-- contrib/pgtap.spec | 12 ++++++------ doc/pgtap.mmd | 4 ++-- pgtap.control | 4 ++-- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Changes b/Changes index 7b69f85f062b..a0c8fa523612 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.26.0 +1.0.0 -------------------------- * Removed the "Supported Versions" section of the documentation. Suffice it to say that pgTAP supports PostgreSQL 8.0 or higher. diff --git a/META.json b/META.json index e31d46c2eee2..eb164e6e5104 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "0.26.0", + "version": "1.0.0", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -14,7 +14,7 @@ "runtime": { "requires": { "plpgsql": 0, - "PostgreSQL": "8.0.0" + "PostgreSQL": "8.1.0" }, "recommends": { "PostgreSQL": "8.4.0" @@ -25,7 +25,7 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "pgtap.sql", - "version": "0.26.0" + "version": "1.0.0" } }, "resources": { diff --git a/README.md b/README.md index eb48dedf592e..73d339d3a4e4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -pgTAP 0.26.0 -============ +pgTAP 1.0.0 +=========== [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written in PL/pgSQL and PL/SQL. It includes a comprehensive collection of diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 9e508bdb609c..51fbd44ff3b2 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 0.26.0 +Version: 1.0.0 Release: 2%{?dist} Group: Applications/Databases License: BSD @@ -44,6 +44,11 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog +* Tue Aug 23 2011 David Wheeler 0.26.0 +- Removed USE_PGXS from Makefile; it has not been supported in some time. +- Removed TAPSCHEMA from Makefile; use PGOPTIONS=--search_path=tap with + psql instead. + * Tue Feb 01 2011 David Wheeler 0.25.0 - Removed pg_prove and pg_tapgen, which are now distributed via CPAN. @@ -60,8 +65,3 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} * Wed Aug 19 2009 Darrell Fuhriman 0.22-1 - initial RPM -* Tue Aug 23 2011 David Wheeler 0.26.0 -- Removed USE_PGXS from Makefile; it has not been supported in some time. -- Removed TAPSCHEMA from Makefile; use PGOPTIONS=--search_path=tap with - psql instead. - diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index c200f730ea77..e3693d655eb9 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,5 +1,5 @@ -pgTAP 0.26.0 -============ +pgTAP 1.0.0 +=========== pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and PL/SQL. It includes a comprehensive collection of diff --git a/pgtap.control b/pgtap.control index 2abf3c54e082..021958260922 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '0.26.0' -module_pathname = '$libdir/semver' +default_version = '1.0.0' +module_pathname = '$libdir/pgtap' relocatable = true superuser = false From 536ccda5407bfb955eecf2e4584bb2b704edc109 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Nov 2011 17:22:15 -0800 Subject: [PATCH 0640/1195] Update changes. --- Changes | 17 ++++++++++++----- contrib/pgtap.spec | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index a0c8fa523612..c7338bf91e67 100644 --- a/Changes +++ b/Changes @@ -5,17 +5,24 @@ Revision history for pgTAP -------------------------- * Removed the "Supported Versions" section of the documentation. Suffice it to say that pgTAP supports PostgreSQL 8.0 or higher. -* Added a build target to create a portable version of pgTAP that can be - included in any distribution and should just work for tesing on PostgreSQL - 8.3+. +* Added a build target to create a portable copy of pgTAP that can be included + in any distribution and should just work for tesing on PostgreSQL 8.3+. The + new files are `pgtap-core.sql`, which contains the core functionality, and + `pgtap-schema.sql`, which depends on pgtap-core and adds in the schema + testing assertion functions. * Added abstract to the `provides` section of `META.json`. * Removed use of `relistemp` column in an internal function, as it has been - removed in PostgreSQL 9.1. Thanks to Tom Lane for the alternate syntax. + removed in PostgreSQL 9.1. As a bonus, it now works on PostgreSQL 8.3 and + older without needing to be patched. Thanks to Tom Lane for the alternate + syntax. * Added PostreSQL 9.1 `CREATE EXTENSION` support. * Removed `TAPSCHEMA` option to `make`. Use `PGOPTIONS=--search_path=tap` with `psql`, instead. * Added `db_owner_is()`. Based on a patch by Gerd Koenig. -* Dropped support for PostgreSQL 8.0. +* Dropped support for PostgreSQL 8.0, as the new directory structure + introduced in 0.25.0 is incompatible with the 8.0 version og `pg_regress`, + making testing all but impossible. 8.0 has been EOLed for some time, so it's + time for pgTAP to finally drop its support, too. 0.25.0 2011-02-02T03:21:55 -------------------------- diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 51fbd44ff3b2..6d5bbfce71d2 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -44,7 +44,7 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog -* Tue Aug 23 2011 David Wheeler 0.26.0 +* Tue Aug 23 2011 David Wheeler 1.0.0 - Removed USE_PGXS from Makefile; it has not been supported in some time. - Removed TAPSCHEMA from Makefile; use PGOPTIONS=--search_path=tap with psql instead. From 5fe73c2c0f09cb5c1de2c51e43aceb4792a0f7a2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Nov 2011 22:08:14 -0800 Subject: [PATCH 0641/1195] Turn pgtap-core and pgtap-schema into proper extensions. --- .gitignore | 4 +++- Makefile | 14 ++++++++++---- pgtap-core.control | 6 ++++++ pgtap-schema.control | 6 ++++++ 4 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 pgtap-core.control create mode 100644 pgtap-schema.control diff --git a/.gitignore b/.gitignore index 189df09f6651..a9aad8322f45 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ pgtap.so regression.* *.html bbin -/sql/pgtap--0.* \ No newline at end of file +/sql/pgtap--1.* +/sql/pgtap-core--* +/sql/pgtap-schema--* diff --git a/Makefile b/Makefile index 12c826b35fa9..be2f79b8d231 100644 --- a/Makefile +++ b/Makefile @@ -75,14 +75,20 @@ OSNAME := $(shell ./getos.sh) all: sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql # Add extension build targets on 9.1 and up. -ifeq ($(shell $(PG_CONFIG) --version | grep -qE "8[.]| 9[.]0" && echo no || echo yes),yes) -all: sql/$(EXTENSION)--$(EXTVERSION).sql +ifeq ($(shell echo $(VERSION) | grep -qE "8[.]|9[.]0" && echo no || echo yes),yes) +all: sql/$(EXTENSION)--$(EXTVERSION).sql sql/$(EXTENSION)-core--$(EXTVERSION).sql sql/$(EXTENSION)-schema--$(EXTVERSION).sql sql/$(EXTENSION)--$(EXTVERSION).sql: sql/$(EXTENSION).sql cp $< $@ -DATA = $(wildcard sql/*--*.sql) sql/$(EXTENSION)--$(EXTVERSION).sql -EXTRA_CLEAN += sql/$(EXTENSION)--$(EXTVERSION).sql +sql/$(EXTENSION)-core--$(EXTVERSION).sql: sql/$(EXTENSION)-core.sql + cp $< $@ + +sql/$(EXTENSION)-schema--$(EXTVERSION).sql: sql/$(EXTENSION)-schema.sql + cp $< $@ + +DATA = $(wildcard sql/*--*.sql) sql/$(EXTENSION)--$(EXTVERSION).sql sql/$(EXTENSION)-core--$(EXTVERSION).sql sql/$(EXTENSION)-schema--$(EXTVERSION).sql +EXTRA_CLEAN += sql/$(EXTENSION)--$(EXTVERSION).sql sql/$(EXTENSION)-core--$(EXTVERSION).sql sql/$(EXTENSION)-schema--$(EXTVERSION).sql endif sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql diff --git a/pgtap-core.control b/pgtap-core.control new file mode 100644 index 000000000000..e0b50583a736 --- /dev/null +++ b/pgtap-core.control @@ -0,0 +1,6 @@ +# pgTAP Core extension +comment = 'Unit testing for PostgreSQL' +default_version = '1.0.0' +module_pathname = '$libdir/pgtap' +relocatable = true +superuser = false diff --git a/pgtap-schema.control b/pgtap-schema.control new file mode 100644 index 000000000000..b0ce1fdfcbf9 --- /dev/null +++ b/pgtap-schema.control @@ -0,0 +1,6 @@ +# pgTAP Schema testing extension +comment = 'Schema unit testing for PostgreSQL' +default_version = '1.0.0' +module_pathname = '$libdir/pgtap' +relocatable = true +superuser = false From 44f077b71861f61941090b1b7a7456fdeeff6103 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Nov 2011 22:18:01 -0800 Subject: [PATCH 0642/1195] Fix core and schema extension files. --- compat/gencore | 16 ++-------------- pgtap-schema.control | 1 + 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/compat/gencore b/compat/gencore index 3456caba02bc..2bb61af3fd7e 100644 --- a/compat/gencore +++ b/compat/gencore @@ -1,4 +1,4 @@ -#!/usr/bin/env perl -w +#!/usr/bin/perl -w use strict; use warnings; @@ -7,8 +7,6 @@ my $invert = shift; my %keep = map { chomp; $_ => 1 } ; -print $invert ? '\\i pgtap-core.sql' : '\\set ECHO 0'; - my ($name, $type) = $invert ? ('Schema', 'schema-testing') : ('Core', 'assertion'); print qq{ -- This file defines pgTAP $name, a portable collection of $type @@ -22,17 +20,7 @@ print qq{ }; -print $invert ? qq{-- Requires pgtap-core.sql --- -} : qq{\\pset format unaligned -\\pset tuples_only true -\\pset pager - --- Revert all changes on failure. -\\set ON_ERROR_ROLLBACK 1 -\\set ON_ERROR_STOP true - -}; +print "-- Requires pgtap-core.sql\n--\n" if $invert; my $print = 0; while (<>) { diff --git a/pgtap-schema.control b/pgtap-schema.control index b0ce1fdfcbf9..f84d39b7593f 100644 --- a/pgtap-schema.control +++ b/pgtap-schema.control @@ -2,5 +2,6 @@ comment = 'Schema unit testing for PostgreSQL' default_version = '1.0.0' module_pathname = '$libdir/pgtap' +requires = 'pgtap-core' relocatable = true superuser = false From 9901cee6fd37766b91bf9347d1e8a51886647112 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 11 Nov 2011 11:04:13 -0800 Subject: [PATCH 0643/1195] Add multiple extensions to the Makefile. --- Makefile | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index be2f79b8d231..02f925511fe2 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ -EXTENSION = pgtap -EXTVERSION = $(shell grep default_version $(EXTENSION).control | \ +MAINEXT = pgtap +EXTENSION = $(MAINEXT) pgtap-core pgtap-schema +EXTVERSION = $(shell grep default_version $(MAINEXT).control | \ sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") NUMVERSION = $(shell echo $(EXTVERSION) | sed -e 's/\([[:digit:]]*[.][[:digit:]]*\).*/\1/') DATA = $(filter-out $(wildcard sql/*--*.sql),$(wildcard sql/*.sql)) @@ -76,19 +77,19 @@ all: sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.s # Add extension build targets on 9.1 and up. ifeq ($(shell echo $(VERSION) | grep -qE "8[.]|9[.]0" && echo no || echo yes),yes) -all: sql/$(EXTENSION)--$(EXTVERSION).sql sql/$(EXTENSION)-core--$(EXTVERSION).sql sql/$(EXTENSION)-schema--$(EXTVERSION).sql +all: sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql -sql/$(EXTENSION)--$(EXTVERSION).sql: sql/$(EXTENSION).sql +sql/$(MAINEXT)--$(EXTVERSION).sql: sql/$(MAINEXT).sql cp $< $@ -sql/$(EXTENSION)-core--$(EXTVERSION).sql: sql/$(EXTENSION)-core.sql +sql/$(MAINEXT)-core--$(EXTVERSION).sql: sql/$(MAINEXT)-core.sql cp $< $@ -sql/$(EXTENSION)-schema--$(EXTVERSION).sql: sql/$(EXTENSION)-schema.sql +sql/$(MAINEXT)-schema--$(EXTVERSION).sql: sql/$(MAINEXT)-schema.sql cp $< $@ -DATA = $(wildcard sql/*--*.sql) sql/$(EXTENSION)--$(EXTVERSION).sql sql/$(EXTENSION)-core--$(EXTVERSION).sql sql/$(EXTENSION)-schema--$(EXTVERSION).sql -EXTRA_CLEAN += sql/$(EXTENSION)--$(EXTVERSION).sql sql/$(EXTENSION)-core--$(EXTVERSION).sql sql/$(EXTENSION)-schema--$(EXTVERSION).sql +DATA = $(wildcard sql/*--*.sql) sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql +EXTRA_CLEAN += sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql endif sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql From 8eaa315d0d8c971380324439aa536f3ffaf35f84 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 11 Nov 2011 11:06:47 -0800 Subject: [PATCH 0644/1195] Decrement to v0.9.0. I expect there to be some bugs to be fixed before we can call it 1.0.0. --- Changes | 2 +- META.json | 8 ++++---- README.md | 2 +- contrib/pgtap.spec | 4 ++-- doc/pgtap.mmd | 2 +- pgtap-core.control | 2 +- pgtap-schema.control | 2 +- pgtap.control | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Changes b/Changes index c7338bf91e67..a34fe4ea69dc 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -1.0.0 +0.9.0 -------------------------- * Removed the "Supported Versions" section of the documentation. Suffice it to say that pgTAP supports PostgreSQL 8.0 or higher. diff --git a/META.json b/META.json index eb164e6e5104..57db8f684d8c 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "1.0.0", + "version": "0.9.0", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -24,8 +24,8 @@ "provides": { "pgtap": { "abstract": "Unit testing for PostgreSQL", - "file": "pgtap.sql", - "version": "1.0.0" + "file": "sql/pgtap.sql", + "version": "0.9.0" } }, "resources": { @@ -41,7 +41,7 @@ }, "generated_by": "David E. Wheeler", "meta-spec": { - "version": "1.0.0", + "version": "0.9.0", "url": "http://pgxn.org/meta/spec.txt" }, "tags": [ diff --git a/README.md b/README.md index 73d339d3a4e4..bace252d0ca6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 1.0.0 +pgTAP 0.9.0 =========== [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 6d5bbfce71d2..f3b4e6f44496 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 1.0.0 +Version: 0.9.0 Release: 2%{?dist} Group: Applications/Databases License: BSD @@ -44,7 +44,7 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog -* Tue Aug 23 2011 David Wheeler 1.0.0 +* Tue Aug 23 2011 David Wheeler 0.9.0 - Removed USE_PGXS from Makefile; it has not been supported in some time. - Removed TAPSCHEMA from Makefile; use PGOPTIONS=--search_path=tap with psql instead. diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index e3693d655eb9..6314a97298d1 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 1.0.0 +pgTAP 0.9.0 =========== pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/pgtap-core.control b/pgtap-core.control index e0b50583a736..4c6d463f5a50 100644 --- a/pgtap-core.control +++ b/pgtap-core.control @@ -1,6 +1,6 @@ # pgTAP Core extension comment = 'Unit testing for PostgreSQL' -default_version = '1.0.0' +default_version = '0.9.0' module_pathname = '$libdir/pgtap' relocatable = true superuser = false diff --git a/pgtap-schema.control b/pgtap-schema.control index f84d39b7593f..48806bbfe9d3 100644 --- a/pgtap-schema.control +++ b/pgtap-schema.control @@ -1,6 +1,6 @@ # pgTAP Schema testing extension comment = 'Schema unit testing for PostgreSQL' -default_version = '1.0.0' +default_version = '0.9.0' module_pathname = '$libdir/pgtap' requires = 'pgtap-core' relocatable = true diff --git a/pgtap.control b/pgtap.control index 021958260922..ce58e56fcfdf 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '1.0.0' +default_version = '0.9.0' module_pathname = '$libdir/pgtap' relocatable = true superuser = false From 8f71df498cc1298e42c9d5d7672e6ba1f33809a1 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 11 Nov 2011 11:10:25 -0800 Subject: [PATCH 0645/1195] Add pgtap-core and pgtap-schema to META.json. --- META.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/META.json b/META.json index 57db8f684d8c..0802df6b472d 100644 --- a/META.json +++ b/META.json @@ -26,6 +26,16 @@ "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", "version": "0.9.0" + }, + "pgtap-core": { + "abstract": "Unit testing for PostgreSQL", + "file": "sql/pgtap-core.sql", + "version": "0.9.0" + }, + "pgtap-schema": { + "abstract": "Schema unit testing for PostgreSQL", + "file": "sql/pgtap-schema.sql", + "version": "0.9.0" } }, "resources": { @@ -41,7 +51,7 @@ }, "generated_by": "David E. Wheeler", "meta-spec": { - "version": "0.9.0", + "version": "1.0.0", "url": "http://pgxn.org/meta/spec.txt" }, "tags": [ From 6fecaf5d5f59653fd38443098bddc5dabb5b3b11 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 11 Nov 2011 11:14:18 -0800 Subject: [PATCH 0646/1195] Go with v0.90.0. v0.9.0 is actually *lower* than the previous version. Oops! --- Changes | 2 +- META.json | 8 ++++---- README.md | 2 +- contrib/pgtap.spec | 4 ++-- doc/pgtap.mmd | 2 +- pgtap-core.control | 2 +- pgtap-schema.control | 2 +- pgtap.control | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Changes b/Changes index a34fe4ea69dc..3268067f5e30 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.9.0 +0.90.0 -------------------------- * Removed the "Supported Versions" section of the documentation. Suffice it to say that pgTAP supports PostgreSQL 8.0 or higher. diff --git a/META.json b/META.json index 0802df6b472d..1d7aa5991f8b 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "0.9.0", + "version": "0.90.0", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "0.9.0" + "version": "0.90.0" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "0.9.0" + "version": "0.90.0" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "0.9.0" + "version": "0.90.0" } }, "resources": { diff --git a/README.md b/README.md index bace252d0ca6..aad5abc36271 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 0.9.0 +pgTAP 0.90.0 =========== [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index f3b4e6f44496..e0e128245b96 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 0.9.0 +Version: 0.90.0 Release: 2%{?dist} Group: Applications/Databases License: BSD @@ -44,7 +44,7 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog -* Tue Aug 23 2011 David Wheeler 0.9.0 +* Tue Aug 23 2011 David Wheeler 0.90.0 - Removed USE_PGXS from Makefile; it has not been supported in some time. - Removed TAPSCHEMA from Makefile; use PGOPTIONS=--search_path=tap with psql instead. diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 6314a97298d1..0114b0fd1339 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 0.9.0 +pgTAP 0.90.0 =========== pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/pgtap-core.control b/pgtap-core.control index 4c6d463f5a50..31a6cb4eea83 100644 --- a/pgtap-core.control +++ b/pgtap-core.control @@ -1,6 +1,6 @@ # pgTAP Core extension comment = 'Unit testing for PostgreSQL' -default_version = '0.9.0' +default_version = '0.90.0' module_pathname = '$libdir/pgtap' relocatable = true superuser = false diff --git a/pgtap-schema.control b/pgtap-schema.control index 48806bbfe9d3..2bb9a116b3ce 100644 --- a/pgtap-schema.control +++ b/pgtap-schema.control @@ -1,6 +1,6 @@ # pgTAP Schema testing extension comment = 'Schema unit testing for PostgreSQL' -default_version = '0.9.0' +default_version = '0.90.0' module_pathname = '$libdir/pgtap' requires = 'pgtap-core' relocatable = true diff --git a/pgtap.control b/pgtap.control index ce58e56fcfdf..543b564cc5f0 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '0.9.0' +default_version = '0.90.0' module_pathname = '$libdir/pgtap' relocatable = true superuser = false From 2bf27856b3f981a55fb15b02e63b723e48a82cc5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 3 Dec 2011 12:02:20 -0800 Subject: [PATCH 0647/1195] Z. --- Changes | 64 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Changes b/Changes index 3268067f5e30..d7c3f6adef7b 100644 --- a/Changes +++ b/Changes @@ -2,7 +2,7 @@ Revision history for pgTAP ========================== 0.90.0 --------------------------- +--------------------------- * Removed the "Supported Versions" section of the documentation. Suffice it to say that pgTAP supports PostgreSQL 8.0 or higher. * Added a build target to create a portable copy of pgTAP that can be included @@ -24,8 +24,8 @@ Revision history for pgTAP making testing all but impossible. 8.0 has been EOLed for some time, so it's time for pgTAP to finally drop its support, too. -0.25.0 2011-02-02T03:21:55 --------------------------- +0.25.0 2011-02-02T03:21:55Z +--------------------------- * Minor documentation tweaks. * The sequence created to keep track of test numbers is now a temporary sequence. This will prevent it from persisting beyond the current @@ -71,8 +71,8 @@ Revision history for pgTAP * Rearranged the source directory layout to more closely match the [preferred PGXN layout](http://manager.pgxn.org/howto#new-order). -0.24 2010-05-24T23:33:22 -------------------------- +0.24 2010-05-24T23:33:22Z +-------------------------- * Got `sql/artap.sql` tests passing again when building with `$TAPSCHEMA` set. * Changed to saner source URL in `contrib/pgtap.spec`. * Fixed a bug in `has_member()` where it failed to confirm that users were @@ -102,7 +102,7 @@ Revision history for pgTAP `uninstall_pgtap.sql`. Thanks to Erik Rijkers. * Removed deprecated `can_ok()` functions. Use `has_function()`, instead. -0.23 2009-12-18T23:03:39 +0.23 2009-12-18T23:03:39Z ------------------------- * Fixed broken Perl detection in `Makefile`. * Copied OS detection from Perl's `Configure` script to new script, @@ -143,7 +143,7 @@ Revision history for pgTAP * Added `display_oper()` so that schemas are always displayed properly when comparing operators. -0.22 2009-07-31T00:26:16 +0.22 2009-07-31T00:26:16Z ------------------------- * Fixed failing test on 8.4rc2. * Added result set testing functions. These allow testers to write queries in @@ -168,7 +168,7 @@ Revision history for pgTAP to `pg_prove` to better reflect that they are for finding xUnit tests, not for finding pgTAP functions. -0.21 2009-05-29T00:04:31 +0.21 2009-05-29T00:04:31Z ------------------------- * Fixed a bug in the processing of the `--schema` and `--match` options that only shows up in Getopt::Long 2.38 or higher. @@ -193,7 +193,7 @@ Revision history for pgTAP * Added `function_lang_is()`, `function_returns()`, `is_definer()`, `is_aggregate()`, `is_strict()`, and `volatility_is()`. -0.20 2009-03-29T19:05:40 +0.20 2009-03-29T19:05:40Z ------------------------- * Changed the names of the functions tested in `sql/do_tap.sql` and `sql/runtests.sql` so that they are less likely to be ordered differently @@ -209,7 +209,7 @@ Revision history for pgTAP `has_index()` could be wrong. Reported by Jeff Wartes. Thanks to Andrew Gierth for help fixing the query. -0.19 2009-02-21T02:09:26 +0.19 2009-02-21T02:09:26Z ------------------------- * Added an alternate version of `col_default_is()` to better handle the common case when a default is specified as a string, such as a text or expression @@ -238,8 +238,8 @@ Revision history for pgTAP shows comments and diagnostics in the non-verbose output. Verbose output still outputs everything. -0.18 2009-02-06T20:06:00 -------------------------- +0.18 2009-02-06T20:06:00Z +-------------------------- * Fixed `pg_version_num()`. It was broken in 0.16; sorry about that! * Fixed a bug in `col_type_is()` where it would die if it was looking for a type in a table that had dropped columns. Thanks to depesz and @@ -251,8 +251,8 @@ Revision history for pgTAP * Fixed a bug in the `Makefile` where the `test_setup.sql` file, which is required for tests, was not always getting created. -0.17 2009-02-06T17:51:52 -------------------------- +0.17 2009-02-06T17:51:52Z +-------------------------- * Fixed `col_default_is()` so that it works properly in cases where the default value is `NULL`. * Fixed `col_default_is()` to gracefully handle different data types, @@ -285,8 +285,8 @@ Revision history for pgTAP * Added version of `todo()` with the `why` and `how_many` arguments reversed, so that I don't have to remember a specific order. -0.16 2009-02-03T17:37:03 -------------------------- +0.16 2009-02-03T17:37:03Z +-------------------------- * Switched from a crazy trinary logic in `is()` and `isnt()` to the use of `IS NOT DISTINCT FROM` and `IS DISTINCT FROM`. Swiped from PGUnit. * Fixed documentation for the short version of `--help` in `pg_prove`. @@ -312,8 +312,8 @@ Revision history for pgTAP * Fixed `pg_version_num()` to work on PostgreSQL 8.4devel. * Fixed test failures on PostgreSQL 8.4devel. -0.15 2009-01-20T18:41:24 -------------------------- +0.15 2009-01-20T18:41:24Z +-------------------------- * Changed `pg_typeof()` from immutable to stable, in compliance with its configuration in the forthcoming PostgreSQL 8.4. * Added `do_tap()`, which finds test functions and runs them, allowing @@ -336,8 +336,8 @@ Revision history for pgTAP the Web site, including a table of contents and documentation for `pg_prove`. -0.14 2008-10-27T22:43:36 -------------------------- +0.14 2008-10-27T22:43:36Z +-------------------------- * Added `SET search_path` statements to `uninstall_pgtap.sql.in` so that it will work properly when TAP is installed in its own schema. Thanks to Ben for the catch! @@ -363,13 +363,13 @@ Revision history for pgTAP * Added a note to `README.pgtap` about the need to avoid `pg_typeof()` and `cmp_ok()` in tests run as part of a distribution. -0.13 2008-10-13T19:09:46 -------------------------- +0.13 2008-10-13T19:09:46Z +-------------------------- * Added `pg_version()` and `pg_version_num()`. * Documented `pg_typeof()`. -0.12 2008-10-11T04:02:42 -------------------------- +0.12 2008-10-11T04:02:42Z +-------------------------- * Updated `plan()` to disable warnings while it creates its tables. This means that `plan()` no longer send NOTICE messages when they run, although tests still might, depending on the setting of @@ -379,8 +379,8 @@ Revision history for pgTAP `col_isnt_fk()`. * Added missing `DROP` statements to `uninstall_pgtap.sql.in`. -0.11 2008-09-24T20:41:42 -------------------------- +0.11 2008-09-24T20:41:42Z +-------------------------- * Simplified the tests so that they now load `test_setup.sql` instead of setting a bunch of stuff themselves. Now only `test_setup.sql` needs to be created from `test_setup.sql.in`, and the other `.sql` files @@ -407,8 +407,8 @@ Revision history for pgTAP * Fixed the `installcheck` target so that it properly installs PL/pgSQL into the target database before the tests run. -0.10 2008-09-18T22:59:31 -------------------------- +0.10 2008-09-18T22:59:31Z +-------------------------- * Changed `pg_prove` to set `QUIET=1` when it runs, so as to prevent the output of extraneous stuff. * Added some `GRANT` statements to `plan()` in order to make it easier @@ -469,8 +469,8 @@ Revision history for pgTAP `make test` so that the schema can be properly set, if pgTAP is built with a schema. -0.02 2008-06-17T16:26:41 -------------------------- +0.02 2008-06-17T16:26:41Z +-------------------------- * Converted the documentation to Markdown. * Added pg_prove, a Perl script that use TAP::Harness to run tests and report the results, just like the Perl program `prove`. @@ -505,7 +505,7 @@ Revision history for pgTAP should allow pgtap to run on versions of PostgreSQL earlier than 8.3. Thanks to Neil Conway for the suggestion. -0.01 2008-06-07T05:24:27 -------------------------- +0.01 2008-06-07T05:24:27Z +-------------------------- * Initial public release. Announcement at http://justatheory.com/computers/databases/postgresql/introducing_pgtap.html From 2d6aa2eab2e07efff12e3d07976da609104622cd Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 3 Dec 2011 12:04:56 -0800 Subject: [PATCH 0648/1195] New download location. --- contrib/pgtap.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index e0e128245b96..7b7d34df69cd 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -5,7 +5,7 @@ Release: 2%{?dist} Group: Applications/Databases License: BSD URL: http://pgtap.projects.postgresql.org -Source0: ftp://ftp.postgresql.org/pub/projects/pgFoundry/pgtap/pgtap-%{version}.tar.gz +Source0: http://master.pgxn.org/dist/pgtap/%{version}/pgtap-%{version}.zip BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root %description From b20188406d6442f96728682459333e3db68b01df Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 3 Dec 2011 12:06:32 -0800 Subject: [PATCH 0649/1195] Make = line the same length as the title. --- doc/pgtap.mmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 0114b0fd1339..bcbd450208c7 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,5 +1,5 @@ pgTAP 0.90.0 -=========== +============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and PL/SQL. It includes a comprehensive collection of From f75d75ac1c233c21414362468296aca446288f0a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 3 Dec 2011 12:17:40 -0800 Subject: [PATCH 0650/1195] Timestamp v0.90.0. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index d7c3f6adef7b..21d3a6dfbd42 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.90.0 +0.90.0 2011-12-03T20:18:16Z --------------------------- * Removed the "Supported Versions" section of the documentation. Suffice it to say that pgTAP supports PostgreSQL 8.0 or higher. From 89932bc48bb51b1ee2160e7d5bfb978f898dbe38 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 3 Dec 2011 12:21:45 -0800 Subject: [PATCH 0651/1195] Increment to v0.91.0. --- Changes | 4 ++++ META.json | 8 ++++---- README.md | 2 +- contrib/pgtap.spec | 4 ++-- doc/pgtap.mmd | 2 +- pgtap-core.control | 2 +- pgtap-schema.control | 2 +- pgtap.control | 2 +- 8 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Changes b/Changes index 21d3a6dfbd42..91cc9a78de39 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,10 @@ Revision history for pgTAP ========================== +0.91.0 +--------------------------- + + 0.90.0 2011-12-03T20:18:16Z --------------------------- * Removed the "Supported Versions" section of the documentation. Suffice it to diff --git a/META.json b/META.json index 1d7aa5991f8b..91c4b9025a6d 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "0.90.0", + "version": "0.91.0", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "0.90.0" + "version": "0.91.0" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "0.90.0" + "version": "0.91.0" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "0.90.0" + "version": "0.91.0" } }, "resources": { diff --git a/README.md b/README.md index aad5abc36271..76e38cdceff6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 0.90.0 +pgTAP 0.91.0 =========== [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 7b7d34df69cd..a635a6ee1505 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 0.90.0 +Version: 0.91.0 Release: 2%{?dist} Group: Applications/Databases License: BSD @@ -44,7 +44,7 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog -* Tue Aug 23 2011 David Wheeler 0.90.0 +* Tue Aug 23 2011 David Wheeler 0.91.0 - Removed USE_PGXS from Makefile; it has not been supported in some time. - Removed TAPSCHEMA from Makefile; use PGOPTIONS=--search_path=tap with psql instead. diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index bcbd450208c7..fb37f58cd05b 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 0.90.0 +pgTAP 0.91.0 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/pgtap-core.control b/pgtap-core.control index 31a6cb4eea83..71b79271e903 100644 --- a/pgtap-core.control +++ b/pgtap-core.control @@ -1,6 +1,6 @@ # pgTAP Core extension comment = 'Unit testing for PostgreSQL' -default_version = '0.90.0' +default_version = '0.91.0' module_pathname = '$libdir/pgtap' relocatable = true superuser = false diff --git a/pgtap-schema.control b/pgtap-schema.control index 2bb9a116b3ce..7602db5242f6 100644 --- a/pgtap-schema.control +++ b/pgtap-schema.control @@ -1,6 +1,6 @@ # pgTAP Schema testing extension comment = 'Schema unit testing for PostgreSQL' -default_version = '0.90.0' +default_version = '0.91.0' module_pathname = '$libdir/pgtap' requires = 'pgtap-core' relocatable = true diff --git a/pgtap.control b/pgtap.control index 543b564cc5f0..f546f06ab8df 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '0.90.0' +default_version = '0.91.0' module_pathname = '$libdir/pgtap' relocatable = true superuser = false From f7e8cb3cf8983414c2e0a7b26f54a33cc9355724 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 3 Dec 2011 12:50:46 -0800 Subject: [PATCH 0652/1195] Make = line the same length as the title. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 76e38cdceff6..68014d25c371 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ pgTAP 0.91.0 -=========== +============ [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written in PL/pgSQL and PL/SQL. It includes a comprehensive collection of From fdfd9e4b85add7d67e6a7e40ee1fce568976319d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 16 Dec 2011 09:23:37 -0800 Subject: [PATCH 0653/1195] Use `$(SHELL)` to execute `getos.sh`. Closes #16. --- Changes | 6 ++++-- Makefile | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 91cc9a78de39..0a0747abf653 100644 --- a/Changes +++ b/Changes @@ -1,9 +1,11 @@ Revision history for pgTAP ========================== -0.91.0 +0.91.1 --------------------------- - +* The OS name is restored to the call to `os_name()`. The `getos.sh` shell + script was not getting called during `make`. Thanks to Peter Eisentraut for + [the report](https://github.com/theory/pgtap/issues/16). 0.90.0 2011-12-03T20:18:16Z --------------------------- diff --git a/Makefile b/Makefile index 02f925511fe2..ebb6cdb25bde 100644 --- a/Makefile +++ b/Makefile @@ -70,7 +70,7 @@ REGRESS := $(filter-out enumtap valueset,$(REGRESS)) endif # Determine the OS. Borrowed from Perl's Configure. -OSNAME := $(shell ./getos.sh) +OSNAME := $(shell $(SHELL) ./getos.sh) # Make sure we build these. all: sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql From 603c8cca57a427677a994d096d68836885d2a689 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 9 Jan 2012 09:12:51 -0800 Subject: [PATCH 0654/1195] Fix a couple of doc sections. --- doc/pgtap.mmd | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index fb37f58cd05b..0d879b5e791a 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2439,14 +2439,21 @@ This function tests whether or not a schema exists in the database. The first argument is a schema name and the second is the test description. If you omit the test description, it will be set to "Schema `:schema` should exist". -### `hasnt_schema( schema, description )` ### -### `hasnt_schema( schema )` ### +### `hasnt_schema()` ### SELECT hasnt_schema( 'someschema', 'There should be no schema someschema' ); +**Parameters** + +`:schema` +: Name of a schema. + +`:description` +: A short description of the test. + This function is the inverse of `has_schema()`. The test passes if the specified schema does *not* exist. @@ -4838,14 +4845,21 @@ will default to "User `:user` should be a super user". Example: If the user does not exist in the database, the diagnostics will say so. -### `isnt_superuser( user, description )` ### -### `isnt_superuser( user )` ### +### `isnt_superuser()` ### SELECT is_superuser( 'dr_evil', 'User "dr_evil" should not be a super user' ); +**Parameters** + +`:user` +: Name of a PostgreSQL user. + +`:description` +: A short description of the test. + The inverse of `is_superuser()`, this function tests that a database user is *not* a super user. Note that if the named user does not exist in the database, the test is still considered a failure, and the diagnostics will say From e60e42bf7b8d68f081a7a7b8e638c055a04894bd Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 9 Jan 2012 21:38:21 -0800 Subject: [PATCH 0655/1195] Remove URL. --- sql/pgtap.sql.in | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 492b20e7825d..b8d69d191066 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1,8 +1,5 @@ -- This file defines pgTAP, a collection of functions for TAP-based unit --- testing. It is distributed under the revised FreeBSD license. You can --- find the original here: --- --- http://github.com/theory/pgtap/raw/master/sql/pgtap.sql.in +-- testing. It is distributed under the revised FreeBSD license. -- -- The home page for the pgTAP project is: -- From 5a9e93a58ca1178c655a07ba59337b4c24db940c Mon Sep 17 00:00:00 2001 From: Jay Levitt Date: Sun, 12 Feb 2012 13:19:49 -0500 Subject: [PATCH 0656/1195] fix typos, mention --verbose --- doc/pgtap.mmd | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 0d879b5e791a..bfdaf95d5e9e 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -206,11 +206,11 @@ Here's an example: Of course, if you already have the pgTAP functions in your testing database, you should skip `\i pgtap.sql` at the beginning of the script. -The only other limitation is that the `pg_typoeof()` function, which is +The only other limitation is that the `pg_typeof()` function, which is written in C, will not be available in 8.3 and lower. You'll want to -comment-out its declaration in the bundled copy of `pgtap.sql` and then avoid +comment out its declaration in the bundled copy of `pgtap.sql` and then avoid using `cmp_ok()`, since that function relies on `pg_typeof()`. Note that -`pg_typeof()` is included in PostgreSQL 8.4, so ou won't need to avoid it on +`pg_typeof()` is included in PostgreSQL 8.4, so you won't need to avoid it on that version or higher. Now you're ready to run your test script! @@ -229,7 +229,7 @@ Using `pg_prove` Or save yourself some effort -- and run a batch of tests scripts or all of your xUnit test functions at once -- by using `pg_prove`, available in the [TAP::Parser::SourceHandler::pgTAP](http://search.cpan.org/dist/TAP-Parser-SourceHandler-pgTAP) -CPAN distribution . If you're not relying on `installcheck`, your test scripts +CPAN distribution. If you're not relying on `installcheck`, your test scripts can be a lot less verbose; you don't need to set all the extra variables, because `pg_prove` takes care of that for you: @@ -262,8 +262,9 @@ through the `runtests()` function, just tell it to do so: % pg_prove -d myapp --runtests -Yep, that's all there is to it. Call `pg_prove --help` to see other supported -options, and `pg_prove --man` to see its entire documentation. +Yep, that's all there is to it. Call `pg_prove --verbose` to see the individual test descriptions, +`pg_prove --help` to see other supported options, and `pg_prove --man` to see its +entire documentation. Using pgTAP =========== @@ -2307,7 +2308,7 @@ the cast strings. Example: 'integer AS reltime', 'integer AS numeric', ]); - + If the description is omitted, a generally useful default description will be generated. @@ -5623,7 +5624,7 @@ handy-dandy test function! `:name` : A brief name for your test, to make it easier to find failures in your test script. Optional. - + `:want_description` : Expected test description to be output by the test. Optional. Use an empty string to test that no description is output. From 25967b0e659fba042d49d3fd8fdcd204aaabd4c4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 12 Feb 2012 11:28:29 -0800 Subject: [PATCH 0657/1195] Credit Jay Levitt. --- Changes | 2 ++ doc/pgtap.mmd | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 0a0747abf653..45af2740d7f5 100644 --- a/Changes +++ b/Changes @@ -6,6 +6,8 @@ Revision history for pgTAP * The OS name is restored to the call to `os_name()`. The `getos.sh` shell script was not getting called during `make`. Thanks to Peter Eisentraut for [the report](https://github.com/theory/pgtap/issues/16). +* Noted `pg_prove --verbose` for showing test descriptions and fixed a few + typos in the documentation. Thanks to Jay Levitt for the pull request! 0.90.0 2011-12-03T20:18:16Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index bfdaf95d5e9e..ebe9ebb8f9cb 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -262,9 +262,9 @@ through the `runtests()` function, just tell it to do so: % pg_prove -d myapp --runtests -Yep, that's all there is to it. Call `pg_prove --verbose` to see the individual test descriptions, -`pg_prove --help` to see other supported options, and `pg_prove --man` to see its -entire documentation. +Yep, that's all there is to it. Call `pg_prove --verbose` to see the +individual test descriptions, `pg_prove --help` to see other supported +options, and `pg_prove --man` to see its entire documentation. Using pgTAP =========== From fd43a10e78dfd7e6b37b27066ead3eff86b7843e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 6 Mar 2012 14:54:22 -0800 Subject: [PATCH 0658/1195] RPM Spec fixes. --- contrib/pgtap.spec | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index a635a6ee1505..6aa2a454345f 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,18 +1,18 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap Version: 0.91.0 -Release: 2%{?dist} +Release: 1%{?dist} Group: Applications/Databases -License: BSD -URL: http://pgtap.projects.postgresql.org +License: PostgreSQL +URL: http://pgtap.org/ Source0: http://master.pgxn.org/dist/pgtap/%{version}/pgtap-%{version}.zip BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root %description pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and PL/SQL. It includes a comprehensive collection of TAP-emitting assertion -functions, as well as the ability to integrate with other TAP-emitting -test frameworks. It can also be used in the xUnit testing style. +functions, as well as the ability to integrate with other TAP-emitting test +frameworks. It can also be used in the xUnit testing style. %define postgresver %(pg_config --version|awk '{print $2}'| cut -d. -f1,2) Requires: postgresql-server = %{postgresver}, perl-Test-Harness >= 3.0 From 3a53e41d0e400559b3d4eb1b444e2ee0affd1ed7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 10 Apr 2012 12:13:34 -0700 Subject: [PATCH 0659/1195] Fix broken `row_eq()` examples. --- Changes | 2 ++ doc/pgtap.mmd | 14 ++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index 45af2740d7f5..99a4aa270e1d 100644 --- a/Changes +++ b/Changes @@ -8,6 +8,8 @@ Revision history for pgTAP [the report](https://github.com/theory/pgtap/issues/16). * Noted `pg_prove --verbose` for showing test descriptions and fixed a few typos in the documentation. Thanks to Jay Levitt for the pull request! +* Fixed broken examples in the `row_eq()` documentation. Thanks to @set5think + for the report! 0.90.0 2011-12-03T20:18:16Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index ebe9ebb8f9cb..51cd546a3014 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1559,7 +1559,7 @@ created composite types and table types are supported. Thus, you can do this: name TEXT ); - SELECT row_eq( ROW(1, 'foo')::sometype, ROW(1, 'foo')::sometype ); + SELECT row_eq( $$ SELECT 1, 'foo' $$, ROW(1, 'foo')::sometype ); And, of course, this: @@ -1569,20 +1569,22 @@ And, of course, this: ); INSERT INTO users VALUES (1, 'theory'); + PREPARE get_user AS SELECT * FROM users LIMIT 1; - SELECT row_eq( id, name), ROW(1, 'theory')::users ) - FROM users; + SELECT row_eq( 'get_user', ROW(1, 'theory')::users ); Compatible types can be compared, though. So if the `users` table actually included an `active` column, for example, and you only wanted to test the `id` and `name`, you could do this: - SELECT row_eq( id, name), ROW(1, 'theory')::sometype ) - FROM users; + SELECT row_eq( + $$ SELECT id, name FROM users $$, + ROW(1, 'theory')::sometype + ); Note the use of the `sometype` composite type for the second argument. The upshot is that you can create composite types in your tests explicitly for -comparing the rerutn values of your queries, if such queries don't return an +comparing the return values of your queries, if such queries don't return an existing valid type. Hopefully someday in the future we'll be able to support arbitrary `record` From fa28dbf96ef07c0f985f1a5dab5b16d6350f4256 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 31 Jul 2012 11:59:34 +0200 Subject: [PATCH 0660/1195] Require plpgsql in control files. Resolves #25. --- pgtap-core.control | 1 + pgtap.control | 1 + 2 files changed, 2 insertions(+) diff --git a/pgtap-core.control b/pgtap-core.control index 71b79271e903..eb51e6258b9d 100644 --- a/pgtap-core.control +++ b/pgtap-core.control @@ -2,5 +2,6 @@ comment = 'Unit testing for PostgreSQL' default_version = '0.91.0' module_pathname = '$libdir/pgtap' +requires = 'plpgsql' relocatable = true superuser = false diff --git a/pgtap.control b/pgtap.control index f546f06ab8df..6b9c84e61b2b 100644 --- a/pgtap.control +++ b/pgtap.control @@ -2,5 +2,6 @@ comment = 'Unit testing for PostgreSQL' default_version = '0.91.0' module_pathname = '$libdir/pgtap' +requires = 'plpgsql' relocatable = true superuser = false From d8f83acdb4e16fe8274930ca891ab598ad858ec1 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 31 Aug 2012 09:53:09 -0700 Subject: [PATCH 0661/1195] Set $(PERL) after loading PGXS. To ensure that it is the same value throughout. Resolves #26. --- Makefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index ebb6cdb25bde..c3beb0556a66 100644 --- a/Makefile +++ b/Makefile @@ -33,11 +33,6 @@ ifeq ($(shell echo $(VERSION) | grep -qE "8[.][123]" && echo yes || echo no),yes MODULES = src/pgtap endif -# We need Perl. -ifndef PERL -PERL := $(shell which perl) -endif - # Load PGXS now that we've set all the variables it might need. ifdef NO_PGXS include $(top_builddir)/src/Makefile.global @@ -46,6 +41,11 @@ else include $(PGXS) endif +# We need Perl. +ifndef PERL +PERL := $(shell which perl) +endif + # Is TAP::Parser::SourceHandler::pgTAP installed? ifdef PERL HAVE_HARNESS := $(shell $(PERL) -le 'eval { require TAP::Parser::SourceHandler::pgTAP }; print 1 unless $$@' ) From 232fbc733f146fcf05502629b377b71fd72eca0a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 10 Sep 2012 15:40:15 -0700 Subject: [PATCH 0662/1195] Fix 9.2 compatibility issue. Resolves #27. --- Changes | 2 ++ sql/pgtap.sql.in | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 99a4aa270e1d..0ab99a6ea84c 100644 --- a/Changes +++ b/Changes @@ -10,6 +10,8 @@ Revision history for pgTAP typos in the documentation. Thanks to Jay Levitt for the pull request! * Fixed broken examples in the `row_eq()` documentation. Thanks to @set5think for the report! +* Fixed `has_tablespace()` to work on PostgreSQL 9.2, where its failure was + preventing pgTAP from even being installed. 0.90.0 2011-12-03T20:18:16Z --------------------------- diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index b8d69d191066..7757ba2d57b3 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -3181,7 +3181,7 @@ RETURNS TEXT AS $$ SELECT true FROM pg_catalog.pg_tablespace WHERE spcname = $1 - AND spclocation = $2 + AND pg_tablespace_location(oid) = $2 ), $3 ); $$ LANGUAGE sql; From 5c1fe36e51ee86f01a632b779d9bcd8a00129871 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 10 Sep 2012 15:43:41 -0700 Subject: [PATCH 0663/1195] Forgot to rename upgrade script. --- ...gtap--unpackaged--0.26.0.sql => pgtap--unpackaged--0.90.0.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename sql/{pgtap--unpackaged--0.26.0.sql => pgtap--unpackaged--0.90.0.sql} (100%) diff --git a/sql/pgtap--unpackaged--0.26.0.sql b/sql/pgtap--unpackaged--0.90.0.sql similarity index 100% rename from sql/pgtap--unpackaged--0.26.0.sql rename to sql/pgtap--unpackaged--0.90.0.sql From db836aca5fb25ba1c24b14c8af820dce6dcbe316 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 10 Sep 2012 16:05:07 -0700 Subject: [PATCH 0664/1195] Increment upgrade from upackaged to 0.91.0. --- ...gtap--unpackaged--0.90.0.sql => pgtap--unpackaged--0.91.0.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename sql/{pgtap--unpackaged--0.90.0.sql => pgtap--unpackaged--0.91.0.sql} (100%) diff --git a/sql/pgtap--unpackaged--0.90.0.sql b/sql/pgtap--unpackaged--0.91.0.sql similarity index 100% rename from sql/pgtap--unpackaged--0.90.0.sql rename to sql/pgtap--unpackaged--0.91.0.sql From 53250077912412e420b8643d1ebc98906db13df4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 10 Sep 2012 16:10:29 -0700 Subject: [PATCH 0665/1195] Add script to upgrade v0.90.0 to v0.91.0. --- sql/pgtap--0.90.0--0.91.0.sql | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 sql/pgtap--0.90.0--0.91.0.sql diff --git a/sql/pgtap--0.90.0--0.91.0.sql b/sql/pgtap--0.90.0--0.91.0.sql new file mode 100644 index 000000000000..a0ec4851f425 --- /dev/null +++ b/sql/pgtap--0.90.0--0.91.0.sql @@ -0,0 +1,27 @@ +DO $$ +BEGIN + IF pg_version_num() < 9.0 THEN + EXECUTE $E$ + CREATE FUNCTION pg_tablespace_location( + OID + ) RETURNS TEXT LANGUAGE SQL AS $F$ + SELECT spclocation + FROM pg_catalog.pg_tablespace + WHERE OID = $1; + $F$; + $E$; + END IF; +END; +$$; + +CREATE OR REPLACE FUNCTION has_tablespace( NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( + EXISTS( + SELECT true + FROM pg_catalog.pg_tablespace + WHERE spcname = $1 + AND pg_tablespace_location(oid) = $2 + ), $3 + ); +$$ LANGUAGE sql; From 2d33df9b8f2f6c3ad84c7c5788d217e166aff48c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 10 Sep 2012 16:18:01 -0700 Subject: [PATCH 0666/1195] Silence -core and -schema patch application. And delete the .orig files. No way to stop these, since the 8.3 patches have offsets that vary for these derived scripts. --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c3beb0556a66..4bff3ddb8b96 100644 --- a/Makefile +++ b/Makefile @@ -117,17 +117,19 @@ endif sql/pgtap-core.sql: sql/pgtap.sql.in cp $< $@ - sed -e 's,sql/pgtap,sql/pgtap-core,g' compat/install-8.3.patch | patch -p0 + sed -e 's,sql/pgtap,sql/pgtap-core,g' compat/install-8.3.patch | patch -s -p0 sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap-core.sql > sql/pgtap-core.tmp $(PERL) compat/gencore 0 sql/pgtap-core.tmp > sql/pgtap-core.sql rm sql/pgtap-core.tmp + rm sql/pgtap-core.sql.orig sql/pgtap-schema.sql: sql/pgtap.sql.in cp $< $@ - sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-8.3.patch | patch -p0 + sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-8.3.patch | patch -s -p0 sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap-schema.sql > sql/pgtap-schema.tmp $(PERL) compat/gencore 1 sql/pgtap-schema.tmp > sql/pgtap-schema.sql rm sql/pgtap-schema.tmp + rm sql/pgtap-schema.sql.orig # Make sure that we build the regression tests. installcheck: test/setup.sql From bab5ce52e50bcc378cc2c73f415d561316ca9001 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 10 Sep 2012 16:52:04 -0700 Subject: [PATCH 0667/1195] Support 9.2 and earlier in single function. Makes it much easier to maintain extension upgrade scripts. --- sql/pgtap--0.90.0--0.91.0.sql | 42 +++++++++++++++++------------------ sql/pgtap.sql.in | 31 ++++++++++++++++++-------- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/sql/pgtap--0.90.0--0.91.0.sql b/sql/pgtap--0.90.0--0.91.0.sql index a0ec4851f425..f8f6271136c7 100644 --- a/sql/pgtap--0.90.0--0.91.0.sql +++ b/sql/pgtap--0.90.0--0.91.0.sql @@ -1,27 +1,25 @@ -DO $$ +CREATE OR REPLACE FUNCTION has_tablespace( NAME, TEXT, TEXT ) +RETURNS TEXT AS $$ BEGIN - IF pg_version_num() < 9.0 THEN - EXECUTE $E$ - CREATE FUNCTION pg_tablespace_location( - OID - ) RETURNS TEXT LANGUAGE SQL AS $F$ - SELECT spclocation + 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 OID = $1; - $F$; - $E$; + WHERE spcname = $1 + AND spclocation = $2 + ), $3 + ); END IF; END; -$$; +$$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION has_tablespace( NAME, TEXT, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( - EXISTS( - SELECT true - FROM pg_catalog.pg_tablespace - WHERE spcname = $1 - AND pg_tablespace_location(oid) = $2 - ), $3 - ); -$$ LANGUAGE sql; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 7757ba2d57b3..ddbf13118cfc 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -3176,15 +3176,28 @@ $$ LANGUAGE sql; -- has_tablespace( tablespace, location, description ) CREATE OR REPLACE FUNCTION has_tablespace( NAME, TEXT, TEXT ) RETURNS TEXT AS $$ - SELECT ok( - EXISTS( - SELECT true - FROM pg_catalog.pg_tablespace - WHERE spcname = $1 - AND pg_tablespace_location(oid) = $2 - ), $3 - ); -$$ LANGUAGE sql; +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 ) From 5951e4dc2895aa047bd85e558a828a62cacb8673 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 10 Sep 2012 17:03:33 -0700 Subject: [PATCH 0668/1195] Update 8.3 patch. Turns out it applies cleanly for the core and schema files, too, so remove the stuff to work around those issues. --- Makefile | 6 ++---- compat/install-8.3.patch | 14 +++++++------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 4bff3ddb8b96..c3beb0556a66 100644 --- a/Makefile +++ b/Makefile @@ -117,19 +117,17 @@ endif sql/pgtap-core.sql: sql/pgtap.sql.in cp $< $@ - sed -e 's,sql/pgtap,sql/pgtap-core,g' compat/install-8.3.patch | patch -s -p0 + sed -e 's,sql/pgtap,sql/pgtap-core,g' compat/install-8.3.patch | patch -p0 sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap-core.sql > sql/pgtap-core.tmp $(PERL) compat/gencore 0 sql/pgtap-core.tmp > sql/pgtap-core.sql rm sql/pgtap-core.tmp - rm sql/pgtap-core.sql.orig sql/pgtap-schema.sql: sql/pgtap.sql.in cp $< $@ - sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-8.3.patch | patch -s -p0 + sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-8.3.patch | patch -p0 sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap-schema.sql > sql/pgtap-schema.tmp $(PERL) compat/gencore 1 sql/pgtap-schema.tmp > sql/pgtap-schema.sql rm sql/pgtap-schema.tmp - rm sql/pgtap-schema.sql.orig # Make sure that we build the regression tests. installcheck: test/setup.sql diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index 90aa6e235992..1aded62e3d76 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -1,6 +1,6 @@ ---- sql/pgtap.sql.saf 2011-11-10 14:58:17.000000000 -0800 -+++ sql/pgtap.sql 2011-11-10 14:59:43.000000000 -0800 -@@ -12,6 +12,11 @@ +--- sql/pgtap.sql.saf 2012-09-10 16:57:28.000000000 -0700 ++++ sql/pgtap.sql 2012-09-10 16:57:48.000000000 -0700 +@@ -9,6 +9,11 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -12,7 +12,7 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT s.a[1]::int * 10000 -@@ -6244,7 +6249,7 @@ +@@ -6254,7 +6259,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -21,7 +21,7 @@ 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' || -@@ -6399,7 +6404,7 @@ +@@ -6409,7 +6414,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -30,7 +30,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6585,13 +6590,7 @@ +@@ -6595,13 +6600,7 @@ $$ LANGUAGE sql; -- collect_tap( tap, tap, tap ) @@ -45,7 +45,7 @@ RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); $$ LANGUAGE sql; -@@ -7063,7 +7062,7 @@ +@@ -7073,7 +7072,7 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; From 8e65c5c091c4de22fb4611f7e5fe0398ee913d98 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 10 Sep 2012 17:08:08 -0700 Subject: [PATCH 0669/1195] Update 8.2 patch. --- compat/install-8.2.patch | 54 ++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 762788c89cc0..93daa75e901b 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,6 +1,6 @@ ---- sql/pgtap.sql.saf 2011-11-10 15:40:08.000000000 -0800 -+++ sql/pgtap.sql 2011-11-10 15:40:43.000000000 -0800 -@@ -8,6 +8,59 @@ +--- sql/pgtap.sql.saf 2012-09-10 17:04:49.000000000 -0700 ++++ sql/pgtap.sql 2012-09-10 17:05:13.000000000 -0700 +@@ -5,6 +5,59 @@ -- -- http://pgtap.org/ @@ -60,7 +60,7 @@ CREATE OR REPLACE FUNCTION pg_version() RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; -@@ -191,11 +244,11 @@ +@@ -188,11 +241,11 @@ RETURNS integer AS $$ BEGIN EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) @@ -77,7 +77,7 @@ RETURN currval('__tresults___numb_seq'); END; $$ LANGUAGE plpgsql; -@@ -270,21 +323,6 @@ +@@ -267,21 +320,6 @@ ); $$ LANGUAGE sql strict; @@ -99,7 +99,7 @@ CREATE OR REPLACE FUNCTION ok ( boolean, text ) RETURNS TEXT AS $$ DECLARE -@@ -497,9 +535,9 @@ +@@ -494,9 +532,9 @@ output TEXT; BEGIN EXECUTE 'SELECT ' || @@ -111,7 +111,7 @@ INTO result; output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( -@@ -1103,7 +1141,7 @@ +@@ -1100,7 +1138,7 @@ SELECT COALESCE(substring( pg_catalog.format_type($1, $2), '(("(?!")([^"]|"")+"|[^.]+)([(][^)]+[)])?)$' @@ -120,7 +120,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION display_type ( NAME, OID, INTEGER ) -@@ -2194,7 +2232,7 @@ +@@ -2191,7 +2229,7 @@ p.proname AS name, array_to_string(p.proargtypes::regtype[], ',') AS args, CASE p.proretset WHEN TRUE THEN 'setof ' ELSE '' END @@ -129,7 +129,7 @@ p.prolang AS langoid, p.proisstrict AS is_strict, p.proisagg AS is_agg, -@@ -3399,63 +3437,6 @@ +@@ -3409,63 +3447,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -193,7 +193,7 @@ CREATE OR REPLACE FUNCTION _has_role( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( -@@ -5924,13 +5905,13 @@ +@@ -5934,13 +5915,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -209,7 +209,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6154,7 +6135,7 @@ +@@ -6164,7 +6145,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -218,7 +218,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6249,11 +6230,11 @@ +@@ -6259,11 +6240,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -233,7 +233,7 @@ ); END IF; rownum = rownum + 1; -@@ -6268,8 +6249,8 @@ +@@ -6278,8 +6259,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -244,7 +244,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6404,7 +6385,7 @@ +@@ -6414,7 +6395,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -253,7 +253,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6418,8 +6399,8 @@ +@@ -6428,8 +6409,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -264,7 +264,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6544,9 +6525,9 @@ +@@ -6554,9 +6535,9 @@ DECLARE typeof regtype := pg_typeof($1); BEGIN @@ -277,7 +277,7 @@ END; $$ LANGUAGE plpgsql; -@@ -6567,7 +6548,7 @@ +@@ -6577,7 +6558,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -286,7 +286,7 @@ END LOOP; -- What extra records do we have? -@@ -6712,7 +6693,7 @@ +@@ -6722,7 +6703,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -295,7 +295,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT -@@ -6730,7 +6711,7 @@ +@@ -6740,7 +6721,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -304,7 +304,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) ), -@@ -6763,7 +6744,7 @@ +@@ -6773,7 +6754,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -313,7 +313,7 @@ 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']) ) -@@ -6782,7 +6763,7 @@ +@@ -6792,7 +6773,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -322,7 +322,7 @@ 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']) ) -@@ -7062,10 +7043,12 @@ +@@ -7072,10 +7053,12 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; @@ -338,7 +338,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7210,7 +7193,7 @@ +@@ -7220,7 +7203,7 @@ CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) RETURNS TEXT AS $$ @@ -347,7 +347,7 @@ $$ LANGUAGE SQL; -- operators_are( schema, operators[], description ) -@@ -7219,7 +7202,7 @@ +@@ -7229,7 +7212,7 @@ SELECT _areni( 'operators', ARRAY( @@ -356,7 +356,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7231,7 +7214,7 @@ +@@ -7241,7 +7224,7 @@ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT @@ -365,7 +365,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7252,7 +7235,7 @@ +@@ -7262,7 +7245,7 @@ SELECT _areni( 'operators', ARRAY( @@ -374,7 +374,7 @@ 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) -@@ -7265,7 +7248,7 @@ +@@ -7275,7 +7258,7 @@ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT From df291272002e35223d0b0d645cd2e61855705bee Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 10 Sep 2012 17:19:20 -0700 Subject: [PATCH 0670/1195] Update 8.1 patch. --- compat/install-8.1.patch | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index 07742067ffc4..9e89c5cd592f 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ ---- sql/pgtap.sql.saf 2011-11-10 15:49:06.000000000 -0800 -+++ sql/pgtap.sql 2011-11-10 15:49:12.000000000 -0800 -@@ -1993,13 +1993,13 @@ +--- sql/pgtap.sql.saf 2012-09-10 17:09:02.000000000 -0700 ++++ sql/pgtap.sql 2012-09-10 17:09:26.000000000 -0700 +@@ -1990,13 +1990,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -18,7 +18,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $6 || ' constriants'; -@@ -2017,13 +2017,13 @@ +@@ -2014,13 +2014,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -36,7 +36,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $5 || ' constriants'; -@@ -5677,7 +5677,7 @@ +@@ -5687,7 +5687,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -45,7 +45,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -5685,8 +5685,8 @@ +@@ -5695,8 +5695,8 @@ -- 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. @@ -56,7 +56,7 @@ END LOOP; END LOOP; RETURN; -@@ -5756,14 +5756,14 @@ +@@ -5766,14 +5766,14 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -73,7 +73,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -5778,15 +5778,15 @@ +@@ -5788,15 +5788,15 @@ IF verbos THEN RETURN NEXT diag_test_name(tests[i]); END IF; -- Run the setup functions. @@ -93,7 +93,7 @@ -- Remember how many failed and then roll back. num_faild := num_faild + num_failed(); -@@ -5801,7 +5801,7 @@ +@@ -5811,7 +5811,7 @@ END LOOP; -- Run the shutdown functions. @@ -102,7 +102,7 @@ -- Raise an exception to rollback any changes. RAISE EXCEPTION '__TAP_ROLLBACK__'; -@@ -5812,8 +5812,8 @@ +@@ -5822,8 +5822,8 @@ END IF; END; -- Finish up. @@ -113,7 +113,7 @@ END LOOP; -- Clean up and return. -@@ -7042,7 +7042,7 @@ +@@ -7052,7 +7052,7 @@ DECLARE rec RECORD; BEGIN From 8aa311cbed0a688c52244b91e229ee40433ce5ba Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 10 Sep 2012 17:21:57 -0700 Subject: [PATCH 0671/1195] 8.1 or later, not 8.0. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c3beb0556a66..5f7165544651 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') # We support 8.1 and later. ifeq ($(shell echo $(VERSION) | grep -qE " 7[.]|8[.]0" && echo yes || echo no),yes) -$(error pgTAP requires PostgreSQL 8.0 or later. This is $(VERSION)) +$(error pgTAP requires PostgreSQL 8.1 or later. This is $(VERSION)) endif # Compile the C code only if we're on 8.3 or older. From da6eaab658f5cc23ae4687849bd3c27323c95ec8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 10 Sep 2012 17:26:14 -0700 Subject: [PATCH 0672/1195] Timestamp v0.91.0. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 0ab99a6ea84c..7dd2b2619145 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.91.1 +0.91.1 2012-09-11T00:26:52Z --------------------------- * The OS name is restored to the call to `os_name()`. The `getos.sh` shell script was not getting called during `make`. Thanks to Peter Eisentraut for From 01451d578c3ea6e6c40c0f636b3c64b5996a2a17 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 10 Sep 2012 17:30:16 -0700 Subject: [PATCH 0673/1195] Increment distribution to v0.92.0. --- Changes | 3 +++ META.json | 2 +- README.md | 2 +- doc/pgtap.mmd | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 7dd2b2619145..72cc6f30fb17 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Revision history for pgTAP ========================== +0.92.0 +--------------------------- + 0.91.1 2012-09-11T00:26:52Z --------------------------- * The OS name is restored to the call to `os_name()`. The `getos.sh` shell diff --git a/META.json b/META.json index 91c4b9025a6d..7a6024c4bc82 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "0.91.0", + "version": "0.92.0", "maintainer": [ "David E. Wheeler ", "pgTAP List " diff --git a/README.md b/README.md index 68014d25c371..6ef87d33c4e9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 0.91.0 +pgTAP 0.92.0 ============ [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 51cd546a3014..aaf9b7fb7ff5 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 0.91.0 +pgTAP 0.92.0 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and From 082d57cac5f566b63501ddb4eb6dd3259853d680 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 10 Sep 2012 17:36:05 -0700 Subject: [PATCH 0674/1195] I kant tipe. --- doc/pgtap.mmd | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index aaf9b7fb7ff5..15dc41009978 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2603,7 +2603,7 @@ specified sequence does *not* exist. ### `has_type()` ### - SLEECT has_type( schema, type, description ); + SELECT has_type( schema, type, description ); SELECT has_type( schema, type ); SELECT has_type( type, description ); SELECT has_type( type ); @@ -2640,7 +2640,7 @@ are a part of it, use the column testing functions to verify them, like so: ### `hasnt_type()` ### - SLEECT hasnt_type( schema, type, description ); + SELECT hasnt_type( schema, type, description ); SELECT hasnt_type( schema, type ); SELECT hasnt_type( type, description ); SELECT hasnt_type( type ); @@ -2661,7 +2661,7 @@ type does *not* exist. ### `has_domain()` ### - SLEECT has_domain( schema, domain, description ); + SELECT has_domain( schema, domain, description ); SELECT has_domain( schema, domain ); SELECT has_domain( domain, description ); SELECT has_domain( domain ); @@ -2691,7 +2691,7 @@ get treated as a description. ### `hasnt_domain()` ### - SLEECT hasnt_domain( schema, domain, description ); + SELECT hasnt_domain( schema, domain, description ); SELECT hasnt_domain( schema, domain ); SELECT hasnt_domain( domain, description ); SELECT hasnt_domain( domain ); @@ -2712,7 +2712,7 @@ domain does *not* exist. ### `has_enum()` ### - SLEECT has_enum( schema, enum, description ); + SELECT has_enum( schema, enum, description ); SELECT has_enum( schema, enum ); SELECT has_enum( enum, description ); SELECT has_enum( enum ); @@ -2742,7 +2742,7 @@ treated as a description. ### `hasnt_enum()` ### - SLEECT hasnt_enum( schema, enum, description ); + SELECT hasnt_enum( schema, enum, description ); SELECT hasnt_enum( schema, enum ); SELECT hasnt_enum( enum, description ); SELECT hasnt_enum( enum ); From 2c7d48ea240f46191845890fa583aaa6e8860719 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 12 Sep 2012 13:54:39 -0700 Subject: [PATCH 0675/1195] Add note not to include typemod when testing for casts. Thanks to Nathan Wagner for alerting me to this issue. --- doc/pgtap.mmd | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 15dc41009978..f1150338985d 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -3057,10 +3057,10 @@ specified function (optionally with the specified signature) does *not* exist. **Parameters** `:source_type` -: Data type of the source value. +: Data type of the source value without typemod. `:target_type` -: Data type of the target value. +: Data type of the target value without typemod. `:schema` : Schema in which to find the operator function. @@ -3084,6 +3084,15 @@ resolve the function name as a description. For example: pgTAP will generate a useful description if you don't provide one. +Note that pgTAP does not compare typemods. So if you wanted to test for a cast +between, say, a `uuid` type and `bit(128)`, this will not work: + + SELECT has_cast( 'integer', 'bit(128)' ); + +But this will: + + SELECT has_cast( 'integer', 'bit' ); + ### `hasnt_cast()` ### SELECT hasnt_cast( :source_type, :target_type, :schema, :function, :description ); From 56f748ac2dfd71e2a0dcbc57d0f1b99d372c9062 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 12 Sep 2012 13:56:24 -0700 Subject: [PATCH 0676/1195] Note typemod doc note. --- Changes | 1 + 1 file changed, 1 insertion(+) diff --git a/Changes b/Changes index 72cc6f30fb17..2bf0efd6c5cd 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,7 @@ Revision history for pgTAP 0.92.0 --------------------------- +* Added note about the lack of typemods to the documentation for `has_cast()`. 0.91.1 2012-09-11T00:26:52Z --------------------------- From ed3aea8ab6d05cf6fcc79f00b053ccb5a61660b3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 12 Sep 2012 13:57:29 -0700 Subject: [PATCH 0677/1195] Note typo fix commited in 082d57ca. --- Changes | 1 + 1 file changed, 1 insertion(+) diff --git a/Changes b/Changes index 2bf0efd6c5cd..7cbdd7d250c7 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,7 @@ Revision history for pgTAP 0.92.0 --------------------------- +* Fixed some mis-spellings of `SELECT` in documentation examples. * Added note about the lack of typemods to the documentation for `has_cast()`. 0.91.1 2012-09-11T00:26:52Z From 5e582568f8fe84b069354523cb37bc80cbfc4154 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 25 Sep 2012 14:08:23 -0700 Subject: [PATCH 0678/1195] Check for table visibility when checking column defaults. --- Changes | 2 + META.json | 6 +- sql/pgtap--0.91.0--0.92.0.sql | 13 + sql/pgtap-core.sql.orig | 7413 +++++++++++++++++++++++++++++++++ sql/pgtap-schema.sql.orig | 7413 +++++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 1 + 6 files changed, 14845 insertions(+), 3 deletions(-) create mode 100644 sql/pgtap--0.91.0--0.92.0.sql create mode 100644 sql/pgtap-core.sql.orig create mode 100644 sql/pgtap-schema.sql.orig diff --git a/Changes b/Changes index 7cbdd7d250c7..779dc4b5d933 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,8 @@ Revision history for pgTAP --------------------------- * Fixed some mis-spellings of `SELECT` in documentation examples. * Added note about the lack of typemods to the documentation for `has_cast()`. +* Added check to ensure a table is visible in tests for column defaults. + Thanks to Henk Enting for the report. 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/META.json b/META.json index 7a6024c4bc82..8219407832f7 100644 --- a/META.json +++ b/META.json @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "0.91.0" + "version": "0.92.0" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "0.91.0" + "version": "0.92.0" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "0.91.0" + "version": "0.92.0" } }, "resources": { diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql new file mode 100644 index 000000000000..4d1937f50406 --- /dev/null +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -0,0 +1,13 @@ +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; diff --git a/sql/pgtap-core.sql.orig b/sql/pgtap-core.sql.orig new file mode 100644 index 000000000000..92d46a4ca6bd --- /dev/null +++ b/sql/pgtap-core.sql.orig @@ -0,0 +1,7413 @@ +-- 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: +-- +-- http://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 s.a[1]::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() +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; + CREATE TEMP TABLE __tresults__ ( + numb INTEGER NOT NULL DEFAULT nextval(''__tresults___numb_seq''), + ok BOOLEAN NOT NULL DEFAULT TRUE, + aok BOOLEAN NOT NULL DEFAULT TRUE, + descr TEXT NOT NULL DEFAULT '''', + type TEXT NOT NULL DEFAULT '''', + reason TEXT NOT NULL DEFAULT '''' + ); + CREATE UNIQUE INDEX __tresults___key ON __tresults__(numb); + GRANT ALL ON TABLE __tresults__ TO PUBLIC; + 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 ); + 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; +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 + EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) + VALUES( ' || $1 || ', ' + || $2 || ', ' + || quote_literal(COALESCE($3, '')) || ', ' + || quote_literal($4) || ', ' + || quote_literal($5) || ' )'; + RETURN currval('__tresults___numb_seq'); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION num_failed () +RETURNS INTEGER AS $$ +DECLARE + ret integer; +BEGIN + EXECUTE 'SELECT COUNT(*)::INTEGER FROM __tresults__ WHERE ok = FALSE' INTO ret; + RETURN ret; +END; +$$ LANGUAGE plpgsql strict; + +CREATE OR REPLACE FUNCTION _finish ( INTEGER, INTEGER, INTEGER) +RETURNS SETOF TEXT AS $$ +DECLARE + curr_test ALIAS FOR $1; + exp_tests INTEGER := $2; + num_faild ALIAS FOR $3; + plural CHAR; +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 + 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 () +RETURNS SETOF TEXT AS $$ + SELECT * FROM _finish( + _get('curr_test'), + _get('plan'), + num_failed() + ); +$$ 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, '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, 'SKIP: ' || $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 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; + +-- lives_ok( sql, description ) +CREATE OR REPLACE FUNCTION lives_ok ( TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + code TEXT := _query($1); + descr ALIAS FOR $2; +BEGIN + EXECUTE code; + RETURN ok( TRUE, descr ); +EXCEPTION WHEN OTHERS THEN + -- There should have been no exception. + RETURN ok( FALSE, descr ) || E'\n' || diag( + ' died: ' || SQLSTATE || ': ' || SQLERRM + ); +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; + +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 = $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 = $1 + AND pg_catalog.pg_table_is_visible(c.oid) + AND c.relname = $2 + ); +$$ LANGUAGE SQL; + +-- has_table( schema, table, description ) +CREATE OR REPLACE FUNCTION has_table ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists( 'r', $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- has_table( table, description ) +CREATE OR REPLACE FUNCTION has_table ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists( 'r', $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', $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_table( table, description ) +CREATE OR REPLACE FUNCTION hasnt_table ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _rexists( 'r', $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( 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( 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( 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; + +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 $$ +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( + 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 + ), $4 + ); +END; +$$ LANGUAGE plpgsql; + +-- _col_is_null( table, column, desc, null ) +CREATE OR REPLACE FUNCTION _col_is_null ( NAME, NAME, TEXT, bool ) +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( + 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 + ), $3 + ); +END; +$$ LANGUAGE plpgsql; + +-- col_not_null( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, $3, $4, true ); +$$ LANGUAGE SQL; + +-- col_not_null( table, column, description ) +CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, $3, true ); +$$ LANGUAGE SQL; + +-- col_not_null( table, column ) +CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should be NOT NULL', true ); +$$ LANGUAGE SQL; + +-- col_is_null( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, $3, $4, false ); +$$ LANGUAGE SQL; + +-- col_is_null( schema, table, column ) +CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, $3, false ); +$$ LANGUAGE SQL; + +-- col_is_null( table, column ) +CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should allow NULL', false ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION display_type ( OID, INTEGER ) +RETURNS TEXT AS $$ + SELECT COALESCE(substring( + pg_catalog.format_type($1, $2), + '(("(?!")([^"]|"")+"|[^.]+)([(][^)]+[)])?)$' + ), '') +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION display_type ( NAME, OID, INTEGER ) +RETURNS TEXT AS $$ + SELECT CASE WHEN $1 IS NULL THEN '' ELSE quote_ident($1) || '.' END + || display_type($2, $3) +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT display_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 display_type(a.atttypid, a.atttypmod) + FROM pg_catalog.pg_attribute a + JOIN pg_catalog.pg_class c ON a.attrelid = c.oid + WHERE pg_table_is_visible(c.oid) + AND c.relname = $1 + AND a.attname = $2 + AND attnum > 0 + AND NOT a.attisdropped + AND pg_type_is_visible(a.atttypid) +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_col_ns_type ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT display_type(tn.nspname, 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 _quote_ident_like(TEXT, TEXT) +RETURNS TEXT AS $$ +DECLARE + have TEXT; + pcision TEXT; +BEGIN + -- Just return it if rhs isn't quoted. + IF $2 !~ '"' THEN RETURN $1; END IF; + + -- If it's quoted ident without precision, return it quoted. + IF $2 ~ '"$' THEN RETURN quote_ident($1); END IF; + + pcision := substring($1 FROM '[(][^")]+[)]$'); + + -- Just quote it if thre is no precision. + if pcision IS NULL THEN RETURN quote_ident($1); END IF; + + -- Quote the non-precision part and concatenate with precision. + RETURN quote_ident(substring($1 FOR char_length($1) - char_length(pcision))) + || pcision; +END; +$$ LANGUAGE plpgsql; + +-- 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; + + want_type := quote_ident($4) || '.' || _quote_ident_like($5, have_type); + 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 := _quote_ident_like($4, have_type); + 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 + IF $1 ~ '^[^'']+[(]' THEN + -- It's a functional default. + 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), + display_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), + display_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 c.relhaspkey = true + AND 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 c.relhaspkey = true + AND 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( 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: http://pgfoundry.org/projects/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: http://pgfoundry.org/projects/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: http://pgfoundry.org/projects/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) + 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 +$$ 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) + 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 +$$ 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( 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( 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 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 || ' constriants'; + 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 || ' constriants'; + 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( 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 + 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; + +CREATE OR REPLACE VIEW tap_funky + AS SELECT p.oid AS oid, + n.nspname AS schema, + p.proname AS name, + 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, + p.proisagg AS is_agg, + 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 _got_func ( NAME, NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT TRUE + FROM tap_funky + WHERE schema = $1 + AND name = $2 + AND args = array_to_string($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 = array_to_string($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 NAME[] AS $$ + SELECT ARRAY( + SELECT a.attname + 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_attribute a ON ct.oid = a.attrelid + JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) + ON a.attnum = x.indkey[s.i] + 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 NAME[] AS $$ + SELECT ARRAY( + SELECT a.attname + 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_attribute a ON ct.oid = a.attrelid + JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) + ON a.attnum = x.indkey[s.i] + 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 ct.relname = $2 + AND ci.relname = $3 + AND n.nspname = $1 + ); +$$ 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 + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME, NAME) +RETURNS TEXT AS $$ + SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) + 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 +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME) +RETURNS TEXT AS $$ + SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) + 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) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')', + quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_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/expression, description ) +CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME, text ) +RETURNS TEXT AS $$ +DECLARE + expr text; +BEGIN + IF $4 NOT LIKE '%(%' THEN + -- Not a functional index. + RETURN has_index( $1, $2, $3, ARRAY[$4], $5 ); + END IF; + + -- Get the functional expression. + expr := _iexpr($1, $2, $3); + + IF expr IS NULL 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) || '(' || expr || ')', + quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || $4 || ')', + $5 + ); +END; +$$ LANGUAGE plpgsql; + +-- has_index( schema, table, index, columns/expression ) +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) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')', + quote_ident($2) || ' ON ' || quote_ident($1) || '(' || _ident_array_to_string( $3, ', ' ) || ')', + $4 + ); +END; +$$ LANGUAGE plpgsql; + +-- has_index( table, index, columns[], description ) +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 $$ +DECLARE + want_expr text; + descr text; + have_expr text; + idx name; + tab text; +BEGIN + IF $3 NOT LIKE '%(%' THEN + -- Not a functional index. + IF _is_schema( $1 ) THEN + -- Looking for schema.table index. + RETURN ok ( _have_index( $1, $2, $3 ), $4); + END IF; + -- Looking for particular columns. + RETURN has_index( $1, $2, ARRAY[$3], $4 ); + END IF; + + -- Get the functional expression. + IF _is_schema( $1 ) THEN + -- Looking for an index within a schema. + have_expr := _iexpr($1, $2, $3); + want_expr := $4; + descr := 'Index ' || quote_ident($3) || ' should exist'; + idx := $3; + tab := quote_ident($1) || '.' || quote_ident($2); + ELSE + -- Looking for an index without a schema spec. + have_expr := _iexpr($1, $2); + want_expr := $3; + descr := $4; + idx := $2; + tab := quote_ident($1); + END IF; + + IF have_expr IS NULL THEN + RETURN ok( false, descr ) || E'\n' + || diag( 'Index ' || idx || ' ON ' || tab || ' not found'); + END IF; + + RETURN is( + quote_ident(idx) || ' ON ' || tab || '(' || have_expr || ')', + quote_ident(idx) || ' ON ' || tab || '(' || want_expr || ')', + descr + ); +END; +$$ LANGUAGE plpgsql; + +-- 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; + +-- 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 + ); +$$ 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.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( + 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( group, user[], 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_user ON usename = $2[i] + WHERE usesysid IS NULL + OR NOT usesysid = 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( + ' Users missing from the ' || quote_ident($1) || E' group:\n ' || + array_to_string( missing, E'\n ') + ); +END; +$$ LANGUAGE plpgsql; + +-- is_member_of( group, user, 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( group, user[] ) +CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT is_member_of( $1, $2, 'Should have members of group ' || quote_ident($1) ); +$$ LANGUAGE SQL; + +-- is_member_of( group, user ) +CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT is_member_of( $1, ARRAY[$2] ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _cmp_types(oid, name) +RETURNS BOOLEAN AS $$ +DECLARE + dtype TEXT := display_type($1, NULL); +BEGIN + RETURN dtype = _quote_ident_like($2, dtype); +END; +$$ LANGUAGE plpgsql; + +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; + +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, $1) END + AND CASE o.oprkind WHEN 'r' THEN $4 IS NULL + ELSE _cmp_types(o.oprright, $4) END + AND _cmp_types(o.oprresult, $5) + ); +$$ LANGUAGE SQL; + +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, $1) END + AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL + ELSE _cmp_types(o.oprright, $3) END + AND _cmp_types(o.oprresult, $4) + ); +$$ LANGUAGE SQL; + +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, $1) END + AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL + ELSE _cmp_types(o.oprright, $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; + +-- 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; + +-- 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; + +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_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_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 = $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 = $1 + AND c.relname NOT IN ('__tcache__', '__tresults__', '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 _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 = $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 = $1 + ); +$$ LANGUAGE SQL; + +-- tables_are( schema, tables, description ) +CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( 'tables', _extras('r', $1, $2), _missing('r', $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', $1), _missing('r', $1), $2); +$$ LANGUAGE SQL; + +-- tables_are( schema, tables ) +CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _are( + 'tables', _extras('r', $1, $2), _missing('r', $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', $1), _missing('r', $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 = COALESCE($1, n.nspname) + AND oc.opcname = $2 + ); +$$ 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( NULL, $1 ), $2) +$$ LANGUAGE SQL; + +-- has_opclass( name ) +CREATE OR REPLACE FUNCTION has_opclass( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _opc_exists( NULL, $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 exist' ); +$$ LANGUAGE SQL; + +-- hasnt_opclass( name, description ) +CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _opc_exists( NULL, $1 ), $2) +$$ LANGUAGE SQL; + +-- hasnt_opclass( name ) +CREATE OR REPLACE FUNCTION hasnt_opclass( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _opc_exists( NULL, $1 ), 'Operator class ' || quote_ident($1) || ' should 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 = array_to_string($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 = array_to_string($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 = array_to_string($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 = array_to_string($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; + +-- 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), $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), $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), $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), $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 = array_to_string($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 = array_to_string($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; + +CREATE OR REPLACE FUNCTION _agg ( NAME, NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT is_agg + FROM tap_funky + WHERE schema = $1 + AND name = $2 + AND args = array_to_string($3, ',') +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _agg ( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT is_agg FROM tap_funky WHERE schema = $1 AND name = $2 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _agg ( NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT is_agg + FROM tap_funky + WHERE name = $1 + AND args = array_to_string($2, ',') + AND is_visible; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _agg ( NAME ) +RETURNS BOOLEAN AS $$ + SELECT is_agg FROM tap_funky WHERE name = $1 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, _agg($1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- is_aggregate( schema, function, args[] ) +CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + _agg($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, _agg($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- is_aggregate( schema, function ) +CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _agg($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, _agg($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- is_aggregate( function, args[] ) +CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + _agg($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, _agg($1), $2 ); +$$ LANGUAGE sql; + +-- is_aggregate( function ) +CREATE OR REPLACE FUNCTION is_aggregate( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _agg($1), 'Function ' || quote_ident($1) || '() should 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 = array_to_string($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 = array_to_string($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; + +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 = array_to_string($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 = array_to_string($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. + EXECUTE 'SELECT aok, descr FROM __tresults__ WHERE numb = ' || tnumb + INTO aok, adescr; + + -- Now delete those results. + EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb; + + -- 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 3 + char_length( diag( adescr ) ) ); + END IF; + + -- Remove failure message from ok(). + IF NOT aok THEN + adiag := substring( + adiag + FROM 14 + char_length(tnumb::text) + + CASE adescr WHEN '' THEN 3 ELSE 3 + char_length( diag( adescr ) ) END + ); + 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 ) +RETURNS TEXT[] AS $$ + SELECT ARRAY( + SELECT DISTINCT quote_ident(n.nspname) || '.' || quote_ident(p.proname) 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 + ORDER BY pname + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION findfuncs( TEXT ) +RETURNS TEXT[] AS $$ + SELECT ARRAY( + SELECT DISTINCT quote_ident(n.nspname) || '.' || quote_ident(p.proname) 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 + ORDER BY pname + ); +$$ 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 TABLE __tresults__; + 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; + verbos boolean := _is_verbose(); -- verbose is a reserved word in 8.5. + num_faild INTEGER := 0; +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; + + BEGIN + FOR i IN 1..array_upper(tests, 1) LOOP + BEGIN + -- What test are we running? + IF verbos THEN RETURN NEXT diag_test_name(tests[i]); END IF; + + -- Run the setup functions. + FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; + + -- Run the actual test function. + FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP + RETURN NEXT tap; + END LOOP; + + -- Run the teardown functions. + FOR tap IN SELECT * FROM _runem(teardown, false) LOOP RETURN NEXT tap; END LOOP; + + -- Remember how many failed and then roll back. + num_faild := num_faild + num_failed(); + RAISE EXCEPTION '__TAP_ROLLBACK__'; + + EXCEPTION WHEN raise_exception THEN + IF SQLERRM <> '__TAP_ROLLBACK__' THEN + -- We didn't raise it, so propagate it. + RAISE EXCEPTION '%', SQLERRM; + END IF; + END; + END LOOP; + + -- Run the shutdown functions. + FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; + + -- Raise an exception to rollback any changes. + RAISE EXCEPTION '__TAP_ROLLBACK__'; + EXCEPTION WHEN raise_exception THEN + IF SQLERRM <> '__TAP_ROLLBACK__' THEN + -- We didn't raise it, so propagate it. + RAISE EXCEPTION '%', SQLERRM; + END IF; + END; + -- Finish up. + FOR tap IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) 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 ) + ); +$$ 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 ) + ); +$$ 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; +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 + 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 + ); +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; +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 + 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 + ); +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; + +-- 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 $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.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 $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 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 display_type(CASE WHEN $3 THEN tn.nspname ELSE NULL END, 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 d.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 display_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 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; + + RETURN is( actual_type, quote_ident($3) || '.' || _quote_ident_like($4, actual_type), $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, _quote_ident_like($3, actual_type), $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, _quote_ident_like($2, actual_type), $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; + + RETURN isnt( actual_type, quote_ident($3) || '.' || _quote_ident_like($4, actual_type), $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, _quote_ident_like($3, actual_type), $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, _quote_ident_like($2, actual_type), $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 + 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 + ), + $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') + 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') + ), + $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_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_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 display_type(castsource, NULL) || ' AS ' || display_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 display_type(castsource, NULL) || ' AS ' || display_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; + diff --git a/sql/pgtap-schema.sql.orig b/sql/pgtap-schema.sql.orig new file mode 100644 index 000000000000..92d46a4ca6bd --- /dev/null +++ b/sql/pgtap-schema.sql.orig @@ -0,0 +1,7413 @@ +-- 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: +-- +-- http://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 s.a[1]::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() +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; + CREATE TEMP TABLE __tresults__ ( + numb INTEGER NOT NULL DEFAULT nextval(''__tresults___numb_seq''), + ok BOOLEAN NOT NULL DEFAULT TRUE, + aok BOOLEAN NOT NULL DEFAULT TRUE, + descr TEXT NOT NULL DEFAULT '''', + type TEXT NOT NULL DEFAULT '''', + reason TEXT NOT NULL DEFAULT '''' + ); + CREATE UNIQUE INDEX __tresults___key ON __tresults__(numb); + GRANT ALL ON TABLE __tresults__ TO PUBLIC; + 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 ); + 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; +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 + EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) + VALUES( ' || $1 || ', ' + || $2 || ', ' + || quote_literal(COALESCE($3, '')) || ', ' + || quote_literal($4) || ', ' + || quote_literal($5) || ' )'; + RETURN currval('__tresults___numb_seq'); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION num_failed () +RETURNS INTEGER AS $$ +DECLARE + ret integer; +BEGIN + EXECUTE 'SELECT COUNT(*)::INTEGER FROM __tresults__ WHERE ok = FALSE' INTO ret; + RETURN ret; +END; +$$ LANGUAGE plpgsql strict; + +CREATE OR REPLACE FUNCTION _finish ( INTEGER, INTEGER, INTEGER) +RETURNS SETOF TEXT AS $$ +DECLARE + curr_test ALIAS FOR $1; + exp_tests INTEGER := $2; + num_faild ALIAS FOR $3; + plural CHAR; +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 + 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 () +RETURNS SETOF TEXT AS $$ + SELECT * FROM _finish( + _get('curr_test'), + _get('plan'), + num_failed() + ); +$$ 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, '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, 'SKIP: ' || $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 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; + +-- lives_ok( sql, description ) +CREATE OR REPLACE FUNCTION lives_ok ( TEXT, TEXT ) +RETURNS TEXT AS $$ +DECLARE + code TEXT := _query($1); + descr ALIAS FOR $2; +BEGIN + EXECUTE code; + RETURN ok( TRUE, descr ); +EXCEPTION WHEN OTHERS THEN + -- There should have been no exception. + RETURN ok( FALSE, descr ) || E'\n' || diag( + ' died: ' || SQLSTATE || ': ' || SQLERRM + ); +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; + +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 = $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 = $1 + AND pg_catalog.pg_table_is_visible(c.oid) + AND c.relname = $2 + ); +$$ LANGUAGE SQL; + +-- has_table( schema, table, description ) +CREATE OR REPLACE FUNCTION has_table ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists( 'r', $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- has_table( table, description ) +CREATE OR REPLACE FUNCTION has_table ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _rexists( 'r', $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', $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- hasnt_table( table, description ) +CREATE OR REPLACE FUNCTION hasnt_table ( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _rexists( 'r', $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( 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( 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( 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; + +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 $$ +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( + 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 + ), $4 + ); +END; +$$ LANGUAGE plpgsql; + +-- _col_is_null( table, column, desc, null ) +CREATE OR REPLACE FUNCTION _col_is_null ( NAME, NAME, TEXT, bool ) +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( + 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 + ), $3 + ); +END; +$$ LANGUAGE plpgsql; + +-- col_not_null( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, $3, $4, true ); +$$ LANGUAGE SQL; + +-- col_not_null( table, column, description ) +CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, $3, true ); +$$ LANGUAGE SQL; + +-- col_not_null( table, column ) +CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should be NOT NULL', true ); +$$ LANGUAGE SQL; + +-- col_is_null( schema, table, column, description ) +CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, $3, $4, false ); +$$ LANGUAGE SQL; + +-- col_is_null( schema, table, column ) +CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, $3, false ); +$$ LANGUAGE SQL; + +-- col_is_null( table, column ) +CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should allow NULL', false ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION display_type ( OID, INTEGER ) +RETURNS TEXT AS $$ + SELECT COALESCE(substring( + pg_catalog.format_type($1, $2), + '(("(?!")([^"]|"")+"|[^.]+)([(][^)]+[)])?)$' + ), '') +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION display_type ( NAME, OID, INTEGER ) +RETURNS TEXT AS $$ + SELECT CASE WHEN $1 IS NULL THEN '' ELSE quote_ident($1) || '.' END + || display_type($2, $3) +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT display_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 display_type(a.atttypid, a.atttypmod) + FROM pg_catalog.pg_attribute a + JOIN pg_catalog.pg_class c ON a.attrelid = c.oid + WHERE pg_table_is_visible(c.oid) + AND c.relname = $1 + AND a.attname = $2 + AND attnum > 0 + AND NOT a.attisdropped + AND pg_type_is_visible(a.atttypid) +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_col_ns_type ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT display_type(tn.nspname, 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 _quote_ident_like(TEXT, TEXT) +RETURNS TEXT AS $$ +DECLARE + have TEXT; + pcision TEXT; +BEGIN + -- Just return it if rhs isn't quoted. + IF $2 !~ '"' THEN RETURN $1; END IF; + + -- If it's quoted ident without precision, return it quoted. + IF $2 ~ '"$' THEN RETURN quote_ident($1); END IF; + + pcision := substring($1 FROM '[(][^")]+[)]$'); + + -- Just quote it if thre is no precision. + if pcision IS NULL THEN RETURN quote_ident($1); END IF; + + -- Quote the non-precision part and concatenate with precision. + RETURN quote_ident(substring($1 FOR char_length($1) - char_length(pcision))) + || pcision; +END; +$$ LANGUAGE plpgsql; + +-- 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; + + want_type := quote_ident($4) || '.' || _quote_ident_like($5, have_type); + 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 := _quote_ident_like($4, have_type); + 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 + IF $1 ~ '^[^'']+[(]' THEN + -- It's a functional default. + 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), + display_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), + display_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 c.relhaspkey = true + AND 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 c.relhaspkey = true + AND 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( 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: http://pgfoundry.org/projects/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: http://pgfoundry.org/projects/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: http://pgfoundry.org/projects/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) + 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 +$$ 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) + 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 +$$ 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( 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( 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 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 || ' constriants'; + 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 || ' constriants'; + 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( 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 + 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; + +CREATE OR REPLACE VIEW tap_funky + AS SELECT p.oid AS oid, + n.nspname AS schema, + p.proname AS name, + 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, + p.proisagg AS is_agg, + 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 _got_func ( NAME, NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT TRUE + FROM tap_funky + WHERE schema = $1 + AND name = $2 + AND args = array_to_string($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 = array_to_string($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 NAME[] AS $$ + SELECT ARRAY( + SELECT a.attname + 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_attribute a ON ct.oid = a.attrelid + JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) + ON a.attnum = x.indkey[s.i] + 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 NAME[] AS $$ + SELECT ARRAY( + SELECT a.attname + 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_attribute a ON ct.oid = a.attrelid + JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) + ON a.attnum = x.indkey[s.i] + 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 ct.relname = $2 + AND ci.relname = $3 + AND n.nspname = $1 + ); +$$ 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 + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME, NAME) +RETURNS TEXT AS $$ + SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) + 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 +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME) +RETURNS TEXT AS $$ + SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) + 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) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')', + quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_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/expression, description ) +CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME, text ) +RETURNS TEXT AS $$ +DECLARE + expr text; +BEGIN + IF $4 NOT LIKE '%(%' THEN + -- Not a functional index. + RETURN has_index( $1, $2, $3, ARRAY[$4], $5 ); + END IF; + + -- Get the functional expression. + expr := _iexpr($1, $2, $3); + + IF expr IS NULL 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) || '(' || expr || ')', + quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || $4 || ')', + $5 + ); +END; +$$ LANGUAGE plpgsql; + +-- has_index( schema, table, index, columns/expression ) +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) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')', + quote_ident($2) || ' ON ' || quote_ident($1) || '(' || _ident_array_to_string( $3, ', ' ) || ')', + $4 + ); +END; +$$ LANGUAGE plpgsql; + +-- has_index( table, index, columns[], description ) +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 $$ +DECLARE + want_expr text; + descr text; + have_expr text; + idx name; + tab text; +BEGIN + IF $3 NOT LIKE '%(%' THEN + -- Not a functional index. + IF _is_schema( $1 ) THEN + -- Looking for schema.table index. + RETURN ok ( _have_index( $1, $2, $3 ), $4); + END IF; + -- Looking for particular columns. + RETURN has_index( $1, $2, ARRAY[$3], $4 ); + END IF; + + -- Get the functional expression. + IF _is_schema( $1 ) THEN + -- Looking for an index within a schema. + have_expr := _iexpr($1, $2, $3); + want_expr := $4; + descr := 'Index ' || quote_ident($3) || ' should exist'; + idx := $3; + tab := quote_ident($1) || '.' || quote_ident($2); + ELSE + -- Looking for an index without a schema spec. + have_expr := _iexpr($1, $2); + want_expr := $3; + descr := $4; + idx := $2; + tab := quote_ident($1); + END IF; + + IF have_expr IS NULL THEN + RETURN ok( false, descr ) || E'\n' + || diag( 'Index ' || idx || ' ON ' || tab || ' not found'); + END IF; + + RETURN is( + quote_ident(idx) || ' ON ' || tab || '(' || have_expr || ')', + quote_ident(idx) || ' ON ' || tab || '(' || want_expr || ')', + descr + ); +END; +$$ LANGUAGE plpgsql; + +-- 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; + +-- 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 + ); +$$ 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.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( + 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( group, user[], 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_user ON usename = $2[i] + WHERE usesysid IS NULL + OR NOT usesysid = 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( + ' Users missing from the ' || quote_ident($1) || E' group:\n ' || + array_to_string( missing, E'\n ') + ); +END; +$$ LANGUAGE plpgsql; + +-- is_member_of( group, user, 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( group, user[] ) +CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT is_member_of( $1, $2, 'Should have members of group ' || quote_ident($1) ); +$$ LANGUAGE SQL; + +-- is_member_of( group, user ) +CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT is_member_of( $1, ARRAY[$2] ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _cmp_types(oid, name) +RETURNS BOOLEAN AS $$ +DECLARE + dtype TEXT := display_type($1, NULL); +BEGIN + RETURN dtype = _quote_ident_like($2, dtype); +END; +$$ LANGUAGE plpgsql; + +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; + +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, $1) END + AND CASE o.oprkind WHEN 'r' THEN $4 IS NULL + ELSE _cmp_types(o.oprright, $4) END + AND _cmp_types(o.oprresult, $5) + ); +$$ LANGUAGE SQL; + +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, $1) END + AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL + ELSE _cmp_types(o.oprright, $3) END + AND _cmp_types(o.oprresult, $4) + ); +$$ LANGUAGE SQL; + +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, $1) END + AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL + ELSE _cmp_types(o.oprright, $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; + +-- 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; + +-- 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; + +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_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_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 = $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 = $1 + AND c.relname NOT IN ('__tcache__', '__tresults__', '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 _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 = $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 = $1 + ); +$$ LANGUAGE SQL; + +-- tables_are( schema, tables, description ) +CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT _are( 'tables', _extras('r', $1, $2), _missing('r', $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', $1), _missing('r', $1), $2); +$$ LANGUAGE SQL; + +-- tables_are( schema, tables ) +CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT _are( + 'tables', _extras('r', $1, $2), _missing('r', $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', $1), _missing('r', $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 = COALESCE($1, n.nspname) + AND oc.opcname = $2 + ); +$$ 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( NULL, $1 ), $2) +$$ LANGUAGE SQL; + +-- has_opclass( name ) +CREATE OR REPLACE FUNCTION has_opclass( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _opc_exists( NULL, $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 exist' ); +$$ LANGUAGE SQL; + +-- hasnt_opclass( name, description ) +CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _opc_exists( NULL, $1 ), $2) +$$ LANGUAGE SQL; + +-- hasnt_opclass( name ) +CREATE OR REPLACE FUNCTION hasnt_opclass( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _opc_exists( NULL, $1 ), 'Operator class ' || quote_ident($1) || ' should 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 = array_to_string($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 = array_to_string($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 = array_to_string($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 = array_to_string($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; + +-- 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), $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), $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), $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), $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 = array_to_string($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 = array_to_string($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; + +CREATE OR REPLACE FUNCTION _agg ( NAME, NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT is_agg + FROM tap_funky + WHERE schema = $1 + AND name = $2 + AND args = array_to_string($3, ',') +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _agg ( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT is_agg FROM tap_funky WHERE schema = $1 AND name = $2 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _agg ( NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT is_agg + FROM tap_funky + WHERE name = $1 + AND args = array_to_string($2, ',') + AND is_visible; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _agg ( NAME ) +RETURNS BOOLEAN AS $$ + SELECT is_agg FROM tap_funky WHERE name = $1 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, _agg($1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- is_aggregate( schema, function, args[] ) +CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + _agg($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, _agg($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- is_aggregate( schema, function ) +CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _agg($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, _agg($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- is_aggregate( function, args[] ) +CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + _agg($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, _agg($1), $2 ); +$$ LANGUAGE sql; + +-- is_aggregate( function ) +CREATE OR REPLACE FUNCTION is_aggregate( NAME ) +RETURNS TEXT AS $$ + SELECT ok( _agg($1), 'Function ' || quote_ident($1) || '() should 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 = array_to_string($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 = array_to_string($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; + +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 = array_to_string($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 = array_to_string($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. + EXECUTE 'SELECT aok, descr FROM __tresults__ WHERE numb = ' || tnumb + INTO aok, adescr; + + -- Now delete those results. + EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb; + + -- 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 3 + char_length( diag( adescr ) ) ); + END IF; + + -- Remove failure message from ok(). + IF NOT aok THEN + adiag := substring( + adiag + FROM 14 + char_length(tnumb::text) + + CASE adescr WHEN '' THEN 3 ELSE 3 + char_length( diag( adescr ) ) END + ); + 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 ) +RETURNS TEXT[] AS $$ + SELECT ARRAY( + SELECT DISTINCT quote_ident(n.nspname) || '.' || quote_ident(p.proname) 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 + ORDER BY pname + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION findfuncs( TEXT ) +RETURNS TEXT[] AS $$ + SELECT ARRAY( + SELECT DISTINCT quote_ident(n.nspname) || '.' || quote_ident(p.proname) 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 + ORDER BY pname + ); +$$ 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 TABLE __tresults__; + 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; + verbos boolean := _is_verbose(); -- verbose is a reserved word in 8.5. + num_faild INTEGER := 0; +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; + + BEGIN + FOR i IN 1..array_upper(tests, 1) LOOP + BEGIN + -- What test are we running? + IF verbos THEN RETURN NEXT diag_test_name(tests[i]); END IF; + + -- Run the setup functions. + FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; + + -- Run the actual test function. + FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP + RETURN NEXT tap; + END LOOP; + + -- Run the teardown functions. + FOR tap IN SELECT * FROM _runem(teardown, false) LOOP RETURN NEXT tap; END LOOP; + + -- Remember how many failed and then roll back. + num_faild := num_faild + num_failed(); + RAISE EXCEPTION '__TAP_ROLLBACK__'; + + EXCEPTION WHEN raise_exception THEN + IF SQLERRM <> '__TAP_ROLLBACK__' THEN + -- We didn't raise it, so propagate it. + RAISE EXCEPTION '%', SQLERRM; + END IF; + END; + END LOOP; + + -- Run the shutdown functions. + FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; + + -- Raise an exception to rollback any changes. + RAISE EXCEPTION '__TAP_ROLLBACK__'; + EXCEPTION WHEN raise_exception THEN + IF SQLERRM <> '__TAP_ROLLBACK__' THEN + -- We didn't raise it, so propagate it. + RAISE EXCEPTION '%', SQLERRM; + END IF; + END; + -- Finish up. + FOR tap IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) 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 ) + ); +$$ 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 ) + ); +$$ 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; +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 + 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 + ); +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; +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 + 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 + ); +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; + +-- 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 $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.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 $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 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 display_type(CASE WHEN $3 THEN tn.nspname ELSE NULL END, 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 d.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 display_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 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; + + RETURN is( actual_type, quote_ident($3) || '.' || _quote_ident_like($4, actual_type), $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, _quote_ident_like($3, actual_type), $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, _quote_ident_like($2, actual_type), $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; + + RETURN isnt( actual_type, quote_ident($3) || '.' || _quote_ident_like($4, actual_type), $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, _quote_ident_like($3, actual_type), $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, _quote_ident_like($2, actual_type), $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 + 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 + ), + $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') + 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') + ), + $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_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_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 display_type(castsource, NULL) || ' AS ' || display_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 display_type(castsource, NULL) || ' AS ' || display_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; + diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index ddbf13118cfc..92d46a4ca6bd 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1278,6 +1278,7 @@ RETURNS boolean AS $$ 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 ) From 70b4354ec48e1920a83060a6e15ed5332a537541 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 15 Oct 2012 09:05:10 -0700 Subject: [PATCH 0679/1195] Did not mean to commit those files. :-( --- sql/pgtap-core.sql.orig | 7413 ------------------------------------- sql/pgtap-schema.sql.orig | 7413 ------------------------------------- 2 files changed, 14826 deletions(-) delete mode 100644 sql/pgtap-core.sql.orig delete mode 100644 sql/pgtap-schema.sql.orig diff --git a/sql/pgtap-core.sql.orig b/sql/pgtap-core.sql.orig deleted file mode 100644 index 92d46a4ca6bd..000000000000 --- a/sql/pgtap-core.sql.orig +++ /dev/null @@ -1,7413 +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: --- --- http://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 s.a[1]::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() -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; - CREATE TEMP TABLE __tresults__ ( - numb INTEGER NOT NULL DEFAULT nextval(''__tresults___numb_seq''), - ok BOOLEAN NOT NULL DEFAULT TRUE, - aok BOOLEAN NOT NULL DEFAULT TRUE, - descr TEXT NOT NULL DEFAULT '''', - type TEXT NOT NULL DEFAULT '''', - reason TEXT NOT NULL DEFAULT '''' - ); - CREATE UNIQUE INDEX __tresults___key ON __tresults__(numb); - GRANT ALL ON TABLE __tresults__ TO PUBLIC; - 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 ); - 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; -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 - EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) - VALUES( ' || $1 || ', ' - || $2 || ', ' - || quote_literal(COALESCE($3, '')) || ', ' - || quote_literal($4) || ', ' - || quote_literal($5) || ' )'; - RETURN currval('__tresults___numb_seq'); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION num_failed () -RETURNS INTEGER AS $$ -DECLARE - ret integer; -BEGIN - EXECUTE 'SELECT COUNT(*)::INTEGER FROM __tresults__ WHERE ok = FALSE' INTO ret; - RETURN ret; -END; -$$ LANGUAGE plpgsql strict; - -CREATE OR REPLACE FUNCTION _finish ( INTEGER, INTEGER, INTEGER) -RETURNS SETOF TEXT AS $$ -DECLARE - curr_test ALIAS FOR $1; - exp_tests INTEGER := $2; - num_faild ALIAS FOR $3; - plural CHAR; -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 - 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 () -RETURNS SETOF TEXT AS $$ - SELECT * FROM _finish( - _get('curr_test'), - _get('plan'), - num_failed() - ); -$$ 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, '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, 'SKIP: ' || $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 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; - --- lives_ok( sql, description ) -CREATE OR REPLACE FUNCTION lives_ok ( TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - code TEXT := _query($1); - descr ALIAS FOR $2; -BEGIN - EXECUTE code; - RETURN ok( TRUE, descr ); -EXCEPTION WHEN OTHERS THEN - -- There should have been no exception. - RETURN ok( FALSE, descr ) || E'\n' || diag( - ' died: ' || SQLSTATE || ': ' || SQLERRM - ); -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; - -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 = $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 = $1 - AND pg_catalog.pg_table_is_visible(c.oid) - AND c.relname = $2 - ); -$$ LANGUAGE SQL; - --- has_table( schema, table, description ) -CREATE OR REPLACE FUNCTION has_table ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists( 'r', $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- has_table( table, description ) -CREATE OR REPLACE FUNCTION has_table ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists( 'r', $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', $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- hasnt_table( table, description ) -CREATE OR REPLACE FUNCTION hasnt_table ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _rexists( 'r', $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( 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( 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( 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; - -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 $$ -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( - 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 - ), $4 - ); -END; -$$ LANGUAGE plpgsql; - --- _col_is_null( table, column, desc, null ) -CREATE OR REPLACE FUNCTION _col_is_null ( NAME, NAME, TEXT, bool ) -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( - 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 - ), $3 - ); -END; -$$ LANGUAGE plpgsql; - --- col_not_null( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, $4, true ); -$$ LANGUAGE SQL; - --- col_not_null( table, column, description ) -CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, true ); -$$ LANGUAGE SQL; - --- col_not_null( table, column ) -CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should be NOT NULL', true ); -$$ LANGUAGE SQL; - --- col_is_null( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, $4, false ); -$$ LANGUAGE SQL; - --- col_is_null( schema, table, column ) -CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, false ); -$$ LANGUAGE SQL; - --- col_is_null( table, column ) -CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should allow NULL', false ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION display_type ( OID, INTEGER ) -RETURNS TEXT AS $$ - SELECT COALESCE(substring( - pg_catalog.format_type($1, $2), - '(("(?!")([^"]|"")+"|[^.]+)([(][^)]+[)])?)$' - ), '') -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION display_type ( NAME, OID, INTEGER ) -RETURNS TEXT AS $$ - SELECT CASE WHEN $1 IS NULL THEN '' ELSE quote_ident($1) || '.' END - || display_type($2, $3) -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT display_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 display_type(a.atttypid, a.atttypmod) - FROM pg_catalog.pg_attribute a - JOIN pg_catalog.pg_class c ON a.attrelid = c.oid - WHERE pg_table_is_visible(c.oid) - AND c.relname = $1 - AND a.attname = $2 - AND attnum > 0 - AND NOT a.attisdropped - AND pg_type_is_visible(a.atttypid) -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_col_ns_type ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT display_type(tn.nspname, 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 _quote_ident_like(TEXT, TEXT) -RETURNS TEXT AS $$ -DECLARE - have TEXT; - pcision TEXT; -BEGIN - -- Just return it if rhs isn't quoted. - IF $2 !~ '"' THEN RETURN $1; END IF; - - -- If it's quoted ident without precision, return it quoted. - IF $2 ~ '"$' THEN RETURN quote_ident($1); END IF; - - pcision := substring($1 FROM '[(][^")]+[)]$'); - - -- Just quote it if thre is no precision. - if pcision IS NULL THEN RETURN quote_ident($1); END IF; - - -- Quote the non-precision part and concatenate with precision. - RETURN quote_ident(substring($1 FOR char_length($1) - char_length(pcision))) - || pcision; -END; -$$ LANGUAGE plpgsql; - --- 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; - - want_type := quote_ident($4) || '.' || _quote_ident_like($5, have_type); - 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 := _quote_ident_like($4, have_type); - 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 - IF $1 ~ '^[^'']+[(]' THEN - -- It's a functional default. - 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), - display_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), - display_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 c.relhaspkey = true - AND 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 c.relhaspkey = true - AND 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( 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: http://pgfoundry.org/projects/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: http://pgfoundry.org/projects/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: http://pgfoundry.org/projects/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) - 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 -$$ 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) - 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 -$$ 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( 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( 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 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 || ' constriants'; - 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 || ' constriants'; - 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( 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 - 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; - -CREATE OR REPLACE VIEW tap_funky - AS SELECT p.oid AS oid, - n.nspname AS schema, - p.proname AS name, - 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, - p.proisagg AS is_agg, - 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 _got_func ( NAME, NAME, NAME[] ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT TRUE - FROM tap_funky - WHERE schema = $1 - AND name = $2 - AND args = array_to_string($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 = array_to_string($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 NAME[] AS $$ - SELECT ARRAY( - SELECT a.attname - 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_attribute a ON ct.oid = a.attrelid - JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) - ON a.attnum = x.indkey[s.i] - 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 NAME[] AS $$ - SELECT ARRAY( - SELECT a.attname - 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_attribute a ON ct.oid = a.attrelid - JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) - ON a.attnum = x.indkey[s.i] - 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 ct.relname = $2 - AND ci.relname = $3 - AND n.nspname = $1 - ); -$$ 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 - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME, NAME) -RETURNS TEXT AS $$ - SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) - 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 -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME) -RETURNS TEXT AS $$ - SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) - 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) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')', - quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_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/expression, description ) -CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME, text ) -RETURNS TEXT AS $$ -DECLARE - expr text; -BEGIN - IF $4 NOT LIKE '%(%' THEN - -- Not a functional index. - RETURN has_index( $1, $2, $3, ARRAY[$4], $5 ); - END IF; - - -- Get the functional expression. - expr := _iexpr($1, $2, $3); - - IF expr IS NULL 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) || '(' || expr || ')', - quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || $4 || ')', - $5 - ); -END; -$$ LANGUAGE plpgsql; - --- has_index( schema, table, index, columns/expression ) -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) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')', - quote_ident($2) || ' ON ' || quote_ident($1) || '(' || _ident_array_to_string( $3, ', ' ) || ')', - $4 - ); -END; -$$ LANGUAGE plpgsql; - --- has_index( table, index, columns[], description ) -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 $$ -DECLARE - want_expr text; - descr text; - have_expr text; - idx name; - tab text; -BEGIN - IF $3 NOT LIKE '%(%' THEN - -- Not a functional index. - IF _is_schema( $1 ) THEN - -- Looking for schema.table index. - RETURN ok ( _have_index( $1, $2, $3 ), $4); - END IF; - -- Looking for particular columns. - RETURN has_index( $1, $2, ARRAY[$3], $4 ); - END IF; - - -- Get the functional expression. - IF _is_schema( $1 ) THEN - -- Looking for an index within a schema. - have_expr := _iexpr($1, $2, $3); - want_expr := $4; - descr := 'Index ' || quote_ident($3) || ' should exist'; - idx := $3; - tab := quote_ident($1) || '.' || quote_ident($2); - ELSE - -- Looking for an index without a schema spec. - have_expr := _iexpr($1, $2); - want_expr := $3; - descr := $4; - idx := $2; - tab := quote_ident($1); - END IF; - - IF have_expr IS NULL THEN - RETURN ok( false, descr ) || E'\n' - || diag( 'Index ' || idx || ' ON ' || tab || ' not found'); - END IF; - - RETURN is( - quote_ident(idx) || ' ON ' || tab || '(' || have_expr || ')', - quote_ident(idx) || ' ON ' || tab || '(' || want_expr || ')', - descr - ); -END; -$$ LANGUAGE plpgsql; - --- 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; - --- 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 - ); -$$ 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.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( - 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( group, user[], 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_user ON usename = $2[i] - WHERE usesysid IS NULL - OR NOT usesysid = 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( - ' Users missing from the ' || quote_ident($1) || E' group:\n ' || - array_to_string( missing, E'\n ') - ); -END; -$$ LANGUAGE plpgsql; - --- is_member_of( group, user, 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( group, user[] ) -CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT is_member_of( $1, $2, 'Should have members of group ' || quote_ident($1) ); -$$ LANGUAGE SQL; - --- is_member_of( group, user ) -CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT is_member_of( $1, ARRAY[$2] ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _cmp_types(oid, name) -RETURNS BOOLEAN AS $$ -DECLARE - dtype TEXT := display_type($1, NULL); -BEGIN - RETURN dtype = _quote_ident_like($2, dtype); -END; -$$ LANGUAGE plpgsql; - -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; - -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, $1) END - AND CASE o.oprkind WHEN 'r' THEN $4 IS NULL - ELSE _cmp_types(o.oprright, $4) END - AND _cmp_types(o.oprresult, $5) - ); -$$ LANGUAGE SQL; - -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, $1) END - AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL - ELSE _cmp_types(o.oprright, $3) END - AND _cmp_types(o.oprresult, $4) - ); -$$ LANGUAGE SQL; - -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, $1) END - AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL - ELSE _cmp_types(o.oprright, $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; - --- 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; - --- 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; - -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_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_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 = $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 = $1 - AND c.relname NOT IN ('__tcache__', '__tresults__', '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 _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 = $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 = $1 - ); -$$ LANGUAGE SQL; - --- tables_are( schema, tables, description ) -CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( 'tables', _extras('r', $1, $2), _missing('r', $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', $1), _missing('r', $1), $2); -$$ LANGUAGE SQL; - --- tables_are( schema, tables ) -CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _are( - 'tables', _extras('r', $1, $2), _missing('r', $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', $1), _missing('r', $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 = COALESCE($1, n.nspname) - AND oc.opcname = $2 - ); -$$ 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( NULL, $1 ), $2) -$$ LANGUAGE SQL; - --- has_opclass( name ) -CREATE OR REPLACE FUNCTION has_opclass( NAME ) -RETURNS TEXT AS $$ - SELECT ok( _opc_exists( NULL, $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 exist' ); -$$ LANGUAGE SQL; - --- hasnt_opclass( name, description ) -CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _opc_exists( NULL, $1 ), $2) -$$ LANGUAGE SQL; - --- hasnt_opclass( name ) -CREATE OR REPLACE FUNCTION hasnt_opclass( NAME ) -RETURNS TEXT AS $$ - SELECT ok( NOT _opc_exists( NULL, $1 ), 'Operator class ' || quote_ident($1) || ' should 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 = array_to_string($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 = array_to_string($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 = array_to_string($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 = array_to_string($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; - --- 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), $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), $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), $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), $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 = array_to_string($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 = array_to_string($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; - -CREATE OR REPLACE FUNCTION _agg ( NAME, NAME, NAME[] ) -RETURNS BOOLEAN AS $$ - SELECT is_agg - FROM tap_funky - WHERE schema = $1 - AND name = $2 - AND args = array_to_string($3, ',') -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _agg ( NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT is_agg FROM tap_funky WHERE schema = $1 AND name = $2 -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _agg ( NAME, NAME[] ) -RETURNS BOOLEAN AS $$ - SELECT is_agg - FROM tap_funky - WHERE name = $1 - AND args = array_to_string($2, ',') - AND is_visible; -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _agg ( NAME ) -RETURNS BOOLEAN AS $$ - SELECT is_agg FROM tap_funky WHERE name = $1 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, _agg($1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- is_aggregate( schema, function, args[] ) -CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - _agg($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, _agg($1, $2), $3 ); -$$ LANGUAGE SQL; - --- is_aggregate( schema, function ) -CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _agg($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, _agg($1, $2), $3 ); -$$ LANGUAGE SQL; - --- is_aggregate( function, args[] ) -CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - _agg($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, _agg($1), $2 ); -$$ LANGUAGE sql; - --- is_aggregate( function ) -CREATE OR REPLACE FUNCTION is_aggregate( NAME ) -RETURNS TEXT AS $$ - SELECT ok( _agg($1), 'Function ' || quote_ident($1) || '() should 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 = array_to_string($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 = array_to_string($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; - -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 = array_to_string($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 = array_to_string($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. - EXECUTE 'SELECT aok, descr FROM __tresults__ WHERE numb = ' || tnumb - INTO aok, adescr; - - -- Now delete those results. - EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; - EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb; - - -- 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 3 + char_length( diag( adescr ) ) ); - END IF; - - -- Remove failure message from ok(). - IF NOT aok THEN - adiag := substring( - adiag - FROM 14 + char_length(tnumb::text) - + CASE adescr WHEN '' THEN 3 ELSE 3 + char_length( diag( adescr ) ) END - ); - 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 ) -RETURNS TEXT[] AS $$ - SELECT ARRAY( - SELECT DISTINCT quote_ident(n.nspname) || '.' || quote_ident(p.proname) 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 - ORDER BY pname - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION findfuncs( TEXT ) -RETURNS TEXT[] AS $$ - SELECT ARRAY( - SELECT DISTINCT quote_ident(n.nspname) || '.' || quote_ident(p.proname) 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 - ORDER BY pname - ); -$$ 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 TABLE __tresults__; - 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; - verbos boolean := _is_verbose(); -- verbose is a reserved word in 8.5. - num_faild INTEGER := 0; -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; - - BEGIN - FOR i IN 1..array_upper(tests, 1) LOOP - BEGIN - -- What test are we running? - IF verbos THEN RETURN NEXT diag_test_name(tests[i]); END IF; - - -- Run the setup functions. - FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; - - -- Run the actual test function. - FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP - RETURN NEXT tap; - END LOOP; - - -- Run the teardown functions. - FOR tap IN SELECT * FROM _runem(teardown, false) LOOP RETURN NEXT tap; END LOOP; - - -- Remember how many failed and then roll back. - num_faild := num_faild + num_failed(); - RAISE EXCEPTION '__TAP_ROLLBACK__'; - - EXCEPTION WHEN raise_exception THEN - IF SQLERRM <> '__TAP_ROLLBACK__' THEN - -- We didn't raise it, so propagate it. - RAISE EXCEPTION '%', SQLERRM; - END IF; - END; - END LOOP; - - -- Run the shutdown functions. - FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; - - -- Raise an exception to rollback any changes. - RAISE EXCEPTION '__TAP_ROLLBACK__'; - EXCEPTION WHEN raise_exception THEN - IF SQLERRM <> '__TAP_ROLLBACK__' THEN - -- We didn't raise it, so propagate it. - RAISE EXCEPTION '%', SQLERRM; - END IF; - END; - -- Finish up. - FOR tap IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) 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 ) - ); -$$ 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 ) - ); -$$ 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; -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 - 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 - ); -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; -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 - 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 - ); -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; - --- 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 $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.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 $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 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 display_type(CASE WHEN $3 THEN tn.nspname ELSE NULL END, 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 d.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 display_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 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; - - RETURN is( actual_type, quote_ident($3) || '.' || _quote_ident_like($4, actual_type), $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, _quote_ident_like($3, actual_type), $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, _quote_ident_like($2, actual_type), $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; - - RETURN isnt( actual_type, quote_ident($3) || '.' || _quote_ident_like($4, actual_type), $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, _quote_ident_like($3, actual_type), $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, _quote_ident_like($2, actual_type), $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 - 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 - ), - $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') - 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') - ), - $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_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_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 display_type(castsource, NULL) || ' AS ' || display_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 display_type(castsource, NULL) || ' AS ' || display_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; - diff --git a/sql/pgtap-schema.sql.orig b/sql/pgtap-schema.sql.orig deleted file mode 100644 index 92d46a4ca6bd..000000000000 --- a/sql/pgtap-schema.sql.orig +++ /dev/null @@ -1,7413 +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: --- --- http://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 s.a[1]::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() -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; - CREATE TEMP TABLE __tresults__ ( - numb INTEGER NOT NULL DEFAULT nextval(''__tresults___numb_seq''), - ok BOOLEAN NOT NULL DEFAULT TRUE, - aok BOOLEAN NOT NULL DEFAULT TRUE, - descr TEXT NOT NULL DEFAULT '''', - type TEXT NOT NULL DEFAULT '''', - reason TEXT NOT NULL DEFAULT '''' - ); - CREATE UNIQUE INDEX __tresults___key ON __tresults__(numb); - GRANT ALL ON TABLE __tresults__ TO PUBLIC; - 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 ); - 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; -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 - EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) - VALUES( ' || $1 || ', ' - || $2 || ', ' - || quote_literal(COALESCE($3, '')) || ', ' - || quote_literal($4) || ', ' - || quote_literal($5) || ' )'; - RETURN currval('__tresults___numb_seq'); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION num_failed () -RETURNS INTEGER AS $$ -DECLARE - ret integer; -BEGIN - EXECUTE 'SELECT COUNT(*)::INTEGER FROM __tresults__ WHERE ok = FALSE' INTO ret; - RETURN ret; -END; -$$ LANGUAGE plpgsql strict; - -CREATE OR REPLACE FUNCTION _finish ( INTEGER, INTEGER, INTEGER) -RETURNS SETOF TEXT AS $$ -DECLARE - curr_test ALIAS FOR $1; - exp_tests INTEGER := $2; - num_faild ALIAS FOR $3; - plural CHAR; -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 - 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 () -RETURNS SETOF TEXT AS $$ - SELECT * FROM _finish( - _get('curr_test'), - _get('plan'), - num_failed() - ); -$$ 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, '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, 'SKIP: ' || $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 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; - --- lives_ok( sql, description ) -CREATE OR REPLACE FUNCTION lives_ok ( TEXT, TEXT ) -RETURNS TEXT AS $$ -DECLARE - code TEXT := _query($1); - descr ALIAS FOR $2; -BEGIN - EXECUTE code; - RETURN ok( TRUE, descr ); -EXCEPTION WHEN OTHERS THEN - -- There should have been no exception. - RETURN ok( FALSE, descr ) || E'\n' || diag( - ' died: ' || SQLSTATE || ': ' || SQLERRM - ); -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; - -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 = $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 = $1 - AND pg_catalog.pg_table_is_visible(c.oid) - AND c.relname = $2 - ); -$$ LANGUAGE SQL; - --- has_table( schema, table, description ) -CREATE OR REPLACE FUNCTION has_table ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists( 'r', $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- has_table( table, description ) -CREATE OR REPLACE FUNCTION has_table ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _rexists( 'r', $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', $1, $2 ), $3 ); -$$ LANGUAGE SQL; - --- hasnt_table( table, description ) -CREATE OR REPLACE FUNCTION hasnt_table ( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _rexists( 'r', $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( 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( 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( 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; - -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 $$ -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( - 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 - ), $4 - ); -END; -$$ LANGUAGE plpgsql; - --- _col_is_null( table, column, desc, null ) -CREATE OR REPLACE FUNCTION _col_is_null ( NAME, NAME, TEXT, bool ) -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( - 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 - ), $3 - ); -END; -$$ LANGUAGE plpgsql; - --- col_not_null( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, $4, true ); -$$ LANGUAGE SQL; - --- col_not_null( table, column, description ) -CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, true ); -$$ LANGUAGE SQL; - --- col_not_null( table, column ) -CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should be NOT NULL', true ); -$$ LANGUAGE SQL; - --- col_is_null( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, $4, false ); -$$ LANGUAGE SQL; - --- col_is_null( schema, table, column ) -CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, false ); -$$ LANGUAGE SQL; - --- col_is_null( table, column ) -CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should allow NULL', false ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION display_type ( OID, INTEGER ) -RETURNS TEXT AS $$ - SELECT COALESCE(substring( - pg_catalog.format_type($1, $2), - '(("(?!")([^"]|"")+"|[^.]+)([(][^)]+[)])?)$' - ), '') -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION display_type ( NAME, OID, INTEGER ) -RETURNS TEXT AS $$ - SELECT CASE WHEN $1 IS NULL THEN '' ELSE quote_ident($1) || '.' END - || display_type($2, $3) -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT display_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 display_type(a.atttypid, a.atttypmod) - FROM pg_catalog.pg_attribute a - JOIN pg_catalog.pg_class c ON a.attrelid = c.oid - WHERE pg_table_is_visible(c.oid) - AND c.relname = $1 - AND a.attname = $2 - AND attnum > 0 - AND NOT a.attisdropped - AND pg_type_is_visible(a.atttypid) -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _get_col_ns_type ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT display_type(tn.nspname, 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 _quote_ident_like(TEXT, TEXT) -RETURNS TEXT AS $$ -DECLARE - have TEXT; - pcision TEXT; -BEGIN - -- Just return it if rhs isn't quoted. - IF $2 !~ '"' THEN RETURN $1; END IF; - - -- If it's quoted ident without precision, return it quoted. - IF $2 ~ '"$' THEN RETURN quote_ident($1); END IF; - - pcision := substring($1 FROM '[(][^")]+[)]$'); - - -- Just quote it if thre is no precision. - if pcision IS NULL THEN RETURN quote_ident($1); END IF; - - -- Quote the non-precision part and concatenate with precision. - RETURN quote_ident(substring($1 FOR char_length($1) - char_length(pcision))) - || pcision; -END; -$$ LANGUAGE plpgsql; - --- 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; - - want_type := quote_ident($4) || '.' || _quote_ident_like($5, have_type); - 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 := _quote_ident_like($4, have_type); - 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 - IF $1 ~ '^[^'']+[(]' THEN - -- It's a functional default. - 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), - display_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), - display_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 c.relhaspkey = true - AND 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 c.relhaspkey = true - AND 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( 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: http://pgfoundry.org/projects/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: http://pgfoundry.org/projects/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: http://pgfoundry.org/projects/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) - 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 -$$ 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) - 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 -$$ 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( 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( 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 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 || ' constriants'; - 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 || ' constriants'; - 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( 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 - 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; - -CREATE OR REPLACE VIEW tap_funky - AS SELECT p.oid AS oid, - n.nspname AS schema, - p.proname AS name, - 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, - p.proisagg AS is_agg, - 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 _got_func ( NAME, NAME, NAME[] ) -RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT TRUE - FROM tap_funky - WHERE schema = $1 - AND name = $2 - AND args = array_to_string($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 = array_to_string($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 NAME[] AS $$ - SELECT ARRAY( - SELECT a.attname - 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_attribute a ON ct.oid = a.attrelid - JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) - ON a.attnum = x.indkey[s.i] - 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 NAME[] AS $$ - SELECT ARRAY( - SELECT a.attname - 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_attribute a ON ct.oid = a.attrelid - JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) - ON a.attnum = x.indkey[s.i] - 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 ct.relname = $2 - AND ci.relname = $3 - AND n.nspname = $1 - ); -$$ 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 - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME, NAME) -RETURNS TEXT AS $$ - SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) - 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 -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME) -RETURNS TEXT AS $$ - SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) - 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) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')', - quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_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/expression, description ) -CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME, text ) -RETURNS TEXT AS $$ -DECLARE - expr text; -BEGIN - IF $4 NOT LIKE '%(%' THEN - -- Not a functional index. - RETURN has_index( $1, $2, $3, ARRAY[$4], $5 ); - END IF; - - -- Get the functional expression. - expr := _iexpr($1, $2, $3); - - IF expr IS NULL 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) || '(' || expr || ')', - quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || $4 || ')', - $5 - ); -END; -$$ LANGUAGE plpgsql; - --- has_index( schema, table, index, columns/expression ) -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) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')', - quote_ident($2) || ' ON ' || quote_ident($1) || '(' || _ident_array_to_string( $3, ', ' ) || ')', - $4 - ); -END; -$$ LANGUAGE plpgsql; - --- has_index( table, index, columns[], description ) -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 $$ -DECLARE - want_expr text; - descr text; - have_expr text; - idx name; - tab text; -BEGIN - IF $3 NOT LIKE '%(%' THEN - -- Not a functional index. - IF _is_schema( $1 ) THEN - -- Looking for schema.table index. - RETURN ok ( _have_index( $1, $2, $3 ), $4); - END IF; - -- Looking for particular columns. - RETURN has_index( $1, $2, ARRAY[$3], $4 ); - END IF; - - -- Get the functional expression. - IF _is_schema( $1 ) THEN - -- Looking for an index within a schema. - have_expr := _iexpr($1, $2, $3); - want_expr := $4; - descr := 'Index ' || quote_ident($3) || ' should exist'; - idx := $3; - tab := quote_ident($1) || '.' || quote_ident($2); - ELSE - -- Looking for an index without a schema spec. - have_expr := _iexpr($1, $2); - want_expr := $3; - descr := $4; - idx := $2; - tab := quote_ident($1); - END IF; - - IF have_expr IS NULL THEN - RETURN ok( false, descr ) || E'\n' - || diag( 'Index ' || idx || ' ON ' || tab || ' not found'); - END IF; - - RETURN is( - quote_ident(idx) || ' ON ' || tab || '(' || have_expr || ')', - quote_ident(idx) || ' ON ' || tab || '(' || want_expr || ')', - descr - ); -END; -$$ LANGUAGE plpgsql; - --- 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; - --- 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 - ); -$$ 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.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( - 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( group, user[], 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_user ON usename = $2[i] - WHERE usesysid IS NULL - OR NOT usesysid = 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( - ' Users missing from the ' || quote_ident($1) || E' group:\n ' || - array_to_string( missing, E'\n ') - ); -END; -$$ LANGUAGE plpgsql; - --- is_member_of( group, user, 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( group, user[] ) -CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT is_member_of( $1, $2, 'Should have members of group ' || quote_ident($1) ); -$$ LANGUAGE SQL; - --- is_member_of( group, user ) -CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT is_member_of( $1, ARRAY[$2] ); -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _cmp_types(oid, name) -RETURNS BOOLEAN AS $$ -DECLARE - dtype TEXT := display_type($1, NULL); -BEGIN - RETURN dtype = _quote_ident_like($2, dtype); -END; -$$ LANGUAGE plpgsql; - -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; - -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, $1) END - AND CASE o.oprkind WHEN 'r' THEN $4 IS NULL - ELSE _cmp_types(o.oprright, $4) END - AND _cmp_types(o.oprresult, $5) - ); -$$ LANGUAGE SQL; - -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, $1) END - AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL - ELSE _cmp_types(o.oprright, $3) END - AND _cmp_types(o.oprresult, $4) - ); -$$ LANGUAGE SQL; - -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, $1) END - AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL - ELSE _cmp_types(o.oprright, $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; - --- 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; - --- 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; - -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_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_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 = $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 = $1 - AND c.relname NOT IN ('__tcache__', '__tresults__', '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 _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 = $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 = $1 - ); -$$ LANGUAGE SQL; - --- tables_are( schema, tables, description ) -CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ - SELECT _are( 'tables', _extras('r', $1, $2), _missing('r', $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', $1), _missing('r', $1), $2); -$$ LANGUAGE SQL; - --- tables_are( schema, tables ) -CREATE OR REPLACE FUNCTION tables_are ( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT _are( - 'tables', _extras('r', $1, $2), _missing('r', $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', $1), _missing('r', $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 = COALESCE($1, n.nspname) - AND oc.opcname = $2 - ); -$$ 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( NULL, $1 ), $2) -$$ LANGUAGE SQL; - --- has_opclass( name ) -CREATE OR REPLACE FUNCTION has_opclass( NAME ) -RETURNS TEXT AS $$ - SELECT ok( _opc_exists( NULL, $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 exist' ); -$$ LANGUAGE SQL; - --- hasnt_opclass( name, description ) -CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( NOT _opc_exists( NULL, $1 ), $2) -$$ LANGUAGE SQL; - --- hasnt_opclass( name ) -CREATE OR REPLACE FUNCTION hasnt_opclass( NAME ) -RETURNS TEXT AS $$ - SELECT ok( NOT _opc_exists( NULL, $1 ), 'Operator class ' || quote_ident($1) || ' should 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 = array_to_string($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 = array_to_string($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 = array_to_string($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 = array_to_string($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; - --- 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), $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), $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), $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), $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 = array_to_string($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 = array_to_string($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; - -CREATE OR REPLACE FUNCTION _agg ( NAME, NAME, NAME[] ) -RETURNS BOOLEAN AS $$ - SELECT is_agg - FROM tap_funky - WHERE schema = $1 - AND name = $2 - AND args = array_to_string($3, ',') -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _agg ( NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT is_agg FROM tap_funky WHERE schema = $1 AND name = $2 -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _agg ( NAME, NAME[] ) -RETURNS BOOLEAN AS $$ - SELECT is_agg - FROM tap_funky - WHERE name = $1 - AND args = array_to_string($2, ',') - AND is_visible; -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION _agg ( NAME ) -RETURNS BOOLEAN AS $$ - SELECT is_agg FROM tap_funky WHERE name = $1 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, _agg($1, $2, $3), $4 ); -$$ LANGUAGE SQL; - --- is_aggregate( schema, function, args[] ) -CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - _agg($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, _agg($1, $2), $3 ); -$$ LANGUAGE SQL; - --- is_aggregate( schema, function ) -CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT ok( - _agg($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, _agg($1, $2), $3 ); -$$ LANGUAGE SQL; - --- is_aggregate( function, args[] ) -CREATE OR REPLACE FUNCTION is_aggregate( NAME, NAME[] ) -RETURNS TEXT AS $$ - SELECT ok( - _agg($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, _agg($1), $2 ); -$$ LANGUAGE sql; - --- is_aggregate( function ) -CREATE OR REPLACE FUNCTION is_aggregate( NAME ) -RETURNS TEXT AS $$ - SELECT ok( _agg($1), 'Function ' || quote_ident($1) || '() should 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 = array_to_string($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 = array_to_string($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; - -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 = array_to_string($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 = array_to_string($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. - EXECUTE 'SELECT aok, descr FROM __tresults__ WHERE numb = ' || tnumb - INTO aok, adescr; - - -- Now delete those results. - EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; - EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb; - - -- 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 3 + char_length( diag( adescr ) ) ); - END IF; - - -- Remove failure message from ok(). - IF NOT aok THEN - adiag := substring( - adiag - FROM 14 + char_length(tnumb::text) - + CASE adescr WHEN '' THEN 3 ELSE 3 + char_length( diag( adescr ) ) END - ); - 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 ) -RETURNS TEXT[] AS $$ - SELECT ARRAY( - SELECT DISTINCT quote_ident(n.nspname) || '.' || quote_ident(p.proname) 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 - ORDER BY pname - ); -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION findfuncs( TEXT ) -RETURNS TEXT[] AS $$ - SELECT ARRAY( - SELECT DISTINCT quote_ident(n.nspname) || '.' || quote_ident(p.proname) 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 - ORDER BY pname - ); -$$ 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 TABLE __tresults__; - 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; - verbos boolean := _is_verbose(); -- verbose is a reserved word in 8.5. - num_faild INTEGER := 0; -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; - - BEGIN - FOR i IN 1..array_upper(tests, 1) LOOP - BEGIN - -- What test are we running? - IF verbos THEN RETURN NEXT diag_test_name(tests[i]); END IF; - - -- Run the setup functions. - FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; - - -- Run the actual test function. - FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP - RETURN NEXT tap; - END LOOP; - - -- Run the teardown functions. - FOR tap IN SELECT * FROM _runem(teardown, false) LOOP RETURN NEXT tap; END LOOP; - - -- Remember how many failed and then roll back. - num_faild := num_faild + num_failed(); - RAISE EXCEPTION '__TAP_ROLLBACK__'; - - EXCEPTION WHEN raise_exception THEN - IF SQLERRM <> '__TAP_ROLLBACK__' THEN - -- We didn't raise it, so propagate it. - RAISE EXCEPTION '%', SQLERRM; - END IF; - END; - END LOOP; - - -- Run the shutdown functions. - FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; - - -- Raise an exception to rollback any changes. - RAISE EXCEPTION '__TAP_ROLLBACK__'; - EXCEPTION WHEN raise_exception THEN - IF SQLERRM <> '__TAP_ROLLBACK__' THEN - -- We didn't raise it, so propagate it. - RAISE EXCEPTION '%', SQLERRM; - END IF; - END; - -- Finish up. - FOR tap IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) 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 ) - ); -$$ 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 ) - ); -$$ 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; -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 - 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 - ); -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; -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 - 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 - ); -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; - --- 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 $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.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 $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 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 display_type(CASE WHEN $3 THEN tn.nspname ELSE NULL END, 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 d.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 display_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 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; - - RETURN is( actual_type, quote_ident($3) || '.' || _quote_ident_like($4, actual_type), $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, _quote_ident_like($3, actual_type), $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, _quote_ident_like($2, actual_type), $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; - - RETURN isnt( actual_type, quote_ident($3) || '.' || _quote_ident_like($4, actual_type), $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, _quote_ident_like($3, actual_type), $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, _quote_ident_like($2, actual_type), $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 - 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 - ), - $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') - 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') - ), - $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_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_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 display_type(castsource, NULL) || ' AS ' || display_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 display_type(castsource, NULL) || ' AS ' || display_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; - From f669247606cf2bcd9efaa1197646df298525dba7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 10:51:34 -0800 Subject: [PATCH 0680/1195] Add has_foreign_table() and hasnt_foreign_table(). --- .gitignore | 2 +- Changes | 1 + doc/pgtap.mmd | 50 +++++++ sql/pgtap--0.91.0--0.92.0.sql | 36 +++++ sql/pgtap.sql.in | 36 +++++ test/expected/hastap.out | 37 ++++- test/sql/hastap.sql | 262 +++++++++++++++++++++++++++++++++- 7 files changed, 421 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index a9aad8322f45..1b561faa67fc 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,6 @@ pgtap.so regression.* *.html bbin -/sql/pgtap--1.* +/sql/pgtap--?.??.?.sql /sql/pgtap-core--* /sql/pgtap-schema--* diff --git a/Changes b/Changes index 779dc4b5d933..be31f4bad8e0 100644 --- a/Changes +++ b/Changes @@ -7,6 +7,7 @@ Revision history for pgTAP * Added note about the lack of typemods to the documentation for `has_cast()`. * Added check to ensure a table is visible in tests for column defaults. Thanks to Henk Enting for the report. +* Added `has_foreign_table()` and `hasnt_foreign_table()`. 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index f1150338985d..44ba6d62dc70 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2487,6 +2487,9 @@ search path. Example: If you omit the test description, it will be set to "Table `:table` should exist". +Note that this function will not recognize foreign tables; use +`has_foreign_table()` to test for the presence of foreign tables. + ### `hasnt_table()` ### SELECT hasnt_table( :schema, :table, :description ); @@ -2601,6 +2604,53 @@ exist". This function is the inverse of `has_sequence()`. The test passes if the specified sequence does *not* exist. +### `has_foreign_table()` ### + + SELECT has_foreign_table( :schema, :table, :description ); + SELECT has_foreign_table( :table, :description ); + SELECT has_foreign_table( :table ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the foreign table. + +`:table` +: Name of a foreign table. + +`:description` +: A short description of the test. + +This function tests whether or not a foreign table exists in the database. The +first argument is a schema name, the second is a foreign table name, and the +third is the test description. If you omit the schema, the foreign table must +be visible in the search path. Example: + + SELECT has_foreign_table('myschema', 'some_foriegn_table'); + +If you omit the test description, it will be set to "Foreign table `:table` +should exist". + +### `hasnt_foreign_table()` ### + + SELECT hasnt_foreign_table( :schema, :table, :description ); + SELECT hasnt_foreign_table( :table, :description ); + SELECT hasnt_foreign_table( :table ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the foreign table. + +`:table` +: Name of a foreign table. + +`:description` +: A short description of the test. + +This function is the inverse of `has_foreign_table()`. The test passes if the +specified forieign table does *not* exist. + ### `has_type()` ### SELECT has_type( schema, type, description ); diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index 4d1937f50406..6bfbc3351751 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -11,3 +11,39 @@ RETURNS BOOLEAN AS $$ AND a.attname = $2 ); $$ 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( 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( 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 92d46a4ca6bd..e4ec5178874a 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -940,6 +940,42 @@ 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( 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( 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; + CREATE OR REPLACE FUNCTION _cexists ( NAME, NAME, NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( diff --git a/test/expected/hastap.out b/test/expected/hastap.out index 2a6c046f2614..066d1757ec27 100644 --- a/test/expected/hastap.out +++ b/test/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..678 +1..713 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -678,3 +678,38 @@ ok 675 - domain_type_isnt(nondomain, type, desc) should have the proper diagnost ok 676 - domain_type_isnt(type, type, desc) should fail ok 677 - domain_type_isnt(type, type, desc) should have the proper description ok 678 - domain_type_isnt(type, type, desc) should have the proper diagnostics +ok 679 - has_foreign_table(non-existent table) should fail +ok 680 - has_foreign_table(non-existent table) should have the proper description +ok 681 - has_foreign_table(non-existent table) should have the proper diagnostics +ok 682 - has_foreign_table(non-existent schema, tab) should fail +ok 683 - has_foreign_table(non-existent schema, tab) should have the proper description +ok 684 - has_foreign_table(non-existent schema, tab) should have the proper diagnostics +ok 685 - has_foreign_table(sch, non-existent table, desc) should fail +ok 686 - has_foreign_table(sch, non-existent table, desc) should have the proper description +ok 687 - has_foreign_table(sch, non-existent table, desc) should have the proper diagnostics +ok 688 - has_foreign_table(tab, desc) should pass +ok 689 - has_foreign_table(tab, desc) should have the proper description +ok 690 - has_foreign_table(tab, desc) should have the proper diagnostics +ok 691 - has_foreign_table(sch, tab, desc) should pass +ok 692 - has_foreign_table(sch, tab, desc) should have the proper description +ok 693 - has_foreign_table(sch, tab, desc) should have the proper diagnostics +ok 694 - has_foreign_table(sch, view, desc) should fail +ok 695 - has_foreign_table(sch, view, desc) should have the proper description +ok 696 - has_foreign_table(sch, view, desc) should have the proper diagnostics +ok 697 - has_foreign_table(type, desc) should fail +ok 698 - has_foreign_table(type, desc) should have the proper description +ok 699 - has_foreign_table(type, desc) should have the proper diagnostics +ok 700 - hasnt_foreign_table(non-existent table) should pass +ok 701 - hasnt_foreign_table(non-existent table) should have the proper description +ok 702 - hasnt_foreign_table(non-existent table) should have the proper diagnostics +ok 703 - hasnt_foreign_table(non-existent schema, tab) should pass +ok 704 - hasnt_foreign_table(non-existent schema, tab) should have the proper description +ok 705 - hasnt_foreign_table(sch, non-existent tab, desc) should pass +ok 706 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper description +ok 707 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper diagnostics +ok 708 - hasnt_foreign_table(tab, desc) should fail +ok 709 - hasnt_foreign_table(tab, desc) should have the proper description +ok 710 - hasnt_foreign_table(tab, desc) should have the proper diagnostics +ok 711 - hasnt_foreign_table(sch, tab, desc) should fail +ok 712 - hasnt_foreign_table(sch, tab, desc) should have the proper description +ok 713 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index a1db46434459..af8fbd760b7a 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(678); +SELECT plan(713); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -1892,6 +1892,266 @@ SELECT * FROM check_test( ' Domain integer does not exist' ); +/****************************************************************************/ +-- Test has_foreign_table() and hasnt_foreign_table(). +CREATE FUNCTION test_fdw() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() >= 92100 THEN + CREATE FOREIGN DATA WRAPPER dummy; + CREATE SERVER foo FOREIGN DATA WRAPPER dummy; + CREATE FOREIGN TABLE public.my_fdw (id int) SERVER foo; + + FOR tap IN SELECT * FROM check_test( + has_foreign_table( '__SDFSDFD__' ), + false, + 'has_foreign_table(non-existent table)', + 'Foreign table "__SDFSDFD__" should exist', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_foreign_table( '__SDFSDFD__', 'lol' ), + false, + 'has_foreign_table(non-existent schema, tab)', + 'lol', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_foreign_table( 'foo', '__SDFSDFD__', 'desc' ), + false, + 'has_foreign_table(sch, non-existent table, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_foreign_table( 'my_fdw', 'lol' ), + true, + 'has_foreign_table(tab, desc)', + 'lol', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_foreign_table( 'public', 'my_fdw', 'desc' ), + true, + 'has_foreign_table(sch, tab, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + -- It should ignore views and types. + FOR tap IN SELECT * FROM check_test( + has_foreign_table( 'pg_catalog', 'pg_foreign_tables', 'desc' ), + false, + 'has_foreign_table(sch, view, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_foreign_table( 'sometype', 'desc' ), + false, + 'has_foreign_table(type, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_foreign_table( '__SDFSDFD__' ), + true, + 'hasnt_foreign_table(non-existent table)', + 'Foreign table "__SDFSDFD__" should not exist', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_foreign_table( '__SDFSDFD__', 'lol' ), + true, + 'hasnt_foreign_table(non-existent schema, tab)', + 'lol' + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_foreign_table( 'foo', '__SDFSDFD__', 'desc' ), + true, + 'hasnt_foreign_table(sch, non-existent tab, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_foreign_table( 'my_fdw', 'lol' ), + false, + 'hasnt_foreign_table(tab, desc)', + 'lol', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_foreign_table( 'public', 'my_fdw', 'desc' ), + false, + 'hasnt_foreign_table(sch, tab, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + ELSE + -- Fake it with has_table(). + FOR tap IN SELECT * FROM check_test( + has_table( '__SDFSDFD__' ), + false, + 'has_foreign_table(non-existent table)', + 'Table "__SDFSDFD__" should exist', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_table( '__SDFSDFD__', 'lol' ), + false, + 'has_foreign_table(non-existent schema, tab)', + 'lol', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_table( 'foo', '__SDFSDFD__', 'desc' ), + false, + 'has_foreign_table(sch, non-existent table, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_table( 'pg_type', 'lol' ), + true, + 'has_foreign_table(tab, desc)', + 'lol', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_table( 'pg_catalog', 'pg_type', 'desc' ), + true, + 'has_foreign_table(sch, tab, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + -- It should ignore views and types. + FOR tap IN SELECT * FROM check_test( + has_table( 'pg_catalog', 'pg_foreign_tables', 'desc' ), + false, + 'has_foreign_table(sch, view, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_table( 'sometype', 'desc' ), + false, + 'has_foreign_table(type, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_table( '__SDFSDFD__' ), + true, + 'hasnt_foreign_table(non-existent table)', + 'Table "__SDFSDFD__" should not exist', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_table( '__SDFSDFD__', 'lol' ), + true, + 'hasnt_foreign_table(non-existent schema, tab)', + 'lol' + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_table( 'foo', '__SDFSDFD__', 'desc' ), + true, + 'hasnt_foreign_table(sch, non-existent tab, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_table( 'pg_type', 'lol' ), + false, + 'hasnt_foreign_table(tab, desc)', + 'lol', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_table( 'pg_catalog', 'pg_type', 'desc' ), + false, + 'hasnt_foreign_table(sch, tab, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + END IF; + RETURN; +END; +$$ LANGUAGE PLPGSQL; + +SELECT * FROM test_fdw(); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 6ae5561fd9ae4feb89e56ac67188436ac19c8e84 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 11:06:07 -0800 Subject: [PATCH 0681/1195] Add has_composite() and hasnt_composite(). --- Changes | 1 + doc/pgtap.mmd | 64 +- sql/pgtap--0.91.0--0.92.0.sql | 37 ++ sql/pgtap.sql.in | 36 + test/expected/hastap.out | 1176 +++++++++++++++++---------------- test/sql/hastap.sql | 104 ++- 6 files changed, 841 insertions(+), 577 deletions(-) diff --git a/Changes b/Changes index be31f4bad8e0..202a4f9505b5 100644 --- a/Changes +++ b/Changes @@ -8,6 +8,7 @@ Revision history for pgTAP * Added check to ensure a table is visible in tests for column defaults. Thanks to Henk Enting for the report. * Added `has_foreign_table()` and `hasnt_foreign_table()`. +* Added `has_composite()` and `hasnt_composite()`. 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 44ba6d62dc70..b0b0d53a53bf 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2661,7 +2661,7 @@ specified forieign table does *not* exist. **Parameters** `:schema` -: Name of a schema in which to find the sequence. +: Name of a schema in which to find the data type. `:type` : Name of a data type. @@ -2698,7 +2698,7 @@ are a part of it, use the column testing functions to verify them, like so: **Parameters** `:schema` -: Name of a schema in which to find the sequence. +: Name of a schema in which to find the data type. `:type` : Name of a data type. @@ -2709,6 +2709,58 @@ are a part of it, use the column testing functions to verify them, like so: This function is the inverse of `has_type()`. The test passes if the specified type does *not* exist. +### `has_composite()` ### + + SELECT has_composite( schema, type, description ); + SELECT has_composite( schema, type ); + SELECT has_composite( type, description ); + SELECT has_composite( type ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the composite type. + +`:composite type` +: Name of a composite type. + +`:description` +: A short description of the test. + +This function tests whether or not a composite type exists in the database. +The first argument is a schema name, the second is the name of a composite +type, and the third is the test description. If you omit the schema, the +composite type must be visible in the search path. If you omit the test +description, it will be set to "Composite type `:composite type` should +exist". Example: + + SELECT has_composite( 'myschema', 'somecomposite' ); + +If you're passing a schema and composite type rather than composite type and +description, be sure to cast the arguments to `name` values so that your +composite type name doesn't get treated as a description. + +### `hasnt_composite()` ### + + SELECT hasnt_composite( schema, type, description ); + SELECT hasnt_composite( schema, type ); + SELECT hasnt_composite( type, description ); + SELECT hasnt_composite( type ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the composite type. + +`:composite type` +: Name of a composite type. + +`:description` +: A short description of the test. + +This function is the inverse of `has_composite()`. The test passes if the +specified composite type does *not* exist. + ### `has_domain()` ### SELECT has_domain( schema, domain, description ); @@ -2719,7 +2771,7 @@ type does *not* exist. **Parameters** `:schema` -: Name of a schema in which to find the sequence. +: Name of a schema in which to find the domain. `:domain` : Name of a domain. @@ -2749,7 +2801,7 @@ get treated as a description. **Parameters** `:schema` -: Name of a schema in which to find the sequence. +: Name of a schema in which to find the domain. `:domain` : Name of a domain. @@ -2770,7 +2822,7 @@ domain does *not* exist. **Parameters** `:schema` -: Name of a schema in which to find the sequence. +: Name of a schema in which to find the enum. `:enum` : Name of a enum. @@ -2800,7 +2852,7 @@ treated as a description. **Parameters** `:schema` -: Name of a schema in which to find the sequence. +: Name of a schema in which to find the enum. `:enum` : Name of a enum. diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index 6bfbc3351751..498401d1ca78 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -47,3 +47,40 @@ 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( 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( 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index e4ec5178874a..47e77acca2ea 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -976,6 +976,42 @@ 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( 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( 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( diff --git a/test/expected/hastap.out b/test/expected/hastap.out index 066d1757ec27..a106e2957e0e 100644 --- a/test/expected/hastap.out +++ b/test/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..713 +1..749 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -144,572 +144,608 @@ ok 141 - hasnt_sequence(sequence, desc) should have the proper diagnostics ok 142 - hasnt_sequence(sch, sequence, desc) should fail ok 143 - hasnt_sequence(sch, sequence, desc) should have the proper description ok 144 - hasnt_sequence(sch, sequence, desc) should have the proper diagnostics -ok 145 - has_type(type) should pass -ok 146 - has_type(type) should have the proper description -ok 147 - has_type(type) should have the proper diagnostics -ok 148 - has_type(type, desc) should pass -ok 149 - has_type(type, desc) should have the proper description -ok 150 - has_type(type, desc) should have the proper diagnostics -ok 151 - has_type(scheam, type) should pass -ok 152 - has_type(scheam, type) should have the proper description -ok 153 - has_type(scheam, type) should have the proper diagnostics -ok 154 - has_type(schema, type, desc) should pass -ok 155 - has_type(schema, type, desc) should have the proper description -ok 156 - has_type(schema, type, desc) should have the proper diagnostics -ok 157 - has_type(myType) should pass -ok 158 - has_type(myType) should have the proper description -ok 159 - has_type(myType) should have the proper diagnostics -ok 160 - has_type(myType, desc) should pass -ok 161 - has_type(myType, desc) should have the proper description -ok 162 - has_type(myType, desc) should have the proper diagnostics -ok 163 - has_type(scheam, myType) should pass -ok 164 - has_type(scheam, myType) should have the proper description -ok 165 - has_type(scheam, myType) should have the proper diagnostics -ok 166 - has_type(schema, myType, desc) should pass -ok 167 - has_type(schema, myType, desc) should have the proper description -ok 168 - has_type(schema, myType, desc) should have the proper diagnostics -ok 169 - has_type(type) should fail -ok 170 - has_type(type) should have the proper description -ok 171 - has_type(type) should have the proper diagnostics -ok 172 - has_type(type, desc) should fail -ok 173 - has_type(type, desc) should have the proper description -ok 174 - has_type(type, desc) should have the proper diagnostics -ok 175 - has_type(scheam, type) should fail -ok 176 - has_type(scheam, type) should have the proper description -ok 177 - has_type(scheam, type) should have the proper diagnostics -ok 178 - has_type(schema, type, desc) should fail -ok 179 - has_type(schema, type, desc) should have the proper description -ok 180 - has_type(schema, type, desc) should have the proper diagnostics -ok 181 - has_type(domain) should pass -ok 182 - has_type(domain) should have the proper description -ok 183 - has_type(domain) should have the proper diagnostics -ok 184 - has_type(myDomain) should pass -ok 185 - has_type(myDomain) should have the proper description -ok 186 - has_type(myDomain) should have the proper diagnostics -ok 187 - hasnt_type(type) should pass -ok 188 - hasnt_type(type) should have the proper description -ok 189 - hasnt_type(type) should have the proper diagnostics -ok 190 - hasnt_type(type, desc) should pass -ok 191 - hasnt_type(type, desc) should have the proper description -ok 192 - hasnt_type(type, desc) should have the proper diagnostics -ok 193 - hasnt_type(scheam, type) should pass -ok 194 - hasnt_type(scheam, type) should have the proper description -ok 195 - hasnt_type(scheam, type) should have the proper diagnostics -ok 196 - hasnt_type(schema, type, desc) should pass -ok 197 - hasnt_type(schema, type, desc) should have the proper description -ok 198 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 199 - hasnt_type(type) should fail -ok 200 - hasnt_type(type) should have the proper description -ok 201 - hasnt_type(type) should have the proper diagnostics -ok 202 - hasnt_type(type, desc) should fail -ok 203 - hasnt_type(type, desc) should have the proper description -ok 204 - hasnt_type(type, desc) should have the proper diagnostics -ok 205 - hasnt_type(scheam, type) should fail -ok 206 - hasnt_type(scheam, type) should have the proper description -ok 207 - hasnt_type(scheam, type) should have the proper diagnostics -ok 208 - hasnt_type(schema, type, desc) should fail -ok 209 - hasnt_type(schema, type, desc) should have the proper description -ok 210 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 211 - has_domain(domain) should pass -ok 212 - has_domain(domain) should have the proper description -ok 213 - has_domain(domain) should have the proper diagnostics -ok 214 - has_domain(domain, desc) should pass -ok 215 - has_domain(domain, desc) should have the proper description -ok 216 - has_domain(domain, desc) should have the proper diagnostics -ok 217 - has_domain(scheam, domain) should pass -ok 218 - has_domain(scheam, domain) should have the proper description -ok 219 - has_domain(scheam, domain) should have the proper diagnostics -ok 220 - has_domain(schema, domain, desc) should pass -ok 221 - has_domain(schema, domain, desc) should have the proper description -ok 222 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 223 - has_domain(myDomain) should pass -ok 224 - has_domain(myDomain) should have the proper description -ok 225 - has_domain(myDomain) should have the proper diagnostics -ok 226 - has_domain(myDomain, desc) should pass -ok 227 - has_domain(myDomain, desc) should have the proper description -ok 228 - has_domain(myDomain, desc) should have the proper diagnostics -ok 229 - has_domain(scheam, myDomain) should pass -ok 230 - has_domain(scheam, myDomain) should have the proper description -ok 231 - has_domain(scheam, myDomain) should have the proper diagnostics -ok 232 - has_domain(schema, myDomain, desc) should pass -ok 233 - has_domain(schema, myDomain, desc) should have the proper description -ok 234 - has_domain(schema, myDomain, desc) should have the proper diagnostics -ok 235 - has_domain(domain) should fail -ok 236 - has_domain(domain) should have the proper description -ok 237 - has_domain(domain) should have the proper diagnostics -ok 238 - has_domain(domain, desc) should fail -ok 239 - has_domain(domain, desc) should have the proper description -ok 240 - has_domain(domain, desc) should have the proper diagnostics -ok 241 - has_domain(scheam, domain) should fail -ok 242 - has_domain(scheam, domain) should have the proper description -ok 243 - has_domain(scheam, domain) should have the proper diagnostics -ok 244 - has_domain(schema, domain, desc) should fail -ok 245 - has_domain(schema, domain, desc) should have the proper description -ok 246 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 247 - hasnt_domain(domain) should pass -ok 248 - hasnt_domain(domain) should have the proper description -ok 249 - hasnt_domain(domain) should have the proper diagnostics -ok 250 - hasnt_domain(domain, desc) should pass -ok 251 - hasnt_domain(domain, desc) should have the proper description -ok 252 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 253 - hasnt_domain(scheam, domain) should pass -ok 254 - hasnt_domain(scheam, domain) should have the proper description -ok 255 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 256 - hasnt_domain(schema, domain, desc) should pass -ok 257 - hasnt_domain(schema, domain, desc) should have the proper description -ok 258 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 259 - hasnt_domain(domain) should fail -ok 260 - hasnt_domain(domain) should have the proper description -ok 261 - hasnt_domain(domain) should have the proper diagnostics -ok 262 - hasnt_domain(domain, desc) should fail -ok 263 - hasnt_domain(domain, desc) should have the proper description -ok 264 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 265 - hasnt_domain(scheam, domain) should fail -ok 266 - hasnt_domain(scheam, domain) should have the proper description -ok 267 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 268 - hasnt_domain(schema, domain, desc) should fail -ok 269 - hasnt_domain(schema, domain, desc) should have the proper description -ok 270 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 271 - has_column(non-existent tab, col) should fail -ok 272 - has_column(non-existent tab, col) should have the proper description -ok 273 - has_column(non-existent tab, col) should have the proper diagnostics -ok 274 - has_column(non-existent tab, col, desc) should fail -ok 275 - has_column(non-existent tab, col, desc) should have the proper description -ok 276 - has_column(non-existent tab, col, desc) should have the proper diagnostics -ok 277 - has_column(non-existent sch, tab, col, desc) should fail -ok 278 - has_column(non-existent sch, tab, col, desc) should have the proper description -ok 279 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 280 - has_column(table, column) should pass -ok 281 - has_column(table, column) should have the proper description -ok 282 - has_column(table, column) should have the proper diagnostics -ok 283 - has_column(sch, tab, col, desc) should pass -ok 284 - has_column(sch, tab, col, desc) should have the proper description -ok 285 - has_column(sch, tab, col, desc) should have the proper diagnostics -ok 286 - has_column(table, camleCase column) should pass -ok 287 - has_column(table, camleCase column) should have the proper description -ok 288 - has_column(table, camleCase column) should have the proper diagnostics -ok 289 - has_column(view, column) should pass -ok 290 - has_column(view, column) should have the proper description -ok 291 - has_column(view, column) should have the proper diagnostics -ok 292 - has_column(type, column) should pass -ok 293 - has_column(type, column) should have the proper description -ok 294 - has_column(type, column) should have the proper diagnostics -ok 295 - hasnt_column(non-existent tab, col) should pass -ok 296 - hasnt_column(non-existent tab, col) should have the proper description -ok 297 - hasnt_column(non-existent tab, col) should have the proper diagnostics -ok 298 - hasnt_column(non-existent tab, col, desc) should pass -ok 299 - hasnt_column(non-existent tab, col, desc) should have the proper description -ok 300 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics -ok 301 - hasnt_column(non-existent sch, tab, col, desc) should pass -ok 302 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description -ok 303 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 304 - hasnt_column(table, column) should fail -ok 305 - hasnt_column(table, column) should have the proper description -ok 306 - hasnt_column(table, column) should have the proper diagnostics -ok 307 - hasnt_column(sch, tab, col, desc) should fail -ok 308 - hasnt_column(sch, tab, col, desc) should have the proper description -ok 309 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics -ok 310 - hasnt_column(view, column) should pass -ok 311 - hasnt_column(view, column) should have the proper description -ok 312 - hasnt_column(view, column) should have the proper diagnostics -ok 313 - hasnt_column(type, column) should pass -ok 314 - hasnt_column(type, column) should have the proper description -ok 315 - hasnt_column(type, column) should have the proper diagnostics -ok 316 - has_cast( src, targ, schema, func, desc) should pass -ok 317 - has_cast( src, targ, schema, func, desc) should have the proper description -ok 318 - has_cast( src, targ, schema, func, desc) should have the proper diagnostics -ok 319 - has_cast( src, targ, schema, func ) should pass -ok 320 - has_cast( src, targ, schema, func ) should have the proper description -ok 321 - has_cast( src, targ, schema, func ) should have the proper diagnostics -ok 322 - has_cast( src, targ, func, desc ) should pass -ok 323 - has_cast( src, targ, func, desc ) should have the proper description -ok 324 - has_cast( src, targ, func, desc ) should have the proper diagnostics -ok 325 - has_cast( src, targ, func) should pass -ok 326 - has_cast( src, targ, func) should have the proper description -ok 327 - has_cast( src, targ, func) should have the proper diagnostics -ok 328 - has_cast( src, targ, desc ) should pass -ok 329 - has_cast( src, targ, desc ) should have the proper description -ok 330 - has_cast( src, targ, desc ) should have the proper diagnostics -ok 331 - has_cast( src, targ ) should pass -ok 332 - has_cast( src, targ ) should have the proper description -ok 333 - has_cast( src, targ ) should have the proper diagnostics -ok 334 - has_cast( src, targ, schema, func, desc) fail should fail -ok 335 - has_cast( src, targ, schema, func, desc) fail should have the proper description -ok 336 - has_cast( src, targ, schema, func, desc) fail should have the proper diagnostics -ok 337 - has_cast( src, targ, func, desc ) fail should fail -ok 338 - has_cast( src, targ, func, desc ) fail should have the proper description -ok 339 - has_cast( src, targ, func, desc ) fail should have the proper diagnostics -ok 340 - has_cast( src, targ, desc ) fail should fail -ok 341 - has_cast( src, targ, desc ) fail should have the proper description -ok 342 - has_cast( src, targ, desc ) fail should have the proper diagnostics -ok 343 - hasnt_cast( src, targ, schema, func, desc) should fail -ok 344 - hasnt_cast( src, targ, schema, func, desc) should have the proper description -ok 345 - hasnt_cast( src, targ, schema, func, desc) should have the proper diagnostics -ok 346 - hasnt_cast( src, targ, schema, func ) should fail -ok 347 - hasnt_cast( src, targ, schema, func ) should have the proper description -ok 348 - hasnt_cast( src, targ, schema, func ) should have the proper diagnostics -ok 349 - hasnt_cast( src, targ, func, desc ) should fail -ok 350 - hasnt_cast( src, targ, func, desc ) should have the proper description -ok 351 - hasnt_cast( src, targ, func, desc ) should have the proper diagnostics -ok 352 - hasnt_cast( src, targ, func) should fail -ok 353 - hasnt_cast( src, targ, func) should have the proper description -ok 354 - hasnt_cast( src, targ, func) should have the proper diagnostics -ok 355 - hasnt_cast( src, targ, desc ) should fail -ok 356 - hasnt_cast( src, targ, desc ) should have the proper description -ok 357 - hasnt_cast( src, targ, desc ) should have the proper diagnostics -ok 358 - hasnt_cast( src, targ ) should fail -ok 359 - hasnt_cast( src, targ ) should have the proper description -ok 360 - hasnt_cast( src, targ ) should have the proper diagnostics -ok 361 - hasnt_cast( src, targ, schema, func, desc) fail should pass -ok 362 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper description -ok 363 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper diagnostics -ok 364 - hasnt_cast( src, targ, func, desc ) fail should pass -ok 365 - hasnt_cast( src, targ, func, desc ) fail should have the proper description -ok 366 - hasnt_cast( src, targ, func, desc ) fail should have the proper diagnostics -ok 367 - hasnt_cast( src, targ, desc ) fail should pass -ok 368 - hasnt_cast( src, targ, desc ) fail should have the proper description -ok 369 - hasnt_cast( src, targ, desc ) fail should have the proper diagnostics -ok 370 - cast_context_is( src, targ, context, desc ) should pass -ok 371 - cast_context_is( src, targ, context, desc ) should have the proper description -ok 372 - cast_context_is( src, targ, context, desc ) should have the proper diagnostics -ok 373 - cast_context_is( src, targ, context ) should pass -ok 374 - cast_context_is( src, targ, context ) should have the proper description -ok 375 - cast_context_is( src, targ, context ) should have the proper diagnostics -ok 376 - cast_context_is( src, targ, i, desc ) should pass -ok 377 - cast_context_is( src, targ, i, desc ) should have the proper description -ok 378 - cast_context_is( src, targ, i, desc ) should have the proper diagnostics -ok 379 - cast_context_is( src, targ, IMPL, desc ) should pass -ok 380 - cast_context_is( src, targ, IMPL, desc ) should have the proper description -ok 381 - cast_context_is( src, targ, IMPL, desc ) should have the proper diagnostics -ok 382 - cast_context_is( src, targ, assignment, desc ) should pass -ok 383 - cast_context_is( src, targ, assignment, desc ) should have the proper description -ok 384 - cast_context_is( src, targ, assignment, desc ) should have the proper diagnostics -ok 385 - cast_context_is( src, targ, a, desc ) should pass -ok 386 - cast_context_is( src, targ, a, desc ) should have the proper description -ok 387 - cast_context_is( src, targ, a, desc ) should have the proper diagnostics -ok 388 - cast_context_is( src, targ, ASS, desc ) should pass -ok 389 - cast_context_is( src, targ, ASS, desc ) should have the proper description -ok 390 - cast_context_is( src, targ, ASS, desc ) should have the proper diagnostics -ok 391 - cast_context_is( src, targ, explicit, desc ) should pass -ok 392 - cast_context_is( src, targ, explicit, desc ) should have the proper description -ok 393 - cast_context_is( src, targ, explicit, desc ) should have the proper diagnostics -ok 394 - cast_context_is( src, targ, e, desc ) should pass -ok 395 - cast_context_is( src, targ, e, desc ) should have the proper description -ok 396 - cast_context_is( src, targ, e, desc ) should have the proper diagnostics -ok 397 - cast_context_is( src, targ, EX, desc ) should pass -ok 398 - cast_context_is( src, targ, EX, desc ) should have the proper description -ok 399 - cast_context_is( src, targ, EX, desc ) should have the proper diagnostics -ok 400 - cast_context_is( src, targ, context, desc ) fail should fail -ok 401 - cast_context_is( src, targ, context, desc ) fail should have the proper description -ok 402 - cast_context_is( src, targ, context, desc ) fail should have the proper diagnostics -ok 403 - cast_context_is( src, targ, context ) fail should fail -ok 404 - cast_context_is( src, targ, context ) fail should have the proper description -ok 405 - cast_context_is( src, targ, context ) fail should have the proper diagnostics -ok 406 - cast_context_is( src, targ, context, desc ) noexist should fail -ok 407 - cast_context_is( src, targ, context, desc ) noexist should have the proper description -ok 408 - cast_context_is( src, targ, context, desc ) noexist should have the proper diagnostics -ok 409 - has_operator( left, schema, name, right, result, desc ) should pass -ok 410 - has_operator( left, schema, name, right, result, desc ) should have the proper description -ok 411 - has_operator( left, schema, name, right, result, desc ) should have the proper diagnostics -ok 412 - has_operator( left, schema, name, right, result ) should pass -ok 413 - has_operator( left, schema, name, right, result ) should have the proper description -ok 414 - has_operator( left, schema, name, right, result ) should have the proper diagnostics -ok 415 - has_operator( left, name, right, result, desc ) should pass -ok 416 - has_operator( left, name, right, result, desc ) should have the proper description -ok 417 - has_operator( left, name, right, result, desc ) should have the proper diagnostics -ok 418 - has_operator( left, name, right, result ) should pass -ok 419 - has_operator( left, name, right, result ) should have the proper description -ok 420 - has_operator( left, name, right, result ) should have the proper diagnostics -ok 421 - has_operator( left, name, right, desc ) should pass -ok 422 - has_operator( left, name, right, desc ) should have the proper description -ok 423 - has_operator( left, name, right, desc ) should have the proper diagnostics -ok 424 - has_operator( left, name, right ) should pass -ok 425 - has_operator( left, name, right ) should have the proper description -ok 426 - has_operator( left, name, right ) should have the proper diagnostics -ok 427 - has_operator( left, schema, name, right, result, desc ) fail should fail -ok 428 - has_operator( left, schema, name, right, result, desc ) fail should have the proper description -ok 429 - has_operator( left, schema, name, right, result, desc ) fail should have the proper diagnostics -ok 430 - has_operator( left, schema, name, right, result ) fail should fail -ok 431 - has_operator( left, schema, name, right, result ) fail should have the proper description -ok 432 - has_operator( left, schema, name, right, result ) fail should have the proper diagnostics -ok 433 - has_operator( left, name, right, result, desc ) fail should fail -ok 434 - has_operator( left, name, right, result, desc ) fail should have the proper description -ok 435 - has_operator( left, name, right, result, desc ) fail should have the proper diagnostics -ok 436 - has_operator( left, name, right, result ) fail should fail -ok 437 - has_operator( left, name, right, result ) fail should have the proper description -ok 438 - has_operator( left, name, right, result ) fail should have the proper diagnostics -ok 439 - has_operator( left, name, right, desc ) fail should fail -ok 440 - has_operator( left, name, right, desc ) fail should have the proper description -ok 441 - has_operator( left, name, right, desc ) fail should have the proper diagnostics -ok 442 - has_operator( left, name, right ) fail should fail -ok 443 - has_operator( left, name, right ) fail should have the proper description -ok 444 - has_operator( left, name, right ) fail should have the proper diagnostics -ok 445 - has_leftop( schema, name, right, result, desc ) should pass -ok 446 - has_leftop( schema, name, right, result, desc ) should have the proper description -ok 447 - has_leftop( schema, name, right, result, desc ) should have the proper diagnostics -ok 448 - has_leftop( schema, name, right, result ) should pass -ok 449 - has_leftop( schema, name, right, result ) should have the proper description -ok 450 - has_leftop( schema, name, right, result ) should have the proper diagnostics -ok 451 - has_leftop( name, right, result, desc ) should pass -ok 452 - has_leftop( name, right, result, desc ) should have the proper description -ok 453 - has_leftop( name, right, result, desc ) should have the proper diagnostics -ok 454 - has_leftop( name, right, result ) should pass -ok 455 - has_leftop( name, right, result ) should have the proper description -ok 456 - has_leftop( name, right, result ) should have the proper diagnostics -ok 457 - has_leftop( name, right, desc ) should pass -ok 458 - has_leftop( name, right, desc ) should have the proper description -ok 459 - has_leftop( name, right, desc ) should have the proper diagnostics -ok 460 - has_leftop( name, right ) should pass -ok 461 - has_leftop( name, right ) should have the proper description -ok 462 - has_leftop( name, right ) should have the proper diagnostics -ok 463 - has_leftop( schema, name, right, result, desc ) fail should fail -ok 464 - has_leftop( schema, name, right, result, desc ) fail should have the proper description -ok 465 - has_leftop( schema, name, right, result, desc ) fail should have the proper diagnostics -ok 466 - has_leftop( schema, name, right, result ) fail should fail -ok 467 - has_leftop( schema, name, right, result ) fail should have the proper description -ok 468 - has_leftop( schema, name, right, result ) fail should have the proper diagnostics -ok 469 - has_leftop( name, right, result, desc ) fail should fail -ok 470 - has_leftop( name, right, result, desc ) fail should have the proper description -ok 471 - has_leftop( name, right, result, desc ) fail should have the proper diagnostics -ok 472 - has_leftop( name, right, result ) fail should fail -ok 473 - has_leftop( name, right, result ) fail should have the proper description -ok 474 - has_leftop( name, right, result ) fail should have the proper diagnostics -ok 475 - has_leftop( name, right, desc ) fail should fail -ok 476 - has_leftop( name, right, desc ) fail should have the proper description -ok 477 - has_leftop( name, right, desc ) fail should have the proper diagnostics -ok 478 - has_leftop( name, right ) fail should fail -ok 479 - has_leftop( name, right ) fail should have the proper description -ok 480 - has_leftop( name, right ) fail should have the proper diagnostics -ok 481 - has_rightop( left, schema, name, result, desc ) should pass -ok 482 - has_rightop( left, schema, name, result, desc ) should have the proper description -ok 483 - has_rightop( left, schema, name, result, desc ) should have the proper diagnostics -ok 484 - has_rightop( left, schema, name, result ) should pass -ok 485 - has_rightop( left, schema, name, result ) should have the proper description -ok 486 - has_rightop( left, schema, name, result ) should have the proper diagnostics -ok 487 - has_rightop( left, name, result, desc ) should pass -ok 488 - has_rightop( left, name, result, desc ) should have the proper description -ok 489 - has_rightop( left, name, result, desc ) should have the proper diagnostics -ok 490 - has_rightop( left, name, result ) should pass -ok 491 - has_rightop( left, name, result ) should have the proper description -ok 492 - has_rightop( left, name, result ) should have the proper diagnostics -ok 493 - has_rightop( left, name, desc ) should pass -ok 494 - has_rightop( left, name, desc ) should have the proper description -ok 495 - has_rightop( left, name, desc ) should have the proper diagnostics -ok 496 - has_rightop( left, name ) should pass -ok 497 - has_rightop( left, name ) should have the proper description -ok 498 - has_rightop( left, name ) should have the proper diagnostics -ok 499 - has_rightop( left, schema, name, result, desc ) fail should fail -ok 500 - has_rightop( left, schema, name, result, desc ) fail should have the proper description -ok 501 - has_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics -ok 502 - has_rightop( left, schema, name, result ) fail should fail -ok 503 - has_rightop( left, schema, name, result ) fail should have the proper description -ok 504 - has_rightop( left, schema, name, result ) fail should have the proper diagnostics -ok 505 - has_rightop( left, name, result, desc ) fail should fail -ok 506 - has_rightop( left, name, result, desc ) fail should have the proper description -ok 507 - has_rightop( left, name, result, desc ) fail should have the proper diagnostics -ok 508 - has_rightop( left, name, result ) fail should fail -ok 509 - has_rightop( left, name, result ) fail should have the proper description -ok 510 - has_rightop( left, name, result ) fail should have the proper diagnostics -ok 511 - has_rightop( left, name, desc ) fail should fail -ok 512 - has_rightop( left, name, desc ) fail should have the proper description -ok 513 - has_rightop( left, name, desc ) fail should have the proper diagnostics -ok 514 - has_rightop( left, name ) fail should fail -ok 515 - has_rightop( left, name ) fail should have the proper description -ok 516 - has_rightop( left, name ) fail should have the proper diagnostics -ok 517 - has_language(language) should pass -ok 518 - has_language(language) should have the proper description -ok 519 - has_language(language) should have the proper diagnostics -ok 520 - has_language(language, desc) should pass -ok 521 - has_language(language, desc) should have the proper description -ok 522 - has_language(language, desc) should have the proper diagnostics -ok 523 - has_language(nonexistent language) should fail -ok 524 - has_language(nonexistent language) should have the proper description -ok 525 - has_language(nonexistent language) should have the proper diagnostics -ok 526 - has_language(nonexistent language, desc) should fail -ok 527 - has_language(nonexistent language, desc) should have the proper description -ok 528 - has_language(nonexistent language, desc) should have the proper diagnostics -ok 529 - hasnt_language(language) should fail -ok 530 - hasnt_language(language) should have the proper description -ok 531 - hasnt_language(language) should have the proper diagnostics -ok 532 - hasnt_language(language, desc) should fail -ok 533 - hasnt_language(language, desc) should have the proper description -ok 534 - hasnt_language(language, desc) should have the proper diagnostics -ok 535 - hasnt_language(nonexistent language) should pass -ok 536 - hasnt_language(nonexistent language) should have the proper description -ok 537 - hasnt_language(nonexistent language) should have the proper diagnostics -ok 538 - hasnt_language(nonexistent language, desc) should pass -ok 539 - hasnt_language(nonexistent language, desc) should have the proper description -ok 540 - hasnt_language(nonexistent language, desc) should have the proper diagnostics -ok 541 - language_is_trusted(language, desc) should pass -ok 542 - language_is_trusted(language, desc) should have the proper description -ok 543 - language_is_trusted(language, desc) should have the proper diagnostics -ok 544 - language_is_trusted(language) should pass -ok 545 - language_is_trusted(language) should have the proper description -ok 546 - language_is_trusted(language) should have the proper diagnostics -ok 547 - language_is_trusted(language, desc) fail should fail -ok 548 - language_is_trusted(language, desc) fail should have the proper description -ok 549 - language_is_trusted(language, desc) fail should have the proper diagnostics -ok 550 - language_is_trusted(language, desc) non-existent should fail -ok 551 - language_is_trusted(language, desc) non-existent should have the proper description -ok 552 - language_is_trusted(language, desc) non-existent should have the proper diagnostics -ok 553 - has_opclass( schema, name, desc ) should pass -ok 554 - has_opclass( schema, name, desc ) should have the proper description -ok 555 - has_opclass( schema, name, desc ) should have the proper diagnostics -ok 556 - has_opclass( schema, name ) should pass -ok 557 - has_opclass( schema, name ) should have the proper description -ok 558 - has_opclass( schema, name ) should have the proper diagnostics -ok 559 - has_opclass( name, desc ) should pass -ok 560 - has_opclass( name, desc ) should have the proper description -ok 561 - has_opclass( name, desc ) should have the proper diagnostics -ok 562 - has_opclass( name ) should pass -ok 563 - has_opclass( name ) should have the proper description -ok 564 - has_opclass( name ) should have the proper diagnostics -ok 565 - has_opclass( schema, name, desc ) fail should fail -ok 566 - has_opclass( schema, name, desc ) fail should have the proper description -ok 567 - has_opclass( schema, name, desc ) fail should have the proper diagnostics -ok 568 - has_opclass( name, desc ) fail should fail -ok 569 - has_opclass( name, desc ) fail should have the proper description -ok 570 - has_opclass( name, desc ) fail should have the proper diagnostics -ok 571 - hasnt_opclass( schema, name, desc ) should fail -ok 572 - hasnt_opclass( schema, name, desc ) should have the proper description -ok 573 - hasnt_opclass( schema, name, desc ) should have the proper diagnostics -ok 574 - hasnt_opclass( schema, name ) should fail -ok 575 - hasnt_opclass( schema, name ) should have the proper description -ok 576 - hasnt_opclass( schema, name ) should have the proper diagnostics -ok 577 - hasnt_opclass( name, desc ) should fail -ok 578 - hasnt_opclass( name, desc ) should have the proper description -ok 579 - hasnt_opclass( name, desc ) should have the proper diagnostics -ok 580 - hasnt_opclass( name ) should fail -ok 581 - hasnt_opclass( name ) should have the proper description -ok 582 - hasnt_opclass( name ) should have the proper diagnostics -ok 583 - hasnt_opclass( schema, name, desc ) fail should pass -ok 584 - hasnt_opclass( schema, name, desc ) fail should have the proper description -ok 585 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnostics -ok 586 - hasnt_opclass( name, desc ) fail should pass -ok 587 - hasnt_opclass( name, desc ) fail should have the proper description -ok 588 - hasnt_opclass( name, desc ) fail should have the proper diagnostics -ok 589 - domain_type_is(schema, domain, schema, type, desc) should pass -ok 590 - domain_type_is(schema, domain, schema, type, desc) should have the proper description -ok 591 - domain_type_is(schema, domain, schema, type, desc) should have the proper diagnostics -ok 592 - domain_type_is(schema, domain, schema, type) should pass -ok 593 - domain_type_is(schema, domain, schema, type) should have the proper description -ok 594 - domain_type_is(schema, domain, schema, type) should have the proper diagnostics -ok 595 - domain_type_is(schema, domain, schema, type, desc) fail should fail -ok 596 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper description -ok 597 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper diagnostics -ok 598 - domain_type_is(schema, nondomain, schema, type, desc) should fail -ok 599 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper description -ok 600 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper diagnostics -ok 601 - domain_type_is(schema, type, schema, type, desc) fail should fail -ok 602 - domain_type_is(schema, type, schema, type, desc) fail should have the proper description -ok 603 - domain_type_is(schema, type, schema, type, desc) fail should have the proper diagnostics -ok 604 - domain_type_is(schema, domain, type, desc) should pass -ok 605 - domain_type_is(schema, domain, type, desc) should have the proper description -ok 606 - domain_type_is(schema, domain, type, desc) should have the proper diagnostics -ok 607 - domain_type_is(schema, domain, type) should pass -ok 608 - domain_type_is(schema, domain, type) should have the proper description -ok 609 - domain_type_is(schema, domain, type) should have the proper diagnostics -ok 610 - domain_type_is(schema, domain, type, desc) fail should fail -ok 611 - domain_type_is(schema, domain, type, desc) fail should have the proper description -ok 612 - domain_type_is(schema, domain, type, desc) fail should have the proper diagnostics -ok 613 - domain_type_is(schema, nondomain, type, desc) should fail -ok 614 - domain_type_is(schema, nondomain, type, desc) should have the proper description -ok 615 - domain_type_is(schema, nondomain, type, desc) should have the proper diagnostics -ok 616 - domain_type_is(schema, type, type, desc) fail should fail -ok 617 - domain_type_is(schema, type, type, desc) fail should have the proper description -ok 618 - domain_type_is(schema, type, type, desc) fail should have the proper diagnostics -ok 619 - domain_type_is(domain, type, desc) should pass -ok 620 - domain_type_is(domain, type, desc) should have the proper description -ok 621 - domain_type_is(domain, type, desc) should have the proper diagnostics -ok 622 - domain_type_is(domain, type) should pass -ok 623 - domain_type_is(domain, type) should have the proper description -ok 624 - domain_type_is(domain, type) should have the proper diagnostics -ok 625 - domain_type_is(domain, type, desc) fail should fail -ok 626 - domain_type_is(domain, type, desc) fail should have the proper description -ok 627 - domain_type_is(domain, type, desc) fail should have the proper diagnostics -ok 628 - domain_type_is(nondomain, type, desc) should fail -ok 629 - domain_type_is(nondomain, type, desc) should have the proper description -ok 630 - domain_type_is(nondomain, type, desc) should have the proper diagnostics -ok 631 - domain_type_is(type, type, desc) fail should fail -ok 632 - domain_type_is(type, type, desc) fail should have the proper description -ok 633 - domain_type_is(type, type, desc) fail should have the proper diagnostics -ok 634 - domain_type_isnt(schema, domain, schema, type, desc) should pass -ok 635 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper description -ok 636 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper diagnostics -ok 637 - domain_type_isnt(schema, domain, schema, type) should pass -ok 638 - domain_type_isnt(schema, domain, schema, type) should have the proper description -ok 639 - domain_type_isnt(schema, domain, schema, type) should have the proper diagnostics -ok 640 - domain_type_isnt(schema, domain, schema, type, desc) fail should fail -ok 641 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper description -ok 642 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper diagnostics -ok 643 - domain_type_isnt(schema, nondomain, schema, type, desc) should fail -ok 644 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper description -ok 645 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper diagnostics -ok 646 - domain_type_isnt(schema, type, schema, type, desc) should fail -ok 647 - domain_type_isnt(schema, type, schema, type, desc) should have the proper description -ok 648 - domain_type_isnt(schema, type, schema, type, desc) should have the proper diagnostics -ok 649 - domain_type_isnt(schema, domain, type, desc) should pass -ok 650 - domain_type_isnt(schema, domain, type, desc) should have the proper description -ok 651 - domain_type_isnt(schema, domain, type, desc) should have the proper diagnostics -ok 652 - domain_type_isnt(schema, domain, type) should pass -ok 653 - domain_type_isnt(schema, domain, type) should have the proper description -ok 654 - domain_type_isnt(schema, domain, type) should have the proper diagnostics -ok 655 - domain_type_isnt(schema, domain, type, desc) fail should fail -ok 656 - domain_type_isnt(schema, domain, type, desc) fail should have the proper description -ok 657 - domain_type_isnt(schema, domain, type, desc) fail should have the proper diagnostics -ok 658 - domain_type_isnt(schema, nondomain, type, desc) should fail -ok 659 - domain_type_isnt(schema, nondomain, type, desc) should have the proper description -ok 660 - domain_type_isnt(schema, nondomain, type, desc) should have the proper diagnostics -ok 661 - domain_type_isnt(schema, type, type, desc) should fail -ok 662 - domain_type_isnt(schema, type, type, desc) should have the proper description -ok 663 - domain_type_isnt(schema, type, type, desc) should have the proper diagnostics -ok 664 - domain_type_isnt(domain, type, desc) should pass -ok 665 - domain_type_isnt(domain, type, desc) should have the proper description -ok 666 - domain_type_isnt(domain, type, desc) should have the proper diagnostics -ok 667 - domain_type_isnt(domain, type) should pass -ok 668 - domain_type_isnt(domain, type) should have the proper description -ok 669 - domain_type_isnt(domain, type) should have the proper diagnostics -ok 670 - domain_type_isnt(domain, type, desc) fail should fail -ok 671 - domain_type_isnt(domain, type, desc) fail should have the proper description -ok 672 - domain_type_isnt(domain, type, desc) fail should have the proper diagnostics -ok 673 - domain_type_isnt(nondomain, type, desc) should fail -ok 674 - domain_type_isnt(nondomain, type, desc) should have the proper description -ok 675 - domain_type_isnt(nondomain, type, desc) should have the proper diagnostics -ok 676 - domain_type_isnt(type, type, desc) should fail -ok 677 - domain_type_isnt(type, type, desc) should have the proper description -ok 678 - domain_type_isnt(type, type, desc) should have the proper diagnostics -ok 679 - has_foreign_table(non-existent table) should fail -ok 680 - has_foreign_table(non-existent table) should have the proper description -ok 681 - has_foreign_table(non-existent table) should have the proper diagnostics -ok 682 - has_foreign_table(non-existent schema, tab) should fail -ok 683 - has_foreign_table(non-existent schema, tab) should have the proper description -ok 684 - has_foreign_table(non-existent schema, tab) should have the proper diagnostics -ok 685 - has_foreign_table(sch, non-existent table, desc) should fail -ok 686 - has_foreign_table(sch, non-existent table, desc) should have the proper description -ok 687 - has_foreign_table(sch, non-existent table, desc) should have the proper diagnostics -ok 688 - has_foreign_table(tab, desc) should pass -ok 689 - has_foreign_table(tab, desc) should have the proper description -ok 690 - has_foreign_table(tab, desc) should have the proper diagnostics -ok 691 - has_foreign_table(sch, tab, desc) should pass -ok 692 - has_foreign_table(sch, tab, desc) should have the proper description -ok 693 - has_foreign_table(sch, tab, desc) should have the proper diagnostics -ok 694 - has_foreign_table(sch, view, desc) should fail -ok 695 - has_foreign_table(sch, view, desc) should have the proper description -ok 696 - has_foreign_table(sch, view, desc) should have the proper diagnostics -ok 697 - has_foreign_table(type, desc) should fail -ok 698 - has_foreign_table(type, desc) should have the proper description -ok 699 - has_foreign_table(type, desc) should have the proper diagnostics -ok 700 - hasnt_foreign_table(non-existent table) should pass -ok 701 - hasnt_foreign_table(non-existent table) should have the proper description -ok 702 - hasnt_foreign_table(non-existent table) should have the proper diagnostics -ok 703 - hasnt_foreign_table(non-existent schema, tab) should pass -ok 704 - hasnt_foreign_table(non-existent schema, tab) should have the proper description -ok 705 - hasnt_foreign_table(sch, non-existent tab, desc) should pass -ok 706 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper description -ok 707 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper diagnostics -ok 708 - hasnt_foreign_table(tab, desc) should fail -ok 709 - hasnt_foreign_table(tab, desc) should have the proper description -ok 710 - hasnt_foreign_table(tab, desc) should have the proper diagnostics -ok 711 - hasnt_foreign_table(sch, tab, desc) should fail -ok 712 - hasnt_foreign_table(sch, tab, desc) should have the proper description -ok 713 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics +ok 145 - has_composite(non-existent composite type) should fail +ok 146 - has_composite(non-existent composite type) should have the proper description +ok 147 - has_composite(non-existent composite type) should have the proper diagnostics +ok 148 - has_composite(non-existent schema, tab) should fail +ok 149 - has_composite(non-existent schema, tab) should have the proper description +ok 150 - has_composite(non-existent schema, tab) should have the proper diagnostics +ok 151 - has_composite(sch, non-existent composite type, desc) should fail +ok 152 - has_composite(sch, non-existent composite type, desc) should have the proper description +ok 153 - has_composite(sch, non-existent composite type, desc) should have the proper diagnostics +ok 154 - has_composite(tab, desc) should pass +ok 155 - has_composite(tab, desc) should have the proper description +ok 156 - has_composite(tab, desc) should have the proper diagnostics +ok 157 - has_composite(sch, tab, desc) should pass +ok 158 - has_composite(sch, tab, desc) should have the proper description +ok 159 - has_composite(sch, tab, desc) should have the proper diagnostics +ok 160 - has_composite(sch, view, desc) should fail +ok 161 - has_composite(sch, view, desc) should have the proper description +ok 162 - has_composite(sch, view, desc) should have the proper diagnostics +ok 163 - has_composite(type, desc) should fail +ok 164 - has_composite(type, desc) should have the proper description +ok 165 - has_composite(type, desc) should have the proper diagnostics +ok 166 - hasnt_composite(non-existent composite type) should pass +ok 167 - hasnt_composite(non-existent composite type) should have the proper description +ok 168 - hasnt_composite(non-existent composite type) should have the proper diagnostics +ok 169 - hasnt_composite(non-existent schema, tab) should pass +ok 170 - hasnt_composite(non-existent schema, tab) should have the proper description +ok 171 - hasnt_composite(non-existent schema, tab) should have the proper diagnostics +ok 172 - hasnt_composite(sch, non-existent tab, desc) should pass +ok 173 - hasnt_composite(sch, non-existent tab, desc) should have the proper description +ok 174 - hasnt_composite(sch, non-existent tab, desc) should have the proper diagnostics +ok 175 - hasnt_composite(tab, desc) should fail +ok 176 - hasnt_composite(tab, desc) should have the proper description +ok 177 - hasnt_composite(tab, desc) should have the proper diagnostics +ok 178 - hasnt_composite(sch, tab, desc) should fail +ok 179 - hasnt_composite(sch, tab, desc) should have the proper description +ok 180 - hasnt_composite(sch, tab, desc) should have the proper diagnostics +ok 181 - has_type(type) should pass +ok 182 - has_type(type) should have the proper description +ok 183 - has_type(type) should have the proper diagnostics +ok 184 - has_type(type, desc) should pass +ok 185 - has_type(type, desc) should have the proper description +ok 186 - has_type(type, desc) should have the proper diagnostics +ok 187 - has_type(scheam, type) should pass +ok 188 - has_type(scheam, type) should have the proper description +ok 189 - has_type(scheam, type) should have the proper diagnostics +ok 190 - has_type(schema, type, desc) should pass +ok 191 - has_type(schema, type, desc) should have the proper description +ok 192 - has_type(schema, type, desc) should have the proper diagnostics +ok 193 - has_type(myType) should pass +ok 194 - has_type(myType) should have the proper description +ok 195 - has_type(myType) should have the proper diagnostics +ok 196 - has_type(myType, desc) should pass +ok 197 - has_type(myType, desc) should have the proper description +ok 198 - has_type(myType, desc) should have the proper diagnostics +ok 199 - has_type(scheam, myType) should pass +ok 200 - has_type(scheam, myType) should have the proper description +ok 201 - has_type(scheam, myType) should have the proper diagnostics +ok 202 - has_type(schema, myType, desc) should pass +ok 203 - has_type(schema, myType, desc) should have the proper description +ok 204 - has_type(schema, myType, desc) should have the proper diagnostics +ok 205 - has_type(type) should fail +ok 206 - has_type(type) should have the proper description +ok 207 - has_type(type) should have the proper diagnostics +ok 208 - has_type(type, desc) should fail +ok 209 - has_type(type, desc) should have the proper description +ok 210 - has_type(type, desc) should have the proper diagnostics +ok 211 - has_type(scheam, type) should fail +ok 212 - has_type(scheam, type) should have the proper description +ok 213 - has_type(scheam, type) should have the proper diagnostics +ok 214 - has_type(schema, type, desc) should fail +ok 215 - has_type(schema, type, desc) should have the proper description +ok 216 - has_type(schema, type, desc) should have the proper diagnostics +ok 217 - has_type(domain) should pass +ok 218 - has_type(domain) should have the proper description +ok 219 - has_type(domain) should have the proper diagnostics +ok 220 - has_type(myDomain) should pass +ok 221 - has_type(myDomain) should have the proper description +ok 222 - has_type(myDomain) should have the proper diagnostics +ok 223 - hasnt_type(type) should pass +ok 224 - hasnt_type(type) should have the proper description +ok 225 - hasnt_type(type) should have the proper diagnostics +ok 226 - hasnt_type(type, desc) should pass +ok 227 - hasnt_type(type, desc) should have the proper description +ok 228 - hasnt_type(type, desc) should have the proper diagnostics +ok 229 - hasnt_type(scheam, type) should pass +ok 230 - hasnt_type(scheam, type) should have the proper description +ok 231 - hasnt_type(scheam, type) should have the proper diagnostics +ok 232 - hasnt_type(schema, type, desc) should pass +ok 233 - hasnt_type(schema, type, desc) should have the proper description +ok 234 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 235 - hasnt_type(type) should fail +ok 236 - hasnt_type(type) should have the proper description +ok 237 - hasnt_type(type) should have the proper diagnostics +ok 238 - hasnt_type(type, desc) should fail +ok 239 - hasnt_type(type, desc) should have the proper description +ok 240 - hasnt_type(type, desc) should have the proper diagnostics +ok 241 - hasnt_type(scheam, type) should fail +ok 242 - hasnt_type(scheam, type) should have the proper description +ok 243 - hasnt_type(scheam, type) should have the proper diagnostics +ok 244 - hasnt_type(schema, type, desc) should fail +ok 245 - hasnt_type(schema, type, desc) should have the proper description +ok 246 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 247 - has_domain(domain) should pass +ok 248 - has_domain(domain) should have the proper description +ok 249 - has_domain(domain) should have the proper diagnostics +ok 250 - has_domain(domain, desc) should pass +ok 251 - has_domain(domain, desc) should have the proper description +ok 252 - has_domain(domain, desc) should have the proper diagnostics +ok 253 - has_domain(scheam, domain) should pass +ok 254 - has_domain(scheam, domain) should have the proper description +ok 255 - has_domain(scheam, domain) should have the proper diagnostics +ok 256 - has_domain(schema, domain, desc) should pass +ok 257 - has_domain(schema, domain, desc) should have the proper description +ok 258 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 259 - has_domain(myDomain) should pass +ok 260 - has_domain(myDomain) should have the proper description +ok 261 - has_domain(myDomain) should have the proper diagnostics +ok 262 - has_domain(myDomain, desc) should pass +ok 263 - has_domain(myDomain, desc) should have the proper description +ok 264 - has_domain(myDomain, desc) should have the proper diagnostics +ok 265 - has_domain(scheam, myDomain) should pass +ok 266 - has_domain(scheam, myDomain) should have the proper description +ok 267 - has_domain(scheam, myDomain) should have the proper diagnostics +ok 268 - has_domain(schema, myDomain, desc) should pass +ok 269 - has_domain(schema, myDomain, desc) should have the proper description +ok 270 - has_domain(schema, myDomain, desc) should have the proper diagnostics +ok 271 - has_domain(domain) should fail +ok 272 - has_domain(domain) should have the proper description +ok 273 - has_domain(domain) should have the proper diagnostics +ok 274 - has_domain(domain, desc) should fail +ok 275 - has_domain(domain, desc) should have the proper description +ok 276 - has_domain(domain, desc) should have the proper diagnostics +ok 277 - has_domain(scheam, domain) should fail +ok 278 - has_domain(scheam, domain) should have the proper description +ok 279 - has_domain(scheam, domain) should have the proper diagnostics +ok 280 - has_domain(schema, domain, desc) should fail +ok 281 - has_domain(schema, domain, desc) should have the proper description +ok 282 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 283 - hasnt_domain(domain) should pass +ok 284 - hasnt_domain(domain) should have the proper description +ok 285 - hasnt_domain(domain) should have the proper diagnostics +ok 286 - hasnt_domain(domain, desc) should pass +ok 287 - hasnt_domain(domain, desc) should have the proper description +ok 288 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 289 - hasnt_domain(scheam, domain) should pass +ok 290 - hasnt_domain(scheam, domain) should have the proper description +ok 291 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 292 - hasnt_domain(schema, domain, desc) should pass +ok 293 - hasnt_domain(schema, domain, desc) should have the proper description +ok 294 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 295 - hasnt_domain(domain) should fail +ok 296 - hasnt_domain(domain) should have the proper description +ok 297 - hasnt_domain(domain) should have the proper diagnostics +ok 298 - hasnt_domain(domain, desc) should fail +ok 299 - hasnt_domain(domain, desc) should have the proper description +ok 300 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 301 - hasnt_domain(scheam, domain) should fail +ok 302 - hasnt_domain(scheam, domain) should have the proper description +ok 303 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 304 - hasnt_domain(schema, domain, desc) should fail +ok 305 - hasnt_domain(schema, domain, desc) should have the proper description +ok 306 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 307 - has_column(non-existent tab, col) should fail +ok 308 - has_column(non-existent tab, col) should have the proper description +ok 309 - has_column(non-existent tab, col) should have the proper diagnostics +ok 310 - has_column(non-existent tab, col, desc) should fail +ok 311 - has_column(non-existent tab, col, desc) should have the proper description +ok 312 - has_column(non-existent tab, col, desc) should have the proper diagnostics +ok 313 - has_column(non-existent sch, tab, col, desc) should fail +ok 314 - has_column(non-existent sch, tab, col, desc) should have the proper description +ok 315 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 316 - has_column(table, column) should pass +ok 317 - has_column(table, column) should have the proper description +ok 318 - has_column(table, column) should have the proper diagnostics +ok 319 - has_column(sch, tab, col, desc) should pass +ok 320 - has_column(sch, tab, col, desc) should have the proper description +ok 321 - has_column(sch, tab, col, desc) should have the proper diagnostics +ok 322 - has_column(table, camleCase column) should pass +ok 323 - has_column(table, camleCase column) should have the proper description +ok 324 - has_column(table, camleCase column) should have the proper diagnostics +ok 325 - has_column(view, column) should pass +ok 326 - has_column(view, column) should have the proper description +ok 327 - has_column(view, column) should have the proper diagnostics +ok 328 - has_column(type, column) should pass +ok 329 - has_column(type, column) should have the proper description +ok 330 - has_column(type, column) should have the proper diagnostics +ok 331 - hasnt_column(non-existent tab, col) should pass +ok 332 - hasnt_column(non-existent tab, col) should have the proper description +ok 333 - hasnt_column(non-existent tab, col) should have the proper diagnostics +ok 334 - hasnt_column(non-existent tab, col, desc) should pass +ok 335 - hasnt_column(non-existent tab, col, desc) should have the proper description +ok 336 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics +ok 337 - hasnt_column(non-existent sch, tab, col, desc) should pass +ok 338 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description +ok 339 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 340 - hasnt_column(table, column) should fail +ok 341 - hasnt_column(table, column) should have the proper description +ok 342 - hasnt_column(table, column) should have the proper diagnostics +ok 343 - hasnt_column(sch, tab, col, desc) should fail +ok 344 - hasnt_column(sch, tab, col, desc) should have the proper description +ok 345 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics +ok 346 - hasnt_column(view, column) should pass +ok 347 - hasnt_column(view, column) should have the proper description +ok 348 - hasnt_column(view, column) should have the proper diagnostics +ok 349 - hasnt_column(type, column) should pass +ok 350 - hasnt_column(type, column) should have the proper description +ok 351 - hasnt_column(type, column) should have the proper diagnostics +ok 352 - has_cast( src, targ, schema, func, desc) should pass +ok 353 - has_cast( src, targ, schema, func, desc) should have the proper description +ok 354 - has_cast( src, targ, schema, func, desc) should have the proper diagnostics +ok 355 - has_cast( src, targ, schema, func ) should pass +ok 356 - has_cast( src, targ, schema, func ) should have the proper description +ok 357 - has_cast( src, targ, schema, func ) should have the proper diagnostics +ok 358 - has_cast( src, targ, func, desc ) should pass +ok 359 - has_cast( src, targ, func, desc ) should have the proper description +ok 360 - has_cast( src, targ, func, desc ) should have the proper diagnostics +ok 361 - has_cast( src, targ, func) should pass +ok 362 - has_cast( src, targ, func) should have the proper description +ok 363 - has_cast( src, targ, func) should have the proper diagnostics +ok 364 - has_cast( src, targ, desc ) should pass +ok 365 - has_cast( src, targ, desc ) should have the proper description +ok 366 - has_cast( src, targ, desc ) should have the proper diagnostics +ok 367 - has_cast( src, targ ) should pass +ok 368 - has_cast( src, targ ) should have the proper description +ok 369 - has_cast( src, targ ) should have the proper diagnostics +ok 370 - has_cast( src, targ, schema, func, desc) fail should fail +ok 371 - has_cast( src, targ, schema, func, desc) fail should have the proper description +ok 372 - has_cast( src, targ, schema, func, desc) fail should have the proper diagnostics +ok 373 - has_cast( src, targ, func, desc ) fail should fail +ok 374 - has_cast( src, targ, func, desc ) fail should have the proper description +ok 375 - has_cast( src, targ, func, desc ) fail should have the proper diagnostics +ok 376 - has_cast( src, targ, desc ) fail should fail +ok 377 - has_cast( src, targ, desc ) fail should have the proper description +ok 378 - has_cast( src, targ, desc ) fail should have the proper diagnostics +ok 379 - hasnt_cast( src, targ, schema, func, desc) should fail +ok 380 - hasnt_cast( src, targ, schema, func, desc) should have the proper description +ok 381 - hasnt_cast( src, targ, schema, func, desc) should have the proper diagnostics +ok 382 - hasnt_cast( src, targ, schema, func ) should fail +ok 383 - hasnt_cast( src, targ, schema, func ) should have the proper description +ok 384 - hasnt_cast( src, targ, schema, func ) should have the proper diagnostics +ok 385 - hasnt_cast( src, targ, func, desc ) should fail +ok 386 - hasnt_cast( src, targ, func, desc ) should have the proper description +ok 387 - hasnt_cast( src, targ, func, desc ) should have the proper diagnostics +ok 388 - hasnt_cast( src, targ, func) should fail +ok 389 - hasnt_cast( src, targ, func) should have the proper description +ok 390 - hasnt_cast( src, targ, func) should have the proper diagnostics +ok 391 - hasnt_cast( src, targ, desc ) should fail +ok 392 - hasnt_cast( src, targ, desc ) should have the proper description +ok 393 - hasnt_cast( src, targ, desc ) should have the proper diagnostics +ok 394 - hasnt_cast( src, targ ) should fail +ok 395 - hasnt_cast( src, targ ) should have the proper description +ok 396 - hasnt_cast( src, targ ) should have the proper diagnostics +ok 397 - hasnt_cast( src, targ, schema, func, desc) fail should pass +ok 398 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper description +ok 399 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper diagnostics +ok 400 - hasnt_cast( src, targ, func, desc ) fail should pass +ok 401 - hasnt_cast( src, targ, func, desc ) fail should have the proper description +ok 402 - hasnt_cast( src, targ, func, desc ) fail should have the proper diagnostics +ok 403 - hasnt_cast( src, targ, desc ) fail should pass +ok 404 - hasnt_cast( src, targ, desc ) fail should have the proper description +ok 405 - hasnt_cast( src, targ, desc ) fail should have the proper diagnostics +ok 406 - cast_context_is( src, targ, context, desc ) should pass +ok 407 - cast_context_is( src, targ, context, desc ) should have the proper description +ok 408 - cast_context_is( src, targ, context, desc ) should have the proper diagnostics +ok 409 - cast_context_is( src, targ, context ) should pass +ok 410 - cast_context_is( src, targ, context ) should have the proper description +ok 411 - cast_context_is( src, targ, context ) should have the proper diagnostics +ok 412 - cast_context_is( src, targ, i, desc ) should pass +ok 413 - cast_context_is( src, targ, i, desc ) should have the proper description +ok 414 - cast_context_is( src, targ, i, desc ) should have the proper diagnostics +ok 415 - cast_context_is( src, targ, IMPL, desc ) should pass +ok 416 - cast_context_is( src, targ, IMPL, desc ) should have the proper description +ok 417 - cast_context_is( src, targ, IMPL, desc ) should have the proper diagnostics +ok 418 - cast_context_is( src, targ, assignment, desc ) should pass +ok 419 - cast_context_is( src, targ, assignment, desc ) should have the proper description +ok 420 - cast_context_is( src, targ, assignment, desc ) should have the proper diagnostics +ok 421 - cast_context_is( src, targ, a, desc ) should pass +ok 422 - cast_context_is( src, targ, a, desc ) should have the proper description +ok 423 - cast_context_is( src, targ, a, desc ) should have the proper diagnostics +ok 424 - cast_context_is( src, targ, ASS, desc ) should pass +ok 425 - cast_context_is( src, targ, ASS, desc ) should have the proper description +ok 426 - cast_context_is( src, targ, ASS, desc ) should have the proper diagnostics +ok 427 - cast_context_is( src, targ, explicit, desc ) should pass +ok 428 - cast_context_is( src, targ, explicit, desc ) should have the proper description +ok 429 - cast_context_is( src, targ, explicit, desc ) should have the proper diagnostics +ok 430 - cast_context_is( src, targ, e, desc ) should pass +ok 431 - cast_context_is( src, targ, e, desc ) should have the proper description +ok 432 - cast_context_is( src, targ, e, desc ) should have the proper diagnostics +ok 433 - cast_context_is( src, targ, EX, desc ) should pass +ok 434 - cast_context_is( src, targ, EX, desc ) should have the proper description +ok 435 - cast_context_is( src, targ, EX, desc ) should have the proper diagnostics +ok 436 - cast_context_is( src, targ, context, desc ) fail should fail +ok 437 - cast_context_is( src, targ, context, desc ) fail should have the proper description +ok 438 - cast_context_is( src, targ, context, desc ) fail should have the proper diagnostics +ok 439 - cast_context_is( src, targ, context ) fail should fail +ok 440 - cast_context_is( src, targ, context ) fail should have the proper description +ok 441 - cast_context_is( src, targ, context ) fail should have the proper diagnostics +ok 442 - cast_context_is( src, targ, context, desc ) noexist should fail +ok 443 - cast_context_is( src, targ, context, desc ) noexist should have the proper description +ok 444 - cast_context_is( src, targ, context, desc ) noexist should have the proper diagnostics +ok 445 - has_operator( left, schema, name, right, result, desc ) should pass +ok 446 - has_operator( left, schema, name, right, result, desc ) should have the proper description +ok 447 - has_operator( left, schema, name, right, result, desc ) should have the proper diagnostics +ok 448 - has_operator( left, schema, name, right, result ) should pass +ok 449 - has_operator( left, schema, name, right, result ) should have the proper description +ok 450 - has_operator( left, schema, name, right, result ) should have the proper diagnostics +ok 451 - has_operator( left, name, right, result, desc ) should pass +ok 452 - has_operator( left, name, right, result, desc ) should have the proper description +ok 453 - has_operator( left, name, right, result, desc ) should have the proper diagnostics +ok 454 - has_operator( left, name, right, result ) should pass +ok 455 - has_operator( left, name, right, result ) should have the proper description +ok 456 - has_operator( left, name, right, result ) should have the proper diagnostics +ok 457 - has_operator( left, name, right, desc ) should pass +ok 458 - has_operator( left, name, right, desc ) should have the proper description +ok 459 - has_operator( left, name, right, desc ) should have the proper diagnostics +ok 460 - has_operator( left, name, right ) should pass +ok 461 - has_operator( left, name, right ) should have the proper description +ok 462 - has_operator( left, name, right ) should have the proper diagnostics +ok 463 - has_operator( left, schema, name, right, result, desc ) fail should fail +ok 464 - has_operator( left, schema, name, right, result, desc ) fail should have the proper description +ok 465 - has_operator( left, schema, name, right, result, desc ) fail should have the proper diagnostics +ok 466 - has_operator( left, schema, name, right, result ) fail should fail +ok 467 - has_operator( left, schema, name, right, result ) fail should have the proper description +ok 468 - has_operator( left, schema, name, right, result ) fail should have the proper diagnostics +ok 469 - has_operator( left, name, right, result, desc ) fail should fail +ok 470 - has_operator( left, name, right, result, desc ) fail should have the proper description +ok 471 - has_operator( left, name, right, result, desc ) fail should have the proper diagnostics +ok 472 - has_operator( left, name, right, result ) fail should fail +ok 473 - has_operator( left, name, right, result ) fail should have the proper description +ok 474 - has_operator( left, name, right, result ) fail should have the proper diagnostics +ok 475 - has_operator( left, name, right, desc ) fail should fail +ok 476 - has_operator( left, name, right, desc ) fail should have the proper description +ok 477 - has_operator( left, name, right, desc ) fail should have the proper diagnostics +ok 478 - has_operator( left, name, right ) fail should fail +ok 479 - has_operator( left, name, right ) fail should have the proper description +ok 480 - has_operator( left, name, right ) fail should have the proper diagnostics +ok 481 - has_leftop( schema, name, right, result, desc ) should pass +ok 482 - has_leftop( schema, name, right, result, desc ) should have the proper description +ok 483 - has_leftop( schema, name, right, result, desc ) should have the proper diagnostics +ok 484 - has_leftop( schema, name, right, result ) should pass +ok 485 - has_leftop( schema, name, right, result ) should have the proper description +ok 486 - has_leftop( schema, name, right, result ) should have the proper diagnostics +ok 487 - has_leftop( name, right, result, desc ) should pass +ok 488 - has_leftop( name, right, result, desc ) should have the proper description +ok 489 - has_leftop( name, right, result, desc ) should have the proper diagnostics +ok 490 - has_leftop( name, right, result ) should pass +ok 491 - has_leftop( name, right, result ) should have the proper description +ok 492 - has_leftop( name, right, result ) should have the proper diagnostics +ok 493 - has_leftop( name, right, desc ) should pass +ok 494 - has_leftop( name, right, desc ) should have the proper description +ok 495 - has_leftop( name, right, desc ) should have the proper diagnostics +ok 496 - has_leftop( name, right ) should pass +ok 497 - has_leftop( name, right ) should have the proper description +ok 498 - has_leftop( name, right ) should have the proper diagnostics +ok 499 - has_leftop( schema, name, right, result, desc ) fail should fail +ok 500 - has_leftop( schema, name, right, result, desc ) fail should have the proper description +ok 501 - has_leftop( schema, name, right, result, desc ) fail should have the proper diagnostics +ok 502 - has_leftop( schema, name, right, result ) fail should fail +ok 503 - has_leftop( schema, name, right, result ) fail should have the proper description +ok 504 - has_leftop( schema, name, right, result ) fail should have the proper diagnostics +ok 505 - has_leftop( name, right, result, desc ) fail should fail +ok 506 - has_leftop( name, right, result, desc ) fail should have the proper description +ok 507 - has_leftop( name, right, result, desc ) fail should have the proper diagnostics +ok 508 - has_leftop( name, right, result ) fail should fail +ok 509 - has_leftop( name, right, result ) fail should have the proper description +ok 510 - has_leftop( name, right, result ) fail should have the proper diagnostics +ok 511 - has_leftop( name, right, desc ) fail should fail +ok 512 - has_leftop( name, right, desc ) fail should have the proper description +ok 513 - has_leftop( name, right, desc ) fail should have the proper diagnostics +ok 514 - has_leftop( name, right ) fail should fail +ok 515 - has_leftop( name, right ) fail should have the proper description +ok 516 - has_leftop( name, right ) fail should have the proper diagnostics +ok 517 - has_rightop( left, schema, name, result, desc ) should pass +ok 518 - has_rightop( left, schema, name, result, desc ) should have the proper description +ok 519 - has_rightop( left, schema, name, result, desc ) should have the proper diagnostics +ok 520 - has_rightop( left, schema, name, result ) should pass +ok 521 - has_rightop( left, schema, name, result ) should have the proper description +ok 522 - has_rightop( left, schema, name, result ) should have the proper diagnostics +ok 523 - has_rightop( left, name, result, desc ) should pass +ok 524 - has_rightop( left, name, result, desc ) should have the proper description +ok 525 - has_rightop( left, name, result, desc ) should have the proper diagnostics +ok 526 - has_rightop( left, name, result ) should pass +ok 527 - has_rightop( left, name, result ) should have the proper description +ok 528 - has_rightop( left, name, result ) should have the proper diagnostics +ok 529 - has_rightop( left, name, desc ) should pass +ok 530 - has_rightop( left, name, desc ) should have the proper description +ok 531 - has_rightop( left, name, desc ) should have the proper diagnostics +ok 532 - has_rightop( left, name ) should pass +ok 533 - has_rightop( left, name ) should have the proper description +ok 534 - has_rightop( left, name ) should have the proper diagnostics +ok 535 - has_rightop( left, schema, name, result, desc ) fail should fail +ok 536 - has_rightop( left, schema, name, result, desc ) fail should have the proper description +ok 537 - has_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics +ok 538 - has_rightop( left, schema, name, result ) fail should fail +ok 539 - has_rightop( left, schema, name, result ) fail should have the proper description +ok 540 - has_rightop( left, schema, name, result ) fail should have the proper diagnostics +ok 541 - has_rightop( left, name, result, desc ) fail should fail +ok 542 - has_rightop( left, name, result, desc ) fail should have the proper description +ok 543 - has_rightop( left, name, result, desc ) fail should have the proper diagnostics +ok 544 - has_rightop( left, name, result ) fail should fail +ok 545 - has_rightop( left, name, result ) fail should have the proper description +ok 546 - has_rightop( left, name, result ) fail should have the proper diagnostics +ok 547 - has_rightop( left, name, desc ) fail should fail +ok 548 - has_rightop( left, name, desc ) fail should have the proper description +ok 549 - has_rightop( left, name, desc ) fail should have the proper diagnostics +ok 550 - has_rightop( left, name ) fail should fail +ok 551 - has_rightop( left, name ) fail should have the proper description +ok 552 - has_rightop( left, name ) fail should have the proper diagnostics +ok 553 - has_language(language) should pass +ok 554 - has_language(language) should have the proper description +ok 555 - has_language(language) should have the proper diagnostics +ok 556 - has_language(language, desc) should pass +ok 557 - has_language(language, desc) should have the proper description +ok 558 - has_language(language, desc) should have the proper diagnostics +ok 559 - has_language(nonexistent language) should fail +ok 560 - has_language(nonexistent language) should have the proper description +ok 561 - has_language(nonexistent language) should have the proper diagnostics +ok 562 - has_language(nonexistent language, desc) should fail +ok 563 - has_language(nonexistent language, desc) should have the proper description +ok 564 - has_language(nonexistent language, desc) should have the proper diagnostics +ok 565 - hasnt_language(language) should fail +ok 566 - hasnt_language(language) should have the proper description +ok 567 - hasnt_language(language) should have the proper diagnostics +ok 568 - hasnt_language(language, desc) should fail +ok 569 - hasnt_language(language, desc) should have the proper description +ok 570 - hasnt_language(language, desc) should have the proper diagnostics +ok 571 - hasnt_language(nonexistent language) should pass +ok 572 - hasnt_language(nonexistent language) should have the proper description +ok 573 - hasnt_language(nonexistent language) should have the proper diagnostics +ok 574 - hasnt_language(nonexistent language, desc) should pass +ok 575 - hasnt_language(nonexistent language, desc) should have the proper description +ok 576 - hasnt_language(nonexistent language, desc) should have the proper diagnostics +ok 577 - language_is_trusted(language, desc) should pass +ok 578 - language_is_trusted(language, desc) should have the proper description +ok 579 - language_is_trusted(language, desc) should have the proper diagnostics +ok 580 - language_is_trusted(language) should pass +ok 581 - language_is_trusted(language) should have the proper description +ok 582 - language_is_trusted(language) should have the proper diagnostics +ok 583 - language_is_trusted(language, desc) fail should fail +ok 584 - language_is_trusted(language, desc) fail should have the proper description +ok 585 - language_is_trusted(language, desc) fail should have the proper diagnostics +ok 586 - language_is_trusted(language, desc) non-existent should fail +ok 587 - language_is_trusted(language, desc) non-existent should have the proper description +ok 588 - language_is_trusted(language, desc) non-existent should have the proper diagnostics +ok 589 - has_opclass( schema, name, desc ) should pass +ok 590 - has_opclass( schema, name, desc ) should have the proper description +ok 591 - has_opclass( schema, name, desc ) should have the proper diagnostics +ok 592 - has_opclass( schema, name ) should pass +ok 593 - has_opclass( schema, name ) should have the proper description +ok 594 - has_opclass( schema, name ) should have the proper diagnostics +ok 595 - has_opclass( name, desc ) should pass +ok 596 - has_opclass( name, desc ) should have the proper description +ok 597 - has_opclass( name, desc ) should have the proper diagnostics +ok 598 - has_opclass( name ) should pass +ok 599 - has_opclass( name ) should have the proper description +ok 600 - has_opclass( name ) should have the proper diagnostics +ok 601 - has_opclass( schema, name, desc ) fail should fail +ok 602 - has_opclass( schema, name, desc ) fail should have the proper description +ok 603 - has_opclass( schema, name, desc ) fail should have the proper diagnostics +ok 604 - has_opclass( name, desc ) fail should fail +ok 605 - has_opclass( name, desc ) fail should have the proper description +ok 606 - has_opclass( name, desc ) fail should have the proper diagnostics +ok 607 - hasnt_opclass( schema, name, desc ) should fail +ok 608 - hasnt_opclass( schema, name, desc ) should have the proper description +ok 609 - hasnt_opclass( schema, name, desc ) should have the proper diagnostics +ok 610 - hasnt_opclass( schema, name ) should fail +ok 611 - hasnt_opclass( schema, name ) should have the proper description +ok 612 - hasnt_opclass( schema, name ) should have the proper diagnostics +ok 613 - hasnt_opclass( name, desc ) should fail +ok 614 - hasnt_opclass( name, desc ) should have the proper description +ok 615 - hasnt_opclass( name, desc ) should have the proper diagnostics +ok 616 - hasnt_opclass( name ) should fail +ok 617 - hasnt_opclass( name ) should have the proper description +ok 618 - hasnt_opclass( name ) should have the proper diagnostics +ok 619 - hasnt_opclass( schema, name, desc ) fail should pass +ok 620 - hasnt_opclass( schema, name, desc ) fail should have the proper description +ok 621 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnostics +ok 622 - hasnt_opclass( name, desc ) fail should pass +ok 623 - hasnt_opclass( name, desc ) fail should have the proper description +ok 624 - hasnt_opclass( name, desc ) fail should have the proper diagnostics +ok 625 - domain_type_is(schema, domain, schema, type, desc) should pass +ok 626 - domain_type_is(schema, domain, schema, type, desc) should have the proper description +ok 627 - domain_type_is(schema, domain, schema, type, desc) should have the proper diagnostics +ok 628 - domain_type_is(schema, domain, schema, type) should pass +ok 629 - domain_type_is(schema, domain, schema, type) should have the proper description +ok 630 - domain_type_is(schema, domain, schema, type) should have the proper diagnostics +ok 631 - domain_type_is(schema, domain, schema, type, desc) fail should fail +ok 632 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper description +ok 633 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 634 - domain_type_is(schema, nondomain, schema, type, desc) should fail +ok 635 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper description +ok 636 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 637 - domain_type_is(schema, type, schema, type, desc) fail should fail +ok 638 - domain_type_is(schema, type, schema, type, desc) fail should have the proper description +ok 639 - domain_type_is(schema, type, schema, type, desc) fail should have the proper diagnostics +ok 640 - domain_type_is(schema, domain, type, desc) should pass +ok 641 - domain_type_is(schema, domain, type, desc) should have the proper description +ok 642 - domain_type_is(schema, domain, type, desc) should have the proper diagnostics +ok 643 - domain_type_is(schema, domain, type) should pass +ok 644 - domain_type_is(schema, domain, type) should have the proper description +ok 645 - domain_type_is(schema, domain, type) should have the proper diagnostics +ok 646 - domain_type_is(schema, domain, type, desc) fail should fail +ok 647 - domain_type_is(schema, domain, type, desc) fail should have the proper description +ok 648 - domain_type_is(schema, domain, type, desc) fail should have the proper diagnostics +ok 649 - domain_type_is(schema, nondomain, type, desc) should fail +ok 650 - domain_type_is(schema, nondomain, type, desc) should have the proper description +ok 651 - domain_type_is(schema, nondomain, type, desc) should have the proper diagnostics +ok 652 - domain_type_is(schema, type, type, desc) fail should fail +ok 653 - domain_type_is(schema, type, type, desc) fail should have the proper description +ok 654 - domain_type_is(schema, type, type, desc) fail should have the proper diagnostics +ok 655 - domain_type_is(domain, type, desc) should pass +ok 656 - domain_type_is(domain, type, desc) should have the proper description +ok 657 - domain_type_is(domain, type, desc) should have the proper diagnostics +ok 658 - domain_type_is(domain, type) should pass +ok 659 - domain_type_is(domain, type) should have the proper description +ok 660 - domain_type_is(domain, type) should have the proper diagnostics +ok 661 - domain_type_is(domain, type, desc) fail should fail +ok 662 - domain_type_is(domain, type, desc) fail should have the proper description +ok 663 - domain_type_is(domain, type, desc) fail should have the proper diagnostics +ok 664 - domain_type_is(nondomain, type, desc) should fail +ok 665 - domain_type_is(nondomain, type, desc) should have the proper description +ok 666 - domain_type_is(nondomain, type, desc) should have the proper diagnostics +ok 667 - domain_type_is(type, type, desc) fail should fail +ok 668 - domain_type_is(type, type, desc) fail should have the proper description +ok 669 - domain_type_is(type, type, desc) fail should have the proper diagnostics +ok 670 - domain_type_isnt(schema, domain, schema, type, desc) should pass +ok 671 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper description +ok 672 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper diagnostics +ok 673 - domain_type_isnt(schema, domain, schema, type) should pass +ok 674 - domain_type_isnt(schema, domain, schema, type) should have the proper description +ok 675 - domain_type_isnt(schema, domain, schema, type) should have the proper diagnostics +ok 676 - domain_type_isnt(schema, domain, schema, type, desc) fail should fail +ok 677 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper description +ok 678 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 679 - domain_type_isnt(schema, nondomain, schema, type, desc) should fail +ok 680 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper description +ok 681 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 682 - domain_type_isnt(schema, type, schema, type, desc) should fail +ok 683 - domain_type_isnt(schema, type, schema, type, desc) should have the proper description +ok 684 - domain_type_isnt(schema, type, schema, type, desc) should have the proper diagnostics +ok 685 - domain_type_isnt(schema, domain, type, desc) should pass +ok 686 - domain_type_isnt(schema, domain, type, desc) should have the proper description +ok 687 - domain_type_isnt(schema, domain, type, desc) should have the proper diagnostics +ok 688 - domain_type_isnt(schema, domain, type) should pass +ok 689 - domain_type_isnt(schema, domain, type) should have the proper description +ok 690 - domain_type_isnt(schema, domain, type) should have the proper diagnostics +ok 691 - domain_type_isnt(schema, domain, type, desc) fail should fail +ok 692 - domain_type_isnt(schema, domain, type, desc) fail should have the proper description +ok 693 - domain_type_isnt(schema, domain, type, desc) fail should have the proper diagnostics +ok 694 - domain_type_isnt(schema, nondomain, type, desc) should fail +ok 695 - domain_type_isnt(schema, nondomain, type, desc) should have the proper description +ok 696 - domain_type_isnt(schema, nondomain, type, desc) should have the proper diagnostics +ok 697 - domain_type_isnt(schema, type, type, desc) should fail +ok 698 - domain_type_isnt(schema, type, type, desc) should have the proper description +ok 699 - domain_type_isnt(schema, type, type, desc) should have the proper diagnostics +ok 700 - domain_type_isnt(domain, type, desc) should pass +ok 701 - domain_type_isnt(domain, type, desc) should have the proper description +ok 702 - domain_type_isnt(domain, type, desc) should have the proper diagnostics +ok 703 - domain_type_isnt(domain, type) should pass +ok 704 - domain_type_isnt(domain, type) should have the proper description +ok 705 - domain_type_isnt(domain, type) should have the proper diagnostics +ok 706 - domain_type_isnt(domain, type, desc) fail should fail +ok 707 - domain_type_isnt(domain, type, desc) fail should have the proper description +ok 708 - domain_type_isnt(domain, type, desc) fail should have the proper diagnostics +ok 709 - domain_type_isnt(nondomain, type, desc) should fail +ok 710 - domain_type_isnt(nondomain, type, desc) should have the proper description +ok 711 - domain_type_isnt(nondomain, type, desc) should have the proper diagnostics +ok 712 - domain_type_isnt(type, type, desc) should fail +ok 713 - domain_type_isnt(type, type, desc) should have the proper description +ok 714 - domain_type_isnt(type, type, desc) should have the proper diagnostics +ok 715 - has_foreign_table(non-existent table) should fail +ok 716 - has_foreign_table(non-existent table) should have the proper description +ok 717 - has_foreign_table(non-existent table) should have the proper diagnostics +ok 718 - has_foreign_table(non-existent schema, tab) should fail +ok 719 - has_foreign_table(non-existent schema, tab) should have the proper description +ok 720 - has_foreign_table(non-existent schema, tab) should have the proper diagnostics +ok 721 - has_foreign_table(sch, non-existent table, desc) should fail +ok 722 - has_foreign_table(sch, non-existent table, desc) should have the proper description +ok 723 - has_foreign_table(sch, non-existent table, desc) should have the proper diagnostics +ok 724 - has_foreign_table(tab, desc) should pass +ok 725 - has_foreign_table(tab, desc) should have the proper description +ok 726 - has_foreign_table(tab, desc) should have the proper diagnostics +ok 727 - has_foreign_table(sch, tab, desc) should pass +ok 728 - has_foreign_table(sch, tab, desc) should have the proper description +ok 729 - has_foreign_table(sch, tab, desc) should have the proper diagnostics +ok 730 - has_foreign_table(sch, view, desc) should fail +ok 731 - has_foreign_table(sch, view, desc) should have the proper description +ok 732 - has_foreign_table(sch, view, desc) should have the proper diagnostics +ok 733 - has_foreign_table(type, desc) should fail +ok 734 - has_foreign_table(type, desc) should have the proper description +ok 735 - has_foreign_table(type, desc) should have the proper diagnostics +ok 736 - hasnt_foreign_table(non-existent table) should pass +ok 737 - hasnt_foreign_table(non-existent table) should have the proper description +ok 738 - hasnt_foreign_table(non-existent table) should have the proper diagnostics +ok 739 - hasnt_foreign_table(non-existent schema, tab) should pass +ok 740 - hasnt_foreign_table(non-existent schema, tab) should have the proper description +ok 741 - hasnt_foreign_table(sch, non-existent tab, desc) should pass +ok 742 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper description +ok 743 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper diagnostics +ok 744 - hasnt_foreign_table(tab, desc) should fail +ok 745 - hasnt_foreign_table(tab, desc) should have the proper description +ok 746 - hasnt_foreign_table(tab, desc) should have the proper diagnostics +ok 747 - hasnt_foreign_table(sch, tab, desc) should fail +ok 748 - hasnt_foreign_table(sch, tab, desc) should have the proper description +ok 749 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index af8fbd760b7a..be5119355466 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(713); +SELECT plan(749); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -437,6 +437,108 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test has_composite(). + +SELECT * FROM check_test( + has_composite( '__SDFSDFD__' ), + false, + 'has_composite(non-existent composite type)', + 'Composite type "__SDFSDFD__" should exist', + '' +); + +SELECT * FROM check_test( + has_composite( '__SDFSDFD__', 'lol' ), + false, + 'has_composite(non-existent schema, tab)', + 'lol', + '' +); + +SELECT * FROM check_test( + has_composite( 'foo', '__SDFSDFD__', 'desc' ), + false, + 'has_composite(sch, non-existent composite type, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + has_composite( 'sometype', 'lol' ), + true, + 'has_composite(tab, desc)', + 'lol', + '' +); + +SELECT * FROM check_test( + has_composite( 'public', 'sometype', 'desc' ), + true, + 'has_composite(sch, tab, desc)', + 'desc', + '' +); + +-- It should ignore views and tables. +SELECT * FROM check_test( + has_composite( 'pg_catalog', 'pg_composites', 'desc' ), + false, + 'has_composite(sch, view, desc)', + 'desc', + '' +); +SELECT * FROM check_test( + has_composite( 'sometab', 'desc' ), + false, + 'has_composite(type, desc)', + 'desc', + '' +); + +/****************************************************************************/ +-- Test hasnt_composite(). + +SELECT * FROM check_test( + hasnt_composite( '__SDFSDFD__' ), + true, + 'hasnt_composite(non-existent composite type)', + 'Composite type "__SDFSDFD__" should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_composite( '__SDFSDFD__', 'lol' ), + true, + 'hasnt_composite(non-existent schema, tab)', + 'lol', + '' +); + +SELECT * FROM check_test( + hasnt_composite( 'foo', '__SDFSDFD__', 'desc' ), + true, + 'hasnt_composite(sch, non-existent tab, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_composite( 'sometype', 'lol' ), + false, + 'hasnt_composite(tab, desc)', + 'lol', + '' +); + +SELECT * FROM check_test( + hasnt_composite( 'public', 'sometype', 'desc' ), + false, + 'hasnt_composite(sch, tab, desc)', + 'desc', + '' +); + /****************************************************************************/ -- Test has_type(). SELECT * FROM check_test( From ea2ef1580b909c03beead57b7c80b9c2920f04d4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 12:02:10 -0800 Subject: [PATCH 0682/1195] Add `rel_owner_is()`. --- Changes | 1 + doc/pgtap.mmd | 42 ++++++++++ sql/pgtap--0.91.0--0.92.0.sql | 70 ++++++++++++++++ sql/pgtap.sql.in | 69 ++++++++++++++++ test/expected/ownership.out | 44 +++++++++- test/sql/ownership.sql | 146 ++++++++++++++++++++++++++++++++-- 6 files changed, 366 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index 202a4f9505b5..53329a258b57 100644 --- a/Changes +++ b/Changes @@ -9,6 +9,7 @@ Revision history for pgTAP Thanks to Henk Enting for the report. * Added `has_foreign_table()` and `hasnt_foreign_table()`. * Added `has_composite()` and `hasnt_composite()`. +* Added `rel_owner_is()`. 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index b0b0d53a53bf..f3ef4dd888fc 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5140,6 +5140,48 @@ diagnostics will look something like: # have: postgres # want: root +### `rel_owner_is ()` ### + + SELECT rel_owner_is ( :schema, :relation, :user, :description ); + SELECT rel_owner_is ( :relation, :user, :description ); + SELECT rel_owner_is ( :schema, :relation, :user ); + SELECT rel_owner_is ( :relation, :user ); + +**Parameters** + +`:schema` +: Name of a schema in which to the `:relation`. + +`:relation` +: Name of a relation. + +`:user` +: Name of a user. + +`:description` +: A short description of the test. + +Tests the ownership of a relation. Relations are tables, views, seqences, +composite types, foreign tables, and toast tables. If the `:description` +argument is omitted, an appropriate description will be created. Examples: + + SELECT rel_owner_is( 'public', 'mytable', 'someuser', 'mytable should be owned by someuser' ); + SELECT rel_owner_is( current_schema(), 'mysequence', current_user ); + +In the event that the test fails because the relation in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Relation foo should be owned by www" + # Relation foo does not exist + +If the test fails because the relation is not owned by the specified user, the +diagnostics will look something like: + + # Failed test 17: "Relation bar should be owned by root" + # have: postgres + # want: root + No Test for the Wicked ====================== diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index 498401d1ca78..f1c3302683f7 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -84,3 +84,73 @@ 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 _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; + +-- rel_owner_is ( schema, relation, user, description ) +CREATE OR REPLACE FUNCTION rel_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; + +-- rel_owner_is ( schema, relation, user ) +CREATE OR REPLACE FUNCTION rel_owner_is ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT rel_owner_is( + $1, $2, $3, + 'Relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) + ); +$$ LANGUAGE sql; + +-- rel_owner_is ( relation, user, description ) +CREATE OR REPLACE FUNCTION rel_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; + +-- rel_owner_is ( relation, user ) +CREATE OR REPLACE FUNCTION rel_owner_is ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT rel_owner_is( + $1, $2, + 'Relation ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) + ); +$$ LANGUAGE sql; + diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 47e77acca2ea..da4aa3e97986 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7483,3 +7483,72 @@ RETURNS TEXT AS $$ ); $$ 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; + +-- rel_owner_is ( schema, relation, user, description ) +CREATE OR REPLACE FUNCTION rel_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; + +-- rel_owner_is ( schema, relation, user ) +CREATE OR REPLACE FUNCTION rel_owner_is ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT rel_owner_is( + $1, $2, $3, + 'Relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) + ); +$$ LANGUAGE sql; + +-- rel_owner_is ( relation, user, description ) +CREATE OR REPLACE FUNCTION rel_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; + +-- rel_owner_is ( relation, user ) +CREATE OR REPLACE FUNCTION rel_owner_is ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT rel_owner_is( + $1, $2, + 'Relation ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) + ); +$$ LANGUAGE sql; + diff --git a/test/expected/ownership.out b/test/expected/ownership.out index 188b35699222..a5eb52abf612 100644 --- a/test/expected/ownership.out +++ b/test/expected/ownership.out @@ -1,5 +1,5 @@ \unset ECHO -1..12 +1..54 ok 1 - db_owner_is(db, user, desc) should pass ok 2 - db_owner_is(db, user, desc) should have the proper description ok 3 - db_owner_is(db, user, desc) should have the proper diagnostics @@ -12,3 +12,45 @@ ok 9 - db_owner_is(non-db, user) should have the proper diagnostics ok 10 - db_owner_is(db, non-user) should fail ok 11 - db_owner_is(db, non-user) should have the proper description ok 12 - db_owner_is(db, non-user) should have the proper diagnostics +ok 13 - rel_owner_is(sch, tab, user, desc) should pass +ok 14 - rel_owner_is(sch, tab, user, desc) should have the proper description +ok 15 - rel_owner_is(sch, tab, user, desc) should have the proper diagnostics +ok 16 - rel_owner_is(sch, tab, user) should pass +ok 17 - rel_owner_is(sch, tab, user) should have the proper description +ok 18 - rel_owner_is(sch, tab, user) should have the proper diagnostics +ok 19 - rel_owner_is(non-sch, tab, user) should fail +ok 20 - rel_owner_is(non-sch, tab, user) should have the proper description +ok 21 - rel_owner_is(non-sch, tab, user) should have the proper diagnostics +ok 22 - rel_owner_is(sch, non-tab, user) should fail +ok 23 - rel_owner_is(sch, non-tab, user) should have the proper description +ok 24 - rel_owner_is(sch, non-tab, user) should have the proper diagnostics +ok 25 - rel_owner_is(tab, user, desc) should pass +ok 26 - rel_owner_is(tab, user, desc) should have the proper description +ok 27 - rel_owner_is(tab, user, desc) should have the proper diagnostics +ok 28 - rel_owner_is(tab, user) should pass +ok 29 - rel_owner_is(tab, user) should have the proper description +ok 30 - rel_owner_is(tab, user) should have the proper diagnostics +ok 31 - rel_owner_is(non-tab, user) should fail +ok 32 - rel_owner_is(non-tab, user) should have the proper description +ok 33 - rel_owner_is(non-tab, user) should have the proper diagnostics +ok 34 - rel_owner_is(sch, seq, user, desc) should pass +ok 35 - rel_owner_is(sch, seq, user, desc) should have the proper description +ok 36 - rel_owner_is(sch, seq, user, desc) should have the proper diagnostics +ok 37 - rel_owner_is(sch, seq, user) should pass +ok 38 - rel_owner_is(sch, seq, user) should have the proper description +ok 39 - rel_owner_is(sch, seq, user) should have the proper diagnostics +ok 40 - rel_owner_is(non-sch, seq, user) should fail +ok 41 - rel_owner_is(non-sch, seq, user) should have the proper description +ok 42 - rel_owner_is(non-sch, seq, user) should have the proper diagnostics +ok 43 - rel_owner_is(sch, non-seq, user) should fail +ok 44 - rel_owner_is(sch, non-seq, user) should have the proper description +ok 45 - rel_owner_is(sch, non-seq, user) should have the proper diagnostics +ok 46 - rel_owner_is(seq, user, desc) should pass +ok 47 - rel_owner_is(seq, user, desc) should have the proper description +ok 48 - rel_owner_is(seq, user, desc) should have the proper diagnostics +ok 49 - rel_owner_is(seq, user) should pass +ok 50 - rel_owner_is(seq, user) should have the proper description +ok 51 - rel_owner_is(seq, user) should have the proper diagnostics +ok 52 - rel_owner_is(non-seq, user) should fail +ok 53 - rel_owner_is(non-seq, user) should have the proper description +ok 54 - rel_owner_is(non-seq, user) should have the proper diagnostics diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 15d515bb7434..10dfb18cf70f 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -1,11 +1,31 @@ \unset ECHO \i test/setup.sql -SELECT plan(12); +SELECT plan(54); --SELECT * FROM no_plan(); +-- This will be rolled back. :-) +SET client_min_messages = warning; +CREATE TABLE public.sometab( + id INT NOT NULL PRIMARY KEY, + name TEXT DEFAULT '', + numb NUMERIC(10, 2), + "myInt" NUMERIC(8) +); + +CREATE TYPE public.sometype AS ( + id INT, + name TEXT +); + +CREATE SEQUENCE public.someseq; + +CREATE SCHEMA someschema; + +RESET client_min_messages; + /****************************************************************************/ -SELECT * From check_test( +SELECT * FROM check_test( db_owner_is(current_database(), current_user, 'mumble'), true, 'db_owner_is(db, user, desc)', @@ -13,7 +33,7 @@ SELECT * From check_test( '' ); -SELECT * From check_test( +SELECT * FROM check_test( db_owner_is(current_database(), current_user), true, 'db_owner_is(db, user)', @@ -21,7 +41,7 @@ SELECT * From check_test( '' ); -SELECT * From check_test( +SELECT * FROM check_test( db_owner_is('__not__' || current_database(), current_user, 'mumble'), false, 'db_owner_is(non-db, user)', @@ -29,7 +49,7 @@ SELECT * From check_test( ' Database __not__' || current_database() || ' does not exist' ); -SELECT * From check_test( +SELECT * FROM check_test( db_owner_is(current_database(), '__not__' || current_user, 'mumble'), false, 'db_owner_is(db, non-user)', @@ -38,6 +58,122 @@ SELECT * From check_test( want: __not__' || current_user ); +/****************************************************************************/ +-- Test rel_owner_is() with a table. +SELECT * FROM check_test( + rel_owner_is('public', 'sometab', current_user, 'mumble'), + true, + 'rel_owner_is(sch, tab, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + rel_owner_is('public', 'sometab', current_user), + true, + 'rel_owner_is(sch, tab, user)', + 'Relation public.sometab should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + rel_owner_is('__not__public', 'sometab', current_user, 'mumble'), + false, + 'rel_owner_is(non-sch, tab, user)', + 'mumble', + ' Relation __not__public.sometab does not exist' +); + +SELECT * FROM check_test( + rel_owner_is('public', '__not__sometab', current_user, 'mumble'), + false, + 'rel_owner_is(sch, non-tab, user)', + 'mumble', + ' Relation public.__not__sometab does not exist' +); + +SELECT * FROM check_test( + rel_owner_is('sometab', current_user, 'mumble'), + true, + 'rel_owner_is(tab, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + rel_owner_is('sometab', current_user), + true, + 'rel_owner_is(tab, user)', + 'Relation sometab should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + rel_owner_is('__not__sometab', current_user, 'mumble'), + false, + 'rel_owner_is(non-tab, user)', + 'mumble', + ' Relation __not__sometab does not exist' +); + +/****************************************************************************/ +-- Test rel_owner_is() with a schema. +SELECT * FROM check_test( + rel_owner_is('public', 'someseq', current_user, 'mumble'), + true, + 'rel_owner_is(sch, seq, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + rel_owner_is('public', 'someseq', current_user), + true, + 'rel_owner_is(sch, seq, user)', + 'Relation public.someseq should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + rel_owner_is('__not__public', 'someseq', current_user, 'mumble'), + false, + 'rel_owner_is(non-sch, seq, user)', + 'mumble', + ' Relation __not__public.someseq does not exist' +); + +SELECT * FROM check_test( + rel_owner_is('public', '__not__someseq', current_user, 'mumble'), + false, + 'rel_owner_is(sch, non-seq, user)', + 'mumble', + ' Relation public.__not__someseq does not exist' +); + +SELECT * FROM check_test( + rel_owner_is('someseq', current_user, 'mumble'), + true, + 'rel_owner_is(seq, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + rel_owner_is('someseq', current_user), + true, + 'rel_owner_is(seq, user)', + 'Relation someseq should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + rel_owner_is('__not__someseq', current_user, 'mumble'), + false, + 'rel_owner_is(non-seq, user)', + 'mumble', + ' Relation __not__someseq does not exist' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 3a7de002db837e8367de5bf035fb253791e0b0c4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 13:32:03 -0800 Subject: [PATCH 0683/1195] Add `table_owner_is()`. --- Changes | 2 +- doc/pgtap.mmd | 43 ++++++++++++++++++- sql/pgtap--0.91.0--0.92.0.sql | 70 +++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 71 +++++++++++++++++++++++++++++++ test/expected/ownership.out | 29 ++++++++++++- test/sql/ownership.sql | 78 ++++++++++++++++++++++++++++++++++- 6 files changed, 289 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 53329a258b57..fb0be8edfd6e 100644 --- a/Changes +++ b/Changes @@ -9,7 +9,7 @@ Revision history for pgTAP Thanks to Henk Enting for the report. * Added `has_foreign_table()` and `hasnt_foreign_table()`. * Added `has_composite()` and `hasnt_composite()`. -* Added `rel_owner_is()`. +* Added `rel_owner_is()` and `table_owner_is()`. 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index f3ef4dd888fc..07ec58ebfeee 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5150,7 +5150,7 @@ diagnostics will look something like: **Parameters** `:schema` -: Name of a schema in which to the `:relation`. +: Name of a schema in which find to the `:relation`. `:relation` : Name of a relation. @@ -5182,6 +5182,47 @@ diagnostics will look something like: # have: postgres # want: root +### `table_owner_is ()` ### + + SELECT table_owner_is ( :schema, :table, :user, :description ); + SELECT table_owner_is ( :table, :user, :description ); + SELECT table_owner_is ( :schema, :table, :user ); + SELECT table_owner_is ( :table, :user ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the `:table`. + +`:table` +: Name of a table. + +`:user` +: Name of a user. + +`:description` +: A short description of the test. + +Tests the ownership of a table. If the `:description` argument is omitted, an +appropriate description will be created. Examples: + + SELECT table_owner_is( 'public', 'mytable', 'someuser', 'mytable should be owned by someuser' ); + SELECT table_owner_is( 'widgets', current_user ); + +In the event that the test fails because the table in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Table foo should be owned by www" + # Table foo does not exist + +If the test fails because the table is not owned by the specified user, the +diagnostics will look something like: + + # Failed test 17: "Table bar should be owned by root" + # have: postgres + # want: root + No Test for the Wicked ====================== diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index f1c3302683f7..6e4fff5b2350 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -154,3 +154,73 @@ RETURNS TEXT AS $$ ); $$ 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 = $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 = $1 + AND c.relname = $2 + AND pg_catalog.pg_table_is_visible(c.oid) +$$ 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'::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'::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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index da4aa3e97986..e3667855fb47 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7552,3 +7552,74 @@ RETURNS TEXT AS $$ ); $$ 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 = $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 = $1 + AND c.relname = $2 + AND pg_catalog.pg_table_is_visible(c.oid) +$$ 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'::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'::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; + diff --git a/test/expected/ownership.out b/test/expected/ownership.out index a5eb52abf612..db5776c4dfa7 100644 --- a/test/expected/ownership.out +++ b/test/expected/ownership.out @@ -1,5 +1,5 @@ \unset ECHO -1..54 +1..81 ok 1 - db_owner_is(db, user, desc) should pass ok 2 - db_owner_is(db, user, desc) should have the proper description ok 3 - db_owner_is(db, user, desc) should have the proper diagnostics @@ -54,3 +54,30 @@ ok 51 - rel_owner_is(seq, user) should have the proper diagnostics ok 52 - rel_owner_is(non-seq, user) should fail ok 53 - rel_owner_is(non-seq, user) should have the proper description ok 54 - rel_owner_is(non-seq, user) should have the proper diagnostics +ok 55 - table_owner_is(sch, tab, user, desc) should pass +ok 56 - table_owner_is(sch, tab, user, desc) should have the proper description +ok 57 - table_owner_is(sch, tab, user, desc) should have the proper diagnostics +ok 58 - table_owner_is(sch, tab, user) should pass +ok 59 - table_owner_is(sch, tab, user) should have the proper description +ok 60 - table_owner_is(sch, tab, user) should have the proper diagnostics +ok 61 - table_owner_is(non-sch, tab, user) should fail +ok 62 - table_owner_is(non-sch, tab, user) should have the proper description +ok 63 - table_owner_is(non-sch, tab, user) should have the proper diagnostics +ok 64 - table_owner_is(sch, non-tab, user) should fail +ok 65 - table_owner_is(sch, non-tab, user) should have the proper description +ok 66 - table_owner_is(sch, non-tab, user) should have the proper diagnostics +ok 67 - table_owner_is(tab, user, desc) should pass +ok 68 - table_owner_is(tab, user, desc) should have the proper description +ok 69 - table_owner_is(tab, user, desc) should have the proper diagnostics +ok 70 - table_owner_is(tab, user) should pass +ok 71 - table_owner_is(tab, user) should have the proper description +ok 72 - table_owner_is(tab, user) should have the proper diagnostics +ok 73 - table_owner_is(non-tab, user) should fail +ok 74 - table_owner_is(non-tab, user) should have the proper description +ok 75 - table_owner_is(non-tab, user) should have the proper diagnostics +ok 76 - table_owner_is(sch, seq, user, desc) should fail +ok 77 - table_owner_is(sch, seq, user, desc) should have the proper description +ok 78 - table_owner_is(sch, seq, user, desc) should have the proper diagnostics +ok 79 - table_owner_is(seq, user, desc) should fail +ok 80 - table_owner_is(seq, user, desc) should have the proper description +ok 81 - table_owner_is(seq, user, desc) should have the proper diagnostics diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 10dfb18cf70f..909f009b93a7 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(54); +SELECT plan(81); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -174,6 +174,82 @@ SELECT * FROM check_test( ' Relation __not__someseq does not exist' ); +/****************************************************************************/ +-- Test table_owner_is() with a table. +SELECT * FROM check_test( + table_owner_is('public', 'sometab', current_user, 'mumble'), + true, + 'table_owner_is(sch, tab, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + table_owner_is('public', 'sometab', current_user), + true, + 'table_owner_is(sch, tab, user)', + 'Table public.sometab should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + table_owner_is('__not__public', 'sometab', current_user, 'mumble'), + false, + 'table_owner_is(non-sch, tab, user)', + 'mumble', + ' Table __not__public.sometab does not exist' +); + +SELECT * FROM check_test( + table_owner_is('public', '__not__sometab', current_user, 'mumble'), + false, + 'table_owner_is(sch, non-tab, user)', + 'mumble', + ' Table public.__not__sometab does not exist' +); + +SELECT * FROM check_test( + table_owner_is('sometab', current_user, 'mumble'), + true, + 'table_owner_is(tab, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + table_owner_is('sometab', current_user), + true, + 'table_owner_is(tab, user)', + 'Table sometab should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + table_owner_is('__not__sometab', current_user, 'mumble'), + false, + 'table_owner_is(non-tab, user)', + 'mumble', + ' Table __not__sometab does not exist' +); + +-- It should ignore the sequence. +SELECT * FROM check_test( + table_owner_is('public', 'someseq', current_user, 'mumble'), + false, + 'table_owner_is(sch, seq, user, desc)', + 'mumble', + ' Table public.someseq does not exist' +); + +SELECT * FROM check_test( + table_owner_is('someseq', current_user, 'mumble'), + false, + 'table_owner_is(seq, user, desc)', + 'mumble', + ' Table someseq does not exist' +); + + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From d306a81aa2e5fb8e007af042001615ffd0c6441e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 13:32:29 -0800 Subject: [PATCH 0684/1195] Ignore .orig. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1b561faa67fc..bea6d1aaac2c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ bbin /sql/pgtap--?.??.?.sql /sql/pgtap-core--* /sql/pgtap-schema--* +*.sql.orig From d10a25ba0d74edb68cbf49128396051dfb944558 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 13:40:05 -0800 Subject: [PATCH 0685/1195] Add `view_owner_is()`. --- Changes | 5 ++- doc/pgtap.mmd | 41 ++++++++++++++++++ sql/pgtap--0.91.0--0.92.0.sql | 52 ++++++++++++++++++++++ sql/pgtap.sql.in | 52 ++++++++++++++++++++++ test/expected/ownership.out | 29 ++++++++++++- test/sql/ownership.sql | 81 ++++++++++++++++++++++++++++++++++- 6 files changed, 256 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index fb0be8edfd6e..e7b3b3b31ed0 100644 --- a/Changes +++ b/Changes @@ -9,7 +9,10 @@ Revision history for pgTAP Thanks to Henk Enting for the report. * Added `has_foreign_table()` and `hasnt_foreign_table()`. * Added `has_composite()` and `hasnt_composite()`. -* Added `rel_owner_is()` and `table_owner_is()`. +* Added new ownership assertion functions: + + `rel_owner_is()` + + `table_owner_is()` + + `view_owner_is()` 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 07ec58ebfeee..ee52581c9e65 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5223,6 +5223,47 @@ diagnostics will look something like: # have: postgres # want: root +### `view_owner_is ()` ### + + SELECT view_owner_is ( :schema, :view, :user, :description ); + SELECT view_owner_is ( :view, :user, :description ); + SELECT view_owner_is ( :schema, :view, :user ); + SELECT view_owner_is ( :view, :user ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the `:view`. + +`:view` +: Name of a view. + +`:user` +: Name of a user. + +`:description` +: A short description of the test. + +Tests the ownership of a view. If the `:description` argument is omitted, an +appropriate description will be created. Examples: + + SELECT view_owner_is( 'public', 'myview', 'someuser', 'myview should be owned by someuser' ); + SELECT view_owner_is( 'widgets', current_user ); + +In the event that the test fails because the view in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "View foo should be owned by www" + # View foo does not exist + +If the test fails because the view is not owned by the specified user, the +diagnostics will look something like: + + # Failed test 17: "View bar should be owned by root" + # have: postgres + # want: root + No Test for the Wicked ====================== diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index 6e4fff5b2350..4b054c573c16 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -224,3 +224,55 @@ RETURNS TEXT AS $$ '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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index e3667855fb47..423244068335 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7623,3 +7623,55 @@ RETURNS TEXT AS $$ ); $$ 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; + diff --git a/test/expected/ownership.out b/test/expected/ownership.out index db5776c4dfa7..7df81de96bf1 100644 --- a/test/expected/ownership.out +++ b/test/expected/ownership.out @@ -1,5 +1,5 @@ \unset ECHO -1..81 +1..108 ok 1 - db_owner_is(db, user, desc) should pass ok 2 - db_owner_is(db, user, desc) should have the proper description ok 3 - db_owner_is(db, user, desc) should have the proper diagnostics @@ -81,3 +81,30 @@ ok 78 - table_owner_is(sch, seq, user, desc) should have the proper diagnostics ok 79 - table_owner_is(seq, user, desc) should fail ok 80 - table_owner_is(seq, user, desc) should have the proper description ok 81 - table_owner_is(seq, user, desc) should have the proper diagnostics +ok 82 - view_owner_is(sch, view, user, desc) should pass +ok 83 - view_owner_is(sch, view, user, desc) should have the proper description +ok 84 - view_owner_is(sch, view, user, desc) should have the proper diagnostics +ok 85 - view_owner_is(sch, view, user) should pass +ok 86 - view_owner_is(sch, view, user) should have the proper description +ok 87 - view_owner_is(sch, view, user) should have the proper diagnostics +ok 88 - view_owner_is(non-sch, view, user) should fail +ok 89 - view_owner_is(non-sch, view, user) should have the proper description +ok 90 - view_owner_is(non-sch, view, user) should have the proper diagnostics +ok 91 - view_owner_is(sch, non-view, user) should fail +ok 92 - view_owner_is(sch, non-view, user) should have the proper description +ok 93 - view_owner_is(sch, non-view, user) should have the proper diagnostics +ok 94 - view_owner_is(view, user, desc) should pass +ok 95 - view_owner_is(view, user, desc) should have the proper description +ok 96 - view_owner_is(view, user, desc) should have the proper diagnostics +ok 97 - view_owner_is(view, user) should pass +ok 98 - view_owner_is(view, user) should have the proper description +ok 99 - view_owner_is(view, user) should have the proper diagnostics +ok 100 - view_owner_is(non-view, user) should fail +ok 101 - view_owner_is(non-view, user) should have the proper description +ok 102 - view_owner_is(non-view, user) should have the proper diagnostics +ok 103 - view_owner_is(sch, seq, user, desc) should fail +ok 104 - view_owner_is(sch, seq, user, desc) should have the proper description +ok 105 - view_owner_is(sch, seq, user, desc) should have the proper diagnostics +ok 106 - view_owner_is(seq, user, desc) should fail +ok 107 - view_owner_is(seq, user, desc) should have the proper description +ok 108 - view_owner_is(seq, user, desc) should have the proper diagnostics diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 909f009b93a7..c536e71fffda 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(81); +SELECT plan(108); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -13,6 +13,8 @@ CREATE TABLE public.sometab( "myInt" NUMERIC(8) ); +CREATE VIEW public.someview AS SELECT * FROM public.sometab; + CREATE TYPE public.sometype AS ( id INT, name TEXT @@ -175,7 +177,7 @@ SELECT * FROM check_test( ); /****************************************************************************/ --- Test table_owner_is() with a table. +-- Test table_owner_is(). SELECT * FROM check_test( table_owner_is('public', 'sometab', current_user, 'mumble'), true, @@ -249,6 +251,81 @@ SELECT * FROM check_test( ' Table someseq does not exist' ); +/****************************************************************************/ +-- Test view_owner_is(). +SELECT * FROM check_test( + view_owner_is('public', 'someview', current_user, 'mumble'), + true, + 'view_owner_is(sch, view, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + view_owner_is('public', 'someview', current_user), + true, + 'view_owner_is(sch, view, user)', + 'View public.someview should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + view_owner_is('__not__public', 'someview', current_user, 'mumble'), + false, + 'view_owner_is(non-sch, view, user)', + 'mumble', + ' View __not__public.someview does not exist' +); + +SELECT * FROM check_test( + view_owner_is('public', '__not__someview', current_user, 'mumble'), + false, + 'view_owner_is(sch, non-view, user)', + 'mumble', + ' View public.__not__someview does not exist' +); + +SELECT * FROM check_test( + view_owner_is('someview', current_user, 'mumble'), + true, + 'view_owner_is(view, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + view_owner_is('someview', current_user), + true, + 'view_owner_is(view, user)', + 'View someview should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + view_owner_is('__not__someview', current_user, 'mumble'), + false, + 'view_owner_is(non-view, user)', + 'mumble', + ' View __not__someview does not exist' +); + +-- It should ignore the sequence. +SELECT * FROM check_test( + view_owner_is('public', 'someseq', current_user, 'mumble'), + false, + 'view_owner_is(sch, seq, user, desc)', + 'mumble', + ' View public.someseq does not exist' +); + +SELECT * FROM check_test( + view_owner_is('someseq', current_user, 'mumble'), + false, + 'view_owner_is(seq, user, desc)', + 'mumble', + ' View someseq does not exist' +); + /****************************************************************************/ -- Finish the tests and clean up. From 1c5ead3f3af0db3cf4f1894af5a891da9e533e21 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 13:45:17 -0800 Subject: [PATCH 0686/1195] Add `sequence_owner_is()`. --- Changes | 1 + doc/pgtap.mmd | 41 +++++++++++++++++++ sql/pgtap--0.91.0--0.92.0.sql | 52 ++++++++++++++++++++++++ sql/pgtap.sql.in | 51 +++++++++++++++++++++++ test/expected/ownership.out | 29 ++++++++++++- test/sql/ownership.sql | 76 ++++++++++++++++++++++++++++++++++- 6 files changed, 248 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index e7b3b3b31ed0..c2746a05105e 100644 --- a/Changes +++ b/Changes @@ -13,6 +13,7 @@ Revision history for pgTAP + `rel_owner_is()` + `table_owner_is()` + `view_owner_is()` + + `sequence_owner_is()` 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index ee52581c9e65..9257c45f0bf9 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5264,6 +5264,47 @@ diagnostics will look something like: # have: postgres # want: root +### `sequence_owner_is ()` ### + + SELECT sequence_owner_is ( :schema, :sequence, :user, :description ); + SELECT sequence_owner_is ( :sequence, :user, :description ); + SELECT sequence_owner_is ( :schema, :sequence, :user ); + SELECT sequence_owner_is ( :sequence, :user ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the `:sequence`. + +`:sequence` +: Name of a sequence. + +`:user` +: Name of a user. + +`:description` +: A short description of the test. + +Tests the ownership of a sequence. If the `:description` argument is omitted, an +appropriate description will be created. Examples: + + SELECT sequence_owner_is( 'public', 'mysequence', 'someuser', 'mysequence should be owned by someuser' ); + SELECT sequence_owner_is( 'widgets', current_user ); + +In the event that the test fails because the sequence in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Sequence foo should be owned by www" + # Sequence foo does not exist + +If the test fails because the sequence is not owned by the specified user, the +diagnostics will look something like: + + # Failed test 17: "Sequence bar should be owned by root" + # have: postgres + # want: root + No Test for the Wicked ====================== diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index 4b054c573c16..efc160599110 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -276,3 +276,55 @@ RETURNS TEXT AS $$ '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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 423244068335..af2a556daf25 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7675,3 +7675,54 @@ RETURNS TEXT AS $$ ); $$ 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; diff --git a/test/expected/ownership.out b/test/expected/ownership.out index 7df81de96bf1..718e455bfb3b 100644 --- a/test/expected/ownership.out +++ b/test/expected/ownership.out @@ -1,5 +1,5 @@ \unset ECHO -1..108 +1..135 ok 1 - db_owner_is(db, user, desc) should pass ok 2 - db_owner_is(db, user, desc) should have the proper description ok 3 - db_owner_is(db, user, desc) should have the proper diagnostics @@ -108,3 +108,30 @@ ok 105 - view_owner_is(sch, seq, user, desc) should have the proper diagnostics ok 106 - view_owner_is(seq, user, desc) should fail ok 107 - view_owner_is(seq, user, desc) should have the proper description ok 108 - view_owner_is(seq, user, desc) should have the proper diagnostics +ok 109 - sequence_owner_is(sch, sequence, user, desc) should pass +ok 110 - sequence_owner_is(sch, sequence, user, desc) should have the proper description +ok 111 - sequence_owner_is(sch, sequence, user, desc) should have the proper diagnostics +ok 112 - sequence_owner_is(sch, sequence, user) should pass +ok 113 - sequence_owner_is(sch, sequence, user) should have the proper description +ok 114 - sequence_owner_is(sch, sequence, user) should have the proper diagnostics +ok 115 - sequence_owner_is(non-sch, sequence, user) should fail +ok 116 - sequence_owner_is(non-sch, sequence, user) should have the proper description +ok 117 - sequence_owner_is(non-sch, sequence, user) should have the proper diagnostics +ok 118 - sequence_owner_is(sch, non-sequence, user) should fail +ok 119 - sequence_owner_is(sch, non-sequence, user) should have the proper description +ok 120 - sequence_owner_is(sch, non-sequence, user) should have the proper diagnostics +ok 121 - sequence_owner_is(sequence, user, desc) should pass +ok 122 - sequence_owner_is(sequence, user, desc) should have the proper description +ok 123 - sequence_owner_is(sequence, user, desc) should have the proper diagnostics +ok 124 - sequence_owner_is(sequence, user) should pass +ok 125 - sequence_owner_is(sequence, user) should have the proper description +ok 126 - sequence_owner_is(sequence, user) should have the proper diagnostics +ok 127 - sequence_owner_is(non-sequence, user) should fail +ok 128 - sequence_owner_is(non-sequence, user) should have the proper description +ok 129 - sequence_owner_is(non-sequence, user) should have the proper diagnostics +ok 130 - sequence_owner_is(sch, view, user, desc) should fail +ok 131 - sequence_owner_is(sch, view, user, desc) should have the proper description +ok 132 - sequence_owner_is(sch, view, user, desc) should have the proper diagnostics +ok 133 - sequence_owner_is(view, user, desc) should fail +ok 134 - sequence_owner_is(view, user, desc) should have the proper description +ok 135 - sequence_owner_is(view, user, desc) should have the proper diagnostics diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index c536e71fffda..58a2274a5801 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(108); +SELECT plan(135); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -326,6 +326,80 @@ SELECT * FROM check_test( ' View someseq does not exist' ); +/****************************************************************************/ +-- Test sequence_owner_is(). +SELECT * FROM check_test( + sequence_owner_is('public', 'someseq', current_user, 'mumble'), + true, + 'sequence_owner_is(sch, sequence, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + sequence_owner_is('public', 'someseq', current_user), + true, + 'sequence_owner_is(sch, sequence, user)', + 'Sequence public.someseq should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + sequence_owner_is('__not__public', 'someseq', current_user, 'mumble'), + false, + 'sequence_owner_is(non-sch, sequence, user)', + 'mumble', + ' Sequence __not__public.someseq does not exist' +); + +SELECT * FROM check_test( + sequence_owner_is('public', '__not__someseq', current_user, 'mumble'), + false, + 'sequence_owner_is(sch, non-sequence, user)', + 'mumble', + ' Sequence public.__not__someseq does not exist' +); + +SELECT * FROM check_test( + sequence_owner_is('someseq', current_user, 'mumble'), + true, + 'sequence_owner_is(sequence, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + sequence_owner_is('someseq', current_user), + true, + 'sequence_owner_is(sequence, user)', + 'Sequence someseq should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + sequence_owner_is('__not__someseq', current_user, 'mumble'), + false, + 'sequence_owner_is(non-sequence, user)', + 'mumble', + ' Sequence __not__someseq does not exist' +); + +-- It should ignore the view. +SELECT * FROM check_test( + sequence_owner_is('public', 'someview', current_user, 'mumble'), + false, + 'sequence_owner_is(sch, view, user, desc)', + 'mumble', + ' Sequence public.someview does not exist' +); + +SELECT * FROM check_test( + sequence_owner_is('someview', current_user, 'mumble'), + false, + 'sequence_owner_is(view, user, desc)', + 'mumble', + ' Sequence someview does not exist' +); /****************************************************************************/ -- Finish the tests and clean up. From 2c016e760a8fba66fbc83bbe49a03746dd57d611 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 13:52:07 -0800 Subject: [PATCH 0687/1195] Add `composite_owner_is()`. --- Changes | 1 + doc/pgtap.mmd | 64 +++++++++++++++++++++++++++-- sql/pgtap--0.91.0--0.92.0.sql | 52 +++++++++++++++++++++++ sql/pgtap.sql.in | 52 +++++++++++++++++++++++ test/expected/ownership.out | 29 ++++++++++++- test/sql/ownership.sql | 77 ++++++++++++++++++++++++++++++++++- 6 files changed, 269 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index c2746a05105e..2d06afd15a93 100644 --- a/Changes +++ b/Changes @@ -14,6 +14,7 @@ Revision history for pgTAP + `table_owner_is()` + `view_owner_is()` + `sequence_owner_is()` + + `composite_owner_is()` 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 9257c45f0bf9..d52b09d00825 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5165,7 +5165,10 @@ Tests the ownership of a relation. Relations are tables, views, seqences, composite types, foreign tables, and toast tables. If the `:description` argument is omitted, an appropriate description will be created. Examples: - SELECT rel_owner_is( 'public', 'mytable', 'someuser', 'mytable should be owned by someuser' ); + SELECT rel_owner_is( + 'public', 'mytable', 'someuser', + 'mytable should be owned by someuser' + ); SELECT rel_owner_is( current_schema(), 'mysequence', current_user ); In the event that the test fails because the relation in question does not @@ -5206,7 +5209,10 @@ diagnostics will look something like: Tests the ownership of a table. If the `:description` argument is omitted, an appropriate description will be created. Examples: - SELECT table_owner_is( 'public', 'mytable', 'someuser', 'mytable should be owned by someuser' ); + SELECT table_owner_is( + 'public', 'mytable', 'someuser', + 'mytable should be owned by someuser' + ); SELECT table_owner_is( 'widgets', current_user ); In the event that the test fails because the table in question does not @@ -5247,7 +5253,10 @@ diagnostics will look something like: Tests the ownership of a view. If the `:description` argument is omitted, an appropriate description will be created. Examples: - SELECT view_owner_is( 'public', 'myview', 'someuser', 'myview should be owned by someuser' ); + SELECT view_owner_is( + 'public', 'myview', 'someuser', + 'myview should be owned by someuser' + ); SELECT view_owner_is( 'widgets', current_user ); In the event that the test fails because the view in question does not @@ -5288,7 +5297,10 @@ diagnostics will look something like: Tests the ownership of a sequence. If the `:description` argument is omitted, an appropriate description will be created. Examples: - SELECT sequence_owner_is( 'public', 'mysequence', 'someuser', 'mysequence should be owned by someuser' ); + SELECT sequence_owner_is( + 'public', 'mysequence', 'someuser', + 'mysequence should be owned by someuser' + ); SELECT sequence_owner_is( 'widgets', current_user ); In the event that the test fails because the sequence in question does not @@ -5305,6 +5317,50 @@ diagnostics will look something like: # have: postgres # want: root +### `composite_owner_is ()` ### + + SELECT composite_owner_is ( :schema, :composite, :user, :description ); + SELECT composite_owner_is ( :composite, :user, :description ); + SELECT composite_owner_is ( :schema, :composite, :user ); + SELECT composite_owner_is ( :composite, :user ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the `:composite` type. + +`:composite` +: Name of a composite type. + +`:user` +: Name of a user. + +`:description` +: A short description of the test. + +Tests the ownership of a composite. If the `:description` argument is omitted, an +appropriate description will be created. Examples: + + SELECT composite_owner_is( + 'public', 'mycomposite', 'someuser', + 'mycomposite should be owned by someuser' + ); + SELECT composite_owner_is( 'widgets', current_user ); + +In the event that the test fails because the composite in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Composite type foo should be owned by www" + # Composite type foo does not exist + +If the test fails because the composite is not owned by the specified user, the +diagnostics will look something like: + + # Failed test 17: "Composite type bar should be owned by root" + # have: postgres + # want: root + No Test for the Wicked ====================== diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index efc160599110..899029b498f1 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -328,3 +328,55 @@ RETURNS TEXT AS $$ '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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index af2a556daf25..66a0a5ec220b 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7726,3 +7726,55 @@ RETURNS TEXT AS $$ '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; diff --git a/test/expected/ownership.out b/test/expected/ownership.out index 718e455bfb3b..df82dde8ddfe 100644 --- a/test/expected/ownership.out +++ b/test/expected/ownership.out @@ -1,5 +1,5 @@ \unset ECHO -1..135 +1..162 ok 1 - db_owner_is(db, user, desc) should pass ok 2 - db_owner_is(db, user, desc) should have the proper description ok 3 - db_owner_is(db, user, desc) should have the proper diagnostics @@ -135,3 +135,30 @@ ok 132 - sequence_owner_is(sch, view, user, desc) should have the proper diagnos ok 133 - sequence_owner_is(view, user, desc) should fail ok 134 - sequence_owner_is(view, user, desc) should have the proper description ok 135 - sequence_owner_is(view, user, desc) should have the proper diagnostics +ok 136 - composite_owner_is(sch, composite, user, desc) should pass +ok 137 - composite_owner_is(sch, composite, user, desc) should have the proper description +ok 138 - composite_owner_is(sch, composite, user, desc) should have the proper diagnostics +ok 139 - composite_owner_is(sch, composite, user) should pass +ok 140 - composite_owner_is(sch, composite, user) should have the proper description +ok 141 - composite_owner_is(sch, composite, user) should have the proper diagnostics +ok 142 - composite_owner_is(non-sch, composite, user) should fail +ok 143 - composite_owner_is(non-sch, composite, user) should have the proper description +ok 144 - composite_owner_is(non-sch, composite, user) should have the proper diagnostics +ok 145 - composite_owner_is(sch, non-composite, user) should fail +ok 146 - composite_owner_is(sch, non-composite, user) should have the proper description +ok 147 - composite_owner_is(sch, non-composite, user) should have the proper diagnostics +ok 148 - composite_owner_is(composite, user, desc) should pass +ok 149 - composite_owner_is(composite, user, desc) should have the proper description +ok 150 - composite_owner_is(composite, user, desc) should have the proper diagnostics +ok 151 - composite_owner_is(composite, user) should pass +ok 152 - composite_owner_is(composite, user) should have the proper description +ok 153 - composite_owner_is(composite, user) should have the proper diagnostics +ok 154 - composite_owner_is(non-composite, user) should fail +ok 155 - composite_owner_is(non-composite, user) should have the proper description +ok 156 - composite_owner_is(non-composite, user) should have the proper diagnostics +ok 157 - composite_owner_is(sch, view, user, desc) should fail +ok 158 - composite_owner_is(sch, view, user, desc) should have the proper description +ok 159 - composite_owner_is(sch, view, user, desc) should have the proper diagnostics +ok 160 - composite_owner_is(view, user, desc) should fail +ok 161 - composite_owner_is(view, user, desc) should have the proper description +ok 162 - composite_owner_is(view, user, desc) should have the proper diagnostics diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 58a2274a5801..f2733202a092 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(135); +SELECT plan(162); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -401,6 +401,81 @@ SELECT * FROM check_test( ' Sequence someview does not exist' ); +/****************************************************************************/ +-- Test composite_owner_is(). +SELECT * FROM check_test( + composite_owner_is('public', 'sometype', current_user, 'mumble'), + true, + 'composite_owner_is(sch, composite, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + composite_owner_is('public', 'sometype', current_user), + true, + 'composite_owner_is(sch, composite, user)', + 'Composite type public.sometype should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + composite_owner_is('__not__public', 'sometype', current_user, 'mumble'), + false, + 'composite_owner_is(non-sch, composite, user)', + 'mumble', + ' Composite type __not__public.sometype does not exist' +); + +SELECT * FROM check_test( + composite_owner_is('public', '__not__sometype', current_user, 'mumble'), + false, + 'composite_owner_is(sch, non-composite, user)', + 'mumble', + ' Composite type public.__not__sometype does not exist' +); + +SELECT * FROM check_test( + composite_owner_is('sometype', current_user, 'mumble'), + true, + 'composite_owner_is(composite, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + composite_owner_is('sometype', current_user), + true, + 'composite_owner_is(composite, user)', + 'Composite type sometype should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + composite_owner_is('__not__sometype', current_user, 'mumble'), + false, + 'composite_owner_is(non-composite, user)', + 'mumble', + ' Composite type __not__sometype does not exist' +); + +-- It should ignore the view. +SELECT * FROM check_test( + composite_owner_is('public', 'someview', current_user, 'mumble'), + false, + 'composite_owner_is(sch, view, user, desc)', + 'mumble', + ' Composite type public.someview does not exist' +); + +SELECT * FROM check_test( + composite_owner_is('someview', current_user, 'mumble'), + false, + 'composite_owner_is(view, user, desc)', + 'mumble', + ' Composite type someview does not exist' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From bde7d81b721d1e3875448d3938c634d67b27880e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 14:06:20 -0800 Subject: [PATCH 0688/1195] Add `foreign_table_owner_is()`. --- Changes | 1 + doc/pgtap.mmd | 51 ++++++++++- sql/pgtap--0.91.0--0.92.0.sql | 52 +++++++++++ sql/pgtap.sql.in | 52 +++++++++++ test/expected/ownership.out | 29 +++++- test/sql/hastap.sql | 2 +- test/sql/ownership.sql | 165 +++++++++++++++++++++++++++++++++- 7 files changed, 347 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 2d06afd15a93..07f823d30f06 100644 --- a/Changes +++ b/Changes @@ -15,6 +15,7 @@ Revision history for pgTAP + `view_owner_is()` + `sequence_owner_is()` + `composite_owner_is()` + + `foreign_table_owner_is()` 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index d52b09d00825..ea392b828c5e 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2626,7 +2626,7 @@ first argument is a schema name, the second is a foreign table name, and the third is the test description. If you omit the schema, the foreign table must be visible in the search path. Example: - SELECT has_foreign_table('myschema', 'some_foriegn_table'); + SELECT has_foreign_table('myschema', 'some_foreign_table'); If you omit the test description, it will be set to "Foreign table `:table` should exist". @@ -2649,7 +2649,7 @@ should exist". : A short description of the test. This function is the inverse of `has_foreign_table()`. The test passes if the -specified forieign table does *not* exist. +specified foreign table does *not* exist. ### `has_type()` ### @@ -5215,6 +5215,9 @@ appropriate description will be created. Examples: ); SELECT table_owner_is( 'widgets', current_user ); +Note that this function will not recognize foreign tables; use +`foreign_table_owner_is()` to test for the presence of foreign tables. + In the event that the test fails because the table in question does not actually exist or is not visible, you will see an appropriate diagnostic such as: @@ -5361,6 +5364,50 @@ diagnostics will look something like: # have: postgres # want: root +### `foreign_table_owner_is ()` ### + + SELECT foreign_table_owner_is ( :schema, :foreign_table, :user, :description ); + SELECT foreign_table_owner_is ( :foreign_table, :user, :description ); + SELECT foreign_table_owner_is ( :schema, :foreign_table, :user ); + SELECT foreign_table_owner_is ( :foreign_table, :user ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the `:foreign_table`. + +`:foreign_table` +: Name of a foreign table. + +`:user` +: Name of a user. + +`:description` +: A short description of the test. + +Tests the ownership of a foreign table. If the `:description` argument is +omitted, an appropriate description will be created. Examples: + + SELECT foreign_table_owner_is( + 'public', 'mytable', 'someuser', + 'mytable should be owned by someuser' + ); + SELECT foreign_table_owner_is( 'widgets', current_user ); + +In the event that the test fails because the table in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Foreign table foo should be owned by www" + # Foreign table foo does not exist + +If the test fails because the table is not owned by the specified user, the +diagnostics will look something like: + + # Failed test 17: "Foreign table bar should be owned by root" + # have: postgres + # want: root + No Test for the Wicked ====================== diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index 899029b498f1..200af86784e5 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -380,3 +380,55 @@ RETURNS TEXT AS $$ '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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 66a0a5ec220b..0382737c6bc5 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7778,3 +7778,55 @@ RETURNS TEXT AS $$ '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; diff --git a/test/expected/ownership.out b/test/expected/ownership.out index df82dde8ddfe..3e917d76ef18 100644 --- a/test/expected/ownership.out +++ b/test/expected/ownership.out @@ -1,5 +1,5 @@ \unset ECHO -1..162 +1..189 ok 1 - db_owner_is(db, user, desc) should pass ok 2 - db_owner_is(db, user, desc) should have the proper description ok 3 - db_owner_is(db, user, desc) should have the proper diagnostics @@ -162,3 +162,30 @@ ok 159 - composite_owner_is(sch, view, user, desc) should have the proper diagno ok 160 - composite_owner_is(view, user, desc) should fail ok 161 - composite_owner_is(view, user, desc) should have the proper description ok 162 - composite_owner_is(view, user, desc) should have the proper diagnostics +ok 163 - foreign_table_owner_is(sch, tab, user, desc) should pass +ok 164 - foreign_table_owner_is(sch, tab, user, desc) should have the proper description +ok 165 - foreign_table_owner_is(sch, tab, user, desc) should have the proper diagnostics +ok 166 - foreign_table_owner_is(sch, tab, user) should pass +ok 167 - foreign_table_owner_is(sch, tab, user) should have the proper description +ok 168 - foreign_table_owner_is(sch, tab, user) should have the proper diagnostics +ok 169 - foreign_table_owner_is(non-sch, tab, user) should fail +ok 170 - foreign_table_owner_is(non-sch, tab, user) should have the proper description +ok 171 - foreign_table_owner_is(non-sch, tab, user) should have the proper diagnostics +ok 172 - foreign_table_owner_is(sch, non-tab, user) should fail +ok 173 - foreign_table_owner_is(sch, non-tab, user) should have the proper description +ok 174 - foreign_table_owner_is(sch, non-tab, user) should have the proper diagnostics +ok 175 - foreign_table_owner_is(tab, user, desc) should pass +ok 176 - foreign_table_owner_is(tab, user, desc) should have the proper description +ok 177 - foreign_table_owner_is(tab, user, desc) should have the proper diagnostics +ok 178 - foreign_table_owner_is(tab, user) should pass +ok 179 - foreign_table_owner_is(tab, user) should have the proper description +ok 180 - foreign_table_owner_is(tab, user) should have the proper diagnostics +ok 181 - foreign_table_owner_is(non-tab, user) should fail +ok 182 - foreign_table_owner_is(non-tab, user) should have the proper description +ok 183 - foreign_table_owner_is(non-tab, user) should have the proper diagnostics +ok 184 - foreign_table_owner_is(sch, tab, user, desc) should fail +ok 185 - foreign_table_owner_is(sch, tab, user, desc) should have the proper description +ok 186 - foreign_table_owner_is(sch, tab, user, desc) should have the proper diagnostics +ok 187 - foreign_table_owner_is(tab, user, desc) should fail +ok 188 - foreign_table_owner_is(tab, user, desc) should have the proper description +ok 189 - foreign_table_owner_is(tab, user, desc) should have the proper diagnostics diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index be5119355466..577d01ce22ea 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -2000,7 +2000,7 @@ CREATE FUNCTION test_fdw() RETURNS SETOF TEXT AS $$ DECLARE tap record; BEGIN - IF pg_version_num() >= 92100 THEN + IF pg_version_num() >= 90100 THEN CREATE FOREIGN DATA WRAPPER dummy; CREATE SERVER foo FOREIGN DATA WRAPPER dummy; CREATE FOREIGN TABLE public.my_fdw (id int) SERVER foo; diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index f2733202a092..11c3c977a25c 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(162); +SELECT plan(189); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -476,6 +476,169 @@ SELECT * FROM check_test( ' Composite type someview does not exist' ); +/****************************************************************************/ +-- Test foreign_table_owner_is(). +CREATE FUNCTION test_fdw() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() >= 90100 THEN + CREATE FOREIGN DATA WRAPPER dummy; + CREATE SERVER foo FOREIGN DATA WRAPPER dummy; + CREATE FOREIGN TABLE public.my_fdw (id int) SERVER foo; + + FOR tap IN SELECT * FROM check_test( + foreign_table_owner_is('public', 'my_fdw', current_user, 'mumble'), + true, + 'foreign_table_owner_is(sch, tab, user, desc)', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + foreign_table_owner_is('public', 'my_fdw', current_user), + true, + 'foreign_table_owner_is(sch, tab, user)', + 'Foreign table public.my_fdw should be owned by ' || current_user, + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + foreign_table_owner_is('__not__public', 'my_fdw', current_user, 'mumble'), + false, + 'foreign_table_owner_is(non-sch, tab, user)', + 'mumble', + ' Foreign table __not__public.my_fdw does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + foreign_table_owner_is('public', '__not__my_fdw', current_user, 'mumble'), + false, + 'foreign_table_owner_is(sch, non-tab, user)', + 'mumble', + ' Foreign table public.__not__my_fdw does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + foreign_table_owner_is('my_fdw', current_user, 'mumble'), + true, + 'foreign_table_owner_is(tab, user, desc)', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + foreign_table_owner_is('my_fdw', current_user), + true, + 'foreign_table_owner_is(tab, user)', + 'Foreign table my_fdw should be owned by ' || current_user, + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + foreign_table_owner_is('__not__my_fdw', current_user, 'mumble'), + false, + 'foreign_table_owner_is(non-tab, user)', + 'mumble', + ' Foreign table __not__my_fdw does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- It should ignore the table. + FOR tap IN SELECT * FROM check_test( + foreign_table_owner_is('public', 'sometab', current_user, 'mumble'), + false, + 'foreign_table_owner_is(sch, tab, user, desc)', + 'mumble', + ' Foreign table public.sometab does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + foreign_table_owner_is('sometab', current_user, 'mumble'), + false, + 'foreign_table_owner_is(tab, user, desc)', + 'mumble', + ' Foreign table sometab does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + ELSE + -- Fake it with table_owner_is(). + FOR tap IN SELECT * FROM check_test( + table_owner_is('public', 'sometab', current_user, 'mumble'), + true, + 'foreign_table_owner_is(sch, tab, user, desc)', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + table_owner_is('public', 'sometab', current_user), + true, + 'foreign_table_owner_is(sch, tab, user)', + 'Table public.sometab should be owned by ' || current_user, + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + table_owner_is('__not__public', 'sometab', current_user, 'mumble'), + false, + 'foreign_table_owner_is(non-sch, tab, user)', + 'mumble', + ' Table __not__public.sometab does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + table_owner_is('public', '__not__sometab', current_user, 'mumble'), + false, + 'foreign_table_owner_is(sch, non-tab, user)', + 'mumble', + ' Table public.__not__sometab does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + table_owner_is('sometab', current_user, 'mumble'), + true, + 'foreign_table_owner_is(tab, user, desc)', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + table_owner_is('sometab', current_user), + true, + 'foreign_table_owner_is(tab, user)', + 'Table sometab should be owned by ' || current_user, + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + table_owner_is('__not__sometab', current_user, 'mumble'), + false, + 'foreign_table_owner_is(non-tab, user)', + 'mumble', + ' Table __not__sometab does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- It should ignore the sequence. + FOR tap IN SELECT * FROM check_test( + table_owner_is('public', 'someseq', current_user, 'mumble'), + false, + 'foreign_table_owner_is(sch, seq, user, desc)', + 'mumble', + ' Table public.someseq does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + table_owner_is('someseq', current_user, 'mumble'), + false, + 'foreign_table_owner_is(seq, user, desc)', + 'mumble', + ' Table someseq does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + END IF; + RETURN; +END; +$$ LANGUAGE PLPGSQL; + +SELECT * FROM test_fdw(); /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From a4d88859715abbb063993f33df6381a7d6b3db59 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 14:19:23 -0800 Subject: [PATCH 0689/1195] Add `has_relation()`. --- Changes | 1 + doc/pgtap.mmd | 53 +++++++++++++++-- sql/pgtap--0.91.0--0.92.0.sql | 57 ++++++++++++++++++ sql/pgtap.sql.in | 57 ++++++++++++++++++ test/expected/hastap.out | 108 ++++++++++++++++++++++------------ test/sql/hastap.sql | 105 ++++++++++++++++++++++++++++++++- 6 files changed, 340 insertions(+), 41 deletions(-) diff --git a/Changes b/Changes index 07f823d30f06..db8a96e7ead0 100644 --- a/Changes +++ b/Changes @@ -7,6 +7,7 @@ Revision history for pgTAP * Added note about the lack of typemods to the documentation for `has_cast()`. * Added check to ensure a table is visible in tests for column defaults. Thanks to Henk Enting for the report. +* Added `has_relation()` and `hasnt_relation()`. * Added `has_foreign_table()` and `hasnt_foreign_table()`. * Added `has_composite()` and `hasnt_composite()`. * Added new ownership assertion functions: diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index ea392b828c5e..cd0f1a0dd298 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2460,6 +2460,54 @@ the test description, it will be set to "Schema `:schema` should exist". This function is the inverse of `has_schema()`. The test passes if the specified schema does *not* exist. +### `has_relation()` ### + + SELECT has_relation( :schema, :relation, :description ); + SELECT has_relation( :relation, :description ); + SELECT has_relation( :relation ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the relation. + +`:relation` +: Name of a relation. + +`:description` +: A short description of the test. + +This function tests whether or not a relation exists in the database. +Relations are tables, views, seqences, composite types, foreign tables, and +toast tables. The first argument is a schema name, the second is a relation +name, and the third is the test description. If you omit the schema, the +relation must be visible in the search path. Example: + + SELECT has_relation('myschema', 'somerelation'); + +If you omit the test description, it will be set to "Relation `:relation` +should exist". + +### `hasnt_relation()` ### + + SELECT hasnt_relation( :schema, :relation, :description ); + SELECT hasnt_relation( :relation, :description ); + SELECT hasnt_relation( :relation ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the relation. + +`:relation` +: Name of a relation. + +`:description` +: A short description of the test. + +This function is the inverse of `has_relation()`. The test passes if the +specified relation does *not* exist. + ### `has_table()` ### SELECT has_table( :schema, :table, :description ); @@ -6183,10 +6231,7 @@ To Do * Add `schema, table, colname` variations of the table-checking functions (`table_has_column()`, `col_type_is()`, etc.). That is, allow the description to be optional if the schema is included in the function call. -* Add functions to test for object ownership. - + `table_owner_is()` - + `view_owner_is()` - + `sequence_owner_is()` +* Add more functions to test for object ownership. + `function_owner_is()` + etc. * Add some sort of tests for permisions. Something like: diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index 200af86784e5..c58483317a0e 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -432,3 +432,60 @@ RETURNS TEXT AS $$ 'Foreign table ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) ); $$ 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 0382737c6bc5..5ab7edfa2c31 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -809,6 +809,63 @@ RETURNS TEXT AS $$ ); $$ 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( diff --git a/test/expected/hastap.out b/test/expected/hastap.out index a106e2957e0e..cf0401b925d0 100644 --- a/test/expected/hastap.out +++ b/test/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..749 +1..785 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -714,38 +714,74 @@ ok 711 - domain_type_isnt(nondomain, type, desc) should have the proper diagnost ok 712 - domain_type_isnt(type, type, desc) should fail ok 713 - domain_type_isnt(type, type, desc) should have the proper description ok 714 - domain_type_isnt(type, type, desc) should have the proper diagnostics -ok 715 - has_foreign_table(non-existent table) should fail -ok 716 - has_foreign_table(non-existent table) should have the proper description -ok 717 - has_foreign_table(non-existent table) should have the proper diagnostics -ok 718 - has_foreign_table(non-existent schema, tab) should fail -ok 719 - has_foreign_table(non-existent schema, tab) should have the proper description -ok 720 - has_foreign_table(non-existent schema, tab) should have the proper diagnostics -ok 721 - has_foreign_table(sch, non-existent table, desc) should fail -ok 722 - has_foreign_table(sch, non-existent table, desc) should have the proper description -ok 723 - has_foreign_table(sch, non-existent table, desc) should have the proper diagnostics -ok 724 - has_foreign_table(tab, desc) should pass -ok 725 - has_foreign_table(tab, desc) should have the proper description -ok 726 - has_foreign_table(tab, desc) should have the proper diagnostics -ok 727 - has_foreign_table(sch, tab, desc) should pass -ok 728 - has_foreign_table(sch, tab, desc) should have the proper description -ok 729 - has_foreign_table(sch, tab, desc) should have the proper diagnostics -ok 730 - has_foreign_table(sch, view, desc) should fail -ok 731 - has_foreign_table(sch, view, desc) should have the proper description -ok 732 - has_foreign_table(sch, view, desc) should have the proper diagnostics -ok 733 - has_foreign_table(type, desc) should fail -ok 734 - has_foreign_table(type, desc) should have the proper description -ok 735 - has_foreign_table(type, desc) should have the proper diagnostics -ok 736 - hasnt_foreign_table(non-existent table) should pass -ok 737 - hasnt_foreign_table(non-existent table) should have the proper description -ok 738 - hasnt_foreign_table(non-existent table) should have the proper diagnostics -ok 739 - hasnt_foreign_table(non-existent schema, tab) should pass -ok 740 - hasnt_foreign_table(non-existent schema, tab) should have the proper description -ok 741 - hasnt_foreign_table(sch, non-existent tab, desc) should pass -ok 742 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper description -ok 743 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper diagnostics -ok 744 - hasnt_foreign_table(tab, desc) should fail -ok 745 - hasnt_foreign_table(tab, desc) should have the proper description -ok 746 - hasnt_foreign_table(tab, desc) should have the proper diagnostics -ok 747 - hasnt_foreign_table(sch, tab, desc) should fail -ok 748 - hasnt_foreign_table(sch, tab, desc) should have the proper description -ok 749 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics +ok 715 - has_relation(non-existent relation) should fail +ok 716 - has_relation(non-existent relation) should have the proper description +ok 717 - has_relation(non-existent relation) should have the proper diagnostics +ok 718 - has_relation(non-existent schema, tab) should fail +ok 719 - has_relation(non-existent schema, tab) should have the proper description +ok 720 - has_relation(non-existent schema, tab) should have the proper diagnostics +ok 721 - has_relation(sch, non-existent relation, desc) should fail +ok 722 - has_relation(sch, non-existent relation, desc) should have the proper description +ok 723 - has_relation(sch, non-existent relation, desc) should have the proper diagnostics +ok 724 - has_relation(tab, desc) should pass +ok 725 - has_relation(tab, desc) should have the proper description +ok 726 - has_relation(tab, desc) should have the proper diagnostics +ok 727 - has_relation(sch, tab, desc) should pass +ok 728 - has_relation(sch, tab, desc) should have the proper description +ok 729 - has_relation(sch, tab, desc) should have the proper diagnostics +ok 730 - has_relation(sch, view, desc) should pass +ok 731 - has_relation(sch, view, desc) should have the proper description +ok 732 - has_relation(sch, view, desc) should have the proper diagnostics +ok 733 - has_relation(type, desc) should pass +ok 734 - has_relation(type, desc) should have the proper description +ok 735 - has_relation(type, desc) should have the proper diagnostics +ok 736 - hasnt_relation(non-existent relation) should pass +ok 737 - hasnt_relation(non-existent relation) should have the proper description +ok 738 - hasnt_relation(non-existent relation) should have the proper diagnostics +ok 739 - hasnt_relation(non-existent schema, tab) should pass +ok 740 - hasnt_relation(non-existent schema, tab) should have the proper description +ok 741 - hasnt_relation(non-existent schema, tab) should have the proper diagnostics +ok 742 - hasnt_relation(sch, non-existent tab, desc) should pass +ok 743 - hasnt_relation(sch, non-existent tab, desc) should have the proper description +ok 744 - hasnt_relation(sch, non-existent tab, desc) should have the proper diagnostics +ok 745 - hasnt_relation(tab, desc) should fail +ok 746 - hasnt_relation(tab, desc) should have the proper description +ok 747 - hasnt_relation(tab, desc) should have the proper diagnostics +ok 748 - hasnt_relation(sch, tab, desc) should fail +ok 749 - hasnt_relation(sch, tab, desc) should have the proper description +ok 750 - hasnt_relation(sch, tab, desc) should have the proper diagnostics +ok 751 - has_foreign_table(non-existent table) should fail +ok 752 - has_foreign_table(non-existent table) should have the proper description +ok 753 - has_foreign_table(non-existent table) should have the proper diagnostics +ok 754 - has_foreign_table(non-existent schema, tab) should fail +ok 755 - has_foreign_table(non-existent schema, tab) should have the proper description +ok 756 - has_foreign_table(non-existent schema, tab) should have the proper diagnostics +ok 757 - has_foreign_table(sch, non-existent table, desc) should fail +ok 758 - has_foreign_table(sch, non-existent table, desc) should have the proper description +ok 759 - has_foreign_table(sch, non-existent table, desc) should have the proper diagnostics +ok 760 - has_foreign_table(tab, desc) should pass +ok 761 - has_foreign_table(tab, desc) should have the proper description +ok 762 - has_foreign_table(tab, desc) should have the proper diagnostics +ok 763 - has_foreign_table(sch, tab, desc) should pass +ok 764 - has_foreign_table(sch, tab, desc) should have the proper description +ok 765 - has_foreign_table(sch, tab, desc) should have the proper diagnostics +ok 766 - has_foreign_table(sch, view, desc) should fail +ok 767 - has_foreign_table(sch, view, desc) should have the proper description +ok 768 - has_foreign_table(sch, view, desc) should have the proper diagnostics +ok 769 - has_foreign_table(type, desc) should fail +ok 770 - has_foreign_table(type, desc) should have the proper description +ok 771 - has_foreign_table(type, desc) should have the proper diagnostics +ok 772 - hasnt_foreign_table(non-existent table) should pass +ok 773 - hasnt_foreign_table(non-existent table) should have the proper description +ok 774 - hasnt_foreign_table(non-existent table) should have the proper diagnostics +ok 775 - hasnt_foreign_table(non-existent schema, tab) should pass +ok 776 - hasnt_foreign_table(non-existent schema, tab) should have the proper description +ok 777 - hasnt_foreign_table(sch, non-existent tab, desc) should pass +ok 778 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper description +ok 779 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper diagnostics +ok 780 - hasnt_foreign_table(tab, desc) should fail +ok 781 - hasnt_foreign_table(tab, desc) should have the proper description +ok 782 - hasnt_foreign_table(tab, desc) should have the proper diagnostics +ok 783 - hasnt_foreign_table(sch, tab, desc) should fail +ok 784 - hasnt_foreign_table(sch, tab, desc) should have the proper description +ok 785 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index 577d01ce22ea..c3f3bdca5b44 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(749); +SELECT plan(785); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -2252,6 +2252,109 @@ BEGIN END; $$ LANGUAGE PLPGSQL; +/****************************************************************************/ +-- Test has_relation(). + +SELECT * FROM check_test( + has_relation( '__SDFSDFD__' ), + false, + 'has_relation(non-existent relation)', + 'Relation "__SDFSDFD__" should exist', + '' +); + +SELECT * FROM check_test( + has_relation( '__SDFSDFD__', 'lol' ), + false, + 'has_relation(non-existent schema, tab)', + 'lol', + '' +); + +SELECT * FROM check_test( + has_relation( 'foo', '__SDFSDFD__', 'desc' ), + false, + 'has_relation(sch, non-existent relation, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + has_relation( 'pg_type', 'lol' ), + true, + 'has_relation(tab, desc)', + 'lol', + '' +); + +SELECT * FROM check_test( + has_relation( 'pg_catalog', 'pg_type', 'desc' ), + true, + 'has_relation(sch, tab, desc)', + 'desc', + '' +); + +-- It should not ignore views and types. +SELECT * FROM check_test( + has_relation( 'pg_catalog', 'pg_type', 'desc' ), + true, + 'has_relation(sch, view, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + has_relation( 'sometype', 'desc' ), + true, + 'has_relation(type, desc)', + 'desc', + '' +); + +/****************************************************************************/ +-- Test hasnt_relation(). + +SELECT * FROM check_test( + hasnt_relation( '__SDFSDFD__' ), + true, + 'hasnt_relation(non-existent relation)', + 'Relation "__SDFSDFD__" should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_relation( '__SDFSDFD__', 'lol' ), + true, + 'hasnt_relation(non-existent schema, tab)', + 'lol', + '' +); + +SELECT * FROM check_test( + hasnt_relation( 'foo', '__SDFSDFD__', 'desc' ), + true, + 'hasnt_relation(sch, non-existent tab, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_relation( 'pg_type', 'lol' ), + false, + 'hasnt_relation(tab, desc)', + 'lol', + '' +); + +SELECT * FROM check_test( + hasnt_relation( 'pg_catalog', 'pg_type', 'desc' ), + false, + 'hasnt_relation(sch, tab, desc)', + 'desc', + '' +); + SELECT * FROM test_fdw(); /****************************************************************************/ From 6722eff271a0e338929bd882f647988c0e87b4e9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 14:22:10 -0800 Subject: [PATCH 0690/1195] Rename `get_rel_owner()` to `get_relation_owner()`. --- Changes | 2 +- doc/pgtap.mmd | 14 +++--- sql/pgtap--0.91.0--0.92.0.sql | 20 ++++----- sql/pgtap.sql.in | 20 ++++----- test/expected/ownership.out | 84 +++++++++++++++++------------------ test/sql/ownership.sql | 60 ++++++++++++------------- 6 files changed, 100 insertions(+), 100 deletions(-) diff --git a/Changes b/Changes index db8a96e7ead0..921e59680415 100644 --- a/Changes +++ b/Changes @@ -11,7 +11,7 @@ Revision history for pgTAP * Added `has_foreign_table()` and `hasnt_foreign_table()`. * Added `has_composite()` and `hasnt_composite()`. * Added new ownership assertion functions: - + `rel_owner_is()` + + `relation_owner_is()` + `table_owner_is()` + `view_owner_is()` + `sequence_owner_is()` diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index cd0f1a0dd298..4c56fc6c1e99 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5188,12 +5188,12 @@ diagnostics will look something like: # have: postgres # want: root -### `rel_owner_is ()` ### +### `relation_owner_is ()` ### - SELECT rel_owner_is ( :schema, :relation, :user, :description ); - SELECT rel_owner_is ( :relation, :user, :description ); - SELECT rel_owner_is ( :schema, :relation, :user ); - SELECT rel_owner_is ( :relation, :user ); + SELECT relation_owner_is ( :schema, :relation, :user, :description ); + SELECT relation_owner_is ( :relation, :user, :description ); + SELECT relation_owner_is ( :schema, :relation, :user ); + SELECT relation_owner_is ( :relation, :user ); **Parameters** @@ -5213,11 +5213,11 @@ Tests the ownership of a relation. Relations are tables, views, seqences, composite types, foreign tables, and toast tables. If the `:description` argument is omitted, an appropriate description will be created. Examples: - SELECT rel_owner_is( + SELECT relation_owner_is( 'public', 'mytable', 'someuser', 'mytable should be owned by someuser' ); - SELECT rel_owner_is( current_schema(), 'mysequence', current_user ); + SELECT relation_owner_is( current_schema(), 'mysequence', current_user ); In the event that the test fails because the relation in question does not actually exist or is not visible, you will see an appropriate diagnostic such diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index c58483317a0e..ade97c763521 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -102,8 +102,8 @@ RETURNS NAME AS $$ AND pg_catalog.pg_table_is_visible(c.oid) $$ LANGUAGE SQL; --- rel_owner_is ( schema, relation, user, description ) -CREATE OR REPLACE FUNCTION rel_owner_is ( NAME, NAME, NAME, TEXT ) +-- 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); @@ -119,17 +119,17 @@ BEGIN END; $$ LANGUAGE plpgsql; --- rel_owner_is ( schema, relation, user ) -CREATE OR REPLACE FUNCTION rel_owner_is ( NAME, NAME, NAME ) +-- relation_owner_is ( schema, relation, user ) +CREATE OR REPLACE FUNCTION relation_owner_is ( NAME, NAME, NAME ) RETURNS TEXT AS $$ - SELECT rel_owner_is( + SELECT relation_owner_is( $1, $2, $3, 'Relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) ); $$ LANGUAGE sql; --- rel_owner_is ( relation, user, description ) -CREATE OR REPLACE FUNCTION rel_owner_is ( NAME, NAME, TEXT ) +-- 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); @@ -145,10 +145,10 @@ BEGIN END; $$ LANGUAGE plpgsql; --- rel_owner_is ( relation, user ) -CREATE OR REPLACE FUNCTION rel_owner_is ( NAME, NAME ) +-- relation_owner_is ( relation, user ) +CREATE OR REPLACE FUNCTION relation_owner_is ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT rel_owner_is( + SELECT relation_owner_is( $1, $2, 'Relation ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) ); diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 5ab7edfa2c31..2246bbb72e17 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7557,8 +7557,8 @@ RETURNS NAME AS $$ AND pg_catalog.pg_table_is_visible(c.oid) $$ LANGUAGE SQL; --- rel_owner_is ( schema, relation, user, description ) -CREATE OR REPLACE FUNCTION rel_owner_is ( NAME, NAME, NAME, TEXT ) +-- 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); @@ -7574,17 +7574,17 @@ BEGIN END; $$ LANGUAGE plpgsql; --- rel_owner_is ( schema, relation, user ) -CREATE OR REPLACE FUNCTION rel_owner_is ( NAME, NAME, NAME ) +-- relation_owner_is ( schema, relation, user ) +CREATE OR REPLACE FUNCTION relation_owner_is ( NAME, NAME, NAME ) RETURNS TEXT AS $$ - SELECT rel_owner_is( + SELECT relation_owner_is( $1, $2, $3, 'Relation ' || quote_ident($1) || '.' || quote_ident($2) || ' should be owned by ' || quote_ident($3) ); $$ LANGUAGE sql; --- rel_owner_is ( relation, user, description ) -CREATE OR REPLACE FUNCTION rel_owner_is ( NAME, NAME, TEXT ) +-- 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); @@ -7600,10 +7600,10 @@ BEGIN END; $$ LANGUAGE plpgsql; --- rel_owner_is ( relation, user ) -CREATE OR REPLACE FUNCTION rel_owner_is ( NAME, NAME ) +-- relation_owner_is ( relation, user ) +CREATE OR REPLACE FUNCTION relation_owner_is ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT rel_owner_is( + SELECT relation_owner_is( $1, $2, 'Relation ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) ); diff --git a/test/expected/ownership.out b/test/expected/ownership.out index 3e917d76ef18..3cd4839ad867 100644 --- a/test/expected/ownership.out +++ b/test/expected/ownership.out @@ -12,48 +12,48 @@ ok 9 - db_owner_is(non-db, user) should have the proper diagnostics ok 10 - db_owner_is(db, non-user) should fail ok 11 - db_owner_is(db, non-user) should have the proper description ok 12 - db_owner_is(db, non-user) should have the proper diagnostics -ok 13 - rel_owner_is(sch, tab, user, desc) should pass -ok 14 - rel_owner_is(sch, tab, user, desc) should have the proper description -ok 15 - rel_owner_is(sch, tab, user, desc) should have the proper diagnostics -ok 16 - rel_owner_is(sch, tab, user) should pass -ok 17 - rel_owner_is(sch, tab, user) should have the proper description -ok 18 - rel_owner_is(sch, tab, user) should have the proper diagnostics -ok 19 - rel_owner_is(non-sch, tab, user) should fail -ok 20 - rel_owner_is(non-sch, tab, user) should have the proper description -ok 21 - rel_owner_is(non-sch, tab, user) should have the proper diagnostics -ok 22 - rel_owner_is(sch, non-tab, user) should fail -ok 23 - rel_owner_is(sch, non-tab, user) should have the proper description -ok 24 - rel_owner_is(sch, non-tab, user) should have the proper diagnostics -ok 25 - rel_owner_is(tab, user, desc) should pass -ok 26 - rel_owner_is(tab, user, desc) should have the proper description -ok 27 - rel_owner_is(tab, user, desc) should have the proper diagnostics -ok 28 - rel_owner_is(tab, user) should pass -ok 29 - rel_owner_is(tab, user) should have the proper description -ok 30 - rel_owner_is(tab, user) should have the proper diagnostics -ok 31 - rel_owner_is(non-tab, user) should fail -ok 32 - rel_owner_is(non-tab, user) should have the proper description -ok 33 - rel_owner_is(non-tab, user) should have the proper diagnostics -ok 34 - rel_owner_is(sch, seq, user, desc) should pass -ok 35 - rel_owner_is(sch, seq, user, desc) should have the proper description -ok 36 - rel_owner_is(sch, seq, user, desc) should have the proper diagnostics -ok 37 - rel_owner_is(sch, seq, user) should pass -ok 38 - rel_owner_is(sch, seq, user) should have the proper description -ok 39 - rel_owner_is(sch, seq, user) should have the proper diagnostics -ok 40 - rel_owner_is(non-sch, seq, user) should fail -ok 41 - rel_owner_is(non-sch, seq, user) should have the proper description -ok 42 - rel_owner_is(non-sch, seq, user) should have the proper diagnostics -ok 43 - rel_owner_is(sch, non-seq, user) should fail -ok 44 - rel_owner_is(sch, non-seq, user) should have the proper description -ok 45 - rel_owner_is(sch, non-seq, user) should have the proper diagnostics -ok 46 - rel_owner_is(seq, user, desc) should pass -ok 47 - rel_owner_is(seq, user, desc) should have the proper description -ok 48 - rel_owner_is(seq, user, desc) should have the proper diagnostics -ok 49 - rel_owner_is(seq, user) should pass -ok 50 - rel_owner_is(seq, user) should have the proper description -ok 51 - rel_owner_is(seq, user) should have the proper diagnostics -ok 52 - rel_owner_is(non-seq, user) should fail -ok 53 - rel_owner_is(non-seq, user) should have the proper description -ok 54 - rel_owner_is(non-seq, user) should have the proper diagnostics +ok 13 - relation_owner_is(sch, tab, user, desc) should pass +ok 14 - relation_owner_is(sch, tab, user, desc) should have the proper description +ok 15 - relation_owner_is(sch, tab, user, desc) should have the proper diagnostics +ok 16 - relation_owner_is(sch, tab, user) should pass +ok 17 - relation_owner_is(sch, tab, user) should have the proper description +ok 18 - relation_owner_is(sch, tab, user) should have the proper diagnostics +ok 19 - relation_owner_is(non-sch, tab, user) should fail +ok 20 - relation_owner_is(non-sch, tab, user) should have the proper description +ok 21 - relation_owner_is(non-sch, tab, user) should have the proper diagnostics +ok 22 - relation_owner_is(sch, non-tab, user) should fail +ok 23 - relation_owner_is(sch, non-tab, user) should have the proper description +ok 24 - relation_owner_is(sch, non-tab, user) should have the proper diagnostics +ok 25 - relation_owner_is(tab, user, desc) should pass +ok 26 - relation_owner_is(tab, user, desc) should have the proper description +ok 27 - relation_owner_is(tab, user, desc) should have the proper diagnostics +ok 28 - relation_owner_is(tab, user) should pass +ok 29 - relation_owner_is(tab, user) should have the proper description +ok 30 - relation_owner_is(tab, user) should have the proper diagnostics +ok 31 - relation_owner_is(non-tab, user) should fail +ok 32 - relation_owner_is(non-tab, user) should have the proper description +ok 33 - relation_owner_is(non-tab, user) should have the proper diagnostics +ok 34 - relation_owner_is(sch, seq, user, desc) should pass +ok 35 - relation_owner_is(sch, seq, user, desc) should have the proper description +ok 36 - relation_owner_is(sch, seq, user, desc) should have the proper diagnostics +ok 37 - relation_owner_is(sch, seq, user) should pass +ok 38 - relation_owner_is(sch, seq, user) should have the proper description +ok 39 - relation_owner_is(sch, seq, user) should have the proper diagnostics +ok 40 - relation_owner_is(non-sch, seq, user) should fail +ok 41 - relation_owner_is(non-sch, seq, user) should have the proper description +ok 42 - relation_owner_is(non-sch, seq, user) should have the proper diagnostics +ok 43 - relation_owner_is(sch, non-seq, user) should fail +ok 44 - relation_owner_is(sch, non-seq, user) should have the proper description +ok 45 - relation_owner_is(sch, non-seq, user) should have the proper diagnostics +ok 46 - relation_owner_is(seq, user, desc) should pass +ok 47 - relation_owner_is(seq, user, desc) should have the proper description +ok 48 - relation_owner_is(seq, user, desc) should have the proper diagnostics +ok 49 - relation_owner_is(seq, user) should pass +ok 50 - relation_owner_is(seq, user) should have the proper description +ok 51 - relation_owner_is(seq, user) should have the proper diagnostics +ok 52 - relation_owner_is(non-seq, user) should fail +ok 53 - relation_owner_is(non-seq, user) should have the proper description +ok 54 - relation_owner_is(non-seq, user) should have the proper diagnostics ok 55 - table_owner_is(sch, tab, user, desc) should pass ok 56 - table_owner_is(sch, tab, user, desc) should have the proper description ok 57 - table_owner_is(sch, tab, user, desc) should have the proper diagnostics diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 11c3c977a25c..3f43e9f5beb2 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -61,117 +61,117 @@ SELECT * FROM check_test( ); /****************************************************************************/ --- Test rel_owner_is() with a table. +-- Test relation_owner_is() with a table. SELECT * FROM check_test( - rel_owner_is('public', 'sometab', current_user, 'mumble'), + relation_owner_is('public', 'sometab', current_user, 'mumble'), true, - 'rel_owner_is(sch, tab, user, desc)', + 'relation_owner_is(sch, tab, user, desc)', 'mumble', '' ); SELECT * FROM check_test( - rel_owner_is('public', 'sometab', current_user), + relation_owner_is('public', 'sometab', current_user), true, - 'rel_owner_is(sch, tab, user)', + 'relation_owner_is(sch, tab, user)', 'Relation public.sometab should be owned by ' || current_user, '' ); SELECT * FROM check_test( - rel_owner_is('__not__public', 'sometab', current_user, 'mumble'), + relation_owner_is('__not__public', 'sometab', current_user, 'mumble'), false, - 'rel_owner_is(non-sch, tab, user)', + 'relation_owner_is(non-sch, tab, user)', 'mumble', ' Relation __not__public.sometab does not exist' ); SELECT * FROM check_test( - rel_owner_is('public', '__not__sometab', current_user, 'mumble'), + relation_owner_is('public', '__not__sometab', current_user, 'mumble'), false, - 'rel_owner_is(sch, non-tab, user)', + 'relation_owner_is(sch, non-tab, user)', 'mumble', ' Relation public.__not__sometab does not exist' ); SELECT * FROM check_test( - rel_owner_is('sometab', current_user, 'mumble'), + relation_owner_is('sometab', current_user, 'mumble'), true, - 'rel_owner_is(tab, user, desc)', + 'relation_owner_is(tab, user, desc)', 'mumble', '' ); SELECT * FROM check_test( - rel_owner_is('sometab', current_user), + relation_owner_is('sometab', current_user), true, - 'rel_owner_is(tab, user)', + 'relation_owner_is(tab, user)', 'Relation sometab should be owned by ' || current_user, '' ); SELECT * FROM check_test( - rel_owner_is('__not__sometab', current_user, 'mumble'), + relation_owner_is('__not__sometab', current_user, 'mumble'), false, - 'rel_owner_is(non-tab, user)', + 'relation_owner_is(non-tab, user)', 'mumble', ' Relation __not__sometab does not exist' ); /****************************************************************************/ --- Test rel_owner_is() with a schema. +-- Test relation_owner_is() with a schema. SELECT * FROM check_test( - rel_owner_is('public', 'someseq', current_user, 'mumble'), + relation_owner_is('public', 'someseq', current_user, 'mumble'), true, - 'rel_owner_is(sch, seq, user, desc)', + 'relation_owner_is(sch, seq, user, desc)', 'mumble', '' ); SELECT * FROM check_test( - rel_owner_is('public', 'someseq', current_user), + relation_owner_is('public', 'someseq', current_user), true, - 'rel_owner_is(sch, seq, user)', + 'relation_owner_is(sch, seq, user)', 'Relation public.someseq should be owned by ' || current_user, '' ); SELECT * FROM check_test( - rel_owner_is('__not__public', 'someseq', current_user, 'mumble'), + relation_owner_is('__not__public', 'someseq', current_user, 'mumble'), false, - 'rel_owner_is(non-sch, seq, user)', + 'relation_owner_is(non-sch, seq, user)', 'mumble', ' Relation __not__public.someseq does not exist' ); SELECT * FROM check_test( - rel_owner_is('public', '__not__someseq', current_user, 'mumble'), + relation_owner_is('public', '__not__someseq', current_user, 'mumble'), false, - 'rel_owner_is(sch, non-seq, user)', + 'relation_owner_is(sch, non-seq, user)', 'mumble', ' Relation public.__not__someseq does not exist' ); SELECT * FROM check_test( - rel_owner_is('someseq', current_user, 'mumble'), + relation_owner_is('someseq', current_user, 'mumble'), true, - 'rel_owner_is(seq, user, desc)', + 'relation_owner_is(seq, user, desc)', 'mumble', '' ); SELECT * FROM check_test( - rel_owner_is('someseq', current_user), + relation_owner_is('someseq', current_user), true, - 'rel_owner_is(seq, user)', + 'relation_owner_is(seq, user)', 'Relation someseq should be owned by ' || current_user, '' ); SELECT * FROM check_test( - rel_owner_is('__not__someseq', current_user, 'mumble'), + relation_owner_is('__not__someseq', current_user, 'mumble'), false, - 'rel_owner_is(non-seq, user)', + 'relation_owner_is(non-seq, user)', 'mumble', ' Relation __not__someseq does not exist' ); From 52dacd9ed672ad48501bd9254cd49675dc5514fa Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 15:15:05 -0800 Subject: [PATCH 0691/1195] Add `function_owner_is()`. --- Changes | 1 + doc/pgtap.mmd | 50 ++++++++++++++- sql/pgtap--0.91.0--0.92.0.sql | 94 ++++++++++++++++++++++++++++ sql/pgtap.sql.in | 76 ++++++++++++++++++++++- test/expected/ownership.out | 41 +++++++++++- test/sql/ownership.sql | 114 +++++++++++++++++++++++++++++++++- 6 files changed, 369 insertions(+), 7 deletions(-) diff --git a/Changes b/Changes index 921e59680415..41c6e249a9a0 100644 --- a/Changes +++ b/Changes @@ -17,6 +17,7 @@ Revision history for pgTAP + `sequence_owner_is()` + `composite_owner_is()` + `foreign_table_owner_is()` + + `function_owner_is()` 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 4c56fc6c1e99..c9b622a736ec 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5456,6 +5456,53 @@ diagnostics will look something like: # have: postgres # want: root +### `function_owner_is ()` ### + + SELECT function_owner_is ( :schema, :function, :args, :user, :description ); + SELECT function_owner_is ( :function, :args, :user, :description ); + SELECT function_owner_is ( :schema, :function, :args, :user ); + SELECT function_owner_is ( :function, :args, :user ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the `:function`. + +`:function` +: Name of a function. + +`:args` +: Array of data types of the function arguments. + +`:user` +: Name of a user. + +`:description` +: A short description of the test. + +Tests the ownership of a function. If the `:description` argument is omitted, +an appropriate description will be created. Examples: + + SELECT function_owner_is( + 'public', 'frobulate', ARRAY['integer', 'text'], 'someuser', + 'public.frobulate(integer, text) should be owned by someuser' + ); + SELECT function_owner_is( 'masticate', ARRAY['text'], current_user ); + +In the event that the test fails because the function in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Function foo() should be owned by www" + # Function foo() does not exist + +If the test fails because the function is not owned by the specified user, the +diagnostics will look something like: + + # Failed test 17: "Function bar() should be owned by root" + # have: postgres + # want: root + No Test for the Wicked ====================== @@ -6231,9 +6278,6 @@ To Do * Add `schema, table, colname` variations of the table-checking functions (`table_has_column()`, `col_type_is()`, etc.). That is, allow the description to be optional if the schema is included in the function call. -* Add more functions to test for object ownership. - + `function_owner_is()` - + etc. * Add some sort of tests for permisions. Something like: `table_privs_are(:table, :user, :privs[])`, and would have variations for database, sequence, function, language, schema, and tablespace. diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index ade97c763521..0d5b8d86279d 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -489,3 +489,97 @@ CREATE OR REPLACE FUNCTION hasnt_relation ( NAME ) RETURNS TEXT AS $$ SELECT hasnt_relation( $1, 'Relation ' || quote_ident($1) || ' should not exist' ); $$ LANGUAGE SQL; + +DROP VIEW tap_funky; +CREATE 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, + p.proisagg AS is_agg, + 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 _get_func_owner ( NAME, NAME, NAME[] ) +RETURNS NAME AS $$ + SELECT owner + FROM tap_funky + WHERE schema = $1 + AND name = $2 + AND args = array_to_string($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 = array_to_string($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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 2246bbb72e17..64279d9876d3 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2314,6 +2314,7 @@ 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, @@ -5803,7 +5804,6 @@ RETURNS SETOF TEXT AS $$ SELECT * FROM check_test( $1, $2, NULL, NULL, NULL, FALSE ); $$ LANGUAGE sql; - CREATE OR REPLACE FUNCTION findfuncs( NAME, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( @@ -7887,3 +7887,77 @@ RETURNS TEXT AS $$ '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 = array_to_string($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 = array_to_string($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; diff --git a/test/expected/ownership.out b/test/expected/ownership.out index 3cd4839ad867..c1254703ccd0 100644 --- a/test/expected/ownership.out +++ b/test/expected/ownership.out @@ -1,5 +1,5 @@ \unset ECHO -1..189 +1..228 ok 1 - db_owner_is(db, user, desc) should pass ok 2 - db_owner_is(db, user, desc) should have the proper description ok 3 - db_owner_is(db, user, desc) should have the proper diagnostics @@ -189,3 +189,42 @@ ok 186 - foreign_table_owner_is(sch, tab, user, desc) should have the proper dia ok 187 - foreign_table_owner_is(tab, user, desc) should fail ok 188 - foreign_table_owner_is(tab, user, desc) should have the proper description ok 189 - foreign_table_owner_is(tab, user, desc) should have the proper diagnostics +ok 190 - function_owner_is(sch, function, args[integer], user, desc) should pass +ok 191 - function_owner_is(sch, function, args[integer], user, desc) should have the proper description +ok 192 - function_owner_is(sch, function, args[integer], user, desc) should have the proper diagnostics +ok 193 - function_owner_is(sch, function, args[integer], user) should pass +ok 194 - function_owner_is(sch, function, args[integer], user) should have the proper description +ok 195 - function_owner_is(sch, function, args[integer], user) should have the proper diagnostics +ok 196 - function_owner_is(sch, function, args[], user, desc) should pass +ok 197 - function_owner_is(sch, function, args[], user, desc) should have the proper description +ok 198 - function_owner_is(sch, function, args[], user, desc) should have the proper diagnostics +ok 199 - function_owner_is(sch, function, args[], user) should pass +ok 200 - function_owner_is(sch, function, args[], user) should have the proper description +ok 201 - function_owner_is(sch, function, args[], user) should have the proper diagnostics +ok 202 - function_owner_is(function, args[integer], user, desc) should pass +ok 203 - function_owner_is(function, args[integer], user, desc) should have the proper description +ok 204 - function_owner_is(function, args[integer], user, desc) should have the proper diagnostics +ok 205 - function_owner_is(function, args[integer], user) should pass +ok 206 - function_owner_is(function, args[integer], user) should have the proper description +ok 207 - function_owner_is(function, args[integer], user) should have the proper diagnostics +ok 208 - function_owner_is(function, args[], user, desc) should pass +ok 209 - function_owner_is(function, args[], user, desc) should have the proper description +ok 210 - function_owner_is(function, args[], user, desc) should have the proper diagnostics +ok 211 - function_owner_is(function, args[], user) should pass +ok 212 - function_owner_is(function, args[], user) should have the proper description +ok 213 - function_owner_is(function, args[], user) should have the proper diagnostics +ok 214 - function_owner_is(sch, non-function, args[integer], user, desc) should fail +ok 215 - function_owner_is(sch, non-function, args[integer], user, desc) should have the proper description +ok 216 - function_owner_is(sch, non-function, args[integer], user, desc) should have the proper diagnostics +ok 217 - function_owner_is(non-sch, function, args[integer], user, desc) should fail +ok 218 - function_owner_is(non-sch, function, args[integer], user, desc) should have the proper description +ok 219 - function_owner_is(non-sch, function, args[integer], user, desc) should have the proper diagnostics +ok 220 - function_owner_is(non-function, args[integer], user, desc) should fail +ok 221 - function_owner_is(non-function, args[integer], user, desc) should have the proper description +ok 222 - function_owner_is(non-function, args[integer], user, desc) should have the proper diagnostics +ok 223 - function_owner_is(sch, function, args[integer], non-user, desc) should fail +ok 224 - function_owner_is(sch, function, args[integer], non-user, desc) should have the proper description +ok 225 - function_owner_is(sch, function, args[integer], non-user, desc) should have the proper diagnostics +ok 226 - function_owner_is(function, args[integer], non-user, desc) should fail +ok 227 - function_owner_is(function, args[integer], non-user, desc) should have the proper description +ok 228 - function_owner_is(function, args[integer], non-user, desc) should have the proper diagnostics diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 3f43e9f5beb2..b4ba84cdfa05 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(189); +SELECT plan(228); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -24,6 +24,8 @@ CREATE SEQUENCE public.someseq; CREATE SCHEMA someschema; +CREATE FUNCTION public.somefunction(int) RETURNS VOID LANGUAGE SQL AS ''; + RESET client_min_messages; /****************************************************************************/ @@ -639,8 +641,116 @@ END; $$ LANGUAGE PLPGSQL; SELECT * FROM test_fdw(); + +/****************************************************************************/ +-- Test function_owner_is(). +SELECT * FROM check_test( + function_owner_is('public', 'somefunction', ARRAY['integer'], current_user, 'mumble'), + true, + 'function_owner_is(sch, function, args[integer], user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + function_owner_is('public', 'somefunction', ARRAY['integer'], current_user), + true, + 'function_owner_is(sch, function, args[integer], user)', + 'Function public.somefunction(integer) should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + function_owner_is('public', 'test_fdw', ARRAY[]::NAME[], current_user, 'mumble'), + true, + 'function_owner_is(sch, function, args[], user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + function_owner_is('public', 'test_fdw', ARRAY[]::NAME[], current_user), + true, + 'function_owner_is(sch, function, args[], user)', + 'Function public.test_fdw() should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + function_owner_is('somefunction', ARRAY['integer'], current_user, 'mumble'), + true, + 'function_owner_is(function, args[integer], user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + function_owner_is('somefunction', ARRAY['integer'], current_user), + true, + 'function_owner_is(function, args[integer], user)', + 'Function somefunction(integer) should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + function_owner_is('test_fdw', ARRAY[]::NAME[], current_user, 'mumble'), + true, + 'function_owner_is(function, args[], user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + function_owner_is('test_fdw', ARRAY[]::NAME[], current_user), + true, + 'function_owner_is(function, args[], user)', + 'Function test_fdw() should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + function_owner_is('public', '__non__function', ARRAY['integer'], current_user, 'mumble'), + false, + 'function_owner_is(sch, non-function, args[integer], user, desc)', + 'mumble', + ' Function public.__non__function(integer) does not exist' +); + +SELECT * FROM check_test( + function_owner_is('__non__public', 'function', ARRAY['integer'], current_user, 'mumble'), + false, + 'function_owner_is(non-sch, function, args[integer], user, desc)', + 'mumble', + ' Function __non__public.function(integer) does not exist' +); + +SELECT * FROM check_test( + function_owner_is('__non__function', ARRAY['integer'], current_user, 'mumble'), + false, + 'function_owner_is(non-function, args[integer], user, desc)', + 'mumble', + ' Function __non__function(integer) does not exist' +); + +SELECT * FROM check_test( + function_owner_is('public', 'somefunction', ARRAY['integer'], 'no one', 'mumble'), + false, + 'function_owner_is(sch, function, args[integer], non-user, desc)', + 'mumble', + ' have: ' || current_user || ' + want: no one' +); + +SELECT * FROM check_test( + function_owner_is('somefunction', ARRAY['integer'], 'no one', 'mumble'), + false, + 'function_owner_is(function, args[integer], non-user, desc)', + 'mumble', + ' have: ' || current_user || ' + want: no one' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); ROLLBACK; - From 4682f71e79f3ec1a27621dc06a82b4ac5f54e8d1 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 15:18:32 -0800 Subject: [PATCH 0692/1195] Increment to v0.92.0. --- pgtap-core.control | 2 +- pgtap-schema.control | 2 +- pgtap.control | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pgtap-core.control b/pgtap-core.control index eb51e6258b9d..ebf84baedd9a 100644 --- a/pgtap-core.control +++ b/pgtap-core.control @@ -1,6 +1,6 @@ # pgTAP Core extension comment = 'Unit testing for PostgreSQL' -default_version = '0.91.0' +default_version = '0.92.0' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true diff --git a/pgtap-schema.control b/pgtap-schema.control index 7602db5242f6..ab146ef61251 100644 --- a/pgtap-schema.control +++ b/pgtap-schema.control @@ -1,6 +1,6 @@ # pgTAP Schema testing extension comment = 'Schema unit testing for PostgreSQL' -default_version = '0.91.0' +default_version = '0.92.0' module_pathname = '$libdir/pgtap' requires = 'pgtap-core' relocatable = true diff --git a/pgtap.control b/pgtap.control index 6b9c84e61b2b..945c72d81aa2 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '0.91.0' +default_version = '0.92.0' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true From b57a4713569e4e333209e4f3b36faabd30ceea11 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 15:27:49 -0800 Subject: [PATCH 0693/1195] More friendly message when no plan. Closes #22. --- Changes | 3 +++ sql/pgtap--0.91.0--0.92.0.sql | 14 ++++++++++++++ sql/pgtap.sql.in | 2 ++ 3 files changed, 19 insertions(+) diff --git a/Changes b/Changes index 41c6e249a9a0..dfc44756b5f6 100644 --- a/Changes +++ b/Changes @@ -18,6 +18,9 @@ Revision history for pgTAP + `composite_owner_is()` + `foreign_table_owner_is()` + `function_owner_is()` +* Changed the uninformative error 'relation "__tcache__" does not exist' to + the more useful 'You tried to run a test without a plan! Gotta have a plan'. + Thanks to Steven Samuel Cole for the report (issue #22). 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index 0d5b8d86279d..de364befc6b4 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -583,3 +583,17 @@ RETURNS TEXT AS $$ array_to_string($2, ', ') || ') should be owned by ' || quote_ident($3) ); $$ LANGUAGE sql; + +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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 64279d9876d3..3752f689f7db 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -101,6 +101,8 @@ BEGIN 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; From 28173a9844f1fb49533a1fe034b24cef8a178c9d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 15:36:10 -0800 Subject: [PATCH 0694/1195] Ignore internal triggers in `triggers_are()`. Closes #17. --- Changes | 2 + sql/pgtap--0.91.0--0.92.0.sql | 92 +++++++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 4 ++ 3 files changed, 98 insertions(+) diff --git a/Changes b/Changes index dfc44756b5f6..88a0727d9b81 100644 --- a/Changes +++ b/Changes @@ -21,6 +21,8 @@ Revision history for pgTAP * Changed the uninformative error 'relation "__tcache__" does not exist' to the more useful 'You tried to run a test without a plan! Gotta have a plan'. Thanks to Steven Samuel Cole for the report (issue #22). +* Changed `triggers_are()` so that it ignores internal triggers on PostgreSQL + 9.1 and higher. Thanks to Sherrylyn Branchaw for the report! 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index de364befc6b4..e6265a035ad0 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -597,3 +597,95 @@ 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 _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 + AND NOT t.tgisinternal + ); +$$ 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 NOT t.tgisinternal + ); +$$ 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( 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; + diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 3752f689f7db..1af3f3754d62 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7231,6 +7231,7 @@ RETURNS TEXT AS $$ 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) @@ -7245,6 +7246,7 @@ RETURNS TEXT AS $$ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 + AND NOT t.tgisinternal ), $4 ); @@ -7268,6 +7270,7 @@ RETURNS TEXT AS $$ 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) @@ -7281,6 +7284,7 @@ RETURNS TEXT AS $$ 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 ); From 037f49a51d08fc70ee2dafb74d45a6ac5559eb61 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 16:58:10 -0800 Subject: [PATCH 0695/1195] Offer more useful diagnostics on data type errors in results_eq(). It was confusing people when output looked like this: Columns differ between queries: have: (1,2,3) want: (1,2,3) This would happen when the data types were different but the values the same (e.g., the first row might have integers while the second would have bigints). Make two changes to try to adddress this information: 1. Change the diagnostic message to the more informative "Number of columns or their types differ between the queries". 2. Don't show the "have" and the "want" if they're identical. Hopefully this will be less confusing to folks. Closes #15. --- Changes | 7 +- doc/pgtap.mmd | 11 +- sql/pgtap--0.91.0--0.92.0.sql | 43 +++ sql/pgtap.sql.in | 8 +- test/expected/resultset.out | 639 +++++++++++++++++----------------- test/sql/resultset.sql | 22 +- test/sql/valueset.sql | 8 +- 7 files changed, 401 insertions(+), 337 deletions(-) diff --git a/Changes b/Changes index 88a0727d9b81..819980d84313 100644 --- a/Changes +++ b/Changes @@ -20,9 +20,14 @@ Revision history for pgTAP + `function_owner_is()` * Changed the uninformative error 'relation "__tcache__" does not exist' to the more useful 'You tried to run a test without a plan! Gotta have a plan'. - Thanks to Steven Samuel Cole for the report (issue #22). + Thanks to Steven Samuel Cole for [the + report](https://github.com/theory/pgtap/issues/22). * Changed `triggers_are()` so that it ignores internal triggers on PostgreSQL 9.1 and higher. Thanks to Sherrylyn Branchaw for the report! +* Changed the diagnostic output of `results_eq()` for data type differences to + reduce confusion when the rows have the same values of different types. + Thanks to Peter Eisentraut and Taylor Ralston for [the + nudging](https://github.com/theory/pgtap/issues/15). 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index c9b622a736ec..1ae7effbeba4 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1207,7 +1207,7 @@ sets, or if results are of different data types, you'll get diagnostics like so: # Failed test 148 - # Column types differ between queries: + # Number of columns or their types differ between the queries: # have: (1) # want: (foo,1) @@ -1215,9 +1215,12 @@ On PostgreSQL 8.3 and down, the rows are cast to text for comparison, rather than compared as `record` objects. The downside to this necessity is that the test cannot detect incompatibilities in column numbers or types, or differences in columns that convert to the same text representation. For -example, a `NULL` column will be equivalent to an empty string. The upshot: -read failure diagnostics carefully and pay attention to data types on 8.3 and -down. +example, a `NULL` column will be equivalent to an empty string. As a result, +pgTAP will not show the `have` and `want` values if they are the same, just +the error message, like so: + + # Failed test 149 + # Number of columns or their types differ between the queries ### `results_ne()` ### diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index e6265a035ad0..bb83c5649657 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -689,3 +689,46 @@ RETURNS TEXT AS $$ ); $$ 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; +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 + 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 + ); +END; +$$ LANGUAGE plpgsql; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 1af3f3754d62..6217b462b2b1 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -6404,9 +6404,11 @@ BEGIN EXCEPTION 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 + 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 ); END; $$ LANGUAGE plpgsql; diff --git a/test/expected/resultset.out b/test/expected/resultset.out index 9f8ccc09bc7d..59b7d3f41259 100644 --- a/test/expected/resultset.out +++ b/test/expected/resultset.out @@ -1,5 +1,5 @@ \unset ECHO -1..515 +1..518 ok 1 - Should create temp table with simple query ok 2 - Table __foonames__ should exist ok 3 - Should create a temp table for a prepared statement @@ -175,343 +175,346 @@ ok 172 - results_eq(values, values) mismatch should have the proper diagnostics ok 173 - results_eq(values, values) subtle mismatch should fail ok 174 - results_eq(values, values) subtle mismatch should have the proper description ok 175 - results_eq(values, values) subtle mismatch should have the proper diagnostics -ok 176 - results_eq(values, values) fail column count should fail -ok 177 - results_eq(values, values) fail column count should have the proper description -ok 178 - results_eq(values, values) fail column count should have the proper diagnostics -ok 179 - results_eq(cursor, cursor) should pass -ok 180 - results_eq(cursor, cursor) should have the proper description -ok 181 - results_eq(cursor, cursor) should have the proper diagnostics -ok 182 - results_eq(cursor, prepared) should pass -ok 183 - results_eq(cursor, prepared) should have the proper description -ok 184 - results_eq(cursor, prepared) should have the proper diagnostics -ok 185 - results_eq(prepared, cursor) should pass -ok 186 - results_eq(prepared, cursor) should have the proper description -ok 187 - results_eq(prepared, cursor) should have the proper diagnostics -ok 188 - results_eq(cursor, sql) should pass -ok 189 - results_eq(cursor, sql) should have the proper description -ok 190 - results_eq(cursor, sql) should have the proper diagnostics -ok 191 - results_eq(sql, cursor) should pass -ok 192 - results_eq(sql, cursor) should have the proper description -ok 193 - results_eq(sql, cursor) should have the proper diagnostics -ok 194 - set_has( prepared, prepared, description ) should pass -ok 195 - set_has( prepared, prepared, description ) should have the proper description -ok 196 - set_has( prepared, prepared, description ) should have the proper diagnostics -ok 197 - set_has( prepared, subprepared ) should pass -ok 198 - set_has( prepared, subprepared ) should have the proper description -ok 199 - set_has( prepared, subprepared ) should have the proper diagnostics -ok 200 - set_has( execute, execute ) should pass -ok 201 - set_has( execute, execute ) should have the proper description -ok 202 - set_has( execute, execute ) should have the proper diagnostics -ok 203 - set_has( select, select ) should pass -ok 204 - set_has( select, select ) should have the proper description -ok 205 - set_has( select, select ) should have the proper diagnostics -ok 206 - set_has( prepared, empty ) should pass -ok 207 - set_has( prepared, empty ) should have the proper description -ok 208 - set_has( prepared, empty ) should have the proper diagnostics -ok 209 - set_has( prepared, dupes ) should pass -ok 210 - set_has( prepared, dupes ) should have the proper description -ok 211 - set_has( prepared, dupes ) should have the proper diagnostics -ok 212 - set_has( dupes, values ) should pass -ok 213 - set_has( dupes, values ) should have the proper description -ok 214 - set_has( dupes, values ) should have the proper diagnostics -ok 215 - set_has( missing1, expect ) should fail -ok 216 - set_has( missing1, expect ) should have the proper description -ok 217 - set_has( missing1, expect ) should have the proper diagnostics -ok 218 - set_has(missing2, expect ) should fail -ok 219 - set_has(missing2, expect ) should have the proper description -ok 220 - set_has(missing2, expect ) should have the proper diagnostics -ok 221 - set_has((int,text), (text,int)) should fail -ok 222 - set_has((int,text), (text,int)) should have the proper description -ok 223 - set_has((int,text), (text,int)) should have the proper diagnostics -ok 224 - set_has((int), (text,int)) should fail -ok 225 - set_has((int), (text,int)) should have the proper description -ok 226 - set_has((int), (text,int)) should have the proper diagnostics -ok 227 - bag_has( prepared, prepared, description ) should pass -ok 228 - bag_has( prepared, prepared, description ) should have the proper description -ok 229 - bag_has( prepared, prepared, description ) should have the proper diagnostics -ok 230 - bag_has( prepared, subprepared ) should pass -ok 231 - bag_has( prepared, subprepared ) should have the proper description -ok 232 - bag_has( prepared, subprepared ) should have the proper diagnostics -ok 233 - bag_has( execute, execute ) should pass -ok 234 - bag_has( execute, execute ) should have the proper description -ok 235 - bag_has( execute, execute ) should have the proper diagnostics -ok 236 - bag_has( select, select ) should pass -ok 237 - bag_has( select, select ) should have the proper description -ok 238 - bag_has( select, select ) should have the proper diagnostics -ok 239 - bag_has( prepared, empty ) should pass -ok 240 - bag_has( prepared, empty ) should have the proper description -ok 241 - bag_has( prepared, empty ) should have the proper diagnostics -ok 242 - bag_has( prepared, dupes ) should fail -ok 243 - bag_has( prepared, dupes ) should have the proper description -ok 244 - bag_has( prepared, dupes ) should have the proper diagnostics -ok 245 - bag_has( dupes, values ) should pass -ok 246 - bag_has( dupes, values ) should have the proper description -ok 247 - bag_has( dupes, values ) should have the proper diagnostics -ok 248 - bag_has( missing1, expect ) should fail -ok 249 - bag_has( missing1, expect ) should have the proper description -ok 250 - bag_has( missing1, expect ) should have the proper diagnostics -ok 251 - bag_has(missing2, expect ) should fail -ok 252 - bag_has(missing2, expect ) should have the proper description -ok 253 - bag_has(missing2, expect ) should have the proper diagnostics -ok 254 - bag_has((int,text), (text,int)) should fail -ok 255 - bag_has((int,text), (text,int)) should have the proper description -ok 256 - bag_has((int,text), (text,int)) should have the proper diagnostics -ok 257 - bag_has((int), (text,int)) should fail -ok 258 - bag_has((int), (text,int)) should have the proper description -ok 259 - bag_has((int), (text,int)) should have the proper diagnostics -ok 260 - set_hasnt( prepared, prepared, description ) should pass -ok 261 - set_hasnt( prepared, prepared, description ) should have the proper description -ok 262 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 176 - results_eq(values, values) integer type mismatch should fail +ok 177 - results_eq(values, values) integer type mismatch should have the proper description +ok 178 - results_eq(values, values) integer type mismatch should have the proper diagnostics +ok 179 - results_eq(values, values) fail column count should fail +ok 180 - results_eq(values, values) fail column count should have the proper description +ok 181 - results_eq(values, values) fail column count should have the proper diagnostics +ok 182 - results_eq(cursor, cursor) should pass +ok 183 - results_eq(cursor, cursor) should have the proper description +ok 184 - results_eq(cursor, cursor) should have the proper diagnostics +ok 185 - results_eq(cursor, prepared) should pass +ok 186 - results_eq(cursor, prepared) should have the proper description +ok 187 - results_eq(cursor, prepared) should have the proper diagnostics +ok 188 - results_eq(prepared, cursor) should pass +ok 189 - results_eq(prepared, cursor) should have the proper description +ok 190 - results_eq(prepared, cursor) should have the proper diagnostics +ok 191 - results_eq(cursor, sql) should pass +ok 192 - results_eq(cursor, sql) should have the proper description +ok 193 - results_eq(cursor, sql) should have the proper diagnostics +ok 194 - results_eq(sql, cursor) should pass +ok 195 - results_eq(sql, cursor) should have the proper description +ok 196 - results_eq(sql, cursor) should have the proper diagnostics +ok 197 - set_has( prepared, prepared, description ) should pass +ok 198 - set_has( prepared, prepared, description ) should have the proper description +ok 199 - set_has( prepared, prepared, description ) should have the proper diagnostics +ok 200 - set_has( prepared, subprepared ) should pass +ok 201 - set_has( prepared, subprepared ) should have the proper description +ok 202 - set_has( prepared, subprepared ) should have the proper diagnostics +ok 203 - set_has( execute, execute ) should pass +ok 204 - set_has( execute, execute ) should have the proper description +ok 205 - set_has( execute, execute ) should have the proper diagnostics +ok 206 - set_has( select, select ) should pass +ok 207 - set_has( select, select ) should have the proper description +ok 208 - set_has( select, select ) should have the proper diagnostics +ok 209 - set_has( prepared, empty ) should pass +ok 210 - set_has( prepared, empty ) should have the proper description +ok 211 - set_has( prepared, empty ) should have the proper diagnostics +ok 212 - set_has( prepared, dupes ) should pass +ok 213 - set_has( prepared, dupes ) should have the proper description +ok 214 - set_has( prepared, dupes ) should have the proper diagnostics +ok 215 - set_has( dupes, values ) should pass +ok 216 - set_has( dupes, values ) should have the proper description +ok 217 - set_has( dupes, values ) should have the proper diagnostics +ok 218 - set_has( missing1, expect ) should fail +ok 219 - set_has( missing1, expect ) should have the proper description +ok 220 - set_has( missing1, expect ) should have the proper diagnostics +ok 221 - set_has(missing2, expect ) should fail +ok 222 - set_has(missing2, expect ) should have the proper description +ok 223 - set_has(missing2, expect ) should have the proper diagnostics +ok 224 - set_has((int,text), (text,int)) should fail +ok 225 - set_has((int,text), (text,int)) should have the proper description +ok 226 - set_has((int,text), (text,int)) should have the proper diagnostics +ok 227 - set_has((int), (text,int)) should fail +ok 228 - set_has((int), (text,int)) should have the proper description +ok 229 - set_has((int), (text,int)) should have the proper diagnostics +ok 230 - bag_has( prepared, prepared, description ) should pass +ok 231 - bag_has( prepared, prepared, description ) should have the proper description +ok 232 - bag_has( prepared, prepared, description ) should have the proper diagnostics +ok 233 - bag_has( prepared, subprepared ) should pass +ok 234 - bag_has( prepared, subprepared ) should have the proper description +ok 235 - bag_has( prepared, subprepared ) should have the proper diagnostics +ok 236 - bag_has( execute, execute ) should pass +ok 237 - bag_has( execute, execute ) should have the proper description +ok 238 - bag_has( execute, execute ) should have the proper diagnostics +ok 239 - bag_has( select, select ) should pass +ok 240 - bag_has( select, select ) should have the proper description +ok 241 - bag_has( select, select ) should have the proper diagnostics +ok 242 - bag_has( prepared, empty ) should pass +ok 243 - bag_has( prepared, empty ) should have the proper description +ok 244 - bag_has( prepared, empty ) should have the proper diagnostics +ok 245 - bag_has( prepared, dupes ) should fail +ok 246 - bag_has( prepared, dupes ) should have the proper description +ok 247 - bag_has( prepared, dupes ) should have the proper diagnostics +ok 248 - bag_has( dupes, values ) should pass +ok 249 - bag_has( dupes, values ) should have the proper description +ok 250 - bag_has( dupes, values ) should have the proper diagnostics +ok 251 - bag_has( missing1, expect ) should fail +ok 252 - bag_has( missing1, expect ) should have the proper description +ok 253 - bag_has( missing1, expect ) should have the proper diagnostics +ok 254 - bag_has(missing2, expect ) should fail +ok 255 - bag_has(missing2, expect ) should have the proper description +ok 256 - bag_has(missing2, expect ) should have the proper diagnostics +ok 257 - bag_has((int,text), (text,int)) should fail +ok 258 - bag_has((int,text), (text,int)) should have the proper description +ok 259 - bag_has((int,text), (text,int)) should have the proper diagnostics +ok 260 - bag_has((int), (text,int)) should fail +ok 261 - bag_has((int), (text,int)) should have the proper description +ok 262 - bag_has((int), (text,int)) should have the proper diagnostics ok 263 - set_hasnt( prepared, prepared, description ) should pass ok 264 - set_hasnt( prepared, prepared, description ) should have the proper description ok 265 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics -ok 266 - set_hasnt( execute, execute ) should pass -ok 267 - set_hasnt( execute, execute ) should have the proper description -ok 268 - set_hasnt( execute, execute ) should have the proper diagnostics -ok 269 - set_hasnt( select, select ) should pass -ok 270 - set_hasnt( select, select ) should have the proper description -ok 271 - set_hasnt( select, select ) should have the proper diagnostics -ok 272 - set_hasnt( prepared, empty ) should pass -ok 273 - set_hasnt( prepared, empty ) should have the proper description -ok 274 - set_hasnt( prepared, empty ) should have the proper diagnostics -ok 275 - set_hasnt( prepared, dupes ) should pass -ok 276 - set_hasnt( prepared, dupes ) should have the proper description -ok 277 - set_hasnt( prepared, dupes ) should have the proper diagnostics -ok 278 - set_hasnt( prepared, value ) should fail -ok 279 - set_hasnt( prepared, value ) should have the proper description -ok 280 - set_hasnt( prepared, value ) should have the proper diagnostics -ok 281 - set_hasnt( prepared, values ) should fail -ok 282 - set_hasnt( prepared, values ) should have the proper description -ok 283 - set_hasnt( prepared, values ) should have the proper diagnostics -ok 284 - set_hasnt((int,text), (text,int)) should fail -ok 285 - set_hasnt((int,text), (text,int)) should have the proper description -ok 286 - set_hasnt((int,text), (text,int)) should have the proper diagnostics -ok 287 - set_hasnt((int), (text,int)) should fail -ok 288 - set_hasnt((int), (text,int)) should have the proper description -ok 289 - set_hasnt((int), (text,int)) should have the proper diagnostics -ok 290 - bag_hasnt( prepared, prepared, description ) should pass -ok 291 - bag_hasnt( prepared, prepared, description ) should have the proper description -ok 292 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 266 - set_hasnt( prepared, prepared, description ) should pass +ok 267 - set_hasnt( prepared, prepared, description ) should have the proper description +ok 268 - set_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 269 - set_hasnt( execute, execute ) should pass +ok 270 - set_hasnt( execute, execute ) should have the proper description +ok 271 - set_hasnt( execute, execute ) should have the proper diagnostics +ok 272 - set_hasnt( select, select ) should pass +ok 273 - set_hasnt( select, select ) should have the proper description +ok 274 - set_hasnt( select, select ) should have the proper diagnostics +ok 275 - set_hasnt( prepared, empty ) should pass +ok 276 - set_hasnt( prepared, empty ) should have the proper description +ok 277 - set_hasnt( prepared, empty ) should have the proper diagnostics +ok 278 - set_hasnt( prepared, dupes ) should pass +ok 279 - set_hasnt( prepared, dupes ) should have the proper description +ok 280 - set_hasnt( prepared, dupes ) should have the proper diagnostics +ok 281 - set_hasnt( prepared, value ) should fail +ok 282 - set_hasnt( prepared, value ) should have the proper description +ok 283 - set_hasnt( prepared, value ) should have the proper diagnostics +ok 284 - set_hasnt( prepared, values ) should fail +ok 285 - set_hasnt( prepared, values ) should have the proper description +ok 286 - set_hasnt( prepared, values ) should have the proper diagnostics +ok 287 - set_hasnt((int,text), (text,int)) should fail +ok 288 - set_hasnt((int,text), (text,int)) should have the proper description +ok 289 - set_hasnt((int,text), (text,int)) should have the proper diagnostics +ok 290 - set_hasnt((int), (text,int)) should fail +ok 291 - set_hasnt((int), (text,int)) should have the proper description +ok 292 - set_hasnt((int), (text,int)) should have the proper diagnostics ok 293 - bag_hasnt( prepared, prepared, description ) should pass ok 294 - bag_hasnt( prepared, prepared, description ) should have the proper description ok 295 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics -ok 296 - bag_hasnt( execute, execute ) should pass -ok 297 - bag_hasnt( execute, execute ) should have the proper description -ok 298 - bag_hasnt( execute, execute ) should have the proper diagnostics -ok 299 - bag_hasnt( select, select ) should pass -ok 300 - bag_hasnt( select, select ) should have the proper description -ok 301 - bag_hasnt( select, select ) should have the proper diagnostics -ok 302 - bag_hasnt( prepared, empty ) should pass -ok 303 - bag_hasnt( prepared, empty ) should have the proper description -ok 304 - bag_hasnt( prepared, empty ) should have the proper diagnostics -ok 305 - bag_hasnt( prepared, value ) should fail -ok 306 - bag_hasnt( prepared, value ) should have the proper description -ok 307 - bag_hasnt( prepared, value ) should have the proper diagnostics -ok 308 - bag_hasnt( prepared, values ) should fail -ok 309 - bag_hasnt( prepared, values ) should have the proper description -ok 310 - bag_hasnt( prepared, values ) should have the proper diagnostics -ok 311 - bag_hasnt((int,text), (text,int)) should fail -ok 312 - bag_hasnt((int,text), (text,int)) should have the proper description -ok 313 - bag_hasnt((int,text), (text,int)) should have the proper diagnostics -ok 314 - bag_hasnt((int), (text,int)) should fail -ok 315 - bag_hasnt((int), (text,int)) should have the proper description -ok 316 - bag_hasnt((int), (text,int)) should have the proper diagnostics -ok 317 - bag_hasnt( dupes, dupes ) should fail -ok 318 - bag_hasnt( dupes, dupes ) should have the proper description -ok 319 - bag_hasnt( dupes, dupes ) should have the proper diagnostics -ok 320 - bag_hasnt( value, dupes ) should fail -ok 321 - bag_hasnt( value, dupes ) should have the proper description -ok 322 - bag_hasnt( value, dupes ) should have the proper diagnostics -ok 323 - set_eq(sql, array, desc) should pass -ok 324 - set_eq(sql, array, desc) should have the proper description -ok 325 - set_eq(sql, array, desc) should have the proper diagnostics -ok 326 - set_eq(sql, array) should pass -ok 327 - set_eq(sql, array) should have the proper description -ok 328 - set_eq(sql, array) should have the proper diagnostics -ok 329 - set_eq(sql, dupe array) should pass -ok 330 - set_eq(sql, dupe array) should have the proper description -ok 331 - set_eq(sql, dupe array) should have the proper diagnostics -ok 332 - set_eq(sql, array) extra record should fail -ok 333 - set_eq(sql, array) extra record should have the proper description -ok 334 - set_eq(sql, array) extra record should have the proper diagnostics -ok 335 - set_eq(sql, array) missing record should fail -ok 336 - set_eq(sql, array) missing record should have the proper description -ok 337 - set_eq(sql, array) missing record should have the proper diagnostics -ok 338 - set_eq(sql, array) incompatible types should fail -ok 339 - set_eq(sql, array) incompatible types should have the proper description -ok 340 - set_eq(sql, array) incompatible types should have the proper diagnostics +ok 296 - bag_hasnt( prepared, prepared, description ) should pass +ok 297 - bag_hasnt( prepared, prepared, description ) should have the proper description +ok 298 - bag_hasnt( prepared, prepared, description ) should have the proper diagnostics +ok 299 - bag_hasnt( execute, execute ) should pass +ok 300 - bag_hasnt( execute, execute ) should have the proper description +ok 301 - bag_hasnt( execute, execute ) should have the proper diagnostics +ok 302 - bag_hasnt( select, select ) should pass +ok 303 - bag_hasnt( select, select ) should have the proper description +ok 304 - bag_hasnt( select, select ) should have the proper diagnostics +ok 305 - bag_hasnt( prepared, empty ) should pass +ok 306 - bag_hasnt( prepared, empty ) should have the proper description +ok 307 - bag_hasnt( prepared, empty ) should have the proper diagnostics +ok 308 - bag_hasnt( prepared, value ) should fail +ok 309 - bag_hasnt( prepared, value ) should have the proper description +ok 310 - bag_hasnt( prepared, value ) should have the proper diagnostics +ok 311 - bag_hasnt( prepared, values ) should fail +ok 312 - bag_hasnt( prepared, values ) should have the proper description +ok 313 - bag_hasnt( prepared, values ) should have the proper diagnostics +ok 314 - bag_hasnt((int,text), (text,int)) should fail +ok 315 - bag_hasnt((int,text), (text,int)) should have the proper description +ok 316 - bag_hasnt((int,text), (text,int)) should have the proper diagnostics +ok 317 - bag_hasnt((int), (text,int)) should fail +ok 318 - bag_hasnt((int), (text,int)) should have the proper description +ok 319 - bag_hasnt((int), (text,int)) should have the proper diagnostics +ok 320 - bag_hasnt( dupes, dupes ) should fail +ok 321 - bag_hasnt( dupes, dupes ) should have the proper description +ok 322 - bag_hasnt( dupes, dupes ) should have the proper diagnostics +ok 323 - bag_hasnt( value, dupes ) should fail +ok 324 - bag_hasnt( value, dupes ) should have the proper description +ok 325 - bag_hasnt( value, dupes ) should have the proper diagnostics +ok 326 - set_eq(sql, array, desc) should pass +ok 327 - set_eq(sql, array, desc) should have the proper description +ok 328 - set_eq(sql, array, desc) should have the proper diagnostics +ok 329 - set_eq(sql, array) should pass +ok 330 - set_eq(sql, array) should have the proper description +ok 331 - set_eq(sql, array) should have the proper diagnostics +ok 332 - set_eq(sql, dupe array) should pass +ok 333 - set_eq(sql, dupe array) should have the proper description +ok 334 - set_eq(sql, dupe array) should have the proper diagnostics +ok 335 - set_eq(sql, array) extra record should fail +ok 336 - set_eq(sql, array) extra record should have the proper description +ok 337 - set_eq(sql, array) extra record should have the proper diagnostics +ok 338 - set_eq(sql, array) missing record should fail +ok 339 - set_eq(sql, array) missing record should have the proper description +ok 340 - set_eq(sql, array) missing record should have the proper diagnostics ok 341 - set_eq(sql, array) incompatible types should fail ok 342 - set_eq(sql, array) incompatible types should have the proper description ok 343 - set_eq(sql, array) incompatible types should have the proper diagnostics -ok 344 - bag_eq(sql, array, desc) should pass -ok 345 - bag_eq(sql, array, desc) should have the proper description -ok 346 - bag_eq(sql, array, desc) should have the proper diagnostics -ok 347 - bag_eq(sql, array) should pass -ok 348 - bag_eq(sql, array) should have the proper description -ok 349 - bag_eq(sql, array) should have the proper diagnostics -ok 350 - bag_eq(sql, dupe array) fail should fail -ok 351 - bag_eq(sql, dupe array) fail should have the proper description -ok 352 - bag_eq(sql, dupe array) fail should have the proper diagnostics -ok 353 - bag_eq(sql, array) extra record should fail -ok 354 - bag_eq(sql, array) extra record should have the proper description -ok 355 - bag_eq(sql, array) extra record should have the proper diagnostics -ok 356 - bag_eq(sql, array) missing record should fail -ok 357 - bag_eq(sql, array) missing record should have the proper description -ok 358 - bag_eq(sql, array) missing record should have the proper diagnostics -ok 359 - bag_eq(sql, array) incompatible types should fail -ok 360 - bag_eq(sql, array) incompatible types should have the proper description -ok 361 - bag_eq(sql, array) incompatible types should have the proper diagnostics +ok 344 - set_eq(sql, array) incompatible types should fail +ok 345 - set_eq(sql, array) incompatible types should have the proper description +ok 346 - set_eq(sql, array) incompatible types should have the proper diagnostics +ok 347 - bag_eq(sql, array, desc) should pass +ok 348 - bag_eq(sql, array, desc) should have the proper description +ok 349 - bag_eq(sql, array, desc) should have the proper diagnostics +ok 350 - bag_eq(sql, array) should pass +ok 351 - bag_eq(sql, array) should have the proper description +ok 352 - bag_eq(sql, array) should have the proper diagnostics +ok 353 - bag_eq(sql, dupe array) fail should fail +ok 354 - bag_eq(sql, dupe array) fail should have the proper description +ok 355 - bag_eq(sql, dupe array) fail should have the proper diagnostics +ok 356 - bag_eq(sql, array) extra record should fail +ok 357 - bag_eq(sql, array) extra record should have the proper description +ok 358 - bag_eq(sql, array) extra record should have the proper diagnostics +ok 359 - bag_eq(sql, array) missing record should fail +ok 360 - bag_eq(sql, array) missing record should have the proper description +ok 361 - bag_eq(sql, array) missing record should have the proper diagnostics ok 362 - bag_eq(sql, array) incompatible types should fail ok 363 - bag_eq(sql, array) incompatible types should have the proper description ok 364 - bag_eq(sql, array) incompatible types should have the proper diagnostics -ok 365 - set_ne(sql, array, desc) should pass -ok 366 - set_ne(sql, array, desc) should have the proper description -ok 367 - set_ne(sql, array, desc) should have the proper diagnostics -ok 368 - set_ne(sql, array) should pass -ok 369 - set_ne(sql, array) should have the proper description -ok 370 - set_ne(sql, array) should have the proper diagnostics -ok 371 - set_ne(sql, array) fail should fail -ok 372 - set_ne(sql, array) fail should have the proper description -ok 373 - set_ne(sql, array) fail should have the proper diagnostics -ok 374 - set_ne(sql, dupes array) fail should fail -ok 375 - set_ne(sql, dupes array) fail should have the proper description -ok 376 - set_ne(sql, dupes array) fail should have the proper diagnostics -ok 377 - set_ne(sql, array) incompatible types should fail -ok 378 - set_ne(sql, array) incompatible types should have the proper description -ok 379 - set_ne(sql, array) incompatible types should have the proper diagnostics +ok 365 - bag_eq(sql, array) incompatible types should fail +ok 366 - bag_eq(sql, array) incompatible types should have the proper description +ok 367 - bag_eq(sql, array) incompatible types should have the proper diagnostics +ok 368 - set_ne(sql, array, desc) should pass +ok 369 - set_ne(sql, array, desc) should have the proper description +ok 370 - set_ne(sql, array, desc) should have the proper diagnostics +ok 371 - set_ne(sql, array) should pass +ok 372 - set_ne(sql, array) should have the proper description +ok 373 - set_ne(sql, array) should have the proper diagnostics +ok 374 - set_ne(sql, array) fail should fail +ok 375 - set_ne(sql, array) fail should have the proper description +ok 376 - set_ne(sql, array) fail should have the proper diagnostics +ok 377 - set_ne(sql, dupes array) fail should fail +ok 378 - set_ne(sql, dupes array) fail should have the proper description +ok 379 - set_ne(sql, dupes array) fail should have the proper diagnostics ok 380 - set_ne(sql, array) incompatible types should fail ok 381 - set_ne(sql, array) incompatible types should have the proper description ok 382 - set_ne(sql, array) incompatible types should have the proper diagnostics -ok 383 - bag_ne(sql, array, desc) should pass -ok 384 - bag_ne(sql, array, desc) should have the proper description -ok 385 - bag_ne(sql, array, desc) should have the proper diagnostics -ok 386 - bag_ne(sql, array) should pass -ok 387 - bag_ne(sql, array) should have the proper description -ok 388 - bag_ne(sql, array) should have the proper diagnostics -ok 389 - bag_ne(sql, array) fail should fail -ok 390 - bag_ne(sql, array) fail should have the proper description -ok 391 - bag_ne(sql, array) fail should have the proper diagnostics -ok 392 - bag_ne(sql, dupes array) should pass -ok 393 - bag_ne(sql, dupes array) should have the proper description -ok 394 - bag_ne(sql, dupes array) should have the proper diagnostics -ok 395 - bag_ne(sql, array) incompatible types should fail -ok 396 - bag_ne(sql, array) incompatible types should have the proper description -ok 397 - bag_ne(sql, array) incompatible types should have the proper diagnostics +ok 383 - set_ne(sql, array) incompatible types should fail +ok 384 - set_ne(sql, array) incompatible types should have the proper description +ok 385 - set_ne(sql, array) incompatible types should have the proper diagnostics +ok 386 - bag_ne(sql, array, desc) should pass +ok 387 - bag_ne(sql, array, desc) should have the proper description +ok 388 - bag_ne(sql, array, desc) should have the proper diagnostics +ok 389 - bag_ne(sql, array) should pass +ok 390 - bag_ne(sql, array) should have the proper description +ok 391 - bag_ne(sql, array) should have the proper diagnostics +ok 392 - bag_ne(sql, array) fail should fail +ok 393 - bag_ne(sql, array) fail should have the proper description +ok 394 - bag_ne(sql, array) fail should have the proper diagnostics +ok 395 - bag_ne(sql, dupes array) should pass +ok 396 - bag_ne(sql, dupes array) should have the proper description +ok 397 - bag_ne(sql, dupes array) should have the proper diagnostics ok 398 - bag_ne(sql, array) incompatible types should fail ok 399 - bag_ne(sql, array) incompatible types should have the proper description ok 400 - bag_ne(sql, array) incompatible types should have the proper diagnostics -ok 401 - results_eq(prepared, array, desc) should pass -ok 402 - results_eq(prepared, array, desc) should have the proper description -ok 403 - results_eq(prepared, array, desc) should have the proper diagnostics -ok 404 - results_eq(prepared, array) should pass -ok 405 - results_eq(prepared, array) should have the proper description -ok 406 - results_eq(prepared, array) should have the proper diagnostics -ok 407 - results_eq(sql, array, desc) should pass -ok 408 - results_eq(sql, array, desc) should have the proper description -ok 409 - results_eq(sql, array, desc) should have the proper diagnostics +ok 401 - bag_ne(sql, array) incompatible types should fail +ok 402 - bag_ne(sql, array) incompatible types should have the proper description +ok 403 - bag_ne(sql, array) incompatible types should have the proper diagnostics +ok 404 - results_eq(prepared, array, desc) should pass +ok 405 - results_eq(prepared, array, desc) should have the proper description +ok 406 - results_eq(prepared, array, desc) should have the proper diagnostics +ok 407 - results_eq(prepared, array) should pass +ok 408 - results_eq(prepared, array) should have the proper description +ok 409 - results_eq(prepared, array) should have the proper diagnostics ok 410 - results_eq(sql, array, desc) should pass ok 411 - results_eq(sql, array, desc) should have the proper description ok 412 - results_eq(sql, array, desc) should have the proper diagnostics -ok 413 - results_eq(prepared, array) extra record should fail -ok 414 - results_eq(prepared, array) extra record should have the proper description -ok 415 - results_eq(prepared, array) extra record should have the proper diagnostics -ok 416 - results_eq(select, array) missing record should fail -ok 417 - results_eq(select, array) missing record should have the proper description -ok 418 - results_eq(select, array) missing record should have the proper diagnostics -ok 419 - results_ne(prepared, prepared, desc) should pass -ok 420 - results_ne(prepared, prepared, desc) should have the proper description -ok 421 - results_ne(prepared, prepared, desc) should have the proper diagnostics -ok 422 - results_ne(prepared, prepared) should pass -ok 423 - results_ne(prepared, prepared) should have the proper description -ok 424 - results_ne(prepared, prepared) should have the proper diagnostics -ok 425 - results_ne(execute, execute) should pass -ok 426 - results_ne(execute, execute) should have the proper description -ok 427 - results_ne(execute, execute) should have the proper diagnostics -ok 428 - results_ne(select, select) should pass -ok 429 - results_ne(select, select) should have the proper description -ok 430 - results_ne(select, select) should have the proper diagnostics -ok 431 - results_ne(dupe values, dupe values) should pass -ok 432 - results_ne(dupe values, dupe values) should have the proper description -ok 433 - results_ne(dupe values, dupe values) should have the proper diagnostics -ok 434 - results_ne(values with null, values with null) should pass -ok 435 - results_ne(values with null, values with null) should have the proper description -ok 436 - results_ne(values with null, values with null) should have the proper diagnostics -ok 437 - results_ne(nulls, nulls) should pass -ok 438 - results_ne(nulls, nulls) should have the proper description -ok 439 - results_ne(nulls, nulls) should have the proper diagnostics -ok 440 - results_ne(prepared, select) fail should fail -ok 441 - results_ne(prepared, select) fail should have the proper description -ok 442 - results_ne(prepared, select) fail should have the proper diagnostics -ok 443 - results_ne(select, prepared) missing last row should pass -ok 444 - results_ne(select, prepared) missing last row should have the proper description -ok 445 - results_ne(select, prepared) missing last row should have the proper diagnostics -ok 446 - results_ne(prepared, select) missing first row should pass -ok 447 - results_ne(prepared, select) missing first row should have the proper description -ok 448 - results_ne(prepared, select) missing first row should have the proper diagnostics -ok 449 - results_ne(values dupe, values) should pass -ok 450 - results_ne(values dupe, values) should have the proper description -ok 451 - results_ne(values dupe, values) should have the proper diagnostics -ok 452 - results_ne(values null, values) should pass -ok 453 - results_ne(values null, values) should have the proper description -ok 454 - results_ne(values null, values) should have the proper diagnostics -ok 455 - results_ne(values, values) mismatch should fail -ok 456 - results_ne(values, values) mismatch should have the proper description -ok 457 - results_ne(values, values) mismatch should have the proper diagnostics -ok 458 - results_ne(values, values) subtle mismatch should fail -ok 459 - results_ne(values, values) subtle mismatch should have the proper description -ok 460 - results_ne(values, values) subtle mismatch should have the proper diagnostics -ok 461 - results_ne(values, values) fail column count should fail -ok 462 - results_ne(values, values) fail column count should have the proper description -ok 463 - results_ne(values, values) fail column count should have the proper diagnostics -ok 464 - results_ne(cursor, cursor) should fail -ok 465 - results_ne(cursor, cursor) should have the proper description -ok 466 - results_ne(cursor, cursor) should have the proper diagnostics -ok 467 - results_ne(cursor, prepared) should fail -ok 468 - results_ne(cursor, prepared) should have the proper description -ok 469 - results_ne(cursor, prepared) should have the proper diagnostics -ok 470 - results_ne(prepared, cursor) should fail -ok 471 - results_ne(prepared, cursor) should have the proper description -ok 472 - results_ne(prepared, cursor) should have the proper diagnostics -ok 473 - results_ne(cursor, sql) should pass -ok 474 - results_ne(cursor, sql) should have the proper description -ok 475 - results_ne(cursor, sql) should have the proper diagnostics -ok 476 - results_ne(sql, cursor) should fail -ok 477 - results_ne(sql, cursor) should have the proper description -ok 478 - results_ne(sql, cursor) should have the proper diagnostics -ok 479 - is_empty(sql, desc) should pass -ok 480 - is_empty(sql, desc) should have the proper description -ok 481 - is_empty(sql, desc) should have the proper diagnostics -ok 482 - is_empty(sql) should pass -ok 483 - is_empty(sql) should have the proper description -ok 484 - is_empty(sql) should have the proper diagnostics -ok 485 - is_empty(prepared, desc) should pass -ok 486 - is_empty(prepared, desc) should have the proper description -ok 487 - is_empty(prepared, desc) should have the proper diagnostics -ok 488 - is_empty(prepared) should pass -ok 489 - is_empty(prepared) should have the proper description -ok 490 - is_empty(prepared) should have the proper diagnostics -ok 491 - is_empty(prepared, desc) fail should fail -ok 492 - is_empty(prepared, desc) fail should have the proper description -ok 493 - is_empty(prepared, desc) fail should have the proper diagnostics -ok 494 - is_empty(prepared) fail should fail -ok 495 - is_empty(prepared) fail should have the proper description -ok 496 - is_empty(prepared) fail should have the proper diagnostics -ok 497 - row_eq(prepared, record, desc) should pass -ok 498 - row_eq(prepared, record, desc) should have the proper description -ok 499 - row_eq(prepared, record, desc) should have the proper diagnostics -ok 500 - row_eq(sql, record, desc) should pass -ok 501 - row_eq(sql, record, desc) should have the proper description -ok 502 - row_eq(sql, record, desc) should have the proper diagnostics -ok 503 - row_eq(prepared, record, desc) should pass -ok 504 - row_eq(prepared, record, desc) should have the proper description -ok 505 - row_eq(prepared, record, desc) should have the proper diagnostics -ok 506 - row_eq(prepared, record, desc) should fail +ok 413 - results_eq(sql, array, desc) should pass +ok 414 - results_eq(sql, array, desc) should have the proper description +ok 415 - results_eq(sql, array, desc) should have the proper diagnostics +ok 416 - results_eq(prepared, array) extra record should fail +ok 417 - results_eq(prepared, array) extra record should have the proper description +ok 418 - results_eq(prepared, array) extra record should have the proper diagnostics +ok 419 - results_eq(select, array) missing record should fail +ok 420 - results_eq(select, array) missing record should have the proper description +ok 421 - results_eq(select, array) missing record should have the proper diagnostics +ok 422 - results_ne(prepared, prepared, desc) should pass +ok 423 - results_ne(prepared, prepared, desc) should have the proper description +ok 424 - results_ne(prepared, prepared, desc) should have the proper diagnostics +ok 425 - results_ne(prepared, prepared) should pass +ok 426 - results_ne(prepared, prepared) should have the proper description +ok 427 - results_ne(prepared, prepared) should have the proper diagnostics +ok 428 - results_ne(execute, execute) should pass +ok 429 - results_ne(execute, execute) should have the proper description +ok 430 - results_ne(execute, execute) should have the proper diagnostics +ok 431 - results_ne(select, select) should pass +ok 432 - results_ne(select, select) should have the proper description +ok 433 - results_ne(select, select) should have the proper diagnostics +ok 434 - results_ne(dupe values, dupe values) should pass +ok 435 - results_ne(dupe values, dupe values) should have the proper description +ok 436 - results_ne(dupe values, dupe values) should have the proper diagnostics +ok 437 - results_ne(values with null, values with null) should pass +ok 438 - results_ne(values with null, values with null) should have the proper description +ok 439 - results_ne(values with null, values with null) should have the proper diagnostics +ok 440 - results_ne(nulls, nulls) should pass +ok 441 - results_ne(nulls, nulls) should have the proper description +ok 442 - results_ne(nulls, nulls) should have the proper diagnostics +ok 443 - results_ne(prepared, select) fail should fail +ok 444 - results_ne(prepared, select) fail should have the proper description +ok 445 - results_ne(prepared, select) fail should have the proper diagnostics +ok 446 - results_ne(select, prepared) missing last row should pass +ok 447 - results_ne(select, prepared) missing last row should have the proper description +ok 448 - results_ne(select, prepared) missing last row should have the proper diagnostics +ok 449 - results_ne(prepared, select) missing first row should pass +ok 450 - results_ne(prepared, select) missing first row should have the proper description +ok 451 - results_ne(prepared, select) missing first row should have the proper diagnostics +ok 452 - results_ne(values dupe, values) should pass +ok 453 - results_ne(values dupe, values) should have the proper description +ok 454 - results_ne(values dupe, values) should have the proper diagnostics +ok 455 - results_ne(values null, values) should pass +ok 456 - results_ne(values null, values) should have the proper description +ok 457 - results_ne(values null, values) should have the proper diagnostics +ok 458 - results_ne(values, values) mismatch should fail +ok 459 - results_ne(values, values) mismatch should have the proper description +ok 460 - results_ne(values, values) mismatch should have the proper diagnostics +ok 461 - results_ne(values, values) subtle mismatch should fail +ok 462 - results_ne(values, values) subtle mismatch should have the proper description +ok 463 - results_ne(values, values) subtle mismatch should have the proper diagnostics +ok 464 - results_ne(values, values) fail column count should fail +ok 465 - results_ne(values, values) fail column count should have the proper description +ok 466 - results_ne(values, values) fail column count should have the proper diagnostics +ok 467 - results_ne(cursor, cursor) should fail +ok 468 - results_ne(cursor, cursor) should have the proper description +ok 469 - results_ne(cursor, cursor) should have the proper diagnostics +ok 470 - results_ne(cursor, prepared) should fail +ok 471 - results_ne(cursor, prepared) should have the proper description +ok 472 - results_ne(cursor, prepared) should have the proper diagnostics +ok 473 - results_ne(prepared, cursor) should fail +ok 474 - results_ne(prepared, cursor) should have the proper description +ok 475 - results_ne(prepared, cursor) should have the proper diagnostics +ok 476 - results_ne(cursor, sql) should pass +ok 477 - results_ne(cursor, sql) should have the proper description +ok 478 - results_ne(cursor, sql) should have the proper diagnostics +ok 479 - results_ne(sql, cursor) should fail +ok 480 - results_ne(sql, cursor) should have the proper description +ok 481 - results_ne(sql, cursor) should have the proper diagnostics +ok 482 - is_empty(sql, desc) should pass +ok 483 - is_empty(sql, desc) should have the proper description +ok 484 - is_empty(sql, desc) should have the proper diagnostics +ok 485 - is_empty(sql) should pass +ok 486 - is_empty(sql) should have the proper description +ok 487 - is_empty(sql) should have the proper diagnostics +ok 488 - is_empty(prepared, desc) should pass +ok 489 - is_empty(prepared, desc) should have the proper description +ok 490 - is_empty(prepared, desc) should have the proper diagnostics +ok 491 - is_empty(prepared) should pass +ok 492 - is_empty(prepared) should have the proper description +ok 493 - is_empty(prepared) should have the proper diagnostics +ok 494 - is_empty(prepared, desc) fail should fail +ok 495 - is_empty(prepared, desc) fail should have the proper description +ok 496 - is_empty(prepared, desc) fail should have the proper diagnostics +ok 497 - is_empty(prepared) fail should fail +ok 498 - is_empty(prepared) fail should have the proper description +ok 499 - is_empty(prepared) fail should have the proper diagnostics +ok 500 - row_eq(prepared, record, desc) should pass +ok 501 - row_eq(prepared, record, desc) should have the proper description +ok 502 - row_eq(prepared, record, desc) should have the proper diagnostics +ok 503 - row_eq(sql, record, desc) should pass +ok 504 - row_eq(sql, record, desc) should have the proper description +ok 505 - row_eq(sql, record, desc) should have the proper diagnostics +ok 506 - row_eq(prepared, record, desc) should pass ok 507 - row_eq(prepared, record, desc) should have the proper description ok 508 - row_eq(prepared, record, desc) should have the proper diagnostics -ok 509 - row_eq(prepared, sometype, desc) should pass -ok 510 - row_eq(prepared, sometype, desc) should have the proper description -ok 511 - row_eq(prepared, sometype, desc) should have the proper diagnostics -ok 512 - row_eq(sqlrow, sometype, desc) should pass -ok 513 - row_eq(sqlrow, sometype, desc) should have the proper description -ok 514 - row_eq(sqlrow, sometype, desc) should have the proper diagnostics -ok 515 - threw 0A000 +ok 509 - row_eq(prepared, record, desc) should fail +ok 510 - row_eq(prepared, record, desc) should have the proper description +ok 511 - row_eq(prepared, record, desc) should have the proper diagnostics +ok 512 - row_eq(prepared, sometype, desc) should pass +ok 513 - row_eq(prepared, sometype, desc) should have the proper description +ok 514 - row_eq(prepared, sometype, desc) should have the proper diagnostics +ok 515 - row_eq(sqlrow, sometype, desc) should pass +ok 516 - row_eq(sqlrow, sometype, desc) should have the proper description +ok 517 - row_eq(sqlrow, sometype, desc) should have the proper diagnostics +ok 518 - threw 0A000 diff --git a/test/sql/resultset.sql b/test/sql/resultset.sql index 2e30e4d8711c..37ea7c668898 100644 --- a/test/sql/resultset.sql +++ b/test/sql/resultset.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(515); +SELECT plan(518); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -981,7 +981,7 @@ SELECT * FROM check_test( false, 'results_eq(values, values) mismatch', '', - CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Columns differ between queries:' END || ' + CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Number of columns or their types differ between the queries:' END || ' have: (1,foo) want: (foo,1)' ); @@ -1006,9 +1006,19 @@ BEGIN false, 'results_eq(values, values) subtle mismatch', '', - ' Columns differ between queries: - have: (1,foo) - want: (1,foo)' ) AS a(b) LOOP + ' Number of columns or their types differ between the queries' ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + results_eq( + 'VALUES (1::int), (2::int)', + 'VALUES (1::bigint), (2::bigint)' + ), + false, + 'results_eq(values, values) integer type mismatch', + '', + ' Number of columns or their types differ between the queries' ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; END IF; @@ -1026,7 +1036,7 @@ SELECT * FROM check_test( false, 'results_eq(values, values) fail column count', '', - CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Columns differ between queries:' END || ' + CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Number of columns or their types differ between the queries:' END || ' have: (1) want: (foo,1)' ); diff --git a/test/sql/valueset.sql b/test/sql/valueset.sql index 7eb0aaab2f01..75c4b398807a 100644 --- a/test/sql/valueset.sql +++ b/test/sql/valueset.sql @@ -679,7 +679,7 @@ SELECT * FROM check_test( false, 'results_eq(values, values) mismatch', '', - CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Columns differ between queries:' END || ' + CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Number of columns or their types differ between the queries:' END || ' have: (1,foo) want: (foo,1)' ); @@ -704,9 +704,7 @@ BEGIN false, 'results_eq(values, values) subtle mismatch', '', - ' Columns differ between queries: - have: (1,foo) - want: (1,foo)' ) AS a(b) LOOP + ' Number of columns or their types differ between the queries' ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; END IF; @@ -721,7 +719,7 @@ SELECT * FROM check_test( false, 'results_eq(values, values) fail column count', '', - CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Columns differ between queries:' END || ' + CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Number of columns or their types differ between the queries:' END || ' have: (1) want: (foo,1)' ); From 69566d82dc29a34c187c3465878b8bb49f779979 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 17:12:15 -0800 Subject: [PATCH 0696/1195] Moved To-Dos to Github. See https://github.com/theory/pgtap/issues. --- doc/pgtap.mmd | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 1ae7effbeba4..ea5ef174594d 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -6271,39 +6271,6 @@ No changes. Everything should just work. + `regtype` to `text` * Two operators, `=` and `<>`, are added to compare `name[]` values. -To Do -===== -* Add parameter names matching the docs so parameters can be passed by name on - 9.0 and higher. -* Add `isnt_empty()` to complement `is_empty()`. -* Add variants of `set_eq()`, `bag_eq()`, and `results_eq()` that take an - array of records as the second argument. -* Add `schema, table, colname` variations of the table-checking functions - (`table_has_column()`, `col_type_is()`, etc.). That is, allow the - description to be optional if the schema is included in the function call. -* Add some sort of tests for permisions. Something like: - `table_privs_are(:table, :user, :privs[])`, and would have variations for - database, sequence, function, language, schema, and tablespace. -* Have `has_function()` manage OUT, INOUT, and VARIADIC arguments. -* Useful schema testing functions to consider adding: - + `sequence_has_range()` - + `sequence_increments_by()` - + `sequence_starts_at()` - + `sequence_cycles()` -* Useful result testing function to consider adding (but might require C - code): `rowtype_is()` -* Split test functions into separate files (and maybe distributions?): - + One with scalar comparison functions: `ok()`, `is()`, `like()`, etc. - + One with relation comparison functions: `set_eq()`, `results_eq()`, etc. - + One with schema testing functions: `has_table()`, `tables_are()`, etc. -* Add useful negation function tests: - + `isnt_definer()` - + `isnt_strict()` - + `isnt_aggregate()` -* Modify function testing assertions so that functions can be specified with - full signatures, so that a polymorphic functions can be independently tested - for language, volatility, etc. - Metadata ======== From 054c119d26764c883fd8efc655e52e8f81a38968 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 9 Jan 2013 17:26:03 -0800 Subject: [PATCH 0697/1195] Add `isnt_empty()`. Closes #31. --- Changes | 1 + doc/pgtap.mmd | 17 +++++++++ sql/pgtap--0.91.0--0.92.0.sql | 23 ++++++++++++ sql/pgtap.sql.in | 23 ++++++++++++ test/expected/resultset.out | 64 ++++++++++++++++++++++---------- test/sql/resultset.sql | 69 ++++++++++++++++++++++++++++++++++- 6 files changed, 176 insertions(+), 21 deletions(-) diff --git a/Changes b/Changes index 819980d84313..d41eb6cabdad 100644 --- a/Changes +++ b/Changes @@ -28,6 +28,7 @@ Revision history for pgTAP reduce confusion when the rows have the same values of different types. Thanks to Peter Eisentraut and Taylor Ralston for [the nudging](https://github.com/theory/pgtap/issues/15). +* Added `isnt_empty()` to ensure that a query does not return the empty set. 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index ea5ef174594d..0108f88ae550 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1535,6 +1535,23 @@ fails and the results are displayed in the failure diagnostics, like so: # (1,Jacob,false) # (2,Emily,false) +### `isnt_empty()` ### + + SELECT isnt_empty( :sql, :description ); + SELECT isnt_empty( :sql ); + +**Parameters** + +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. + +`:description` +: A short description of the test. + +This function is the inverse of `is_empty()`. The test passes if the specified +query, when executed, returns at least one row. If it returns no rows, the +test fails. + ### `row_eq()` ### SELECT row_eq( :sql, :record, :description ); diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index bb83c5649657..96dc2a5d583b 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -732,3 +732,26 @@ EXCEPTION ); END; $$ LANGUAGE plpgsql; + +-- 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 6217b462b2b1..19ab5248ea27 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -6728,6 +6728,29 @@ 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 $$ diff --git a/test/expected/resultset.out b/test/expected/resultset.out index 59b7d3f41259..5b994441d917 100644 --- a/test/expected/resultset.out +++ b/test/expected/resultset.out @@ -1,5 +1,5 @@ \unset ECHO -1..518 +1..542 ok 1 - Should create temp table with simple query ok 2 - Table __foonames__ should exist ok 3 - Should create a temp table for a prepared statement @@ -499,22 +499,46 @@ ok 496 - is_empty(prepared, desc) fail should have the proper diagnostics ok 497 - is_empty(prepared) fail should fail ok 498 - is_empty(prepared) fail should have the proper description ok 499 - is_empty(prepared) fail should have the proper diagnostics -ok 500 - row_eq(prepared, record, desc) should pass -ok 501 - row_eq(prepared, record, desc) should have the proper description -ok 502 - row_eq(prepared, record, desc) should have the proper diagnostics -ok 503 - row_eq(sql, record, desc) should pass -ok 504 - row_eq(sql, record, desc) should have the proper description -ok 505 - row_eq(sql, record, desc) should have the proper diagnostics -ok 506 - row_eq(prepared, record, desc) should pass -ok 507 - row_eq(prepared, record, desc) should have the proper description -ok 508 - row_eq(prepared, record, desc) should have the proper diagnostics -ok 509 - row_eq(prepared, record, desc) should fail -ok 510 - row_eq(prepared, record, desc) should have the proper description -ok 511 - row_eq(prepared, record, desc) should have the proper diagnostics -ok 512 - row_eq(prepared, sometype, desc) should pass -ok 513 - row_eq(prepared, sometype, desc) should have the proper description -ok 514 - row_eq(prepared, sometype, desc) should have the proper diagnostics -ok 515 - row_eq(sqlrow, sometype, desc) should pass -ok 516 - row_eq(sqlrow, sometype, desc) should have the proper description -ok 517 - row_eq(sqlrow, sometype, desc) should have the proper diagnostics -ok 518 - threw 0A000 +ok 500 - isnt_empty(sql, desc) should pass +ok 501 - isnt_empty(sql, desc) should have the proper description +ok 502 - isnt_empty(sql, desc) should have the proper diagnostics +ok 503 - isnt_empty(sql, desc) should fail +ok 504 - isnt_empty(sql, desc) should have the proper description +ok 505 - isnt_empty(sql, desc) should have the proper diagnostics +ok 506 - isnt_empty(sql) should fail +ok 507 - isnt_empty(sql) should have the proper description +ok 508 - isnt_empty(sql) should have the proper diagnostics +ok 509 - isnt_empty(sql) should pass +ok 510 - isnt_empty(sql) should have the proper description +ok 511 - isnt_empty(sql) should have the proper diagnostics +ok 512 - isnt_empty(prepared, desc) should pass +ok 513 - isnt_empty(prepared, desc) should have the proper description +ok 514 - isnt_empty(prepared, desc) should have the proper diagnostics +ok 515 - isnt_empty(prepared, desc) should fail +ok 516 - isnt_empty(prepared, desc) should have the proper description +ok 517 - isnt_empty(prepared, desc) should have the proper diagnostics +ok 518 - isnt_empty(prepared) should pass +ok 519 - isnt_empty(prepared) should have the proper description +ok 520 - isnt_empty(prepared) should have the proper diagnostics +ok 521 - isnt_empty(prepared) should fail +ok 522 - isnt_empty(prepared) should have the proper description +ok 523 - isnt_empty(prepared) should have the proper diagnostics +ok 524 - row_eq(prepared, record, desc) should pass +ok 525 - row_eq(prepared, record, desc) should have the proper description +ok 526 - row_eq(prepared, record, desc) should have the proper diagnostics +ok 527 - row_eq(sql, record, desc) should pass +ok 528 - row_eq(sql, record, desc) should have the proper description +ok 529 - row_eq(sql, record, desc) should have the proper diagnostics +ok 530 - row_eq(prepared, record, desc) should pass +ok 531 - row_eq(prepared, record, desc) should have the proper description +ok 532 - row_eq(prepared, record, desc) should have the proper diagnostics +ok 533 - row_eq(prepared, record, desc) should fail +ok 534 - row_eq(prepared, record, desc) should have the proper description +ok 535 - row_eq(prepared, record, desc) should have the proper diagnostics +ok 536 - row_eq(prepared, sometype, desc) should pass +ok 537 - row_eq(prepared, sometype, desc) should have the proper description +ok 538 - row_eq(prepared, sometype, desc) should have the proper diagnostics +ok 539 - row_eq(sqlrow, sometype, desc) should pass +ok 540 - row_eq(sqlrow, sometype, desc) should have the proper description +ok 541 - row_eq(sqlrow, sometype, desc) should have the proper diagnostics +ok 542 - threw 0A000 diff --git a/test/sql/resultset.sql b/test/sql/resultset.sql index 37ea7c668898..4f7afcac54c7 100644 --- a/test/sql/resultset.sql +++ b/test/sql/resultset.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(518); +SELECT plan(542); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -2347,6 +2347,73 @@ END; $$ LANGUAGE PLPGSQL; SELECT * FROM test_empty_fail(); +/****************************************************************************/ +-- Now test isnt_empty(). +SELECT * FROM check_test( + isnt_empty( 'SELECT 1', 'whatever' ), + true, + 'isnt_empty(sql, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_empty( 'SELECT 1 WHERE FALSE', 'whatever' ), + false, + 'isnt_empty(sql, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_empty( 'SELECT 1 WHERE FALSE' ), + false, + 'isnt_empty(sql)', + '', + '' +); + +SELECT * FROM check_test( + isnt_empty( 'SELECT 1' ), + true, + 'isnt_empty(sql)', + '', + '' +); + +PREPARE someset(boolean) AS SELECT * FROM names WHERE $1; +SELECT * FROM check_test( + isnt_empty( 'EXECUTE someset(true)', 'whatever' ), + true, + 'isnt_empty(prepared, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_empty( 'EXECUTE someset(false)', 'whatever' ), + false, + 'isnt_empty(prepared, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_empty( 'EXECUTE someset(true)' ), + true, + 'isnt_empty(prepared)', + '', + '' +); + +SELECT * FROM check_test( + isnt_empty( 'EXECUTE someset(false)' ), + false, + 'isnt_empty(prepared)', + '', + '' +); + /****************************************************************************/ -- Test row_eq(). PREPARE arow AS SELECT id, name FROM names WHERE name = 'Jacob'; From d8ff5c05906872a076e6a96ff3967d682e5e1c86 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Jan 2013 14:13:31 -0800 Subject: [PATCH 0698/1195] Improve handling of expressions in `has_index()`. Rather than allowing only one expression, allow columns and one expression to be mixed in a single query. This is much closer to what one should expect. The downside is that, if an index has multiple expressions, there is no way to separate them, yet. Will fix if I get an answer to post to -hackers about it. Meanwhile, this should get us further along than we were before, and requires less code, to boot! (Ref #12.) --- Changes | 1 + doc/pgtap.mmd | 13 +- sql/pgtap--0.91.0--0.92.0.sql | 102 +++++++++ sql/pgtap.sql.in | 124 ++-------- test/expected/index.out | 416 +++++++++++++++++----------------- test/sql/index.sql | 19 +- 6 files changed, 361 insertions(+), 314 deletions(-) diff --git a/Changes b/Changes index d41eb6cabdad..e00d4f9005c9 100644 --- a/Changes +++ b/Changes @@ -29,6 +29,7 @@ Revision history for pgTAP Thanks to Peter Eisentraut and Taylor Ralston for [the nudging](https://github.com/theory/pgtap/issues/15). * Added `isnt_empty()` to ensure that a query does not return the empty set. +* Fixed `has_index()` to better handle indexes with expressions. 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 0108f88ae550..8892fb72200e 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2958,7 +2958,7 @@ enum does *not* exist. : Name of an index. `:columns` -: Array of the columns in the index. +: Array of the columns and/or expressions in the index. `:column` : Idexed column name or expression. @@ -2969,21 +2969,20 @@ enum does *not* exist. Checks for the existence of an index associated with the named table. The `:schema` argument is optional, as is the column name or names or expression, and the description. The columns argument may be a string naming one column or -an array of column names. It may also be a string representing an expression, -such as `lower(foo)`. For expressions, you must use lowercase for all SQL -keywords and functions to properly compare to PostgreSQL's internal form of -the expression. A few examples: +expression, or an array of column names and/or expressions. For expressions, +you must use lowercase for all SQL keywords and functions to properly compare +to PostgreSQL's internal form of the expression. A few examples: SELECT has_index( 'myschema', 'sometable', 'myindex', - ARRAY[ 'somecolumn', 'anothercolumn' ], + ARRAY[ 'somecolumn', 'anothercolumn', 'lower(txtcolumn)' ], 'Index "myindex" should exist' ); SELECT has_index('myschema', 'sometable', 'anidx', 'somecolumn'); - SELECT has_index('myschema', 'sometable', 'loweridx', 'LOWER(somecolumn)'); + SELECT has_index('myschema', 'sometable', 'loweridx', 'lower(somecolumn)'); SELECT has_index('sometable', 'someindex'); If you find that the function call seems to be getting confused, cast the diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index 96dc2a5d583b..1bb99535f240 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -755,3 +755,105 @@ CREATE OR REPLACE FUNCTION isnt_empty( TEXT ) RETURNS TEXT AS $$ SELECT isnt_empty( $1, NULL ); $$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _ikeys( NAME, NAME, NAME) +RETURNS NAME[] AS $$ + SELECT ARRAY( + SELECT COALESCE(a.attname, pg_catalog.pg_get_expr( x.indexprs, ct.oid )) + 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 + LEFT JOIN pg_catalog.pg_attribute a + ON ct.oid = a.attrelid + AND a.attnum = x.indkey[s.i] + 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 NAME[] AS $$ + SELECT ARRAY( + SELECT COALESCE(a.attname, pg_catalog.pg_get_expr( x.indexprs, ct.oid )) + 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 + LEFT JOIN pg_catalog.pg_attribute a + ON ct.oid = a.attrelid + AND a.attnum = x.indkey[s.i] + WHERE ct.relname = $1 + AND ci.relname = $2 + AND pg_catalog.pg_table_is_visible(ct.oid) + ORDER BY s.i + ); +$$ LANGUAGE sql; + +DROP FUNCTION _iexpr( NAME, NAME, NAME ); +DROP FUNCTION _iexpr( NAME, NAME ); + +-- 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; + +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( 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; + +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; + + diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 19ab5248ea27..d8e18e9ac7f7 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2557,14 +2557,16 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _ikeys( NAME, NAME, NAME) RETURNS NAME[] AS $$ SELECT ARRAY( - SELECT a.attname + SELECT COALESCE(a.attname, pg_catalog.pg_get_expr( x.indexprs, ct.oid )) 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_attribute a ON ct.oid = a.attrelid JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) - ON a.attnum = x.indkey[s.i] + ON x.indkey[s.i] IS NOT NULL + LEFT JOIN pg_catalog.pg_attribute a + ON ct.oid = a.attrelid + AND a.attnum = x.indkey[s.i] WHERE ct.relname = $2 AND ci.relname = $3 AND n.nspname = $1 @@ -2575,13 +2577,15 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _ikeys( NAME, NAME) RETURNS NAME[] AS $$ SELECT ARRAY( - SELECT a.attname + SELECT COALESCE(a.attname, pg_catalog.pg_get_expr( x.indexprs, ct.oid )) 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_attribute a ON ct.oid = a.attrelid JOIN generate_series(0, current_setting('max_index_keys')::int - 1) s(i) - ON a.attnum = x.indkey[s.i] + ON x.indkey[s.i] IS NOT NULL + LEFT JOIN pg_catalog.pg_attribute a + ON ct.oid = a.attrelid + AND a.attnum = x.indkey[s.i] WHERE ct.relname = $1 AND ci.relname = $2 AND pg_catalog.pg_table_is_visible(ct.oid) @@ -2615,29 +2619,6 @@ RETURNS BOOLEAN AS $$ ); $$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME, NAME) -RETURNS TEXT AS $$ - SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) - 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 -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _iexpr( NAME, NAME) -RETURNS TEXT AS $$ - SELECT pg_catalog.pg_get_expr( x.indexprs, ct.oid ) - 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 $$ @@ -2652,8 +2633,8 @@ BEGIN END IF; RETURN is( - quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')', - quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || _ident_array_to_string( $4, ', ' ) || ')', + 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; @@ -2668,29 +2649,8 @@ $$ LANGUAGE sql; -- has_index( schema, table, index, column/expression, description ) CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME, text ) RETURNS TEXT AS $$ -DECLARE - expr text; -BEGIN - IF $4 NOT LIKE '%(%' THEN - -- Not a functional index. - RETURN has_index( $1, $2, $3, ARRAY[$4], $5 ); - END IF; - - -- Get the functional expression. - expr := _iexpr($1, $2, $3); - - IF expr IS NULL 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) || '(' || expr || ')', - quote_ident($3) || ' ON ' || quote_ident($1) || '.' || quote_ident($2) || '(' || $4 || ')', - $5 - ); -END; -$$ LANGUAGE plpgsql; + SELECT has_index( $1, $2, $3, ARRAY[$4], $5 ); +$$ LANGUAGE sql; -- has_index( schema, table, index, columns/expression ) CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME ) @@ -2712,8 +2672,8 @@ BEGIN END IF; RETURN is( - quote_ident($2) || ' ON ' || quote_ident($1) || '(' || _ident_array_to_string( index_cols, ', ' ) || ')', - quote_ident($2) || ' ON ' || quote_ident($1) || '(' || _ident_array_to_string( $3, ', ' ) || ')', + 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; @@ -2739,52 +2699,14 @@ $$ LANGUAGE sql; -- has_index( schema, table, index, column/expression ) CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, text ) RETURNS TEXT AS $$ -DECLARE - want_expr text; - descr text; - have_expr text; - idx name; - tab text; -BEGIN - IF $3 NOT LIKE '%(%' THEN - -- Not a functional index. - IF _is_schema( $1 ) THEN - -- Looking for schema.table index. - RETURN ok ( _have_index( $1, $2, $3 ), $4); - END IF; + SELECT CASE WHEN _is_schema( $1 ) THEN + -- Looking for schema.table index. + ok ( _have_index( $1, $2, $3 ), $4) + ELSE -- Looking for particular columns. - RETURN has_index( $1, $2, ARRAY[$3], $4 ); - END IF; - - -- Get the functional expression. - IF _is_schema( $1 ) THEN - -- Looking for an index within a schema. - have_expr := _iexpr($1, $2, $3); - want_expr := $4; - descr := 'Index ' || quote_ident($3) || ' should exist'; - idx := $3; - tab := quote_ident($1) || '.' || quote_ident($2); - ELSE - -- Looking for an index without a schema spec. - have_expr := _iexpr($1, $2); - want_expr := $3; - descr := $4; - idx := $2; - tab := quote_ident($1); - END IF; - - IF have_expr IS NULL THEN - RETURN ok( false, descr ) || E'\n' - || diag( 'Index ' || idx || ' ON ' || tab || ' not found'); - END IF; - - RETURN is( - quote_ident(idx) || ' ON ' || tab || '(' || have_expr || ')', - quote_ident(idx) || ' ON ' || tab || '(' || want_expr || ')', - descr - ); -END; -$$ LANGUAGE plpgsql; + has_index( $1, $2, ARRAY[$3], $4 ) + END; +$$ LANGUAGE sql; -- has_index( table, index, column/expression ) -- has_index( schema, table, index ) diff --git a/test/expected/index.out b/test/expected/index.out index 6a0086df0662..f9b8c8e03264 100644 --- a/test/expected/index.out +++ b/test/expected/index.out @@ -1,5 +1,5 @@ \unset ECHO -1..225 +1..231 ok 1 - has_index() single column should pass ok 2 - has_index() single column should have the proper description ok 3 - has_index() single column should have the proper diagnostics @@ -21,207 +21,213 @@ ok 18 - has_index() multi-column no desc should have the proper diagnostics ok 19 - has_index() functional should pass ok 20 - has_index() functional should have the proper description ok 21 - has_index() functional should have the proper diagnostics -ok 22 - has_index() no cols should pass -ok 23 - has_index() no cols should have the proper description -ok 24 - has_index() no cols should have the proper diagnostics -ok 25 - has_index() hash index should pass -ok 26 - has_index() hash index should have the proper description -ok 27 - has_index() hash index should have the proper diagnostics -ok 28 - has_index() no cols no desc should pass -ok 29 - has_index() no cols no desc should have the proper description -ok 30 - has_index() no cols no desc should have the proper diagnostics -ok 31 - has_index() no cols hash index no desc should pass -ok 32 - has_index() no cols hash index no desc should have the proper description -ok 33 - has_index() no cols hash index no desc should have the proper diagnostics -ok 34 - has_index() no schema single column should pass -ok 35 - has_index() no schema single column should have the proper description -ok 36 - has_index() no schema single column should have the proper diagnostics -ok 37 - has_index() no schema single column no desc should pass -ok 38 - has_index() no schema single column no desc should have the proper description -ok 39 - has_index() no schema single column no desc should have the proper diagnostics -ok 40 - has_index() no schema multi-column should pass -ok 41 - has_index() no schema multi-column should have the proper description -ok 42 - has_index() no schema multi-column should have the proper diagnostics -ok 43 - has_index() no schema multi-column no desc should pass -ok 44 - has_index() no schema multi-column no desc should have the proper description -ok 45 - has_index() no schema multi-column no desc should have the proper diagnostics -ok 46 - has_index() no schema functional should pass -ok 47 - has_index() no schema functional should have the proper description -ok 48 - has_index() no schema functional should have the proper diagnostics -ok 49 - has_index() no schema functional no desc should pass -ok 50 - has_index() no schema functional no desc should have the proper description -ok 51 - has_index() no schema functional no desc should have the proper diagnostics -ok 52 - has_index() no schema or cols should pass -ok 53 - has_index() no schema or cols should have the proper description -ok 54 - has_index() no schema or cols should have the proper diagnostics -ok 55 - has_index() hash index no schema or cols should pass -ok 56 - has_index() hash index no schema or cols should have the proper description -ok 57 - has_index() hash index no schema or cols should have the proper diagnostics -ok 58 - has_index() no schema or cols or desc should pass -ok 59 - has_index() no schema or cols or desc should have the proper description -ok 60 - has_index() no schema or cols or desc should have the proper diagnostics -ok 61 - has_index() hash index no schema or cols or desc should pass -ok 62 - has_index() hash index no schema or cols or desc should have the proper description -ok 63 - has_index() hash index no schema or cols or desc should have the proper diagnostics -ok 64 - has_index() non-existent should fail -ok 65 - has_index() non-existent should have the proper description -ok 66 - has_index() non-existent should have the proper diagnostics -ok 67 - has_index() missing should fail -ok 68 - has_index() missing should have the proper description -ok 69 - has_index() missing should have the proper diagnostics -ok 70 - has_index() invalid should fail -ok 71 - has_index() invalid should have the proper description -ok 72 - has_index() invalid should have the proper diagnostics -ok 73 - has_index() missing column should fail -ok 74 - has_index() missing column should have the proper description -ok 75 - has_index() missing column should have the proper diagnostics -ok 76 - has_index() missing no schema should fail -ok 77 - has_index() missing no schema should have the proper description -ok 78 - has_index() missing no schema should have the proper diagnostics -ok 79 - has_index() invalid no schema should fail -ok 80 - has_index() invalid no schema should have the proper description -ok 81 - has_index() invalid no schema should have the proper diagnostics -ok 82 - has_index() functional fail should fail -ok 83 - has_index() functional fail should have the proper description -ok 84 - has_index() functional fail should have the proper diagnostics -ok 85 - has_index() functional fail no schema should fail -ok 86 - has_index() functional fail no schema should have the proper description -ok 87 - has_index() functional fail no schema should have the proper diagnostics -ok 88 - hasnt_index(schema, table, index, desc) should fail -ok 89 - hasnt_index(schema, table, index, desc) should have the proper description -ok 90 - hasnt_index(schema, table, index, desc) should have the proper diagnostics -ok 91 - hasnt_index(schema, table, index) should fail -ok 92 - hasnt_index(schema, table, index) should have the proper description -ok 93 - hasnt_index(schema, table, index) should have the proper diagnostics -ok 94 - hasnt_index(schema, table, non-index, desc) should pass -ok 95 - hasnt_index(schema, table, non-index, desc) should have the proper description -ok 96 - hasnt_index(schema, table, non-index, desc) should have the proper diagnostics -ok 97 - hasnt_index(schema, table, non-index) should pass -ok 98 - hasnt_index(schema, table, non-index) should have the proper description -ok 99 - hasnt_index(schema, table, non-index) should have the proper diagnostics -ok 100 - hasnt_index(table, index, desc) should fail -ok 101 - hasnt_index(table, index, desc) should have the proper description -ok 102 - hasnt_index(table, index, desc) should have the proper diagnostics -ok 103 - hasnt_index(table, index) should fail -ok 104 - hasnt_index(table, index) should have the proper description -ok 105 - hasnt_index(table, index) should have the proper diagnostics -ok 106 - hasnt_index(table, non-index, desc) should pass -ok 107 - hasnt_index(table, non-index, desc) should have the proper description -ok 108 - hasnt_index(table, non-index, desc) should have the proper diagnostics -ok 109 - hasnt_index(table, non-index) should pass -ok 110 - hasnt_index(table, non-index) should have the proper description -ok 111 - hasnt_index(table, non-index) should have the proper diagnostics -ok 112 - index_is_unique() should pass -ok 113 - index_is_unique() should have the proper description -ok 114 - index_is_unique() should have the proper diagnostics -ok 115 - index_is_unique() no desc should pass -ok 116 - index_is_unique() no desc should have the proper description -ok 117 - index_is_unique() no desc should have the proper diagnostics -ok 118 - index_is_unique() no schema should pass -ok 119 - index_is_unique() no schema should have the proper description -ok 120 - index_is_unique() no schema should have the proper diagnostics -ok 121 - index_is_unique() index only should pass -ok 122 - index_is_unique() index only should have the proper description -ok 123 - index_is_unique() index only should have the proper diagnostics -ok 124 - index_is_unique() on pk should pass -ok 125 - index_is_unique() on pk should have the proper description -ok 126 - index_is_unique() on pk should have the proper diagnostics -ok 127 - index_is_unique() on pk no desc should pass -ok 128 - index_is_unique() on pk no desc should have the proper description -ok 129 - index_is_unique() on pk no desc should have the proper diagnostics -ok 130 - index_is_unique() on pk no schema should pass -ok 131 - index_is_unique() on pk no schema should have the proper description -ok 132 - index_is_unique() on pk no schema should have the proper diagnostics -ok 133 - index_is_unique() on pk index only should pass -ok 134 - index_is_unique() on pk index only should have the proper description -ok 135 - index_is_unique() on pk index only should have the proper diagnostics -ok 136 - index_is_unique() fail should fail -ok 137 - index_is_unique() fail should have the proper description -ok 138 - index_is_unique() fail should have the proper diagnostics -ok 139 - index_is_unique() fail no desc should fail -ok 140 - index_is_unique() fail no desc should have the proper description -ok 141 - index_is_unique() fail no desc should have the proper diagnostics -ok 142 - index_is_unique() fail no schema should fail -ok 143 - index_is_unique() fail no schema should have the proper description -ok 144 - index_is_unique() fail no schema should have the proper diagnostics -ok 145 - index_is_unique() fail index only should fail -ok 146 - index_is_unique() fail index only should have the proper description -ok 147 - index_is_unique() fail index only should have the proper diagnostics -ok 148 - index_is_unique() no such index should fail -ok 149 - index_is_unique() no such index should have the proper description -ok 150 - index_is_unique() no such index should have the proper diagnostics -ok 151 - index_is_primary() should pass -ok 152 - index_is_primary() should have the proper description -ok 153 - index_is_primary() should have the proper diagnostics -ok 154 - index_is_primary() no desc should pass -ok 155 - index_is_primary() no desc should have the proper description -ok 156 - index_is_primary() no desc should have the proper diagnostics -ok 157 - index_is_primary() no schema should pass -ok 158 - index_is_primary() no schema should have the proper description -ok 159 - index_is_primary() no schema should have the proper diagnostics -ok 160 - index_is_primary() index only should pass -ok 161 - index_is_primary() index only should have the proper description -ok 162 - index_is_primary() index only should have the proper diagnostics -ok 163 - index_is_primary() fail should fail -ok 164 - index_is_primary() fail should have the proper description -ok 165 - index_is_primary() fail should have the proper diagnostics -ok 166 - index_is_primary() fail no desc should fail -ok 167 - index_is_primary() fail no desc should have the proper description -ok 168 - index_is_primary() fail no desc should have the proper diagnostics -ok 169 - index_is_primary() fail no schema should fail -ok 170 - index_is_primary() fail no schema should have the proper description -ok 171 - index_is_primary() fail no schema should have the proper diagnostics -ok 172 - index_is_primary() fail index only should fail -ok 173 - index_is_primary() fail index only should have the proper description -ok 174 - index_is_primary() fail index only should have the proper diagnostics -ok 175 - index_is_primary() no such index should fail -ok 176 - index_is_primary() no such index should have the proper description -ok 177 - index_is_primary() no such index should have the proper diagnostics -ok 178 - is_clustered() fail should fail -ok 179 - is_clustered() fail should have the proper description -ok 180 - is_clustered() fail should have the proper diagnostics -ok 181 - is_clustered() fail no desc should fail -ok 182 - is_clustered() fail no desc should have the proper description -ok 183 - is_clustered() fail no desc should have the proper diagnostics -ok 184 - is_clustered() fail no schema should fail -ok 185 - is_clustered() fail no schema should have the proper description -ok 186 - is_clustered() fail no schema should have the proper diagnostics -ok 187 - is_clustered() fail index only should fail -ok 188 - is_clustered() fail index only should have the proper description -ok 189 - is_clustered() fail index only should have the proper diagnostics -ok 190 - is_clustered() should pass -ok 191 - is_clustered() should have the proper description -ok 192 - is_clustered() should have the proper diagnostics -ok 193 - is_clustered() no desc should pass -ok 194 - is_clustered() no desc should have the proper description -ok 195 - is_clustered() no desc should have the proper diagnostics -ok 196 - is_clustered() no schema should pass -ok 197 - is_clustered() no schema should have the proper description -ok 198 - is_clustered() no schema should have the proper diagnostics -ok 199 - is_clustered() index only should pass -ok 200 - is_clustered() index only should have the proper description -ok 201 - is_clustered() index only should have the proper diagnostics -ok 202 - index_is_type() should pass -ok 203 - index_is_type() should have the proper description -ok 204 - index_is_type() should have the proper diagnostics -ok 205 - index_is_type() no desc should pass -ok 206 - index_is_type() no desc should have the proper description -ok 207 - index_is_type() no desc should have the proper diagnostics -ok 208 - index_is_type() fail should fail -ok 209 - index_is_type() fail should have the proper description -ok 210 - index_is_type() fail should have the proper diagnostics -ok 211 - index_is_type() no schema should pass -ok 212 - index_is_type() no schema should have the proper description -ok 213 - index_is_type() no schema should have the proper diagnostics -ok 214 - index_is_type() no schema fail should fail -ok 215 - index_is_type() no schema fail should have the proper description -ok 216 - index_is_type() no schema fail should have the proper diagnostics -ok 217 - index_is_type() no table should pass -ok 218 - index_is_type() no table should have the proper description -ok 219 - index_is_type() no table should have the proper diagnostics -ok 220 - index_is_type() no table fail should fail -ok 221 - index_is_type() no table fail should have the proper description -ok 222 - index_is_type() no table fail should have the proper diagnostics -ok 223 - index_is_type() hash should pass -ok 224 - index_is_type() hash should have the proper description -ok 225 - index_is_type() hash should have the proper diagnostics +ok 22 - has_index() [functional] should pass +ok 23 - has_index() [functional] should have the proper description +ok 24 - has_index() [functional] should have the proper diagnostics +ok 25 - has_index() [col, expr] should pass +ok 26 - has_index() [col, expr] should have the proper description +ok 27 - has_index() [col, expr] should have the proper diagnostics +ok 28 - has_index() no cols should pass +ok 29 - has_index() no cols should have the proper description +ok 30 - has_index() no cols should have the proper diagnostics +ok 31 - has_index() hash index should pass +ok 32 - has_index() hash index should have the proper description +ok 33 - has_index() hash index should have the proper diagnostics +ok 34 - has_index() no cols no desc should pass +ok 35 - has_index() no cols no desc should have the proper description +ok 36 - has_index() no cols no desc should have the proper diagnostics +ok 37 - has_index() no cols hash index no desc should pass +ok 38 - has_index() no cols hash index no desc should have the proper description +ok 39 - has_index() no cols hash index no desc should have the proper diagnostics +ok 40 - has_index() no schema single column should pass +ok 41 - has_index() no schema single column should have the proper description +ok 42 - has_index() no schema single column should have the proper diagnostics +ok 43 - has_index() no schema single column no desc should pass +ok 44 - has_index() no schema single column no desc should have the proper description +ok 45 - has_index() no schema single column no desc should have the proper diagnostics +ok 46 - has_index() no schema multi-column should pass +ok 47 - has_index() no schema multi-column should have the proper description +ok 48 - has_index() no schema multi-column should have the proper diagnostics +ok 49 - has_index() no schema multi-column no desc should pass +ok 50 - has_index() no schema multi-column no desc should have the proper description +ok 51 - has_index() no schema multi-column no desc should have the proper diagnostics +ok 52 - has_index() no schema functional should pass +ok 53 - has_index() no schema functional should have the proper description +ok 54 - has_index() no schema functional should have the proper diagnostics +ok 55 - has_index() no schema functional no desc should pass +ok 56 - has_index() no schema functional no desc should have the proper description +ok 57 - has_index() no schema functional no desc should have the proper diagnostics +ok 58 - has_index() no schema or cols should pass +ok 59 - has_index() no schema or cols should have the proper description +ok 60 - has_index() no schema or cols should have the proper diagnostics +ok 61 - has_index() hash index no schema or cols should pass +ok 62 - has_index() hash index no schema or cols should have the proper description +ok 63 - has_index() hash index no schema or cols should have the proper diagnostics +ok 64 - has_index() no schema or cols or desc should pass +ok 65 - has_index() no schema or cols or desc should have the proper description +ok 66 - has_index() no schema or cols or desc should have the proper diagnostics +ok 67 - has_index() hash index no schema or cols or desc should pass +ok 68 - has_index() hash index no schema or cols or desc should have the proper description +ok 69 - has_index() hash index no schema or cols or desc should have the proper diagnostics +ok 70 - has_index() non-existent should fail +ok 71 - has_index() non-existent should have the proper description +ok 72 - has_index() non-existent should have the proper diagnostics +ok 73 - has_index() missing should fail +ok 74 - has_index() missing should have the proper description +ok 75 - has_index() missing should have the proper diagnostics +ok 76 - has_index() invalid should fail +ok 77 - has_index() invalid should have the proper description +ok 78 - has_index() invalid should have the proper diagnostics +ok 79 - has_index() missing column should fail +ok 80 - has_index() missing column should have the proper description +ok 81 - has_index() missing column should have the proper diagnostics +ok 82 - has_index() missing no schema should fail +ok 83 - has_index() missing no schema should have the proper description +ok 84 - has_index() missing no schema should have the proper diagnostics +ok 85 - has_index() invalid no schema should fail +ok 86 - has_index() invalid no schema should have the proper description +ok 87 - has_index() invalid no schema should have the proper diagnostics +ok 88 - has_index() functional fail should fail +ok 89 - has_index() functional fail should have the proper description +ok 90 - has_index() functional fail should have the proper diagnostics +ok 91 - has_index() functional fail no schema should fail +ok 92 - has_index() functional fail no schema should have the proper description +ok 93 - has_index() functional fail no schema should have the proper diagnostics +ok 94 - hasnt_index(schema, table, index, desc) should fail +ok 95 - hasnt_index(schema, table, index, desc) should have the proper description +ok 96 - hasnt_index(schema, table, index, desc) should have the proper diagnostics +ok 97 - hasnt_index(schema, table, index) should fail +ok 98 - hasnt_index(schema, table, index) should have the proper description +ok 99 - hasnt_index(schema, table, index) should have the proper diagnostics +ok 100 - hasnt_index(schema, table, non-index, desc) should pass +ok 101 - hasnt_index(schema, table, non-index, desc) should have the proper description +ok 102 - hasnt_index(schema, table, non-index, desc) should have the proper diagnostics +ok 103 - hasnt_index(schema, table, non-index) should pass +ok 104 - hasnt_index(schema, table, non-index) should have the proper description +ok 105 - hasnt_index(schema, table, non-index) should have the proper diagnostics +ok 106 - hasnt_index(table, index, desc) should fail +ok 107 - hasnt_index(table, index, desc) should have the proper description +ok 108 - hasnt_index(table, index, desc) should have the proper diagnostics +ok 109 - hasnt_index(table, index) should fail +ok 110 - hasnt_index(table, index) should have the proper description +ok 111 - hasnt_index(table, index) should have the proper diagnostics +ok 112 - hasnt_index(table, non-index, desc) should pass +ok 113 - hasnt_index(table, non-index, desc) should have the proper description +ok 114 - hasnt_index(table, non-index, desc) should have the proper diagnostics +ok 115 - hasnt_index(table, non-index) should pass +ok 116 - hasnt_index(table, non-index) should have the proper description +ok 117 - hasnt_index(table, non-index) should have the proper diagnostics +ok 118 - index_is_unique() should pass +ok 119 - index_is_unique() should have the proper description +ok 120 - index_is_unique() should have the proper diagnostics +ok 121 - index_is_unique() no desc should pass +ok 122 - index_is_unique() no desc should have the proper description +ok 123 - index_is_unique() no desc should have the proper diagnostics +ok 124 - index_is_unique() no schema should pass +ok 125 - index_is_unique() no schema should have the proper description +ok 126 - index_is_unique() no schema should have the proper diagnostics +ok 127 - index_is_unique() index only should pass +ok 128 - index_is_unique() index only should have the proper description +ok 129 - index_is_unique() index only should have the proper diagnostics +ok 130 - index_is_unique() on pk should pass +ok 131 - index_is_unique() on pk should have the proper description +ok 132 - index_is_unique() on pk should have the proper diagnostics +ok 133 - index_is_unique() on pk no desc should pass +ok 134 - index_is_unique() on pk no desc should have the proper description +ok 135 - index_is_unique() on pk no desc should have the proper diagnostics +ok 136 - index_is_unique() on pk no schema should pass +ok 137 - index_is_unique() on pk no schema should have the proper description +ok 138 - index_is_unique() on pk no schema should have the proper diagnostics +ok 139 - index_is_unique() on pk index only should pass +ok 140 - index_is_unique() on pk index only should have the proper description +ok 141 - index_is_unique() on pk index only should have the proper diagnostics +ok 142 - index_is_unique() fail should fail +ok 143 - index_is_unique() fail should have the proper description +ok 144 - index_is_unique() fail should have the proper diagnostics +ok 145 - index_is_unique() fail no desc should fail +ok 146 - index_is_unique() fail no desc should have the proper description +ok 147 - index_is_unique() fail no desc should have the proper diagnostics +ok 148 - index_is_unique() fail no schema should fail +ok 149 - index_is_unique() fail no schema should have the proper description +ok 150 - index_is_unique() fail no schema should have the proper diagnostics +ok 151 - index_is_unique() fail index only should fail +ok 152 - index_is_unique() fail index only should have the proper description +ok 153 - index_is_unique() fail index only should have the proper diagnostics +ok 154 - index_is_unique() no such index should fail +ok 155 - index_is_unique() no such index should have the proper description +ok 156 - index_is_unique() no such index should have the proper diagnostics +ok 157 - index_is_primary() should pass +ok 158 - index_is_primary() should have the proper description +ok 159 - index_is_primary() should have the proper diagnostics +ok 160 - index_is_primary() no desc should pass +ok 161 - index_is_primary() no desc should have the proper description +ok 162 - index_is_primary() no desc should have the proper diagnostics +ok 163 - index_is_primary() no schema should pass +ok 164 - index_is_primary() no schema should have the proper description +ok 165 - index_is_primary() no schema should have the proper diagnostics +ok 166 - index_is_primary() index only should pass +ok 167 - index_is_primary() index only should have the proper description +ok 168 - index_is_primary() index only should have the proper diagnostics +ok 169 - index_is_primary() fail should fail +ok 170 - index_is_primary() fail should have the proper description +ok 171 - index_is_primary() fail should have the proper diagnostics +ok 172 - index_is_primary() fail no desc should fail +ok 173 - index_is_primary() fail no desc should have the proper description +ok 174 - index_is_primary() fail no desc should have the proper diagnostics +ok 175 - index_is_primary() fail no schema should fail +ok 176 - index_is_primary() fail no schema should have the proper description +ok 177 - index_is_primary() fail no schema should have the proper diagnostics +ok 178 - index_is_primary() fail index only should fail +ok 179 - index_is_primary() fail index only should have the proper description +ok 180 - index_is_primary() fail index only should have the proper diagnostics +ok 181 - index_is_primary() no such index should fail +ok 182 - index_is_primary() no such index should have the proper description +ok 183 - index_is_primary() no such index should have the proper diagnostics +ok 184 - is_clustered() fail should fail +ok 185 - is_clustered() fail should have the proper description +ok 186 - is_clustered() fail should have the proper diagnostics +ok 187 - is_clustered() fail no desc should fail +ok 188 - is_clustered() fail no desc should have the proper description +ok 189 - is_clustered() fail no desc should have the proper diagnostics +ok 190 - is_clustered() fail no schema should fail +ok 191 - is_clustered() fail no schema should have the proper description +ok 192 - is_clustered() fail no schema should have the proper diagnostics +ok 193 - is_clustered() fail index only should fail +ok 194 - is_clustered() fail index only should have the proper description +ok 195 - is_clustered() fail index only should have the proper diagnostics +ok 196 - is_clustered() should pass +ok 197 - is_clustered() should have the proper description +ok 198 - is_clustered() should have the proper diagnostics +ok 199 - is_clustered() no desc should pass +ok 200 - is_clustered() no desc should have the proper description +ok 201 - is_clustered() no desc should have the proper diagnostics +ok 202 - is_clustered() no schema should pass +ok 203 - is_clustered() no schema should have the proper description +ok 204 - is_clustered() no schema should have the proper diagnostics +ok 205 - is_clustered() index only should pass +ok 206 - is_clustered() index only should have the proper description +ok 207 - is_clustered() index only should have the proper diagnostics +ok 208 - index_is_type() should pass +ok 209 - index_is_type() should have the proper description +ok 210 - index_is_type() should have the proper diagnostics +ok 211 - index_is_type() no desc should pass +ok 212 - index_is_type() no desc should have the proper description +ok 213 - index_is_type() no desc should have the proper diagnostics +ok 214 - index_is_type() fail should fail +ok 215 - index_is_type() fail should have the proper description +ok 216 - index_is_type() fail should have the proper diagnostics +ok 217 - index_is_type() no schema should pass +ok 218 - index_is_type() no schema should have the proper description +ok 219 - index_is_type() no schema should have the proper diagnostics +ok 220 - index_is_type() no schema fail should fail +ok 221 - index_is_type() no schema fail should have the proper description +ok 222 - index_is_type() no schema fail should have the proper diagnostics +ok 223 - index_is_type() no table should pass +ok 224 - index_is_type() no table should have the proper description +ok 225 - index_is_type() no table should have the proper diagnostics +ok 226 - index_is_type() no table fail should fail +ok 227 - index_is_type() no table fail should have the proper description +ok 228 - index_is_type() no table fail should have the proper diagnostics +ok 229 - index_is_type() hash should pass +ok 230 - index_is_type() hash should have the proper description +ok 231 - index_is_type() hash should have the proper diagnostics diff --git a/test/sql/index.sql b/test/sql/index.sql index ab34b3b540c9..8c4b1a66f8a7 100644 --- a/test/sql/index.sql +++ b/test/sql/index.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(225); +SELECT plan(231); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -16,6 +16,7 @@ CREATE INDEX idx_hey ON public.sometab(numb); CREATE INDEX idx_foo ON public.sometab using hash(name); CREATE INDEX idx_bar ON public.sometab(numb, name); CREATE UNIQUE INDEX idx_baz ON public.sometab(LOWER(name)); +CREATE INDEX idx_mul ON public.sometab(numb, LOWER(name)); RESET client_min_messages; /****************************************************************************/ @@ -77,6 +78,22 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'idx_baz', ARRAY['lower(name)'], 'whatever' ), + true, + 'has_index() [functional]', + 'whatever', + '' +); + +SELECT * FROM check_test( + has_index( 'public', 'sometab', 'idx_mul', ARRAY['numb', 'lower(name)'], 'whatever' ), + true, + 'has_index() [col, expr]', + 'whatever', + '' +); + SELECT * FROM check_test( has_index( 'public', 'sometab', 'idx_baz', 'whatever' ), true, From b7f240f658814288994a8301b56945ce8edfa77c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Jan 2013 14:34:32 -0800 Subject: [PATCH 0699/1195] Properly query both columns and expressions from indexes. Thanks to Tom Lane for pointing me to `pg_catalog.pg_get_expr()`. Closes #12. --- doc/pgtap.mmd | 5 +- sql/pgtap--0.91.0--0.92.0.sql | 22 +- sql/pgtap.sql.in | 14 +- test/expected/index.out | 413 +++++++++++++++++----------------- test/sql/index.sql | 15 +- 5 files changed, 238 insertions(+), 231 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 8892fb72200e..7a7fd55da221 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2971,7 +2971,8 @@ Checks for the existence of an index associated with the named table. The and the description. The columns argument may be a string naming one column or expression, or an array of column names and/or expressions. For expressions, you must use lowercase for all SQL keywords and functions to properly compare -to PostgreSQL's internal form of the expression. A few examples: +to PostgreSQL's internal form of the expression. Non-functional expressions +should also be wrapped in parentheses. A few examples: SELECT has_index( 'myschema', @@ -2982,7 +2983,7 @@ to PostgreSQL's internal form of the expression. A few examples: ); SELECT has_index('myschema', 'sometable', 'anidx', 'somecolumn'); - SELECT has_index('myschema', 'sometable', 'loweridx', 'lower(somecolumn)'); + SELECT has_index('myschema', 'sometable', 'loweridx', '(somearray[1])'); SELECT has_index('sometable', 'someindex'); If you find that the function call seems to be getting confused, cast the diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index 1bb99535f240..0757c12ce860 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -756,19 +756,21 @@ RETURNS TEXT AS $$ SELECT isnt_empty( $1, NULL ); $$ LANGUAGE sql; +DROP FUNCTION _ikeys( NAME, NAME, NAME ); +DROP FUNCTION _ikeys( NAME, NAME ); +DROP FUNCTION _iexpr( NAME, NAME, NAME ); +DROP FUNCTION _iexpr( NAME, NAME ); + CREATE OR REPLACE FUNCTION _ikeys( NAME, NAME, NAME) -RETURNS NAME[] AS $$ +RETURNS TEXT[] AS $$ SELECT ARRAY( - SELECT COALESCE(a.attname, pg_catalog.pg_get_expr( x.indexprs, ct.oid )) + 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 - LEFT JOIN pg_catalog.pg_attribute a - ON ct.oid = a.attrelid - AND a.attnum = x.indkey[s.i] WHERE ct.relname = $2 AND ci.relname = $3 AND n.nspname = $1 @@ -777,17 +779,14 @@ RETURNS NAME[] AS $$ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _ikeys( NAME, NAME) -RETURNS NAME[] AS $$ +RETURNS TEXT[] AS $$ SELECT ARRAY( - SELECT COALESCE(a.attname, pg_catalog.pg_get_expr( x.indexprs, ct.oid )) + 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 - LEFT JOIN pg_catalog.pg_attribute a - ON ct.oid = a.attrelid - AND a.attnum = x.indkey[s.i] WHERE ct.relname = $1 AND ci.relname = $2 AND pg_catalog.pg_table_is_visible(ct.oid) @@ -795,9 +794,6 @@ RETURNS NAME[] AS $$ ); $$ LANGUAGE sql; -DROP FUNCTION _iexpr( NAME, NAME, NAME ); -DROP FUNCTION _iexpr( NAME, NAME ); - -- has_index( schema, table, index, columns[], description ) CREATE OR REPLACE FUNCTION has_index ( NAME, NAME, NAME, NAME[], text ) RETURNS TEXT AS $$ diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index d8e18e9ac7f7..55c916174b72 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2555,18 +2555,15 @@ RETURNS TEXT AS $$ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _ikeys( NAME, NAME, NAME) -RETURNS NAME[] AS $$ +RETURNS TEXT[] AS $$ SELECT ARRAY( - SELECT COALESCE(a.attname, pg_catalog.pg_get_expr( x.indexprs, ct.oid )) + 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 - LEFT JOIN pg_catalog.pg_attribute a - ON ct.oid = a.attrelid - AND a.attnum = x.indkey[s.i] WHERE ct.relname = $2 AND ci.relname = $3 AND n.nspname = $1 @@ -2575,17 +2572,14 @@ RETURNS NAME[] AS $$ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _ikeys( NAME, NAME) -RETURNS NAME[] AS $$ +RETURNS TEXT[] AS $$ SELECT ARRAY( - SELECT COALESCE(a.attname, pg_catalog.pg_get_expr( x.indexprs, ct.oid )) + 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 - LEFT JOIN pg_catalog.pg_attribute a - ON ct.oid = a.attrelid - AND a.attnum = x.indkey[s.i] WHERE ct.relname = $1 AND ci.relname = $2 AND pg_catalog.pg_table_is_visible(ct.oid) diff --git a/test/expected/index.out b/test/expected/index.out index f9b8c8e03264..8a4365160640 100644 --- a/test/expected/index.out +++ b/test/expected/index.out @@ -1,5 +1,5 @@ \unset ECHO -1..231 +1..234 ok 1 - has_index() single column should pass ok 2 - has_index() single column should have the proper description ok 3 - has_index() single column should have the proper diagnostics @@ -27,207 +27,210 @@ ok 24 - has_index() [functional] should have the proper diagnostics ok 25 - has_index() [col, expr] should pass ok 26 - has_index() [col, expr] should have the proper description ok 27 - has_index() [col, expr] should have the proper diagnostics -ok 28 - has_index() no cols should pass -ok 29 - has_index() no cols should have the proper description -ok 30 - has_index() no cols should have the proper diagnostics -ok 31 - has_index() hash index should pass -ok 32 - has_index() hash index should have the proper description -ok 33 - has_index() hash index should have the proper diagnostics -ok 34 - has_index() no cols no desc should pass -ok 35 - has_index() no cols no desc should have the proper description -ok 36 - has_index() no cols no desc should have the proper diagnostics -ok 37 - has_index() no cols hash index no desc should pass -ok 38 - has_index() no cols hash index no desc should have the proper description -ok 39 - has_index() no cols hash index no desc should have the proper diagnostics -ok 40 - has_index() no schema single column should pass -ok 41 - has_index() no schema single column should have the proper description -ok 42 - has_index() no schema single column should have the proper diagnostics -ok 43 - has_index() no schema single column no desc should pass -ok 44 - has_index() no schema single column no desc should have the proper description -ok 45 - has_index() no schema single column no desc should have the proper diagnostics -ok 46 - has_index() no schema multi-column should pass -ok 47 - has_index() no schema multi-column should have the proper description -ok 48 - has_index() no schema multi-column should have the proper diagnostics -ok 49 - has_index() no schema multi-column no desc should pass -ok 50 - has_index() no schema multi-column no desc should have the proper description -ok 51 - has_index() no schema multi-column no desc should have the proper diagnostics -ok 52 - has_index() no schema functional should pass -ok 53 - has_index() no schema functional should have the proper description -ok 54 - has_index() no schema functional should have the proper diagnostics -ok 55 - has_index() no schema functional no desc should pass -ok 56 - has_index() no schema functional no desc should have the proper description -ok 57 - has_index() no schema functional no desc should have the proper diagnostics -ok 58 - has_index() no schema or cols should pass -ok 59 - has_index() no schema or cols should have the proper description -ok 60 - has_index() no schema or cols should have the proper diagnostics -ok 61 - has_index() hash index no schema or cols should pass -ok 62 - has_index() hash index no schema or cols should have the proper description -ok 63 - has_index() hash index no schema or cols should have the proper diagnostics -ok 64 - has_index() no schema or cols or desc should pass -ok 65 - has_index() no schema or cols or desc should have the proper description -ok 66 - has_index() no schema or cols or desc should have the proper diagnostics -ok 67 - has_index() hash index no schema or cols or desc should pass -ok 68 - has_index() hash index no schema or cols or desc should have the proper description -ok 69 - has_index() hash index no schema or cols or desc should have the proper diagnostics -ok 70 - has_index() non-existent should fail -ok 71 - has_index() non-existent should have the proper description -ok 72 - has_index() non-existent should have the proper diagnostics -ok 73 - has_index() missing should fail -ok 74 - has_index() missing should have the proper description -ok 75 - has_index() missing should have the proper diagnostics -ok 76 - has_index() invalid should fail -ok 77 - has_index() invalid should have the proper description -ok 78 - has_index() invalid should have the proper diagnostics -ok 79 - has_index() missing column should fail -ok 80 - has_index() missing column should have the proper description -ok 81 - has_index() missing column should have the proper diagnostics -ok 82 - has_index() missing no schema should fail -ok 83 - has_index() missing no schema should have the proper description -ok 84 - has_index() missing no schema should have the proper diagnostics -ok 85 - has_index() invalid no schema should fail -ok 86 - has_index() invalid no schema should have the proper description -ok 87 - has_index() invalid no schema should have the proper diagnostics -ok 88 - has_index() functional fail should fail -ok 89 - has_index() functional fail should have the proper description -ok 90 - has_index() functional fail should have the proper diagnostics -ok 91 - has_index() functional fail no schema should fail -ok 92 - has_index() functional fail no schema should have the proper description -ok 93 - has_index() functional fail no schema should have the proper diagnostics -ok 94 - hasnt_index(schema, table, index, desc) should fail -ok 95 - hasnt_index(schema, table, index, desc) should have the proper description -ok 96 - hasnt_index(schema, table, index, desc) should have the proper diagnostics -ok 97 - hasnt_index(schema, table, index) should fail -ok 98 - hasnt_index(schema, table, index) should have the proper description -ok 99 - hasnt_index(schema, table, index) should have the proper diagnostics -ok 100 - hasnt_index(schema, table, non-index, desc) should pass -ok 101 - hasnt_index(schema, table, non-index, desc) should have the proper description -ok 102 - hasnt_index(schema, table, non-index, desc) should have the proper diagnostics -ok 103 - hasnt_index(schema, table, non-index) should pass -ok 104 - hasnt_index(schema, table, non-index) should have the proper description -ok 105 - hasnt_index(schema, table, non-index) should have the proper diagnostics -ok 106 - hasnt_index(table, index, desc) should fail -ok 107 - hasnt_index(table, index, desc) should have the proper description -ok 108 - hasnt_index(table, index, desc) should have the proper diagnostics -ok 109 - hasnt_index(table, index) should fail -ok 110 - hasnt_index(table, index) should have the proper description -ok 111 - hasnt_index(table, index) should have the proper diagnostics -ok 112 - hasnt_index(table, non-index, desc) should pass -ok 113 - hasnt_index(table, non-index, desc) should have the proper description -ok 114 - hasnt_index(table, non-index, desc) should have the proper diagnostics -ok 115 - hasnt_index(table, non-index) should pass -ok 116 - hasnt_index(table, non-index) should have the proper description -ok 117 - hasnt_index(table, non-index) should have the proper diagnostics -ok 118 - index_is_unique() should pass -ok 119 - index_is_unique() should have the proper description -ok 120 - index_is_unique() should have the proper diagnostics -ok 121 - index_is_unique() no desc should pass -ok 122 - index_is_unique() no desc should have the proper description -ok 123 - index_is_unique() no desc should have the proper diagnostics -ok 124 - index_is_unique() no schema should pass -ok 125 - index_is_unique() no schema should have the proper description -ok 126 - index_is_unique() no schema should have the proper diagnostics -ok 127 - index_is_unique() index only should pass -ok 128 - index_is_unique() index only should have the proper description -ok 129 - index_is_unique() index only should have the proper diagnostics -ok 130 - index_is_unique() on pk should pass -ok 131 - index_is_unique() on pk should have the proper description -ok 132 - index_is_unique() on pk should have the proper diagnostics -ok 133 - index_is_unique() on pk no desc should pass -ok 134 - index_is_unique() on pk no desc should have the proper description -ok 135 - index_is_unique() on pk no desc should have the proper diagnostics -ok 136 - index_is_unique() on pk no schema should pass -ok 137 - index_is_unique() on pk no schema should have the proper description -ok 138 - index_is_unique() on pk no schema should have the proper diagnostics -ok 139 - index_is_unique() on pk index only should pass -ok 140 - index_is_unique() on pk index only should have the proper description -ok 141 - index_is_unique() on pk index only should have the proper diagnostics -ok 142 - index_is_unique() fail should fail -ok 143 - index_is_unique() fail should have the proper description -ok 144 - index_is_unique() fail should have the proper diagnostics -ok 145 - index_is_unique() fail no desc should fail -ok 146 - index_is_unique() fail no desc should have the proper description -ok 147 - index_is_unique() fail no desc should have the proper diagnostics -ok 148 - index_is_unique() fail no schema should fail -ok 149 - index_is_unique() fail no schema should have the proper description -ok 150 - index_is_unique() fail no schema should have the proper diagnostics -ok 151 - index_is_unique() fail index only should fail -ok 152 - index_is_unique() fail index only should have the proper description -ok 153 - index_is_unique() fail index only should have the proper diagnostics -ok 154 - index_is_unique() no such index should fail -ok 155 - index_is_unique() no such index should have the proper description -ok 156 - index_is_unique() no such index should have the proper diagnostics -ok 157 - index_is_primary() should pass -ok 158 - index_is_primary() should have the proper description -ok 159 - index_is_primary() should have the proper diagnostics -ok 160 - index_is_primary() no desc should pass -ok 161 - index_is_primary() no desc should have the proper description -ok 162 - index_is_primary() no desc should have the proper diagnostics -ok 163 - index_is_primary() no schema should pass -ok 164 - index_is_primary() no schema should have the proper description -ok 165 - index_is_primary() no schema should have the proper diagnostics -ok 166 - index_is_primary() index only should pass -ok 167 - index_is_primary() index only should have the proper description -ok 168 - index_is_primary() index only should have the proper diagnostics -ok 169 - index_is_primary() fail should fail -ok 170 - index_is_primary() fail should have the proper description -ok 171 - index_is_primary() fail should have the proper diagnostics -ok 172 - index_is_primary() fail no desc should fail -ok 173 - index_is_primary() fail no desc should have the proper description -ok 174 - index_is_primary() fail no desc should have the proper diagnostics -ok 175 - index_is_primary() fail no schema should fail -ok 176 - index_is_primary() fail no schema should have the proper description -ok 177 - index_is_primary() fail no schema should have the proper diagnostics -ok 178 - index_is_primary() fail index only should fail -ok 179 - index_is_primary() fail index only should have the proper description -ok 180 - index_is_primary() fail index only should have the proper diagnostics -ok 181 - index_is_primary() no such index should fail -ok 182 - index_is_primary() no such index should have the proper description -ok 183 - index_is_primary() no such index should have the proper diagnostics -ok 184 - is_clustered() fail should fail -ok 185 - is_clustered() fail should have the proper description -ok 186 - is_clustered() fail should have the proper diagnostics -ok 187 - is_clustered() fail no desc should fail -ok 188 - is_clustered() fail no desc should have the proper description -ok 189 - is_clustered() fail no desc should have the proper diagnostics -ok 190 - is_clustered() fail no schema should fail -ok 191 - is_clustered() fail no schema should have the proper description -ok 192 - is_clustered() fail no schema should have the proper diagnostics -ok 193 - is_clustered() fail index only should fail -ok 194 - is_clustered() fail index only should have the proper description -ok 195 - is_clustered() fail index only should have the proper diagnostics -ok 196 - is_clustered() should pass -ok 197 - is_clustered() should have the proper description -ok 198 - is_clustered() should have the proper diagnostics -ok 199 - is_clustered() no desc should pass -ok 200 - is_clustered() no desc should have the proper description -ok 201 - is_clustered() no desc should have the proper diagnostics -ok 202 - is_clustered() no schema should pass -ok 203 - is_clustered() no schema should have the proper description -ok 204 - is_clustered() no schema should have the proper diagnostics -ok 205 - is_clustered() index only should pass -ok 206 - is_clustered() index only should have the proper description -ok 207 - is_clustered() index only should have the proper diagnostics -ok 208 - index_is_type() should pass -ok 209 - index_is_type() should have the proper description -ok 210 - index_is_type() should have the proper diagnostics -ok 211 - index_is_type() no desc should pass -ok 212 - index_is_type() no desc should have the proper description -ok 213 - index_is_type() no desc should have the proper diagnostics -ok 214 - index_is_type() fail should fail -ok 215 - index_is_type() fail should have the proper description -ok 216 - index_is_type() fail should have the proper diagnostics -ok 217 - index_is_type() no schema should pass -ok 218 - index_is_type() no schema should have the proper description -ok 219 - index_is_type() no schema should have the proper diagnostics -ok 220 - index_is_type() no schema fail should fail -ok 221 - index_is_type() no schema fail should have the proper description -ok 222 - index_is_type() no schema fail should have the proper diagnostics -ok 223 - index_is_type() no table should pass -ok 224 - index_is_type() no table should have the proper description -ok 225 - index_is_type() no table should have the proper diagnostics -ok 226 - index_is_type() no table fail should fail -ok 227 - index_is_type() no table fail should have the proper description -ok 228 - index_is_type() no table fail should have the proper diagnostics -ok 229 - index_is_type() hash should pass -ok 230 - index_is_type() hash should have the proper description -ok 231 - index_is_type() hash should have the proper diagnostics +ok 28 - has_index() [expr, col, expr] should pass +ok 29 - has_index() [expr, col, expr] should have the proper description +ok 30 - has_index() [expr, col, expr] should have the proper diagnostics +ok 31 - has_index() no cols should pass +ok 32 - has_index() no cols should have the proper description +ok 33 - has_index() no cols should have the proper diagnostics +ok 34 - has_index() hash index should pass +ok 35 - has_index() hash index should have the proper description +ok 36 - has_index() hash index should have the proper diagnostics +ok 37 - has_index() no cols no desc should pass +ok 38 - has_index() no cols no desc should have the proper description +ok 39 - has_index() no cols no desc should have the proper diagnostics +ok 40 - has_index() no cols hash index no desc should pass +ok 41 - has_index() no cols hash index no desc should have the proper description +ok 42 - has_index() no cols hash index no desc should have the proper diagnostics +ok 43 - has_index() no schema single column should pass +ok 44 - has_index() no schema single column should have the proper description +ok 45 - has_index() no schema single column should have the proper diagnostics +ok 46 - has_index() no schema single column no desc should pass +ok 47 - has_index() no schema single column no desc should have the proper description +ok 48 - has_index() no schema single column no desc should have the proper diagnostics +ok 49 - has_index() no schema multi-column should pass +ok 50 - has_index() no schema multi-column should have the proper description +ok 51 - has_index() no schema multi-column should have the proper diagnostics +ok 52 - has_index() no schema multi-column no desc should pass +ok 53 - has_index() no schema multi-column no desc should have the proper description +ok 54 - has_index() no schema multi-column no desc should have the proper diagnostics +ok 55 - has_index() no schema functional should pass +ok 56 - has_index() no schema functional should have the proper description +ok 57 - has_index() no schema functional should have the proper diagnostics +ok 58 - has_index() no schema functional no desc should pass +ok 59 - has_index() no schema functional no desc should have the proper description +ok 60 - has_index() no schema functional no desc should have the proper diagnostics +ok 61 - has_index() no schema or cols should pass +ok 62 - has_index() no schema or cols should have the proper description +ok 63 - has_index() no schema or cols should have the proper diagnostics +ok 64 - has_index() hash index no schema or cols should pass +ok 65 - has_index() hash index no schema or cols should have the proper description +ok 66 - has_index() hash index no schema or cols should have the proper diagnostics +ok 67 - has_index() no schema or cols or desc should pass +ok 68 - has_index() no schema or cols or desc should have the proper description +ok 69 - has_index() no schema or cols or desc should have the proper diagnostics +ok 70 - has_index() hash index no schema or cols or desc should pass +ok 71 - has_index() hash index no schema or cols or desc should have the proper description +ok 72 - has_index() hash index no schema or cols or desc should have the proper diagnostics +ok 73 - has_index() non-existent should fail +ok 74 - has_index() non-existent should have the proper description +ok 75 - has_index() non-existent should have the proper diagnostics +ok 76 - has_index() missing should fail +ok 77 - has_index() missing should have the proper description +ok 78 - has_index() missing should have the proper diagnostics +ok 79 - has_index() invalid should fail +ok 80 - has_index() invalid should have the proper description +ok 81 - has_index() invalid should have the proper diagnostics +ok 82 - has_index() missing column should fail +ok 83 - has_index() missing column should have the proper description +ok 84 - has_index() missing column should have the proper diagnostics +ok 85 - has_index() missing no schema should fail +ok 86 - has_index() missing no schema should have the proper description +ok 87 - has_index() missing no schema should have the proper diagnostics +ok 88 - has_index() invalid no schema should fail +ok 89 - has_index() invalid no schema should have the proper description +ok 90 - has_index() invalid no schema should have the proper diagnostics +ok 91 - has_index() functional fail should fail +ok 92 - has_index() functional fail should have the proper description +ok 93 - has_index() functional fail should have the proper diagnostics +ok 94 - has_index() functional fail no schema should fail +ok 95 - has_index() functional fail no schema should have the proper description +ok 96 - has_index() functional fail no schema should have the proper diagnostics +ok 97 - hasnt_index(schema, table, index, desc) should fail +ok 98 - hasnt_index(schema, table, index, desc) should have the proper description +ok 99 - hasnt_index(schema, table, index, desc) should have the proper diagnostics +ok 100 - hasnt_index(schema, table, index) should fail +ok 101 - hasnt_index(schema, table, index) should have the proper description +ok 102 - hasnt_index(schema, table, index) should have the proper diagnostics +ok 103 - hasnt_index(schema, table, non-index, desc) should pass +ok 104 - hasnt_index(schema, table, non-index, desc) should have the proper description +ok 105 - hasnt_index(schema, table, non-index, desc) should have the proper diagnostics +ok 106 - hasnt_index(schema, table, non-index) should pass +ok 107 - hasnt_index(schema, table, non-index) should have the proper description +ok 108 - hasnt_index(schema, table, non-index) should have the proper diagnostics +ok 109 - hasnt_index(table, index, desc) should fail +ok 110 - hasnt_index(table, index, desc) should have the proper description +ok 111 - hasnt_index(table, index, desc) should have the proper diagnostics +ok 112 - hasnt_index(table, index) should fail +ok 113 - hasnt_index(table, index) should have the proper description +ok 114 - hasnt_index(table, index) should have the proper diagnostics +ok 115 - hasnt_index(table, non-index, desc) should pass +ok 116 - hasnt_index(table, non-index, desc) should have the proper description +ok 117 - hasnt_index(table, non-index, desc) should have the proper diagnostics +ok 118 - hasnt_index(table, non-index) should pass +ok 119 - hasnt_index(table, non-index) should have the proper description +ok 120 - hasnt_index(table, non-index) should have the proper diagnostics +ok 121 - index_is_unique() should pass +ok 122 - index_is_unique() should have the proper description +ok 123 - index_is_unique() should have the proper diagnostics +ok 124 - index_is_unique() no desc should pass +ok 125 - index_is_unique() no desc should have the proper description +ok 126 - index_is_unique() no desc should have the proper diagnostics +ok 127 - index_is_unique() no schema should pass +ok 128 - index_is_unique() no schema should have the proper description +ok 129 - index_is_unique() no schema should have the proper diagnostics +ok 130 - index_is_unique() index only should pass +ok 131 - index_is_unique() index only should have the proper description +ok 132 - index_is_unique() index only should have the proper diagnostics +ok 133 - index_is_unique() on pk should pass +ok 134 - index_is_unique() on pk should have the proper description +ok 135 - index_is_unique() on pk should have the proper diagnostics +ok 136 - index_is_unique() on pk no desc should pass +ok 137 - index_is_unique() on pk no desc should have the proper description +ok 138 - index_is_unique() on pk no desc should have the proper diagnostics +ok 139 - index_is_unique() on pk no schema should pass +ok 140 - index_is_unique() on pk no schema should have the proper description +ok 141 - index_is_unique() on pk no schema should have the proper diagnostics +ok 142 - index_is_unique() on pk index only should pass +ok 143 - index_is_unique() on pk index only should have the proper description +ok 144 - index_is_unique() on pk index only should have the proper diagnostics +ok 145 - index_is_unique() fail should fail +ok 146 - index_is_unique() fail should have the proper description +ok 147 - index_is_unique() fail should have the proper diagnostics +ok 148 - index_is_unique() fail no desc should fail +ok 149 - index_is_unique() fail no desc should have the proper description +ok 150 - index_is_unique() fail no desc should have the proper diagnostics +ok 151 - index_is_unique() fail no schema should fail +ok 152 - index_is_unique() fail no schema should have the proper description +ok 153 - index_is_unique() fail no schema should have the proper diagnostics +ok 154 - index_is_unique() fail index only should fail +ok 155 - index_is_unique() fail index only should have the proper description +ok 156 - index_is_unique() fail index only should have the proper diagnostics +ok 157 - index_is_unique() no such index should fail +ok 158 - index_is_unique() no such index should have the proper description +ok 159 - index_is_unique() no such index should have the proper diagnostics +ok 160 - index_is_primary() should pass +ok 161 - index_is_primary() should have the proper description +ok 162 - index_is_primary() should have the proper diagnostics +ok 163 - index_is_primary() no desc should pass +ok 164 - index_is_primary() no desc should have the proper description +ok 165 - index_is_primary() no desc should have the proper diagnostics +ok 166 - index_is_primary() no schema should pass +ok 167 - index_is_primary() no schema should have the proper description +ok 168 - index_is_primary() no schema should have the proper diagnostics +ok 169 - index_is_primary() index only should pass +ok 170 - index_is_primary() index only should have the proper description +ok 171 - index_is_primary() index only should have the proper diagnostics +ok 172 - index_is_primary() fail should fail +ok 173 - index_is_primary() fail should have the proper description +ok 174 - index_is_primary() fail should have the proper diagnostics +ok 175 - index_is_primary() fail no desc should fail +ok 176 - index_is_primary() fail no desc should have the proper description +ok 177 - index_is_primary() fail no desc should have the proper diagnostics +ok 178 - index_is_primary() fail no schema should fail +ok 179 - index_is_primary() fail no schema should have the proper description +ok 180 - index_is_primary() fail no schema should have the proper diagnostics +ok 181 - index_is_primary() fail index only should fail +ok 182 - index_is_primary() fail index only should have the proper description +ok 183 - index_is_primary() fail index only should have the proper diagnostics +ok 184 - index_is_primary() no such index should fail +ok 185 - index_is_primary() no such index should have the proper description +ok 186 - index_is_primary() no such index should have the proper diagnostics +ok 187 - is_clustered() fail should fail +ok 188 - is_clustered() fail should have the proper description +ok 189 - is_clustered() fail should have the proper diagnostics +ok 190 - is_clustered() fail no desc should fail +ok 191 - is_clustered() fail no desc should have the proper description +ok 192 - is_clustered() fail no desc should have the proper diagnostics +ok 193 - is_clustered() fail no schema should fail +ok 194 - is_clustered() fail no schema should have the proper description +ok 195 - is_clustered() fail no schema should have the proper diagnostics +ok 196 - is_clustered() fail index only should fail +ok 197 - is_clustered() fail index only should have the proper description +ok 198 - is_clustered() fail index only should have the proper diagnostics +ok 199 - is_clustered() should pass +ok 200 - is_clustered() should have the proper description +ok 201 - is_clustered() should have the proper diagnostics +ok 202 - is_clustered() no desc should pass +ok 203 - is_clustered() no desc should have the proper description +ok 204 - is_clustered() no desc should have the proper diagnostics +ok 205 - is_clustered() no schema should pass +ok 206 - is_clustered() no schema should have the proper description +ok 207 - is_clustered() no schema should have the proper diagnostics +ok 208 - is_clustered() index only should pass +ok 209 - is_clustered() index only should have the proper description +ok 210 - is_clustered() index only should have the proper diagnostics +ok 211 - index_is_type() should pass +ok 212 - index_is_type() should have the proper description +ok 213 - index_is_type() should have the proper diagnostics +ok 214 - index_is_type() no desc should pass +ok 215 - index_is_type() no desc should have the proper description +ok 216 - index_is_type() no desc should have the proper diagnostics +ok 217 - index_is_type() fail should fail +ok 218 - index_is_type() fail should have the proper description +ok 219 - index_is_type() fail should have the proper diagnostics +ok 220 - index_is_type() no schema should pass +ok 221 - index_is_type() no schema should have the proper description +ok 222 - index_is_type() no schema should have the proper diagnostics +ok 223 - index_is_type() no schema fail should fail +ok 224 - index_is_type() no schema fail should have the proper description +ok 225 - index_is_type() no schema fail should have the proper diagnostics +ok 226 - index_is_type() no table should pass +ok 227 - index_is_type() no table should have the proper description +ok 228 - index_is_type() no table should have the proper diagnostics +ok 229 - index_is_type() no table fail should fail +ok 230 - index_is_type() no table fail should have the proper description +ok 231 - index_is_type() no table fail should have the proper diagnostics +ok 232 - index_is_type() hash should pass +ok 233 - index_is_type() hash should have the proper description +ok 234 - index_is_type() hash should have the proper diagnostics diff --git a/test/sql/index.sql b/test/sql/index.sql index 8c4b1a66f8a7..27dc980b3e4f 100644 --- a/test/sql/index.sql +++ b/test/sql/index.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(231); +SELECT plan(234); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -17,6 +17,7 @@ CREATE INDEX idx_foo ON public.sometab using hash(name); CREATE INDEX idx_bar ON public.sometab(numb, name); CREATE UNIQUE INDEX idx_baz ON public.sometab(LOWER(name)); CREATE INDEX idx_mul ON public.sometab(numb, LOWER(name)); +CREATE INDEX idx_expr ON public.sometab(UPPER(name), numb, LOWER(name)); RESET client_min_messages; /****************************************************************************/ @@ -94,6 +95,18 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + has_index( + 'public', 'sometab', 'idx_expr', + ARRAY['upper(name)', 'numb', 'lower(name)'], + 'whatever' + ), + true, + 'has_index() [expr, col, expr]', + 'whatever', + '' +); + SELECT * FROM check_test( has_index( 'public', 'sometab', 'idx_baz', 'whatever' ), true, From 4f8ec6e47df3df4158d293e24c02acd7ae1c6ec8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Jan 2013 14:36:11 -0800 Subject: [PATCH 0700/1195] Better note for has_index() fix. --- Changes | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index e00d4f9005c9..07daf111ae28 100644 --- a/Changes +++ b/Changes @@ -29,7 +29,8 @@ Revision history for pgTAP Thanks to Peter Eisentraut and Taylor Ralston for [the nudging](https://github.com/theory/pgtap/issues/15). * Added `isnt_empty()` to ensure that a query does not return the empty set. -* Fixed `has_index()` to better handle indexes with expressions. +* Fixed `has_index()` to properly handle indexes composed of both columns and + expressions. 0.91.1 2012-09-11T00:26:52Z --------------------------- From 6ad75b928c2a9dc87a10d033231d379175212d10 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Jan 2013 17:59:04 -0800 Subject: [PATCH 0701/1195] Add `table_privs_are()`. First cut at adding functions to test privileges granted to users on objects. Issue #34. --- Changes | 1 + doc/pgtap.mmd | 72 ++++++++++++++++++ sql/pgtap--0.91.0--0.92.0.sql | 125 +++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 127 +++++++++++++++++++++++++++++++ test/expected/privs.out | 31 ++++++++ test/sql/privs.sql | 136 ++++++++++++++++++++++++++++++++++ 6 files changed, 492 insertions(+) create mode 100644 test/expected/privs.out create mode 100644 test/sql/privs.sql diff --git a/Changes b/Changes index 07daf111ae28..6d43349764fe 100644 --- a/Changes +++ b/Changes @@ -31,6 +31,7 @@ Revision history for pgTAP * Added `isnt_empty()` to ensure that a query does not return the empty set. * Fixed `has_index()` to properly handle indexes composed of both columns and expressions. +* Added `table_privs_are()` for testing a user's access to a table. 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 7a7fd55da221..6c460413ca3b 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5523,6 +5523,78 @@ diagnostics will look something like: # have: postgres # want: root +Prvileged Access +---------------- + +So we know who owns the objects. But what about other roles? Can they access +database objects? Let's find out! + +### `table_privs_are()` + + + SELECT table_privs_are ( :schema, :table, :user, :privileges, :description ); + SELECT table_privs_are ( :schema, :table, :user, :privileges ); + SELECT table_privs_are ( :table, :user, :privileges, :description ); + SELECT table_privs_are ( :table, :user, :privileges ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the table. + +`:table` +: Name of a table. + +`:user` +: Name of a user. + +`:privileges` +: An array of table privileges the user should be granted to the table. + +`:description` +: A short description of the test. + +Tests the privileges granted to a user to access a table. If the +`:description` argument is omitted, an appropriate description will be +created. Examples: + + SELECT table_privs_are( + 'public', 'frobulate', 'fred', ARRAY['SELECT', 'DELETE'], + 'Fred should be able to select and delete on frobulate' + ); + SELECT table_privs_are( 'widgets', 'slim', ARRAY['INSERT', 'UPDATE'] ); + +If the user is granted permissions other than those specified, the diagnostics +will list the extra permissions, like so: + + # Failed test 14: "Role bob should be granted SELECT on widgets" + # Extra privileges: + # DELETE + # INSERT + # UPDATE + +Likewise if the user is not granted some of the specified permissions on the +table: + + # Failed test 15: "Role kurk should be granted SELECT, INSERT, UPDATE on widgets" + # Missing privileges: + # INSERT + # UPDATE + +In the event that the test fails because the table in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Role slim should be granted SELECT on widgets" + # Table widgets does not exist + +If the test fails because the userd does not exist, the diagnostics will look +something like: + + # Failed test 17: "Role slim should be granted SELECT on widgets" + # Role slip does not exist + + No Test for the Wicked ====================== diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index 0757c12ce860..619749b19d71 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -852,4 +852,129 @@ RETURNS TEXT AS $$ END; $$ LANGUAGE sql; +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; + + +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_object}'; + 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 _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; +-- 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_object' 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 ' + || array_to_string($4, ', ') || ' 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, $5) || E'\n' || diag( + ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' + ); + ELSIF grants[1] = 'undefined_object' THEN + RETURN ok(FALSE, $5) || 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 ' + || array_to_string($3, ', ') || ' on table ' || quote_ident($1) + ); +$$ LANGUAGE SQL; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 55c916174b72..95fce0f45777 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7908,3 +7908,130 @@ RETURNS TEXT AS $$ array_to_string($2, ', ') || ') should be owned by ' || quote_ident($3) ); $$ LANGUAGE sql; + +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; + + +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_object}'; + 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 _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; + +-- 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_object' 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 ' + || array_to_string($4, ', ') || ' 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, $5) || E'\n' || diag( + ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' + ); + ELSIF grants[1] = 'undefined_object' THEN + RETURN ok(FALSE, $5) || 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 ' + || array_to_string($3, ', ') || ' on table ' || quote_ident($1) + ); +$$ LANGUAGE SQL; diff --git a/test/expected/privs.out b/test/expected/privs.out new file mode 100644 index 000000000000..f5db7655cb5d --- /dev/null +++ b/test/expected/privs.out @@ -0,0 +1,31 @@ +\unset ECHO +ok 1 - table_privs_are(sch, tab, role, privs, desc) should pass +ok 2 - table_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 3 - table_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 4 - table_privs_are(sch, tab, role, privs) should pass +ok 5 - table_privs_are(sch, tab, role, privs) should have the proper description +ok 6 - table_privs_are(sch, tab, role, privs) should have the proper diagnostics +ok 7 - table_privs_are(tab, role, privs, desc) should pass +ok 8 - table_privs_are(tab, role, privs, desc) should have the proper description +ok 9 - table_privs_are(tab, role, privs, desc) should have the proper diagnostics +ok 10 - table_privs_are(tab, role, privs) should pass +ok 11 - table_privs_are(tab, role, privs) should have the proper description +ok 12 - table_privs_are(tab, role, privs) should have the proper diagnostics +ok 13 - table_privs_are(sch, tab, role, some privs, desc) should fail +ok 14 - table_privs_are(sch, tab, role, some privs, desc) should have the proper description +ok 15 - table_privs_are(sch, tab, role, some privs, desc) should have the proper diagnostics +ok 16 - table_privs_are(tab, role, some privs, desc) should fail +ok 17 - table_privs_are(tab, role, some privs, desc) should have the proper description +ok 18 - table_privs_are(tab, role, some privs, desc) should have the proper diagnostics +ok 19 - table_privs_are(sch, tab, other, privs, desc) should fail +ok 20 - table_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 21 - table_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics +ok 22 - table_privs_are(sch, tab, other, privs, desc) should pass +ok 23 - table_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 24 - table_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics +ok 25 - table_privs_are(sch, tab, role, privs, desc) should fail +ok 26 - table_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 27 - table_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 28 - table_privs_are(sch, tab, role, privs, desc) should fail +ok 29 - table_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 30 - table_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics diff --git a/test/sql/privs.sql b/test/sql/privs.sql new file mode 100644 index 000000000000..98149980e04a --- /dev/null +++ b/test/sql/privs.sql @@ -0,0 +1,136 @@ +\unset ECHO +\i test/setup.sql + +--SELECT plan(24); +SELECT * FROM no_plan(); + +SET client_min_messages = warning; +CREATE SCHEMA ha; +CREATE TABLE ha.sometab(id INT); +SET search_path = ha,public,pg_catalog; +RESET client_min_messages; + +/****************************************************************************/ +-- Test table_privilege_is(). + +SELECT * FROM check_test( + table_privs_are( 'ha', 'sometab', current_user, _table_privs(), 'whatever' ), + true, + 'table_privs_are(sch, tab, role, privs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + table_privs_are( 'ha', 'sometab', current_user, _table_privs() ), + true, + 'table_privs_are(sch, tab, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(_table_privs(), ', ') || ' on table ha.sometab' , + '' +); + +SELECT * FROM check_test( + table_privs_are( 'sometab', current_user, _table_privs(), 'whatever' ), + true, + 'table_privs_are(tab, role, privs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + table_privs_are( 'sometab', current_user, _table_privs() ), + true, + 'table_privs_are(tab, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(_table_privs(), ', ') || ' on table sometab' , + '' +); + +CREATE OR REPLACE FUNCTION run_extra_fails() RETURNS SETOF TEXT LANGUAGE plpgsql AS $$ +DECLARE + allowed_privs TEXT[]; + test_privs TEXT[]; + missing_privs TEXT[]; + tap record; + last_index INTEGER; +BEGIN + -- Test table failure. + allowed_privs := _table_privs(); + last_index := array_upper(allowed_privs, 1); + FOR i IN 1..last_index - 2 LOOP + test_privs := test_privs || allowed_privs[i]; + END LOOP; + FOR i IN last_index - 1..last_index LOOP + missing_privs := missing_privs || allowed_privs[i]; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + table_privs_are( 'ha', 'sometab', current_user, test_privs, 'whatever' ), + false, + 'table_privs_are(sch, tab, role, some privs, desc)', + 'whatever', + ' Extra privileges: + ' || array_to_string(missing_privs, E'\n ') + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + table_privs_are( 'sometab', current_user, test_privs, 'whatever' ), + false, + 'table_privs_are(tab, role, some privs, desc)', + 'whatever', + ' Extra privileges: + ' || array_to_string(missing_privs, E'\n ') + ) AS b LOOP RETURN NEXT tap.b; END LOOP; +END; +$$; + +SELECT * FROM run_extra_fails(); + +-- Create another role. +CREATE USER __someone_else; + +SELECT * FROM check_test( + table_privs_are( 'ha', 'sometab', '__someone_else', _table_privs(), 'whatever' ), + false, + 'table_privs_are(sch, tab, other, privs, desc)', + 'whatever', + ' Missing privileges: + ' || array_to_string(_table_privs(), E'\n ') +); + +-- Grant them some permission. +GRANT SELECT, INSERT, UPDATE, DELETE ON ha.sometab TO __someone_else; + +SELECT * FROM check_test( + table_privs_are( 'ha', 'sometab', '__someone_else', ARRAY[ + 'SELECT', 'INSERT', 'UPDATE', 'DELETE' + ], 'whatever'), + true, + 'table_privs_are(sch, tab, other, privs, desc)', + 'whatever', + '' +); + +-- Try a non-existent table. +SELECT * FROM check_test( + table_privs_are( 'ha', 'nonesuch', current_user, _table_privs(), 'whatever' ), + false, + 'table_privs_are(sch, tab, role, privs, desc)', + 'whatever', + ' Table ha.nonesuch does not exist' +); + +-- Try a non-existent user. +SELECT * FROM check_test( + table_privs_are( 'ha', 'sometab', '__nonesuch', _table_privs(), 'whatever' ), + false, + 'table_privs_are(sch, tab, role, privs, desc)', + 'whatever', + ' Role __nonesuch does not exist' +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From 8c0f54bf562a97b0fa04cf82f4a6b045df329ff5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Jan 2013 18:22:52 -0800 Subject: [PATCH 0702/1195] Point to the docs for the list of privs. --- doc/pgtap.mmd | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 6c460413ca3b..f059b71bfcd5 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5554,8 +5554,11 @@ database objects? Let's find out! `:description` : A short description of the test. -Tests the privileges granted to a user to access a table. If the -`:description` argument is omitted, an appropriate description will be +Tests the privileges granted to a user to access a table. Consult [the +documentation](http://www.postgresql.org/docs/current/static/functions-info.html#FUNCTIONS-INFO-ACCESS-TABLE) +for the list of table privileges available in your version of PostgreSQL. + +If the `:description` argument is omitted, an appropriate description will be created. Examples: SELECT table_privs_are( From 02410774e2259ab405d0f1321d4457211faf43b7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 11 Jan 2013 16:32:22 -0800 Subject: [PATCH 0703/1195] Add `db_privs_are()`. Also use the term "role" instead of "user" in the docs. Issue #34. --- Changes | 4 +- doc/pgtap.mmd | 86 +++++++++++++++++++++++++++++------ sql/pgtap--0.91.0--0.92.0.sql | 68 ++++++++++++++++++++++++++- sql/pgtap.sql.in | 67 +++++++++++++++++++++++++++ test/expected/privs.out | 19 ++++++++ test/sql/privs.sql | 66 ++++++++++++++++++++++++++- 6 files changed, 292 insertions(+), 18 deletions(-) diff --git a/Changes b/Changes index 6d43349764fe..6a8983617496 100644 --- a/Changes +++ b/Changes @@ -31,7 +31,9 @@ Revision history for pgTAP * Added `isnt_empty()` to ensure that a query does not return the empty set. * Fixed `has_index()` to properly handle indexes composed of both columns and expressions. -* Added `table_privs_are()` for testing a user's access to a table. +* Added new functions for testing a user's access to database objects: + + `db_privs_are()` + + `table_privs_are()` 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index f059b71bfcd5..61b127f028e6 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5529,13 +5529,72 @@ Prvileged Access So we know who owns the objects. But what about other roles? Can they access database objects? Let's find out! -### `table_privs_are()` +### `db_privs_are()` + + SELECT db_privs_are ( :db, :role, :privileges, :description ); + SELECT db_privs_are ( :db, :role, :privileges ); + +**Parameters** + +`:db` +: Name of a database. + +`:role` +: Name of a user or group role. + +`:privileges` +: An array of table privileges the role should be granted to the database. +`:description` +: A short description of the test. + +Tests the privileges granted to a role to access a database. Consult [the +documentation](http://www.postgresql.org/docs/current/static/functions-info.html#FUNCTIONS-INFO-ACCESS-DATABASE) +for the list of database privileges available in your version of PostgreSQL. + +If the `:description` argument is omitted, an appropriate description will be +created. Examples: - SELECT table_privs_are ( :schema, :table, :user, :privileges, :description ); - SELECT table_privs_are ( :schema, :table, :user, :privileges ); - SELECT table_privs_are ( :table, :user, :privileges, :description ); - SELECT table_privs_are ( :table, :user, :privileges ); + SELECT db_privs_are( + 'flipr', 'fred', ARRAY['CONNECT', 'TEMPORARY'], + 'Fred should be granted CONNECT and TERMPORARY on db "flipr"' + ); + SELECT db_privs_are( 'dept_corrections', ARRAY['CREATE'] ); + +If the role is granted permissions other than those specified, the diagnostics +will list the extra permissions, like so: + + # Failed test 14: "Role bob should be granted CREATE on banks" + # Extra privileges: + # CONNECT + # TEMPORARY + +Likewise if the role is not granted some of the specified permissions on the +database: + + # Failed test 15: "Role kurk should be granted CONNECT, TEMPORARY on banks" + # Missing privileges: + # CREATE + +In the event that the test fails because the database in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Role slim should be granted CONNECT on maindb" + # Database maindb does not exist + +If the test fails because the role does not exist, the diagnostics will look +something like: + + # Failed test 17: "Role slim should be granted CONNECT, CREATE on widgets" + # Role slim does not exist + +### `table_privs_are()` + + SELECT table_privs_are ( :schema, :table, :role, :privileges, :description ); + SELECT table_privs_are ( :schema, :table, :role, :privileges ); + SELECT table_privs_are ( :table, :role, :privileges, :description ); + SELECT table_privs_are ( :table, :role, :privileges ); **Parameters** @@ -5545,16 +5604,16 @@ database objects? Let's find out! `:table` : Name of a table. -`:user` -: Name of a user. +`:role` +: Name of a user or group role. `:privileges` -: An array of table privileges the user should be granted to the table. +: An array of table privileges the role should be granted to the table. `:description` : A short description of the test. -Tests the privileges granted to a user to access a table. Consult [the +Tests the privileges granted to a role to access a table. Consult [the documentation](http://www.postgresql.org/docs/current/static/functions-info.html#FUNCTIONS-INFO-ACCESS-TABLE) for the list of table privileges available in your version of PostgreSQL. @@ -5567,7 +5626,7 @@ created. Examples: ); SELECT table_privs_are( 'widgets', 'slim', ARRAY['INSERT', 'UPDATE'] ); -If the user is granted permissions other than those specified, the diagnostics +If the role is granted permissions other than those specified, the diagnostics will list the extra permissions, like so: # Failed test 14: "Role bob should be granted SELECT on widgets" @@ -5576,7 +5635,7 @@ will list the extra permissions, like so: # INSERT # UPDATE -Likewise if the user is not granted some of the specified permissions on the +Likewise if the role is not granted some of the specified permissions on the table: # Failed test 15: "Role kurk should be granted SELECT, INSERT, UPDATE on widgets" @@ -5591,12 +5650,11 @@ as: # Failed test 16: "Role slim should be granted SELECT on widgets" # Table widgets does not exist -If the test fails because the userd does not exist, the diagnostics will look +If the test fails because the role does not exist, the diagnostics will look something like: # Failed test 17: "Role slim should be granted SELECT on widgets" - # Role slip does not exist - + # Role slim does not exist No Test for the Wicked ====================== diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index 619749b19d71..0308adf65ea2 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -870,7 +870,6 @@ BEGIN END; $$ language plpgsql; - CREATE OR REPLACE FUNCTION _get_table_privs(NAME, TEXT) RETURNS TEXT[] AS $$ DECLARE @@ -978,3 +977,70 @@ RETURNS TEXT AS $$ || array_to_string($3, ', ') || ' 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_object}'; + WHEN invalid_parameter_value THEN + -- Not a valid permission on this version of PostgreSQL; ignore; + END; + END LOOP; + RETURN grants; +END; +$$ LANGUAGE plpgsql; + +-- db_privs_are ( db, user, privileges[], description ) +CREATE OR REPLACE FUNCTION db_privs_are ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + grants TEXT[] := _get_db_privs( $2, quote_ident($1) ); +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_object' 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; + +-- db_privs_are ( db, user, privileges[] ) +CREATE OR REPLACE FUNCTION db_privs_are ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT db_privs_are( + $1, $2, $3, + 'Role ' || quote_ident($2) || ' should be granted ' + || array_to_string($3, ', ') || ' on database ' || quote_ident($1) + ); +$$ LANGUAGE SQL; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 95fce0f45777..7f4b1d7c3fbe 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -8035,3 +8035,70 @@ RETURNS TEXT AS $$ || array_to_string($3, ', ') || ' 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_object}'; + WHEN invalid_parameter_value THEN + -- Not a valid permission on this version of PostgreSQL; ignore; + END; + END LOOP; + RETURN grants; +END; +$$ LANGUAGE plpgsql; + +-- db_privs_are ( db, user, privileges[], description ) +CREATE OR REPLACE FUNCTION db_privs_are ( NAME, NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ +DECLARE + grants TEXT[] := _get_db_privs( $2, quote_ident($1) ); +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_object' 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; + +-- db_privs_are ( db, user, privileges[] ) +CREATE OR REPLACE FUNCTION db_privs_are ( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT db_privs_are( + $1, $2, $3, + 'Role ' || quote_ident($2) || ' should be granted ' + || array_to_string($3, ', ') || ' on database ' || quote_ident($1) + ); +$$ LANGUAGE SQL; diff --git a/test/expected/privs.out b/test/expected/privs.out index f5db7655cb5d..f30c25dc98bf 100644 --- a/test/expected/privs.out +++ b/test/expected/privs.out @@ -1,4 +1,5 @@ \unset ECHO +1..48 ok 1 - table_privs_are(sch, tab, role, privs, desc) should pass ok 2 - table_privs_are(sch, tab, role, privs, desc) should have the proper description ok 3 - table_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics @@ -29,3 +30,21 @@ ok 27 - table_privs_are(sch, tab, role, privs, desc) should have the proper diag ok 28 - table_privs_are(sch, tab, role, privs, desc) should fail ok 29 - table_privs_are(sch, tab, role, privs, desc) should have the proper description ok 30 - table_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 31 - db_privs_are(db, role, privs, desc) should pass +ok 32 - db_privs_are(db, role, privs, desc) should have the proper description +ok 33 - db_privs_are(db, role, privs, desc) should have the proper diagnostics +ok 34 - db_privs_are(db, role, privs, desc) should pass +ok 35 - db_privs_are(db, role, privs, desc) should have the proper description +ok 36 - db_privs_are(db, role, privs, desc) should have the proper diagnostics +ok 37 - db_privs_are(non-db, role, privs, desc) should fail +ok 38 - db_privs_are(non-db, role, privs, desc) should have the proper description +ok 39 - db_privs_are(non-db, role, privs, desc) should have the proper diagnostics +ok 40 - db_privs_are(db, non-role, privs, desc) should fail +ok 41 - db_privs_are(db, non-role, privs, desc) should have the proper description +ok 42 - db_privs_are(db, non-role, privs, desc) should have the proper diagnostics +ok 43 - db_privs_are(db, ungranted, privs, desc) should fail +ok 44 - db_privs_are(db, ungranted, privs, desc) should have the proper description +ok 45 - db_privs_are(db, ungranted, privs, desc) should have the proper diagnostics +ok 46 - db_privs_are(db, ungranted, privs, desc) should fail +ok 47 - db_privs_are(db, ungranted, privs, desc) should have the proper description +ok 48 - db_privs_are(db, ungranted, privs, desc) should have the proper diagnostics diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 98149980e04a..ca6611fbe049 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -1,8 +1,8 @@ \unset ECHO \i test/setup.sql ---SELECT plan(24); -SELECT * FROM no_plan(); +SELECT plan(48); +--SELECT * FROM no_plan(); SET client_min_messages = warning; CREATE SCHEMA ha; @@ -130,6 +130,68 @@ SELECT * FROM check_test( ' Role __nonesuch does not exist' ); +/****************************************************************************/ +-- Test db_privilege_is(). + +SELECT * FROM check_test( + db_privs_are( current_database(), current_user, _db_privs(), 'whatever' ), + true, + 'db_privs_are(db, role, privs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + db_privs_are( current_database(), current_user, _db_privs() ), + true, + 'db_privs_are(db, role, privs, desc)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(_db_privs(), ', ') || ' on database ' || current_database(), + '' +); + +-- Try nonexistent database. +SELECT * FROM check_test( + db_privs_are( '__nonesuch', current_user, _db_privs(), 'whatever' ), + false, + 'db_privs_are(non-db, role, privs, desc)', + 'whatever', + ' Database __nonesuch does not exist' +); + +-- Try nonexistent user. +SELECT * FROM check_test( + db_privs_are( current_database(), '__noone', _db_privs(), 'whatever' ), + false, + 'db_privs_are(db, non-role, privs, desc)', + 'whatever', + ' Role __noone does not exist' +); + +-- Try another user. +SELECT * FROM check_test( + db_privs_are( current_database(), '__someone_else', _db_privs(), 'whatever' ), + false, + 'db_privs_are(db, ungranted, privs, desc)', + 'whatever', + ' Missing privileges: + CREATE' +); + +-- Try a subset of privs. +SELECT * FROM check_test( + db_privs_are( + current_database(), current_user, + CASE WHEN pg_version_num() < 80200 THEN ARRAY['CREATE'] ELSE ARRAY['CREATE', 'CONNECT'] END, + 'whatever' + ), + false, + 'db_privs_are(db, ungranted, privs, desc)', + 'whatever', + ' Extra privileges: + TEMPORARY' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 7dcfd981ea1c70d3d7c963898c7b09e7059e1031 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 11 Jan 2013 17:24:57 -0800 Subject: [PATCH 0704/1195] Add `function_privs_are()`. Need to modify the others to support an empty list of privs in the default descriptions. --- Changes | 1 + doc/pgtap.mmd | 67 +++++++++++ sql/pgtap--0.91.0--0.92.0.sql | 75 +++++++++++++ sql/pgtap.sql.in | 2 +- test/expected/privs.out | 48 +++++++- test/sql/privs.sql | 206 +++++++++++++++++++++++++++++++++- 6 files changed, 395 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 6a8983617496..cf4667bde99b 100644 --- a/Changes +++ b/Changes @@ -34,6 +34,7 @@ Revision history for pgTAP * Added new functions for testing a user's access to database objects: + `db_privs_are()` + `table_privs_are()` + + `function_privs_are()` 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 61b127f028e6..e00c3a0be98b 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5656,6 +5656,73 @@ something like: # Failed test 17: "Role slim should be granted SELECT on widgets" # Role slim does not exist +### `function_privs_are()` + + SELECT function_privs_are ( :schema, :function, :args, :role, :privileges, :description ); + SELECT function_privs_are ( :schema, :function, :args, :role, :privileges ); + SELECT function_privs_are ( :function, :args, :role, :privileges, :description ); + SELECT function_privs_are ( :function, :args, :role, :privileges ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the function. + +`:function` +: Name of a function. + +`:args` +: Array of function arguments. + +`:role` +: Name of a user or group role. + +`:privileges` +: An array of function privileges the role should be granted to the function. + +`:description` +: A short description of the test. + +Tests the privileges granted to a role to access a function. Consult [the +documentation](http://www.postgresql.org/docs/current/static/functions-info.html#FUNCTIONS-INFO-ACCESS-FUNCTION) +for the list of function privileges available in your version of PostgreSQL. + +If the `:description` argument is omitted, an appropriate description will be +created. Examples: + + SELECT function_privs_are( + 'public', 'frobulate', ARRAY['integer'], 'fred', ARRAY['EXECUTE'], + 'Fred should be able to execute frobulate(int)' + ); + SELECT function_privs_are( 'bake', '{}', 'slim', '{}'); + +If the role is granted permissions other than those specified, the diagnostics +will list the extra permissions, like so: + + # Failed test 14: "Role bob should be granted no privileges on foo()" + # Extra privileges: + # EXECUTE + +Likewise if the role is not granted some of the specified permissions on the +function: + + # Failed test 15: "Role kurk should be granted EXECUTE foo()" + # Missing privileges: + # EXECUTE + +In the event that the test fails because the function in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Role slim should be granted EXECUTE on foo(int)" + # Function foo(int) does not exist + +If the test fails because the role does not exist, the diagnostics will look +something like: + + # Failed test 17: "Role slim should be granted EXECUTE on foo()" + # Role slim does not exist + No Test for the Wicked ====================== diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index 0308adf65ea2..43c1fd233cb0 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -1044,3 +1044,78 @@ RETURNS TEXT AS $$ || array_to_string($3, ', ') || ' on database ' || quote_ident($1) ); $$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_func_privs(NAME, 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_object}'; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _fprivs_are ( NAME, 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_object' 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 7f4b1d7c3fbe..3de6f8ae69d5 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7927,7 +7927,6 @@ BEGIN END; $$ language plpgsql; - CREATE OR REPLACE FUNCTION _get_table_privs(NAME, TEXT) RETURNS TEXT[] AS $$ DECLARE @@ -8102,3 +8101,4 @@ RETURNS TEXT AS $$ || array_to_string($3, ', ') || ' on database ' || quote_ident($1) ); $$ LANGUAGE SQL; + diff --git a/test/expected/privs.out b/test/expected/privs.out index f30c25dc98bf..e72f4086f479 100644 --- a/test/expected/privs.out +++ b/test/expected/privs.out @@ -1,5 +1,4 @@ \unset ECHO -1..48 ok 1 - table_privs_are(sch, tab, role, privs, desc) should pass ok 2 - table_privs_are(sch, tab, role, privs, desc) should have the proper description ok 3 - table_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics @@ -48,3 +47,50 @@ ok 45 - db_privs_are(db, ungranted, privs, desc) should have the proper diagnost ok 46 - db_privs_are(db, ungranted, privs, desc) should fail ok 47 - db_privs_are(db, ungranted, privs, desc) should have the proper description ok 48 - db_privs_are(db, ungranted, privs, desc) should have the proper diagnostics +ok 49 - function_privs_are(sch, func, args, role, privs, desc) should pass +ok 50 - function_privs_are(sch, func, args, role, privs, desc) should have the proper description +ok 51 - function_privs_are(sch, func, args, role, privs, desc) should have the proper diagnostics +ok 52 - Role david should be granted EXECUTE on function public.foo(integer, text) should pass +ok 53 - function_privs_are(func, args, role, privs, desc) should pass +ok 54 - function_privs_are(func, args, role, privs, desc) should have the proper description +ok 55 - function_privs_are(func, args, role, privs, desc) should have the proper diagnostics +ok 56 - function_privs_are(func, args, role, privs) should pass +ok 57 - function_privs_are(func, args, role, privs) should have the proper description +ok 58 - function_privs_are(sch, non-func, args, role, privs, desc) should fail +ok 59 - function_privs_are(sch, non-func, args, role, privs, desc) should have the proper description +ok 60 - function_privs_are(sch, non-func, args, role, privs, desc) should have the proper diagnostics +ok 61 - function_privs_are(non-func, args, role, privs, desc) should fail +ok 62 - function_privs_are(non-func, args, role, privs, desc) should have the proper description +ok 63 - function_privs_are(non-func, args, role, privs, desc) should have the proper diagnostics +ok 64 - function_privs_are(sch, func, args, noone, privs, desc) should fail +ok 65 - function_privs_are(sch, func, args, noone, privs, desc) should have the proper description +ok 66 - function_privs_are(sch, func, args, noone, privs, desc) should have the proper diagnostics +ok 67 - function_privs_are(func, args, noone, privs, desc) should fail +ok 68 - function_privs_are(func, args, noone, privs, desc) should have the proper description +ok 69 - function_privs_are(func, args, noone, privs, desc) should have the proper diagnostics +ok 70 - function_privs_are(sch, func, args, other, privs, desc) should pass +ok 71 - function_privs_are(sch, func, args, other, privs, desc) should have the proper description +ok 72 - function_privs_are(sch, func, args, other, privs, desc) should have the proper diagnostics +ok 73 - function_privs_are(func, args, other, privs, desc) should pass +ok 74 - function_privs_are(func, args, other, privs, desc) should have the proper description +ok 75 - function_privs_are(func, args, other, privs, desc) should have the proper diagnostics +ok 76 - function_privs_are(sch, func, args, unpriv, privs, desc) should fail +ok 77 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper description +ok 78 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper diagnostics +ok 79 - function_privs_are(func, args, unpriv, privs, desc) should fail +ok 80 - function_privs_are(func, args, unpriv, privs, desc) should have the proper description +ok 81 - function_privs_are(func, args, unpriv, privs, desc) should have the proper diagnostics +ok 82 - function_privs_are(sch, func, args, unpriv, empty, desc) should pass +ok 83 - function_privs_are(sch, func, args, unpriv, empty, desc) should have the proper description +ok 84 - function_privs_are(sch, func, args, unpriv, empty, desc) should have the proper diagnostics +ok 85 - function_privs_are(sch, func, args, unpriv, empty) should pass +ok 86 - function_privs_are(sch, func, args, unpriv, empty) should have the proper description +ok 87 - function_privs_are(func, args, unpriv, empty) should pass +ok 88 - function_privs_are(func, args, unpriv, empty) should have the proper description +ok 89 - function_privs_are(sch, func, args, unpriv, privs, desc) should fail +ok 90 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper description +ok 91 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper diagnostics +ok 92 - function_privs_are(func, args, unpriv, privs, desc) should fail +ok 93 - function_privs_are(func, args, unpriv, privs, desc) should have the proper description +ok 94 - function_privs_are(func, args, unpriv, privs, desc) should have the proper diagnostics +1..94 diff --git a/test/sql/privs.sql b/test/sql/privs.sql index ca6611fbe049..428507659df8 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -1,8 +1,8 @@ \unset ECHO \i test/setup.sql -SELECT plan(48); ---SELECT * FROM no_plan(); +--SELECT plan(48); +SELECT * FROM no_plan(); SET client_min_messages = warning; CREATE SCHEMA ha; @@ -192,6 +192,208 @@ SELECT * FROM check_test( TEMPORARY' ); +/****************************************************************************/ +-- Test function_privilege_is(). +CREATE OR REPLACE FUNCTION public.foo(int, text) RETURNS VOID LANGUAGE SQL AS ''; + +SELECT * FROM check_test( + function_privs_are( + 'public', 'foo', ARRAY['integer', 'text'], + current_user, ARRAY['EXECUTE'], 'whatever' + ), + true, + 'function_privs_are(sch, func, args, role, privs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_privs_are( + 'public', 'foo', ARRAY['integer', 'text'], + current_user, ARRAY['EXECUTE'] + ), + true, + 'Role ' || current_user || ' should be granted EXECUTE on function public.foo(integer, text)' + '' +); + +SELECT * FROM check_test( + function_privs_are( + 'foo', ARRAY['integer', 'text'], + current_user, ARRAY['EXECUTE'], 'whatever' + ), + true, + 'function_privs_are(func, args, role, privs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_privs_are( + 'foo', ARRAY['integer', 'text'], + current_user, ARRAY['EXECUTE'] + ), + true, + 'function_privs_are(func, args, role, privs)', + 'Role ' || current_user || ' should be granted EXECUTE on function foo(integer, text)' + '' +); + +-- Try a nonexistent funtion. +SELECT * FROM check_test( + function_privs_are( + 'public', '__nonesuch', ARRAY['integer', 'text'], + current_user, ARRAY['EXECUTE'], 'whatever' + ), + false, + 'function_privs_are(sch, non-func, args, role, privs, desc)', + 'whatever', + ' Function public.__nonesuch(integer, text) does not exist' +); + +SELECT * FROM check_test( + function_privs_are( + '__nonesuch', ARRAY['integer', 'text'], + current_user, ARRAY['EXECUTE'], 'whatever' + ), + false, + 'function_privs_are(non-func, args, role, privs, desc)', + 'whatever', + ' Function __nonesuch(integer, text) does not exist' +); + +-- Try a nonexistent user. +SELECT * FROM check_test( + function_privs_are( + 'public', 'foo', ARRAY['integer', 'text'], + '__noone', ARRAY['EXECUTE'], 'whatever' + ), + false, + 'function_privs_are(sch, func, args, noone, privs, desc)', + 'whatever', + ' Role __noone does not exist' +); + +SELECT * FROM check_test( + function_privs_are( + 'foo', ARRAY['integer', 'text'], + '__noone', ARRAY['EXECUTE'], 'whatever' + ), + false, + 'function_privs_are(func, args, noone, privs, desc)', + 'whatever', + ' Role __noone does not exist' +); + +-- Try an unprivileged user. +SELECT * FROM check_test( + function_privs_are( + 'public', 'foo', ARRAY['integer', 'text'], + '__someone_else', ARRAY['EXECUTE'], 'whatever' + ), + true, + 'function_privs_are(sch, func, args, other, privs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_privs_are( + 'foo', ARRAY['integer', 'text'], + '__someone_else', ARRAY['EXECUTE'], 'whatever' + ), + true, + 'function_privs_are(func, args, other, privs, desc)', + 'whatever', + '' +); + +REVOKE EXECUTE ON FUNCTION public.foo(int, text) FROM PUBLIC; + +-- Now fail. +SELECT * FROM check_test( + function_privs_are( + 'public', 'foo', ARRAY['integer', 'text'], + '__someone_else', ARRAY['EXECUTE'], 'whatever' + ), + false, + 'function_privs_are(sch, func, args, unpriv, privs, desc)', + 'whatever', + ' Missing privileges: + EXECUTE' +); + +SELECT * FROM check_test( + function_privs_are( + 'foo', ARRAY['integer', 'text'], + '__someone_else', ARRAY['EXECUTE'], 'whatever' + ), + false, + 'function_privs_are(func, args, unpriv, privs, desc)', + 'whatever', + ' Missing privileges: + EXECUTE' +); + +-- It should work for an empty array. +SELECT * FROM check_test( + function_privs_are( + 'public', 'foo', ARRAY['integer', 'text'], + '__someone_else', ARRAY[]::text[], 'whatever' + ), + true, + 'function_privs_are(sch, func, args, unpriv, empty, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_privs_are( + 'public', 'foo', ARRAY['integer', 'text'], + '__someone_else', ARRAY[]::text[] + ), + true, + 'function_privs_are(sch, func, args, unpriv, empty)', + 'Role __someone_else should be granted no privileges on function public.foo(integer, text)' + '' +); + +SELECT * FROM check_test( + function_privs_are( + 'foo', ARRAY['integer', 'text'], + '__someone_else', ARRAY[]::text[] + ), + true, + 'function_privs_are(func, args, unpriv, empty)', + 'Role __someone_else should be granted no privileges on function foo(integer, text)' + '' +); + +-- Empty for the current user should yeild an extra permission. +SELECT * FROM check_test( + function_privs_are( + 'public', 'foo', ARRAY['integer', 'text'], + current_user, ARRAY[]::text[], 'whatever' + ), + false, + 'function_privs_are(sch, func, args, unpriv, privs, desc)', + 'whatever', + ' Extra privileges: + EXECUTE' +); + +SELECT * FROM check_test( + function_privs_are( + 'foo', ARRAY['integer', 'text'], + current_user, ARRAY[]::text[], 'whatever' + ), + false, + 'function_privs_are(func, args, unpriv, privs, desc)', + 'whatever', + ' Extra privileges: + EXECUTE' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 4c3c4d57e4add4f483c0cc78d5177a0566ae35f3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Jan 2013 11:17:55 -0800 Subject: [PATCH 0705/1195] Add missing function priv functions. Not sure how I omitted them last week, but they were in the upgrade script, so easy enough to copoy over. --- sql/pgtap.sql.in | 74 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 3de6f8ae69d5..e67ae72ce24c 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -8102,3 +8102,77 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; +CREATE OR REPLACE FUNCTION _get_func_privs(NAME, 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_object}'; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _fprivs_are ( NAME, 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_object' 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; From efae364edfa30735dc863bea05bcd8129585d77f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Jan 2013 11:32:07 -0800 Subject: [PATCH 0706/1195] Handle "no privileges" in table and db functions. Also fix the description creation in the table priv testing function. --- sql/pgtap--0.91.0--0.92.0.sql | 14 ++-- sql/pgtap.sql.in | 14 ++-- test/expected/privs.out | 139 ++++++++++++++++++---------------- test/sql/privs.sql | 32 +++++++- 4 files changed, 120 insertions(+), 79 deletions(-) diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index 43c1fd233cb0..c337d7b59082 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -944,8 +944,8 @@ RETURNS TEXT AS $$ SELECT table_privs_are( $1, $2, $3, $4, 'Role ' || quote_ident($3) || ' should be granted ' - || array_to_string($4, ', ') || ' on table ' - || quote_ident($1) || '.' || quote_ident($2) + || 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; @@ -956,11 +956,11 @@ DECLARE grants TEXT[] := _get_table_privs( $2, quote_ident($1) ); BEGIN IF grants[1] = 'undefined_table' THEN - RETURN ok(FALSE, $5) || E'\n' || diag( + RETURN ok(FALSE, $4) || E'\n' || diag( ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' ); ELSIF grants[1] = 'undefined_object' THEN - RETURN ok(FALSE, $5) || E'\n' || diag( + RETURN ok(FALSE, $4) || E'\n' || diag( ' Role ' || quote_ident($2) || ' does not exist' ); END IF; @@ -974,7 +974,8 @@ RETURNS TEXT AS $$ SELECT table_privs_are( $1, $2, $3, 'Role ' || quote_ident($2) || ' should be granted ' - || array_to_string($3, ', ') || ' on table ' || quote_ident($1) + || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END + || ' on table ' || quote_ident($1) ); $$ LANGUAGE SQL; @@ -1041,7 +1042,8 @@ RETURNS TEXT AS $$ SELECT db_privs_are( $1, $2, $3, 'Role ' || quote_ident($2) || ' should be granted ' - || array_to_string($3, ', ') || ' on database ' || quote_ident($1) + || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END + || ' on database ' || quote_ident($1) ); $$ LANGUAGE SQL; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index e67ae72ce24c..ed20a31406ca 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -8001,8 +8001,8 @@ RETURNS TEXT AS $$ SELECT table_privs_are( $1, $2, $3, $4, 'Role ' || quote_ident($3) || ' should be granted ' - || array_to_string($4, ', ') || ' on table ' - || quote_ident($1) || '.' || quote_ident($2) + || 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; @@ -8013,11 +8013,11 @@ DECLARE grants TEXT[] := _get_table_privs( $2, quote_ident($1) ); BEGIN IF grants[1] = 'undefined_table' THEN - RETURN ok(FALSE, $5) || E'\n' || diag( + RETURN ok(FALSE, $4) || E'\n' || diag( ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' ); ELSIF grants[1] = 'undefined_object' THEN - RETURN ok(FALSE, $5) || E'\n' || diag( + RETURN ok(FALSE, $4) || E'\n' || diag( ' Role ' || quote_ident($2) || ' does not exist' ); END IF; @@ -8031,7 +8031,8 @@ RETURNS TEXT AS $$ SELECT table_privs_are( $1, $2, $3, 'Role ' || quote_ident($2) || ' should be granted ' - || array_to_string($3, ', ') || ' on table ' || quote_ident($1) + || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END + || ' on table ' || quote_ident($1) ); $$ LANGUAGE SQL; @@ -8098,7 +8099,8 @@ RETURNS TEXT AS $$ SELECT db_privs_are( $1, $2, $3, 'Role ' || quote_ident($2) || ' should be granted ' - || array_to_string($3, ', ') || ' on database ' || quote_ident($1) + || CASE WHEN $3[1] IS NULL THEN 'no privileges' ELSE array_to_string($3, ', ') END + || ' on database ' || quote_ident($1) ); $$ LANGUAGE SQL; diff --git a/test/expected/privs.out b/test/expected/privs.out index e72f4086f479..e70188da149b 100644 --- a/test/expected/privs.out +++ b/test/expected/privs.out @@ -1,4 +1,5 @@ \unset ECHO +1..103 ok 1 - table_privs_are(sch, tab, role, privs, desc) should pass ok 2 - table_privs_are(sch, tab, role, privs, desc) should have the proper description ok 3 - table_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics @@ -29,68 +30,76 @@ ok 27 - table_privs_are(sch, tab, role, privs, desc) should have the proper diag ok 28 - table_privs_are(sch, tab, role, privs, desc) should fail ok 29 - table_privs_are(sch, tab, role, privs, desc) should have the proper description ok 30 - table_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 31 - db_privs_are(db, role, privs, desc) should pass -ok 32 - db_privs_are(db, role, privs, desc) should have the proper description -ok 33 - db_privs_are(db, role, privs, desc) should have the proper diagnostics -ok 34 - db_privs_are(db, role, privs, desc) should pass -ok 35 - db_privs_are(db, role, privs, desc) should have the proper description -ok 36 - db_privs_are(db, role, privs, desc) should have the proper diagnostics -ok 37 - db_privs_are(non-db, role, privs, desc) should fail -ok 38 - db_privs_are(non-db, role, privs, desc) should have the proper description -ok 39 - db_privs_are(non-db, role, privs, desc) should have the proper diagnostics -ok 40 - db_privs_are(db, non-role, privs, desc) should fail -ok 41 - db_privs_are(db, non-role, privs, desc) should have the proper description -ok 42 - db_privs_are(db, non-role, privs, desc) should have the proper diagnostics -ok 43 - db_privs_are(db, ungranted, privs, desc) should fail -ok 44 - db_privs_are(db, ungranted, privs, desc) should have the proper description -ok 45 - db_privs_are(db, ungranted, privs, desc) should have the proper diagnostics -ok 46 - db_privs_are(db, ungranted, privs, desc) should fail -ok 47 - db_privs_are(db, ungranted, privs, desc) should have the proper description -ok 48 - db_privs_are(db, ungranted, privs, desc) should have the proper diagnostics -ok 49 - function_privs_are(sch, func, args, role, privs, desc) should pass -ok 50 - function_privs_are(sch, func, args, role, privs, desc) should have the proper description -ok 51 - function_privs_are(sch, func, args, role, privs, desc) should have the proper diagnostics -ok 52 - Role david should be granted EXECUTE on function public.foo(integer, text) should pass -ok 53 - function_privs_are(func, args, role, privs, desc) should pass -ok 54 - function_privs_are(func, args, role, privs, desc) should have the proper description -ok 55 - function_privs_are(func, args, role, privs, desc) should have the proper diagnostics -ok 56 - function_privs_are(func, args, role, privs) should pass -ok 57 - function_privs_are(func, args, role, privs) should have the proper description -ok 58 - function_privs_are(sch, non-func, args, role, privs, desc) should fail -ok 59 - function_privs_are(sch, non-func, args, role, privs, desc) should have the proper description -ok 60 - function_privs_are(sch, non-func, args, role, privs, desc) should have the proper diagnostics -ok 61 - function_privs_are(non-func, args, role, privs, desc) should fail -ok 62 - function_privs_are(non-func, args, role, privs, desc) should have the proper description -ok 63 - function_privs_are(non-func, args, role, privs, desc) should have the proper diagnostics -ok 64 - function_privs_are(sch, func, args, noone, privs, desc) should fail -ok 65 - function_privs_are(sch, func, args, noone, privs, desc) should have the proper description -ok 66 - function_privs_are(sch, func, args, noone, privs, desc) should have the proper diagnostics -ok 67 - function_privs_are(func, args, noone, privs, desc) should fail -ok 68 - function_privs_are(func, args, noone, privs, desc) should have the proper description -ok 69 - function_privs_are(func, args, noone, privs, desc) should have the proper diagnostics -ok 70 - function_privs_are(sch, func, args, other, privs, desc) should pass -ok 71 - function_privs_are(sch, func, args, other, privs, desc) should have the proper description -ok 72 - function_privs_are(sch, func, args, other, privs, desc) should have the proper diagnostics -ok 73 - function_privs_are(func, args, other, privs, desc) should pass -ok 74 - function_privs_are(func, args, other, privs, desc) should have the proper description -ok 75 - function_privs_are(func, args, other, privs, desc) should have the proper diagnostics -ok 76 - function_privs_are(sch, func, args, unpriv, privs, desc) should fail -ok 77 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper description -ok 78 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper diagnostics -ok 79 - function_privs_are(func, args, unpriv, privs, desc) should fail -ok 80 - function_privs_are(func, args, unpriv, privs, desc) should have the proper description -ok 81 - function_privs_are(func, args, unpriv, privs, desc) should have the proper diagnostics -ok 82 - function_privs_are(sch, func, args, unpriv, empty, desc) should pass -ok 83 - function_privs_are(sch, func, args, unpriv, empty, desc) should have the proper description -ok 84 - function_privs_are(sch, func, args, unpriv, empty, desc) should have the proper diagnostics -ok 85 - function_privs_are(sch, func, args, unpriv, empty) should pass -ok 86 - function_privs_are(sch, func, args, unpriv, empty) should have the proper description -ok 87 - function_privs_are(func, args, unpriv, empty) should pass -ok 88 - function_privs_are(func, args, unpriv, empty) should have the proper description -ok 89 - function_privs_are(sch, func, args, unpriv, privs, desc) should fail -ok 90 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper description -ok 91 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper diagnostics -ok 92 - function_privs_are(func, args, unpriv, privs, desc) should fail -ok 93 - function_privs_are(func, args, unpriv, privs, desc) should have the proper description -ok 94 - function_privs_are(func, args, unpriv, privs, desc) should have the proper diagnostics -1..94 +ok 31 - table_privs_are(sch, tab, role, no privs) should fail +ok 32 - table_privs_are(sch, tab, role, no privs) should have the proper description +ok 33 - table_privs_are(sch, tab, role, no privs) should have the proper diagnostics +ok 34 - table_privs_are(tab, role, no privs) should fail +ok 35 - table_privs_are(tab, role, no privs) should have the proper description +ok 36 - table_privs_are(tab, role, no privs) should have the proper diagnostics +ok 37 - db_privs_are(db, role, privs, desc) should pass +ok 38 - db_privs_are(db, role, privs, desc) should have the proper description +ok 39 - db_privs_are(db, role, privs, desc) should have the proper diagnostics +ok 40 - db_privs_are(db, role, privs, desc) should pass +ok 41 - db_privs_are(db, role, privs, desc) should have the proper description +ok 42 - db_privs_are(db, role, privs, desc) should have the proper diagnostics +ok 43 - db_privs_are(non-db, role, privs, desc) should fail +ok 44 - db_privs_are(non-db, role, privs, desc) should have the proper description +ok 45 - db_privs_are(non-db, role, privs, desc) should have the proper diagnostics +ok 46 - db_privs_are(db, non-role, privs, desc) should fail +ok 47 - db_privs_are(db, non-role, privs, desc) should have the proper description +ok 48 - db_privs_are(db, non-role, privs, desc) should have the proper diagnostics +ok 49 - db_privs_are(db, ungranted, privs, desc) should fail +ok 50 - db_privs_are(db, ungranted, privs, desc) should have the proper description +ok 51 - db_privs_are(db, ungranted, privs, desc) should have the proper diagnostics +ok 52 - db_privs_are(db, ungranted, privs, desc) should fail +ok 53 - db_privs_are(db, ungranted, privs, desc) should have the proper description +ok 54 - db_privs_are(db, ungranted, privs, desc) should have the proper diagnostics +ok 55 - db_privs_are(db, non-role, no privs) should fail +ok 56 - db_privs_are(db, non-role, no privs) should have the proper description +ok 57 - db_privs_are(db, non-role, no privs) should have the proper diagnostics +ok 58 - function_privs_are(sch, func, args, role, privs, desc) should pass +ok 59 - function_privs_are(sch, func, args, role, privs, desc) should have the proper description +ok 60 - function_privs_are(sch, func, args, role, privs, desc) should have the proper diagnostics +ok 61 - Role david should be granted EXECUTE on function public.foo(integer, text) should pass +ok 62 - function_privs_are(func, args, role, privs, desc) should pass +ok 63 - function_privs_are(func, args, role, privs, desc) should have the proper description +ok 64 - function_privs_are(func, args, role, privs, desc) should have the proper diagnostics +ok 65 - function_privs_are(func, args, role, privs) should pass +ok 66 - function_privs_are(func, args, role, privs) should have the proper description +ok 67 - function_privs_are(sch, non-func, args, role, privs, desc) should fail +ok 68 - function_privs_are(sch, non-func, args, role, privs, desc) should have the proper description +ok 69 - function_privs_are(sch, non-func, args, role, privs, desc) should have the proper diagnostics +ok 70 - function_privs_are(non-func, args, role, privs, desc) should fail +ok 71 - function_privs_are(non-func, args, role, privs, desc) should have the proper description +ok 72 - function_privs_are(non-func, args, role, privs, desc) should have the proper diagnostics +ok 73 - function_privs_are(sch, func, args, noone, privs, desc) should fail +ok 74 - function_privs_are(sch, func, args, noone, privs, desc) should have the proper description +ok 75 - function_privs_are(sch, func, args, noone, privs, desc) should have the proper diagnostics +ok 76 - function_privs_are(func, args, noone, privs, desc) should fail +ok 77 - function_privs_are(func, args, noone, privs, desc) should have the proper description +ok 78 - function_privs_are(func, args, noone, privs, desc) should have the proper diagnostics +ok 79 - function_privs_are(sch, func, args, other, privs, desc) should pass +ok 80 - function_privs_are(sch, func, args, other, privs, desc) should have the proper description +ok 81 - function_privs_are(sch, func, args, other, privs, desc) should have the proper diagnostics +ok 82 - function_privs_are(func, args, other, privs, desc) should pass +ok 83 - function_privs_are(func, args, other, privs, desc) should have the proper description +ok 84 - function_privs_are(func, args, other, privs, desc) should have the proper diagnostics +ok 85 - function_privs_are(sch, func, args, unpriv, privs, desc) should fail +ok 86 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper description +ok 87 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper diagnostics +ok 88 - function_privs_are(func, args, unpriv, privs, desc) should fail +ok 89 - function_privs_are(func, args, unpriv, privs, desc) should have the proper description +ok 90 - function_privs_are(func, args, unpriv, privs, desc) should have the proper diagnostics +ok 91 - function_privs_are(sch, func, args, unpriv, empty, desc) should pass +ok 92 - function_privs_are(sch, func, args, unpriv, empty, desc) should have the proper description +ok 93 - function_privs_are(sch, func, args, unpriv, empty, desc) should have the proper diagnostics +ok 94 - function_privs_are(sch, func, args, unpriv, empty) should pass +ok 95 - function_privs_are(sch, func, args, unpriv, empty) should have the proper description +ok 96 - function_privs_are(func, args, unpriv, empty) should pass +ok 97 - function_privs_are(func, args, unpriv, empty) should have the proper description +ok 98 - function_privs_are(sch, func, args, unpriv, privs, desc) should fail +ok 99 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper description +ok 100 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper diagnostics +ok 101 - function_privs_are(func, args, unpriv, privs, desc) should fail +ok 102 - function_privs_are(func, args, unpriv, privs, desc) should have the proper description +ok 103 - function_privs_are(func, args, unpriv, privs, desc) should have the proper diagnostics diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 428507659df8..ca5446573100 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -1,8 +1,8 @@ \unset ECHO \i test/setup.sql ---SELECT plan(48); -SELECT * FROM no_plan(); +SELECT plan(103); +--SELECT * FROM no_plan(); SET client_min_messages = warning; CREATE SCHEMA ha; @@ -130,6 +130,24 @@ SELECT * FROM check_test( ' Role __nonesuch does not exist' ); +-- Test default description with no permissions. +SELECT * FROM check_test( + table_privs_are( 'ha', 'sometab', '__nonesuch', '{}'::text[] ), + false, + 'table_privs_are(sch, tab, role, no privs)', + 'Role __nonesuch should be granted no privileges on table ha.sometab' , + ' Role __nonesuch does not exist' +); + +SELECT * FROM check_test( + table_privs_are( 'sometab', '__nonesuch', '{}'::text[] ), + false, + 'table_privs_are(tab, role, no privs)', + 'Role __nonesuch should be granted no privileges on table sometab' , + ' Role __nonesuch does not exist' +); + + /****************************************************************************/ -- Test db_privilege_is(). @@ -192,6 +210,16 @@ SELECT * FROM check_test( TEMPORARY' ); +-- Try testing default description for no permissions. +SELECT * FROM check_test( + db_privs_are( current_database(), '__noone', '{}'::text[] ), + false, + 'db_privs_are(db, non-role, no privs)', + 'Role __noone should be granted no privileges on database ' || current_database(), + ' Role __noone does not exist' +); + + /****************************************************************************/ -- Test function_privilege_is(). CREATE OR REPLACE FUNCTION public.foo(int, text) RETURNS VOID LANGUAGE SQL AS ''; From e78c145e27d640594202a4854516c84a62e05101 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Jan 2013 13:40:40 -0800 Subject: [PATCH 0707/1195] Add languate_privs_are(). --- Changes | 1 + doc/pgtap.mmd | 59 ++++++++++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 63 ++++++++++++++++++++++++++++++++++++----- test/expected/privs.out | 20 ++++++++++++- test/sql/privs.sql | 62 ++++++++++++++++++++++++++++++++++++++-- 5 files changed, 194 insertions(+), 11 deletions(-) diff --git a/Changes b/Changes index cf4667bde99b..204ebea5bb6c 100644 --- a/Changes +++ b/Changes @@ -35,6 +35,7 @@ Revision history for pgTAP + `db_privs_are()` + `table_privs_are()` + `function_privs_are()` + + `language_privs_are()` 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index e00c3a0be98b..7652f2009e5d 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5723,6 +5723,65 @@ something like: # Failed test 17: "Role slim should be granted EXECUTE on foo()" # Role slim does not exist +### `language_privs_are()` + + SELECT language_privs_are ( :lang, :role, :privileges, :description ); + SELECT language_privs_are ( :lang, :role, :privileges ); + +**Parameters** + +`:lang` +: Name of a language. + +`:role` +: Name of a user or group role. + +`:privileges` +: An array of table privileges the role should be granted to the language. + +`:description` +: A short description of the test. + +Tests the privileges granted to a role to access a language. Consult [the +documentation](http://www.postgresql.org/docs/current/static/functions-info.html#FUNCTIONS-INFO-ACCESS-LANGUAGE) +for the list of language privileges available in your version of PostgreSQL. + +If the `:description` argument is omitted, an appropriate description will be +created. Examples: + + SELECT language_privs_are( + 'plpgsql', 'fred', ARRAY['USAGE'], + 'Fred should be granted USAGE on db "flipr"' + ); + SELECT language_privs_are( 'plperl', ARRAY['USAGE'] ); + +If the role is granted permissions other than those specified, the diagnostics +will list the extra permissions, like so: + + # Failed test 14: "Role bob should be granted no privileges on banks" + # Extra privileges: + # USAGE + +Likewise if the role is not granted some of the specified permissions on the +language: + + # Failed test 15: "Role kurk should be granted USGAE on banks" + # Missing privileges: + # USAGE + +In the event that the test fails because the language in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Role slim should be granted USAGE on plr" + # Language maindb does not exist + +If the test fails because the role does not exist, the diagnostics will look +something like: + + # Failed test 17: "Role slim should be granted USAGE on pllolcode" + # Role slim does not exist + No Test for the Wicked ====================== diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index ed20a31406ca..194fd1025ee0 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7943,7 +7943,7 @@ BEGIN RETURN '{undefined_table}'; WHEN undefined_object THEN -- Not a valid role. - RETURN '{undefined_object}'; + RETURN '{undefined_role}'; WHEN invalid_parameter_value THEN -- Not a valid permission on this version of PostgreSQL; ignore; END; @@ -7986,7 +7986,7 @@ BEGIN RETURN ok(FALSE, $5) || E'\n' || diag( ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' ); - ELSIF grants[1] = 'undefined_object' THEN + ELSIF grants[1] = 'undefined_role' THEN RETURN ok(FALSE, $5) || E'\n' || diag( ' Role ' || quote_ident($3) || ' does not exist' ); @@ -8016,7 +8016,7 @@ BEGIN RETURN ok(FALSE, $4) || E'\n' || diag( ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' ); - ELSIF grants[1] = 'undefined_object' THEN + ELSIF grants[1] = 'undefined_role' THEN RETURN ok(FALSE, $4) || E'\n' || diag( ' Role ' || quote_ident($2) || ' does not exist' ); @@ -8065,7 +8065,7 @@ BEGIN RETURN '{invalid_catalog_name}'; WHEN undefined_object THEN -- Not a valid role. - RETURN '{undefined_object}'; + RETURN '{undefined_role}'; WHEN invalid_parameter_value THEN -- Not a valid permission on this version of PostgreSQL; ignore; END; @@ -8084,7 +8084,7 @@ BEGIN RETURN ok(FALSE, $4) || E'\n' || diag( ' Database ' || quote_ident($1) || ' does not exist' ); - ELSIF grants[1] = 'undefined_object' THEN + ELSIF grants[1] = 'undefined_role' THEN RETURN ok(FALSE, $4) || E'\n' || diag( ' Role ' || quote_ident($2) || ' does not exist' ); @@ -8116,7 +8116,7 @@ EXCEPTION -- Not a valid func name. WHEN undefined_function THEN RETURN '{undefined_function}'; -- Not a valid role. - WHEN undefined_object THEN RETURN '{undefined_object}'; + WHEN undefined_object THEN RETURN '{undefined_role}'; END; $$ LANGUAGE plpgsql; @@ -8129,7 +8129,7 @@ BEGIN RETURN ok(FALSE, $4) || E'\n' || diag( ' Function ' || $1 || ' does not exist' ); - ELSIF grants[1] = 'undefined_object' THEN + ELSIF grants[1] = 'undefined_role' THEN RETURN ok(FALSE, $4) || E'\n' || diag( ' Role ' || quote_ident($2) || ' does not exist' ); @@ -8178,3 +8178,52 @@ RETURNS TEXT AS $$ || ' 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 and 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; + diff --git a/test/expected/privs.out b/test/expected/privs.out index e70188da149b..40d6e94d7261 100644 --- a/test/expected/privs.out +++ b/test/expected/privs.out @@ -1,5 +1,4 @@ \unset ECHO -1..103 ok 1 - table_privs_are(sch, tab, role, privs, desc) should pass ok 2 - table_privs_are(sch, tab, role, privs, desc) should have the proper description ok 3 - table_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics @@ -103,3 +102,22 @@ ok 100 - function_privs_are(sch, func, args, unpriv, privs, desc) should have th ok 101 - function_privs_are(func, args, unpriv, privs, desc) should fail ok 102 - function_privs_are(func, args, unpriv, privs, desc) should have the proper description ok 103 - function_privs_are(func, args, unpriv, privs, desc) should have the proper diagnostics +ok 104 - language_privs_are(lang, role, privs, desc) should pass +ok 105 - language_privs_are(lang, role, privs, desc) should have the proper description +ok 106 - language_privs_are(lang, role, privs, desc) should have the proper diagnostics +ok 107 - language_privs_are(lang, role, privs, desc) should pass +ok 108 - language_privs_are(lang, role, privs, desc) should have the proper description +ok 109 - language_privs_are(lang, role, privs, desc) should have the proper diagnostics +ok 110 - language_privs_are(non-lang, role, privs, desc) should fail +ok 111 - language_privs_are(non-lang, role, privs, desc) should have the proper description +ok 112 - language_privs_are(non-lang, role, privs, desc) should have the proper diagnostics +ok 113 - language_privs_are(lang, non-role, privs, desc) should fail +ok 114 - language_privs_are(lang, non-role, privs, desc) should have the proper description +ok 115 - language_privs_are(lang, non-role, privs, desc) should have the proper diagnostics +ok 116 - language_privs_are(lang, ungranted, privs, desc) should fail +ok 117 - language_privs_are(lang, ungranted, privs, desc) should have the proper description +ok 118 - language_privs_are(lang, ungranted, privs, desc) should have the proper diagnostics +ok 119 - language_privs_are(lang, role, no privs) should pass +ok 120 - language_privs_are(lang, role, no privs) should have the proper description +ok 121 - language_privs_are(lang, role, no privs) should have the proper diagnostics +1..121 diff --git a/test/sql/privs.sql b/test/sql/privs.sql index ca5446573100..c064b56af9d5 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -1,8 +1,8 @@ \unset ECHO \i test/setup.sql -SELECT plan(103); ---SELECT * FROM no_plan(); +--SELECT plan(103); +SELECT * FROM no_plan(); SET client_min_messages = warning; CREATE SCHEMA ha; @@ -219,7 +219,6 @@ SELECT * FROM check_test( ' Role __noone does not exist' ); - /****************************************************************************/ -- Test function_privilege_is(). CREATE OR REPLACE FUNCTION public.foo(int, text) RETURNS VOID LANGUAGE SQL AS ''; @@ -422,6 +421,63 @@ SELECT * FROM check_test( EXECUTE' ); +/****************************************************************************/ +-- Test language_privilege_is(). + +SELECT * FROM check_test( + language_privs_are( 'plpgsql', current_user, '{USAGE}', 'whatever' ), + true, + 'language_privs_are(lang, role, privs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + language_privs_are( 'plpgsql', current_user, '{USAGE}' ), + true, + 'language_privs_are(lang, role, privs, desc)', + 'Role ' || current_user || ' should be granted USAGE on language plpgsql', + '' +); + +-- Try nonexistent language. +SELECT * FROM check_test( + language_privs_are( '__nonesuch', current_user, '{USAGE}', 'whatever' ), + false, + 'language_privs_are(non-lang, role, privs, desc)', + 'whatever', + ' Language __nonesuch does not exist' +); + +-- Try nonexistent user. +SELECT * FROM check_test( + language_privs_are( 'plpgsql', '__noone', '{USAGE}', 'whatever' ), + false, + 'language_privs_are(lang, non-role, privs, desc)', + 'whatever', + ' Role __noone does not exist' +); + +-- Try another user. +REVOKE USAGE ON LANGUAGE plpgsql FROM public; +SELECT * FROM check_test( + language_privs_are( 'plpgsql', '__someone_else', '{USAGE}', 'whatever' ), + false, + 'language_privs_are(lang, ungranted, privs, desc)', + 'whatever', + ' Missing privileges: + USAGE' +); + +-- Try testing default description for no permissions. +SELECT * FROM check_test( + language_privs_are( 'plpgsql', '__someone_else', '{}'::text[] ), + true, + 'language_privs_are(lang, role, no privs)', + 'Role __someone_else should be granted no privileges on language plpgsql', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From d13bbe2a8742a27065a1653eb960f4f6c01631ca Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Jan 2013 13:45:33 -0800 Subject: [PATCH 0708/1195] s/db_privs_are/database_privs_are/g --- Changes | 2 +- doc/pgtap.mmd | 10 ++++----- sql/pgtap--0.91.0--0.92.0.sql | 10 ++++----- sql/pgtap.sql.in | 10 ++++----- test/expected/privs.out | 42 +++++++++++++++++------------------ test/sql/privs.sql | 28 +++++++++++------------ 6 files changed, 51 insertions(+), 51 deletions(-) diff --git a/Changes b/Changes index 204ebea5bb6c..f5eb3429732a 100644 --- a/Changes +++ b/Changes @@ -32,7 +32,7 @@ Revision history for pgTAP * Fixed `has_index()` to properly handle indexes composed of both columns and expressions. * Added new functions for testing a user's access to database objects: - + `db_privs_are()` + + `database_privs_are()` + `table_privs_are()` + `function_privs_are()` + `language_privs_are()` diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 7652f2009e5d..1b018ec2328d 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5529,10 +5529,10 @@ Prvileged Access So we know who owns the objects. But what about other roles? Can they access database objects? Let's find out! -### `db_privs_are()` +### `database_privs_are()` - SELECT db_privs_are ( :db, :role, :privileges, :description ); - SELECT db_privs_are ( :db, :role, :privileges ); + SELECT database_privs_are ( :db, :role, :privileges, :description ); + SELECT database_privs_are ( :db, :role, :privileges ); **Parameters** @@ -5555,11 +5555,11 @@ for the list of database privileges available in your version of PostgreSQL. If the `:description` argument is omitted, an appropriate description will be created. Examples: - SELECT db_privs_are( + SELECT database_privs_are( 'flipr', 'fred', ARRAY['CONNECT', 'TEMPORARY'], 'Fred should be granted CONNECT and TERMPORARY on db "flipr"' ); - SELECT db_privs_are( 'dept_corrections', ARRAY['CREATE'] ); + SELECT database_privs_are( 'dept_corrections', ARRAY['CREATE'] ); If the role is granted permissions other than those specified, the diagnostics will list the extra permissions, like so: diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index c337d7b59082..c8dbf137f749 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -1017,8 +1017,8 @@ BEGIN END; $$ LANGUAGE plpgsql; --- db_privs_are ( db, user, privileges[], description ) -CREATE OR REPLACE FUNCTION db_privs_are ( NAME, NAME, NAME[], TEXT ) +-- 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, quote_ident($1) ); @@ -1036,10 +1036,10 @@ BEGIN END; $$ LANGUAGE plpgsql; --- db_privs_are ( db, user, privileges[] ) -CREATE OR REPLACE FUNCTION db_privs_are ( NAME, NAME, NAME[] ) +-- database_privs_are ( db, user, privileges[] ) +CREATE OR REPLACE FUNCTION database_privs_are ( NAME, NAME, NAME[] ) RETURNS TEXT AS $$ - SELECT db_privs_are( + 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 diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 194fd1025ee0..aac73d4673a0 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -8074,8 +8074,8 @@ BEGIN END; $$ LANGUAGE plpgsql; --- db_privs_are ( db, user, privileges[], description ) -CREATE OR REPLACE FUNCTION db_privs_are ( NAME, NAME, NAME[], TEXT ) +-- 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, quote_ident($1) ); @@ -8093,10 +8093,10 @@ BEGIN END; $$ LANGUAGE plpgsql; --- db_privs_are ( db, user, privileges[] ) -CREATE OR REPLACE FUNCTION db_privs_are ( NAME, NAME, NAME[] ) +-- database_privs_are ( db, user, privileges[] ) +CREATE OR REPLACE FUNCTION database_privs_are ( NAME, NAME, NAME[] ) RETURNS TEXT AS $$ - SELECT db_privs_are( + 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 diff --git a/test/expected/privs.out b/test/expected/privs.out index 40d6e94d7261..d568c9946281 100644 --- a/test/expected/privs.out +++ b/test/expected/privs.out @@ -35,27 +35,27 @@ ok 33 - table_privs_are(sch, tab, role, no privs) should have the proper diagnos ok 34 - table_privs_are(tab, role, no privs) should fail ok 35 - table_privs_are(tab, role, no privs) should have the proper description ok 36 - table_privs_are(tab, role, no privs) should have the proper diagnostics -ok 37 - db_privs_are(db, role, privs, desc) should pass -ok 38 - db_privs_are(db, role, privs, desc) should have the proper description -ok 39 - db_privs_are(db, role, privs, desc) should have the proper diagnostics -ok 40 - db_privs_are(db, role, privs, desc) should pass -ok 41 - db_privs_are(db, role, privs, desc) should have the proper description -ok 42 - db_privs_are(db, role, privs, desc) should have the proper diagnostics -ok 43 - db_privs_are(non-db, role, privs, desc) should fail -ok 44 - db_privs_are(non-db, role, privs, desc) should have the proper description -ok 45 - db_privs_are(non-db, role, privs, desc) should have the proper diagnostics -ok 46 - db_privs_are(db, non-role, privs, desc) should fail -ok 47 - db_privs_are(db, non-role, privs, desc) should have the proper description -ok 48 - db_privs_are(db, non-role, privs, desc) should have the proper diagnostics -ok 49 - db_privs_are(db, ungranted, privs, desc) should fail -ok 50 - db_privs_are(db, ungranted, privs, desc) should have the proper description -ok 51 - db_privs_are(db, ungranted, privs, desc) should have the proper diagnostics -ok 52 - db_privs_are(db, ungranted, privs, desc) should fail -ok 53 - db_privs_are(db, ungranted, privs, desc) should have the proper description -ok 54 - db_privs_are(db, ungranted, privs, desc) should have the proper diagnostics -ok 55 - db_privs_are(db, non-role, no privs) should fail -ok 56 - db_privs_are(db, non-role, no privs) should have the proper description -ok 57 - db_privs_are(db, non-role, no privs) should have the proper diagnostics +ok 37 - database_privs_are(db, role, privs, desc) should pass +ok 38 - database_privs_are(db, role, privs, desc) should have the proper description +ok 39 - database_privs_are(db, role, privs, desc) should have the proper diagnostics +ok 40 - database_privs_are(db, role, privs, desc) should pass +ok 41 - database_privs_are(db, role, privs, desc) should have the proper description +ok 42 - database_privs_are(db, role, privs, desc) should have the proper diagnostics +ok 43 - database_privs_are(non-db, role, privs, desc) should fail +ok 44 - database_privs_are(non-db, role, privs, desc) should have the proper description +ok 45 - database_privs_are(non-db, role, privs, desc) should have the proper diagnostics +ok 46 - database_privs_are(db, non-role, privs, desc) should fail +ok 47 - database_privs_are(db, non-role, privs, desc) should have the proper description +ok 48 - database_privs_are(db, non-role, privs, desc) should have the proper diagnostics +ok 49 - database_privs_are(db, ungranted, privs, desc) should fail +ok 50 - database_privs_are(db, ungranted, privs, desc) should have the proper description +ok 51 - database_privs_are(db, ungranted, privs, desc) should have the proper diagnostics +ok 52 - database_privs_are(db, ungranted, privs, desc) should fail +ok 53 - database_privs_are(db, ungranted, privs, desc) should have the proper description +ok 54 - database_privs_are(db, ungranted, privs, desc) should have the proper diagnostics +ok 55 - database_privs_are(db, non-role, no privs) should fail +ok 56 - database_privs_are(db, non-role, no privs) should have the proper description +ok 57 - database_privs_are(db, non-role, no privs) should have the proper diagnostics ok 58 - function_privs_are(sch, func, args, role, privs, desc) should pass ok 59 - function_privs_are(sch, func, args, role, privs, desc) should have the proper description ok 60 - function_privs_are(sch, func, args, role, privs, desc) should have the proper diagnostics diff --git a/test/sql/privs.sql b/test/sql/privs.sql index c064b56af9d5..942b57c22a0b 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -152,17 +152,17 @@ SELECT * FROM check_test( -- Test db_privilege_is(). SELECT * FROM check_test( - db_privs_are( current_database(), current_user, _db_privs(), 'whatever' ), + database_privs_are( current_database(), current_user, _db_privs(), 'whatever' ), true, - 'db_privs_are(db, role, privs, desc)', + 'database_privs_are(db, role, privs, desc)', 'whatever', '' ); SELECT * FROM check_test( - db_privs_are( current_database(), current_user, _db_privs() ), + database_privs_are( current_database(), current_user, _db_privs() ), true, - 'db_privs_are(db, role, privs, desc)', + 'database_privs_are(db, role, privs, desc)', 'Role ' || current_user || ' should be granted ' || array_to_string(_db_privs(), ', ') || ' on database ' || current_database(), '' @@ -170,27 +170,27 @@ SELECT * FROM check_test( -- Try nonexistent database. SELECT * FROM check_test( - db_privs_are( '__nonesuch', current_user, _db_privs(), 'whatever' ), + database_privs_are( '__nonesuch', current_user, _db_privs(), 'whatever' ), false, - 'db_privs_are(non-db, role, privs, desc)', + 'database_privs_are(non-db, role, privs, desc)', 'whatever', ' Database __nonesuch does not exist' ); -- Try nonexistent user. SELECT * FROM check_test( - db_privs_are( current_database(), '__noone', _db_privs(), 'whatever' ), + database_privs_are( current_database(), '__noone', _db_privs(), 'whatever' ), false, - 'db_privs_are(db, non-role, privs, desc)', + 'database_privs_are(db, non-role, privs, desc)', 'whatever', ' Role __noone does not exist' ); -- Try another user. SELECT * FROM check_test( - db_privs_are( current_database(), '__someone_else', _db_privs(), 'whatever' ), + database_privs_are( current_database(), '__someone_else', _db_privs(), 'whatever' ), false, - 'db_privs_are(db, ungranted, privs, desc)', + 'database_privs_are(db, ungranted, privs, desc)', 'whatever', ' Missing privileges: CREATE' @@ -198,13 +198,13 @@ SELECT * FROM check_test( -- Try a subset of privs. SELECT * FROM check_test( - db_privs_are( + database_privs_are( current_database(), current_user, CASE WHEN pg_version_num() < 80200 THEN ARRAY['CREATE'] ELSE ARRAY['CREATE', 'CONNECT'] END, 'whatever' ), false, - 'db_privs_are(db, ungranted, privs, desc)', + 'database_privs_are(db, ungranted, privs, desc)', 'whatever', ' Extra privileges: TEMPORARY' @@ -212,9 +212,9 @@ SELECT * FROM check_test( -- Try testing default description for no permissions. SELECT * FROM check_test( - db_privs_are( current_database(), '__noone', '{}'::text[] ), + database_privs_are( current_database(), '__noone', '{}'::text[] ), false, - 'db_privs_are(db, non-role, no privs)', + 'database_privs_are(db, non-role, no privs)', 'Role __noone should be granted no privileges on database ' || current_database(), ' Role __noone does not exist' ); From 23b64d557226db19b5abf1f9608d961340d17e28 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Jan 2013 14:03:47 -0800 Subject: [PATCH 0709/1195] Add schema_privs_are(). --- Changes | 1 + doc/pgtap.mmd | 68 +++++++++++++++++++++++++++++++++++++++--- sql/pgtap.sql.in | 49 +++++++++++++++++++++++++++++++ test/sql/privs.sql | 73 +++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 186 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index f5eb3429732a..8210158db55d 100644 --- a/Changes +++ b/Changes @@ -33,6 +33,7 @@ Revision history for pgTAP expressions. * Added new functions for testing a user's access to database objects: + `database_privs_are()` + + `schema_privs_are()` + `table_privs_are()` + `function_privs_are()` + `language_privs_are()` diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 1b018ec2328d..17663990b3ed 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5564,7 +5564,7 @@ created. Examples: If the role is granted permissions other than those specified, the diagnostics will list the extra permissions, like so: - # Failed test 14: "Role bob should be granted CREATE on banks" + # Failed test 14: "Role bob should be granted CREATE on database banks" # Extra privileges: # CONNECT # TEMPORARY @@ -5572,7 +5572,7 @@ will list the extra permissions, like so: Likewise if the role is not granted some of the specified permissions on the database: - # Failed test 15: "Role kurk should be granted CONNECT, TEMPORARY on banks" + # Failed test 15: "Role kurk should be granted CONNECT, TEMPORARY on database banks" # Missing privileges: # CREATE @@ -5580,13 +5580,73 @@ In the event that the test fails because the database in question does not actually exist or is not visible, you will see an appropriate diagnostic such as: - # Failed test 16: "Role slim should be granted CONNECT on maindb" + # Failed test 16: "Role slim should be granted CONNECT on database maindb" # Database maindb does not exist If the test fails because the role does not exist, the diagnostics will look something like: - # Failed test 17: "Role slim should be granted CONNECT, CREATE on widgets" + # Failed test 17: "Role slim should be granted CONNECT, CREATE on database widgets" + # Role slim does not exist + +### `schema_privs_are()` + + SELECT schema_privs_are ( :schema, :role, :privileges, :description ); + SELECT schema_privs_are ( :schema, :role, :privileges ); + +**Parameters** + +`:schema` +: Name of a schema. + +`:role` +: Name of a user or group role. + +`:privileges` +: An array of table privileges the role should be granted to the schema. + +`:description` +: A short description of the test. + +Tests the privileges granted to a role to access a schema. Consult [the +documentation](http://www.postgresql.org/docs/current/static/functions-info.html#FUNCTIONS-INFO-ACCESS-SCHEMA) +for the list of schema privileges available in your version of PostgreSQL. + +If the `:description` argument is omitted, an appropriate description will be +created. Examples: + + SELECT schema_privs_are( + 'flipr', 'fred', ARRAY['CREATE', 'USAGE'], + 'Fred should be granted CREATE and USAGE on schema "flipr"' + ); + SELECT schema_privs_are( 'hr', ARRAY['USAGE'] ); + +If the role is granted permissions other than those specified, the diagnostics +will list the extra permissions, like so: + + # Failed test 14: "Role bob should be granted no privileges on schema pinata" + # Extra privileges: + # CREATE + # USAGE + +Likewise if the role is not granted some of the specified permissions on the +schema: + + # Failed test 15: "Role kurk should be granted CREATE, USAGE on schema stuff" + # Missing privileges: + # CREATE + +In the event that the test fails because the schema in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Role slim should be granted USAGE on schema main" + # Schema main does not exist + +If the test fails because the role does not exist, the diagnostics will look +something like: + + # Failed test 17: "Role slim should be granted CREATE, USAGE on schema admin" # Role slim does not exist ### `table_privs_are()` diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index aac73d4673a0..0114078cb864 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -8227,3 +8227,52 @@ RETURNS TEXT AS $$ ); $$ 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, quote_ident($1) ); +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; diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 942b57c22a0b..0a0d15d16335 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -149,7 +149,7 @@ SELECT * FROM check_test( /****************************************************************************/ --- Test db_privilege_is(). +-- Test database_privileges_are(). SELECT * FROM check_test( database_privs_are( current_database(), current_user, _db_privs(), 'whatever' ), @@ -478,6 +478,77 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test schema_privileges_are(). + +SELECT * FROM check_test( + schema_privs_are( current_schema(), current_user, ARRAY['CREATE', 'USAGE'], 'whatever' ), + true, + 'schema_privs_are(schema, role, privs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + schema_privs_are( current_schema(), current_user, ARRAY['CREATE', 'USAGE'] ), + true, + 'schema_privs_are(schema, role, privs, desc)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['CREATE', 'USAGE'], ', ') || ' on schema ' || current_schema(), + '' +); + +-- Try nonexistent schema. +SELECT * FROM check_test( + schema_privs_are( '__nonesuch', current_user, ARRAY['CREATE', 'USAGE'], 'whatever' ), + false, + 'schema_privs_are(non-schema, role, privs, desc)', + 'whatever', + ' Schema __nonesuch does not exist' +); + +-- Try nonexistent user. +SELECT * FROM check_test( + schema_privs_are( current_schema(), '__noone', ARRAY['CREATE', 'USAGE'], 'whatever' ), + false, + 'schema_privs_are(schema, non-role, privs, desc)', + 'whatever', + ' Role __noone does not exist' +); + +-- Try another user. +SELECT * FROM check_test( + schema_privs_are( current_schema(), '__someone_else', ARRAY['CREATE', 'USAGE'], 'whatever' ), + false, + 'schema_privs_are(schema, ungranted, privs, desc)', + 'whatever', + ' Missing privileges: + CREATE + USAGE' +); + +-- Try a subset of privs. +SELECT * FROM check_test( + schema_privs_are( + current_schema(), current_user, ARRAY['CREATE'], + 'whatever' + ), + false, + 'schema_privs_are(schema, ungranted, privs, desc)', + 'whatever', + ' Extra privileges: + USAGE' +); + +-- Try testing default description for no permissions. +SELECT * FROM check_test( + schema_privs_are( current_schema(), '__noone', '{}'::text[] ), + false, + 'schema_privs_are(schema, non-role, no privs)', + 'Role __noone should be granted no privileges on schema ' || current_schema(), + ' Role __noone does not exist' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 18aa66fb3c3daa60fac8018bc1a81aa06c3a3929 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Jan 2013 14:10:03 -0800 Subject: [PATCH 0710/1195] Document the available privileges. --- doc/pgtap.mmd | 50 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 17663990b3ed..2697ed97e976 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5548,9 +5548,14 @@ database objects? Let's find out! `:description` : A short description of the test. -Tests the privileges granted to a role to access a database. Consult [the -documentation](http://www.postgresql.org/docs/current/static/functions-info.html#FUNCTIONS-INFO-ACCESS-DATABASE) -for the list of database privileges available in your version of PostgreSQL. +Tests the privileges granted to a role to access a database. The available +database privileges are: + +* CREATE +* CONNECT +* TEMPORARY + +Although CONNECT is not available before PostgreSQL 8.2. If the `:description` argument is omitted, an appropriate description will be created. Examples: @@ -5608,9 +5613,11 @@ something like: `:description` : A short description of the test. -Tests the privileges granted to a role to access a schema. Consult [the -documentation](http://www.postgresql.org/docs/current/static/functions-info.html#FUNCTIONS-INFO-ACCESS-SCHEMA) -for the list of schema privileges available in your version of PostgreSQL. +Tests the privileges granted to a role to access a schema. The available +schema privileges are: + +* CREATE +* USAGE If the `:description` argument is omitted, an appropriate description will be created. Examples: @@ -5673,9 +5680,20 @@ something like: `:description` : A short description of the test. -Tests the privileges granted to a role to access a table. Consult [the -documentation](http://www.postgresql.org/docs/current/static/functions-info.html#FUNCTIONS-INFO-ACCESS-TABLE) -for the list of table privileges available in your version of PostgreSQL. +Tests the privileges granted to a role to access a table. The available +table privileges are: + +* DELETE +* INSERT +* REFERENCES +* RULE +* SELECT +* TRIGGER +* TRUNCATE +* UPDATE + +Note that the privilege RULE is not available aftter PostgreSQL 8.1, and that +TRIGGER was added in 8.4. If the `:description` argument is omitted, an appropriate description will be created. Examples: @@ -5743,9 +5761,10 @@ something like: `:description` : A short description of the test. -Tests the privileges granted to a role to access a function. Consult [the -documentation](http://www.postgresql.org/docs/current/static/functions-info.html#FUNCTIONS-INFO-ACCESS-FUNCTION) -for the list of function privileges available in your version of PostgreSQL. +Tests the privileges granted to a role to access a function. The available +function privileges are: + +* EXECUTE If the `:description` argument is omitted, an appropriate description will be created. Examples: @@ -5802,9 +5821,10 @@ something like: `:description` : A short description of the test. -Tests the privileges granted to a role to access a language. Consult [the -documentation](http://www.postgresql.org/docs/current/static/functions-info.html#FUNCTIONS-INFO-ACCESS-LANGUAGE) -for the list of language privileges available in your version of PostgreSQL. +Tests the privileges granted to a role to access a language. The available +function privileges are: + +* USAGE If the `:description` argument is omitted, an appropriate description will be created. Examples: From 2a4adfe3ca96a8dbebcdecf0c32e51877aceb3c8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Jan 2013 14:21:24 -0800 Subject: [PATCH 0711/1195] Add `tablespace_privs_are()`. Issue #34. --- Changes | 1 + doc/pgtap.mmd | 66 +++++++++++++++++++++++++++++++++++++++++++--- sql/pgtap.sql.in | 51 ++++++++++++++++++++++++++++++++++- test/sql/privs.sql | 57 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 8210158db55d..426fc9d48c3f 100644 --- a/Changes +++ b/Changes @@ -33,6 +33,7 @@ Revision history for pgTAP expressions. * Added new functions for testing a user's access to database objects: + `database_privs_are()` + + `tablespace_privs_are()` + `schema_privs_are()` + `table_privs_are()` + `function_privs_are()` diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 2697ed97e976..26c717afea15 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5594,6 +5594,66 @@ something like: # Failed test 17: "Role slim should be granted CONNECT, CREATE on database widgets" # Role slim does not exist +### `tablespace_privs_are()` + + SELECT tablespace_privs_are ( :tablespace, :role, :privileges, :description ); + SELECT tablespace_privs_are ( :tablespace, :role, :privileges ); + +**Parameters** + +`:tablespace` +: Name of a tablespace. + +`:role` +: Name of a user or group role. + +`:privileges` +: An array of table privileges the role should be granted to the tablespace. + +`:description` +: A short description of the test. + +Tests the privileges granted to a role to access a tablespace. The available +function privileges are: + +* CREATE + +If the `:description` argument is omitted, an appropriate description will be +created. Examples: + + SELECT tablespace_privs_are( + 'ssd', 'fred', ARRAY['CREATE'], + 'Fred should be granted CREATE on tablespace "ssd"' + ); + SELECT tablespace_privs_are( 'san', ARRAY['CREATE'] ); + +If the role is granted permissions other than those specified, the diagnostics +will list the extra permissions, like so: + + # Failed test 14: "Role bob should be granted no privileges on tablespace hdd" + # Extra privileges: + # CREATE + +Likewise if the role is not granted some of the specified permissions on the +tablespace: + + # Failed test 15: "Role kurk should be granted USAGE on ssd" + # Missing privileges: + # CREATE + +In the event that the test fails because the tablespace in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Role slim should be granted CREATE on tablespace tape" + # Tablespace tape does not exist + +If the test fails because the role does not exist, the diagnostics will look +something like: + + # Failed test 17: "Role slim should be granted CREATE on san" + # Role slim does not exist + ### `schema_privs_are()` SELECT schema_privs_are ( :schema, :role, :privileges, :description ); @@ -5831,7 +5891,7 @@ created. Examples: SELECT language_privs_are( 'plpgsql', 'fred', ARRAY['USAGE'], - 'Fred should be granted USAGE on db "flipr"' + 'Fred should be granted USAGE on language "flipr"' ); SELECT language_privs_are( 'plperl', ARRAY['USAGE'] ); @@ -5845,7 +5905,7 @@ will list the extra permissions, like so: Likewise if the role is not granted some of the specified permissions on the language: - # Failed test 15: "Role kurk should be granted USGAE on banks" + # Failed test 15: "Role kurk should be granted USAGE on banks" # Missing privileges: # USAGE @@ -5854,7 +5914,7 @@ actually exist or is not visible, you will see an appropriate diagnostic such as: # Failed test 16: "Role slim should be granted USAGE on plr" - # Language maindb does not exist + # Language plr does not exist If the test fails because the role does not exist, the diagnostics will look something like: diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 0114078cb864..4dea53a5e298 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -8188,7 +8188,7 @@ BEGIN RETURN '{}'; END IF; EXCEPTION WHEN undefined_object THEN - -- Same error code for unknown user and language. So figure out which. + -- Same error code for unknown user or language. So figure out which. RETURN CASE WHEN SQLERRM LIKE '%' || $1 || '%' THEN '{undefined_role}' ELSE @@ -8276,3 +8276,52 @@ RETURNS TEXT AS $$ || ' 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, quote_ident($1) ); +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; + diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 0a0d15d16335..0cfb27a6181c 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -549,6 +549,63 @@ SELECT * FROM check_test( ' Role __noone does not exist' ); +/****************************************************************************/ +-- Test tablespace_privilege_is(). + +SELECT * FROM check_test( + tablespace_privs_are( 'pg_default', current_user, '{CREATE}', 'whatever' ), + true, + 'tablespace_privs_are(tablespace, role, privs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + tablespace_privs_are( 'pg_default', current_user, '{CREATE}' ), + true, + 'tablespace_privs_are(tablespace, role, privs, desc)', + 'Role ' || current_user || ' should be granted CREATE on tablespace pg_default', + '' +); + +-- Try nonexistent tablespace. +SELECT * FROM check_test( + tablespace_privs_are( '__nonesuch', current_user, '{CREATE}', 'whatever' ), + false, + 'tablespace_privs_are(non-tablespace, role, privs, desc)', + 'whatever', + ' Tablespace __nonesuch does not exist' +); + +-- Try nonexistent user. +SELECT * FROM check_test( + tablespace_privs_are( 'pg_default', '__noone', '{CREATE}', 'whatever' ), + false, + 'tablespace_privs_are(tablespace, non-role, privs, desc)', + 'whatever', + ' Role __noone does not exist' +); + +-- Try another user. +REVOKE CREATE ON TABLESPACE pg_default FROM public; +SELECT * FROM check_test( + tablespace_privs_are( 'pg_default', '__someone_else', '{CREATE}', 'whatever' ), + false, + 'tablespace_privs_are(tablespace, ungranted, privs, desc)', + 'whatever', + ' Missing privileges: + CREATE' +); + +-- Try testing default description for no permissions. +SELECT * FROM check_test( + tablespace_privs_are( 'pg_default', '__someone_else', '{}'::text[] ), + true, + 'tablespace_privs_are(tablespace, role, no privs)', + 'Role __someone_else should be granted no privileges on tablespace pg_default', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From efb2d15f4a5fe26760d25312647dd33a9f374084 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Jan 2013 14:50:00 -0800 Subject: [PATCH 0712/1195] Add `sequence_privs_are()`. --- Changes | 1 + doc/pgtap.mmd | 72 ++++++++++++++++++++++++ sql/pgtap.sql.in | 121 ++++++++++++++++++++++++++++++++++------ test/sql/privs.sql | 136 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 311 insertions(+), 19 deletions(-) diff --git a/Changes b/Changes index 426fc9d48c3f..dc12858f5c9d 100644 --- a/Changes +++ b/Changes @@ -36,6 +36,7 @@ Revision history for pgTAP + `tablespace_privs_are()` + `schema_privs_are()` + `table_privs_are()` + + `sequence_privs_are()` + `function_privs_are()` + `language_privs_are()` diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 26c717afea15..4182abca7882 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5794,6 +5794,78 @@ something like: # Failed test 17: "Role slim should be granted SELECT on widgets" # Role slim does not exist +### `sequence_privs_are()` + + SELECT sequence_privs_are ( :schema, :sequence, :role, :privileges, :description ); + SELECT sequence_privs_are ( :schema, :sequence, :role, :privileges ); + SELECT sequence_privs_are ( :sequence, :role, :privileges, :description ); + SELECT sequence_privs_are ( :sequence, :role, :privileges ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the sequence. + +`:sequence` +: Name of a sequence. + +`:role` +: Name of a user or group role. + +`:privileges` +: An array of sequence privileges the role should be granted to the sequence. + +`:description` +: A short description of the test. + +Tests the privileges granted to a role to access a sequence. The available +sequence privileges are: + +* SELECT +* UPDATE +* USAGE + +Note that sequence privileges were added in PostgreSQL 9.0, so this function +will likley throw an exception on earlier versions. + +If the `:description` argument is omitted, an appropriate description will be +created. Examples: + + SELECT sequence_privs_are( + 'public', 'seq_ids', 'fred', ARRAY['SELECT', 'UPDATE'], + 'Fred should be able to select and update seq_ids' + ); + SELECT sequence_privs_are( 'seq_u', 'slim', ARRAY['USAGE'] ); + +If the role is granted permissions other than those specified, the diagnostics +will list the extra permissions, like so: + + # Failed test 14: "Role bob should be granted SELECT on seq_foo_id" + # Extra privileges: + # UPDATE + # USAGE + +Likewise if the role is not granted some of the specified permissions on the +sequence: + + # Failed test 15: "Role kurk should be granted USAGE on seq_widgets" + # Missing privileges: + # SELECT + # UPDATE + +In the event that the test fails because the sequence in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Role slim should be granted SELECT on seq_widgets" + # Sequence widgets does not exist + +If the test fails because the role does not exist, the diagnostics will look +something like: + + # Failed test 17: "Role slim should be granted SELECT on seq_widgets" + # Role slim does not exist + ### `function_privs_are()` SELECT function_privs_are ( :schema, :function, :args, :role, :privileges, :description ); diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 4dea53a5e298..e97e4af482c6 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7909,24 +7909,6 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; -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; - CREATE OR REPLACE FUNCTION _get_table_privs(NAME, TEXT) RETURNS TEXT[] AS $$ DECLARE @@ -7952,6 +7934,24 @@ BEGIN 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; + CREATE OR REPLACE FUNCTION _assets_are ( text, text[], text[], TEXT ) RETURNS TEXT AS $$ SELECT _areni( @@ -8325,3 +8325,88 @@ RETURNS TEXT AS $$ ); $$ 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; + diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 0cfb27a6181c..78d1db4abab1 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -7,6 +7,7 @@ SELECT * FROM no_plan(); SET client_min_messages = warning; CREATE SCHEMA ha; CREATE TABLE ha.sometab(id INT); +CREATE SEQUENCE ha.someseq; SET search_path = ha,public,pg_catalog; RESET client_min_messages; @@ -147,7 +148,6 @@ SELECT * FROM check_test( ' Role __nonesuch does not exist' ); - /****************************************************************************/ -- Test database_privileges_are(). @@ -606,6 +606,140 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test sequence_privilege_is(). + +SELECT * FROM check_test( + sequence_privs_are( 'ha', 'someseq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'], 'whatever' ), + true, + 'sequence_privs_are(sch, seq, role, privs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + sequence_privs_are( 'ha', 'someseq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'] ), + true, + 'sequence_privs_are(sch, seq, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['USAGE', 'SELECT', 'UPDATE'], ', ') || ' on sequence ha.someseq' , + '' +); + +SELECT * FROM check_test( + sequence_privs_are( 'someseq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'], 'whatever' ), + true, + 'sequence_privs_are(seq, role, privs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + sequence_privs_are( 'someseq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'] ), + true, + 'sequence_privs_are(seq, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['USAGE', 'SELECT', 'UPDATE'], ', ') || ' on sequence someseq' , + '' +); + +CREATE OR REPLACE FUNCTION run_extra_fails() RETURNS SETOF TEXT LANGUAGE plpgsql AS $$ +DECLARE + allowed_privs TEXT[]; + test_privs TEXT[]; + missing_privs TEXT[]; + tap record; + last_index INTEGER; +BEGIN + -- Test sequence failure. + allowed_privs := ARRAY['USAGE', 'SELECT', 'UPDATE']; + last_index := array_upper(allowed_privs, 1); + FOR i IN 1..last_index - 2 LOOP + test_privs := test_privs || allowed_privs[i]; + END LOOP; + FOR i IN last_index - 1..last_index LOOP + missing_privs := missing_privs || allowed_privs[i]; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + sequence_privs_are( 'ha', 'someseq', current_user, test_privs, 'whatever' ), + false, + 'sequence_privs_are(sch, seq, role, some privs, desc)', + 'whatever', + ' Extra privileges: + ' || array_to_string(missing_privs, E'\n ') + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + sequence_privs_are( 'someseq', current_user, test_privs, 'whatever' ), + false, + 'sequence_privs_are(seq, role, some privs, desc)', + 'whatever', + ' Extra privileges: + ' || array_to_string(missing_privs, E'\n ') + ) AS b LOOP RETURN NEXT tap.b; END LOOP; +END; +$$; + +SELECT * FROM run_extra_fails(); + +SELECT * FROM check_test( + sequence_privs_are( 'ha', 'someseq', '__someone_else', ARRAY['USAGE', 'SELECT', 'UPDATE'], 'whatever' ), + false, + 'sequence_privs_are(sch, seq, other, privs, desc)', + 'whatever', + ' Missing privileges: + ' || array_to_string(ARRAY['SELECT', 'UPDATE', 'USAGE'], E'\n ') +); + +-- Grant them some permission. +GRANT SELECT, UPDATE ON ha.someseq TO __someone_else; + +SELECT * FROM check_test( + sequence_privs_are( 'ha', 'someseq', '__someone_else', ARRAY[ + 'SELECT', 'UPDATE' + ], 'whatever'), + true, + 'sequence_privs_are(sch, seq, other, privs, desc)', + 'whatever', + '' +); + +-- Try a non-existent sequence. +SELECT * FROM check_test( + sequence_privs_are( 'ha', 'nonesuch', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'], 'whatever' ), + false, + 'sequence_privs_are(sch, seq, role, privs, desc)', + 'whatever', + ' Sequence ha.nonesuch does not exist' +); + +-- Try a non-existent user. +SELECT * FROM check_test( + sequence_privs_are( 'ha', 'someseq', '__nonesuch', ARRAY['USAGE', 'SELECT', 'UPDATE'], 'whatever' ), + false, + 'sequence_privs_are(sch, seq, role, privs, desc)', + 'whatever', + ' Role __nonesuch does not exist' +); + +-- Test default description with no permissions. +SELECT * FROM check_test( + sequence_privs_are( 'ha', 'someseq', '__nonesuch', '{}'::text[] ), + false, + 'sequence_privs_are(sch, seq, role, no privs)', + 'Role __nonesuch should be granted no privileges on sequence ha.someseq' , + ' Role __nonesuch does not exist' +); + +SELECT * FROM check_test( + sequence_privs_are( 'someseq', '__nonesuch', '{}'::text[] ), + false, + 'sequence_privs_are(seq, role, no privs)', + 'Role __nonesuch should be granted no privileges on sequence someseq' , + ' Role __nonesuch does not exist' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 9808e121c87ca657d4655be47f9547398ee1e97f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Jan 2013 15:08:33 -0800 Subject: [PATCH 0713/1195] Add `any_column_privs_are()`. --- Changes | 1 + doc/pgtap.mmd | 73 +++++++++++++++++++++++++ sql/pgtap.sql.in | 84 ++++++++++++++++++++++++++++ test/sql/privs.sql | 133 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 290 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index dc12858f5c9d..69a93be3d4ea 100644 --- a/Changes +++ b/Changes @@ -37,6 +37,7 @@ Revision history for pgTAP + `schema_privs_are()` + `table_privs_are()` + `sequence_privs_are()` + + `any_column_privs_are()` + `function_privs_are()` + `language_privs_are()` diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 4182abca7882..532416994c9a 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5866,6 +5866,79 @@ something like: # Failed test 17: "Role slim should be granted SELECT on seq_widgets" # Role slim does not exist +### `any_column_privs_are()` + + SELECT any_column_privs_are ( :schema, :table, :role, :privileges, :description ); + SELECT any_column_privs_are ( :schema, :table, :role, :privileges ); + SELECT any_column_privs_are ( :table, :role, :privileges, :description ); + SELECT any_column_privs_are ( :table, :role, :privileges ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the table. + +`:table` +: Name of a table. + +`:role` +: Name of a user or group role. + +`:privileges` +: An array of table privileges the role should be granted to the table. + +`:description` +: A short description of the test. + +Tests the privileges granted to access one or more of the columns in a table. +The available column privileges are: + +* INSERT +* REFERENCES +* SELECT +* UPDATE + +Note that column privileges were added in PostgreSQL 8.4, so this function +will likley throw an exception on earlier versions. + +If the `:description` argument is omitted, an appropriate description will be +created. Examples: + + SELECT any_column_privs_are( + 'public', 'frobulate', 'fred', ARRAY['SELECT', 'UPDATE'], + 'Fred should be able to select and update columns in frobulate' + ); + SELECT any_column_privs_are( 'widgets', 'slim', ARRAY['INSERT', 'UPDATE'] ); + +If the role is granted permissions other than those specified, the diagnostics +will list the extra permissions, like so: + + # Failed test 14: "Role bob should be granted SELECT on columns in widgets" + # Extra privileges: + # INSERT + # UPDATE + +Likewise if the role is not granted some of the specified permissions on the +table: + + # Failed test 15: "Role kurk should be granted SELECT, INSERT, UPDATE on columns in widgets" + # Missing privileges: + # INSERT + # UPDATE + +In the event that the test fails because the table in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Role slim should be granted SELECT on columns in widgets" + # Table widgets does not exist + +If the test fails because the role does not exist, the diagnostics will look +something like: + + # Failed test 17: "Role slim should be granted SELECT on columns in widgets" + # Role slim does not exist + ### `function_privs_are()` SELECT function_privs_are ( :schema, :function, :args, :role, :privileges, :description ); diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index e97e4af482c6..3dde6bc7dc4a 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -8410,3 +8410,87 @@ RETURNS TEXT AS $$ ); $$ 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; diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 78d1db4abab1..2c86c2ab5a76 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -12,7 +12,7 @@ SET search_path = ha,public,pg_catalog; RESET client_min_messages; /****************************************************************************/ --- Test table_privilege_is(). +-- Test table_privs_are(). SELECT * FROM check_test( table_privs_are( 'ha', 'sometab', current_user, _table_privs(), 'whatever' ), @@ -740,6 +740,137 @@ SELECT * FROM check_test( ' Role __nonesuch does not exist' ); +/****************************************************************************/ +-- Test any_column_privs_are(). + +SELECT * FROM check_test( + any_column_privs_are( 'ha', 'sometab', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), + true, + 'any_column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + any_column_privs_are( 'ha', 'sometab', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'] ), + true, + 'any_column_privs_are(sch, tab, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') || ' on any column in ha.sometab' , + '' +); + +SELECT * FROM check_test( + any_column_privs_are( 'sometab', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), + true, + 'any_column_privs_are(tab, role, privs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + any_column_privs_are( 'sometab', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'] ), + true, + 'any_column_privs_are(tab, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') || ' on any column in sometab' , + '' +); + +CREATE OR REPLACE FUNCTION run_extra_fails() RETURNS SETOF TEXT LANGUAGE plpgsql AS $$ +DECLARE + allowed_privs TEXT[]; + test_privs TEXT[]; + missing_privs TEXT[]; + tap record; + last_index INTEGER; +BEGIN + -- Test table failure. + allowed_privs := ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE']; + last_index := array_upper(allowed_privs, 1); + FOR i IN 1..last_index - 2 LOOP + test_privs := test_privs || allowed_privs[i]; + END LOOP; + FOR i IN last_index - 1..last_index LOOP + missing_privs := missing_privs || allowed_privs[i]; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + any_column_privs_are( 'ha', 'sometab', current_user, test_privs, 'whatever' ), + false, + 'any_column_privs_are(sch, tab, role, some privs, desc)', + 'whatever', + ' Extra privileges: + ' || array_to_string(missing_privs, E'\n ') + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + any_column_privs_are( 'sometab', current_user, test_privs, 'whatever' ), + false, + 'any_column_privs_are(tab, role, some privs, desc)', + 'whatever', + ' Extra privileges: + ' || array_to_string(missing_privs, E'\n ') + ) AS b LOOP RETURN NEXT tap.b; END LOOP; +END; +$$; + +SELECT * FROM run_extra_fails(); + +SELECT * FROM check_test( + any_column_privs_are( 'ha', 'sometab', '__someone_else', ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), + false, + 'any_column_privs_are(sch, tab, other, privs, desc)', + 'whatever', + ' Missing privileges: + ' || array_to_string(ARRAY['REFERENCES'], E'\n ') +); + +SELECT * FROM check_test( + any_column_privs_are( 'ha', 'sometab', '__someone_else', ARRAY[ + 'SELECT', 'INSERT', 'UPDATE' + ], 'whatever'), + true, + 'any_column_privs_are(sch, tab, other, privs, desc)', + 'whatever', + '' +); + +-- Try a non-existent table. +SELECT * FROM check_test( + any_column_privs_are( 'ha', 'nonesuch', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), + false, + 'any_column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + ' Table ha.nonesuch does not exist' +); + +-- Try a non-existent user. +SELECT * FROM check_test( + any_column_privs_are( 'ha', 'sometab', '__nonesuch', ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), + false, + 'any_column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + ' Role __nonesuch does not exist' +); + +-- Test default description with no permissions. +SELECT * FROM check_test( + any_column_privs_are( 'ha', 'sometab', '__nonesuch', '{}'::text[] ), + false, + 'any_column_privs_are(sch, tab, role, no privs)', + 'Role __nonesuch should be granted no privileges on any column in ha.sometab' , + ' Role __nonesuch does not exist' +); + +SELECT * FROM check_test( + any_column_privs_are( 'sometab', '__nonesuch', '{}'::text[] ), + false, + 'any_column_privs_are(tab, role, no privs)', + 'Role __nonesuch should be granted no privileges on any column in sometab' , + ' Role __nonesuch does not exist' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From da0bf9813e0c52849a2a9873e5dbb32639b3efda Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Jan 2013 15:55:19 -0800 Subject: [PATCH 0714/1195] Add column_privs_are()`. Ref #34. --- Changes | 1 + doc/pgtap.mmd | 82 +++++++++++++++++++++++++++ sql/pgtap.sql.in | 93 ++++++++++++++++++++++++++++++- test/sql/privs.sql | 134 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 309 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 69a93be3d4ea..1a20beb0d998 100644 --- a/Changes +++ b/Changes @@ -38,6 +38,7 @@ Revision history for pgTAP + `table_privs_are()` + `sequence_privs_are()` + `any_column_privs_are()` + + `column_privs_are()` + `function_privs_are()` + `language_privs_are()` diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 532416994c9a..56d29c93bf8c 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5939,6 +5939,88 @@ something like: # Failed test 17: "Role slim should be granted SELECT on columns in widgets" # Role slim does not exist +### `column_privs_are()` + + SELECT column_privs_are ( :schema, :table, :column, :role, :privileges, :description ); + SELECT column_privs_are ( :schema, :table, :column, :role, :privileges ); + SELECT column_privs_are ( :table, :column, :role, :privileges, :description ); + SELECT column_privs_are ( :table, :column, :role, :privileges ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the table. + +`:table` +: Name of a table. + +`:column` +: Name of a column. + +`:role` +: Name of a user or group role. + +`:privileges` +: An array of column privileges the role should be granted to the column. + +`:description` +: A short description of the test. + +Tests the privileges granted to a role to access a single column. The +available column privileges are: + +* INSERT +* REFERENCES +* SELECT +* UPDATE + +Note that column privileges were added in PostgreSQL 8.4, so this function +will likley throw an exception on earlier versions. + +If the `:description` argument is omitted, an appropriate description will be +created. Examples: + + SELECT column_privs_are( + 'public', 'frobulate', 'id', 'fred', ARRAY['SELECT', 'UPDATE'], + 'Fred should be able to select and update frobulate.id' + ); + SELECT column_privs_are( 'widgets', 'name', 'slim', ARRAY['INSERT', 'UPDATE'] ); + +If the role is granted permissions other than those specified, the diagnostics +will list the extra permissions, like so: + + # Failed test 14: "Role bob should be granted SELECT on widgets.foo" + # Extra privileges: + # INSERT + # UPDATE + +Likewise if the role is not granted some of the specified permissions on the +table: + + # Failed test 15: "Role kurk should be granted SELECT, INSERT, UPDATE on widgets.foo" + # Missing privileges: + # INSERT + # UPDATE + +In the event that the test fails because the table in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Role slim should be granted SELECT on widgets.foo" + # Table widgets does not exist + +If the test fails because the column does not actually exist or is not +visible, the diagnostics will tell you: + + # Failed test 17: "Role slim should be granted SELECT on gadgets.foo" + # Column gadgets.foo does not exist + +If the test fails because the role does not exist, the diagnostics will look +something like: + + # Failed test 18: "Role slim should be granted SELECT on gadgets.foo" + # Role slim does not exist + ### `function_privs_are()` SELECT function_privs_are ( :schema, :function, :args, :role, :privileges, :description ); diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 3dde6bc7dc4a..1be1be05ff75 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -8002,7 +8002,7 @@ RETURNS TEXT AS $$ $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) + || ' on table ' || quote_ident($1) || '.' || quote_ident($2) ); $$ LANGUAGE SQL; @@ -8494,3 +8494,94 @@ RETURNS TEXT AS $$ || ' on any column in ' || quote_ident($1) ); $$ LANGUAGE SQL; + +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; diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 2c86c2ab5a76..a6d4194c1651 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -871,6 +871,140 @@ SELECT * FROM check_test( ' Role __nonesuch does not exist' ); +/****************************************************************************/ +-- Test column_privs_are(). + +SELECT * FROM check_test( + column_privs_are( 'ha', 'sometab', 'id', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), + true, + 'column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + column_privs_are( 'ha', 'sometab', 'id', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'] ), + true, + 'column_privs_are(sch, tab, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') || ' on column ha.sometab.id' , + '' +); + +SELECT * FROM check_test( + column_privs_are( 'sometab', 'id', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), + true, + 'column_privs_are(tab, role, privs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + column_privs_are( 'sometab', 'id', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'] ), + true, + 'column_privs_are(tab, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') || ' on column sometab.id' , + '' +); + +CREATE OR REPLACE FUNCTION run_extra_fails() RETURNS SETOF TEXT LANGUAGE plpgsql AS $$ +DECLARE + allowed_privs TEXT[]; + test_privs TEXT[]; + missing_privs TEXT[]; + tap record; + last_index INTEGER; +BEGIN + -- Test table failure. + allowed_privs := ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE']; + last_index := array_upper(allowed_privs, 1); + FOR i IN 1..last_index - 2 LOOP + test_privs := test_privs || allowed_privs[i]; + END LOOP; + FOR i IN last_index - 1..last_index LOOP + missing_privs := missing_privs || allowed_privs[i]; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + column_privs_are( 'ha', 'sometab', 'id', current_user, test_privs, 'whatever' ), + false, + 'column_privs_are(sch, tab, role, some privs, desc)', + 'whatever', + ' Extra privileges: + ' || array_to_string(missing_privs, E'\n ') + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + column_privs_are( 'sometab', 'id', current_user, test_privs, 'whatever' ), + false, + 'column_privs_are(tab, role, some privs, desc)', + 'whatever', + ' Extra privileges: + ' || array_to_string(missing_privs, E'\n ') + ) AS b LOOP RETURN NEXT tap.b; END LOOP; +END; +$$; + +SELECT * FROM run_extra_fails(); + +SELECT * FROM check_test( + column_privs_are( 'ha', 'sometab', 'id', '__someone_else', ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), + false, + 'column_privs_are(sch, tab, other, privs, desc)', + 'whatever', + ' Missing privileges: + ' || array_to_string(ARRAY['REFERENCES'], E'\n ') +); + +-- Grant them some permission. +GRANT SELECT, INSERT, UPDATE (id) ON ha.sometab TO __someone_else; + +SELECT * FROM check_test( + column_privs_are( 'ha', 'sometab', 'id', '__someone_else', ARRAY[ + 'SELECT', 'INSERT', 'UPDATE' + ], 'whatever'), + true, + 'column_privs_are(sch, tab, other, privs, desc)', + 'whatever', + '' +); + +-- Try a non-existent table. +SELECT * FROM check_test( + column_privs_are( 'ha', 'nonesuch', 'id', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), + false, + 'column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + ' Table ha.nonesuch does not exist' +); + +-- Try a non-existent user. +SELECT * FROM check_test( + column_privs_are( 'ha', 'sometab', 'id', '__nonesuch', ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), + false, + 'column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + ' Role __nonesuch does not exist' +); + +-- Test default description with no permissions. +SELECT * FROM check_test( + column_privs_are( 'ha', 'sometab', 'id', '__nonesuch', '{}'::text[] ), + false, + 'column_privs_are(sch, tab, role, no privs)', + 'Role __nonesuch should be granted no privileges on column ha.sometab.id' , + ' Role __nonesuch does not exist' +); + +SELECT * FROM check_test( + column_privs_are( 'sometab', 'id', '__nonesuch', '{}'::text[] ), + false, + 'column_privs_are(tab, role, no privs)', + 'Role __nonesuch should be granted no privileges on column sometab.id' , + ' Role __nonesuch does not exist' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 47e5272fdaa7043a16a20cfc38776d15700d15d3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Jan 2013 16:27:59 -0800 Subject: [PATCH 0715/1195] Add `fdw_privs_are()`. Ref #34. --- Changes | 1 + doc/pgtap.mmd | 63 ++++++++++++++++++++++ sql/pgtap.sql.in | 68 ++++++++++++++++++++++++ test/sql/privs.sql | 129 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 261 insertions(+) diff --git a/Changes b/Changes index 1a20beb0d998..da973e58f43f 100644 --- a/Changes +++ b/Changes @@ -41,6 +41,7 @@ Revision history for pgTAP + `column_privs_are()` + `function_privs_are()` + `language_privs_are()` + + `fdw_privs_are()` 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 56d29c93bf8c..c4a17e7d100f 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -6149,6 +6149,69 @@ something like: # Failed test 17: "Role slim should be granted USAGE on pllolcode" # Role slim does not exist +### `fdw_privs_are()` + + SELECT fdw_privs_are ( :fdw, :role, :privileges, :description ); + SELECT fdw_privs_are ( :fdw, :role, :privileges ); + +**Parameters** + +`:fdw` +: Name of a foreign data wrapper. + +`:role` +: Name of a user or group role. + +`:privileges` +: An array of table privileges the role should be granted to the foreign data wrapper. + +`:description` +: A short description of the test. + +Tests the privileges granted to a role to access a foreign data wrapper. The +available function privileges are: + +* USAGE + +Note that foreign data wrapper privileges were added in PostgreSQL 8.4, so +this function will likley throw an exception on earlier versions. + +If the `:description` argument is omitted, an appropriate description will be +created. Examples: + + SELECT fdw_privs_are( + 'oracle', 'fred', ARRAY['USAGE'], + 'Fred should be granted USAGE on fdw "oracle"' + ); + SELECT fdw_privs_are( 'log_csv', ARRAY['USAGE'] ); + +If the role is granted permissions other than those specified, the diagnostics +will list the extra permissions, like so: + + # Failed test 14: "Role bob should be granted no privileges on odbc" + # Extra privileges: + # USAGE + +Likewise if the role is not granted some of the specified permissions on the +fdw: + + # Failed test 15: "Role kurk should be granted USAGE on odbc" + # Missing privileges: + # USAGE + +In the event that the test fails because the fdw in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Role slim should be granted USAGE on FDW sqlite" + # FDW sqlite plr does not exist + +If the test fails because the role does not exist, the diagnostics will look +something like: + + # Failed test 17: "Role slim should be granted USAGE on sqlite" + # Role slim does not exist + No Test for the Wicked ====================== diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 1be1be05ff75..31fd661dddd7 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -8585,3 +8585,71 @@ RETURNS TEXT AS $$ || ' 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, quote_ident($1) ); +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_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; diff --git a/test/sql/privs.sql b/test/sql/privs.sql index a6d4194c1651..dcb532dd7912 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -1005,6 +1005,135 @@ SELECT * FROM check_test( ' Role __nonesuch does not exist' ); +CREATE FUNCTION test_fdw() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() >= 80400 THEN + CREATE FOREIGN DATA WRAPPER dummy; + + FOR tap IN SELECT * FROM check_test( + fdw_privs_are( 'dummy', current_user, '{USAGE}', 'whatever' ), + true, + 'fdw_privs_are(lang, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fdw_privs_are( 'dummy', current_user, '{USAGE}' ), + true, + 'fdw_privs_are(lang, role, privs, desc)', + 'Role ' || current_user || ' should be granted USAGE on FDW dummy', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + + -- Try nonexistent fdw. + FOR tap IN SELECT * FROM check_test( + fdw_privs_are( '__nonesuch', current_user, '{USAGE}', 'whatever' ), + false, + 'fdw_privs_are(non-lang, role, privs, desc)', + 'whatever', + ' FDW __nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + + -- Try nonexistent user. + FOR tap IN SELECT * FROM check_test( + fdw_privs_are( 'dummy', '__noone', '{USAGE}', 'whatever' ), + false, + 'fdw_privs_are(lang, non-role, privs, desc)', + 'whatever', + ' Role __noone does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + + -- Try another user. + FOR tap IN SELECT * FROM check_test( + fdw_privs_are( 'dummy', '__someone_else', '{USAGE}', 'whatever' ), + false, + 'fdw_privs_are(lang, ungranted, privs, desc)', + 'whatever', + ' Missing privileges: + USAGE' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- Try testing default description for no permissions. + FOR tap IN SELECT * FROM check_test( + fdw_privs_are( 'dummy', '__someone_else', '{}'::text[] ), + true, + 'fdw_privs_are(lang, role, no privs)', + 'Role __someone_else should be granted no privileges on FDW dummy', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + ELSE + -- Fake it with language_privs_are(). + FOR tap IN SELECT * FROM check_test( + language_privs_are( 'plpgsql', current_user, '{USAGE}', 'whatever' ), + true, + 'fdw_privs_are(lang, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + language_privs_are( 'plpgsql', current_user, '{USAGE}' ), + true, + 'fdw_privs_are(lang, role, privs, desc)', + 'Role ' || current_user || ' should be granted USAGE on language plpgsql', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + + -- Try nonexistent fdw. + FOR tap IN SELECT * FROM check_test( + language_privs_are( '__nonesuch', current_user, '{USAGE}', 'whatever' ), + false, + 'fdw_privs_are(non-lang, role, privs, desc)', + 'whatever', + ' Language __nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + + -- Try nonexistent user. + FOR tap IN SELECT * FROM check_test( + language_privs_are( 'plpgsql', '__noone', '{USAGE}', 'whatever' ), + false, + 'fdw_privs_are(lang, non-role, privs, desc)', + 'whatever', + ' Role __noone does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + + -- Try another user. + FOR tap IN SELECT * FROM check_test( + language_privs_are( 'plpgsql', '__someone_else', '{USAGE}', 'whatever' ), + false, + 'fdw_privs_are(lang, ungranted, privs, desc)', + 'whatever', + ' Missing privileges: + USAGE' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- Try testing default description for no permissions. + FOR tap IN SELECT * FROM check_test( + language_privs_are( 'plpgsql', '__someone_else', '{}'::text[] ), + true, + 'fdw_privs_are(lang, role, no privs)', + 'Role __someone_else should be granted no privileges on language plpgsql', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + END IF; + RETURN; +END; +$$ LANGUAGE plpgsql; + +SELECT * FROM test_fdw(); + + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 73146ab9fc01341fa1aa4c8487e2399d6c516313 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Jan 2013 16:37:16 -0800 Subject: [PATCH 0716/1195] Add `server_privs_are()`. --- Changes | 1 + doc/pgtap.mmd | 65 ++++++++++++++++++- sql/pgtap.sql.in | 49 +++++++++++++++ test/sql/privs.sql | 152 +++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 254 insertions(+), 13 deletions(-) diff --git a/Changes b/Changes index da973e58f43f..ff6147dd7db1 100644 --- a/Changes +++ b/Changes @@ -42,6 +42,7 @@ Revision history for pgTAP + `function_privs_are()` + `language_privs_are()` + `fdw_privs_are()` + + `server_privs_are()` 0.91.1 2012-09-11T00:26:52Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index c4a17e7d100f..1c91be29dbaa 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -6204,7 +6204,7 @@ actually exist or is not visible, you will see an appropriate diagnostic such as: # Failed test 16: "Role slim should be granted USAGE on FDW sqlite" - # FDW sqlite plr does not exist + # FDW sqlite does not exist If the test fails because the role does not exist, the diagnostics will look something like: @@ -6212,6 +6212,69 @@ something like: # Failed test 17: "Role slim should be granted USAGE on sqlite" # Role slim does not exist +### `server_privs_are()` + + SELECT server_privs_are ( :server, :role, :privileges, :description ); + SELECT server_privs_are ( :server, :role, :privileges ); + +**Parameters** + +`:server` +: Name of a server. + +`:role` +: Name of a user or group role. + +`:privileges` +: An array of table privileges the role should be granted to the server. + +`:description` +: A short description of the test. + +Tests the privileges granted to a role to access a server. The available +function privileges are: + +* USAGE + +Note that server privileges were added in PostgreSQL 8.4, so this function +will likley throw an exception on earlier versions. + +If the `:description` argument is omitted, an appropriate description will be +created. Examples: + + SELECT server_privs_are( + 'otherdb', 'fred', ARRAY['USAGE'], + 'Fred should be granted USAGE on server "otherdb"' + ); + SELECT server_privs_are( 'myserv', ARRAY['USAGE'] ); + +If the role is granted permissions other than those specified, the diagnostics +will list the extra permissions, like so: + + # Failed test 14: "Role bob should be granted no privileges on myserv" + # Extra privileges: + # USAGE + +Likewise if the role is not granted some of the specified permissions on the +server: + + # Failed test 15: "Role kurk should be granted USAGE on oltp" + # Missing privileges: + # USAGE + +In the event that the test fails because the server in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Role slim should be granted USAGE on server oltp" + # server oltp does not exist + +If the test fails because the role does not exist, the diagnostics will look +something like: + + # Failed test 17: "Role slim should be granted USAGE on oltp" + # Role slim does not exist + No Test for the Wicked ====================== diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 31fd661dddd7..5544cb36afd7 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -8653,3 +8653,52 @@ EXCEPTION WHEN undefined_object THEN RETURN '{undefined_role}'; END; $$ LANGUAGE plpgsql; + +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, quote_ident($1) ); +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; + diff --git a/test/sql/privs.sql b/test/sql/privs.sql index dcb532dd7912..03931183663a 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -1015,7 +1015,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( fdw_privs_are( 'dummy', current_user, '{USAGE}', 'whatever' ), true, - 'fdw_privs_are(lang, role, privs, desc)', + 'fdw_privs_are(fdw, role, privs, desc)', 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1023,7 +1023,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( fdw_privs_are( 'dummy', current_user, '{USAGE}' ), true, - 'fdw_privs_are(lang, role, privs, desc)', + 'fdw_privs_are(fdw, role, privs, desc)', 'Role ' || current_user || ' should be granted USAGE on FDW dummy', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1033,7 +1033,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( fdw_privs_are( '__nonesuch', current_user, '{USAGE}', 'whatever' ), false, - 'fdw_privs_are(non-lang, role, privs, desc)', + 'fdw_privs_are(non-fdw, role, privs, desc)', 'whatever', ' FDW __nonesuch does not exist' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1043,7 +1043,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( fdw_privs_are( 'dummy', '__noone', '{USAGE}', 'whatever' ), false, - 'fdw_privs_are(lang, non-role, privs, desc)', + 'fdw_privs_are(fdw, non-role, privs, desc)', 'whatever', ' Role __noone does not exist' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1053,7 +1053,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( fdw_privs_are( 'dummy', '__someone_else', '{USAGE}', 'whatever' ), false, - 'fdw_privs_are(lang, ungranted, privs, desc)', + 'fdw_privs_are(fdw, ungranted, privs, desc)', 'whatever', ' Missing privileges: USAGE' @@ -1063,7 +1063,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( fdw_privs_are( 'dummy', '__someone_else', '{}'::text[] ), true, - 'fdw_privs_are(lang, role, no privs)', + 'fdw_privs_are(fdw, role, no privs)', 'Role __someone_else should be granted no privileges on FDW dummy', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1073,7 +1073,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( language_privs_are( 'plpgsql', current_user, '{USAGE}', 'whatever' ), true, - 'fdw_privs_are(lang, role, privs, desc)', + 'fdw_privs_are(fdw, role, privs, desc)', 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1081,7 +1081,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( language_privs_are( 'plpgsql', current_user, '{USAGE}' ), true, - 'fdw_privs_are(lang, role, privs, desc)', + 'fdw_privs_are(fdw, role, privs, desc)', 'Role ' || current_user || ' should be granted USAGE on language plpgsql', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1091,7 +1091,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( language_privs_are( '__nonesuch', current_user, '{USAGE}', 'whatever' ), false, - 'fdw_privs_are(non-lang, role, privs, desc)', + 'fdw_privs_are(non-fdw, role, privs, desc)', 'whatever', ' Language __nonesuch does not exist' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1101,7 +1101,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( language_privs_are( 'plpgsql', '__noone', '{USAGE}', 'whatever' ), false, - 'fdw_privs_are(lang, non-role, privs, desc)', + 'fdw_privs_are(fdw, non-role, privs, desc)', 'whatever', ' Role __noone does not exist' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1111,7 +1111,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( language_privs_are( 'plpgsql', '__someone_else', '{USAGE}', 'whatever' ), false, - 'fdw_privs_are(lang, ungranted, privs, desc)', + 'fdw_privs_are(fdw, ungranted, privs, desc)', 'whatever', ' Missing privileges: USAGE' @@ -1121,7 +1121,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( language_privs_are( 'plpgsql', '__someone_else', '{}'::text[] ), true, - 'fdw_privs_are(lang, role, no privs)', + 'fdw_privs_are(fdw, role, no privs)', 'Role __someone_else should be granted no privileges on language plpgsql', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1133,6 +1133,134 @@ $$ LANGUAGE plpgsql; SELECT * FROM test_fdw(); +CREATE FUNCTION test_server() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() >= 80400 THEN + CREATE SERVER foo FOREIGN DATA WRAPPER dummy; + + FOR tap IN SELECT * FROM check_test( + server_privs_are( 'foo', current_user, '{USAGE}', 'whatever' ), + true, + 'server_privs_are(server, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + server_privs_are( 'foo', current_user, '{USAGE}' ), + true, + 'server_privs_are(server, role, privs, desc)', + 'Role ' || current_user || ' should be granted USAGE on server foo', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + + -- Try nonexistent server. + FOR tap IN SELECT * FROM check_test( + server_privs_are( '__nonesuch', current_user, '{USAGE}', 'whatever' ), + false, + 'server_privs_are(non-server, role, privs, desc)', + 'whatever', + ' Server __nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + + -- Try nonexistent user. + FOR tap IN SELECT * FROM check_test( + server_privs_are( 'foo', '__noone', '{USAGE}', 'whatever' ), + false, + 'server_privs_are(server, non-role, privs, desc)', + 'whatever', + ' Role __noone does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + + -- Try another user. + FOR tap IN SELECT * FROM check_test( + server_privs_are( 'foo', '__someone_else', '{USAGE}', 'whatever' ), + false, + 'server_privs_are(server, ungranted, privs, desc)', + 'whatever', + ' Missing privileges: + USAGE' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- Try testing default description for no permissions. + FOR tap IN SELECT * FROM check_test( + server_privs_are( 'foo', '__someone_else', '{}'::text[] ), + true, + 'server_privs_are(server, role, no privs)', + 'Role __someone_else should be granted no privileges on server foo', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + ELSE + -- Fake it with language_privs_are(). + FOR tap IN SELECT * FROM check_test( + language_privs_are( 'plpgsql', current_user, '{USAGE}', 'whatever' ), + true, + 'server_privs_are(server, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + language_privs_are( 'plpgsql', current_user, '{USAGE}' ), + true, + 'server_privs_are(server, role, privs, desc)', + 'Role ' || current_user || ' should be granted USAGE on language plpgsql', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + + -- Try nonexistent fdw. + FOR tap IN SELECT * FROM check_test( + language_privs_are( '__nonesuch', current_user, '{USAGE}', 'whatever' ), + false, + 'server_privs_are(non-server, role, privs, desc)', + 'whatever', + ' Language __nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + + -- Try nonexistent user. + FOR tap IN SELECT * FROM check_test( + language_privs_are( 'plpgsql', '__noone', '{USAGE}', 'whatever' ), + false, + 'server_privs_are(server, non-role, privs, desc)', + 'whatever', + ' Role __noone does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + + -- Try another user. + FOR tap IN SELECT * FROM check_test( + language_privs_are( 'plpgsql', '__someone_else', '{USAGE}', 'whatever' ), + false, + 'server_privs_are(server, ungranted, privs, desc)', + 'whatever', + ' Missing privileges: + USAGE' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- Try testing default description for no permissions. + FOR tap IN SELECT * FROM check_test( + language_privs_are( 'plpgsql', '__someone_else', '{}'::text[] ), + true, + 'server_privs_are(server, role, no privs)', + 'Role __someone_else should be granted no privileges on language plpgsql', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + END IF; + RETURN; +END; +$$ LANGUAGE plpgsql; + +SELECT * FROM test_server(); + /****************************************************************************/ -- Finish the tests and clean up. From 1f9c2ae17e99f888b760362a1c26fdcdfeae619f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Jan 2013 16:39:02 -0800 Subject: [PATCH 0717/1195] Update priv test expected output. --- test/expected/privs.out | 185 +++++++++++++++++++++++++++++++++++++++- test/sql/privs.sql | 4 +- 2 files changed, 186 insertions(+), 3 deletions(-) diff --git a/test/expected/privs.out b/test/expected/privs.out index d568c9946281..01c513a18de6 100644 --- a/test/expected/privs.out +++ b/test/expected/privs.out @@ -1,4 +1,5 @@ \unset ECHO +1..304 ok 1 - table_privs_are(sch, tab, role, privs, desc) should pass ok 2 - table_privs_are(sch, tab, role, privs, desc) should have the proper description ok 3 - table_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics @@ -120,4 +121,186 @@ ok 118 - language_privs_are(lang, ungranted, privs, desc) should have the proper ok 119 - language_privs_are(lang, role, no privs) should pass ok 120 - language_privs_are(lang, role, no privs) should have the proper description ok 121 - language_privs_are(lang, role, no privs) should have the proper diagnostics -1..121 +ok 122 - schema_privs_are(schema, role, privs, desc) should pass +ok 123 - schema_privs_are(schema, role, privs, desc) should have the proper description +ok 124 - schema_privs_are(schema, role, privs, desc) should have the proper diagnostics +ok 125 - schema_privs_are(schema, role, privs, desc) should pass +ok 126 - schema_privs_are(schema, role, privs, desc) should have the proper description +ok 127 - schema_privs_are(schema, role, privs, desc) should have the proper diagnostics +ok 128 - schema_privs_are(non-schema, role, privs, desc) should fail +ok 129 - schema_privs_are(non-schema, role, privs, desc) should have the proper description +ok 130 - schema_privs_are(non-schema, role, privs, desc) should have the proper diagnostics +ok 131 - schema_privs_are(schema, non-role, privs, desc) should fail +ok 132 - schema_privs_are(schema, non-role, privs, desc) should have the proper description +ok 133 - schema_privs_are(schema, non-role, privs, desc) should have the proper diagnostics +ok 134 - schema_privs_are(schema, ungranted, privs, desc) should fail +ok 135 - schema_privs_are(schema, ungranted, privs, desc) should have the proper description +ok 136 - schema_privs_are(schema, ungranted, privs, desc) should have the proper diagnostics +ok 137 - schema_privs_are(schema, ungranted, privs, desc) should fail +ok 138 - schema_privs_are(schema, ungranted, privs, desc) should have the proper description +ok 139 - schema_privs_are(schema, ungranted, privs, desc) should have the proper diagnostics +ok 140 - schema_privs_are(schema, non-role, no privs) should fail +ok 141 - schema_privs_are(schema, non-role, no privs) should have the proper description +ok 142 - schema_privs_are(schema, non-role, no privs) should have the proper diagnostics +ok 143 - tablespace_privs_are(tablespace, role, privs, desc) should pass +ok 144 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper description +ok 145 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper diagnostics +ok 146 - tablespace_privs_are(tablespace, role, privs, desc) should pass +ok 147 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper description +ok 148 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper diagnostics +ok 149 - tablespace_privs_are(non-tablespace, role, privs, desc) should fail +ok 150 - tablespace_privs_are(non-tablespace, role, privs, desc) should have the proper description +ok 151 - tablespace_privs_are(non-tablespace, role, privs, desc) should have the proper diagnostics +ok 152 - tablespace_privs_are(tablespace, non-role, privs, desc) should fail +ok 153 - tablespace_privs_are(tablespace, non-role, privs, desc) should have the proper description +ok 154 - tablespace_privs_are(tablespace, non-role, privs, desc) should have the proper diagnostics +ok 155 - tablespace_privs_are(tablespace, ungranted, privs, desc) should fail +ok 156 - tablespace_privs_are(tablespace, ungranted, privs, desc) should have the proper description +ok 157 - tablespace_privs_are(tablespace, ungranted, privs, desc) should have the proper diagnostics +ok 158 - tablespace_privs_are(tablespace, role, no privs) should pass +ok 159 - tablespace_privs_are(tablespace, role, no privs) should have the proper description +ok 160 - tablespace_privs_are(tablespace, role, no privs) should have the proper diagnostics +ok 161 - sequence_privs_are(sch, seq, role, privs, desc) should pass +ok 162 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description +ok 163 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics +ok 164 - sequence_privs_are(sch, seq, role, privs) should pass +ok 165 - sequence_privs_are(sch, seq, role, privs) should have the proper description +ok 166 - sequence_privs_are(sch, seq, role, privs) should have the proper diagnostics +ok 167 - sequence_privs_are(seq, role, privs, desc) should pass +ok 168 - sequence_privs_are(seq, role, privs, desc) should have the proper description +ok 169 - sequence_privs_are(seq, role, privs, desc) should have the proper diagnostics +ok 170 - sequence_privs_are(seq, role, privs) should pass +ok 171 - sequence_privs_are(seq, role, privs) should have the proper description +ok 172 - sequence_privs_are(seq, role, privs) should have the proper diagnostics +ok 173 - sequence_privs_are(sch, seq, role, some privs, desc) should fail +ok 174 - sequence_privs_are(sch, seq, role, some privs, desc) should have the proper description +ok 175 - sequence_privs_are(sch, seq, role, some privs, desc) should have the proper diagnostics +ok 176 - sequence_privs_are(seq, role, some privs, desc) should fail +ok 177 - sequence_privs_are(seq, role, some privs, desc) should have the proper description +ok 178 - sequence_privs_are(seq, role, some privs, desc) should have the proper diagnostics +ok 179 - sequence_privs_are(sch, seq, other, privs, desc) should fail +ok 180 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper description +ok 181 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper diagnostics +ok 182 - sequence_privs_are(sch, seq, other, privs, desc) should pass +ok 183 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper description +ok 184 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper diagnostics +ok 185 - sequence_privs_are(sch, seq, role, privs, desc) should fail +ok 186 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description +ok 187 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics +ok 188 - sequence_privs_are(sch, seq, role, privs, desc) should fail +ok 189 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description +ok 190 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics +ok 191 - sequence_privs_are(sch, seq, role, no privs) should fail +ok 192 - sequence_privs_are(sch, seq, role, no privs) should have the proper description +ok 193 - sequence_privs_are(sch, seq, role, no privs) should have the proper diagnostics +ok 194 - sequence_privs_are(seq, role, no privs) should fail +ok 195 - sequence_privs_are(seq, role, no privs) should have the proper description +ok 196 - sequence_privs_are(seq, role, no privs) should have the proper diagnostics +ok 197 - any_column_privs_are(sch, tab, role, privs, desc) should pass +ok 198 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 199 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 200 - any_column_privs_are(sch, tab, role, privs) should pass +ok 201 - any_column_privs_are(sch, tab, role, privs) should have the proper description +ok 202 - any_column_privs_are(sch, tab, role, privs) should have the proper diagnostics +ok 203 - any_column_privs_are(tab, role, privs, desc) should pass +ok 204 - any_column_privs_are(tab, role, privs, desc) should have the proper description +ok 205 - any_column_privs_are(tab, role, privs, desc) should have the proper diagnostics +ok 206 - any_column_privs_are(tab, role, privs) should pass +ok 207 - any_column_privs_are(tab, role, privs) should have the proper description +ok 208 - any_column_privs_are(tab, role, privs) should have the proper diagnostics +ok 209 - any_column_privs_are(sch, tab, role, some privs, desc) should fail +ok 210 - any_column_privs_are(sch, tab, role, some privs, desc) should have the proper description +ok 211 - any_column_privs_are(sch, tab, role, some privs, desc) should have the proper diagnostics +ok 212 - any_column_privs_are(tab, role, some privs, desc) should fail +ok 213 - any_column_privs_are(tab, role, some privs, desc) should have the proper description +ok 214 - any_column_privs_are(tab, role, some privs, desc) should have the proper diagnostics +ok 215 - any_column_privs_are(sch, tab, other, privs, desc) should fail +ok 216 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 217 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics +ok 218 - any_column_privs_are(sch, tab, other, privs, desc) should pass +ok 219 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 220 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics +ok 221 - any_column_privs_are(sch, tab, role, privs, desc) should fail +ok 222 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 223 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 224 - any_column_privs_are(sch, tab, role, privs, desc) should fail +ok 225 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 226 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 227 - any_column_privs_are(sch, tab, role, no privs) should fail +ok 228 - any_column_privs_are(sch, tab, role, no privs) should have the proper description +ok 229 - any_column_privs_are(sch, tab, role, no privs) should have the proper diagnostics +ok 230 - any_column_privs_are(tab, role, no privs) should fail +ok 231 - any_column_privs_are(tab, role, no privs) should have the proper description +ok 232 - any_column_privs_are(tab, role, no privs) should have the proper diagnostics +ok 233 - column_privs_are(sch, tab, role, privs, desc) should pass +ok 234 - column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 235 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 236 - column_privs_are(sch, tab, role, privs) should pass +ok 237 - column_privs_are(sch, tab, role, privs) should have the proper description +ok 238 - column_privs_are(sch, tab, role, privs) should have the proper diagnostics +ok 239 - column_privs_are(tab, role, privs, desc) should pass +ok 240 - column_privs_are(tab, role, privs, desc) should have the proper description +ok 241 - column_privs_are(tab, role, privs, desc) should have the proper diagnostics +ok 242 - column_privs_are(tab, role, privs) should pass +ok 243 - column_privs_are(tab, role, privs) should have the proper description +ok 244 - column_privs_are(tab, role, privs) should have the proper diagnostics +ok 245 - column_privs_are(sch, tab, role, some privs, desc) should fail +ok 246 - column_privs_are(sch, tab, role, some privs, desc) should have the proper description +ok 247 - column_privs_are(sch, tab, role, some privs, desc) should have the proper diagnostics +ok 248 - column_privs_are(tab, role, some privs, desc) should fail +ok 249 - column_privs_are(tab, role, some privs, desc) should have the proper description +ok 250 - column_privs_are(tab, role, some privs, desc) should have the proper diagnostics +ok 251 - column_privs_are(sch, tab, other, privs, desc) should fail +ok 252 - column_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 253 - column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics +ok 254 - column_privs_are(sch, tab, other, privs, desc) should pass +ok 255 - column_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 256 - column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics +ok 257 - column_privs_are(sch, tab, role, privs, desc) should fail +ok 258 - column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 259 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 260 - column_privs_are(sch, tab, role, privs, desc) should fail +ok 261 - column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 262 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 263 - column_privs_are(sch, tab, role, no privs) should fail +ok 264 - column_privs_are(sch, tab, role, no privs) should have the proper description +ok 265 - column_privs_are(sch, tab, role, no privs) should have the proper diagnostics +ok 266 - column_privs_are(tab, role, no privs) should fail +ok 267 - column_privs_are(tab, role, no privs) should have the proper description +ok 268 - column_privs_are(tab, role, no privs) should have the proper diagnostics +ok 269 - fdw_privs_are(fdw, role, privs, desc) should pass +ok 270 - fdw_privs_are(fdw, role, privs, desc) should have the proper description +ok 271 - fdw_privs_are(fdw, role, privs, desc) should have the proper diagnostics +ok 272 - fdw_privs_are(fdw, role, privs, desc) should pass +ok 273 - fdw_privs_are(fdw, role, privs, desc) should have the proper description +ok 274 - fdw_privs_are(fdw, role, privs, desc) should have the proper diagnostics +ok 275 - fdw_privs_are(non-fdw, role, privs, desc) should fail +ok 276 - fdw_privs_are(non-fdw, role, privs, desc) should have the proper description +ok 277 - fdw_privs_are(non-fdw, role, privs, desc) should have the proper diagnostics +ok 278 - fdw_privs_are(fdw, non-role, privs, desc) should fail +ok 279 - fdw_privs_are(fdw, non-role, privs, desc) should have the proper description +ok 280 - fdw_privs_are(fdw, non-role, privs, desc) should have the proper diagnostics +ok 281 - fdw_privs_are(fdw, ungranted, privs, desc) should fail +ok 282 - fdw_privs_are(fdw, ungranted, privs, desc) should have the proper description +ok 283 - fdw_privs_are(fdw, ungranted, privs, desc) should have the proper diagnostics +ok 284 - fdw_privs_are(fdw, role, no privs) should pass +ok 285 - fdw_privs_are(fdw, role, no privs) should have the proper description +ok 286 - fdw_privs_are(fdw, role, no privs) should have the proper diagnostics +ok 287 - server_privs_are(server, role, privs, desc) should pass +ok 288 - server_privs_are(server, role, privs, desc) should have the proper description +ok 289 - server_privs_are(server, role, privs, desc) should have the proper diagnostics +ok 290 - server_privs_are(server, role, privs, desc) should pass +ok 291 - server_privs_are(server, role, privs, desc) should have the proper description +ok 292 - server_privs_are(server, role, privs, desc) should have the proper diagnostics +ok 293 - server_privs_are(non-server, role, privs, desc) should fail +ok 294 - server_privs_are(non-server, role, privs, desc) should have the proper description +ok 295 - server_privs_are(non-server, role, privs, desc) should have the proper diagnostics +ok 296 - server_privs_are(server, non-role, privs, desc) should fail +ok 297 - server_privs_are(server, non-role, privs, desc) should have the proper description +ok 298 - server_privs_are(server, non-role, privs, desc) should have the proper diagnostics +ok 299 - server_privs_are(server, ungranted, privs, desc) should fail +ok 300 - server_privs_are(server, ungranted, privs, desc) should have the proper description +ok 301 - server_privs_are(server, ungranted, privs, desc) should have the proper diagnostics +ok 302 - server_privs_are(server, role, no privs) should pass +ok 303 - server_privs_are(server, role, no privs) should have the proper description +ok 304 - server_privs_are(server, role, no privs) should have the proper diagnostics diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 03931183663a..f9ce984a25e7 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -1,8 +1,8 @@ \unset ECHO \i test/setup.sql ---SELECT plan(103); -SELECT * FROM no_plan(); +SELECT plan(304); +--SELECT * FROM no_plan(); SET client_min_messages = warning; CREATE SCHEMA ha; From dbde7e898abb7f0f25040b07f31d4820f4cd5123 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Jan 2013 16:41:14 -0800 Subject: [PATCH 0718/1195] Add all priv stuff to the v0.92.0 update script. --- sql/pgtap--0.91.0--0.92.0.sql | 619 +++++++++++++++++++++++++++++++--- sql/pgtap.sql.in | 59 ++-- 2 files changed, 600 insertions(+), 78 deletions(-) diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index c8dbf137f749..61c0429ff90e 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -852,23 +852,29 @@ RETURNS TEXT AS $$ END; $$ LANGUAGE sql; -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; +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 $$ @@ -886,7 +892,7 @@ BEGIN RETURN '{undefined_table}'; WHEN undefined_object THEN -- Not a valid role. - RETURN '{undefined_object}'; + RETURN '{undefined_role}'; WHEN invalid_parameter_value THEN -- Not a valid permission on this version of PostgreSQL; ignore; END; @@ -895,29 +901,23 @@ BEGIN END; $$ LANGUAGE plpgsql; -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 _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 ) @@ -929,7 +929,7 @@ BEGIN RETURN ok(FALSE, $5) || E'\n' || diag( ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' ); - ELSIF grants[1] = 'undefined_object' THEN + ELSIF grants[1] = 'undefined_role' THEN RETURN ok(FALSE, $5) || E'\n' || diag( ' Role ' || quote_ident($3) || ' does not exist' ); @@ -945,7 +945,7 @@ RETURNS TEXT AS $$ $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) + || ' on table ' || quote_ident($1) || '.' || quote_ident($2) ); $$ LANGUAGE SQL; @@ -959,7 +959,7 @@ BEGIN RETURN ok(FALSE, $4) || E'\n' || diag( ' Table ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' ); - ELSIF grants[1] = 'undefined_object' THEN + ELSIF grants[1] = 'undefined_role' THEN RETURN ok(FALSE, $4) || E'\n' || diag( ' Role ' || quote_ident($2) || ' does not exist' ); @@ -1008,7 +1008,7 @@ BEGIN RETURN '{invalid_catalog_name}'; WHEN undefined_object THEN -- Not a valid role. - RETURN '{undefined_object}'; + RETURN '{undefined_role}'; WHEN invalid_parameter_value THEN -- Not a valid permission on this version of PostgreSQL; ignore; END; @@ -1027,7 +1027,7 @@ BEGIN RETURN ok(FALSE, $4) || E'\n' || diag( ' Database ' || quote_ident($1) || ' does not exist' ); - ELSIF grants[1] = 'undefined_object' THEN + ELSIF grants[1] = 'undefined_role' THEN RETURN ok(FALSE, $4) || E'\n' || diag( ' Role ' || quote_ident($2) || ' does not exist' ); @@ -1059,7 +1059,7 @@ EXCEPTION -- Not a valid func name. WHEN undefined_function THEN RETURN '{undefined_function}'; -- Not a valid role. - WHEN undefined_object THEN RETURN '{undefined_object}'; + WHEN undefined_object THEN RETURN '{undefined_role}'; END; $$ LANGUAGE plpgsql; @@ -1072,7 +1072,7 @@ BEGIN RETURN ok(FALSE, $4) || E'\n' || diag( ' Function ' || $1 || ' does not exist' ); - ELSIF grants[1] = 'undefined_object' THEN + ELSIF grants[1] = 'undefined_role' THEN RETURN ok(FALSE, $4) || E'\n' || diag( ' Role ' || quote_ident($2) || ' does not exist' ); @@ -1121,3 +1121,526 @@ RETURNS TEXT AS $$ || ' 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, quote_ident($1) ); +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, quote_ident($1) ); +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; + +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, quote_ident($1) ); +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_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; + +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, quote_ident($1) ); +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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 5544cb36afd7..7aee8f0ecd3b 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7909,6 +7909,30 @@ RETURNS TEXT AS $$ ); $$ 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 @@ -7952,30 +7976,6 @@ BEGIN END; $$ language plpgsql; -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; - -- table_privs_are ( schema, table, user, privileges[], description ) CREATE OR REPLACE FUNCTION table_privs_are ( NAME, NAME, NAME, NAME[], TEXT ) RETURNS TEXT AS $$ @@ -8188,7 +8188,7 @@ BEGIN RETURN '{}'; END IF; EXCEPTION WHEN undefined_object THEN - -- Same error code for unknown user or language. So figure out which. + -- Same error code for unknown user or language. So figure out which. RETURN CASE WHEN SQLERRM LIKE '%' || $1 || '%' THEN '{undefined_role}' ELSE @@ -8286,7 +8286,7 @@ BEGIN RETURN '{}'; END IF; EXCEPTION WHEN undefined_object THEN - -- Same error code for unknown user or tablespace. So figure out which. + -- Same error code for unknown user or tablespace. So figure out which. RETURN CASE WHEN SQLERRM LIKE '%' || $1 || '%' THEN '{undefined_role}' ELSE @@ -8507,7 +8507,7 @@ BEGIN END IF; END LOOP; RETURN grants; -EXCEPTION +EXCEPTION -- Not a valid column name. WHEN undefined_column THEN RETURN '{undefined_column}'; -- Not a valid table name. @@ -8595,7 +8595,7 @@ BEGIN RETURN '{}'; END IF; EXCEPTION WHEN undefined_object THEN - -- Same error code for unknown user or fdw. So figure out which. + -- Same error code for unknown user or fdw. So figure out which. RETURN CASE WHEN SQLERRM LIKE '%' || $1 || '%' THEN '{undefined_role}' ELSE @@ -8663,7 +8663,7 @@ BEGIN RETURN '{}'; END IF; EXCEPTION WHEN undefined_object THEN - -- Same error code for unknown user or server. So figure out which. + -- Same error code for unknown user or server. So figure out which. RETURN CASE WHEN SQLERRM LIKE '%' || $1 || '%' THEN '{undefined_role}' ELSE @@ -8701,4 +8701,3 @@ RETURNS TEXT AS $$ || ' on server ' || quote_ident($1) ); $$ LANGUAGE SQL; - From 5191112f3e417ac3e53814091df41291deeaa2f0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Jan 2013 17:03:20 -0800 Subject: [PATCH 0719/1195] Protect any_column_privs() tests on < 8.4. --- test/sql/privs.sql | 179 ++++++++++++++++++++++++++++++++------------- 1 file changed, 127 insertions(+), 52 deletions(-) diff --git a/test/sql/privs.sql b/test/sql/privs.sql index f9ce984a25e7..7915223729ae 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -742,42 +742,7 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test any_column_privs_are(). - -SELECT * FROM check_test( - any_column_privs_are( 'ha', 'sometab', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), - true, - 'any_column_privs_are(sch, tab, role, privs, desc)', - 'whatever', - '' -); - -SELECT * FROM check_test( - any_column_privs_are( 'ha', 'sometab', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'] ), - true, - 'any_column_privs_are(sch, tab, role, privs)', - 'Role ' || current_user || ' should be granted ' - || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') || ' on any column in ha.sometab' , - '' -); - -SELECT * FROM check_test( - any_column_privs_are( 'sometab', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), - true, - 'any_column_privs_are(tab, role, privs, desc)', - 'whatever', - '' -); - -SELECT * FROM check_test( - any_column_privs_are( 'sometab', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'] ), - true, - 'any_column_privs_are(tab, role, privs)', - 'Role ' || current_user || ' should be granted ' - || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') || ' on any column in sometab' , - '' -); - -CREATE OR REPLACE FUNCTION run_extra_fails() RETURNS SETOF TEXT LANGUAGE plpgsql AS $$ +CREATE FUNCTION test_anycols() RETURNS SETOF TEXT AS $$ DECLARE allowed_privs TEXT[]; test_privs TEXT[]; @@ -785,37 +750,147 @@ DECLARE tap record; last_index INTEGER; BEGIN - -- Test table failure. - allowed_privs := ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE']; - last_index := array_upper(allowed_privs, 1); - FOR i IN 1..last_index - 2 LOOP - test_privs := test_privs || allowed_privs[i]; - END LOOP; - FOR i IN last_index - 1..last_index LOOP - missing_privs := missing_privs || allowed_privs[i]; - END LOOP; + IF pg_version_num() >= 80400 THEN + FOR tap IN SELECT * FROM check_test( + any_column_privs_are( 'ha', 'sometab', current_user, ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ], 'whatever' ), + true, + 'any_column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; - FOR tap IN SELECT * FROM check_test( - any_column_privs_are( 'ha', 'sometab', current_user, test_privs, 'whatever' ), + FOR tap IN SELECT * FROM check_test( + any_column_privs_are( 'ha', 'sometab', current_user, ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ] ), + true, + 'any_column_privs_are(sch, tab, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') + || ' on any column in ha.sometab' , + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + any_column_privs_are( 'sometab', current_user, ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ], 'whatever' ), + true, + 'any_column_privs_are(tab, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + any_column_privs_are( 'sometab', current_user, ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ] ), + true, + 'any_column_privs_are(tab, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') + || ' on any column in sometab' , + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- Test table failure. + allowed_privs := ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE']; + last_index := array_upper(allowed_privs, 1); + FOR i IN 1..last_index - 2 LOOP + test_privs := test_privs || allowed_privs[i]; + END LOOP; + FOR i IN last_index - 1..last_index LOOP + missing_privs := missing_privs || allowed_privs[i]; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + any_column_privs_are( 'ha', 'sometab', current_user, test_privs, 'whatever' ), false, 'any_column_privs_are(sch, tab, role, some privs, desc)', 'whatever', ' Extra privileges: ' || array_to_string(missing_privs, E'\n ') - ) AS b LOOP RETURN NEXT tap.b; END LOOP; + ) AS b LOOP RETURN NEXT tap.b; END LOOP; - FOR tap IN SELECT * FROM check_test( + FOR tap IN SELECT * FROM check_test( any_column_privs_are( 'sometab', current_user, test_privs, 'whatever' ), false, 'any_column_privs_are(tab, role, some privs, desc)', 'whatever', ' Extra privileges: ' || array_to_string(missing_privs, E'\n ') - ) AS b LOOP RETURN NEXT tap.b; END LOOP; + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + ELSE + -- Fake it with table tests. + FOR tap IN SELECT * FROM check_test( + table_privs_are( 'ha', 'sometab', current_user, _table_privs(), 'whatever' ), + true, + 'any_column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + table_privs_are( 'ha', 'sometab', current_user, _table_privs() ), + true, + 'any_column_privs_are(sch, tab, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(_table_privs(), ', ') + || ' on table ha.sometab' , + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + table_privs_are( 'sometab', current_user, _table_privs(), 'whatever' ), + true, + 'any_column_privs_are(tab, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + allowed_privs := _table_privs(); + last_index := array_upper(allowed_privs, 1); + FOR i IN 1..last_index - 2 LOOP + test_privs := test_privs || allowed_privs[i]; + END LOOP; + FOR i IN last_index - 1..last_index LOOP + missing_privs := missing_privs || allowed_privs[i]; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + table_privs_are( 'sometab', current_user, _table_privs() ), + true, + 'any_column_privs_are(tab, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(_table_privs(), E', ') + || ' on table sometab' , + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + table_privs_are( 'ha', 'sometab', current_user, test_privs, 'whatever' ), + false, + 'any_column_privs_are(sch, tab, role, some privs, desc)', + 'whatever', + ' Extra privileges: + ' || array_to_string(missing_privs, E'\n ') + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + table_privs_are( 'sometab', current_user, test_privs, 'whatever' ), + false, + 'any_column_privs_are(tab, role, some privs, desc)', + 'whatever', + ' Extra privileges: + ' || array_to_string(missing_privs, E'\n ') + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + END IF; END; -$$; +$$ LANGUAGE PLPGSQL; -SELECT * FROM run_extra_fails(); +SELECT * FROM test_anycols(); SELECT * FROM check_test( any_column_privs_are( 'ha', 'sometab', '__someone_else', ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), From bca47ab185d24a2282a32f8ae4ea90e3ea29f6d0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Jan 2013 17:18:36 -0800 Subject: [PATCH 0720/1195] Save the rest of the `any_column_privs_are() tests from earlie versions. --- test/sql/privs.sql | 164 +++++++++++++++++++++++++++++++-------------- 1 file changed, 112 insertions(+), 52 deletions(-) diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 7915223729ae..05d7ef0c002b 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -822,6 +822,67 @@ BEGIN ' Extra privileges: ' || array_to_string(missing_privs, E'\n ') ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + any_column_privs_are( 'ha', 'sometab', '__someone_else', ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ], 'whatever' ), + false, + 'any_column_privs_are(sch, tab, other, privs, desc)', + 'whatever', + ' Missing privileges: + ' || array_to_string(ARRAY['REFERENCES'], E'\n ') + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + any_column_privs_are( 'ha', 'sometab', '__someone_else', ARRAY[ + 'SELECT', 'INSERT', 'UPDATE' + ], 'whatever'), + true, + 'any_column_privs_are(sch, tab, other, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- Try a non-existent table. + FOR tap IN SELECT * FROM check_test( + any_column_privs_are( 'ha', 'nonesuch', current_user, ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ], 'whatever' ), + false, + 'any_column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + ' Table ha.nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- Try a non-existent user. + FOR tap IN SELECT * FROM check_test( + any_column_privs_are( 'ha', 'sometab', '__nonesuch', ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ], 'whatever' ), + false, + 'any_column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + ' Role __nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- Test default description with no permissions. + FOR tap IN SELECT * FROM check_test( + any_column_privs_are( 'ha', 'sometab', '__nonesuch', '{}'::text[] ), + false, + 'any_column_privs_are(sch, tab, role, no privs)', + 'Role __nonesuch should be granted no privileges on any column in ha.sometab' , + ' Role __nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + any_column_privs_are( 'sometab', '__nonesuch', '{}'::text[] ), + false, + 'any_column_privs_are(tab, role, no privs)', + 'Role __nonesuch should be granted no privileges on any column in sometab' , + ' Role __nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + ELSE -- Fake it with table tests. FOR tap IN SELECT * FROM check_test( @@ -886,65 +947,64 @@ BEGIN ' Extra privileges: ' || array_to_string(missing_privs, E'\n ') ) AS b LOOP RETURN NEXT tap.b; END LOOP; - END IF; -END; -$$ LANGUAGE PLPGSQL; -SELECT * FROM test_anycols(); + FOR tap IN SELECT * FROM check_test( + table_privs_are( 'ha', 'sometab', current_user, test_privs, 'whatever' ), + false, + 'any_column_privs_are(sch, tab, other, privs, desc)', + 'whatever', + ' Extra privileges: + ' || array_to_string(missing_privs, E'\n ') + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - any_column_privs_are( 'ha', 'sometab', '__someone_else', ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), - false, - 'any_column_privs_are(sch, tab, other, privs, desc)', - 'whatever', - ' Missing privileges: - ' || array_to_string(ARRAY['REFERENCES'], E'\n ') -); + FOR tap IN SELECT * FROM check_test( + table_privs_are( 'ha', 'sometab', current_user, _table_privs(),'whatever'), + true, + 'any_column_privs_are(sch, tab, other, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - any_column_privs_are( 'ha', 'sometab', '__someone_else', ARRAY[ - 'SELECT', 'INSERT', 'UPDATE' - ], 'whatever'), - true, - 'any_column_privs_are(sch, tab, other, privs, desc)', - 'whatever', - '' -); + -- Try a non-existent table. + FOR tap IN SELECT * FROM check_test( + table_privs_are( 'ha', 'nonesuch', current_user, test_privs, 'whatever' ), + false, + 'any_column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + ' Table ha.nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; --- Try a non-existent table. -SELECT * FROM check_test( - any_column_privs_are( 'ha', 'nonesuch', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), - false, - 'any_column_privs_are(sch, tab, role, privs, desc)', - 'whatever', - ' Table ha.nonesuch does not exist' -); + -- Try a non-existent user. + FOR tap IN SELECT * FROM check_test( + table_privs_are( 'ha', 'sometab', '__nonesuch', test_privs, 'whatever' ), + false, + 'any_column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + ' Role __nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; --- Try a non-existent user. -SELECT * FROM check_test( - any_column_privs_are( 'ha', 'sometab', '__nonesuch', ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), - false, - 'any_column_privs_are(sch, tab, role, privs, desc)', - 'whatever', - ' Role __nonesuch does not exist' -); + -- Test default description with no permissions. + FOR tap IN SELECT * FROM check_test( + table_privs_are( 'ha', 'sometab', '__nonesuch', '{}'::text[] ), + false, + 'any_column_privs_are(sch, tab, role, no privs)', + 'Role __nonesuch should be granted no privileges on table ha.sometab' , + ' Role __nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; --- Test default description with no permissions. -SELECT * FROM check_test( - any_column_privs_are( 'ha', 'sometab', '__nonesuch', '{}'::text[] ), - false, - 'any_column_privs_are(sch, tab, role, no privs)', - 'Role __nonesuch should be granted no privileges on any column in ha.sometab' , - ' Role __nonesuch does not exist' -); + FOR tap IN SELECT * FROM check_test( + table_privs_are( 'sometab', '__nonesuch', '{}'::text[] ), + false, + 'any_column_privs_are(tab, role, no privs)', + 'Role __nonesuch should be granted no privileges on table sometab' , + ' Role __nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - any_column_privs_are( 'sometab', '__nonesuch', '{}'::text[] ), - false, - 'any_column_privs_are(tab, role, no privs)', - 'Role __nonesuch should be granted no privileges on any column in sometab' , - ' Role __nonesuch does not exist' -); + END IF; +END; +$$ LANGUAGE PLPGSQL; + +SELECT * FROM test_anycols(); /****************************************************************************/ -- Test column_privs_are(). From a5beb1e0ba5d0fbfe79dec28ac363a421bc9dffa Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Jan 2013 17:29:02 -0800 Subject: [PATCH 0721/1195] Start protecting column_privs_are() tests from earlier versions. Need to dupe them with another function, but right now I need to head home. --- test/sql/privs.sql | 233 +++++++++++++++++++++++++-------------------- 1 file changed, 129 insertions(+), 104 deletions(-) diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 05d7ef0c002b..92a5974048b3 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -1009,41 +1009,7 @@ SELECT * FROM test_anycols(); /****************************************************************************/ -- Test column_privs_are(). -SELECT * FROM check_test( - column_privs_are( 'ha', 'sometab', 'id', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), - true, - 'column_privs_are(sch, tab, role, privs, desc)', - 'whatever', - '' -); - -SELECT * FROM check_test( - column_privs_are( 'ha', 'sometab', 'id', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'] ), - true, - 'column_privs_are(sch, tab, role, privs)', - 'Role ' || current_user || ' should be granted ' - || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') || ' on column ha.sometab.id' , - '' -); - -SELECT * FROM check_test( - column_privs_are( 'sometab', 'id', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), - true, - 'column_privs_are(tab, role, privs, desc)', - 'whatever', - '' -); - -SELECT * FROM check_test( - column_privs_are( 'sometab', 'id', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'] ), - true, - 'column_privs_are(tab, role, privs)', - 'Role ' || current_user || ' should be granted ' - || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') || ' on column sometab.id' , - '' -); - -CREATE OR REPLACE FUNCTION run_extra_fails() RETURNS SETOF TEXT LANGUAGE plpgsql AS $$ +CREATE FUNCTION test_cols() RETURNS SETOF TEXT AS $$ DECLARE allowed_privs TEXT[]; test_privs TEXT[]; @@ -1051,94 +1017,153 @@ DECLARE tap record; last_index INTEGER; BEGIN - -- Test table failure. - allowed_privs := ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE']; - last_index := array_upper(allowed_privs, 1); - FOR i IN 1..last_index - 2 LOOP - test_privs := test_privs || allowed_privs[i]; - END LOOP; - FOR i IN last_index - 1..last_index LOOP - missing_privs := missing_privs || allowed_privs[i]; - END LOOP; + IF pg_version_num() >= 80400 THEN + FOR tap IN SELECT * FROM check_test( + column_privs_are( 'ha', 'sometab', 'id', current_user, ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ], 'whatever' ), + true, + 'column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; - FOR tap IN SELECT * FROM check_test( - column_privs_are( 'ha', 'sometab', 'id', current_user, test_privs, 'whatever' ), - false, - 'column_privs_are(sch, tab, role, some privs, desc)', + FOR tap IN SELECT * FROM check_test( + column_privs_are( 'ha', 'sometab', 'id', current_user, ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ] ), + true, + 'column_privs_are(sch, tab, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') + || ' on column ha.sometab.id' , + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + column_privs_are( 'sometab', 'id', current_user, ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ], 'whatever' ), + true, + 'column_privs_are(tab, role, privs, desc)', 'whatever', - ' Extra privileges: + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + column_privs_are( 'sometab', 'id', current_user, ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ] ), + true, + 'column_privs_are(tab, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') + || ' on column sometab.id' , + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- Test table failure. + allowed_privs := ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE']; + last_index := array_upper(allowed_privs, 1); + FOR i IN 1..last_index - 2 LOOP + test_privs := test_privs || allowed_privs[i]; + END LOOP; + FOR i IN last_index - 1..last_index LOOP + missing_privs := missing_privs || allowed_privs[i]; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + column_privs_are( 'ha', 'sometab', 'id', current_user, test_privs, 'whatever' ), + false, + 'column_privs_are(sch, tab, role, some privs, desc)', + 'whatever', + ' Extra privileges: ' || array_to_string(missing_privs, E'\n ') - ) AS b LOOP RETURN NEXT tap.b; END LOOP; + ) AS b LOOP RETURN NEXT tap.b; END LOOP; - FOR tap IN SELECT * FROM check_test( + FOR tap IN SELECT * FROM check_test( column_privs_are( 'sometab', 'id', current_user, test_privs, 'whatever' ), false, 'column_privs_are(tab, role, some privs, desc)', 'whatever', ' Extra privileges: ' || array_to_string(missing_privs, E'\n ') + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + column_privs_are( 'ha', 'sometab', 'id', '__someone_else', ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ], 'whatever' ), + false, + 'column_privs_are(sch, tab, other, privs, desc)', + 'whatever', + ' Missing privileges: + ' || array_to_string(ARRAY['REFERENCES'], E'\n ') ) AS b LOOP RETURN NEXT tap.b; END LOOP; -END; -$$; -SELECT * FROM run_extra_fails(); + -- Grant them some permission. + GRANT SELECT, INSERT, UPDATE (id) ON ha.sometab TO __someone_else; -SELECT * FROM check_test( - column_privs_are( 'ha', 'sometab', 'id', '__someone_else', ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), - false, - 'column_privs_are(sch, tab, other, privs, desc)', - 'whatever', - ' Missing privileges: - ' || array_to_string(ARRAY['REFERENCES'], E'\n ') -); + FOR tap IN SELECT * FROM check_test( + column_privs_are( 'ha', 'sometab', 'id', '__someone_else', ARRAY[ + 'SELECT', 'INSERT', 'UPDATE' + ], 'whatever'), + true, + 'column_privs_are(sch, tab, other, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; --- Grant them some permission. -GRANT SELECT, INSERT, UPDATE (id) ON ha.sometab TO __someone_else; + -- Try a non-existent table. + FOR tap IN SELECT * FROM check_test( + column_privs_are( 'ha', 'nonesuch', 'id', current_user, ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ], 'whatever' ), + false, + 'column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + ' Table ha.nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - column_privs_are( 'ha', 'sometab', 'id', '__someone_else', ARRAY[ - 'SELECT', 'INSERT', 'UPDATE' - ], 'whatever'), - true, - 'column_privs_are(sch, tab, other, privs, desc)', - 'whatever', - '' -); + -- Try a non-existent user. + FOR tap IN SELECT * FROM check_test( + column_privs_are( 'ha', 'sometab', 'id', '__nonesuch', ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ], 'whatever' ), + false, + 'column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + ' Role __nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; --- Try a non-existent table. -SELECT * FROM check_test( - column_privs_are( 'ha', 'nonesuch', 'id', current_user, ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), - false, - 'column_privs_are(sch, tab, role, privs, desc)', - 'whatever', - ' Table ha.nonesuch does not exist' -); + -- Test default description with no permissions. + FOR tap IN SELECT * FROM check_test( + column_privs_are( 'ha', 'sometab', 'id', '__nonesuch', '{}'::text[] ), + false, + 'column_privs_are(sch, tab, role, no privs)', + 'Role __nonesuch should be granted no privileges on column ha.sometab.id' , + ' Role __nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; --- Try a non-existent user. -SELECT * FROM check_test( - column_privs_are( 'ha', 'sometab', 'id', '__nonesuch', ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], 'whatever' ), - false, - 'column_privs_are(sch, tab, role, privs, desc)', - 'whatever', - ' Role __nonesuch does not exist' -); + FOR tap IN SELECT * FROM check_test( + column_privs_are( 'sometab', 'id', '__nonesuch', '{}'::text[] ), + false, + 'column_privs_are(tab, role, no privs)', + 'Role __nonesuch should be granted no privileges on column sometab.id' , + ' Role __nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; --- Test default description with no permissions. -SELECT * FROM check_test( - column_privs_are( 'ha', 'sometab', 'id', '__nonesuch', '{}'::text[] ), - false, - 'column_privs_are(sch, tab, role, no privs)', - 'Role __nonesuch should be granted no privileges on column ha.sometab.id' , - ' Role __nonesuch does not exist' -); + ELSE + -- Fake it with table_privs_are(). -SELECT * FROM check_test( - column_privs_are( 'sometab', 'id', '__nonesuch', '{}'::text[] ), - false, - 'column_privs_are(tab, role, no privs)', - 'Role __nonesuch should be granted no privileges on column sometab.id' , - ' Role __nonesuch does not exist' -); + END IF; +END; +$$ LANGUAGE PLPGSQL; + +SELECT * FROM test_cols(); + +/****************************************************************************/ +-- Test fdw_privs_are(). CREATE FUNCTION test_fdw() RETURNS SETOF TEXT AS $$ DECLARE From 264303bcfb4513cec6a540e2d82ffc885cc32c24 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 09:22:27 -0800 Subject: [PATCH 0722/1195] Fake `column_privs_are()` tests on 8.3 and lower. --- test/sql/privs.sql | 110 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 6 deletions(-) diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 92a5974048b3..cd2f8948eb93 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -1074,10 +1074,10 @@ BEGIN FOR tap IN SELECT * FROM check_test( column_privs_are( 'ha', 'sometab', 'id', current_user, test_privs, 'whatever' ), - false, - 'column_privs_are(sch, tab, role, some privs, desc)', - 'whatever', - ' Extra privileges: + false, + 'column_privs_are(sch, tab, role, some privs, desc)', + 'whatever', + ' Extra privileges: ' || array_to_string(missing_privs, E'\n ') ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1099,7 +1099,7 @@ BEGIN 'whatever', ' Missing privileges: ' || array_to_string(ARRAY['REFERENCES'], E'\n ') - ) AS b LOOP RETURN NEXT tap.b; END LOOP; + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -- Grant them some permission. GRANT SELECT, INSERT, UPDATE (id) ON ha.sometab TO __someone_else; @@ -1154,7 +1154,105 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; ELSE - -- Fake it with table_privs_are(). + -- Fake it. + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'column_privs_are(sch, tab, role, privs)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'column_privs_are(tab, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'column_privs_are(tab, role, privs)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('whatever'), + false, + 'column_privs_are(sch, tab, role, some privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('whatever'), + false, + 'column_privs_are(tab, role, some privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('whatever'), + false, + 'column_privs_are(sch, tab, other, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'column_privs_are(sch, tab, other, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- Try a non-existent table. + FOR tap IN SELECT * FROM check_test( + fail('whatever'), + false, + 'column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- Try a non-existent user. + FOR tap IN SELECT * FROM check_test( + fail('whatever'), + false, + 'column_privs_are(sch, tab, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- Test default description with no permissions. + FOR tap IN SELECT * FROM check_test( + fail('whatever'), + false, + 'column_privs_are(sch, tab, role, no privs)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('whatever'), + false, + 'column_privs_are(tab, role, no privs)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; END IF; END; From 6af7863addb3e4108ac98580275536ef2ab2f367 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 09:37:17 -0800 Subject: [PATCH 0723/1195] Fake any_column_privs_are() with pass() and fail(). Rather than table_privs_are(), whih is much more complicated. --- test/sql/privs.sql | 73 ++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 45 deletions(-) diff --git a/test/sql/privs.sql b/test/sql/privs.sql index cd2f8948eb93..a36288b6d08b 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -750,7 +750,7 @@ DECLARE tap record; last_index INTEGER; BEGIN - IF pg_version_num() >= 80400 THEN + IF pg_version_num() >= 90400 THEN FOR tap IN SELECT * FROM check_test( any_column_privs_are( 'ha', 'sometab', current_user, ARRAY[ 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' @@ -884,9 +884,9 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; ELSE - -- Fake it with table tests. + -- Fake it with pass() and fail(). FOR tap IN SELECT * FROM check_test( - table_privs_are( 'ha', 'sometab', current_user, _table_privs(), 'whatever' ), + pass('whatever'), true, 'any_column_privs_are(sch, tab, role, privs, desc)', 'whatever', @@ -894,71 +894,55 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - table_privs_are( 'ha', 'sometab', current_user, _table_privs() ), + pass('whatever'), true, 'any_column_privs_are(sch, tab, role, privs)', - 'Role ' || current_user || ' should be granted ' - || array_to_string(_table_privs(), ', ') - || ' on table ha.sometab' , + 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - table_privs_are( 'sometab', current_user, _table_privs(), 'whatever' ), + pass('whatever'), true, 'any_column_privs_are(tab, role, privs, desc)', 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; - allowed_privs := _table_privs(); - last_index := array_upper(allowed_privs, 1); - FOR i IN 1..last_index - 2 LOOP - test_privs := test_privs || allowed_privs[i]; - END LOOP; - FOR i IN last_index - 1..last_index LOOP - missing_privs := missing_privs || allowed_privs[i]; - END LOOP; - FOR tap IN SELECT * FROM check_test( - table_privs_are( 'sometab', current_user, _table_privs() ), + pass('whatever'), true, 'any_column_privs_are(tab, role, privs)', - 'Role ' || current_user || ' should be granted ' - || array_to_string(_table_privs(), E', ') - || ' on table sometab' , + 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - table_privs_are( 'ha', 'sometab', current_user, test_privs, 'whatever' ), + fail('whatever'), false, 'any_column_privs_are(sch, tab, role, some privs, desc)', 'whatever', - ' Extra privileges: - ' || array_to_string(missing_privs, E'\n ') + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - table_privs_are( 'sometab', current_user, test_privs, 'whatever' ), + fail('whatever'), false, 'any_column_privs_are(tab, role, some privs, desc)', 'whatever', - ' Extra privileges: - ' || array_to_string(missing_privs, E'\n ') + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - table_privs_are( 'ha', 'sometab', current_user, test_privs, 'whatever' ), - false, - 'any_column_privs_are(sch, tab, other, privs, desc)', - 'whatever', - ' Extra privileges: - ' || array_to_string(missing_privs, E'\n ') + fail('whatever'), + false, + 'any_column_privs_are(sch, tab, other, privs, desc)', + 'whatever', + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - table_privs_are( 'ha', 'sometab', current_user, _table_privs(),'whatever'), + pass('whatever'), true, 'any_column_privs_are(sch, tab, other, privs, desc)', 'whatever', @@ -967,39 +951,38 @@ BEGIN -- Try a non-existent table. FOR tap IN SELECT * FROM check_test( - table_privs_are( 'ha', 'nonesuch', current_user, test_privs, 'whatever' ), + fail('whatever'), false, 'any_column_privs_are(sch, tab, role, privs, desc)', 'whatever', - ' Table ha.nonesuch does not exist' + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; -- Try a non-existent user. FOR tap IN SELECT * FROM check_test( - table_privs_are( 'ha', 'sometab', '__nonesuch', test_privs, 'whatever' ), + fail('whatever'), false, 'any_column_privs_are(sch, tab, role, privs, desc)', 'whatever', - ' Role __nonesuch does not exist' + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; -- Test default description with no permissions. FOR tap IN SELECT * FROM check_test( - table_privs_are( 'ha', 'sometab', '__nonesuch', '{}'::text[] ), + fail('whatever'), false, 'any_column_privs_are(sch, tab, role, no privs)', - 'Role __nonesuch should be granted no privileges on table ha.sometab' , - ' Role __nonesuch does not exist' + 'whatever', + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - table_privs_are( 'sometab', '__nonesuch', '{}'::text[] ), + fail('whatever'), false, 'any_column_privs_are(tab, role, no privs)', - 'Role __nonesuch should be granted no privileges on table sometab' , - ' Role __nonesuch does not exist' + 'whatever', + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; - END IF; END; $$ LANGUAGE PLPGSQL; From e35126daf892cd326495cdb51c62ab5ba31009e4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 09:45:10 -0800 Subject: [PATCH 0724/1195] Fake fdw_privs_are() using pass() and fail(). Rather than language_privs_are(), which is more complicated. --- test/sql/privs.sql | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/test/sql/privs.sql b/test/sql/privs.sql index a36288b6d08b..39e5494ccaed 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -750,7 +750,7 @@ DECLARE tap record; last_index INTEGER; BEGIN - IF pg_version_num() >= 90400 THEN + IF pg_version_num() >= 80400 THEN FOR tap IN SELECT * FROM check_test( any_column_privs_are( 'ha', 'sometab', current_user, ARRAY[ 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' @@ -1310,9 +1310,9 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; ELSE - -- Fake it with language_privs_are(). + -- Fake it with pass() and fail(). FOR tap IN SELECT * FROM check_test( - language_privs_are( 'plpgsql', current_user, '{USAGE}', 'whatever' ), + pass('whatever'), true, 'fdw_privs_are(fdw, role, privs, desc)', 'whatever', @@ -1320,50 +1320,49 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - language_privs_are( 'plpgsql', current_user, '{USAGE}' ), + pass('whatever'), true, 'fdw_privs_are(fdw, role, privs, desc)', - 'Role ' || current_user || ' should be granted USAGE on language plpgsql', + 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; -- Try nonexistent fdw. FOR tap IN SELECT * FROM check_test( - language_privs_are( '__nonesuch', current_user, '{USAGE}', 'whatever' ), + fail('whatever'), false, 'fdw_privs_are(non-fdw, role, privs, desc)', 'whatever', - ' Language __nonesuch does not exist' + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; -- Try nonexistent user. FOR tap IN SELECT * FROM check_test( - language_privs_are( 'plpgsql', '__noone', '{USAGE}', 'whatever' ), + fail('whatever'), false, 'fdw_privs_are(fdw, non-role, privs, desc)', 'whatever', - ' Role __noone does not exist' + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; -- Try another user. FOR tap IN SELECT * FROM check_test( - language_privs_are( 'plpgsql', '__someone_else', '{USAGE}', 'whatever' ), + fail('whatever'), false, 'fdw_privs_are(fdw, ungranted, privs, desc)', 'whatever', - ' Missing privileges: - USAGE' + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; -- Try testing default description for no permissions. FOR tap IN SELECT * FROM check_test( - language_privs_are( 'plpgsql', '__someone_else', '{}'::text[] ), + pass('whatever'), true, 'fdw_privs_are(fdw, role, no privs)', - 'Role __someone_else should be granted no privileges on language plpgsql', + 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; From 350e1ae6074dee6d0f06abe1bae5316f6777eb8f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 10:46:41 -0800 Subject: [PATCH 0725/1195] Fake server_privs_are() using pass() and fail(). Rather than language_privs_are(), which is more complicated. --- test/sql/privs.sql | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 39e5494ccaed..f915a247f6a2 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -1437,9 +1437,9 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; ELSE - -- Fake it with language_privs_are(). + -- Fake it with pass() and fail(). FOR tap IN SELECT * FROM check_test( - language_privs_are( 'plpgsql', current_user, '{USAGE}', 'whatever' ), + pass('whatever'), true, 'server_privs_are(server, role, privs, desc)', 'whatever', @@ -1447,50 +1447,49 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - language_privs_are( 'plpgsql', current_user, '{USAGE}' ), + pass('whatever'), true, 'server_privs_are(server, role, privs, desc)', - 'Role ' || current_user || ' should be granted USAGE on language plpgsql', + 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; - -- Try nonexistent fdw. + -- Try nonexistent server. FOR tap IN SELECT * FROM check_test( - language_privs_are( '__nonesuch', current_user, '{USAGE}', 'whatever' ), + fail('whatever'), false, 'server_privs_are(non-server, role, privs, desc)', 'whatever', - ' Language __nonesuch does not exist' + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; -- Try nonexistent user. FOR tap IN SELECT * FROM check_test( - language_privs_are( 'plpgsql', '__noone', '{USAGE}', 'whatever' ), + fail('whatever'), false, 'server_privs_are(server, non-role, privs, desc)', 'whatever', - ' Role __noone does not exist' + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; -- Try another user. FOR tap IN SELECT * FROM check_test( - language_privs_are( 'plpgsql', '__someone_else', '{USAGE}', 'whatever' ), + fail('whatever'), false, 'server_privs_are(server, ungranted, privs, desc)', 'whatever', - ' Missing privileges: - USAGE' + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; -- Try testing default description for no permissions. FOR tap IN SELECT * FROM check_test( - language_privs_are( 'plpgsql', '__someone_else', '{}'::text[] ), + pass('whatever'), true, 'server_privs_are(server, role, no privs)', - 'Role __someone_else should be granted no privileges on language plpgsql', + 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; From c489fe7c1445bfe66ecf313d4d8b0b260ab74308 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 10:59:54 -0800 Subject: [PATCH 0726/1195] Mock sequence_priv_tests() on versions of Postgres that don't support it. --- test/sql/privs.sql | 319 ++++++++++++++++++++++++++++++--------------- 1 file changed, 217 insertions(+), 102 deletions(-) diff --git a/test/sql/privs.sql b/test/sql/privs.sql index f915a247f6a2..11b0cfda3b6c 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -609,41 +609,7 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test sequence_privilege_is(). -SELECT * FROM check_test( - sequence_privs_are( 'ha', 'someseq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'], 'whatever' ), - true, - 'sequence_privs_are(sch, seq, role, privs, desc)', - 'whatever', - '' -); - -SELECT * FROM check_test( - sequence_privs_are( 'ha', 'someseq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'] ), - true, - 'sequence_privs_are(sch, seq, role, privs)', - 'Role ' || current_user || ' should be granted ' - || array_to_string(ARRAY['USAGE', 'SELECT', 'UPDATE'], ', ') || ' on sequence ha.someseq' , - '' -); - -SELECT * FROM check_test( - sequence_privs_are( 'someseq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'], 'whatever' ), - true, - 'sequence_privs_are(seq, role, privs, desc)', - 'whatever', - '' -); - -SELECT * FROM check_test( - sequence_privs_are( 'someseq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'] ), - true, - 'sequence_privs_are(seq, role, privs)', - 'Role ' || current_user || ' should be granted ' - || array_to_string(ARRAY['USAGE', 'SELECT', 'UPDATE'], ', ') || ' on sequence someseq' , - '' -); - -CREATE OR REPLACE FUNCTION run_extra_fails() RETURNS SETOF TEXT LANGUAGE plpgsql AS $$ +CREATE FUNCTION test_sequence() RETURNS SETOF TEXT AS $$ DECLARE allowed_privs TEXT[]; test_privs TEXT[]; @@ -651,94 +617,243 @@ DECLARE tap record; last_index INTEGER; BEGIN - -- Test sequence failure. - allowed_privs := ARRAY['USAGE', 'SELECT', 'UPDATE']; - last_index := array_upper(allowed_privs, 1); - FOR i IN 1..last_index - 2 LOOP - test_privs := test_privs || allowed_privs[i]; - END LOOP; - FOR i IN last_index - 1..last_index LOOP - missing_privs := missing_privs || allowed_privs[i]; - END LOOP; + IF pg_version_num() >= 90000 THEN + FOR tap IN SELECT * FROM check_test( + sequence_privs_are( 'ha', 'someseq', current_user, ARRAY[ + 'USAGE', 'SELECT', 'UPDATE' + ], 'whatever' ), + true, + 'sequence_privs_are(sch, seq, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; - FOR tap IN SELECT * FROM check_test( - sequence_privs_are( 'ha', 'someseq', current_user, test_privs, 'whatever' ), + FOR tap IN SELECT * FROM check_test( + sequence_privs_are( 'ha', 'someseq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'] ), + true, + 'sequence_privs_are(sch, seq, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['USAGE', 'SELECT', 'UPDATE'], ', ') + || ' on sequence ha.someseq' , + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + sequence_privs_are( 'someseq', current_user, ARRAY[ + 'USAGE', 'SELECT', 'UPDATE' + ], 'whatever' ), + true, + 'sequence_privs_are(seq, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + sequence_privs_are( 'someseq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'] ), + true, + 'sequence_privs_are(seq, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['USAGE', 'SELECT', 'UPDATE'], ', ') + || ' on sequence someseq' , + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- Test sequence failure. + allowed_privs := ARRAY['USAGE', 'SELECT', 'UPDATE']; + last_index := array_upper(allowed_privs, 1); + FOR i IN 1..last_index - 2 LOOP + test_privs := test_privs || allowed_privs[i]; + END LOOP; + FOR i IN last_index - 1..last_index LOOP + missing_privs := missing_privs || allowed_privs[i]; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + sequence_privs_are( 'ha', 'someseq', current_user, test_privs, 'whatever' ), false, 'sequence_privs_are(sch, seq, role, some privs, desc)', 'whatever', ' Extra privileges: ' || array_to_string(missing_privs, E'\n ') - ) AS b LOOP RETURN NEXT tap.b; END LOOP; + ) AS b LOOP RETURN NEXT tap.b; END LOOP; - FOR tap IN SELECT * FROM check_test( + FOR tap IN SELECT * FROM check_test( sequence_privs_are( 'someseq', current_user, test_privs, 'whatever' ), false, 'sequence_privs_are(seq, role, some privs, desc)', 'whatever', ' Extra privileges: ' || array_to_string(missing_privs, E'\n ') - ) AS b LOOP RETURN NEXT tap.b; END LOOP; -END; -$$; - -SELECT * FROM run_extra_fails(); + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - sequence_privs_are( 'ha', 'someseq', '__someone_else', ARRAY['USAGE', 'SELECT', 'UPDATE'], 'whatever' ), - false, - 'sequence_privs_are(sch, seq, other, privs, desc)', - 'whatever', - ' Missing privileges: + FOR tap IN SELECT * FROM check_test( + sequence_privs_are( 'ha', 'someseq', '__someone_else', ARRAY[ + 'USAGE', 'SELECT', 'UPDATE' + ], 'whatever' ), + false, + 'sequence_privs_are(sch, seq, other, privs, desc)', + 'whatever', + ' Missing privileges: ' || array_to_string(ARRAY['SELECT', 'UPDATE', 'USAGE'], E'\n ') -); + ) AS b LOOP RETURN NEXT tap.b; END LOOP; --- Grant them some permission. -GRANT SELECT, UPDATE ON ha.someseq TO __someone_else; + -- Grant them some permission. + GRANT SELECT, UPDATE ON ha.someseq TO __someone_else; + FOR tap IN SELECT * FROM check_test( + sequence_privs_are( 'ha', 'someseq', '__someone_else', ARRAY[ + 'SELECT', 'UPDATE' + ], 'whatever'), + true, + 'sequence_privs_are(sch, seq, other, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - sequence_privs_are( 'ha', 'someseq', '__someone_else', ARRAY[ - 'SELECT', 'UPDATE' - ], 'whatever'), - true, - 'sequence_privs_are(sch, seq, other, privs, desc)', - 'whatever', - '' -); + -- Try a non-existent sequence. + FOR tap IN SELECT * FROM check_test( + sequence_privs_are( 'ha', 'nonesuch', current_user, ARRAY[ + 'USAGE', 'SELECT', 'UPDATE' + ], 'whatever' ), + false, + 'sequence_privs_are(sch, seq, role, privs, desc)', + 'whatever', + ' Sequence ha.nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; --- Try a non-existent sequence. -SELECT * FROM check_test( - sequence_privs_are( 'ha', 'nonesuch', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'], 'whatever' ), - false, - 'sequence_privs_are(sch, seq, role, privs, desc)', - 'whatever', - ' Sequence ha.nonesuch does not exist' -); + -- Try a non-existent user. + FOR tap IN SELECT * FROM check_test( + sequence_privs_are( 'ha', 'someseq', '__nonesuch', ARRAY[ + 'USAGE', 'SELECT', 'UPDATE' + ], 'whatever' ), + false, + 'sequence_privs_are(sch, seq, role, privs, desc)', + 'whatever', + ' Role __nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; --- Try a non-existent user. -SELECT * FROM check_test( - sequence_privs_are( 'ha', 'someseq', '__nonesuch', ARRAY['USAGE', 'SELECT', 'UPDATE'], 'whatever' ), - false, - 'sequence_privs_are(sch, seq, role, privs, desc)', - 'whatever', - ' Role __nonesuch does not exist' -); + -- Test default description with no permissions. + FOR tap IN SELECT * FROM check_test( + sequence_privs_are( 'ha', 'someseq', '__nonesuch', '{}'::text[] ), + false, + 'sequence_privs_are(sch, seq, role, no privs)', + 'Role __nonesuch should be granted no privileges on sequence ha.someseq' , + ' Role __nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; --- Test default description with no permissions. -SELECT * FROM check_test( - sequence_privs_are( 'ha', 'someseq', '__nonesuch', '{}'::text[] ), - false, - 'sequence_privs_are(sch, seq, role, no privs)', - 'Role __nonesuch should be granted no privileges on sequence ha.someseq' , - ' Role __nonesuch does not exist' -); + FOR tap IN SELECT * FROM check_test( + sequence_privs_are( 'someseq', '__nonesuch', '{}'::text[] ), + false, + 'sequence_privs_are(seq, role, no privs)', + 'Role __nonesuch should be granted no privileges on sequence someseq' , + ' Role __nonesuch does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - sequence_privs_are( 'someseq', '__nonesuch', '{}'::text[] ), - false, - 'sequence_privs_are(seq, role, no privs)', - 'Role __nonesuch should be granted no privileges on sequence someseq' , - ' Role __nonesuch does not exist' -); + ELSE + -- Fake it with pass() and fail(). + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'sequence_privs_are(sch, seq, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'sequence_privs_are(sch, seq, role, privs)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'sequence_privs_are(seq, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'sequence_privs_are(seq, role, privs)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('whatever'), + false, + 'sequence_privs_are(sch, seq, role, some privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('whatever'), + false, + 'sequence_privs_are(seq, role, some privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('whatever'), + false, + 'sequence_privs_are(sch, seq, other, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- Grant them some permission. + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'sequence_privs_are(sch, seq, other, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- Try a non-existent sequence. + FOR tap IN SELECT * FROM check_test( + fail('whatever'), + false, + 'sequence_privs_are(sch, seq, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- Try a non-existent user. + FOR tap IN SELECT * FROM check_test( + fail('whatever'), + false, + 'sequence_privs_are(sch, seq, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + -- Test default description with no permissions. + FOR tap IN SELECT * FROM check_test( + fail('whatever'), + false, + 'sequence_privs_are(sch, seq, role, no privs)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('whatever'), + false, + 'sequence_privs_are(seq, role, no privs)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + END IF; +END; +$$ LANGUAGE PLPGSQL; + +SELECT * FROM test_sequence(); /****************************************************************************/ -- Test any_column_privs_are(). From 7a4e55f46d8582a18367d5e7764773926fdb2b7d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 11:39:37 -0800 Subject: [PATCH 0727/1195] Get tests passing on 9.0. --- test/expected/privs.out | 491 ++++++++++++++++++++-------------------- test/sql/hastap.sql | 10 +- test/sql/ownership.sql | 50 ++-- test/sql/privs.sql | 3 +- 4 files changed, 280 insertions(+), 274 deletions(-) diff --git a/test/expected/privs.out b/test/expected/privs.out index 01c513a18de6..4746a8038686 100644 --- a/test/expected/privs.out +++ b/test/expected/privs.out @@ -1,5 +1,5 @@ \unset ECHO -1..304 +1..305 ok 1 - table_privs_are(sch, tab, role, privs, desc) should pass ok 2 - table_privs_are(sch, tab, role, privs, desc) should have the proper description ok 3 - table_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics @@ -60,247 +60,248 @@ ok 57 - database_privs_are(db, non-role, no privs) should have the proper diagno ok 58 - function_privs_are(sch, func, args, role, privs, desc) should pass ok 59 - function_privs_are(sch, func, args, role, privs, desc) should have the proper description ok 60 - function_privs_are(sch, func, args, role, privs, desc) should have the proper diagnostics -ok 61 - Role david should be granted EXECUTE on function public.foo(integer, text) should pass -ok 62 - function_privs_are(func, args, role, privs, desc) should pass -ok 63 - function_privs_are(func, args, role, privs, desc) should have the proper description -ok 64 - function_privs_are(func, args, role, privs, desc) should have the proper diagnostics -ok 65 - function_privs_are(func, args, role, privs) should pass -ok 66 - function_privs_are(func, args, role, privs) should have the proper description -ok 67 - function_privs_are(sch, non-func, args, role, privs, desc) should fail -ok 68 - function_privs_are(sch, non-func, args, role, privs, desc) should have the proper description -ok 69 - function_privs_are(sch, non-func, args, role, privs, desc) should have the proper diagnostics -ok 70 - function_privs_are(non-func, args, role, privs, desc) should fail -ok 71 - function_privs_are(non-func, args, role, privs, desc) should have the proper description -ok 72 - function_privs_are(non-func, args, role, privs, desc) should have the proper diagnostics -ok 73 - function_privs_are(sch, func, args, noone, privs, desc) should fail -ok 74 - function_privs_are(sch, func, args, noone, privs, desc) should have the proper description -ok 75 - function_privs_are(sch, func, args, noone, privs, desc) should have the proper diagnostics -ok 76 - function_privs_are(func, args, noone, privs, desc) should fail -ok 77 - function_privs_are(func, args, noone, privs, desc) should have the proper description -ok 78 - function_privs_are(func, args, noone, privs, desc) should have the proper diagnostics -ok 79 - function_privs_are(sch, func, args, other, privs, desc) should pass -ok 80 - function_privs_are(sch, func, args, other, privs, desc) should have the proper description -ok 81 - function_privs_are(sch, func, args, other, privs, desc) should have the proper diagnostics -ok 82 - function_privs_are(func, args, other, privs, desc) should pass -ok 83 - function_privs_are(func, args, other, privs, desc) should have the proper description -ok 84 - function_privs_are(func, args, other, privs, desc) should have the proper diagnostics -ok 85 - function_privs_are(sch, func, args, unpriv, privs, desc) should fail -ok 86 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper description -ok 87 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper diagnostics -ok 88 - function_privs_are(func, args, unpriv, privs, desc) should fail -ok 89 - function_privs_are(func, args, unpriv, privs, desc) should have the proper description -ok 90 - function_privs_are(func, args, unpriv, privs, desc) should have the proper diagnostics -ok 91 - function_privs_are(sch, func, args, unpriv, empty, desc) should pass -ok 92 - function_privs_are(sch, func, args, unpriv, empty, desc) should have the proper description -ok 93 - function_privs_are(sch, func, args, unpriv, empty, desc) should have the proper diagnostics -ok 94 - function_privs_are(sch, func, args, unpriv, empty) should pass -ok 95 - function_privs_are(sch, func, args, unpriv, empty) should have the proper description -ok 96 - function_privs_are(func, args, unpriv, empty) should pass -ok 97 - function_privs_are(func, args, unpriv, empty) should have the proper description -ok 98 - function_privs_are(sch, func, args, unpriv, privs, desc) should fail -ok 99 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper description -ok 100 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper diagnostics -ok 101 - function_privs_are(func, args, unpriv, privs, desc) should fail -ok 102 - function_privs_are(func, args, unpriv, privs, desc) should have the proper description -ok 103 - function_privs_are(func, args, unpriv, privs, desc) should have the proper diagnostics -ok 104 - language_privs_are(lang, role, privs, desc) should pass -ok 105 - language_privs_are(lang, role, privs, desc) should have the proper description -ok 106 - language_privs_are(lang, role, privs, desc) should have the proper diagnostics -ok 107 - language_privs_are(lang, role, privs, desc) should pass -ok 108 - language_privs_are(lang, role, privs, desc) should have the proper description -ok 109 - language_privs_are(lang, role, privs, desc) should have the proper diagnostics -ok 110 - language_privs_are(non-lang, role, privs, desc) should fail -ok 111 - language_privs_are(non-lang, role, privs, desc) should have the proper description -ok 112 - language_privs_are(non-lang, role, privs, desc) should have the proper diagnostics -ok 113 - language_privs_are(lang, non-role, privs, desc) should fail -ok 114 - language_privs_are(lang, non-role, privs, desc) should have the proper description -ok 115 - language_privs_are(lang, non-role, privs, desc) should have the proper diagnostics -ok 116 - language_privs_are(lang, ungranted, privs, desc) should fail -ok 117 - language_privs_are(lang, ungranted, privs, desc) should have the proper description -ok 118 - language_privs_are(lang, ungranted, privs, desc) should have the proper diagnostics -ok 119 - language_privs_are(lang, role, no privs) should pass -ok 120 - language_privs_are(lang, role, no privs) should have the proper description -ok 121 - language_privs_are(lang, role, no privs) should have the proper diagnostics -ok 122 - schema_privs_are(schema, role, privs, desc) should pass -ok 123 - schema_privs_are(schema, role, privs, desc) should have the proper description -ok 124 - schema_privs_are(schema, role, privs, desc) should have the proper diagnostics -ok 125 - schema_privs_are(schema, role, privs, desc) should pass -ok 126 - schema_privs_are(schema, role, privs, desc) should have the proper description -ok 127 - schema_privs_are(schema, role, privs, desc) should have the proper diagnostics -ok 128 - schema_privs_are(non-schema, role, privs, desc) should fail -ok 129 - schema_privs_are(non-schema, role, privs, desc) should have the proper description -ok 130 - schema_privs_are(non-schema, role, privs, desc) should have the proper diagnostics -ok 131 - schema_privs_are(schema, non-role, privs, desc) should fail -ok 132 - schema_privs_are(schema, non-role, privs, desc) should have the proper description -ok 133 - schema_privs_are(schema, non-role, privs, desc) should have the proper diagnostics -ok 134 - schema_privs_are(schema, ungranted, privs, desc) should fail -ok 135 - schema_privs_are(schema, ungranted, privs, desc) should have the proper description -ok 136 - schema_privs_are(schema, ungranted, privs, desc) should have the proper diagnostics -ok 137 - schema_privs_are(schema, ungranted, privs, desc) should fail -ok 138 - schema_privs_are(schema, ungranted, privs, desc) should have the proper description -ok 139 - schema_privs_are(schema, ungranted, privs, desc) should have the proper diagnostics -ok 140 - schema_privs_are(schema, non-role, no privs) should fail -ok 141 - schema_privs_are(schema, non-role, no privs) should have the proper description -ok 142 - schema_privs_are(schema, non-role, no privs) should have the proper diagnostics -ok 143 - tablespace_privs_are(tablespace, role, privs, desc) should pass -ok 144 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper description -ok 145 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper diagnostics -ok 146 - tablespace_privs_are(tablespace, role, privs, desc) should pass -ok 147 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper description -ok 148 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper diagnostics -ok 149 - tablespace_privs_are(non-tablespace, role, privs, desc) should fail -ok 150 - tablespace_privs_are(non-tablespace, role, privs, desc) should have the proper description -ok 151 - tablespace_privs_are(non-tablespace, role, privs, desc) should have the proper diagnostics -ok 152 - tablespace_privs_are(tablespace, non-role, privs, desc) should fail -ok 153 - tablespace_privs_are(tablespace, non-role, privs, desc) should have the proper description -ok 154 - tablespace_privs_are(tablespace, non-role, privs, desc) should have the proper diagnostics -ok 155 - tablespace_privs_are(tablespace, ungranted, privs, desc) should fail -ok 156 - tablespace_privs_are(tablespace, ungranted, privs, desc) should have the proper description -ok 157 - tablespace_privs_are(tablespace, ungranted, privs, desc) should have the proper diagnostics -ok 158 - tablespace_privs_are(tablespace, role, no privs) should pass -ok 159 - tablespace_privs_are(tablespace, role, no privs) should have the proper description -ok 160 - tablespace_privs_are(tablespace, role, no privs) should have the proper diagnostics -ok 161 - sequence_privs_are(sch, seq, role, privs, desc) should pass -ok 162 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description -ok 163 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics -ok 164 - sequence_privs_are(sch, seq, role, privs) should pass -ok 165 - sequence_privs_are(sch, seq, role, privs) should have the proper description -ok 166 - sequence_privs_are(sch, seq, role, privs) should have the proper diagnostics -ok 167 - sequence_privs_are(seq, role, privs, desc) should pass -ok 168 - sequence_privs_are(seq, role, privs, desc) should have the proper description -ok 169 - sequence_privs_are(seq, role, privs, desc) should have the proper diagnostics -ok 170 - sequence_privs_are(seq, role, privs) should pass -ok 171 - sequence_privs_are(seq, role, privs) should have the proper description -ok 172 - sequence_privs_are(seq, role, privs) should have the proper diagnostics -ok 173 - sequence_privs_are(sch, seq, role, some privs, desc) should fail -ok 174 - sequence_privs_are(sch, seq, role, some privs, desc) should have the proper description -ok 175 - sequence_privs_are(sch, seq, role, some privs, desc) should have the proper diagnostics -ok 176 - sequence_privs_are(seq, role, some privs, desc) should fail -ok 177 - sequence_privs_are(seq, role, some privs, desc) should have the proper description -ok 178 - sequence_privs_are(seq, role, some privs, desc) should have the proper diagnostics -ok 179 - sequence_privs_are(sch, seq, other, privs, desc) should fail -ok 180 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper description -ok 181 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper diagnostics -ok 182 - sequence_privs_are(sch, seq, other, privs, desc) should pass -ok 183 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper description -ok 184 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper diagnostics -ok 185 - sequence_privs_are(sch, seq, role, privs, desc) should fail -ok 186 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description -ok 187 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics -ok 188 - sequence_privs_are(sch, seq, role, privs, desc) should fail -ok 189 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description -ok 190 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics -ok 191 - sequence_privs_are(sch, seq, role, no privs) should fail -ok 192 - sequence_privs_are(sch, seq, role, no privs) should have the proper description -ok 193 - sequence_privs_are(sch, seq, role, no privs) should have the proper diagnostics -ok 194 - sequence_privs_are(seq, role, no privs) should fail -ok 195 - sequence_privs_are(seq, role, no privs) should have the proper description -ok 196 - sequence_privs_are(seq, role, no privs) should have the proper diagnostics -ok 197 - any_column_privs_are(sch, tab, role, privs, desc) should pass -ok 198 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 199 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 200 - any_column_privs_are(sch, tab, role, privs) should pass -ok 201 - any_column_privs_are(sch, tab, role, privs) should have the proper description -ok 202 - any_column_privs_are(sch, tab, role, privs) should have the proper diagnostics -ok 203 - any_column_privs_are(tab, role, privs, desc) should pass -ok 204 - any_column_privs_are(tab, role, privs, desc) should have the proper description -ok 205 - any_column_privs_are(tab, role, privs, desc) should have the proper diagnostics -ok 206 - any_column_privs_are(tab, role, privs) should pass -ok 207 - any_column_privs_are(tab, role, privs) should have the proper description -ok 208 - any_column_privs_are(tab, role, privs) should have the proper diagnostics -ok 209 - any_column_privs_are(sch, tab, role, some privs, desc) should fail -ok 210 - any_column_privs_are(sch, tab, role, some privs, desc) should have the proper description -ok 211 - any_column_privs_are(sch, tab, role, some privs, desc) should have the proper diagnostics -ok 212 - any_column_privs_are(tab, role, some privs, desc) should fail -ok 213 - any_column_privs_are(tab, role, some privs, desc) should have the proper description -ok 214 - any_column_privs_are(tab, role, some privs, desc) should have the proper diagnostics -ok 215 - any_column_privs_are(sch, tab, other, privs, desc) should fail -ok 216 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper description -ok 217 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics -ok 218 - any_column_privs_are(sch, tab, other, privs, desc) should pass -ok 219 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper description -ok 220 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics -ok 221 - any_column_privs_are(sch, tab, role, privs, desc) should fail -ok 222 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 223 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 224 - any_column_privs_are(sch, tab, role, privs, desc) should fail -ok 225 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 226 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 227 - any_column_privs_are(sch, tab, role, no privs) should fail -ok 228 - any_column_privs_are(sch, tab, role, no privs) should have the proper description -ok 229 - any_column_privs_are(sch, tab, role, no privs) should have the proper diagnostics -ok 230 - any_column_privs_are(tab, role, no privs) should fail -ok 231 - any_column_privs_are(tab, role, no privs) should have the proper description -ok 232 - any_column_privs_are(tab, role, no privs) should have the proper diagnostics -ok 233 - column_privs_are(sch, tab, role, privs, desc) should pass -ok 234 - column_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 235 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 236 - column_privs_are(sch, tab, role, privs) should pass -ok 237 - column_privs_are(sch, tab, role, privs) should have the proper description -ok 238 - column_privs_are(sch, tab, role, privs) should have the proper diagnostics -ok 239 - column_privs_are(tab, role, privs, desc) should pass -ok 240 - column_privs_are(tab, role, privs, desc) should have the proper description -ok 241 - column_privs_are(tab, role, privs, desc) should have the proper diagnostics -ok 242 - column_privs_are(tab, role, privs) should pass -ok 243 - column_privs_are(tab, role, privs) should have the proper description -ok 244 - column_privs_are(tab, role, privs) should have the proper diagnostics -ok 245 - column_privs_are(sch, tab, role, some privs, desc) should fail -ok 246 - column_privs_are(sch, tab, role, some privs, desc) should have the proper description -ok 247 - column_privs_are(sch, tab, role, some privs, desc) should have the proper diagnostics -ok 248 - column_privs_are(tab, role, some privs, desc) should fail -ok 249 - column_privs_are(tab, role, some privs, desc) should have the proper description -ok 250 - column_privs_are(tab, role, some privs, desc) should have the proper diagnostics -ok 251 - column_privs_are(sch, tab, other, privs, desc) should fail -ok 252 - column_privs_are(sch, tab, other, privs, desc) should have the proper description -ok 253 - column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics -ok 254 - column_privs_are(sch, tab, other, privs, desc) should pass -ok 255 - column_privs_are(sch, tab, other, privs, desc) should have the proper description -ok 256 - column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics -ok 257 - column_privs_are(sch, tab, role, privs, desc) should fail -ok 258 - column_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 259 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 260 - column_privs_are(sch, tab, role, privs, desc) should fail -ok 261 - column_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 262 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 263 - column_privs_are(sch, tab, role, no privs) should fail -ok 264 - column_privs_are(sch, tab, role, no privs) should have the proper description -ok 265 - column_privs_are(sch, tab, role, no privs) should have the proper diagnostics -ok 266 - column_privs_are(tab, role, no privs) should fail -ok 267 - column_privs_are(tab, role, no privs) should have the proper description -ok 268 - column_privs_are(tab, role, no privs) should have the proper diagnostics -ok 269 - fdw_privs_are(fdw, role, privs, desc) should pass -ok 270 - fdw_privs_are(fdw, role, privs, desc) should have the proper description -ok 271 - fdw_privs_are(fdw, role, privs, desc) should have the proper diagnostics -ok 272 - fdw_privs_are(fdw, role, privs, desc) should pass -ok 273 - fdw_privs_are(fdw, role, privs, desc) should have the proper description -ok 274 - fdw_privs_are(fdw, role, privs, desc) should have the proper diagnostics -ok 275 - fdw_privs_are(non-fdw, role, privs, desc) should fail -ok 276 - fdw_privs_are(non-fdw, role, privs, desc) should have the proper description -ok 277 - fdw_privs_are(non-fdw, role, privs, desc) should have the proper diagnostics -ok 278 - fdw_privs_are(fdw, non-role, privs, desc) should fail -ok 279 - fdw_privs_are(fdw, non-role, privs, desc) should have the proper description -ok 280 - fdw_privs_are(fdw, non-role, privs, desc) should have the proper diagnostics -ok 281 - fdw_privs_are(fdw, ungranted, privs, desc) should fail -ok 282 - fdw_privs_are(fdw, ungranted, privs, desc) should have the proper description -ok 283 - fdw_privs_are(fdw, ungranted, privs, desc) should have the proper diagnostics -ok 284 - fdw_privs_are(fdw, role, no privs) should pass -ok 285 - fdw_privs_are(fdw, role, no privs) should have the proper description -ok 286 - fdw_privs_are(fdw, role, no privs) should have the proper diagnostics -ok 287 - server_privs_are(server, role, privs, desc) should pass -ok 288 - server_privs_are(server, role, privs, desc) should have the proper description -ok 289 - server_privs_are(server, role, privs, desc) should have the proper diagnostics -ok 290 - server_privs_are(server, role, privs, desc) should pass -ok 291 - server_privs_are(server, role, privs, desc) should have the proper description -ok 292 - server_privs_are(server, role, privs, desc) should have the proper diagnostics -ok 293 - server_privs_are(non-server, role, privs, desc) should fail -ok 294 - server_privs_are(non-server, role, privs, desc) should have the proper description -ok 295 - server_privs_are(non-server, role, privs, desc) should have the proper diagnostics -ok 296 - server_privs_are(server, non-role, privs, desc) should fail -ok 297 - server_privs_are(server, non-role, privs, desc) should have the proper description -ok 298 - server_privs_are(server, non-role, privs, desc) should have the proper diagnostics -ok 299 - server_privs_are(server, ungranted, privs, desc) should fail -ok 300 - server_privs_are(server, ungranted, privs, desc) should have the proper description -ok 301 - server_privs_are(server, ungranted, privs, desc) should have the proper diagnostics -ok 302 - server_privs_are(server, role, no privs) should pass -ok 303 - server_privs_are(server, role, no privs) should have the proper description -ok 304 - server_privs_are(server, role, no privs) should have the proper diagnostics +ok 61 - function_privs_are(sch, func, args, role, privs) should pass +ok 62 - function_privs_are(sch, func, args, role, privs) should have the proper description +ok 63 - function_privs_are(func, args, role, privs, desc) should pass +ok 64 - function_privs_are(func, args, role, privs, desc) should have the proper description +ok 65 - function_privs_are(func, args, role, privs, desc) should have the proper diagnostics +ok 66 - function_privs_are(func, args, role, privs) should pass +ok 67 - function_privs_are(func, args, role, privs) should have the proper description +ok 68 - function_privs_are(sch, non-func, args, role, privs, desc) should fail +ok 69 - function_privs_are(sch, non-func, args, role, privs, desc) should have the proper description +ok 70 - function_privs_are(sch, non-func, args, role, privs, desc) should have the proper diagnostics +ok 71 - function_privs_are(non-func, args, role, privs, desc) should fail +ok 72 - function_privs_are(non-func, args, role, privs, desc) should have the proper description +ok 73 - function_privs_are(non-func, args, role, privs, desc) should have the proper diagnostics +ok 74 - function_privs_are(sch, func, args, noone, privs, desc) should fail +ok 75 - function_privs_are(sch, func, args, noone, privs, desc) should have the proper description +ok 76 - function_privs_are(sch, func, args, noone, privs, desc) should have the proper diagnostics +ok 77 - function_privs_are(func, args, noone, privs, desc) should fail +ok 78 - function_privs_are(func, args, noone, privs, desc) should have the proper description +ok 79 - function_privs_are(func, args, noone, privs, desc) should have the proper diagnostics +ok 80 - function_privs_are(sch, func, args, other, privs, desc) should pass +ok 81 - function_privs_are(sch, func, args, other, privs, desc) should have the proper description +ok 82 - function_privs_are(sch, func, args, other, privs, desc) should have the proper diagnostics +ok 83 - function_privs_are(func, args, other, privs, desc) should pass +ok 84 - function_privs_are(func, args, other, privs, desc) should have the proper description +ok 85 - function_privs_are(func, args, other, privs, desc) should have the proper diagnostics +ok 86 - function_privs_are(sch, func, args, unpriv, privs, desc) should fail +ok 87 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper description +ok 88 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper diagnostics +ok 89 - function_privs_are(func, args, unpriv, privs, desc) should fail +ok 90 - function_privs_are(func, args, unpriv, privs, desc) should have the proper description +ok 91 - function_privs_are(func, args, unpriv, privs, desc) should have the proper diagnostics +ok 92 - function_privs_are(sch, func, args, unpriv, empty, desc) should pass +ok 93 - function_privs_are(sch, func, args, unpriv, empty, desc) should have the proper description +ok 94 - function_privs_are(sch, func, args, unpriv, empty, desc) should have the proper diagnostics +ok 95 - function_privs_are(sch, func, args, unpriv, empty) should pass +ok 96 - function_privs_are(sch, func, args, unpriv, empty) should have the proper description +ok 97 - function_privs_are(func, args, unpriv, empty) should pass +ok 98 - function_privs_are(func, args, unpriv, empty) should have the proper description +ok 99 - function_privs_are(sch, func, args, unpriv, privs, desc) should fail +ok 100 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper description +ok 101 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper diagnostics +ok 102 - function_privs_are(func, args, unpriv, privs, desc) should fail +ok 103 - function_privs_are(func, args, unpriv, privs, desc) should have the proper description +ok 104 - function_privs_are(func, args, unpriv, privs, desc) should have the proper diagnostics +ok 105 - language_privs_are(lang, role, privs, desc) should pass +ok 106 - language_privs_are(lang, role, privs, desc) should have the proper description +ok 107 - language_privs_are(lang, role, privs, desc) should have the proper diagnostics +ok 108 - language_privs_are(lang, role, privs, desc) should pass +ok 109 - language_privs_are(lang, role, privs, desc) should have the proper description +ok 110 - language_privs_are(lang, role, privs, desc) should have the proper diagnostics +ok 111 - language_privs_are(non-lang, role, privs, desc) should fail +ok 112 - language_privs_are(non-lang, role, privs, desc) should have the proper description +ok 113 - language_privs_are(non-lang, role, privs, desc) should have the proper diagnostics +ok 114 - language_privs_are(lang, non-role, privs, desc) should fail +ok 115 - language_privs_are(lang, non-role, privs, desc) should have the proper description +ok 116 - language_privs_are(lang, non-role, privs, desc) should have the proper diagnostics +ok 117 - language_privs_are(lang, ungranted, privs, desc) should fail +ok 118 - language_privs_are(lang, ungranted, privs, desc) should have the proper description +ok 119 - language_privs_are(lang, ungranted, privs, desc) should have the proper diagnostics +ok 120 - language_privs_are(lang, role, no privs) should pass +ok 121 - language_privs_are(lang, role, no privs) should have the proper description +ok 122 - language_privs_are(lang, role, no privs) should have the proper diagnostics +ok 123 - schema_privs_are(schema, role, privs, desc) should pass +ok 124 - schema_privs_are(schema, role, privs, desc) should have the proper description +ok 125 - schema_privs_are(schema, role, privs, desc) should have the proper diagnostics +ok 126 - schema_privs_are(schema, role, privs, desc) should pass +ok 127 - schema_privs_are(schema, role, privs, desc) should have the proper description +ok 128 - schema_privs_are(schema, role, privs, desc) should have the proper diagnostics +ok 129 - schema_privs_are(non-schema, role, privs, desc) should fail +ok 130 - schema_privs_are(non-schema, role, privs, desc) should have the proper description +ok 131 - schema_privs_are(non-schema, role, privs, desc) should have the proper diagnostics +ok 132 - schema_privs_are(schema, non-role, privs, desc) should fail +ok 133 - schema_privs_are(schema, non-role, privs, desc) should have the proper description +ok 134 - schema_privs_are(schema, non-role, privs, desc) should have the proper diagnostics +ok 135 - schema_privs_are(schema, ungranted, privs, desc) should fail +ok 136 - schema_privs_are(schema, ungranted, privs, desc) should have the proper description +ok 137 - schema_privs_are(schema, ungranted, privs, desc) should have the proper diagnostics +ok 138 - schema_privs_are(schema, ungranted, privs, desc) should fail +ok 139 - schema_privs_are(schema, ungranted, privs, desc) should have the proper description +ok 140 - schema_privs_are(schema, ungranted, privs, desc) should have the proper diagnostics +ok 141 - schema_privs_are(schema, non-role, no privs) should fail +ok 142 - schema_privs_are(schema, non-role, no privs) should have the proper description +ok 143 - schema_privs_are(schema, non-role, no privs) should have the proper diagnostics +ok 144 - tablespace_privs_are(tablespace, role, privs, desc) should pass +ok 145 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper description +ok 146 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper diagnostics +ok 147 - tablespace_privs_are(tablespace, role, privs, desc) should pass +ok 148 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper description +ok 149 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper diagnostics +ok 150 - tablespace_privs_are(non-tablespace, role, privs, desc) should fail +ok 151 - tablespace_privs_are(non-tablespace, role, privs, desc) should have the proper description +ok 152 - tablespace_privs_are(non-tablespace, role, privs, desc) should have the proper diagnostics +ok 153 - tablespace_privs_are(tablespace, non-role, privs, desc) should fail +ok 154 - tablespace_privs_are(tablespace, non-role, privs, desc) should have the proper description +ok 155 - tablespace_privs_are(tablespace, non-role, privs, desc) should have the proper diagnostics +ok 156 - tablespace_privs_are(tablespace, ungranted, privs, desc) should fail +ok 157 - tablespace_privs_are(tablespace, ungranted, privs, desc) should have the proper description +ok 158 - tablespace_privs_are(tablespace, ungranted, privs, desc) should have the proper diagnostics +ok 159 - tablespace_privs_are(tablespace, role, no privs) should pass +ok 160 - tablespace_privs_are(tablespace, role, no privs) should have the proper description +ok 161 - tablespace_privs_are(tablespace, role, no privs) should have the proper diagnostics +ok 162 - sequence_privs_are(sch, seq, role, privs, desc) should pass +ok 163 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description +ok 164 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics +ok 165 - sequence_privs_are(sch, seq, role, privs) should pass +ok 166 - sequence_privs_are(sch, seq, role, privs) should have the proper description +ok 167 - sequence_privs_are(sch, seq, role, privs) should have the proper diagnostics +ok 168 - sequence_privs_are(seq, role, privs, desc) should pass +ok 169 - sequence_privs_are(seq, role, privs, desc) should have the proper description +ok 170 - sequence_privs_are(seq, role, privs, desc) should have the proper diagnostics +ok 171 - sequence_privs_are(seq, role, privs) should pass +ok 172 - sequence_privs_are(seq, role, privs) should have the proper description +ok 173 - sequence_privs_are(seq, role, privs) should have the proper diagnostics +ok 174 - sequence_privs_are(sch, seq, role, some privs, desc) should fail +ok 175 - sequence_privs_are(sch, seq, role, some privs, desc) should have the proper description +ok 176 - sequence_privs_are(sch, seq, role, some privs, desc) should have the proper diagnostics +ok 177 - sequence_privs_are(seq, role, some privs, desc) should fail +ok 178 - sequence_privs_are(seq, role, some privs, desc) should have the proper description +ok 179 - sequence_privs_are(seq, role, some privs, desc) should have the proper diagnostics +ok 180 - sequence_privs_are(sch, seq, other, privs, desc) should fail +ok 181 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper description +ok 182 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper diagnostics +ok 183 - sequence_privs_are(sch, seq, other, privs, desc) should pass +ok 184 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper description +ok 185 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper diagnostics +ok 186 - sequence_privs_are(sch, seq, role, privs, desc) should fail +ok 187 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description +ok 188 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics +ok 189 - sequence_privs_are(sch, seq, role, privs, desc) should fail +ok 190 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description +ok 191 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics +ok 192 - sequence_privs_are(sch, seq, role, no privs) should fail +ok 193 - sequence_privs_are(sch, seq, role, no privs) should have the proper description +ok 194 - sequence_privs_are(sch, seq, role, no privs) should have the proper diagnostics +ok 195 - sequence_privs_are(seq, role, no privs) should fail +ok 196 - sequence_privs_are(seq, role, no privs) should have the proper description +ok 197 - sequence_privs_are(seq, role, no privs) should have the proper diagnostics +ok 198 - any_column_privs_are(sch, tab, role, privs, desc) should pass +ok 199 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 200 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 201 - any_column_privs_are(sch, tab, role, privs) should pass +ok 202 - any_column_privs_are(sch, tab, role, privs) should have the proper description +ok 203 - any_column_privs_are(sch, tab, role, privs) should have the proper diagnostics +ok 204 - any_column_privs_are(tab, role, privs, desc) should pass +ok 205 - any_column_privs_are(tab, role, privs, desc) should have the proper description +ok 206 - any_column_privs_are(tab, role, privs, desc) should have the proper diagnostics +ok 207 - any_column_privs_are(tab, role, privs) should pass +ok 208 - any_column_privs_are(tab, role, privs) should have the proper description +ok 209 - any_column_privs_are(tab, role, privs) should have the proper diagnostics +ok 210 - any_column_privs_are(sch, tab, role, some privs, desc) should fail +ok 211 - any_column_privs_are(sch, tab, role, some privs, desc) should have the proper description +ok 212 - any_column_privs_are(sch, tab, role, some privs, desc) should have the proper diagnostics +ok 213 - any_column_privs_are(tab, role, some privs, desc) should fail +ok 214 - any_column_privs_are(tab, role, some privs, desc) should have the proper description +ok 215 - any_column_privs_are(tab, role, some privs, desc) should have the proper diagnostics +ok 216 - any_column_privs_are(sch, tab, other, privs, desc) should fail +ok 217 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 218 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics +ok 219 - any_column_privs_are(sch, tab, other, privs, desc) should pass +ok 220 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 221 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics +ok 222 - any_column_privs_are(sch, tab, role, privs, desc) should fail +ok 223 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 224 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 225 - any_column_privs_are(sch, tab, role, privs, desc) should fail +ok 226 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 227 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 228 - any_column_privs_are(sch, tab, role, no privs) should fail +ok 229 - any_column_privs_are(sch, tab, role, no privs) should have the proper description +ok 230 - any_column_privs_are(sch, tab, role, no privs) should have the proper diagnostics +ok 231 - any_column_privs_are(tab, role, no privs) should fail +ok 232 - any_column_privs_are(tab, role, no privs) should have the proper description +ok 233 - any_column_privs_are(tab, role, no privs) should have the proper diagnostics +ok 234 - column_privs_are(sch, tab, role, privs, desc) should pass +ok 235 - column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 236 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 237 - column_privs_are(sch, tab, role, privs) should pass +ok 238 - column_privs_are(sch, tab, role, privs) should have the proper description +ok 239 - column_privs_are(sch, tab, role, privs) should have the proper diagnostics +ok 240 - column_privs_are(tab, role, privs, desc) should pass +ok 241 - column_privs_are(tab, role, privs, desc) should have the proper description +ok 242 - column_privs_are(tab, role, privs, desc) should have the proper diagnostics +ok 243 - column_privs_are(tab, role, privs) should pass +ok 244 - column_privs_are(tab, role, privs) should have the proper description +ok 245 - column_privs_are(tab, role, privs) should have the proper diagnostics +ok 246 - column_privs_are(sch, tab, role, some privs, desc) should fail +ok 247 - column_privs_are(sch, tab, role, some privs, desc) should have the proper description +ok 248 - column_privs_are(sch, tab, role, some privs, desc) should have the proper diagnostics +ok 249 - column_privs_are(tab, role, some privs, desc) should fail +ok 250 - column_privs_are(tab, role, some privs, desc) should have the proper description +ok 251 - column_privs_are(tab, role, some privs, desc) should have the proper diagnostics +ok 252 - column_privs_are(sch, tab, other, privs, desc) should fail +ok 253 - column_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 254 - column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics +ok 255 - column_privs_are(sch, tab, other, privs, desc) should pass +ok 256 - column_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 257 - column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics +ok 258 - column_privs_are(sch, tab, role, privs, desc) should fail +ok 259 - column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 260 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 261 - column_privs_are(sch, tab, role, privs, desc) should fail +ok 262 - column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 263 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 264 - column_privs_are(sch, tab, role, no privs) should fail +ok 265 - column_privs_are(sch, tab, role, no privs) should have the proper description +ok 266 - column_privs_are(sch, tab, role, no privs) should have the proper diagnostics +ok 267 - column_privs_are(tab, role, no privs) should fail +ok 268 - column_privs_are(tab, role, no privs) should have the proper description +ok 269 - column_privs_are(tab, role, no privs) should have the proper diagnostics +ok 270 - fdw_privs_are(fdw, role, privs, desc) should pass +ok 271 - fdw_privs_are(fdw, role, privs, desc) should have the proper description +ok 272 - fdw_privs_are(fdw, role, privs, desc) should have the proper diagnostics +ok 273 - fdw_privs_are(fdw, role, privs, desc) should pass +ok 274 - fdw_privs_are(fdw, role, privs, desc) should have the proper description +ok 275 - fdw_privs_are(fdw, role, privs, desc) should have the proper diagnostics +ok 276 - fdw_privs_are(non-fdw, role, privs, desc) should fail +ok 277 - fdw_privs_are(non-fdw, role, privs, desc) should have the proper description +ok 278 - fdw_privs_are(non-fdw, role, privs, desc) should have the proper diagnostics +ok 279 - fdw_privs_are(fdw, non-role, privs, desc) should fail +ok 280 - fdw_privs_are(fdw, non-role, privs, desc) should have the proper description +ok 281 - fdw_privs_are(fdw, non-role, privs, desc) should have the proper diagnostics +ok 282 - fdw_privs_are(fdw, ungranted, privs, desc) should fail +ok 283 - fdw_privs_are(fdw, ungranted, privs, desc) should have the proper description +ok 284 - fdw_privs_are(fdw, ungranted, privs, desc) should have the proper diagnostics +ok 285 - fdw_privs_are(fdw, role, no privs) should pass +ok 286 - fdw_privs_are(fdw, role, no privs) should have the proper description +ok 287 - fdw_privs_are(fdw, role, no privs) should have the proper diagnostics +ok 288 - server_privs_are(server, role, privs, desc) should pass +ok 289 - server_privs_are(server, role, privs, desc) should have the proper description +ok 290 - server_privs_are(server, role, privs, desc) should have the proper diagnostics +ok 291 - server_privs_are(server, role, privs, desc) should pass +ok 292 - server_privs_are(server, role, privs, desc) should have the proper description +ok 293 - server_privs_are(server, role, privs, desc) should have the proper diagnostics +ok 294 - server_privs_are(non-server, role, privs, desc) should fail +ok 295 - server_privs_are(non-server, role, privs, desc) should have the proper description +ok 296 - server_privs_are(non-server, role, privs, desc) should have the proper diagnostics +ok 297 - server_privs_are(server, non-role, privs, desc) should fail +ok 298 - server_privs_are(server, non-role, privs, desc) should have the proper description +ok 299 - server_privs_are(server, non-role, privs, desc) should have the proper diagnostics +ok 300 - server_privs_are(server, ungranted, privs, desc) should fail +ok 301 - server_privs_are(server, ungranted, privs, desc) should have the proper description +ok 302 - server_privs_are(server, ungranted, privs, desc) should have the proper diagnostics +ok 303 - server_privs_are(server, role, no privs) should pass +ok 304 - server_privs_are(server, role, no privs) should have the proper description +ok 305 - server_privs_are(server, role, no privs) should have the proper diagnostics diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index c3f3bdca5b44..bad13afa30fa 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1999,11 +1999,13 @@ SELECT * FROM check_test( CREATE FUNCTION test_fdw() RETURNS SETOF TEXT AS $$ DECLARE tap record; -BEGIN +BEGIN IF pg_version_num() >= 90100 THEN - CREATE FOREIGN DATA WRAPPER dummy; - CREATE SERVER foo FOREIGN DATA WRAPPER dummy; - CREATE FOREIGN TABLE public.my_fdw (id int) SERVER foo; + EXECUTE $E$ + CREATE FOREIGN DATA WRAPPER dummy; + CREATE SERVER foo FOREIGN DATA WRAPPER dummy; + CREATE FOREIGN TABLE public.my_fdw (id int) SERVER foo; + $E$; FOR tap IN SELECT * FROM check_test( has_foreign_table( '__SDFSDFD__' ), diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index b4ba84cdfa05..4d66a985702c 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -485,9 +485,11 @@ DECLARE tap record; BEGIN IF pg_version_num() >= 90100 THEN - CREATE FOREIGN DATA WRAPPER dummy; - CREATE SERVER foo FOREIGN DATA WRAPPER dummy; - CREATE FOREIGN TABLE public.my_fdw (id int) SERVER foo; + EXECUTE $E$ + CREATE FOREIGN DATA WRAPPER dummy; + CREATE SERVER foo FOREIGN DATA WRAPPER dummy; + CREATE FOREIGN TABLE public.my_fdw (id int) SERVER foo; + $E$; FOR tap IN SELECT * FROM check_test( foreign_table_owner_is('public', 'my_fdw', current_user, 'mumble'), @@ -559,12 +561,12 @@ BEGIN false, 'foreign_table_owner_is(tab, user, desc)', 'mumble', - ' Foreign table sometab does not exist' + ' Foreign table sometab does not exist' ) AS b LOOP RETURN NEXT tap.b; END LOOP; ELSE - -- Fake it with table_owner_is(). + -- Fake it with pass() and fail(). FOR tap IN SELECT * FROM check_test( - table_owner_is('public', 'sometab', current_user, 'mumble'), + pass('mumble'), true, 'foreign_table_owner_is(sch, tab, user, desc)', 'mumble', @@ -572,31 +574,31 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - table_owner_is('public', 'sometab', current_user), + pass('mumble'), true, 'foreign_table_owner_is(sch, tab, user)', - 'Table public.sometab should be owned by ' || current_user, + 'mumble', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - table_owner_is('__not__public', 'sometab', current_user, 'mumble'), + fail('mumble'), false, 'foreign_table_owner_is(non-sch, tab, user)', 'mumble', - ' Table __not__public.sometab does not exist' + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - table_owner_is('public', '__not__sometab', current_user, 'mumble'), + fail('mumble'), false, 'foreign_table_owner_is(sch, non-tab, user)', 'mumble', - ' Table public.__not__sometab does not exist' + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - table_owner_is('sometab', current_user, 'mumble'), + pass('mumble'), true, 'foreign_table_owner_is(tab, user, desc)', 'mumble', @@ -604,36 +606,36 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - table_owner_is('sometab', current_user), + pass('mumble'), true, 'foreign_table_owner_is(tab, user)', - 'Table sometab should be owned by ' || current_user, + 'mumble', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - table_owner_is('__not__sometab', current_user, 'mumble'), + fail('mumble'), false, 'foreign_table_owner_is(non-tab, user)', 'mumble', - ' Table __not__sometab does not exist' + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; - -- It should ignore the sequence. + -- It should ignore the table. FOR tap IN SELECT * FROM check_test( - table_owner_is('public', 'someseq', current_user, 'mumble'), + fail('mumble'), false, - 'foreign_table_owner_is(sch, seq, user, desc)', + 'foreign_table_owner_is(sch, tab, user, desc)', 'mumble', - ' Table public.someseq does not exist' + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - table_owner_is('someseq', current_user, 'mumble'), + fail('mumble'), false, - 'foreign_table_owner_is(seq, user, desc)', + 'foreign_table_owner_is(tab, user, desc)', 'mumble', - ' Table someseq does not exist' + '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; END IF; RETURN; diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 11b0cfda3b6c..768fc025d03a 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(304); +SELECT plan(305); --SELECT * FROM no_plan(); SET client_min_messages = warning; @@ -240,6 +240,7 @@ SELECT * FROM check_test( current_user, ARRAY['EXECUTE'] ), true, + 'function_privs_are(sch, func, args, role, privs)', 'Role ' || current_user || ' should be granted EXECUTE on function public.foo(integer, text)' '' ); From 5098f94006488e20224469592fe7fcbd989a0bc3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 11:48:38 -0800 Subject: [PATCH 0728/1195] Note new compatability issues. --- doc/pgtap.mmd | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 1c91be29dbaa..a2f25bfe9d85 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7009,11 +7009,21 @@ instead: To see the specifics for each version of PostgreSQL, consult the files in the `compat/` directory in the pgTAP distribution. -8.4 and Up +9.1 and Up ---------- No changes. Everything should just work. +9.0 and Down +------------ +* The `foreign_table_owner_is()` function will not work, because, of course, + there were no foreign tables until 9.1. + +8.4 and Down +------------ +* The `sequence_privs_are()` function does not work, because privileges could + not be granted on sequences before 9.0. + 8.3 and Down ------------ * A patch is applied to modify `results_eq()` and `row_eq()` to cast records @@ -7025,6 +7035,13 @@ No changes. Everything should just work. for `cmp_ok()` and `isa_ok()` to work. * The variadic forms of `diag()` and `collect_tap()` are not available. You can pass an array of TAP to `collect_tap()`, however. +* These permission-testing functions don't work, because one cannot grant + permissions on the relevant objects until 8.4: + + + `has_any_column_privilege()` + + `has_column_privilege()` + + `has_foreign_data_wrapper_privilege()` + + `has_server_privilege()` 8.2 and Down ------------ From b196c2c33f622c2b7ba9b0bd71a88b9c3178d40b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 12:06:43 -0800 Subject: [PATCH 0729/1195] Get tests passing on 8.4. --- Makefile | 3 +++ compat/install-8.4.patch | 34 ++++++++++++++++++++++++++++++++++ doc/pgtap.mmd | 1 + 3 files changed, 38 insertions(+) create mode 100644 compat/install-8.4.patch diff --git a/Makefile b/Makefile index 5f7165544651..8050c34a055a 100644 --- a/Makefile +++ b/Makefile @@ -94,6 +94,9 @@ endif sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql cp $< $@ +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][1234]" && echo yes || echo no),yes) + patch -p0 < compat/install-8.4.patch +endif ifeq ($(shell echo $(VERSION) | grep -qE "8[.][123]" && echo yes || echo no),yes) patch -p0 < compat/install-8.3.patch endif diff --git a/compat/install-8.4.patch b/compat/install-8.4.patch new file mode 100644 index 000000000000..4e2a8d09eb1c --- /dev/null +++ b/compat/install-8.4.patch @@ -0,0 +1,34 @@ +--- sql/pgtap.sql.saf 2013-01-15 12:01:39.000000000 -0800 ++++ sql/pgtap.sql 2013-01-15 12:01:55.000000000 -0800 +@@ -7172,7 +7172,6 @@ + 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) +@@ -7187,7 +7186,6 @@ + JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = $1 + AND c.relname = $2 +- AND NOT t.tgisinternal + ), + $4 + ); +@@ -7211,7 +7209,6 @@ + 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) +@@ -7225,7 +7222,6 @@ + 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 + ); diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index a2f25bfe9d85..a3320a4c2015 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7023,6 +7023,7 @@ No changes. Everything should just work. ------------ * The `sequence_privs_are()` function does not work, because privileges could not be granted on sequences before 9.0. +* The `triggers_are()` function does not ignore internally-created triggers. 8.3 and Down ------------ From 841068f14283cb80dd9520d5861f5df7c778dd8d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 12:11:53 -0800 Subject: [PATCH 0730/1195] Update 8.3-compat diff file. --- compat/install-8.3.patch | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index 1aded62e3d76..f5d3a931e62c 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -1,5 +1,5 @@ ---- sql/pgtap.sql.saf 2012-09-10 16:57:28.000000000 -0700 -+++ sql/pgtap.sql 2012-09-10 16:57:48.000000000 -0700 +--- sql/pgtap.sql.saf 2013-01-15 12:09:45.000000000 -0800 ++++ sql/pgtap.sql 2013-01-15 12:10:18.000000000 -0800 @@ -9,6 +9,11 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -12,7 +12,7 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT s.a[1]::int * 10000 -@@ -6254,7 +6259,7 @@ +@@ -6302,7 +6307,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -21,7 +21,7 @@ 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' || -@@ -6409,7 +6414,7 @@ +@@ -6459,7 +6464,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -30,7 +30,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6595,13 +6600,7 @@ +@@ -6668,13 +6673,7 @@ $$ LANGUAGE sql; -- collect_tap( tap, tap, tap ) @@ -45,7 +45,7 @@ RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); $$ LANGUAGE sql; -@@ -7073,7 +7072,7 @@ +@@ -7146,7 +7145,7 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; From a4cbec813c3bdefbd972dc01fd7b780486857c8d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 12:23:51 -0800 Subject: [PATCH 0731/1195] Get tests passing on 8.3. --- test/expected/hastap.out | 21 +++++++++++---------- test/sql/hastap.sql | 6 +++--- test/sql/ownership.sql | 8 ++++---- test/sql/privs.sql | 16 ++++++++-------- test/sql/resultset.sql | 20 +++++++++++++++++--- 5 files changed, 43 insertions(+), 28 deletions(-) diff --git a/test/expected/hastap.out b/test/expected/hastap.out index cf0401b925d0..cdcf72381d95 100644 --- a/test/expected/hastap.out +++ b/test/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..785 +1..786 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -776,12 +776,13 @@ ok 773 - hasnt_foreign_table(non-existent table) should have the proper descript ok 774 - hasnt_foreign_table(non-existent table) should have the proper diagnostics ok 775 - hasnt_foreign_table(non-existent schema, tab) should pass ok 776 - hasnt_foreign_table(non-existent schema, tab) should have the proper description -ok 777 - hasnt_foreign_table(sch, non-existent tab, desc) should pass -ok 778 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper description -ok 779 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper diagnostics -ok 780 - hasnt_foreign_table(tab, desc) should fail -ok 781 - hasnt_foreign_table(tab, desc) should have the proper description -ok 782 - hasnt_foreign_table(tab, desc) should have the proper diagnostics -ok 783 - hasnt_foreign_table(sch, tab, desc) should fail -ok 784 - hasnt_foreign_table(sch, tab, desc) should have the proper description -ok 785 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics +ok 777 - hasnt_foreign_table(non-existent schema, tab) should have the proper diagnostics +ok 778 - hasnt_foreign_table(sch, non-existent tab, desc) should pass +ok 779 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper description +ok 780 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper diagnostics +ok 781 - hasnt_foreign_table(tab, desc) should fail +ok 782 - hasnt_foreign_table(tab, desc) should have the proper description +ok 783 - hasnt_foreign_table(tab, desc) should have the proper diagnostics +ok 784 - hasnt_foreign_table(sch, tab, desc) should fail +ok 785 - hasnt_foreign_table(sch, tab, desc) should have the proper description +ok 786 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index bad13afa30fa..d328b7193ca0 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(785); +SELECT plan(786); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -2092,7 +2092,7 @@ BEGIN hasnt_foreign_table( '__SDFSDFD__', 'lol' ), true, 'hasnt_foreign_table(non-existent schema, tab)', - 'lol' + 'lol', '' ) AS b LOOP RETURN NEXT tap.b; @@ -2214,7 +2214,7 @@ BEGIN hasnt_table( '__SDFSDFD__', 'lol' ), true, 'hasnt_foreign_table(non-existent schema, tab)', - 'lol' + 'lol', '' ) AS b LOOP RETURN NEXT tap.b; diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 4d66a985702c..427a25dce459 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -663,7 +663,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_owner_is('public', 'test_fdw', ARRAY[]::NAME[], current_user, 'mumble'), + function_owner_is('public', 'test_fdw', '{}'::NAME[], current_user, 'mumble'), true, 'function_owner_is(sch, function, args[], user, desc)', 'mumble', @@ -671,7 +671,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_owner_is('public', 'test_fdw', ARRAY[]::NAME[], current_user), + function_owner_is('public', 'test_fdw', '{}'::NAME[], current_user), true, 'function_owner_is(sch, function, args[], user)', 'Function public.test_fdw() should be owned by ' || current_user, @@ -695,7 +695,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_owner_is('test_fdw', ARRAY[]::NAME[], current_user, 'mumble'), + function_owner_is('test_fdw', '{}'::NAME[], current_user, 'mumble'), true, 'function_owner_is(function, args[], user, desc)', 'mumble', @@ -703,7 +703,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_owner_is('test_fdw', ARRAY[]::NAME[], current_user), + function_owner_is('test_fdw', '{}'::NAME[], current_user), true, 'function_owner_is(function, args[], user)', 'Function test_fdw() should be owned by ' || current_user, diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 768fc025d03a..e44af65aa8f8 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -367,7 +367,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_privs_are( 'public', 'foo', ARRAY['integer', 'text'], - '__someone_else', ARRAY[]::text[], 'whatever' + '__someone_else', '{}'::text[], 'whatever' ), true, 'function_privs_are(sch, func, args, unpriv, empty, desc)', @@ -378,7 +378,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_privs_are( 'public', 'foo', ARRAY['integer', 'text'], - '__someone_else', ARRAY[]::text[] + '__someone_else', '{}'::text[] ), true, 'function_privs_are(sch, func, args, unpriv, empty)', @@ -389,7 +389,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_privs_are( 'foo', ARRAY['integer', 'text'], - '__someone_else', ARRAY[]::text[] + '__someone_else', '{}'::text[] ), true, 'function_privs_are(func, args, unpriv, empty)', @@ -401,7 +401,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_privs_are( 'public', 'foo', ARRAY['integer', 'text'], - current_user, ARRAY[]::text[], 'whatever' + current_user, '{}'::text[], 'whatever' ), false, 'function_privs_are(sch, func, args, unpriv, privs, desc)', @@ -413,7 +413,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( function_privs_are( 'foo', ARRAY['integer', 'text'], - current_user, ARRAY[]::text[], 'whatever' + current_user, '{}'::text[], 'whatever' ), false, 'function_privs_are(func, args, unpriv, privs, desc)', @@ -1201,7 +1201,7 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; -- Grant them some permission. - GRANT SELECT, INSERT, UPDATE (id) ON ha.sometab TO __someone_else; + EXECUTE 'GRANT SELECT, INSERT, UPDATE (id) ON ha.sometab TO __someone_else;'; FOR tap IN SELECT * FROM check_test( column_privs_are( 'ha', 'sometab', 'id', '__someone_else', ARRAY[ @@ -1367,7 +1367,7 @@ DECLARE tap record; BEGIN IF pg_version_num() >= 80400 THEN - CREATE FOREIGN DATA WRAPPER dummy; + EXECUTE 'CREATE FOREIGN DATA WRAPPER dummy;'; FOR tap IN SELECT * FROM check_test( fdw_privs_are( 'dummy', current_user, '{USAGE}', 'whatever' ), @@ -1494,7 +1494,7 @@ DECLARE tap record; BEGIN IF pg_version_num() >= 80400 THEN - CREATE SERVER foo FOREIGN DATA WRAPPER dummy; + EXECUTE 'CREATE SERVER foo FOREIGN DATA WRAPPER dummy;'; FOR tap IN SELECT * FROM check_test( server_privs_are( 'foo', current_user, '{USAGE}', 'whatever' ), diff --git a/test/sql/resultset.sql b/test/sql/resultset.sql index 4f7afcac54c7..ee9eb9a5ec19 100644 --- a/test/sql/resultset.sql +++ b/test/sql/resultset.sql @@ -993,9 +993,23 @@ DECLARE BEGIN IF pg_version_num() < 80400 THEN -- 8.3 and earlier cast records to text, so subtlety is out. - RETURN NEXT pass('results_eq(values, values) subtle mismatch should fail'); - RETURN NEXT pass('results_eq(values, values) subtle mismatch should have the proper description'); - RETURN NEXT pass('results_eq(values, values) subtle mismatch should have the proper diagnostics'); + -- Fake out pg_regress by running equivalent tests with fail(). + FOR tap IN SELECT * FROM check_test( + fail('whatever'), + false, + 'results_eq(values, values) subtle mismatch', + 'whatever', + '' + ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('whatever'), + false, + 'results_eq(values, values) integer type mismatch', + 'whatever', + '' + ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; + ELSE -- 8.4 does true record comparisions, yay! FOR tap IN SELECT * FROM check_test( From a5224018ed4739ef8304a7ae4d623d76e8df6fe9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 13:44:54 -0800 Subject: [PATCH 0732/1195] Apply the 8.4 patch to pgtap-schema.sql and pgtap-core.sql. --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 8050c34a055a..1bf99074a622 100644 --- a/Makefile +++ b/Makefile @@ -120,6 +120,7 @@ endif sql/pgtap-core.sql: sql/pgtap.sql.in cp $< $@ + sed -e 's,sql/pgtap,sql/pgtap-core,g' compat/install-8.4.patch | patch -p0 sed -e 's,sql/pgtap,sql/pgtap-core,g' compat/install-8.3.patch | patch -p0 sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap-core.sql > sql/pgtap-core.tmp $(PERL) compat/gencore 0 sql/pgtap-core.tmp > sql/pgtap-core.sql @@ -127,6 +128,7 @@ sql/pgtap-core.sql: sql/pgtap.sql.in sql/pgtap-schema.sql: sql/pgtap.sql.in cp $< $@ + sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-8.4.patch | patch -p0 sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-8.3.patch | patch -p0 sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap-schema.sql > sql/pgtap-schema.tmp $(PERL) compat/gencore 1 sql/pgtap-schema.tmp > sql/pgtap-schema.sql From 5ac1952dfddc9e856543c00c52b9c3fe486814d3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 14:03:25 -0800 Subject: [PATCH 0733/1195] Update diff and get tests passing on 8.2. --- compat/install-8.2.patch | 68 +++++++++++++++++++++------------------- test/sql/ownership.sql | 4 +-- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 93daa75e901b..57584fddd08b 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,5 +1,5 @@ ---- sql/pgtap.sql.saf 2012-09-10 17:04:49.000000000 -0700 -+++ sql/pgtap.sql 2012-09-10 17:05:13.000000000 -0700 +--- sql/pgtap.sql.saf 2013-01-15 13:55:16.000000000 -0800 ++++ sql/pgtap.sql 2013-01-15 13:57:48.000000000 -0800 @@ -5,6 +5,59 @@ -- -- http://pgtap.org/ @@ -60,7 +60,7 @@ CREATE OR REPLACE FUNCTION pg_version() RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; -@@ -188,11 +241,11 @@ +@@ -190,11 +243,11 @@ RETURNS integer AS $$ BEGIN EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) @@ -77,7 +77,7 @@ RETURN currval('__tresults___numb_seq'); END; $$ LANGUAGE plpgsql; -@@ -267,21 +320,6 @@ +@@ -269,21 +322,6 @@ ); $$ LANGUAGE sql strict; @@ -99,7 +99,7 @@ CREATE OR REPLACE FUNCTION ok ( boolean, text ) RETURNS TEXT AS $$ DECLARE -@@ -494,9 +532,9 @@ +@@ -496,9 +534,9 @@ output TEXT; BEGIN EXECUTE 'SELECT ' || @@ -111,7 +111,7 @@ INTO result; output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( -@@ -1100,7 +1138,7 @@ +@@ -1231,7 +1269,7 @@ SELECT COALESCE(substring( pg_catalog.format_type($1, $2), '(("(?!")([^"]|"")+"|[^.]+)([(][^)]+[)])?)$' @@ -120,8 +120,8 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION display_type ( NAME, OID, INTEGER ) -@@ -2191,7 +2229,7 @@ - p.proname AS name, +@@ -2324,7 +2362,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, @@ -129,7 +129,7 @@ p.prolang AS langoid, p.proisstrict AS is_strict, p.proisagg AS is_agg, -@@ -3409,63 +3447,6 @@ +@@ -3458,63 +3496,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -193,7 +193,7 @@ CREATE OR REPLACE FUNCTION _has_role( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( -@@ -5934,13 +5915,13 @@ +@@ -5982,13 +5963,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -209,7 +209,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6164,7 +6145,7 @@ +@@ -6212,7 +6193,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -218,7 +218,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6259,11 +6240,11 @@ +@@ -6307,11 +6288,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -233,18 +233,20 @@ ); END IF; rownum = rownum + 1; -@@ -6278,8 +6259,8 @@ +@@ -6326,9 +6307,9 @@ 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 + 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; - $$ LANGUAGE plpgsql; -@@ -6414,7 +6395,7 @@ +@@ -6464,7 +6445,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -253,7 +255,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6428,8 +6409,8 @@ +@@ -6478,8 +6459,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -264,7 +266,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6554,9 +6535,9 @@ +@@ -6604,9 +6585,9 @@ DECLARE typeof regtype := pg_typeof($1); BEGIN @@ -277,7 +279,7 @@ END; $$ LANGUAGE plpgsql; -@@ -6577,7 +6558,7 @@ +@@ -6627,7 +6608,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -286,7 +288,7 @@ END LOOP; -- What extra records do we have? -@@ -6722,7 +6703,7 @@ +@@ -6795,7 +6776,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -295,7 +297,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT -@@ -6740,7 +6721,7 @@ +@@ -6813,7 +6794,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -304,7 +306,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) ), -@@ -6773,7 +6754,7 @@ +@@ -6846,7 +6827,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -313,7 +315,7 @@ 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']) ) -@@ -6792,7 +6773,7 @@ +@@ -6865,7 +6846,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -322,7 +324,7 @@ 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']) ) -@@ -7072,10 +7053,12 @@ +@@ -7145,10 +7126,12 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; @@ -338,7 +340,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7220,7 +7203,7 @@ +@@ -7293,7 +7276,7 @@ CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) RETURNS TEXT AS $$ @@ -347,7 +349,7 @@ $$ LANGUAGE SQL; -- operators_are( schema, operators[], description ) -@@ -7229,7 +7212,7 @@ +@@ -7302,7 +7285,7 @@ SELECT _areni( 'operators', ARRAY( @@ -356,7 +358,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7241,7 +7224,7 @@ +@@ -7314,7 +7297,7 @@ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT @@ -365,7 +367,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7262,7 +7245,7 @@ +@@ -7335,7 +7318,7 @@ SELECT _areni( 'operators', ARRAY( @@ -374,7 +376,7 @@ 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) -@@ -7275,7 +7258,7 @@ +@@ -7348,7 +7331,7 @@ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 427a25dce459..b5f0de4de6ef 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -719,11 +719,11 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_owner_is('__non__public', 'function', ARRAY['integer'], current_user, 'mumble'), + function_owner_is('__non__public', 'afunction', ARRAY['integer'], current_user, 'mumble'), false, 'function_owner_is(non-sch, function, args[integer], user, desc)', 'mumble', - ' Function __non__public.function(integer) does not exist' + ' Function __non__public.afunction(integer) does not exist' ); SELECT * FROM check_test( From 6181955f2728e3586e885c0f0396d6b59428279b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 14:25:55 -0800 Subject: [PATCH 0734/1195] Update diff and get tests passing on 8.1. --- compat/install-8.1.patch | 22 +++++++++++----------- test/sql/privs.sql | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index 9e89c5cd592f..f178eba067af 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ ---- sql/pgtap.sql.saf 2012-09-10 17:09:02.000000000 -0700 -+++ sql/pgtap.sql 2012-09-10 17:09:26.000000000 -0700 -@@ -1990,13 +1990,13 @@ +--- sql/pgtap.sql.saf 2013-01-15 14:04:18.000000000 -0800 ++++ sql/pgtap.sql 2013-01-15 14:04:23.000000000 -0800 +@@ -2122,13 +2122,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -18,7 +18,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $6 || ' constriants'; -@@ -2014,13 +2014,13 @@ +@@ -2146,13 +2146,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -36,7 +36,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $5 || ' constriants'; -@@ -5687,7 +5687,7 @@ +@@ -5735,7 +5735,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -45,7 +45,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -5695,8 +5695,8 @@ +@@ -5743,8 +5743,8 @@ -- 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. @@ -56,7 +56,7 @@ END LOOP; END LOOP; RETURN; -@@ -5766,14 +5766,14 @@ +@@ -5814,14 +5814,14 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -73,7 +73,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -5788,15 +5788,15 @@ +@@ -5836,15 +5836,15 @@ IF verbos THEN RETURN NEXT diag_test_name(tests[i]); END IF; -- Run the setup functions. @@ -93,7 +93,7 @@ -- Remember how many failed and then roll back. num_faild := num_faild + num_failed(); -@@ -5811,7 +5811,7 @@ +@@ -5859,7 +5859,7 @@ END LOOP; -- Run the shutdown functions. @@ -102,7 +102,7 @@ -- Raise an exception to rollback any changes. RAISE EXCEPTION '__TAP_ROLLBACK__'; -@@ -5822,8 +5822,8 @@ +@@ -5870,8 +5870,8 @@ END IF; END; -- Finish up. @@ -113,7 +113,7 @@ END LOOP; -- Clean up and return. -@@ -7052,7 +7052,7 @@ +@@ -7125,7 +7125,7 @@ DECLARE rec RECORD; BEGIN diff --git a/test/sql/privs.sql b/test/sql/privs.sql index e44af65aa8f8..35904effafc5 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -51,8 +51,8 @@ SELECT * FROM check_test( CREATE OR REPLACE FUNCTION run_extra_fails() RETURNS SETOF TEXT LANGUAGE plpgsql AS $$ DECLARE allowed_privs TEXT[]; - test_privs TEXT[]; - missing_privs TEXT[]; + test_privs TEXT[] := '{}'; + missing_privs TEXT[] := '{}'; tap record; last_index INTEGER; BEGIN From 157f4a14d9cf8d05e0a6c5894583193fdde0b47b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 14:40:19 -0800 Subject: [PATCH 0735/1195] Update uninstall script. --- compat/uninstall-8.2.patch | 10 ++-- compat/uninstall-8.3.patch | 12 ++--- sql/uninstall_pgtap.sql.in | 106 ++++++++++++++++++++++++++++++++++++- 3 files changed, 115 insertions(+), 13 deletions(-) diff --git a/compat/uninstall-8.2.patch b/compat/uninstall-8.2.patch index db67c883ca81..8a0dda280541 100644 --- a/compat/uninstall-8.2.patch +++ b/compat/uninstall-8.2.patch @@ -1,6 +1,6 @@ ---- sql/uninstall_pgtap.sql.saf 2011-11-10 15:40:19.000000000 -0800 -+++ sql/uninstall_pgtap.sql 2011-11-10 15:40:43.000000000 -0800 -@@ -370,10 +370,6 @@ +--- sql/uninstall_pgtap.sql.saf 2013-01-15 14:38:13.000000000 -0800 ++++ sql/uninstall_pgtap.sql 2013-01-15 14:38:58.000000000 -0800 +@@ -454,10 +454,6 @@ DROP FUNCTION has_role( NAME ); DROP FUNCTION has_role( NAME, TEXT ); DROP FUNCTION _has_role( NAME ); @@ -11,7 +11,7 @@ DROP FUNCTION hasnt_enum( NAME ); DROP FUNCTION hasnt_enum( NAME, TEXT ); DROP FUNCTION hasnt_enum( NAME, NAME ); -@@ -683,9 +679,6 @@ +@@ -785,9 +781,6 @@ DROP FUNCTION is (anyelement, anyelement, text); DROP FUNCTION ok ( boolean ); DROP FUNCTION ok ( boolean, text ); @@ -21,7 +21,7 @@ DROP FUNCTION diag ( msg text ); DROP FUNCTION finish (); DROP FUNCTION _finish ( INTEGER, INTEGER, INTEGER); -@@ -708,3 +701,15 @@ +@@ -810,3 +803,15 @@ DROP FUNCTION os_name(); DROP FUNCTION pg_version_num(); DROP FUNCTION pg_version(); diff --git a/compat/uninstall-8.3.patch b/compat/uninstall-8.3.patch index dbede62b4c04..47dccc0f687d 100644 --- a/compat/uninstall-8.3.patch +++ b/compat/uninstall-8.3.patch @@ -1,16 +1,16 @@ ---- sql/uninstall_pgtap.sql.saf 2011-11-10 15:00:44.000000000 -0800 -+++ sql/uninstall_pgtap.sql 2011-11-10 15:00:53.000000000 -0800 -@@ -60,8 +60,7 @@ +--- sql/uninstall_pgtap.sql.saf 2013-01-15 14:32:03.000000000 -0800 ++++ sql/uninstall_pgtap.sql 2013-01-15 14:33:58.000000000 -0800 +@@ -142,8 +142,7 @@ DROP FUNCTION throws_like ( TEXT, TEXT ); DROP FUNCTION throws_like ( TEXT, TEXT, TEXT ); DROP FUNCTION _tlike ( BOOLEAN, TEXT, TEXT, TEXT ); -DROP FUNCTION collect_tap( VARCHAR[] ); -DROP FUNCTION collect_tap( VARIADIC text[] ); +DROP FUNCTION collect_tap( text[] ); + DROP FUNCTION isnt_empty( TEXT ); + DROP FUNCTION isnt_empty( TEXT, TEXT ); DROP FUNCTION is_empty( TEXT ); - DROP FUNCTION is_empty( TEXT, TEXT ); - DROP FUNCTION isa_ok( anyelement, regtype ); -@@ -704,6 +703,7 @@ +@@ -806,6 +805,7 @@ DROP FUNCTION _get ( text ); DROP FUNCTION no_plan(); DROP FUNCTION plan( integer ); diff --git a/sql/uninstall_pgtap.sql.in b/sql/uninstall_pgtap.sql.in index 36a8b5c40606..5282afec52d4 100644 --- a/sql/uninstall_pgtap.sql.in +++ b/sql/uninstall_pgtap.sql.in @@ -1,3 +1,85 @@ +DROP FUNCTION server_privs_are ( NAME, NAME, NAME[] ); +DROP FUNCTION server_privs_are ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _get_server_privs (NAME, TEXT); +DROP FUNCTION _get_schema_privs(NAME, TEXT); +DROP FUNCTION fdw_privs_are ( NAME, NAME, NAME[] ); +DROP FUNCTION fdw_privs_are ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _get_fdw_privs (NAME, TEXT); +DROP FUNCTION column_privs_are ( NAME, NAME, NAME, NAME[] ); +DROP FUNCTION column_privs_are ( NAME, NAME, NAME, NAME[], TEXT ); +DROP FUNCTION column_privs_are ( NAME, NAME, NAME, NAME, NAME[] ); +DROP FUNCTION column_privs_are ( NAME, NAME, NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _get_col_privs(NAME, TEXT, NAME); +DROP FUNCTION any_column_privs_are ( NAME, NAME, NAME[] ); +DROP FUNCTION any_column_privs_are ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION any_column_privs_are ( NAME, NAME, NAME, NAME[] ); +DROP FUNCTION any_column_privs_are ( NAME, NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _get_ac_privs(NAME, TEXT); +DROP FUNCTION sequence_privs_are ( NAME, NAME, NAME[] ); +DROP FUNCTION sequence_privs_are ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION sequence_privs_are ( NAME, NAME, NAME, NAME[] ); +DROP FUNCTION sequence_privs_are ( NAME, NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _get_sequence_privs(NAME, TEXT); +DROP FUNCTION tablespace_privs_are ( NAME, NAME, NAME[] ); +DROP FUNCTION tablespace_privs_are ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _get_tablespaceprivs (NAME, TEXT); +DROP FUNCTION schema_privs_are ( NAME, NAME, NAME[] ); +DROP FUNCTION schema_privs_are ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _get_schema_privs(NAME, TEXT); +DROP FUNCTION language_privs_are ( NAME, NAME, NAME[] ); +DROP FUNCTION language_privs_are ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _get_lang_privs (NAME, TEXT); +DROP FUNCTION function_privs_are ( NAME, NAME[], NAME, NAME[] ); +DROP FUNCTION function_privs_are ( NAME, NAME[], NAME, NAME[], TEXT ); +DROP FUNCTION function_privs_are ( NAME, NAME, NAME[], NAME, NAME[] ); +DROP FUNCTION function_privs_are ( NAME, NAME, NAME[], NAME, NAME[], TEXT ); +DROP FUNCTION _fprivs_are ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _get_func_privs(NAME, TEXT); +DROP FUNCTION database_privs_are ( NAME, NAME, NAME[] ); +DROP FUNCTION database_privs_are ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _get_db_privs(NAME, TEXT); +DROP FUNCTION _db_privs(); +DROP FUNCTION table_privs_are ( NAME, NAME, NAME[] ); +DROP FUNCTION table_privs_are ( NAME, NAME, NAME[], TEXT ); +DROP FUNCTION table_privs_are ( NAME, NAME, NAME, NAME[] ); +DROP FUNCTION table_privs_are ( NAME, NAME, NAME, NAME[], TEXT ); +DROP FUNCTION _table_privs(); +DROP FUNCTION _get_table_privs(NAME, TEXT); +DROP FUNCTION _assets_are ( text, text[], text[], TEXT ); +DROP FUNCTION function_owner_is( NAME, NAME[], NAME ); +DROP FUNCTION function_owner_is ( NAME, NAME[], NAME, TEXT ); +DROP FUNCTION function_owner_is( NAME, NAME, NAME[], NAME ); +DROP FUNCTION function_owner_is ( NAME, NAME, NAME[], NAME, TEXT ); +DROP FUNCTION _get_func_owner ( NAME, NAME[] ); +DROP FUNCTION _get_func_owner ( NAME, NAME, NAME[] ); +DROP FUNCTION foreign_table_owner_is ( NAME, NAME ); +DROP FUNCTION foreign_table_owner_is ( NAME, NAME, TEXT ); +DROP FUNCTION foreign_table_owner_is ( NAME, NAME, NAME ); +DROP FUNCTION foreign_table_owner_is ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION composite_owner_is ( NAME, NAME ); +DROP FUNCTION composite_owner_is ( NAME, NAME, TEXT ); +DROP FUNCTION composite_owner_is ( NAME, NAME, NAME ); +DROP FUNCTION composite_owner_is ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION sequence_owner_is ( NAME, NAME ); +DROP FUNCTION sequence_owner_is ( NAME, NAME, TEXT ); +DROP FUNCTION sequence_owner_is ( NAME, NAME, NAME ); +DROP FUNCTION sequence_owner_is ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION view_owner_is ( NAME, NAME ); +DROP FUNCTION view_owner_is ( NAME, NAME, TEXT ); +DROP FUNCTION view_owner_is ( NAME, NAME, NAME ); +DROP FUNCTION view_owner_is ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION table_owner_is ( NAME, NAME ); +DROP FUNCTION table_owner_is ( NAME, NAME, TEXT ); +DROP FUNCTION table_owner_is ( NAME, NAME, NAME ); +DROP FUNCTION table_owner_is ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION _get_rel_owner ( CHAR, NAME ); +DROP FUNCTION _get_rel_owner ( CHAR, NAME, NAME ); +DROP FUNCTION relation_owner_is ( NAME, NAME ); +DROP FUNCTION relation_owner_is ( NAME, NAME, TEXT ); +DROP FUNCTION relation_owner_is ( NAME, NAME, NAME ); +DROP FUNCTION relation_owner_is ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION _get_rel_owner ( NAME ); +DROP FUNCTION _get_rel_owner ( NAME, NAME ); DROP FUNCTION db_owner_is ( NAME, NAME ); DROP FUNCTION db_owner_is ( NAME, NAME, TEXT ); DROP FUNCTION _get_db_owner( NAME ); @@ -62,6 +144,8 @@ DROP FUNCTION throws_like ( TEXT, TEXT, TEXT ); DROP FUNCTION _tlike ( BOOLEAN, TEXT, TEXT, TEXT ); DROP FUNCTION collect_tap( VARCHAR[] ); DROP FUNCTION collect_tap( VARIADIC text[] ); +DROP FUNCTION isnt_empty( TEXT ); +DROP FUNCTION isnt_empty( TEXT, TEXT ); DROP FUNCTION is_empty( TEXT ); DROP FUNCTION is_empty( TEXT, TEXT ); DROP FUNCTION isa_ok( anyelement, regtype ); @@ -455,8 +539,6 @@ DROP FUNCTION has_index ( NAME, NAME, NAME, NAME ); DROP FUNCTION has_index ( NAME, NAME, NAME, NAME, text ); DROP FUNCTION has_index ( NAME, NAME, NAME, NAME[] ); DROP FUNCTION has_index ( NAME, NAME, NAME, NAME[], text ); -DROP FUNCTION _iexpr( NAME, NAME); -DROP FUNCTION _iexpr( NAME, NAME, NAME); DROP FUNCTION _have_index( NAME, NAME); DROP FUNCTION _have_index( NAME, NAME, NAME); DROP FUNCTION _ikeys( NAME, NAME); @@ -609,6 +691,18 @@ DROP FUNCTION has_column ( NAME, NAME, TEXT ); DROP FUNCTION has_column ( NAME, NAME, NAME, TEXT ); DROP FUNCTION _cexists ( NAME, NAME ); DROP FUNCTION _cexists ( NAME, NAME, NAME ); +DROP FUNCTION hasnt_composite ( NAME ); +DROP FUNCTION hasnt_composite ( NAME, TEXT ); +DROP FUNCTION hasnt_composite ( NAME, NAME, TEXT ); +DROP FUNCTION has_composite ( NAME ); +DROP FUNCTION has_composite ( NAME, TEXT ); +DROP FUNCTION has_composite ( NAME, NAME, TEXT ); +DROP FUNCTION hasnt_foreign_table ( NAME ); +DROP FUNCTION hasnt_foreign_table ( NAME, TEXT ); +DROP FUNCTION hasnt_foreign_table ( NAME, NAME, TEXT ); +DROP FUNCTION has_foreign_table ( NAME ); +DROP FUNCTION has_foreign_table ( NAME, TEXT ); +DROP FUNCTION has_foreign_table ( NAME, NAME, TEXT ); DROP FUNCTION hasnt_sequence ( NAME ); DROP FUNCTION hasnt_sequence ( NAME, TEXT ); DROP FUNCTION hasnt_sequence ( NAME, NAME, TEXT ); @@ -629,6 +723,14 @@ DROP FUNCTION has_table ( NAME, TEXT ); DROP FUNCTION has_table ( NAME, NAME, TEXT ); DROP FUNCTION _rexists ( CHAR, NAME ); DROP FUNCTION _rexists ( CHAR, NAME, NAME ); +DROP FUNCTION hasnt_relation ( NAME ); +DROP FUNCTION hasnt_relation ( NAME, TEXT ); +DROP FUNCTION hasnt_relation ( NAME, NAME, TEXT ); +DROP FUNCTION has_relation ( NAME ); +DROP FUNCTION has_relation ( NAME, TEXT ); +DROP FUNCTION has_relation ( NAME, NAME, TEXT ); +DROP FUNCTION _relexists ( NAME ); +DROP FUNCTION _relexists ( NAME, NAME ); DROP FUNCTION performs_ok ( TEXT, NUMERIC ); DROP FUNCTION performs_ok ( TEXT, NUMERIC, TEXT ); DROP FUNCTION lives_ok ( TEXT ); From c19f65a013f4c817960d7ae97bf58266351e3b39 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 15:36:11 -0800 Subject: [PATCH 0736/1195] Spelling, Markdown tweaking. --- Changes | 2 +- doc/pgtap.mmd | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index ff6147dd7db1..57e8260bcae4 100644 --- a/Changes +++ b/Changes @@ -3,7 +3,7 @@ Revision history for pgTAP 0.92.0 --------------------------- -* Fixed some mis-spellings of `SELECT` in documentation examples. +* Fixed some misspellings of `SELECT` in documentation examples. * Added note about the lack of typemods to the documentation for `has_cast()`. * Added check to ensure a table is visible in tests for column defaults. Thanks to Henk Enting for the report. diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index a3320a4c2015..ad5ef4c54ebc 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7023,6 +7023,7 @@ No changes. Everything should just work. ------------ * The `sequence_privs_are()` function does not work, because privileges could not be granted on sequences before 9.0. + * The `triggers_are()` function does not ignore internally-created triggers. 8.3 and Down @@ -7031,11 +7032,14 @@ No changes. Everything should just work. to text before comparing them. This means that things will mainly be correct, but it also means that two queries with incompatible types that convert to the same text string may be considered incorrectly equivalent. + * A C function, `pg_typeof()`, is built and installed in a DSO. This is for compatibility with the same function that ships in 8.4 core, and is required for `cmp_ok()` and `isa_ok()` to work. + * The variadic forms of `diag()` and `collect_tap()` are not available. You can pass an array of TAP to `collect_tap()`, however. + * These permission-testing functions don't work, because one cannot grant permissions on the relevant objects until 8.4: @@ -7056,6 +7060,7 @@ No changes. Everything should just work. + `text[]` to `text` + `name[]` to `text` + `regtype` to `text` + * Two operators, `=` and `<>`, are added to compare `name[]` values. Metadata From dc1175c2c94d591537be967dd1079066c93ed54a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 15:38:45 -0800 Subject: [PATCH 0737/1195] Update copyright year. --- README.md | 2 +- doc/pgtap.mmd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6ef87d33c4e9..06c6a32b6c77 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ full use of its API. It also requires PL/pgSQL. Copyright and License --------------------- -Copyright (c) 2008-2011 David E. Wheeler. Some rights reserved. +Copyright (c) 2008-2013 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index ad5ef4c54ebc..9d1a79a40b27 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7094,7 +7094,7 @@ Credits Copyright and License --------------------- -Copyright (c) 2008-2011 David E. Wheeler. Some rights reserved. +Copyright (c) 2008-2013 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is From a4cd74c7d303caf0aaf2c34717b6a789f0b8a249 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 15:51:24 -0800 Subject: [PATCH 0738/1195] Fix some doc nerbles that caused the HTML to be messed up. --- doc/pgtap.mmd | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 9d1a79a40b27..486b542df5cd 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -3168,9 +3168,9 @@ regard to its arguments. Some examples: The `:args` argument should be formatted as it would be displayed in the view of a function using the `\df` command in `psql`. For example, even if you have -a numeric column with a precision of 8, you should specify -`ARRAY['numeric']`". If you created a `varchar(64)` column, you should pass -the `:args` argument as `ARRAY['character varying']`. +a numeric column with a precision of 8, you should specify `ARRAY['numeric']`. +If you created a `varchar(64)` column, you should pass the `:args` argument as +`ARRAY['character varying']`. If you wish to use the two-argument form of `has_function()`, specifying only the schema and the function name, you must cast the `:function` argument to @@ -4510,7 +4510,7 @@ wrong, for example: If the function does not exist, you'll be told that, too. - # Failed test 212: "Function myschema.grab() should be written in sql + # Failed test 212: "Function myschema.grab() should be written in sql" # Function myschema.grab() does not exist But then you check with `has_function()` first, right? @@ -4661,7 +4661,7 @@ reasonable substitute will be created. Examples: If the function does not exist, a handy diagnostic message will let you know: - # Failed test 290: "Function nasty() should be strict + # Failed test 290: "Function nasty() should be strict" # Function nasty() does not exist But then you check with `has_function()` first, right? @@ -4706,7 +4706,7 @@ Examples: If the function does not exist, a handy diagnostic message will let you know: - # Failed test 290: "Function nasty() should be strict + # Failed test 290: "Function nasty() should be strict" # Function nasty() does not exist But then you check with `has_function()` first, right? @@ -4764,7 +4764,7 @@ wrong, for example: If the function does not exist, you'll be told that, too. - # Failed test 212: "Function myschema.grab() should be IMMUTABLE + # Failed test 212: "Function myschema.grab() should be IMMUTABLE" # Function myschema.grab() does not exist But then you check with `has_function()` first, right? From c6ccdbcb73be62b170cd4b18d729027a9bc3b4ab Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 16:40:30 -0800 Subject: [PATCH 0739/1195] Timestamp v0.92.0. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 57e8260bcae4..0c2a55b5062c 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.92.0 +0.92.0 2013-01-16T00:41:07Z --------------------------- * Fixed some misspellings of `SELECT` in documentation examples. * Added note about the lack of typemods to the documentation for `has_cast()`. From e10fb88d80a247b3281ca386f131a197d8618fb5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 15 Jan 2013 16:43:55 -0800 Subject: [PATCH 0740/1195] Increment to v0.93.0. --- Changes | 4 ++++ README.md | 2 +- doc/pgtap.mmd | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 0c2a55b5062c..3978ca0e7bec 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,10 @@ Revision history for pgTAP ========================== +0.93.0 +--------------------------- + + 0.92.0 2013-01-16T00:41:07Z --------------------------- * Fixed some misspellings of `SELECT` in documentation examples. diff --git a/README.md b/README.md index 06c6a32b6c77..a735136fb105 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 0.92.0 +pgTAP 0.93.0 ============ [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 486b542df5cd..0c10b3d44efd 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 0.92.0 +pgTAP 0.93.0 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and From a92a8066c8a69d05a9b50e13109d21bc498b9d25 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 18 Jan 2013 16:19:39 -0800 Subject: [PATCH 0741/1195] Add `schema_owner_is()`. Ref issue #40. --- Changes | 2 +- doc/pgtap.mmd | 35 +++ sql/pgtap--0.92.0--0.93.0.sql | 33 +++ sql/pgtap.sql.in | 34 +++ test/expected/ownership.out | 442 +++++++++++++++++----------------- test/sql/ownership.sql | 36 ++- 6 files changed, 365 insertions(+), 217 deletions(-) create mode 100644 sql/pgtap--0.92.0--0.93.0.sql diff --git a/Changes b/Changes index 3978ca0e7bec..41540b702ec1 100644 --- a/Changes +++ b/Changes @@ -3,7 +3,7 @@ Revision history for pgTAP 0.93.0 --------------------------- - +* Added `schema_owner_is()`. Not sure how I overlooked adding it in v0.92.0. 0.92.0 2013-01-16T00:41:07Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 0c10b3d44efd..29ab0eb3b3dd 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5208,6 +5208,41 @@ diagnostics will look something like: # have: postgres # want: root +### `schema_owner_is ()` ### + + SELECT schema_owner_is ( :schemaname, :user, :description ); + SELECT schema_owner_is ( :schemaname, :user ); + +**Parameters** + +`:schemaname` +: Name of a schema. + +`:user` +: Name of a user. + +`:description` +: A short description of the test. + +Tests the ownership of the schema. If the `:description` argument is +omitted, an appropriate description will be created. Examples: + + SELECT schema_owner_is( 'myschema', 'someuser', 'myschema should be owned by someuser' ); + SELECT schema_owner_is( current_schema(), current_user ); + +In the event that the test fails because the schema in question does not +actually exist, you will see an appropriate diagnostic such as: + + # Failed test 16: "Schema foo should be owned by www" + # Schema foo does not exist + +If the test fails because the schema is not owned by the specified user, the +diagnostics will look something like: + + # Failed test 17: "Schema bar should be owned by root" + # have: postgres + # want: root + ### `relation_owner_is ()` ### SELECT relation_owner_is ( :schema, :relation, :user, :description ); diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql new file mode 100644 index 000000000000..da847f642d64 --- /dev/null +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -0,0 +1,33 @@ +-- _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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 7aee8f0ecd3b..60ac371950a6 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7487,6 +7487,40 @@ RETURNS TEXT AS $$ ); $$ 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) diff --git a/test/expected/ownership.out b/test/expected/ownership.out index c1254703ccd0..2c772026e9b4 100644 --- a/test/expected/ownership.out +++ b/test/expected/ownership.out @@ -1,5 +1,5 @@ \unset ECHO -1..228 +1..240 ok 1 - db_owner_is(db, user, desc) should pass ok 2 - db_owner_is(db, user, desc) should have the proper description ok 3 - db_owner_is(db, user, desc) should have the proper diagnostics @@ -12,219 +12,231 @@ ok 9 - db_owner_is(non-db, user) should have the proper diagnostics ok 10 - db_owner_is(db, non-user) should fail ok 11 - db_owner_is(db, non-user) should have the proper description ok 12 - db_owner_is(db, non-user) should have the proper diagnostics -ok 13 - relation_owner_is(sch, tab, user, desc) should pass -ok 14 - relation_owner_is(sch, tab, user, desc) should have the proper description -ok 15 - relation_owner_is(sch, tab, user, desc) should have the proper diagnostics -ok 16 - relation_owner_is(sch, tab, user) should pass -ok 17 - relation_owner_is(sch, tab, user) should have the proper description -ok 18 - relation_owner_is(sch, tab, user) should have the proper diagnostics -ok 19 - relation_owner_is(non-sch, tab, user) should fail -ok 20 - relation_owner_is(non-sch, tab, user) should have the proper description -ok 21 - relation_owner_is(non-sch, tab, user) should have the proper diagnostics -ok 22 - relation_owner_is(sch, non-tab, user) should fail -ok 23 - relation_owner_is(sch, non-tab, user) should have the proper description -ok 24 - relation_owner_is(sch, non-tab, user) should have the proper diagnostics -ok 25 - relation_owner_is(tab, user, desc) should pass -ok 26 - relation_owner_is(tab, user, desc) should have the proper description -ok 27 - relation_owner_is(tab, user, desc) should have the proper diagnostics -ok 28 - relation_owner_is(tab, user) should pass -ok 29 - relation_owner_is(tab, user) should have the proper description -ok 30 - relation_owner_is(tab, user) should have the proper diagnostics -ok 31 - relation_owner_is(non-tab, user) should fail -ok 32 - relation_owner_is(non-tab, user) should have the proper description -ok 33 - relation_owner_is(non-tab, user) should have the proper diagnostics -ok 34 - relation_owner_is(sch, seq, user, desc) should pass -ok 35 - relation_owner_is(sch, seq, user, desc) should have the proper description -ok 36 - relation_owner_is(sch, seq, user, desc) should have the proper diagnostics -ok 37 - relation_owner_is(sch, seq, user) should pass -ok 38 - relation_owner_is(sch, seq, user) should have the proper description -ok 39 - relation_owner_is(sch, seq, user) should have the proper diagnostics -ok 40 - relation_owner_is(non-sch, seq, user) should fail -ok 41 - relation_owner_is(non-sch, seq, user) should have the proper description -ok 42 - relation_owner_is(non-sch, seq, user) should have the proper diagnostics -ok 43 - relation_owner_is(sch, non-seq, user) should fail -ok 44 - relation_owner_is(sch, non-seq, user) should have the proper description -ok 45 - relation_owner_is(sch, non-seq, user) should have the proper diagnostics -ok 46 - relation_owner_is(seq, user, desc) should pass -ok 47 - relation_owner_is(seq, user, desc) should have the proper description -ok 48 - relation_owner_is(seq, user, desc) should have the proper diagnostics -ok 49 - relation_owner_is(seq, user) should pass -ok 50 - relation_owner_is(seq, user) should have the proper description -ok 51 - relation_owner_is(seq, user) should have the proper diagnostics -ok 52 - relation_owner_is(non-seq, user) should fail -ok 53 - relation_owner_is(non-seq, user) should have the proper description -ok 54 - relation_owner_is(non-seq, user) should have the proper diagnostics -ok 55 - table_owner_is(sch, tab, user, desc) should pass -ok 56 - table_owner_is(sch, tab, user, desc) should have the proper description -ok 57 - table_owner_is(sch, tab, user, desc) should have the proper diagnostics -ok 58 - table_owner_is(sch, tab, user) should pass -ok 59 - table_owner_is(sch, tab, user) should have the proper description -ok 60 - table_owner_is(sch, tab, user) should have the proper diagnostics -ok 61 - table_owner_is(non-sch, tab, user) should fail -ok 62 - table_owner_is(non-sch, tab, user) should have the proper description -ok 63 - table_owner_is(non-sch, tab, user) should have the proper diagnostics -ok 64 - table_owner_is(sch, non-tab, user) should fail -ok 65 - table_owner_is(sch, non-tab, user) should have the proper description -ok 66 - table_owner_is(sch, non-tab, user) should have the proper diagnostics -ok 67 - table_owner_is(tab, user, desc) should pass -ok 68 - table_owner_is(tab, user, desc) should have the proper description -ok 69 - table_owner_is(tab, user, desc) should have the proper diagnostics -ok 70 - table_owner_is(tab, user) should pass -ok 71 - table_owner_is(tab, user) should have the proper description -ok 72 - table_owner_is(tab, user) should have the proper diagnostics -ok 73 - table_owner_is(non-tab, user) should fail -ok 74 - table_owner_is(non-tab, user) should have the proper description -ok 75 - table_owner_is(non-tab, user) should have the proper diagnostics -ok 76 - table_owner_is(sch, seq, user, desc) should fail -ok 77 - table_owner_is(sch, seq, user, desc) should have the proper description -ok 78 - table_owner_is(sch, seq, user, desc) should have the proper diagnostics -ok 79 - table_owner_is(seq, user, desc) should fail -ok 80 - table_owner_is(seq, user, desc) should have the proper description -ok 81 - table_owner_is(seq, user, desc) should have the proper diagnostics -ok 82 - view_owner_is(sch, view, user, desc) should pass -ok 83 - view_owner_is(sch, view, user, desc) should have the proper description -ok 84 - view_owner_is(sch, view, user, desc) should have the proper diagnostics -ok 85 - view_owner_is(sch, view, user) should pass -ok 86 - view_owner_is(sch, view, user) should have the proper description -ok 87 - view_owner_is(sch, view, user) should have the proper diagnostics -ok 88 - view_owner_is(non-sch, view, user) should fail -ok 89 - view_owner_is(non-sch, view, user) should have the proper description -ok 90 - view_owner_is(non-sch, view, user) should have the proper diagnostics -ok 91 - view_owner_is(sch, non-view, user) should fail -ok 92 - view_owner_is(sch, non-view, user) should have the proper description -ok 93 - view_owner_is(sch, non-view, user) should have the proper diagnostics -ok 94 - view_owner_is(view, user, desc) should pass -ok 95 - view_owner_is(view, user, desc) should have the proper description -ok 96 - view_owner_is(view, user, desc) should have the proper diagnostics -ok 97 - view_owner_is(view, user) should pass -ok 98 - view_owner_is(view, user) should have the proper description -ok 99 - view_owner_is(view, user) should have the proper diagnostics -ok 100 - view_owner_is(non-view, user) should fail -ok 101 - view_owner_is(non-view, user) should have the proper description -ok 102 - view_owner_is(non-view, user) should have the proper diagnostics -ok 103 - view_owner_is(sch, seq, user, desc) should fail -ok 104 - view_owner_is(sch, seq, user, desc) should have the proper description -ok 105 - view_owner_is(sch, seq, user, desc) should have the proper diagnostics -ok 106 - view_owner_is(seq, user, desc) should fail -ok 107 - view_owner_is(seq, user, desc) should have the proper description -ok 108 - view_owner_is(seq, user, desc) should have the proper diagnostics -ok 109 - sequence_owner_is(sch, sequence, user, desc) should pass -ok 110 - sequence_owner_is(sch, sequence, user, desc) should have the proper description -ok 111 - sequence_owner_is(sch, sequence, user, desc) should have the proper diagnostics -ok 112 - sequence_owner_is(sch, sequence, user) should pass -ok 113 - sequence_owner_is(sch, sequence, user) should have the proper description -ok 114 - sequence_owner_is(sch, sequence, user) should have the proper diagnostics -ok 115 - sequence_owner_is(non-sch, sequence, user) should fail -ok 116 - sequence_owner_is(non-sch, sequence, user) should have the proper description -ok 117 - sequence_owner_is(non-sch, sequence, user) should have the proper diagnostics -ok 118 - sequence_owner_is(sch, non-sequence, user) should fail -ok 119 - sequence_owner_is(sch, non-sequence, user) should have the proper description -ok 120 - sequence_owner_is(sch, non-sequence, user) should have the proper diagnostics -ok 121 - sequence_owner_is(sequence, user, desc) should pass -ok 122 - sequence_owner_is(sequence, user, desc) should have the proper description -ok 123 - sequence_owner_is(sequence, user, desc) should have the proper diagnostics -ok 124 - sequence_owner_is(sequence, user) should pass -ok 125 - sequence_owner_is(sequence, user) should have the proper description -ok 126 - sequence_owner_is(sequence, user) should have the proper diagnostics -ok 127 - sequence_owner_is(non-sequence, user) should fail -ok 128 - sequence_owner_is(non-sequence, user) should have the proper description -ok 129 - sequence_owner_is(non-sequence, user) should have the proper diagnostics -ok 130 - sequence_owner_is(sch, view, user, desc) should fail -ok 131 - sequence_owner_is(sch, view, user, desc) should have the proper description -ok 132 - sequence_owner_is(sch, view, user, desc) should have the proper diagnostics -ok 133 - sequence_owner_is(view, user, desc) should fail -ok 134 - sequence_owner_is(view, user, desc) should have the proper description -ok 135 - sequence_owner_is(view, user, desc) should have the proper diagnostics -ok 136 - composite_owner_is(sch, composite, user, desc) should pass -ok 137 - composite_owner_is(sch, composite, user, desc) should have the proper description -ok 138 - composite_owner_is(sch, composite, user, desc) should have the proper diagnostics -ok 139 - composite_owner_is(sch, composite, user) should pass -ok 140 - composite_owner_is(sch, composite, user) should have the proper description -ok 141 - composite_owner_is(sch, composite, user) should have the proper diagnostics -ok 142 - composite_owner_is(non-sch, composite, user) should fail -ok 143 - composite_owner_is(non-sch, composite, user) should have the proper description -ok 144 - composite_owner_is(non-sch, composite, user) should have the proper diagnostics -ok 145 - composite_owner_is(sch, non-composite, user) should fail -ok 146 - composite_owner_is(sch, non-composite, user) should have the proper description -ok 147 - composite_owner_is(sch, non-composite, user) should have the proper diagnostics -ok 148 - composite_owner_is(composite, user, desc) should pass -ok 149 - composite_owner_is(composite, user, desc) should have the proper description -ok 150 - composite_owner_is(composite, user, desc) should have the proper diagnostics -ok 151 - composite_owner_is(composite, user) should pass -ok 152 - composite_owner_is(composite, user) should have the proper description -ok 153 - composite_owner_is(composite, user) should have the proper diagnostics -ok 154 - composite_owner_is(non-composite, user) should fail -ok 155 - composite_owner_is(non-composite, user) should have the proper description -ok 156 - composite_owner_is(non-composite, user) should have the proper diagnostics -ok 157 - composite_owner_is(sch, view, user, desc) should fail -ok 158 - composite_owner_is(sch, view, user, desc) should have the proper description -ok 159 - composite_owner_is(sch, view, user, desc) should have the proper diagnostics -ok 160 - composite_owner_is(view, user, desc) should fail -ok 161 - composite_owner_is(view, user, desc) should have the proper description -ok 162 - composite_owner_is(view, user, desc) should have the proper diagnostics -ok 163 - foreign_table_owner_is(sch, tab, user, desc) should pass -ok 164 - foreign_table_owner_is(sch, tab, user, desc) should have the proper description -ok 165 - foreign_table_owner_is(sch, tab, user, desc) should have the proper diagnostics -ok 166 - foreign_table_owner_is(sch, tab, user) should pass -ok 167 - foreign_table_owner_is(sch, tab, user) should have the proper description -ok 168 - foreign_table_owner_is(sch, tab, user) should have the proper diagnostics -ok 169 - foreign_table_owner_is(non-sch, tab, user) should fail -ok 170 - foreign_table_owner_is(non-sch, tab, user) should have the proper description -ok 171 - foreign_table_owner_is(non-sch, tab, user) should have the proper diagnostics -ok 172 - foreign_table_owner_is(sch, non-tab, user) should fail -ok 173 - foreign_table_owner_is(sch, non-tab, user) should have the proper description -ok 174 - foreign_table_owner_is(sch, non-tab, user) should have the proper diagnostics -ok 175 - foreign_table_owner_is(tab, user, desc) should pass -ok 176 - foreign_table_owner_is(tab, user, desc) should have the proper description -ok 177 - foreign_table_owner_is(tab, user, desc) should have the proper diagnostics -ok 178 - foreign_table_owner_is(tab, user) should pass -ok 179 - foreign_table_owner_is(tab, user) should have the proper description -ok 180 - foreign_table_owner_is(tab, user) should have the proper diagnostics -ok 181 - foreign_table_owner_is(non-tab, user) should fail -ok 182 - foreign_table_owner_is(non-tab, user) should have the proper description -ok 183 - foreign_table_owner_is(non-tab, user) should have the proper diagnostics -ok 184 - foreign_table_owner_is(sch, tab, user, desc) should fail -ok 185 - foreign_table_owner_is(sch, tab, user, desc) should have the proper description -ok 186 - foreign_table_owner_is(sch, tab, user, desc) should have the proper diagnostics -ok 187 - foreign_table_owner_is(tab, user, desc) should fail +ok 13 - schema_owner_is(schema, user, desc) should pass +ok 14 - schema_owner_is(schema, user, desc) should have the proper description +ok 15 - schema_owner_is(schema, user, desc) should have the proper diagnostics +ok 16 - schema_owner_is(schema, user) should pass +ok 17 - schema_owner_is(schema, user) should have the proper description +ok 18 - schema_owner_is(schema, user) should have the proper diagnostics +ok 19 - schema_owner_is(non-schema, user) should fail +ok 20 - schema_owner_is(non-schema, user) should have the proper description +ok 21 - schema_owner_is(non-schema, user) should have the proper diagnostics +ok 22 - schema_owner_is(schema, non-user) should fail +ok 23 - schema_owner_is(schema, non-user) should have the proper description +ok 24 - schema_owner_is(schema, non-user) should have the proper diagnostics +ok 25 - relation_owner_is(sch, tab, user, desc) should pass +ok 26 - relation_owner_is(sch, tab, user, desc) should have the proper description +ok 27 - relation_owner_is(sch, tab, user, desc) should have the proper diagnostics +ok 28 - relation_owner_is(sch, tab, user) should pass +ok 29 - relation_owner_is(sch, tab, user) should have the proper description +ok 30 - relation_owner_is(sch, tab, user) should have the proper diagnostics +ok 31 - relation_owner_is(non-sch, tab, user) should fail +ok 32 - relation_owner_is(non-sch, tab, user) should have the proper description +ok 33 - relation_owner_is(non-sch, tab, user) should have the proper diagnostics +ok 34 - relation_owner_is(sch, non-tab, user) should fail +ok 35 - relation_owner_is(sch, non-tab, user) should have the proper description +ok 36 - relation_owner_is(sch, non-tab, user) should have the proper diagnostics +ok 37 - relation_owner_is(tab, user, desc) should pass +ok 38 - relation_owner_is(tab, user, desc) should have the proper description +ok 39 - relation_owner_is(tab, user, desc) should have the proper diagnostics +ok 40 - relation_owner_is(tab, user) should pass +ok 41 - relation_owner_is(tab, user) should have the proper description +ok 42 - relation_owner_is(tab, user) should have the proper diagnostics +ok 43 - relation_owner_is(non-tab, user) should fail +ok 44 - relation_owner_is(non-tab, user) should have the proper description +ok 45 - relation_owner_is(non-tab, user) should have the proper diagnostics +ok 46 - relation_owner_is(sch, seq, user, desc) should pass +ok 47 - relation_owner_is(sch, seq, user, desc) should have the proper description +ok 48 - relation_owner_is(sch, seq, user, desc) should have the proper diagnostics +ok 49 - relation_owner_is(sch, seq, user) should pass +ok 50 - relation_owner_is(sch, seq, user) should have the proper description +ok 51 - relation_owner_is(sch, seq, user) should have the proper diagnostics +ok 52 - relation_owner_is(non-sch, seq, user) should fail +ok 53 - relation_owner_is(non-sch, seq, user) should have the proper description +ok 54 - relation_owner_is(non-sch, seq, user) should have the proper diagnostics +ok 55 - relation_owner_is(sch, non-seq, user) should fail +ok 56 - relation_owner_is(sch, non-seq, user) should have the proper description +ok 57 - relation_owner_is(sch, non-seq, user) should have the proper diagnostics +ok 58 - relation_owner_is(seq, user, desc) should pass +ok 59 - relation_owner_is(seq, user, desc) should have the proper description +ok 60 - relation_owner_is(seq, user, desc) should have the proper diagnostics +ok 61 - relation_owner_is(seq, user) should pass +ok 62 - relation_owner_is(seq, user) should have the proper description +ok 63 - relation_owner_is(seq, user) should have the proper diagnostics +ok 64 - relation_owner_is(non-seq, user) should fail +ok 65 - relation_owner_is(non-seq, user) should have the proper description +ok 66 - relation_owner_is(non-seq, user) should have the proper diagnostics +ok 67 - table_owner_is(sch, tab, user, desc) should pass +ok 68 - table_owner_is(sch, tab, user, desc) should have the proper description +ok 69 - table_owner_is(sch, tab, user, desc) should have the proper diagnostics +ok 70 - table_owner_is(sch, tab, user) should pass +ok 71 - table_owner_is(sch, tab, user) should have the proper description +ok 72 - table_owner_is(sch, tab, user) should have the proper diagnostics +ok 73 - table_owner_is(non-sch, tab, user) should fail +ok 74 - table_owner_is(non-sch, tab, user) should have the proper description +ok 75 - table_owner_is(non-sch, tab, user) should have the proper diagnostics +ok 76 - table_owner_is(sch, non-tab, user) should fail +ok 77 - table_owner_is(sch, non-tab, user) should have the proper description +ok 78 - table_owner_is(sch, non-tab, user) should have the proper diagnostics +ok 79 - table_owner_is(tab, user, desc) should pass +ok 80 - table_owner_is(tab, user, desc) should have the proper description +ok 81 - table_owner_is(tab, user, desc) should have the proper diagnostics +ok 82 - table_owner_is(tab, user) should pass +ok 83 - table_owner_is(tab, user) should have the proper description +ok 84 - table_owner_is(tab, user) should have the proper diagnostics +ok 85 - table_owner_is(non-tab, user) should fail +ok 86 - table_owner_is(non-tab, user) should have the proper description +ok 87 - table_owner_is(non-tab, user) should have the proper diagnostics +ok 88 - table_owner_is(sch, seq, user, desc) should fail +ok 89 - table_owner_is(sch, seq, user, desc) should have the proper description +ok 90 - table_owner_is(sch, seq, user, desc) should have the proper diagnostics +ok 91 - table_owner_is(seq, user, desc) should fail +ok 92 - table_owner_is(seq, user, desc) should have the proper description +ok 93 - table_owner_is(seq, user, desc) should have the proper diagnostics +ok 94 - view_owner_is(sch, view, user, desc) should pass +ok 95 - view_owner_is(sch, view, user, desc) should have the proper description +ok 96 - view_owner_is(sch, view, user, desc) should have the proper diagnostics +ok 97 - view_owner_is(sch, view, user) should pass +ok 98 - view_owner_is(sch, view, user) should have the proper description +ok 99 - view_owner_is(sch, view, user) should have the proper diagnostics +ok 100 - view_owner_is(non-sch, view, user) should fail +ok 101 - view_owner_is(non-sch, view, user) should have the proper description +ok 102 - view_owner_is(non-sch, view, user) should have the proper diagnostics +ok 103 - view_owner_is(sch, non-view, user) should fail +ok 104 - view_owner_is(sch, non-view, user) should have the proper description +ok 105 - view_owner_is(sch, non-view, user) should have the proper diagnostics +ok 106 - view_owner_is(view, user, desc) should pass +ok 107 - view_owner_is(view, user, desc) should have the proper description +ok 108 - view_owner_is(view, user, desc) should have the proper diagnostics +ok 109 - view_owner_is(view, user) should pass +ok 110 - view_owner_is(view, user) should have the proper description +ok 111 - view_owner_is(view, user) should have the proper diagnostics +ok 112 - view_owner_is(non-view, user) should fail +ok 113 - view_owner_is(non-view, user) should have the proper description +ok 114 - view_owner_is(non-view, user) should have the proper diagnostics +ok 115 - view_owner_is(sch, seq, user, desc) should fail +ok 116 - view_owner_is(sch, seq, user, desc) should have the proper description +ok 117 - view_owner_is(sch, seq, user, desc) should have the proper diagnostics +ok 118 - view_owner_is(seq, user, desc) should fail +ok 119 - view_owner_is(seq, user, desc) should have the proper description +ok 120 - view_owner_is(seq, user, desc) should have the proper diagnostics +ok 121 - sequence_owner_is(sch, sequence, user, desc) should pass +ok 122 - sequence_owner_is(sch, sequence, user, desc) should have the proper description +ok 123 - sequence_owner_is(sch, sequence, user, desc) should have the proper diagnostics +ok 124 - sequence_owner_is(sch, sequence, user) should pass +ok 125 - sequence_owner_is(sch, sequence, user) should have the proper description +ok 126 - sequence_owner_is(sch, sequence, user) should have the proper diagnostics +ok 127 - sequence_owner_is(non-sch, sequence, user) should fail +ok 128 - sequence_owner_is(non-sch, sequence, user) should have the proper description +ok 129 - sequence_owner_is(non-sch, sequence, user) should have the proper diagnostics +ok 130 - sequence_owner_is(sch, non-sequence, user) should fail +ok 131 - sequence_owner_is(sch, non-sequence, user) should have the proper description +ok 132 - sequence_owner_is(sch, non-sequence, user) should have the proper diagnostics +ok 133 - sequence_owner_is(sequence, user, desc) should pass +ok 134 - sequence_owner_is(sequence, user, desc) should have the proper description +ok 135 - sequence_owner_is(sequence, user, desc) should have the proper diagnostics +ok 136 - sequence_owner_is(sequence, user) should pass +ok 137 - sequence_owner_is(sequence, user) should have the proper description +ok 138 - sequence_owner_is(sequence, user) should have the proper diagnostics +ok 139 - sequence_owner_is(non-sequence, user) should fail +ok 140 - sequence_owner_is(non-sequence, user) should have the proper description +ok 141 - sequence_owner_is(non-sequence, user) should have the proper diagnostics +ok 142 - sequence_owner_is(sch, view, user, desc) should fail +ok 143 - sequence_owner_is(sch, view, user, desc) should have the proper description +ok 144 - sequence_owner_is(sch, view, user, desc) should have the proper diagnostics +ok 145 - sequence_owner_is(view, user, desc) should fail +ok 146 - sequence_owner_is(view, user, desc) should have the proper description +ok 147 - sequence_owner_is(view, user, desc) should have the proper diagnostics +ok 148 - composite_owner_is(sch, composite, user, desc) should pass +ok 149 - composite_owner_is(sch, composite, user, desc) should have the proper description +ok 150 - composite_owner_is(sch, composite, user, desc) should have the proper diagnostics +ok 151 - composite_owner_is(sch, composite, user) should pass +ok 152 - composite_owner_is(sch, composite, user) should have the proper description +ok 153 - composite_owner_is(sch, composite, user) should have the proper diagnostics +ok 154 - composite_owner_is(non-sch, composite, user) should fail +ok 155 - composite_owner_is(non-sch, composite, user) should have the proper description +ok 156 - composite_owner_is(non-sch, composite, user) should have the proper diagnostics +ok 157 - composite_owner_is(sch, non-composite, user) should fail +ok 158 - composite_owner_is(sch, non-composite, user) should have the proper description +ok 159 - composite_owner_is(sch, non-composite, user) should have the proper diagnostics +ok 160 - composite_owner_is(composite, user, desc) should pass +ok 161 - composite_owner_is(composite, user, desc) should have the proper description +ok 162 - composite_owner_is(composite, user, desc) should have the proper diagnostics +ok 163 - composite_owner_is(composite, user) should pass +ok 164 - composite_owner_is(composite, user) should have the proper description +ok 165 - composite_owner_is(composite, user) should have the proper diagnostics +ok 166 - composite_owner_is(non-composite, user) should fail +ok 167 - composite_owner_is(non-composite, user) should have the proper description +ok 168 - composite_owner_is(non-composite, user) should have the proper diagnostics +ok 169 - composite_owner_is(sch, view, user, desc) should fail +ok 170 - composite_owner_is(sch, view, user, desc) should have the proper description +ok 171 - composite_owner_is(sch, view, user, desc) should have the proper diagnostics +ok 172 - composite_owner_is(view, user, desc) should fail +ok 173 - composite_owner_is(view, user, desc) should have the proper description +ok 174 - composite_owner_is(view, user, desc) should have the proper diagnostics +ok 175 - foreign_table_owner_is(sch, tab, user, desc) should pass +ok 176 - foreign_table_owner_is(sch, tab, user, desc) should have the proper description +ok 177 - foreign_table_owner_is(sch, tab, user, desc) should have the proper diagnostics +ok 178 - foreign_table_owner_is(sch, tab, user) should pass +ok 179 - foreign_table_owner_is(sch, tab, user) should have the proper description +ok 180 - foreign_table_owner_is(sch, tab, user) should have the proper diagnostics +ok 181 - foreign_table_owner_is(non-sch, tab, user) should fail +ok 182 - foreign_table_owner_is(non-sch, tab, user) should have the proper description +ok 183 - foreign_table_owner_is(non-sch, tab, user) should have the proper diagnostics +ok 184 - foreign_table_owner_is(sch, non-tab, user) should fail +ok 185 - foreign_table_owner_is(sch, non-tab, user) should have the proper description +ok 186 - foreign_table_owner_is(sch, non-tab, user) should have the proper diagnostics +ok 187 - foreign_table_owner_is(tab, user, desc) should pass ok 188 - foreign_table_owner_is(tab, user, desc) should have the proper description ok 189 - foreign_table_owner_is(tab, user, desc) should have the proper diagnostics -ok 190 - function_owner_is(sch, function, args[integer], user, desc) should pass -ok 191 - function_owner_is(sch, function, args[integer], user, desc) should have the proper description -ok 192 - function_owner_is(sch, function, args[integer], user, desc) should have the proper diagnostics -ok 193 - function_owner_is(sch, function, args[integer], user) should pass -ok 194 - function_owner_is(sch, function, args[integer], user) should have the proper description -ok 195 - function_owner_is(sch, function, args[integer], user) should have the proper diagnostics -ok 196 - function_owner_is(sch, function, args[], user, desc) should pass -ok 197 - function_owner_is(sch, function, args[], user, desc) should have the proper description -ok 198 - function_owner_is(sch, function, args[], user, desc) should have the proper diagnostics -ok 199 - function_owner_is(sch, function, args[], user) should pass -ok 200 - function_owner_is(sch, function, args[], user) should have the proper description -ok 201 - function_owner_is(sch, function, args[], user) should have the proper diagnostics -ok 202 - function_owner_is(function, args[integer], user, desc) should pass -ok 203 - function_owner_is(function, args[integer], user, desc) should have the proper description -ok 204 - function_owner_is(function, args[integer], user, desc) should have the proper diagnostics -ok 205 - function_owner_is(function, args[integer], user) should pass -ok 206 - function_owner_is(function, args[integer], user) should have the proper description -ok 207 - function_owner_is(function, args[integer], user) should have the proper diagnostics -ok 208 - function_owner_is(function, args[], user, desc) should pass -ok 209 - function_owner_is(function, args[], user, desc) should have the proper description -ok 210 - function_owner_is(function, args[], user, desc) should have the proper diagnostics -ok 211 - function_owner_is(function, args[], user) should pass -ok 212 - function_owner_is(function, args[], user) should have the proper description -ok 213 - function_owner_is(function, args[], user) should have the proper diagnostics -ok 214 - function_owner_is(sch, non-function, args[integer], user, desc) should fail -ok 215 - function_owner_is(sch, non-function, args[integer], user, desc) should have the proper description -ok 216 - function_owner_is(sch, non-function, args[integer], user, desc) should have the proper diagnostics -ok 217 - function_owner_is(non-sch, function, args[integer], user, desc) should fail -ok 218 - function_owner_is(non-sch, function, args[integer], user, desc) should have the proper description -ok 219 - function_owner_is(non-sch, function, args[integer], user, desc) should have the proper diagnostics -ok 220 - function_owner_is(non-function, args[integer], user, desc) should fail -ok 221 - function_owner_is(non-function, args[integer], user, desc) should have the proper description -ok 222 - function_owner_is(non-function, args[integer], user, desc) should have the proper diagnostics -ok 223 - function_owner_is(sch, function, args[integer], non-user, desc) should fail -ok 224 - function_owner_is(sch, function, args[integer], non-user, desc) should have the proper description -ok 225 - function_owner_is(sch, function, args[integer], non-user, desc) should have the proper diagnostics -ok 226 - function_owner_is(function, args[integer], non-user, desc) should fail -ok 227 - function_owner_is(function, args[integer], non-user, desc) should have the proper description -ok 228 - function_owner_is(function, args[integer], non-user, desc) should have the proper diagnostics +ok 190 - foreign_table_owner_is(tab, user) should pass +ok 191 - foreign_table_owner_is(tab, user) should have the proper description +ok 192 - foreign_table_owner_is(tab, user) should have the proper diagnostics +ok 193 - foreign_table_owner_is(non-tab, user) should fail +ok 194 - foreign_table_owner_is(non-tab, user) should have the proper description +ok 195 - foreign_table_owner_is(non-tab, user) should have the proper diagnostics +ok 196 - foreign_table_owner_is(sch, tab, user, desc) should fail +ok 197 - foreign_table_owner_is(sch, tab, user, desc) should have the proper description +ok 198 - foreign_table_owner_is(sch, tab, user, desc) should have the proper diagnostics +ok 199 - foreign_table_owner_is(tab, user, desc) should fail +ok 200 - foreign_table_owner_is(tab, user, desc) should have the proper description +ok 201 - foreign_table_owner_is(tab, user, desc) should have the proper diagnostics +ok 202 - function_owner_is(sch, function, args[integer], user, desc) should pass +ok 203 - function_owner_is(sch, function, args[integer], user, desc) should have the proper description +ok 204 - function_owner_is(sch, function, args[integer], user, desc) should have the proper diagnostics +ok 205 - function_owner_is(sch, function, args[integer], user) should pass +ok 206 - function_owner_is(sch, function, args[integer], user) should have the proper description +ok 207 - function_owner_is(sch, function, args[integer], user) should have the proper diagnostics +ok 208 - function_owner_is(sch, function, args[], user, desc) should pass +ok 209 - function_owner_is(sch, function, args[], user, desc) should have the proper description +ok 210 - function_owner_is(sch, function, args[], user, desc) should have the proper diagnostics +ok 211 - function_owner_is(sch, function, args[], user) should pass +ok 212 - function_owner_is(sch, function, args[], user) should have the proper description +ok 213 - function_owner_is(sch, function, args[], user) should have the proper diagnostics +ok 214 - function_owner_is(function, args[integer], user, desc) should pass +ok 215 - function_owner_is(function, args[integer], user, desc) should have the proper description +ok 216 - function_owner_is(function, args[integer], user, desc) should have the proper diagnostics +ok 217 - function_owner_is(function, args[integer], user) should pass +ok 218 - function_owner_is(function, args[integer], user) should have the proper description +ok 219 - function_owner_is(function, args[integer], user) should have the proper diagnostics +ok 220 - function_owner_is(function, args[], user, desc) should pass +ok 221 - function_owner_is(function, args[], user, desc) should have the proper description +ok 222 - function_owner_is(function, args[], user, desc) should have the proper diagnostics +ok 223 - function_owner_is(function, args[], user) should pass +ok 224 - function_owner_is(function, args[], user) should have the proper description +ok 225 - function_owner_is(function, args[], user) should have the proper diagnostics +ok 226 - function_owner_is(sch, non-function, args[integer], user, desc) should fail +ok 227 - function_owner_is(sch, non-function, args[integer], user, desc) should have the proper description +ok 228 - function_owner_is(sch, non-function, args[integer], user, desc) should have the proper diagnostics +ok 229 - function_owner_is(non-sch, function, args[integer], user, desc) should fail +ok 230 - function_owner_is(non-sch, function, args[integer], user, desc) should have the proper description +ok 231 - function_owner_is(non-sch, function, args[integer], user, desc) should have the proper diagnostics +ok 232 - function_owner_is(non-function, args[integer], user, desc) should fail +ok 233 - function_owner_is(non-function, args[integer], user, desc) should have the proper description +ok 234 - function_owner_is(non-function, args[integer], user, desc) should have the proper diagnostics +ok 235 - function_owner_is(sch, function, args[integer], non-user, desc) should fail +ok 236 - function_owner_is(sch, function, args[integer], non-user, desc) should have the proper description +ok 237 - function_owner_is(sch, function, args[integer], non-user, desc) should have the proper diagnostics +ok 238 - function_owner_is(function, args[integer], non-user, desc) should fail +ok 239 - function_owner_is(function, args[integer], non-user, desc) should have the proper description +ok 240 - function_owner_is(function, args[integer], non-user, desc) should have the proper diagnostics diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index b5f0de4de6ef..3a2fbc150bcb 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(228); +SELECT plan(240); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -62,6 +62,40 @@ SELECT * FROM check_test( want: __not__' || current_user ); +/****************************************************************************/ +SELECT * FROM check_test( + schema_owner_is(current_schema(), _get_schema_owner(current_schema()), 'mumble'), + true, + 'schema_owner_is(schema, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + schema_owner_is(current_schema(), _get_schema_owner(current_schema())), + true, + 'schema_owner_is(schema, user)', + 'Schema ' || quote_ident(current_schema()) || ' should be owned by ' || _get_schema_owner(current_schema()), + '' +); + +SELECT * FROM check_test( + schema_owner_is('__not__' || current_schema(), _get_schema_owner(current_schema()), 'mumble'), + false, + 'schema_owner_is(non-schema, user)', + 'mumble', + ' Schema __not__' || current_schema() || ' does not exist' +); + +SELECT * FROM check_test( + schema_owner_is(current_schema(), '__not__' || _get_schema_owner(current_schema()), 'mumble'), + false, + 'schema_owner_is(schema, non-user)', + 'mumble', + ' have: ' || _get_schema_owner(current_schema()) || ' + want: __not__' || _get_schema_owner(current_schema()) +); + /****************************************************************************/ -- Test relation_owner_is() with a table. SELECT * FROM check_test( From 446980b9b80291e4ad77d99710a8864656c274f6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 21 Jan 2013 15:22:33 -0800 Subject: [PATCH 0742/1195] I kant spel. --- Changes | 1 + compat/install-8.1.patch | 4 +-- sql/pgtap--0.92.0--0.93.0.sql | 48 +++++++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 4 +-- 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 41540b702ec1..0a303370607a 100644 --- a/Changes +++ b/Changes @@ -4,6 +4,7 @@ Revision history for pgTAP 0.93.0 --------------------------- * Added `schema_owner_is()`. Not sure how I overlooked adding it in v0.92.0. +* Fixed misselling of "constraint" in constraint test diagnostic output. 0.92.0 2013-01-16T00:41:07Z --------------------------- diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index f178eba067af..245b6ee98163 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -17,7 +17,7 @@ + keys = keys || rec.a::text; END LOOP; IF array_upper(keys, 0) = 1 THEN - have := 'No ' || $6 || ' constriants'; + have := 'No ' || $6 || ' constraints'; @@ -2146,13 +2146,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ @@ -35,7 +35,7 @@ + keys = keys || rec.a::text; END LOOP; IF array_upper(keys, 0) = 1 THEN - have := 'No ' || $5 || ' constriants'; + have := 'No ' || $5 || ' constraints'; @@ -5735,7 +5735,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql index da847f642d64..a3d04396d0f1 100644 --- a/sql/pgtap--0.92.0--0.93.0.sql +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -31,3 +31,51 @@ RETURNS TEXT AS $$ 'Schema ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) ); $$ 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 60ac371950a6..1389a5c9547a 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2088,7 +2088,7 @@ BEGIN keys = keys || akey::text; END LOOP; IF array_upper(keys, 0) = 1 THEN - have := 'No ' || $6 || ' constriants'; + have := 'No ' || $6 || ' constraints'; ELSE have := array_to_string(keys, E'\n '); END IF; @@ -2112,7 +2112,7 @@ BEGIN keys = keys || akey::text; END LOOP; IF array_upper(keys, 0) = 1 THEN - have := 'No ' || $5 || ' constriants'; + have := 'No ' || $5 || ' constraints'; ELSE have := array_to_string(keys, E'\n '); END IF; From 0b040efb8e4f6dde79f87208ab4d0b50e29d8b2f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 21 Jan 2013 15:39:07 -0800 Subject: [PATCH 0743/1195] Foreign key table must be visible if no schema specified. --- sql/pgtap--0.92.0--0.93.0.sql | 27 +++++++++++++++++++++++++++ sql/pgtap.sql.in | 5 +++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql index a3d04396d0f1..6e99885c2bf6 100644 --- a/sql/pgtap--0.92.0--0.93.0.sql +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -79,3 +79,30 @@ BEGIN ); 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 1389a5c9547a..07887c2ecfaa 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2251,8 +2251,9 @@ DECLARE BEGIN SELECT pk_table_name, pk_columns FROM pg_all_foreign_keys - WHERE fk_table_name = $1 - AND fk_columns = $2 + WHERE fk_table_name = $1 + AND fk_columns = $2 + AND pg_catalog.pg_table_is_visible(fk_table_oid) INTO tab, cols; RETURN is( From afbb24aecfc152c66f94d24addf1633004b62c14 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 21 Jan 2013 15:39:51 -0800 Subject: [PATCH 0744/1195] Note fk_ok() fix. --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 0a303370607a..061827f678c1 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,8 @@ Revision history for pgTAP --------------------------- * Added `schema_owner_is()`. Not sure how I overlooked adding it in v0.92.0. * Fixed misselling of "constraint" in constraint test diagnostic output. +* Fixed `fk_ok()` so that the table must be visible in the search path when + the schema name is not specified. 0.92.0 2013-01-16T00:41:07Z --------------------------- From a76bf092b13d366a07714ee3f5b42964af29a8f2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 24 Jan 2013 15:29:59 -0800 Subject: [PATCH 0745/1195] Non-schema-qualified tables must be visible when testing constraints. --- Changes | 2 ++ sql/pgtap--0.92.0--0.93.0.sql | 11 +++++++++++ sql/pgtap.sql.in | 1 + 3 files changed, 14 insertions(+) diff --git a/Changes b/Changes index 061827f678c1..e913dad26ba0 100644 --- a/Changes +++ b/Changes @@ -7,6 +7,8 @@ Revision history for pgTAP * Fixed misselling of "constraint" in constraint test diagnostic output. * Fixed `fk_ok()` so that the table must be visible in the search path when the schema name is not specified. +* Fixed constraint-checking functions so that they only look at tables visible + in the search path unles the schema is specified. 0.92.0 2013-01-16T00:41:07Z --------------------------- diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql index 6e99885c2bf6..3d2b6c9d77f9 100644 --- a/sql/pgtap--0.92.0--0.93.0.sql +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -106,3 +106,14 @@ BEGIN ); END; $$ LANGUAGE plpgsql; + +-- _keys( table, constraint_type ) +CREATE OR REPLACE FUNCTION _keys ( NAME, CHAR ) +RETURNS SETOF NAME[] AS $$ + SELECT _pg_sv_column_array(x.conrelid,x.conkey) + 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) +$$ LANGUAGE sql; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 07887c2ecfaa..8df9832c50f0 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1790,6 +1790,7 @@ RETURNS SETOF NAME[] AS $$ 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) $$ LANGUAGE sql; -- _ckeys( schema, table, constraint_type ) From 870d1ba8755e6bccb54ca644ee29fb7aae8e5782 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 24 Jan 2013 15:32:35 -0800 Subject: [PATCH 0746/1195] Non-schema-qualified tables must be visible when testing triggers. --- Changes | 4 ++-- sql/pgtap--0.92.0--0.93.0.sql | 12 ++++++++++++ sql/pgtap.sql.in | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index e913dad26ba0..e2456c05d8d2 100644 --- a/Changes +++ b/Changes @@ -7,8 +7,8 @@ Revision history for pgTAP * Fixed misselling of "constraint" in constraint test diagnostic output. * Fixed `fk_ok()` so that the table must be visible in the search path when the schema name is not specified. -* Fixed constraint-checking functions so that they only look at tables visible - in the search path unles the schema is specified. +* Fixed constraint- and trigger-checking functions so that they only look at + tables visible in the search path unles the schema is specified. 0.92.0 2013-01-16T00:41:07Z --------------------------- diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql index 3d2b6c9d77f9..452b0d611dbf 100644 --- a/sql/pgtap--0.92.0--0.93.0.sql +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -117,3 +117,15 @@ RETURNS SETOF NAME[] AS $$ AND x.contype = $2 WHERE pg_catalog.pg_table_is_visible(c.oid) $$ 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 8df9832c50f0..c1b8925ae4d6 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -3073,6 +3073,7 @@ RETURNS BOOLEAN AS $$ 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; From f6fb778e9bcb3800b61278ad915dd5da5932b3b9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 24 Jan 2013 15:38:14 -0800 Subject: [PATCH 0747/1195] Don't report a column as missing when its type is not visible. --- Changes | 2 ++ sql/pgtap--0.92.0--0.93.0.sql | 12 ++++++++++++ sql/pgtap.sql.in | 3 +-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index e2456c05d8d2..60b090013be9 100644 --- a/Changes +++ b/Changes @@ -9,6 +9,8 @@ Revision history for pgTAP the schema name is not specified. * Fixed constraint- and trigger-checking functions so that they only look at tables visible in the search path unles the schema is specified. +* Fixed `col_type_is()` so that it won't report the column as not existing + when the column's data type is not in the search path. 0.92.0 2013-01-16T00:41:07Z --------------------------- diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql index 452b0d611dbf..e833c6e1891c 100644 --- a/sql/pgtap--0.92.0--0.93.0.sql +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -129,3 +129,15 @@ RETURNS BOOLEAN AS $$ AND pg_catalog.pg_table_is_visible(c.oid) ); $$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT display_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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index c1b8925ae4d6..c3f869e4b65c 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1253,12 +1253,11 @@ RETURNS TEXT AS $$ SELECT display_type(a.atttypid, a.atttypmod) FROM pg_catalog.pg_attribute a JOIN pg_catalog.pg_class c ON a.attrelid = c.oid - WHERE pg_table_is_visible(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 - AND pg_type_is_visible(a.atttypid) $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _get_col_ns_type ( NAME, NAME, NAME ) From 979651f7a833dd7e83eb3f32e16ef8f788548469 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 24 Jan 2013 17:00:53 -0800 Subject: [PATCH 0748/1195] Make sure tables are visible when testing FKs. --- Changes | 5 +++-- sql/pgtap--0.92.0--0.93.0.sql | 11 +++++++++++ sql/pgtap.sql.in | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 60b090013be9..67aaeee6e785 100644 --- a/Changes +++ b/Changes @@ -7,8 +7,9 @@ Revision history for pgTAP * Fixed misselling of "constraint" in constraint test diagnostic output. * Fixed `fk_ok()` so that the table must be visible in the search path when the schema name is not specified. -* Fixed constraint- and trigger-checking functions so that they only look at - tables visible in the search path unles the schema is specified. +* Fixed constraint, trigger, and foreign key-checking functions so that they + only look at tables visible in the search path unles the schema is + specified. * Fixed `col_type_is()` so that it won't report the column as not existing when the column's data type is not in the search path. diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql index e833c6e1891c..962a942ff974 100644 --- a/sql/pgtap--0.92.0--0.93.0.sql +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -141,3 +141,14 @@ RETURNS TEXT AS $$ AND attnum > 0 AND NOT a.attisdropped $$ 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index c3f869e4b65c..78d3b8854580 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1929,6 +1929,7 @@ RETURNS BOOLEAN AS $$ 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; From 35cf17eecc29a1af7111fb8194de7cf62a9f324a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 24 Jan 2013 16:28:20 -0800 Subject: [PATCH 0749/1195] Remove format_type(); use pg_display_type(), instead. --- Changes | 4 + doc/pgtap.mmd | 28 +----- sql/pgtap--0.92.0--0.93.0.sql | 170 ++++++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 42 ++++----- test/expected/util.out | 23 +---- test/sql/aretap.sql | 3 +- test/sql/hastap.sql | 28 +++--- test/sql/util.sql | 49 +--------- 8 files changed, 210 insertions(+), 137 deletions(-) diff --git a/Changes b/Changes index 67aaeee6e785..de6a0bd14a82 100644 --- a/Changes +++ b/Changes @@ -12,6 +12,10 @@ Revision history for pgTAP specified. * Fixed `col_type_is()` so that it won't report the column as not existing when the column's data type is not in the search path. +* Removed `format_type()` and switched to the core PostgreSQL type formatting + function so that type names always include the schema name if the schema is + not in the search path. You might have to update data type tests to include + the schema name if the type is not in the search path. 0.92.0 2013-01-16T00:41:07Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 29ab0eb3b3dd..695129d7a35a 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -6611,32 +6611,6 @@ On PostgreSQL 8.4 and higher, it can take any number of arguments. Lower than ok(false, 'This should fail) ]); -### `display_type()` ### - - SELECT display_type( :schema, :typeoid, typemod ); - SELECT display_type( :typeoid, :typemod ); - -**Parameters** - -`:schema` -: Schema in which to find the type. - -`:typeoid` -: OID of the type. - -`:typemod` -: Typemode for the type. - -Like `pg_catalog.format_type()`, except that the returned value is not -prepended with the schema name unless it is passed as the first argument. Some -examples: - - SELECT display_type('public', 'varchar'::regtype, NULL ); - SELECT display_type('numeric'::regtype, 196612 ); - -Used internally by pgTAP to compare type names, but may be more generally -useful. - ### `display_oper()` ### SELECT display_oper( :opername, :operoid ); @@ -6652,7 +6626,7 @@ useful. Similar to casting an operator OID to regoperator, only the schema is not included in the display. For example: - SELECT display_type(oprname, oid ) FROM pg_operator; + SELECT display_oper(oprname, oid ) FROM pg_operator; Used internally by pgTAP to compare operators, but may be more generally useful. diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql index 962a942ff974..4b4aad3bbf4e 100644 --- a/sql/pgtap--0.92.0--0.93.0.sql +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -152,3 +152,173 @@ RETURNS BOOLEAN AS $$ AND fk_columns = $2 ); $$ LANGUAGE SQL; + +DROP FUNCTION display_type ( OID, INTEGER ); +DROP FUNCTION display_type ( NAME, OID, INTEGER ); + +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 $$ + SELECT quote_ident(tn.nspname) || '.' || 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; + +-- _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; + +CREATE OR REPLACE FUNCTION _cmp_types(oid, name) +RETURNS BOOLEAN AS $$ +DECLARE + dtype TEXT := pg_catalog.format_type($1, NULL); +BEGIN + RETURN dtype = _quote_ident_like($2, dtype); +END; +$$ LANGUAGE plpgsql; + +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; + +-- 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 78d3b8854580..db0ca193edb1 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1221,23 +1221,9 @@ RETURNS TEXT AS $$ SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should allow NULL', false ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION display_type ( OID, INTEGER ) -RETURNS TEXT AS $$ - SELECT COALESCE(substring( - pg_catalog.format_type($1, $2), - '(("(?!")([^"]|"")+"|[^.]+)([(][^)]+[)])?)$' - ), '') -$$ LANGUAGE SQL; - -CREATE OR REPLACE FUNCTION display_type ( NAME, OID, INTEGER ) -RETURNS TEXT AS $$ - SELECT CASE WHEN $1 IS NULL THEN '' ELSE quote_ident($1) || '.' END - || display_type($2, $3) -$$ LANGUAGE SQL; - CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME, NAME ) RETURNS TEXT AS $$ - SELECT display_type(a.atttypid, a.atttypmod) + 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 @@ -1250,7 +1236,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT display_type(a.atttypid, a.atttypmod) + 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) @@ -1262,7 +1248,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _get_col_ns_type ( NAME, NAME, NAME ) RETURNS TEXT AS $$ - SELECT display_type(tn.nspname, a.atttypid, a.atttypmod) + SELECT quote_ident(tn.nspname) || '.' || 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 @@ -1506,7 +1492,7 @@ BEGIN RETURN _def_is( pg_catalog.pg_get_expr(d.adbin, d.adrelid), - display_type(a.atttypid, a.atttypmod), + 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, @@ -1540,7 +1526,7 @@ BEGIN RETURN _def_is( pg_catalog.pg_get_expr(d.adbin, d.adrelid), - display_type(a.atttypid, a.atttypmod), + 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 @@ -3714,7 +3700,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _cmp_types(oid, name) RETURNS BOOLEAN AS $$ DECLARE - dtype TEXT := display_type($1, NULL); + dtype TEXT := pg_catalog.format_type($1, NULL); BEGIN RETURN dtype = _quote_ident_like($2, dtype); END; @@ -6963,11 +6949,14 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _get_dtype( NAME, TEXT, BOOLEAN ) RETURNS TEXT AS $$ - SELECT display_type(CASE WHEN $3 THEN tn.nspname ELSE NULL END, t.oid, t.typtypmod) + 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 d.typnamespace = tn.oid + JOIN pg_catalog.pg_namespace tn ON t.typnamespace = tn.oid WHERE d.typisdefined AND dn.nspname = $1 AND d.typname = LOWER($2) @@ -6976,10 +6965,11 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _get_dtype( NAME ) RETURNS TEXT AS $$ - SELECT display_type(t.oid, t.typtypmod) + 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; @@ -7276,7 +7266,8 @@ RETURNS TEXT AS $$ SELECT _areni( 'casts', ARRAY( - SELECT display_type(castsource, NULL) || ' AS ' || display_type(casttarget, NULL) + SELECT pg_catalog.format_type(castsource, NULL) + || ' AS ' || pg_catalog.format_type(casttarget, NULL) FROM pg_catalog.pg_cast c EXCEPT SELECT $1[i] @@ -7286,7 +7277,8 @@ RETURNS TEXT AS $$ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT - SELECT display_type(castsource, NULL) || ' AS ' || display_type(casttarget, NULL) + SELECT pg_catalog.format_type(castsource, NULL) + || ' AS ' || pg_catalog.format_type(casttarget, NULL) FROM pg_catalog.pg_cast c ), $2 diff --git a/test/expected/util.out b/test/expected/util.out index 03a39fcff008..2fca170c4cf8 100644 --- a/test/expected/util.out +++ b/test/expected/util.out @@ -1,5 +1,5 @@ \unset ECHO -1..35 +1..14 ok 1 - pg_type(int) should work ok 2 - pg_type(numeric) should work ok 3 - pg_type(text) should work @@ -14,24 +14,3 @@ ok 11 - findfincs() should return distinct values ok 12 - pgtap_version() should work ok 13 - collect_tap(text[]) should simply collect tap ok 14 - variadic collect_tap() should simply collect tap -ok 15 - display_type(int4) -ok 16 - display_type(numeric) -ok 17 - display_type(numeric, typmod) -ok 18 - display_type("char") -ok 19 - display_type(char) -ok 20 - display_type(timestamp) -ok 21 - display_type(timestamptz) -ok 22 - display_type(foo, int4) -ok 23 - display_type(HEY, numeric) -ok 24 - display_type(t z, int4) -ok 25 - display type_type(text) -ok 26 - display_type(__foo.goofy) -ok 27 - display_type(__foo."this.that") -ok 28 - display_type(__foo."this"".that") -ok 29 - display_type(__foo."hey"".yoman", 13) -ok 30 - display_type("try.this""", 42) -ok 31 - No quoting is required -ok 32 - Just quote -ok 33 - No quoting is required (with precision) -ok 34 - Quote as type with precision -ok 35 - Quote as ident without precision diff --git a/test/sql/aretap.sql b/test/sql/aretap.sql index ee578faa3bfb..3301c08a0f85 100644 --- a/test/sql/aretap.sql +++ b/test/sql/aretap.sql @@ -1224,7 +1224,8 @@ SELECT * FROM check_test( CREATE OR REPLACE FUNCTION ___mycasts(ex text) RETURNS TEXT[] AS $$ SELECT ARRAY( - SELECT display_type(castsource, NULL) || ' AS ' || display_type(casttarget, NULL) + SELECT pg_catalog.format_type(castsource, NULL) + || ' AS ' || pg_catalog.format_type(casttarget, NULL) FROM pg_catalog.pg_cast WHERE castsource::regtype::text <> $1 ); diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index d328b7193ca0..41f2aa873fda 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1749,7 +1749,7 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test domain_type_is() and domain_type_isnt(). SELECT * FROM check_test( - domain_type_is( 'public', 'us_postal_code', 'public', 'text', 'whatever'), + domain_type_is( 'public', 'us_postal_code', 'pg_catalog', 'text', 'whatever'), true, 'domain_type_is(schema, domain, schema, type, desc)', 'whatever', @@ -1757,24 +1757,24 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_is( 'public', 'us_postal_code', 'public'::name, 'text'), + domain_type_is( 'public', 'us_postal_code', 'pg_catalog'::name, 'text'), true, 'domain_type_is(schema, domain, schema, type)', - 'Domain public.us_postal_code should extend type public.text', + 'Domain public.us_postal_code should extend type pg_catalog.text', '' ); SELECT * FROM check_test( - domain_type_is( 'public', 'us_postal_code', 'public', 'integer', 'whatever'), + domain_type_is( 'public', 'us_postal_code', 'pg_catalog', 'integer', 'whatever'), false, 'domain_type_is(schema, domain, schema, type, desc) fail', 'whatever', - ' have: public.text - want: public.integer' + ' have: pg_catalog.text + want: pg_catalog.integer' ); SELECT * FROM check_test( - domain_type_is( 'public', 'zip_code', 'public', 'integer', 'whatever'), + domain_type_is( 'public', 'zip_code', 'pg_catalog', 'integer', 'whatever'), false, 'domain_type_is(schema, nondomain, schema, type, desc)', 'whatever', @@ -1782,7 +1782,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_is( 'public', 'integer', 'public', 'integer', 'whatever'), + domain_type_is( 'public', 'integer', 'pg_catalog', 'integer', 'whatever'), false, 'domain_type_is(schema, type, schema, type, desc) fail', 'whatever', @@ -1880,24 +1880,24 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_isnt( 'public', 'us_postal_code', 'public'::name, 'integer'), + domain_type_isnt( 'public', 'us_postal_code', 'pg_catalog'::name, 'integer'), true, 'domain_type_isnt(schema, domain, schema, type)', - 'Domain public.us_postal_code should not extend type public.integer', + 'Domain public.us_postal_code should not extend type pg_catalog.integer', '' ); SELECT * FROM check_test( - domain_type_isnt( 'public', 'us_postal_code', 'public', 'text', 'whatever'), + domain_type_isnt( 'public', 'us_postal_code', 'pg_catalog', 'text', 'whatever'), false, 'domain_type_isnt(schema, domain, schema, type, desc) fail', 'whatever', - ' have: public.text + ' have: pg_catalog.text want: anything else' ); SELECT * FROM check_test( - domain_type_isnt( 'public', 'zip_code', 'public', 'text', 'whatever'), + domain_type_isnt( 'public', 'zip_code', 'pg_catalog', 'text', 'whatever'), false, 'domain_type_isnt(schema, nondomain, schema, type, desc)', 'whatever', @@ -1905,7 +1905,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_isnt( 'public', 'integer', 'public', 'text', 'whatever'), + domain_type_isnt( 'public', 'integer', 'pg_catalog', 'text', 'whatever'), false, 'domain_type_isnt(schema, type, schema, type, desc)', 'whatever', diff --git a/test/sql/util.sql b/test/sql/util.sql index 568fc3d8538a..ae32d81a66f2 100644 --- a/test/sql/util.sql +++ b/test/sql/util.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(35); +SELECT plan(14); --SELECT * FROM no_plan(); SELECT is( pg_typeof(42), 'integer', 'pg_type(int) should work' ); @@ -86,53 +86,6 @@ baz', 'variadic collect_tap() should simply collect tap' ); -/****************************************************************************/ --- Test display_type(). -SELECT is( display_type('int4'::regtype, NULL), 'integer', 'display_type(int4)'); -SELECT is( display_type('numeric'::regtype, NULL), 'numeric', 'display_type(numeric)'); -SELECT is( display_type('numeric'::regtype, 196612), 'numeric(3,0)', 'display_type(numeric, typmod)'); -SELECT is( display_type('"char"'::regtype, NULL), '"char"', 'display_type("char")'); -SELECT is( display_type('char'::regtype, NULL), 'character', 'display_type(char)'); -SELECT is( display_type('timestamp'::regtype, NULL), 'timestamp without time zone', 'display_type(timestamp)'); -SELECT is( display_type('timestamptz'::regtype, NULL), 'timestamp with time zone', 'display_type(timestamptz)'); - -SELECT is( display_type('foo', 'int4'::regtype, NULL), 'foo.integer', 'display_type(foo, int4)'); -SELECT is( display_type('HEY', 'numeric'::regtype, NULL), '"HEY".numeric', 'display_type(HEY, numeric)'); -SELECT is( display_type('t z', 'int4'::regtype, NULL), '"t z".integer', 'display_type(t z, int4)'); -SELECT is( display_type('text'::regtype, NULL), 'text', 'display type_type(text)'); - --- Look at a type not in the current schema. -CREATE SCHEMA __foo; -CREATE DOMAIN __foo.goofy AS text CHECK ( TRUE ); -SELECT is( display_type( oid, NULL ), 'goofy', 'display_type(__foo.goofy)' ) - FROM pg_type WHERE typname = 'goofy'; - --- Look at types with funny names. -CREATE DOMAIN __foo."this.that" AS text CHECK (TRUE); -SELECT is( display_type( oid, NULL ), '"this.that"', 'display_type(__foo."this.that")' ) - FROM pg_type WHERE typname = 'this.that'; - -CREATE DOMAIN __foo."this"".that" AS text CHECK (TRUE); -SELECT is( display_type( oid, NULL ), '"this"".that"', 'display_type(__foo."this"".that")' ) - FROM pg_type WHERE typname = 'this".that'; - --- Look at types with precision. -CREATE DOMAIN __foo."hey"".yoman" AS numeric CHECK (TRUE); -SELECT is( display_type( oid, 13 ), '"hey"".yoman"(13)', 'display_type(__foo."hey"".yoman", 13)' ) - FROM pg_type WHERE typname = 'hey".yoman'; - -CREATE DOMAIN "try.this""" AS numeric CHECK (TRUE); -SELECT is( display_type( oid, 42 ), '"try.this"""(42)', 'display_type("try.this""", 42)' ) - FROM pg_type WHERE typname = 'try.this"'; - --- Take care about quoting with/without precision -SELECT is(_quote_ident_like('test','public.test'), 'test', 'No quoting is required'); -SELECT is(_quote_ident_like('test type','public."test type"'), '"test type"', 'Just quote'); -SELECT is(_quote_ident_like('varchar(12)', 'varchar(12)'), 'varchar(12)', 'No quoting is required (with precision)'); -SELECT is(_quote_ident_like('test type(123)','myschema."test type"(234)'), '"test type"(123)', 'Quote as type with precision'); -SELECT is(_quote_ident_like('test table (123)','public."test table (123)"'), '"test table (123)"', 'Quote as ident without precision'); - - /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 11e494998fc5e09c580f0729bfa643fcf56ba166 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 25 Jan 2013 10:16:19 -0800 Subject: [PATCH 0750/1195] Need to update `pgtap_version()`, too. --- Changes | 2 ++ sql/pgtap--0.90.0--0.91.0.sql | 4 ++++ sql/pgtap--0.91.0--0.92.0.sql | 4 ++++ sql/pgtap--0.92.0--0.93.0.sql | 4 ++++ 4 files changed, 14 insertions(+) diff --git a/Changes b/Changes index de6a0bd14a82..dd015672d198 100644 --- a/Changes +++ b/Changes @@ -16,6 +16,8 @@ Revision history for pgTAP function so that type names always include the schema name if the schema is not in the search path. You might have to update data type tests to include the schema name if the type is not in the search path. +* Added code to the `ALTER EXTENSION` upgrade scripts to update the value + returned by `pgtap_version()`. 0.92.0 2013-01-16T00:41:07Z --------------------------- diff --git a/sql/pgtap--0.90.0--0.91.0.sql b/sql/pgtap--0.90.0--0.91.0.sql index f8f6271136c7..4c9df66f18eb 100644 --- a/sql/pgtap--0.90.0--0.91.0.sql +++ b/sql/pgtap--0.90.0--0.91.0.sql @@ -1,3 +1,7 @@ +CREATE OR REPLACE FUNCTION pgtap_version() +RETURNS NUMERIC AS 'SELECT 0.91;' +LANGUAGE SQL IMMUTABLE; + CREATE OR REPLACE FUNCTION has_tablespace( NAME, TEXT, TEXT ) RETURNS TEXT AS $$ BEGIN diff --git a/sql/pgtap--0.91.0--0.92.0.sql b/sql/pgtap--0.91.0--0.92.0.sql index 61c0429ff90e..08a3638369f2 100644 --- a/sql/pgtap--0.91.0--0.92.0.sql +++ b/sql/pgtap--0.91.0--0.92.0.sql @@ -1,3 +1,7 @@ +CREATE OR REPLACE FUNCTION pgtap_version() +RETURNS NUMERIC AS 'SELECT 0.92;' +LANGUAGE SQL IMMUTABLE; + CREATE OR REPLACE FUNCTION _cexists ( NAME, NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql index 4b4aad3bbf4e..832f71794141 100644 --- a/sql/pgtap--0.92.0--0.93.0.sql +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -1,3 +1,7 @@ +CREATE OR REPLACE FUNCTION pgtap_version() +RETURNS NUMERIC AS 'SELECT 0.93;' +LANGUAGE SQL IMMUTABLE; + -- _get_schema_owner( schema ) CREATE OR REPLACE FUNCTION _get_schema_owner( NAME ) RETURNS NAME AS $$ From 74277a456bf28b0006af67c76c98faacec2bac09 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 25 Jan 2013 14:23:36 -0800 Subject: [PATCH 0751/1195] Avoid displaying a type schema twice. --- sql/pgtap--0.92.0--0.93.0.sql | 6 +++++- sql/pgtap.sql.in | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql index 832f71794141..ead546a87d89 100644 --- a/sql/pgtap--0.92.0--0.93.0.sql +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -187,7 +187,11 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _get_col_ns_type ( NAME, NAME, NAME ) RETURNS TEXT AS $$ - SELECT quote_ident(tn.nspname) || '.' || pg_catalog.format_type(a.atttypid, a.atttypmod) + -- 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 diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index db0ca193edb1..bd6b67db54bf 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1248,7 +1248,11 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _get_col_ns_type ( NAME, NAME, NAME ) RETURNS TEXT AS $$ - SELECT quote_ident(tn.nspname) || '.' || pg_catalog.format_type(a.atttypid, a.atttypmod) + -- 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 From c24eec824515ffb133a8865a0150d06521857ad2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 25 Jan 2013 14:24:50 -0800 Subject: [PATCH 0752/1195] Add missing doc header. --- doc/pgtap.mmd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 695129d7a35a..8f894d8e80b4 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -3744,6 +3744,8 @@ This function is the inverse of `col_has_default()`. The test passes if the specified column does *not* have a default. It will still fail if the column does not exist, and emit useful diagnostics to let you know. +### `col_type_is()` ### + SELECT col_type_is( :schema, :table, :column, :type_schema, :type, :description ); SELECT col_type_is( :schema, :table, :column, :type_schema, :type ); SELECT col_type_is( :schema, :table, :column, :type, :description ); From 1ab4dfa0c9b8bcecfff2cf87a883b70d143c34c6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 25 Jan 2013 14:37:47 -0800 Subject: [PATCH 0753/1195] Note that one can include the schema for the type in the type arg to col_type_is() and domain_type_is(). --- doc/pgtap.mmd | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 8f894d8e80b4..2d6195011ba7 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -3777,11 +3777,16 @@ This function tests that the specified column is of a particular type. If it fails, it will emit diagnostics naming the actual type. The first argument is the schema name, the second the table name, the third the column name, the fourth the type's schema, the fifth the type, and the sixth is the test -description. Example: If the table schema is omitted, the table and the type -must be visible in the search path. If the test description is omitted, it -will be set to "Column `:schema.:table.:column` should be type -`:schema.:type`". Note that this test will fail if the table or column in -question does not exist. +description. + +If the table schema is omitted, the table must be visible in the search path. +If the type schema is omitted, it must be visible in the search path; +otherwise, the diagnostics will report the schema it's actually in. The schema +can optinally be included in the `:type` argument, e.g., `'contrib.citext`. + +If the test description is omitted, it will be set to "Column +`:schema.:table.:column` should be type `:schema.:type`". Note that this test +will fail if the table or column in question does not exist. The type argument should be formatted as it would be displayed in the view of a table using the `\d` command in `psql`. For example, if you have a numeric @@ -4899,13 +4904,17 @@ omit the test description, it will be set to "Enum `:enum` should have labels `:description` : A short description of the test. -Tests the data type underlying a domain. The first two are arguments are the +Tests the data type underlying a domain. The first two arguments are the schema and name of the domain. The second two are the schema and name of the type that the domain should extend. The fifth argument is a description. If -there is no description, a reasonable default description will be created. The -schema arguments are also optional (though if there is no schema for the -domain then there cannot be one for the type). For the 3- and 4-argument -forms with schemas, cast the schemas to `NAME` to avoid ambiguities. Example: +there is no description, a reasonable default description will be created. + +The schema arguments are also optional. However, if there is no `:schema` +argument, there cannot be a `:type_schema` argument, either, though the +schema can be included in the `type` argument, e.g., `contrib.citext`. + +For the 3- and 4-argument forms with schemas, cast the schemas to `NAME` to +avoid ambiguities. Example: SELECT domain_type_is( 'public'::name, 'us_postal_code', From 4e425daf810ed0c9543c87f3be4a48e232d9f233 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 25 Jan 2013 14:56:29 -0800 Subject: [PATCH 0754/1195] Add tablespace_owner_is()`. Ref #40. --- Changes | 4 +++- doc/pgtap.mmd | 35 ++++++++++++++++++++++++++++++ sql/pgtap--0.92.0--0.93.0.sql | 34 +++++++++++++++++++++++++++++ sql/pgtap.sql.in | 34 +++++++++++++++++++++++++++++ test/expected/ownership.out | 14 +++++++++++- test/sql/ownership.sql | 41 ++++++++++++++++++++++++++++++++++- 6 files changed, 159 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index dd015672d198..2d3a34dba8db 100644 --- a/Changes +++ b/Changes @@ -3,7 +3,9 @@ Revision history for pgTAP 0.93.0 --------------------------- -* Added `schema_owner_is()`. Not sure how I overlooked adding it in v0.92.0. +* Added additional ownership-testing functions: + + `tablespace_owner_is()` + + `schema_owner_is()` * Fixed misselling of "constraint" in constraint test diagnostic output. * Fixed `fk_ok()` so that the table must be visible in the search path when the schema name is not specified. diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 2d6195011ba7..beee6cffe87e 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5254,6 +5254,41 @@ diagnostics will look something like: # have: postgres # want: root +### `tablespace_owner_is ()` ### + + SELECT tablespace_owner_is ( :tablespacename, :user, :description ); + SELECT tablespace_owner_is ( :tablespacename, :user ); + +**Parameters** + +`:tablespacename` +: Name of a tablespace. + +`:user` +: Name of a user. + +`:description` +: A short description of the test. + +Tests the ownership of the tablespace. If the `:description` argument is +omitted, an appropriate description will be created. Examples: + + SELECT tablespace_owner_is( 'myts', 'joe', 'Joe has mytablespace' ); + SELECT tablespace_owner_is( 'pg_default', current_user ); + +In the event that the test fails because the tablespace in question does not +actually exist, you will see an appropriate diagnostic such as: + + # Failed test 16: "Tablespace ssd should be owned by www" + # Tablespace ssd does not exist + +If the test fails because the tablespace is not owned by the specified user, +the diagnostics will look something like: + + # Failed test 17: "Tablespace raid_hds should be owned by root" + # have: postgres + # want: root + ### `relation_owner_is ()` ### SELECT relation_owner_is ( :schema, :relation, :user, :description ); diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql index ead546a87d89..a10edbda3c66 100644 --- a/sql/pgtap--0.92.0--0.93.0.sql +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -330,3 +330,37 @@ RETURNS TEXT AS $$ $2 ); $$ 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index bd6b67db54bf..b181ee06640c 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7942,6 +7942,40 @@ RETURNS TEXT AS $$ ); $$ 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 _assets_are ( text, text[], text[], TEXT ) RETURNS TEXT AS $$ SELECT _areni( diff --git a/test/expected/ownership.out b/test/expected/ownership.out index 2c772026e9b4..fd94c5cb8d2a 100644 --- a/test/expected/ownership.out +++ b/test/expected/ownership.out @@ -1,5 +1,5 @@ \unset ECHO -1..240 +1..252 ok 1 - db_owner_is(db, user, desc) should pass ok 2 - db_owner_is(db, user, desc) should have the proper description ok 3 - db_owner_is(db, user, desc) should have the proper diagnostics @@ -240,3 +240,15 @@ ok 237 - function_owner_is(sch, function, args[integer], non-user, desc) should ok 238 - function_owner_is(function, args[integer], non-user, desc) should fail ok 239 - function_owner_is(function, args[integer], non-user, desc) should have the proper description ok 240 - function_owner_is(function, args[integer], non-user, desc) should have the proper diagnostics +ok 241 - tablespace_owner_is(tablespace, user, desc) should pass +ok 242 - tablespace_owner_is(tablespace, user, desc) should have the proper description +ok 243 - tablespace_owner_is(tablespace, user, desc) should have the proper diagnostics +ok 244 - tablespace_owner_is(tablespace, user) should pass +ok 245 - tablespace_owner_is(tablespace, user) should have the proper description +ok 246 - tablespace_owner_is(tablespace, user) should have the proper diagnostics +ok 247 - tablespace_owner_is(non-tablespace, user) should fail +ok 248 - tablespace_owner_is(non-tablespace, user) should have the proper description +ok 249 - tablespace_owner_is(non-tablespace, user) should have the proper diagnostics +ok 250 - tablespace_owner_is(tablespace, non-user) should fail +ok 251 - tablespace_owner_is(tablespace, non-user) should have the proper description +ok 252 - tablespace_owner_is(tablespace, non-user) should have the proper diagnostics diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 3a2fbc150bcb..fbc9e6b50447 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(240); +SELECT plan(252); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -29,6 +29,7 @@ CREATE FUNCTION public.somefunction(int) RETURNS VOID LANGUAGE SQL AS ''; RESET client_min_messages; /****************************************************************************/ +-- Test db_owner_is(). SELECT * FROM check_test( db_owner_is(current_database(), current_user, 'mumble'), true, @@ -63,6 +64,7 @@ SELECT * FROM check_test( ); /****************************************************************************/ +-- Test schema_owner_is(). SELECT * FROM check_test( schema_owner_is(current_schema(), _get_schema_owner(current_schema()), 'mumble'), true, @@ -786,6 +788,43 @@ SELECT * FROM check_test( want: no one' ); +/****************************************************************************/ +-- Test tablespace_owner_is(). + +SELECT * FROM check_test( + tablespace_owner_is('pg_default', _get_tablespace_owner('pg_default'), 'mumble'), + true, + 'tablespace_owner_is(tablespace, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + tablespace_owner_is('pg_default', _get_tablespace_owner('pg_default')), + true, + 'tablespace_owner_is(tablespace, user)', + 'Tablespace ' || quote_ident('pg_default') || ' should be owned by ' || _get_tablespace_owner('pg_default'), + '' +); + +SELECT * FROM check_test( + tablespace_owner_is('__not__' || 'pg_default', _get_tablespace_owner('pg_default'), 'mumble'), + false, + 'tablespace_owner_is(non-tablespace, user)', + 'mumble', + ' Tablespace __not__' || 'pg_default' || ' does not exist' +); + +SELECT * FROM check_test( + tablespace_owner_is('pg_default', '__not__' || _get_tablespace_owner('pg_default'), 'mumble'), + false, + 'tablespace_owner_is(tablespace, non-user)', + 'mumble', + ' have: ' || _get_tablespace_owner('pg_default') || ' + want: __not__' || _get_tablespace_owner('pg_default') +); + + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From e2b34682e1f6c5967b35b97ebb5a2fb33f4d694d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 25 Jan 2013 15:08:30 -0800 Subject: [PATCH 0755/1195] Make sure tables are visible when checking indexes. Also try to use parameters in the order in which they are passed. --- Changes | 4 ++-- sql/pgtap--0.92.0--0.93.0.sql | 27 +++++++++++++++++++++++++++ sql/pgtap.sql.in | 5 +++-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 2d3a34dba8db..0246020281e8 100644 --- a/Changes +++ b/Changes @@ -9,8 +9,8 @@ Revision history for pgTAP * Fixed misselling of "constraint" in constraint test diagnostic output. * Fixed `fk_ok()` so that the table must be visible in the search path when the schema name is not specified. -* Fixed constraint, trigger, and foreign key-checking functions so that they - only look at tables visible in the search path unles the schema is +* Fixed constraint, trigger, index, and foreign key-checking functions so that + they only look at tables visible in the search path unles the schema is specified. * Fixed `col_type_is()` so that it won't report the column as not existing when the column's data type is not in the search path. diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql index a10edbda3c66..6e7be99d67f6 100644 --- a/sql/pgtap--0.92.0--0.93.0.sql +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -364,3 +364,30 @@ RETURNS TEXT AS $$ 'Tablespace ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) ); $$ 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index b181ee06640c..a0375c3e243f 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2587,9 +2587,9 @@ RETURNS BOOLEAN AS $$ 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 + WHERE n.nspname = $1 + AND ct.relname = $2 AND ci.relname = $3 - AND n.nspname = $1 ); $$ LANGUAGE sql; @@ -2602,6 +2602,7 @@ RETURNS BOOLEAN AS $$ 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; From b838eb05dd3034ffa968d1e1a0dfb04ab2a87217 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 25 Jan 2013 15:09:27 -0800 Subject: [PATCH 0756/1195] Wordsmithing. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 0246020281e8..e640ef601f54 100644 --- a/Changes +++ b/Changes @@ -10,7 +10,7 @@ Revision history for pgTAP * Fixed `fk_ok()` so that the table must be visible in the search path when the schema name is not specified. * Fixed constraint, trigger, index, and foreign key-checking functions so that - they only look at tables visible in the search path unles the schema is + they only don't find tables outside the search path unless the schema is specified. * Fixed `col_type_is()` so that it won't report the column as not existing when the column's data type is not in the search path. From 13fcfa43eac58b6c93f80dc2caf49d521a42b8b5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 25 Jan 2013 15:38:11 -0800 Subject: [PATCH 0757/1195] Add index_owner_is()`. Ref #40. --- Changes | 1 + doc/pgtap.mmd | 47 +++++++++++++++ sql/pgtap--0.92.0--0.93.0.sql | 79 ++++++++++++++++++++++++ sql/pgtap.sql.in | 79 ++++++++++++++++++++++++ test/expected/ownership.out | 38 +++++++++++- test/sql/ownership.sql | 109 +++++++++++++++++++++++++++++++++- 6 files changed, 351 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index e640ef601f54..8929e514be53 100644 --- a/Changes +++ b/Changes @@ -6,6 +6,7 @@ Revision history for pgTAP * Added additional ownership-testing functions: + `tablespace_owner_is()` + `schema_owner_is()` + + `index_owner_is()` * Fixed misselling of "constraint" in constraint test diagnostic output. * Fixed `fk_ok()` so that the table must be visible in the search path when the schema name is not specified. diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index beee6cffe87e..9d68dda29450 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5557,6 +5557,53 @@ diagnostics will look something like: # have: postgres # want: root +### `index_owner_is ()` ### + + SELECT index_owner_is ( :schema, :table, :index, :user, :description ); + SELECT index_owner_is ( :table, :index, :user, :description ); + SELECT index_owner_is ( :schema, :table, :index, :user ); + SELECT index_owner_is ( :table, :index, :user ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the `:table, :index`. + +`:table` +: Name of a table. + +`:table` +: Name of an index on the table. + +`:user` +: Name of a user. + +`:description` +: A short description of the test. + +Tests the ownership of an index. If the `:description` argument is omitted, an +appropriate description will be created. Examples: + + SELECT index_owner_is( + 'public', 'mytable', 'idx_name', 'someuser', + 'Index "idx_name" on mytable should be owned by someuser' + ); + SELECT index_owner_is( 'widgets', 'widgets_pkey', current_user ); + +In the event that the test fails because the index in question does not +actually exist, or the table or schema it's on does not exist or is not +visible, you will see an appropriate diagnostic such as: + + # Failed test 16: "Index idx_foo should be owned by root" + # Index idx_foo on table darfoo not found + +If the test fails because the table is not owned by the specified user, the +diagnostics will look something like: + + # Failed test 17: "Index idx_foo on table bar should be owned by bob" + # have: postgres + # want: bob + ### `function_owner_is ()` ### SELECT function_owner_is ( :schema, :function, :args, :user, :description ); diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql index 6e7be99d67f6..ab87296a53a7 100644 --- a/sql/pgtap--0.92.0--0.93.0.sql +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -391,3 +391,82 @@ RETURNS BOOLEAN AS $$ AND pg_catalog.pg_table_is_visible(ct.oid) ); $$ 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index a0375c3e243f..e1c4344be5cf 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7977,6 +7977,85 @@ RETURNS TEXT AS $$ ); $$ 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; + CREATE OR REPLACE FUNCTION _assets_are ( text, text[], text[], TEXT ) RETURNS TEXT AS $$ SELECT _areni( diff --git a/test/expected/ownership.out b/test/expected/ownership.out index fd94c5cb8d2a..9eb12a782c9d 100644 --- a/test/expected/ownership.out +++ b/test/expected/ownership.out @@ -1,5 +1,5 @@ \unset ECHO -1..252 +1..288 ok 1 - db_owner_is(db, user, desc) should pass ok 2 - db_owner_is(db, user, desc) should have the proper description ok 3 - db_owner_is(db, user, desc) should have the proper diagnostics @@ -252,3 +252,39 @@ ok 249 - tablespace_owner_is(non-tablespace, user) should have the proper diagno ok 250 - tablespace_owner_is(tablespace, non-user) should fail ok 251 - tablespace_owner_is(tablespace, non-user) should have the proper description ok 252 - tablespace_owner_is(tablespace, non-user) should have the proper diagnostics +ok 253 - index_owner_is(schema, table, index, user, desc) should pass +ok 254 - index_owner_is(schema, table, index, user, desc) should have the proper description +ok 255 - index_owner_is(schema, table, index, user, desc) should have the proper diagnostics +ok 256 - index_owner_is(schema, table, index, user) should pass +ok 257 - index_owner_is(schema, table, index, user) should have the proper description +ok 258 - index_owner_is(schema, table, index, user) should have the proper diagnostics +ok 259 - index_owner_is(schema, table, non-index, user, desc) should fail +ok 260 - index_owner_is(schema, table, non-index, user, desc) should have the proper description +ok 261 - index_owner_is(schema, table, non-index, user, desc) should have the proper diagnostics +ok 262 - index_owner_is(schema, non-table, index, user, desc) should fail +ok 263 - index_owner_is(schema, non-table, index, user, desc) should have the proper description +ok 264 - index_owner_is(schema, non-table, index, user, desc) should have the proper diagnostics +ok 265 - index_owner_is(non-schema, table, index, user, desc) should fail +ok 266 - index_owner_is(non-schema, table, index, user, desc) should have the proper description +ok 267 - index_owner_is(non-schema, table, index, user, desc) should have the proper diagnostics +ok 268 - index_owner_is(schema, table, index, non-user, desc) should fail +ok 269 - index_owner_is(schema, table, index, non-user, desc) should have the proper description +ok 270 - index_owner_is(schema, table, index, non-user, desc) should have the proper diagnostics +ok 271 - index_owner_is(invisible-table, index, user, desc) should fail +ok 272 - index_owner_is(invisible-table, index, user, desc) should have the proper description +ok 273 - index_owner_is(invisible-table, index, user, desc) should have the proper diagnostics +ok 274 - index_owner_is(table, index, user, desc) should pass +ok 275 - index_owner_is(table, index, user, desc) should have the proper description +ok 276 - index_owner_is(table, index, user, desc) should have the proper diagnostics +ok 277 - index_owner_is(table, index, user) should pass +ok 278 - index_owner_is(table, index, user) should have the proper description +ok 279 - index_owner_is(table, index, user) should have the proper diagnostics +ok 280 - index_owner_is(non-table, index, user) should fail +ok 281 - index_owner_is(non-table, index, user) should have the proper description +ok 282 - index_owner_is(non-table, index, user) should have the proper diagnostics +ok 283 - index_owner_is(table, non-index, user) should fail +ok 284 - index_owner_is(table, non-index, user) should have the proper description +ok 285 - index_owner_is(table, non-index, user) should have the proper diagnostics +ok 286 - index_owner_is(table, index, non-user) should fail +ok 287 - index_owner_is(table, index, non-user) should have the proper description +ok 288 - index_owner_is(table, index, non-user) should have the proper diagnostics diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index fbc9e6b50447..2f917f7b0ead 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(252); +SELECT plan(288); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -13,6 +13,8 @@ CREATE TABLE public.sometab( "myInt" NUMERIC(8) ); +CREATE INDEX idx_hey ON public.sometab(numb); + CREATE VIEW public.someview AS SELECT * FROM public.sometab; CREATE TYPE public.sometype AS ( @@ -23,6 +25,12 @@ CREATE TYPE public.sometype AS ( CREATE SEQUENCE public.someseq; CREATE SCHEMA someschema; +CREATE TABLE someschema.anothertab( + id INT PRIMARY KEY, + name TEXT DEFAULT '' +); + +CREATE INDEX idx_name ON someschema.anothertab(name); CREATE FUNCTION public.somefunction(int) RETURNS VOID LANGUAGE SQL AS ''; @@ -824,6 +832,105 @@ SELECT * FROM check_test( want: __not__' || _get_tablespace_owner('pg_default') ); +/****************************************************************************/ +-- Test index_owner_is(). +SELECT * FROM check_test( + index_owner_is('someschema', 'anothertab', 'idx_name', current_user, 'mumble'), + true, + 'index_owner_is(schema, table, index, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + index_owner_is('someschema', 'anothertab', 'idx_name', current_user), + true, + 'index_owner_is(schema, table, index, user)', + 'Index idx_name ON someschema.anothertab should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + index_owner_is('someschema', 'anothertab', 'idx_foo', current_user, 'mumble'), + false, + 'index_owner_is(schema, table, non-index, user, desc)', + 'mumble', + ' Index idx_foo ON someschema.anothertab not found' +); + +SELECT * FROM check_test( + index_owner_is('someschema', 'nonesuch', 'idx_name', current_user, 'mumble'), + false, + 'index_owner_is(schema, non-table, index, user, desc)', + 'mumble', + ' Index idx_name ON someschema.nonesuch not found' +); + +SELECT * FROM check_test( + index_owner_is('nonesuch', 'anothertab', 'idx_name', current_user, 'mumble'), + false, + 'index_owner_is(non-schema, table, index, user, desc)', + 'mumble', + ' Index idx_name ON nonesuch.anothertab not found' +); + +SELECT * FROM check_test( + index_owner_is('someschema', 'anothertab', 'idx_name', '__noone', 'mumble'), + false, + 'index_owner_is(schema, table, index, non-user, desc)', + 'mumble', + ' have: ' || current_user || ' + want: __noone' +); + +SELECT * FROM check_test( + index_owner_is('anothertab', 'idx_name', current_user, 'mumble'), + false, + 'index_owner_is(invisible-table, index, user, desc)', + 'mumble', + ' Index idx_name ON anothertab not found' +); + +SELECT * FROM check_test( + index_owner_is('sometab', 'idx_hey', current_user, 'mumble'), + true, + 'index_owner_is(table, index, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + index_owner_is('sometab', 'idx_hey', current_user), + true, + 'index_owner_is(table, index, user)', + 'Index idx_hey ON sometab should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + index_owner_is('notab', 'idx_hey', current_user, 'mumble'), + false, + 'index_owner_is(non-table, index, user)', + 'mumble', + ' Index idx_hey ON notab not found' +); + +SELECT * FROM check_test( + index_owner_is('sometab', 'idx_foo', current_user, 'mumble'), + false, + 'index_owner_is(table, non-index, user)', + 'mumble', + ' Index idx_foo ON sometab not found' +); + +SELECT * FROM check_test( + index_owner_is('sometab', 'idx_hey', '__no-one', 'mumble'), + false, + 'index_owner_is(table, index, non-user)', + 'mumble', + ' have: ' || current_user || ' + want: __no-one' +); /****************************************************************************/ -- Finish the tests and clean up. From 6e101fc06b4f79c48c2a0ce9e56b0a890becf635 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 25 Jan 2013 15:44:18 -0800 Subject: [PATCH 0758/1195] Add language_owner_is()`. Ref #40. --- Changes | 1 + doc/pgtap.mmd | 35 +++++++++++++++++++++++++++++++++ sql/pgtap--0.92.0--0.93.0.sql | 34 ++++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 34 ++++++++++++++++++++++++++++++++ test/expected/ownership.out | 14 ++++++++++++- test/sql/ownership.sql | 37 ++++++++++++++++++++++++++++++++++- 6 files changed, 153 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 8929e514be53..b7c92ebfbf6c 100644 --- a/Changes +++ b/Changes @@ -7,6 +7,7 @@ Revision history for pgTAP + `tablespace_owner_is()` + `schema_owner_is()` + `index_owner_is()` + + `language_owner_is()` * Fixed misselling of "constraint" in constraint test diagnostic output. * Fixed `fk_ok()` so that the table must be visible in the search path when the schema name is not specified. diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 9d68dda29450..b396f9395966 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5651,6 +5651,41 @@ diagnostics will look something like: # have: postgres # want: root +### `language_owner_is ()` ### + + SELECT language_owner_is ( :languagename, :user, :description ); + SELECT language_owner_is ( :languagename, :user ); + +**Parameters** + +`:languagename` +: Name of a language. + +`:user` +: Name of a user. + +`:description` +: A short description of the test. + +Tests the ownership of a procedural language. If the `:description` argument +is omitted, an appropriate description will be created. Examples: + + SELECT language_owner_is( 'plpgsql', 'larry', 'Larry should own plpgsql' ); + SELECT language_owner_is( 'plperl', current_user ); + +In the event that the test fails because the language in question does not +actually exist, you will see an appropriate diagnostic such as: + + # Failed test 16: "Language pllolcode should be owned by meow" + # Language pllolcode does not exist + +If the test fails because the language is not owned by the specified user, +the diagnostics will look something like: + + # Failed test 17: "Language plruby should be owned by mats" + # have: postgres + # want: mats + Prvileged Access ---------------- diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql index ab87296a53a7..bc3010c36cfd 100644 --- a/sql/pgtap--0.92.0--0.93.0.sql +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -470,3 +470,37 @@ RETURNS TEXT AS $$ || 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index e1c4344be5cf..044aa79d5c11 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -8056,6 +8056,40 @@ RETURNS TEXT AS $$ ); $$ 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 _assets_are ( text, text[], text[], TEXT ) RETURNS TEXT AS $$ SELECT _areni( diff --git a/test/expected/ownership.out b/test/expected/ownership.out index 9eb12a782c9d..8b58eb76c5d9 100644 --- a/test/expected/ownership.out +++ b/test/expected/ownership.out @@ -1,5 +1,5 @@ \unset ECHO -1..288 +1..300 ok 1 - db_owner_is(db, user, desc) should pass ok 2 - db_owner_is(db, user, desc) should have the proper description ok 3 - db_owner_is(db, user, desc) should have the proper diagnostics @@ -288,3 +288,15 @@ ok 285 - index_owner_is(table, non-index, user) should have the proper diagnosti ok 286 - index_owner_is(table, index, non-user) should fail ok 287 - index_owner_is(table, index, non-user) should have the proper description ok 288 - index_owner_is(table, index, non-user) should have the proper diagnostics +ok 289 - language_owner_is(language, user, desc) should pass +ok 290 - language_owner_is(language, user, desc) should have the proper description +ok 291 - language_owner_is(language, user, desc) should have the proper diagnostics +ok 292 - language_owner_is(language, user) should pass +ok 293 - language_owner_is(language, user) should have the proper description +ok 294 - language_owner_is(language, user) should have the proper diagnostics +ok 295 - language_owner_is(non-language, user) should fail +ok 296 - language_owner_is(non-language, user) should have the proper description +ok 297 - language_owner_is(non-language, user) should have the proper diagnostics +ok 298 - language_owner_is(language, non-user) should fail +ok 299 - language_owner_is(language, non-user) should have the proper description +ok 300 - language_owner_is(language, non-user) should have the proper diagnostics diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 2f917f7b0ead..3e72a50d8f14 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(288); +SELECT plan(300); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -932,6 +932,41 @@ SELECT * FROM check_test( want: __no-one' ); +/****************************************************************************/ +-- Test language_owner_is(). +SELECT * FROM check_test( + language_owner_is('plpgsql', _get_language_owner('plpgsql'), 'mumble'), + true, + 'language_owner_is(language, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + language_owner_is('plpgsql', _get_language_owner('plpgsql')), + true, + 'language_owner_is(language, user)', + 'Language ' || quote_ident('plpgsql') || ' should be owned by ' || _get_language_owner('plpgsql'), + '' +); + +SELECT * FROM check_test( + language_owner_is('__not__' || 'plpgsql', _get_language_owner('plpgsql'), 'mumble'), + false, + 'language_owner_is(non-language, user)', + 'mumble', + ' Language __not__' || 'plpgsql' || ' does not exist' +); + +SELECT * FROM check_test( + language_owner_is('plpgsql', '__not__' || _get_language_owner('plpgsql'), 'mumble'), + false, + 'language_owner_is(language, non-user)', + 'mumble', + ' have: ' || _get_language_owner('plpgsql') || ' + want: __not__' || _get_language_owner('plpgsql') +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 0048f4a956b5473a9c6bc3d3260c1d687e46e215 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 25 Jan 2013 15:47:07 -0800 Subject: [PATCH 0759/1195] Concatenate less. --- test/sql/ownership.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 3e72a50d8f14..0056a104e890 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -951,11 +951,11 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - language_owner_is('__not__' || 'plpgsql', _get_language_owner('plpgsql'), 'mumble'), + language_owner_is('__not__plpgsql', _get_language_owner('plpgsql'), 'mumble'), false, 'language_owner_is(non-language, user)', 'mumble', - ' Language __not__' || 'plpgsql' || ' does not exist' + ' Language __not__plpgsql does not exist' ); SELECT * FROM check_test( From e544477a97e47189bc08906d5e300a4057339b02 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 25 Jan 2013 16:08:24 -0800 Subject: [PATCH 0760/1195] Add opclass_owner_is()`. Ref #40. --- Changes | 1 + doc/pgtap.mmd | 54 ++++++++++++++++++-- sql/pgtap--0.92.0--0.93.0.sql | 71 ++++++++++++++++++++++++++ sql/pgtap.sql.in | 71 ++++++++++++++++++++++++++ test/expected/ownership.out | 29 ++++++++++- test/sql/ownership.sql | 95 ++++++++++++++++++++++++++++++++++- 6 files changed, 314 insertions(+), 7 deletions(-) diff --git a/Changes b/Changes index b7c92ebfbf6c..484380cb625b 100644 --- a/Changes +++ b/Changes @@ -8,6 +8,7 @@ Revision history for pgTAP + `schema_owner_is()` + `index_owner_is()` + `language_owner_is()` + + `opclass_owner_is()` * Fixed misselling of "constraint" in constraint test diagnostic output. * Fixed `fk_ok()` so that the table must be visible in the search path when the schema name is not specified. diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index b396f9395966..bbb7d6635893 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5543,15 +5543,15 @@ omitted, an appropriate description will be created. Examples: ); SELECT foreign_table_owner_is( 'widgets', current_user ); -In the event that the test fails because the table in question does not -actually exist or is not visible, you will see an appropriate diagnostic such -as: +In the event that the test fails because the foreign table in question does +not actually exist or is not visible, you will see an appropriate diagnostic +such as: # Failed test 16: "Foreign table foo should be owned by www" # Foreign table foo does not exist -If the test fails because the table is not owned by the specified user, the -diagnostics will look something like: +If the test fails because the foreign table is not owned by the specified +user, the diagnostics will look something like: # Failed test 17: "Foreign table bar should be owned by root" # have: postgres @@ -5686,6 +5686,50 @@ the diagnostics will look something like: # have: postgres # want: mats +## `opclass_owner_is ()` ### + + SELECT opclass_owner_is ( :schema, :opclass, :user, :description ); + SELECT opclass_owner_is ( :opclass, :user, :description ); + SELECT opclass_owner_is ( :schema, :opclass, :user ); + SELECT opclass_owner_is ( :opclass, :user ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the `:opclass`. + +`:opclass` +: Name of an operator class. + +`:user` +: Name of a user. + +`:description` +: A short description of the test. + +Tests the ownership of an operator class. If the `:description` argument is +omitted, an appropriate description will be created. Examples: + + SELECT opclass_owner_is( + 'pg_catalog', 'int4_ops', 'postgres', + 'Operator class int4_ops should be owned by postgres' + ); + SELECT opclass_owner_is( 'my_ops', current_user ); + +In the event that the test fails because the operator class in question does +not actually exist or is not visible, you will see an appropriate diagnostic +such as: + + # Failed test 16: "operator class foo should be owned by www" + # operator class foo does not exist + +If the test fails because the operator class is not owned by the specified +user, the diagnostics will look something like: + + # Failed test 17: "operator class bar should be owned by root" + # have: postgres + # want: root + Prvileged Access ---------------- diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql index bc3010c36cfd..369bce307ec5 100644 --- a/sql/pgtap--0.92.0--0.93.0.sql +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -504,3 +504,74 @@ RETURNS TEXT AS $$ '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_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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 044aa79d5c11..e0f77d6df4f3 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -8090,6 +8090,77 @@ RETURNS TEXT AS $$ ); $$ 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_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 _assets_are ( text, text[], text[], TEXT ) RETURNS TEXT AS $$ SELECT _areni( diff --git a/test/expected/ownership.out b/test/expected/ownership.out index 8b58eb76c5d9..cd4da3c4ef28 100644 --- a/test/expected/ownership.out +++ b/test/expected/ownership.out @@ -1,5 +1,5 @@ \unset ECHO -1..300 +1..327 ok 1 - db_owner_is(db, user, desc) should pass ok 2 - db_owner_is(db, user, desc) should have the proper description ok 3 - db_owner_is(db, user, desc) should have the proper diagnostics @@ -300,3 +300,30 @@ ok 297 - language_owner_is(non-language, user) should have the proper diagnostic ok 298 - language_owner_is(language, non-user) should fail ok 299 - language_owner_is(language, non-user) should have the proper description ok 300 - language_owner_is(language, non-user) should have the proper diagnostics +ok 301 - opclass_owner_is(schema, opclass, user, desc) should pass +ok 302 - opclass_owner_is(schema, opclass, user, desc) should have the proper description +ok 303 - opclass_owner_is(schema, opclass, user, desc) should have the proper diagnostics +ok 304 - opclass_owner_is(schema, opclass, user) should pass +ok 305 - opclass_owner_is(schema, opclass, user) should have the proper description +ok 306 - opclass_owner_is(schema, opclass, user) should have the proper diagnostics +ok 307 - opclass_owner_is(non-schema, opclass, user, desc) should fail +ok 308 - opclass_owner_is(non-schema, opclass, user, desc) should have the proper description +ok 309 - opclass_owner_is(non-schema, opclass, user, desc) should have the proper diagnostics +ok 310 - opclass_owner_is(schema, not-opclass, user, desc) should fail +ok 311 - opclass_owner_is(schema, not-opclass, user, desc) should have the proper description +ok 312 - opclass_owner_is(schema, not-opclass, user, desc) should have the proper diagnostics +ok 313 - opclass_owner_is(schema, opclass, non-user, desc) should fail +ok 314 - opclass_owner_is(schema, opclass, non-user, desc) should have the proper description +ok 315 - opclass_owner_is(schema, opclass, non-user, desc) should have the proper diagnostics +ok 316 - opclass_owner_is(opclass, user, desc) should pass +ok 317 - opclass_owner_is(opclass, user, desc) should have the proper description +ok 318 - opclass_owner_is(opclass, user, desc) should have the proper diagnostics +ok 319 - opclass_owner_is(opclass, user) should pass +ok 320 - opclass_owner_is(opclass, user) should have the proper description +ok 321 - opclass_owner_is(opclass, user) should have the proper diagnostics +ok 322 - opclass_owner_is(non-opclass, user, desc) should fail +ok 323 - opclass_owner_is(non-opclass, user, desc) should have the proper description +ok 324 - opclass_owner_is(non-opclass, user, desc) should have the proper diagnostics +ok 325 - opclass_owner_is(opclass, non-user, desc) should fail +ok 326 - opclass_owner_is(opclass, non-user, desc) should have the proper description +ok 327 - opclass_owner_is(opclass, non-user, desc) should have the proper diagnostics diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 0056a104e890..1a5731a71bda 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(300); +SELECT plan(327); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -967,6 +967,99 @@ SELECT * FROM check_test( want: __not__' || _get_language_owner('plpgsql') ); +/****************************************************************************/ +-- Test opclass_owner_is(). +SELECT * FROM check_test( + opclass_owner_is( + 'pg_catalog', 'int4_ops', + _get_opclass_owner('pg_catalog', 'int4_ops'), + 'mumble' + ), + true, + 'opclass_owner_is(schema, opclass, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + opclass_owner_is( + 'pg_catalog', 'int4_ops', + _get_opclass_owner('pg_catalog', 'int4_ops') + ), + true, + 'opclass_owner_is(schema, opclass, user)', + 'Operator class pg_catalog.int4_ops should be owned by ' || _get_opclass_owner('pg_catalog', 'int4_ops'), + '' +); + +SELECT * FROM check_test( + opclass_owner_is( + 'not_pg_catalog', 'int4_ops', + _get_opclass_owner('pg_catalog', 'int4_ops'), + 'mumble' + ), + false, + 'opclass_owner_is(non-schema, opclass, user, desc)', + 'mumble', + ' Operator class not_pg_catalog.int4_ops not found' +); + +SELECT * FROM check_test( + opclass_owner_is( + 'pg_catalog', 'int4_nots', + _get_opclass_owner('pg_catalog', 'int4_ops'), + 'mumble' + ), + false, + 'opclass_owner_is(schema, not-opclass, user, desc)', + 'mumble', + ' Operator class pg_catalog.int4_nots not found' +); + +SELECT * FROM check_test( + opclass_owner_is( + 'pg_catalog', 'int4_ops', '__no-one', 'mumble' + ), + false, + 'opclass_owner_is(schema, opclass, non-user, desc)', + 'mumble', + ' have: ' || _get_opclass_owner('pg_catalog', 'int4_ops') || ' + want: __no-one' +); + +SELECT * FROM check_test( + opclass_owner_is('int4_ops', _get_opclass_owner('int4_ops'), 'mumble'), + true, + 'opclass_owner_is(opclass, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + opclass_owner_is('int4_ops', _get_opclass_owner('int4_ops')), + true, + 'opclass_owner_is(opclass, user)', + 'Operator class int4_ops should be owned by ' || _get_opclass_owner('int4_ops'), + '' +); + +SELECT * FROM check_test( + opclass_owner_is('int4_nots', _get_opclass_owner('int4_ops'), 'mumble'), + false, + 'opclass_owner_is(non-opclass, user, desc)', + 'mumble', + ' Operator class int4_nots not found' +); + +SELECT * FROM check_test( + opclass_owner_is('int4_ops', '__no-one', 'mumble'), + false, + 'opclass_owner_is(opclass, non-user, desc)', + 'mumble', + ' have: ' || _get_opclass_owner('int4_ops') || ' + want: __no-one' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 9e9fa8fe78dfea8340536ab3bca12d06c62085aa Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 25 Jan 2013 16:12:42 -0800 Subject: [PATCH 0761/1195] Make sure opclasses are visible. --- Changes | 8 +++--- sql/pgtap--0.92.0--0.93.0.sql | 48 ++++++++++++++++++++++++++++++++++- sql/pgtap.sql.in | 22 +++++++++++----- 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/Changes b/Changes index 484380cb625b..82cab487a8c1 100644 --- a/Changes +++ b/Changes @@ -10,11 +10,9 @@ Revision history for pgTAP + `language_owner_is()` + `opclass_owner_is()` * Fixed misselling of "constraint" in constraint test diagnostic output. -* Fixed `fk_ok()` so that the table must be visible in the search path when - the schema name is not specified. -* Fixed constraint, trigger, index, and foreign key-checking functions so that - they only don't find tables outside the search path unless the schema is - specified. +* Fixed constraint, trigger, index, opclaass, and foreign key test functions + so that they only don't find objects outside the search path unless the + schema is specified. * Fixed `col_type_is()` so that it won't report the column as not existing when the column's data type is not in the search path. * Removed `format_type()` and switched to the core PostgreSQL type formatting diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql index 369bce307ec5..457460e5775b 100644 --- a/sql/pgtap--0.92.0--0.93.0.sql +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -519,7 +519,7 @@ RETURNS NAME AS $$ SELECT pg_catalog.pg_get_userbyid(opcowner) FROM pg_catalog.pg_opclass WHERE opcname = $1 - AND pg_opclass_is_visible(oid); + AND pg_catalog.pg_opclass_is_visible(oid); $$ LANGUAGE SQL; -- opclass_owner_is( schema, opclass, user, description ) @@ -575,3 +575,49 @@ RETURNS TEXT AS $$ 'Operator class ' || quote_ident($1) || ' should be owned by ' || quote_ident($2) ); $$ 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( 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( 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 exist' ); +$$ LANGUAGE SQL; + diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index e0f77d6df4f3..602912368773 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -4596,11 +4596,21 @@ RETURNS BOOLEAN AS $$ SELECT TRUE FROM pg_catalog.pg_opclass oc JOIN pg_catalog.pg_namespace n ON oc.opcnamespace = n.oid - WHERE n.nspname = COALESCE($1, n.nspname) + 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 $$ @@ -4616,13 +4626,13 @@ $$ LANGUAGE SQL; -- has_opclass( name, description ) CREATE OR REPLACE FUNCTION has_opclass( NAME, TEXT ) RETURNS TEXT AS $$ - SELECT ok( _opc_exists( NULL, $1 ), $2) + 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( NULL, $1 ), 'Operator class ' || quote_ident($1) || ' should exist' ); + SELECT ok( _opc_exists( $1 ), 'Operator class ' || quote_ident($1) || ' should exist' ); $$ LANGUAGE SQL; -- hasnt_opclass( schema, name, description ) @@ -4640,13 +4650,13 @@ $$ LANGUAGE SQL; -- hasnt_opclass( name, description ) CREATE OR REPLACE FUNCTION hasnt_opclass( NAME, TEXT ) RETURNS TEXT AS $$ - SELECT ok( NOT _opc_exists( NULL, $1 ), $2) + 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( NULL, $1 ), 'Operator class ' || quote_ident($1) || ' should exist' ); + SELECT ok( NOT _opc_exists( $1 ), 'Operator class ' || quote_ident($1) || ' should exist' ); $$ LANGUAGE SQL; -- opclasses_are( schema, opclasses[], description ) @@ -8104,7 +8114,7 @@ RETURNS NAME AS $$ SELECT pg_catalog.pg_get_userbyid(opcowner) FROM pg_catalog.pg_opclass WHERE opcname = $1 - AND pg_opclass_is_visible(oid); + AND pg_catalog.pg_opclass_is_visible(oid); $$ LANGUAGE SQL; -- opclass_owner_is( schema, opclass, user, description ) From 319fb34b91ff7c449924c95f4c433ded34d989ce Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 25 Jan 2013 16:33:15 -0800 Subject: [PATCH 0762/1195] Add type_owner_is()`. Ref #40. --- Changes | 1 + doc/pgtap.mmd | 44 +++++++++++++++++ sql/pgtap--0.92.0--0.93.0.sql | 68 ++++++++++++++++++++++++++ sql/pgtap.sql.in | 69 +++++++++++++++++++++++++++ test/expected/ownership.out | 32 ++++++++++++- test/sql/ownership.sql | 90 ++++++++++++++++++++++++++++++++++- 6 files changed, 302 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 82cab487a8c1..99b608d12baf 100644 --- a/Changes +++ b/Changes @@ -9,6 +9,7 @@ Revision history for pgTAP + `index_owner_is()` + `language_owner_is()` + `opclass_owner_is()` + + `type_owner_is()` * Fixed misselling of "constraint" in constraint test diagnostic output. * Fixed constraint, trigger, index, opclaass, and foreign key test functions so that they only don't find objects outside the search path unless the diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index bbb7d6635893..d42bccfb1953 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5730,6 +5730,50 @@ user, the diagnostics will look something like: # have: postgres # want: root +## `type_owner_is ()` ### + + SELECT type_owner_is ( :schema, :type, :user, :description ); + SELECT type_owner_is ( :type, :user, :description ); + SELECT type_owner_is ( :schema, :type, :user ); + SELECT type_owner_is ( :type, :user ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the `:type`. + +`:type` +: Name of a type. + +`:user` +: Name of a user. + +`:description` +: A short description of the test. + +Tests the ownership of a data type. If the `:description` argument is omitted, +an appropriate description will be created. Examples: + + SELECT type_owner_is( + 'pg_catalog', 'int4', 'postgres', + 'type int4 should be owned by postgres' + ); + SELECT type_owner_is( 'us_postal_code', current_user ); + +In the event that the test fails because the type in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "type uk_postal_code should be owned by www" + # type uk_postal_code does not exist + +If the test fails because the type is not owned by the specified +user, the diagnostics will look something like: + + # Failed test 17: "type us_postal_code should be owned by root" + # have: postgres + # want: root + Prvileged Access ---------------- diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql index 457460e5775b..a9cf1b9b6b07 100644 --- a/sql/pgtap--0.92.0--0.93.0.sql +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -621,3 +621,71 @@ RETURNS TEXT AS $$ SELECT ok( NOT _opc_exists( $1 ), 'Operator class ' || quote_ident($1) || ' should exist' ); $$ 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 602912368773..7150f8f363f3 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -8171,6 +8171,75 @@ RETURNS TEXT AS $$ ); $$ 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( diff --git a/test/expected/ownership.out b/test/expected/ownership.out index cd4da3c4ef28..ecef7aec473e 100644 --- a/test/expected/ownership.out +++ b/test/expected/ownership.out @@ -1,5 +1,5 @@ \unset ECHO -1..327 +1..357 ok 1 - db_owner_is(db, user, desc) should pass ok 2 - db_owner_is(db, user, desc) should have the proper description ok 3 - db_owner_is(db, user, desc) should have the proper diagnostics @@ -327,3 +327,33 @@ ok 324 - opclass_owner_is(non-opclass, user, desc) should have the proper diagno ok 325 - opclass_owner_is(opclass, non-user, desc) should fail ok 326 - opclass_owner_is(opclass, non-user, desc) should have the proper description ok 327 - opclass_owner_is(opclass, non-user, desc) should have the proper diagnostics +ok 328 - type_owner_is(schema, type, user, desc) should pass +ok 329 - type_owner_is(schema, type, user, desc) should have the proper description +ok 330 - type_owner_is(schema, type, user, desc) should have the proper diagnostics +ok 331 - type_owner_is(schema, type, user) should pass +ok 332 - type_owner_is(schema, type, user) should have the proper description +ok 333 - type_owner_is(schema, type, user) should have the proper diagnostics +ok 334 - type_owner_is(non-schema, type, user, desc) should fail +ok 335 - type_owner_is(non-schema, type, user, desc) should have the proper description +ok 336 - type_owner_is(non-schema, type, user, desc) should have the proper diagnostics +ok 337 - type_owner_is(schema, non-type, user, desc) should fail +ok 338 - type_owner_is(schema, non-type, user, desc) should have the proper description +ok 339 - type_owner_is(schema, non-type, user, desc) should have the proper diagnostics +ok 340 - type_owner_is(schema, type, non-user, desc) should fail +ok 341 - type_owner_is(schema, type, non-user, desc) should have the proper description +ok 342 - type_owner_is(schema, type, non-user, desc) should have the proper diagnostics +ok 343 - type_owner_is( invisible-type, user, desc) should fail +ok 344 - type_owner_is( invisible-type, user, desc) should have the proper description +ok 345 - type_owner_is( invisible-type, user, desc) should have the proper diagnostics +ok 346 - type_owner_is(type, user, desc) should pass +ok 347 - type_owner_is(type, user, desc) should have the proper description +ok 348 - type_owner_is(type, user, desc) should have the proper diagnostics +ok 349 - type_owner_is(type, user) should pass +ok 350 - type_owner_is(type, user) should have the proper description +ok 351 - type_owner_is(type, user) should have the proper diagnostics +ok 352 - type_owner_is(non-type, user, desc) should fail +ok 353 - type_owner_is(non-type, user, desc) should have the proper description +ok 354 - type_owner_is(non-type, user, desc) should have the proper diagnostics +ok 355 - type_owner_is(type, non-user, desc) should fail +ok 356 - type_owner_is(type, non-user, desc) should have the proper description +ok 357 - type_owner_is(type, non-user, desc) should have the proper diagnostics diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 1a5731a71bda..450b2f980f21 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(327); +SELECT plan(357); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -30,6 +30,10 @@ CREATE TABLE someschema.anothertab( name TEXT DEFAULT '' ); +CREATE DOMAIN someschema.us_postal_code AS TEXT CHECK( + VALUE ~ '^[[:digit:]]{5}$' OR VALUE ~ '^[[:digit:]]{5}-[[:digit:]]{4}$' +); + CREATE INDEX idx_name ON someschema.anothertab(name); CREATE FUNCTION public.somefunction(int) RETURNS VOID LANGUAGE SQL AS ''; @@ -1060,6 +1064,90 @@ SELECT * FROM check_test( want: __no-one' ); +/****************************************************************************/ +-- Test type_owner_is() with a table. +SELECT * FROM check_test( + type_owner_is('someschema', 'us_postal_code', current_user, 'mumble'), + true, + 'type_owner_is(schema, type, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + type_owner_is('someschema', 'us_postal_code', current_user), + true, + 'type_owner_is(schema, type, user)', + 'Type someschema.us_postal_code should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + type_owner_is('--nonesuch', 'us_postal_code', current_user, 'mumble'), + false, + 'type_owner_is(non-schema, type, user, desc)', + 'mumble', + ' Type "--nonesuch".us_postal_code not found' +); + +SELECT * FROM check_test( + type_owner_is('someschema', 'uk_postal_code', current_user, 'mumble'), + false, + 'type_owner_is(schema, non-type, user, desc)', + 'mumble', + ' Type someschema.uk_postal_code not found' +); + +SELECT * FROM check_test( + type_owner_is('someschema', 'us_postal_code', '__no-one', 'mumble'), + false, + 'type_owner_is(schema, type, non-user, desc)', + 'mumble', + ' have: ' || current_user || ' + want: __no-one' +); + +SELECT * FROM check_test( + type_owner_is('us_postal_code', current_user, 'mumble'), + false, + 'type_owner_is( invisible-type, user, desc)', + 'mumble', + ' Type us_postal_code not found' +); + +SELECT * FROM check_test( + type_owner_is('sometype', current_user, 'mumble'), + true, + 'type_owner_is(type, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + type_owner_is('sometype', current_user), + true, + 'type_owner_is(type, user)', + 'Type sometype should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + type_owner_is('__notype', current_user, 'mumble'), + false, + 'type_owner_is(non-type, user, desc)', + 'mumble', + ' Type __notype not found' +); + +SELECT * FROM check_test( + type_owner_is('sometype', '__no-one', 'mumble'), + false, + 'type_owner_is(type, non-user, desc)', + 'mumble', + ' have: ' || current_user || ' + want: __no-one' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 819db75fdbd7d95cb8ee321597edd2decb14aa20 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 25 Jan 2013 16:39:57 -0800 Subject: [PATCH 0763/1195] Add missing doc header. --- doc/pgtap.mmd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index d42bccfb1953..cf9151753a7f 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -3292,6 +3292,8 @@ But this will: This function is the inverse of `has_cast()`: the test passes if the specified cast does *not* exist. +### `has_operator()` ### + SELECT has_operator( :left_type, :schema, :name, :right_type, :return_type, :description ); SELECT has_operator( :left_type, :schema, :name, :right_type, :return_type ); SELECT has_operator( :left_type, :name, :right_type, :return_type, :description ); From a2d10f557a910f86994d087147a1ea9e455a5521 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 28 Jan 2013 11:13:32 -0800 Subject: [PATCH 0764/1195] Update 8.4 patch. --- compat/install-8.4.patch | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compat/install-8.4.patch b/compat/install-8.4.patch index 4e2a8d09eb1c..94f3c7243416 100644 --- a/compat/install-8.4.patch +++ b/compat/install-8.4.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql.saf 2013-01-15 12:01:39.000000000 -0800 +++ sql/pgtap.sql 2013-01-15 12:01:55.000000000 -0800 -@@ -7172,7 +7172,6 @@ +@@ -7180,7 +7180,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -8,7 +8,7 @@ EXCEPT SELECT $3[i] FROM generate_series(1, array_upper($3, 1)) s(i) -@@ -7187,7 +7186,6 @@ +@@ -7195,7 +7194,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -16,7 +16,7 @@ ), $4 ); -@@ -7211,7 +7209,6 @@ +@@ -7219,7 +7217,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = $1 AND n.nspname NOT IN ('pg_catalog', 'information_schema') @@ -24,7 +24,7 @@ EXCEPT SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) -@@ -7225,7 +7222,6 @@ +@@ -7233,7 +7230,6 @@ 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') From 26c3e0ff1d7d7a113b14ae7c26abaa04e674e2cc Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 28 Jan 2013 11:22:02 -0800 Subject: [PATCH 0765/1195] Update 8.3 patch. --- compat/install-8.3.patch | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index f5d3a931e62c..b6618e05cd7f 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -1,5 +1,5 @@ ---- sql/pgtap.sql.saf 2013-01-15 12:09:45.000000000 -0800 -+++ sql/pgtap.sql 2013-01-15 12:10:18.000000000 -0800 +--- sql/pgtap.sql.saf 2013-01-28 11:20:18.000000000 -0800 ++++ sql/pgtap.sql 2013-01-28 11:21:00.000000000 -0800 @@ -9,6 +9,11 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -12,7 +12,7 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT s.a[1]::int * 10000 -@@ -6302,7 +6307,7 @@ +@@ -6306,7 +6311,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -21,7 +21,7 @@ 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' || -@@ -6459,7 +6464,7 @@ +@@ -6463,7 +6468,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -30,7 +30,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6668,13 +6673,7 @@ +@@ -6672,13 +6677,7 @@ $$ LANGUAGE sql; -- collect_tap( tap, tap, tap ) @@ -45,7 +45,7 @@ RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); $$ LANGUAGE sql; -@@ -7146,7 +7145,7 @@ +@@ -7154,7 +7153,7 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; From b3d28da1ad0ed0aa33b74d0b24751d35b70beca4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 28 Jan 2013 11:23:34 -0800 Subject: [PATCH 0766/1195] Update uninstall_pgtap.sql. --- sql/uninstall_pgtap.sql.in | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/sql/uninstall_pgtap.sql.in b/sql/uninstall_pgtap.sql.in index 5282afec52d4..bce051b42760 100644 --- a/sql/uninstall_pgtap.sql.in +++ b/sql/uninstall_pgtap.sql.in @@ -46,6 +46,30 @@ DROP FUNCTION table_privs_are ( NAME, NAME, NAME, NAME[], TEXT ); DROP FUNCTION _table_privs(); DROP FUNCTION _get_table_privs(NAME, TEXT); DROP FUNCTION _assets_are ( text, text[], text[], TEXT ); +DROP FUNCTION type_owner_is ( NAME, NAME ); +DROP FUNCTION type_owner_is ( NAME, NAME, TEXT ); +DROP FUNCTION type_owner_is ( NAME, NAME, NAME ); +DROP FUNCTION type_owner_is ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION _get_type_owner ( NAME ); +DROP FUNCTION _get_type_owner ( NAME, NAME ); +DROP FUNCTION opclass_owner_is( NAME, NAME ); +DROP FUNCTION opclass_owner_is ( NAME, NAME, TEXT ); +DROP FUNCTION opclass_owner_is( NAME, NAME, NAME ); +DROP FUNCTION opclass_owner_is ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION _get_opclass_owner ( NAME ); +DROP FUNCTION _get_opclass_owner ( NAME, NAME ); +DROP FUNCTION language_owner_is ( NAME, NAME ); +DROP FUNCTION language_owner_is ( NAME, NAME, TEXT ); +DROP FUNCTION _get_language_owner( NAME ); +DROP FUNCTION index_owner_is ( NAME, NAME, NAME ); +DROP FUNCTION index_owner_is ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION index_owner_is ( NAME, NAME, NAME, NAME ); +DROP FUNCTION index_owner_is ( NAME, NAME, NAME, NAME, TEXT ); +DROP FUNCTION _get_index_owner( NAME, NAME ); +DROP FUNCTION _get_index_owner( NAME, NAME, NAME ); +DROP FUNCTION tablespace_owner_is ( NAME, NAME ); +DROP FUNCTION tablespace_owner_is ( NAME, NAME, TEXT ); +DROP FUNCTION _get_tablespace_owner( NAME ); DROP FUNCTION function_owner_is( NAME, NAME[], NAME ); DROP FUNCTION function_owner_is ( NAME, NAME[], NAME, TEXT ); DROP FUNCTION function_owner_is( NAME, NAME, NAME[], NAME ); @@ -80,6 +104,9 @@ DROP FUNCTION relation_owner_is ( NAME, NAME, NAME ); DROP FUNCTION relation_owner_is ( NAME, NAME, NAME, TEXT ); DROP FUNCTION _get_rel_owner ( NAME ); DROP FUNCTION _get_rel_owner ( NAME, NAME ); +DROP FUNCTION schema_owner_is ( NAME, NAME ); +DROP FUNCTION schema_owner_is ( NAME, NAME, TEXT ); +DROP FUNCTION _get_schema_owner( NAME ); DROP FUNCTION db_owner_is ( NAME, NAME ); DROP FUNCTION db_owner_is ( NAME, NAME, TEXT ); DROP FUNCTION _get_db_owner( NAME ); @@ -346,6 +373,7 @@ DROP FUNCTION has_opclass( NAME ); DROP FUNCTION has_opclass( NAME, TEXT ); DROP FUNCTION has_opclass( NAME, NAME ); DROP FUNCTION has_opclass( NAME, NAME, TEXT ); +DROP FUNCTION _opc_exists( NAME ); DROP FUNCTION _opc_exists( NAME, NAME ); DROP FUNCTION language_is_trusted( NAME ); DROP FUNCTION language_is_trusted( NAME, TEXT ); @@ -673,8 +701,6 @@ DROP FUNCTION _quote_ident_like(TEXT, TEXT); DROP FUNCTION _get_col_ns_type ( NAME, NAME, NAME ); DROP FUNCTION _get_col_type ( NAME, NAME ); DROP FUNCTION _get_col_type ( NAME, NAME, NAME ); -DROP FUNCTION display_type ( NAME, OID, INTEGER ); -DROP FUNCTION display_type ( OID, INTEGER ); DROP FUNCTION col_is_null ( NAME, NAME ); DROP FUNCTION col_is_null ( NAME, NAME, NAME ); DROP FUNCTION col_is_null ( NAME, NAME, NAME, TEXT ); From 16f557d138d17e80a14f68ea27008e10ecfceec9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 28 Jan 2013 11:45:55 -0800 Subject: [PATCH 0767/1195] Update for 8.2. Notably, language ownership cannot be tested before 8.3. --- compat/install-8.2.patch | 94 ++++++++++++++++++++++++------------ compat/uninstall-8.2.patch | 10 ++-- doc/pgtap.mmd | 20 +++++--- test/sql/ownership.sql | 99 +++++++++++++++++++++++++++----------- 4 files changed, 153 insertions(+), 70 deletions(-) diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 57584fddd08b..b702378d2c8d 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,5 +1,5 @@ ---- sql/pgtap.sql.saf 2013-01-15 13:55:16.000000000 -0800 -+++ sql/pgtap.sql 2013-01-15 13:57:48.000000000 -0800 +--- sql/pgtap.sql.saf 2013-01-28 11:25:36.000000000 -0800 ++++ sql/pgtap.sql 2013-01-28 11:30:46.000000000 -0800 @@ -5,6 +5,59 @@ -- -- http://pgtap.org/ @@ -111,16 +111,7 @@ INTO result; output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( -@@ -1231,7 +1269,7 @@ - SELECT COALESCE(substring( - pg_catalog.format_type($1, $2), - '(("(?!")([^"]|"")+"|[^.]+)([(][^)]+[)])?)$' -- ), '') -+ ) || CASE WHEN $2 IS NULL OR $2 < 0 OR pg_catalog.format_type($1, $2) LIKE '%)' THEN '' ELSE '(' || $2 || ')' END, '') - $$ LANGUAGE SQL; - - CREATE OR REPLACE FUNCTION display_type ( NAME, OID, INTEGER ) -@@ -2324,7 +2362,7 @@ +@@ -2316,7 +2354,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 @@ -129,7 +120,7 @@ p.prolang AS langoid, p.proisstrict AS is_strict, p.proisagg AS is_agg, -@@ -3458,63 +3496,6 @@ +@@ -3452,63 +3490,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -193,7 +184,7 @@ CREATE OR REPLACE FUNCTION _has_role( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( -@@ -5982,13 +5963,13 @@ +@@ -5986,13 +5967,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -209,7 +200,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6212,7 +6193,7 @@ +@@ -6216,7 +6197,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -218,7 +209,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6307,11 +6288,11 @@ +@@ -6311,11 +6292,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -233,7 +224,7 @@ ); END IF; rownum = rownum + 1; -@@ -6326,9 +6307,9 @@ +@@ -6330,9 +6311,9 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Number of columns or their types differ between the queries' || @@ -246,7 +237,7 @@ END ); END; -@@ -6464,7 +6445,7 @@ +@@ -6468,7 +6449,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -255,7 +246,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6478,8 +6459,8 @@ +@@ -6482,8 +6463,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -266,7 +257,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6604,9 +6585,9 @@ +@@ -6608,9 +6589,9 @@ DECLARE typeof regtype := pg_typeof($1); BEGIN @@ -279,7 +270,7 @@ END; $$ LANGUAGE plpgsql; -@@ -6627,7 +6608,7 @@ +@@ -6631,7 +6612,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -288,7 +279,7 @@ END LOOP; -- What extra records do we have? -@@ -6795,7 +6776,7 @@ +@@ -6799,7 +6780,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -297,7 +288,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT -@@ -6813,7 +6794,7 @@ +@@ -6817,7 +6798,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -306,7 +297,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) ), -@@ -6846,7 +6827,7 @@ +@@ -6850,7 +6831,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -315,7 +306,7 @@ 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']) ) -@@ -6865,7 +6846,7 @@ +@@ -6869,7 +6850,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -324,7 +315,7 @@ 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']) ) -@@ -7145,10 +7126,12 @@ +@@ -7153,10 +7134,12 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; @@ -340,7 +331,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7293,7 +7276,7 @@ +@@ -7303,7 +7286,7 @@ CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) RETURNS TEXT AS $$ @@ -349,7 +340,7 @@ $$ LANGUAGE SQL; -- operators_are( schema, operators[], description ) -@@ -7302,7 +7285,7 @@ +@@ -7312,7 +7295,7 @@ SELECT _areni( 'operators', ARRAY( @@ -358,7 +349,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7314,7 +7297,7 @@ +@@ -7324,7 +7307,7 @@ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT @@ -367,7 +358,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7335,7 +7318,7 @@ +@@ -7345,7 +7328,7 @@ SELECT _areni( 'operators', ARRAY( @@ -376,7 +367,7 @@ 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) -@@ -7348,7 +7331,7 @@ +@@ -7358,7 +7341,7 @@ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT @@ -385,3 +376,44 @@ 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) +@@ -8061,40 +8044,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) diff --git a/compat/uninstall-8.2.patch b/compat/uninstall-8.2.patch index 8a0dda280541..1842c2ffea17 100644 --- a/compat/uninstall-8.2.patch +++ b/compat/uninstall-8.2.patch @@ -1,6 +1,6 @@ ---- sql/uninstall_pgtap.sql.saf 2013-01-15 14:38:13.000000000 -0800 -+++ sql/uninstall_pgtap.sql 2013-01-15 14:38:58.000000000 -0800 -@@ -454,10 +454,6 @@ +--- sql/uninstall_pgtap.sql.saf 2013-01-28 11:44:27.000000000 -0800 ++++ sql/uninstall_pgtap.sql 2013-01-28 11:44:33.000000000 -0800 +@@ -482,10 +482,6 @@ DROP FUNCTION has_role( NAME ); DROP FUNCTION has_role( NAME, TEXT ); DROP FUNCTION _has_role( NAME ); @@ -11,7 +11,7 @@ DROP FUNCTION hasnt_enum( NAME ); DROP FUNCTION hasnt_enum( NAME, TEXT ); DROP FUNCTION hasnt_enum( NAME, NAME ); -@@ -785,9 +781,6 @@ +@@ -811,9 +807,6 @@ DROP FUNCTION is (anyelement, anyelement, text); DROP FUNCTION ok ( boolean ); DROP FUNCTION ok ( boolean, text ); @@ -21,7 +21,7 @@ DROP FUNCTION diag ( msg text ); DROP FUNCTION finish (); DROP FUNCTION _finish ( INTEGER, INTEGER, INTEGER); -@@ -810,3 +803,15 @@ +@@ -836,3 +829,15 @@ DROP FUNCTION os_name(); DROP FUNCTION pg_version_num(); DROP FUNCTION pg_version(); diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index cf9151753a7f..f92c058e39f5 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5670,7 +5670,8 @@ diagnostics will look something like: : A short description of the test. Tests the ownership of a procedural language. If the `:description` argument -is omitted, an appropriate description will be created. Examples: +is omitted, an appropriate description will be created. Works on PostgreSQL +8.3 and higher. Examples: SELECT language_owner_is( 'plpgsql', 'larry', 'Larry should own plpgsql' ); SELECT language_owner_is( 'plperl', current_user ); @@ -7277,11 +7278,18 @@ No changes. Everything should just work. 8.2 and Down ------------ -* A patch is applied that removes the `enum_has_labels()`, the - `diag(anyelement)` function and `col_has_default()` cannot be used to test - for columns specified with `DEFAULT NULL` (even though that's the implied - default default). The `has_enums()` function won't work. Also, a number of - assignments casts are added to increase compatibility. The casts are: + +* A patch is applied that removes `enum_has_labels()` and + `language_owner_is()`, since neither are testable before 8.3. + +* the `diag(anyelement)` function and `col_has_default()` cannot be used to + test for columns specified with `DEFAULT NULL` (even though that's the + implied default default). + +* The `has_enums()` function won't work. + +* A number of assignments casts are added to increase compatibility. The casts + are: + `boolean` to `text` + `text[]` to `text` diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 450b2f980f21..215459357455 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -938,38 +938,81 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test language_owner_is(). -SELECT * FROM check_test( - language_owner_is('plpgsql', _get_language_owner('plpgsql'), 'mumble'), - true, - 'language_owner_is(language, user, desc)', - 'mumble', - '' -); +CREATE FUNCTION test_lang() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() >= 80300 THEN + FOR tap IN SELECT * FROM check_test( + language_owner_is('plpgsql', _get_language_owner('plpgsql'), 'mumble'), + true, + 'language_owner_is(language, user, desc)', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - language_owner_is('plpgsql', _get_language_owner('plpgsql')), - true, - 'language_owner_is(language, user)', - 'Language ' || quote_ident('plpgsql') || ' should be owned by ' || _get_language_owner('plpgsql'), - '' -); + FOR tap IN SELECT * FROM check_test( + language_owner_is('plpgsql', _get_language_owner('plpgsql')), + true, + 'language_owner_is(language, user)', + 'Language ' || quote_ident('plpgsql') || ' should be owned by ' || _get_language_owner('plpgsql'), + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - language_owner_is('__not__plpgsql', _get_language_owner('plpgsql'), 'mumble'), - false, - 'language_owner_is(non-language, user)', - 'mumble', - ' Language __not__plpgsql does not exist' -); + FOR tap IN SELECT * FROM check_test( + language_owner_is('__not__plpgsql', _get_language_owner('plpgsql'), 'mumble'), + false, + 'language_owner_is(non-language, user)', + 'mumble', + ' Language __not__plpgsql does not exist' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - language_owner_is('plpgsql', '__not__' || _get_language_owner('plpgsql'), 'mumble'), - false, - 'language_owner_is(language, non-user)', - 'mumble', - ' have: ' || _get_language_owner('plpgsql') || ' + FOR tap IN SELECT * FROM check_test( + language_owner_is('plpgsql', '__not__' || _get_language_owner('plpgsql'), 'mumble'), + false, + 'language_owner_is(language, non-user)', + 'mumble', + ' have: ' || _get_language_owner('plpgsql') || ' want: __not__' || _get_language_owner('plpgsql') -); + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + ELSE + -- Fake it with pass() and fail(). + FOR tap IN SELECT * FROM check_test( + pass('mumble'), + true, + 'language_owner_is(language, user, desc)', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('mumble'), + true, + 'language_owner_is(language, user)', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('mumble'), + false, + 'language_owner_is(non-language, user)', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('mumble'), + false, + 'language_owner_is(language, non-user)', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + END IF; +END; +$$ LANGUAGE PLPGSQL; + +SELECT * FROM test_lang(); /****************************************************************************/ -- Test opclass_owner_is(). From be3982f9e4f090bedfdb9584a346d676193cfbf0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 28 Jan 2013 11:50:36 -0800 Subject: [PATCH 0768/1195] Update patch for 8.1. --- compat/install-8.1.patch | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index 245b6ee98163..e7326bb0c11b 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ ---- sql/pgtap.sql.saf 2013-01-15 14:04:18.000000000 -0800 -+++ sql/pgtap.sql 2013-01-15 14:04:23.000000000 -0800 -@@ -2122,13 +2122,13 @@ +--- sql/pgtap.sql.saf 2013-01-28 11:47:39.000000000 -0800 ++++ sql/pgtap.sql 2013-01-28 11:47:45.000000000 -0800 +@@ -2113,13 +2113,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -18,7 +18,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $6 || ' constraints'; -@@ -2146,13 +2146,13 @@ +@@ -2137,13 +2137,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -36,7 +36,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $5 || ' constraints'; -@@ -5735,7 +5735,7 @@ +@@ -5739,7 +5739,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -45,7 +45,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -5743,8 +5743,8 @@ +@@ -5747,8 +5747,8 @@ -- 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. @@ -56,7 +56,7 @@ END LOOP; END LOOP; RETURN; -@@ -5814,14 +5814,14 @@ +@@ -5818,14 +5818,14 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -73,7 +73,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -5836,15 +5836,15 @@ +@@ -5840,15 +5840,15 @@ IF verbos THEN RETURN NEXT diag_test_name(tests[i]); END IF; -- Run the setup functions. @@ -93,7 +93,7 @@ -- Remember how many failed and then roll back. num_faild := num_faild + num_failed(); -@@ -5859,7 +5859,7 @@ +@@ -5863,7 +5863,7 @@ END LOOP; -- Run the shutdown functions. @@ -102,7 +102,7 @@ -- Raise an exception to rollback any changes. RAISE EXCEPTION '__TAP_ROLLBACK__'; -@@ -5870,8 +5870,8 @@ +@@ -5874,8 +5874,8 @@ END IF; END; -- Finish up. @@ -113,7 +113,7 @@ END LOOP; -- Clean up and return. -@@ -7125,7 +7125,7 @@ +@@ -7133,7 +7133,7 @@ DECLARE rec RECORD; BEGIN From c23007f94150b56eeb12202e0eab5a4b8e45026c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 28 Jan 2013 11:53:56 -0800 Subject: [PATCH 0769/1195] Update 8.3 uninstall patch. --- compat/uninstall-8.3.patch | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compat/uninstall-8.3.patch b/compat/uninstall-8.3.patch index 47dccc0f687d..53284e885ea2 100644 --- a/compat/uninstall-8.3.patch +++ b/compat/uninstall-8.3.patch @@ -1,6 +1,6 @@ ---- sql/uninstall_pgtap.sql.saf 2013-01-15 14:32:03.000000000 -0800 -+++ sql/uninstall_pgtap.sql 2013-01-15 14:33:58.000000000 -0800 -@@ -142,8 +142,7 @@ +--- sql/uninstall_pgtap.sql.saf 2013-01-28 11:52:12.000000000 -0800 ++++ sql/uninstall_pgtap.sql 2013-01-28 11:52:27.000000000 -0800 +@@ -169,8 +169,7 @@ DROP FUNCTION throws_like ( TEXT, TEXT ); DROP FUNCTION throws_like ( TEXT, TEXT, TEXT ); DROP FUNCTION _tlike ( BOOLEAN, TEXT, TEXT, TEXT ); @@ -10,7 +10,7 @@ DROP FUNCTION isnt_empty( TEXT ); DROP FUNCTION isnt_empty( TEXT, TEXT ); DROP FUNCTION is_empty( TEXT ); -@@ -806,6 +805,7 @@ +@@ -832,6 +831,7 @@ DROP FUNCTION _get ( text ); DROP FUNCTION no_plan(); DROP FUNCTION plan( integer ); From ce2b147b1852a614307c9859a1c47d08fd04e70a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 28 Jan 2013 12:01:41 -0800 Subject: [PATCH 0770/1195] Increment to v0.93. --- META.json | 8 ++++---- pgtap-core.control | 2 +- pgtap-schema.control | 2 +- pgtap.control | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/META.json b/META.json index 8219407832f7..e6246e70922e 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "0.92.0", + "version": "0.93.0", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "0.92.0" + "version": "0.93.0" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "0.92.0" + "version": "0.93.0" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "0.92.0" + "version": "0.93.0" } }, "resources": { diff --git a/pgtap-core.control b/pgtap-core.control index ebf84baedd9a..eec4539c3a19 100644 --- a/pgtap-core.control +++ b/pgtap-core.control @@ -1,6 +1,6 @@ # pgTAP Core extension comment = 'Unit testing for PostgreSQL' -default_version = '0.92.0' +default_version = '0.93.0' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true diff --git a/pgtap-schema.control b/pgtap-schema.control index ab146ef61251..e1613a8f8297 100644 --- a/pgtap-schema.control +++ b/pgtap-schema.control @@ -1,6 +1,6 @@ # pgTAP Schema testing extension comment = 'Schema unit testing for PostgreSQL' -default_version = '0.92.0' +default_version = '0.93.0' module_pathname = '$libdir/pgtap' requires = 'pgtap-core' relocatable = true diff --git a/pgtap.control b/pgtap.control index 945c72d81aa2..f480c05f8dc6 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '0.92.0' +default_version = '0.93.0' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true From 9ed21f36c04b76c8115e2320d435dcc725db10ca Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 28 Jan 2013 12:09:35 -0800 Subject: [PATCH 0771/1195] Remove CREATE EXTENSION support for -core and -schema. They were never really supported, anyway. The files are still created for folks to copy around. --- Changes | 5 +++++ Makefile | 2 +- pgtap-core.control | 7 ------- pgtap-schema.control | 7 ------- 4 files changed, 6 insertions(+), 15 deletions(-) delete mode 100644 pgtap-core.control delete mode 100644 pgtap-schema.control diff --git a/Changes b/Changes index 99b608d12baf..9369ac1bd868 100644 --- a/Changes +++ b/Changes @@ -22,6 +22,11 @@ Revision history for pgTAP the schema name if the type is not in the search path. * Added code to the `ALTER EXTENSION` upgrade scripts to update the value returned by `pgtap_version()`. +* Removed `CREATE EXTENSION` support for `pgtap-core` and `pgtap-control` (by + deleting `pgtap-core.control` and `pgtap-schema.control`). There were never + upgrade scripts for them, and that would be difficult to maintain anyway. + They are really designed for copying into another project's distrinution for + inline testing support. 0.92.0 2013-01-16T00:41:07Z --------------------------- diff --git a/Makefile b/Makefile index 1bf99074a622..e33285364603 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ MAINEXT = pgtap -EXTENSION = $(MAINEXT) pgtap-core pgtap-schema +EXTENSION = $(MAINEXT) EXTVERSION = $(shell grep default_version $(MAINEXT).control | \ sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") NUMVERSION = $(shell echo $(EXTVERSION) | sed -e 's/\([[:digit:]]*[.][[:digit:]]*\).*/\1/') diff --git a/pgtap-core.control b/pgtap-core.control deleted file mode 100644 index eec4539c3a19..000000000000 --- a/pgtap-core.control +++ /dev/null @@ -1,7 +0,0 @@ -# pgTAP Core extension -comment = 'Unit testing for PostgreSQL' -default_version = '0.93.0' -module_pathname = '$libdir/pgtap' -requires = 'plpgsql' -relocatable = true -superuser = false diff --git a/pgtap-schema.control b/pgtap-schema.control deleted file mode 100644 index e1613a8f8297..000000000000 --- a/pgtap-schema.control +++ /dev/null @@ -1,7 +0,0 @@ -# pgTAP Schema testing extension -comment = 'Schema unit testing for PostgreSQL' -default_version = '0.93.0' -module_pathname = '$libdir/pgtap' -requires = 'pgtap-core' -relocatable = true -superuser = false From 41e6d05b6558eeebd605bd62a0c43da104b7e41b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 28 Jan 2013 12:11:56 -0800 Subject: [PATCH 0772/1195] Changes tweaks. --- Changes | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index 9369ac1bd868..9adf0b003d83 100644 --- a/Changes +++ b/Changes @@ -12,20 +12,20 @@ Revision history for pgTAP + `type_owner_is()` * Fixed misselling of "constraint" in constraint test diagnostic output. * Fixed constraint, trigger, index, opclaass, and foreign key test functions - so that they only don't find objects outside the search path unless the - schema is specified. + so that they don't find objects outside the search path unless the schema is + specified. * Fixed `col_type_is()` so that it won't report the column as not existing when the column's data type is not in the search path. * Removed `format_type()` and switched to the core PostgreSQL type formatting function so that type names always include the schema name if the schema is not in the search path. You might have to update data type tests to include the schema name if the type is not in the search path. -* Added code to the `ALTER EXTENSION` upgrade scripts to update the value - returned by `pgtap_version()`. +* `ALTER EXTENSION` now properly updates the value returned by + `pgtap_version()`. * Removed `CREATE EXTENSION` support for `pgtap-core` and `pgtap-control` (by deleting `pgtap-core.control` and `pgtap-schema.control`). There were never - upgrade scripts for them, and that would be difficult to maintain anyway. - They are really designed for copying into another project's distrinution for + upgrade scripts for them, and upgrade scripts would be difficult to maintain + anyway. These files are really designed for copying into other projects for inline testing support. 0.92.0 2013-01-16T00:41:07Z From 31f740cbe5d33c37e82c371063529992b5bd7776 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 28 Jan 2013 12:14:21 -0800 Subject: [PATCH 0773/1195] Timestamp v0.93.0. --- Changes | 2 +- contrib/pgtap.spec | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 9adf0b003d83..1213da48248b 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.93.0 +0.93.0 2013-01-28T20:14:58Z --------------------------- * Added additional ownership-testing functions: + `tablespace_owner_is()` diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 6aa2a454345f..255f42589d4f 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 0.91.0 +Version: 0.93.0 Release: 1%{?dist} Group: Applications/Databases License: PostgreSQL @@ -44,6 +44,12 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog +* Tue Jan 15 2013 David E. Wheeler 0.92.0-1 +- Upgraded to pgTAP 0.92.0 + +* Mon Jan 28 2013 David Wheeler 0.93.0 +- Upgraded to pgTAP 0.93.0 + * Tue Aug 23 2011 David Wheeler 0.91.0 - Removed USE_PGXS from Makefile; it has not been supported in some time. - Removed TAPSCHEMA from Makefile; use PGOPTIONS=--search_path=tap with From 469ba042d54d399757575fd583e7d3b5d4424177 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 28 Jan 2013 12:16:53 -0800 Subject: [PATCH 0774/1195] Increment to v0.94.0. --- Changes | 4 ++++ META.json | 2 +- README.md | 2 +- doc/pgtap.mmd | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 1213da48248b..c79d2ad386a0 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,10 @@ Revision history for pgTAP ========================== +0.94.0 +--------------------------- + + 0.93.0 2013-01-28T20:14:58Z --------------------------- * Added additional ownership-testing functions: diff --git a/META.json b/META.json index e6246e70922e..7ad5eea3701d 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "0.93.0", + "version": "0.94.0", "maintainer": [ "David E. Wheeler ", "pgTAP List " diff --git a/README.md b/README.md index a735136fb105..a2ba6e579254 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 0.93.0 +pgTAP 0.94.0 ============ [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index f92c058e39f5..cc4d5e3bf870 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 0.93.0 +pgTAP 0.94.0 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and From a6431c9e2b36d34ab987bd83eb18f9af8c17417d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 28 Jan 2013 12:21:45 -0800 Subject: [PATCH 0775/1195] Remove brokenated function from the 9.2-9.3 upgrade script. --- sql/pgtap--0.92.0--0.93.0.sql | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/sql/pgtap--0.92.0--0.93.0.sql b/sql/pgtap--0.92.0--0.93.0.sql index a9cf1b9b6b07..d4e6d4a0c857 100644 --- a/sql/pgtap--0.92.0--0.93.0.sql +++ b/sql/pgtap--0.92.0--0.93.0.sql @@ -134,18 +134,6 @@ RETURNS BOOLEAN AS $$ ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT display_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 _fkexists ( NAME, NAME[] ) RETURNS BOOLEAN AS $$ SELECT EXISTS( From db8f91e2b645efd1812e7606c29f943d244e8091 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 4 Feb 2013 09:22:29 -0800 Subject: [PATCH 0776/1195] Fix a couple of doc headers, make GitHub URL SSL. --- doc/pgtap.mmd | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index cc4d5e3bf870..4ca0ea42a6ce 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5689,7 +5689,7 @@ the diagnostics will look something like: # have: postgres # want: mats -## `opclass_owner_is ()` ### +### `opclass_owner_is ()` ### SELECT opclass_owner_is ( :schema, :opclass, :user, :description ); SELECT opclass_owner_is ( :opclass, :user, :description ); @@ -5733,7 +5733,7 @@ user, the diagnostics will look something like: # have: postgres # want: root -## `type_owner_is ()` ### +### `type_owner_is ()` ### SELECT type_owner_is ( :schema, :type, :user, :description ); SELECT type_owner_is ( :type, :user, :description ); @@ -7305,7 +7305,7 @@ Public Repository ----------------- The source code for pgTAP is available on -[GitHub](http://github.com/theory/pgtap/tree/). Please feel free to fork and +[GitHub](https://github.com/theory/pgtap/tree/). Please feel free to fork and contribute! Mail List From bd968e80c2a6ddc996f34bf45b3c9a0b04f00c0e Mon Sep 17 00:00:00 2001 From: "Aaron W. Swenson" Date: Sat, 30 Mar 2013 17:02:28 +0000 Subject: [PATCH 0777/1195] Allow make to be called as 'PG_CONFIG="/path/to/pg_config" make' without having to export an environment variable. --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e33285364603..5080f7ca6a22 100644 --- a/Makefile +++ b/Makefile @@ -9,14 +9,15 @@ EXTRA_CLEAN = sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgta DOCS = doc/pgtap.mmd REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --inputdir=test --load-language=plpgsql +ifndef PG_CONFIG PG_CONFIG = pg_config +endif ifdef NO_PGXS top_builddir = ../.. PG_CONFIG := $(top_builddir)/src/bin/pg_config/pg_config else # Run pg_config to get the PGXS Makefiles -PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) endif @@ -145,4 +146,3 @@ html: MultiMarkdown.pl doc/pgtap.mmd > doc/pgtap.html ./tocgen doc/pgtap.html 2> doc/toc.html perl -MPod::Simple::XHTML -E "my \$$p = Pod::Simple::XHTML->new; \$$p->html_header_tags(''); \$$p->strip_verbatim_indent(sub { (my \$$i = \$$_[0]->[0]) =~ s/\\S.*//; \$$i }); \$$p->parse_from_file('`perldoc -l pg_prove`')" > doc/pg_prove.html - From 05f8b3943ede257a4dc9cfce3688b9d2791b0b5f Mon Sep 17 00:00:00 2001 From: "Aaron W. Swenson" Date: Tue, 2 Apr 2013 00:24:09 +0000 Subject: [PATCH 0778/1195] Easier way of saying the same thing. --- Makefile | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 5080f7ca6a22..51be35b4c303 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ MAINEXT = pgtap EXTENSION = $(MAINEXT) EXTVERSION = $(shell grep default_version $(MAINEXT).control | \ - sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") + sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") NUMVERSION = $(shell echo $(EXTVERSION) | sed -e 's/\([[:digit:]]*[.][[:digit:]]*\).*/\1/') DATA = $(filter-out $(wildcard sql/*--*.sql),$(wildcard sql/*.sql)) TESTS = $(wildcard test/sql/*.sql) @@ -9,9 +9,7 @@ EXTRA_CLEAN = sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgta DOCS = doc/pgtap.mmd REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --inputdir=test --load-language=plpgsql -ifndef PG_CONFIG -PG_CONFIG = pg_config -endif +PG_CONFIG ?= pg_config ifdef NO_PGXS top_builddir = ../.. @@ -55,7 +53,7 @@ endif ifndef HAVE_HARNESS $(warning To use pg_prove, TAP::Parser::SourceHandler::pgTAP Perl module) $(warning must be installed from CPAN. To do so, simply run:) - $(warning cpan TAP::Parser::SourceHandler::pgTAP) + $(warning cpan TAP::Parser::SourceHandler::pgTAP) endif # Enum tests not supported by 8.2 and earlier. From 4f0210645e850a4c208fc7ecac8e48d14a3f8cf1 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 1 Apr 2013 22:07:23 -0700 Subject: [PATCH 0779/1195] Whitespace tweaks. --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 51be35b4c303..77229c29313f 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ MAINEXT = pgtap EXTENSION = $(MAINEXT) EXTVERSION = $(shell grep default_version $(MAINEXT).control | \ - sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") + sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") NUMVERSION = $(shell echo $(EXTVERSION) | sed -e 's/\([[:digit:]]*[.][[:digit:]]*\).*/\1/') DATA = $(filter-out $(wildcard sql/*--*.sql),$(wildcard sql/*.sql)) TESTS = $(wildcard test/sql/*.sql) @@ -51,9 +51,9 @@ HAVE_HARNESS := $(shell $(PERL) -le 'eval { require TAP::Parser::SourceHandler:: endif ifndef HAVE_HARNESS - $(warning To use pg_prove, TAP::Parser::SourceHandler::pgTAP Perl module) - $(warning must be installed from CPAN. To do so, simply run:) - $(warning cpan TAP::Parser::SourceHandler::pgTAP) + $(warning To use pg_prove, TAP::Parser::SourceHandler::pgTAP Perl module) + $(warning must be installed from CPAN. To do so, simply run:) + $(warning cpan TAP::Parser::SourceHandler::pgTAP) endif # Enum tests not supported by 8.2 and earlier. From 35464e301f5fb799db97bdfcf40628bb840e7836 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 1 Apr 2013 22:08:35 -0700 Subject: [PATCH 0780/1195] Give credit where credit is due. --- Changes | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index c79d2ad386a0..a944f6de3112 100644 --- a/Changes +++ b/Changes @@ -3,7 +3,8 @@ Revision history for pgTAP 0.94.0 --------------------------- - +* Fixed the Makefile to allow `PG_CONFIG=/path/to/pg_config` to actually work. + Patch from Aaron W. Swenson. 0.93.0 2013-01-28T20:14:58Z --------------------------- From e56c6ea6fae972d1736b2dbea971e7b21a7a7248 Mon Sep 17 00:00:00 2001 From: Eric Neault Date: Thu, 25 Apr 2013 21:07:08 -0400 Subject: [PATCH 0781/1195] fixed bug in Makefile lines 54-56 --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 77229c29313f..b5f7708bcfd2 100644 --- a/Makefile +++ b/Makefile @@ -51,9 +51,9 @@ HAVE_HARNESS := $(shell $(PERL) -le 'eval { require TAP::Parser::SourceHandler:: endif ifndef HAVE_HARNESS - $(warning To use pg_prove, TAP::Parser::SourceHandler::pgTAP Perl module) - $(warning must be installed from CPAN. To do so, simply run:) - $(warning cpan TAP::Parser::SourceHandler::pgTAP) +$(warning To use pg_prove, TAP::Parser::SourceHandler::pgTAP Perl module) +$(warning must be installed from CPAN. To do so, simply run:) +$(warning cpan TAP::Parser::SourceHandler::pgTAP) endif # Enum tests not supported by 8.2 and earlier. From 38d3fd68e5fbb3ab582206f22b5e273b32b4d05c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 26 Apr 2013 10:12:57 -0700 Subject: [PATCH 0782/1195] Note Makefile fix from Eric Neault. --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index a944f6de3112..450e0c131eee 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,8 @@ Revision history for pgTAP --------------------------- * Fixed the Makefile to allow `PG_CONFIG=/path/to/pg_config` to actually work. Patch from Aaron W. Swenson. +* Eliminated superfluous tabs in the `Makefile` that could cause it to fail in + some situations. Patch from Eric Neault. 0.93.0 2013-01-28T20:14:58Z --------------------------- From d4fa0fc5600a10d11f5cfdcbb5d4c238e080db86 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 3 Jun 2013 09:47:44 -0700 Subject: [PATCH 0783/1195] Fix signature length bug in function_privs_are(). Requires an upgrade script, and therefore also a bump in the version to v0.94.0. --- Changes | 2 + META.json | 6 +- pgtap.control | 2 +- sql/pgtap--0.93.0--0.94.0.sql | 33 ++++ sql/pgtap.sql.in | 4 +- test/expected/privs.out | 341 +++++++++++++++++----------------- test/sql/privs.sql | 21 ++- 7 files changed, 232 insertions(+), 177 deletions(-) create mode 100644 sql/pgtap--0.93.0--0.94.0.sql diff --git a/Changes b/Changes index 450e0c131eee..3396b73519e6 100644 --- a/Changes +++ b/Changes @@ -7,6 +7,8 @@ Revision history for pgTAP Patch from Aaron W. Swenson. * Eliminated superfluous tabs in the `Makefile` that could cause it to fail in some situations. Patch from Eric Neault. +* Fixed `function_privs_are() to work with functions with long names and/or + signatures. Thanks to Jörg Beyer for the detailed report. 0.93.0 2013-01-28T20:14:58Z --------------------------- diff --git a/META.json b/META.json index 7ad5eea3701d..f08412bb6a0d 100644 --- a/META.json +++ b/META.json @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "0.93.0" + "version": "0.94.0" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "0.93.0" + "version": "0.94.0" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "0.93.0" + "version": "0.94.0" } }, "resources": { diff --git a/pgtap.control b/pgtap.control index f480c05f8dc6..f1de4122d631 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '0.93.0' +default_version = '0.94.0' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true diff --git a/sql/pgtap--0.93.0--0.94.0.sql b/sql/pgtap--0.93.0--0.94.0.sql new file mode 100644 index 000000000000..6e526bf91d41 --- /dev/null +++ b/sql/pgtap--0.93.0--0.94.0.sql @@ -0,0 +1,33 @@ +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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 7150f8f363f3..2cd89e7db41c 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -8435,7 +8435,7 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _get_func_privs(NAME, TEXT) +CREATE OR REPLACE FUNCTION _get_func_privs(TEXT, TEXT) RETURNS TEXT[] AS $$ BEGIN IF pg_catalog.has_function_privilege($1, $2, 'EXECUTE') THEN @@ -8451,7 +8451,7 @@ EXCEPTION END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION _fprivs_are ( NAME, NAME, NAME[], TEXT ) +CREATE OR REPLACE FUNCTION _fprivs_are ( TEXT, NAME, NAME[], TEXT ) RETURNS TEXT AS $$ DECLARE grants TEXT[] := _get_func_privs($2, $1); diff --git a/test/expected/privs.out b/test/expected/privs.out index 4746a8038686..b892a12c366f 100644 --- a/test/expected/privs.out +++ b/test/expected/privs.out @@ -1,5 +1,5 @@ \unset ECHO -1..305 +1..308 ok 1 - table_privs_are(sch, tab, role, privs, desc) should pass ok 2 - table_privs_are(sch, tab, role, privs, desc) should have the proper description ok 3 - table_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics @@ -104,204 +104,207 @@ ok 101 - function_privs_are(sch, func, args, unpriv, privs, desc) should have th ok 102 - function_privs_are(func, args, unpriv, privs, desc) should fail ok 103 - function_privs_are(func, args, unpriv, privs, desc) should have the proper description ok 104 - function_privs_are(func, args, unpriv, privs, desc) should have the proper diagnostics -ok 105 - language_privs_are(lang, role, privs, desc) should pass -ok 106 - language_privs_are(lang, role, privs, desc) should have the proper description -ok 107 - language_privs_are(lang, role, privs, desc) should have the proper diagnostics +ok 105 - long function signature should pass +ok 106 - long function signature should have the proper description +ok 107 - long function signature should have the proper diagnostics ok 108 - language_privs_are(lang, role, privs, desc) should pass ok 109 - language_privs_are(lang, role, privs, desc) should have the proper description ok 110 - language_privs_are(lang, role, privs, desc) should have the proper diagnostics -ok 111 - language_privs_are(non-lang, role, privs, desc) should fail -ok 112 - language_privs_are(non-lang, role, privs, desc) should have the proper description -ok 113 - language_privs_are(non-lang, role, privs, desc) should have the proper diagnostics -ok 114 - language_privs_are(lang, non-role, privs, desc) should fail -ok 115 - language_privs_are(lang, non-role, privs, desc) should have the proper description -ok 116 - language_privs_are(lang, non-role, privs, desc) should have the proper diagnostics -ok 117 - language_privs_are(lang, ungranted, privs, desc) should fail -ok 118 - language_privs_are(lang, ungranted, privs, desc) should have the proper description -ok 119 - language_privs_are(lang, ungranted, privs, desc) should have the proper diagnostics -ok 120 - language_privs_are(lang, role, no privs) should pass -ok 121 - language_privs_are(lang, role, no privs) should have the proper description -ok 122 - language_privs_are(lang, role, no privs) should have the proper diagnostics -ok 123 - schema_privs_are(schema, role, privs, desc) should pass -ok 124 - schema_privs_are(schema, role, privs, desc) should have the proper description -ok 125 - schema_privs_are(schema, role, privs, desc) should have the proper diagnostics +ok 111 - language_privs_are(lang, role, privs, desc) should pass +ok 112 - language_privs_are(lang, role, privs, desc) should have the proper description +ok 113 - language_privs_are(lang, role, privs, desc) should have the proper diagnostics +ok 114 - language_privs_are(non-lang, role, privs, desc) should fail +ok 115 - language_privs_are(non-lang, role, privs, desc) should have the proper description +ok 116 - language_privs_are(non-lang, role, privs, desc) should have the proper diagnostics +ok 117 - language_privs_are(lang, non-role, privs, desc) should fail +ok 118 - language_privs_are(lang, non-role, privs, desc) should have the proper description +ok 119 - language_privs_are(lang, non-role, privs, desc) should have the proper diagnostics +ok 120 - language_privs_are(lang, ungranted, privs, desc) should fail +ok 121 - language_privs_are(lang, ungranted, privs, desc) should have the proper description +ok 122 - language_privs_are(lang, ungranted, privs, desc) should have the proper diagnostics +ok 123 - language_privs_are(lang, role, no privs) should pass +ok 124 - language_privs_are(lang, role, no privs) should have the proper description +ok 125 - language_privs_are(lang, role, no privs) should have the proper diagnostics ok 126 - schema_privs_are(schema, role, privs, desc) should pass ok 127 - schema_privs_are(schema, role, privs, desc) should have the proper description ok 128 - schema_privs_are(schema, role, privs, desc) should have the proper diagnostics -ok 129 - schema_privs_are(non-schema, role, privs, desc) should fail -ok 130 - schema_privs_are(non-schema, role, privs, desc) should have the proper description -ok 131 - schema_privs_are(non-schema, role, privs, desc) should have the proper diagnostics -ok 132 - schema_privs_are(schema, non-role, privs, desc) should fail -ok 133 - schema_privs_are(schema, non-role, privs, desc) should have the proper description -ok 134 - schema_privs_are(schema, non-role, privs, desc) should have the proper diagnostics -ok 135 - schema_privs_are(schema, ungranted, privs, desc) should fail -ok 136 - schema_privs_are(schema, ungranted, privs, desc) should have the proper description -ok 137 - schema_privs_are(schema, ungranted, privs, desc) should have the proper diagnostics +ok 129 - schema_privs_are(schema, role, privs, desc) should pass +ok 130 - schema_privs_are(schema, role, privs, desc) should have the proper description +ok 131 - schema_privs_are(schema, role, privs, desc) should have the proper diagnostics +ok 132 - schema_privs_are(non-schema, role, privs, desc) should fail +ok 133 - schema_privs_are(non-schema, role, privs, desc) should have the proper description +ok 134 - schema_privs_are(non-schema, role, privs, desc) should have the proper diagnostics +ok 135 - schema_privs_are(schema, non-role, privs, desc) should fail +ok 136 - schema_privs_are(schema, non-role, privs, desc) should have the proper description +ok 137 - schema_privs_are(schema, non-role, privs, desc) should have the proper diagnostics ok 138 - schema_privs_are(schema, ungranted, privs, desc) should fail ok 139 - schema_privs_are(schema, ungranted, privs, desc) should have the proper description ok 140 - schema_privs_are(schema, ungranted, privs, desc) should have the proper diagnostics -ok 141 - schema_privs_are(schema, non-role, no privs) should fail -ok 142 - schema_privs_are(schema, non-role, no privs) should have the proper description -ok 143 - schema_privs_are(schema, non-role, no privs) should have the proper diagnostics -ok 144 - tablespace_privs_are(tablespace, role, privs, desc) should pass -ok 145 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper description -ok 146 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper diagnostics +ok 141 - schema_privs_are(schema, ungranted, privs, desc) should fail +ok 142 - schema_privs_are(schema, ungranted, privs, desc) should have the proper description +ok 143 - schema_privs_are(schema, ungranted, privs, desc) should have the proper diagnostics +ok 144 - schema_privs_are(schema, non-role, no privs) should fail +ok 145 - schema_privs_are(schema, non-role, no privs) should have the proper description +ok 146 - schema_privs_are(schema, non-role, no privs) should have the proper diagnostics ok 147 - tablespace_privs_are(tablespace, role, privs, desc) should pass ok 148 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper description ok 149 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper diagnostics -ok 150 - tablespace_privs_are(non-tablespace, role, privs, desc) should fail -ok 151 - tablespace_privs_are(non-tablespace, role, privs, desc) should have the proper description -ok 152 - tablespace_privs_are(non-tablespace, role, privs, desc) should have the proper diagnostics -ok 153 - tablespace_privs_are(tablespace, non-role, privs, desc) should fail -ok 154 - tablespace_privs_are(tablespace, non-role, privs, desc) should have the proper description -ok 155 - tablespace_privs_are(tablespace, non-role, privs, desc) should have the proper diagnostics -ok 156 - tablespace_privs_are(tablespace, ungranted, privs, desc) should fail -ok 157 - tablespace_privs_are(tablespace, ungranted, privs, desc) should have the proper description -ok 158 - tablespace_privs_are(tablespace, ungranted, privs, desc) should have the proper diagnostics -ok 159 - tablespace_privs_are(tablespace, role, no privs) should pass -ok 160 - tablespace_privs_are(tablespace, role, no privs) should have the proper description -ok 161 - tablespace_privs_are(tablespace, role, no privs) should have the proper diagnostics -ok 162 - sequence_privs_are(sch, seq, role, privs, desc) should pass -ok 163 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description -ok 164 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics -ok 165 - sequence_privs_are(sch, seq, role, privs) should pass -ok 166 - sequence_privs_are(sch, seq, role, privs) should have the proper description -ok 167 - sequence_privs_are(sch, seq, role, privs) should have the proper diagnostics -ok 168 - sequence_privs_are(seq, role, privs, desc) should pass -ok 169 - sequence_privs_are(seq, role, privs, desc) should have the proper description -ok 170 - sequence_privs_are(seq, role, privs, desc) should have the proper diagnostics -ok 171 - sequence_privs_are(seq, role, privs) should pass -ok 172 - sequence_privs_are(seq, role, privs) should have the proper description -ok 173 - sequence_privs_are(seq, role, privs) should have the proper diagnostics -ok 174 - sequence_privs_are(sch, seq, role, some privs, desc) should fail -ok 175 - sequence_privs_are(sch, seq, role, some privs, desc) should have the proper description -ok 176 - sequence_privs_are(sch, seq, role, some privs, desc) should have the proper diagnostics -ok 177 - sequence_privs_are(seq, role, some privs, desc) should fail -ok 178 - sequence_privs_are(seq, role, some privs, desc) should have the proper description -ok 179 - sequence_privs_are(seq, role, some privs, desc) should have the proper diagnostics -ok 180 - sequence_privs_are(sch, seq, other, privs, desc) should fail -ok 181 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper description -ok 182 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper diagnostics -ok 183 - sequence_privs_are(sch, seq, other, privs, desc) should pass +ok 150 - tablespace_privs_are(tablespace, role, privs, desc) should pass +ok 151 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper description +ok 152 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper diagnostics +ok 153 - tablespace_privs_are(non-tablespace, role, privs, desc) should fail +ok 154 - tablespace_privs_are(non-tablespace, role, privs, desc) should have the proper description +ok 155 - tablespace_privs_are(non-tablespace, role, privs, desc) should have the proper diagnostics +ok 156 - tablespace_privs_are(tablespace, non-role, privs, desc) should fail +ok 157 - tablespace_privs_are(tablespace, non-role, privs, desc) should have the proper description +ok 158 - tablespace_privs_are(tablespace, non-role, privs, desc) should have the proper diagnostics +ok 159 - tablespace_privs_are(tablespace, ungranted, privs, desc) should fail +ok 160 - tablespace_privs_are(tablespace, ungranted, privs, desc) should have the proper description +ok 161 - tablespace_privs_are(tablespace, ungranted, privs, desc) should have the proper diagnostics +ok 162 - tablespace_privs_are(tablespace, role, no privs) should pass +ok 163 - tablespace_privs_are(tablespace, role, no privs) should have the proper description +ok 164 - tablespace_privs_are(tablespace, role, no privs) should have the proper diagnostics +ok 165 - sequence_privs_are(sch, seq, role, privs, desc) should pass +ok 166 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description +ok 167 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics +ok 168 - sequence_privs_are(sch, seq, role, privs) should pass +ok 169 - sequence_privs_are(sch, seq, role, privs) should have the proper description +ok 170 - sequence_privs_are(sch, seq, role, privs) should have the proper diagnostics +ok 171 - sequence_privs_are(seq, role, privs, desc) should pass +ok 172 - sequence_privs_are(seq, role, privs, desc) should have the proper description +ok 173 - sequence_privs_are(seq, role, privs, desc) should have the proper diagnostics +ok 174 - sequence_privs_are(seq, role, privs) should pass +ok 175 - sequence_privs_are(seq, role, privs) should have the proper description +ok 176 - sequence_privs_are(seq, role, privs) should have the proper diagnostics +ok 177 - sequence_privs_are(sch, seq, role, some privs, desc) should fail +ok 178 - sequence_privs_are(sch, seq, role, some privs, desc) should have the proper description +ok 179 - sequence_privs_are(sch, seq, role, some privs, desc) should have the proper diagnostics +ok 180 - sequence_privs_are(seq, role, some privs, desc) should fail +ok 181 - sequence_privs_are(seq, role, some privs, desc) should have the proper description +ok 182 - sequence_privs_are(seq, role, some privs, desc) should have the proper diagnostics +ok 183 - sequence_privs_are(sch, seq, other, privs, desc) should fail ok 184 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper description ok 185 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper diagnostics -ok 186 - sequence_privs_are(sch, seq, role, privs, desc) should fail -ok 187 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description -ok 188 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics +ok 186 - sequence_privs_are(sch, seq, other, privs, desc) should pass +ok 187 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper description +ok 188 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper diagnostics ok 189 - sequence_privs_are(sch, seq, role, privs, desc) should fail ok 190 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description ok 191 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics -ok 192 - sequence_privs_are(sch, seq, role, no privs) should fail -ok 193 - sequence_privs_are(sch, seq, role, no privs) should have the proper description -ok 194 - sequence_privs_are(sch, seq, role, no privs) should have the proper diagnostics -ok 195 - sequence_privs_are(seq, role, no privs) should fail -ok 196 - sequence_privs_are(seq, role, no privs) should have the proper description -ok 197 - sequence_privs_are(seq, role, no privs) should have the proper diagnostics -ok 198 - any_column_privs_are(sch, tab, role, privs, desc) should pass -ok 199 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 200 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 201 - any_column_privs_are(sch, tab, role, privs) should pass -ok 202 - any_column_privs_are(sch, tab, role, privs) should have the proper description -ok 203 - any_column_privs_are(sch, tab, role, privs) should have the proper diagnostics -ok 204 - any_column_privs_are(tab, role, privs, desc) should pass -ok 205 - any_column_privs_are(tab, role, privs, desc) should have the proper description -ok 206 - any_column_privs_are(tab, role, privs, desc) should have the proper diagnostics -ok 207 - any_column_privs_are(tab, role, privs) should pass -ok 208 - any_column_privs_are(tab, role, privs) should have the proper description -ok 209 - any_column_privs_are(tab, role, privs) should have the proper diagnostics -ok 210 - any_column_privs_are(sch, tab, role, some privs, desc) should fail -ok 211 - any_column_privs_are(sch, tab, role, some privs, desc) should have the proper description -ok 212 - any_column_privs_are(sch, tab, role, some privs, desc) should have the proper diagnostics -ok 213 - any_column_privs_are(tab, role, some privs, desc) should fail -ok 214 - any_column_privs_are(tab, role, some privs, desc) should have the proper description -ok 215 - any_column_privs_are(tab, role, some privs, desc) should have the proper diagnostics -ok 216 - any_column_privs_are(sch, tab, other, privs, desc) should fail -ok 217 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper description -ok 218 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics -ok 219 - any_column_privs_are(sch, tab, other, privs, desc) should pass +ok 192 - sequence_privs_are(sch, seq, role, privs, desc) should fail +ok 193 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description +ok 194 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics +ok 195 - sequence_privs_are(sch, seq, role, no privs) should fail +ok 196 - sequence_privs_are(sch, seq, role, no privs) should have the proper description +ok 197 - sequence_privs_are(sch, seq, role, no privs) should have the proper diagnostics +ok 198 - sequence_privs_are(seq, role, no privs) should fail +ok 199 - sequence_privs_are(seq, role, no privs) should have the proper description +ok 200 - sequence_privs_are(seq, role, no privs) should have the proper diagnostics +ok 201 - any_column_privs_are(sch, tab, role, privs, desc) should pass +ok 202 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 203 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 204 - any_column_privs_are(sch, tab, role, privs) should pass +ok 205 - any_column_privs_are(sch, tab, role, privs) should have the proper description +ok 206 - any_column_privs_are(sch, tab, role, privs) should have the proper diagnostics +ok 207 - any_column_privs_are(tab, role, privs, desc) should pass +ok 208 - any_column_privs_are(tab, role, privs, desc) should have the proper description +ok 209 - any_column_privs_are(tab, role, privs, desc) should have the proper diagnostics +ok 210 - any_column_privs_are(tab, role, privs) should pass +ok 211 - any_column_privs_are(tab, role, privs) should have the proper description +ok 212 - any_column_privs_are(tab, role, privs) should have the proper diagnostics +ok 213 - any_column_privs_are(sch, tab, role, some privs, desc) should fail +ok 214 - any_column_privs_are(sch, tab, role, some privs, desc) should have the proper description +ok 215 - any_column_privs_are(sch, tab, role, some privs, desc) should have the proper diagnostics +ok 216 - any_column_privs_are(tab, role, some privs, desc) should fail +ok 217 - any_column_privs_are(tab, role, some privs, desc) should have the proper description +ok 218 - any_column_privs_are(tab, role, some privs, desc) should have the proper diagnostics +ok 219 - any_column_privs_are(sch, tab, other, privs, desc) should fail ok 220 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper description ok 221 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics -ok 222 - any_column_privs_are(sch, tab, role, privs, desc) should fail -ok 223 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 224 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 222 - any_column_privs_are(sch, tab, other, privs, desc) should pass +ok 223 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 224 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics ok 225 - any_column_privs_are(sch, tab, role, privs, desc) should fail ok 226 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description ok 227 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 228 - any_column_privs_are(sch, tab, role, no privs) should fail -ok 229 - any_column_privs_are(sch, tab, role, no privs) should have the proper description -ok 230 - any_column_privs_are(sch, tab, role, no privs) should have the proper diagnostics -ok 231 - any_column_privs_are(tab, role, no privs) should fail -ok 232 - any_column_privs_are(tab, role, no privs) should have the proper description -ok 233 - any_column_privs_are(tab, role, no privs) should have the proper diagnostics -ok 234 - column_privs_are(sch, tab, role, privs, desc) should pass -ok 235 - column_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 236 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 237 - column_privs_are(sch, tab, role, privs) should pass -ok 238 - column_privs_are(sch, tab, role, privs) should have the proper description -ok 239 - column_privs_are(sch, tab, role, privs) should have the proper diagnostics -ok 240 - column_privs_are(tab, role, privs, desc) should pass -ok 241 - column_privs_are(tab, role, privs, desc) should have the proper description -ok 242 - column_privs_are(tab, role, privs, desc) should have the proper diagnostics -ok 243 - column_privs_are(tab, role, privs) should pass -ok 244 - column_privs_are(tab, role, privs) should have the proper description -ok 245 - column_privs_are(tab, role, privs) should have the proper diagnostics -ok 246 - column_privs_are(sch, tab, role, some privs, desc) should fail -ok 247 - column_privs_are(sch, tab, role, some privs, desc) should have the proper description -ok 248 - column_privs_are(sch, tab, role, some privs, desc) should have the proper diagnostics -ok 249 - column_privs_are(tab, role, some privs, desc) should fail -ok 250 - column_privs_are(tab, role, some privs, desc) should have the proper description -ok 251 - column_privs_are(tab, role, some privs, desc) should have the proper diagnostics -ok 252 - column_privs_are(sch, tab, other, privs, desc) should fail -ok 253 - column_privs_are(sch, tab, other, privs, desc) should have the proper description -ok 254 - column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics -ok 255 - column_privs_are(sch, tab, other, privs, desc) should pass +ok 228 - any_column_privs_are(sch, tab, role, privs, desc) should fail +ok 229 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 230 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 231 - any_column_privs_are(sch, tab, role, no privs) should fail +ok 232 - any_column_privs_are(sch, tab, role, no privs) should have the proper description +ok 233 - any_column_privs_are(sch, tab, role, no privs) should have the proper diagnostics +ok 234 - any_column_privs_are(tab, role, no privs) should fail +ok 235 - any_column_privs_are(tab, role, no privs) should have the proper description +ok 236 - any_column_privs_are(tab, role, no privs) should have the proper diagnostics +ok 237 - column_privs_are(sch, tab, role, privs, desc) should pass +ok 238 - column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 239 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 240 - column_privs_are(sch, tab, role, privs) should pass +ok 241 - column_privs_are(sch, tab, role, privs) should have the proper description +ok 242 - column_privs_are(sch, tab, role, privs) should have the proper diagnostics +ok 243 - column_privs_are(tab, role, privs, desc) should pass +ok 244 - column_privs_are(tab, role, privs, desc) should have the proper description +ok 245 - column_privs_are(tab, role, privs, desc) should have the proper diagnostics +ok 246 - column_privs_are(tab, role, privs) should pass +ok 247 - column_privs_are(tab, role, privs) should have the proper description +ok 248 - column_privs_are(tab, role, privs) should have the proper diagnostics +ok 249 - column_privs_are(sch, tab, role, some privs, desc) should fail +ok 250 - column_privs_are(sch, tab, role, some privs, desc) should have the proper description +ok 251 - column_privs_are(sch, tab, role, some privs, desc) should have the proper diagnostics +ok 252 - column_privs_are(tab, role, some privs, desc) should fail +ok 253 - column_privs_are(tab, role, some privs, desc) should have the proper description +ok 254 - column_privs_are(tab, role, some privs, desc) should have the proper diagnostics +ok 255 - column_privs_are(sch, tab, other, privs, desc) should fail ok 256 - column_privs_are(sch, tab, other, privs, desc) should have the proper description ok 257 - column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics -ok 258 - column_privs_are(sch, tab, role, privs, desc) should fail -ok 259 - column_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 260 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 258 - column_privs_are(sch, tab, other, privs, desc) should pass +ok 259 - column_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 260 - column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics ok 261 - column_privs_are(sch, tab, role, privs, desc) should fail ok 262 - column_privs_are(sch, tab, role, privs, desc) should have the proper description ok 263 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 264 - column_privs_are(sch, tab, role, no privs) should fail -ok 265 - column_privs_are(sch, tab, role, no privs) should have the proper description -ok 266 - column_privs_are(sch, tab, role, no privs) should have the proper diagnostics -ok 267 - column_privs_are(tab, role, no privs) should fail -ok 268 - column_privs_are(tab, role, no privs) should have the proper description -ok 269 - column_privs_are(tab, role, no privs) should have the proper diagnostics -ok 270 - fdw_privs_are(fdw, role, privs, desc) should pass -ok 271 - fdw_privs_are(fdw, role, privs, desc) should have the proper description -ok 272 - fdw_privs_are(fdw, role, privs, desc) should have the proper diagnostics +ok 264 - column_privs_are(sch, tab, role, privs, desc) should fail +ok 265 - column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 266 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 267 - column_privs_are(sch, tab, role, no privs) should fail +ok 268 - column_privs_are(sch, tab, role, no privs) should have the proper description +ok 269 - column_privs_are(sch, tab, role, no privs) should have the proper diagnostics +ok 270 - column_privs_are(tab, role, no privs) should fail +ok 271 - column_privs_are(tab, role, no privs) should have the proper description +ok 272 - column_privs_are(tab, role, no privs) should have the proper diagnostics ok 273 - fdw_privs_are(fdw, role, privs, desc) should pass ok 274 - fdw_privs_are(fdw, role, privs, desc) should have the proper description ok 275 - fdw_privs_are(fdw, role, privs, desc) should have the proper diagnostics -ok 276 - fdw_privs_are(non-fdw, role, privs, desc) should fail -ok 277 - fdw_privs_are(non-fdw, role, privs, desc) should have the proper description -ok 278 - fdw_privs_are(non-fdw, role, privs, desc) should have the proper diagnostics -ok 279 - fdw_privs_are(fdw, non-role, privs, desc) should fail -ok 280 - fdw_privs_are(fdw, non-role, privs, desc) should have the proper description -ok 281 - fdw_privs_are(fdw, non-role, privs, desc) should have the proper diagnostics -ok 282 - fdw_privs_are(fdw, ungranted, privs, desc) should fail -ok 283 - fdw_privs_are(fdw, ungranted, privs, desc) should have the proper description -ok 284 - fdw_privs_are(fdw, ungranted, privs, desc) should have the proper diagnostics -ok 285 - fdw_privs_are(fdw, role, no privs) should pass -ok 286 - fdw_privs_are(fdw, role, no privs) should have the proper description -ok 287 - fdw_privs_are(fdw, role, no privs) should have the proper diagnostics -ok 288 - server_privs_are(server, role, privs, desc) should pass -ok 289 - server_privs_are(server, role, privs, desc) should have the proper description -ok 290 - server_privs_are(server, role, privs, desc) should have the proper diagnostics +ok 276 - fdw_privs_are(fdw, role, privs, desc) should pass +ok 277 - fdw_privs_are(fdw, role, privs, desc) should have the proper description +ok 278 - fdw_privs_are(fdw, role, privs, desc) should have the proper diagnostics +ok 279 - fdw_privs_are(non-fdw, role, privs, desc) should fail +ok 280 - fdw_privs_are(non-fdw, role, privs, desc) should have the proper description +ok 281 - fdw_privs_are(non-fdw, role, privs, desc) should have the proper diagnostics +ok 282 - fdw_privs_are(fdw, non-role, privs, desc) should fail +ok 283 - fdw_privs_are(fdw, non-role, privs, desc) should have the proper description +ok 284 - fdw_privs_are(fdw, non-role, privs, desc) should have the proper diagnostics +ok 285 - fdw_privs_are(fdw, ungranted, privs, desc) should fail +ok 286 - fdw_privs_are(fdw, ungranted, privs, desc) should have the proper description +ok 287 - fdw_privs_are(fdw, ungranted, privs, desc) should have the proper diagnostics +ok 288 - fdw_privs_are(fdw, role, no privs) should pass +ok 289 - fdw_privs_are(fdw, role, no privs) should have the proper description +ok 290 - fdw_privs_are(fdw, role, no privs) should have the proper diagnostics ok 291 - server_privs_are(server, role, privs, desc) should pass ok 292 - server_privs_are(server, role, privs, desc) should have the proper description ok 293 - server_privs_are(server, role, privs, desc) should have the proper diagnostics -ok 294 - server_privs_are(non-server, role, privs, desc) should fail -ok 295 - server_privs_are(non-server, role, privs, desc) should have the proper description -ok 296 - server_privs_are(non-server, role, privs, desc) should have the proper diagnostics -ok 297 - server_privs_are(server, non-role, privs, desc) should fail -ok 298 - server_privs_are(server, non-role, privs, desc) should have the proper description -ok 299 - server_privs_are(server, non-role, privs, desc) should have the proper diagnostics -ok 300 - server_privs_are(server, ungranted, privs, desc) should fail -ok 301 - server_privs_are(server, ungranted, privs, desc) should have the proper description -ok 302 - server_privs_are(server, ungranted, privs, desc) should have the proper diagnostics -ok 303 - server_privs_are(server, role, no privs) should pass -ok 304 - server_privs_are(server, role, no privs) should have the proper description -ok 305 - server_privs_are(server, role, no privs) should have the proper diagnostics +ok 294 - server_privs_are(server, role, privs, desc) should pass +ok 295 - server_privs_are(server, role, privs, desc) should have the proper description +ok 296 - server_privs_are(server, role, privs, desc) should have the proper diagnostics +ok 297 - server_privs_are(non-server, role, privs, desc) should fail +ok 298 - server_privs_are(non-server, role, privs, desc) should have the proper description +ok 299 - server_privs_are(non-server, role, privs, desc) should have the proper diagnostics +ok 300 - server_privs_are(server, non-role, privs, desc) should fail +ok 301 - server_privs_are(server, non-role, privs, desc) should have the proper description +ok 302 - server_privs_are(server, non-role, privs, desc) should have the proper diagnostics +ok 303 - server_privs_are(server, ungranted, privs, desc) should fail +ok 304 - server_privs_are(server, ungranted, privs, desc) should have the proper description +ok 305 - server_privs_are(server, ungranted, privs, desc) should have the proper diagnostics +ok 306 - server_privs_are(server, role, no privs) should pass +ok 307 - server_privs_are(server, role, no privs) should have the proper description +ok 308 - server_privs_are(server, role, no privs) should have the proper diagnostics diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 35904effafc5..20e618454294 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(305); +SELECT plan(308); --SELECT * FROM no_plan(); SET client_min_messages = warning; @@ -220,7 +220,7 @@ SELECT * FROM check_test( ); /****************************************************************************/ --- Test function_privilege_is(). +-- Test function_privs_are(). CREATE OR REPLACE FUNCTION public.foo(int, text) RETURNS VOID LANGUAGE SQL AS ''; SELECT * FROM check_test( @@ -422,6 +422,23 @@ SELECT * FROM check_test( EXECUTE' ); +-- Try a really long function signature. +CREATE OR REPLACE FUNCTION public.function_with_a_moderate_signature( + anyelement, date, text[], boolean +) RETURNS VOID LANGUAGE SQL AS ''; + +SELECT * FROM check_test( + function_privs_are( + 'public', 'function_with_a_moderate_signature', + ARRAY['anyelement','date','text[]','boolean'], + current_user, ARRAY['EXECUTE'], 'whatever' + ), + true, + 'long function signature', + 'whatever', + '' +); + /****************************************************************************/ -- Test language_privilege_is(). From aec2ea22224a04bab1836add2d81fa833597c313 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 4 Sep 2013 15:53:55 -0700 Subject: [PATCH 0784/1195] Add .travis.yml. --- .travis.yml | 14 ++++++++++++++ README.md | 2 ++ 2 files changed, 16 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000000..3b7b6bd91a87 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +language: c +before_install: + - wget https://gist.github.com/petere/5893799/raw/apt.postgresql.org.sh + - wget https://gist.github.com/petere/6023944/raw/pg-travis-test.sh + - sudo sh ./apt.postgresql.org.sh +env: + - PGVERSION=8.1 + - PGVERSION=8.2 + - PGVERSION=8.3 + - PGVERSION=8.4 + - PGVERSION=9.0 + - PGVERSION=9.1 + - PGVERSION=9.2 +script: bash ./pg-travis-test.sh diff --git a/README.md b/README.md index a2ba6e579254..155abe43044d 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ used in the xUnit testing style. For detailed documentation, see the documentation in `doc/pgtap.mmd` or [online](http://pgtap.org/documentation.html "Complete pgTAP Documentation"). +[![Build Status](https://travis-ci.org/theory/pgtap.png)](https://travis-ci.org/theory/sqitch) + To build it, just do this: make From 77b3bfe6212075cea975f21c7a5addde81ed6d9b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 4 Sep 2013 16:00:04 -0700 Subject: [PATCH 0785/1195] PostgreSQL 8.1 not available in apt.postgresql.org. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3b7b6bd91a87..bcc030b63979 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ before_install: - wget https://gist.github.com/petere/6023944/raw/pg-travis-test.sh - sudo sh ./apt.postgresql.org.sh env: - - PGVERSION=8.1 - PGVERSION=8.2 - PGVERSION=8.3 - PGVERSION=8.4 From 2c1457a782ecd09678b885c2869b10bcc7334329 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 5 Sep 2013 09:58:12 -0700 Subject: [PATCH 0786/1195] Simplify DATA wildcard. Closes #48. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b5f7708bcfd2..c9c46800365c 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ EXTENSION = $(MAINEXT) EXTVERSION = $(shell grep default_version $(MAINEXT).control | \ sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") NUMVERSION = $(shell echo $(EXTVERSION) | sed -e 's/\([[:digit:]]*[.][[:digit:]]*\).*/\1/') -DATA = $(filter-out $(wildcard sql/*--*.sql),$(wildcard sql/*.sql)) +DATA = $(wildcard sql/*--*.sql) TESTS = $(wildcard test/sql/*.sql) EXTRA_CLEAN = sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql doc/*.html DOCS = doc/pgtap.mmd From 7bcb44a0e0b6c80a785e385c15044ef2639e98c8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 19 Sep 2013 15:53:41 -0700 Subject: [PATCH 0787/1195] Add missing has_table() functions. + `has_table(:schema, :table)` + `hasnt_table(:schema, :table)` + `has_foreign_table(:schema, :table)` + `hasnt_foreign_table(:schema, :table)` Closes #54. --- Changes | 5 + doc/pgtap.mmd | 8 +- sql/pgtap--0.93.0--0.94.0.sql | 37 + sql/pgtap.sql.in | 36 + test/expected/hastap.out | 1478 +++++++++++++++++---------------- test/sql/hastap.sql | 70 +- 6 files changed, 892 insertions(+), 742 deletions(-) diff --git a/Changes b/Changes index 3396b73519e6..3bc3456889f4 100644 --- a/Changes +++ b/Changes @@ -9,6 +9,11 @@ Revision history for pgTAP some situations. Patch from Eric Neault. * Fixed `function_privs_are() to work with functions with long names and/or signatures. Thanks to Jörg Beyer for the detailed report. +* Added missing functions mentioned in the docs (Issue #54): + + `has_table(:schema, :table)` + + `hasnt_table(:schema, :table)` + + `has_foreign_table(:schema, :table)` + + `hasnt_foreign_table(:schema, :table)` 0.93.0 2013-01-28T20:14:58Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 4ca0ea42a6ce..1eda9a7a9c2a 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2531,6 +2531,7 @@ specified relation does *not* exist. ### `has_table()` ### SELECT has_table( :schema, :table, :description ); + SELECT has_table( :schema, :table ); SELECT has_table( :table, :description ); SELECT has_table( :table ); @@ -2550,7 +2551,7 @@ argument is a schema name, the second is a table name, and the third is the test description. If you omit the schema, the table must be visible in the search path. Example: - SELECT has_table('myschema', 'sometable'); + SELECT has_table('myschema'::name, 'sometable'::name); If you omit the test description, it will be set to "Table `:table` should exist". @@ -2561,6 +2562,7 @@ Note that this function will not recognize foreign tables; use ### `hasnt_table()` ### SELECT hasnt_table( :schema, :table, :description ); + SELECT hasnt_table( :schema, :table ); SELECT hasnt_table( :table, :description ); SELECT hasnt_table( :table ); @@ -2675,6 +2677,7 @@ specified sequence does *not* exist. ### `has_foreign_table()` ### SELECT has_foreign_table( :schema, :table, :description ); + SELECT has_foreign_table( :schema, :table ); SELECT has_foreign_table( :table, :description ); SELECT has_foreign_table( :table ); @@ -2694,7 +2697,7 @@ first argument is a schema name, the second is a foreign table name, and the third is the test description. If you omit the schema, the foreign table must be visible in the search path. Example: - SELECT has_foreign_table('myschema', 'some_foreign_table'); + SELECT has_foreign_table('myschema'::name, 'some_foreign_table'::name); If you omit the test description, it will be set to "Foreign table `:table` should exist". @@ -2702,6 +2705,7 @@ should exist". ### `hasnt_foreign_table()` ### SELECT hasnt_foreign_table( :schema, :table, :description ); + SELECT hasnt_foreign_table( :schema, :table ); SELECT hasnt_foreign_table( :table, :description ); SELECT hasnt_foreign_table( :table ); diff --git a/sql/pgtap--0.93.0--0.94.0.sql b/sql/pgtap--0.93.0--0.94.0.sql index 6e526bf91d41..33c76ce6e327 100644 --- a/sql/pgtap--0.93.0--0.94.0.sql +++ b/sql/pgtap--0.93.0--0.94.0.sql @@ -31,3 +31,40 @@ BEGIN RETURN _assets_are('privileges', grants, $3, $4); END; $$ LANGUAGE plpgsql; + +-- has_table( schema, table ) +CREATE OR REPLACE FUNCTION has_table ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _rexists( 'r', $1, $2 ), + 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' + ); +$$ LANGUAGE SQL; + +-- hasnt_table( schema, table ) +CREATE OR REPLACE FUNCTION hasnt_table ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _rexists( 'r', $1, $2 ), + 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' + ); +$$ 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; + +-- 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) || ' not should exist' + ); +$$ LANGUAGE SQL; + diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 2cd89e7db41c..30ea311593ec 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -897,6 +897,15 @@ RETURNS TEXT AS $$ SELECT ok( _rexists( 'r', $1, $2 ), $3 ); $$ LANGUAGE SQL; +-- has_table( schema, table ) +CREATE OR REPLACE FUNCTION has_table ( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _rexists( 'r', $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 $$ @@ -915,6 +924,15 @@ RETURNS TEXT AS $$ SELECT ok( NOT _rexists( 'r', $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', $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 $$ @@ -1005,6 +1023,15 @@ 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 $$ @@ -1023,6 +1050,15 @@ 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 $$ diff --git a/test/expected/hastap.out b/test/expected/hastap.out index cdcf72381d95..0983558ef853 100644 --- a/test/expected/hastap.out +++ b/test/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..786 +1..798 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -54,735 +54,747 @@ ok 51 - has_table(non-existent table) should have the proper diagnostics ok 52 - has_table(non-existent schema, tab) should fail ok 53 - has_table(non-existent schema, tab) should have the proper description ok 54 - has_table(non-existent schema, tab) should have the proper diagnostics -ok 55 - has_table(sch, non-existent table, desc) should fail -ok 56 - has_table(sch, non-existent table, desc) should have the proper description -ok 57 - has_table(sch, non-existent table, desc) should have the proper diagnostics -ok 58 - has_table(tab, desc) should pass -ok 59 - has_table(tab, desc) should have the proper description -ok 60 - has_table(tab, desc) should have the proper diagnostics -ok 61 - has_table(sch, tab, desc) should pass -ok 62 - has_table(sch, tab, desc) should have the proper description -ok 63 - has_table(sch, tab, desc) should have the proper diagnostics -ok 64 - has_table(sch, view, desc) should fail -ok 65 - has_table(sch, view, desc) should have the proper description -ok 66 - has_table(sch, view, desc) should have the proper diagnostics -ok 67 - has_table(type, desc) should fail -ok 68 - has_table(type, desc) should have the proper description -ok 69 - has_table(type, desc) should have the proper diagnostics -ok 70 - hasnt_table(non-existent table) should pass -ok 71 - hasnt_table(non-existent table) should have the proper description -ok 72 - hasnt_table(non-existent table) should have the proper diagnostics -ok 73 - hasnt_table(non-existent schema, tab) should pass -ok 74 - hasnt_table(non-existent schema, tab) should have the proper description -ok 75 - hasnt_table(non-existent schema, tab) should have the proper diagnostics -ok 76 - hasnt_table(sch, non-existent tab, desc) should pass -ok 77 - hasnt_table(sch, non-existent tab, desc) should have the proper description -ok 78 - hasnt_table(sch, non-existent tab, desc) should have the proper diagnostics -ok 79 - hasnt_table(tab, desc) should fail -ok 80 - hasnt_table(tab, desc) should have the proper description -ok 81 - hasnt_table(tab, desc) should have the proper diagnostics -ok 82 - hasnt_table(sch, tab, desc) should fail -ok 83 - hasnt_table(sch, tab, desc) should have the proper description -ok 84 - hasnt_table(sch, tab, desc) should have the proper diagnostics -ok 85 - has_view(non-existent view) should fail -ok 86 - has_view(non-existent view) should have the proper description -ok 87 - has_view(non-existent view) should have the proper diagnostics -ok 88 - has_view(non-existent view, desc) should fail -ok 89 - has_view(non-existent view, desc) should have the proper description -ok 90 - has_view(non-existent view, desc) should have the proper diagnostics -ok 91 - has_view(sch, non-existtent view, desc) should fail -ok 92 - has_view(sch, non-existtent view, desc) should have the proper description -ok 93 - has_view(sch, non-existtent view, desc) should have the proper diagnostics -ok 94 - has_view(view, desc) should pass -ok 95 - has_view(view, desc) should have the proper description -ok 96 - has_view(view, desc) should have the proper diagnostics -ok 97 - has_view(sch, view, desc) should pass -ok 98 - has_view(sch, view, desc) should have the proper description -ok 99 - has_view(sch, view, desc) should have the proper diagnostics -ok 100 - hasnt_view(non-existent view) should pass -ok 101 - hasnt_view(non-existent view) should have the proper description -ok 102 - hasnt_view(non-existent view) should have the proper diagnostics -ok 103 - hasnt_view(non-existent view, desc) should pass -ok 104 - hasnt_view(non-existent view, desc) should have the proper description -ok 105 - hasnt_view(non-existent view, desc) should have the proper diagnostics -ok 106 - hasnt_view(sch, non-existtent view, desc) should pass -ok 107 - hasnt_view(sch, non-existtent view, desc) should have the proper description -ok 108 - hasnt_view(sch, non-existtent view, desc) should have the proper diagnostics -ok 109 - hasnt_view(view, desc) should fail -ok 110 - hasnt_view(view, desc) should have the proper description -ok 111 - hasnt_view(view, desc) should have the proper diagnostics -ok 112 - hasnt_view(sch, view, desc) should fail -ok 113 - hasnt_view(sch, view, desc) should have the proper description -ok 114 - hasnt_view(sch, view, desc) should have the proper diagnostics -ok 115 - has_sequence(non-existent sequence) should fail -ok 116 - has_sequence(non-existent sequence) should have the proper description -ok 117 - has_sequence(non-existent sequence) should have the proper diagnostics -ok 118 - has_sequence(non-existent sequence, desc) should fail -ok 119 - has_sequence(non-existent sequence, desc) should have the proper description -ok 120 - has_sequence(non-existent sequence, desc) should have the proper diagnostics -ok 121 - has_sequence(sch, non-existtent sequence, desc) should fail -ok 122 - has_sequence(sch, non-existtent sequence, desc) should have the proper description -ok 123 - has_sequence(sch, non-existtent sequence, desc) should have the proper diagnostics -ok 124 - has_sequence(sequence, desc) should pass -ok 125 - has_sequence(sequence, desc) should have the proper description -ok 126 - has_sequence(sequence, desc) should have the proper diagnostics -ok 127 - has_sequence(sch, sequence, desc) should pass -ok 128 - has_sequence(sch, sequence, desc) should have the proper description -ok 129 - has_sequence(sch, sequence, desc) should have the proper diagnostics -ok 130 - hasnt_sequence(non-existent sequence) should pass -ok 131 - hasnt_sequence(non-existent sequence) should have the proper description -ok 132 - hasnt_sequence(non-existent sequence) should have the proper diagnostics -ok 133 - hasnt_sequence(non-existent sequence, desc) should pass -ok 134 - hasnt_sequence(non-existent sequence, desc) should have the proper description -ok 135 - hasnt_sequence(non-existent sequence, desc) should have the proper diagnostics -ok 136 - hasnt_sequence(sch, non-existtent sequence, desc) should pass -ok 137 - hasnt_sequence(sch, non-existtent sequence, desc) should have the proper description -ok 138 - hasnt_sequence(sch, non-existtent sequence, desc) should have the proper diagnostics -ok 139 - hasnt_sequence(sequence, desc) should fail -ok 140 - hasnt_sequence(sequence, desc) should have the proper description -ok 141 - hasnt_sequence(sequence, desc) should have the proper diagnostics -ok 142 - hasnt_sequence(sch, sequence, desc) should fail -ok 143 - hasnt_sequence(sch, sequence, desc) should have the proper description -ok 144 - hasnt_sequence(sch, sequence, desc) should have the proper diagnostics -ok 145 - has_composite(non-existent composite type) should fail -ok 146 - has_composite(non-existent composite type) should have the proper description -ok 147 - has_composite(non-existent composite type) should have the proper diagnostics -ok 148 - has_composite(non-existent schema, tab) should fail -ok 149 - has_composite(non-existent schema, tab) should have the proper description -ok 150 - has_composite(non-existent schema, tab) should have the proper diagnostics -ok 151 - has_composite(sch, non-existent composite type, desc) should fail -ok 152 - has_composite(sch, non-existent composite type, desc) should have the proper description -ok 153 - has_composite(sch, non-existent composite type, desc) should have the proper diagnostics -ok 154 - has_composite(tab, desc) should pass -ok 155 - has_composite(tab, desc) should have the proper description -ok 156 - has_composite(tab, desc) should have the proper diagnostics -ok 157 - has_composite(sch, tab, desc) should pass -ok 158 - has_composite(sch, tab, desc) should have the proper description -ok 159 - has_composite(sch, tab, desc) should have the proper diagnostics -ok 160 - has_composite(sch, view, desc) should fail -ok 161 - has_composite(sch, view, desc) should have the proper description -ok 162 - has_composite(sch, view, desc) should have the proper diagnostics -ok 163 - has_composite(type, desc) should fail -ok 164 - has_composite(type, desc) should have the proper description -ok 165 - has_composite(type, desc) should have the proper diagnostics -ok 166 - hasnt_composite(non-existent composite type) should pass -ok 167 - hasnt_composite(non-existent composite type) should have the proper description -ok 168 - hasnt_composite(non-existent composite type) should have the proper diagnostics -ok 169 - hasnt_composite(non-existent schema, tab) should pass -ok 170 - hasnt_composite(non-existent schema, tab) should have the proper description -ok 171 - hasnt_composite(non-existent schema, tab) should have the proper diagnostics -ok 172 - hasnt_composite(sch, non-existent tab, desc) should pass -ok 173 - hasnt_composite(sch, non-existent tab, desc) should have the proper description -ok 174 - hasnt_composite(sch, non-existent tab, desc) should have the proper diagnostics -ok 175 - hasnt_composite(tab, desc) should fail -ok 176 - hasnt_composite(tab, desc) should have the proper description -ok 177 - hasnt_composite(tab, desc) should have the proper diagnostics -ok 178 - hasnt_composite(sch, tab, desc) should fail -ok 179 - hasnt_composite(sch, tab, desc) should have the proper description -ok 180 - hasnt_composite(sch, tab, desc) should have the proper diagnostics -ok 181 - has_type(type) should pass -ok 182 - has_type(type) should have the proper description -ok 183 - has_type(type) should have the proper diagnostics -ok 184 - has_type(type, desc) should pass -ok 185 - has_type(type, desc) should have the proper description -ok 186 - has_type(type, desc) should have the proper diagnostics -ok 187 - has_type(scheam, type) should pass -ok 188 - has_type(scheam, type) should have the proper description -ok 189 - has_type(scheam, type) should have the proper diagnostics -ok 190 - has_type(schema, type, desc) should pass -ok 191 - has_type(schema, type, desc) should have the proper description -ok 192 - has_type(schema, type, desc) should have the proper diagnostics -ok 193 - has_type(myType) should pass -ok 194 - has_type(myType) should have the proper description -ok 195 - has_type(myType) should have the proper diagnostics -ok 196 - has_type(myType, desc) should pass -ok 197 - has_type(myType, desc) should have the proper description -ok 198 - has_type(myType, desc) should have the proper diagnostics -ok 199 - has_type(scheam, myType) should pass -ok 200 - has_type(scheam, myType) should have the proper description -ok 201 - has_type(scheam, myType) should have the proper diagnostics -ok 202 - has_type(schema, myType, desc) should pass -ok 203 - has_type(schema, myType, desc) should have the proper description -ok 204 - has_type(schema, myType, desc) should have the proper diagnostics -ok 205 - has_type(type) should fail -ok 206 - has_type(type) should have the proper description -ok 207 - has_type(type) should have the proper diagnostics -ok 208 - has_type(type, desc) should fail -ok 209 - has_type(type, desc) should have the proper description -ok 210 - has_type(type, desc) should have the proper diagnostics -ok 211 - has_type(scheam, type) should fail -ok 212 - has_type(scheam, type) should have the proper description -ok 213 - has_type(scheam, type) should have the proper diagnostics -ok 214 - has_type(schema, type, desc) should fail -ok 215 - has_type(schema, type, desc) should have the proper description -ok 216 - has_type(schema, type, desc) should have the proper diagnostics -ok 217 - has_type(domain) should pass -ok 218 - has_type(domain) should have the proper description -ok 219 - has_type(domain) should have the proper diagnostics -ok 220 - has_type(myDomain) should pass -ok 221 - has_type(myDomain) should have the proper description -ok 222 - has_type(myDomain) should have the proper diagnostics -ok 223 - hasnt_type(type) should pass -ok 224 - hasnt_type(type) should have the proper description -ok 225 - hasnt_type(type) should have the proper diagnostics -ok 226 - hasnt_type(type, desc) should pass -ok 227 - hasnt_type(type, desc) should have the proper description -ok 228 - hasnt_type(type, desc) should have the proper diagnostics -ok 229 - hasnt_type(scheam, type) should pass -ok 230 - hasnt_type(scheam, type) should have the proper description -ok 231 - hasnt_type(scheam, type) should have the proper diagnostics -ok 232 - hasnt_type(schema, type, desc) should pass -ok 233 - hasnt_type(schema, type, desc) should have the proper description -ok 234 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 235 - hasnt_type(type) should fail -ok 236 - hasnt_type(type) should have the proper description -ok 237 - hasnt_type(type) should have the proper diagnostics -ok 238 - hasnt_type(type, desc) should fail -ok 239 - hasnt_type(type, desc) should have the proper description -ok 240 - hasnt_type(type, desc) should have the proper diagnostics -ok 241 - hasnt_type(scheam, type) should fail -ok 242 - hasnt_type(scheam, type) should have the proper description -ok 243 - hasnt_type(scheam, type) should have the proper diagnostics -ok 244 - hasnt_type(schema, type, desc) should fail -ok 245 - hasnt_type(schema, type, desc) should have the proper description -ok 246 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 247 - has_domain(domain) should pass -ok 248 - has_domain(domain) should have the proper description -ok 249 - has_domain(domain) should have the proper diagnostics -ok 250 - has_domain(domain, desc) should pass -ok 251 - has_domain(domain, desc) should have the proper description -ok 252 - has_domain(domain, desc) should have the proper diagnostics -ok 253 - has_domain(scheam, domain) should pass -ok 254 - has_domain(scheam, domain) should have the proper description -ok 255 - has_domain(scheam, domain) should have the proper diagnostics -ok 256 - has_domain(schema, domain, desc) should pass -ok 257 - has_domain(schema, domain, desc) should have the proper description -ok 258 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 259 - has_domain(myDomain) should pass -ok 260 - has_domain(myDomain) should have the proper description -ok 261 - has_domain(myDomain) should have the proper diagnostics -ok 262 - has_domain(myDomain, desc) should pass -ok 263 - has_domain(myDomain, desc) should have the proper description -ok 264 - has_domain(myDomain, desc) should have the proper diagnostics -ok 265 - has_domain(scheam, myDomain) should pass -ok 266 - has_domain(scheam, myDomain) should have the proper description -ok 267 - has_domain(scheam, myDomain) should have the proper diagnostics -ok 268 - has_domain(schema, myDomain, desc) should pass -ok 269 - has_domain(schema, myDomain, desc) should have the proper description -ok 270 - has_domain(schema, myDomain, desc) should have the proper diagnostics -ok 271 - has_domain(domain) should fail -ok 272 - has_domain(domain) should have the proper description -ok 273 - has_domain(domain) should have the proper diagnostics -ok 274 - has_domain(domain, desc) should fail -ok 275 - has_domain(domain, desc) should have the proper description -ok 276 - has_domain(domain, desc) should have the proper diagnostics -ok 277 - has_domain(scheam, domain) should fail -ok 278 - has_domain(scheam, domain) should have the proper description -ok 279 - has_domain(scheam, domain) should have the proper diagnostics -ok 280 - has_domain(schema, domain, desc) should fail -ok 281 - has_domain(schema, domain, desc) should have the proper description -ok 282 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 283 - hasnt_domain(domain) should pass -ok 284 - hasnt_domain(domain) should have the proper description -ok 285 - hasnt_domain(domain) should have the proper diagnostics -ok 286 - hasnt_domain(domain, desc) should pass -ok 287 - hasnt_domain(domain, desc) should have the proper description -ok 288 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 289 - hasnt_domain(scheam, domain) should pass -ok 290 - hasnt_domain(scheam, domain) should have the proper description -ok 291 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 292 - hasnt_domain(schema, domain, desc) should pass -ok 293 - hasnt_domain(schema, domain, desc) should have the proper description -ok 294 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 295 - hasnt_domain(domain) should fail -ok 296 - hasnt_domain(domain) should have the proper description -ok 297 - hasnt_domain(domain) should have the proper diagnostics -ok 298 - hasnt_domain(domain, desc) should fail -ok 299 - hasnt_domain(domain, desc) should have the proper description -ok 300 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 301 - hasnt_domain(scheam, domain) should fail -ok 302 - hasnt_domain(scheam, domain) should have the proper description -ok 303 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 304 - hasnt_domain(schema, domain, desc) should fail -ok 305 - hasnt_domain(schema, domain, desc) should have the proper description -ok 306 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 307 - has_column(non-existent tab, col) should fail -ok 308 - has_column(non-existent tab, col) should have the proper description -ok 309 - has_column(non-existent tab, col) should have the proper diagnostics -ok 310 - has_column(non-existent tab, col, desc) should fail -ok 311 - has_column(non-existent tab, col, desc) should have the proper description -ok 312 - has_column(non-existent tab, col, desc) should have the proper diagnostics -ok 313 - has_column(non-existent sch, tab, col, desc) should fail -ok 314 - has_column(non-existent sch, tab, col, desc) should have the proper description -ok 315 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 316 - has_column(table, column) should pass -ok 317 - has_column(table, column) should have the proper description -ok 318 - has_column(table, column) should have the proper diagnostics -ok 319 - has_column(sch, tab, col, desc) should pass -ok 320 - has_column(sch, tab, col, desc) should have the proper description -ok 321 - has_column(sch, tab, col, desc) should have the proper diagnostics -ok 322 - has_column(table, camleCase column) should pass -ok 323 - has_column(table, camleCase column) should have the proper description -ok 324 - has_column(table, camleCase column) should have the proper diagnostics -ok 325 - has_column(view, column) should pass -ok 326 - has_column(view, column) should have the proper description -ok 327 - has_column(view, column) should have the proper diagnostics -ok 328 - has_column(type, column) should pass -ok 329 - has_column(type, column) should have the proper description -ok 330 - has_column(type, column) should have the proper diagnostics -ok 331 - hasnt_column(non-existent tab, col) should pass -ok 332 - hasnt_column(non-existent tab, col) should have the proper description -ok 333 - hasnt_column(non-existent tab, col) should have the proper diagnostics -ok 334 - hasnt_column(non-existent tab, col, desc) should pass -ok 335 - hasnt_column(non-existent tab, col, desc) should have the proper description -ok 336 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics -ok 337 - hasnt_column(non-existent sch, tab, col, desc) should pass -ok 338 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description -ok 339 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 340 - hasnt_column(table, column) should fail -ok 341 - hasnt_column(table, column) should have the proper description -ok 342 - hasnt_column(table, column) should have the proper diagnostics -ok 343 - hasnt_column(sch, tab, col, desc) should fail -ok 344 - hasnt_column(sch, tab, col, desc) should have the proper description -ok 345 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics -ok 346 - hasnt_column(view, column) should pass -ok 347 - hasnt_column(view, column) should have the proper description -ok 348 - hasnt_column(view, column) should have the proper diagnostics -ok 349 - hasnt_column(type, column) should pass -ok 350 - hasnt_column(type, column) should have the proper description -ok 351 - hasnt_column(type, column) should have the proper diagnostics -ok 352 - has_cast( src, targ, schema, func, desc) should pass -ok 353 - has_cast( src, targ, schema, func, desc) should have the proper description -ok 354 - has_cast( src, targ, schema, func, desc) should have the proper diagnostics -ok 355 - has_cast( src, targ, schema, func ) should pass -ok 356 - has_cast( src, targ, schema, func ) should have the proper description -ok 357 - has_cast( src, targ, schema, func ) should have the proper diagnostics -ok 358 - has_cast( src, targ, func, desc ) should pass -ok 359 - has_cast( src, targ, func, desc ) should have the proper description -ok 360 - has_cast( src, targ, func, desc ) should have the proper diagnostics -ok 361 - has_cast( src, targ, func) should pass -ok 362 - has_cast( src, targ, func) should have the proper description -ok 363 - has_cast( src, targ, func) should have the proper diagnostics -ok 364 - has_cast( src, targ, desc ) should pass -ok 365 - has_cast( src, targ, desc ) should have the proper description -ok 366 - has_cast( src, targ, desc ) should have the proper diagnostics -ok 367 - has_cast( src, targ ) should pass -ok 368 - has_cast( src, targ ) should have the proper description -ok 369 - has_cast( src, targ ) should have the proper diagnostics -ok 370 - has_cast( src, targ, schema, func, desc) fail should fail -ok 371 - has_cast( src, targ, schema, func, desc) fail should have the proper description -ok 372 - has_cast( src, targ, schema, func, desc) fail should have the proper diagnostics -ok 373 - has_cast( src, targ, func, desc ) fail should fail -ok 374 - has_cast( src, targ, func, desc ) fail should have the proper description -ok 375 - has_cast( src, targ, func, desc ) fail should have the proper diagnostics -ok 376 - has_cast( src, targ, desc ) fail should fail -ok 377 - has_cast( src, targ, desc ) fail should have the proper description -ok 378 - has_cast( src, targ, desc ) fail should have the proper diagnostics -ok 379 - hasnt_cast( src, targ, schema, func, desc) should fail -ok 380 - hasnt_cast( src, targ, schema, func, desc) should have the proper description -ok 381 - hasnt_cast( src, targ, schema, func, desc) should have the proper diagnostics -ok 382 - hasnt_cast( src, targ, schema, func ) should fail -ok 383 - hasnt_cast( src, targ, schema, func ) should have the proper description -ok 384 - hasnt_cast( src, targ, schema, func ) should have the proper diagnostics -ok 385 - hasnt_cast( src, targ, func, desc ) should fail -ok 386 - hasnt_cast( src, targ, func, desc ) should have the proper description -ok 387 - hasnt_cast( src, targ, func, desc ) should have the proper diagnostics -ok 388 - hasnt_cast( src, targ, func) should fail -ok 389 - hasnt_cast( src, targ, func) should have the proper description -ok 390 - hasnt_cast( src, targ, func) should have the proper diagnostics -ok 391 - hasnt_cast( src, targ, desc ) should fail -ok 392 - hasnt_cast( src, targ, desc ) should have the proper description -ok 393 - hasnt_cast( src, targ, desc ) should have the proper diagnostics -ok 394 - hasnt_cast( src, targ ) should fail -ok 395 - hasnt_cast( src, targ ) should have the proper description -ok 396 - hasnt_cast( src, targ ) should have the proper diagnostics -ok 397 - hasnt_cast( src, targ, schema, func, desc) fail should pass -ok 398 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper description -ok 399 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper diagnostics -ok 400 - hasnt_cast( src, targ, func, desc ) fail should pass -ok 401 - hasnt_cast( src, targ, func, desc ) fail should have the proper description -ok 402 - hasnt_cast( src, targ, func, desc ) fail should have the proper diagnostics -ok 403 - hasnt_cast( src, targ, desc ) fail should pass -ok 404 - hasnt_cast( src, targ, desc ) fail should have the proper description -ok 405 - hasnt_cast( src, targ, desc ) fail should have the proper diagnostics -ok 406 - cast_context_is( src, targ, context, desc ) should pass -ok 407 - cast_context_is( src, targ, context, desc ) should have the proper description -ok 408 - cast_context_is( src, targ, context, desc ) should have the proper diagnostics -ok 409 - cast_context_is( src, targ, context ) should pass -ok 410 - cast_context_is( src, targ, context ) should have the proper description -ok 411 - cast_context_is( src, targ, context ) should have the proper diagnostics -ok 412 - cast_context_is( src, targ, i, desc ) should pass -ok 413 - cast_context_is( src, targ, i, desc ) should have the proper description -ok 414 - cast_context_is( src, targ, i, desc ) should have the proper diagnostics -ok 415 - cast_context_is( src, targ, IMPL, desc ) should pass -ok 416 - cast_context_is( src, targ, IMPL, desc ) should have the proper description -ok 417 - cast_context_is( src, targ, IMPL, desc ) should have the proper diagnostics -ok 418 - cast_context_is( src, targ, assignment, desc ) should pass -ok 419 - cast_context_is( src, targ, assignment, desc ) should have the proper description -ok 420 - cast_context_is( src, targ, assignment, desc ) should have the proper diagnostics -ok 421 - cast_context_is( src, targ, a, desc ) should pass -ok 422 - cast_context_is( src, targ, a, desc ) should have the proper description -ok 423 - cast_context_is( src, targ, a, desc ) should have the proper diagnostics -ok 424 - cast_context_is( src, targ, ASS, desc ) should pass -ok 425 - cast_context_is( src, targ, ASS, desc ) should have the proper description -ok 426 - cast_context_is( src, targ, ASS, desc ) should have the proper diagnostics -ok 427 - cast_context_is( src, targ, explicit, desc ) should pass -ok 428 - cast_context_is( src, targ, explicit, desc ) should have the proper description -ok 429 - cast_context_is( src, targ, explicit, desc ) should have the proper diagnostics -ok 430 - cast_context_is( src, targ, e, desc ) should pass -ok 431 - cast_context_is( src, targ, e, desc ) should have the proper description -ok 432 - cast_context_is( src, targ, e, desc ) should have the proper diagnostics -ok 433 - cast_context_is( src, targ, EX, desc ) should pass -ok 434 - cast_context_is( src, targ, EX, desc ) should have the proper description -ok 435 - cast_context_is( src, targ, EX, desc ) should have the proper diagnostics -ok 436 - cast_context_is( src, targ, context, desc ) fail should fail -ok 437 - cast_context_is( src, targ, context, desc ) fail should have the proper description -ok 438 - cast_context_is( src, targ, context, desc ) fail should have the proper diagnostics -ok 439 - cast_context_is( src, targ, context ) fail should fail -ok 440 - cast_context_is( src, targ, context ) fail should have the proper description -ok 441 - cast_context_is( src, targ, context ) fail should have the proper diagnostics -ok 442 - cast_context_is( src, targ, context, desc ) noexist should fail -ok 443 - cast_context_is( src, targ, context, desc ) noexist should have the proper description -ok 444 - cast_context_is( src, targ, context, desc ) noexist should have the proper diagnostics -ok 445 - has_operator( left, schema, name, right, result, desc ) should pass -ok 446 - has_operator( left, schema, name, right, result, desc ) should have the proper description -ok 447 - has_operator( left, schema, name, right, result, desc ) should have the proper diagnostics -ok 448 - has_operator( left, schema, name, right, result ) should pass -ok 449 - has_operator( left, schema, name, right, result ) should have the proper description -ok 450 - has_operator( left, schema, name, right, result ) should have the proper diagnostics -ok 451 - has_operator( left, name, right, result, desc ) should pass -ok 452 - has_operator( left, name, right, result, desc ) should have the proper description -ok 453 - has_operator( left, name, right, result, desc ) should have the proper diagnostics -ok 454 - has_operator( left, name, right, result ) should pass -ok 455 - has_operator( left, name, right, result ) should have the proper description -ok 456 - has_operator( left, name, right, result ) should have the proper diagnostics -ok 457 - has_operator( left, name, right, desc ) should pass -ok 458 - has_operator( left, name, right, desc ) should have the proper description -ok 459 - has_operator( left, name, right, desc ) should have the proper diagnostics -ok 460 - has_operator( left, name, right ) should pass -ok 461 - has_operator( left, name, right ) should have the proper description -ok 462 - has_operator( left, name, right ) should have the proper diagnostics -ok 463 - has_operator( left, schema, name, right, result, desc ) fail should fail -ok 464 - has_operator( left, schema, name, right, result, desc ) fail should have the proper description -ok 465 - has_operator( left, schema, name, right, result, desc ) fail should have the proper diagnostics -ok 466 - has_operator( left, schema, name, right, result ) fail should fail -ok 467 - has_operator( left, schema, name, right, result ) fail should have the proper description -ok 468 - has_operator( left, schema, name, right, result ) fail should have the proper diagnostics -ok 469 - has_operator( left, name, right, result, desc ) fail should fail -ok 470 - has_operator( left, name, right, result, desc ) fail should have the proper description -ok 471 - has_operator( left, name, right, result, desc ) fail should have the proper diagnostics -ok 472 - has_operator( left, name, right, result ) fail should fail -ok 473 - has_operator( left, name, right, result ) fail should have the proper description -ok 474 - has_operator( left, name, right, result ) fail should have the proper diagnostics -ok 475 - has_operator( left, name, right, desc ) fail should fail -ok 476 - has_operator( left, name, right, desc ) fail should have the proper description -ok 477 - has_operator( left, name, right, desc ) fail should have the proper diagnostics -ok 478 - has_operator( left, name, right ) fail should fail -ok 479 - has_operator( left, name, right ) fail should have the proper description -ok 480 - has_operator( left, name, right ) fail should have the proper diagnostics -ok 481 - has_leftop( schema, name, right, result, desc ) should pass -ok 482 - has_leftop( schema, name, right, result, desc ) should have the proper description -ok 483 - has_leftop( schema, name, right, result, desc ) should have the proper diagnostics -ok 484 - has_leftop( schema, name, right, result ) should pass -ok 485 - has_leftop( schema, name, right, result ) should have the proper description -ok 486 - has_leftop( schema, name, right, result ) should have the proper diagnostics -ok 487 - has_leftop( name, right, result, desc ) should pass -ok 488 - has_leftop( name, right, result, desc ) should have the proper description -ok 489 - has_leftop( name, right, result, desc ) should have the proper diagnostics -ok 490 - has_leftop( name, right, result ) should pass -ok 491 - has_leftop( name, right, result ) should have the proper description -ok 492 - has_leftop( name, right, result ) should have the proper diagnostics -ok 493 - has_leftop( name, right, desc ) should pass -ok 494 - has_leftop( name, right, desc ) should have the proper description -ok 495 - has_leftop( name, right, desc ) should have the proper diagnostics -ok 496 - has_leftop( name, right ) should pass -ok 497 - has_leftop( name, right ) should have the proper description -ok 498 - has_leftop( name, right ) should have the proper diagnostics -ok 499 - has_leftop( schema, name, right, result, desc ) fail should fail -ok 500 - has_leftop( schema, name, right, result, desc ) fail should have the proper description -ok 501 - has_leftop( schema, name, right, result, desc ) fail should have the proper diagnostics -ok 502 - has_leftop( schema, name, right, result ) fail should fail -ok 503 - has_leftop( schema, name, right, result ) fail should have the proper description -ok 504 - has_leftop( schema, name, right, result ) fail should have the proper diagnostics -ok 505 - has_leftop( name, right, result, desc ) fail should fail -ok 506 - has_leftop( name, right, result, desc ) fail should have the proper description -ok 507 - has_leftop( name, right, result, desc ) fail should have the proper diagnostics -ok 508 - has_leftop( name, right, result ) fail should fail -ok 509 - has_leftop( name, right, result ) fail should have the proper description -ok 510 - has_leftop( name, right, result ) fail should have the proper diagnostics -ok 511 - has_leftop( name, right, desc ) fail should fail -ok 512 - has_leftop( name, right, desc ) fail should have the proper description -ok 513 - has_leftop( name, right, desc ) fail should have the proper diagnostics -ok 514 - has_leftop( name, right ) fail should fail -ok 515 - has_leftop( name, right ) fail should have the proper description -ok 516 - has_leftop( name, right ) fail should have the proper diagnostics -ok 517 - has_rightop( left, schema, name, result, desc ) should pass -ok 518 - has_rightop( left, schema, name, result, desc ) should have the proper description -ok 519 - has_rightop( left, schema, name, result, desc ) should have the proper diagnostics -ok 520 - has_rightop( left, schema, name, result ) should pass -ok 521 - has_rightop( left, schema, name, result ) should have the proper description -ok 522 - has_rightop( left, schema, name, result ) should have the proper diagnostics -ok 523 - has_rightop( left, name, result, desc ) should pass -ok 524 - has_rightop( left, name, result, desc ) should have the proper description -ok 525 - has_rightop( left, name, result, desc ) should have the proper diagnostics -ok 526 - has_rightop( left, name, result ) should pass -ok 527 - has_rightop( left, name, result ) should have the proper description -ok 528 - has_rightop( left, name, result ) should have the proper diagnostics -ok 529 - has_rightop( left, name, desc ) should pass -ok 530 - has_rightop( left, name, desc ) should have the proper description -ok 531 - has_rightop( left, name, desc ) should have the proper diagnostics -ok 532 - has_rightop( left, name ) should pass -ok 533 - has_rightop( left, name ) should have the proper description -ok 534 - has_rightop( left, name ) should have the proper diagnostics -ok 535 - has_rightop( left, schema, name, result, desc ) fail should fail -ok 536 - has_rightop( left, schema, name, result, desc ) fail should have the proper description -ok 537 - has_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics -ok 538 - has_rightop( left, schema, name, result ) fail should fail -ok 539 - has_rightop( left, schema, name, result ) fail should have the proper description -ok 540 - has_rightop( left, schema, name, result ) fail should have the proper diagnostics -ok 541 - has_rightop( left, name, result, desc ) fail should fail -ok 542 - has_rightop( left, name, result, desc ) fail should have the proper description -ok 543 - has_rightop( left, name, result, desc ) fail should have the proper diagnostics -ok 544 - has_rightop( left, name, result ) fail should fail -ok 545 - has_rightop( left, name, result ) fail should have the proper description -ok 546 - has_rightop( left, name, result ) fail should have the proper diagnostics -ok 547 - has_rightop( left, name, desc ) fail should fail -ok 548 - has_rightop( left, name, desc ) fail should have the proper description -ok 549 - has_rightop( left, name, desc ) fail should have the proper diagnostics -ok 550 - has_rightop( left, name ) fail should fail -ok 551 - has_rightop( left, name ) fail should have the proper description -ok 552 - has_rightop( left, name ) fail should have the proper diagnostics -ok 553 - has_language(language) should pass -ok 554 - has_language(language) should have the proper description -ok 555 - has_language(language) should have the proper diagnostics -ok 556 - has_language(language, desc) should pass -ok 557 - has_language(language, desc) should have the proper description -ok 558 - has_language(language, desc) should have the proper diagnostics -ok 559 - has_language(nonexistent language) should fail -ok 560 - has_language(nonexistent language) should have the proper description -ok 561 - has_language(nonexistent language) should have the proper diagnostics -ok 562 - has_language(nonexistent language, desc) should fail -ok 563 - has_language(nonexistent language, desc) should have the proper description -ok 564 - has_language(nonexistent language, desc) should have the proper diagnostics -ok 565 - hasnt_language(language) should fail -ok 566 - hasnt_language(language) should have the proper description -ok 567 - hasnt_language(language) should have the proper diagnostics -ok 568 - hasnt_language(language, desc) should fail -ok 569 - hasnt_language(language, desc) should have the proper description -ok 570 - hasnt_language(language, desc) should have the proper diagnostics -ok 571 - hasnt_language(nonexistent language) should pass -ok 572 - hasnt_language(nonexistent language) should have the proper description -ok 573 - hasnt_language(nonexistent language) should have the proper diagnostics -ok 574 - hasnt_language(nonexistent language, desc) should pass -ok 575 - hasnt_language(nonexistent language, desc) should have the proper description -ok 576 - hasnt_language(nonexistent language, desc) should have the proper diagnostics -ok 577 - language_is_trusted(language, desc) should pass -ok 578 - language_is_trusted(language, desc) should have the proper description -ok 579 - language_is_trusted(language, desc) should have the proper diagnostics -ok 580 - language_is_trusted(language) should pass -ok 581 - language_is_trusted(language) should have the proper description -ok 582 - language_is_trusted(language) should have the proper diagnostics -ok 583 - language_is_trusted(language, desc) fail should fail -ok 584 - language_is_trusted(language, desc) fail should have the proper description -ok 585 - language_is_trusted(language, desc) fail should have the proper diagnostics -ok 586 - language_is_trusted(language, desc) non-existent should fail -ok 587 - language_is_trusted(language, desc) non-existent should have the proper description -ok 588 - language_is_trusted(language, desc) non-existent should have the proper diagnostics -ok 589 - has_opclass( schema, name, desc ) should pass -ok 590 - has_opclass( schema, name, desc ) should have the proper description -ok 591 - has_opclass( schema, name, desc ) should have the proper diagnostics -ok 592 - has_opclass( schema, name ) should pass -ok 593 - has_opclass( schema, name ) should have the proper description -ok 594 - has_opclass( schema, name ) should have the proper diagnostics -ok 595 - has_opclass( name, desc ) should pass -ok 596 - has_opclass( name, desc ) should have the proper description -ok 597 - has_opclass( name, desc ) should have the proper diagnostics -ok 598 - has_opclass( name ) should pass -ok 599 - has_opclass( name ) should have the proper description -ok 600 - has_opclass( name ) should have the proper diagnostics -ok 601 - has_opclass( schema, name, desc ) fail should fail -ok 602 - has_opclass( schema, name, desc ) fail should have the proper description -ok 603 - has_opclass( schema, name, desc ) fail should have the proper diagnostics -ok 604 - has_opclass( name, desc ) fail should fail -ok 605 - has_opclass( name, desc ) fail should have the proper description -ok 606 - has_opclass( name, desc ) fail should have the proper diagnostics -ok 607 - hasnt_opclass( schema, name, desc ) should fail -ok 608 - hasnt_opclass( schema, name, desc ) should have the proper description -ok 609 - hasnt_opclass( schema, name, desc ) should have the proper diagnostics -ok 610 - hasnt_opclass( schema, name ) should fail -ok 611 - hasnt_opclass( schema, name ) should have the proper description -ok 612 - hasnt_opclass( schema, name ) should have the proper diagnostics -ok 613 - hasnt_opclass( name, desc ) should fail -ok 614 - hasnt_opclass( name, desc ) should have the proper description -ok 615 - hasnt_opclass( name, desc ) should have the proper diagnostics -ok 616 - hasnt_opclass( name ) should fail -ok 617 - hasnt_opclass( name ) should have the proper description -ok 618 - hasnt_opclass( name ) should have the proper diagnostics -ok 619 - hasnt_opclass( schema, name, desc ) fail should pass -ok 620 - hasnt_opclass( schema, name, desc ) fail should have the proper description -ok 621 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnostics -ok 622 - hasnt_opclass( name, desc ) fail should pass -ok 623 - hasnt_opclass( name, desc ) fail should have the proper description -ok 624 - hasnt_opclass( name, desc ) fail should have the proper diagnostics -ok 625 - domain_type_is(schema, domain, schema, type, desc) should pass -ok 626 - domain_type_is(schema, domain, schema, type, desc) should have the proper description -ok 627 - domain_type_is(schema, domain, schema, type, desc) should have the proper diagnostics -ok 628 - domain_type_is(schema, domain, schema, type) should pass -ok 629 - domain_type_is(schema, domain, schema, type) should have the proper description -ok 630 - domain_type_is(schema, domain, schema, type) should have the proper diagnostics -ok 631 - domain_type_is(schema, domain, schema, type, desc) fail should fail -ok 632 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper description -ok 633 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper diagnostics -ok 634 - domain_type_is(schema, nondomain, schema, type, desc) should fail -ok 635 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper description -ok 636 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper diagnostics -ok 637 - domain_type_is(schema, type, schema, type, desc) fail should fail -ok 638 - domain_type_is(schema, type, schema, type, desc) fail should have the proper description -ok 639 - domain_type_is(schema, type, schema, type, desc) fail should have the proper diagnostics -ok 640 - domain_type_is(schema, domain, type, desc) should pass -ok 641 - domain_type_is(schema, domain, type, desc) should have the proper description -ok 642 - domain_type_is(schema, domain, type, desc) should have the proper diagnostics -ok 643 - domain_type_is(schema, domain, type) should pass -ok 644 - domain_type_is(schema, domain, type) should have the proper description -ok 645 - domain_type_is(schema, domain, type) should have the proper diagnostics -ok 646 - domain_type_is(schema, domain, type, desc) fail should fail -ok 647 - domain_type_is(schema, domain, type, desc) fail should have the proper description -ok 648 - domain_type_is(schema, domain, type, desc) fail should have the proper diagnostics -ok 649 - domain_type_is(schema, nondomain, type, desc) should fail -ok 650 - domain_type_is(schema, nondomain, type, desc) should have the proper description -ok 651 - domain_type_is(schema, nondomain, type, desc) should have the proper diagnostics -ok 652 - domain_type_is(schema, type, type, desc) fail should fail -ok 653 - domain_type_is(schema, type, type, desc) fail should have the proper description -ok 654 - domain_type_is(schema, type, type, desc) fail should have the proper diagnostics -ok 655 - domain_type_is(domain, type, desc) should pass -ok 656 - domain_type_is(domain, type, desc) should have the proper description -ok 657 - domain_type_is(domain, type, desc) should have the proper diagnostics -ok 658 - domain_type_is(domain, type) should pass -ok 659 - domain_type_is(domain, type) should have the proper description -ok 660 - domain_type_is(domain, type) should have the proper diagnostics -ok 661 - domain_type_is(domain, type, desc) fail should fail -ok 662 - domain_type_is(domain, type, desc) fail should have the proper description -ok 663 - domain_type_is(domain, type, desc) fail should have the proper diagnostics -ok 664 - domain_type_is(nondomain, type, desc) should fail -ok 665 - domain_type_is(nondomain, type, desc) should have the proper description -ok 666 - domain_type_is(nondomain, type, desc) should have the proper diagnostics -ok 667 - domain_type_is(type, type, desc) fail should fail -ok 668 - domain_type_is(type, type, desc) fail should have the proper description -ok 669 - domain_type_is(type, type, desc) fail should have the proper diagnostics -ok 670 - domain_type_isnt(schema, domain, schema, type, desc) should pass -ok 671 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper description -ok 672 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper diagnostics -ok 673 - domain_type_isnt(schema, domain, schema, type) should pass -ok 674 - domain_type_isnt(schema, domain, schema, type) should have the proper description -ok 675 - domain_type_isnt(schema, domain, schema, type) should have the proper diagnostics -ok 676 - domain_type_isnt(schema, domain, schema, type, desc) fail should fail -ok 677 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper description -ok 678 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper diagnostics -ok 679 - domain_type_isnt(schema, nondomain, schema, type, desc) should fail -ok 680 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper description -ok 681 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper diagnostics -ok 682 - domain_type_isnt(schema, type, schema, type, desc) should fail -ok 683 - domain_type_isnt(schema, type, schema, type, desc) should have the proper description -ok 684 - domain_type_isnt(schema, type, schema, type, desc) should have the proper diagnostics -ok 685 - domain_type_isnt(schema, domain, type, desc) should pass -ok 686 - domain_type_isnt(schema, domain, type, desc) should have the proper description -ok 687 - domain_type_isnt(schema, domain, type, desc) should have the proper diagnostics -ok 688 - domain_type_isnt(schema, domain, type) should pass -ok 689 - domain_type_isnt(schema, domain, type) should have the proper description -ok 690 - domain_type_isnt(schema, domain, type) should have the proper diagnostics -ok 691 - domain_type_isnt(schema, domain, type, desc) fail should fail -ok 692 - domain_type_isnt(schema, domain, type, desc) fail should have the proper description -ok 693 - domain_type_isnt(schema, domain, type, desc) fail should have the proper diagnostics -ok 694 - domain_type_isnt(schema, nondomain, type, desc) should fail -ok 695 - domain_type_isnt(schema, nondomain, type, desc) should have the proper description -ok 696 - domain_type_isnt(schema, nondomain, type, desc) should have the proper diagnostics -ok 697 - domain_type_isnt(schema, type, type, desc) should fail -ok 698 - domain_type_isnt(schema, type, type, desc) should have the proper description -ok 699 - domain_type_isnt(schema, type, type, desc) should have the proper diagnostics -ok 700 - domain_type_isnt(domain, type, desc) should pass -ok 701 - domain_type_isnt(domain, type, desc) should have the proper description -ok 702 - domain_type_isnt(domain, type, desc) should have the proper diagnostics -ok 703 - domain_type_isnt(domain, type) should pass -ok 704 - domain_type_isnt(domain, type) should have the proper description -ok 705 - domain_type_isnt(domain, type) should have the proper diagnostics -ok 706 - domain_type_isnt(domain, type, desc) fail should fail -ok 707 - domain_type_isnt(domain, type, desc) fail should have the proper description -ok 708 - domain_type_isnt(domain, type, desc) fail should have the proper diagnostics -ok 709 - domain_type_isnt(nondomain, type, desc) should fail -ok 710 - domain_type_isnt(nondomain, type, desc) should have the proper description -ok 711 - domain_type_isnt(nondomain, type, desc) should have the proper diagnostics -ok 712 - domain_type_isnt(type, type, desc) should fail -ok 713 - domain_type_isnt(type, type, desc) should have the proper description -ok 714 - domain_type_isnt(type, type, desc) should have the proper diagnostics -ok 715 - has_relation(non-existent relation) should fail -ok 716 - has_relation(non-existent relation) should have the proper description -ok 717 - has_relation(non-existent relation) should have the proper diagnostics -ok 718 - has_relation(non-existent schema, tab) should fail -ok 719 - has_relation(non-existent schema, tab) should have the proper description -ok 720 - has_relation(non-existent schema, tab) should have the proper diagnostics -ok 721 - has_relation(sch, non-existent relation, desc) should fail -ok 722 - has_relation(sch, non-existent relation, desc) should have the proper description -ok 723 - has_relation(sch, non-existent relation, desc) should have the proper diagnostics -ok 724 - has_relation(tab, desc) should pass -ok 725 - has_relation(tab, desc) should have the proper description -ok 726 - has_relation(tab, desc) should have the proper diagnostics -ok 727 - has_relation(sch, tab, desc) should pass -ok 728 - has_relation(sch, tab, desc) should have the proper description -ok 729 - has_relation(sch, tab, desc) should have the proper diagnostics -ok 730 - has_relation(sch, view, desc) should pass -ok 731 - has_relation(sch, view, desc) should have the proper description -ok 732 - has_relation(sch, view, desc) should have the proper diagnostics -ok 733 - has_relation(type, desc) should pass -ok 734 - has_relation(type, desc) should have the proper description -ok 735 - has_relation(type, desc) should have the proper diagnostics -ok 736 - hasnt_relation(non-existent relation) should pass -ok 737 - hasnt_relation(non-existent relation) should have the proper description -ok 738 - hasnt_relation(non-existent relation) should have the proper diagnostics -ok 739 - hasnt_relation(non-existent schema, tab) should pass -ok 740 - hasnt_relation(non-existent schema, tab) should have the proper description -ok 741 - hasnt_relation(non-existent schema, tab) should have the proper diagnostics -ok 742 - hasnt_relation(sch, non-existent tab, desc) should pass -ok 743 - hasnt_relation(sch, non-existent tab, desc) should have the proper description -ok 744 - hasnt_relation(sch, non-existent tab, desc) should have the proper diagnostics -ok 745 - hasnt_relation(tab, desc) should fail -ok 746 - hasnt_relation(tab, desc) should have the proper description -ok 747 - hasnt_relation(tab, desc) should have the proper diagnostics -ok 748 - hasnt_relation(sch, tab, desc) should fail -ok 749 - hasnt_relation(sch, tab, desc) should have the proper description -ok 750 - hasnt_relation(sch, tab, desc) should have the proper diagnostics -ok 751 - has_foreign_table(non-existent table) should fail -ok 752 - has_foreign_table(non-existent table) should have the proper description -ok 753 - has_foreign_table(non-existent table) should have the proper diagnostics -ok 754 - has_foreign_table(non-existent schema, tab) should fail -ok 755 - has_foreign_table(non-existent schema, tab) should have the proper description -ok 756 - has_foreign_table(non-existent schema, tab) should have the proper diagnostics -ok 757 - has_foreign_table(sch, non-existent table, desc) should fail -ok 758 - has_foreign_table(sch, non-existent table, desc) should have the proper description -ok 759 - has_foreign_table(sch, non-existent table, desc) should have the proper diagnostics -ok 760 - has_foreign_table(tab, desc) should pass -ok 761 - has_foreign_table(tab, desc) should have the proper description -ok 762 - has_foreign_table(tab, desc) should have the proper diagnostics -ok 763 - has_foreign_table(sch, tab, desc) should pass -ok 764 - has_foreign_table(sch, tab, desc) should have the proper description -ok 765 - has_foreign_table(sch, tab, desc) should have the proper diagnostics -ok 766 - has_foreign_table(sch, view, desc) should fail -ok 767 - has_foreign_table(sch, view, desc) should have the proper description -ok 768 - has_foreign_table(sch, view, desc) should have the proper diagnostics -ok 769 - has_foreign_table(type, desc) should fail -ok 770 - has_foreign_table(type, desc) should have the proper description -ok 771 - has_foreign_table(type, desc) should have the proper diagnostics -ok 772 - hasnt_foreign_table(non-existent table) should pass -ok 773 - hasnt_foreign_table(non-existent table) should have the proper description -ok 774 - hasnt_foreign_table(non-existent table) should have the proper diagnostics -ok 775 - hasnt_foreign_table(non-existent schema, tab) should pass -ok 776 - hasnt_foreign_table(non-existent schema, tab) should have the proper description -ok 777 - hasnt_foreign_table(non-existent schema, tab) should have the proper diagnostics -ok 778 - hasnt_foreign_table(sch, non-existent tab, desc) should pass -ok 779 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper description -ok 780 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper diagnostics -ok 781 - hasnt_foreign_table(tab, desc) should fail -ok 782 - hasnt_foreign_table(tab, desc) should have the proper description -ok 783 - hasnt_foreign_table(tab, desc) should have the proper diagnostics -ok 784 - hasnt_foreign_table(sch, tab, desc) should fail -ok 785 - hasnt_foreign_table(sch, tab, desc) should have the proper description -ok 786 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics +ok 55 - has_table(non-existent table, desc) should fail +ok 56 - has_table(non-existent table, desc) should have the proper description +ok 57 - has_table(non-existent table, desc) should have the proper diagnostics +ok 58 - has_table(sch, non-existent table, desc) should fail +ok 59 - has_table(sch, non-existent table, desc) should have the proper description +ok 60 - has_table(sch, non-existent table, desc) should have the proper diagnostics +ok 61 - has_table(tab, desc) should pass +ok 62 - has_table(tab, desc) should have the proper description +ok 63 - has_table(tab, desc) should have the proper diagnostics +ok 64 - has_table(sch, tab, desc) should pass +ok 65 - has_table(sch, tab, desc) should have the proper description +ok 66 - has_table(sch, tab, desc) should have the proper diagnostics +ok 67 - has_table(sch, view, desc) should fail +ok 68 - has_table(sch, view, desc) should have the proper description +ok 69 - has_table(sch, view, desc) should have the proper diagnostics +ok 70 - has_table(type, desc) should fail +ok 71 - has_table(type, desc) should have the proper description +ok 72 - has_table(type, desc) should have the proper diagnostics +ok 73 - hasnt_table(non-existent table) should pass +ok 74 - hasnt_table(non-existent table) should have the proper description +ok 75 - hasnt_table(non-existent table) should have the proper diagnostics +ok 76 - hasnt_table(non-existent schema, tab) should pass +ok 77 - hasnt_table(non-existent schema, tab) should have the proper description +ok 78 - hasnt_table(non-existent schema, tab) should have the proper diagnostics +ok 79 - hasnt_table(non-existent table, desc) should pass +ok 80 - hasnt_table(non-existent table, desc) should have the proper description +ok 81 - hasnt_table(non-existent table, desc) should have the proper diagnostics +ok 82 - hasnt_table(sch, non-existent tab, desc) should pass +ok 83 - hasnt_table(sch, non-existent tab, desc) should have the proper description +ok 84 - hasnt_table(sch, non-existent tab, desc) should have the proper diagnostics +ok 85 - hasnt_table(tab, desc) should fail +ok 86 - hasnt_table(tab, desc) should have the proper description +ok 87 - hasnt_table(tab, desc) should have the proper diagnostics +ok 88 - hasnt_table(sch, tab, desc) should fail +ok 89 - hasnt_table(sch, tab, desc) should have the proper description +ok 90 - hasnt_table(sch, tab, desc) should have the proper diagnostics +ok 91 - has_view(non-existent view) should fail +ok 92 - has_view(non-existent view) should have the proper description +ok 93 - has_view(non-existent view) should have the proper diagnostics +ok 94 - has_view(non-existent view, desc) should fail +ok 95 - has_view(non-existent view, desc) should have the proper description +ok 96 - has_view(non-existent view, desc) should have the proper diagnostics +ok 97 - has_view(sch, non-existtent view, desc) should fail +ok 98 - has_view(sch, non-existtent view, desc) should have the proper description +ok 99 - has_view(sch, non-existtent view, desc) should have the proper diagnostics +ok 100 - has_view(view, desc) should pass +ok 101 - has_view(view, desc) should have the proper description +ok 102 - has_view(view, desc) should have the proper diagnostics +ok 103 - has_view(sch, view, desc) should pass +ok 104 - has_view(sch, view, desc) should have the proper description +ok 105 - has_view(sch, view, desc) should have the proper diagnostics +ok 106 - hasnt_view(non-existent view) should pass +ok 107 - hasnt_view(non-existent view) should have the proper description +ok 108 - hasnt_view(non-existent view) should have the proper diagnostics +ok 109 - hasnt_view(non-existent view, desc) should pass +ok 110 - hasnt_view(non-existent view, desc) should have the proper description +ok 111 - hasnt_view(non-existent view, desc) should have the proper diagnostics +ok 112 - hasnt_view(sch, non-existtent view, desc) should pass +ok 113 - hasnt_view(sch, non-existtent view, desc) should have the proper description +ok 114 - hasnt_view(sch, non-existtent view, desc) should have the proper diagnostics +ok 115 - hasnt_view(view, desc) should fail +ok 116 - hasnt_view(view, desc) should have the proper description +ok 117 - hasnt_view(view, desc) should have the proper diagnostics +ok 118 - hasnt_view(sch, view, desc) should fail +ok 119 - hasnt_view(sch, view, desc) should have the proper description +ok 120 - hasnt_view(sch, view, desc) should have the proper diagnostics +ok 121 - has_sequence(non-existent sequence) should fail +ok 122 - has_sequence(non-existent sequence) should have the proper description +ok 123 - has_sequence(non-existent sequence) should have the proper diagnostics +ok 124 - has_sequence(non-existent sequence, desc) should fail +ok 125 - has_sequence(non-existent sequence, desc) should have the proper description +ok 126 - has_sequence(non-existent sequence, desc) should have the proper diagnostics +ok 127 - has_sequence(sch, non-existtent sequence, desc) should fail +ok 128 - has_sequence(sch, non-existtent sequence, desc) should have the proper description +ok 129 - has_sequence(sch, non-existtent sequence, desc) should have the proper diagnostics +ok 130 - has_sequence(sequence, desc) should pass +ok 131 - has_sequence(sequence, desc) should have the proper description +ok 132 - has_sequence(sequence, desc) should have the proper diagnostics +ok 133 - has_sequence(sch, sequence, desc) should pass +ok 134 - has_sequence(sch, sequence, desc) should have the proper description +ok 135 - has_sequence(sch, sequence, desc) should have the proper diagnostics +ok 136 - hasnt_sequence(non-existent sequence) should pass +ok 137 - hasnt_sequence(non-existent sequence) should have the proper description +ok 138 - hasnt_sequence(non-existent sequence) should have the proper diagnostics +ok 139 - hasnt_sequence(non-existent sequence, desc) should pass +ok 140 - hasnt_sequence(non-existent sequence, desc) should have the proper description +ok 141 - hasnt_sequence(non-existent sequence, desc) should have the proper diagnostics +ok 142 - hasnt_sequence(sch, non-existtent sequence, desc) should pass +ok 143 - hasnt_sequence(sch, non-existtent sequence, desc) should have the proper description +ok 144 - hasnt_sequence(sch, non-existtent sequence, desc) should have the proper diagnostics +ok 145 - hasnt_sequence(sequence, desc) should fail +ok 146 - hasnt_sequence(sequence, desc) should have the proper description +ok 147 - hasnt_sequence(sequence, desc) should have the proper diagnostics +ok 148 - hasnt_sequence(sch, sequence, desc) should fail +ok 149 - hasnt_sequence(sch, sequence, desc) should have the proper description +ok 150 - hasnt_sequence(sch, sequence, desc) should have the proper diagnostics +ok 151 - has_composite(non-existent composite type) should fail +ok 152 - has_composite(non-existent composite type) should have the proper description +ok 153 - has_composite(non-existent composite type) should have the proper diagnostics +ok 154 - has_composite(non-existent schema, tab) should fail +ok 155 - has_composite(non-existent schema, tab) should have the proper description +ok 156 - has_composite(non-existent schema, tab) should have the proper diagnostics +ok 157 - has_composite(sch, non-existent composite type, desc) should fail +ok 158 - has_composite(sch, non-existent composite type, desc) should have the proper description +ok 159 - has_composite(sch, non-existent composite type, desc) should have the proper diagnostics +ok 160 - has_composite(tab, desc) should pass +ok 161 - has_composite(tab, desc) should have the proper description +ok 162 - has_composite(tab, desc) should have the proper diagnostics +ok 163 - has_composite(sch, tab, desc) should pass +ok 164 - has_composite(sch, tab, desc) should have the proper description +ok 165 - has_composite(sch, tab, desc) should have the proper diagnostics +ok 166 - has_composite(sch, view, desc) should fail +ok 167 - has_composite(sch, view, desc) should have the proper description +ok 168 - has_composite(sch, view, desc) should have the proper diagnostics +ok 169 - has_composite(type, desc) should fail +ok 170 - has_composite(type, desc) should have the proper description +ok 171 - has_composite(type, desc) should have the proper diagnostics +ok 172 - hasnt_composite(non-existent composite type) should pass +ok 173 - hasnt_composite(non-existent composite type) should have the proper description +ok 174 - hasnt_composite(non-existent composite type) should have the proper diagnostics +ok 175 - hasnt_composite(non-existent schema, tab) should pass +ok 176 - hasnt_composite(non-existent schema, tab) should have the proper description +ok 177 - hasnt_composite(non-existent schema, tab) should have the proper diagnostics +ok 178 - hasnt_composite(sch, non-existent tab, desc) should pass +ok 179 - hasnt_composite(sch, non-existent tab, desc) should have the proper description +ok 180 - hasnt_composite(sch, non-existent tab, desc) should have the proper diagnostics +ok 181 - hasnt_composite(tab, desc) should fail +ok 182 - hasnt_composite(tab, desc) should have the proper description +ok 183 - hasnt_composite(tab, desc) should have the proper diagnostics +ok 184 - hasnt_composite(sch, tab, desc) should fail +ok 185 - hasnt_composite(sch, tab, desc) should have the proper description +ok 186 - hasnt_composite(sch, tab, desc) should have the proper diagnostics +ok 187 - has_type(type) should pass +ok 188 - has_type(type) should have the proper description +ok 189 - has_type(type) should have the proper diagnostics +ok 190 - has_type(type, desc) should pass +ok 191 - has_type(type, desc) should have the proper description +ok 192 - has_type(type, desc) should have the proper diagnostics +ok 193 - has_type(scheam, type) should pass +ok 194 - has_type(scheam, type) should have the proper description +ok 195 - has_type(scheam, type) should have the proper diagnostics +ok 196 - has_type(schema, type, desc) should pass +ok 197 - has_type(schema, type, desc) should have the proper description +ok 198 - has_type(schema, type, desc) should have the proper diagnostics +ok 199 - has_type(myType) should pass +ok 200 - has_type(myType) should have the proper description +ok 201 - has_type(myType) should have the proper diagnostics +ok 202 - has_type(myType, desc) should pass +ok 203 - has_type(myType, desc) should have the proper description +ok 204 - has_type(myType, desc) should have the proper diagnostics +ok 205 - has_type(scheam, myType) should pass +ok 206 - has_type(scheam, myType) should have the proper description +ok 207 - has_type(scheam, myType) should have the proper diagnostics +ok 208 - has_type(schema, myType, desc) should pass +ok 209 - has_type(schema, myType, desc) should have the proper description +ok 210 - has_type(schema, myType, desc) should have the proper diagnostics +ok 211 - has_type(type) should fail +ok 212 - has_type(type) should have the proper description +ok 213 - has_type(type) should have the proper diagnostics +ok 214 - has_type(type, desc) should fail +ok 215 - has_type(type, desc) should have the proper description +ok 216 - has_type(type, desc) should have the proper diagnostics +ok 217 - has_type(scheam, type) should fail +ok 218 - has_type(scheam, type) should have the proper description +ok 219 - has_type(scheam, type) should have the proper diagnostics +ok 220 - has_type(schema, type, desc) should fail +ok 221 - has_type(schema, type, desc) should have the proper description +ok 222 - has_type(schema, type, desc) should have the proper diagnostics +ok 223 - has_type(domain) should pass +ok 224 - has_type(domain) should have the proper description +ok 225 - has_type(domain) should have the proper diagnostics +ok 226 - has_type(myDomain) should pass +ok 227 - has_type(myDomain) should have the proper description +ok 228 - has_type(myDomain) should have the proper diagnostics +ok 229 - hasnt_type(type) should pass +ok 230 - hasnt_type(type) should have the proper description +ok 231 - hasnt_type(type) should have the proper diagnostics +ok 232 - hasnt_type(type, desc) should pass +ok 233 - hasnt_type(type, desc) should have the proper description +ok 234 - hasnt_type(type, desc) should have the proper diagnostics +ok 235 - hasnt_type(scheam, type) should pass +ok 236 - hasnt_type(scheam, type) should have the proper description +ok 237 - hasnt_type(scheam, type) should have the proper diagnostics +ok 238 - hasnt_type(schema, type, desc) should pass +ok 239 - hasnt_type(schema, type, desc) should have the proper description +ok 240 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 241 - hasnt_type(type) should fail +ok 242 - hasnt_type(type) should have the proper description +ok 243 - hasnt_type(type) should have the proper diagnostics +ok 244 - hasnt_type(type, desc) should fail +ok 245 - hasnt_type(type, desc) should have the proper description +ok 246 - hasnt_type(type, desc) should have the proper diagnostics +ok 247 - hasnt_type(scheam, type) should fail +ok 248 - hasnt_type(scheam, type) should have the proper description +ok 249 - hasnt_type(scheam, type) should have the proper diagnostics +ok 250 - hasnt_type(schema, type, desc) should fail +ok 251 - hasnt_type(schema, type, desc) should have the proper description +ok 252 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 253 - has_domain(domain) should pass +ok 254 - has_domain(domain) should have the proper description +ok 255 - has_domain(domain) should have the proper diagnostics +ok 256 - has_domain(domain, desc) should pass +ok 257 - has_domain(domain, desc) should have the proper description +ok 258 - has_domain(domain, desc) should have the proper diagnostics +ok 259 - has_domain(scheam, domain) should pass +ok 260 - has_domain(scheam, domain) should have the proper description +ok 261 - has_domain(scheam, domain) should have the proper diagnostics +ok 262 - has_domain(schema, domain, desc) should pass +ok 263 - has_domain(schema, domain, desc) should have the proper description +ok 264 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 265 - has_domain(myDomain) should pass +ok 266 - has_domain(myDomain) should have the proper description +ok 267 - has_domain(myDomain) should have the proper diagnostics +ok 268 - has_domain(myDomain, desc) should pass +ok 269 - has_domain(myDomain, desc) should have the proper description +ok 270 - has_domain(myDomain, desc) should have the proper diagnostics +ok 271 - has_domain(scheam, myDomain) should pass +ok 272 - has_domain(scheam, myDomain) should have the proper description +ok 273 - has_domain(scheam, myDomain) should have the proper diagnostics +ok 274 - has_domain(schema, myDomain, desc) should pass +ok 275 - has_domain(schema, myDomain, desc) should have the proper description +ok 276 - has_domain(schema, myDomain, desc) should have the proper diagnostics +ok 277 - has_domain(domain) should fail +ok 278 - has_domain(domain) should have the proper description +ok 279 - has_domain(domain) should have the proper diagnostics +ok 280 - has_domain(domain, desc) should fail +ok 281 - has_domain(domain, desc) should have the proper description +ok 282 - has_domain(domain, desc) should have the proper diagnostics +ok 283 - has_domain(scheam, domain) should fail +ok 284 - has_domain(scheam, domain) should have the proper description +ok 285 - has_domain(scheam, domain) should have the proper diagnostics +ok 286 - has_domain(schema, domain, desc) should fail +ok 287 - has_domain(schema, domain, desc) should have the proper description +ok 288 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 289 - hasnt_domain(domain) should pass +ok 290 - hasnt_domain(domain) should have the proper description +ok 291 - hasnt_domain(domain) should have the proper diagnostics +ok 292 - hasnt_domain(domain, desc) should pass +ok 293 - hasnt_domain(domain, desc) should have the proper description +ok 294 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 295 - hasnt_domain(scheam, domain) should pass +ok 296 - hasnt_domain(scheam, domain) should have the proper description +ok 297 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 298 - hasnt_domain(schema, domain, desc) should pass +ok 299 - hasnt_domain(schema, domain, desc) should have the proper description +ok 300 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 301 - hasnt_domain(domain) should fail +ok 302 - hasnt_domain(domain) should have the proper description +ok 303 - hasnt_domain(domain) should have the proper diagnostics +ok 304 - hasnt_domain(domain, desc) should fail +ok 305 - hasnt_domain(domain, desc) should have the proper description +ok 306 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 307 - hasnt_domain(scheam, domain) should fail +ok 308 - hasnt_domain(scheam, domain) should have the proper description +ok 309 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 310 - hasnt_domain(schema, domain, desc) should fail +ok 311 - hasnt_domain(schema, domain, desc) should have the proper description +ok 312 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 313 - has_column(non-existent tab, col) should fail +ok 314 - has_column(non-existent tab, col) should have the proper description +ok 315 - has_column(non-existent tab, col) should have the proper diagnostics +ok 316 - has_column(non-existent tab, col, desc) should fail +ok 317 - has_column(non-existent tab, col, desc) should have the proper description +ok 318 - has_column(non-existent tab, col, desc) should have the proper diagnostics +ok 319 - has_column(non-existent sch, tab, col, desc) should fail +ok 320 - has_column(non-existent sch, tab, col, desc) should have the proper description +ok 321 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 322 - has_column(table, column) should pass +ok 323 - has_column(table, column) should have the proper description +ok 324 - has_column(table, column) should have the proper diagnostics +ok 325 - has_column(sch, tab, col, desc) should pass +ok 326 - has_column(sch, tab, col, desc) should have the proper description +ok 327 - has_column(sch, tab, col, desc) should have the proper diagnostics +ok 328 - has_column(table, camleCase column) should pass +ok 329 - has_column(table, camleCase column) should have the proper description +ok 330 - has_column(table, camleCase column) should have the proper diagnostics +ok 331 - has_column(view, column) should pass +ok 332 - has_column(view, column) should have the proper description +ok 333 - has_column(view, column) should have the proper diagnostics +ok 334 - has_column(type, column) should pass +ok 335 - has_column(type, column) should have the proper description +ok 336 - has_column(type, column) should have the proper diagnostics +ok 337 - hasnt_column(non-existent tab, col) should pass +ok 338 - hasnt_column(non-existent tab, col) should have the proper description +ok 339 - hasnt_column(non-existent tab, col) should have the proper diagnostics +ok 340 - hasnt_column(non-existent tab, col, desc) should pass +ok 341 - hasnt_column(non-existent tab, col, desc) should have the proper description +ok 342 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics +ok 343 - hasnt_column(non-existent sch, tab, col, desc) should pass +ok 344 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description +ok 345 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 346 - hasnt_column(table, column) should fail +ok 347 - hasnt_column(table, column) should have the proper description +ok 348 - hasnt_column(table, column) should have the proper diagnostics +ok 349 - hasnt_column(sch, tab, col, desc) should fail +ok 350 - hasnt_column(sch, tab, col, desc) should have the proper description +ok 351 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics +ok 352 - hasnt_column(view, column) should pass +ok 353 - hasnt_column(view, column) should have the proper description +ok 354 - hasnt_column(view, column) should have the proper diagnostics +ok 355 - hasnt_column(type, column) should pass +ok 356 - hasnt_column(type, column) should have the proper description +ok 357 - hasnt_column(type, column) should have the proper diagnostics +ok 358 - has_cast( src, targ, schema, func, desc) should pass +ok 359 - has_cast( src, targ, schema, func, desc) should have the proper description +ok 360 - has_cast( src, targ, schema, func, desc) should have the proper diagnostics +ok 361 - has_cast( src, targ, schema, func ) should pass +ok 362 - has_cast( src, targ, schema, func ) should have the proper description +ok 363 - has_cast( src, targ, schema, func ) should have the proper diagnostics +ok 364 - has_cast( src, targ, func, desc ) should pass +ok 365 - has_cast( src, targ, func, desc ) should have the proper description +ok 366 - has_cast( src, targ, func, desc ) should have the proper diagnostics +ok 367 - has_cast( src, targ, func) should pass +ok 368 - has_cast( src, targ, func) should have the proper description +ok 369 - has_cast( src, targ, func) should have the proper diagnostics +ok 370 - has_cast( src, targ, desc ) should pass +ok 371 - has_cast( src, targ, desc ) should have the proper description +ok 372 - has_cast( src, targ, desc ) should have the proper diagnostics +ok 373 - has_cast( src, targ ) should pass +ok 374 - has_cast( src, targ ) should have the proper description +ok 375 - has_cast( src, targ ) should have the proper diagnostics +ok 376 - has_cast( src, targ, schema, func, desc) fail should fail +ok 377 - has_cast( src, targ, schema, func, desc) fail should have the proper description +ok 378 - has_cast( src, targ, schema, func, desc) fail should have the proper diagnostics +ok 379 - has_cast( src, targ, func, desc ) fail should fail +ok 380 - has_cast( src, targ, func, desc ) fail should have the proper description +ok 381 - has_cast( src, targ, func, desc ) fail should have the proper diagnostics +ok 382 - has_cast( src, targ, desc ) fail should fail +ok 383 - has_cast( src, targ, desc ) fail should have the proper description +ok 384 - has_cast( src, targ, desc ) fail should have the proper diagnostics +ok 385 - hasnt_cast( src, targ, schema, func, desc) should fail +ok 386 - hasnt_cast( src, targ, schema, func, desc) should have the proper description +ok 387 - hasnt_cast( src, targ, schema, func, desc) should have the proper diagnostics +ok 388 - hasnt_cast( src, targ, schema, func ) should fail +ok 389 - hasnt_cast( src, targ, schema, func ) should have the proper description +ok 390 - hasnt_cast( src, targ, schema, func ) should have the proper diagnostics +ok 391 - hasnt_cast( src, targ, func, desc ) should fail +ok 392 - hasnt_cast( src, targ, func, desc ) should have the proper description +ok 393 - hasnt_cast( src, targ, func, desc ) should have the proper diagnostics +ok 394 - hasnt_cast( src, targ, func) should fail +ok 395 - hasnt_cast( src, targ, func) should have the proper description +ok 396 - hasnt_cast( src, targ, func) should have the proper diagnostics +ok 397 - hasnt_cast( src, targ, desc ) should fail +ok 398 - hasnt_cast( src, targ, desc ) should have the proper description +ok 399 - hasnt_cast( src, targ, desc ) should have the proper diagnostics +ok 400 - hasnt_cast( src, targ ) should fail +ok 401 - hasnt_cast( src, targ ) should have the proper description +ok 402 - hasnt_cast( src, targ ) should have the proper diagnostics +ok 403 - hasnt_cast( src, targ, schema, func, desc) fail should pass +ok 404 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper description +ok 405 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper diagnostics +ok 406 - hasnt_cast( src, targ, func, desc ) fail should pass +ok 407 - hasnt_cast( src, targ, func, desc ) fail should have the proper description +ok 408 - hasnt_cast( src, targ, func, desc ) fail should have the proper diagnostics +ok 409 - hasnt_cast( src, targ, desc ) fail should pass +ok 410 - hasnt_cast( src, targ, desc ) fail should have the proper description +ok 411 - hasnt_cast( src, targ, desc ) fail should have the proper diagnostics +ok 412 - cast_context_is( src, targ, context, desc ) should pass +ok 413 - cast_context_is( src, targ, context, desc ) should have the proper description +ok 414 - cast_context_is( src, targ, context, desc ) should have the proper diagnostics +ok 415 - cast_context_is( src, targ, context ) should pass +ok 416 - cast_context_is( src, targ, context ) should have the proper description +ok 417 - cast_context_is( src, targ, context ) should have the proper diagnostics +ok 418 - cast_context_is( src, targ, i, desc ) should pass +ok 419 - cast_context_is( src, targ, i, desc ) should have the proper description +ok 420 - cast_context_is( src, targ, i, desc ) should have the proper diagnostics +ok 421 - cast_context_is( src, targ, IMPL, desc ) should pass +ok 422 - cast_context_is( src, targ, IMPL, desc ) should have the proper description +ok 423 - cast_context_is( src, targ, IMPL, desc ) should have the proper diagnostics +ok 424 - cast_context_is( src, targ, assignment, desc ) should pass +ok 425 - cast_context_is( src, targ, assignment, desc ) should have the proper description +ok 426 - cast_context_is( src, targ, assignment, desc ) should have the proper diagnostics +ok 427 - cast_context_is( src, targ, a, desc ) should pass +ok 428 - cast_context_is( src, targ, a, desc ) should have the proper description +ok 429 - cast_context_is( src, targ, a, desc ) should have the proper diagnostics +ok 430 - cast_context_is( src, targ, ASS, desc ) should pass +ok 431 - cast_context_is( src, targ, ASS, desc ) should have the proper description +ok 432 - cast_context_is( src, targ, ASS, desc ) should have the proper diagnostics +ok 433 - cast_context_is( src, targ, explicit, desc ) should pass +ok 434 - cast_context_is( src, targ, explicit, desc ) should have the proper description +ok 435 - cast_context_is( src, targ, explicit, desc ) should have the proper diagnostics +ok 436 - cast_context_is( src, targ, e, desc ) should pass +ok 437 - cast_context_is( src, targ, e, desc ) should have the proper description +ok 438 - cast_context_is( src, targ, e, desc ) should have the proper diagnostics +ok 439 - cast_context_is( src, targ, EX, desc ) should pass +ok 440 - cast_context_is( src, targ, EX, desc ) should have the proper description +ok 441 - cast_context_is( src, targ, EX, desc ) should have the proper diagnostics +ok 442 - cast_context_is( src, targ, context, desc ) fail should fail +ok 443 - cast_context_is( src, targ, context, desc ) fail should have the proper description +ok 444 - cast_context_is( src, targ, context, desc ) fail should have the proper diagnostics +ok 445 - cast_context_is( src, targ, context ) fail should fail +ok 446 - cast_context_is( src, targ, context ) fail should have the proper description +ok 447 - cast_context_is( src, targ, context ) fail should have the proper diagnostics +ok 448 - cast_context_is( src, targ, context, desc ) noexist should fail +ok 449 - cast_context_is( src, targ, context, desc ) noexist should have the proper description +ok 450 - cast_context_is( src, targ, context, desc ) noexist should have the proper diagnostics +ok 451 - has_operator( left, schema, name, right, result, desc ) should pass +ok 452 - has_operator( left, schema, name, right, result, desc ) should have the proper description +ok 453 - has_operator( left, schema, name, right, result, desc ) should have the proper diagnostics +ok 454 - has_operator( left, schema, name, right, result ) should pass +ok 455 - has_operator( left, schema, name, right, result ) should have the proper description +ok 456 - has_operator( left, schema, name, right, result ) should have the proper diagnostics +ok 457 - has_operator( left, name, right, result, desc ) should pass +ok 458 - has_operator( left, name, right, result, desc ) should have the proper description +ok 459 - has_operator( left, name, right, result, desc ) should have the proper diagnostics +ok 460 - has_operator( left, name, right, result ) should pass +ok 461 - has_operator( left, name, right, result ) should have the proper description +ok 462 - has_operator( left, name, right, result ) should have the proper diagnostics +ok 463 - has_operator( left, name, right, desc ) should pass +ok 464 - has_operator( left, name, right, desc ) should have the proper description +ok 465 - has_operator( left, name, right, desc ) should have the proper diagnostics +ok 466 - has_operator( left, name, right ) should pass +ok 467 - has_operator( left, name, right ) should have the proper description +ok 468 - has_operator( left, name, right ) should have the proper diagnostics +ok 469 - has_operator( left, schema, name, right, result, desc ) fail should fail +ok 470 - has_operator( left, schema, name, right, result, desc ) fail should have the proper description +ok 471 - has_operator( left, schema, name, right, result, desc ) fail should have the proper diagnostics +ok 472 - has_operator( left, schema, name, right, result ) fail should fail +ok 473 - has_operator( left, schema, name, right, result ) fail should have the proper description +ok 474 - has_operator( left, schema, name, right, result ) fail should have the proper diagnostics +ok 475 - has_operator( left, name, right, result, desc ) fail should fail +ok 476 - has_operator( left, name, right, result, desc ) fail should have the proper description +ok 477 - has_operator( left, name, right, result, desc ) fail should have the proper diagnostics +ok 478 - has_operator( left, name, right, result ) fail should fail +ok 479 - has_operator( left, name, right, result ) fail should have the proper description +ok 480 - has_operator( left, name, right, result ) fail should have the proper diagnostics +ok 481 - has_operator( left, name, right, desc ) fail should fail +ok 482 - has_operator( left, name, right, desc ) fail should have the proper description +ok 483 - has_operator( left, name, right, desc ) fail should have the proper diagnostics +ok 484 - has_operator( left, name, right ) fail should fail +ok 485 - has_operator( left, name, right ) fail should have the proper description +ok 486 - has_operator( left, name, right ) fail should have the proper diagnostics +ok 487 - has_leftop( schema, name, right, result, desc ) should pass +ok 488 - has_leftop( schema, name, right, result, desc ) should have the proper description +ok 489 - has_leftop( schema, name, right, result, desc ) should have the proper diagnostics +ok 490 - has_leftop( schema, name, right, result ) should pass +ok 491 - has_leftop( schema, name, right, result ) should have the proper description +ok 492 - has_leftop( schema, name, right, result ) should have the proper diagnostics +ok 493 - has_leftop( name, right, result, desc ) should pass +ok 494 - has_leftop( name, right, result, desc ) should have the proper description +ok 495 - has_leftop( name, right, result, desc ) should have the proper diagnostics +ok 496 - has_leftop( name, right, result ) should pass +ok 497 - has_leftop( name, right, result ) should have the proper description +ok 498 - has_leftop( name, right, result ) should have the proper diagnostics +ok 499 - has_leftop( name, right, desc ) should pass +ok 500 - has_leftop( name, right, desc ) should have the proper description +ok 501 - has_leftop( name, right, desc ) should have the proper diagnostics +ok 502 - has_leftop( name, right ) should pass +ok 503 - has_leftop( name, right ) should have the proper description +ok 504 - has_leftop( name, right ) should have the proper diagnostics +ok 505 - has_leftop( schema, name, right, result, desc ) fail should fail +ok 506 - has_leftop( schema, name, right, result, desc ) fail should have the proper description +ok 507 - has_leftop( schema, name, right, result, desc ) fail should have the proper diagnostics +ok 508 - has_leftop( schema, name, right, result ) fail should fail +ok 509 - has_leftop( schema, name, right, result ) fail should have the proper description +ok 510 - has_leftop( schema, name, right, result ) fail should have the proper diagnostics +ok 511 - has_leftop( name, right, result, desc ) fail should fail +ok 512 - has_leftop( name, right, result, desc ) fail should have the proper description +ok 513 - has_leftop( name, right, result, desc ) fail should have the proper diagnostics +ok 514 - has_leftop( name, right, result ) fail should fail +ok 515 - has_leftop( name, right, result ) fail should have the proper description +ok 516 - has_leftop( name, right, result ) fail should have the proper diagnostics +ok 517 - has_leftop( name, right, desc ) fail should fail +ok 518 - has_leftop( name, right, desc ) fail should have the proper description +ok 519 - has_leftop( name, right, desc ) fail should have the proper diagnostics +ok 520 - has_leftop( name, right ) fail should fail +ok 521 - has_leftop( name, right ) fail should have the proper description +ok 522 - has_leftop( name, right ) fail should have the proper diagnostics +ok 523 - has_rightop( left, schema, name, result, desc ) should pass +ok 524 - has_rightop( left, schema, name, result, desc ) should have the proper description +ok 525 - has_rightop( left, schema, name, result, desc ) should have the proper diagnostics +ok 526 - has_rightop( left, schema, name, result ) should pass +ok 527 - has_rightop( left, schema, name, result ) should have the proper description +ok 528 - has_rightop( left, schema, name, result ) should have the proper diagnostics +ok 529 - has_rightop( left, name, result, desc ) should pass +ok 530 - has_rightop( left, name, result, desc ) should have the proper description +ok 531 - has_rightop( left, name, result, desc ) should have the proper diagnostics +ok 532 - has_rightop( left, name, result ) should pass +ok 533 - has_rightop( left, name, result ) should have the proper description +ok 534 - has_rightop( left, name, result ) should have the proper diagnostics +ok 535 - has_rightop( left, name, desc ) should pass +ok 536 - has_rightop( left, name, desc ) should have the proper description +ok 537 - has_rightop( left, name, desc ) should have the proper diagnostics +ok 538 - has_rightop( left, name ) should pass +ok 539 - has_rightop( left, name ) should have the proper description +ok 540 - has_rightop( left, name ) should have the proper diagnostics +ok 541 - has_rightop( left, schema, name, result, desc ) fail should fail +ok 542 - has_rightop( left, schema, name, result, desc ) fail should have the proper description +ok 543 - has_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics +ok 544 - has_rightop( left, schema, name, result ) fail should fail +ok 545 - has_rightop( left, schema, name, result ) fail should have the proper description +ok 546 - has_rightop( left, schema, name, result ) fail should have the proper diagnostics +ok 547 - has_rightop( left, name, result, desc ) fail should fail +ok 548 - has_rightop( left, name, result, desc ) fail should have the proper description +ok 549 - has_rightop( left, name, result, desc ) fail should have the proper diagnostics +ok 550 - has_rightop( left, name, result ) fail should fail +ok 551 - has_rightop( left, name, result ) fail should have the proper description +ok 552 - has_rightop( left, name, result ) fail should have the proper diagnostics +ok 553 - has_rightop( left, name, desc ) fail should fail +ok 554 - has_rightop( left, name, desc ) fail should have the proper description +ok 555 - has_rightop( left, name, desc ) fail should have the proper diagnostics +ok 556 - has_rightop( left, name ) fail should fail +ok 557 - has_rightop( left, name ) fail should have the proper description +ok 558 - has_rightop( left, name ) fail should have the proper diagnostics +ok 559 - has_language(language) should pass +ok 560 - has_language(language) should have the proper description +ok 561 - has_language(language) should have the proper diagnostics +ok 562 - has_language(language, desc) should pass +ok 563 - has_language(language, desc) should have the proper description +ok 564 - has_language(language, desc) should have the proper diagnostics +ok 565 - has_language(nonexistent language) should fail +ok 566 - has_language(nonexistent language) should have the proper description +ok 567 - has_language(nonexistent language) should have the proper diagnostics +ok 568 - has_language(nonexistent language, desc) should fail +ok 569 - has_language(nonexistent language, desc) should have the proper description +ok 570 - has_language(nonexistent language, desc) should have the proper diagnostics +ok 571 - hasnt_language(language) should fail +ok 572 - hasnt_language(language) should have the proper description +ok 573 - hasnt_language(language) should have the proper diagnostics +ok 574 - hasnt_language(language, desc) should fail +ok 575 - hasnt_language(language, desc) should have the proper description +ok 576 - hasnt_language(language, desc) should have the proper diagnostics +ok 577 - hasnt_language(nonexistent language) should pass +ok 578 - hasnt_language(nonexistent language) should have the proper description +ok 579 - hasnt_language(nonexistent language) should have the proper diagnostics +ok 580 - hasnt_language(nonexistent language, desc) should pass +ok 581 - hasnt_language(nonexistent language, desc) should have the proper description +ok 582 - hasnt_language(nonexistent language, desc) should have the proper diagnostics +ok 583 - language_is_trusted(language, desc) should pass +ok 584 - language_is_trusted(language, desc) should have the proper description +ok 585 - language_is_trusted(language, desc) should have the proper diagnostics +ok 586 - language_is_trusted(language) should pass +ok 587 - language_is_trusted(language) should have the proper description +ok 588 - language_is_trusted(language) should have the proper diagnostics +ok 589 - language_is_trusted(language, desc) fail should fail +ok 590 - language_is_trusted(language, desc) fail should have the proper description +ok 591 - language_is_trusted(language, desc) fail should have the proper diagnostics +ok 592 - language_is_trusted(language, desc) non-existent should fail +ok 593 - language_is_trusted(language, desc) non-existent should have the proper description +ok 594 - language_is_trusted(language, desc) non-existent should have the proper diagnostics +ok 595 - has_opclass( schema, name, desc ) should pass +ok 596 - has_opclass( schema, name, desc ) should have the proper description +ok 597 - has_opclass( schema, name, desc ) should have the proper diagnostics +ok 598 - has_opclass( schema, name ) should pass +ok 599 - has_opclass( schema, name ) should have the proper description +ok 600 - has_opclass( schema, name ) should have the proper diagnostics +ok 601 - has_opclass( name, desc ) should pass +ok 602 - has_opclass( name, desc ) should have the proper description +ok 603 - has_opclass( name, desc ) should have the proper diagnostics +ok 604 - has_opclass( name ) should pass +ok 605 - has_opclass( name ) should have the proper description +ok 606 - has_opclass( name ) should have the proper diagnostics +ok 607 - has_opclass( schema, name, desc ) fail should fail +ok 608 - has_opclass( schema, name, desc ) fail should have the proper description +ok 609 - has_opclass( schema, name, desc ) fail should have the proper diagnostics +ok 610 - has_opclass( name, desc ) fail should fail +ok 611 - has_opclass( name, desc ) fail should have the proper description +ok 612 - has_opclass( name, desc ) fail should have the proper diagnostics +ok 613 - hasnt_opclass( schema, name, desc ) should fail +ok 614 - hasnt_opclass( schema, name, desc ) should have the proper description +ok 615 - hasnt_opclass( schema, name, desc ) should have the proper diagnostics +ok 616 - hasnt_opclass( schema, name ) should fail +ok 617 - hasnt_opclass( schema, name ) should have the proper description +ok 618 - hasnt_opclass( schema, name ) should have the proper diagnostics +ok 619 - hasnt_opclass( name, desc ) should fail +ok 620 - hasnt_opclass( name, desc ) should have the proper description +ok 621 - hasnt_opclass( name, desc ) should have the proper diagnostics +ok 622 - hasnt_opclass( name ) should fail +ok 623 - hasnt_opclass( name ) should have the proper description +ok 624 - hasnt_opclass( name ) should have the proper diagnostics +ok 625 - hasnt_opclass( schema, name, desc ) fail should pass +ok 626 - hasnt_opclass( schema, name, desc ) fail should have the proper description +ok 627 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnostics +ok 628 - hasnt_opclass( name, desc ) fail should pass +ok 629 - hasnt_opclass( name, desc ) fail should have the proper description +ok 630 - hasnt_opclass( name, desc ) fail should have the proper diagnostics +ok 631 - domain_type_is(schema, domain, schema, type, desc) should pass +ok 632 - domain_type_is(schema, domain, schema, type, desc) should have the proper description +ok 633 - domain_type_is(schema, domain, schema, type, desc) should have the proper diagnostics +ok 634 - domain_type_is(schema, domain, schema, type) should pass +ok 635 - domain_type_is(schema, domain, schema, type) should have the proper description +ok 636 - domain_type_is(schema, domain, schema, type) should have the proper diagnostics +ok 637 - domain_type_is(schema, domain, schema, type, desc) fail should fail +ok 638 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper description +ok 639 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 640 - domain_type_is(schema, nondomain, schema, type, desc) should fail +ok 641 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper description +ok 642 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 643 - domain_type_is(schema, type, schema, type, desc) fail should fail +ok 644 - domain_type_is(schema, type, schema, type, desc) fail should have the proper description +ok 645 - domain_type_is(schema, type, schema, type, desc) fail should have the proper diagnostics +ok 646 - domain_type_is(schema, domain, type, desc) should pass +ok 647 - domain_type_is(schema, domain, type, desc) should have the proper description +ok 648 - domain_type_is(schema, domain, type, desc) should have the proper diagnostics +ok 649 - domain_type_is(schema, domain, type) should pass +ok 650 - domain_type_is(schema, domain, type) should have the proper description +ok 651 - domain_type_is(schema, domain, type) should have the proper diagnostics +ok 652 - domain_type_is(schema, domain, type, desc) fail should fail +ok 653 - domain_type_is(schema, domain, type, desc) fail should have the proper description +ok 654 - domain_type_is(schema, domain, type, desc) fail should have the proper diagnostics +ok 655 - domain_type_is(schema, nondomain, type, desc) should fail +ok 656 - domain_type_is(schema, nondomain, type, desc) should have the proper description +ok 657 - domain_type_is(schema, nondomain, type, desc) should have the proper diagnostics +ok 658 - domain_type_is(schema, type, type, desc) fail should fail +ok 659 - domain_type_is(schema, type, type, desc) fail should have the proper description +ok 660 - domain_type_is(schema, type, type, desc) fail should have the proper diagnostics +ok 661 - domain_type_is(domain, type, desc) should pass +ok 662 - domain_type_is(domain, type, desc) should have the proper description +ok 663 - domain_type_is(domain, type, desc) should have the proper diagnostics +ok 664 - domain_type_is(domain, type) should pass +ok 665 - domain_type_is(domain, type) should have the proper description +ok 666 - domain_type_is(domain, type) should have the proper diagnostics +ok 667 - domain_type_is(domain, type, desc) fail should fail +ok 668 - domain_type_is(domain, type, desc) fail should have the proper description +ok 669 - domain_type_is(domain, type, desc) fail should have the proper diagnostics +ok 670 - domain_type_is(nondomain, type, desc) should fail +ok 671 - domain_type_is(nondomain, type, desc) should have the proper description +ok 672 - domain_type_is(nondomain, type, desc) should have the proper diagnostics +ok 673 - domain_type_is(type, type, desc) fail should fail +ok 674 - domain_type_is(type, type, desc) fail should have the proper description +ok 675 - domain_type_is(type, type, desc) fail should have the proper diagnostics +ok 676 - domain_type_isnt(schema, domain, schema, type, desc) should pass +ok 677 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper description +ok 678 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper diagnostics +ok 679 - domain_type_isnt(schema, domain, schema, type) should pass +ok 680 - domain_type_isnt(schema, domain, schema, type) should have the proper description +ok 681 - domain_type_isnt(schema, domain, schema, type) should have the proper diagnostics +ok 682 - domain_type_isnt(schema, domain, schema, type, desc) fail should fail +ok 683 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper description +ok 684 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 685 - domain_type_isnt(schema, nondomain, schema, type, desc) should fail +ok 686 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper description +ok 687 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 688 - domain_type_isnt(schema, type, schema, type, desc) should fail +ok 689 - domain_type_isnt(schema, type, schema, type, desc) should have the proper description +ok 690 - domain_type_isnt(schema, type, schema, type, desc) should have the proper diagnostics +ok 691 - domain_type_isnt(schema, domain, type, desc) should pass +ok 692 - domain_type_isnt(schema, domain, type, desc) should have the proper description +ok 693 - domain_type_isnt(schema, domain, type, desc) should have the proper diagnostics +ok 694 - domain_type_isnt(schema, domain, type) should pass +ok 695 - domain_type_isnt(schema, domain, type) should have the proper description +ok 696 - domain_type_isnt(schema, domain, type) should have the proper diagnostics +ok 697 - domain_type_isnt(schema, domain, type, desc) fail should fail +ok 698 - domain_type_isnt(schema, domain, type, desc) fail should have the proper description +ok 699 - domain_type_isnt(schema, domain, type, desc) fail should have the proper diagnostics +ok 700 - domain_type_isnt(schema, nondomain, type, desc) should fail +ok 701 - domain_type_isnt(schema, nondomain, type, desc) should have the proper description +ok 702 - domain_type_isnt(schema, nondomain, type, desc) should have the proper diagnostics +ok 703 - domain_type_isnt(schema, type, type, desc) should fail +ok 704 - domain_type_isnt(schema, type, type, desc) should have the proper description +ok 705 - domain_type_isnt(schema, type, type, desc) should have the proper diagnostics +ok 706 - domain_type_isnt(domain, type, desc) should pass +ok 707 - domain_type_isnt(domain, type, desc) should have the proper description +ok 708 - domain_type_isnt(domain, type, desc) should have the proper diagnostics +ok 709 - domain_type_isnt(domain, type) should pass +ok 710 - domain_type_isnt(domain, type) should have the proper description +ok 711 - domain_type_isnt(domain, type) should have the proper diagnostics +ok 712 - domain_type_isnt(domain, type, desc) fail should fail +ok 713 - domain_type_isnt(domain, type, desc) fail should have the proper description +ok 714 - domain_type_isnt(domain, type, desc) fail should have the proper diagnostics +ok 715 - domain_type_isnt(nondomain, type, desc) should fail +ok 716 - domain_type_isnt(nondomain, type, desc) should have the proper description +ok 717 - domain_type_isnt(nondomain, type, desc) should have the proper diagnostics +ok 718 - domain_type_isnt(type, type, desc) should fail +ok 719 - domain_type_isnt(type, type, desc) should have the proper description +ok 720 - domain_type_isnt(type, type, desc) should have the proper diagnostics +ok 721 - has_relation(non-existent relation) should fail +ok 722 - has_relation(non-existent relation) should have the proper description +ok 723 - has_relation(non-existent relation) should have the proper diagnostics +ok 724 - has_relation(non-existent schema, tab) should fail +ok 725 - has_relation(non-existent schema, tab) should have the proper description +ok 726 - has_relation(non-existent schema, tab) should have the proper diagnostics +ok 727 - has_relation(sch, non-existent relation, desc) should fail +ok 728 - has_relation(sch, non-existent relation, desc) should have the proper description +ok 729 - has_relation(sch, non-existent relation, desc) should have the proper diagnostics +ok 730 - has_relation(tab, desc) should pass +ok 731 - has_relation(tab, desc) should have the proper description +ok 732 - has_relation(tab, desc) should have the proper diagnostics +ok 733 - has_relation(sch, tab, desc) should pass +ok 734 - has_relation(sch, tab, desc) should have the proper description +ok 735 - has_relation(sch, tab, desc) should have the proper diagnostics +ok 736 - has_relation(sch, view, desc) should pass +ok 737 - has_relation(sch, view, desc) should have the proper description +ok 738 - has_relation(sch, view, desc) should have the proper diagnostics +ok 739 - has_relation(type, desc) should pass +ok 740 - has_relation(type, desc) should have the proper description +ok 741 - has_relation(type, desc) should have the proper diagnostics +ok 742 - hasnt_relation(non-existent relation) should pass +ok 743 - hasnt_relation(non-existent relation) should have the proper description +ok 744 - hasnt_relation(non-existent relation) should have the proper diagnostics +ok 745 - hasnt_relation(non-existent schema, tab) should pass +ok 746 - hasnt_relation(non-existent schema, tab) should have the proper description +ok 747 - hasnt_relation(non-existent schema, tab) should have the proper diagnostics +ok 748 - hasnt_relation(sch, non-existent tab, desc) should pass +ok 749 - hasnt_relation(sch, non-existent tab, desc) should have the proper description +ok 750 - hasnt_relation(sch, non-existent tab, desc) should have the proper diagnostics +ok 751 - hasnt_relation(tab, desc) should fail +ok 752 - hasnt_relation(tab, desc) should have the proper description +ok 753 - hasnt_relation(tab, desc) should have the proper diagnostics +ok 754 - hasnt_relation(sch, tab, desc) should fail +ok 755 - hasnt_relation(sch, tab, desc) should have the proper description +ok 756 - hasnt_relation(sch, tab, desc) should have the proper diagnostics +ok 757 - has_foreign_table(non-existent table) should fail +ok 758 - has_foreign_table(non-existent table) should have the proper description +ok 759 - has_foreign_table(non-existent table) should have the proper diagnostics +ok 760 - has_foreign_table(non-existent schema, tab) should fail +ok 761 - has_foreign_table(non-existent schema, tab) should have the proper description +ok 762 - has_foreign_table(non-existent schema, tab) should have the proper diagnostics +ok 763 - has_foreign_table(non-existent table, desc) should fail +ok 764 - has_foreign_table(non-existent table, desc) should have the proper description +ok 765 - has_foreign_table(non-existent table, desc) should have the proper diagnostics +ok 766 - has_foreign_table(sch, non-existent table, desc) should fail +ok 767 - has_foreign_table(sch, non-existent table, desc) should have the proper description +ok 768 - has_foreign_table(sch, non-existent table, desc) should have the proper diagnostics +ok 769 - has_foreign_table(tab, desc) should pass +ok 770 - has_foreign_table(tab, desc) should have the proper description +ok 771 - has_foreign_table(tab, desc) should have the proper diagnostics +ok 772 - has_foreign_table(sch, tab, desc) should pass +ok 773 - has_foreign_table(sch, tab, desc) should have the proper description +ok 774 - has_foreign_table(sch, tab, desc) should have the proper diagnostics +ok 775 - has_foreign_table(sch, view, desc) should fail +ok 776 - has_foreign_table(sch, view, desc) should have the proper description +ok 777 - has_foreign_table(sch, view, desc) should have the proper diagnostics +ok 778 - has_foreign_table(type, desc) should fail +ok 779 - has_foreign_table(type, desc) should have the proper description +ok 780 - has_foreign_table(type, desc) should have the proper diagnostics +ok 781 - hasnt_foreign_table(non-existent table) should pass +ok 782 - hasnt_foreign_table(non-existent table) should have the proper description +ok 783 - hasnt_foreign_table(non-existent table) should have the proper diagnostics +ok 784 - hasnt_foreign_table(non-existent schema, tab) should pass +ok 785 - hasnt_foreign_table(non-existent schema, tab) should have the proper description +ok 786 - hasnt_foreign_table(non-existent schema, tab) should have the proper diagnostics +ok 787 - hasnt_foreign_table(non-existent table, desc) should pass +ok 788 - hasnt_foreign_table(non-existent table, desc) should have the proper description +ok 789 - hasnt_foreign_table(non-existent table, desc) should have the proper diagnostics +ok 790 - hasnt_foreign_table(sch, non-existent tab, desc) should pass +ok 791 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper description +ok 792 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper diagnostics +ok 793 - hasnt_foreign_table(tab, desc) should fail +ok 794 - hasnt_foreign_table(tab, desc) should have the proper description +ok 795 - hasnt_foreign_table(tab, desc) should have the proper diagnostics +ok 796 - hasnt_foreign_table(sch, tab, desc) should fail +ok 797 - hasnt_foreign_table(sch, tab, desc) should have the proper description +ok 798 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index 41f2aa873fda..e64f0e9f16a9 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(786); +SELECT plan(798); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -175,9 +175,17 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_table( '__SDFSDFD__', 'lol' ), + has_table( '__SDFSDFD__', 'lol'::name ), false, 'has_table(non-existent schema, tab)', + 'Table "__SDFSDFD__".lol should exist', + '' +); + +SELECT * FROM check_test( + has_table( '__SDFSDFD__', 'lol' ), + false, + 'has_table(non-existent table, desc)', 'lol', '' ); @@ -234,9 +242,17 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_table( '__SDFSDFD__', 'lol' ), + hasnt_table( '__SDFSDFD__', 'lol'::name ), true, 'hasnt_table(non-existent schema, tab)', + 'Table "__SDFSDFD__".lol should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_table( '__SDFSDFD__', 'lol' ), + true, + 'hasnt_table(non-existent table, desc)', 'lol', '' ); @@ -2018,9 +2034,19 @@ BEGIN END LOOP; FOR tap IN SELECT * FROM check_test( - has_foreign_table( '__SDFSDFD__', 'lol' ), + has_foreign_table( '__SDFSDFD__', 'lol'::name ), false, 'has_foreign_table(non-existent schema, tab)', + 'Foreign table "__SDFSDFD__".lol should exist', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_foreign_table( '__SDFSDFD__', 'lol' ), + false, + 'has_foreign_table(non-existent table, desc)', 'lol', '' ) AS b LOOP @@ -2089,9 +2115,19 @@ BEGIN END LOOP; FOR tap IN SELECT * FROM check_test( - hasnt_foreign_table( '__SDFSDFD__', 'lol' ), + hasnt_foreign_table( '__SDFSDFD__', 'lol'::name ), true, 'hasnt_foreign_table(non-existent schema, tab)', + 'Foreign table "__SDFSDFD__".lol should not exist', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_foreign_table( '__SDFSDFD__', 'lol' ), + true, + 'hasnt_foreign_table(non-existent table, desc)', 'lol', '' ) AS b LOOP @@ -2140,9 +2176,19 @@ BEGIN END LOOP; FOR tap IN SELECT * FROM check_test( - has_table( '__SDFSDFD__', 'lol' ), + has_table( '__SDFSDFD__', 'lol'::name ), false, 'has_foreign_table(non-existent schema, tab)', + 'Table "__SDFSDFD__".lol should exist', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_table( '__SDFSDFD__', 'lol' ), + false, + 'has_foreign_table(non-existent table, desc)', 'lol', '' ) AS b LOOP @@ -2211,9 +2257,19 @@ BEGIN END LOOP; FOR tap IN SELECT * FROM check_test( - hasnt_table( '__SDFSDFD__', 'lol' ), + hasnt_table( '__SDFSDFD__', 'lol'::name ), true, 'hasnt_foreign_table(non-existent schema, tab)', + 'Table "__SDFSDFD__".lol should not exist', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_table( '__SDFSDFD__', 'lol' ), + true, + 'hasnt_foreign_table(non-existent table, desc)', 'lol', '' ) AS b LOOP From a8981995dbd0e2d308bb0583adb0b26ca5ed2a9e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 4 Oct 2013 10:42:37 -0700 Subject: [PATCH 0788/1195] Bring the format of SKIP in line with the TAP spec. Resolves #55. No idea why I had it the way it was. Maybe older versions of Test::More worked that way? --- Changes | 2 ++ sql/pgtap--0.93.0--0.94.0.sql | 21 +++++++++++++++++++++ sql/pgtap.sql.in | 7 +++++-- test/sql/todotap.sql | 22 +++++++++++----------- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/Changes b/Changes index 3bc3456889f4..1a78b938f58c 100644 --- a/Changes +++ b/Changes @@ -14,6 +14,8 @@ Revision history for pgTAP + `hasnt_table(:schema, :table)` + `has_foreign_table(:schema, :table)` + `hasnt_foreign_table(:schema, :table)` +* Fixed the format of SKIP output to match the + [TAP spec](http://podwiki.hexten.net/TAP/TAP.html#Skippingtests). 0.93.0 2013-01-28T20:14:58Z --------------------------- diff --git a/sql/pgtap--0.93.0--0.94.0.sql b/sql/pgtap--0.93.0--0.94.0.sql index 33c76ce6e327..1a34d4803572 100644 --- a/sql/pgtap--0.93.0--0.94.0.sql +++ b/sql/pgtap--0.93.0--0.94.0.sql @@ -68,3 +68,24 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; +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; + diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 30ea311593ec..140430e209eb 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -642,7 +642,10 @@ DECLARE BEGIN output := '{}'; FOR i IN 1..how_many LOOP - output = array_append(output, ok( TRUE, 'SKIP: ' || COALESCE( why, '') ) ); + output = array_append( + output, + ok( TRUE ) || ' ' || diag( 'SKIP' || COALESCE( ' ' || why, '') ) + ); END LOOP; RETURN array_to_string(output, E'\n'); END; @@ -650,7 +653,7 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION skip ( text ) RETURNS TEXT AS $$ - SELECT ok( TRUE, 'SKIP: ' || $1 ); + SELECT ok( TRUE ) || ' ' || diag( 'SKIP' || COALESCE(' ' || $1, '') ); $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION skip( int, text ) diff --git a/test/sql/todotap.sql b/test/sql/todotap.sql index dbd14d43b99c..847d1bd2f4c4 100644 --- a/test/sql/todotap.sql +++ b/test/sql/todotap.sql @@ -50,16 +50,16 @@ SELECT * FROM check_test( skip('Just because'), true, 'simple skip', - 'SKIP: Just because', - '' + '', + ' SKIP Just because' ); SELECT * FROM check_test( skip('Just because', 1), true, 'skip with num', - 'SKIP: Just because', - '' + '', + ' SKIP Just because' ); \echo ok 15 - Skip multiple @@ -67,9 +67,9 @@ SELECT * FROM check_test( \echo ok 17 - Skip multiple SELECT is( skip( 'Whatever', 3 ), - 'ok 15 - SKIP: Whatever -ok 16 - SKIP: Whatever -ok 17 - SKIP: Whatever', + 'ok 15 # SKIP Whatever +ok 16 # SKIP Whatever +ok 17 # SKIP Whatever', 'We should get the proper output for multiple skips' ); @@ -78,8 +78,8 @@ SELECT * FROM check_test( skip(1, 'Just because'), true, 'inverted skip', - 'SKIP: Just because', - '' + '', + ' SKIP Just because' ); -- Test num only. @@ -87,8 +87,8 @@ SELECT * FROM check_test( skip(1), true, 'num only', - 'SKIP: ', - '' + '', + ' SKIP' ); /****************************************************************************/ From aa8babf2ac018704454c84cb5edb9c415900cf23 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 4 Oct 2013 12:19:05 -0700 Subject: [PATCH 0789/1195] Fix handling of leading whitespace in check_test(). It was not properly calculating the length of failure diagnostics, which should be ignored, and was failing to remove a single leading space character before the diagnostic message. So calculate the lenghts of strings to remove using the same code as is used to create those strings in `ok()`. This finally fixes a bunch of tests where leading whitespace in expected results was one character shorter than it should have been -- and fixes the SKIP tests so that the leading space that appears before the SKIP diagnostic output is removed. --- Changes | 2 + sql/pgtap--0.93.0--0.94.0.sql | 99 +++++++++++++++++++++++ sql/pgtap.sql.in | 20 +++-- test/sql/istap.sql | 18 ++--- test/sql/matching.sql | 8 +- test/sql/resultset.sql | 144 +++++++++++++++++----------------- test/sql/throwtap.sql | 2 +- test/sql/todotap.sql | 8 +- test/sql/valueset.sql | 108 ++++++++++++------------- 9 files changed, 258 insertions(+), 151 deletions(-) diff --git a/Changes b/Changes index 1a78b938f58c..000c363c1c0a 100644 --- a/Changes +++ b/Changes @@ -16,6 +16,8 @@ Revision history for pgTAP + `hasnt_foreign_table(:schema, :table)` * Fixed the format of SKIP output to match the [TAP spec](http://podwiki.hexten.net/TAP/TAP.html#Skippingtests). +* Fixed incorrect handling of leading space when comparing diagnostic output + in `check_test()`. 0.93.0 2013-01-28T20:14:58Z --------------------------- diff --git a/sql/pgtap--0.93.0--0.94.0.sql b/sql/pgtap--0.93.0--0.94.0.sql index 1a34d4803572..a3d66e4d6d97 100644 --- a/sql/pgtap--0.93.0--0.94.0.sql +++ b/sql/pgtap--0.93.0--0.94.0.sql @@ -89,3 +89,102 @@ RETURNS TEXT AS $$ SELECT ok( TRUE ) || ' ' || diag( 'SKIP' || COALESCE(' ' || $1, '') ); $$ 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. + EXECUTE 'SELECT aok, descr FROM __tresults__ WHERE numb = ' || tnumb + INTO aok, adescr; + + -- Now delete those results. + EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb; + + -- 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; + diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 140430e209eb..f3b70deb16a1 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -5705,16 +5705,22 @@ BEGIN -- Remove the description, if there is one. IF adescr <> '' THEN - adiag := substring( adiag FROM 3 + char_length( diag( adescr ) ) ); + adiag := substring( + adiag FROM 1 + char_length( ' - ' || substr(diag( adescr ), 3) ) + ); END IF; - -- Remove failure message from ok(). IF NOT aok THEN - adiag := substring( - adiag - FROM 14 + char_length(tnumb::text) - + CASE adescr WHEN '' THEN 3 ELSE 3 + char_length( diag( adescr ) ) END - ); + -- 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. diff --git a/test/sql/istap.sql b/test/sql/istap.sql index 885a49ac612d..46e6a88ee75d 100644 --- a/test/sql/istap.sql +++ b/test/sql/istap.sql @@ -12,13 +12,13 @@ SELECT * FROM check_test( is(1.1, 1.10), true, 'is(1.1, 1.10)', '', '' ); SELECT * FROM check_test( is(true, true), true, 'is(true, true)', '', '' ); SELECT * FROM check_test( is(false, false), true, 'is(false, false)', '', '' ); SELECT * FROM check_test( is(1, 1, 'foo'), true, 'is(1, 1, desc)', 'foo', '' ); -SELECT * FROM check_test( is( 1, 2 ), false, 'is(1, 2)', '', ' have: 1 +SELECT * FROM check_test( is( 1, 2 ), false, 'is(1, 2)', '', ' have: 1 want: 2'); /****************************************************************************/ -- Test isnt(). SELECT * FROM check_test( isnt(1, 2), true, 'isnt(1, 2)', '', '' ); -SELECT * FROM check_test( isnt( 1, 1 ), false, 'isnt(1, 1)', '', ' have: 1 +SELECT * FROM check_test( isnt( 1, 1 ), false, 'isnt(1, 1)', '', ' have: 1 want: anything else' ); /****************************************************************************/ @@ -43,7 +43,7 @@ SELECT * FROM check_test( false, 'is(NULL, foo)', '', - ' have: NULL + ' have: NULL want: foo' ); @@ -52,7 +52,7 @@ SELECT * FROM check_test( false, 'is(foo, NULL)', '', - ' have: foo + ' have: foo want: NULL' ); @@ -87,7 +87,7 @@ BEGIN false, 'is(mumble, row) fail', '', - ' have: (1,hey) + ' have: (1,hey) want: (1,HEY)' ) AS b FROM mumble LOOP RETURN NEXT tap.b; @@ -98,7 +98,7 @@ BEGIN false, 'is(mumble, row) fail with NULL', '', - ' have: (1,hey) + ' have: (1,hey) want: (1,)' ) AS b FROM mumble LOOP RETURN NEXT tap.b; @@ -109,7 +109,7 @@ BEGIN false, 'is(mumble, NULL)', '', - ' have: (1,hey) + ' have: (1,hey) want: NULL' ) AS b FROM mumble LOOP RETURN NEXT tap.b; @@ -123,7 +123,7 @@ BEGIN false, 'is(mumble, row) fail', '', - ' have: (1,hey) + ' have: (1,hey) want: (1,HEY)' ) AS b FROM mumble LOOP RETURN NEXT tap.b; @@ -134,7 +134,7 @@ BEGIN false, 'is(mumble, row) fail with NULL', '', - ' have: (1,hey) + ' have: (1,hey) want: (1,)' ) AS b FROM mumble LOOP RETURN NEXT tap.b; diff --git a/test/sql/matching.sql b/test/sql/matching.sql index 29bd3fd4084b..2301f53555b2 100644 --- a/test/sql/matching.sql +++ b/test/sql/matching.sql @@ -15,7 +15,7 @@ SELECT * FROM check_test( false, 'matches() fail', '', - ' ''foo'' + ' ''foo'' doesn''t match: ''^a''' ); @@ -30,7 +30,7 @@ SELECT * FROM check_test( false, 'doesnt_match() fail', '', - ' ''foo'' + ' ''foo'' matches: ''o''' ); @@ -46,7 +46,7 @@ SELECT * FROM check_test( false, 'alike() fail', '', - ' ''foo'' + ' ''foo'' doesn''t match: ''a%''' ); @@ -61,7 +61,7 @@ SELECT * FROM check_test( false, 'unalike() fail', '', - ' ''foo'' + ' ''foo'' matches: ''f%''' ); diff --git a/test/sql/resultset.sql b/test/sql/resultset.sql index ee9eb9a5ec19..3970cf385e46 100644 --- a/test/sql/resultset.sql +++ b/test/sql/resultset.sql @@ -324,7 +324,7 @@ SELECT * FROM check_test( false, 'set_eq(prepared, select) fail extra', '', - ' Extra records: + ' Extra records: (44,Anna)' ); @@ -336,7 +336,7 @@ SELECT * FROM check_test( false, 'set_eq(prepared, select) fail extras', '', - ' Extra records: + ' Extra records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true @@ -350,7 +350,7 @@ SELECT * FROM check_test( false, 'set_eq(select, prepared) fail missing', '', - ' Missing records: + ' Missing records: (44,Anna)' ); @@ -362,7 +362,7 @@ SELECT * FROM check_test( false, 'set_eq(select, prepared) fail missings', '', - ' Missing records: + ' Missing records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true @@ -376,7 +376,7 @@ SELECT * FROM check_test( false, 'set_eq(select, select) fail extra & missing', '', - ' Extra records: + ' Extra records: (1,Jacob) Missing records: (44,Anna)' @@ -390,7 +390,7 @@ SELECT * FROM check_test( false, 'set_eq(select, select) fail extras & missings', '', - ' Extra records: + ' Extra records: [(](1,Jacob|87,Jackson)[)] [(](1,Jacob|87,Jackson)[)] Missing records: @@ -407,7 +407,7 @@ SELECT * FROM check_test( false, 'set_eq(values, values) fail mismatch', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -421,7 +421,7 @@ SELECT * FROM check_test( false, 'set_eq(values, values) fail column count', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); @@ -446,7 +446,7 @@ SELECT * FROM check_test( false, 'set_eq(sql, sql) fail type schema visibility', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (text,__myfoo.text) want: (text,__myfoo.text,integer)' ); @@ -512,7 +512,7 @@ SELECT * FROM check_test( false, 'bag_eq(prepared, select) fail extra', '', - ' Extra records: + ' Extra records: (44,Anna)' ); @@ -524,7 +524,7 @@ SELECT * FROM check_test( false, 'bag_eq(prepared, select) fail extras', '', - ' Extra records: + ' Extra records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true @@ -538,7 +538,7 @@ SELECT * FROM check_test( false, 'bag_eq(select, prepared) fail missing', '', - ' Missing records: + ' Missing records: (44,Anna)' ); @@ -550,7 +550,7 @@ SELECT * FROM check_test( false, 'bag_eq(select, prepared) fail missings', '', - ' Missing records: + ' Missing records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true @@ -564,7 +564,7 @@ SELECT * FROM check_test( false, 'bag_eq(select, select) fail extra & missing', '', - ' Extra records: + ' Extra records: (1,Jacob) Missing records: (44,Anna)' @@ -578,7 +578,7 @@ SELECT * FROM check_test( false, 'bag_eq(select, select) fail extras & missings', '', - ' Extra records: + ' Extra records: [(](1,Jacob|87,Jackson)[)] [(](1,Jacob|87,Jackson)[)] Missing records: @@ -596,7 +596,7 @@ SELECT * FROM check_test( false, 'bag_eq(values, values) fail mismatch', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -610,7 +610,7 @@ SELECT * FROM check_test( false, 'bag_eq(values, values) fail column count', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); @@ -624,7 +624,7 @@ SELECT * FROM check_test( false, 'bag_eq(values, values) fail missing dupe', '', - ' Extra records: + ' Extra records: (1,Anna)' ); @@ -671,7 +671,7 @@ SELECT * FROM check_test( false, 'set_ne fail with column mismatch', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -685,7 +685,7 @@ SELECT * FROM check_test( false, 'set_ne fail with different col counts', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); @@ -744,7 +744,7 @@ SELECT * FROM check_test( false, 'bag_ne fail with column mismatch', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -770,7 +770,7 @@ SELECT * FROM check_test( false, 'bag_ne fail with column mismatch', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -784,7 +784,7 @@ SELECT * FROM check_test( false, 'bag_ne fail with different col counts', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); @@ -886,7 +886,7 @@ SELECT * FROM check_test( false, 'results_eq(nulls, nulls) fail', '', - ' Results differ beginning at row 2: + ' Results differ beginning at row 2: have: (,) want: NULL' ); @@ -900,7 +900,7 @@ SELECT * FROM check_test( false, 'results_eq(prepared, select) fail', '', - ' Results differ beginning at row 3: + ' Results differ beginning at row 3: have: (44,Anna) want: (63,Angel)' ); @@ -914,7 +914,7 @@ SELECT * FROM check_test( false, 'results_eq(select, prepared) fail missing last row', '', - ' Results differ beginning at row 7: + ' Results differ beginning at row 7: have: NULL want: (183,Antonio)' ); @@ -928,7 +928,7 @@ SELECT * FROM check_test( false, 'results_eq(prepared, select) fail missing first row', '', - ' Results differ beginning at row 7: + ' Results differ beginning at row 7: have: (183,Antonio) want: NULL' ); @@ -949,7 +949,7 @@ SELECT * FROM check_test( false, 'results_eq(values dupe, values)', '', - ' Results differ beginning at row 3: + ' Results differ beginning at row 3: have: (1,Anna) want: NULL' ); @@ -964,7 +964,7 @@ SELECT * FROM check_test( false, 'results_eq(values null, values)', '', - ' Results differ beginning at row 1: + ' Results differ beginning at row 1: have: (1,) want: (1,Anna)' ); @@ -981,7 +981,7 @@ SELECT * FROM check_test( false, 'results_eq(values, values) mismatch', '', - CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Number of columns or their types differ between the queries:' END || ' + CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Number of columns or their types differ between the queries:' END || ' have: (1,foo) want: (foo,1)' ); @@ -1020,7 +1020,7 @@ BEGIN false, 'results_eq(values, values) subtle mismatch', '', - ' Number of columns or their types differ between the queries' ) AS a(b) LOOP + ' Number of columns or their types differ between the queries' ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; @@ -1032,7 +1032,7 @@ BEGIN false, 'results_eq(values, values) integer type mismatch', '', - ' Number of columns or their types differ between the queries' ) AS a(b) LOOP + ' Number of columns or their types differ between the queries' ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; END IF; @@ -1050,7 +1050,7 @@ SELECT * FROM check_test( false, 'results_eq(values, values) fail column count', '', - CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Number of columns or their types differ between the queries:' END || ' + CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Number of columns or their types differ between the queries:' END || ' have: (1) want: (foo,1)' ); @@ -1185,7 +1185,7 @@ SELECT * FROM check_test( false, 'set_has( missing1, expect )', '', - ' Missing records: + ' Missing records: (44,Anna)' ); @@ -1197,7 +1197,7 @@ SELECT * FROM check_test( false, 'set_has(missing2, expect )', '', - ' Missing records: + ' Missing records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true @@ -1211,7 +1211,7 @@ SELECT * FROM check_test( false, 'set_has((int,text), (text,int))', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -1225,7 +1225,7 @@ SELECT * FROM check_test( false, 'set_has((int), (text,int))', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); @@ -1286,7 +1286,7 @@ SELECT * FROM check_test( false, 'bag_has( prepared, dupes )', '', - ' Missing records: + ' Missing records: (44,Anna)' ); @@ -1309,7 +1309,7 @@ SELECT * FROM check_test( false, 'bag_has( missing1, expect )', '', - ' Missing records: + ' Missing records: (44,Anna)' ); @@ -1321,7 +1321,7 @@ SELECT * FROM check_test( false, 'bag_has(missing2, expect )', '', - ' Missing records: + ' Missing records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true @@ -1336,7 +1336,7 @@ SELECT * FROM check_test( false, 'bag_has((int,text), (text,int))', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -1350,7 +1350,7 @@ SELECT * FROM check_test( false, 'bag_has((int), (text,int))', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); @@ -1423,7 +1423,7 @@ SELECT * FROM check_test( false, 'set_hasnt( prepared, value )', '', - ' Extra records: + ' Extra records: (44,Anna)' ); @@ -1432,7 +1432,7 @@ SELECT * FROM check_test( false, 'set_hasnt( prepared, values )', '', - ' Extra records: + ' Extra records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true @@ -1447,7 +1447,7 @@ SELECT * FROM check_test( false, 'set_hasnt((int,text), (text,int))', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -1461,7 +1461,7 @@ SELECT * FROM check_test( false, 'set_hasnt((int), (text,int))', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); @@ -1519,7 +1519,7 @@ SELECT * FROM check_test( false, 'bag_hasnt( prepared, value )', '', - ' Extra records: + ' Extra records: (44,Anna)' ); @@ -1528,7 +1528,7 @@ SELECT * FROM check_test( false, 'bag_hasnt( prepared, values )', '', - ' Extra records: + ' Extra records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true @@ -1543,7 +1543,7 @@ SELECT * FROM check_test( false, 'bag_hasnt((int,text), (text,int))', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -1554,7 +1554,7 @@ SELECT * FROM check_test( false, 'bag_hasnt((int), (text,int))', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); @@ -1568,7 +1568,7 @@ SELECT * FROM check_test( false, 'bag_hasnt( dupes, dupes )', '', - ' Extra records: + ' Extra records: (44,Anna) (44,Anna)' ); @@ -1582,7 +1582,7 @@ SELECT * FROM check_test( false, 'bag_hasnt( value, dupes )', '', - ' Extra records: + ' Extra records: (44,Anna)' ); @@ -1631,7 +1631,7 @@ SELECT * FROM check_test( false, 'set_eq(sql, array) extra record', '', - ' Extra records: + ' Extra records: (Anthony)' ); @@ -1644,7 +1644,7 @@ SELECT * FROM check_test( false, 'set_eq(sql, array) missing record', '', - ' Missing records: + ' Missing records: (Alan)' ); @@ -1657,7 +1657,7 @@ SELECT * FROM check_test( false, 'set_eq(sql, array) incompatible types', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (text) want: (integer)' ); @@ -1671,7 +1671,7 @@ SELECT * FROM check_test( false, 'set_eq(sql, array) incompatible types', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text)' ); @@ -1709,7 +1709,7 @@ SELECT * FROM check_test( false, 'bag_eq(sql, dupe array) fail', '', - ' Missing records: + ' Missing records: (Anna)' ); @@ -1722,7 +1722,7 @@ SELECT * FROM check_test( false, 'bag_eq(sql, array) extra record', '', - ' Extra records: + ' Extra records: (Anthony)' ); @@ -1735,7 +1735,7 @@ SELECT * FROM check_test( false, 'bag_eq(sql, array) missing record', '', - ' Missing records: + ' Missing records: (Alan)' ); @@ -1748,7 +1748,7 @@ SELECT * FROM check_test( false, 'bag_eq(sql, array) incompatible types', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (text) want: (integer)' ); @@ -1762,7 +1762,7 @@ SELECT * FROM check_test( false, 'bag_eq(sql, array) incompatible types', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text)' ); @@ -1824,7 +1824,7 @@ SELECT * FROM check_test( false, 'set_ne(sql, array) incompatible types', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (text) want: (integer)' ); @@ -1838,7 +1838,7 @@ SELECT * FROM check_test( false, 'set_ne(sql, array) incompatible types', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text)' ); @@ -1900,7 +1900,7 @@ SELECT * FROM check_test( false, 'bag_ne(sql, array) incompatible types', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (text) want: (integer)' ); @@ -1914,7 +1914,7 @@ SELECT * FROM check_test( false, 'bag_ne(sql, array) incompatible types', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text)' ); @@ -1978,7 +1978,7 @@ SELECT * FROM check_test( false, 'results_eq(prepared, array) extra record', '', - ' Results differ beginning at row 7: + ' Results differ beginning at row 7: have: (Antonio) want: NULL' ); @@ -1991,7 +1991,7 @@ SELECT * FROM check_test( false, 'results_eq(select, array) missing record', '', - ' Results differ beginning at row 5: + ' Results differ beginning at row 5: have: (Anthony) want: (Anna)' ); @@ -2186,7 +2186,7 @@ BEGIN false, 'results_ne(values, values) mismatch', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (1,foo) want: (foo,1)' ) AS a(b) LOOP @@ -2202,7 +2202,7 @@ BEGIN false, 'results_ne(values, values) subtle mismatch', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (1,foo) want: (1,foo)' ) AS a(b) LOOP RETURN NEXT tap.b; @@ -2214,7 +2214,7 @@ BEGIN false, 'results_ne(values, values) fail column count', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (1) want: (foo,1)' ) AS a(b) LOOP @@ -2349,7 +2349,7 @@ BEGIN false, 'is_empty(prepared) fail', '', - ' Unexpected records: + ' Unexpected records: (1,Jacob) (2,Emily)' ) AS a(b) LOOP @@ -2497,7 +2497,7 @@ BEGIN false, 'row_eq(prepared, record, desc)', '', - ' have: (1,Jacob) + ' have: (1,Jacob) want: (1,Larry)' ) AS a(b) LOOP RETURN NEXT tap.b; diff --git a/test/sql/throwtap.sql b/test/sql/throwtap.sql index cef2eb2358cf..efa47c7bdc83 100644 --- a/test/sql/throwtap.sql +++ b/test/sql/throwtap.sql @@ -122,7 +122,7 @@ SELECT * FROM check_test( false, 'lives_ok failure diagnostics', '', - ' died: P0001: todo_end() called without todo_start()' + ' died: P0001: todo_end() called without todo_start()' ); /****************************************************************************/ diff --git a/test/sql/todotap.sql b/test/sql/todotap.sql index 847d1bd2f4c4..1b308f2cbd27 100644 --- a/test/sql/todotap.sql +++ b/test/sql/todotap.sql @@ -51,7 +51,7 @@ SELECT * FROM check_test( true, 'simple skip', '', - ' SKIP Just because' + 'SKIP Just because' ); SELECT * FROM check_test( @@ -59,7 +59,7 @@ SELECT * FROM check_test( true, 'skip with num', '', - ' SKIP Just because' + 'SKIP Just because' ); \echo ok 15 - Skip multiple @@ -79,7 +79,7 @@ SELECT * FROM check_test( true, 'inverted skip', '', - ' SKIP Just because' + 'SKIP Just because' ); -- Test num only. @@ -88,7 +88,7 @@ SELECT * FROM check_test( true, 'num only', '', - ' SKIP' + 'SKIP' ); /****************************************************************************/ diff --git a/test/sql/valueset.sql b/test/sql/valueset.sql index 75c4b398807a..9e07288305b4 100644 --- a/test/sql/valueset.sql +++ b/test/sql/valueset.sql @@ -292,7 +292,7 @@ SELECT * FROM check_test( false, 'set_eq(select, prepared) fail missing', '', - ' Missing records: + ' Missing records: (44,Anna)' ); @@ -304,7 +304,7 @@ SELECT * FROM check_test( false, 'set_eq(select, prepared) fail missings', '', - E' Missing records: + E' Missing records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true @@ -316,7 +316,7 @@ SELECT * FROM check_test( false, 'set_eq(values, values) fail mismatch', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -327,7 +327,7 @@ SELECT * FROM check_test( false, 'set_eq(values, values) fail column count', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); @@ -381,7 +381,7 @@ SELECT * FROM check_test( false, 'bag_eq(select, prepared) fail missing', '', - ' Missing records: + ' Missing records: (44,Anna)' ); @@ -393,7 +393,7 @@ SELECT * FROM check_test( false, 'bag_eq(select, prepared) fail missings', '', - E' Missing records: + E' Missing records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true @@ -405,7 +405,7 @@ SELECT * FROM check_test( false, 'bag_eq(values, values) fail mismatch', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -416,7 +416,7 @@ SELECT * FROM check_test( false, 'bag_eq(values, values) fail column count', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); @@ -430,7 +430,7 @@ SELECT * FROM check_test( false, 'bag_eq(values, values) fail missing dupe', '', - ' Extra records: + ' Extra records: (1,Anna)' ); @@ -451,7 +451,7 @@ SELECT * FROM check_test( false, 'set_ne fail with column mismatch', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -462,7 +462,7 @@ SELECT * FROM check_test( false, 'set_ne fail with different col counts', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); @@ -495,7 +495,7 @@ SELECT * FROM check_test( false, 'bag_ne fail with column mismatch', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -518,7 +518,7 @@ SELECT * FROM check_test( false, 'bag_ne fail with column mismatch', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -529,7 +529,7 @@ SELECT * FROM check_test( false, 'bag_ne fail with different col counts', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); @@ -612,7 +612,7 @@ SELECT * FROM check_test( false, 'results_eq(nulls, nulls) fail', '', - ' Results differ beginning at row 2: + ' Results differ beginning at row 2: have: (,) want: NULL' ); @@ -626,7 +626,7 @@ SELECT * FROM check_test( false, 'results_eq(select, prepared) fail missing last row', '', - ' Results differ beginning at row 7: + ' Results differ beginning at row 7: have: NULL want: (183,Antonio)' ); @@ -640,7 +640,7 @@ SELECT * FROM check_test( false, 'results_eq(prepared, select) fail missing first row', '', - ' Results differ beginning at row 7: + ' Results differ beginning at row 7: have: (183,Antonio) want: NULL' ); @@ -654,7 +654,7 @@ SELECT * FROM check_test( false, 'results_eq(values dupe, values)', '', - ' Results differ beginning at row 3: + ' Results differ beginning at row 3: have: (1,Anna) want: NULL' ); @@ -668,7 +668,7 @@ SELECT * FROM check_test( false, 'results_eq(values null, values)', '', - ' Results differ beginning at row 1: + ' Results differ beginning at row 1: have: (1,) want: (1,Anna)' ); @@ -679,7 +679,7 @@ SELECT * FROM check_test( false, 'results_eq(values, values) mismatch', '', - CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Number of columns or their types differ between the queries:' END || ' + CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Number of columns or their types differ between the queries:' END || ' have: (1,foo) want: (foo,1)' ); @@ -704,7 +704,7 @@ BEGIN false, 'results_eq(values, values) subtle mismatch', '', - ' Number of columns or their types differ between the queries' ) AS a(b) LOOP + ' Number of columns or their types differ between the queries' ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; END IF; @@ -719,7 +719,7 @@ SELECT * FROM check_test( false, 'results_eq(values, values) fail column count', '', - CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Number of columns or their types differ between the queries:' END || ' + CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Number of columns or their types differ between the queries:' END || ' have: (1) want: (foo,1)' ); @@ -804,7 +804,7 @@ SELECT * FROM check_test( false, 'set_has( missing1, expect )', '', - ' Missing records: + ' Missing records: (44,Anna)' ); @@ -816,7 +816,7 @@ SELECT * FROM check_test( false, 'set_has(missing2, expect )', '', - E' Missing records: + E' Missing records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true @@ -828,7 +828,7 @@ SELECT * FROM check_test( false, 'set_has((int,text), (text,int))', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -839,7 +839,7 @@ SELECT * FROM check_test( false, 'set_has((int), (text,int))', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); @@ -876,7 +876,7 @@ SELECT * FROM check_test( false, 'bag_has( prepared, dupes )', '', - ' Missing records: + ' Missing records: (44,Anna)' ); @@ -896,7 +896,7 @@ SELECT * FROM check_test( false, 'bag_has( missing1, expect )', '', - ' Missing records: + ' Missing records: (44,Anna)' ); @@ -908,7 +908,7 @@ SELECT * FROM check_test( false, 'bag_has(missing2, expect )', '', - E' Missing records: + E' Missing records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true @@ -920,7 +920,7 @@ SELECT * FROM check_test( false, 'bag_has((int,text), (text,int))', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -931,7 +931,7 @@ SELECT * FROM check_test( false, 'bag_has((int), (text,int))', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); @@ -979,7 +979,7 @@ SELECT * FROM check_test( false, 'set_hasnt( prepared, value )', '', - ' Extra records: + ' Extra records: (44,Anna)' ); @@ -988,7 +988,7 @@ SELECT * FROM check_test( false, 'set_hasnt( prepared, values )', '', - E' Extra records: + E' Extra records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true @@ -1000,7 +1000,7 @@ SELECT * FROM check_test( false, 'set_hasnt((int,text), (text,int))', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -1011,7 +1011,7 @@ SELECT * FROM check_test( false, 'set_hasnt((int), (text,int))', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); @@ -1048,7 +1048,7 @@ SELECT * FROM check_test( false, 'bag_hasnt( prepared, value )', '', - ' Extra records: + ' Extra records: (44,Anna)' ); @@ -1057,7 +1057,7 @@ SELECT * FROM check_test( false, 'bag_hasnt( prepared, values )', '', - E' Extra records: + E' Extra records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true @@ -1069,7 +1069,7 @@ SELECT * FROM check_test( false, 'bag_hasnt((int,text), (text,int))', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); @@ -1080,7 +1080,7 @@ SELECT * FROM check_test( false, 'bag_hasnt((int), (text,int))', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (integer) want: (text,integer)' ); @@ -1094,7 +1094,7 @@ SELECT * FROM check_test( false, 'bag_hasnt( dupes, dupes )', '', - ' Extra records: + ' Extra records: (44,Anna) (44,Anna)' ); @@ -1108,7 +1108,7 @@ SELECT * FROM check_test( false, 'bag_hasnt( value, dupes )', '', - ' Extra records: + ' Extra records: (44,Anna)' ); @@ -1159,7 +1159,7 @@ SELECT * FROM check_test( false, 'set_eq(prepared, array) extra record', '', - ' Extra records: + ' Extra records: (Anthony)' ); @@ -1172,7 +1172,7 @@ SELECT * FROM check_test( false, 'set_eq(prepared, array) missing record', '', - ' Missing records: + ' Missing records: (Alan)' ); @@ -1185,7 +1185,7 @@ SELECT * FROM check_test( false, 'set_eq(sql, array) incompatible types', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (text) want: (integer)' ); @@ -1223,7 +1223,7 @@ SELECT * FROM check_test( false, 'bag_eq(prepared, dupe array) fail', '', - ' Missing records: + ' Missing records: (Anna)' ); @@ -1236,7 +1236,7 @@ SELECT * FROM check_test( false, 'bag_eq(prepared, array) extra record', '', - ' Extra records: + ' Extra records: (Anthony)' ); @@ -1249,7 +1249,7 @@ SELECT * FROM check_test( false, 'bag_eq(prepared, array) missing record', '', - ' Missing records: + ' Missing records: (Alan)' ); @@ -1262,7 +1262,7 @@ SELECT * FROM check_test( false, 'bag_eq(prepared, array) incompatible types', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (text) want: (integer)' ); @@ -1324,7 +1324,7 @@ SELECT * FROM check_test( false, 'set_ne(sql, array) incompatible types', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (text) want: (integer)' ); @@ -1386,7 +1386,7 @@ SELECT * FROM check_test( false, 'bag_ne(prepared, array) incompatible types', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (text) want: (integer)' ); @@ -1428,7 +1428,7 @@ SELECT * FROM check_test( false, 'results_eq(prepared, array) extra record', '', - ' Results differ beginning at row 7: + ' Results differ beginning at row 7: have: (Antonio) want: NULL' ); @@ -1550,7 +1550,7 @@ BEGIN false, 'results_ne(values, values) mismatch', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (1,foo) want: (foo,1)' ) AS a(b) LOOP @@ -1566,7 +1566,7 @@ BEGIN false, 'results_ne(values, values) subtle mismatch', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (1,foo) want: (1,foo)' ) AS a(b) LOOP RETURN NEXT tap.b; @@ -1578,7 +1578,7 @@ BEGIN false, 'results_ne(values, values) fail column count', '', - ' Columns differ between queries: + ' Columns differ between queries: have: (1) want: (foo,1)' ) AS a(b) LOOP From cd2b0950681dfb0503720cc55e3183063701d79a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 3 Jan 2014 16:30:14 -0800 Subject: [PATCH 0790/1195] Avoid installing the same file twice. --- Changes | 1 + Makefile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 000c363c1c0a..4ef009b44770 100644 --- a/Changes +++ b/Changes @@ -18,6 +18,7 @@ Revision history for pgTAP [TAP spec](http://podwiki.hexten.net/TAP/TAP.html#Skippingtests). * Fixed incorrect handling of leading space when comparing diagnostic output in `check_test()`. +* Fixed an installation issue on PostgreSQL 9.3.2. 0.93.0 2013-01-28T20:14:58Z --------------------------- diff --git a/Makefile b/Makefile index c9c46800365c..0a8534443b70 100644 --- a/Makefile +++ b/Makefile @@ -87,7 +87,7 @@ sql/$(MAINEXT)-core--$(EXTVERSION).sql: sql/$(MAINEXT)-core.sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql: sql/$(MAINEXT)-schema.sql cp $< $@ -DATA = $(wildcard sql/*--*.sql) sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql +DATA = $(wildcard sql/*--*.sql) EXTRA_CLEAN += sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql endif From 1bc03f07177f9467511bbe2bae2786422bb4b0ba Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Jan 2014 15:36:38 -0800 Subject: [PATCH 0791/1195] Get tests passing with functions in another schema. Need to set `PGOPTIONS=--search_path=tap` and uncomment a line in `test/setup.sql` and then it should test everything installed in the schema named `tap`. --- test/setup.sql | 6 +++++- test/sql/ownership.sql | 4 ++-- test/sql/privs.sql | 13 ++++++++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/test/setup.sql b/test/setup.sql index aa231f054007..e79981c3b53d 100644 --- a/test/setup.sql +++ b/test/setup.sql @@ -13,6 +13,10 @@ \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true --- Load the TAP functions. BEGIN; + +-- Uncomment when testing with PGOPTIONS=--search_path=tap +-- CREATE SCHEMA tap; SET search_path TO tap,public; + +-- Load the TAP functions. \i sql/pgtap.sql diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 215459357455..f5791b815a40 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -528,7 +528,7 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test foreign_table_owner_is(). -CREATE FUNCTION test_fdw() RETURNS SETOF TEXT AS $$ +CREATE FUNCTION public.test_fdw() RETURNS SETOF TEXT AS $$ DECLARE tap record; BEGIN @@ -690,7 +690,7 @@ BEGIN END; $$ LANGUAGE PLPGSQL; -SELECT * FROM test_fdw(); +SELECT * FROM public.test_fdw(); /****************************************************************************/ -- Test function_owner_is(). diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 20e618454294..bcaa7dca48c0 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -8,7 +8,18 @@ SET client_min_messages = warning; CREATE SCHEMA ha; CREATE TABLE ha.sometab(id INT); CREATE SEQUENCE ha.someseq; -SET search_path = ha,public,pg_catalog; +-- Include the new schema in the path. +CREATE OR REPLACE FUNCTION set_search_path() returns setof text as $$ +BEGIN + PERFORM set_config( + 'search_path', + 'ha, ' || current_setting('search_path') || ', pg_catalog', + true + ); + RETURN; +END; +$$ language plpgsql; +SELECT set_search_path(); RESET client_min_messages; /****************************************************************************/ From 7c0bd791f53d5e4651559b49d8d91bab586874bc Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Jan 2014 15:57:57 -0800 Subject: [PATCH 0792/1195] Update 8.4 patch. --- compat/install-8.4.patch | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compat/install-8.4.patch b/compat/install-8.4.patch index 94f3c7243416..32305b4e0599 100644 --- a/compat/install-8.4.patch +++ b/compat/install-8.4.patch @@ -1,6 +1,6 @@ ---- sql/pgtap.sql.saf 2013-01-15 12:01:39.000000000 -0800 -+++ sql/pgtap.sql 2013-01-15 12:01:55.000000000 -0800 -@@ -7180,7 +7180,6 @@ +--- sql/pgtap.sql.saf 2014-01-06 15:56:37.000000000 -0800 ++++ sql/pgtap.sql 2014-01-06 15:56:44.000000000 -0800 +@@ -7225,7 +7225,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -8,7 +8,7 @@ EXCEPT SELECT $3[i] FROM generate_series(1, array_upper($3, 1)) s(i) -@@ -7195,7 +7194,6 @@ +@@ -7240,7 +7239,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -16,7 +16,7 @@ ), $4 ); -@@ -7219,7 +7217,6 @@ +@@ -7264,7 +7262,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = $1 AND n.nspname NOT IN ('pg_catalog', 'information_schema') @@ -24,7 +24,7 @@ EXCEPT SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) -@@ -7233,7 +7230,6 @@ +@@ -7278,7 +7275,6 @@ 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') From ae0feb795bcbbf49cddce696a3913a18849e24ae Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Jan 2014 16:20:31 -0800 Subject: [PATCH 0793/1195] Update for 8.3. --- compat/install-8.3.patch | 12 ++++++------ test/sql/istap.sql | 2 +- test/sql/privs.sql | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index b6618e05cd7f..11a69bd8f540 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -1,5 +1,5 @@ ---- sql/pgtap.sql.saf 2013-01-28 11:20:18.000000000 -0800 -+++ sql/pgtap.sql 2013-01-28 11:21:00.000000000 -0800 +--- sql/pgtap.sql.saf 2014-01-06 15:58:30.000000000 -0800 ++++ sql/pgtap.sql 2014-01-06 15:58:45.000000000 -0800 @@ -9,6 +9,11 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -12,7 +12,7 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT s.a[1]::int * 10000 -@@ -6306,7 +6311,7 @@ +@@ -6351,7 +6356,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -21,7 +21,7 @@ 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' || -@@ -6463,7 +6468,7 @@ +@@ -6508,7 +6513,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -30,7 +30,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6672,13 +6677,7 @@ +@@ -6717,13 +6722,7 @@ $$ LANGUAGE sql; -- collect_tap( tap, tap, tap ) @@ -45,7 +45,7 @@ RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); $$ LANGUAGE sql; -@@ -7154,7 +7153,7 @@ +@@ -7199,7 +7198,7 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; diff --git a/test/sql/istap.sql b/test/sql/istap.sql index 46e6a88ee75d..87b5dc182761 100644 --- a/test/sql/istap.sql +++ b/test/sql/istap.sql @@ -145,7 +145,7 @@ BEGIN false, 'is(mumble, NULL)', '', - ' have: (1,hey) + ' have: (1,hey) want: NULL' ) AS b FROM mumble LOOP RETURN NEXT tap.b; diff --git a/test/sql/privs.sql b/test/sql/privs.sql index bcaa7dca48c0..30c5ae79d442 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -19,7 +19,7 @@ BEGIN RETURN; END; $$ language plpgsql; -SELECT set_search_path(); +SELECT * FROM set_search_path(); RESET client_min_messages; /****************************************************************************/ From 22946ee21c1fd3a5ce8b2d7b3a2398a872539ae4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Jan 2014 16:29:08 -0800 Subject: [PATCH 0794/1195] Update for 8.2. --- compat/install-8.2.patch | 46 ++++++++++++++++++++-------------------- test/sql/privs.sql | 6 +----- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index b702378d2c8d..79435b67d226 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,5 +1,5 @@ ---- sql/pgtap.sql.saf 2013-01-28 11:25:36.000000000 -0800 -+++ sql/pgtap.sql 2013-01-28 11:30:46.000000000 -0800 +--- sql/pgtap.sql.saf 2014-01-06 16:21:22.000000000 -0800 ++++ sql/pgtap.sql 2014-01-06 16:21:28.000000000 -0800 @@ -5,6 +5,59 @@ -- -- http://pgtap.org/ @@ -111,7 +111,7 @@ INTO result; output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( -@@ -2316,7 +2354,7 @@ +@@ -2355,7 +2393,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 @@ -120,7 +120,7 @@ p.prolang AS langoid, p.proisstrict AS is_strict, p.proisagg AS is_agg, -@@ -3452,63 +3490,6 @@ +@@ -3491,63 +3529,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -184,7 +184,7 @@ CREATE OR REPLACE FUNCTION _has_role( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( -@@ -5986,13 +5967,13 @@ +@@ -6031,13 +6012,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -200,7 +200,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6216,7 +6197,7 @@ +@@ -6261,7 +6242,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -209,7 +209,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6311,11 +6292,11 @@ +@@ -6356,11 +6337,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -224,7 +224,7 @@ ); END IF; rownum = rownum + 1; -@@ -6330,9 +6311,9 @@ +@@ -6375,9 +6356,9 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Number of columns or their types differ between the queries' || @@ -237,7 +237,7 @@ END ); END; -@@ -6468,7 +6449,7 @@ +@@ -6513,7 +6494,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -246,7 +246,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6482,8 +6463,8 @@ +@@ -6527,8 +6508,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -257,7 +257,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6608,9 +6589,9 @@ +@@ -6653,9 +6634,9 @@ DECLARE typeof regtype := pg_typeof($1); BEGIN @@ -270,7 +270,7 @@ END; $$ LANGUAGE plpgsql; -@@ -6631,7 +6612,7 @@ +@@ -6676,7 +6657,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -279,7 +279,7 @@ END LOOP; -- What extra records do we have? -@@ -6799,7 +6780,7 @@ +@@ -6844,7 +6825,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -288,7 +288,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT -@@ -6817,7 +6798,7 @@ +@@ -6862,7 +6843,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -297,7 +297,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) ), -@@ -6850,7 +6831,7 @@ +@@ -6895,7 +6876,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -306,7 +306,7 @@ 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']) ) -@@ -6869,7 +6850,7 @@ +@@ -6914,7 +6895,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -315,7 +315,7 @@ 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']) ) -@@ -7153,10 +7134,12 @@ +@@ -7198,10 +7179,12 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; @@ -331,7 +331,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7303,7 +7286,7 @@ +@@ -7348,7 +7331,7 @@ CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) RETURNS TEXT AS $$ @@ -340,7 +340,7 @@ $$ LANGUAGE SQL; -- operators_are( schema, operators[], description ) -@@ -7312,7 +7295,7 @@ +@@ -7357,7 +7340,7 @@ SELECT _areni( 'operators', ARRAY( @@ -349,7 +349,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7324,7 +7307,7 @@ +@@ -7369,7 +7352,7 @@ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT @@ -358,7 +358,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7345,7 +7328,7 @@ +@@ -7390,7 +7373,7 @@ SELECT _areni( 'operators', ARRAY( @@ -367,7 +367,7 @@ 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) -@@ -7358,7 +7341,7 @@ +@@ -7403,7 +7386,7 @@ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT @@ -376,7 +376,7 @@ 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) -@@ -8061,40 +8044,6 @@ +@@ -8106,40 +8089,6 @@ ); $$ LANGUAGE sql; diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 30c5ae79d442..d7b015285968 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -11,11 +11,7 @@ CREATE SEQUENCE ha.someseq; -- Include the new schema in the path. CREATE OR REPLACE FUNCTION set_search_path() returns setof text as $$ BEGIN - PERFORM set_config( - 'search_path', - 'ha, ' || current_setting('search_path') || ', pg_catalog', - true - ); + EXECUTE 'SET search_path = ha, ' || current_setting('search_path') || ', pg_catalog'; RETURN; END; $$ language plpgsql; From c192ad6b3ee2b76ce03d87289d57e1e1198b73f2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Jan 2014 16:58:52 -0800 Subject: [PATCH 0795/1195] Update for 8.1. --- compat/install-8.1.patch | 22 +++++++++++----------- test/sql/privs.sql | 4 +++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index e7326bb0c11b..5bac44f55a48 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ ---- sql/pgtap.sql.saf 2013-01-28 11:47:39.000000000 -0800 -+++ sql/pgtap.sql 2013-01-28 11:47:45.000000000 -0800 -@@ -2113,13 +2113,13 @@ +--- sql/pgtap.sql.saf 2014-01-06 16:30:01.000000000 -0800 ++++ sql/pgtap.sql 2014-01-06 16:30:06.000000000 -0800 +@@ -2152,13 +2152,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -18,7 +18,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $6 || ' constraints'; -@@ -2137,13 +2137,13 @@ +@@ -2176,13 +2176,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -36,7 +36,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $5 || ' constraints'; -@@ -5739,7 +5739,7 @@ +@@ -5784,7 +5784,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -45,7 +45,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -5747,8 +5747,8 @@ +@@ -5792,8 +5792,8 @@ -- 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. @@ -56,7 +56,7 @@ END LOOP; END LOOP; RETURN; -@@ -5818,14 +5818,14 @@ +@@ -5863,14 +5863,14 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -73,7 +73,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -5840,15 +5840,15 @@ +@@ -5885,15 +5885,15 @@ IF verbos THEN RETURN NEXT diag_test_name(tests[i]); END IF; -- Run the setup functions. @@ -93,7 +93,7 @@ -- Remember how many failed and then roll back. num_faild := num_faild + num_failed(); -@@ -5863,7 +5863,7 @@ +@@ -5908,7 +5908,7 @@ END LOOP; -- Run the shutdown functions. @@ -102,7 +102,7 @@ -- Raise an exception to rollback any changes. RAISE EXCEPTION '__TAP_ROLLBACK__'; -@@ -5874,8 +5874,8 @@ +@@ -5919,8 +5919,8 @@ END IF; END; -- Finish up. @@ -113,7 +113,7 @@ END LOOP; -- Clean up and return. -@@ -7133,7 +7133,7 @@ +@@ -7178,7 +7178,7 @@ DECLARE rec RECORD; BEGIN diff --git a/test/sql/privs.sql b/test/sql/privs.sql index d7b015285968..d93d7e053a5f 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -11,7 +11,9 @@ CREATE SEQUENCE ha.someseq; -- Include the new schema in the path. CREATE OR REPLACE FUNCTION set_search_path() returns setof text as $$ BEGIN - EXECUTE 'SET search_path = ha, ' || current_setting('search_path') || ', pg_catalog'; + EXECUTE 'SET search_path = ha, ' + || regexp_replace(current_setting('search_path'), '[$][^,]+,', '') + || ', pg_catalog'; RETURN; END; $$ language plpgsql; From 10ca46fa560f2bf13c8ce2c72d7b784c498775d8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Jan 2014 17:21:15 -0800 Subject: [PATCH 0796/1195] 8.2 already double-quotes $user. --- test/sql/privs.sql | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/sql/privs.sql b/test/sql/privs.sql index d93d7e053a5f..b6a7a437e1c0 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -11,10 +11,15 @@ CREATE SEQUENCE ha.someseq; -- Include the new schema in the path. CREATE OR REPLACE FUNCTION set_search_path() returns setof text as $$ BEGIN - EXECUTE 'SET search_path = ha, ' - || regexp_replace(current_setting('search_path'), '[$][^,]+,', '') - || ', pg_catalog'; - RETURN; + IF pg_version_num() < 80200 THEN + EXECUTE 'SET search_path = ha, ' + || regexp_replace(current_setting('search_path'), '[$][^,]+,', '') + || ', pg_catalog'; + RETURN; + ELSE + EXECUTE 'SET search_path = ha, ' || current_setting('search_path') || ', pg_catalog'; + END IF; + END; $$ language plpgsql; SELECT * FROM set_search_path(); From 7ab7852d55aaf489e524ae028ee52140535fa0b4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Jan 2014 17:31:09 -0800 Subject: [PATCH 0797/1195] Timestamp v0.94.0. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 4ef009b44770..ffda23abe278 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.94.0 +0.94.0 2014-01-07T01:32:36Z --------------------------- * Fixed the Makefile to allow `PG_CONFIG=/path/to/pg_config` to actually work. Patch from Aaron W. Swenson. From 8a173ea8ec1e2879d1f8899defec6af89038a2d7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Jan 2014 17:34:14 -0800 Subject: [PATCH 0798/1195] Increment to v0.95.0. --- Changes | 4 ++++ META.json | 2 +- README.md | 2 +- doc/pgtap.mmd | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index ffda23abe278..9aa73305576e 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,10 @@ Revision history for pgTAP ========================== +0.95.0 +--------------------------- + + 0.94.0 2014-01-07T01:32:36Z --------------------------- * Fixed the Makefile to allow `PG_CONFIG=/path/to/pg_config` to actually work. diff --git a/META.json b/META.json index f08412bb6a0d..3e6f4a211f4a 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "0.94.0", + "version": "0.95.0", "maintainer": [ "David E. Wheeler ", "pgTAP List " diff --git a/README.md b/README.md index 155abe43044d..3f2ceb1e9389 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 0.94.0 +pgTAP 0.95.0 ============ [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 1eda9a7a9c2a..f6d46b6ef5d1 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 0.94.0 +pgTAP 0.95.0 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and From a1afd73acedf5a2ec6ff8664c75277fc696c0fcb Mon Sep 17 00:00:00 2001 From: Marcello Nuccio Date: Thu, 30 Jan 2014 18:22:11 +0100 Subject: [PATCH 0799/1195] Fix syntax error in documentation There's an error in the markdown syntax, making the link not clickable. --- doc/pgtap.mmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index f6d46b6ef5d1..e6ea07be7a8f 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -753,8 +753,8 @@ an unnecessary PITA. Each of the query-executing functions in this section thus support an alternative to make your tests more SQLish: using prepared statements. -[Prepared statements](http://www.postgresql.org/docs/current/static/sql-prepare.html -"PostgreSQL Documentation: PREPARE") allow you to just write SQL and simply +[Prepared statements](http://www.postgresql.org/docs/current/static/sql-prepare.html) +allow you to just write SQL and simply pass the prepared statement names to test functions. For example, the above example can be rewritten as: From cef05bb183c378d90d36aa4dc7f038d20d91ea2d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 6 Feb 2014 18:14:17 -0800 Subject: [PATCH 0800/1195] Don't remove first line of verbatim block. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0a8534443b70..37d064d13566 100644 --- a/Makefile +++ b/Makefile @@ -143,4 +143,4 @@ test: test/setup.sql html: MultiMarkdown.pl doc/pgtap.mmd > doc/pgtap.html ./tocgen doc/pgtap.html 2> doc/toc.html - perl -MPod::Simple::XHTML -E "my \$$p = Pod::Simple::XHTML->new; \$$p->html_header_tags(''); \$$p->strip_verbatim_indent(sub { (my \$$i = \$$_[0]->[0]) =~ s/\\S.*//; \$$i }); \$$p->parse_from_file('`perldoc -l pg_prove`')" > doc/pg_prove.html + perl -MPod::Simple::XHTML -E "my \$$p = Pod::Simple::XHTML->new; \$$p->html_header_tags(''); \$$p->strip_verbatim_indent(sub { my \$$l = shift; (my \$$i = \$$l->[0]) =~ s/\\S.*//; \$$i }); \$$p->parse_from_file('`perldoc -l pg_prove`')" > doc/pg_prove.html From 9241027a225b7b0722685990f845fef1c8279880 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 8 Feb 2014 10:50:05 -0800 Subject: [PATCH 0801/1195] Fix Trvis link in README. Resolves #59. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f2ceb1e9389..a028b920b7f7 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ used in the xUnit testing style. For detailed documentation, see the documentation in `doc/pgtap.mmd` or [online](http://pgtap.org/documentation.html "Complete pgTAP Documentation"). -[![Build Status](https://travis-ci.org/theory/pgtap.png)](https://travis-ci.org/theory/sqitch) +[![Build Status](https://travis-ci.org/theory/pgtap.png)](https://travis-ci.org/theory/pgtap) To build it, just do this: From 7d6724237d8cadbd3adb52cfd17a7470f78b7a12 Mon Sep 17 00:00:00 2001 From: Charly Batista Date: Wed, 16 Apr 2014 22:08:21 -0300 Subject: [PATCH 0802/1195] Add missing materialized_views functions. + `materialized_views_are( :schema, :materialized_views, :description )` + `materialized_views_are( :materialized_views, :description )` + `materialized_views_are( :schema, :materialized_views )` + `materialized_views_are( :materialized_views )` + `materialized_view_owner_is ( :schema, :materialized_view, :user, :description )` + `materialized_view_owner_is ( :schema, :materialized_view, :user )` + `materialized_view_owner_is ( :materialized_view, :user, :description )` + `materialized_view_owner_is ( :materialized_view, :user )` + `has_materialized_view( :schema, :materialized_view, :description )` + `has_materialized_view( :materialized_view, :description )` + `has_materialized_view( :materialized_view )` + `hasnt_materialized_view( :schema, :materialized_view, :description )` + `hasnt_materialized_view( :materialized_view, :description )` + `hasnt_materialized_view( :materialized_view )` Resolves #61. --- sql/pgtap.sql.in | 119 ++++++++++++++++++++++++++++++++++++ test/expected/aretap.out | 32 +++++++++- test/expected/hastap.out | 32 +++++++++- test/expected/ownership.out | 29 ++++++++- test/sql/aretap.sql | 103 ++++++++++++++++++++++++++++++- test/sql/hastap.sql | 91 ++++++++++++++++++++++++++- test/sql/ownership.sql | 79 +++++++++++++++++++++++- 7 files changed, 479 insertions(+), 6 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index f3b70deb16a1..136f952db81b 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -9077,3 +9077,122 @@ RETURNS TEXT AS $$ || ' 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; + diff --git a/test/expected/aretap.out b/test/expected/aretap.out index b99f39f66cd8..69fdee4d3385 100644 --- a/test/expected/aretap.out +++ b/test/expected/aretap.out @@ -1,5 +1,5 @@ \unset ECHO -1..399 +1..429 ok 1 - tablespaces_are(tablespaces, desc) should pass ok 2 - tablespaces_are(tablespaces, desc) should have the proper description ok 3 - tablespaces_are(tablespaces, desc) should have the proper diagnostics @@ -399,3 +399,33 @@ ok 396 - columns_are(table, columns) + missing should have the proper diagnostic ok 397 - columns_are(table, columns) + extra & missing should fail ok 398 - columns_are(table, columns) + extra & missing should have the proper description ok 399 - columns_are(table, columns) + extra & missing should have the proper diagnostics +ok 400 - materialized_views_are(schema, materialized_views, desc) should pass +ok 401 - materialized_views_are(schema, materialized_views, desc) should have the proper description +ok 402 - materialized_views_are(schema, materialized_views, desc) should have the proper diagnostics +ok 403 - materialized_views_are(schema, materialized_views) should pass +ok 404 - materialized_views_are(schema, materialized_views) should have the proper description +ok 405 - materialized_views_are(schema, materialized_views) should have the proper diagnostics +ok 406 - materialized_views_are(views) should pass +ok 407 - materialized_views_are(views) should have the proper description +ok 408 - materialized_views_are(views) should have the proper diagnostics +ok 409 - materialized_views_are(views, desc) should pass +ok 410 - materialized_views_are(views, desc) should have the proper description +ok 411 - materialized_views_are(views, desc) should have the proper diagnostics +ok 412 - materialized_views_are(schema, materialized_views) missing should fail +ok 413 - materialized_views_are(schema, materialized_views) missing should have the proper description +ok 414 - materialized_views_are(schema, materialized_views) missing should have the proper diagnostics +ok 415 - materialized_views_are(materialized_views) missing should fail +ok 416 - materialized_views_are(materialized_views) missing should have the proper description +ok 417 - materialized_views_are(materialized_views) missing should have the proper diagnostics +ok 418 - materialized_views_are(schema, materialized_views) extra should fail +ok 419 - materialized_views_are(schema, materialized_views) extra should have the proper description +ok 420 - materialized_views_are(schema, materialized_views) extra should have the proper diagnostics +ok 421 - materialized_views_are(materialized_views) extra should fail +ok 422 - materialized_views_are(materialized_views) extra should have the proper description +ok 423 - materialized_views_are(materialized_views) extra should have the proper diagnostics +ok 424 - materialized_views_are(schema, materialized_views) extra and missing should fail +ok 425 - materialized_views_are(schema, materialized_views) extra and missing should have the proper description +ok 426 - materialized_views_are(schema, materialized_views) extra and missing should have the proper diagnostics +ok 427 - materialized_views_are(materialized_views) extra and missing should fail +ok 428 - materialized_views_are(materialized_views) extra and missing should have the proper description +ok 429 - materialized_views_are(materialized_views) extra and missing should have the proper diagnostics diff --git a/test/expected/hastap.out b/test/expected/hastap.out index 0983558ef853..1b888e9bf07a 100644 --- a/test/expected/hastap.out +++ b/test/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..798 +1..828 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -798,3 +798,33 @@ ok 795 - hasnt_foreign_table(tab, desc) should have the proper diagnostics ok 796 - hasnt_foreign_table(sch, tab, desc) should fail ok 797 - hasnt_foreign_table(sch, tab, desc) should have the proper description ok 798 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics +ok 799 - has_materialized_view(non-existent materialized_view) should fail +ok 800 - has_materialized_view(non-existent materialized_view) should have the proper description +ok 801 - has_materialized_view(non-existent materialized_view) should have the proper diagnostics +ok 802 - has_materialized_view(non-existent materialized_view, desc) should fail +ok 803 - has_materialized_view(non-existent materialized_view, desc) should have the proper description +ok 804 - has_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics +ok 805 - has_materialized_view(sch, non-existtent materialized_view, desc) should fail +ok 806 - has_materialized_view(sch, non-existtent materialized_view, desc) should have the proper description +ok 807 - has_materialized_view(sch, non-existtent materialized_view, desc) should have the proper diagnostics +ok 808 - has_materialized_view(materialized_viewiew, desc) should pass +ok 809 - has_materialized_view(materialized_viewiew, desc) should have the proper description +ok 810 - has_materialized_view(materialized_viewiew, desc) should have the proper diagnostics +ok 811 - has_materialized_view(sch, materialized_view, desc) should pass +ok 812 - has_materialized_view(sch, materialized_view, desc) should have the proper description +ok 813 - has_materialized_view(sch, materialized_view, desc) should have the proper diagnostics +ok 814 - hasnt_materialized_view(non-existent materialized_view) should pass +ok 815 - hasnt_materialized_view(non-existent materialized_view) should have the proper description +ok 816 - hasnt_materialized_view(non-existent materialized_view) should have the proper diagnostics +ok 817 - hasnt_materialized_view(non-existent materialized_view, desc) should pass +ok 818 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper description +ok 819 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics +ok 820 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should pass +ok 821 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should have the proper description +ok 822 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should have the proper diagnostics +ok 823 - hasnt_materialized_view(materialized_viewiew, desc) should fail +ok 824 - hasnt_materialized_view(materialized_viewiew, desc) should have the proper description +ok 825 - hasnt_materialized_view(materialized_viewiew, desc) should have the proper diagnostics +ok 826 - hasnt_materialized_view(sch, materialized_view, desc) should fail +ok 827 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper description +ok 828 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper diagnostics diff --git a/test/expected/ownership.out b/test/expected/ownership.out index ecef7aec473e..6c84101dec28 100644 --- a/test/expected/ownership.out +++ b/test/expected/ownership.out @@ -1,5 +1,5 @@ \unset ECHO -1..357 +1..384 ok 1 - db_owner_is(db, user, desc) should pass ok 2 - db_owner_is(db, user, desc) should have the proper description ok 3 - db_owner_is(db, user, desc) should have the proper diagnostics @@ -357,3 +357,30 @@ ok 354 - type_owner_is(non-type, user, desc) should have the proper diagnostics ok 355 - type_owner_is(type, non-user, desc) should fail ok 356 - type_owner_is(type, non-user, desc) should have the proper description ok 357 - type_owner_is(type, non-user, desc) should have the proper diagnostics +ok 358 - materialized_view_owner_is(sch, materialized_view, user, desc) should pass +ok 359 - materialized_view_owner_is(sch, materialized_view, user, desc) should have the proper description +ok 360 - materialized_view_owner_is(sch, materialized_view, user, desc) should have the proper diagnostics +ok 361 - materialized_view_owner_is(sch, materialized_view, user) should pass +ok 362 - materialized_view_owner_is(sch, materialized_view, user) should have the proper description +ok 363 - materialized_view_owner_is(sch, materialized_view, user) should have the proper diagnostics +ok 364 - materialized_view_owner_is(non-sch, materialized_view, user) should fail +ok 365 - materialized_view_owner_is(non-sch, materialized_view, user) should have the proper description +ok 366 - materialized_view_owner_is(non-sch, materialized_view, user) should have the proper diagnostics +ok 367 - materialized_view_owner_is(sch, non-materialized_view, user) should fail +ok 368 - materialized_view_owner_is(sch, non-materialized_view, user) should have the proper description +ok 369 - materialized_view_owner_is(sch, non-materialized_view, user) should have the proper diagnostics +ok 370 - materialized_view_owner_is(materialized_view, user, desc) should pass +ok 371 - materialized_view_owner_is(materialized_view, user, desc) should have the proper description +ok 372 - materialized_view_owner_is(materialized_view, user, desc) should have the proper diagnostics +ok 373 - materialized_view_owner_is(view, user) should pass +ok 374 - materialized_view_owner_is(view, user) should have the proper description +ok 375 - materialized_view_owner_is(view, user) should have the proper diagnostics +ok 376 - materialized_view_owner_is(non-materialized_view, user) should fail +ok 377 - materialized_view_owner_is(non-materialized_view, user) should have the proper description +ok 378 - materialized_view_owner_is(non-materialized_view, user) should have the proper diagnostics +ok 379 - materialized_view_owner_is(sch, seq, user, desc) should fail +ok 380 - materialized_view_owner_is(sch, seq, user, desc) should have the proper description +ok 381 - materialized_view_owner_is(sch, seq, user, desc) should have the proper diagnostics +ok 382 - materialized_view_owner_is(seq, user, desc) should fail +ok 383 - materialized_view_owner_is(seq, user, desc) should have the proper description +ok 384 - materialized_view_owner_is(seq, user, desc) should have the proper diagnostics diff --git a/test/sql/aretap.sql b/test/sql/aretap.sql index 3301c08a0f85..6ac715204b16 100644 --- a/test/sql/aretap.sql +++ b/test/sql/aretap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(399); +SELECT plan(429); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -69,6 +69,9 @@ CREATE TYPE someschema."myType" AS ( foo INT ); +CREATE MATERIALIZED VIEW public.moo AS SELECT * FROM foo; +CREATE MATERIALIZED VIEW public.mou AS SELECT * FROM fou; + RESET client_min_messages; /****************************************************************************/ @@ -1459,6 +1462,104 @@ SELECT * FROM check_test( howdy' ); +/****************************************************************************/ +-- Test materialized_views_are(). +SELECT * FROM check_test( + materialized_views_are( 'public', ARRAY['mou', 'moo'], 'whatever' ), + true, + 'materialized_views_are(schema, materialized_views, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + materialized_views_are( 'public', ARRAY['mou', 'moo'] ), + true, + 'materialized_views_are(schema, materialized_views)', + 'Schema public should have the correct materialized views', + '' +); + +SELECT * FROM check_test( + materialized_views_are( ARRAY['mou', 'moo'] ), + true, + 'materialized_views_are(views)', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct materialized views', + '' +); + +SELECT * FROM check_test( + materialized_views_are( ARRAY['mou', 'moo'], 'whatever' ), + true, + 'materialized_views_are(views, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + materialized_views_are( 'public', ARRAY['mou', 'moo', 'bar'] ), + false, + 'materialized_views_are(schema, materialized_views) missing', + 'Schema public should have the correct materialized views', + ' Missing Materialized views: + bar' +); + +SELECT * FROM check_test( + materialized_views_are( ARRAY['mou', 'moo', 'bar'] ), + false, + 'materialized_views_are(materialized_views) missing', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct materialized views', + ' Missing Materialized views: + bar' +); + +SELECT * FROM check_test( + materialized_views_are( 'public', ARRAY['mou'] ), + false, + 'materialized_views_are(schema, materialized_views) extra', + 'Schema public should have the correct materialized views', + ' Extra Materialized views: + moo' +); + +SELECT * FROM check_test( + materialized_views_are( ARRAY['mou'] ), + false, + 'materialized_views_are(materialized_views) extra', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct materialized views', + ' Extra Materialized views: + moo' +); + +SELECT * FROM check_test( + materialized_views_are( 'public', ARRAY['bar', 'baz'] ), + false, + 'materialized_views_are(schema, materialized_views) extra and missing', + 'Schema public should have the correct materialized views', + ' Extra Materialized views: + mo[ou] + mo[ou] + Missing Materialized views: + ba[rz] + ba[rz]', + true +); + +SELECT * FROM check_test( + materialized_views_are( ARRAY['bar', 'baz'] ), + false, + 'materialized_views_are(materialized_views) extra and missing', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct materialized views', + ' Extra Materialized views:' || ' + mo[ou] + mo[ou] + Missing Materialized views:' || ' + ba[rz] + ba[rz]', + true +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index e64f0e9f16a9..2c6a0f139ff5 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(798); +SELECT plan(828); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -32,6 +32,9 @@ CREATE DOMAIN public."myDomain" AS TEXT CHECK(TRUE); CREATE SEQUENCE public.someseq; CREATE SCHEMA someschema; + +CREATE MATERIALIZED VIEW public.mview AS SELECT * FROM public.sometab; + RESET client_min_messages; /****************************************************************************/ @@ -2415,6 +2418,92 @@ SELECT * FROM check_test( SELECT * FROM test_fdw(); +/****************************************************************************/ +-- Test has_materialized_view(). + +SELECT * FROM check_test( + has_materialized_view( '__SDFSDFD__' ), + false, + 'has_materialized_view(non-existent materialized_view)', + 'Materialized view "__SDFSDFD__" should exist', + '' +); + +SELECT * FROM check_test( + has_materialized_view( '__SDFSDFD__', 'howdy' ), + false, + 'has_materialized_view(non-existent materialized_view, desc)', + 'howdy', + '' +); + +SELECT * FROM check_test( + has_materialized_view( 'foo', '__SDFSDFD__', 'desc' ), + false, + 'has_materialized_view(sch, non-existtent materialized_view, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + has_materialized_view( 'mview', 'yowza' ), + true, + 'has_materialized_view(materialized_viewiew, desc)', + 'yowza', + '' +); + +SELECT * FROM check_test( + has_materialized_view( 'public', 'mview', 'desc' ), + true, + 'has_materialized_view(sch, materialized_view, desc)', + 'desc', + '' +); + +/****************************************************************************/ +-- Test hasnt_materialized_view(). + +SELECT * FROM check_test( + hasnt_materialized_view( '__SDFSDFD__' ), + true, + 'hasnt_materialized_view(non-existent materialized_view)', + 'Materialized view "__SDFSDFD__" should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_materialized_view( '__SDFSDFD__', 'howdy' ), + true, + 'hasnt_materialized_view(non-existent materialized_view, desc)', + 'howdy', + '' +); + +SELECT * FROM check_test( + hasnt_materialized_view( 'foo', '__SDFSDFD__', 'desc' ), + true, + 'hasnt_materialized_view(sch, non-existtent materialized_view, desc)', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_materialized_view( 'mview', 'yowza' ), + false, + 'hasnt_materialized_view(materialized_viewiew, desc)', + 'yowza', + '' +); + +SELECT * FROM check_test( + hasnt_materialized_view( 'public', 'mview', 'desc' ), + false, + 'hasnt_materialized_view(sch, materialized_view, desc)', + 'desc', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index f5791b815a40..2f69f5962331 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(357); +SELECT plan(384); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -38,6 +38,8 @@ CREATE INDEX idx_name ON someschema.anothertab(name); CREATE FUNCTION public.somefunction(int) RETURNS VOID LANGUAGE SQL AS ''; +CREATE MATERIALIZED VIEW public.somemview AS SELECT * FROM public.sometab; + RESET client_min_messages; /****************************************************************************/ @@ -1191,6 +1193,81 @@ SELECT * FROM check_test( want: __no-one' ); +/****************************************************************************/ +-- Test materialized_view_owner_is(). +SELECT * FROM check_test( + materialized_view_owner_is('public', 'somemview', current_user, 'mumble'), + true, + 'materialized_view_owner_is(sch, materialized_view, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + materialized_view_owner_is('public', 'somemview', current_user), + true, + 'materialized_view_owner_is(sch, materialized_view, user)', + 'Materialized view public.somemview should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + materialized_view_owner_is('__not__public', 'somemview', current_user, 'mumble'), + false, + 'materialized_view_owner_is(non-sch, materialized_view, user)', + 'mumble', + ' Materialized view __not__public.somemview does not exist' +); + +SELECT * FROM check_test( + materialized_view_owner_is('public', '__not__somemview', current_user, 'mumble'), + false, + 'materialized_view_owner_is(sch, non-materialized_view, user)', + 'mumble', + ' Materialized view public.__not__somemview does not exist' +); + +SELECT * FROM check_test( + materialized_view_owner_is('somemview', current_user, 'mumble'), + true, + 'materialized_view_owner_is(materialized_view, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + materialized_view_owner_is('somemview', current_user), + true, + 'materialized_view_owner_is(view, user)', + 'Materialized view somemview should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + materialized_view_owner_is('__not__somemview', current_user, 'mumble'), + false, + 'materialized_view_owner_is(non-materialized_view, user)', + 'mumble', + ' Materialized view __not__somemview does not exist' +); + +-- It should ignore the sequence. +SELECT * FROM check_test( + materialized_view_owner_is('public', 'someseq', current_user, 'mumble'), + false, + 'materialized_view_owner_is(sch, seq, user, desc)', + 'mumble', + ' Materialized view public.someseq does not exist' +); + +SELECT * FROM check_test( + materialized_view_owner_is('someseq', current_user, 'mumble'), + false, + 'materialized_view_owner_is(seq, user, desc)', + 'mumble', + ' Materialized view someseq does not exist' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From e2fd7ab1ddbcc914146554edbdd9bacb3007eefb Mon Sep 17 00:00:00 2001 From: Charly Batista Date: Fri, 18 Apr 2014 20:08:06 -0300 Subject: [PATCH 0803/1195] Created PL/pgSQL function that checks the Postgres version. If it's 9.3 or higher, the function create the materialized views and run the tests. Otherwise, it mock those tests with a regular view calling *view* functions instead of *materialized_view*. Resolves #61. --- test/expected/hastap.out | 12 +- test/sql/aretap.sql | 322 ++++++++++++++++++++++++++++----------- test/sql/hastap.sql | 292 ++++++++++++++++++++++++++--------- test/sql/ownership.sql | 259 +++++++++++++++++++++++-------- 4 files changed, 654 insertions(+), 231 deletions(-) diff --git a/test/expected/hastap.out b/test/expected/hastap.out index 1b888e9bf07a..43e2fffc912b 100644 --- a/test/expected/hastap.out +++ b/test/expected/hastap.out @@ -807,9 +807,9 @@ ok 804 - has_materialized_view(non-existent materialized_view, desc) should have ok 805 - has_materialized_view(sch, non-existtent materialized_view, desc) should fail ok 806 - has_materialized_view(sch, non-existtent materialized_view, desc) should have the proper description ok 807 - has_materialized_view(sch, non-existtent materialized_view, desc) should have the proper diagnostics -ok 808 - has_materialized_view(materialized_viewiew, desc) should pass -ok 809 - has_materialized_view(materialized_viewiew, desc) should have the proper description -ok 810 - has_materialized_view(materialized_viewiew, desc) should have the proper diagnostics +ok 808 - has_materialized_view(materialized_view, desc) should pass +ok 809 - has_materialized_view(materialized_view, desc) should have the proper description +ok 810 - has_materialized_view(materialized_view, desc) should have the proper diagnostics ok 811 - has_materialized_view(sch, materialized_view, desc) should pass ok 812 - has_materialized_view(sch, materialized_view, desc) should have the proper description ok 813 - has_materialized_view(sch, materialized_view, desc) should have the proper diagnostics @@ -822,9 +822,9 @@ ok 819 - hasnt_materialized_view(non-existent materialized_view, desc) should ha ok 820 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should pass ok 821 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should have the proper description ok 822 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should have the proper diagnostics -ok 823 - hasnt_materialized_view(materialized_viewiew, desc) should fail -ok 824 - hasnt_materialized_view(materialized_viewiew, desc) should have the proper description -ok 825 - hasnt_materialized_view(materialized_viewiew, desc) should have the proper diagnostics +ok 823 - hasnt_materialized_view(materialized_view, desc) should fail +ok 824 - hasnt_materialized_view(materialized_view, desc) should have the proper description +ok 825 - hasnt_materialized_view(materialized_view, desc) should have the proper diagnostics ok 826 - hasnt_materialized_view(sch, materialized_view, desc) should fail ok 827 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper description ok 828 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper diagnostics diff --git a/test/sql/aretap.sql b/test/sql/aretap.sql index 6ac715204b16..87c92bf5c3cc 100644 --- a/test/sql/aretap.sql +++ b/test/sql/aretap.sql @@ -1,4 +1,4 @@ -\unset ECHO +\unset ECHO \i test/setup.sql SELECT plan(429); @@ -69,9 +69,6 @@ CREATE TYPE someschema."myType" AS ( foo INT ); -CREATE MATERIALIZED VIEW public.moo AS SELECT * FROM foo; -CREATE MATERIALIZED VIEW public.mou AS SELECT * FROM fou; - RESET client_min_messages; /****************************************************************************/ @@ -1464,103 +1461,258 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test materialized_views_are(). -SELECT * FROM check_test( - materialized_views_are( 'public', ARRAY['mou', 'moo'], 'whatever' ), - true, - 'materialized_views_are(schema, materialized_views, desc)', - 'whatever', - '' -); - -SELECT * FROM check_test( - materialized_views_are( 'public', ARRAY['mou', 'moo'] ), - true, - 'materialized_views_are(schema, materialized_views)', - 'Schema public should have the correct materialized views', - '' -); - -SELECT * FROM check_test( - materialized_views_are( ARRAY['mou', 'moo'] ), - true, - 'materialized_views_are(views)', - 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct materialized views', - '' -); - -SELECT * FROM check_test( - materialized_views_are( ARRAY['mou', 'moo'], 'whatever' ), - true, - 'materialized_views_are(views, desc)', - 'whatever', - '' -); - -SELECT * FROM check_test( - materialized_views_are( 'public', ARRAY['mou', 'moo', 'bar'] ), - false, - 'materialized_views_are(schema, materialized_views) missing', - 'Schema public should have the correct materialized views', - ' Missing Materialized views: +CREATE FUNCTION test_materialized_views_are() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() >= 90300 THEN + EXECUTE $E$ + CREATE MATERIALIZED VIEW public.moo AS SELECT * FROM foo; + CREATE MATERIALIZED VIEW public.mou AS SELECT * FROM fou; + $E$; + + FOR tap IN SELECT * FROM check_test( + materialized_views_are( 'public', ARRAY['mou', 'moo'], 'whatever' ), + true, + 'materialized_views_are(schema, materialized_views, desc)', + 'whatever', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + materialized_views_are( 'public', ARRAY['mou', 'moo'] ), + true, + 'materialized_views_are(schema, materialized_views)', + 'Schema public should have the correct materialized views', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + materialized_views_are( ARRAY['mou', 'moo'] ), + true, + 'materialized_views_are(views)', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct materialized views', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + materialized_views_are( ARRAY['mou', 'moo'], 'whatever' ), + true, + 'materialized_views_are(views, desc)', + 'whatever', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + materialized_views_are( 'public', ARRAY['mou', 'moo', 'bar'] ), + false, + 'materialized_views_are(schema, materialized_views) missing', + 'Schema public should have the correct materialized views', + ' Missing Materialized views: bar' -); - -SELECT * FROM check_test( - materialized_views_are( ARRAY['mou', 'moo', 'bar'] ), - false, - 'materialized_views_are(materialized_views) missing', - 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct materialized views', - ' Missing Materialized views: + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + materialized_views_are( ARRAY['mou', 'moo', 'bar'] ), + false, + 'materialized_views_are(materialized_views) missing', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct materialized views', + ' Missing Materialized views: bar' -); - -SELECT * FROM check_test( - materialized_views_are( 'public', ARRAY['mou'] ), - false, - 'materialized_views_are(schema, materialized_views) extra', - 'Schema public should have the correct materialized views', - ' Extra Materialized views: + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + materialized_views_are( 'public', ARRAY['mou'] ), + false, + 'materialized_views_are(schema, materialized_views) extra', + 'Schema public should have the correct materialized views', + ' Extra Materialized views: moo' -); - -SELECT * FROM check_test( - materialized_views_are( ARRAY['mou'] ), - false, - 'materialized_views_are(materialized_views) extra', - 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct materialized views', - ' Extra Materialized views: + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + materialized_views_are( ARRAY['mou'] ), + false, + 'materialized_views_are(materialized_views) extra', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct materialized views', + ' Extra Materialized views: moo' -); - -SELECT * FROM check_test( - materialized_views_are( 'public', ARRAY['bar', 'baz'] ), - false, - 'materialized_views_are(schema, materialized_views) extra and missing', - 'Schema public should have the correct materialized views', - ' Extra Materialized views: + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + materialized_views_are( 'public', ARRAY['bar', 'baz'] ), + false, + 'materialized_views_are(schema, materialized_views) extra and missing', + 'Schema public should have the correct materialized views', + ' Extra Materialized views: mo[ou] mo[ou] Missing Materialized views: ba[rz] ba[rz]', - true -); - -SELECT * FROM check_test( - materialized_views_are( ARRAY['bar', 'baz'] ), - false, - 'materialized_views_are(materialized_views) extra and missing', - 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct materialized views', - ' Extra Materialized views:' || ' + true + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + materialized_views_are( ARRAY['bar', 'baz'] ), + false, + 'materialized_views_are(materialized_views) extra and missing', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct materialized views', + ' Extra Materialized views:' || ' mo[ou] mo[ou] Missing Materialized views:' || ' ba[rz] ba[rz]', - true -); + true + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + ELSE + -- Fake it with views_are + FOR tap IN SELECT * FROM check_test( + views_are( 'public', ARRAY['vou', 'voo'], 'whatever' ), + true, + 'materialized_views_are(schema, materialized_views, desc)', + 'whatever', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + views_are( 'public', ARRAY['vou', 'voo'] ), + true, + 'materialized_views_are(schema, materialized_views)', + 'Schema public should have the correct views', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + views_are( ARRAY['vou', 'voo'] ), + true, + 'materialized_views_are(views)', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + views_are( ARRAY['vou', 'voo'], 'whatever' ), + true, + 'materialized_views_are(views, desc)', + 'whatever', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + views_are( 'public', ARRAY['vou', 'voo', 'bar'] ), + false, + 'materialized_views_are(schema, materialized_views) missing', + 'Schema public should have the correct views', + ' Missing views: + bar' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + views_are( ARRAY['vou', 'voo', 'bar'] ), + false, + 'materialized_views_are(materialized_views) missing', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views', + ' Missing views: + bar' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + views_are( 'public', ARRAY['vou'] ), + false, + 'materialized_views_are(schema, materialized_views) extra', + 'Schema public should have the correct views', + ' Extra views: + voo' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + views_are( ARRAY['vou'] ), + false, + 'materialized_views_are(materialized_views) extra', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views', + ' Extra views: + voo' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + views_are( 'public', ARRAY['bar', 'baz'] ), + false, + 'materialized_views_are(schema, materialized_views) extra and missing', + 'Schema public should have the correct views', + ' Extra views: + vo[ou] + vo[ou] + Missing views: + ba[rz] + ba[rz]', + true + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + views_are( ARRAY['bar', 'baz'] ), + false, + 'materialized_views_are(materialized_views) extra and missing', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct views', + ' Extra views:' || ' + vo[ou] + vo[ou] + Missing views:' || ' + ba[rz] + ba[rz]', + true + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + end IF; + + RETURN; +END; +$$LANGUAGE PLPGSQL; + +SELECT * FROM test_materialized_views_are(); /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); ROLLBACK; + \ No newline at end of file diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index 2c6a0f139ff5..8736178c63c3 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1,4 +1,4 @@ -\unset ECHO +\unset ECHO \i test/setup.sql SELECT plan(828); @@ -33,8 +33,6 @@ CREATE SEQUENCE public.someseq; CREATE SCHEMA someschema; -CREATE MATERIALIZED VIEW public.mview AS SELECT * FROM public.sometab; - RESET client_min_messages; /****************************************************************************/ @@ -2421,88 +2419,236 @@ SELECT * FROM test_fdw(); /****************************************************************************/ -- Test has_materialized_view(). -SELECT * FROM check_test( - has_materialized_view( '__SDFSDFD__' ), - false, - 'has_materialized_view(non-existent materialized_view)', - 'Materialized view "__SDFSDFD__" should exist', - '' -); +CREATE FUNCTION test_has_materialized_view() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() >= 93000 THEN + EXECUTE $E$ + CREATE MATERIALIZED VIEW public.mview AS SELECT * FROM public.sometab; + $E$; -SELECT * FROM check_test( - has_materialized_view( '__SDFSDFD__', 'howdy' ), - false, - 'has_materialized_view(non-existent materialized_view, desc)', - 'howdy', - '' -); + FOR tap IN SELECT * FROM check_test( + has_materialized_view( '__SDFSDFD__' ), + false, + 'has_materialized_view(non-existent materialized_view)', + 'Materialized view "__SDFSDFD__" should exist', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT * FROM check_test( - has_materialized_view( 'foo', '__SDFSDFD__', 'desc' ), - false, - 'has_materialized_view(sch, non-existtent materialized_view, desc)', - 'desc', - '' -); + FOR tap IN SELECT * FROM check_test( + has_materialized_view( '__SDFSDFD__', 'howdy' ), + false, + 'has_materialized_view(non-existent materialized_view, desc)', + 'howdy', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT * FROM check_test( - has_materialized_view( 'mview', 'yowza' ), - true, - 'has_materialized_view(materialized_viewiew, desc)', - 'yowza', - '' -); + FOR tap IN SELECT * FROM check_test( + has_materialized_view( 'foo', '__SDFSDFD__', 'desc' ), + false, + 'has_materialized_view(sch, non-existtent materialized_view, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT * FROM check_test( - has_materialized_view( 'public', 'mview', 'desc' ), - true, - 'has_materialized_view(sch, materialized_view, desc)', - 'desc', - '' -); + FOR tap IN SELECT * FROM check_test( + has_materialized_view( 'mview', 'yowza' ), + true, + 'has_materialized_view(materialized_viewiew, desc)', + 'yowza', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_materialized_view( 'public', 'mview', 'desc' ), + true, + 'has_materialized_view(sch, materialized_view, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + ELSE + FOR tap IN SELECT * FROM check_test( + has_view( '__SDFSDFD__' ), + false, + 'has_materialized_view(non-existent materialized_view)', + 'View "__SDFSDFD__" should exist', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_view( '__SDFSDFD__', 'howdy' ), + false, + 'has_materialized_view(non-existent materialized_view, desc)', + 'howdy', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_view( 'foo', '__SDFSDFD__', 'desc' ), + false, + 'has_materialized_view(sch, non-existtent materialized_view, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_view( 'pg_tables', 'yowza' ), + true, + 'has_materialized_view(materialized_view, desc)', + 'yowza', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_view( 'information_schema', 'tables', 'desc' ), + true, + 'has_materialized_view(sch, materialized_view, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + end if; + + return; +end; $$language PLPGSQL; /****************************************************************************/ -- Test hasnt_materialized_view(). +CREATE FUNCTION test_hasnt_materialized_views_are() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() >= 93000 THEN -SELECT * FROM check_test( - hasnt_materialized_view( '__SDFSDFD__' ), - true, - 'hasnt_materialized_view(non-existent materialized_view)', - 'Materialized view "__SDFSDFD__" should not exist', - '' -); + FOR tap IN SELECT * FROM check_test( + hasnt_materialized_view( '__SDFSDFD__' ), + true, + 'hasnt_materialized_view(non-existent materialized_view)', + 'Materialized view "__SDFSDFD__" should not exist', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT * FROM check_test( - hasnt_materialized_view( '__SDFSDFD__', 'howdy' ), - true, - 'hasnt_materialized_view(non-existent materialized_view, desc)', - 'howdy', - '' -); + FOR tap IN SELECT * FROM check_test( + hasnt_materialized_view( '__SDFSDFD__', 'howdy' ), + true, + 'hasnt_materialized_view(non-existent materialized_view, desc)', + 'howdy', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT * FROM check_test( - hasnt_materialized_view( 'foo', '__SDFSDFD__', 'desc' ), - true, - 'hasnt_materialized_view(sch, non-existtent materialized_view, desc)', - 'desc', - '' -); + FOR tap IN SELECT * FROM check_test( + hasnt_materialized_view( 'foo', '__SDFSDFD__', 'desc' ), + true, + 'hasnt_materialized_view(sch, non-existtent materialized_view, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT * FROM check_test( - hasnt_materialized_view( 'mview', 'yowza' ), - false, - 'hasnt_materialized_view(materialized_viewiew, desc)', - 'yowza', - '' -); + FOR tap IN SELECT * FROM check_test( + hasnt_materialized_view( 'mview', 'yowza' ), + false, + 'hasnt_materialized_view(materialized_viewiew, desc)', + 'yowza', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT * FROM check_test( - hasnt_materialized_view( 'public', 'mview', 'desc' ), - false, - 'hasnt_materialized_view(sch, materialized_view, desc)', - 'desc', - '' -); + FOR tap IN SELECT * FROM check_test( + hasnt_materialized_view( 'public', 'mview', 'desc' ), + false, + 'hasnt_materialized_view(sch, materialized_view, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + else + FOR tap IN SELECT * FROM check_test( + hasnt_view( '__SDFSDFD__' ), + true, + 'hasnt_materialized_view(non-existent materialized_view)', + 'View "__SDFSDFD__" should not exist', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_view( '__SDFSDFD__', 'howdy' ), + true, + 'hasnt_materialized_view(non-existent materialized_view, desc)', + 'howdy', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_view( 'foo', '__SDFSDFD__', 'desc' ), + true, + 'hasnt_materialized_view(sch, non-existtent materialized_view, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_view( 'pg_tables', 'yowza' ), + false, + 'hasnt_materialized_view(materialized_view, desc)', + 'yowza', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_view( 'information_schema', 'tables', 'desc' ), + false, + 'hasnt_materialized_view(sch, materialized_view, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + end if; + + return; +end; $$language PLPGSQL; + +SELECT * FROM test_has_materialized_view(); + +SELECT * FROm test_hasnt_materialized_views_are(); /****************************************************************************/ -- Finish the tests and clean up. diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 2f69f5962331..98fade5aee3d 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -1,4 +1,4 @@ -\unset ECHO +\unset ECHO \i test/setup.sql SELECT plan(384); @@ -38,8 +38,6 @@ CREATE INDEX idx_name ON someschema.anothertab(name); CREATE FUNCTION public.somefunction(int) RETURNS VOID LANGUAGE SQL AS ''; -CREATE MATERIALIZED VIEW public.somemview AS SELECT * FROM public.sometab; - RESET client_min_messages; /****************************************************************************/ @@ -1195,78 +1193,205 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test materialized_view_owner_is(). -SELECT * FROM check_test( - materialized_view_owner_is('public', 'somemview', current_user, 'mumble'), - true, - 'materialized_view_owner_is(sch, materialized_view, user, desc)', - 'mumble', - '' -); +CREATE FUNCTION test_materialized_view_owner_is() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() >= 93000 THEN + EXECUTE $E$ + CREATE MATERIALIZED VIEW public.somemview AS SELECT * FROM public.sometab; + $E$; -SELECT * FROM check_test( - materialized_view_owner_is('public', 'somemview', current_user), - true, - 'materialized_view_owner_is(sch, materialized_view, user)', - 'Materialized view public.somemview should be owned by ' || current_user, - '' -); + FOR tap IN SELECT * FROM check_test( + materialized_view_owner_is('public', 'somemview', current_user, 'mumble'), + true, + 'materialized_view_owner_is(sch, materialized_view, user, desc)', + 'mumble', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT * FROM check_test( - materialized_view_owner_is('__not__public', 'somemview', current_user, 'mumble'), - false, - 'materialized_view_owner_is(non-sch, materialized_view, user)', - 'mumble', - ' Materialized view __not__public.somemview does not exist' -); + FOR tap IN SELECT * FROM check_test( + materialized_view_owner_is('public', 'somemview', current_user), + true, + 'materialized_view_owner_is(sch, materialized_view, user)', + 'Materialized view public.somemview should be owned by ' || current_user, + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT * FROM check_test( - materialized_view_owner_is('public', '__not__somemview', current_user, 'mumble'), - false, - 'materialized_view_owner_is(sch, non-materialized_view, user)', - 'mumble', - ' Materialized view public.__not__somemview does not exist' -); + FOR tap IN SELECT * FROM check_test( + materialized_view_owner_is('__not__public', 'somemview', current_user, 'mumble'), + false, + 'materialized_view_owner_is(non-sch, materialized_view, user)', + 'mumble', + ' Materialized view __not__public.somemview does not exist' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT * FROM check_test( - materialized_view_owner_is('somemview', current_user, 'mumble'), - true, - 'materialized_view_owner_is(materialized_view, user, desc)', - 'mumble', - '' -); + FOR tap IN SELECT * FROM check_test( + materialized_view_owner_is('public', '__not__somemview', current_user, 'mumble'), + false, + 'materialized_view_owner_is(sch, non-materialized_view, user)', + 'mumble', + ' Materialized view public.__not__somemview does not exist' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT * FROM check_test( - materialized_view_owner_is('somemview', current_user), - true, - 'materialized_view_owner_is(view, user)', - 'Materialized view somemview should be owned by ' || current_user, - '' -); + FOR tap IN SELECT * FROM check_test( + materialized_view_owner_is('somemview', current_user, 'mumble'), + true, + 'materialized_view_owner_is(materialized_view, user, desc)', + 'mumble', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT * FROM check_test( - materialized_view_owner_is('__not__somemview', current_user, 'mumble'), - false, - 'materialized_view_owner_is(non-materialized_view, user)', - 'mumble', - ' Materialized view __not__somemview does not exist' -); + FOR tap IN SELECT * FROM check_test( + materialized_view_owner_is('somemview', current_user), + true, + 'materialized_view_owner_is(view, user)', + 'Materialized view somemview should be owned by ' || current_user, + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; --- It should ignore the sequence. -SELECT * FROM check_test( - materialized_view_owner_is('public', 'someseq', current_user, 'mumble'), - false, - 'materialized_view_owner_is(sch, seq, user, desc)', - 'mumble', - ' Materialized view public.someseq does not exist' -); + FOR tap IN SELECT * FROM check_test( + materialized_view_owner_is('__not__somemview', current_user, 'mumble'), + false, + 'materialized_view_owner_is(non-materialized_view, user)', + 'mumble', + ' Materialized view __not__somemview does not exist' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; -SELECT * FROM check_test( - materialized_view_owner_is('someseq', current_user, 'mumble'), - false, - 'materialized_view_owner_is(seq, user, desc)', - 'mumble', - ' Materialized view someseq does not exist' -); + -- It should ignore the sequence. + FOR tap IN SELECT * FROM check_test( + materialized_view_owner_is('public', 'someseq', current_user, 'mumble'), + false, + 'materialized_view_owner_is(sch, seq, user, desc)', + 'mumble', + ' Materialized view public.someseq does not exist' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + materialized_view_owner_is('someseq', current_user, 'mumble'), + false, + 'materialized_view_owner_is(seq, user, desc)', + 'mumble', + ' Materialized view someseq does not exist' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + else + + FOR tap IN SELECT * FROM check_test( + view_owner_is('public', 'someview', current_user, 'mumble'), + true, + 'materialized_view_owner_is(sch, materialized_view, user, desc)', + 'mumble', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + view_owner_is('public', 'someview', current_user), + true, + 'materialized_view_owner_is(sch, materialized_view, user)', + 'View public.someview should be owned by ' || current_user, + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + view_owner_is('__not__public', 'someview', current_user, 'mumble'), + false, + 'materialized_view_owner_is(non-sch, materialized_view, user)', + 'mumble', + ' View __not__public.someview does not exist' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + view_owner_is('public', '__not__someview', current_user, 'mumble'), + false, + 'materialized_view_owner_is(sch, non-materialized_view, user)', + 'mumble', + ' View public.__not__someview does not exist' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + view_owner_is('someview', current_user, 'mumble'), + true, + 'materialized_view_owner_is(materialized_view, user, desc)', + 'mumble', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + view_owner_is('someview', current_user), + true, + 'materialized_view_owner_is(view, user)', + 'View someview should be owned by ' || current_user, + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + view_owner_is('__not__someview', current_user, 'mumble'), + false, + 'materialized_view_owner_is(non-materialized_view, user)', + 'mumble', + ' View __not__someview does not exist' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + -- It should ignore the sequence. + FOR tap IN SELECT * FROM check_test( + view_owner_is('public', 'someseq', current_user, 'mumble'), + false, + 'materialized_view_owner_is(sch, seq, user, desc)', + 'mumble', + ' View public.someseq does not exist' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + view_owner_is('someseq', current_user, 'mumble'), + false, + 'materialized_view_owner_is(seq, user, desc)', + 'mumble', + ' View someseq does not exist' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + end if; + + return; + +end; $$language PLPGSQL; + +SELECT * from test_materialized_view_owner_is(); /****************************************************************************/ -- Finish the tests and clean up. From 6bd03a7c95eb1cfcfd3f496d30ac33f91501d150 Mon Sep 17 00:00:00 2001 From: Charly Batista Date: Fri, 18 Apr 2014 21:54:09 -0300 Subject: [PATCH 0804/1195] Remove special character to compile the tests in versions 8.x Resolves #61. --- test/sql/aretap.sql | 3 +-- test/sql/hastap.sql | 3 ++- test/sql/ownership.sql | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/test/sql/aretap.sql b/test/sql/aretap.sql index 87c92bf5c3cc..0cecd42b1dea 100644 --- a/test/sql/aretap.sql +++ b/test/sql/aretap.sql @@ -1,4 +1,4 @@ -\unset ECHO +\unset ECHO \i test/setup.sql SELECT plan(429); @@ -1715,4 +1715,3 @@ SELECT * FROM test_materialized_views_are(); -- Finish the tests and clean up. SELECT * FROM finish(); ROLLBACK; - \ No newline at end of file diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index 8736178c63c3..222301fddd8e 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1,4 +1,4 @@ -\unset ECHO +\unset ECHO \i test/setup.sql SELECT plan(828); @@ -2654,3 +2654,4 @@ SELECT * FROm test_hasnt_materialized_views_are(); -- Finish the tests and clean up. SELECT * FROM finish(); ROLLBACK; + diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 98fade5aee3d..a058d6c8cb6a 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -1,4 +1,4 @@ -\unset ECHO +\unset ECHO \i test/setup.sql SELECT plan(384); @@ -1397,3 +1397,4 @@ SELECT * from test_materialized_view_owner_is(); -- Finish the tests and clean up. SELECT * FROM finish(); ROLLBACK; + From 9daec206f538a317533526880b1639eb75e69a6d Mon Sep 17 00:00:00 2001 From: Charly Batista Date: Thu, 24 Apr 2014 10:27:13 -0300 Subject: [PATCH 0805/1195] Documentation new materialized_views functions The documentation of the new materialized_views functions --- doc/pgtap.mmd | 141 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 136 insertions(+), 5 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index e6ea07be7a8f..a50896a15595 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1804,6 +1804,46 @@ missing views, like so: # v_userlog # eated +### `materialized_views_are()` ### + + SELECT materialized_views_are( :schema, :materialized_views, :description ); + SELECT materialized_views_are( :schema, :materialized_views ); + SELECT materialized_views_are( :materialized_views, :description ); + SELECT materialized_views_are( :materialized_views ); + +**Parameters** + +`:schema` +: Name of a schema in which to find materialized views. + +`:materialized_views` +: An array of materialized view names. + +`:description` +: A short description of the test. + +This function tests that all of the materialized views in the named schema, or that are +visible in the search path, are only the materialized views that *should* be there. If the +`:schema` argument is omitted, materialized views will be sought in the search path, +excluding `pg_catalog` and `information_schema` If the description is omitted, +a generally useful default description will be generated. Example: + + SELECT materialized_views_are( + 'myschema', + ARRAY[ 'users', 'widgets', 'gadgets', 'session' ] + ); + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing materialized views, like so: + + # Failed test 92: "Schema public should have the correct materialized views" + # Extra materialized views: + # v_userlog_tmp + # __test_view + # Missing materialized views: + # v_userlog + # eated + ### `sequences_are()` ### SELECT sequences_are( :schema, :sequences, :description ); @@ -2178,7 +2218,7 @@ missing opclasses, like so: : A short description of the test. This function tests that all of the rules on the named relation are only the -rules that *should* be on that relation (a table or a view). If the `:schema` +rules that *should* be on that relation (a table, view or a materialized view). If the `:schema` argument is omitted, the rules must be visible in the search path, excluding `pg_catalog` and `information_schema`. If the description is omitted, a generally useful default description will be generated. Example: @@ -2498,7 +2538,7 @@ specified schema does *not* exist. : A short description of the test. This function tests whether or not a relation exists in the database. -Relations are tables, views, seqences, composite types, foreign tables, and +Relations are tables, views, materialized views, seqences, composite types, foreign tables, and toast tables. The first argument is a schema name, the second is a relation name, and the third is the test description. If you omit the schema, the relation must be visible in the search path. Example: @@ -2627,6 +2667,53 @@ exist". This function is the inverse of `has_view()`. The test passes if the specified view does *not* exist. +### `has_materialized_view()` ### + + SELECT has_materialized_view( :schema, :materialized_view, :description ); + SELECT has_materialized_view( :materialized_view, :description ); + SELECT has_materialized_view( :materialized_view ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the materialized view. + +`:materialized_view` +: Name of a materialized view. + +`:description` +: A short description of the test. + +This function tests whether or not a materialized view exists in the database. The first +argument is a schema name, the second is a materialized view name, and the third is the +test description. If you omit the schema, the materialized view must be visible in the +search path. Example: + + SELECT has_materialized_view('myschema', 'some_materialized_view'); + +If you omit the test description, it will be set to "Materialized view `:materialized_view` should +exist". + +### `hasnt_materialized_view()` ### + + SELECT hasnt_materialized_view( :schema, :materialized_view, :description ); + SELECT hasnt_materialized_view( :materialized_view, :description ); + SELECT hasnt_materialized_view( :materialized_view ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the materialized view. + +`:materialized_view` +: Name of a materialized view. + +`:description` +: A short description of the test. + +This function is the inverse of `has_view()`. The test passes if the +specified materialized view does *not* exist. + ### `has_sequence()` ### SELECT has_sequence( :schema, :sequence, :description ); @@ -3610,7 +3697,7 @@ So we have the assertions to validate 'em. `:description` : A short description of the test. -Tests whether or not a column exists in a given table, view, or composite +Tests whether or not a column exists in a given table, view, materialized view or composite type. The first argument is the schema name, the second the table name, the third the column name, and the fourth is the test description. If the schema is omitted, the table must be visible in the search path. If the test @@ -3638,7 +3725,7 @@ exist". : A short description of the test. This function is the inverse of `has_column()`. The test passes if the -specified column does *not* exist in the specified table, view, or composite +specified column does *not* exist in the specified table, view, materialized view or composite type. ### `col_not_null()` ### @@ -5316,7 +5403,7 @@ the diagnostics will look something like: `:description` : A short description of the test. -Tests the ownership of a relation. Relations are tables, views, seqences, +Tests the ownership of a relation. Relations are tables, views, materialized views, seqences, composite types, foreign tables, and toast tables. If the `:description` argument is omitted, an appropriate description will be created. Examples: @@ -5431,6 +5518,50 @@ diagnostics will look something like: # have: postgres # want: root +### `materialized_view_owner_is ()` ### + + SELECT materialized_view_owner_is ( :schema, :materialized_view, :user, :description ); + SELECT materialized_view_owner_is ( :materialized_view, :user, :description ); + SELECT materialized_view_owner_is ( :schema, :materialized_view, :user ); + SELECT materialized_view_owner_is ( :materialized_view, :user ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the `:materialized_view`. + +`:materialized_view` +: Name of a materialized view. + +`:user` +: Name of a user. + +`:description` +: A short description of the test. + +Tests the ownership of a materialized view. If the `:description` argument is omitted, an +appropriate description will be created. Examples: + + SELECT view_owner_is( + 'public', 'mymaterializedview', 'someuser', + 'mymaterializedview should be owned by someuser' + ); + SELECT materialized_view_owner_is( 'widgets', current_user ); + +In the event that the test fails because the materialized view in question does not +actually exist or is not visible, you will see an appropriate diagnostic such +as: + + # Failed test 16: "Materialized view foo should be owned by www" + # Materialized view foo does not exist + +If the test fails because the materialized view is not owned by the specified user, the +diagnostics will look something like: + + # Failed test 17: "Materialized view bar should be owned by root" + # have: postgres + # want: root + ### `sequence_owner_is ()` ### SELECT sequence_owner_is ( :schema, :sequence, :user, :description ); From 5351d1a6827b8023b0e77bdb4becf1cd12c30f81 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 24 Apr 2014 10:26:39 -0700 Subject: [PATCH 0806/1195] Credit @cfrankl. --- Changes | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 9aa73305576e..168030bd8a8a 100644 --- a/Changes +++ b/Changes @@ -3,7 +3,12 @@ Revision history for pgTAP 0.95.0 --------------------------- - +* Added materialized-view testing assertions functions: + + `materialized_views_are()` + + `has_materialized_view()` + + `hasnt_materialized_view()` + + `materialized_view_owner_is()` + Thanks to Charly Batista for the contribution! 0.94.0 2014-01-07T01:32:36Z --------------------------- From 048389e8ed63af1b392657acec06340a1509237d Mon Sep 17 00:00:00 2001 From: Ryan Duryea Date: Wed, 30 Apr 2014 16:53:45 -0700 Subject: [PATCH 0807/1195] Fix error in docs. Change throws_ok => performs_ok --- doc/pgtap.mmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index a50896a15595..0333993c36b4 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1023,7 +1023,7 @@ number of milliseconds. An example: The first argument should be the name of a prepared statement or a string representing the query to be executed (see the [summary](#Pursuing+Your+Query) -for query argument details). `throws_ok()` will use the PL/pgSQL `EXECUTE` +for query argument details). `performs_ok()` will use the PL/pgSQL `EXECUTE` statement to execute the query. The second argument is the maximum number of milliseconds it should take for From 033b9f3068d3acacc40a1afd93f3e94873c21629 Mon Sep 17 00:00:00 2001 From: Ryan Duryea Date: Wed, 30 Apr 2014 16:55:13 -0700 Subject: [PATCH 0808/1195] Strip some trailing whitespace --- doc/pgtap.mmd | 4 ++-- sql/pgtap.sql.in | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 0333993c36b4..5d491eba280b 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1843,7 +1843,7 @@ missing materialized views, like so: # Missing materialized views: # v_userlog # eated - + ### `sequences_are()` ### SELECT sequences_are( :schema, :sequences, :description ); @@ -7421,7 +7421,7 @@ No changes. Everything should just work. test for columns specified with `DEFAULT NULL` (even though that's the implied default default). -* The `has_enums()` function won't work. +* The `has_enums()` function won't work. * A number of assignments casts are added to increase compatibility. The casts are: diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 136f952db81b..aaf6f6d9d2b8 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -9195,4 +9195,4 @@ 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; - + From 33496001b24f7077b2728d31872c58035494651f Mon Sep 17 00:00:00 2001 From: Ryan Duryea Date: Wed, 30 Apr 2014 16:56:00 -0700 Subject: [PATCH 0809/1195] Rename perform.sql => performs_ok.sql --- test/sql/{perform.sql => performs_ok.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/sql/{perform.sql => performs_ok.sql} (100%) diff --git a/test/sql/perform.sql b/test/sql/performs_ok.sql similarity index 100% rename from test/sql/perform.sql rename to test/sql/performs_ok.sql From cccf6a070a96d7e5dfebb5349996a27c75f61e39 Mon Sep 17 00:00:00 2001 From: Ryan Duryea Date: Wed, 30 Apr 2014 17:41:37 -0700 Subject: [PATCH 0810/1195] Added performs_within() --- compat/gencore | 2 + doc/pgtap.mmd | 74 +++++++++++++++++++++++++++ sql/pgtap--unpackaged--0.91.0.sql | 4 ++ sql/pgtap.sql.in | 80 +++++++++++++++++++++++++++++ sql/uninstall_pgtap.sql.in | 5 ++ test/sql/performs_within.sql | 83 +++++++++++++++++++++++++++++++ 6 files changed, 248 insertions(+) create mode 100644 test/sql/performs_within.sql diff --git a/compat/gencore b/compat/gencore index 2bb61af3fd7e..793dfebe4ff5 100644 --- a/compat/gencore +++ b/compat/gencore @@ -119,6 +119,8 @@ throws_ok lives_ok lives_ok performs_ok +performs_within +_time_trials _ident_array_to_string tap_funky _got_func diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 5d491eba280b..57ef3a08fe1a 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1048,6 +1048,80 @@ string. You will want to account for this and pad your estimates accordingly. It's best to think of this as a brute force comparison of runtimes, in order to ensure that a query is not *really* slow (think seconds). +### `performs_within()` ### + + SELECT performs_within( :sql, :average_milliseconds, :within, :iterations, :description ); + SELECT performs_within( :sql, :average_milliseconds, :within, :description ); + SELECT performs_within( :sql, :average_milliseconds, :within, :iterations); + SELECT performs_within( :sql, :average_milliseconds, :within); + +**Parameters** + +`:sql` +: An SQL statement or the name of a prepared statement, passed as a string. + +`:average_milliseconds` +: Number of milliseconds the query should take on average. + +`:within` +: The number of milliseconds that the average is allowed to vary. + +`:iterations` +: The number of times to run the query. + +`:description` +: A short description of the test. + +This function makes sure that an SQL statement, on average, performs within +an expected window. It does so by running the query a default of 10 times. +It throws out the top and bottom 10% of runs, and averages the middle 80% of +the runs it made. If the average execution time is outside the range specified +by `within`, the test will fails. An example: + + PREPARE fast_query AS SELECT id FROM try WHERE name = 'Larry'; + SELECT performs_within( + 'fast_query', + 250, + 10, + 100, + 'A select by name should be fast' + ); + +The first argument should be the name of a prepared statement or a string +representing the query to be executed (see the [summary](#Pursuing+Your+Query) +for query argument details). `performs_within()` will use the PL/pgSQL `EXECUTE` +statement to execute the query. + +The second argument is the average number of milliseconds it should take for +the SQL statement to execute. This argument is numeric, so you can even use +fractions of milliseconds if it floats your boat. + +The third argument is the number of milliseconds the query is allowed to vary +around the average and still still pass the test. If the query's average is +falls outside this window, either too fast or too slow, it will fail. + +The fourth argument is either the number of iterations or the usual description. +If not provided, `performs_within()` will execute 10 runs of the query and +will generate the description "Should run in $average_milliseconds +/- $within ms". +You'll likely want to provide your own description if you have more than a +couple of these in a test script or function. + +The fifth argument is the usual description as described above, assuming +you've also specified the number of iterations. + +Should a `performs_within()` test fail it produces appropriate diagnostic +messages. For example: + + # Failed test 19: "The lookup should be fast!" + # average runtime: 210.266 ms + # desired average: 200 +/- 10 ms + +*Note:* There is a little extra time included in the execution time for the +the overhead of PL/pgSQL's `EXECUTE`, which must compile and execute the SQL +string. You will want to account for this and pad your estimates accordingly. +It's best to think of this as a brute force comparison of runtimes, in order +to ensure that a query is not *really* slow (think seconds). + Can You Relate? --------------- diff --git a/sql/pgtap--unpackaged--0.91.0.sql b/sql/pgtap--unpackaged--0.91.0.sql index fc2a7e62fc08..e8875c32f75b 100644 --- a/sql/pgtap--unpackaged--0.91.0.sql +++ b/sql/pgtap--unpackaged--0.91.0.sql @@ -77,6 +77,10 @@ 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 ); diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index aaf6f6d9d2b8..d01ebfffb2e9 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -814,6 +814,86 @@ RETURNS TEXT AS $$ ); $$ 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 OR REPLACE FUNCTION _time_trials(TEXT, INT, NUMERIC) +RETURNS TABLE(a_time NUMERIC) 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; +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; + + RETURN QUERY SELECT * + FROM (select unnest(times) AS a_time) t2 + ORDER BY a_time + OFFSET offset_it + LIMIT limit_it; +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( diff --git a/sql/uninstall_pgtap.sql.in b/sql/uninstall_pgtap.sql.in index bce051b42760..65b720074c08 100644 --- a/sql/uninstall_pgtap.sql.in +++ b/sql/uninstall_pgtap.sql.in @@ -759,6 +759,11 @@ DROP FUNCTION _relexists ( NAME ); DROP FUNCTION _relexists ( NAME, NAME ); DROP FUNCTION performs_ok ( TEXT, NUMERIC ); DROP FUNCTION performs_ok ( TEXT, NUMERIC, TEXT ); +DROP FUNCTION performs_within ( TEXT, NUMERIC, NUMERIC, INT, TEXT ); +DROP FUNCTION performs_within ( TEXT, NUMERIC, NUMERIC, TEXT ); +DROP FUNCTION performs_within ( TEXT, NUMERIC, NUMERIC, INT ); +DROP FUNCTION performs_within ( TEXT, NUMERIC, NUMERIC ); +DROP FUNCTION _time_trials( TEXT, INT, NUMERIC ); DROP FUNCTION lives_ok ( TEXT ); DROP FUNCTION lives_ok ( TEXT, TEXT ); DROP FUNCTION throws_ok ( TEXT, int4 ); diff --git a/test/sql/performs_within.sql b/test/sql/performs_within.sql new file mode 100644 index 000000000000..dcba2300ac6d --- /dev/null +++ b/test/sql/performs_within.sql @@ -0,0 +1,83 @@ +\unset ECHO +\i test/setup.sql + +SELECT plan(24); +--SELECT * FROM no_plan(); + +/****************************************************************************/ +-- Test performs_within(). +SELECT * FROM check_test( + performs_within( 'SELECT TRUE', 500, 500, 1, 'whatever' ), + true, + 'simple select', + 'whatever', + '' +); + +SELECT * FROM check_test( + performs_within( 'SELECT TRUE', 500, 500, 1 ), + true, + 'simple select no desc', + 'Should run within 500 +/- 500 ms', + '' +); + +SELECT * FROM check_test( + performs_within( 'SELECT TRUE', 99.99, 99.99 ), + true, + 'simple select numeric', + 'Should run within 99.99 +/- 99.99 ms', + '' +); + +PREPARE mytest AS SELECT TRUE; +SELECT * FROM check_test( + performs_within( 'mytest', 500, 500 ), + true, + 'simple prepare', + 'Should run within 500 +/- 500 ms', + '' +); + +SELECT * FROM check_test( + performs_within( 'EXECUTE mytest', 500, 500 ), + true, + 'simple execute', + 'Should run within 500 +/- 500 ms', + '' +); + +SELECT * FROM check_test( + performs_within( 'SELECT TRUE', 0, 0, 'whatever' ), + false, + 'simple select fail', + 'whatever', + ' average runtime: [[:digit:]]+([.][[:digit:]]+)? ms + desired average: 0 \+/- 0 ms', + true +); + +SELECT * FROM check_test( + performs_within( 'SELECT TRUE', 0, 0 ), + false, + 'simple select no desc fail', + 'Should run within 0 +/- 0 ms', + ' average runtime: [[:digit:]]+([.][[:digit:]]+)? ms + desired average: 0 \+/- 0 ms', + true +); + +SELECT * FROM check_test( + performs_within( 'SELECT TRUE', 0.0, 0.0 ), + false, + 'simple select no desc numeric fail', + 'Should run within 0.0 +/- 0.0 ms', + ' average runtime: [[:digit:]]+([.][[:digit:]]+)? ms + desired average: 0.0 \+/- 0.0 ms', + true +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From f7a28bfa6f06731ceabd2627460f244b334c4898 Mon Sep 17 00:00:00 2001 From: Ryan Duryea Date: Fri, 2 May 2014 09:29:01 -0700 Subject: [PATCH 0811/1195] Rename test/expected/perform.out => performs_ok.out --- test/expected/{perform.out => performs_ok.out} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/expected/{perform.out => performs_ok.out} (100%) diff --git a/test/expected/perform.out b/test/expected/performs_ok.out similarity index 100% rename from test/expected/perform.out rename to test/expected/performs_ok.out From 9fc7751cff7f59838f21d01fa1ac9e8cb25757bc Mon Sep 17 00:00:00 2001 From: Ryan Duryea Date: Fri, 2 May 2014 09:29:18 -0700 Subject: [PATCH 0812/1195] Add test/expected/performs_within.out --- test/expected/performs_within.out | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 test/expected/performs_within.out diff --git a/test/expected/performs_within.out b/test/expected/performs_within.out new file mode 100644 index 000000000000..25f9e68e1812 --- /dev/null +++ b/test/expected/performs_within.out @@ -0,0 +1,26 @@ +\unset ECHO +1..24 +ok 1 - simple select should pass +ok 2 - simple select should have the proper description +ok 3 - simple select should have the proper diagnostics +ok 4 - simple select no desc should pass +ok 5 - simple select no desc should have the proper description +ok 6 - simple select no desc should have the proper diagnostics +ok 7 - simple select numeric should pass +ok 8 - simple select numeric should have the proper description +ok 9 - simple select numeric should have the proper diagnostics +ok 10 - simple prepare should pass +ok 11 - simple prepare should have the proper description +ok 12 - simple prepare should have the proper diagnostics +ok 13 - simple execute should pass +ok 14 - simple execute should have the proper description +ok 15 - simple execute should have the proper diagnostics +ok 16 - simple select fail should fail +ok 17 - simple select fail should have the proper description +ok 18 - simple select fail should have the proper diagnostics +ok 19 - simple select no desc fail should fail +ok 20 - simple select no desc fail should have the proper description +ok 21 - simple select no desc fail should have the proper diagnostics +ok 22 - simple select no desc numeric fail should fail +ok 23 - simple select no desc numeric fail should have the proper description +ok 24 - simple select no desc numeric fail should have the proper diagnostics From 5747328f3728b5d8949a860885675356a4fcc607 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 2 May 2014 11:07:51 -0700 Subject: [PATCH 0813/1195] Credit Ryan Duryea. --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 168030bd8a8a..1f5b932d878f 100644 --- a/Changes +++ b/Changes @@ -9,6 +9,8 @@ Revision history for pgTAP + `hasnt_materialized_view()` + `materialized_view_owner_is()` Thanks to Charly Batista for the contribution! +* Added `performs_within()`, a function that tests the average performance of + an SQL statement over a number of iterations. Contributed by Ryan Duryea. 0.94.0 2014-01-07T01:32:36Z --------------------------- From 27e0545d98ef08372550fd106eceff97ca336dcb Mon Sep 17 00:00:00 2001 From: Ryan Duryea Date: Fri, 2 May 2014 11:44:49 -0700 Subject: [PATCH 0814/1195] Try to escape expected output of performs_within to satisfy pre-9.1 --- test/sql/performs_within.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/sql/performs_within.sql b/test/sql/performs_within.sql index dcba2300ac6d..71842fd8a366 100644 --- a/test/sql/performs_within.sql +++ b/test/sql/performs_within.sql @@ -52,8 +52,8 @@ SELECT * FROM check_test( false, 'simple select fail', 'whatever', - ' average runtime: [[:digit:]]+([.][[:digit:]]+)? ms - desired average: 0 \+/- 0 ms', + ' average runtime: [[:digit:]]+([.][[:digit:]]+)? ms' || + E'\n' || ' desired average: 0 \+/- 0 ms', true ); @@ -62,8 +62,8 @@ SELECT * FROM check_test( false, 'simple select no desc fail', 'Should run within 0 +/- 0 ms', - ' average runtime: [[:digit:]]+([.][[:digit:]]+)? ms - desired average: 0 \+/- 0 ms', + ' average runtime: [[:digit:]]+([.][[:digit:]]+)? ms' || + E'\n' || ' desired average: 0 \+/- 0 ms', true ); @@ -72,8 +72,8 @@ SELECT * FROM check_test( false, 'simple select no desc numeric fail', 'Should run within 0.0 +/- 0.0 ms', - ' average runtime: [[:digit:]]+([.][[:digit:]]+)? ms - desired average: 0.0 \+/- 0.0 ms', + ' average runtime: [[:digit:]]+([.][[:digit:]]+)? ms' || + E'\n' || ' desired average: 0.0 \+/- 0.0 ms', true ); From a32221570691b15d097cce01cc63d4b65276d3ca Mon Sep 17 00:00:00 2001 From: Ryan Duryea Date: Fri, 2 May 2014 20:49:05 -0700 Subject: [PATCH 0815/1195] Rewrite performs_within to work with v8.2 and up --- sql/pgtap.sql.in | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index d01ebfffb2e9..91ba023cec98 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -822,8 +822,9 @@ $$ LANGUAGE sql; -- 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 TABLE(a_time NUMERIC) AS $$ +RETURNS SETOF _time_trial_type AS $$ DECLARE query TEXT := _query($1); iterations ALIAS FOR $2; @@ -834,6 +835,7 @@ DECLARE 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 @@ -848,11 +850,13 @@ BEGIN -- Ensure that with limit the query to returning only the middle X% of runs SELECT GREATEST((return_percent * iterations)::int, 1) INTO limit_it; - RETURN QUERY SELECT * - FROM (select unnest(times) AS a_time) t2 - ORDER BY a_time + 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; + LIMIT limit_it LOOP + RETURN NEXT a_time; + END LOOP; END; $$ LANGUAGE plpgsql; @@ -866,7 +870,7 @@ DECLARE descr ALIAS FOR $5; avg_time NUMERIC; BEGIN - select avg(a_time) from _time_trials(query, iterations, 0.8) t1 INTO avg_time; + 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' From 97c3eb25161d8cce31e949ee42d46b4ba40d4789 Mon Sep 17 00:00:00 2001 From: Ryan Duryea Date: Fri, 2 May 2014 21:28:00 -0700 Subject: [PATCH 0816/1195] Properly escape plus character in tests --- test/sql/performs_within.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/sql/performs_within.sql b/test/sql/performs_within.sql index 71842fd8a366..8d52204e81ac 100644 --- a/test/sql/performs_within.sql +++ b/test/sql/performs_within.sql @@ -53,7 +53,7 @@ SELECT * FROM check_test( 'simple select fail', 'whatever', ' average runtime: [[:digit:]]+([.][[:digit:]]+)? ms' || - E'\n' || ' desired average: 0 \+/- 0 ms', + E'\n' || E' desired average: 0 \\+/- 0 ms', true ); @@ -63,7 +63,7 @@ SELECT * FROM check_test( 'simple select no desc fail', 'Should run within 0 +/- 0 ms', ' average runtime: [[:digit:]]+([.][[:digit:]]+)? ms' || - E'\n' || ' desired average: 0 \+/- 0 ms', + E'\n' || E' desired average: 0 \\+/- 0 ms', true ); @@ -73,7 +73,7 @@ SELECT * FROM check_test( 'simple select no desc numeric fail', 'Should run within 0.0 +/- 0.0 ms', ' average runtime: [[:digit:]]+([.][[:digit:]]+)? ms' || - E'\n' || ' desired average: 0.0 \+/- 0.0 ms', + E'\n' || E' desired average: 0.0 \\+/- 0.0 ms', true ); From d133ef6a9821fea709c6b7fd31bc3fb84f2a4358 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Thu, 31 Jul 2014 19:03:25 -0500 Subject: [PATCH 0817/1195] Change is_member_of to use pg_roles --- sql/pgtap.sql.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 91ba023cec98..e25f57e28c48 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -3792,9 +3792,9 @@ BEGIN SELECT ARRAY( SELECT quote_ident($2[i]) FROM generate_series(1, array_upper($2, 1)) s(i) - LEFT JOIN pg_catalog.pg_user ON usename = $2[i] - WHERE usesysid IS NULL - OR NOT usesysid = ANY ( _grolist($1) ) + 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 From 83a839b506fb5b06c77872405fedb31541b98834 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Thu, 31 Jul 2014 19:07:32 -0500 Subject: [PATCH 0818/1195] Add upgrade script --- sql/.pgtap.sql.in.swp | Bin 0 -> 335872 bytes sql/pgtap--0.94.0--0.95.0.sql | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 sql/.pgtap.sql.in.swp create mode 100644 sql/pgtap--0.94.0--0.95.0.sql diff --git a/sql/.pgtap.sql.in.swp b/sql/.pgtap.sql.in.swp new file mode 100644 index 0000000000000000000000000000000000000000..cfdd4097ad5f38e4a5e9d83e95a835f67991709e GIT binary patch literal 335872 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 O-ZV#kb-D`z`+osB*MDvR literal 0 HcmV?d00001 diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql new file mode 100644 index 000000000000..66ccc3910388 --- /dev/null +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -0,0 +1,28 @@ +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( + ' Users missing from the ' || quote_ident($1) || E' group:\n ' || + array_to_string( missing, E'\n ') + ); +END; +$$ LANGUAGE plpgsql; From 635a2a0a73f0b32d64211746d914c245b0c7b8f5 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Fri, 1 Aug 2014 11:46:04 -0700 Subject: [PATCH 0819/1195] Change terms in is_member_of(). Change "group" to "role". Change "user" to "member". Include these changes in the upgrade script. --- Changes | 2 ++ doc/pgtap.mmd | 36 +++++++++++++++++------------------ sql/pgtap--0.94.0--0.95.0.sql | 9 ++++++++- sql/pgtap.sql.in | 12 ++++++------ test/sql/usergroup.sql | 4 ++-- 5 files changed, 36 insertions(+), 27 deletions(-) diff --git a/Changes b/Changes index 1f5b932d878f..7f1391f582dc 100644 --- a/Changes +++ b/Changes @@ -11,6 +11,8 @@ Revision history for pgTAP Thanks to Charly Batista for the contribution! * Added `performs_within()`, a function that tests the average performance of an SQL statement over a number of iterations. Contributed by Ryan Duryea. +* Changed is_member_of to work with roles instead of users. Patch from Jim + Nasby. 0.94.0 2014-01-07T01:32:36Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 57ef3a08fe1a..037b95e3a704 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5227,21 +5227,21 @@ so. ### `is_member_of()` ### - SELECT is_member_of( :group, :users, :description ); - SELECT is_member_of( :group, :users ); - SELECT is_member_of( :group, :user, :description ); - SELECT is_member_of( :group, :user ); + SELECT is_member_of( :role, :members, :description ); + SELECT is_member_of( :role, :members ); + SELECT is_member_of( :role, :member, :description ); + SELECT is_member_of( :role, :member ); **Parameters** -`:group` -: Name of a PostgreSQL group. +`:role` +: Name of a PostgreSQL group role. -`:users` -: Array of names of users that should be members of the group. +`:members` +: Array of names of roles that should be members of the group role. -`:user` -: Name of a user that should be a member of the group. +`:member` +: Name of a role that should be a member of the group role. `:description` : A short description of the test. @@ -5249,18 +5249,18 @@ so. SELECT is_member_of( 'sweeties', 'anna' 'Anna should be a sweetie' ); SELECT is_member_of( 'meanies', ARRAY['dr_evil', 'dr_no' ] ); -Checks whether a group contains a user or all of an array of users. If the -description is omitted, it will default to "Should have members of group -`:group`." On failure, `is_member_of()` will output diagnostics listing the -missing users, like so: +Checks whether a group role contains a member role or all of an array of +member roles. If the description is omitted, it will default to "Should have +members of role `:role`." On failure, `is_member_of()` will output +diagnostics listing the missing member roles, like so: - # Failed test 370: "Should have members of group meanies" - # Users missing from the meanies group: + # Failed test 370: "Should have members of role meanies" + # Members missing from the meanies role: # theory # agliodbs -If the group does not exist, the diagnostics will tell you that, instead. But -you use `has_group()` to make sure the group exists before you check its +If the group role does not exist, the diagnostics will tell you that, instead. +But you use `has_role()` to make sure the role exists before you check its members, don't you? Of course you do. ### `rule_is_instead()` ### diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql index 66ccc3910388..d76b37be8ef6 100644 --- a/sql/pgtap--0.94.0--0.95.0.sql +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -1,3 +1,4 @@ +-- is_member_of( role, members[], description ) CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ DECLARE @@ -21,8 +22,14 @@ BEGIN RETURN ok( true, $3 ); END IF; RETURN ok( false, $3 ) || E'\n' || diag( - ' Users missing from the ' || quote_ident($1) || E' group:\n ' || + ' Members missing from the ' || quote_ident($1) || E' role:\n ' || array_to_string( missing, E'\n ') ); END; $$ LANGUAGE plpgsql; + +-- 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index e25f57e28c48..e8139659fd5b 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -3777,7 +3777,7 @@ RETURNS oid[] AS $$ ); $$ LANGUAGE sql; --- is_member_of( group, user[], description ) +-- is_member_of( role, members[], description ) CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ DECLARE @@ -3801,25 +3801,25 @@ BEGIN RETURN ok( true, $3 ); END IF; RETURN ok( false, $3 ) || E'\n' || diag( - ' Users missing from the ' || quote_ident($1) || E' group:\n ' || + ' Members missing from the ' || quote_ident($1) || E' role:\n ' || array_to_string( missing, E'\n ') ); END; $$ LANGUAGE plpgsql; --- is_member_of( group, user, description ) +-- 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( group, user[] ) +-- 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 group ' || quote_ident($1) ); + SELECT is_member_of( $1, $2, 'Should have members of role ' || quote_ident($1) ); $$ LANGUAGE SQL; --- is_member_of( group, user ) +-- is_member_of( role, member ) CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME ) RETURNS TEXT AS $$ SELECT is_member_of( $1, ARRAY[$2] ); diff --git a/test/sql/usergroup.sql b/test/sql/usergroup.sql index 577d6a1b2f6f..c140c1c4af3c 100644 --- a/test/sql/usergroup.sql +++ b/test/sql/usergroup.sql @@ -203,7 +203,7 @@ SELECT * FROM check_test( is_member_of('meanies', ARRAY[current_user] ), true, 'is_member_of(meanies, [current_user])', - 'Should have members of group meanies', + 'Should have members of role meanies', '' ); @@ -219,7 +219,7 @@ SELECT * FROM check_test( is_member_of('meanies', current_user ), true, 'is_member_of(meanies, current_user)', - 'Should have members of group meanies', + 'Should have members of role meanies', '' ); From 231070d9e805b42206b6e0965c93d7151e288301 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Fri, 1 Aug 2014 00:05:42 -0500 Subject: [PATCH 0820/1195] Ignore vi .swp files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bea6d1aaac2c..12ae109db8ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.*.swp pgtap.sql pgtap-core.sql pgtap-schema.sql From b4f414550698b0eb3cf19e0f3427cd5f6a7b16e5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 9 Aug 2014 15:03:03 -0700 Subject: [PATCH 0821/1195] GRANT SELECT on views. Otherwise there can be failures when tests are run by an unprivileged user. Thanks to Jim Nasby for the report! --- Changes | 3 +++ sql/pgtap--0.93.0--0.94.0.sql | 2 ++ sql/pgtap.sql.in | 2 ++ 3 files changed, 7 insertions(+) diff --git a/Changes b/Changes index 7f1391f582dc..574d697ef96d 100644 --- a/Changes +++ b/Changes @@ -13,6 +13,9 @@ Revision history for pgTAP an SQL statement over a number of iterations. Contributed by Ryan Duryea. * Changed is_member_of to work with roles instead of users. Patch from Jim Nasby. +* Granted SELECT access to the views created by pgTAP to PUBLIC, to avoid + permission errors when the tests are run by a user other than the owner of + those views. Thanks to Jim Nasby for the report. 0.94.0 2014-01-07T01:32:36Z --------------------------- diff --git a/sql/pgtap--0.93.0--0.94.0.sql b/sql/pgtap--0.93.0--0.94.0.sql index a3d66e4d6d97..b55a7a3d4467 100644 --- a/sql/pgtap--0.93.0--0.94.0.sql +++ b/sql/pgtap--0.93.0--0.94.0.sql @@ -188,3 +188,5 @@ BEGIN END; $$ LANGUAGE plpgsql; +GRANT SELECT ON tap_funky TO PUBLIC; +GRANT SELECT ON pg_all_foreign_keys TO PUBLIC; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index e8139659fd5b..ff375cc7718b 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -9280,3 +9280,5 @@ RETURNS TEXT AS $$ SELECT hasnt_materialized_view( $1, 'Materialized view ' || quote_ident($1) || ' should not exist' ); $$ LANGUAGE SQL; +GRANT SELECT ON tap_funky TO PUBLIC; +GRANT SELECT ON pg_all_foreign_keys TO PUBLIC; From d715ac8c697d0b2115857989812b61652bb2ec3c Mon Sep 17 00:00:00 2001 From: Chad Slaughter Date: Mon, 8 Sep 2014 02:47:47 -0500 Subject: [PATCH 0822/1195] add foregin_tables_are functions. the *_are functions are used by pg_tapgen in the generated test scripts. --- sql/pgtap.sql.in | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index ff375cc7718b..55cccafd70d5 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -9280,5 +9280,36 @@ 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 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 tables' + ); +$$ LANGUAGE SQL; + GRANT SELECT ON tap_funky TO PUBLIC; GRANT SELECT ON pg_all_foreign_keys TO PUBLIC; From cc6c0a75739c1d22d88466616a21781d72611a90 Mon Sep 17 00:00:00 2001 From: Chad Slaughter Date: Tue, 9 Sep 2014 16:44:50 -0500 Subject: [PATCH 0823/1195] fix foreign_table_are default messages. add foreign adjective to the default messages. --- sql/pgtap.sql.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 55cccafd70d5..e36ab669daf5 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -9298,7 +9298,7 @@ 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 tables' + 'Schema ' || quote_ident($1) || ' should have the correct foreign tables' ); $$ LANGUAGE SQL; @@ -9307,7 +9307,7 @@ 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 tables' + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct foreign tables' ); $$ LANGUAGE SQL; From 423998479aecc5889c45dfc391f32d313a24e443 Mon Sep 17 00:00:00 2001 From: Chad Slaughter Date: Tue, 9 Sep 2014 16:46:07 -0500 Subject: [PATCH 0824/1195] tests for foreign_tables_are() functions Replicated the tests used by tables_are. --- test/expected/aretap.out | 32 ++++++++- test/sql/aretap.sql | 146 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 176 insertions(+), 2 deletions(-) diff --git a/test/expected/aretap.out b/test/expected/aretap.out index 69fdee4d3385..1e9dfedaa07b 100644 --- a/test/expected/aretap.out +++ b/test/expected/aretap.out @@ -1,5 +1,5 @@ \unset ECHO -1..429 +1..459 ok 1 - tablespaces_are(tablespaces, desc) should pass ok 2 - tablespaces_are(tablespaces, desc) should have the proper description ok 3 - tablespaces_are(tablespaces, desc) should have the proper diagnostics @@ -429,3 +429,33 @@ ok 426 - materialized_views_are(schema, materialized_views) extra and missing sh ok 427 - materialized_views_are(materialized_views) extra and missing should fail ok 428 - materialized_views_are(materialized_views) extra and missing should have the proper description ok 429 - materialized_views_are(materialized_views) extra and missing should have the proper diagnostics +ok 430 - foreign_tables_are(schema, tables, desc) should pass +ok 431 - foreign_tables_are(schema, tables, desc) should have the proper description +ok 432 - foreign_tables_are(schema, tables, desc) should have the proper diagnostics +ok 433 - foreign_tables_are(tables, desc) should pass +ok 434 - foreign_tables_are(tables, desc) should have the proper description +ok 435 - foreign_tables_are(tables, desc) should have the proper diagnostics +ok 436 - foreign_tables_are(schema, tables) should pass +ok 437 - foreign_tables_are(schema, tables) should have the proper description +ok 438 - foreign_tables_are(schema, tables) should have the proper diagnostics +ok 439 - foreign_tables_are(tables) should pass +ok 440 - foreign_tables_are(tables) should have the proper description +ok 441 - foreign_tables_are(tables) should have the proper diagnostics +ok 442 - foreign_tables_are(schema, tables) missing should fail +ok 443 - foreign_tables_are(schema, tables) missing should have the proper description +ok 444 - foreign_tables_are(schema, tables) missing should have the proper diagnostics +ok 445 - foreign_tables_are(tables) missing should fail +ok 446 - foreign_tables_are(tables) missing should have the proper description +ok 447 - foreign_tables_are(tables) missing should have the proper diagnostics +ok 448 - foreign_tables_are(schema, tables) extra should fail +ok 449 - foreign_tables_are(schema, tables) extra should have the proper description +ok 450 - foreign_tables_are(schema, tables) extra should have the proper diagnostics +ok 451 - foreign_tables_are(tables) extra should fail +ok 452 - foreign_tables_are(tables) extra should have the proper description +ok 453 - foreign_tables_are(tables) extra should have the proper diagnostics +ok 454 - foreign_tables_are(schema, tables) extra and missing should fail +ok 455 - foreign_tables_are(schema, tables) extra and missing should have the proper description +ok 456 - foreign_tables_are(schema, tables) extra and missing should have the proper diagnostics +ok 457 - foreign_tables_are(tables) extra and missing should fail +ok 458 - foreign_tables_are(tables) extra and missing should have the proper description +ok 459 - foreign_tables_are(tables) extra and missing should have the proper diagnostics diff --git a/test/sql/aretap.sql b/test/sql/aretap.sql index 0cecd42b1dea..f43d1f468ac9 100644 --- a/test/sql/aretap.sql +++ b/test/sql/aretap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(429); +SELECT plan(459); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -69,6 +69,23 @@ CREATE TYPE someschema."myType" AS ( foo INT ); + +CREATE FOREIGN DATA WRAPPER null_fdw; +CREATE SERVER server_null_fdw FOREIGN DATA WRAPPER null_fdw; + +CREATE FOREIGN TABLE public.ft_foo( + id INT , + "name" TEXT , + num NUMERIC(10, 2), + "myInt" int +) +SERVER server_null_fdw ; +CREATE FOREIGN TABLE public.ft_bar( + id INT +) +SERVER server_null_fdw ; +; + RESET client_min_messages; /****************************************************************************/ @@ -1712,6 +1729,133 @@ $$LANGUAGE PLPGSQL; SELECT * FROM test_materialized_views_are(); /****************************************************************************/ +/* Foreign Table are function tests */ + +-- foreign_tables_are( schema, tables, description ) +-- CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME, NAME[], TEXT ) +SELECT * FROM check_test( + foreign_tables_are( 'public', ARRAY['ft_foo', 'ft_bar'], 'Correct foreign tables in named schema with custom message' ), + true, + 'foreign_tables_are(schema, tables, desc)', + 'Correct foreign tables in named schema with custom message', + '' +); +-- foreign_tables_are( tables, description ) +--CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME[], TEXT ) +SELECT * FROM check_test( + foreign_tables_are( ARRAY['ft_foo', 'ft_bar'], 'Correct foreign tables in search_path with custom message' ), + true, + 'foreign_tables_are(tables, desc)', + 'Correct foreign tables in search_path with custom message', + '' +); + + +-- foreign_tables_are( schema, tables ) +-- CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME, NAME[] ) +SELECT * FROM check_test( + foreign_tables_are( 'public', ARRAY['ft_foo', 'ft_bar'] ), + true, + 'foreign_tables_are(schema, tables)', + 'Schema public should have the correct foreign tables', + '' +); + +-- foreign_tables_are( tables ) +-- +CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME[] ) + +SELECT * FROM check_test( + foreign_tables_are( ARRAY['ft_foo', 'ft_bar'] ), + true, + 'foreign_tables_are(tables)', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct foreign tables', + '' +); + + +SELECT * FROM check_test( + foreign_tables_are( 'public', ARRAY['ft_foo', 'ft_bar', 'bar'] ), + false, + 'foreign_tables_are(schema, tables) missing', + 'Schema public should have the correct foreign tables', + ' Missing foreign tables: + bar' +); + +SELECT * FROM check_test( + foreign_tables_are( ARRAY['ft_foo', 'ft_bar', 'bar'] ), + false, + 'foreign_tables_are(tables) missing', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct foreign tables', + ' Missing foreign tables: + bar' +); + +SELECT * FROM check_test( + foreign_tables_are( 'public', ARRAY['ft_foo'] ), + false, + 'foreign_tables_are(schema, tables) extra', + 'Schema public should have the correct foreign tables', + ' Extra foreign tables: + ft_bar' +); + +SELECT * FROM check_test( + foreign_tables_are( ARRAY['ft_foo'] ), + false, + 'foreign_tables_are(tables) extra', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct foreign tables', + ' Extra foreign tables: + ft_bar' +); + +SELECT * FROM check_test( + foreign_tables_are( 'public', ARRAY['bar', 'baz'] ), + false, + 'foreign_tables_are(schema, tables) extra and missing', + 'Schema public should have the correct foreign tables', + ' Extra foreign tables: + ft_foo + ft_bar + Missing foreign tables: + ba[rz] + ba[rz]', + true +); +/* +"foreign_tables_are(schema, tables) extra and missing should have the proper diagnostics" +# ' Extra foreign tables: +# ft_foo +# ft_bar +# Missing foreign tables: +# bar +# baz' +# doesn't match: ' Extra foreign tables: +# fo[ou] +# fo[ou] +# Missing foreign tables: +# ba[rz] +# ba[rz]' + + + +*/ + +SELECT * FROM check_test( + foreign_tables_are( ARRAY['bar', 'baz'] ), + false, + 'foreign_tables_are(tables) extra and missing', + 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct foreign tables', + ' Extra foreign tables:' || ' + ft_[fobar]+ + ft_[fobar]+ + Missing foreign tables:' || ' + ba[rz] + ba[rz]', + true +); + + -- Finish the tests and clean up. SELECT * FROM finish(); ROLLBACK; From a5fd999355eb439c593ae977ebeca9a8c9387897 Mon Sep 17 00:00:00 2001 From: Chad Slaughter Date: Tue, 9 Sep 2014 16:53:17 -0500 Subject: [PATCH 0825/1195] Add section for foreign_tables_are() functions. Documentaiton for foreign_tables_are fucntions. I used the tables_are documentation as a starting point. --- doc/pgtap.mmd | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 037b95e3a704..99ec4a23e78b 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1838,6 +1838,46 @@ missing tables, like so: # users # widgets +### `foreign_tables_are()` ### + + SELECT foreign_tables_are( :schema, :foreign_tables, :description ); + SELECT foreign_tables_are( :schema, :foreign_tables ); + SELECT foreign_tables_are( :foreign_tables, :description ); + SELECT foreign_tables_are( :foreign_tables ); + +**Parameters** + +`:schema` +: Name of a schema in which to find foreign tables. + +`:foreign_tables` +: An array of foreign table names. + +`:description` +: A short description of the test. + +This function tests that all of the foreign tables in the named schema, or that are +visible in the search path, are only the foreign tables that *should* be there. If the +`:schema` argument is omitted, foreign tables will be sought in the search path, +excluding `pg_catalog` and `information_schema` If the description is omitted, +a generally useful default description will be generated. Example: + + SELECT foreign_tables_are( + 'myschema', + ARRAY[ 'users', 'widgets', 'gadgets', 'session' ] + ); + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing foreign tables, like so: + + # Failed test 91: "Schema public should have the correct foreign tables" + # Extra foreign tables: + # mallots + # __test_table + # Missing foreign tables: + # users + # widgets + ### `views_are()` ### SELECT views_are( :schema, :views, :description ); From 2d405f6c7d1c60ba39030713aae766b319a2dd20 Mon Sep 17 00:00:00 2001 From: Chad Slaughter Date: Tue, 16 Sep 2014 16:39:23 -0500 Subject: [PATCH 0826/1195] Add fake tests for foreign_tables_are functions in older postgres. Older version of postgres do not support foreign_tables. Rewrite the tests for the foreign_tables_are functions to check the version and run the real tests if foreign tables are supported. If an older version of postgres run pass/fail tests which create the same output as the real tests. --- test/sql/aretap.sql | 178 ++++++++++++++++++++++++++++++-------------- 1 file changed, 122 insertions(+), 56 deletions(-) diff --git a/test/sql/aretap.sql b/test/sql/aretap.sql index f43d1f468ac9..e359458b0b8f 100644 --- a/test/sql/aretap.sql +++ b/test/sql/aretap.sql @@ -70,22 +70,6 @@ CREATE TYPE someschema."myType" AS ( ); -CREATE FOREIGN DATA WRAPPER null_fdw; -CREATE SERVER server_null_fdw FOREIGN DATA WRAPPER null_fdw; - -CREATE FOREIGN TABLE public.ft_foo( - id INT , - "name" TEXT , - num NUMERIC(10, 2), - "myInt" int -) -SERVER server_null_fdw ; -CREATE FOREIGN TABLE public.ft_bar( - id INT -) -SERVER server_null_fdw ; -; - RESET client_min_messages; /****************************************************************************/ @@ -1729,87 +1713,123 @@ $$LANGUAGE PLPGSQL; SELECT * FROM test_materialized_views_are(); /****************************************************************************/ -/* Foreign Table are function tests */ +-- tests for foreign_tables_are functions + +CREATE FUNCTION test_foreign_tables_are() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; + i int; +BEGIN + IF false and pg_version_num() >= 90100 THEN + -- setup tables for tests + CREATE FOREIGN DATA WRAPPER null_fdw; + CREATE SERVER server_null_fdw FOREIGN DATA WRAPPER null_fdw; + CREATE FOREIGN TABLE public.ft_foo( + id INT , + "name" TEXT , + num NUMERIC(10, 2), + "myInt" int + ) + SERVER server_null_fdw ; + CREATE FOREIGN TABLE public.ft_bar( + id INT + ) + SERVER server_null_fdw ; -- foreign_tables_are( schema, tables, description ) -- CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME, NAME[], TEXT ) -SELECT * FROM check_test( + FOR tap IN SELECT * FROM check_test( foreign_tables_are( 'public', ARRAY['ft_foo', 'ft_bar'], 'Correct foreign tables in named schema with custom message' ), true, 'foreign_tables_are(schema, tables, desc)', 'Correct foreign tables in named schema with custom message', '' -); + ) AS r LOOP + RETURN NEXT tap.r; + END LOOP; -- foreign_tables_are( tables, description ) --CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME[], TEXT ) -SELECT * FROM check_test( + FOR tap IN SELECT * FROM check_test( foreign_tables_are( ARRAY['ft_foo', 'ft_bar'], 'Correct foreign tables in search_path with custom message' ), true, 'foreign_tables_are(tables, desc)', 'Correct foreign tables in search_path with custom message', '' -); + ) AS r LOOP + RETURN NEXT tap.r; + END LOOP; -- foreign_tables_are( schema, tables ) -- CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME, NAME[] ) -SELECT * FROM check_test( + FOR tap IN SELECT * FROM check_test( foreign_tables_are( 'public', ARRAY['ft_foo', 'ft_bar'] ), true, 'foreign_tables_are(schema, tables)', 'Schema public should have the correct foreign tables', '' -); + ) AS r LOOP + RETURN NEXT tap.r; + END LOOP; -- foreign_tables_are( tables ) -- +CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME[] ) - -SELECT * FROM check_test( + FOR tap IN SELECT * FROM check_test( foreign_tables_are( ARRAY['ft_foo', 'ft_bar'] ), true, 'foreign_tables_are(tables)', 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct foreign tables', '' -); + ) AS r LOOP + RETURN NEXT tap.r; + END LOOP; -SELECT * FROM check_test( + FOR tap IN SELECT * FROM check_test( foreign_tables_are( 'public', ARRAY['ft_foo', 'ft_bar', 'bar'] ), false, 'foreign_tables_are(schema, tables) missing', 'Schema public should have the correct foreign tables', ' Missing foreign tables: bar' -); + ) AS r LOOP + RETURN NEXT tap.r; + END LOOP; -SELECT * FROM check_test( + FOR tap IN SELECT * FROM check_test( foreign_tables_are( ARRAY['ft_foo', 'ft_bar', 'bar'] ), false, 'foreign_tables_are(tables) missing', 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct foreign tables', ' Missing foreign tables: bar' -); + ) AS r LOOP + RETURN NEXT tap.r; + END LOOP; -SELECT * FROM check_test( + FOR tap IN SELECT * FROM check_test( foreign_tables_are( 'public', ARRAY['ft_foo'] ), false, 'foreign_tables_are(schema, tables) extra', 'Schema public should have the correct foreign tables', ' Extra foreign tables: ft_bar' -); + ) AS r LOOP + RETURN NEXT tap.r; + END LOOP; -SELECT * FROM check_test( + FOR tap IN SELECT * FROM check_test( foreign_tables_are( ARRAY['ft_foo'] ), false, 'foreign_tables_are(tables) extra', 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct foreign tables', ' Extra foreign tables: ft_bar' -); + ) AS r LOOP + RETURN NEXT tap.r; + END LOOP; -SELECT * FROM check_test( + FOR tap IN SELECT * FROM check_test( foreign_tables_are( 'public', ARRAY['bar', 'baz'] ), false, 'foreign_tables_are(schema, tables) extra and missing', @@ -1821,27 +1841,11 @@ SELECT * FROM check_test( ba[rz] ba[rz]', true -); -/* -"foreign_tables_are(schema, tables) extra and missing should have the proper diagnostics" -# ' Extra foreign tables: -# ft_foo -# ft_bar -# Missing foreign tables: -# bar -# baz' -# doesn't match: ' Extra foreign tables: -# fo[ou] -# fo[ou] -# Missing foreign tables: -# ba[rz] -# ba[rz]' - - - -*/ + ) AS r LOOP + RETURN NEXT tap.r; + END LOOP; -SELECT * FROM check_test( + FOR tap IN SELECT * FROM check_test( foreign_tables_are( ARRAY['bar', 'baz'] ), false, 'foreign_tables_are(tables) extra and missing', @@ -1853,8 +1857,70 @@ SELECT * FROM check_test( ba[rz] ba[rz]', true -); + ) AS r LOOP + RETURN NEXT tap.r; + END LOOP; + + else + -- 10 fake pass/fail tests to make test work in older postgres. + FOR tap IN SELECT * FROM check_test(pass('pass'), true, + 'foreign_tables_are(schema, tables, desc)' + , 'pass', '') AS r LOOP + return next tap.r; + end loop; + FOR tap IN SELECT * FROM check_test(pass('pass'), true, + 'foreign_tables_are(tables, desc)' + , 'pass', '') AS r LOOP + return next tap.r; + end loop; + FOR tap IN SELECT * FROM check_test(pass('pass'), true, + 'foreign_tables_are(schema, tables)' + ,'pass' ,'') AS r LOOP + RETURN NEXT tap.r; + END LOOP; + FOR tap IN SELECT * FROM check_test(pass('pass'), true, + 'foreign_tables_are(tables)' + ,'pass' ,'') AS r LOOP + RETURN NEXT tap.r; + END LOOP; + FOR tap IN SELECT * FROM check_test(fail('fail'), false, + 'foreign_tables_are(schema, tables) missing' + ,'fail' ,'') AS r LOOP + RETURN NEXT tap.r; + END LOOP; + FOR tap IN SELECT * FROM check_test(fail('fail'), false, + 'foreign_tables_are(tables) missing' + ,'fail' ,'') AS r LOOP + RETURN NEXT tap.r; + END LOOP; + FOR tap IN SELECT * FROM check_test(fail('fail'), false, + 'foreign_tables_are(schema, tables) extra' + ,'fail' ,'') AS r LOOP + RETURN NEXT tap.r; + END LOOP; + FOR tap IN SELECT * FROM check_test(fail('fail'), false, + 'foreign_tables_are(tables) extra' + ,'fail' ,'') AS r LOOP + RETURN NEXT tap.r; + END LOOP; + FOR tap IN SELECT * FROM check_test(fail('fail'), false, + 'foreign_tables_are(schema, tables) extra and missing' + ,'fail' ,'') AS r LOOP + RETURN NEXT tap.r; + END LOOP; + FOR tap IN SELECT * FROM check_test(fail('fail'), false, + 'foreign_tables_are(tables) extra and missing' + ,'fail' ,'') AS r LOOP + RETURN NEXT tap.r; + END LOOP; + end if; + RETURN; +END; +$$ LANGUAGE PLPGSQL; +SELECT * FROM test_foreign_tables_are(); + +/****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From c2d329aac0e4c39626801863678ddbb3d6d6159c Mon Sep 17 00:00:00 2001 From: Chad Slaughter Date: Tue, 16 Sep 2014 16:46:29 -0500 Subject: [PATCH 0827/1195] Use an execute string for newer syntax. Put the create foreign table, et al into a string to execute so it can still be parsed by older postgresql versions. --- test/sql/aretap.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/sql/aretap.sql b/test/sql/aretap.sql index e359458b0b8f..afc253db1fe4 100644 --- a/test/sql/aretap.sql +++ b/test/sql/aretap.sql @@ -1720,8 +1720,9 @@ DECLARE tap record; i int; BEGIN - IF false and pg_version_num() >= 90100 THEN + IF pg_version_num() >= 90100 THEN -- setup tables for tests + EXECUTE $setup$ CREATE FOREIGN DATA WRAPPER null_fdw; CREATE SERVER server_null_fdw FOREIGN DATA WRAPPER null_fdw; CREATE FOREIGN TABLE public.ft_foo( @@ -1735,6 +1736,7 @@ BEGIN id INT ) SERVER server_null_fdw ; + $setup$; -- foreign_tables_are( schema, tables, description ) -- CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME, NAME[], TEXT ) From 3f00e0c69a4b1979412dc041af3824b60b1cc94c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2014 11:41:44 -0700 Subject: [PATCH 0828/1195] Add foreign_tables_are() to upgrade script. And credit Chad for his contribution! --- Changes | 1 + sql/pgtap--0.94.0--0.95.0.sql | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/Changes b/Changes index 574d697ef96d..a8d56b0525ba 100644 --- a/Changes +++ b/Changes @@ -16,6 +16,7 @@ Revision history for pgTAP * Granted SELECT access to the views created by pgTAP to PUBLIC, to avoid permission errors when the tests are run by a user other than the owner of those views. Thanks to Jim Nasby for the report. +* Added `foreign_tables_are()` functions. Thanks to Chad for the pull request! 0.94.0 2014-01-07T01:32:36Z --------------------------- diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql index d76b37be8ef6..bf54b6e3a908 100644 --- a/sql/pgtap--0.94.0--0.95.0.sql +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -33,3 +33,33 @@ 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; + +-- 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; From 3c09676802e0d4103a9bbca333390a2f51e47aa2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2014 11:45:01 -0700 Subject: [PATCH 0829/1195] Add missing period and wrap at 78 chars. --- doc/pgtap.mmd | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 99ec4a23e78b..df600a8ac72a 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1856,11 +1856,12 @@ missing tables, like so: `:description` : A short description of the test. -This function tests that all of the foreign tables in the named schema, or that are -visible in the search path, are only the foreign tables that *should* be there. If the -`:schema` argument is omitted, foreign tables will be sought in the search path, -excluding `pg_catalog` and `information_schema` If the description is omitted, -a generally useful default description will be generated. Example: +This function tests that all of the foreign tables in the named schema, or +that are visible in the search path, are only the foreign tables that *should* +be there. If the `:schema` argument is omitted, foreign tables will be sought +in the search path, excluding `pg_catalog` and `information_schema`. If the +description is omitted, a generally useful default description will be +generated. Example: SELECT foreign_tables_are( 'myschema', From f98f54f89ef0e917592a232e08de328cad46fc32 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Sep 2014 11:45:23 -0700 Subject: [PATCH 0830/1195] Test on Postgres 9.3, too. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index bcc030b63979..cc452c58c8c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,4 +10,5 @@ env: - PGVERSION=9.0 - PGVERSION=9.1 - PGVERSION=9.2 + - PGVERSION=9.3 script: bash ./pg-travis-test.sh From 5da98c9fd6c7772b54f45dd9b392365c7d0903a0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Feb 2015 14:32:51 -0800 Subject: [PATCH 0831/1195] Fix label ordering in `enum_has_labels()`. On 9.0 and higher, where enums have an explcit order required by `ALTER TYPE ADD VALUE`. Resolves #60. --- Changes | 2 + Makefile | 4 ++ compat/install-9.0.patch | 20 ++++++++++ sql/pgtap.sql.in | 4 +- test/expected/enumtap.out | 14 ++++++- test/sql/enumtap.sql | 78 ++++++++++++++++++++++++++++++++++++++- 6 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 compat/install-9.0.patch diff --git a/Changes b/Changes index a8d56b0525ba..040f34223748 100644 --- a/Changes +++ b/Changes @@ -17,6 +17,8 @@ Revision history for pgTAP permission errors when the tests are run by a user other than the owner of those views. Thanks to Jim Nasby for the report. * Added `foreign_tables_are()` functions. Thanks to Chad for the pull request! +* Updated `enum_has_labels()` to properly sort labels added by `ALTER TYPE ADD + VALUE`. 0.94.0 2014-01-07T01:32:36Z --------------------------- diff --git a/Makefile b/Makefile index 37d064d13566..902b75da355d 100644 --- a/Makefile +++ b/Makefile @@ -93,6 +93,9 @@ endif sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql cp $< $@ +ifeq ($(shell echo $(VERSION) | grep -qE "9[.]0|8[.][1234]" && echo yes || echo no),yes) + patch -p0 < compat/install-9.0.patch +endif ifeq ($(shell echo $(VERSION) | grep -qE "8[.][1234]" && echo yes || echo no),yes) patch -p0 < compat/install-8.4.patch endif @@ -127,6 +130,7 @@ sql/pgtap-core.sql: sql/pgtap.sql.in sql/pgtap-schema.sql: sql/pgtap.sql.in cp $< $@ + sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-9.0.patch | patch -p0 sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-8.4.patch | patch -p0 sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-8.3.patch | patch -p0 sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap-schema.sql > sql/pgtap-schema.tmp diff --git a/compat/install-9.0.patch b/compat/install-9.0.patch new file mode 100644 index 000000000000..f4c3c8123ae6 --- /dev/null +++ b/compat/install-9.0.patch @@ -0,0 +1,20 @@ +--- sql/pgtap.sql ++++ sql/pgtap.sql +@@ -3583,7 +3583,7 @@ RETURNS TEXT AS $$ + AND n.nspname = $1 + AND t.typname = $2 + AND t.typtype = 'e' +- ORDER BY e.enumsortorder ++ ORDER BY e.oid + ), + $3, + $4 +@@ -3611,7 +3611,7 @@ RETURNS TEXT AS $$ + AND pg_catalog.pg_type_is_visible(t.oid) + AND t.typname = $1 + AND t.typtype = 'e' +- ORDER BY e.enumsortorder ++ ORDER BY e.oid + ), + $2, + $3 diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index e36ab669daf5..6c61e79ecaa9 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -3583,7 +3583,7 @@ RETURNS TEXT AS $$ AND n.nspname = $1 AND t.typname = $2 AND t.typtype = 'e' - ORDER BY e.oid + ORDER BY e.enumsortorder ), $3, $4 @@ -3611,7 +3611,7 @@ RETURNS TEXT AS $$ AND pg_catalog.pg_type_is_visible(t.oid) AND t.typname = $1 AND t.typtype = 'e' - ORDER BY e.oid + ORDER BY e.enumsortorder ), $2, $3 diff --git a/test/expected/enumtap.out b/test/expected/enumtap.out index 084604a0a8ba..73452c0496d4 100644 --- a/test/expected/enumtap.out +++ b/test/expected/enumtap.out @@ -1,5 +1,5 @@ \unset ECHO -1..96 +1..108 ok 1 - has_type(enum) should pass ok 2 - has_type(enum) should have the proper description ok 3 - has_type(enum) should have the proper diagnostics @@ -96,3 +96,15 @@ ok 93 - enums_are(enums, desc) fail should have the proper diagnostics ok 94 - enums_are(enums) fail should fail ok 95 - enums_are(enums) fail should have the proper description ok 96 - enums_are(enums) fail should have the proper diagnostics +ok 97 - enum_has_labels(schema, altered_enum, labels, desc) should pass +ok 98 - enum_has_labels(schema, altered_enum, labels, desc) should have the proper description +ok 99 - enum_has_labels(schema, altered_enum, labels, desc) should have the proper diagnostics +ok 100 - enum_has_labels(schema, altered_enum, labels) should pass +ok 101 - enum_has_labels(schema, altered_enum, labels) should have the proper description +ok 102 - enum_has_labels(schema, altered_enum, labels) should have the proper diagnostics +ok 103 - enum_has_labels(altered_enum, labels, desc) should pass +ok 104 - enum_has_labels(altered_enum, labels, desc) should have the proper description +ok 105 - enum_has_labels(altered_enum, labels, desc) should have the proper diagnostics +ok 106 - enum_has_labels(altered_enum, labels) should pass +ok 107 - enum_has_labels(altered_enum, labels) should have the proper description +ok 108 - enum_has_labels(altered_enum, labels) should have the proper diagnostics diff --git a/test/sql/enumtap.sql b/test/sql/enumtap.sql index 8620a77fd2d3..e50052783ab8 100644 --- a/test/sql/enumtap.sql +++ b/test/sql/enumtap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(96); +SELECT plan(108); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -295,6 +295,82 @@ SELECT * FROM check_test( fredy' ); +/****************************************************************************/ +-- Make sure that we properly handle reordered labels. +CREATE FUNCTION test_alter_enum() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; + expect TEXT[]; + labels TEXT; +BEGIN + IF pg_version_num() >= 90000 THEN + -- Mimic ALTER TYPE ADD VALUE by reordering labels. + EXECUTE $E$ + UPDATE pg_catalog.pg_enum + SET enumsortorder = CASE enumlabel + WHEN 'closed' THEN 4 + WHEN 'open' THEN 5 + ELSE 6 + END + WHERE enumtypid IN ( + SELECT t.oid + FROM pg_catalog.pg_type t + JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid + WHERE n.nspname = 'public' + AND t.typname = 'bug_status' + AND t.typtype = 'e' + ); + $E$; + expect := ARRAY['closed', 'open', 'new']; + ELSE + expect := ARRAY['new', 'open', 'closed']; + END IF; + labels := array_to_string(expect, ', '); + + FOR tap IN SELECT * FROM check_test( + enum_has_labels( 'public', 'bug_status', expect, 'mydesc' ), + true, + 'enum_has_labels(schema, altered_enum, labels, desc)', + 'mydesc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + enum_has_labels( 'public', 'bug_status', expect ), + true, + 'enum_has_labels(schema, altered_enum, labels)', + 'Enum public.bug_status should have labels ('|| labels || ')', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + enum_has_labels( 'bug_status', expect, 'mydesc' ), + true, + 'enum_has_labels(altered_enum, labels, desc)', + 'mydesc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + enum_has_labels( 'bug_status', expect ), + true, + 'enum_has_labels(altered_enum, labels)', + 'Enum bug_status should have labels (' || labels || ')', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; +END; +$$ LANGUAGE PLPGSQL; + +SELECT * FROM test_alter_enum(); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 77a37a805b4e26f91146d69bc6c57032e96284c8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Feb 2015 14:36:24 -0800 Subject: [PATCH 0832/1195] Upgrade enum_has_labels(). Ref #60. --- sql/pgtap--0.94.0--0.95.0.sql | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql index bf54b6e3a908..73e01b9de781 100644 --- a/sql/pgtap--0.94.0--0.95.0.sql +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -63,3 +63,42 @@ RETURNS TEXT AS $$ 'Search path ' || pg_catalog.current_setting('search_path') || ' should have the correct foreign tables' ); $$ 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( 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; From 190c664f7d280071b4cf54aad14977e572247322 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Feb 2015 14:39:43 -0800 Subject: [PATCH 0833/1195] Add missing pgtap_version() updates to upgrade scripts. --- sql/pgtap--0.93.0--0.94.0.sql | 4 ++++ sql/pgtap--0.94.0--0.95.0.sql | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/sql/pgtap--0.93.0--0.94.0.sql b/sql/pgtap--0.93.0--0.94.0.sql index b55a7a3d4467..ea2fcbe402a6 100644 --- a/sql/pgtap--0.93.0--0.94.0.sql +++ b/sql/pgtap--0.93.0--0.94.0.sql @@ -1,3 +1,7 @@ +CREATE OR REPLACE FUNCTION pgtap_version() +RETURNS NUMERIC AS 'SELECT 0.94;' +LANGUAGE SQL IMMUTABLE; + CREATE OR REPLACE FUNCTION _get_func_privs(TEXT, TEXT) RETURNS TEXT[] AS $$ BEGIN diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql index 73e01b9de781..480fdd177ff5 100644 --- a/sql/pgtap--0.94.0--0.95.0.sql +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -1,3 +1,7 @@ +CREATE OR REPLACE FUNCTION pgtap_version() +RETURNS NUMERIC AS 'SELECT 0.95;' +LANGUAGE SQL IMMUTABLE; + -- is_member_of( role, members[], description ) CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ From 8eaecd69f686874b788da5adaf0ba16b3435cfd5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Feb 2015 14:47:09 -0800 Subject: [PATCH 0834/1195] Only upgrade enum_has_labels() if appropriate. --- sql/pgtap--0.94.0--0.95.0.sql | 85 ++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql index 480fdd177ff5..20708be48f77 100644 --- a/sql/pgtap--0.94.0--0.95.0.sql +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -68,41 +68,52 @@ RETURNS TEXT AS $$ ); $$ 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; +CREATE OR REPLACE FUNCTION _fix_enum_has_labels( +) RETURNS VOID LANGUAGE PLPGSQL AS $FIX$ +BEGIN + IF pg_version_num() <= 90000 THEN RETURN; END IF; + EXECUTE $RUN$ + -- 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( 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, 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; + $RUN$; +END; +$FIX$; + +SELECT _fix_enum_has_labels(); +DROP FUNCTION _fix_enum_has_labels(); From 233179de833ce59636ac0855b21489262e8f2272 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Feb 2015 14:49:13 -0800 Subject: [PATCH 0835/1195] Anything less than 9.1 --- sql/pgtap--0.94.0--0.95.0.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql index 20708be48f77..8d623575da63 100644 --- a/sql/pgtap--0.94.0--0.95.0.sql +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -71,7 +71,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _fix_enum_has_labels( ) RETURNS VOID LANGUAGE PLPGSQL AS $FIX$ BEGIN - IF pg_version_num() <= 90000 THEN RETURN; END IF; + IF pg_version_num() < 90100 THEN RETURN; END IF; EXECUTE $RUN$ -- enum_has_labels( schema, enum, labels, description ) CREATE OR REPLACE FUNCTION enum_has_labels( NAME, NAME, NAME[], TEXT ) From 52a23cbefa9b71288dda432544c1c66bba46d9ea Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Feb 2015 15:12:54 -0800 Subject: [PATCH 0836/1195] 9.1, not 9.0. --- test/sql/enumtap.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sql/enumtap.sql b/test/sql/enumtap.sql index e50052783ab8..0a62276d09b5 100644 --- a/test/sql/enumtap.sql +++ b/test/sql/enumtap.sql @@ -303,7 +303,7 @@ DECLARE expect TEXT[]; labels TEXT; BEGIN - IF pg_version_num() >= 90000 THEN + IF pg_version_num() >= 90100 THEN -- Mimic ALTER TYPE ADD VALUE by reordering labels. EXECUTE $E$ UPDATE pg_catalog.pg_enum From 537ca6776b9f757e4d6bcdafb8a80163e163506c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 5 Mar 2015 09:08:31 -0800 Subject: [PATCH 0837/1195] Add diag_test_name() to pgtap-core. --- Changes | 2 ++ compat/gencore | 1 + 2 files changed, 3 insertions(+) diff --git a/Changes b/Changes index 040f34223748..0bf0ecfa81af 100644 --- a/Changes +++ b/Changes @@ -19,6 +19,8 @@ Revision history for pgTAP * Added `foreign_tables_are()` functions. Thanks to Chad for the pull request! * Updated `enum_has_labels()` to properly sort labels added by `ALTER TYPE ADD VALUE`. +* Added `diag_test_name()` to `pgtap-core`, since it's called by `_runner()` + in that extension. Thanks to Jim Nasby for the spot. 0.94.0 2014-01-07T01:32:36Z --------------------------- diff --git a/compat/gencore b/compat/gencore index 793dfebe4ff5..fb4839360b34 100644 --- a/compat/gencore +++ b/compat/gencore @@ -63,6 +63,7 @@ diag diag diag diag +diag_test_name ok ok is From 21cd3318d3b8d029ae1dd89109b73252a6549f7f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 5 Mar 2015 09:09:20 -0800 Subject: [PATCH 0838/1195] Eliminate dupes. --- compat/gencore | 251 ------------------------------------------------- 1 file changed, 251 deletions(-) diff --git a/compat/gencore b/compat/gencore index fb4839360b34..84432c097311 100644 --- a/compat/gencore +++ b/compat/gencore @@ -47,77 +47,39 @@ plan no_plan _get _get_latest -_get_latest -_get_note _get_note _set -_set -_set -_add _add add_result num_failed _finish finish diag -diag -diag -diag diag_test_name ok -ok -is is isnt -isnt _alike matches -matches imatches -imatches -alike alike ialike -ialike _unalike doesnt_match -doesnt_match doesnt_imatch -doesnt_imatch -unalike unalike unialike -unialike -cmp_ok -cmp_ok -cmp_ok cmp_ok pass -pass -fail fail todo -todo -todo -todo -todo_start todo_start in_todo todo_end _todo skip -skip -skip -skip _query throws_ok -throws_ok -throws_ok -throws_ok -throws_ok -throws_ok -throws_ok -lives_ok lives_ok performs_ok performs_within @@ -125,300 +87,87 @@ _time_trials _ident_array_to_string tap_funky _got_func -_got_func -_got_func -_got_func -has_function -has_function -has_function -has_function -has_function -has_function -has_function has_function hasnt_function -hasnt_function -hasnt_function -hasnt_function -hasnt_function -hasnt_function -hasnt_function -hasnt_function _pg_sv_type_array can -can -can -can _has_type -_has_type -has_type has_type -has_type -has_type -hasnt_type hasnt_type -hasnt_type -hasnt_type -has_domain -has_domain has_domain -has_domain -hasnt_domain -hasnt_domain -hasnt_domain hasnt_domain has_enum -has_enum -has_enum -has_enum -hasnt_enum hasnt_enum -hasnt_enum -hasnt_enum -enum_has_labels -enum_has_labels enum_has_labels -enum_has_labels -display_type display_type _cmp_types _cast_exists -_cast_exists -_cast_exists -has_cast -has_cast -has_cast -has_cast has_cast -has_cast -hasnt_cast -hasnt_cast -hasnt_cast -hasnt_cast -hasnt_cast hasnt_cast _expand_context _get_context cast_context_is -cast_context_is -_op_exists _op_exists -_op_exists -has_operator -has_operator -has_operator has_operator -has_operator -has_operator -has_leftop -has_leftop has_leftop -has_leftop -has_leftop -has_leftop -has_rightop -has_rightop -has_rightop -has_rightop -has_rightop has_rightop _is_trusted has_language -has_language -hasnt_language hasnt_language language_is_trusted -language_is_trusted _opc_exists has_opclass -has_opclass -has_opclass -has_opclass -hasnt_opclass -hasnt_opclass -hasnt_opclass hasnt_opclass _nosuch _func_compare -_func_compare -_func_compare -_func_compare -_lang -_lang -_lang _lang function_lang_is -function_lang_is -function_lang_is -function_lang_is -function_lang_is -function_lang_is -function_lang_is -function_lang_is -_returns _returns -_returns -_returns -function_returns -function_returns -function_returns -function_returns -function_returns -function_returns function_returns -function_returns -_definer -_definer -_definer _definer is_definer -is_definer -is_definer -is_definer -is_definer -is_definer -is_definer -is_definer -_agg _agg -_agg -_agg -is_aggregate -is_aggregate is_aggregate -is_aggregate -is_aggregate -is_aggregate -is_aggregate -is_aggregate -_strict -_strict -_strict _strict is_strict -is_strict -is_strict -is_strict -is_strict -is_strict -is_strict -is_strict _expand_vol _refine_vol _vol -_vol -_vol -_vol -volatility_is -volatility_is -volatility_is -volatility_is -volatility_is -volatility_is volatility_is -volatility_is -findfuncs findfuncs _runem _is_verbose do_tap -do_tap -do_tap -do_tap _currtest _cleanup _runner runtests -runtests -runtests -runtests -_temptable _temptable _temptypes _docomp _relcomp -_relcomp -set_eq set_eq -set_eq -set_eq -bag_eq -bag_eq -bag_eq bag_eq _do_ne _relne -_relne -set_ne -set_ne set_ne -set_ne -bag_ne -bag_ne -bag_ne bag_ne -_relcomp -set_has set_has bag_has -bag_has -set_hasnt set_hasnt bag_hasnt -bag_hasnt -results_eq -results_eq -results_eq -results_eq -results_eq -results_eq -results_eq -results_eq -results_eq -results_eq results_eq -results_eq -results_ne -results_ne -results_ne -results_ne -results_ne -results_ne -results_ne -results_ne -results_ne -results_ne -results_ne results_ne isa_ok -isa_ok -is_empty is_empty collect_tap _tlike throws_like -throws_like -throws_ilike throws_ilike throws_matching -throws_matching -throws_imatching throws_imatching _dexists -_dexists -_get_dtype _get_dtype domain_type_is -domain_type_is -domain_type_is -domain_type_is -domain_type_is -domain_type_is -domain_type_isnt -domain_type_isnt -domain_type_isnt domain_type_isnt -domain_type_isnt -domain_type_isnt -row_eq row_eq From e08dd49b39a4b7f44cda3be79eb42b621861b333 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 10 Mar 2015 11:47:50 -0700 Subject: [PATCH 0839/1195] Add isnt_strict(). --- Changes | 1 + compat/gencore | 1 + doc/pgtap.mmd | 35 +++++ sql/pgtap--0.94.0--0.95.0.sql | 59 +++++++ sql/pgtap.sql.in | 59 +++++++ sql/uninstall_pgtap.sql.in | 8 + test/expected/functap.out | 286 ++++++++++++++++++++-------------- test/sql/functap.sql | 148 +++++++++++++++++- 8 files changed, 479 insertions(+), 118 deletions(-) diff --git a/Changes b/Changes index 0bf0ecfa81af..e1818f8cb12d 100644 --- a/Changes +++ b/Changes @@ -21,6 +21,7 @@ Revision history for pgTAP VALUE`. * Added `diag_test_name()` to `pgtap-core`, since it's called by `_runner()` in that extension. Thanks to Jim Nasby for the spot. +* Added `isnt_strict()` to complement `is_strict()`. Requested by Jim Nasby. 0.94.0 2014-01-07T01:32:36Z --------------------------- diff --git a/compat/gencore b/compat/gencore index 84432c097311..093d8fa9d047 100644 --- a/compat/gencore +++ b/compat/gencore @@ -130,6 +130,7 @@ _agg is_aggregate _strict is_strict +isnt_strict _expand_vol _refine_vol _vol diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index df600a8ac72a..1dc091425272 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4881,6 +4881,41 @@ If the function does not exist, a handy diagnostic message will let you know: But then you check with `has_function()` first, right? +### `isnt_strict()` ### + + SELECT isnt_strict( :schema, :function, :args, :description ); + SELECT isnt_strict( :schema, :function, :args ); + SELECT isnt_strict( :schema, :function, :description ); + SELECT isnt_strict( :schema, :function ); + SELECT isnt_strict( :function, :args, :description ); + SELECT isnt_strict( :function, :args ); + SELECT isnt_strict( :function, :description ); + SELECT isnt_strict( :function ); + +**Parameters** + +`:schema` +: Schema in which to find the function. + +`:function` +: Function name. + +`:args` +: Array of data types for the function arguments. + +`:description` +: A short description of the test. + +This function is the inverse of `is_strict()`. The test passes if the specified +function is not strict. + +If the function does not exist, a handy diagnostic message will let you know: + + # Failed test 290: "Function nasty() should be strict" + # Function nasty() does not exist + +But then you check with `has_function()` first, right? + ### `is_aggregate()` ### SELECT is_aggregate( :schema, :function, :args, :description ); diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql index 8d623575da63..5535a3da76f4 100644 --- a/sql/pgtap--0.94.0--0.95.0.sql +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -117,3 +117,62 @@ $FIX$; SELECT _fix_enum_has_labels(); DROP FUNCTION _fix_enum_has_labels(); + +-- 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 6c61e79ecaa9..eca58080566e 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -5625,6 +5625,65 @@ 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 diff --git a/sql/uninstall_pgtap.sql.in b/sql/uninstall_pgtap.sql.in index 65b720074c08..a7d6389911e2 100644 --- a/sql/uninstall_pgtap.sql.in +++ b/sql/uninstall_pgtap.sql.in @@ -270,6 +270,14 @@ DROP FUNCTION _vol ( NAME, NAME ); DROP FUNCTION _vol ( NAME, NAME, NAME[] ); DROP FUNCTION _refine_vol( text ); DROP FUNCTION _expand_vol( char ); +DROP FUNCTION isnt_strict( NAME ); +DROP FUNCTION isnt_strict( NAME, TEXT ); +DROP FUNCTION isnt_strict( NAME, NAME[] ); +DROP FUNCTION isnt_strict ( NAME, NAME[], TEXT ); +DROP FUNCTION isnt_strict( NAME, NAME ); +DROP FUNCTION isnt_strict ( NAME, NAME, TEXT ); +DROP FUNCTION isnt_strict( NAME, NAME, NAME[] ); +DROP FUNCTION isnt_strict ( NAME, NAME, NAME[], TEXT ); DROP FUNCTION is_strict( NAME ); DROP FUNCTION is_strict( NAME, TEXT ); DROP FUNCTION is_strict( NAME, NAME[] ); diff --git a/test/expected/functap.out b/test/expected/functap.out index 678092930890..7f024018d11d 100644 --- a/test/expected/functap.out +++ b/test/expected/functap.out @@ -1,5 +1,5 @@ \unset ECHO -1..466 +1..520 ok 1 - simple function should pass ok 2 - simple function should have the proper description ok 3 - simple function should have the proper diagnostics @@ -351,118 +351,172 @@ ok 348 - is_aggregate(func) should have the proper diagnostics ok 349 - is_strict(schema, func, 0 args, desc) should pass ok 350 - is_strict(schema, func, 0 args, desc) should have the proper description ok 351 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 352 - is_strict(schema, func, 0 args) should pass -ok 353 - is_strict(schema, func, 0 args) should have the proper description -ok 354 - is_strict(schema, func, 0 args) should have the proper diagnostics -ok 355 - is_strict(schema, func, args, desc) should fail -ok 356 - is_strict(schema, func, args, desc) should have the proper description -ok 357 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 358 - is_strict(schema, func, args) should fail -ok 359 - is_strict(schema, func, args) should have the proper description -ok 360 - is_strict(schema, func, args) should have the proper diagnostics -ok 361 - is_strict(schema, func, desc) should pass -ok 362 - is_strict(schema, func, desc) should have the proper description -ok 363 - is_strict(schema, func, desc) should have the proper diagnostics -ok 364 - is_strict(schema, func) should pass -ok 365 - is_strict(schema, func) should have the proper description -ok 366 - is_strict(schema, func) should have the proper diagnostics -ok 367 - is_strict(schema, func, 0 args, desc) should pass -ok 368 - is_strict(schema, func, 0 args, desc) should have the proper description -ok 369 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 370 - is_strict(schema, func, 0 args) should pass -ok 371 - is_strict(schema, func, 0 args) should have the proper description -ok 372 - is_strict(schema, func, 0 args) should have the proper diagnostics -ok 373 - is_strict(schema, func, args, desc) should fail -ok 374 - is_strict(schema, func, args, desc) should have the proper description -ok 375 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 376 - is_strict(schema, func, args) should fail -ok 377 - is_strict(schema, func, args) should have the proper description -ok 378 - is_strict(schema, func, args) should have the proper diagnostics -ok 379 - is_strict(schema, func, desc) should pass -ok 380 - is_strict(schema, func, desc) should have the proper description -ok 381 - is_strict(schema, func, desc) should have the proper diagnostics -ok 382 - is_strict(schema, func) should pass -ok 383 - is_strict(schema, func) should have the proper description -ok 384 - is_strict(schema, func) should have the proper diagnostics -ok 385 - is_strict(func, 0 args, desc) should pass -ok 386 - is_strict(func, 0 args, desc) should have the proper description -ok 387 - is_strict(func, 0 args, desc) should have the proper diagnostics -ok 388 - is_strict(func, 0 args) should pass -ok 389 - is_strict(func, 0 args) should have the proper description -ok 390 - is_strict(func, 0 args) should have the proper diagnostics -ok 391 - is_strict(func, args, desc) should fail -ok 392 - is_strict(func, args, desc) should have the proper description -ok 393 - is_strict(func, args, desc) should have the proper diagnostics -ok 394 - is_strict(func, args) should fail -ok 395 - is_strict(func, args) should have the proper description -ok 396 - is_strict(func, args) should have the proper diagnostics -ok 397 - is_strict(func, desc) should pass -ok 398 - is_strict(func, desc) should have the proper description -ok 399 - is_strict(func, desc) should have the proper diagnostics -ok 400 - is_strict(func) should pass -ok 401 - is_strict(func) should have the proper description -ok 402 - is_strict(func) should have the proper diagnostics -ok 403 - function_volatility(schema, func, 0 args, volatile, desc) should pass -ok 404 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper description -ok 405 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper diagnostics -ok 406 - function_volatility(schema, func, 0 args, VOLATILE, desc) should pass -ok 407 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper description -ok 408 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper diagnostics -ok 409 - function_volatility(schema, func, 0 args, v, desc) should pass -ok 410 - function_volatility(schema, func, 0 args, v, desc) should have the proper description -ok 411 - function_volatility(schema, func, 0 args, v, desc) should have the proper diagnostics -ok 412 - function_volatility(schema, func, args, immutable, desc) should pass -ok 413 - function_volatility(schema, func, args, immutable, desc) should have the proper description -ok 414 - function_volatility(schema, func, args, immutable, desc) should have the proper diagnostics -ok 415 - function_volatility(schema, func, 0 args, stable, desc) should pass -ok 416 - function_volatility(schema, func, 0 args, stable, desc) should have the proper description -ok 417 - function_volatility(schema, func, 0 args, stable, desc) should have the proper diagnostics -ok 418 - function_volatility(schema, func, 0 args, volatile) should pass -ok 419 - function_volatility(schema, func, 0 args, volatile) should have the proper description -ok 420 - function_volatility(schema, func, 0 args, volatile) should have the proper diagnostics -ok 421 - function_volatility(schema, func, args, immutable) should pass -ok 422 - function_volatility(schema, func, args, immutable) should have the proper description -ok 423 - function_volatility(schema, func, volatile, desc) should pass -ok 424 - function_volatility(schema, func, volatile, desc) should have the proper description -ok 425 - function_volatility(schema, func, volatile, desc) should have the proper diagnostics -ok 426 - function_volatility(schema, func, volatile) should pass -ok 427 - function_volatility(schema, func, volatile) should have the proper description -ok 428 - function_volatility(schema, func, volatile) should have the proper diagnostics -ok 429 - function_volatility(schema, func, immutable, desc) should pass -ok 430 - function_volatility(schema, func, immutable, desc) should have the proper description -ok 431 - function_volatility(schema, func, immutable, desc) should have the proper diagnostics -ok 432 - function_volatility(schema, func, stable, desc) should pass -ok 433 - function_volatility(schema, func, stable, desc) should have the proper description -ok 434 - function_volatility(schema, func, stable, desc) should have the proper diagnostics -ok 435 - function_volatility(func, 0 args, volatile, desc) should pass -ok 436 - function_volatility(func, 0 args, volatile, desc) should have the proper description -ok 437 - function_volatility(func, 0 args, volatile, desc) should have the proper diagnostics -ok 438 - function_volatility(func, 0 args, VOLATILE, desc) should pass -ok 439 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper description -ok 440 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper diagnostics -ok 441 - function_volatility(func, 0 args, v, desc) should pass -ok 442 - function_volatility(func, 0 args, v, desc) should have the proper description -ok 443 - function_volatility(func, 0 args, v, desc) should have the proper diagnostics -ok 444 - function_volatility(func, args, immutable, desc) should pass -ok 445 - function_volatility(func, args, immutable, desc) should have the proper description -ok 446 - function_volatility(func, args, immutable, desc) should have the proper diagnostics -ok 447 - function_volatility(func, 0 args, stable, desc) should pass -ok 448 - function_volatility(func, 0 args, stable, desc) should have the proper description -ok 449 - function_volatility(func, 0 args, stable, desc) should have the proper diagnostics -ok 450 - function_volatility(func, 0 args, volatile) should pass -ok 451 - function_volatility(func, 0 args, volatile) should have the proper description -ok 452 - function_volatility(func, 0 args, volatile) should have the proper diagnostics -ok 453 - function_volatility(func, args, immutable) should pass -ok 454 - function_volatility(func, args, immutable) should have the proper description -ok 455 - function_volatility(func, volatile, desc) should pass -ok 456 - function_volatility(func, volatile, desc) should have the proper description -ok 457 - function_volatility(func, volatile, desc) should have the proper diagnostics -ok 458 - function_volatility(func, volatile) should pass -ok 459 - function_volatility(func, volatile) should have the proper description -ok 460 - function_volatility(func, volatile) should have the proper diagnostics -ok 461 - function_volatility(func, immutable, desc) should pass -ok 462 - function_volatility(func, immutable, desc) should have the proper description -ok 463 - function_volatility(func, immutable, desc) should have the proper diagnostics -ok 464 - function_volatility(func, stable, desc) should pass -ok 465 - function_volatility(func, stable, desc) should have the proper description -ok 466 - function_volatility(func, stable, desc) should have the proper diagnostics +ok 352 - isnt_strict(schema, func, 0 args, desc) should fail +ok 353 - isnt_strict(schema, func, 0 args, desc) should have the proper description +ok 354 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 355 - is_strict(schema, func, 0 args) should pass +ok 356 - is_strict(schema, func, 0 args) should have the proper description +ok 357 - is_strict(schema, func, 0 args) should have the proper diagnostics +ok 358 - isnt_strict(schema, func, 0 args) should fail +ok 359 - isnt_strict(schema, func, 0 args) should have the proper description +ok 360 - isnt_strict(schema, func, 0 args) should have the proper diagnostics +ok 361 - is_strict(schema, func, args, desc) should fail +ok 362 - is_strict(schema, func, args, desc) should have the proper description +ok 363 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 364 - is_strict(schema, func, args, desc) should pass +ok 365 - is_strict(schema, func, args, desc) should have the proper description +ok 366 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 367 - is_strict(schema, func, args) should fail +ok 368 - is_strict(schema, func, args) should have the proper description +ok 369 - is_strict(schema, func, args) should have the proper diagnostics +ok 370 - is_strict(schema, func, args) should pass +ok 371 - is_strict(schema, func, args) should have the proper description +ok 372 - is_strict(schema, func, args) should have the proper diagnostics +ok 373 - is_strict(schema, func, desc) should pass +ok 374 - is_strict(schema, func, desc) should have the proper description +ok 375 - is_strict(schema, func, desc) should have the proper diagnostics +ok 376 - isnt_strict(schema, func, desc) should fail +ok 377 - isnt_strict(schema, func, desc) should have the proper description +ok 378 - isnt_strict(schema, func, desc) should have the proper diagnostics +ok 379 - is_strict(schema, func) should pass +ok 380 - is_strict(schema, func) should have the proper description +ok 381 - is_strict(schema, func) should have the proper diagnostics +ok 382 - isnt_strict(schema, func) should fail +ok 383 - isnt_strict(schema, func) should have the proper description +ok 384 - isnt_strict(schema, func) should have the proper diagnostics +ok 385 - is_strict(schema, func, 0 args, desc) should pass +ok 386 - is_strict(schema, func, 0 args, desc) should have the proper description +ok 387 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 388 - isnt_strict(schema, func, 0 args, desc) should fail +ok 389 - isnt_strict(schema, func, 0 args, desc) should have the proper description +ok 390 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 391 - is_strict(schema, func, 0 args) should pass +ok 392 - is_strict(schema, func, 0 args) should have the proper description +ok 393 - is_strict(schema, func, 0 args) should have the proper diagnostics +ok 394 - isnt_strict(schema, func, 0 args) should fail +ok 395 - isnt_strict(schema, func, 0 args) should have the proper description +ok 396 - isnt_strict(schema, func, 0 args) should have the proper diagnostics +ok 397 - is_strict(schema, func, args, desc) should fail +ok 398 - is_strict(schema, func, args, desc) should have the proper description +ok 399 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 400 - isnt_strict(schema, func, args, desc) should pass +ok 401 - isnt_strict(schema, func, args, desc) should have the proper description +ok 402 - isnt_strict(schema, func, args, desc) should have the proper diagnostics +ok 403 - is_strict(schema, func, args) should fail +ok 404 - is_strict(schema, func, args) should have the proper description +ok 405 - is_strict(schema, func, args) should have the proper diagnostics +ok 406 - isnt_strict(schema, func, args) should pass +ok 407 - isnt_strict(schema, func, args) should have the proper description +ok 408 - isnt_strict(schema, func, args) should have the proper diagnostics +ok 409 - is_strict(schema, func, desc) should pass +ok 410 - is_strict(schema, func, desc) should have the proper description +ok 411 - is_strict(schema, func, desc) should have the proper diagnostics +ok 412 - isnt_strict(schema, func, desc) should fail +ok 413 - isnt_strict(schema, func, desc) should have the proper description +ok 414 - isnt_strict(schema, func, desc) should have the proper diagnostics +ok 415 - is_strict(schema, func) should pass +ok 416 - is_strict(schema, func) should have the proper description +ok 417 - is_strict(schema, func) should have the proper diagnostics +ok 418 - isnt_strict(schema, func) should fail +ok 419 - isnt_strict(schema, func) should have the proper description +ok 420 - isnt_strict(schema, func) should have the proper diagnostics +ok 421 - is_strict(func, 0 args, desc) should pass +ok 422 - is_strict(func, 0 args, desc) should have the proper description +ok 423 - is_strict(func, 0 args, desc) should have the proper diagnostics +ok 424 - isnt_strict(func, 0 args, desc) should fail +ok 425 - isnt_strict(func, 0 args, desc) should have the proper description +ok 426 - isnt_strict(func, 0 args, desc) should have the proper diagnostics +ok 427 - is_strict(func, 0 args) should pass +ok 428 - is_strict(func, 0 args) should have the proper description +ok 429 - is_strict(func, 0 args) should have the proper diagnostics +ok 430 - isnt_strict(func, 0 args) should fail +ok 431 - isnt_strict(func, 0 args) should have the proper description +ok 432 - isnt_strict(func, 0 args) should have the proper diagnostics +ok 433 - is_strict(func, args, desc) should fail +ok 434 - is_strict(func, args, desc) should have the proper description +ok 435 - is_strict(func, args, desc) should have the proper diagnostics +ok 436 - isnt_strict(func, args, desc) should pass +ok 437 - isnt_strict(func, args, desc) should have the proper description +ok 438 - isnt_strict(func, args, desc) should have the proper diagnostics +ok 439 - is_strict(func, args) should fail +ok 440 - is_strict(func, args) should have the proper description +ok 441 - is_strict(func, args) should have the proper diagnostics +ok 442 - isnt_strict(func, args) should pass +ok 443 - isnt_strict(func, args) should have the proper description +ok 444 - isnt_strict(func, args) should have the proper diagnostics +ok 445 - is_strict(func, desc) should pass +ok 446 - is_strict(func, desc) should have the proper description +ok 447 - is_strict(func, desc) should have the proper diagnostics +ok 448 - isnt_strict(func, desc) should fail +ok 449 - isnt_strict(func, desc) should have the proper description +ok 450 - isnt_strict(func, desc) should have the proper diagnostics +ok 451 - is_strict(func) should pass +ok 452 - is_strict(func) should have the proper description +ok 453 - is_strict(func) should have the proper diagnostics +ok 454 - isnt_strict(func) should fail +ok 455 - isnt_strict(func) should have the proper description +ok 456 - isnt_strict(func) should have the proper diagnostics +ok 457 - function_volatility(schema, func, 0 args, volatile, desc) should pass +ok 458 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper description +ok 459 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper diagnostics +ok 460 - function_volatility(schema, func, 0 args, VOLATILE, desc) should pass +ok 461 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper description +ok 462 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper diagnostics +ok 463 - function_volatility(schema, func, 0 args, v, desc) should pass +ok 464 - function_volatility(schema, func, 0 args, v, desc) should have the proper description +ok 465 - function_volatility(schema, func, 0 args, v, desc) should have the proper diagnostics +ok 466 - function_volatility(schema, func, args, immutable, desc) should pass +ok 467 - function_volatility(schema, func, args, immutable, desc) should have the proper description +ok 468 - function_volatility(schema, func, args, immutable, desc) should have the proper diagnostics +ok 469 - function_volatility(schema, func, 0 args, stable, desc) should pass +ok 470 - function_volatility(schema, func, 0 args, stable, desc) should have the proper description +ok 471 - function_volatility(schema, func, 0 args, stable, desc) should have the proper diagnostics +ok 472 - function_volatility(schema, func, 0 args, volatile) should pass +ok 473 - function_volatility(schema, func, 0 args, volatile) should have the proper description +ok 474 - function_volatility(schema, func, 0 args, volatile) should have the proper diagnostics +ok 475 - function_volatility(schema, func, args, immutable) should pass +ok 476 - function_volatility(schema, func, args, immutable) should have the proper description +ok 477 - function_volatility(schema, func, volatile, desc) should pass +ok 478 - function_volatility(schema, func, volatile, desc) should have the proper description +ok 479 - function_volatility(schema, func, volatile, desc) should have the proper diagnostics +ok 480 - function_volatility(schema, func, volatile) should pass +ok 481 - function_volatility(schema, func, volatile) should have the proper description +ok 482 - function_volatility(schema, func, volatile) should have the proper diagnostics +ok 483 - function_volatility(schema, func, immutable, desc) should pass +ok 484 - function_volatility(schema, func, immutable, desc) should have the proper description +ok 485 - function_volatility(schema, func, immutable, desc) should have the proper diagnostics +ok 486 - function_volatility(schema, func, stable, desc) should pass +ok 487 - function_volatility(schema, func, stable, desc) should have the proper description +ok 488 - function_volatility(schema, func, stable, desc) should have the proper diagnostics +ok 489 - function_volatility(func, 0 args, volatile, desc) should pass +ok 490 - function_volatility(func, 0 args, volatile, desc) should have the proper description +ok 491 - function_volatility(func, 0 args, volatile, desc) should have the proper diagnostics +ok 492 - function_volatility(func, 0 args, VOLATILE, desc) should pass +ok 493 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper description +ok 494 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper diagnostics +ok 495 - function_volatility(func, 0 args, v, desc) should pass +ok 496 - function_volatility(func, 0 args, v, desc) should have the proper description +ok 497 - function_volatility(func, 0 args, v, desc) should have the proper diagnostics +ok 498 - function_volatility(func, args, immutable, desc) should pass +ok 499 - function_volatility(func, args, immutable, desc) should have the proper description +ok 500 - function_volatility(func, args, immutable, desc) should have the proper diagnostics +ok 501 - function_volatility(func, 0 args, stable, desc) should pass +ok 502 - function_volatility(func, 0 args, stable, desc) should have the proper description +ok 503 - function_volatility(func, 0 args, stable, desc) should have the proper diagnostics +ok 504 - function_volatility(func, 0 args, volatile) should pass +ok 505 - function_volatility(func, 0 args, volatile) should have the proper description +ok 506 - function_volatility(func, 0 args, volatile) should have the proper diagnostics +ok 507 - function_volatility(func, args, immutable) should pass +ok 508 - function_volatility(func, args, immutable) should have the proper description +ok 509 - function_volatility(func, volatile, desc) should pass +ok 510 - function_volatility(func, volatile, desc) should have the proper description +ok 511 - function_volatility(func, volatile, desc) should have the proper diagnostics +ok 512 - function_volatility(func, volatile) should pass +ok 513 - function_volatility(func, volatile) should have the proper description +ok 514 - function_volatility(func, volatile) should have the proper diagnostics +ok 515 - function_volatility(func, immutable, desc) should pass +ok 516 - function_volatility(func, immutable, desc) should have the proper description +ok 517 - function_volatility(func, immutable, desc) should have the proper diagnostics +ok 518 - function_volatility(func, stable, desc) should pass +ok 519 - function_volatility(func, stable, desc) should have the proper description +ok 520 - function_volatility(func, stable, desc) should have the proper diagnostics diff --git a/test/sql/functap.sql b/test/sql/functap.sql index 1f6fc11e7286..41f7d62f24b5 100644 --- a/test/sql/functap.sql +++ b/test/sql/functap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(466); +SELECT plan(520); --SELECT * FROM no_plan(); CREATE SCHEMA someschema; @@ -983,7 +983,7 @@ SELECT * FROM check_test( ); /****************************************************************************/ --- Test is_strict(). +-- Test is_strict() and isnt_strict(). SELECT * FROM check_test( is_strict( 'public', 'yay', '{}'::name[], 'whatever' ), true, @@ -992,6 +992,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'public', 'yay', '{}'::name[], 'whatever' ), + false, + 'isnt_strict(schema, func, 0 args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_strict( 'public', 'yay', '{}'::name[] ), true, @@ -1000,6 +1008,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'public', 'yay', '{}'::name[] ), + false, + 'isnt_strict(schema, func, 0 args)', + 'Function public.yay() should not be strict', + '' +); + SELECT * FROM check_test( is_strict( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), false, @@ -1008,6 +1024,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'is_strict(schema, func, args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_strict( 'public', 'oww', ARRAY['integer', 'text'] ), false, @@ -1016,6 +1040,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'public', 'oww', ARRAY['integer', 'text'] ), + true, + 'is_strict(schema, func, args)', + 'Function public.oww(integer, text) should not be strict', + '' +); + SELECT * FROM check_test( is_strict( 'public', 'yay', 'whatever' ), true, @@ -1024,6 +1056,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'public', 'yay', 'whatever' ), + false, + 'isnt_strict(schema, func, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_strict( 'public', 'yay'::name ), true, @@ -1032,6 +1072,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'public', 'yay'::name ), + false, + 'isnt_strict(schema, func)', + 'Function public.yay() should not be strict', + '' +); + SELECT * FROM check_test( is_strict( 'public', 'yay', '{}'::name[], 'whatever' ), true, @@ -1040,6 +1088,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'public', 'yay', '{}'::name[], 'whatever' ), + false, + 'isnt_strict(schema, func, 0 args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_strict( 'public', 'yay', '{}'::name[] ), true, @@ -1048,6 +1104,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'public', 'yay', '{}'::name[] ), + false, + 'isnt_strict(schema, func, 0 args)', + 'Function public.yay() should not be strict', + '' +); + SELECT * FROM check_test( is_strict( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), false, @@ -1056,6 +1120,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_strict(schema, func, args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_strict( 'public', 'oww', ARRAY['integer', 'text'] ), false, @@ -1064,6 +1136,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'public', 'oww', ARRAY['integer', 'text'] ), + true, + 'isnt_strict(schema, func, args)', + 'Function public.oww(integer, text) should not be strict', + '' +); + SELECT * FROM check_test( is_strict( 'public', 'yay', 'whatever' ), true, @@ -1072,6 +1152,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'public', 'yay', 'whatever' ), + false, + 'isnt_strict(schema, func, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_strict( 'public', 'yay'::name ), true, @@ -1080,6 +1168,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'public', 'yay'::name ), + false, + 'isnt_strict(schema, func)', + 'Function public.yay() should not be strict', + '' +); + SELECT * FROM check_test( is_strict( 'yay', '{}'::name[], 'whatever' ), true, @@ -1088,6 +1184,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'yay', '{}'::name[], 'whatever' ), + false, + 'isnt_strict(func, 0 args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_strict( 'yay', '{}'::name[] ), true, @@ -1096,6 +1200,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'yay', '{}'::name[] ), + false, + 'isnt_strict(func, 0 args)', + 'Function yay() should not be strict', + '' +); + SELECT * FROM check_test( is_strict( 'oww', ARRAY['integer', 'text'], 'whatever' ), false, @@ -1104,6 +1216,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_strict(func, args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_strict( 'oww', ARRAY['integer', 'text'] ), false, @@ -1112,6 +1232,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'oww', ARRAY['integer', 'text'] ), + true, + 'isnt_strict(func, args)', + 'Function oww(integer, text) should not be strict', + '' +); + SELECT * FROM check_test( is_strict( 'yay', 'whatever' ), true, @@ -1120,6 +1248,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'yay', 'whatever' ), + false, + 'isnt_strict(func, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_strict( 'yay'::name ), true, @@ -1128,6 +1264,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'yay'::name ), + false, + 'isnt_strict(func)', + 'Function yay() should not be strict', + '' +); + /****************************************************************************/ -- Test volatility_is(). SELECT * FROM check_test( From 5ba05abf3ebe9cdd2843b0f1f37a4e095a418cce Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Thu, 19 Mar 2015 14:59:39 -0500 Subject: [PATCH 0840/1195] Add schema/table/column variations of col_is_unique --- doc/pgtap.mmd | 6 +++-- sql/pgtap.sql.in | 12 ++++++++++ test/expected/unique.out | 50 ++++++++++++++++++++++------------------ test/sql/unique.sql | 18 ++++++++++++++- 4 files changed, 61 insertions(+), 25 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 1dc091425272..155e2e423cbc 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4421,6 +4421,8 @@ in question does not exist. SELECT col_is_unique( schema, table, columns, description ); SELECT col_is_unique( schema, table, column, description ); + SELECT col_is_unique( schema, table, columns ); + SELECT col_is_unique( schema, table, column ); SELECT col_is_unique( table, columns, description ); SELECT col_is_unique( table, column, description ); SELECT col_is_unique( table, columns ); @@ -4435,10 +4437,10 @@ in question does not exist. : Name of a table containing the unique constraint. `:columns` -: Array of the names of the unique columns. +: Array of the names of the unique columns. Note that if you don't include :description this must be an array of names. `:column` -: Name of the unique column. +: Name of the unique column. Note that if you don't include :description this must be a name, not text. `:description` : A short description of the test. diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index eca58080566e..f416b97cb3bd 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2244,6 +2244,18 @@ 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 $$ diff --git a/test/expected/unique.out b/test/expected/unique.out index 2f191e51a665..44c3c43d7553 100644 --- a/test/expected/unique.out +++ b/test/expected/unique.out @@ -1,5 +1,5 @@ \unset ECHO -1..48 +1..54 ok 1 - has_unique( schema, table, description ) should pass ok 2 - has_unique( schema, table, description ) should have the proper description ok 3 - has_unique( schema, table, description ) should have the proper diagnostics @@ -27,24 +27,30 @@ ok 24 - col_is_unique( table, column, description ) should have the proper diagn ok 25 - col_is_unique( table, columns, description ) should pass ok 26 - col_is_unique( table, columns, description ) should have the proper description ok 27 - col_is_unique( table, columns, description ) should have the proper diagnostics -ok 28 - col_is_unique( table, column ) should pass -ok 29 - col_is_unique( table, column ) should have the proper description -ok 30 - col_is_unique( table, column ) should have the proper diagnostics -ok 31 - col_is_unique( table, columns ) should pass -ok 32 - col_is_unique( table, columns ) should have the proper description -ok 33 - col_is_unique( table, columns ) should have the proper diagnostics -ok 34 - col_is_unique( schema, table, column, description ) fail should fail -ok 35 - col_is_unique( schema, table, column, description ) fail should have the proper description -ok 36 - col_is_unique( schema, table, column, description ) fail should have the proper diagnostics -ok 37 - col_is_unique( table, column, description ) fail should fail -ok 38 - col_is_unique( table, column, description ) fail should have the proper description -ok 39 - col_is_unique( table, column, description ) fail should have the proper diagnostics -ok 40 - col_is_unique( schema, table, column[], description ) should pass -ok 41 - col_is_unique( schema, table, column[], description ) should have the proper description -ok 42 - col_is_unique( schema, table, column[], description ) should have the proper diagnostics -ok 43 - col_is_unique( table, column[], description ) should pass -ok 44 - col_is_unique( table, column[], description ) should have the proper description -ok 45 - col_is_unique( table, column[], description ) should have the proper diagnostics -ok 46 - col_is_unique( table, column[] ) should pass -ok 47 - col_is_unique( table, column[] ) should have the proper description -ok 48 - col_is_unique( table, column[] ) should have the proper diagnostics +ok 28 - col_is_unique( schema, table, column ) should pass +ok 29 - col_is_unique( schema, table, column ) should have the proper description +ok 30 - col_is_unique( schema, table, column ) should have the proper diagnostics +ok 31 - col_is_unique( schema, table, columns ) should pass +ok 32 - col_is_unique( schema, table, columns ) should have the proper description +ok 33 - col_is_unique( schema, table, columns ) should have the proper diagnostics +ok 34 - col_is_unique( table, column ) should pass +ok 35 - col_is_unique( table, column ) should have the proper description +ok 36 - col_is_unique( table, column ) should have the proper diagnostics +ok 37 - col_is_unique( table, columns ) should pass +ok 38 - col_is_unique( table, columns ) should have the proper description +ok 39 - col_is_unique( table, columns ) should have the proper diagnostics +ok 40 - col_is_unique( schema, table, column, description ) fail should fail +ok 41 - col_is_unique( schema, table, column, description ) fail should have the proper description +ok 42 - col_is_unique( schema, table, column, description ) fail should have the proper diagnostics +ok 43 - col_is_unique( table, column, description ) fail should fail +ok 44 - col_is_unique( table, column, description ) fail should have the proper description +ok 45 - col_is_unique( table, column, description ) fail should have the proper diagnostics +ok 46 - col_is_unique( schema, table, column[], description ) should pass +ok 47 - col_is_unique( schema, table, column[], description ) should have the proper description +ok 48 - col_is_unique( schema, table, column[], description ) should have the proper diagnostics +ok 49 - col_is_unique( table, column[], description ) should pass +ok 50 - col_is_unique( table, column[], description ) should have the proper description +ok 51 - col_is_unique( table, column[], description ) should have the proper diagnostics +ok 52 - col_is_unique( table, column[] ) should pass +ok 53 - col_is_unique( table, column[] ) should have the proper description +ok 54 - col_is_unique( table, column[] ) should have the proper diagnostics diff --git a/test/sql/unique.sql b/test/sql/unique.sql index 80c0cae90a73..cd6ec325ba82 100644 --- a/test/sql/unique.sql +++ b/test/sql/unique.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(48); +SELECT plan(18*3); -- This will be rolled back. :-) SET client_min_messages = warning; @@ -92,6 +92,22 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + col_is_unique( 'public', 'sometab', 'name'::name ), + true, + 'col_is_unique( schema, table, column )', + 'Column sometab(name) should have a unique constraint', + '' +); + +SELECT * FROM check_test( + col_is_unique( 'public', 'sometab', ARRAY['numb'::name, 'myint'] ), + true, + 'col_is_unique( schema, table, columns )', + 'Columns sometab(numb, myint) should have a unique constraint', + '' +); + SELECT * FROM check_test( col_is_unique( 'sometab', 'name' ), true, From ab297510ed86f04b45b2ea8dd261ee1a550af498 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Thu, 19 Mar 2015 15:13:49 -0500 Subject: [PATCH 0841/1195] Add to upgrade script --- sql/.pgtap.sql.in.swp | Bin 335872 -> 16384 bytes sql/pgtap--0.94.0--0.95.0.sql | 12 ++++++++++++ 2 files changed, 12 insertions(+) diff --git a/sql/.pgtap.sql.in.swp b/sql/.pgtap.sql.in.swp index cfdd4097ad5f38e4a5e9d83e95a835f67991709e..f1fbb676b9802a840b7355b268b248b7cf8f5e38 100644 GIT binary patch literal 16384 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 literal 335872 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 O-ZV#kb-D`z`+osB*MDvR diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql index 5535a3da76f4..f82924b58497 100644 --- a/sql/pgtap--0.94.0--0.95.0.sql +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -176,3 +176,15 @@ 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; + +-- 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; From bd683d38be23ce9d2d117a8e7d37db02ab49a029 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 19 Mar 2015 14:09:37 -0700 Subject: [PATCH 0842/1195] Credit on col_is_unique(), doc tweaks. --- Changes | 2 ++ doc/pgtap.mmd | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index e1818f8cb12d..335ffe4e251b 100644 --- a/Changes +++ b/Changes @@ -22,6 +22,8 @@ Revision history for pgTAP * Added `diag_test_name()` to `pgtap-core`, since it's called by `_runner()` in that extension. Thanks to Jim Nasby for the spot. * Added `isnt_strict()` to complement `is_strict()`. Requested by Jim Nasby. +* Added schema/table/column variations of `col_is_unique()`. Contributed by + Jim Nasby. 0.94.0 2014-01-07T01:32:36Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 155e2e423cbc..241f12384767 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -3452,7 +3452,7 @@ target data type, and perhaps a (possibly schema-qualified) function. An example SELECT has_cast( 'integer', 'bigint', 'pg_catalog', 'int8' ); -If you omit the description four the 3- or 4-argument version, you'll need to +If you omit the description for the 3- or 4-argument version, you'll need to cast the function name to the `NAME` data type so that PostgreSQL doesn't resolve the function name as a description. For example: @@ -4437,10 +4437,10 @@ in question does not exist. : Name of a table containing the unique constraint. `:columns` -: Array of the names of the unique columns. Note that if you don't include :description this must be an array of names. +: Array of the names of the unique columns. `:column` -: Name of the unique column. Note that if you don't include :description this must be a name, not text. +: Name of the unique column. `:description` : A short description of the test. @@ -4448,8 +4448,17 @@ in question does not exist. Just like `col_is_pk()`, except that it test that the column or array of columns have a unique constraint on them. Examples: - SELECT col_is_unique( 'myschema', 'sometable', 'other_id' ); SELECT col_is_unique( 'contacts', ARRAY['given_name', 'surname'] ); + SELECT col_is_unique( + 'myschema', 'sometable', 'other_id', + 'myschema.sometable.other_id should be unique' + ); + +If you omit the description for the 3-argument version, you'll need to cast +the table and column parameters to the `NAME` data type so that PostgreSQL +doesn't resolve the function name as a description. For example: + + SELECT col_is_unique( 'myschema', 'sometable'::name, 'other_id'::name ); In the event of failure, the diagnostics will list the unique constraints that were actually found, if any: From 62fb15e8bb6361d806436915d92bd0914786d379 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 19 Mar 2015 14:23:03 -0700 Subject: [PATCH 0843/1195] Increment extension version to v0.95.0. Closes #77. --- META.json | 6 +++--- pgtap.control | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/META.json b/META.json index 3e6f4a211f4a..a5fb9254d905 100644 --- a/META.json +++ b/META.json @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "0.94.0" + "version": "0.95.0" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "0.94.0" + "version": "0.95.0" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "0.94.0" + "version": "0.95.0" } }, "resources": { diff --git a/pgtap.control b/pgtap.control index f1de4122d631..5321d6016fe9 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '0.94.0' +default_version = '0.95.0' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true From 8fb3233af75c20fefd6f21159c1869530cb6e033 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 19 Mar 2015 17:46:37 -0700 Subject: [PATCH 0844/1195] Improve test function control in runtests(). There are two major improvements: 1. Each test function is now run as a subtest, with its own plan, test count, and error messages. This is a more natual way for them to run, with independent plans that don't stomp on each other, and is fully supported by existing TAP parsers. The TAP output is different, though, so will require anyone using pg_regress to update their expected output files with the new format. 2. An exception thrown by one function no longer halts execution. Each test is well and truly run in its own subtransaction, which is rolled back. If a test function does throw an error (or any setup or terdown test that wraps it), just that subtest fails, and the error message is emitted as a diagnostic message. Starup and shutdown tests still cause a halt, however. Resolves #68. --- Changes | 6 ++ doc/pgtap.mmd | 20 +++++- sql/pgtap--0.94.0--0.95.0.sql | 122 ++++++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 113 ++++++++++++++++++++++--------- test/expected/runtests.out | 86 ++++++++++++++---------- test/sql/runtests.sql | 24 +++++-- 6 files changed, 294 insertions(+), 77 deletions(-) diff --git a/Changes b/Changes index 335ffe4e251b..e8e6da5e899c 100644 --- a/Changes +++ b/Changes @@ -24,6 +24,12 @@ Revision history for pgTAP * Added `isnt_strict()` to complement `is_strict()`. Requested by Jim Nasby. * Added schema/table/column variations of `col_is_unique()`. Contributed by Jim Nasby. +* The `runtests()` function now executes each test as a TAP subtest, with its + own plan and test count. This allows each test function to function more + independently of the others. It's easier to read the TAP, too. +* An exception thrown by a test no run by `runtests()` no longer halts + execution. It is instead properly rolled back and the error message emitted + as a diagnostic message. Thanks to Vitaliy Kotov for the report. 0.94.0 2014-01-07T01:32:36Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 241f12384767..6dbb7de319a3 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7278,7 +7278,7 @@ failing tests. `:pattern` : Regular expression pattern against which to match function names. -If you'd like pgTAP to plan, run all of your tests functions, and finish all +If you'd like pgTAP to plan, run all of your test functions, and finish, all in one fell swoop, use `runtests()`. This most closely emulates the xUnit testing environment, similar to the functionality of [PGUnit](http://en.dklab.ru/lib/dklab_pgunit/) and @@ -7295,8 +7295,22 @@ than a pattern: Unlike `do_tap()`, `runtests()` fully supports startup, shutdown, setup, and teardown functions, as well as transactional rollbacks between tests. It also -outputs the test plan and fishes the tests, so you don't have to call `plan()` -or `finish()` yourself. +outputs the test plan, executes each test function as a TAP subtest, and +finishes the tests, so you don't have to call `plan()` or `finish()` yourself. +The output, assuming a single startup test, two subtests, and a single +shutdown test, will look something like this: + + ok 1 - Startup test + # Subtest: public.test_this() + ok 1 - simple pass + ok 2 - another simple pass + ok 2 - public.test_this() + # Subtest: public.test_that() + ok 1 - that simple + ok 2 - that simple 2 + ok 3 - public.test_that() + ok 4 - Shutdown test + 1..4 The fixture functions run by `runtests()` are as follows: diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql index f82924b58497..38a1d4327a8c 100644 --- a/sql/pgtap--0.94.0--0.95.0.sql +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -188,3 +188,125 @@ 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; + +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; + verbos BOOLEAN := _is_verbose(); -- verbose is a reserved word in 8.5. + tfaild INTEGER := 0; + ffaild INTEGER := 0; + tnumb INTEGER := 0; + fnumb INTEGER := 0; + tok BOOLEAN := TRUE; + errmsg TEXT; +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..array_upper(tests, 1) LOOP + -- What subtest are we running? + RETURN NEXT ' ' || diag_test_name('Subtest: ' || tests[i]); + + -- Reset the results. + tok := TRUE; + tnumb := currval('__tresults___numb_seq'); + + IF tnumb > 0 THEN + EXECUTE 'TRUNCATE __tresults__'; + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; + END IF; + + 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 := currval('__tresults___numb_seq'); + RETURN NEXT ' 1..' || fnumb; + + -- Emit any error messages. + IF fnumb = 0 THEN + RETURN NEXT ' # No tests run!'; + ELSE + -- Report failures. + ffaild := num_failed(); + IF ffaild > 0 THEN + tok := FALSE; + RETURN NEXT ' ' || diag( + 'Looks like you failed ' || ffaild || ' test' || + CASE tfaild WHEN 1 THEN '' ELSE 's' END + || ' of ' || fnumb + ); + END IF; + END IF; + + EXCEPTION WHEN raise_exception THEN + -- Something went wrong. Record that fact. + errmsg := SQLERRM; + 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 ' ' || diag('Test died: ' || errmsg); + errmsg := NULL; + END IF; + END; + + -- Restore the sequence. + EXECUTE 'TRUNCATE __tresults__'; + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; + + -- 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( currval('__tresults___numb_seq')::integer, 0, tfaild ) LOOP + RETURN NEXT tap; + END LOOP; + + -- Clean up and return. + PERFORM _cleanup(); + RETURN; +END; +$$ LANGUAGE plpgsql; + diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index f416b97cb3bd..2f7d5f6174b2 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -204,7 +204,7 @@ BEGIN END; $$ LANGUAGE plpgsql strict; -CREATE OR REPLACE FUNCTION _finish ( INTEGER, INTEGER, INTEGER) +CREATE OR REPLACE FUNCTION _finish (INTEGER, INTEGER, INTEGER) RETURNS SETOF TEXT AS $$ DECLARE curr_test ALIAS FOR $1; @@ -6032,9 +6032,14 @@ DECLARE setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; - tap text; - verbos boolean := _is_verbose(); -- verbose is a reserved word in 8.5. - num_faild INTEGER := 0; + tap TEXT; + verbos BOOLEAN := _is_verbose(); -- verbose is a reserved word in 8.5. + tfaild INTEGER := 0; + ffaild INTEGER := 0; + tnumb INTEGER := 0; + fnumb INTEGER := 0; + tok BOOLEAN := TRUE; + errmsg TEXT; BEGIN BEGIN -- No plan support. @@ -6043,52 +6048,94 @@ BEGIN 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; + WHEN raise_exception THEN RAISE EXCEPTION '%', SQLERRM; END; - BEGIN - FOR i IN 1..array_upper(tests, 1) LOOP - BEGIN - -- What test are we running? - IF verbos THEN RETURN NEXT diag_test_name(tests[i]); END IF; + -- Record how startup tests have failed. + tfaild := num_failed(); + + FOR i IN 1..array_upper(tests, 1) LOOP + -- What subtest are we running? + RETURN NEXT ' ' || diag_test_name('Subtest: ' || tests[i]); + + -- Reset the results. + tok := TRUE; + tnumb := currval('__tresults___numb_seq'); + + IF tnumb > 0 THEN + EXECUTE 'TRUNCATE __tresults__'; + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; + END IF; + BEGIN + BEGIN -- Run the setup functions. - FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; + 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 tap; + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); END LOOP; -- Run the teardown functions. - FOR tap IN SELECT * FROM _runem(teardown, false) LOOP RETURN NEXT tap; END LOOP; + FOR tap IN SELECT * FROM _runem(teardown, false) LOOP + RETURN NEXT regexp_replace(tap, '^', ' ', 'gn'); + END LOOP; - -- Remember how many failed and then roll back. - num_faild := num_faild + num_failed(); - RAISE EXCEPTION '__TAP_ROLLBACK__'; + -- Emit the plan. + fnumb := currval('__tresults___numb_seq'); + RETURN NEXT ' 1..' || fnumb; + + -- Emit any error messages. + IF fnumb = 0 THEN + RETURN NEXT ' # No tests run!'; + ELSE + -- Report failures. + ffaild := num_failed(); + IF ffaild > 0 THEN + tok := FALSE; + RETURN NEXT ' ' || diag( + 'Looks like you failed ' || ffaild || ' test' || + CASE tfaild WHEN 1 THEN '' ELSE 's' END + || ' of ' || fnumb + ); + END IF; + END IF; EXCEPTION WHEN raise_exception THEN - IF SQLERRM <> '__TAP_ROLLBACK__' THEN - -- We didn't raise it, so propagate it. - RAISE EXCEPTION '%', SQLERRM; - END IF; + -- Something went wrong. Record that fact. + errmsg := SQLERRM; END; - END LOOP; - -- Run the shutdown functions. - FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; + -- 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 ' ' || diag('Test died: ' || errmsg); + errmsg := NULL; + END IF; + END; + + -- Restore the sequence. + EXECUTE 'TRUNCATE __tresults__'; + EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; + + -- 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; - -- Raise an exception to rollback any changes. - RAISE EXCEPTION '__TAP_ROLLBACK__'; - EXCEPTION WHEN raise_exception THEN - IF SQLERRM <> '__TAP_ROLLBACK__' THEN - -- We didn't raise it, so propagate it. - RAISE EXCEPTION '%', SQLERRM; - END IF; - END; -- Finish up. - FOR tap IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) LOOP + FOR tap IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, tfaild ) LOOP RETURN NEXT tap; END LOOP; diff --git a/test/expected/runtests.out b/test/expected/runtests.out index a1f49ae31c79..0d19f8bf8b4a 100644 --- a/test/expected/runtests.out +++ b/test/expected/runtests.out @@ -1,38 +1,54 @@ \unset ECHO ok 1 - starting up ok 2 - starting up some more -# whatever."test ident"() -ok 3 - setup -ok 4 - Should be nothing in the test table -ok 5 - setup more -ok 6 - ident -ok 7 - ident 2 -ok 8 - teardown -ok 9 - teardown more -# whatever.testplpgsql() -ok 10 - setup -ok 11 - Should be nothing in the test table -ok 12 - setup more -ok 13 - plpgsql simple -ok 14 - plpgsql simple 2 -ok 15 - Should be a 1 in the test table -ok 16 - teardown -ok 17 - teardown more -# whatever.testthis() -ok 18 - setup -ok 19 - Should be nothing in the test table -ok 20 - setup more -ok 21 - simple pass -ok 22 - another simple pass -ok 23 - teardown -ok 24 - teardown more -# whatever.testz() -ok 25 - setup -ok 26 - Should be nothing in the test table -ok 27 - setup more -ok 28 - Late test should find nothing in the test table -ok 29 - teardown -ok 30 - teardown more -ok 31 - shutting down -ok 32 - shutting down more -1..32 + # Subtest: whatever."test ident"() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - ident + ok 5 - ident 2 + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 3 - whatever."test ident" + # Subtest: whatever.testexception() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: This test should die, but not halt execution +not ok 4 - whatever.testexception +# Failed test 4: "whatever.testexception" + # Subtest: whatever.testplpgsql() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - plpgsql simple + ok 5 - plpgsql simple 2 + ok 6 - Should be a 1 in the test table + ok 7 - teardown + ok 8 - teardown more + 1..8 +ok 5 - whatever.testplpgsql + # Subtest: whatever.testthis() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - simple pass + ok 5 - another simple pass + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 6 - whatever.testthis + # Subtest: whatever.testz() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - Late test should find nothing in the test table + ok 5 - teardown + ok 6 - teardown more + 1..6 +ok 7 - whatever.testz +ok 8 - shutting down +ok 9 - shutting down more +1..9 +# Looks like you failed 1 test of 9 diff --git a/test/sql/runtests.sql b/test/sql/runtests.sql index df87e03f9b3b..65159ff48ade 100644 --- a/test/sql/runtests.sql +++ b/test/sql/runtests.sql @@ -17,9 +17,10 @@ CREATE OR REPLACE FUNCTION whatever.startupmore() RETURNS SETOF TEXT AS $$ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION whatever.setup() RETURNS SETOF TEXT AS $$ - SELECT pass('setup') - UNION - SELECT is( MAX(id), NULL, 'Should be nothing in the test table') FROM whatever.foo; + SELECT collect_tap( + pass('setup'), + (SELECT is( MAX(id), NULL, 'Should be nothing in the test table') FROM whatever.foo) + ); $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION whatever.setupmore() RETURNS SETOF TEXT AS $$ @@ -43,9 +44,10 @@ CREATE OR REPLACE FUNCTION whatever.shutdownmore() RETURNS SETOF TEXT AS $$ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION whatever.testthis() RETURNS SETOF TEXT AS $$ - SELECT pass('simple pass') AS foo - UNION SELECT pass('another simple pass') - ORDER BY foo ASC; + SELECT collect_tap( + pass('simple pass'), + pass('another simple pass') + ); $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION whatever.testplpgsql() RETURNS SETOF TEXT AS $$ @@ -57,6 +59,16 @@ BEGIN END; $$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION whatever.testexception() RETURNS SETOF TEXT AS $$ +BEGIN + RETURN NEXT pass( 'plpgsql simple' ); + RETURN NEXT pass( 'plpgsql simple 2' ); + INSERT INTO whatever.foo VALUES(1); + RETURN NEXT is( MAX(id), 1, 'Should be a 1 in the test table') FROM whatever.foo; + RAISE EXCEPTION 'This test should die, but not halt execution'; +END; +$$ LANGUAGE plpgsql; + CREATE OR REPLACE FUNCTION whatever.testz() RETURNS SETOF TEXT AS $$ SELECT is( MAX(id), NULL, 'Late test should find nothing in the test table') FROM whatever.foo; $$ LANGUAGE SQL; From bfc08363e79260e2156d6b25d41f47b7c5350584 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 19 Mar 2015 17:49:30 -0700 Subject: [PATCH 0845/1195] Eliminate unused variable. --- sql/pgtap--0.94.0--0.95.0.sql | 1 - sql/pgtap.sql.in | 1 - 2 files changed, 2 deletions(-) diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql index 38a1d4327a8c..7ab86e77bacd 100644 --- a/sql/pgtap--0.94.0--0.95.0.sql +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -198,7 +198,6 @@ DECLARE teardown ALIAS FOR $4; tests ALIAS FOR $5; tap TEXT; - verbos BOOLEAN := _is_verbose(); -- verbose is a reserved word in 8.5. tfaild INTEGER := 0; ffaild INTEGER := 0; tnumb INTEGER := 0; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 2f7d5f6174b2..ce138558d6ce 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -6033,7 +6033,6 @@ DECLARE teardown ALIAS FOR $4; tests ALIAS FOR $5; tap TEXT; - verbos BOOLEAN := _is_verbose(); -- verbose is a reserved word in 8.5. tfaild INTEGER := 0; ffaild INTEGER := 0; tnumb INTEGER := 0; From db04889f33b5818d1b391d02d279037a0218a84b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 19 Mar 2015 17:54:07 -0700 Subject: [PATCH 0846/1195] Add GRANT to 0.95.0 upgrade. It is fixed in 0.95, not 0.94. --- sql/pgtap--0.93.0--0.94.0.sql | 3 --- sql/pgtap--0.94.0--0.95.0.sql | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/sql/pgtap--0.93.0--0.94.0.sql b/sql/pgtap--0.93.0--0.94.0.sql index ea2fcbe402a6..9937ead80b11 100644 --- a/sql/pgtap--0.93.0--0.94.0.sql +++ b/sql/pgtap--0.93.0--0.94.0.sql @@ -191,6 +191,3 @@ BEGIN RETURN; END; $$ LANGUAGE plpgsql; - -GRANT SELECT ON tap_funky TO PUBLIC; -GRANT SELECT ON pg_all_foreign_keys TO PUBLIC; diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql index 7ab86e77bacd..2c4fa3af9798 100644 --- a/sql/pgtap--0.94.0--0.95.0.sql +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -309,3 +309,5 @@ BEGIN END; $$ LANGUAGE plpgsql; +GRANT SELECT ON tap_funky TO PUBLIC; +GRANT SELECT ON pg_all_foreign_keys TO PUBLIC; From 5ea69c7decfbda01708e05b6931f41deb761c8b3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 19 Mar 2015 18:07:48 -0700 Subject: [PATCH 0847/1195] Try to coax test_exception into running *after* "test ident". --- test/expected/runtests.out | 6 +++--- test/sql/runtests.sql | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/expected/runtests.out b/test/expected/runtests.out index 0d19f8bf8b4a..c4ef7d7c5883 100644 --- a/test/expected/runtests.out +++ b/test/expected/runtests.out @@ -11,13 +11,13 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" - # Subtest: whatever.testexception() + # Subtest: whatever.test_exception() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more # Test died: This test should die, but not halt execution -not ok 4 - whatever.testexception -# Failed test 4: "whatever.testexception" +not ok 4 - whatever.test_exception +# Failed test 4: "whatever.test_exception" # Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table diff --git a/test/sql/runtests.sql b/test/sql/runtests.sql index 65159ff48ade..28bfc3631757 100644 --- a/test/sql/runtests.sql +++ b/test/sql/runtests.sql @@ -59,7 +59,7 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION whatever.testexception() RETURNS SETOF TEXT AS $$ +CREATE OR REPLACE FUNCTION whatever.test_exception() RETURNS SETOF TEXT AS $$ BEGIN RETURN NEXT pass( 'plpgsql simple' ); RETURN NEXT pass( 'plpgsql simple 2' ); From 86eac4456f993c24e943eb359ee223c53cb362c7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 19 Mar 2015 19:54:49 -0700 Subject: [PATCH 0848/1195] Make it come after plpgsql. --- test/expected/runtests.out | 18 +++++++++--------- test/sql/runtests.sql | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test/expected/runtests.out b/test/expected/runtests.out index c4ef7d7c5883..d13f041fc2d5 100644 --- a/test/expected/runtests.out +++ b/test/expected/runtests.out @@ -11,13 +11,6 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" - # Subtest: whatever.test_exception() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - # Test died: This test should die, but not halt execution -not ok 4 - whatever.test_exception -# Failed test 4: "whatever.test_exception" # Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table @@ -28,7 +21,14 @@ not ok 4 - whatever.test_exception ok 7 - teardown ok 8 - teardown more 1..8 -ok 5 - whatever.testplpgsql +ok 4 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: This test should die, but not halt execution +not ok 5 - whatever.testplpgsqldie +# Failed test 5: "whatever.testplpgsqldie" # Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table @@ -51,4 +51,4 @@ ok 7 - whatever.testz ok 8 - shutting down ok 9 - shutting down more 1..9 -# Looks like you failed 1 test of 9 + diff --git a/test/sql/runtests.sql b/test/sql/runtests.sql index 28bfc3631757..b3326fb8d5b6 100644 --- a/test/sql/runtests.sql +++ b/test/sql/runtests.sql @@ -59,7 +59,7 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION whatever.test_exception() RETURNS SETOF TEXT AS $$ +CREATE OR REPLACE FUNCTION whatever.testplpgsqldie() RETURNS SETOF TEXT AS $$ BEGIN RETURN NEXT pass( 'plpgsql simple' ); RETURN NEXT pass( 'plpgsql simple 2' ); From 8c2e7a5d1562233904e01159b59ff2d7ed23e073 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 19 Mar 2015 20:01:29 -0700 Subject: [PATCH 0849/1195] Make sure a failing test works properly. --- test/expected/runtests.out | 22 +++++++++++++++++----- test/sql/runtests.sql | 4 ++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/test/expected/runtests.out b/test/expected/runtests.out index d13f041fc2d5..ec7f6df7044d 100644 --- a/test/expected/runtests.out +++ b/test/expected/runtests.out @@ -39,6 +39,18 @@ not ok 5 - whatever.testplpgsqldie ok 7 - teardown more 1..7 ok 6 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + not ok 4 - this test intentionally fails + # Failed test 4: "this test intentionally fails" + ok 5 - teardown + ok 6 - teardown more + 1..6 + # Looks like you failed 1 test of 6 +not ok 7 - whatever.testy +# Failed test 7: "whatever.testy" # Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table @@ -47,8 +59,8 @@ ok 6 - whatever.testthis ok 5 - teardown ok 6 - teardown more 1..6 -ok 7 - whatever.testz -ok 8 - shutting down -ok 9 - shutting down more -1..9 - +ok 8 - whatever.testz +ok 9 - shutting down +ok 10 - shutting down more +1..10 +# Looks like you failed 2 tests of 10 diff --git a/test/sql/runtests.sql b/test/sql/runtests.sql index b3326fb8d5b6..3120a7ee4fea 100644 --- a/test/sql/runtests.sql +++ b/test/sql/runtests.sql @@ -69,6 +69,10 @@ BEGIN END; $$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION whatever.testy() RETURNS SETOF TEXT AS $$ + SELECT fail('this test intentionally fails'); +$$ LANGUAGE SQL; + CREATE OR REPLACE FUNCTION whatever.testz() RETURNS SETOF TEXT AS $$ SELECT is( MAX(id), NULL, 'Late test should find nothing in the test table') FROM whatever.foo; $$ LANGUAGE SQL; From 1aa84013dc66733b34be16aa3ebfe196963d43b4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 19 Mar 2015 20:18:30 -0700 Subject: [PATCH 0850/1195] Make sure runntests() properly deals with no tests. Include a test for it! --- sql/pgtap--0.94.0--0.95.0.sql | 9 +++++---- sql/pgtap.sql.in | 9 +++++---- test/expected/runnotests.out | 8 ++++++++ test/sql/runnotests.sql | 14 ++++++++++++++ 4 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 test/expected/runnotests.out create mode 100644 test/sql/runnotests.sql diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql index 2c4fa3af9798..783a2d30a13b 100644 --- a/sql/pgtap--0.94.0--0.95.0.sql +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -218,13 +218,13 @@ BEGIN -- Record how startup tests have failed. tfaild := num_failed(); - FOR i IN 1..array_upper(tests, 1) LOOP + 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 := currval('__tresults___numb_seq'); + tnumb := COALESCE(_get('curr_test'), 0); IF tnumb > 0 THEN EXECUTE 'TRUNCATE __tresults__'; @@ -249,12 +249,13 @@ BEGIN END LOOP; -- Emit the plan. - fnumb := currval('__tresults___numb_seq'); + 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(); @@ -299,7 +300,7 @@ BEGIN FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; -- Finish up. - FOR tap IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, tfaild ) LOOP + FOR tap IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) LOOP RETURN NEXT tap; END LOOP; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index ce138558d6ce..890c7b491366 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -6053,13 +6053,13 @@ BEGIN -- Record how startup tests have failed. tfaild := num_failed(); - FOR i IN 1..array_upper(tests, 1) LOOP + 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 := currval('__tresults___numb_seq'); + tnumb := COALESCE(_get('curr_test'), 0); IF tnumb > 0 THEN EXECUTE 'TRUNCATE __tresults__'; @@ -6084,12 +6084,13 @@ BEGIN END LOOP; -- Emit the plan. - fnumb := currval('__tresults___numb_seq'); + 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(); @@ -6134,7 +6135,7 @@ BEGIN FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; -- Finish up. - FOR tap IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, tfaild ) LOOP + FOR tap IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) LOOP RETURN NEXT tap; END LOOP; diff --git a/test/expected/runnotests.out b/test/expected/runnotests.out new file mode 100644 index 000000000000..bfa4fd01caaf --- /dev/null +++ b/test/expected/runnotests.out @@ -0,0 +1,8 @@ +\unset ECHO + # Subtest: whatever.testthis() + 1..0 + # No tests run! +not ok 1 - whatever.testthis +# Failed test 1: "whatever.testthis" +1..1 +# Looks like you failed 1 test of 1 diff --git a/test/sql/runnotests.sql b/test/sql/runnotests.sql new file mode 100644 index 000000000000..bc7cf840099a --- /dev/null +++ b/test/sql/runnotests.sql @@ -0,0 +1,14 @@ +\unset ECHO +\i test/setup.sql +SET client_min_messages = warning; + +CREATE SCHEMA whatever; + +CREATE OR REPLACE FUNCTION whatever.testthis() RETURNS SETOF TEXT AS $$ +BEGIN + -- Do nothing. +END; +$$ LANGUAGE plpgsql; + +SELECT * FROM runtests('whatever'::name); +ROLLBACK; From 1dbf4fcc67a96ab07966c03cec29eb3ec09c1e86 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 19 Mar 2015 20:26:41 -0700 Subject: [PATCH 0851/1195] Always reset test number. --- sql/pgtap--0.94.0--0.95.0.sql | 2 ++ sql/pgtap.sql.in | 2 ++ test/expected/runjusttests.out | 40 +++++++++++++++++++++ test/sql/runjusttests.sql | 64 ++++++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 test/expected/runjusttests.out create mode 100644 test/sql/runjusttests.sql diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql index 783a2d30a13b..619f04875276 100644 --- a/sql/pgtap--0.94.0--0.95.0.sql +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -229,6 +229,7 @@ BEGIN IF tnumb > 0 THEN EXECUTE 'TRUNCATE __tresults__'; EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; + PERFORM _set('curr_test', 0); END IF; BEGIN @@ -289,6 +290,7 @@ BEGIN -- Restore the sequence. EXECUTE 'TRUNCATE __tresults__'; EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; + PERFORM _set('curr_test', tnumb); -- Record this test. RETURN NEXT ok(tok, tests[i]); diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 890c7b491366..891f8701864e 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -6064,6 +6064,7 @@ BEGIN IF tnumb > 0 THEN EXECUTE 'TRUNCATE __tresults__'; EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; + PERFORM _set('curr_test', 0); END IF; BEGIN @@ -6124,6 +6125,7 @@ BEGIN -- Restore the sequence. EXECUTE 'TRUNCATE __tresults__'; EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; + PERFORM _set('curr_test', tnumb); -- Record this test. RETURN NEXT ok(tok, tests[i]); diff --git a/test/expected/runjusttests.out b/test/expected/runjusttests.out new file mode 100644 index 000000000000..e9fa7ba97358 --- /dev/null +++ b/test/expected/runjusttests.out @@ -0,0 +1,40 @@ +\unset ECHO + # Subtest: whatever."test ident"() + ok 1 - ident + ok 2 - ident 2 + 1..2 +ok 1 - whatever."test ident" + # Subtest: whatever.testnada() + 1..0 + # No tests run! +not ok 2 - whatever.testnada +# Failed test 2: "whatever.testnada" + # Subtest: whatever.testplpgsql() + ok 1 - plpgsql simple + ok 2 - plpgsql simple 2 + ok 3 - Should be a 1 in the test table + 1..3 +ok 3 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + # Test died: This test should die, but not halt execution +not ok 4 - whatever.testplpgsqldie +# Failed test 4: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - simple pass + ok 2 - another simple pass + 1..2 +ok 5 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - pass + not ok 2 - this test intentionally fails + # Failed test 2: "this test intentionally fails" + 1..2 + # Looks like you failed 1 tests of 2 +not ok 6 - whatever.testy +# Failed test 6: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - Late test should find nothing in the test table + 1..1 +ok 7 - whatever.testz +1..7 +# Looks like you failed 3 tests of 7 diff --git a/test/sql/runjusttests.sql b/test/sql/runjusttests.sql new file mode 100644 index 000000000000..e033f9678fdc --- /dev/null +++ b/test/sql/runjusttests.sql @@ -0,0 +1,64 @@ +\unset ECHO +\i test/setup.sql +SET client_min_messages = warning; + +CREATE SCHEMA whatever; +CREATE TABLE whatever.foo ( id serial primary key ); + +-- Make sure we get test function names. +SET client_min_messages = notice; + +CREATE OR REPLACE FUNCTION whatever.testthis() RETURNS SETOF TEXT AS $$ + SELECT collect_tap( + pass('simple pass'), + pass('another simple pass') + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION whatever.testplpgsql() RETURNS SETOF TEXT AS $$ +BEGIN + RETURN NEXT pass( 'plpgsql simple' ); + RETURN NEXT pass( 'plpgsql simple 2' ); + INSERT INTO whatever.foo VALUES(1); + RETURN NEXT is( MAX(id), 1, 'Should be a 1 in the test table') FROM whatever.foo; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION whatever.testplpgsqldie() RETURNS SETOF TEXT AS $$ +BEGIN + RETURN NEXT pass( 'plpgsql simple' ); + RETURN NEXT pass( 'plpgsql simple 2' ); + INSERT INTO whatever.foo VALUES(1); + RETURN NEXT is( MAX(id), 1, 'Should be a 1 in the test table') FROM whatever.foo; + RAISE EXCEPTION 'This test should die, but not halt execution'; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION whatever.testy() RETURNS SETOF TEXT AS $$ + SELECT collect_tap( + pass('pass'), + fail('this test intentionally fails') + ); +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION whatever.testnada() RETURNS SETOF TEXT AS $$ +BEGIN + -- Do nothing. +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION whatever.testz() RETURNS SETOF TEXT AS $$ + SELECT is( MAX(id), NULL, 'Late test should find nothing in the test table') FROM whatever.foo; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION whatever."test ident"() RETURNS SETOF TEXT AS $$ +BEGIN + RETURN NEXT pass( 'ident' ); + RETURN NEXT pass( 'ident 2' ); +END; +$$ LANGUAGE plpgsql; + +-- Run the actual tests. Yes, it's a one-liner! +SELECT * FROM runtests('whatever'::name); + +ROLLBACK; From ce79e013c914cec184ac5d3a94dac74318bfd410 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Mar 2015 10:39:29 -0700 Subject: [PATCH 0852/1195] Eliminate the __tresults__ table. No reason to write results. The only places we looked at them were when calculating failures and in `check_test()`. So store failure count in the __tcache__ table, instead, and parse the test success and description from the actuat test string. With the overhead of __tresults__ eliminated, tests should run a little faster. --- Changes | 3 + sql/pgtap--0.94.0--0.95.0.sql | 199 +++++++++++++++++++++++++++++++++- sql/pgtap.sql.in | 41 ++----- test/sql/moretap.sql | 10 +- test/sql/todotap.sql | 7 -- 5 files changed, 214 insertions(+), 46 deletions(-) diff --git a/Changes b/Changes index e8e6da5e899c..770f3af629f1 100644 --- a/Changes +++ b/Changes @@ -30,6 +30,9 @@ Revision history for pgTAP * An exception thrown by a test no run by `runtests()` no longer halts execution. It is instead properly rolled back and the error message emitted as a diagnostic message. Thanks to Vitaliy Kotov for the report. +* Eliminated the temporary table `__tresults__`. Test results are no longer + written to a table, but simply emitted. This should make tests run a bit + faster, as that I/O overhead has been eliminated. 0.94.0 2014-01-07T01:32:36Z --------------------------- diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql index 619f04875276..bddf60ef3042 100644 --- a/sql/pgtap--0.94.0--0.95.0.sql +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -2,6 +2,94 @@ CREATE OR REPLACE FUNCTION pgtap_version() RETURNS NUMERIC AS 'SELECT 0.95;' 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 ); + RETURN '1..' || $1; +END; +$$ LANGUAGE plpgsql strict; + +CREATE OR REPLACE FUNCTION add_result ( bool, bool, text, text, text ) +RETURNS integer AS $$ +BEGIN + IF NOT $1 THEN PERFORM _set('failed', COALESCE(_get('failed'), 0) + 1); END IF; + RETURN nextval('__tresults___numb_seq'); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION num_failed () +RETURNS INTEGER AS $$ + SELECT COALESCE(_get('failed'), 0); +$$ LANGUAGE SQL strict; + +CREATE OR REPLACE FUNCTION _finish (INTEGER, INTEGER, INTEGER) +RETURNS SETOF TEXT AS $$ +DECLARE + curr_test ALIAS FOR $1; + exp_tests INTEGER := $2; + num_faild ALIAS FOR $3; + plural CHAR; +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 + 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; + -- is_member_of( role, members[], description ) CREATE OR REPLACE FUNCTION is_member_of( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ @@ -227,9 +315,9 @@ BEGIN tnumb := COALESCE(_get('curr_test'), 0); IF tnumb > 0 THEN - EXECUTE 'TRUNCATE __tresults__'; EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; PERFORM _set('curr_test', 0); + PERFORM _set('failed', 0); END IF; BEGIN @@ -288,9 +376,9 @@ BEGIN END; -- Restore the sequence. - EXECUTE 'TRUNCATE __tresults__'; 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]); @@ -312,5 +400,112 @@ BEGIN END; $$ LANGUAGE plpgsql; +-- 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; + +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; + GRANT SELECT ON tap_funky TO PUBLIC; GRANT SELECT ON pg_all_foreign_keys TO PUBLIC; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 891f8701864e..ccf8e69ff4c7 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -46,16 +46,6 @@ BEGIN GRANT ALL ON TABLE __tcache___id_seq TO PUBLIC; CREATE TEMP SEQUENCE __tresults___numb_seq; - CREATE TEMP TABLE __tresults__ ( - numb INTEGER NOT NULL DEFAULT nextval(''__tresults___numb_seq''), - ok BOOLEAN NOT NULL DEFAULT TRUE, - aok BOOLEAN NOT NULL DEFAULT TRUE, - descr TEXT NOT NULL DEFAULT '''', - type TEXT NOT NULL DEFAULT '''', - reason TEXT NOT NULL DEFAULT '''' - ); - CREATE UNIQUE INDEX __tresults___key ON __tresults__(numb); - GRANT ALL ON TABLE __tresults__ TO PUBLIC; GRANT ALL ON TABLE __tresults___numb_seq TO PUBLIC; '; @@ -184,25 +174,15 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION add_result ( bool, bool, text, text, text ) RETURNS integer AS $$ BEGIN - EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) - VALUES( ' || $1 || ', ' - || $2 || ', ' - || quote_literal(COALESCE($3, '')) || ', ' - || quote_literal($4) || ', ' - || quote_literal($5) || ' )'; - RETURN currval('__tresults___numb_seq'); + IF NOT $1 THEN PERFORM _set('failed', COALESCE(_get('failed'), 0) + 1); END IF; + RETURN nextval('__tresults___numb_seq'); END; $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION num_failed () RETURNS INTEGER AS $$ -DECLARE - ret integer; -BEGIN - EXECUTE 'SELECT COUNT(*)::INTEGER FROM __tresults__ WHERE ok = FALSE' INTO ret; - RETURN ret; -END; -$$ LANGUAGE plpgsql strict; + SELECT COALESCE(_get('failed'), 0); +$$ LANGUAGE SQL strict; CREATE OR REPLACE FUNCTION _finish (INTEGER, INTEGER, INTEGER) RETURNS SETOF TEXT AS $$ @@ -4326,7 +4306,7 @@ RETURNS NAME[] AS $$ WHERE pg_catalog.pg_table_is_visible(c.oid) AND n.nspname <> 'pg_catalog' AND c.relkind = $1 - AND c.relname NOT IN ('__tcache__', '__tresults__', 'pg_all_foreign_keys', 'tap_funky', '__tresults___numb_seq', '__tcache___id_seq') + 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) @@ -5824,12 +5804,12 @@ BEGIN tnumb := currval('__tresults___numb_seq'); -- Fetch the results. - EXECUTE 'SELECT aok, descr FROM __tresults__ WHERE numb = ' || tnumb - INTO aok, adescr; + aok := substring(have, 1, 2) = 'ok'; + adescr := COALESCE(substring(have FROM E'(?:not )?ok [[:digit:]]+ - ([^\n]+)'), ''); -- Now delete those results. - EXECUTE 'DELETE FROM __tresults__ WHERE numb = ' || tnumb; 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 '; @@ -6011,7 +5991,6 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION _cleanup() RETURNS boolean AS $$ - DROP TABLE __tresults__; DROP SEQUENCE __tresults___numb_seq; DROP TABLE __tcache__; DROP SEQUENCE __tcache___id_seq; @@ -6062,9 +6041,9 @@ BEGIN tnumb := COALESCE(_get('curr_test'), 0); IF tnumb > 0 THEN - EXECUTE 'TRUNCATE __tresults__'; EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH 1'; PERFORM _set('curr_test', 0); + PERFORM _set('failed', 0); END IF; BEGIN @@ -6123,9 +6102,9 @@ BEGIN END; -- Restore the sequence. - EXECUTE 'TRUNCATE __tresults__'; 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]); diff --git a/test/sql/moretap.sql b/test/sql/moretap.sql index b6986bef01d8..81607e4adf6d 100644 --- a/test/sql/moretap.sql +++ b/test/sql/moretap.sql @@ -29,7 +29,7 @@ SELECT is( /****************************************************************************/ -- Check num_failed SELECT is( num_failed(), 1, 'We should have one failure' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb = :fail_numb; +UPDATE __tcache__ SET value = 0 WHERE label = 'failed'; SELECT is( num_failed(), 0, 'We should now have no failures' ); /****************************************************************************/ @@ -110,15 +110,13 @@ SELECT * FROM check_test( ok(false, 'foo'), false, 'ok(false, ''foo'')', 'foo', SELECT * FROM check_test( ok(NULL, 'null'), false, 'ok(NULL, ''null'')', 'null', ' (test result was NULL)' ); /****************************************************************************/ --- test multiline description. +-- test multiline description. Second line is effectively diagnostic output. SELECT * FROM check_test( ok( true, 'foo bar' ), true, - 'multiline desc', 'foo -bar', - '' -); + 'multiline desc', 'foo', 'bar' + ); /****************************************************************************/ -- Finish the tests and clean up. diff --git a/test/sql/todotap.sql b/test/sql/todotap.sql index 1b308f2cbd27..b85b7b339e48 100644 --- a/test/sql/todotap.sql +++ b/test/sql/todotap.sql @@ -28,7 +28,6 @@ SELECT is( # Failed (TODO) test 4: "Another todo test"', 'Single default todo test should display properly' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 2, 4 ); -- Try just a number. \echo ok 6 - todo fail @@ -120,8 +119,6 @@ SELECT ok( 'Nested todos should work properly' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 25, 26, 27 ); - /****************************************************************************/ -- Test todo_start() and todo_end(). \echo ok 29 - todo fail @@ -159,8 +156,6 @@ SELECT ok( 'todo_start() and todo_end() should work properly with in_todo()' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 29, 30, 31 ); - /****************************************************************************/ -- Make sure we can reverse the arguments. \echo ok 33 - todo fail @@ -177,8 +172,6 @@ not ok 34 - Another todo test # TODO just because 'Should be able to revers the arguments to todo()' ); -UPDATE __tresults__ SET ok = true, aok = true WHERE numb IN( 32, 33 ); - -- Test the exception when throws_ok() is available. SELECT CASE WHEN pg_version_num() < 80100 THEN pass('Should get an exception when todo_end() is called without todo_start()') From c6cd86181ea17710c903a26bcbd389f56e43c7f3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Mar 2015 10:49:37 -0700 Subject: [PATCH 0853/1195] Always have failed count. --- sql/pgtap--0.94.0--0.95.0.sql | 5 +++-- sql/pgtap.sql.in | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql index bddf60ef3042..f82dcd5c84dc 100644 --- a/sql/pgtap--0.94.0--0.95.0.sql +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -35,6 +35,7 @@ BEGIN -- Save the plan and return. PERFORM _set('plan', $1 ); + PERFORM _set('failed', 0 ); RETURN '1..' || $1; END; $$ LANGUAGE plpgsql strict; @@ -42,14 +43,14 @@ $$ LANGUAGE plpgsql strict; CREATE OR REPLACE FUNCTION add_result ( bool, bool, text, text, text ) RETURNS integer AS $$ BEGIN - IF NOT $1 THEN PERFORM _set('failed', COALESCE(_get('failed'), 0) + 1); END IF; + 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 COALESCE(_get('failed'), 0); + SELECT _get('failed'); $$ LANGUAGE SQL strict; CREATE OR REPLACE FUNCTION _finish (INTEGER, INTEGER, INTEGER) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index ccf8e69ff4c7..dd16fa18f2dd 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -60,6 +60,7 @@ BEGIN -- Save the plan and return. PERFORM _set('plan', $1 ); + PERFORM _set('failed', 0 ); RETURN '1..' || $1; END; $$ LANGUAGE plpgsql strict; @@ -174,14 +175,14 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION add_result ( bool, bool, text, text, text ) RETURNS integer AS $$ BEGIN - IF NOT $1 THEN PERFORM _set('failed', COALESCE(_get('failed'), 0) + 1); END IF; + 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 COALESCE(_get('failed'), 0); + SELECT _get('failed'); $$ LANGUAGE SQL strict; CREATE OR REPLACE FUNCTION _finish (INTEGER, INTEGER, INTEGER) From 3629e17cc522aadc24ca3ebf39526de03be95406 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Mar 2015 11:07:15 -0700 Subject: [PATCH 0854/1195] Add has_sequence(:schema, :sequence). Closes #58. --- Changes | 2 + doc/pgtap.mmd | 10 +- sql/pgtap--0.94.0--0.95.0.sql | 9 + sql/pgtap.sql.in | 11 +- test/expected/hastap.out | 1390 +++++++++++++++++---------------- test/sql/hastap.sql | 10 +- 6 files changed, 733 insertions(+), 699 deletions(-) diff --git a/Changes b/Changes index 770f3af629f1..f22bf714783a 100644 --- a/Changes +++ b/Changes @@ -33,6 +33,8 @@ Revision history for pgTAP * Eliminated the temporary table `__tresults__`. Test results are no longer written to a table, but simply emitted. This should make tests run a bit faster, as that I/O overhead has been eliminated. +* Added missing functions `has_sequence(:schema, :sequence)` mentioned in the + docs (Issue #58): 0.94.0 2014-01-07T01:32:36Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 6dbb7de319a3..a83bcc43d1f2 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2832,6 +2832,7 @@ specified materialized view does *not* exist. ### `has_sequence()` ### SELECT has_sequence( :schema, :sequence, :description ); + SELECT has_sequence( :schema, :sequence ); SELECT has_sequence( :sequence, :description ); SELECT has_sequence( :sequence ); @@ -2851,10 +2852,13 @@ argument is a schema name, the second is a sequence name, and the third is the test description. If you omit the schema, the sequence must be visible in the search path. Example: - SELECT has_sequence('myschema', 'somesequence'); + SELECT has_sequence('somesequence'); -If you omit the test description, it will be set to "Sequence `:sequence` should -exist". +If you omit the test description, it will be set to +"Sequence `:schema`.`:sequence` should exist". If you find that the function +call seems to be getting confused, cast the sequence to the `NAME` type: + + SELECT has_sequence('myschema', 'somesequence'::NAME); ### `hasnt_sequence()` ### diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql index f82dcd5c84dc..8423315eed05 100644 --- a/sql/pgtap--0.94.0--0.95.0.sql +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -508,5 +508,14 @@ RETURNS boolean AS $$ SELECT TRUE; $$ 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; + GRANT SELECT ON tap_funky TO PUBLIC; GRANT SELECT ON pg_all_foreign_keys TO PUBLIC; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index dd16fa18f2dd..f81a0a1d6ae3 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -88,7 +88,7 @@ RETURNS integer[] AS $$ DECLARE ret integer[]; BEGIN - EXECUTE 'SELECT ARRAY[ id, value] FROM __tcache__ WHERE label = ' || + 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; @@ -1055,6 +1055,15 @@ 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 $$ diff --git a/test/expected/hastap.out b/test/expected/hastap.out index 43e2fffc912b..5e96a3486fb7 100644 --- a/test/expected/hastap.out +++ b/test/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..828 +1..830 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -135,696 +135,698 @@ ok 132 - has_sequence(sequence, desc) should have the proper diagnostics ok 133 - has_sequence(sch, sequence, desc) should pass ok 134 - has_sequence(sch, sequence, desc) should have the proper description ok 135 - has_sequence(sch, sequence, desc) should have the proper diagnostics -ok 136 - hasnt_sequence(non-existent sequence) should pass -ok 137 - hasnt_sequence(non-existent sequence) should have the proper description -ok 138 - hasnt_sequence(non-existent sequence) should have the proper diagnostics -ok 139 - hasnt_sequence(non-existent sequence, desc) should pass -ok 140 - hasnt_sequence(non-existent sequence, desc) should have the proper description -ok 141 - hasnt_sequence(non-existent sequence, desc) should have the proper diagnostics -ok 142 - hasnt_sequence(sch, non-existtent sequence, desc) should pass -ok 143 - hasnt_sequence(sch, non-existtent sequence, desc) should have the proper description -ok 144 - hasnt_sequence(sch, non-existtent sequence, desc) should have the proper diagnostics -ok 145 - hasnt_sequence(sequence, desc) should fail -ok 146 - hasnt_sequence(sequence, desc) should have the proper description -ok 147 - hasnt_sequence(sequence, desc) should have the proper diagnostics -ok 148 - hasnt_sequence(sch, sequence, desc) should fail -ok 149 - hasnt_sequence(sch, sequence, desc) should have the proper description -ok 150 - hasnt_sequence(sch, sequence, desc) should have the proper diagnostics -ok 151 - has_composite(non-existent composite type) should fail -ok 152 - has_composite(non-existent composite type) should have the proper description -ok 153 - has_composite(non-existent composite type) should have the proper diagnostics -ok 154 - has_composite(non-existent schema, tab) should fail -ok 155 - has_composite(non-existent schema, tab) should have the proper description -ok 156 - has_composite(non-existent schema, tab) should have the proper diagnostics -ok 157 - has_composite(sch, non-existent composite type, desc) should fail -ok 158 - has_composite(sch, non-existent composite type, desc) should have the proper description -ok 159 - has_composite(sch, non-existent composite type, desc) should have the proper diagnostics -ok 160 - has_composite(tab, desc) should pass -ok 161 - has_composite(tab, desc) should have the proper description -ok 162 - has_composite(tab, desc) should have the proper diagnostics -ok 163 - has_composite(sch, tab, desc) should pass -ok 164 - has_composite(sch, tab, desc) should have the proper description -ok 165 - has_composite(sch, tab, desc) should have the proper diagnostics -ok 166 - has_composite(sch, view, desc) should fail -ok 167 - has_composite(sch, view, desc) should have the proper description -ok 168 - has_composite(sch, view, desc) should have the proper diagnostics -ok 169 - has_composite(type, desc) should fail -ok 170 - has_composite(type, desc) should have the proper description -ok 171 - has_composite(type, desc) should have the proper diagnostics -ok 172 - hasnt_composite(non-existent composite type) should pass -ok 173 - hasnt_composite(non-existent composite type) should have the proper description -ok 174 - hasnt_composite(non-existent composite type) should have the proper diagnostics -ok 175 - hasnt_composite(non-existent schema, tab) should pass -ok 176 - hasnt_composite(non-existent schema, tab) should have the proper description -ok 177 - hasnt_composite(non-existent schema, tab) should have the proper diagnostics -ok 178 - hasnt_composite(sch, non-existent tab, desc) should pass -ok 179 - hasnt_composite(sch, non-existent tab, desc) should have the proper description -ok 180 - hasnt_composite(sch, non-existent tab, desc) should have the proper diagnostics -ok 181 - hasnt_composite(tab, desc) should fail -ok 182 - hasnt_composite(tab, desc) should have the proper description -ok 183 - hasnt_composite(tab, desc) should have the proper diagnostics -ok 184 - hasnt_composite(sch, tab, desc) should fail -ok 185 - hasnt_composite(sch, tab, desc) should have the proper description -ok 186 - hasnt_composite(sch, tab, desc) should have the proper diagnostics -ok 187 - has_type(type) should pass -ok 188 - has_type(type) should have the proper description -ok 189 - has_type(type) should have the proper diagnostics -ok 190 - has_type(type, desc) should pass -ok 191 - has_type(type, desc) should have the proper description -ok 192 - has_type(type, desc) should have the proper diagnostics -ok 193 - has_type(scheam, type) should pass -ok 194 - has_type(scheam, type) should have the proper description -ok 195 - has_type(scheam, type) should have the proper diagnostics -ok 196 - has_type(schema, type, desc) should pass -ok 197 - has_type(schema, type, desc) should have the proper description -ok 198 - has_type(schema, type, desc) should have the proper diagnostics -ok 199 - has_type(myType) should pass -ok 200 - has_type(myType) should have the proper description -ok 201 - has_type(myType) should have the proper diagnostics -ok 202 - has_type(myType, desc) should pass -ok 203 - has_type(myType, desc) should have the proper description -ok 204 - has_type(myType, desc) should have the proper diagnostics -ok 205 - has_type(scheam, myType) should pass -ok 206 - has_type(scheam, myType) should have the proper description -ok 207 - has_type(scheam, myType) should have the proper diagnostics -ok 208 - has_type(schema, myType, desc) should pass -ok 209 - has_type(schema, myType, desc) should have the proper description -ok 210 - has_type(schema, myType, desc) should have the proper diagnostics -ok 211 - has_type(type) should fail -ok 212 - has_type(type) should have the proper description -ok 213 - has_type(type) should have the proper diagnostics -ok 214 - has_type(type, desc) should fail -ok 215 - has_type(type, desc) should have the proper description -ok 216 - has_type(type, desc) should have the proper diagnostics -ok 217 - has_type(scheam, type) should fail -ok 218 - has_type(scheam, type) should have the proper description -ok 219 - has_type(scheam, type) should have the proper diagnostics -ok 220 - has_type(schema, type, desc) should fail -ok 221 - has_type(schema, type, desc) should have the proper description -ok 222 - has_type(schema, type, desc) should have the proper diagnostics -ok 223 - has_type(domain) should pass -ok 224 - has_type(domain) should have the proper description -ok 225 - has_type(domain) should have the proper diagnostics -ok 226 - has_type(myDomain) should pass -ok 227 - has_type(myDomain) should have the proper description -ok 228 - has_type(myDomain) should have the proper diagnostics -ok 229 - hasnt_type(type) should pass -ok 230 - hasnt_type(type) should have the proper description -ok 231 - hasnt_type(type) should have the proper diagnostics -ok 232 - hasnt_type(type, desc) should pass -ok 233 - hasnt_type(type, desc) should have the proper description -ok 234 - hasnt_type(type, desc) should have the proper diagnostics -ok 235 - hasnt_type(scheam, type) should pass -ok 236 - hasnt_type(scheam, type) should have the proper description -ok 237 - hasnt_type(scheam, type) should have the proper diagnostics -ok 238 - hasnt_type(schema, type, desc) should pass -ok 239 - hasnt_type(schema, type, desc) should have the proper description -ok 240 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 241 - hasnt_type(type) should fail -ok 242 - hasnt_type(type) should have the proper description -ok 243 - hasnt_type(type) should have the proper diagnostics -ok 244 - hasnt_type(type, desc) should fail -ok 245 - hasnt_type(type, desc) should have the proper description -ok 246 - hasnt_type(type, desc) should have the proper diagnostics -ok 247 - hasnt_type(scheam, type) should fail -ok 248 - hasnt_type(scheam, type) should have the proper description -ok 249 - hasnt_type(scheam, type) should have the proper diagnostics -ok 250 - hasnt_type(schema, type, desc) should fail -ok 251 - hasnt_type(schema, type, desc) should have the proper description -ok 252 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 253 - has_domain(domain) should pass -ok 254 - has_domain(domain) should have the proper description -ok 255 - has_domain(domain) should have the proper diagnostics -ok 256 - has_domain(domain, desc) should pass -ok 257 - has_domain(domain, desc) should have the proper description -ok 258 - has_domain(domain, desc) should have the proper diagnostics -ok 259 - has_domain(scheam, domain) should pass -ok 260 - has_domain(scheam, domain) should have the proper description -ok 261 - has_domain(scheam, domain) should have the proper diagnostics -ok 262 - has_domain(schema, domain, desc) should pass -ok 263 - has_domain(schema, domain, desc) should have the proper description -ok 264 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 265 - has_domain(myDomain) should pass -ok 266 - has_domain(myDomain) should have the proper description -ok 267 - has_domain(myDomain) should have the proper diagnostics -ok 268 - has_domain(myDomain, desc) should pass -ok 269 - has_domain(myDomain, desc) should have the proper description -ok 270 - has_domain(myDomain, desc) should have the proper diagnostics -ok 271 - has_domain(scheam, myDomain) should pass -ok 272 - has_domain(scheam, myDomain) should have the proper description -ok 273 - has_domain(scheam, myDomain) should have the proper diagnostics -ok 274 - has_domain(schema, myDomain, desc) should pass -ok 275 - has_domain(schema, myDomain, desc) should have the proper description -ok 276 - has_domain(schema, myDomain, desc) should have the proper diagnostics -ok 277 - has_domain(domain) should fail -ok 278 - has_domain(domain) should have the proper description -ok 279 - has_domain(domain) should have the proper diagnostics -ok 280 - has_domain(domain, desc) should fail -ok 281 - has_domain(domain, desc) should have the proper description -ok 282 - has_domain(domain, desc) should have the proper diagnostics -ok 283 - has_domain(scheam, domain) should fail -ok 284 - has_domain(scheam, domain) should have the proper description -ok 285 - has_domain(scheam, domain) should have the proper diagnostics -ok 286 - has_domain(schema, domain, desc) should fail -ok 287 - has_domain(schema, domain, desc) should have the proper description -ok 288 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 289 - hasnt_domain(domain) should pass -ok 290 - hasnt_domain(domain) should have the proper description -ok 291 - hasnt_domain(domain) should have the proper diagnostics -ok 292 - hasnt_domain(domain, desc) should pass -ok 293 - hasnt_domain(domain, desc) should have the proper description -ok 294 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 295 - hasnt_domain(scheam, domain) should pass -ok 296 - hasnt_domain(scheam, domain) should have the proper description -ok 297 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 298 - hasnt_domain(schema, domain, desc) should pass -ok 299 - hasnt_domain(schema, domain, desc) should have the proper description -ok 300 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 301 - hasnt_domain(domain) should fail -ok 302 - hasnt_domain(domain) should have the proper description -ok 303 - hasnt_domain(domain) should have the proper diagnostics -ok 304 - hasnt_domain(domain, desc) should fail -ok 305 - hasnt_domain(domain, desc) should have the proper description -ok 306 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 307 - hasnt_domain(scheam, domain) should fail -ok 308 - hasnt_domain(scheam, domain) should have the proper description -ok 309 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 310 - hasnt_domain(schema, domain, desc) should fail -ok 311 - hasnt_domain(schema, domain, desc) should have the proper description -ok 312 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 313 - has_column(non-existent tab, col) should fail -ok 314 - has_column(non-existent tab, col) should have the proper description -ok 315 - has_column(non-existent tab, col) should have the proper diagnostics -ok 316 - has_column(non-existent tab, col, desc) should fail -ok 317 - has_column(non-existent tab, col, desc) should have the proper description -ok 318 - has_column(non-existent tab, col, desc) should have the proper diagnostics -ok 319 - has_column(non-existent sch, tab, col, desc) should fail -ok 320 - has_column(non-existent sch, tab, col, desc) should have the proper description -ok 321 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 322 - has_column(table, column) should pass -ok 323 - has_column(table, column) should have the proper description -ok 324 - has_column(table, column) should have the proper diagnostics -ok 325 - has_column(sch, tab, col, desc) should pass -ok 326 - has_column(sch, tab, col, desc) should have the proper description -ok 327 - has_column(sch, tab, col, desc) should have the proper diagnostics -ok 328 - has_column(table, camleCase column) should pass -ok 329 - has_column(table, camleCase column) should have the proper description -ok 330 - has_column(table, camleCase column) should have the proper diagnostics -ok 331 - has_column(view, column) should pass -ok 332 - has_column(view, column) should have the proper description -ok 333 - has_column(view, column) should have the proper diagnostics -ok 334 - has_column(type, column) should pass -ok 335 - has_column(type, column) should have the proper description -ok 336 - has_column(type, column) should have the proper diagnostics -ok 337 - hasnt_column(non-existent tab, col) should pass -ok 338 - hasnt_column(non-existent tab, col) should have the proper description -ok 339 - hasnt_column(non-existent tab, col) should have the proper diagnostics -ok 340 - hasnt_column(non-existent tab, col, desc) should pass -ok 341 - hasnt_column(non-existent tab, col, desc) should have the proper description -ok 342 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics -ok 343 - hasnt_column(non-existent sch, tab, col, desc) should pass -ok 344 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description -ok 345 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 346 - hasnt_column(table, column) should fail -ok 347 - hasnt_column(table, column) should have the proper description -ok 348 - hasnt_column(table, column) should have the proper diagnostics -ok 349 - hasnt_column(sch, tab, col, desc) should fail -ok 350 - hasnt_column(sch, tab, col, desc) should have the proper description -ok 351 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics -ok 352 - hasnt_column(view, column) should pass -ok 353 - hasnt_column(view, column) should have the proper description -ok 354 - hasnt_column(view, column) should have the proper diagnostics -ok 355 - hasnt_column(type, column) should pass -ok 356 - hasnt_column(type, column) should have the proper description -ok 357 - hasnt_column(type, column) should have the proper diagnostics -ok 358 - has_cast( src, targ, schema, func, desc) should pass -ok 359 - has_cast( src, targ, schema, func, desc) should have the proper description -ok 360 - has_cast( src, targ, schema, func, desc) should have the proper diagnostics -ok 361 - has_cast( src, targ, schema, func ) should pass -ok 362 - has_cast( src, targ, schema, func ) should have the proper description -ok 363 - has_cast( src, targ, schema, func ) should have the proper diagnostics -ok 364 - has_cast( src, targ, func, desc ) should pass -ok 365 - has_cast( src, targ, func, desc ) should have the proper description -ok 366 - has_cast( src, targ, func, desc ) should have the proper diagnostics -ok 367 - has_cast( src, targ, func) should pass -ok 368 - has_cast( src, targ, func) should have the proper description -ok 369 - has_cast( src, targ, func) should have the proper diagnostics -ok 370 - has_cast( src, targ, desc ) should pass -ok 371 - has_cast( src, targ, desc ) should have the proper description -ok 372 - has_cast( src, targ, desc ) should have the proper diagnostics -ok 373 - has_cast( src, targ ) should pass -ok 374 - has_cast( src, targ ) should have the proper description -ok 375 - has_cast( src, targ ) should have the proper diagnostics -ok 376 - has_cast( src, targ, schema, func, desc) fail should fail -ok 377 - has_cast( src, targ, schema, func, desc) fail should have the proper description -ok 378 - has_cast( src, targ, schema, func, desc) fail should have the proper diagnostics -ok 379 - has_cast( src, targ, func, desc ) fail should fail -ok 380 - has_cast( src, targ, func, desc ) fail should have the proper description -ok 381 - has_cast( src, targ, func, desc ) fail should have the proper diagnostics -ok 382 - has_cast( src, targ, desc ) fail should fail -ok 383 - has_cast( src, targ, desc ) fail should have the proper description -ok 384 - has_cast( src, targ, desc ) fail should have the proper diagnostics -ok 385 - hasnt_cast( src, targ, schema, func, desc) should fail -ok 386 - hasnt_cast( src, targ, schema, func, desc) should have the proper description -ok 387 - hasnt_cast( src, targ, schema, func, desc) should have the proper diagnostics -ok 388 - hasnt_cast( src, targ, schema, func ) should fail -ok 389 - hasnt_cast( src, targ, schema, func ) should have the proper description -ok 390 - hasnt_cast( src, targ, schema, func ) should have the proper diagnostics -ok 391 - hasnt_cast( src, targ, func, desc ) should fail -ok 392 - hasnt_cast( src, targ, func, desc ) should have the proper description -ok 393 - hasnt_cast( src, targ, func, desc ) should have the proper diagnostics -ok 394 - hasnt_cast( src, targ, func) should fail -ok 395 - hasnt_cast( src, targ, func) should have the proper description -ok 396 - hasnt_cast( src, targ, func) should have the proper diagnostics -ok 397 - hasnt_cast( src, targ, desc ) should fail -ok 398 - hasnt_cast( src, targ, desc ) should have the proper description -ok 399 - hasnt_cast( src, targ, desc ) should have the proper diagnostics -ok 400 - hasnt_cast( src, targ ) should fail -ok 401 - hasnt_cast( src, targ ) should have the proper description -ok 402 - hasnt_cast( src, targ ) should have the proper diagnostics -ok 403 - hasnt_cast( src, targ, schema, func, desc) fail should pass -ok 404 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper description -ok 405 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper diagnostics -ok 406 - hasnt_cast( src, targ, func, desc ) fail should pass -ok 407 - hasnt_cast( src, targ, func, desc ) fail should have the proper description -ok 408 - hasnt_cast( src, targ, func, desc ) fail should have the proper diagnostics -ok 409 - hasnt_cast( src, targ, desc ) fail should pass -ok 410 - hasnt_cast( src, targ, desc ) fail should have the proper description -ok 411 - hasnt_cast( src, targ, desc ) fail should have the proper diagnostics -ok 412 - cast_context_is( src, targ, context, desc ) should pass -ok 413 - cast_context_is( src, targ, context, desc ) should have the proper description -ok 414 - cast_context_is( src, targ, context, desc ) should have the proper diagnostics -ok 415 - cast_context_is( src, targ, context ) should pass -ok 416 - cast_context_is( src, targ, context ) should have the proper description -ok 417 - cast_context_is( src, targ, context ) should have the proper diagnostics -ok 418 - cast_context_is( src, targ, i, desc ) should pass -ok 419 - cast_context_is( src, targ, i, desc ) should have the proper description -ok 420 - cast_context_is( src, targ, i, desc ) should have the proper diagnostics -ok 421 - cast_context_is( src, targ, IMPL, desc ) should pass -ok 422 - cast_context_is( src, targ, IMPL, desc ) should have the proper description -ok 423 - cast_context_is( src, targ, IMPL, desc ) should have the proper diagnostics -ok 424 - cast_context_is( src, targ, assignment, desc ) should pass -ok 425 - cast_context_is( src, targ, assignment, desc ) should have the proper description -ok 426 - cast_context_is( src, targ, assignment, desc ) should have the proper diagnostics -ok 427 - cast_context_is( src, targ, a, desc ) should pass -ok 428 - cast_context_is( src, targ, a, desc ) should have the proper description -ok 429 - cast_context_is( src, targ, a, desc ) should have the proper diagnostics -ok 430 - cast_context_is( src, targ, ASS, desc ) should pass -ok 431 - cast_context_is( src, targ, ASS, desc ) should have the proper description -ok 432 - cast_context_is( src, targ, ASS, desc ) should have the proper diagnostics -ok 433 - cast_context_is( src, targ, explicit, desc ) should pass -ok 434 - cast_context_is( src, targ, explicit, desc ) should have the proper description -ok 435 - cast_context_is( src, targ, explicit, desc ) should have the proper diagnostics -ok 436 - cast_context_is( src, targ, e, desc ) should pass -ok 437 - cast_context_is( src, targ, e, desc ) should have the proper description -ok 438 - cast_context_is( src, targ, e, desc ) should have the proper diagnostics -ok 439 - cast_context_is( src, targ, EX, desc ) should pass -ok 440 - cast_context_is( src, targ, EX, desc ) should have the proper description -ok 441 - cast_context_is( src, targ, EX, desc ) should have the proper diagnostics -ok 442 - cast_context_is( src, targ, context, desc ) fail should fail -ok 443 - cast_context_is( src, targ, context, desc ) fail should have the proper description -ok 444 - cast_context_is( src, targ, context, desc ) fail should have the proper diagnostics -ok 445 - cast_context_is( src, targ, context ) fail should fail -ok 446 - cast_context_is( src, targ, context ) fail should have the proper description -ok 447 - cast_context_is( src, targ, context ) fail should have the proper diagnostics -ok 448 - cast_context_is( src, targ, context, desc ) noexist should fail -ok 449 - cast_context_is( src, targ, context, desc ) noexist should have the proper description -ok 450 - cast_context_is( src, targ, context, desc ) noexist should have the proper diagnostics -ok 451 - has_operator( left, schema, name, right, result, desc ) should pass -ok 452 - has_operator( left, schema, name, right, result, desc ) should have the proper description -ok 453 - has_operator( left, schema, name, right, result, desc ) should have the proper diagnostics -ok 454 - has_operator( left, schema, name, right, result ) should pass -ok 455 - has_operator( left, schema, name, right, result ) should have the proper description -ok 456 - has_operator( left, schema, name, right, result ) should have the proper diagnostics -ok 457 - has_operator( left, name, right, result, desc ) should pass -ok 458 - has_operator( left, name, right, result, desc ) should have the proper description -ok 459 - has_operator( left, name, right, result, desc ) should have the proper diagnostics -ok 460 - has_operator( left, name, right, result ) should pass -ok 461 - has_operator( left, name, right, result ) should have the proper description -ok 462 - has_operator( left, name, right, result ) should have the proper diagnostics -ok 463 - has_operator( left, name, right, desc ) should pass -ok 464 - has_operator( left, name, right, desc ) should have the proper description -ok 465 - has_operator( left, name, right, desc ) should have the proper diagnostics -ok 466 - has_operator( left, name, right ) should pass -ok 467 - has_operator( left, name, right ) should have the proper description -ok 468 - has_operator( left, name, right ) should have the proper diagnostics -ok 469 - has_operator( left, schema, name, right, result, desc ) fail should fail -ok 470 - has_operator( left, schema, name, right, result, desc ) fail should have the proper description -ok 471 - has_operator( left, schema, name, right, result, desc ) fail should have the proper diagnostics -ok 472 - has_operator( left, schema, name, right, result ) fail should fail -ok 473 - has_operator( left, schema, name, right, result ) fail should have the proper description -ok 474 - has_operator( left, schema, name, right, result ) fail should have the proper diagnostics -ok 475 - has_operator( left, name, right, result, desc ) fail should fail -ok 476 - has_operator( left, name, right, result, desc ) fail should have the proper description -ok 477 - has_operator( left, name, right, result, desc ) fail should have the proper diagnostics -ok 478 - has_operator( left, name, right, result ) fail should fail -ok 479 - has_operator( left, name, right, result ) fail should have the proper description -ok 480 - has_operator( left, name, right, result ) fail should have the proper diagnostics -ok 481 - has_operator( left, name, right, desc ) fail should fail -ok 482 - has_operator( left, name, right, desc ) fail should have the proper description -ok 483 - has_operator( left, name, right, desc ) fail should have the proper diagnostics -ok 484 - has_operator( left, name, right ) fail should fail -ok 485 - has_operator( left, name, right ) fail should have the proper description -ok 486 - has_operator( left, name, right ) fail should have the proper diagnostics -ok 487 - has_leftop( schema, name, right, result, desc ) should pass -ok 488 - has_leftop( schema, name, right, result, desc ) should have the proper description -ok 489 - has_leftop( schema, name, right, result, desc ) should have the proper diagnostics -ok 490 - has_leftop( schema, name, right, result ) should pass -ok 491 - has_leftop( schema, name, right, result ) should have the proper description -ok 492 - has_leftop( schema, name, right, result ) should have the proper diagnostics -ok 493 - has_leftop( name, right, result, desc ) should pass -ok 494 - has_leftop( name, right, result, desc ) should have the proper description -ok 495 - has_leftop( name, right, result, desc ) should have the proper diagnostics -ok 496 - has_leftop( name, right, result ) should pass -ok 497 - has_leftop( name, right, result ) should have the proper description -ok 498 - has_leftop( name, right, result ) should have the proper diagnostics -ok 499 - has_leftop( name, right, desc ) should pass -ok 500 - has_leftop( name, right, desc ) should have the proper description -ok 501 - has_leftop( name, right, desc ) should have the proper diagnostics -ok 502 - has_leftop( name, right ) should pass -ok 503 - has_leftop( name, right ) should have the proper description -ok 504 - has_leftop( name, right ) should have the proper diagnostics -ok 505 - has_leftop( schema, name, right, result, desc ) fail should fail -ok 506 - has_leftop( schema, name, right, result, desc ) fail should have the proper description -ok 507 - has_leftop( schema, name, right, result, desc ) fail should have the proper diagnostics -ok 508 - has_leftop( schema, name, right, result ) fail should fail -ok 509 - has_leftop( schema, name, right, result ) fail should have the proper description -ok 510 - has_leftop( schema, name, right, result ) fail should have the proper diagnostics -ok 511 - has_leftop( name, right, result, desc ) fail should fail -ok 512 - has_leftop( name, right, result, desc ) fail should have the proper description -ok 513 - has_leftop( name, right, result, desc ) fail should have the proper diagnostics -ok 514 - has_leftop( name, right, result ) fail should fail -ok 515 - has_leftop( name, right, result ) fail should have the proper description -ok 516 - has_leftop( name, right, result ) fail should have the proper diagnostics -ok 517 - has_leftop( name, right, desc ) fail should fail -ok 518 - has_leftop( name, right, desc ) fail should have the proper description -ok 519 - has_leftop( name, right, desc ) fail should have the proper diagnostics -ok 520 - has_leftop( name, right ) fail should fail -ok 521 - has_leftop( name, right ) fail should have the proper description -ok 522 - has_leftop( name, right ) fail should have the proper diagnostics -ok 523 - has_rightop( left, schema, name, result, desc ) should pass -ok 524 - has_rightop( left, schema, name, result, desc ) should have the proper description -ok 525 - has_rightop( left, schema, name, result, desc ) should have the proper diagnostics -ok 526 - has_rightop( left, schema, name, result ) should pass -ok 527 - has_rightop( left, schema, name, result ) should have the proper description -ok 528 - has_rightop( left, schema, name, result ) should have the proper diagnostics -ok 529 - has_rightop( left, name, result, desc ) should pass -ok 530 - has_rightop( left, name, result, desc ) should have the proper description -ok 531 - has_rightop( left, name, result, desc ) should have the proper diagnostics -ok 532 - has_rightop( left, name, result ) should pass -ok 533 - has_rightop( left, name, result ) should have the proper description -ok 534 - has_rightop( left, name, result ) should have the proper diagnostics -ok 535 - has_rightop( left, name, desc ) should pass -ok 536 - has_rightop( left, name, desc ) should have the proper description -ok 537 - has_rightop( left, name, desc ) should have the proper diagnostics -ok 538 - has_rightop( left, name ) should pass -ok 539 - has_rightop( left, name ) should have the proper description -ok 540 - has_rightop( left, name ) should have the proper diagnostics -ok 541 - has_rightop( left, schema, name, result, desc ) fail should fail -ok 542 - has_rightop( left, schema, name, result, desc ) fail should have the proper description -ok 543 - has_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics -ok 544 - has_rightop( left, schema, name, result ) fail should fail -ok 545 - has_rightop( left, schema, name, result ) fail should have the proper description -ok 546 - has_rightop( left, schema, name, result ) fail should have the proper diagnostics -ok 547 - has_rightop( left, name, result, desc ) fail should fail -ok 548 - has_rightop( left, name, result, desc ) fail should have the proper description -ok 549 - has_rightop( left, name, result, desc ) fail should have the proper diagnostics -ok 550 - has_rightop( left, name, result ) fail should fail -ok 551 - has_rightop( left, name, result ) fail should have the proper description -ok 552 - has_rightop( left, name, result ) fail should have the proper diagnostics -ok 553 - has_rightop( left, name, desc ) fail should fail -ok 554 - has_rightop( left, name, desc ) fail should have the proper description -ok 555 - has_rightop( left, name, desc ) fail should have the proper diagnostics -ok 556 - has_rightop( left, name ) fail should fail -ok 557 - has_rightop( left, name ) fail should have the proper description -ok 558 - has_rightop( left, name ) fail should have the proper diagnostics -ok 559 - has_language(language) should pass -ok 560 - has_language(language) should have the proper description -ok 561 - has_language(language) should have the proper diagnostics -ok 562 - has_language(language, desc) should pass -ok 563 - has_language(language, desc) should have the proper description -ok 564 - has_language(language, desc) should have the proper diagnostics -ok 565 - has_language(nonexistent language) should fail -ok 566 - has_language(nonexistent language) should have the proper description -ok 567 - has_language(nonexistent language) should have the proper diagnostics -ok 568 - has_language(nonexistent language, desc) should fail -ok 569 - has_language(nonexistent language, desc) should have the proper description -ok 570 - has_language(nonexistent language, desc) should have the proper diagnostics -ok 571 - hasnt_language(language) should fail -ok 572 - hasnt_language(language) should have the proper description -ok 573 - hasnt_language(language) should have the proper diagnostics -ok 574 - hasnt_language(language, desc) should fail -ok 575 - hasnt_language(language, desc) should have the proper description -ok 576 - hasnt_language(language, desc) should have the proper diagnostics -ok 577 - hasnt_language(nonexistent language) should pass -ok 578 - hasnt_language(nonexistent language) should have the proper description -ok 579 - hasnt_language(nonexistent language) should have the proper diagnostics -ok 580 - hasnt_language(nonexistent language, desc) should pass -ok 581 - hasnt_language(nonexistent language, desc) should have the proper description -ok 582 - hasnt_language(nonexistent language, desc) should have the proper diagnostics -ok 583 - language_is_trusted(language, desc) should pass -ok 584 - language_is_trusted(language, desc) should have the proper description -ok 585 - language_is_trusted(language, desc) should have the proper diagnostics -ok 586 - language_is_trusted(language) should pass -ok 587 - language_is_trusted(language) should have the proper description -ok 588 - language_is_trusted(language) should have the proper diagnostics -ok 589 - language_is_trusted(language, desc) fail should fail -ok 590 - language_is_trusted(language, desc) fail should have the proper description -ok 591 - language_is_trusted(language, desc) fail should have the proper diagnostics -ok 592 - language_is_trusted(language, desc) non-existent should fail -ok 593 - language_is_trusted(language, desc) non-existent should have the proper description -ok 594 - language_is_trusted(language, desc) non-existent should have the proper diagnostics -ok 595 - has_opclass( schema, name, desc ) should pass -ok 596 - has_opclass( schema, name, desc ) should have the proper description -ok 597 - has_opclass( schema, name, desc ) should have the proper diagnostics -ok 598 - has_opclass( schema, name ) should pass -ok 599 - has_opclass( schema, name ) should have the proper description -ok 600 - has_opclass( schema, name ) should have the proper diagnostics -ok 601 - has_opclass( name, desc ) should pass -ok 602 - has_opclass( name, desc ) should have the proper description -ok 603 - has_opclass( name, desc ) should have the proper diagnostics -ok 604 - has_opclass( name ) should pass -ok 605 - has_opclass( name ) should have the proper description -ok 606 - has_opclass( name ) should have the proper diagnostics -ok 607 - has_opclass( schema, name, desc ) fail should fail -ok 608 - has_opclass( schema, name, desc ) fail should have the proper description -ok 609 - has_opclass( schema, name, desc ) fail should have the proper diagnostics -ok 610 - has_opclass( name, desc ) fail should fail -ok 611 - has_opclass( name, desc ) fail should have the proper description -ok 612 - has_opclass( name, desc ) fail should have the proper diagnostics -ok 613 - hasnt_opclass( schema, name, desc ) should fail -ok 614 - hasnt_opclass( schema, name, desc ) should have the proper description -ok 615 - hasnt_opclass( schema, name, desc ) should have the proper diagnostics -ok 616 - hasnt_opclass( schema, name ) should fail -ok 617 - hasnt_opclass( schema, name ) should have the proper description -ok 618 - hasnt_opclass( schema, name ) should have the proper diagnostics -ok 619 - hasnt_opclass( name, desc ) should fail -ok 620 - hasnt_opclass( name, desc ) should have the proper description -ok 621 - hasnt_opclass( name, desc ) should have the proper diagnostics -ok 622 - hasnt_opclass( name ) should fail -ok 623 - hasnt_opclass( name ) should have the proper description -ok 624 - hasnt_opclass( name ) should have the proper diagnostics -ok 625 - hasnt_opclass( schema, name, desc ) fail should pass -ok 626 - hasnt_opclass( schema, name, desc ) fail should have the proper description -ok 627 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnostics -ok 628 - hasnt_opclass( name, desc ) fail should pass -ok 629 - hasnt_opclass( name, desc ) fail should have the proper description -ok 630 - hasnt_opclass( name, desc ) fail should have the proper diagnostics -ok 631 - domain_type_is(schema, domain, schema, type, desc) should pass -ok 632 - domain_type_is(schema, domain, schema, type, desc) should have the proper description -ok 633 - domain_type_is(schema, domain, schema, type, desc) should have the proper diagnostics -ok 634 - domain_type_is(schema, domain, schema, type) should pass -ok 635 - domain_type_is(schema, domain, schema, type) should have the proper description -ok 636 - domain_type_is(schema, domain, schema, type) should have the proper diagnostics -ok 637 - domain_type_is(schema, domain, schema, type, desc) fail should fail -ok 638 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper description -ok 639 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper diagnostics -ok 640 - domain_type_is(schema, nondomain, schema, type, desc) should fail -ok 641 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper description -ok 642 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper diagnostics -ok 643 - domain_type_is(schema, type, schema, type, desc) fail should fail -ok 644 - domain_type_is(schema, type, schema, type, desc) fail should have the proper description -ok 645 - domain_type_is(schema, type, schema, type, desc) fail should have the proper diagnostics -ok 646 - domain_type_is(schema, domain, type, desc) should pass -ok 647 - domain_type_is(schema, domain, type, desc) should have the proper description -ok 648 - domain_type_is(schema, domain, type, desc) should have the proper diagnostics -ok 649 - domain_type_is(schema, domain, type) should pass -ok 650 - domain_type_is(schema, domain, type) should have the proper description -ok 651 - domain_type_is(schema, domain, type) should have the proper diagnostics -ok 652 - domain_type_is(schema, domain, type, desc) fail should fail -ok 653 - domain_type_is(schema, domain, type, desc) fail should have the proper description -ok 654 - domain_type_is(schema, domain, type, desc) fail should have the proper diagnostics -ok 655 - domain_type_is(schema, nondomain, type, desc) should fail -ok 656 - domain_type_is(schema, nondomain, type, desc) should have the proper description -ok 657 - domain_type_is(schema, nondomain, type, desc) should have the proper diagnostics -ok 658 - domain_type_is(schema, type, type, desc) fail should fail -ok 659 - domain_type_is(schema, type, type, desc) fail should have the proper description -ok 660 - domain_type_is(schema, type, type, desc) fail should have the proper diagnostics -ok 661 - domain_type_is(domain, type, desc) should pass -ok 662 - domain_type_is(domain, type, desc) should have the proper description -ok 663 - domain_type_is(domain, type, desc) should have the proper diagnostics -ok 664 - domain_type_is(domain, type) should pass -ok 665 - domain_type_is(domain, type) should have the proper description -ok 666 - domain_type_is(domain, type) should have the proper diagnostics -ok 667 - domain_type_is(domain, type, desc) fail should fail -ok 668 - domain_type_is(domain, type, desc) fail should have the proper description -ok 669 - domain_type_is(domain, type, desc) fail should have the proper diagnostics -ok 670 - domain_type_is(nondomain, type, desc) should fail -ok 671 - domain_type_is(nondomain, type, desc) should have the proper description -ok 672 - domain_type_is(nondomain, type, desc) should have the proper diagnostics -ok 673 - domain_type_is(type, type, desc) fail should fail -ok 674 - domain_type_is(type, type, desc) fail should have the proper description -ok 675 - domain_type_is(type, type, desc) fail should have the proper diagnostics -ok 676 - domain_type_isnt(schema, domain, schema, type, desc) should pass -ok 677 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper description -ok 678 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper diagnostics -ok 679 - domain_type_isnt(schema, domain, schema, type) should pass -ok 680 - domain_type_isnt(schema, domain, schema, type) should have the proper description -ok 681 - domain_type_isnt(schema, domain, schema, type) should have the proper diagnostics -ok 682 - domain_type_isnt(schema, domain, schema, type, desc) fail should fail -ok 683 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper description -ok 684 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper diagnostics -ok 685 - domain_type_isnt(schema, nondomain, schema, type, desc) should fail -ok 686 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper description -ok 687 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper diagnostics -ok 688 - domain_type_isnt(schema, type, schema, type, desc) should fail -ok 689 - domain_type_isnt(schema, type, schema, type, desc) should have the proper description -ok 690 - domain_type_isnt(schema, type, schema, type, desc) should have the proper diagnostics -ok 691 - domain_type_isnt(schema, domain, type, desc) should pass -ok 692 - domain_type_isnt(schema, domain, type, desc) should have the proper description -ok 693 - domain_type_isnt(schema, domain, type, desc) should have the proper diagnostics -ok 694 - domain_type_isnt(schema, domain, type) should pass -ok 695 - domain_type_isnt(schema, domain, type) should have the proper description -ok 696 - domain_type_isnt(schema, domain, type) should have the proper diagnostics -ok 697 - domain_type_isnt(schema, domain, type, desc) fail should fail -ok 698 - domain_type_isnt(schema, domain, type, desc) fail should have the proper description -ok 699 - domain_type_isnt(schema, domain, type, desc) fail should have the proper diagnostics -ok 700 - domain_type_isnt(schema, nondomain, type, desc) should fail -ok 701 - domain_type_isnt(schema, nondomain, type, desc) should have the proper description -ok 702 - domain_type_isnt(schema, nondomain, type, desc) should have the proper diagnostics -ok 703 - domain_type_isnt(schema, type, type, desc) should fail -ok 704 - domain_type_isnt(schema, type, type, desc) should have the proper description -ok 705 - domain_type_isnt(schema, type, type, desc) should have the proper diagnostics -ok 706 - domain_type_isnt(domain, type, desc) should pass -ok 707 - domain_type_isnt(domain, type, desc) should have the proper description -ok 708 - domain_type_isnt(domain, type, desc) should have the proper diagnostics -ok 709 - domain_type_isnt(domain, type) should pass -ok 710 - domain_type_isnt(domain, type) should have the proper description -ok 711 - domain_type_isnt(domain, type) should have the proper diagnostics -ok 712 - domain_type_isnt(domain, type, desc) fail should fail -ok 713 - domain_type_isnt(domain, type, desc) fail should have the proper description -ok 714 - domain_type_isnt(domain, type, desc) fail should have the proper diagnostics -ok 715 - domain_type_isnt(nondomain, type, desc) should fail -ok 716 - domain_type_isnt(nondomain, type, desc) should have the proper description -ok 717 - domain_type_isnt(nondomain, type, desc) should have the proper diagnostics -ok 718 - domain_type_isnt(type, type, desc) should fail -ok 719 - domain_type_isnt(type, type, desc) should have the proper description -ok 720 - domain_type_isnt(type, type, desc) should have the proper diagnostics -ok 721 - has_relation(non-existent relation) should fail -ok 722 - has_relation(non-existent relation) should have the proper description -ok 723 - has_relation(non-existent relation) should have the proper diagnostics -ok 724 - has_relation(non-existent schema, tab) should fail -ok 725 - has_relation(non-existent schema, tab) should have the proper description -ok 726 - has_relation(non-existent schema, tab) should have the proper diagnostics -ok 727 - has_relation(sch, non-existent relation, desc) should fail -ok 728 - has_relation(sch, non-existent relation, desc) should have the proper description -ok 729 - has_relation(sch, non-existent relation, desc) should have the proper diagnostics -ok 730 - has_relation(tab, desc) should pass -ok 731 - has_relation(tab, desc) should have the proper description -ok 732 - has_relation(tab, desc) should have the proper diagnostics -ok 733 - has_relation(sch, tab, desc) should pass -ok 734 - has_relation(sch, tab, desc) should have the proper description -ok 735 - has_relation(sch, tab, desc) should have the proper diagnostics -ok 736 - has_relation(sch, view, desc) should pass -ok 737 - has_relation(sch, view, desc) should have the proper description -ok 738 - has_relation(sch, view, desc) should have the proper diagnostics -ok 739 - has_relation(type, desc) should pass -ok 740 - has_relation(type, desc) should have the proper description -ok 741 - has_relation(type, desc) should have the proper diagnostics -ok 742 - hasnt_relation(non-existent relation) should pass -ok 743 - hasnt_relation(non-existent relation) should have the proper description -ok 744 - hasnt_relation(non-existent relation) should have the proper diagnostics -ok 745 - hasnt_relation(non-existent schema, tab) should pass -ok 746 - hasnt_relation(non-existent schema, tab) should have the proper description -ok 747 - hasnt_relation(non-existent schema, tab) should have the proper diagnostics -ok 748 - hasnt_relation(sch, non-existent tab, desc) should pass -ok 749 - hasnt_relation(sch, non-existent tab, desc) should have the proper description -ok 750 - hasnt_relation(sch, non-existent tab, desc) should have the proper diagnostics -ok 751 - hasnt_relation(tab, desc) should fail -ok 752 - hasnt_relation(tab, desc) should have the proper description -ok 753 - hasnt_relation(tab, desc) should have the proper diagnostics -ok 754 - hasnt_relation(sch, tab, desc) should fail -ok 755 - hasnt_relation(sch, tab, desc) should have the proper description -ok 756 - hasnt_relation(sch, tab, desc) should have the proper diagnostics -ok 757 - has_foreign_table(non-existent table) should fail -ok 758 - has_foreign_table(non-existent table) should have the proper description -ok 759 - has_foreign_table(non-existent table) should have the proper diagnostics -ok 760 - has_foreign_table(non-existent schema, tab) should fail -ok 761 - has_foreign_table(non-existent schema, tab) should have the proper description -ok 762 - has_foreign_table(non-existent schema, tab) should have the proper diagnostics -ok 763 - has_foreign_table(non-existent table, desc) should fail -ok 764 - has_foreign_table(non-existent table, desc) should have the proper description -ok 765 - has_foreign_table(non-existent table, desc) should have the proper diagnostics -ok 766 - has_foreign_table(sch, non-existent table, desc) should fail -ok 767 - has_foreign_table(sch, non-existent table, desc) should have the proper description -ok 768 - has_foreign_table(sch, non-existent table, desc) should have the proper diagnostics -ok 769 - has_foreign_table(tab, desc) should pass -ok 770 - has_foreign_table(tab, desc) should have the proper description -ok 771 - has_foreign_table(tab, desc) should have the proper diagnostics -ok 772 - has_foreign_table(sch, tab, desc) should pass -ok 773 - has_foreign_table(sch, tab, desc) should have the proper description -ok 774 - has_foreign_table(sch, tab, desc) should have the proper diagnostics -ok 775 - has_foreign_table(sch, view, desc) should fail -ok 776 - has_foreign_table(sch, view, desc) should have the proper description -ok 777 - has_foreign_table(sch, view, desc) should have the proper diagnostics -ok 778 - has_foreign_table(type, desc) should fail -ok 779 - has_foreign_table(type, desc) should have the proper description -ok 780 - has_foreign_table(type, desc) should have the proper diagnostics -ok 781 - hasnt_foreign_table(non-existent table) should pass -ok 782 - hasnt_foreign_table(non-existent table) should have the proper description -ok 783 - hasnt_foreign_table(non-existent table) should have the proper diagnostics -ok 784 - hasnt_foreign_table(non-existent schema, tab) should pass -ok 785 - hasnt_foreign_table(non-existent schema, tab) should have the proper description -ok 786 - hasnt_foreign_table(non-existent schema, tab) should have the proper diagnostics -ok 787 - hasnt_foreign_table(non-existent table, desc) should pass -ok 788 - hasnt_foreign_table(non-existent table, desc) should have the proper description -ok 789 - hasnt_foreign_table(non-existent table, desc) should have the proper diagnostics -ok 790 - hasnt_foreign_table(sch, non-existent tab, desc) should pass -ok 791 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper description -ok 792 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper diagnostics -ok 793 - hasnt_foreign_table(tab, desc) should fail -ok 794 - hasnt_foreign_table(tab, desc) should have the proper description -ok 795 - hasnt_foreign_table(tab, desc) should have the proper diagnostics -ok 796 - hasnt_foreign_table(sch, tab, desc) should fail -ok 797 - hasnt_foreign_table(sch, tab, desc) should have the proper description -ok 798 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics -ok 799 - has_materialized_view(non-existent materialized_view) should fail -ok 800 - has_materialized_view(non-existent materialized_view) should have the proper description -ok 801 - has_materialized_view(non-existent materialized_view) should have the proper diagnostics -ok 802 - has_materialized_view(non-existent materialized_view, desc) should fail -ok 803 - has_materialized_view(non-existent materialized_view, desc) should have the proper description -ok 804 - has_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics -ok 805 - has_materialized_view(sch, non-existtent materialized_view, desc) should fail -ok 806 - has_materialized_view(sch, non-existtent materialized_view, desc) should have the proper description -ok 807 - has_materialized_view(sch, non-existtent materialized_view, desc) should have the proper diagnostics -ok 808 - has_materialized_view(materialized_view, desc) should pass -ok 809 - has_materialized_view(materialized_view, desc) should have the proper description -ok 810 - has_materialized_view(materialized_view, desc) should have the proper diagnostics -ok 811 - has_materialized_view(sch, materialized_view, desc) should pass -ok 812 - has_materialized_view(sch, materialized_view, desc) should have the proper description -ok 813 - has_materialized_view(sch, materialized_view, desc) should have the proper diagnostics -ok 814 - hasnt_materialized_view(non-existent materialized_view) should pass -ok 815 - hasnt_materialized_view(non-existent materialized_view) should have the proper description -ok 816 - hasnt_materialized_view(non-existent materialized_view) should have the proper diagnostics -ok 817 - hasnt_materialized_view(non-existent materialized_view, desc) should pass -ok 818 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper description -ok 819 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics -ok 820 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should pass -ok 821 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should have the proper description -ok 822 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should have the proper diagnostics -ok 823 - hasnt_materialized_view(materialized_view, desc) should fail -ok 824 - hasnt_materialized_view(materialized_view, desc) should have the proper description -ok 825 - hasnt_materialized_view(materialized_view, desc) should have the proper diagnostics -ok 826 - hasnt_materialized_view(sch, materialized_view, desc) should fail -ok 827 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper description -ok 828 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper diagnostics +ok 136 - has_sequence(sch, sequence) should pass +ok 137 - has_sequence(sch, sequence) should have the proper description +ok 138 - hasnt_sequence(non-existent sequence) should pass +ok 139 - hasnt_sequence(non-existent sequence) should have the proper description +ok 140 - hasnt_sequence(non-existent sequence) should have the proper diagnostics +ok 141 - hasnt_sequence(non-existent sequence, desc) should pass +ok 142 - hasnt_sequence(non-existent sequence, desc) should have the proper description +ok 143 - hasnt_sequence(non-existent sequence, desc) should have the proper diagnostics +ok 144 - hasnt_sequence(sch, non-existtent sequence, desc) should pass +ok 145 - hasnt_sequence(sch, non-existtent sequence, desc) should have the proper description +ok 146 - hasnt_sequence(sch, non-existtent sequence, desc) should have the proper diagnostics +ok 147 - hasnt_sequence(sequence, desc) should fail +ok 148 - hasnt_sequence(sequence, desc) should have the proper description +ok 149 - hasnt_sequence(sequence, desc) should have the proper diagnostics +ok 150 - hasnt_sequence(sch, sequence, desc) should fail +ok 151 - hasnt_sequence(sch, sequence, desc) should have the proper description +ok 152 - hasnt_sequence(sch, sequence, desc) should have the proper diagnostics +ok 153 - has_composite(non-existent composite type) should fail +ok 154 - has_composite(non-existent composite type) should have the proper description +ok 155 - has_composite(non-existent composite type) should have the proper diagnostics +ok 156 - has_composite(non-existent schema, tab) should fail +ok 157 - has_composite(non-existent schema, tab) should have the proper description +ok 158 - has_composite(non-existent schema, tab) should have the proper diagnostics +ok 159 - has_composite(sch, non-existent composite type, desc) should fail +ok 160 - has_composite(sch, non-existent composite type, desc) should have the proper description +ok 161 - has_composite(sch, non-existent composite type, desc) should have the proper diagnostics +ok 162 - has_composite(tab, desc) should pass +ok 163 - has_composite(tab, desc) should have the proper description +ok 164 - has_composite(tab, desc) should have the proper diagnostics +ok 165 - has_composite(sch, tab, desc) should pass +ok 166 - has_composite(sch, tab, desc) should have the proper description +ok 167 - has_composite(sch, tab, desc) should have the proper diagnostics +ok 168 - has_composite(sch, view, desc) should fail +ok 169 - has_composite(sch, view, desc) should have the proper description +ok 170 - has_composite(sch, view, desc) should have the proper diagnostics +ok 171 - has_composite(type, desc) should fail +ok 172 - has_composite(type, desc) should have the proper description +ok 173 - has_composite(type, desc) should have the proper diagnostics +ok 174 - hasnt_composite(non-existent composite type) should pass +ok 175 - hasnt_composite(non-existent composite type) should have the proper description +ok 176 - hasnt_composite(non-existent composite type) should have the proper diagnostics +ok 177 - hasnt_composite(non-existent schema, tab) should pass +ok 178 - hasnt_composite(non-existent schema, tab) should have the proper description +ok 179 - hasnt_composite(non-existent schema, tab) should have the proper diagnostics +ok 180 - hasnt_composite(sch, non-existent tab, desc) should pass +ok 181 - hasnt_composite(sch, non-existent tab, desc) should have the proper description +ok 182 - hasnt_composite(sch, non-existent tab, desc) should have the proper diagnostics +ok 183 - hasnt_composite(tab, desc) should fail +ok 184 - hasnt_composite(tab, desc) should have the proper description +ok 185 - hasnt_composite(tab, desc) should have the proper diagnostics +ok 186 - hasnt_composite(sch, tab, desc) should fail +ok 187 - hasnt_composite(sch, tab, desc) should have the proper description +ok 188 - hasnt_composite(sch, tab, desc) should have the proper diagnostics +ok 189 - has_type(type) should pass +ok 190 - has_type(type) should have the proper description +ok 191 - has_type(type) should have the proper diagnostics +ok 192 - has_type(type, desc) should pass +ok 193 - has_type(type, desc) should have the proper description +ok 194 - has_type(type, desc) should have the proper diagnostics +ok 195 - has_type(scheam, type) should pass +ok 196 - has_type(scheam, type) should have the proper description +ok 197 - has_type(scheam, type) should have the proper diagnostics +ok 198 - has_type(schema, type, desc) should pass +ok 199 - has_type(schema, type, desc) should have the proper description +ok 200 - has_type(schema, type, desc) should have the proper diagnostics +ok 201 - has_type(myType) should pass +ok 202 - has_type(myType) should have the proper description +ok 203 - has_type(myType) should have the proper diagnostics +ok 204 - has_type(myType, desc) should pass +ok 205 - has_type(myType, desc) should have the proper description +ok 206 - has_type(myType, desc) should have the proper diagnostics +ok 207 - has_type(scheam, myType) should pass +ok 208 - has_type(scheam, myType) should have the proper description +ok 209 - has_type(scheam, myType) should have the proper diagnostics +ok 210 - has_type(schema, myType, desc) should pass +ok 211 - has_type(schema, myType, desc) should have the proper description +ok 212 - has_type(schema, myType, desc) should have the proper diagnostics +ok 213 - has_type(type) should fail +ok 214 - has_type(type) should have the proper description +ok 215 - has_type(type) should have the proper diagnostics +ok 216 - has_type(type, desc) should fail +ok 217 - has_type(type, desc) should have the proper description +ok 218 - has_type(type, desc) should have the proper diagnostics +ok 219 - has_type(scheam, type) should fail +ok 220 - has_type(scheam, type) should have the proper description +ok 221 - has_type(scheam, type) should have the proper diagnostics +ok 222 - has_type(schema, type, desc) should fail +ok 223 - has_type(schema, type, desc) should have the proper description +ok 224 - has_type(schema, type, desc) should have the proper diagnostics +ok 225 - has_type(domain) should pass +ok 226 - has_type(domain) should have the proper description +ok 227 - has_type(domain) should have the proper diagnostics +ok 228 - has_type(myDomain) should pass +ok 229 - has_type(myDomain) should have the proper description +ok 230 - has_type(myDomain) should have the proper diagnostics +ok 231 - hasnt_type(type) should pass +ok 232 - hasnt_type(type) should have the proper description +ok 233 - hasnt_type(type) should have the proper diagnostics +ok 234 - hasnt_type(type, desc) should pass +ok 235 - hasnt_type(type, desc) should have the proper description +ok 236 - hasnt_type(type, desc) should have the proper diagnostics +ok 237 - hasnt_type(scheam, type) should pass +ok 238 - hasnt_type(scheam, type) should have the proper description +ok 239 - hasnt_type(scheam, type) should have the proper diagnostics +ok 240 - hasnt_type(schema, type, desc) should pass +ok 241 - hasnt_type(schema, type, desc) should have the proper description +ok 242 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 243 - hasnt_type(type) should fail +ok 244 - hasnt_type(type) should have the proper description +ok 245 - hasnt_type(type) should have the proper diagnostics +ok 246 - hasnt_type(type, desc) should fail +ok 247 - hasnt_type(type, desc) should have the proper description +ok 248 - hasnt_type(type, desc) should have the proper diagnostics +ok 249 - hasnt_type(scheam, type) should fail +ok 250 - hasnt_type(scheam, type) should have the proper description +ok 251 - hasnt_type(scheam, type) should have the proper diagnostics +ok 252 - hasnt_type(schema, type, desc) should fail +ok 253 - hasnt_type(schema, type, desc) should have the proper description +ok 254 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 255 - has_domain(domain) should pass +ok 256 - has_domain(domain) should have the proper description +ok 257 - has_domain(domain) should have the proper diagnostics +ok 258 - has_domain(domain, desc) should pass +ok 259 - has_domain(domain, desc) should have the proper description +ok 260 - has_domain(domain, desc) should have the proper diagnostics +ok 261 - has_domain(scheam, domain) should pass +ok 262 - has_domain(scheam, domain) should have the proper description +ok 263 - has_domain(scheam, domain) should have the proper diagnostics +ok 264 - has_domain(schema, domain, desc) should pass +ok 265 - has_domain(schema, domain, desc) should have the proper description +ok 266 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 267 - has_domain(myDomain) should pass +ok 268 - has_domain(myDomain) should have the proper description +ok 269 - has_domain(myDomain) should have the proper diagnostics +ok 270 - has_domain(myDomain, desc) should pass +ok 271 - has_domain(myDomain, desc) should have the proper description +ok 272 - has_domain(myDomain, desc) should have the proper diagnostics +ok 273 - has_domain(scheam, myDomain) should pass +ok 274 - has_domain(scheam, myDomain) should have the proper description +ok 275 - has_domain(scheam, myDomain) should have the proper diagnostics +ok 276 - has_domain(schema, myDomain, desc) should pass +ok 277 - has_domain(schema, myDomain, desc) should have the proper description +ok 278 - has_domain(schema, myDomain, desc) should have the proper diagnostics +ok 279 - has_domain(domain) should fail +ok 280 - has_domain(domain) should have the proper description +ok 281 - has_domain(domain) should have the proper diagnostics +ok 282 - has_domain(domain, desc) should fail +ok 283 - has_domain(domain, desc) should have the proper description +ok 284 - has_domain(domain, desc) should have the proper diagnostics +ok 285 - has_domain(scheam, domain) should fail +ok 286 - has_domain(scheam, domain) should have the proper description +ok 287 - has_domain(scheam, domain) should have the proper diagnostics +ok 288 - has_domain(schema, domain, desc) should fail +ok 289 - has_domain(schema, domain, desc) should have the proper description +ok 290 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 291 - hasnt_domain(domain) should pass +ok 292 - hasnt_domain(domain) should have the proper description +ok 293 - hasnt_domain(domain) should have the proper diagnostics +ok 294 - hasnt_domain(domain, desc) should pass +ok 295 - hasnt_domain(domain, desc) should have the proper description +ok 296 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 297 - hasnt_domain(scheam, domain) should pass +ok 298 - hasnt_domain(scheam, domain) should have the proper description +ok 299 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 300 - hasnt_domain(schema, domain, desc) should pass +ok 301 - hasnt_domain(schema, domain, desc) should have the proper description +ok 302 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 303 - hasnt_domain(domain) should fail +ok 304 - hasnt_domain(domain) should have the proper description +ok 305 - hasnt_domain(domain) should have the proper diagnostics +ok 306 - hasnt_domain(domain, desc) should fail +ok 307 - hasnt_domain(domain, desc) should have the proper description +ok 308 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 309 - hasnt_domain(scheam, domain) should fail +ok 310 - hasnt_domain(scheam, domain) should have the proper description +ok 311 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 312 - hasnt_domain(schema, domain, desc) should fail +ok 313 - hasnt_domain(schema, domain, desc) should have the proper description +ok 314 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 315 - has_column(non-existent tab, col) should fail +ok 316 - has_column(non-existent tab, col) should have the proper description +ok 317 - has_column(non-existent tab, col) should have the proper diagnostics +ok 318 - has_column(non-existent tab, col, desc) should fail +ok 319 - has_column(non-existent tab, col, desc) should have the proper description +ok 320 - has_column(non-existent tab, col, desc) should have the proper diagnostics +ok 321 - has_column(non-existent sch, tab, col, desc) should fail +ok 322 - has_column(non-existent sch, tab, col, desc) should have the proper description +ok 323 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 324 - has_column(table, column) should pass +ok 325 - has_column(table, column) should have the proper description +ok 326 - has_column(table, column) should have the proper diagnostics +ok 327 - has_column(sch, tab, col, desc) should pass +ok 328 - has_column(sch, tab, col, desc) should have the proper description +ok 329 - has_column(sch, tab, col, desc) should have the proper diagnostics +ok 330 - has_column(table, camleCase column) should pass +ok 331 - has_column(table, camleCase column) should have the proper description +ok 332 - has_column(table, camleCase column) should have the proper diagnostics +ok 333 - has_column(view, column) should pass +ok 334 - has_column(view, column) should have the proper description +ok 335 - has_column(view, column) should have the proper diagnostics +ok 336 - has_column(type, column) should pass +ok 337 - has_column(type, column) should have the proper description +ok 338 - has_column(type, column) should have the proper diagnostics +ok 339 - hasnt_column(non-existent tab, col) should pass +ok 340 - hasnt_column(non-existent tab, col) should have the proper description +ok 341 - hasnt_column(non-existent tab, col) should have the proper diagnostics +ok 342 - hasnt_column(non-existent tab, col, desc) should pass +ok 343 - hasnt_column(non-existent tab, col, desc) should have the proper description +ok 344 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics +ok 345 - hasnt_column(non-existent sch, tab, col, desc) should pass +ok 346 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description +ok 347 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 348 - hasnt_column(table, column) should fail +ok 349 - hasnt_column(table, column) should have the proper description +ok 350 - hasnt_column(table, column) should have the proper diagnostics +ok 351 - hasnt_column(sch, tab, col, desc) should fail +ok 352 - hasnt_column(sch, tab, col, desc) should have the proper description +ok 353 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics +ok 354 - hasnt_column(view, column) should pass +ok 355 - hasnt_column(view, column) should have the proper description +ok 356 - hasnt_column(view, column) should have the proper diagnostics +ok 357 - hasnt_column(type, column) should pass +ok 358 - hasnt_column(type, column) should have the proper description +ok 359 - hasnt_column(type, column) should have the proper diagnostics +ok 360 - has_cast( src, targ, schema, func, desc) should pass +ok 361 - has_cast( src, targ, schema, func, desc) should have the proper description +ok 362 - has_cast( src, targ, schema, func, desc) should have the proper diagnostics +ok 363 - has_cast( src, targ, schema, func ) should pass +ok 364 - has_cast( src, targ, schema, func ) should have the proper description +ok 365 - has_cast( src, targ, schema, func ) should have the proper diagnostics +ok 366 - has_cast( src, targ, func, desc ) should pass +ok 367 - has_cast( src, targ, func, desc ) should have the proper description +ok 368 - has_cast( src, targ, func, desc ) should have the proper diagnostics +ok 369 - has_cast( src, targ, func) should pass +ok 370 - has_cast( src, targ, func) should have the proper description +ok 371 - has_cast( src, targ, func) should have the proper diagnostics +ok 372 - has_cast( src, targ, desc ) should pass +ok 373 - has_cast( src, targ, desc ) should have the proper description +ok 374 - has_cast( src, targ, desc ) should have the proper diagnostics +ok 375 - has_cast( src, targ ) should pass +ok 376 - has_cast( src, targ ) should have the proper description +ok 377 - has_cast( src, targ ) should have the proper diagnostics +ok 378 - has_cast( src, targ, schema, func, desc) fail should fail +ok 379 - has_cast( src, targ, schema, func, desc) fail should have the proper description +ok 380 - has_cast( src, targ, schema, func, desc) fail should have the proper diagnostics +ok 381 - has_cast( src, targ, func, desc ) fail should fail +ok 382 - has_cast( src, targ, func, desc ) fail should have the proper description +ok 383 - has_cast( src, targ, func, desc ) fail should have the proper diagnostics +ok 384 - has_cast( src, targ, desc ) fail should fail +ok 385 - has_cast( src, targ, desc ) fail should have the proper description +ok 386 - has_cast( src, targ, desc ) fail should have the proper diagnostics +ok 387 - hasnt_cast( src, targ, schema, func, desc) should fail +ok 388 - hasnt_cast( src, targ, schema, func, desc) should have the proper description +ok 389 - hasnt_cast( src, targ, schema, func, desc) should have the proper diagnostics +ok 390 - hasnt_cast( src, targ, schema, func ) should fail +ok 391 - hasnt_cast( src, targ, schema, func ) should have the proper description +ok 392 - hasnt_cast( src, targ, schema, func ) should have the proper diagnostics +ok 393 - hasnt_cast( src, targ, func, desc ) should fail +ok 394 - hasnt_cast( src, targ, func, desc ) should have the proper description +ok 395 - hasnt_cast( src, targ, func, desc ) should have the proper diagnostics +ok 396 - hasnt_cast( src, targ, func) should fail +ok 397 - hasnt_cast( src, targ, func) should have the proper description +ok 398 - hasnt_cast( src, targ, func) should have the proper diagnostics +ok 399 - hasnt_cast( src, targ, desc ) should fail +ok 400 - hasnt_cast( src, targ, desc ) should have the proper description +ok 401 - hasnt_cast( src, targ, desc ) should have the proper diagnostics +ok 402 - hasnt_cast( src, targ ) should fail +ok 403 - hasnt_cast( src, targ ) should have the proper description +ok 404 - hasnt_cast( src, targ ) should have the proper diagnostics +ok 405 - hasnt_cast( src, targ, schema, func, desc) fail should pass +ok 406 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper description +ok 407 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper diagnostics +ok 408 - hasnt_cast( src, targ, func, desc ) fail should pass +ok 409 - hasnt_cast( src, targ, func, desc ) fail should have the proper description +ok 410 - hasnt_cast( src, targ, func, desc ) fail should have the proper diagnostics +ok 411 - hasnt_cast( src, targ, desc ) fail should pass +ok 412 - hasnt_cast( src, targ, desc ) fail should have the proper description +ok 413 - hasnt_cast( src, targ, desc ) fail should have the proper diagnostics +ok 414 - cast_context_is( src, targ, context, desc ) should pass +ok 415 - cast_context_is( src, targ, context, desc ) should have the proper description +ok 416 - cast_context_is( src, targ, context, desc ) should have the proper diagnostics +ok 417 - cast_context_is( src, targ, context ) should pass +ok 418 - cast_context_is( src, targ, context ) should have the proper description +ok 419 - cast_context_is( src, targ, context ) should have the proper diagnostics +ok 420 - cast_context_is( src, targ, i, desc ) should pass +ok 421 - cast_context_is( src, targ, i, desc ) should have the proper description +ok 422 - cast_context_is( src, targ, i, desc ) should have the proper diagnostics +ok 423 - cast_context_is( src, targ, IMPL, desc ) should pass +ok 424 - cast_context_is( src, targ, IMPL, desc ) should have the proper description +ok 425 - cast_context_is( src, targ, IMPL, desc ) should have the proper diagnostics +ok 426 - cast_context_is( src, targ, assignment, desc ) should pass +ok 427 - cast_context_is( src, targ, assignment, desc ) should have the proper description +ok 428 - cast_context_is( src, targ, assignment, desc ) should have the proper diagnostics +ok 429 - cast_context_is( src, targ, a, desc ) should pass +ok 430 - cast_context_is( src, targ, a, desc ) should have the proper description +ok 431 - cast_context_is( src, targ, a, desc ) should have the proper diagnostics +ok 432 - cast_context_is( src, targ, ASS, desc ) should pass +ok 433 - cast_context_is( src, targ, ASS, desc ) should have the proper description +ok 434 - cast_context_is( src, targ, ASS, desc ) should have the proper diagnostics +ok 435 - cast_context_is( src, targ, explicit, desc ) should pass +ok 436 - cast_context_is( src, targ, explicit, desc ) should have the proper description +ok 437 - cast_context_is( src, targ, explicit, desc ) should have the proper diagnostics +ok 438 - cast_context_is( src, targ, e, desc ) should pass +ok 439 - cast_context_is( src, targ, e, desc ) should have the proper description +ok 440 - cast_context_is( src, targ, e, desc ) should have the proper diagnostics +ok 441 - cast_context_is( src, targ, EX, desc ) should pass +ok 442 - cast_context_is( src, targ, EX, desc ) should have the proper description +ok 443 - cast_context_is( src, targ, EX, desc ) should have the proper diagnostics +ok 444 - cast_context_is( src, targ, context, desc ) fail should fail +ok 445 - cast_context_is( src, targ, context, desc ) fail should have the proper description +ok 446 - cast_context_is( src, targ, context, desc ) fail should have the proper diagnostics +ok 447 - cast_context_is( src, targ, context ) fail should fail +ok 448 - cast_context_is( src, targ, context ) fail should have the proper description +ok 449 - cast_context_is( src, targ, context ) fail should have the proper diagnostics +ok 450 - cast_context_is( src, targ, context, desc ) noexist should fail +ok 451 - cast_context_is( src, targ, context, desc ) noexist should have the proper description +ok 452 - cast_context_is( src, targ, context, desc ) noexist should have the proper diagnostics +ok 453 - has_operator( left, schema, name, right, result, desc ) should pass +ok 454 - has_operator( left, schema, name, right, result, desc ) should have the proper description +ok 455 - has_operator( left, schema, name, right, result, desc ) should have the proper diagnostics +ok 456 - has_operator( left, schema, name, right, result ) should pass +ok 457 - has_operator( left, schema, name, right, result ) should have the proper description +ok 458 - has_operator( left, schema, name, right, result ) should have the proper diagnostics +ok 459 - has_operator( left, name, right, result, desc ) should pass +ok 460 - has_operator( left, name, right, result, desc ) should have the proper description +ok 461 - has_operator( left, name, right, result, desc ) should have the proper diagnostics +ok 462 - has_operator( left, name, right, result ) should pass +ok 463 - has_operator( left, name, right, result ) should have the proper description +ok 464 - has_operator( left, name, right, result ) should have the proper diagnostics +ok 465 - has_operator( left, name, right, desc ) should pass +ok 466 - has_operator( left, name, right, desc ) should have the proper description +ok 467 - has_operator( left, name, right, desc ) should have the proper diagnostics +ok 468 - has_operator( left, name, right ) should pass +ok 469 - has_operator( left, name, right ) should have the proper description +ok 470 - has_operator( left, name, right ) should have the proper diagnostics +ok 471 - has_operator( left, schema, name, right, result, desc ) fail should fail +ok 472 - has_operator( left, schema, name, right, result, desc ) fail should have the proper description +ok 473 - has_operator( left, schema, name, right, result, desc ) fail should have the proper diagnostics +ok 474 - has_operator( left, schema, name, right, result ) fail should fail +ok 475 - has_operator( left, schema, name, right, result ) fail should have the proper description +ok 476 - has_operator( left, schema, name, right, result ) fail should have the proper diagnostics +ok 477 - has_operator( left, name, right, result, desc ) fail should fail +ok 478 - has_operator( left, name, right, result, desc ) fail should have the proper description +ok 479 - has_operator( left, name, right, result, desc ) fail should have the proper diagnostics +ok 480 - has_operator( left, name, right, result ) fail should fail +ok 481 - has_operator( left, name, right, result ) fail should have the proper description +ok 482 - has_operator( left, name, right, result ) fail should have the proper diagnostics +ok 483 - has_operator( left, name, right, desc ) fail should fail +ok 484 - has_operator( left, name, right, desc ) fail should have the proper description +ok 485 - has_operator( left, name, right, desc ) fail should have the proper diagnostics +ok 486 - has_operator( left, name, right ) fail should fail +ok 487 - has_operator( left, name, right ) fail should have the proper description +ok 488 - has_operator( left, name, right ) fail should have the proper diagnostics +ok 489 - has_leftop( schema, name, right, result, desc ) should pass +ok 490 - has_leftop( schema, name, right, result, desc ) should have the proper description +ok 491 - has_leftop( schema, name, right, result, desc ) should have the proper diagnostics +ok 492 - has_leftop( schema, name, right, result ) should pass +ok 493 - has_leftop( schema, name, right, result ) should have the proper description +ok 494 - has_leftop( schema, name, right, result ) should have the proper diagnostics +ok 495 - has_leftop( name, right, result, desc ) should pass +ok 496 - has_leftop( name, right, result, desc ) should have the proper description +ok 497 - has_leftop( name, right, result, desc ) should have the proper diagnostics +ok 498 - has_leftop( name, right, result ) should pass +ok 499 - has_leftop( name, right, result ) should have the proper description +ok 500 - has_leftop( name, right, result ) should have the proper diagnostics +ok 501 - has_leftop( name, right, desc ) should pass +ok 502 - has_leftop( name, right, desc ) should have the proper description +ok 503 - has_leftop( name, right, desc ) should have the proper diagnostics +ok 504 - has_leftop( name, right ) should pass +ok 505 - has_leftop( name, right ) should have the proper description +ok 506 - has_leftop( name, right ) should have the proper diagnostics +ok 507 - has_leftop( schema, name, right, result, desc ) fail should fail +ok 508 - has_leftop( schema, name, right, result, desc ) fail should have the proper description +ok 509 - has_leftop( schema, name, right, result, desc ) fail should have the proper diagnostics +ok 510 - has_leftop( schema, name, right, result ) fail should fail +ok 511 - has_leftop( schema, name, right, result ) fail should have the proper description +ok 512 - has_leftop( schema, name, right, result ) fail should have the proper diagnostics +ok 513 - has_leftop( name, right, result, desc ) fail should fail +ok 514 - has_leftop( name, right, result, desc ) fail should have the proper description +ok 515 - has_leftop( name, right, result, desc ) fail should have the proper diagnostics +ok 516 - has_leftop( name, right, result ) fail should fail +ok 517 - has_leftop( name, right, result ) fail should have the proper description +ok 518 - has_leftop( name, right, result ) fail should have the proper diagnostics +ok 519 - has_leftop( name, right, desc ) fail should fail +ok 520 - has_leftop( name, right, desc ) fail should have the proper description +ok 521 - has_leftop( name, right, desc ) fail should have the proper diagnostics +ok 522 - has_leftop( name, right ) fail should fail +ok 523 - has_leftop( name, right ) fail should have the proper description +ok 524 - has_leftop( name, right ) fail should have the proper diagnostics +ok 525 - has_rightop( left, schema, name, result, desc ) should pass +ok 526 - has_rightop( left, schema, name, result, desc ) should have the proper description +ok 527 - has_rightop( left, schema, name, result, desc ) should have the proper diagnostics +ok 528 - has_rightop( left, schema, name, result ) should pass +ok 529 - has_rightop( left, schema, name, result ) should have the proper description +ok 530 - has_rightop( left, schema, name, result ) should have the proper diagnostics +ok 531 - has_rightop( left, name, result, desc ) should pass +ok 532 - has_rightop( left, name, result, desc ) should have the proper description +ok 533 - has_rightop( left, name, result, desc ) should have the proper diagnostics +ok 534 - has_rightop( left, name, result ) should pass +ok 535 - has_rightop( left, name, result ) should have the proper description +ok 536 - has_rightop( left, name, result ) should have the proper diagnostics +ok 537 - has_rightop( left, name, desc ) should pass +ok 538 - has_rightop( left, name, desc ) should have the proper description +ok 539 - has_rightop( left, name, desc ) should have the proper diagnostics +ok 540 - has_rightop( left, name ) should pass +ok 541 - has_rightop( left, name ) should have the proper description +ok 542 - has_rightop( left, name ) should have the proper diagnostics +ok 543 - has_rightop( left, schema, name, result, desc ) fail should fail +ok 544 - has_rightop( left, schema, name, result, desc ) fail should have the proper description +ok 545 - has_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics +ok 546 - has_rightop( left, schema, name, result ) fail should fail +ok 547 - has_rightop( left, schema, name, result ) fail should have the proper description +ok 548 - has_rightop( left, schema, name, result ) fail should have the proper diagnostics +ok 549 - has_rightop( left, name, result, desc ) fail should fail +ok 550 - has_rightop( left, name, result, desc ) fail should have the proper description +ok 551 - has_rightop( left, name, result, desc ) fail should have the proper diagnostics +ok 552 - has_rightop( left, name, result ) fail should fail +ok 553 - has_rightop( left, name, result ) fail should have the proper description +ok 554 - has_rightop( left, name, result ) fail should have the proper diagnostics +ok 555 - has_rightop( left, name, desc ) fail should fail +ok 556 - has_rightop( left, name, desc ) fail should have the proper description +ok 557 - has_rightop( left, name, desc ) fail should have the proper diagnostics +ok 558 - has_rightop( left, name ) fail should fail +ok 559 - has_rightop( left, name ) fail should have the proper description +ok 560 - has_rightop( left, name ) fail should have the proper diagnostics +ok 561 - has_language(language) should pass +ok 562 - has_language(language) should have the proper description +ok 563 - has_language(language) should have the proper diagnostics +ok 564 - has_language(language, desc) should pass +ok 565 - has_language(language, desc) should have the proper description +ok 566 - has_language(language, desc) should have the proper diagnostics +ok 567 - has_language(nonexistent language) should fail +ok 568 - has_language(nonexistent language) should have the proper description +ok 569 - has_language(nonexistent language) should have the proper diagnostics +ok 570 - has_language(nonexistent language, desc) should fail +ok 571 - has_language(nonexistent language, desc) should have the proper description +ok 572 - has_language(nonexistent language, desc) should have the proper diagnostics +ok 573 - hasnt_language(language) should fail +ok 574 - hasnt_language(language) should have the proper description +ok 575 - hasnt_language(language) should have the proper diagnostics +ok 576 - hasnt_language(language, desc) should fail +ok 577 - hasnt_language(language, desc) should have the proper description +ok 578 - hasnt_language(language, desc) should have the proper diagnostics +ok 579 - hasnt_language(nonexistent language) should pass +ok 580 - hasnt_language(nonexistent language) should have the proper description +ok 581 - hasnt_language(nonexistent language) should have the proper diagnostics +ok 582 - hasnt_language(nonexistent language, desc) should pass +ok 583 - hasnt_language(nonexistent language, desc) should have the proper description +ok 584 - hasnt_language(nonexistent language, desc) should have the proper diagnostics +ok 585 - language_is_trusted(language, desc) should pass +ok 586 - language_is_trusted(language, desc) should have the proper description +ok 587 - language_is_trusted(language, desc) should have the proper diagnostics +ok 588 - language_is_trusted(language) should pass +ok 589 - language_is_trusted(language) should have the proper description +ok 590 - language_is_trusted(language) should have the proper diagnostics +ok 591 - language_is_trusted(language, desc) fail should fail +ok 592 - language_is_trusted(language, desc) fail should have the proper description +ok 593 - language_is_trusted(language, desc) fail should have the proper diagnostics +ok 594 - language_is_trusted(language, desc) non-existent should fail +ok 595 - language_is_trusted(language, desc) non-existent should have the proper description +ok 596 - language_is_trusted(language, desc) non-existent should have the proper diagnostics +ok 597 - has_opclass( schema, name, desc ) should pass +ok 598 - has_opclass( schema, name, desc ) should have the proper description +ok 599 - has_opclass( schema, name, desc ) should have the proper diagnostics +ok 600 - has_opclass( schema, name ) should pass +ok 601 - has_opclass( schema, name ) should have the proper description +ok 602 - has_opclass( schema, name ) should have the proper diagnostics +ok 603 - has_opclass( name, desc ) should pass +ok 604 - has_opclass( name, desc ) should have the proper description +ok 605 - has_opclass( name, desc ) should have the proper diagnostics +ok 606 - has_opclass( name ) should pass +ok 607 - has_opclass( name ) should have the proper description +ok 608 - has_opclass( name ) should have the proper diagnostics +ok 609 - has_opclass( schema, name, desc ) fail should fail +ok 610 - has_opclass( schema, name, desc ) fail should have the proper description +ok 611 - has_opclass( schema, name, desc ) fail should have the proper diagnostics +ok 612 - has_opclass( name, desc ) fail should fail +ok 613 - has_opclass( name, desc ) fail should have the proper description +ok 614 - has_opclass( name, desc ) fail should have the proper diagnostics +ok 615 - hasnt_opclass( schema, name, desc ) should fail +ok 616 - hasnt_opclass( schema, name, desc ) should have the proper description +ok 617 - hasnt_opclass( schema, name, desc ) should have the proper diagnostics +ok 618 - hasnt_opclass( schema, name ) should fail +ok 619 - hasnt_opclass( schema, name ) should have the proper description +ok 620 - hasnt_opclass( schema, name ) should have the proper diagnostics +ok 621 - hasnt_opclass( name, desc ) should fail +ok 622 - hasnt_opclass( name, desc ) should have the proper description +ok 623 - hasnt_opclass( name, desc ) should have the proper diagnostics +ok 624 - hasnt_opclass( name ) should fail +ok 625 - hasnt_opclass( name ) should have the proper description +ok 626 - hasnt_opclass( name ) should have the proper diagnostics +ok 627 - hasnt_opclass( schema, name, desc ) fail should pass +ok 628 - hasnt_opclass( schema, name, desc ) fail should have the proper description +ok 629 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnostics +ok 630 - hasnt_opclass( name, desc ) fail should pass +ok 631 - hasnt_opclass( name, desc ) fail should have the proper description +ok 632 - hasnt_opclass( name, desc ) fail should have the proper diagnostics +ok 633 - domain_type_is(schema, domain, schema, type, desc) should pass +ok 634 - domain_type_is(schema, domain, schema, type, desc) should have the proper description +ok 635 - domain_type_is(schema, domain, schema, type, desc) should have the proper diagnostics +ok 636 - domain_type_is(schema, domain, schema, type) should pass +ok 637 - domain_type_is(schema, domain, schema, type) should have the proper description +ok 638 - domain_type_is(schema, domain, schema, type) should have the proper diagnostics +ok 639 - domain_type_is(schema, domain, schema, type, desc) fail should fail +ok 640 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper description +ok 641 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 642 - domain_type_is(schema, nondomain, schema, type, desc) should fail +ok 643 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper description +ok 644 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 645 - domain_type_is(schema, type, schema, type, desc) fail should fail +ok 646 - domain_type_is(schema, type, schema, type, desc) fail should have the proper description +ok 647 - domain_type_is(schema, type, schema, type, desc) fail should have the proper diagnostics +ok 648 - domain_type_is(schema, domain, type, desc) should pass +ok 649 - domain_type_is(schema, domain, type, desc) should have the proper description +ok 650 - domain_type_is(schema, domain, type, desc) should have the proper diagnostics +ok 651 - domain_type_is(schema, domain, type) should pass +ok 652 - domain_type_is(schema, domain, type) should have the proper description +ok 653 - domain_type_is(schema, domain, type) should have the proper diagnostics +ok 654 - domain_type_is(schema, domain, type, desc) fail should fail +ok 655 - domain_type_is(schema, domain, type, desc) fail should have the proper description +ok 656 - domain_type_is(schema, domain, type, desc) fail should have the proper diagnostics +ok 657 - domain_type_is(schema, nondomain, type, desc) should fail +ok 658 - domain_type_is(schema, nondomain, type, desc) should have the proper description +ok 659 - domain_type_is(schema, nondomain, type, desc) should have the proper diagnostics +ok 660 - domain_type_is(schema, type, type, desc) fail should fail +ok 661 - domain_type_is(schema, type, type, desc) fail should have the proper description +ok 662 - domain_type_is(schema, type, type, desc) fail should have the proper diagnostics +ok 663 - domain_type_is(domain, type, desc) should pass +ok 664 - domain_type_is(domain, type, desc) should have the proper description +ok 665 - domain_type_is(domain, type, desc) should have the proper diagnostics +ok 666 - domain_type_is(domain, type) should pass +ok 667 - domain_type_is(domain, type) should have the proper description +ok 668 - domain_type_is(domain, type) should have the proper diagnostics +ok 669 - domain_type_is(domain, type, desc) fail should fail +ok 670 - domain_type_is(domain, type, desc) fail should have the proper description +ok 671 - domain_type_is(domain, type, desc) fail should have the proper diagnostics +ok 672 - domain_type_is(nondomain, type, desc) should fail +ok 673 - domain_type_is(nondomain, type, desc) should have the proper description +ok 674 - domain_type_is(nondomain, type, desc) should have the proper diagnostics +ok 675 - domain_type_is(type, type, desc) fail should fail +ok 676 - domain_type_is(type, type, desc) fail should have the proper description +ok 677 - domain_type_is(type, type, desc) fail should have the proper diagnostics +ok 678 - domain_type_isnt(schema, domain, schema, type, desc) should pass +ok 679 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper description +ok 680 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper diagnostics +ok 681 - domain_type_isnt(schema, domain, schema, type) should pass +ok 682 - domain_type_isnt(schema, domain, schema, type) should have the proper description +ok 683 - domain_type_isnt(schema, domain, schema, type) should have the proper diagnostics +ok 684 - domain_type_isnt(schema, domain, schema, type, desc) fail should fail +ok 685 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper description +ok 686 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 687 - domain_type_isnt(schema, nondomain, schema, type, desc) should fail +ok 688 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper description +ok 689 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 690 - domain_type_isnt(schema, type, schema, type, desc) should fail +ok 691 - domain_type_isnt(schema, type, schema, type, desc) should have the proper description +ok 692 - domain_type_isnt(schema, type, schema, type, desc) should have the proper diagnostics +ok 693 - domain_type_isnt(schema, domain, type, desc) should pass +ok 694 - domain_type_isnt(schema, domain, type, desc) should have the proper description +ok 695 - domain_type_isnt(schema, domain, type, desc) should have the proper diagnostics +ok 696 - domain_type_isnt(schema, domain, type) should pass +ok 697 - domain_type_isnt(schema, domain, type) should have the proper description +ok 698 - domain_type_isnt(schema, domain, type) should have the proper diagnostics +ok 699 - domain_type_isnt(schema, domain, type, desc) fail should fail +ok 700 - domain_type_isnt(schema, domain, type, desc) fail should have the proper description +ok 701 - domain_type_isnt(schema, domain, type, desc) fail should have the proper diagnostics +ok 702 - domain_type_isnt(schema, nondomain, type, desc) should fail +ok 703 - domain_type_isnt(schema, nondomain, type, desc) should have the proper description +ok 704 - domain_type_isnt(schema, nondomain, type, desc) should have the proper diagnostics +ok 705 - domain_type_isnt(schema, type, type, desc) should fail +ok 706 - domain_type_isnt(schema, type, type, desc) should have the proper description +ok 707 - domain_type_isnt(schema, type, type, desc) should have the proper diagnostics +ok 708 - domain_type_isnt(domain, type, desc) should pass +ok 709 - domain_type_isnt(domain, type, desc) should have the proper description +ok 710 - domain_type_isnt(domain, type, desc) should have the proper diagnostics +ok 711 - domain_type_isnt(domain, type) should pass +ok 712 - domain_type_isnt(domain, type) should have the proper description +ok 713 - domain_type_isnt(domain, type) should have the proper diagnostics +ok 714 - domain_type_isnt(domain, type, desc) fail should fail +ok 715 - domain_type_isnt(domain, type, desc) fail should have the proper description +ok 716 - domain_type_isnt(domain, type, desc) fail should have the proper diagnostics +ok 717 - domain_type_isnt(nondomain, type, desc) should fail +ok 718 - domain_type_isnt(nondomain, type, desc) should have the proper description +ok 719 - domain_type_isnt(nondomain, type, desc) should have the proper diagnostics +ok 720 - domain_type_isnt(type, type, desc) should fail +ok 721 - domain_type_isnt(type, type, desc) should have the proper description +ok 722 - domain_type_isnt(type, type, desc) should have the proper diagnostics +ok 723 - has_relation(non-existent relation) should fail +ok 724 - has_relation(non-existent relation) should have the proper description +ok 725 - has_relation(non-existent relation) should have the proper diagnostics +ok 726 - has_relation(non-existent schema, tab) should fail +ok 727 - has_relation(non-existent schema, tab) should have the proper description +ok 728 - has_relation(non-existent schema, tab) should have the proper diagnostics +ok 729 - has_relation(sch, non-existent relation, desc) should fail +ok 730 - has_relation(sch, non-existent relation, desc) should have the proper description +ok 731 - has_relation(sch, non-existent relation, desc) should have the proper diagnostics +ok 732 - has_relation(tab, desc) should pass +ok 733 - has_relation(tab, desc) should have the proper description +ok 734 - has_relation(tab, desc) should have the proper diagnostics +ok 735 - has_relation(sch, tab, desc) should pass +ok 736 - has_relation(sch, tab, desc) should have the proper description +ok 737 - has_relation(sch, tab, desc) should have the proper diagnostics +ok 738 - has_relation(sch, view, desc) should pass +ok 739 - has_relation(sch, view, desc) should have the proper description +ok 740 - has_relation(sch, view, desc) should have the proper diagnostics +ok 741 - has_relation(type, desc) should pass +ok 742 - has_relation(type, desc) should have the proper description +ok 743 - has_relation(type, desc) should have the proper diagnostics +ok 744 - hasnt_relation(non-existent relation) should pass +ok 745 - hasnt_relation(non-existent relation) should have the proper description +ok 746 - hasnt_relation(non-existent relation) should have the proper diagnostics +ok 747 - hasnt_relation(non-existent schema, tab) should pass +ok 748 - hasnt_relation(non-existent schema, tab) should have the proper description +ok 749 - hasnt_relation(non-existent schema, tab) should have the proper diagnostics +ok 750 - hasnt_relation(sch, non-existent tab, desc) should pass +ok 751 - hasnt_relation(sch, non-existent tab, desc) should have the proper description +ok 752 - hasnt_relation(sch, non-existent tab, desc) should have the proper diagnostics +ok 753 - hasnt_relation(tab, desc) should fail +ok 754 - hasnt_relation(tab, desc) should have the proper description +ok 755 - hasnt_relation(tab, desc) should have the proper diagnostics +ok 756 - hasnt_relation(sch, tab, desc) should fail +ok 757 - hasnt_relation(sch, tab, desc) should have the proper description +ok 758 - hasnt_relation(sch, tab, desc) should have the proper diagnostics +ok 759 - has_foreign_table(non-existent table) should fail +ok 760 - has_foreign_table(non-existent table) should have the proper description +ok 761 - has_foreign_table(non-existent table) should have the proper diagnostics +ok 762 - has_foreign_table(non-existent schema, tab) should fail +ok 763 - has_foreign_table(non-existent schema, tab) should have the proper description +ok 764 - has_foreign_table(non-existent schema, tab) should have the proper diagnostics +ok 765 - has_foreign_table(non-existent table, desc) should fail +ok 766 - has_foreign_table(non-existent table, desc) should have the proper description +ok 767 - has_foreign_table(non-existent table, desc) should have the proper diagnostics +ok 768 - has_foreign_table(sch, non-existent table, desc) should fail +ok 769 - has_foreign_table(sch, non-existent table, desc) should have the proper description +ok 770 - has_foreign_table(sch, non-existent table, desc) should have the proper diagnostics +ok 771 - has_foreign_table(tab, desc) should pass +ok 772 - has_foreign_table(tab, desc) should have the proper description +ok 773 - has_foreign_table(tab, desc) should have the proper diagnostics +ok 774 - has_foreign_table(sch, tab, desc) should pass +ok 775 - has_foreign_table(sch, tab, desc) should have the proper description +ok 776 - has_foreign_table(sch, tab, desc) should have the proper diagnostics +ok 777 - has_foreign_table(sch, view, desc) should fail +ok 778 - has_foreign_table(sch, view, desc) should have the proper description +ok 779 - has_foreign_table(sch, view, desc) should have the proper diagnostics +ok 780 - has_foreign_table(type, desc) should fail +ok 781 - has_foreign_table(type, desc) should have the proper description +ok 782 - has_foreign_table(type, desc) should have the proper diagnostics +ok 783 - hasnt_foreign_table(non-existent table) should pass +ok 784 - hasnt_foreign_table(non-existent table) should have the proper description +ok 785 - hasnt_foreign_table(non-existent table) should have the proper diagnostics +ok 786 - hasnt_foreign_table(non-existent schema, tab) should pass +ok 787 - hasnt_foreign_table(non-existent schema, tab) should have the proper description +ok 788 - hasnt_foreign_table(non-existent schema, tab) should have the proper diagnostics +ok 789 - hasnt_foreign_table(non-existent table, desc) should pass +ok 790 - hasnt_foreign_table(non-existent table, desc) should have the proper description +ok 791 - hasnt_foreign_table(non-existent table, desc) should have the proper diagnostics +ok 792 - hasnt_foreign_table(sch, non-existent tab, desc) should pass +ok 793 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper description +ok 794 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper diagnostics +ok 795 - hasnt_foreign_table(tab, desc) should fail +ok 796 - hasnt_foreign_table(tab, desc) should have the proper description +ok 797 - hasnt_foreign_table(tab, desc) should have the proper diagnostics +ok 798 - hasnt_foreign_table(sch, tab, desc) should fail +ok 799 - hasnt_foreign_table(sch, tab, desc) should have the proper description +ok 800 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics +ok 801 - has_materialized_view(non-existent materialized_view) should fail +ok 802 - has_materialized_view(non-existent materialized_view) should have the proper description +ok 803 - has_materialized_view(non-existent materialized_view) should have the proper diagnostics +ok 804 - has_materialized_view(non-existent materialized_view, desc) should fail +ok 805 - has_materialized_view(non-existent materialized_view, desc) should have the proper description +ok 806 - has_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics +ok 807 - has_materialized_view(sch, non-existtent materialized_view, desc) should fail +ok 808 - has_materialized_view(sch, non-existtent materialized_view, desc) should have the proper description +ok 809 - has_materialized_view(sch, non-existtent materialized_view, desc) should have the proper diagnostics +ok 810 - has_materialized_view(materialized_view, desc) should pass +ok 811 - has_materialized_view(materialized_view, desc) should have the proper description +ok 812 - has_materialized_view(materialized_view, desc) should have the proper diagnostics +ok 813 - has_materialized_view(sch, materialized_view, desc) should pass +ok 814 - has_materialized_view(sch, materialized_view, desc) should have the proper description +ok 815 - has_materialized_view(sch, materialized_view, desc) should have the proper diagnostics +ok 816 - hasnt_materialized_view(non-existent materialized_view) should pass +ok 817 - hasnt_materialized_view(non-existent materialized_view) should have the proper description +ok 818 - hasnt_materialized_view(non-existent materialized_view) should have the proper diagnostics +ok 819 - hasnt_materialized_view(non-existent materialized_view, desc) should pass +ok 820 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper description +ok 821 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics +ok 822 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should pass +ok 823 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should have the proper description +ok 824 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should have the proper diagnostics +ok 825 - hasnt_materialized_view(materialized_view, desc) should fail +ok 826 - hasnt_materialized_view(materialized_view, desc) should have the proper description +ok 827 - hasnt_materialized_view(materialized_view, desc) should have the proper diagnostics +ok 828 - hasnt_materialized_view(sch, materialized_view, desc) should fail +ok 829 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper description +ok 830 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper diagnostics diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index 222301fddd8e..4441e7ef6b62 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(828); +SELECT plan(830); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -411,6 +411,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + has_sequence( 'public', 'someseq'::name ), + true, + 'has_sequence(sch, sequence)', + 'Sequence public.someseq should exist' + '' +); + /****************************************************************************/ -- Test hasnt_sequence(). From c7f1891183a46199d84467331929ed4c18c64c56 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Mar 2015 13:11:55 -0700 Subject: [PATCH 0855/1195] Patch back to 8.1. --- compat/install-8.1.patch | 73 +++++++++++++++---------------- compat/install-8.2.patch | 88 ++++++++++++++++++++------------------ compat/install-8.3.patch | 12 +++--- compat/install-8.4.patch | 12 +++--- compat/install-9.0.patch | 4 +- compat/uninstall-8.2.patch | 10 ++--- compat/uninstall-8.3.patch | 6 +-- test/sql/runjusttests.sql | 8 ++-- test/sql/runtests.sql | 8 ++-- 9 files changed, 113 insertions(+), 108 deletions(-) diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index 5bac44f55a48..24f1df7876b8 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ ---- sql/pgtap.sql.saf 2014-01-06 16:30:01.000000000 -0800 -+++ sql/pgtap.sql 2014-01-06 16:30:06.000000000 -0800 -@@ -2152,13 +2152,13 @@ +--- sql/pgtap.sql ++++ sql/pgtap.sql +@@ -2226,13 +2226,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -18,7 +18,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $6 || ' constraints'; -@@ -2176,13 +2176,13 @@ +@@ -2250,13 +2250,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -36,7 +36,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $5 || ' constraints'; -@@ -5784,7 +5784,7 @@ +@@ -5929,7 +5929,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -45,7 +45,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -5792,8 +5792,8 @@ +@@ -5937,8 +5937,8 @@ -- 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. @@ -56,15 +56,16 @@ END LOOP; END LOOP; RETURN; -@@ -5863,14 +5863,14 @@ +@@ -6007,7 +6007,7 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; -- tap text; -+ rec record; - verbos boolean := _is_verbose(); -- verbose is a reserved word in 8.5. - num_faild INTEGER := 0; - BEGIN +- tap TEXT; ++ rec RECORD; + tfaild INTEGER := 0; + ffaild INTEGER := 0; + tnumb INTEGER := 0; +@@ -6018,7 +6018,7 @@ BEGIN -- No plan support. PERFORM * FROM no_plan(); @@ -73,47 +74,47 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -5885,15 +5885,15 @@ - IF verbos THEN RETURN NEXT diag_test_name(tests[i]); END IF; - +@@ -6045,18 +6045,18 @@ + BEGIN + BEGIN -- Run the setup functions. -- FOR tap IN SELECT * FROM _runem(setup, false) LOOP RETURN NEXT tap; END LOOP; -+ FOR rec IN SELECT * FROM _runem(setup, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; +- FOR tap IN SELECT * FROM _runem(setup, false) LOOP +- RETURN NEXT regexp_replace(tap, E'(^|\n)', E'\\1 ', 'g'); ++ FOR rec IN SELECT * FROM _runem(setup, false) AS b(a) LOOP ++ RETURN NEXT regexp_replace(rec.a, E'(^|\n)', E'\\1 ', 'g'); + END LOOP; -- Run the actual test function. - FOR tap IN EXECUTE 'SELECT * FROM ' || tests[i] || '()' LOOP -- RETURN NEXT tap; +- RETURN NEXT regexp_replace(tap, E'(^|\n)', E'\\1 ', 'g'); + FOR rec IN EXECUTE 'SELECT * FROM ' || tests[i] || '() AS b(a)' LOOP -+ RETURN NEXT rec.a; ++ RETURN NEXT regexp_replace(rec.a, E'(^|\n)', E'\\1 ', 'g'); END LOOP; -- Run the teardown functions. -- FOR tap IN SELECT * FROM _runem(teardown, false) LOOP RETURN NEXT tap; END LOOP; -+ FOR rec IN SELECT * FROM _runem(teardown, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; +- FOR tap IN SELECT * FROM _runem(teardown, false) LOOP +- RETURN NEXT regexp_replace(tap, E'(^|\n)', E'\\1 ', 'g'); ++ FOR rec IN SELECT * FROM _runem(teardown, false) AS b(a) LOOP ++ RETURN NEXT regexp_replace(rec.a, E'(^|\n)', E'\\1 ', 'g'); + END LOOP; - -- Remember how many failed and then roll back. - num_faild := num_faild + num_failed(); -@@ -5908,7 +5908,7 @@ - END LOOP; + -- Emit the plan. +@@ -6109,11 +6109,11 @@ + END LOOP; - -- Run the shutdown functions. -- FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; -+ FOR rec IN SELECT * FROM _runem(shutdown, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; + -- Run the shutdown functions. +- FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; ++ FOR rec IN SELECT * FROM _runem(shutdown, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; - -- Raise an exception to rollback any changes. - RAISE EXCEPTION '__TAP_ROLLBACK__'; -@@ -5919,8 +5919,8 @@ - END IF; - END; -- Finish up. -- FOR tap IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) LOOP +- FOR tap IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) LOOP - RETURN NEXT tap; -+ FOR rec IN SELECT * FROM _finish( currval('__tresults___numb_seq')::integer, 0, num_faild ) AS b(a) LOOP ++ FOR rec IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) AS b(a) LOOP + RETURN NEXT rec.a; END LOOP; -- Clean up and return. -@@ -7178,7 +7178,7 @@ +@@ -7371,7 +7371,7 @@ DECLARE rec RECORD; BEGIN diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 79435b67d226..82f8e980a7a2 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,5 +1,5 @@ ---- sql/pgtap.sql.saf 2014-01-06 16:21:22.000000000 -0800 -+++ sql/pgtap.sql 2014-01-06 16:21:28.000000000 -0800 +--- sql/pgtap.sql ++++ sql/pgtap.sql @@ -5,6 +5,59 @@ -- -- http://pgtap.org/ @@ -60,24 +60,7 @@ CREATE OR REPLACE FUNCTION pg_version() RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; -@@ -190,11 +243,11 @@ - RETURNS integer AS $$ - BEGIN - EXECUTE 'INSERT INTO __tresults__ ( ok, aok, descr, type, reason ) -- VALUES( ' || $1 || ', ' -- || $2 || ', ' -- || quote_literal(COALESCE($3, '')) || ', ' -- || quote_literal($4) || ', ' -- || quote_literal($5) || ' )'; -+ VALUES( ' || $1::text || ', ' -+ || $2::text || ', ' -+ || quote_literal(COALESCE($3, '')::text) || ', ' -+ || quote_literal($4::text) || ', ' -+ || quote_literal($5::text) || ' )'; - RETURN currval('__tresults___numb_seq'); - END; - $$ LANGUAGE plpgsql; -@@ -269,21 +322,6 @@ +@@ -250,21 +303,6 @@ ); $$ LANGUAGE sql strict; @@ -99,7 +82,7 @@ CREATE OR REPLACE FUNCTION ok ( boolean, text ) RETURNS TEXT AS $$ DECLARE -@@ -496,9 +534,9 @@ +@@ -477,9 +515,9 @@ output TEXT; BEGIN EXECUTE 'SELECT ' || @@ -111,7 +94,7 @@ INTO result; output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( -@@ -2355,7 +2393,7 @@ +@@ -2441,7 +2479,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 @@ -120,7 +103,7 @@ p.prolang AS langoid, p.proisstrict AS is_strict, p.proisagg AS is_agg, -@@ -3491,63 +3529,6 @@ +@@ -3577,63 +3615,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -184,7 +167,28 @@ CREATE OR REPLACE FUNCTION _has_role( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( -@@ -6031,13 +6012,13 @@ +@@ -6065,17 +6046,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. +@@ -6224,13 +6205,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -200,7 +204,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6261,7 +6242,7 @@ +@@ -6454,7 +6435,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -209,7 +213,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6356,11 +6337,11 @@ +@@ -6549,11 +6530,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -224,7 +228,7 @@ ); END IF; rownum = rownum + 1; -@@ -6375,9 +6356,9 @@ +@@ -6568,9 +6549,9 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Number of columns or their types differ between the queries' || @@ -237,7 +241,7 @@ END ); END; -@@ -6513,7 +6494,7 @@ +@@ -6706,7 +6687,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -246,7 +250,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6527,8 +6508,8 @@ +@@ -6720,8 +6701,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -257,7 +261,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6653,9 +6634,9 @@ +@@ -6846,9 +6827,9 @@ DECLARE typeof regtype := pg_typeof($1); BEGIN @@ -270,7 +274,7 @@ END; $$ LANGUAGE plpgsql; -@@ -6676,7 +6657,7 @@ +@@ -6869,7 +6850,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -279,7 +283,7 @@ END LOOP; -- What extra records do we have? -@@ -6844,7 +6825,7 @@ +@@ -7037,7 +7018,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -288,7 +292,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT -@@ -6862,7 +6843,7 @@ +@@ -7055,7 +7036,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -297,7 +301,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) ), -@@ -6895,7 +6876,7 @@ +@@ -7088,7 +7069,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -306,7 +310,7 @@ 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']) ) -@@ -6914,7 +6895,7 @@ +@@ -7107,7 +7088,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -315,7 +319,7 @@ 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']) ) -@@ -7198,10 +7179,12 @@ +@@ -7391,10 +7372,12 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; @@ -331,7 +335,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7348,7 +7331,7 @@ +@@ -7541,7 +7524,7 @@ CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) RETURNS TEXT AS $$ @@ -340,7 +344,7 @@ $$ LANGUAGE SQL; -- operators_are( schema, operators[], description ) -@@ -7357,7 +7340,7 @@ +@@ -7550,7 +7533,7 @@ SELECT _areni( 'operators', ARRAY( @@ -349,7 +353,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7369,7 +7352,7 @@ +@@ -7562,7 +7545,7 @@ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT @@ -358,7 +362,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7390,7 +7373,7 @@ +@@ -7583,7 +7566,7 @@ SELECT _areni( 'operators', ARRAY( @@ -367,7 +371,7 @@ 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) -@@ -7403,7 +7386,7 @@ +@@ -7596,7 +7579,7 @@ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT @@ -376,7 +380,7 @@ 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) -@@ -8106,40 +8089,6 @@ +@@ -8299,40 +8282,6 @@ ); $$ LANGUAGE sql; diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index 11a69bd8f540..623e3e4d24d9 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -1,5 +1,5 @@ ---- sql/pgtap.sql.saf 2014-01-06 15:58:30.000000000 -0800 -+++ sql/pgtap.sql 2014-01-06 15:58:45.000000000 -0800 +--- sql/pgtap.sql ++++ sql/pgtap.sql @@ -9,6 +9,11 @@ RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; @@ -12,7 +12,7 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT s.a[1]::int * 10000 -@@ -6351,7 +6356,7 @@ +@@ -6544,7 +6549,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -21,7 +21,7 @@ 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' || -@@ -6508,7 +6513,7 @@ +@@ -6701,7 +6706,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -30,7 +30,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6717,13 +6722,7 @@ +@@ -6910,13 +6915,7 @@ $$ LANGUAGE sql; -- collect_tap( tap, tap, tap ) @@ -45,7 +45,7 @@ RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); $$ LANGUAGE sql; -@@ -7199,7 +7198,7 @@ +@@ -7392,7 +7391,7 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; diff --git a/compat/install-8.4.patch b/compat/install-8.4.patch index 32305b4e0599..e6a6d39b95f9 100644 --- a/compat/install-8.4.patch +++ b/compat/install-8.4.patch @@ -1,6 +1,6 @@ ---- sql/pgtap.sql.saf 2014-01-06 15:56:37.000000000 -0800 -+++ sql/pgtap.sql 2014-01-06 15:56:44.000000000 -0800 -@@ -7225,7 +7225,6 @@ +--- sql/pgtap.sql ++++ sql/pgtap.sql +@@ -7418,7 +7418,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -8,7 +8,7 @@ EXCEPT SELECT $3[i] FROM generate_series(1, array_upper($3, 1)) s(i) -@@ -7240,7 +7239,6 @@ +@@ -7433,7 +7432,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -16,7 +16,7 @@ ), $4 ); -@@ -7264,7 +7262,6 @@ +@@ -7457,7 +7455,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = $1 AND n.nspname NOT IN ('pg_catalog', 'information_schema') @@ -24,7 +24,7 @@ EXCEPT SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) -@@ -7278,7 +7275,6 @@ +@@ -7471,7 +7468,6 @@ 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') diff --git a/compat/install-9.0.patch b/compat/install-9.0.patch index f4c3c8123ae6..269c4a0af5f2 100644 --- a/compat/install-9.0.patch +++ b/compat/install-9.0.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -3583,7 +3583,7 @@ RETURNS TEXT AS $$ +@@ -3585,7 +3585,7 @@ RETURNS TEXT AS $$ AND n.nspname = $1 AND t.typname = $2 AND t.typtype = 'e' @@ -9,7 +9,7 @@ ), $3, $4 -@@ -3611,7 +3611,7 @@ RETURNS TEXT AS $$ +@@ -3613,7 +3613,7 @@ RETURNS TEXT AS $$ AND pg_catalog.pg_type_is_visible(t.oid) AND t.typname = $1 AND t.typtype = 'e' diff --git a/compat/uninstall-8.2.patch b/compat/uninstall-8.2.patch index 1842c2ffea17..042951ffa2c4 100644 --- a/compat/uninstall-8.2.patch +++ b/compat/uninstall-8.2.patch @@ -1,6 +1,6 @@ ---- sql/uninstall_pgtap.sql.saf 2013-01-28 11:44:27.000000000 -0800 -+++ sql/uninstall_pgtap.sql 2013-01-28 11:44:33.000000000 -0800 -@@ -482,10 +482,6 @@ +--- sql/uninstall_pgtap.sql ++++ sql/uninstall_pgtap.sql +@@ -490,10 +490,6 @@ DROP FUNCTION has_role( NAME ); DROP FUNCTION has_role( NAME, TEXT ); DROP FUNCTION _has_role( NAME ); @@ -11,7 +11,7 @@ DROP FUNCTION hasnt_enum( NAME ); DROP FUNCTION hasnt_enum( NAME, TEXT ); DROP FUNCTION hasnt_enum( NAME, NAME ); -@@ -811,9 +807,6 @@ +@@ -824,9 +820,6 @@ DROP FUNCTION is (anyelement, anyelement, text); DROP FUNCTION ok ( boolean ); DROP FUNCTION ok ( boolean, text ); @@ -21,7 +21,7 @@ DROP FUNCTION diag ( msg text ); DROP FUNCTION finish (); DROP FUNCTION _finish ( INTEGER, INTEGER, INTEGER); -@@ -836,3 +829,15 @@ +@@ -849,3 +842,15 @@ DROP FUNCTION os_name(); DROP FUNCTION pg_version_num(); DROP FUNCTION pg_version(); diff --git a/compat/uninstall-8.3.patch b/compat/uninstall-8.3.patch index 53284e885ea2..94c74934081c 100644 --- a/compat/uninstall-8.3.patch +++ b/compat/uninstall-8.3.patch @@ -1,5 +1,5 @@ ---- sql/uninstall_pgtap.sql.saf 2013-01-28 11:52:12.000000000 -0800 -+++ sql/uninstall_pgtap.sql 2013-01-28 11:52:27.000000000 -0800 +--- sql/uninstall_pgtap.sql ++++ sql/uninstall_pgtap.sql @@ -169,8 +169,7 @@ DROP FUNCTION throws_like ( TEXT, TEXT ); DROP FUNCTION throws_like ( TEXT, TEXT, TEXT ); @@ -10,7 +10,7 @@ DROP FUNCTION isnt_empty( TEXT ); DROP FUNCTION isnt_empty( TEXT, TEXT ); DROP FUNCTION is_empty( TEXT ); -@@ -832,6 +831,7 @@ +@@ -845,6 +844,7 @@ DROP FUNCTION _get ( text ); DROP FUNCTION no_plan(); DROP FUNCTION plan( integer ); diff --git a/test/sql/runjusttests.sql b/test/sql/runjusttests.sql index e033f9678fdc..0d3daf661761 100644 --- a/test/sql/runjusttests.sql +++ b/test/sql/runjusttests.sql @@ -9,10 +9,10 @@ CREATE TABLE whatever.foo ( id serial primary key ); SET client_min_messages = notice; CREATE OR REPLACE FUNCTION whatever.testthis() RETURNS SETOF TEXT AS $$ - SELECT collect_tap( + SELECT collect_tap(ARRAY[ pass('simple pass'), pass('another simple pass') - ); + ]); $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION whatever.testplpgsql() RETURNS SETOF TEXT AS $$ @@ -35,10 +35,10 @@ END; $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION whatever.testy() RETURNS SETOF TEXT AS $$ - SELECT collect_tap( + SELECT collect_tap(ARRAY[ pass('pass'), fail('this test intentionally fails') - ); + ]); $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION whatever.testnada() RETURNS SETOF TEXT AS $$ diff --git a/test/sql/runtests.sql b/test/sql/runtests.sql index 3120a7ee4fea..4fba128b28fb 100644 --- a/test/sql/runtests.sql +++ b/test/sql/runtests.sql @@ -17,10 +17,10 @@ CREATE OR REPLACE FUNCTION whatever.startupmore() RETURNS SETOF TEXT AS $$ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION whatever.setup() RETURNS SETOF TEXT AS $$ - SELECT collect_tap( + SELECT collect_tap(ARRAY[ pass('setup'), (SELECT is( MAX(id), NULL, 'Should be nothing in the test table') FROM whatever.foo) - ); + ]); $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION whatever.setupmore() RETURNS SETOF TEXT AS $$ @@ -44,10 +44,10 @@ CREATE OR REPLACE FUNCTION whatever.shutdownmore() RETURNS SETOF TEXT AS $$ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION whatever.testthis() RETURNS SETOF TEXT AS $$ - SELECT collect_tap( + SELECT collect_tap(ARRAY[ pass('simple pass'), pass('another simple pass') - ); + ]); $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION whatever.testplpgsql() RETURNS SETOF TEXT AS $$ From ef5ccf9212d166ec16db60d55648064c176f9e72 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Mar 2015 13:25:46 -0700 Subject: [PATCH 0856/1195] Tweak changes. --- Changes | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index f22bf714783a..a3fd9c8a4423 100644 --- a/Changes +++ b/Changes @@ -11,7 +11,7 @@ Revision history for pgTAP Thanks to Charly Batista for the contribution! * Added `performs_within()`, a function that tests the average performance of an SQL statement over a number of iterations. Contributed by Ryan Duryea. -* Changed is_member_of to work with roles instead of users. Patch from Jim +* Changed `is_member_of()` to work with roles instead of users. Patch from Jim Nasby. * Granted SELECT access to the views created by pgTAP to PUBLIC, to avoid permission errors when the tests are run by a user other than the owner of @@ -26,13 +26,16 @@ Revision history for pgTAP Jim Nasby. * The `runtests()` function now executes each test as a TAP subtest, with its own plan and test count. This allows each test function to function more - independently of the others. It's easier to read the TAP, too. + independently of the others. It's easier to read the TAP, too. Users of + xUnit-style tests with `pg_regress` will need to update their expected + output files, however. * An exception thrown by a test no run by `runtests()` no longer halts - execution. It is instead properly rolled back and the error message emitted - as a diagnostic message. Thanks to Vitaliy Kotov for the report. + execution. It is instead properly rolled back, reported as a subtest + failure, and the error message emitted as a diagnostic message. Thanks to + Vitaliy Kotov for the report. * Eliminated the temporary table `__tresults__`. Test results are no longer - written to a table, but simply emitted. This should make tests run a bit - faster, as that I/O overhead has been eliminated. + written to a table, but simply emitted. This elimination of unnecessary I/O + should make tests run a bit faster. * Added missing functions `has_sequence(:schema, :sequence)` mentioned in the docs (Issue #58): From 9f186ea7f3df006af672b68855b1b1010471804d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Mar 2015 13:26:57 -0700 Subject: [PATCH 0857/1195] Update copyright years. --- README.md | 2 +- doc/pgtap.mmd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a028b920b7f7..cb4b3a2d5934 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ full use of its API. It also requires PL/pgSQL. Copyright and License --------------------- -Copyright (c) 2008-2013 David E. Wheeler. Some rights reserved. +Copyright (c) 2008-2015 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index a83bcc43d1f2..275286d30db0 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7643,7 +7643,7 @@ Credits Copyright and License --------------------- -Copyright (c) 2008-2013 David E. Wheeler. Some rights reserved. +Copyright (c) 2008-2015 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is From 105c068f95631a8548145c56a6ac4c9636214c34 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Mar 2015 13:29:05 -0700 Subject: [PATCH 0858/1195] Update multimarkdown. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 902b75da355d..d0c5cb57d026 100644 --- a/Makefile +++ b/Makefile @@ -145,6 +145,6 @@ test: test/setup.sql pg_prove --pset tuples_only=1 $(TESTS) html: - MultiMarkdown.pl doc/pgtap.mmd > doc/pgtap.html + multimarkdown doc/pgtap.mmd > doc/pgtap.html ./tocgen doc/pgtap.html 2> doc/toc.html perl -MPod::Simple::XHTML -E "my \$$p = Pod::Simple::XHTML->new; \$$p->html_header_tags(''); \$$p->strip_verbatim_indent(sub { my \$$l = shift; (my \$$i = \$$l->[0]) =~ s/\\S.*//; \$$i }); \$$p->parse_from_file('`perldoc -l pg_prove`')" > doc/pg_prove.html From 8c2d7a789d51c502ddf1528e9229bf2a56183908 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Mar 2015 13:30:02 -0700 Subject: [PATCH 0859/1195] Timestamp v0.95.0. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index a3fd9c8a4423..3aeaea437fec 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.95.0 +0.95.0 2015-03-20T20:31:41Z --------------------------- * Added materialized-view testing assertions functions: + `materialized_views_are()` From e5410d7279fabba592bea3687fc365bf7fedc73c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 20 Mar 2015 13:34:01 -0700 Subject: [PATCH 0860/1195] Increment to v0.95.1. --- Changes | 3 +++ META.json | 2 +- README.md | 2 +- doc/pgtap.mmd | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 3aeaea437fec..93fb41076194 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Revision history for pgTAP ========================== +0.95.1 +--------------------------- + 0.95.0 2015-03-20T20:31:41Z --------------------------- * Added materialized-view testing assertions functions: diff --git a/META.json b/META.json index a5fb9254d905..bf87bf780ddd 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "0.95.0", + "version": "0.95.1", "maintainer": [ "David E. Wheeler ", "pgTAP List " diff --git a/README.md b/README.md index cb4b3a2d5934..7d94d1d009c1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 0.95.0 +pgTAP 0.95.1 ============ [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 275286d30db0..23340144ed7e 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 0.95.0 +pgTAP 0.95.1 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and From ec66281f071d2fd967778e0a75bbeaf7432903bd Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 23 Mar 2015 10:10:17 -0700 Subject: [PATCH 0861/1195] Disable failing 8.3 and 8.3 testing for now. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index cc452c58c8c8..0fa2c564dbc3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,8 @@ before_install: - wget https://gist.github.com/petere/6023944/raw/pg-travis-test.sh - sudo sh ./apt.postgresql.org.sh env: - - PGVERSION=8.2 - - PGVERSION=8.3 +# - PGVERSION=8.2 +# - PGVERSION=8.3 - PGVERSION=8.4 - PGVERSION=9.0 - PGVERSION=9.1 From b90ac6f747638010bee84fc4f5b104520b36276a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 10 Apr 2015 09:10:04 -0700 Subject: [PATCH 0862/1195] Add PGXN badge. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7d94d1d009c1..d2e9c61ee41a 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ used in the xUnit testing style. For detailed documentation, see the documentation in `doc/pgtap.mmd` or [online](http://pgtap.org/documentation.html "Complete pgTAP Documentation"). +[![PGXN version](https://badge.fury.io/pg/semver.svg)](https://badge.fury.io/pg/semver) [![Build Status](https://travis-ci.org/theory/pgtap.png)](https://travis-ci.org/theory/pgtap) To build it, just do this: From 45a2d7be62db272e33d6d6b41bf4b6010951b6fd Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 10 Apr 2015 09:10:44 -0700 Subject: [PATCH 0863/1195] s/semver/pgtap/ --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d2e9c61ee41a..ab8a64e60121 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ used in the xUnit testing style. For detailed documentation, see the documentation in `doc/pgtap.mmd` or [online](http://pgtap.org/documentation.html "Complete pgTAP Documentation"). -[![PGXN version](https://badge.fury.io/pg/semver.svg)](https://badge.fury.io/pg/semver) +[![PGXN version](https://badge.fury.io/pg/pgtap.svg)](https://badge.fury.io/pg/semver) [![Build Status](https://travis-ci.org/theory/pgtap.png)](https://travis-ci.org/theory/pgtap) To build it, just do this: From 5024e70ef6f3b410ae9e0b998c704cf22b579556 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 10 Apr 2015 21:19:04 -0700 Subject: [PATCH 0864/1195] Missed one. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ab8a64e60121..c4fd057dea7b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ used in the xUnit testing style. For detailed documentation, see the documentation in `doc/pgtap.mmd` or [online](http://pgtap.org/documentation.html "Complete pgTAP Documentation"). -[![PGXN version](https://badge.fury.io/pg/pgtap.svg)](https://badge.fury.io/pg/semver) +[![PGXN version](https://badge.fury.io/pg/pgtap.svg)](https://badge.fury.io/pg/pgtap) [![Build Status](https://travis-ci.org/theory/pgtap.png)](https://travis-ci.org/theory/pgtap) To build it, just do this: From 2fefc26ea00e9f49a3abc14bbba8a97e97565af6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Apr 2015 17:08:21 -0700 Subject: [PATCH 0865/1195] Update pg scripts. --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0fa2c564dbc3..c01af58bb9e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ language: c before_install: - - wget https://gist.github.com/petere/5893799/raw/apt.postgresql.org.sh - - wget https://gist.github.com/petere/6023944/raw/pg-travis-test.sh + - wget https://gist.githubusercontent.com/petere/5893799/raw/apt.postgresql.org.sh + - wget https://gist.githubusercontent.com/petere/6023944/raw/pg-travis-test.sh - sudo sh ./apt.postgresql.org.sh env: -# - PGVERSION=8.2 -# - PGVERSION=8.3 + - PGVERSION=8.2 + - PGVERSION=8.3 - PGVERSION=8.4 - PGVERSION=9.0 - PGVERSION=9.1 From e53af0a20cb0aa64d44cbf8f09c9ac770d47ac8f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 7 May 2015 17:03:50 -0700 Subject: [PATCH 0866/1195] Try removing pgdb-source. As suggested by @meatballhat. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c01af58bb9e0..a7eccd3b85d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ before_install: - wget https://gist.githubusercontent.com/petere/5893799/raw/apt.postgresql.org.sh - wget https://gist.githubusercontent.com/petere/6023944/raw/pg-travis-test.sh - sudo sh ./apt.postgresql.org.sh + - sudo rm -vf /etc/apt/sources.list.d/pgdg-source.list env: - PGVERSION=8.2 - PGVERSION=8.3 From 258ac97f2236bd165ee4b13f5af46a1cfa843197 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Fri, 15 May 2015 19:44:03 -0500 Subject: [PATCH 0867/1195] Remove errant vim file --- sql/.pgtap.sql.in.swp | Bin 16384 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 sql/.pgtap.sql.in.swp diff --git a/sql/.pgtap.sql.in.swp b/sql/.pgtap.sql.in.swp deleted file mode 100644 index f1fbb676b9802a840b7355b268b248b7cf8f5e38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeHOX>c6H6&?p1Ack<*TmgqRsA^UquOypqP$40y5OP#1sU%f_kYD5|0@x4-<8bE!hsy>Mt^mP+IZP4^1jzOE%xh_K3S5pl*S>1?m>~AG3g4 zPAQjR%pag}m*{hz^!YD(uAuU=#Q&4{O;rAt#Q#I$*HZ_gpT86TC6aqx;@>C!QsPHx zz(W2#;>B&nK8b&qcyYC`_7J}zA^$hxN2&b>B>o-Zg?%~?4a&bwytw+uf)Gd{AWo%w;G%USK{xN`Y)XrZ2vyuMf`bQ;(trLh#wc76qNrB@f-YNAel#e58}VX(woCjriC=zN zQ2tiph5!Fp;(t!OnBS9GP<|`%V*mSw#Q%(VvA-R;Bq)Ci@nZdqO8m{li~L}Z#BY(> z?>#-({$}E{ezCGm;%_2eCw}o6LHQer7yI`niNAq(5uf)+{2|nCKYhjnvfrY@U zIDc&geh3T#t-xD2^X&t+178E$f%(9LI7@yP$O2*DG~jWZPp<{m0vR9;Gy!koTzoz7 zIbaBA1(pCO0Ve`)UCKpbz*Iuo9RLyn#97^Y|j4%izYhsL@x@s4yf5odbdboGMa znvTmt%r!T)TAO6jgB{S!MR$T zRf;)t&?=fa-8BXZCQGA`_X$O~qRcii5dM9ArR17=!E#O8D1;hX8`-8!Ol@SbXiqfF zT2&T}rxP<(XOVa(vvO<&3#GJ}){$mCk<}q9*T}k(i5{lwZq~>SnYzv}xTMAk27W$+ z+A2EpMxm|NIx?`dO5%}ZGIC)bnrvdDMxkQ%&!(YOT6fe}655H$=<9D+rkLcu{{5L| zHp|(qv9zqCr`Rm?nqrD3o1sZ9Fs1&p)1JJ0j%xeP)vG1D63HIVZ9^@M?TSA@vDi{t zI?=@jN~MBn6lWeyv7~eV56y7`)YfpAI~r?f4US58OgE(RCe-!g2|sT5pC*Y#p$WB`-3o?$2I&8&(=J0?_~h5S@vw7X~G(eq%Y zXuMNf9qf159OEOZ9!rRig&c)<%^&G2FM@p>*etEBPV=wvM4H7j(63YLiezGGrs7dJ zQ~kJOWfR}06>3tdCHUVt)5+R%mNH7iTzFL?5z`{^+5vLUsqM=bnyVBy8TvvX(Ym!{ zZC^zMPi%%ERjs!4_^hjHV{jxiZ)vTkcM@-5A%$xup6bde4HF$l6rZ%8c7JgT!O5MH z;+Ssb=I)Rp22SC{Q@Eq(al#p?gvZz5I*`6{O^S%_GdPDLG!~zJV3MaRI0wA0AH`*N zt*wW|0FT_>_*w`|ztGmV@9d^t*mZUjAM9bmfa80o!bj@|s;YSE@6Dt{ZAkU3Fk{l_ zg)fnr@Q6B|Bc{%av1m_E#@qec$J5bxrbkOgJ9umqsgbUquO(ARL3O=doi0Q5enG5{ znzn=On+MIAIukmGp7?r9q;y?X+uC@7JAHR0M=u&9rW~Cm#kg&;V{(O@3EMM9c^^-a zi+SBG>4t3^W1(!tw#}l;Bee@6q&lVzjf7U%8JHSIwuM)D)QkJv$ zgbh?&oQn9N#uv8DQ6wli)@7UKs#GUKu3;7(GwgQ=|V&*Dsf9q>)ya{&L{z)skjpYLx2wgF!Txc&Qp`t$!|0Y9~(-4F>j_A;YT z(6I_lD__)+={VInyIdXrF-FJQ9UiONo~v@Iz}*YNj~`~Law9cm8g_OF+mSouT|w{x z;H+X4O8IcjuIPA`bzT{PhF^{_2e&bWoPPnsPl?$Qq7*hUQh<3^F3v=PuS!G{)3F!Z zh7(c;y`v|>m_A}Tju+xYQFX{bI5b1!K|%!T>2IinIsMV_7I2i0M|w2edo=QmvbF*) zviNP0BOmrBw<2zIMhxG8{w%MYHGV)|y%H8_bi zI#0f(#|B#v2IJH+YHapvo?Q+0mr44x5#RkSE*;|)Q z9OU_a+_22QqM{9~W+CakDO92&Bm-fHUMx<$QDpRX z4`cU~@RaqVsh1wf?<%U2VxCKDD*v}<4&f?SLb5m!tQ0QK9~uXIwJQ=!VP```=ee_{ zt|@x&Vse&|_b+Mi;=>y&i>CZL^6^)dNKt!?VUsx(q^>;At~QGwNd1@-ruRy`XTL4( iwXAK0|CZJ^3;vvj5SrTBvO|WA0kuMT`Nd#Wfc-bd@bWDH From 83cb519153801e8baa2c5ec422736db114e15419 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Fri, 15 May 2015 19:45:34 -0500 Subject: [PATCH 0868/1195] Exclude startup, shutdown, etc from being run by runtests(name, text) and runtests(text) --- sql/pgtap.sql.in | 19 ++++++++--- test/expected/do_tap.out | 56 ++++++++++++++++---------------- test/expected/runtests.out | 65 ++++++++++++++++++++++++++++++++++++++ test/sql/do_tap.sql | 16 ++++++++-- test/sql/runtests.sql | 3 ++ test/sql/util.sql | 2 +- 6 files changed, 127 insertions(+), 34 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index f81a0a1d6ae3..de36b2e0b281 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -5916,7 +5916,7 @@ RETURNS SETOF TEXT AS $$ SELECT * FROM check_test( $1, $2, NULL, NULL, NULL, FALSE ); $$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION findfuncs( NAME, TEXT ) +CREATE OR REPLACE FUNCTION findfuncs( NAME, TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( SELECT DISTINCT quote_ident(n.nspname) || '.' || quote_ident(p.proname) AS pname @@ -5924,11 +5924,16 @@ RETURNS TEXT[] AS $$ 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 ) +CREATE OR REPLACE FUNCTION findfuncs( TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( SELECT DISTINCT quote_ident(n.nspname) || '.' || quote_ident(p.proname) AS pname @@ -5936,9 +5941,14 @@ RETURNS TEXT[] AS $$ 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 $$ @@ -6043,6 +6053,7 @@ BEGIN 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]); @@ -6144,7 +6155,7 @@ RETURNS SETOF TEXT AS $$ findfuncs( $1, '^shutdown' ), findfuncs( $1, '^setup' ), findfuncs( $1, '^teardown' ), - findfuncs( $1, $2 ) + findfuncs( $1, $2, '^(startup|shutdown|setup|teardown)' ) ); $$ LANGUAGE sql; @@ -6162,7 +6173,7 @@ RETURNS SETOF TEXT AS $$ findfuncs( '^shutdown' ), findfuncs( '^setup' ), findfuncs( '^teardown' ), - findfuncs( $1 ) + findfuncs( $1, '^(startup|shutdown|setup|teardown)' ) ); $$ LANGUAGE sql; diff --git a/test/expected/do_tap.out b/test/expected/do_tap.out index de8b1b966503..e6c8932f99c3 100644 --- a/test/expected/do_tap.out +++ b/test/expected/do_tap.out @@ -1,40 +1,42 @@ \unset ECHO -1..26 -ok 1 - findfuncs(public, ^test) should work -ok 2 - findfuncs(^test) should work +1..28 +ok 1 - findfuncs(public, ^test, this) should work +ok 2 - findfuncs(public, ^test) should work +ok 3 - findfuncs(^test, this) should work +ok 4 - findfuncs(^test) should work # public."test ident"() -ok 3 - ident -ok 4 - ident 2 +ok 5 - ident +ok 6 - ident 2 # public.testplpgsql() -ok 5 - plpgsql simple -ok 6 - plpgsql simple 2 +ok 7 - plpgsql simple +ok 8 - plpgsql simple 2 # public.testthis() -ok 7 - simple pass -ok 8 - another simple pass +ok 10 - another simple pass +ok 9 - simple pass # public."test ident"() -ok 9 - ident -ok 10 - ident 2 +ok 11 - ident +ok 12 - ident 2 # public.testplpgsql() -ok 11 - plpgsql simple -ok 12 - plpgsql simple 2 +ok 13 - plpgsql simple +ok 14 - plpgsql simple 2 # public.testthis() -ok 13 - simple pass -ok 14 - another simple pass +ok 15 - simple pass +ok 16 - another simple pass # public."test ident"() -ok 15 - ident -ok 16 - ident 2 +ok 17 - ident +ok 18 - ident 2 # public.testplpgsql() -ok 17 - plpgsql simple -ok 18 - plpgsql simple 2 +ok 19 - plpgsql simple +ok 20 - plpgsql simple 2 # public.testthis() -ok 19 - simple pass -ok 20 - another simple pass +ok 21 - simple pass +ok 22 - another simple pass # public."test ident"() -ok 21 - ident -ok 22 - ident 2 +ok 23 - ident +ok 24 - ident 2 # public.testplpgsql() -ok 23 - plpgsql simple -ok 24 - plpgsql simple 2 +ok 25 - plpgsql simple +ok 26 - plpgsql simple 2 # public.testthis() -ok 25 - simple pass -ok 26 - another simple pass +ok 27 - simple pass +ok 28 - another simple pass diff --git a/test/expected/runtests.out b/test/expected/runtests.out index ec7f6df7044d..25c2613a9f0d 100644 --- a/test/expected/runtests.out +++ b/test/expected/runtests.out @@ -64,3 +64,68 @@ ok 9 - shutting down ok 10 - shutting down more 1..10 # Looks like you failed 2 tests of 10 +ok 1 - starting up +ok 2 - starting up some more + # Subtest: whatever."test ident"() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - ident + ok 5 - ident 2 + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 3 - whatever."test ident" + # Subtest: whatever.testplpgsql() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - plpgsql simple + ok 5 - plpgsql simple 2 + ok 6 - Should be a 1 in the test table + ok 7 - teardown + ok 8 - teardown more + 1..8 +ok 4 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: This test should die, but not halt execution +not ok 5 - whatever.testplpgsqldie +# Failed test 5: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - simple pass + ok 5 - another simple pass + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 6 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + not ok 4 - this test intentionally fails + # Failed test 4: "this test intentionally fails" + ok 5 - teardown + ok 6 - teardown more + 1..6 + # Looks like you failed 1 test of 6 +not ok 7 - whatever.testy +# Failed test 7: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - Late test should find nothing in the test table + ok 5 - teardown + ok 6 - teardown more + 1..6 +ok 8 - whatever.testz +ok 9 - shutting down +ok 10 - shutting down more +1..10 +# Looks like you failed 2 tests of 10 diff --git a/test/sql/do_tap.sql b/test/sql/do_tap.sql index fc741f8bd719..a1e17d1b3077 100644 --- a/test/sql/do_tap.sql +++ b/test/sql/do_tap.sql @@ -2,7 +2,7 @@ \i test/setup.sql SET client_min_messages = notice; -SELECT plan(26); +SELECT plan(28); --SELECT * FROM no_plan(); CREATE OR REPLACE FUNCTION public.testthis() RETURNS SETOF TEXT AS $$ @@ -28,11 +28,23 @@ END; $$ LANGUAGE plpgsql; SELECT is( - findfuncs('public', '^test'), + findfuncs('public'::name, '^test', 'this'), + ARRAY[ 'public."test ident"', 'public.testplpgsql'], + 'findfuncs(public, ^test, this) should work' +); + +SELECT is( + findfuncs('public'::name, '^test'), ARRAY[ 'public."test ident"', 'public.testplpgsql', 'public.testthis' ], 'findfuncs(public, ^test) should work' ); +SELECT is( + findfuncs('^test', 'this'), + ARRAY[ 'public."test ident"', 'public.testplpgsql'], + 'findfuncs(^test, this) should work' +); + SELECT is( findfuncs('^test'), ARRAY[ 'public."test ident"', 'public.testplpgsql', 'public.testthis' ], diff --git a/test/sql/runtests.sql b/test/sql/runtests.sql index 4fba128b28fb..627569c352f2 100644 --- a/test/sql/runtests.sql +++ b/test/sql/runtests.sql @@ -87,4 +87,7 @@ $$ LANGUAGE plpgsql; -- Run the actual tests. Yes, it's a one-liner! SELECT * FROM runtests('whatever'::name); +-- Verify that startup, shutdown, etc aren't run as normal tests +SELECT * FROM runtests('whatever'::name, '.*'); + ROLLBACK; diff --git a/test/sql/util.sql b/test/sql/util.sql index ae32d81a66f2..2f46507d1c82 100644 --- a/test/sql/util.sql +++ b/test/sql/util.sql @@ -47,7 +47,7 @@ SELECT matches( ); SELECT is( - findfuncs('pg_catalog', '^abs$'), + findfuncs('pg_catalog'::name, '^abs$'), ARRAY['pg_catalog.abs'], 'findfincs() should return distinct values' ); From 12499d25e5edc23a0a16fc3e596e96be9c6e0ecc Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Wed, 3 Jun 2015 17:05:45 -0500 Subject: [PATCH 0869/1195] Add version upgrade script --- sql/pgtap--0.95.0--0.96.0.sql | 63 +++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 sql/pgtap--0.95.0--0.96.0.sql diff --git a/sql/pgtap--0.95.0--0.96.0.sql b/sql/pgtap--0.95.0--0.96.0.sql new file mode 100644 index 000000000000..bc0acfe17700 --- /dev/null +++ b/sql/pgtap--0.95.0--0.96.0.sql @@ -0,0 +1,63 @@ +CREATE OR REPLACE FUNCTION pgtap_version() +RETURNS NUMERIC AS 'SELECT 0.95;' +LANGUAGE SQL IMMUTABLE; + +DROP FUNCTION findfuncs( NAME, TEXT ); +CREATE OR REPLACE FUNCTION findfuncs( NAME, TEXT, TEXT ) +RETURNS TEXT[] AS $$ + SELECT ARRAY( + SELECT DISTINCT quote_ident(n.nspname) || '.' || quote_ident(p.proname) 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) 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 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( 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; + + From 5e68fcbd7c94d397618a31364589e83ddaf66881 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Jun 2015 17:16:35 -0700 Subject: [PATCH 0870/1195] Document findfuncs() changes. Also fix the version in the upgrade script and remove an unnecessary DROP statement. --- doc/pgtap.mmd | 14 ++++++++++++-- sql/pgtap--0.95.0--0.96.0.sql | 5 +---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 23340144ed7e..7ca9ba0dee4c 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7175,7 +7175,9 @@ in 8.4 and higher. ### `findfuncs()` ### + SELECT findfuncs( :schema, :pattern, :exclude_pattern ); SELECT findfuncs( :schema, :pattern ); + SELECT findfuncs( :pattern, :exclude_pattern ); SELECT findfuncs( :pattern ); **Parameters** @@ -7186,10 +7188,18 @@ in 8.4 and higher. `:pattern` : Regular expression pattern against which to match function names. +`:pattern` +: Regular expression pattern to exclude functions with matching names. + + This function searches the named schema or, if no schema is passed, the search patch, for all functions that match the regular expression pattern. The -functions it finds are returned as an array of text values, with each value -consisting of the schema name, a dot, and the function name. For example: +optional exclude regular expression pattern can be used to prevent matchin +startup/setup/teardown/shutdown functions. + +The functions it finds are returned as an array of text values, with each +value consisting of the schema name, a dot, and the function name. For +example: SELECT findfuncs('tests', '^test); findfuncs diff --git a/sql/pgtap--0.95.0--0.96.0.sql b/sql/pgtap--0.95.0--0.96.0.sql index bc0acfe17700..f60749947e69 100644 --- a/sql/pgtap--0.95.0--0.96.0.sql +++ b/sql/pgtap--0.95.0--0.96.0.sql @@ -1,8 +1,7 @@ CREATE OR REPLACE FUNCTION pgtap_version() -RETURNS NUMERIC AS 'SELECT 0.95;' +RETURNS NUMERIC AS 'SELECT 0.96;' LANGUAGE SQL IMMUTABLE; -DROP FUNCTION findfuncs( NAME, TEXT ); CREATE OR REPLACE FUNCTION findfuncs( NAME, TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( @@ -59,5 +58,3 @@ RETURNS SETOF TEXT AS $$ findfuncs( $1, '^(startup|shutdown|setup|teardown)' ) ); $$ LANGUAGE sql; - - From d78d9091eed56d85479df15d8ff73aa5174bb88d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Jun 2015 17:19:34 -0700 Subject: [PATCH 0871/1195] Note new findfuncs() param in Changes. --- Changes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changes b/Changes index 93fb41076194..c908b6c9dc24 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,9 @@ Revision history for pgTAP 0.95.1 --------------------------- +* Added an optional `:exclude_pattern` parameter to `findfuncs()` to prevent + matching of some functions. This is useful to prevent matching + startup/setup/teardown/shutdown functions. Patch from Jim Nasby. 0.95.0 2015-03-20T20:31:41Z --------------------------- From 96d6d480801ab96ee533f344114bdaadb920c675 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Jun 2015 17:24:22 -0700 Subject: [PATCH 0872/1195] Note the change to runtests(), too. --- Changes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changes b/Changes index c908b6c9dc24..bb5d93c82a06 100644 --- a/Changes +++ b/Changes @@ -6,6 +6,9 @@ Revision history for pgTAP * Added an optional `:exclude_pattern` parameter to `findfuncs()` to prevent matching of some functions. This is useful to prevent matching startup/setup/teardown/shutdown functions. Patch from Jim Nasby. +* Changed `runtests()` to use the new `:exclude_pattern` to prevent the + running of startup/setup/teardown/shutdown functions as test functions. + Patch from Jim Nasby. 0.95.0 2015-03-20T20:31:41Z --------------------------- From 921ebd8013515fd284b7d1fbd384938282f1fac5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 24 Jun 2015 12:16:08 -0700 Subject: [PATCH 0873/1195] Fix pasto. --- doc/pgtap.mmd | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 7ca9ba0dee4c..24ef62dd68cd 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4325,14 +4325,14 @@ specified column or columns are not a foreign key. ### `fk_ok()` ### - SELECT fk_ok( :fk_schema, :fk_table, :fk_columns, :pk_schema, :pk_table, :pk_columns, :description ); - SELECT fk_ok( :fk_schema, :fk_table, :fk_columns, :fk_schema, :pk_table, :pk_columns ); - SELECT fk_ok( :fk_table, :fk_columns, :pk_table, :pk_columns, :description ); - SELECT fk_ok( :fk_table, :fk_columns, :pk_table, :pk_columns ); - SELECT fk_ok( :fk_schema, :fk_table, :fk_column, :pk_schema, :pk_table, :pk_column, :description ); - SELECT fk_ok( :fk_schema, :fk_table, :fk_column, :pk_schema, :pk_table, :pk_column ); - SELECT fk_ok( :fk_table, :fk_column, :pk_table, :pk_column, :description ); - SELECT fk_ok( :fk_table, :fk_column, :pk_table, :pk_column ); + SELECT fk_ok( :fk_schema, :fk_table, :fk_columns, :pk_schema, :pk_table, :pk_columns, :description ); + SELECT fk_ok( :fk_schema, :fk_table, :fk_columns, :pk_schema, :pk_table, :pk_columns ); + SELECT fk_ok( :fk_table, :fk_columns, :pk_table, :pk_columns, :description ); + SELECT fk_ok( :fk_table, :fk_columns, :pk_table, :pk_columns ); + SELECT fk_ok( :fk_schema, :fk_table, :fk_column, :pk_schema, :pk_table, :pk_column, :description ); + SELECT fk_ok( :fk_schema, :fk_table, :fk_column, :pk_schema, :pk_table, :pk_column ); + SELECT fk_ok( :fk_table, :fk_column, :pk_table, :pk_column, :description ); + SELECT fk_ok( :fk_table, :fk_column, :pk_table, :pk_column ); **Parameters** From 62434312edae9726a4472018be8b926bcac147df Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 11 Aug 2015 10:36:25 -0700 Subject: [PATCH 0874/1195] Fix handling of case-sensitive names in database_privs_are(). --- Changes | 2 ++ sql/pgtap--0.95.0--0.96.0.sql | 19 +++++++++++++++++++ sql/pgtap.sql.in | 2 +- test/sql/privs.sql | 4 ++-- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index bb5d93c82a06..dffe863a30bc 100644 --- a/Changes +++ b/Changes @@ -9,6 +9,8 @@ Revision history for pgTAP * Changed `runtests()` to use the new `:exclude_pattern` to prevent the running of startup/setup/teardown/shutdown functions as test functions. Patch from Jim Nasby. +* Fixed a bug where database_privs_are() did not work with a case-sensitive + database name. Thanks to Ingo for the report! 0.95.0 2015-03-20T20:31:41Z --------------------------- diff --git a/sql/pgtap--0.95.0--0.96.0.sql b/sql/pgtap--0.95.0--0.96.0.sql index f60749947e69..cd66167abd60 100644 --- a/sql/pgtap--0.95.0--0.96.0.sql +++ b/sql/pgtap--0.95.0--0.96.0.sql @@ -58,3 +58,22 @@ RETURNS SETOF TEXT AS $$ findfuncs( $1, '^(startup|shutdown|setup|teardown)' ) ); $$ LANGUAGE sql; + +-- 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index de36b2e0b281..635b9ab04c74 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -8658,7 +8658,7 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION database_privs_are ( NAME, NAME, NAME[], TEXT ) RETURNS TEXT AS $$ DECLARE - grants TEXT[] := _get_db_privs( $2, quote_ident($1) ); + grants TEXT[] := _get_db_privs( $2, $1::TEXT ); BEGIN IF grants[1] = 'invalid_catalog_name' THEN RETURN ok(FALSE, $4) || E'\n' || diag( diff --git a/test/sql/privs.sql b/test/sql/privs.sql index b6a7a437e1c0..768e6349726b 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -178,7 +178,7 @@ SELECT * FROM check_test( true, 'database_privs_are(db, role, privs, desc)', 'Role ' || current_user || ' should be granted ' - || array_to_string(_db_privs(), ', ') || ' on database ' || current_database(), + || array_to_string(_db_privs(), ', ') || ' on database ' || quote_ident( current_database() ), '' ); @@ -229,7 +229,7 @@ SELECT * FROM check_test( database_privs_are( current_database(), '__noone', '{}'::text[] ), false, 'database_privs_are(db, non-role, no privs)', - 'Role __noone should be granted no privileges on database ' || current_database(), + 'Role __noone should be granted no privileges on database ' || quote_ident( current_database() ), ' Role __noone does not exist' ); From 218d42c44f9f28b276becab7b4486c3e7a5ff11f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 11 Aug 2015 11:09:28 -0700 Subject: [PATCH 0875/1195] Fix case-sensitive object handling in other priv functions. --- Changes | 5 +- sql/pgtap--0.95.0--0.96.0.sql | 75 ++++ sql/pgtap.sql.in | 9 +- test/expected/privs.out | 676 +++++++++++++++++++--------------- test/sql/privs.sql | 333 ++++++++++++++++- 5 files changed, 774 insertions(+), 324 deletions(-) diff --git a/Changes b/Changes index dffe863a30bc..16562aeff7b6 100644 --- a/Changes +++ b/Changes @@ -9,8 +9,9 @@ Revision history for pgTAP * Changed `runtests()` to use the new `:exclude_pattern` to prevent the running of startup/setup/teardown/shutdown functions as test functions. Patch from Jim Nasby. -* Fixed a bug where database_privs_are() did not work with a case-sensitive - database name. Thanks to Ingo for the report! +* Fixed `database_privs_are()`, `schema_privs_are()`, `tablespace_privs_are()` + `fdw_privs_are()`, and `fdw_privs_are()` to properly handle case-sensitive + object names. Thanks to Ingo for the report! 0.95.0 2015-03-20T20:31:41Z --------------------------- diff --git a/sql/pgtap--0.95.0--0.96.0.sql b/sql/pgtap--0.95.0--0.96.0.sql index cd66167abd60..e6915dda95bf 100644 --- a/sql/pgtap--0.95.0--0.96.0.sql +++ b/sql/pgtap--0.95.0--0.96.0.sql @@ -77,3 +77,78 @@ BEGIN RETURN _assets_are('privileges', grants, $3, $4); 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; + +-- 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; + +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; + +-- 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 635b9ab04c74..ef6c3a99b71f 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -8831,7 +8831,7 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION schema_privs_are ( NAME, NAME, NAME[], TEXT ) RETURNS TEXT AS $$ DECLARE - grants TEXT[] := _get_schema_privs( $2, quote_ident($1) ); + grants TEXT[] := _get_schema_privs( $2, $1::TEXT ); BEGIN IF grants[1] = 'invalid_schema_name' THEN RETURN ok(FALSE, $4) || E'\n' || diag( @@ -8879,7 +8879,7 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION tablespace_privs_are ( NAME, NAME, NAME[], TEXT ) RETURNS TEXT AS $$ DECLARE - grants TEXT[] := _get_tablespaceprivs( $2, quote_ident($1) ); + grants TEXT[] := _get_tablespaceprivs( $2, $1::TEXT ); BEGIN IF grants[1] = 'undefined_tablespace' THEN RETURN ok(FALSE, $4) || E'\n' || diag( @@ -9075,6 +9075,7 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; +-- _get_col_privs(user, table, column) CREATE OR REPLACE FUNCTION _get_col_privs(NAME, TEXT, NAME) RETURNS TEXT[] AS $$ DECLARE @@ -9188,7 +9189,7 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION fdw_privs_are ( NAME, NAME, NAME[], TEXT ) RETURNS TEXT AS $$ DECLARE - grants TEXT[] := _get_fdw_privs( $2, quote_ident($1) ); + grants TEXT[] := _get_fdw_privs( $2, $1::TEXT ); BEGIN IF grants[1] = 'undefined_fdw' THEN RETURN ok(FALSE, $4) || E'\n' || diag( @@ -9256,7 +9257,7 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION server_privs_are ( NAME, NAME, NAME[], TEXT ) RETURNS TEXT AS $$ DECLARE - grants TEXT[] := _get_server_privs( $2, quote_ident($1) ); + grants TEXT[] := _get_server_privs( $2, $1::TEXT ); BEGIN IF grants[1] = 'undefined_server' THEN RETURN ok(FALSE, $4) || E'\n' || diag( diff --git a/test/expected/privs.out b/test/expected/privs.out index b892a12c366f..c5b34d4ab379 100644 --- a/test/expected/privs.out +++ b/test/expected/privs.out @@ -1,310 +1,374 @@ \unset ECHO -1..308 +1..372 ok 1 - table_privs_are(sch, tab, role, privs, desc) should pass ok 2 - table_privs_are(sch, tab, role, privs, desc) should have the proper description ok 3 - table_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 4 - table_privs_are(sch, tab, role, privs) should pass -ok 5 - table_privs_are(sch, tab, role, privs) should have the proper description -ok 6 - table_privs_are(sch, tab, role, privs) should have the proper diagnostics -ok 7 - table_privs_are(tab, role, privs, desc) should pass -ok 8 - table_privs_are(tab, role, privs, desc) should have the proper description -ok 9 - table_privs_are(tab, role, privs, desc) should have the proper diagnostics -ok 10 - table_privs_are(tab, role, privs) should pass -ok 11 - table_privs_are(tab, role, privs) should have the proper description -ok 12 - table_privs_are(tab, role, privs) should have the proper diagnostics -ok 13 - table_privs_are(sch, tab, role, some privs, desc) should fail -ok 14 - table_privs_are(sch, tab, role, some privs, desc) should have the proper description -ok 15 - table_privs_are(sch, tab, role, some privs, desc) should have the proper diagnostics -ok 16 - table_privs_are(tab, role, some privs, desc) should fail -ok 17 - table_privs_are(tab, role, some privs, desc) should have the proper description -ok 18 - table_privs_are(tab, role, some privs, desc) should have the proper diagnostics -ok 19 - table_privs_are(sch, tab, other, privs, desc) should fail -ok 20 - table_privs_are(sch, tab, other, privs, desc) should have the proper description -ok 21 - table_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics -ok 22 - table_privs_are(sch, tab, other, privs, desc) should pass -ok 23 - table_privs_are(sch, tab, other, privs, desc) should have the proper description -ok 24 - table_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics -ok 25 - table_privs_are(sch, tab, role, privs, desc) should fail -ok 26 - table_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 27 - table_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 28 - table_privs_are(sch, tab, role, privs, desc) should fail -ok 29 - table_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 30 - table_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 31 - table_privs_are(sch, tab, role, no privs) should fail -ok 32 - table_privs_are(sch, tab, role, no privs) should have the proper description -ok 33 - table_privs_are(sch, tab, role, no privs) should have the proper diagnostics -ok 34 - table_privs_are(tab, role, no privs) should fail -ok 35 - table_privs_are(tab, role, no privs) should have the proper description -ok 36 - table_privs_are(tab, role, no privs) should have the proper diagnostics -ok 37 - database_privs_are(db, role, privs, desc) should pass -ok 38 - database_privs_are(db, role, privs, desc) should have the proper description -ok 39 - database_privs_are(db, role, privs, desc) should have the proper diagnostics -ok 40 - database_privs_are(db, role, privs, desc) should pass -ok 41 - database_privs_are(db, role, privs, desc) should have the proper description -ok 42 - database_privs_are(db, role, privs, desc) should have the proper diagnostics -ok 43 - database_privs_are(non-db, role, privs, desc) should fail -ok 44 - database_privs_are(non-db, role, privs, desc) should have the proper description -ok 45 - database_privs_are(non-db, role, privs, desc) should have the proper diagnostics -ok 46 - database_privs_are(db, non-role, privs, desc) should fail -ok 47 - database_privs_are(db, non-role, privs, desc) should have the proper description -ok 48 - database_privs_are(db, non-role, privs, desc) should have the proper diagnostics -ok 49 - database_privs_are(db, ungranted, privs, desc) should fail -ok 50 - database_privs_are(db, ungranted, privs, desc) should have the proper description -ok 51 - database_privs_are(db, ungranted, privs, desc) should have the proper diagnostics -ok 52 - database_privs_are(db, ungranted, privs, desc) should fail -ok 53 - database_privs_are(db, ungranted, privs, desc) should have the proper description -ok 54 - database_privs_are(db, ungranted, privs, desc) should have the proper diagnostics -ok 55 - database_privs_are(db, non-role, no privs) should fail -ok 56 - database_privs_are(db, non-role, no privs) should have the proper description -ok 57 - database_privs_are(db, non-role, no privs) should have the proper diagnostics -ok 58 - function_privs_are(sch, func, args, role, privs, desc) should pass -ok 59 - function_privs_are(sch, func, args, role, privs, desc) should have the proper description -ok 60 - function_privs_are(sch, func, args, role, privs, desc) should have the proper diagnostics -ok 61 - function_privs_are(sch, func, args, role, privs) should pass -ok 62 - function_privs_are(sch, func, args, role, privs) should have the proper description -ok 63 - function_privs_are(func, args, role, privs, desc) should pass -ok 64 - function_privs_are(func, args, role, privs, desc) should have the proper description -ok 65 - function_privs_are(func, args, role, privs, desc) should have the proper diagnostics -ok 66 - function_privs_are(func, args, role, privs) should pass -ok 67 - function_privs_are(func, args, role, privs) should have the proper description -ok 68 - function_privs_are(sch, non-func, args, role, privs, desc) should fail -ok 69 - function_privs_are(sch, non-func, args, role, privs, desc) should have the proper description -ok 70 - function_privs_are(sch, non-func, args, role, privs, desc) should have the proper diagnostics -ok 71 - function_privs_are(non-func, args, role, privs, desc) should fail -ok 72 - function_privs_are(non-func, args, role, privs, desc) should have the proper description -ok 73 - function_privs_are(non-func, args, role, privs, desc) should have the proper diagnostics -ok 74 - function_privs_are(sch, func, args, noone, privs, desc) should fail -ok 75 - function_privs_are(sch, func, args, noone, privs, desc) should have the proper description -ok 76 - function_privs_are(sch, func, args, noone, privs, desc) should have the proper diagnostics -ok 77 - function_privs_are(func, args, noone, privs, desc) should fail -ok 78 - function_privs_are(func, args, noone, privs, desc) should have the proper description -ok 79 - function_privs_are(func, args, noone, privs, desc) should have the proper diagnostics -ok 80 - function_privs_are(sch, func, args, other, privs, desc) should pass -ok 81 - function_privs_are(sch, func, args, other, privs, desc) should have the proper description -ok 82 - function_privs_are(sch, func, args, other, privs, desc) should have the proper diagnostics -ok 83 - function_privs_are(func, args, other, privs, desc) should pass -ok 84 - function_privs_are(func, args, other, privs, desc) should have the proper description -ok 85 - function_privs_are(func, args, other, privs, desc) should have the proper diagnostics -ok 86 - function_privs_are(sch, func, args, unpriv, privs, desc) should fail -ok 87 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper description -ok 88 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper diagnostics -ok 89 - function_privs_are(func, args, unpriv, privs, desc) should fail -ok 90 - function_privs_are(func, args, unpriv, privs, desc) should have the proper description -ok 91 - function_privs_are(func, args, unpriv, privs, desc) should have the proper diagnostics -ok 92 - function_privs_are(sch, func, args, unpriv, empty, desc) should pass -ok 93 - function_privs_are(sch, func, args, unpriv, empty, desc) should have the proper description -ok 94 - function_privs_are(sch, func, args, unpriv, empty, desc) should have the proper diagnostics -ok 95 - function_privs_are(sch, func, args, unpriv, empty) should pass -ok 96 - function_privs_are(sch, func, args, unpriv, empty) should have the proper description -ok 97 - function_privs_are(func, args, unpriv, empty) should pass -ok 98 - function_privs_are(func, args, unpriv, empty) should have the proper description -ok 99 - function_privs_are(sch, func, args, unpriv, privs, desc) should fail -ok 100 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper description -ok 101 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper diagnostics -ok 102 - function_privs_are(func, args, unpriv, privs, desc) should fail -ok 103 - function_privs_are(func, args, unpriv, privs, desc) should have the proper description -ok 104 - function_privs_are(func, args, unpriv, privs, desc) should have the proper diagnostics -ok 105 - long function signature should pass -ok 106 - long function signature should have the proper description -ok 107 - long function signature should have the proper diagnostics -ok 108 - language_privs_are(lang, role, privs, desc) should pass -ok 109 - language_privs_are(lang, role, privs, desc) should have the proper description -ok 110 - language_privs_are(lang, role, privs, desc) should have the proper diagnostics -ok 111 - language_privs_are(lang, role, privs, desc) should pass -ok 112 - language_privs_are(lang, role, privs, desc) should have the proper description -ok 113 - language_privs_are(lang, role, privs, desc) should have the proper diagnostics -ok 114 - language_privs_are(non-lang, role, privs, desc) should fail -ok 115 - language_privs_are(non-lang, role, privs, desc) should have the proper description -ok 116 - language_privs_are(non-lang, role, privs, desc) should have the proper diagnostics -ok 117 - language_privs_are(lang, non-role, privs, desc) should fail -ok 118 - language_privs_are(lang, non-role, privs, desc) should have the proper description -ok 119 - language_privs_are(lang, non-role, privs, desc) should have the proper diagnostics -ok 120 - language_privs_are(lang, ungranted, privs, desc) should fail -ok 121 - language_privs_are(lang, ungranted, privs, desc) should have the proper description -ok 122 - language_privs_are(lang, ungranted, privs, desc) should have the proper diagnostics -ok 123 - language_privs_are(lang, role, no privs) should pass -ok 124 - language_privs_are(lang, role, no privs) should have the proper description -ok 125 - language_privs_are(lang, role, no privs) should have the proper diagnostics -ok 126 - schema_privs_are(schema, role, privs, desc) should pass -ok 127 - schema_privs_are(schema, role, privs, desc) should have the proper description -ok 128 - schema_privs_are(schema, role, privs, desc) should have the proper diagnostics -ok 129 - schema_privs_are(schema, role, privs, desc) should pass -ok 130 - schema_privs_are(schema, role, privs, desc) should have the proper description -ok 131 - schema_privs_are(schema, role, privs, desc) should have the proper diagnostics -ok 132 - schema_privs_are(non-schema, role, privs, desc) should fail -ok 133 - schema_privs_are(non-schema, role, privs, desc) should have the proper description -ok 134 - schema_privs_are(non-schema, role, privs, desc) should have the proper diagnostics -ok 135 - schema_privs_are(schema, non-role, privs, desc) should fail -ok 136 - schema_privs_are(schema, non-role, privs, desc) should have the proper description -ok 137 - schema_privs_are(schema, non-role, privs, desc) should have the proper diagnostics -ok 138 - schema_privs_are(schema, ungranted, privs, desc) should fail -ok 139 - schema_privs_are(schema, ungranted, privs, desc) should have the proper description -ok 140 - schema_privs_are(schema, ungranted, privs, desc) should have the proper diagnostics -ok 141 - schema_privs_are(schema, ungranted, privs, desc) should fail -ok 142 - schema_privs_are(schema, ungranted, privs, desc) should have the proper description -ok 143 - schema_privs_are(schema, ungranted, privs, desc) should have the proper diagnostics -ok 144 - schema_privs_are(schema, non-role, no privs) should fail -ok 145 - schema_privs_are(schema, non-role, no privs) should have the proper description -ok 146 - schema_privs_are(schema, non-role, no privs) should have the proper diagnostics -ok 147 - tablespace_privs_are(tablespace, role, privs, desc) should pass -ok 148 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper description -ok 149 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper diagnostics -ok 150 - tablespace_privs_are(tablespace, role, privs, desc) should pass -ok 151 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper description -ok 152 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper diagnostics -ok 153 - tablespace_privs_are(non-tablespace, role, privs, desc) should fail -ok 154 - tablespace_privs_are(non-tablespace, role, privs, desc) should have the proper description -ok 155 - tablespace_privs_are(non-tablespace, role, privs, desc) should have the proper diagnostics -ok 156 - tablespace_privs_are(tablespace, non-role, privs, desc) should fail -ok 157 - tablespace_privs_are(tablespace, non-role, privs, desc) should have the proper description -ok 158 - tablespace_privs_are(tablespace, non-role, privs, desc) should have the proper diagnostics -ok 159 - tablespace_privs_are(tablespace, ungranted, privs, desc) should fail -ok 160 - tablespace_privs_are(tablespace, ungranted, privs, desc) should have the proper description -ok 161 - tablespace_privs_are(tablespace, ungranted, privs, desc) should have the proper diagnostics -ok 162 - tablespace_privs_are(tablespace, role, no privs) should pass -ok 163 - tablespace_privs_are(tablespace, role, no privs) should have the proper description -ok 164 - tablespace_privs_are(tablespace, role, no privs) should have the proper diagnostics -ok 165 - sequence_privs_are(sch, seq, role, privs, desc) should pass -ok 166 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description -ok 167 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics -ok 168 - sequence_privs_are(sch, seq, role, privs) should pass -ok 169 - sequence_privs_are(sch, seq, role, privs) should have the proper description -ok 170 - sequence_privs_are(sch, seq, role, privs) should have the proper diagnostics -ok 171 - sequence_privs_are(seq, role, privs, desc) should pass -ok 172 - sequence_privs_are(seq, role, privs, desc) should have the proper description -ok 173 - sequence_privs_are(seq, role, privs, desc) should have the proper diagnostics -ok 174 - sequence_privs_are(seq, role, privs) should pass -ok 175 - sequence_privs_are(seq, role, privs) should have the proper description -ok 176 - sequence_privs_are(seq, role, privs) should have the proper diagnostics -ok 177 - sequence_privs_are(sch, seq, role, some privs, desc) should fail -ok 178 - sequence_privs_are(sch, seq, role, some privs, desc) should have the proper description -ok 179 - sequence_privs_are(sch, seq, role, some privs, desc) should have the proper diagnostics -ok 180 - sequence_privs_are(seq, role, some privs, desc) should fail -ok 181 - sequence_privs_are(seq, role, some privs, desc) should have the proper description -ok 182 - sequence_privs_are(seq, role, some privs, desc) should have the proper diagnostics -ok 183 - sequence_privs_are(sch, seq, other, privs, desc) should fail -ok 184 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper description -ok 185 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper diagnostics -ok 186 - sequence_privs_are(sch, seq, other, privs, desc) should pass -ok 187 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper description -ok 188 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper diagnostics -ok 189 - sequence_privs_are(sch, seq, role, privs, desc) should fail -ok 190 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description -ok 191 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics -ok 192 - sequence_privs_are(sch, seq, role, privs, desc) should fail -ok 193 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description -ok 194 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics -ok 195 - sequence_privs_are(sch, seq, role, no privs) should fail -ok 196 - sequence_privs_are(sch, seq, role, no privs) should have the proper description -ok 197 - sequence_privs_are(sch, seq, role, no privs) should have the proper diagnostics -ok 198 - sequence_privs_are(seq, role, no privs) should fail -ok 199 - sequence_privs_are(seq, role, no privs) should have the proper description -ok 200 - sequence_privs_are(seq, role, no privs) should have the proper diagnostics -ok 201 - any_column_privs_are(sch, tab, role, privs, desc) should pass -ok 202 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 203 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 204 - any_column_privs_are(sch, tab, role, privs) should pass -ok 205 - any_column_privs_are(sch, tab, role, privs) should have the proper description -ok 206 - any_column_privs_are(sch, tab, role, privs) should have the proper diagnostics -ok 207 - any_column_privs_are(tab, role, privs, desc) should pass -ok 208 - any_column_privs_are(tab, role, privs, desc) should have the proper description -ok 209 - any_column_privs_are(tab, role, privs, desc) should have the proper diagnostics -ok 210 - any_column_privs_are(tab, role, privs) should pass -ok 211 - any_column_privs_are(tab, role, privs) should have the proper description -ok 212 - any_column_privs_are(tab, role, privs) should have the proper diagnostics -ok 213 - any_column_privs_are(sch, tab, role, some privs, desc) should fail -ok 214 - any_column_privs_are(sch, tab, role, some privs, desc) should have the proper description -ok 215 - any_column_privs_are(sch, tab, role, some privs, desc) should have the proper diagnostics -ok 216 - any_column_privs_are(tab, role, some privs, desc) should fail -ok 217 - any_column_privs_are(tab, role, some privs, desc) should have the proper description -ok 218 - any_column_privs_are(tab, role, some privs, desc) should have the proper diagnostics -ok 219 - any_column_privs_are(sch, tab, other, privs, desc) should fail -ok 220 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper description -ok 221 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics -ok 222 - any_column_privs_are(sch, tab, other, privs, desc) should pass -ok 223 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper description -ok 224 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics -ok 225 - any_column_privs_are(sch, tab, role, privs, desc) should fail -ok 226 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 227 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 228 - any_column_privs_are(sch, tab, role, privs, desc) should fail -ok 229 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 230 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 231 - any_column_privs_are(sch, tab, role, no privs) should fail -ok 232 - any_column_privs_are(sch, tab, role, no privs) should have the proper description -ok 233 - any_column_privs_are(sch, tab, role, no privs) should have the proper diagnostics -ok 234 - any_column_privs_are(tab, role, no privs) should fail -ok 235 - any_column_privs_are(tab, role, no privs) should have the proper description -ok 236 - any_column_privs_are(tab, role, no privs) should have the proper diagnostics -ok 237 - column_privs_are(sch, tab, role, privs, desc) should pass -ok 238 - column_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 239 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 240 - column_privs_are(sch, tab, role, privs) should pass -ok 241 - column_privs_are(sch, tab, role, privs) should have the proper description -ok 242 - column_privs_are(sch, tab, role, privs) should have the proper diagnostics -ok 243 - column_privs_are(tab, role, privs, desc) should pass -ok 244 - column_privs_are(tab, role, privs, desc) should have the proper description -ok 245 - column_privs_are(tab, role, privs, desc) should have the proper diagnostics -ok 246 - column_privs_are(tab, role, privs) should pass -ok 247 - column_privs_are(tab, role, privs) should have the proper description -ok 248 - column_privs_are(tab, role, privs) should have the proper diagnostics -ok 249 - column_privs_are(sch, tab, role, some privs, desc) should fail -ok 250 - column_privs_are(sch, tab, role, some privs, desc) should have the proper description -ok 251 - column_privs_are(sch, tab, role, some privs, desc) should have the proper diagnostics -ok 252 - column_privs_are(tab, role, some privs, desc) should fail -ok 253 - column_privs_are(tab, role, some privs, desc) should have the proper description -ok 254 - column_privs_are(tab, role, some privs, desc) should have the proper diagnostics -ok 255 - column_privs_are(sch, tab, other, privs, desc) should fail -ok 256 - column_privs_are(sch, tab, other, privs, desc) should have the proper description -ok 257 - column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics -ok 258 - column_privs_are(sch, tab, other, privs, desc) should pass -ok 259 - column_privs_are(sch, tab, other, privs, desc) should have the proper description -ok 260 - column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics -ok 261 - column_privs_are(sch, tab, role, privs, desc) should fail -ok 262 - column_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 263 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 264 - column_privs_are(sch, tab, role, privs, desc) should fail -ok 265 - column_privs_are(sch, tab, role, privs, desc) should have the proper description -ok 266 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics -ok 267 - column_privs_are(sch, tab, role, no privs) should fail -ok 268 - column_privs_are(sch, tab, role, no privs) should have the proper description -ok 269 - column_privs_are(sch, tab, role, no privs) should have the proper diagnostics -ok 270 - column_privs_are(tab, role, no privs) should fail -ok 271 - column_privs_are(tab, role, no privs) should have the proper description -ok 272 - column_privs_are(tab, role, no privs) should have the proper diagnostics -ok 273 - fdw_privs_are(fdw, role, privs, desc) should pass -ok 274 - fdw_privs_are(fdw, role, privs, desc) should have the proper description -ok 275 - fdw_privs_are(fdw, role, privs, desc) should have the proper diagnostics -ok 276 - fdw_privs_are(fdw, role, privs, desc) should pass -ok 277 - fdw_privs_are(fdw, role, privs, desc) should have the proper description -ok 278 - fdw_privs_are(fdw, role, privs, desc) should have the proper diagnostics -ok 279 - fdw_privs_are(non-fdw, role, privs, desc) should fail -ok 280 - fdw_privs_are(non-fdw, role, privs, desc) should have the proper description -ok 281 - fdw_privs_are(non-fdw, role, privs, desc) should have the proper diagnostics -ok 282 - fdw_privs_are(fdw, non-role, privs, desc) should fail -ok 283 - fdw_privs_are(fdw, non-role, privs, desc) should have the proper description -ok 284 - fdw_privs_are(fdw, non-role, privs, desc) should have the proper diagnostics -ok 285 - fdw_privs_are(fdw, ungranted, privs, desc) should fail -ok 286 - fdw_privs_are(fdw, ungranted, privs, desc) should have the proper description -ok 287 - fdw_privs_are(fdw, ungranted, privs, desc) should have the proper diagnostics -ok 288 - fdw_privs_are(fdw, role, no privs) should pass -ok 289 - fdw_privs_are(fdw, role, no privs) should have the proper description -ok 290 - fdw_privs_are(fdw, role, no privs) should have the proper diagnostics -ok 291 - server_privs_are(server, role, privs, desc) should pass -ok 292 - server_privs_are(server, role, privs, desc) should have the proper description -ok 293 - server_privs_are(server, role, privs, desc) should have the proper diagnostics -ok 294 - server_privs_are(server, role, privs, desc) should pass -ok 295 - server_privs_are(server, role, privs, desc) should have the proper description -ok 296 - server_privs_are(server, role, privs, desc) should have the proper diagnostics -ok 297 - server_privs_are(non-server, role, privs, desc) should fail -ok 298 - server_privs_are(non-server, role, privs, desc) should have the proper description -ok 299 - server_privs_are(non-server, role, privs, desc) should have the proper diagnostics -ok 300 - server_privs_are(server, non-role, privs, desc) should fail -ok 301 - server_privs_are(server, non-role, privs, desc) should have the proper description -ok 302 - server_privs_are(server, non-role, privs, desc) should have the proper diagnostics -ok 303 - server_privs_are(server, ungranted, privs, desc) should fail -ok 304 - server_privs_are(server, ungranted, privs, desc) should have the proper description -ok 305 - server_privs_are(server, ungranted, privs, desc) should have the proper diagnostics -ok 306 - server_privs_are(server, role, no privs) should pass -ok 307 - server_privs_are(server, role, no privs) should have the proper description -ok 308 - server_privs_are(server, role, no privs) should have the proper diagnostics +ok 4 - table_privs_are(LOL, ATable, role, privs, desc) should pass +ok 5 - table_privs_are(LOL, ATable, role, privs, desc) should have the proper description +ok 6 - table_privs_are(LOL, ATable, role, privs, desc) should have the proper diagnostics +ok 7 - table_privs_are(sch, tab, role, privs) should pass +ok 8 - table_privs_are(sch, tab, role, privs) should have the proper description +ok 9 - table_privs_are(sch, tab, role, privs) should have the proper diagnostics +ok 10 - table_privs_are(LOL, ATable, role, privs) should pass +ok 11 - table_privs_are(LOL, ATable, role, privs) should have the proper description +ok 12 - table_privs_are(LOL, ATable, role, privs) should have the proper diagnostics +ok 13 - table_privs_are(tab, role, privs, desc) should pass +ok 14 - table_privs_are(tab, role, privs, desc) should have the proper description +ok 15 - table_privs_are(tab, role, privs, desc) should have the proper diagnostics +ok 16 - table_privs_are(ATable, role, privs, desc) should pass +ok 17 - table_privs_are(ATable, role, privs, desc) should have the proper description +ok 18 - table_privs_are(ATable, role, privs, desc) should have the proper diagnostics +ok 19 - table_privs_are(tab, role, privs) should pass +ok 20 - table_privs_are(tab, role, privs) should have the proper description +ok 21 - table_privs_are(tab, role, privs) should have the proper diagnostics +ok 22 - table_privs_are(ATable, role, privs) should pass +ok 23 - table_privs_are(ATable, role, privs) should have the proper description +ok 24 - table_privs_are(ATable, role, privs) should have the proper diagnostics +ok 25 - table_privs_are(sch, tab, role, some privs, desc) should fail +ok 26 - table_privs_are(sch, tab, role, some privs, desc) should have the proper description +ok 27 - table_privs_are(sch, tab, role, some privs, desc) should have the proper diagnostics +ok 28 - table_privs_are(tab, role, some privs, desc) should fail +ok 29 - table_privs_are(tab, role, some privs, desc) should have the proper description +ok 30 - table_privs_are(tab, role, some privs, desc) should have the proper diagnostics +ok 31 - table_privs_are(sch, tab, other, privs, desc) should fail +ok 32 - table_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 33 - table_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics +ok 34 - table_privs_are(sch, tab, other, privs, desc) should pass +ok 35 - table_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 36 - table_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics +ok 37 - table_privs_are(sch, tab, role, privs, desc) should fail +ok 38 - table_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 39 - table_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 40 - table_privs_are(sch, tab, role, privs, desc) should fail +ok 41 - table_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 42 - table_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 43 - table_privs_are(sch, tab, role, no privs) should fail +ok 44 - table_privs_are(sch, tab, role, no privs) should have the proper description +ok 45 - table_privs_are(sch, tab, role, no privs) should have the proper diagnostics +ok 46 - table_privs_are(tab, role, no privs) should fail +ok 47 - table_privs_are(tab, role, no privs) should have the proper description +ok 48 - table_privs_are(tab, role, no privs) should have the proper diagnostics +ok 49 - database_privs_are(db, role, privs, desc) should pass +ok 50 - database_privs_are(db, role, privs, desc) should have the proper description +ok 51 - database_privs_are(db, role, privs, desc) should have the proper diagnostics +ok 52 - database_privs_are(db, role, privs, desc) should pass +ok 53 - database_privs_are(db, role, privs, desc) should have the proper description +ok 54 - database_privs_are(db, role, privs, desc) should have the proper diagnostics +ok 55 - database_privs_are(non-db, role, privs, desc) should fail +ok 56 - database_privs_are(non-db, role, privs, desc) should have the proper description +ok 57 - database_privs_are(non-db, role, privs, desc) should have the proper diagnostics +ok 58 - database_privs_are(db, non-role, privs, desc) should fail +ok 59 - database_privs_are(db, non-role, privs, desc) should have the proper description +ok 60 - database_privs_are(db, non-role, privs, desc) should have the proper diagnostics +ok 61 - database_privs_are(db, ungranted, privs, desc) should fail +ok 62 - database_privs_are(db, ungranted, privs, desc) should have the proper description +ok 63 - database_privs_are(db, ungranted, privs, desc) should have the proper diagnostics +ok 64 - database_privs_are(db, ungranted, privs, desc) should fail +ok 65 - database_privs_are(db, ungranted, privs, desc) should have the proper description +ok 66 - database_privs_are(db, ungranted, privs, desc) should have the proper diagnostics +ok 67 - database_privs_are(db, non-role, no privs) should fail +ok 68 - database_privs_are(db, non-role, no privs) should have the proper description +ok 69 - database_privs_are(db, non-role, no privs) should have the proper diagnostics +ok 70 - function_privs_are(sch, func, args, role, privs, desc) should pass +ok 71 - function_privs_are(sch, func, args, role, privs, desc) should have the proper description +ok 72 - function_privs_are(sch, func, args, role, privs, desc) should have the proper diagnostics +ok 73 - function_privs_are(LOL, DoIt, role, privs, desc) should pass +ok 74 - function_privs_are(LOL, DoIt, role, privs, desc) should have the proper description +ok 75 - function_privs_are(LOL, DoIt, role, privs, desc) should have the proper diagnostics +ok 76 - function_privs_are(sch, func, args, role, privs) should pass +ok 77 - function_privs_are(sch, func, args, role, privs) should have the proper description +ok 78 - function_privs_are(LOL, DoIt, args, role, privs) should pass +ok 79 - function_privs_are(LOL, DoIt, args, role, privs) should have the proper description +ok 80 - function_privs_are(func, args, role, privs, desc) should pass +ok 81 - function_privs_are(func, args, role, privs, desc) should have the proper description +ok 82 - function_privs_are(func, args, role, privs, desc) should have the proper diagnostics +ok 83 - function_privs_are(DoIt, args, role, privs, desc) should pass +ok 84 - function_privs_are(DoIt, args, role, privs, desc) should have the proper description +ok 85 - function_privs_are(DoIt, args, role, privs, desc) should have the proper diagnostics +ok 86 - function_privs_are(func, args, role, privs) should pass +ok 87 - function_privs_are(func, args, role, privs) should have the proper description +ok 88 - function_privs_are(DoIt, args, role, privs) should pass +ok 89 - function_privs_are(DoIt, args, role, privs) should have the proper description +ok 90 - function_privs_are(sch, non-func, args, role, privs, desc) should fail +ok 91 - function_privs_are(sch, non-func, args, role, privs, desc) should have the proper description +ok 92 - function_privs_are(sch, non-func, args, role, privs, desc) should have the proper diagnostics +ok 93 - function_privs_are(non-func, args, role, privs, desc) should fail +ok 94 - function_privs_are(non-func, args, role, privs, desc) should have the proper description +ok 95 - function_privs_are(non-func, args, role, privs, desc) should have the proper diagnostics +ok 96 - function_privs_are(sch, func, args, noone, privs, desc) should fail +ok 97 - function_privs_are(sch, func, args, noone, privs, desc) should have the proper description +ok 98 - function_privs_are(sch, func, args, noone, privs, desc) should have the proper diagnostics +ok 99 - function_privs_are(func, args, noone, privs, desc) should fail +ok 100 - function_privs_are(func, args, noone, privs, desc) should have the proper description +ok 101 - function_privs_are(func, args, noone, privs, desc) should have the proper diagnostics +ok 102 - function_privs_are(sch, func, args, other, privs, desc) should pass +ok 103 - function_privs_are(sch, func, args, other, privs, desc) should have the proper description +ok 104 - function_privs_are(sch, func, args, other, privs, desc) should have the proper diagnostics +ok 105 - function_privs_are(func, args, other, privs, desc) should pass +ok 106 - function_privs_are(func, args, other, privs, desc) should have the proper description +ok 107 - function_privs_are(func, args, other, privs, desc) should have the proper diagnostics +ok 108 - function_privs_are(sch, func, args, unpriv, privs, desc) should fail +ok 109 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper description +ok 110 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper diagnostics +ok 111 - function_privs_are(func, args, unpriv, privs, desc) should fail +ok 112 - function_privs_are(func, args, unpriv, privs, desc) should have the proper description +ok 113 - function_privs_are(func, args, unpriv, privs, desc) should have the proper diagnostics +ok 114 - function_privs_are(sch, func, args, unpriv, empty, desc) should pass +ok 115 - function_privs_are(sch, func, args, unpriv, empty, desc) should have the proper description +ok 116 - function_privs_are(sch, func, args, unpriv, empty, desc) should have the proper diagnostics +ok 117 - function_privs_are(sch, func, args, unpriv, empty) should pass +ok 118 - function_privs_are(sch, func, args, unpriv, empty) should have the proper description +ok 119 - function_privs_are(func, args, unpriv, empty) should pass +ok 120 - function_privs_are(func, args, unpriv, empty) should have the proper description +ok 121 - function_privs_are(sch, func, args, unpriv, privs, desc) should fail +ok 122 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper description +ok 123 - function_privs_are(sch, func, args, unpriv, privs, desc) should have the proper diagnostics +ok 124 - function_privs_are(func, args, unpriv, privs, desc) should fail +ok 125 - function_privs_are(func, args, unpriv, privs, desc) should have the proper description +ok 126 - function_privs_are(func, args, unpriv, privs, desc) should have the proper diagnostics +ok 127 - long function signature should pass +ok 128 - long function signature should have the proper description +ok 129 - long function signature should have the proper diagnostics +ok 130 - language_privs_are(lang, role, privs, desc) should pass +ok 131 - language_privs_are(lang, role, privs, desc) should have the proper description +ok 132 - language_privs_are(lang, role, privs, desc) should have the proper diagnostics +ok 133 - language_privs_are(lang, role, privs, desc) should pass +ok 134 - language_privs_are(lang, role, privs, desc) should have the proper description +ok 135 - language_privs_are(lang, role, privs, desc) should have the proper diagnostics +ok 136 - language_privs_are(non-lang, role, privs, desc) should fail +ok 137 - language_privs_are(non-lang, role, privs, desc) should have the proper description +ok 138 - language_privs_are(non-lang, role, privs, desc) should have the proper diagnostics +ok 139 - language_privs_are(lang, non-role, privs, desc) should fail +ok 140 - language_privs_are(lang, non-role, privs, desc) should have the proper description +ok 141 - language_privs_are(lang, non-role, privs, desc) should have the proper diagnostics +ok 142 - language_privs_are(lang, ungranted, privs, desc) should fail +ok 143 - language_privs_are(lang, ungranted, privs, desc) should have the proper description +ok 144 - language_privs_are(lang, ungranted, privs, desc) should have the proper diagnostics +ok 145 - language_privs_are(lang, role, no privs) should pass +ok 146 - language_privs_are(lang, role, no privs) should have the proper description +ok 147 - language_privs_are(lang, role, no privs) should have the proper diagnostics +ok 148 - schema_privs_are(schema, role, privs, desc) should pass +ok 149 - schema_privs_are(schema, role, privs, desc) should have the proper description +ok 150 - schema_privs_are(schema, role, privs, desc) should have the proper diagnostics +ok 151 - schema_privs_are(LOL, role, privs, desc) should pass +ok 152 - schema_privs_are(LOL, role, privs, desc) should have the proper description +ok 153 - schema_privs_are(LOL, role, privs, desc) should have the proper diagnostics +ok 154 - schema_privs_are(schema, role, privs, desc) should pass +ok 155 - schema_privs_are(schema, role, privs, desc) should have the proper description +ok 156 - schema_privs_are(schema, role, privs, desc) should have the proper diagnostics +ok 157 - schema_privs_are(LOL, role, privs, desc) should pass +ok 158 - schema_privs_are(LOL, role, privs, desc) should have the proper description +ok 159 - schema_privs_are(LOL, role, privs, desc) should have the proper diagnostics +ok 160 - schema_privs_are(non-schema, role, privs, desc) should fail +ok 161 - schema_privs_are(non-schema, role, privs, desc) should have the proper description +ok 162 - schema_privs_are(non-schema, role, privs, desc) should have the proper diagnostics +ok 163 - schema_privs_are(schema, non-role, privs, desc) should fail +ok 164 - schema_privs_are(schema, non-role, privs, desc) should have the proper description +ok 165 - schema_privs_are(schema, non-role, privs, desc) should have the proper diagnostics +ok 166 - schema_privs_are(schema, ungranted, privs, desc) should fail +ok 167 - schema_privs_are(schema, ungranted, privs, desc) should have the proper description +ok 168 - schema_privs_are(schema, ungranted, privs, desc) should have the proper diagnostics +ok 169 - schema_privs_are(schema, ungranted, privs, desc) should fail +ok 170 - schema_privs_are(schema, ungranted, privs, desc) should have the proper description +ok 171 - schema_privs_are(schema, ungranted, privs, desc) should have the proper diagnostics +ok 172 - schema_privs_are(schema, non-role, no privs) should fail +ok 173 - schema_privs_are(schema, non-role, no privs) should have the proper description +ok 174 - schema_privs_are(schema, non-role, no privs) should have the proper diagnostics +ok 175 - tablespace_privs_are(tablespace, role, privs, desc) should pass +ok 176 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper description +ok 177 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper diagnostics +ok 178 - tablespace_privs_are(tablespace, role, privs, desc) should pass +ok 179 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper description +ok 180 - tablespace_privs_are(tablespace, role, privs, desc) should have the proper diagnostics +ok 181 - tablespace_privs_are(non-tablespace, role, privs, desc) should fail +ok 182 - tablespace_privs_are(non-tablespace, role, privs, desc) should have the proper description +ok 183 - tablespace_privs_are(non-tablespace, role, privs, desc) should have the proper diagnostics +ok 184 - tablespace_privs_are(tablespace, non-role, privs, desc) should fail +ok 185 - tablespace_privs_are(tablespace, non-role, privs, desc) should have the proper description +ok 186 - tablespace_privs_are(tablespace, non-role, privs, desc) should have the proper diagnostics +ok 187 - tablespace_privs_are(tablespace, ungranted, privs, desc) should fail +ok 188 - tablespace_privs_are(tablespace, ungranted, privs, desc) should have the proper description +ok 189 - tablespace_privs_are(tablespace, ungranted, privs, desc) should have the proper diagnostics +ok 190 - tablespace_privs_are(tablespace, role, no privs) should pass +ok 191 - tablespace_privs_are(tablespace, role, no privs) should have the proper description +ok 192 - tablespace_privs_are(tablespace, role, no privs) should have the proper diagnostics +ok 193 - sequence_privs_are(sch, seq, role, privs, desc) should pass +ok 194 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description +ok 195 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics +ok 196 - sequence_privs_are(LOL, ASeq, role, privs, desc) should pass +ok 197 - sequence_privs_are(LOL, ASeq, role, privs, desc) should have the proper description +ok 198 - sequence_privs_are(LOL, ASeq, role, privs, desc) should have the proper diagnostics +ok 199 - sequence_privs_are(sch, seq, role, privs) should pass +ok 200 - sequence_privs_are(sch, seq, role, privs) should have the proper description +ok 201 - sequence_privs_are(sch, seq, role, privs) should have the proper diagnostics +ok 202 - sequence_privs_are(sch, seq, role, privs) should pass +ok 203 - sequence_privs_are(sch, seq, role, privs) should have the proper description +ok 204 - sequence_privs_are(sch, seq, role, privs) should have the proper diagnostics +ok 205 - sequence_privs_are(seq, role, privs, desc) should pass +ok 206 - sequence_privs_are(seq, role, privs, desc) should have the proper description +ok 207 - sequence_privs_are(seq, role, privs, desc) should have the proper diagnostics +ok 208 - sequence_privs_are(ASeq, role, privs, desc) should pass +ok 209 - sequence_privs_are(ASeq, role, privs, desc) should have the proper description +ok 210 - sequence_privs_are(ASeq, role, privs, desc) should have the proper diagnostics +ok 211 - sequence_privs_are(seq, role, privs) should pass +ok 212 - sequence_privs_are(seq, role, privs) should have the proper description +ok 213 - sequence_privs_are(seq, role, privs) should have the proper diagnostics +ok 214 - sequence_privs_are(seq, role, privs) should pass +ok 215 - sequence_privs_are(seq, role, privs) should have the proper description +ok 216 - sequence_privs_are(seq, role, privs) should have the proper diagnostics +ok 217 - sequence_privs_are(sch, seq, role, some privs, desc) should fail +ok 218 - sequence_privs_are(sch, seq, role, some privs, desc) should have the proper description +ok 219 - sequence_privs_are(sch, seq, role, some privs, desc) should have the proper diagnostics +ok 220 - sequence_privs_are(seq, role, some privs, desc) should fail +ok 221 - sequence_privs_are(seq, role, some privs, desc) should have the proper description +ok 222 - sequence_privs_are(seq, role, some privs, desc) should have the proper diagnostics +ok 223 - sequence_privs_are(sch, seq, other, privs, desc) should fail +ok 224 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper description +ok 225 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper diagnostics +ok 226 - sequence_privs_are(sch, seq, other, privs, desc) should pass +ok 227 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper description +ok 228 - sequence_privs_are(sch, seq, other, privs, desc) should have the proper diagnostics +ok 229 - sequence_privs_are(sch, seq, role, privs, desc) should fail +ok 230 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description +ok 231 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics +ok 232 - sequence_privs_are(sch, seq, role, privs, desc) should fail +ok 233 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper description +ok 234 - sequence_privs_are(sch, seq, role, privs, desc) should have the proper diagnostics +ok 235 - sequence_privs_are(sch, seq, role, no privs) should fail +ok 236 - sequence_privs_are(sch, seq, role, no privs) should have the proper description +ok 237 - sequence_privs_are(sch, seq, role, no privs) should have the proper diagnostics +ok 238 - sequence_privs_are(seq, role, no privs) should fail +ok 239 - sequence_privs_are(seq, role, no privs) should have the proper description +ok 240 - sequence_privs_are(seq, role, no privs) should have the proper diagnostics +ok 241 - any_column_privs_are(sch, tab, role, privs, desc) should pass +ok 242 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 243 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 244 - any_column_privs_are(sch, tab, role, privs) should pass +ok 245 - any_column_privs_are(sch, tab, role, privs) should have the proper description +ok 246 - any_column_privs_are(sch, tab, role, privs) should have the proper diagnostics +ok 247 - any_column_privs_are(tab, role, privs, desc) should pass +ok 248 - any_column_privs_are(tab, role, privs, desc) should have the proper description +ok 249 - any_column_privs_are(tab, role, privs, desc) should have the proper diagnostics +ok 250 - any_column_privs_are(tab, role, privs) should pass +ok 251 - any_column_privs_are(tab, role, privs) should have the proper description +ok 252 - any_column_privs_are(tab, role, privs) should have the proper diagnostics +ok 253 - any_column_privs_are(sch, tab, role, some privs, desc) should fail +ok 254 - any_column_privs_are(sch, tab, role, some privs, desc) should have the proper description +ok 255 - any_column_privs_are(sch, tab, role, some privs, desc) should have the proper diagnostics +ok 256 - any_column_privs_are(tab, role, some privs, desc) should fail +ok 257 - any_column_privs_are(tab, role, some privs, desc) should have the proper description +ok 258 - any_column_privs_are(tab, role, some privs, desc) should have the proper diagnostics +ok 259 - any_column_privs_are(sch, tab, other, privs, desc) should fail +ok 260 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 261 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics +ok 262 - any_column_privs_are(sch, tab, other, privs, desc) should pass +ok 263 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 264 - any_column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics +ok 265 - any_column_privs_are(sch, tab, role, privs, desc) should fail +ok 266 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 267 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 268 - any_column_privs_are(sch, tab, role, privs, desc) should fail +ok 269 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 270 - any_column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 271 - any_column_privs_are(sch, tab, role, no privs) should fail +ok 272 - any_column_privs_are(sch, tab, role, no privs) should have the proper description +ok 273 - any_column_privs_are(sch, tab, role, no privs) should have the proper diagnostics +ok 274 - any_column_privs_are(tab, role, no privs) should fail +ok 275 - any_column_privs_are(tab, role, no privs) should have the proper description +ok 276 - any_column_privs_are(tab, role, no privs) should have the proper diagnostics +ok 277 - column_privs_are(sch, tab, col, role, privs, desc) should pass +ok 278 - column_privs_are(sch, tab, col, role, privs, desc) should have the proper description +ok 279 - column_privs_are(sch, tab, col, role, privs, desc) should have the proper diagnostics +ok 280 - column_privs_are(LOL, ATable, AColumn role, privs, desc) should pass +ok 281 - column_privs_are(LOL, ATable, AColumn role, privs, desc) should have the proper description +ok 282 - column_privs_are(LOL, ATable, AColumn role, privs, desc) should have the proper diagnostics +ok 283 - column_privs_are(sch, tab, col, role, privs) should pass +ok 284 - column_privs_are(sch, tab, col, role, privs) should have the proper description +ok 285 - column_privs_are(sch, tab, col, role, privs) should have the proper diagnostics +ok 286 - column_privs_are(LOL, ATable, AColumn, role, privs) should pass +ok 287 - column_privs_are(LOL, ATable, AColumn, role, privs) should have the proper description +ok 288 - column_privs_are(LOL, ATable, AColumn, role, privs) should have the proper diagnostics +ok 289 - column_privs_are(tab, col, role, privs, desc) should pass +ok 290 - column_privs_are(tab, col, role, privs, desc) should have the proper description +ok 291 - column_privs_are(tab, col, role, privs, desc) should have the proper diagnostics +ok 292 - column_privs_are(tab, col, role, privs, desc) should pass +ok 293 - column_privs_are(tab, col, role, privs, desc) should have the proper description +ok 294 - column_privs_are(tab, col, role, privs, desc) should have the proper diagnostics +ok 295 - column_privs_are(tab, col, role, privs) should pass +ok 296 - column_privs_are(tab, col, role, privs) should have the proper description +ok 297 - column_privs_are(tab, col, role, privs) should have the proper diagnostics +ok 298 - column_privs_are(tab, col, role, privs) should pass +ok 299 - column_privs_are(tab, col, role, privs) should have the proper description +ok 300 - column_privs_are(tab, col, role, privs) should have the proper diagnostics +ok 301 - column_privs_are(sch, tab, role, some privs, desc) should fail +ok 302 - column_privs_are(sch, tab, role, some privs, desc) should have the proper description +ok 303 - column_privs_are(sch, tab, role, some privs, desc) should have the proper diagnostics +ok 304 - column_privs_are(tab, role, some privs, desc) should fail +ok 305 - column_privs_are(tab, role, some privs, desc) should have the proper description +ok 306 - column_privs_are(tab, role, some privs, desc) should have the proper diagnostics +ok 307 - column_privs_are(sch, tab, other, privs, desc) should fail +ok 308 - column_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 309 - column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics +ok 310 - column_privs_are(sch, tab, other, privs, desc) should pass +ok 311 - column_privs_are(sch, tab, other, privs, desc) should have the proper description +ok 312 - column_privs_are(sch, tab, other, privs, desc) should have the proper diagnostics +ok 313 - column_privs_are(sch, tab, role, privs, desc) should fail +ok 314 - column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 315 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 316 - column_privs_are(sch, tab, role, privs, desc) should fail +ok 317 - column_privs_are(sch, tab, role, privs, desc) should have the proper description +ok 318 - column_privs_are(sch, tab, role, privs, desc) should have the proper diagnostics +ok 319 - column_privs_are(sch, tab, role, no privs) should fail +ok 320 - column_privs_are(sch, tab, role, no privs) should have the proper description +ok 321 - column_privs_are(sch, tab, role, no privs) should have the proper diagnostics +ok 322 - column_privs_are(tab, role, no privs) should fail +ok 323 - column_privs_are(tab, role, no privs) should have the proper description +ok 324 - column_privs_are(tab, role, no privs) should have the proper diagnostics +ok 325 - fdw_privs_are(fdw, role, privs, desc) should pass +ok 326 - fdw_privs_are(fdw, role, privs, desc) should have the proper description +ok 327 - fdw_privs_are(fdw, role, privs, desc) should have the proper diagnostics +ok 328 - fdw_privs_are(SomeFDW, role, privs, desc) should pass +ok 329 - fdw_privs_are(SomeFDW, role, privs, desc) should have the proper description +ok 330 - fdw_privs_are(SomeFDW, role, privs, desc) should have the proper diagnostics +ok 331 - fdw_privs_are(fdw, role, privs, desc) should pass +ok 332 - fdw_privs_are(fdw, role, privs, desc) should have the proper description +ok 333 - fdw_privs_are(fdw, role, privs, desc) should have the proper diagnostics +ok 334 - fdw_privs_are(SomeFDW, role, privs, desc) should pass +ok 335 - fdw_privs_are(SomeFDW, role, privs, desc) should have the proper description +ok 336 - fdw_privs_are(SomeFDW, role, privs, desc) should have the proper diagnostics +ok 337 - fdw_privs_are(non-fdw, role, privs, desc) should fail +ok 338 - fdw_privs_are(non-fdw, role, privs, desc) should have the proper description +ok 339 - fdw_privs_are(non-fdw, role, privs, desc) should have the proper diagnostics +ok 340 - fdw_privs_are(fdw, non-role, privs, desc) should fail +ok 341 - fdw_privs_are(fdw, non-role, privs, desc) should have the proper description +ok 342 - fdw_privs_are(fdw, non-role, privs, desc) should have the proper diagnostics +ok 343 - fdw_privs_are(fdw, ungranted, privs, desc) should fail +ok 344 - fdw_privs_are(fdw, ungranted, privs, desc) should have the proper description +ok 345 - fdw_privs_are(fdw, ungranted, privs, desc) should have the proper diagnostics +ok 346 - fdw_privs_are(fdw, role, no privs) should pass +ok 347 - fdw_privs_are(fdw, role, no privs) should have the proper description +ok 348 - fdw_privs_are(fdw, role, no privs) should have the proper diagnostics +ok 349 - server_privs_are(server, role, privs, desc) should pass +ok 350 - server_privs_are(server, role, privs, desc) should have the proper description +ok 351 - server_privs_are(server, role, privs, desc) should have the proper diagnostics +ok 352 - server_privs_are(SomeServer, role, privs, desc) should pass +ok 353 - server_privs_are(SomeServer, role, privs, desc) should have the proper description +ok 354 - server_privs_are(SomeServer, role, privs, desc) should have the proper diagnostics +ok 355 - server_privs_are(server, role, privs, desc) should pass +ok 356 - server_privs_are(server, role, privs, desc) should have the proper description +ok 357 - server_privs_are(server, role, privs, desc) should have the proper diagnostics +ok 358 - server_privs_are(SomeSrver, role, privs, desc) should pass +ok 359 - server_privs_are(SomeSrver, role, privs, desc) should have the proper description +ok 360 - server_privs_are(SomeSrver, role, privs, desc) should have the proper diagnostics +ok 361 - server_privs_are(non-server, role, privs, desc) should fail +ok 362 - server_privs_are(non-server, role, privs, desc) should have the proper description +ok 363 - server_privs_are(non-server, role, privs, desc) should have the proper diagnostics +ok 364 - server_privs_are(server, non-role, privs, desc) should fail +ok 365 - server_privs_are(server, non-role, privs, desc) should have the proper description +ok 366 - server_privs_are(server, non-role, privs, desc) should have the proper diagnostics +ok 367 - server_privs_are(server, ungranted, privs, desc) should fail +ok 368 - server_privs_are(server, ungranted, privs, desc) should have the proper description +ok 369 - server_privs_are(server, ungranted, privs, desc) should have the proper diagnostics +ok 370 - server_privs_are(server, role, no privs) should pass +ok 371 - server_privs_are(server, role, no privs) should have the proper description +ok 372 - server_privs_are(server, role, no privs) should have the proper diagnostics diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 768e6349726b..576defe88db4 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -1,23 +1,26 @@ \unset ECHO \i test/setup.sql -SELECT plan(308); +SELECT plan(372); --SELECT * FROM no_plan(); SET client_min_messages = warning; CREATE SCHEMA ha; CREATE TABLE ha.sometab(id INT); CREATE SEQUENCE ha.someseq; --- Include the new schema in the path. +CREATE SCHEMA "LOL"; +CREATE TABLE "LOL"."ATable"("AColumn" INT); +CREATE SEQUENCE "LOL"."ASeq"; +-- Include the new schemas in the path. CREATE OR REPLACE FUNCTION set_search_path() returns setof text as $$ BEGIN IF pg_version_num() < 80200 THEN - EXECUTE 'SET search_path = ha, ' + EXECUTE 'SET search_path = ha, "LOL", ' || regexp_replace(current_setting('search_path'), '[$][^,]+,', '') || ', pg_catalog'; RETURN; ELSE - EXECUTE 'SET search_path = ha, ' || current_setting('search_path') || ', pg_catalog'; + EXECUTE 'SET search_path = ha, "LOL", ' || current_setting('search_path') || ', pg_catalog'; END IF; END; @@ -36,6 +39,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + table_privs_are( 'LOL', 'ATable', current_user, _table_privs(), 'whatever' ), + true, + 'table_privs_are(LOL, ATable, role, privs, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( table_privs_are( 'ha', 'sometab', current_user, _table_privs() ), true, @@ -45,6 +56,15 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + table_privs_are( 'LOL', 'ATable', current_user, _table_privs() ), + true, + 'table_privs_are(LOL, ATable, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(_table_privs(), ', ') || ' on table "LOL"."ATable"' , + '' +); + SELECT * FROM check_test( table_privs_are( 'sometab', current_user, _table_privs(), 'whatever' ), true, @@ -53,6 +73,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + table_privs_are( 'ATable', current_user, _table_privs(), 'whatever' ), + true, + 'table_privs_are(ATable, role, privs, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( table_privs_are( 'sometab', current_user, _table_privs() ), true, @@ -62,6 +90,15 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + table_privs_are( 'ATable', current_user, _table_privs() ), + true, + 'table_privs_are(ATable, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(_table_privs(), ', ') || ' on table "ATable"' , + '' +); + CREATE OR REPLACE FUNCTION run_extra_fails() RETURNS SETOF TEXT LANGUAGE plpgsql AS $$ DECLARE allowed_privs TEXT[]; @@ -236,6 +273,7 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test function_privs_are(). CREATE OR REPLACE FUNCTION public.foo(int, text) RETURNS VOID LANGUAGE SQL AS ''; +CREATE OR REPLACE FUNCTION "LOL"."DoIt"(int, text) RETURNS VOID LANGUAGE SQL AS ''; SELECT * FROM check_test( function_privs_are( @@ -248,6 +286,17 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + function_privs_are( + 'LOL', 'DoIt', ARRAY['integer', 'text'], + current_user, ARRAY['EXECUTE'], 'whatever' + ), + true, + 'function_privs_are(LOL, DoIt, role, privs, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( function_privs_are( 'public', 'foo', ARRAY['integer', 'text'], @@ -259,6 +308,17 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + function_privs_are( + 'LOL', 'DoIt', ARRAY['integer', 'text'], + current_user, ARRAY['EXECUTE'] + ), + true, + 'function_privs_are(LOL, DoIt, args, role, privs)', + 'Role ' || current_user || ' should be granted EXECUTE on function "LOL"."DoIt"(integer, text)' + '' +); + SELECT * FROM check_test( function_privs_are( 'foo', ARRAY['integer', 'text'], @@ -270,6 +330,17 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + function_privs_are( + 'DoIt', ARRAY['integer', 'text'], + current_user, ARRAY['EXECUTE'], 'whatever' + ), + true, + 'function_privs_are(DoIt, args, role, privs, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( function_privs_are( 'foo', ARRAY['integer', 'text'], @@ -281,6 +352,17 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + function_privs_are( + 'DoIt', ARRAY['integer', 'text'], + current_user, ARRAY['EXECUTE'] + ), + true, + 'function_privs_are(DoIt, args, role, privs)', + 'Role ' || current_user || ' should be granted EXECUTE on function "DoIt"(integer, text)' + '' +); + -- Try a nonexistent funtion. SELECT * FROM check_test( function_privs_are( @@ -521,6 +603,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + schema_privs_are( 'LOL', current_user, ARRAY['CREATE', 'USAGE'], 'whatever' ), + true, + 'schema_privs_are(LOL, role, privs, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( schema_privs_are( current_schema(), current_user, ARRAY['CREATE', 'USAGE'] ), true, @@ -530,6 +620,15 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + schema_privs_are( 'LOL', current_user, ARRAY['CREATE', 'USAGE'] ), + true, + 'schema_privs_are(LOL, role, privs, desc)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['CREATE', 'USAGE'], ', ') || ' on schema "LOL"', + '' +); + -- Try nonexistent schema. SELECT * FROM check_test( schema_privs_are( '__nonesuch', current_user, ARRAY['CREATE', 'USAGE'], 'whatever' ), @@ -660,6 +759,16 @@ BEGIN '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + sequence_privs_are( 'LOL', 'ASeq', current_user, ARRAY[ + 'USAGE', 'SELECT', 'UPDATE' + ], 'whatever' ), + true, + 'sequence_privs_are(LOL, ASeq, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( sequence_privs_are( 'ha', 'someseq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'] ), true, @@ -670,6 +779,16 @@ BEGIN '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + sequence_privs_are( 'LOL', 'ASeq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'] ), + true, + 'sequence_privs_are(sch, seq, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['USAGE', 'SELECT', 'UPDATE'], ', ') + || ' on sequence "LOL"."ASeq"' , + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( sequence_privs_are( 'someseq', current_user, ARRAY[ 'USAGE', 'SELECT', 'UPDATE' @@ -680,6 +799,16 @@ BEGIN '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + sequence_privs_are( 'ASeq', current_user, ARRAY[ + 'USAGE', 'SELECT', 'UPDATE' + ], 'whatever' ), + true, + 'sequence_privs_are(ASeq, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( sequence_privs_are( 'someseq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'] ), true, @@ -690,6 +819,16 @@ BEGIN '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + sequence_privs_are( 'ASeq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'] ), + true, + 'sequence_privs_are(seq, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['USAGE', 'SELECT', 'UPDATE'], ', ') + || ' on sequence "ASeq"' , + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + -- Test sequence failure. allowed_privs := ARRAY['USAGE', 'SELECT', 'UPDATE']; last_index := array_upper(allowed_privs, 1); @@ -790,6 +929,14 @@ BEGIN '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'sequence_privs_are(LOL, ASeq, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( pass('whatever'), true, @@ -798,6 +945,14 @@ BEGIN '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'sequence_privs_are(LOL, ASeq, role, privs)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( pass('whatever'), true, @@ -806,6 +961,14 @@ BEGIN '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'sequence_privs_are(ASeq, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( pass('whatever'), true, @@ -814,6 +977,14 @@ BEGIN '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'sequence_privs_are(ASeq, role, privs)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( fail('whatever'), false, @@ -1153,7 +1324,17 @@ BEGIN 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' ], 'whatever' ), true, - 'column_privs_are(sch, tab, role, privs, desc)', + 'column_privs_are(sch, tab, col, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + column_privs_are( 'LOL', 'ATable', 'AColumn', current_user, ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ], 'whatever' ), + true, + 'column_privs_are(LOL, ATable, AColumn role, privs, desc)', 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1163,19 +1344,41 @@ BEGIN 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' ] ), true, - 'column_privs_are(sch, tab, role, privs)', + 'column_privs_are(sch, tab, col, role, privs)', 'Role ' || current_user || ' should be granted ' || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') || ' on column ha.sometab.id' , '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + column_privs_are( 'LOL', 'ATable', 'AColumn', current_user, ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ] ), + true, + 'column_privs_are(LOL, ATable, AColumn, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') + || ' on column "LOL"."ATable"."AColumn"' , + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( column_privs_are( 'sometab', 'id', current_user, ARRAY[ 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' ], 'whatever' ), true, - 'column_privs_are(tab, role, privs, desc)', + 'column_privs_are(tab, col, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + column_privs_are( 'ATable', 'AColumn', current_user, ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ], 'whatever' ), + true, + 'column_privs_are(tab, col, role, privs, desc)', 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1185,13 +1388,25 @@ BEGIN 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' ] ), true, - 'column_privs_are(tab, role, privs)', + 'column_privs_are(tab, col, role, privs)', 'Role ' || current_user || ' should be granted ' || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') || ' on column sometab.id' , '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + column_privs_are( 'ATable', 'AColumn', current_user, ARRAY[ + 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' + ] ), + true, + 'column_privs_are(tab, col, role, privs)', + 'Role ' || current_user || ' should be granted ' + || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') + || ' on column "ATable"."AColumn"' , + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + -- Test table failure. allowed_privs := ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE']; last_index := array_upper(allowed_privs, 1); @@ -1288,7 +1503,23 @@ BEGIN FOR tap IN SELECT * FROM check_test( pass('whatever'), true, - 'column_privs_are(sch, tab, role, privs, desc)', + 'column_privs_are(sch, tab, col, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'column_privs_are(LOL, ATable, AColumn, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'column_privs_are(sch, tab, col, role, privs)', 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1296,7 +1527,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( pass('whatever'), true, - 'column_privs_are(sch, tab, role, privs)', + 'column_privs_are(LOL, ATable, AColumn, role, privs)', 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1304,7 +1535,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( pass('whatever'), true, - 'column_privs_are(tab, role, privs, desc)', + 'column_privs_are(tab, col, role, privs, desc)', 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1312,7 +1543,23 @@ BEGIN FOR tap IN SELECT * FROM check_test( pass('whatever'), true, - 'column_privs_are(tab, role, privs)', + 'column_privs_are(LOL, ATable, AColumn, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'column_privs_are(tab, col, role, privs)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'column_privs_are(LOL, ATable, AColumn, role, privs)', 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1399,6 +1646,7 @@ DECLARE BEGIN IF pg_version_num() >= 80400 THEN EXECUTE 'CREATE FOREIGN DATA WRAPPER dummy;'; + EXECUTE 'CREATE FOREIGN DATA WRAPPER "SomeFDW";'; FOR tap IN SELECT * FROM check_test( fdw_privs_are( 'dummy', current_user, '{USAGE}', 'whatever' ), @@ -1408,6 +1656,14 @@ BEGIN '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + fdw_privs_are( 'SomeFDW', current_user, '{USAGE}', 'whatever' ), + true, + 'fdw_privs_are(SomeFDW, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( fdw_privs_are( 'dummy', current_user, '{USAGE}' ), true, @@ -1416,6 +1672,13 @@ BEGIN '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + fdw_privs_are( 'SomeFDW', current_user, '{USAGE}' ), + true, + 'fdw_privs_are(SomeFDW, role, privs, desc)', + 'Role ' || current_user || ' should be granted USAGE on FDW "SomeFDW"', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -- Try nonexistent fdw. FOR tap IN SELECT * FROM check_test( @@ -1466,6 +1729,14 @@ BEGIN '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'fdw_privs_are(SomeFDW, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( pass('whatever'), true, @@ -1474,6 +1745,13 @@ BEGIN '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'fdw_privs_are(SomeFDW, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -- Try nonexistent fdw. FOR tap IN SELECT * FROM check_test( @@ -1526,6 +1804,7 @@ DECLARE BEGIN IF pg_version_num() >= 80400 THEN EXECUTE 'CREATE SERVER foo FOREIGN DATA WRAPPER dummy;'; + EXECUTE 'CREATE SERVER "SomeServer" FOREIGN DATA WRAPPER "SomeFDW";'; FOR tap IN SELECT * FROM check_test( server_privs_are( 'foo', current_user, '{USAGE}', 'whatever' ), @@ -1535,6 +1814,14 @@ BEGIN '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + server_privs_are( 'SomeServer', current_user, '{USAGE}', 'whatever' ), + true, + 'server_privs_are(SomeServer, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( server_privs_are( 'foo', current_user, '{USAGE}' ), true, @@ -1543,6 +1830,13 @@ BEGIN '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + server_privs_are( 'SomeServer', current_user, '{USAGE}' ), + true, + 'server_privs_are(SomeSrver, role, privs, desc)', + 'Role ' || current_user || ' should be granted USAGE on server "SomeServer"', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -- Try nonexistent server. FOR tap IN SELECT * FROM check_test( @@ -1593,6 +1887,14 @@ BEGIN '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'server_privs_are(SomeServer, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( pass('whatever'), true, @@ -1601,6 +1903,13 @@ BEGIN '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + FOR tap IN SELECT * FROM check_test( + pass('whatever'), + true, + 'server_privs_are(SomeServer, role, privs, desc)', + 'whatever', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -- Try nonexistent server. FOR tap IN SELECT * FROM check_test( From 5aed2cf463e80fbe610216d0c4afd9d968f2ffef Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 11 Aug 2015 11:11:27 -0700 Subject: [PATCH 0876/1195] Fix pasto. --- Changes | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 16562aeff7b6..eea6990cb7fd 100644 --- a/Changes +++ b/Changes @@ -10,8 +10,8 @@ Revision history for pgTAP running of startup/setup/teardown/shutdown functions as test functions. Patch from Jim Nasby. * Fixed `database_privs_are()`, `schema_privs_are()`, `tablespace_privs_are()` - `fdw_privs_are()`, and `fdw_privs_are()` to properly handle case-sensitive - object names. Thanks to Ingo for the report! + `fdw_privs_are()`, and `server_privs_are()` to properly handle + case-sensitive object names. Thanks to Ingo for the report! 0.95.0 2015-03-20T20:31:41Z --------------------------- From fef5b53ce5b5cbc84a371492f8152fbdaa5c7794 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 30 Aug 2015 15:06:51 -0700 Subject: [PATCH 0877/1195] Fix variable names. --- doc/pgtap.mmd | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 24ef62dd68cd..0e5b6f01028a 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -16,20 +16,20 @@ Synopsis -- Various ways to say "ok" SELECT ok( :have = :want, :test_description ); - SELECT is( :have, :want, $test_description ); - SELECT isnt( :have, :want, $test_description ); + SELECT is( :have, :want, :test_description ); + SELECT isnt( :have, :want, :test_description ); -- Rather than \echo # here's what went wrong SELECT diag( 'here''s what went wrong' ); -- Compare values with LIKE or regular expressions. - SELECT alike( :have, :like_expression, $test_description ); - SELECT unalike( :have, :like_expression, $test_description ); + SELECT alike( :have, :like_expression, :test_description ); + SELECT unalike( :have, :like_expression, :test_description ); - SELECT matches( :have, :regex, $test_description ); - SELECT doesnt_match( :have, :regex, $test_description ); + SELECT matches( :have, :regex, :test_description ); + SELECT doesnt_match( :have, :regex, :test_description ); - SELECT cmp_ok(:have, '=', :want, $test_description ); + SELECT cmp_ok(:have, '=', :want, :test_description ); -- Skip tests based on runtime conditions. SELECT CASE WHEN :some_feature THEN collect_tap( From 45fb4afea4b713008610880fb329f18e0024ac5f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 9 Nov 2015 09:21:51 -0800 Subject: [PATCH 0878/1195] Try to handle "missing" perl. --- Changes | 2 ++ Makefile | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/Changes b/Changes index eea6990cb7fd..d3ad34014d5e 100644 --- a/Changes +++ b/Changes @@ -12,6 +12,8 @@ Revision history for pgTAP * Fixed `database_privs_are()`, `schema_privs_are()`, `tablespace_privs_are()` `fdw_privs_are()`, and `server_privs_are()` to properly handle case-sensitive object names. Thanks to Ingo for the report! +* Updated the Makefile to handle the case when PGXS sets the PERL variable to + `missing perl`. 0.95.0 2015-03-20T20:31:41Z --------------------------- diff --git a/Makefile b/Makefile index d0c5cb57d026..6e455c39ea7b 100644 --- a/Makefile +++ b/Makefile @@ -41,9 +41,13 @@ include $(PGXS) endif # We need Perl. +ifneq (,$(findstring missing,$(PERL))) +PERL := $(shell which perl) +else ifndef PERL PERL := $(shell which perl) endif +endif # Is TAP::Parser::SourceHandler::pgTAP installed? ifdef PERL From 2d45730a8073553e23fe1277f30c3f72ae4bfc24 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Wed, 25 Nov 2015 16:05:11 -0600 Subject: [PATCH 0879/1195] Add PG 9.4 as a travis target --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index a7eccd3b85d0..6fcc2a8711e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,4 +12,5 @@ env: - PGVERSION=9.1 - PGVERSION=9.2 - PGVERSION=9.3 + - PGVERSION=9.4 script: bash ./pg-travis-test.sh From febdfa6a36fdde49f0c491c045a431397bc80205 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 1 Dec 2015 11:06:53 -0800 Subject: [PATCH 0880/1195] Fix test failure on 9.5. Just silence the warning on hash index creation. --- Changes | 1 + test/sql/index.sql | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Changes b/Changes index d3ad34014d5e..5960c2c9a807 100644 --- a/Changes +++ b/Changes @@ -14,6 +14,7 @@ Revision history for pgTAP case-sensitive object names. Thanks to Ingo for the report! * Updated the Makefile to handle the case when PGXS sets the PERL variable to `missing perl`. +* Fixed a test failure due to the use of a hash index on PostgresSQL 9.5. 0.95.0 2015-03-20T20:31:41Z --------------------------- diff --git a/test/sql/index.sql b/test/sql/index.sql index 27dc980b3e4f..31da0f6e6e0f 100644 --- a/test/sql/index.sql +++ b/test/sql/index.sql @@ -13,7 +13,9 @@ CREATE TABLE public.sometab( myint NUMERIC(8) ); CREATE INDEX idx_hey ON public.sometab(numb); +SET client_min_messages = error; CREATE INDEX idx_foo ON public.sometab using hash(name); +SET client_min_messages = warning; CREATE INDEX idx_bar ON public.sometab(numb, name); CREATE UNIQUE INDEX idx_baz ON public.sometab(LOWER(name)); CREATE INDEX idx_mul ON public.sometab(numb, LOWER(name)); From 8a3fff50a24f1f14f69249da5a2f6bb15fee6c3c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 12 May 2016 11:51:46 -0700 Subject: [PATCH 0881/1195] Add 9.4 and 9.5 Travis targets. Closes #93. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index a7eccd3b85d0..d963516ed34d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,4 +12,6 @@ env: - PGVERSION=9.1 - PGVERSION=9.2 - PGVERSION=9.3 + - PGVERSION=9.4 + - PGVERSION=9.5 script: bash ./pg-travis-test.sh From ef8707ea56a178953cca1ab5b17910f29e5d3612 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 12 May 2016 15:21:37 -0700 Subject: [PATCH 0882/1195] Skip tests on 8.x. They just don't work, sadly. --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d963516ed34d..3eac6f93afe5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,6 @@ before_install: - sudo sh ./apt.postgresql.org.sh - sudo rm -vf /etc/apt/sources.list.d/pgdg-source.list env: - - PGVERSION=8.2 - - PGVERSION=8.3 - - PGVERSION=8.4 - PGVERSION=9.0 - PGVERSION=9.1 - PGVERSION=9.2 From 3e3e6a50f03e09ffa21263c3f5da40c8d941c954 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 12 May 2016 15:23:46 -0700 Subject: [PATCH 0883/1195] Fix test ordering issue in do_tap.sql. The tap-emitting functions were ordering their output by the text of the tap, which starts with "# $test_number", but the new tests added in 83cb5191 caused the numbers to cross from single-digit to two-digit, so "# 10" was sorting before "# 9". So add another test that finds no tests, which is useful anyway, and pushes all the test result counts above two digits. --- test/expected/do_tap.out | 51 ++++++++++++++++++++-------------------- test/sql/do_tap.sql | 8 ++++++- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/test/expected/do_tap.out b/test/expected/do_tap.out index e6c8932f99c3..9d830dbbe242 100644 --- a/test/expected/do_tap.out +++ b/test/expected/do_tap.out @@ -1,42 +1,43 @@ \unset ECHO -1..28 +1..29 ok 1 - findfuncs(public, ^test, this) should work ok 2 - findfuncs(public, ^test) should work ok 3 - findfuncs(^test, this) should work ok 4 - findfuncs(^test) should work +ok 5 - findfuncs(unknown) should find no tests # public."test ident"() -ok 5 - ident -ok 6 - ident 2 +ok 6 - ident +ok 7 - ident 2 # public.testplpgsql() -ok 7 - plpgsql simple -ok 8 - plpgsql simple 2 +ok 8 - plpgsql simple +ok 9 - plpgsql simple 2 # public.testthis() -ok 10 - another simple pass -ok 9 - simple pass +ok 10 - simple pass +ok 11 - another simple pass # public."test ident"() -ok 11 - ident -ok 12 - ident 2 +ok 12 - ident +ok 13 - ident 2 # public.testplpgsql() -ok 13 - plpgsql simple -ok 14 - plpgsql simple 2 +ok 14 - plpgsql simple +ok 15 - plpgsql simple 2 # public.testthis() -ok 15 - simple pass -ok 16 - another simple pass +ok 16 - simple pass +ok 17 - another simple pass # public."test ident"() -ok 17 - ident -ok 18 - ident 2 +ok 18 - ident +ok 19 - ident 2 # public.testplpgsql() -ok 19 - plpgsql simple -ok 20 - plpgsql simple 2 +ok 20 - plpgsql simple +ok 21 - plpgsql simple 2 # public.testthis() -ok 21 - simple pass -ok 22 - another simple pass +ok 22 - simple pass +ok 23 - another simple pass # public."test ident"() -ok 23 - ident -ok 24 - ident 2 +ok 24 - ident +ok 25 - ident 2 # public.testplpgsql() -ok 25 - plpgsql simple -ok 26 - plpgsql simple 2 +ok 26 - plpgsql simple +ok 27 - plpgsql simple 2 # public.testthis() -ok 27 - simple pass -ok 28 - another simple pass +ok 28 - simple pass +ok 29 - another simple pass diff --git a/test/sql/do_tap.sql b/test/sql/do_tap.sql index a1e17d1b3077..b67f6547121b 100644 --- a/test/sql/do_tap.sql +++ b/test/sql/do_tap.sql @@ -2,7 +2,7 @@ \i test/setup.sql SET client_min_messages = notice; -SELECT plan(28); +SELECT plan(29); --SELECT * FROM no_plan(); CREATE OR REPLACE FUNCTION public.testthis() RETURNS SETOF TEXT AS $$ @@ -51,6 +51,12 @@ SELECT is( 'findfuncs(^test) should work' ); +SELECT is( + findfuncs('foo'), + '{}'::text[], + 'findfuncs(unknown) should find no tests' +); + SELECT * FROM do_tap('public', '^test'); SELECT * FROM do_tap('public'::name); SELECT * FROM do_tap('^test'); From 5cb094918b33f8464a714ec0bd38073133d1f68c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 12 May 2016 16:25:21 -0700 Subject: [PATCH 0884/1195] Add extensions_are(). Thanks to @chanmix51 for the pull request that kicked my butt into doing this. Closes #98. --- Changes | 2 + doc/pgtap.mmd | 42 +++++++++ sql/pgtap--0.95.0--0.96.0.sql | 51 +++++++++++ sql/pgtap.sql.in | 51 +++++++++++ test/expected/extension.out | 26 ++++++ test/sql/extension.sql | 168 ++++++++++++++++++++++++++++++++++ 6 files changed, 340 insertions(+) create mode 100644 test/expected/extension.out create mode 100644 test/sql/extension.sql diff --git a/Changes b/Changes index 5960c2c9a807..b0ee5ec90d24 100644 --- a/Changes +++ b/Changes @@ -15,6 +15,8 @@ Revision history for pgTAP * Updated the Makefile to handle the case when PGXS sets the PERL variable to `missing perl`. * Fixed a test failure due to the use of a hash index on PostgresSQL 9.5. +* Added `extensions_are()`, prompted by a pull request from Grégoire HUBERT + (Issue #98). 0.95.0 2015-03-20T20:31:41Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 0e5b6f01028a..ba684b3f4230 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2550,6 +2550,46 @@ missing operators, like so: # Missing enums: # +(integer,text) RETURNS text +### `extensions_are()` ### + + SELECT extensions_are( :schema, :extensions, :description ); + SELECT extensions_are( :schema, :extensions ); + SELECT extensions_are( :extensions, :description ); + SELECT extensions_are( :extensions ); + +**Parameters** + +`:schema` +: Name of a schema in which to find extensions. + +`:extensions` +: An array of extension names. + +`:description` +: A short description of the test. + +This function tests that all of the extensions in the named schema, or that +are visible in the search path, are only the extensions that *should* be +there. If the `:schema` argument is omitted, all extensions will be checked. +If the description is omitted, a generally useful default description will be +generated. Example: + + SELECT extensions_are( + 'myschema', + ARRAY[ 'citext', 'isn', 'plpgsql' ] + ); + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing extensions, like so: + + # Failed test 91: "Schema public should have the correct extensions" + # Extra extensions: + # pgtap + # ltree + # Missing extensions: + # citext + # isn + To Have or Have Not ------------------- @@ -7570,6 +7610,8 @@ No changes. Everything should just work. ------------ * The `foreign_table_owner_is()` function will not work, because, of course, there were no foreign tables until 9.1. +* The `extensions_are()` functions will not work, because extensions were not + introduced until 9.1. 8.4 and Down ------------ diff --git a/sql/pgtap--0.95.0--0.96.0.sql b/sql/pgtap--0.95.0--0.96.0.sql index e6915dda95bf..91c560669cd3 100644 --- a/sql/pgtap--0.95.0--0.96.0.sql +++ b/sql/pgtap--0.95.0--0.96.0.sql @@ -152,3 +152,54 @@ BEGIN RETURN _assets_are('privileges', grants, $3, $4); END; $$ LANGUAGE plpgsql; + +-- 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index ef6c3a99b71f..80906ca29cde 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -9434,3 +9434,54 @@ $$ 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; diff --git a/test/expected/extension.out b/test/expected/extension.out new file mode 100644 index 000000000000..c12a734df592 --- /dev/null +++ b/test/expected/extension.out @@ -0,0 +1,26 @@ +\unset ECHO +1..24 +ok 1 - extensions_are(sch, exts, desc) should pass +ok 2 - extensions_are(sch, exts, desc) should have the proper description +ok 3 - extensions_are(sch, exts, desc) should have the proper diagnostics +ok 4 - extensions_are(sch, exts) should pass +ok 5 - extensions_are(sch, exts) should have the proper description +ok 6 - extensions_are(sch, exts) should have the proper diagnostics +ok 7 - extensions_are(exts, desc) should pass +ok 8 - extensions_are(exts, desc) should have the proper description +ok 9 - extensions_are(exts, desc) should have the proper diagnostics +ok 10 - extensions_are(exts) should pass +ok 11 - extensions_are(exts) should have the proper description +ok 12 - extensions_are(exts) should have the proper diagnostics +ok 13 - extensions_are(public, exts, desc) should pass +ok 14 - extensions_are(public, exts, desc) should have the proper description +ok 15 - extensions_are(public, exts, desc) should have the proper diagnostics +ok 16 - extensions_are(non-sch, exts) should pass +ok 17 - extensions_are(non-sch, exts) should have the proper description +ok 18 - extensions_are(non-sch, exts) should have the proper diagnostics +ok 19 - extensions_are(sch, good/bad, desc) should fail +ok 20 - extensions_are(sch, good/bad, desc) should have the proper description +ok 21 - extensions_are(sch, good/bad, desc) should have the proper diagnostics +ok 22 - extensions_are(someexts) should fail +ok 23 - extensions_are(someexts) should have the proper description +ok 24 - extensions_are(someexts) should have the proper diagnostics diff --git a/test/sql/extension.sql b/test/sql/extension.sql new file mode 100644 index 000000000000..ea1fc734a95e --- /dev/null +++ b/test/sql/extension.sql @@ -0,0 +1,168 @@ +\unset ECHO +\i test/setup.sql + +SELECT plan(24); +--SELECT * FROM no_plan(); + +-- This will be rolled back. :-) + +/****************************************************************************/ +-- Test extensions_are(). +CREATE FUNCTION public.test_extensions() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() >= 90100 THEN + EXECUTE $E$ + CREATE SCHEMA someschema; + CREATE SCHEMA "empty schema"; + CREATE EXTENSION IF NOT EXISTS citext; + CREATE EXTENSION IF NOT EXISTS isn SCHEMA someschema; + CREATE EXTENSION IF NOT EXISTS ltree SCHEMA someschema; + $E$; + + FOR tap IN SELECT * FROM check_test( + extensions_are( 'someschema', ARRAY['isn', 'ltree'], 'Got em' ), + true, + 'extensions_are(sch, exts, desc)', + 'Got em', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + extensions_are( 'someschema', ARRAY['isn', 'ltree'] ), + true, + 'extensions_are(sch, exts)', + 'Schema someschema should have the correct extensions', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT* FROM check_test( + extensions_are( ARRAY['citext', 'isn', 'ltree', 'plpgsql'], 'Got em' ), + true, + 'extensions_are(exts, desc)', + 'Got em', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT* FROM check_test( + extensions_are( ARRAY['citext', 'isn', 'ltree', 'plpgsql'] ), + true, + 'extensions_are(exts)', + 'Should have the correct extensions', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT* FROM check_test( + extensions_are( 'public', ARRAY['citext'], 'Got em' ), + true, + 'extensions_are(public, exts, desc)', + 'Got em', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT* FROM check_test( + extensions_are( 'empty schema', ARRAY[]::name[] ), + true, + 'extensions_are(non-sch, exts)', + 'Schema "empty schema" should have the correct extensions', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + /********************************************************************/ + -- Test failures and diagnostics. + FOR tap IN SELECT* FROM check_test( + extensions_are( 'someschema', ARRAY['ltree', 'nonesuch'], 'Got em' ), + false, + 'extensions_are(sch, good/bad, desc)', + 'Got em', + ' Extra extensions: + isn + Missing extensions: + nonesuch' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT* FROM check_test( + extensions_are( ARRAY['citext', 'isn', 'ltree', 'nonesuch'] ), + false, + 'extensions_are(someexts)', + 'Should have the correct extensions', + ' Extra extensions: + plpgsql + Missing extensions: + nonesuch' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + ELSE + FOR tap IN SELECT * FROM check_test( + pass('mumble'), + true, + 'extensions_are(sch, exts, desc)', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('mumble'), + true, + 'extensions_are(sch, exts)', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('mumble'), + true, + 'extensions_are(exts, desc)', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('mumble'), + true, + 'extensions_are(exts)', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('mumble'), + true, + 'extensions_are(public, exts, desc)', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('mumble'), + true, + 'extensions_are(non-sch, exts)', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('mumble'), + false, + 'extensions_are(sch, good/bad, desc)', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('mumble'), + false, + 'extensions_are(someexts)', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + END IF; + RETURN; +END; +$$ LANGUAGE PLPGSQL; +SELECT * FROM public.test_extensions(); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From 48848dc742c13dd553fe2c8821f39fd2fd2dbda6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 13 May 2016 09:41:56 -0700 Subject: [PATCH 0885/1195] Add error diagnostic output. To lives_ok() and the xUnit test running code. Currently works on 9.3 and later. Will follow up to make compatible with earlier versions shortly. Original code by Jim Nasby (closes #92) with significant tweaking by me. --- sql/pgtap--0.95.0--0.96.0.sql | 210 +++++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 76 +++++++++++- test/expected/runjusttests.out | 13 +- test/expected/runtests.out | 25 +++- test/sql/runjusttests.sql | 11 +- test/sql/runtests.sql | 12 +- test/sql/throwtap.sql | 5 +- 7 files changed, 342 insertions(+), 10 deletions(-) diff --git a/sql/pgtap--0.95.0--0.96.0.sql b/sql/pgtap--0.95.0--0.96.0.sql index 91c560669cd3..af883acb3201 100644 --- a/sql/pgtap--0.95.0--0.96.0.sql +++ b/sql/pgtap--0.95.0--0.96.0.sql @@ -203,3 +203,213 @@ CREATE OR REPLACE FUNCTION extensions_are( NAME[] ) RETURNS TEXT AS $$ SELECT extensions_are($1, 'Should have the correct extensions'); $$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _error_diag( + sqlstate TEXT DEFAULT NULL, + errmsg TEXT DEFAULT NULL, + detail TEXT DEFAULT NULL, + hint TEXT DEFAULT NULL, + context TEXT DEFAULT NULL, + schname TEXT DEFAULT NULL, + tabname TEXT DEFAULT NULL, + colname TEXT DEFAULT NULL, + chkname TEXT DEFAULT NULL, + typname TEXT DEFAULT NULL +) RETURNS TEXT AS $$ + SELECT COALESCE( + COALESCE( NULLIF(sqlstate, '') || ': ', '' ) || COALESCE( NULLIF(errmsg, ''), '' ), + 'NO ERROR FOUND' + ) + || COALESCE(E'\n DETAIL: ' || nullif(detail, ''), '') + || COALESCE(E'\n HINT: ' || nullif(hint, ''), '') + || COALESCE(E'\n SCHEMA: ' || nullif(schname, ''), '') + || COALESCE(E'\n TABLE: ' || nullif(tabname, ''), '') + || COALESCE(E'\n COLUMN: ' || nullif(colname, ''), '') + || COALESCE(E'\n CONSTRAINT: ' || nullif(chkname, ''), '') + || COALESCE(E'\n TYPE: ' || nullif(typname, ''), '') + -- We need to manually indent all the context lines + || COALESCE(E'\n CONTEXT:\n' + || regexp_replace(NULLIF( context, ''), '^', ' ', '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 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; + +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 tfaild WHEN 1 THEN '' ELSE 's' END + || ' of ' || fnumb + ); + END IF; + END IF; + + EXCEPTION WHEN raise_exception 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; + diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 80906ca29cde..c696c35114bd 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -743,19 +743,65 @@ RETURNS TEXT AS $$ SELECT throws_ok( $1, $2::char(5), NULL, NULL ); $$ LANGUAGE SQL; +CREATE OR REPLACE FUNCTION _error_diag( + sqlstate TEXT DEFAULT NULL, + errmsg TEXT DEFAULT NULL, + detail TEXT DEFAULT NULL, + hint TEXT DEFAULT NULL, + context TEXT DEFAULT NULL, + schname TEXT DEFAULT NULL, + tabname TEXT DEFAULT NULL, + colname TEXT DEFAULT NULL, + chkname TEXT DEFAULT NULL, + typname TEXT DEFAULT NULL +) RETURNS TEXT AS $$ + SELECT COALESCE( + COALESCE( NULLIF(sqlstate, '') || ': ', '' ) || COALESCE( NULLIF(errmsg, ''), '' ), + 'NO ERROR FOUND' + ) + || COALESCE(E'\n DETAIL: ' || nullif(detail, ''), '') + || COALESCE(E'\n HINT: ' || nullif(hint, ''), '') + || COALESCE(E'\n SCHEMA: ' || nullif(schname, ''), '') + || COALESCE(E'\n TABLE: ' || nullif(tabname, ''), '') + || COALESCE(E'\n COLUMN: ' || nullif(colname, ''), '') + || COALESCE(E'\n CONSTRAINT: ' || nullif(chkname, ''), '') + || COALESCE(E'\n TYPE: ' || nullif(typname, ''), '') + -- We need to manually indent all the context lines + || COALESCE(E'\n CONTEXT:\n' + || regexp_replace(NULLIF( context, ''), '^', ' ', '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 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: ' || SQLSTATE || ': ' || SQLERRM + ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); END; $$ LANGUAGE plpgsql; @@ -6037,7 +6083,6 @@ DECLARE tnumb INTEGER := 0; fnumb INTEGER := 0; tok BOOLEAN := TRUE; - errmsg TEXT; BEGIN BEGIN -- No plan support. @@ -6067,6 +6112,17 @@ BEGIN 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. @@ -6107,7 +6163,17 @@ BEGIN EXCEPTION WHEN raise_exception 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. @@ -6117,10 +6183,12 @@ BEGIN IF errmsg IS NOT NULL THEN -- Something went wrong. Emit the error message. tok := FALSE; - RETURN NEXT ' ' || diag('Test died: ' || errmsg); + 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; + END; -- Restore the sequence. EXECUTE 'ALTER SEQUENCE __tresults___numb_seq RESTART WITH ' || tnumb + 1; diff --git a/test/expected/runjusttests.out b/test/expected/runjusttests.out index e9fa7ba97358..78952593d03d 100644 --- a/test/expected/runjusttests.out +++ b/test/expected/runjusttests.out @@ -16,7 +16,18 @@ not ok 2 - whatever.testnada 1..3 ok 3 - whatever.testplpgsql # Subtest: whatever.testplpgsqldie() - # Test died: This test should die, but not halt execution + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. + # DETAIL: DETAIL + # SCHEMA: SCHEMA + # TABLE: TABLE + # COLUMN: COLUMN + # CONSTRAINT: CONSTRAINT + # TYPE: TYPE + # CONTEXT: + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 + # SQL function "runtests" statement 1 not ok 4 - whatever.testplpgsqldie # Failed test 4: "whatever.testplpgsqldie" # Subtest: whatever.testthis() diff --git a/test/expected/runtests.out b/test/expected/runtests.out index 25c2613a9f0d..302dc29dc4f1 100644 --- a/test/expected/runtests.out +++ b/test/expected/runtests.out @@ -26,7 +26,18 @@ ok 4 - whatever.testplpgsql ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more - # Test died: This test should die, but not halt execution + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. + # DETAIL: DETAIL + # SCHEMA: SCHEMA + # TABLE: TABLE + # COLUMN: COLUMN + # CONSTRAINT: CONSTRAINT + # TYPE: TYPE + # CONTEXT: + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 + # SQL function "runtests" statement 1 not ok 5 - whatever.testplpgsqldie # Failed test 5: "whatever.testplpgsqldie" # Subtest: whatever.testthis() @@ -91,7 +102,17 @@ ok 4 - whatever.testplpgsql ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more - # Test died: This test should die, but not halt execution + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. + # DETAIL: DETAIL + # SCHEMA: SCHEMA + # TABLE: TABLE + # COLUMN: COLUMN + # CONSTRAINT: CONSTRAINT + # TYPE: TYPE + # CONTEXT: + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 not ok 5 - whatever.testplpgsqldie # Failed test 5: "whatever.testplpgsqldie" # Subtest: whatever.testthis() diff --git a/test/sql/runjusttests.sql b/test/sql/runjusttests.sql index 0d3daf661761..d5398d7511d2 100644 --- a/test/sql/runjusttests.sql +++ b/test/sql/runjusttests.sql @@ -30,7 +30,16 @@ BEGIN RETURN NEXT pass( 'plpgsql simple 2' ); INSERT INTO whatever.foo VALUES(1); RETURN NEXT is( MAX(id), 1, 'Should be a 1 in the test table') FROM whatever.foo; - RAISE EXCEPTION 'This test should die, but not halt execution'; + RAISE EXCEPTION 'This test should die, but not halt execution. +Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself.' + USING + -- Weird formatting helps errors show up + DETAIL = 'DETAIL', + COLUMN = 'COLUMN', + CONSTRAINT = 'CONSTRAINT', + DATATYPE = 'TYPE', + TABLE = 'TABLE', + SCHEMA = 'SCHEMA'; END; $$ LANGUAGE plpgsql; diff --git a/test/sql/runtests.sql b/test/sql/runtests.sql index 627569c352f2..a8c426c16414 100644 --- a/test/sql/runtests.sql +++ b/test/sql/runtests.sql @@ -65,7 +65,17 @@ BEGIN RETURN NEXT pass( 'plpgsql simple 2' ); INSERT INTO whatever.foo VALUES(1); RETURN NEXT is( MAX(id), 1, 'Should be a 1 in the test table') FROM whatever.foo; - RAISE EXCEPTION 'This test should die, but not halt execution'; + RAISE EXCEPTION 'This test should die, but not halt execution. +Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself.' + USING + -- Weird formatting helps errors show up + DETAIL = 'DETAIL' + , COLUMN = 'COLUMN' + , CONSTRAINT = 'CONSTRAINT' + , DATATYPE = 'TYPE' + , TABLE = 'TABLE' + , SCHEMA = 'SCHEMA' + ; END; $$ LANGUAGE plpgsql; diff --git a/test/sql/throwtap.sql b/test/sql/throwtap.sql index efa47c7bdc83..05ff896a9f44 100644 --- a/test/sql/throwtap.sql +++ b/test/sql/throwtap.sql @@ -122,7 +122,10 @@ SELECT * FROM check_test( false, 'lives_ok failure diagnostics', '', - ' died: P0001: todo_end() called without todo_start()' + ' died: P0001: todo_end() called without todo_start() + CONTEXT: + SQL statement "SELECT * FROM todo_end()" + PL/pgSQL function lives_ok(text,text) line 14 at EXECUTE' ); /****************************************************************************/ From df6a90a5cdcaaa8ad0694c0fa5cd690cd74deb58 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 13 May 2016 09:55:50 -0700 Subject: [PATCH 0886/1195] Adjust context comparison for 9.4. --- test/sql/throwtap.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/test/sql/throwtap.sql b/test/sql/throwtap.sql index 05ff896a9f44..73fe271c598b 100644 --- a/test/sql/throwtap.sql +++ b/test/sql/throwtap.sql @@ -126,6 +126,7 @@ SELECT * FROM check_test( CONTEXT: SQL statement "SELECT * FROM todo_end()" PL/pgSQL function lives_ok(text,text) line 14 at EXECUTE' + || CASE WHEN pg_version_num() >= 90500 THEN '' ELSE ' statement' END ); /****************************************************************************/ From 6ea59aa1a58e14f6f211cd827a9d5fa5e6cbb49a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 13 May 2016 13:43:20 -0700 Subject: [PATCH 0887/1195] Updates error diagnostics output for 9.2. The schema, table, column, data type, and constraint contexts were not included until 9.3, so add a patch to remove them and update the tests to generate and test for their presence programmatically. This requires additional expected output files for a couple of the tests, since the output varies. Fortunately, pg_regress supports up to 9 alternate output files. I've also added alternate output files for those tests for 9.4, where there was a very slight change in the output of context in 9.5 to omit the word "statement" from the first line of context. --- Makefile | 4 + compat/install-9.2.patch | 30 ++++++ doc/pgtap.mmd | 8 +- test/expected/runjusttests.out | 2 + test/expected/runjusttests_1.out | 53 +++++++++++ test/expected/runjusttests_2.out | 48 ++++++++++ test/expected/runtests.out | 4 + test/expected/runtests_1.out | 156 +++++++++++++++++++++++++++++++ test/expected/runtests_2.out | 146 +++++++++++++++++++++++++++++ test/sql/runjusttests.sql | 36 +++++-- test/sql/runtests.sql | 37 ++++++-- 11 files changed, 504 insertions(+), 20 deletions(-) create mode 100644 compat/install-9.2.patch create mode 100644 test/expected/runjusttests_1.out create mode 100644 test/expected/runjusttests_2.out create mode 100644 test/expected/runtests_1.out create mode 100644 test/expected/runtests_2.out diff --git a/Makefile b/Makefile index 6e455c39ea7b..c94d6ca98c50 100644 --- a/Makefile +++ b/Makefile @@ -97,6 +97,9 @@ endif sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql cp $< $@ +ifeq ($(shell echo $(VERSION) | grep -qE "9[.][012]|8[.][1234]" && echo yes || echo no),yes) + patch -p0 < compat/install-9.2.patch +endif ifeq ($(shell echo $(VERSION) | grep -qE "9[.]0|8[.][1234]" && echo yes || echo no),yes) patch -p0 < compat/install-9.0.patch endif @@ -134,6 +137,7 @@ sql/pgtap-core.sql: sql/pgtap.sql.in sql/pgtap-schema.sql: sql/pgtap.sql.in cp $< $@ + sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-9.2.patch | patch -p0 sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-9.0.patch | patch -p0 sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-8.4.patch | patch -p0 sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-8.3.patch | patch -p0 diff --git a/compat/install-9.2.patch b/compat/install-9.2.patch new file mode 100644 index 000000000000..d144eae7b71e --- /dev/null +++ b/compat/install-9.2.patch @@ -0,0 +1,30 @@ +--- sql/pgtap.sql ++++ sql/pgtap.sql +@@ -794,12 +794,7 @@ + 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; ++ context = PG_EXCEPTION_CONTEXT; + RETURN ok( FALSE, descr ) || E'\n' || diag( + ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) + ); +@@ -6168,12 +6163,7 @@ + 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; ++ context = PG_EXCEPTION_CONTEXT; + END; + + -- Always raise an exception to rollback any changes. diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index ba684b3f4230..244b354c8328 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7601,11 +7601,17 @@ instead: To see the specifics for each version of PostgreSQL, consult the files in the `compat/` directory in the pgTAP distribution. -9.1 and Up +9.3 and Up ---------- No changes. Everything should just work. +9.2 and Down +------------ +* Diagnostic output from `lives_ok()` and xUnit function exceptions will not + include schema, table, column, data type, or constraint information, since + such diagnostics were not introduced until 9.3. + 9.0 and Down ------------ * The `foreign_table_owner_is()` function will not work, because, of course, diff --git a/test/expected/runjusttests.out b/test/expected/runjusttests.out index 78952593d03d..58b703812ba5 100644 --- a/test/expected/runjusttests.out +++ b/test/expected/runjusttests.out @@ -25,6 +25,8 @@ ok 3 - whatever.testplpgsql # CONSTRAINT: CONSTRAINT # TYPE: TYPE # CONTEXT: + # SQL statement "SELECT __die();" + # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement # SQL function "runtests" statement 1 # SQL function "runtests" statement 1 diff --git a/test/expected/runjusttests_1.out b/test/expected/runjusttests_1.out new file mode 100644 index 000000000000..65f99c9f0b89 --- /dev/null +++ b/test/expected/runjusttests_1.out @@ -0,0 +1,53 @@ +\unset ECHO + # Subtest: whatever."test ident"() + ok 1 - ident + ok 2 - ident 2 + 1..2 +ok 1 - whatever."test ident" + # Subtest: whatever.testnada() + 1..0 + # No tests run! +not ok 2 - whatever.testnada +# Failed test 2: "whatever.testnada" + # Subtest: whatever.testplpgsql() + ok 1 - plpgsql simple + ok 2 - plpgsql simple 2 + ok 3 - Should be a 1 in the test table + 1..3 +ok 3 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. + # DETAIL: DETAIL + # SCHEMA: SCHEMA + # TABLE: TABLE + # COLUMN: COLUMN + # CONSTRAINT: CONSTRAINT + # TYPE: TYPE + # CONTEXT: + # SQL statement "SELECT __die();" + # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 + # SQL function "runtests" statement 1 +not ok 4 - whatever.testplpgsqldie +# Failed test 4: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - simple pass + ok 2 - another simple pass + 1..2 +ok 5 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - pass + not ok 2 - this test intentionally fails + # Failed test 2: "this test intentionally fails" + 1..2 + # Looks like you failed 1 tests of 2 +not ok 6 - whatever.testy +# Failed test 6: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - Late test should find nothing in the test table + 1..1 +ok 7 - whatever.testz +1..7 +# Looks like you failed 3 tests of 7 diff --git a/test/expected/runjusttests_2.out b/test/expected/runjusttests_2.out new file mode 100644 index 000000000000..3814a9ef9405 --- /dev/null +++ b/test/expected/runjusttests_2.out @@ -0,0 +1,48 @@ +\unset ECHO + # Subtest: whatever."test ident"() + ok 1 - ident + ok 2 - ident 2 + 1..2 +ok 1 - whatever."test ident" + # Subtest: whatever.testnada() + 1..0 + # No tests run! +not ok 2 - whatever.testnada +# Failed test 2: "whatever.testnada" + # Subtest: whatever.testplpgsql() + ok 1 - plpgsql simple + ok 2 - plpgsql simple 2 + ok 3 - Should be a 1 in the test table + 1..3 +ok 3 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. + # DETAIL: DETAIL + # CONTEXT: + # SQL statement "SELECT __die();" + # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 + # SQL function "runtests" statement 1 +not ok 4 - whatever.testplpgsqldie +# Failed test 4: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - simple pass + ok 2 - another simple pass + 1..2 +ok 5 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - pass + not ok 2 - this test intentionally fails + # Failed test 2: "this test intentionally fails" + 1..2 + # Looks like you failed 1 tests of 2 +not ok 6 - whatever.testy +# Failed test 6: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - Late test should find nothing in the test table + 1..1 +ok 7 - whatever.testz +1..7 +# Looks like you failed 3 tests of 7 diff --git a/test/expected/runtests.out b/test/expected/runtests.out index 302dc29dc4f1..04a68d00a275 100644 --- a/test/expected/runtests.out +++ b/test/expected/runtests.out @@ -35,6 +35,8 @@ ok 4 - whatever.testplpgsql # CONSTRAINT: CONSTRAINT # TYPE: TYPE # CONTEXT: + # SQL statement "SELECT __die();" + # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement # SQL function "runtests" statement 1 # SQL function "runtests" statement 1 @@ -111,6 +113,8 @@ ok 4 - whatever.testplpgsql # CONSTRAINT: CONSTRAINT # TYPE: TYPE # CONTEXT: + # SQL statement "SELECT __die();" + # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement # SQL function "runtests" statement 1 not ok 5 - whatever.testplpgsqldie diff --git a/test/expected/runtests_1.out b/test/expected/runtests_1.out new file mode 100644 index 000000000000..8913db0d6797 --- /dev/null +++ b/test/expected/runtests_1.out @@ -0,0 +1,156 @@ +\unset ECHO +ok 1 - starting up +ok 2 - starting up some more + # Subtest: whatever."test ident"() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - ident + ok 5 - ident 2 + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 3 - whatever."test ident" + # Subtest: whatever.testplpgsql() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - plpgsql simple + ok 5 - plpgsql simple 2 + ok 6 - Should be a 1 in the test table + ok 7 - teardown + ok 8 - teardown more + 1..8 +ok 4 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. + # DETAIL: DETAIL + # SCHEMA: SCHEMA + # TABLE: TABLE + # COLUMN: COLUMN + # CONSTRAINT: CONSTRAINT + # TYPE: TYPE + # CONTEXT: + # SQL statement "SELECT __die();" + # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 + # SQL function "runtests" statement 1 +not ok 5 - whatever.testplpgsqldie +# Failed test 5: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - simple pass + ok 5 - another simple pass + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 6 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + not ok 4 - this test intentionally fails + # Failed test 4: "this test intentionally fails" + ok 5 - teardown + ok 6 - teardown more + 1..6 + # Looks like you failed 1 test of 6 +not ok 7 - whatever.testy +# Failed test 7: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - Late test should find nothing in the test table + ok 5 - teardown + ok 6 - teardown more + 1..6 +ok 8 - whatever.testz +ok 9 - shutting down +ok 10 - shutting down more +1..10 +# Looks like you failed 2 tests of 10 +ok 1 - starting up +ok 2 - starting up some more + # Subtest: whatever."test ident"() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - ident + ok 5 - ident 2 + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 3 - whatever."test ident" + # Subtest: whatever.testplpgsql() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - plpgsql simple + ok 5 - plpgsql simple 2 + ok 6 - Should be a 1 in the test table + ok 7 - teardown + ok 8 - teardown more + 1..8 +ok 4 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. + # DETAIL: DETAIL + # SCHEMA: SCHEMA + # TABLE: TABLE + # COLUMN: COLUMN + # CONSTRAINT: CONSTRAINT + # TYPE: TYPE + # CONTEXT: + # SQL statement "SELECT __die();" + # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 +not ok 5 - whatever.testplpgsqldie +# Failed test 5: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - simple pass + ok 5 - another simple pass + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 6 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + not ok 4 - this test intentionally fails + # Failed test 4: "this test intentionally fails" + ok 5 - teardown + ok 6 - teardown more + 1..6 + # Looks like you failed 1 test of 6 +not ok 7 - whatever.testy +# Failed test 7: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - Late test should find nothing in the test table + ok 5 - teardown + ok 6 - teardown more + 1..6 +ok 8 - whatever.testz +ok 9 - shutting down +ok 10 - shutting down more +1..10 +# Looks like you failed 2 tests of 10 diff --git a/test/expected/runtests_2.out b/test/expected/runtests_2.out new file mode 100644 index 000000000000..af097face9e8 --- /dev/null +++ b/test/expected/runtests_2.out @@ -0,0 +1,146 @@ +\unset ECHO +ok 1 - starting up +ok 2 - starting up some more + # Subtest: whatever."test ident"() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - ident + ok 5 - ident 2 + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 3 - whatever."test ident" + # Subtest: whatever.testplpgsql() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - plpgsql simple + ok 5 - plpgsql simple 2 + ok 6 - Should be a 1 in the test table + ok 7 - teardown + ok 8 - teardown more + 1..8 +ok 4 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. + # DETAIL: DETAIL + # CONTEXT: + # SQL statement "SELECT __die();" + # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 + # SQL function "runtests" statement 1 +not ok 5 - whatever.testplpgsqldie +# Failed test 5: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - simple pass + ok 5 - another simple pass + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 6 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + not ok 4 - this test intentionally fails + # Failed test 4: "this test intentionally fails" + ok 5 - teardown + ok 6 - teardown more + 1..6 + # Looks like you failed 1 test of 6 +not ok 7 - whatever.testy +# Failed test 7: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - Late test should find nothing in the test table + ok 5 - teardown + ok 6 - teardown more + 1..6 +ok 8 - whatever.testz +ok 9 - shutting down +ok 10 - shutting down more +1..10 +# Looks like you failed 2 tests of 10 +ok 1 - starting up +ok 2 - starting up some more + # Subtest: whatever."test ident"() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - ident + ok 5 - ident 2 + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 3 - whatever."test ident" + # Subtest: whatever.testplpgsql() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - plpgsql simple + ok 5 - plpgsql simple 2 + ok 6 - Should be a 1 in the test table + ok 7 - teardown + ok 8 - teardown more + 1..8 +ok 4 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. + # DETAIL: DETAIL + # CONTEXT: + # SQL statement "SELECT __die();" + # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 +not ok 5 - whatever.testplpgsqldie +# Failed test 5: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - simple pass + ok 5 - another simple pass + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 6 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + not ok 4 - this test intentionally fails + # Failed test 4: "this test intentionally fails" + ok 5 - teardown + ok 6 - teardown more + 1..6 + # Looks like you failed 1 test of 6 +not ok 7 - whatever.testy +# Failed test 7: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - Late test should find nothing in the test table + ok 5 - teardown + ok 6 - teardown more + 1..6 +ok 8 - whatever.testz +ok 9 - shutting down +ok 10 - shutting down more +1..10 +# Looks like you failed 2 tests of 10 diff --git a/test/sql/runjusttests.sql b/test/sql/runjusttests.sql index d5398d7511d2..1b611da6a01e 100644 --- a/test/sql/runjusttests.sql +++ b/test/sql/runjusttests.sql @@ -30,16 +30,34 @@ BEGIN RETURN NEXT pass( 'plpgsql simple 2' ); INSERT INTO whatever.foo VALUES(1); RETURN NEXT is( MAX(id), 1, 'Should be a 1 in the test table') FROM whatever.foo; - RAISE EXCEPTION 'This test should die, but not halt execution. + IF pg_version_num() >= 90300 THEN + EXECUTE $E$ + CREATE OR REPLACE FUNCTION __die() RETURNS VOID LANGUAGE plpgsql AS $F$ + BEGIN + RAISE EXCEPTION 'This test should die, but not halt execution. Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself.' - USING - -- Weird formatting helps errors show up - DETAIL = 'DETAIL', - COLUMN = 'COLUMN', - CONSTRAINT = 'CONSTRAINT', - DATATYPE = 'TYPE', - TABLE = 'TABLE', - SCHEMA = 'SCHEMA'; + USING + DETAIL = 'DETAIL', + COLUMN = 'COLUMN', + CONSTRAINT = 'CONSTRAINT', + DATATYPE = 'TYPE', + TABLE = 'TABLE', + SCHEMA = 'SCHEMA'; + END; + $F$; + $E$; + ELSE + EXECUTE $E$ + CREATE OR REPLACE FUNCTION __die() RETURNS VOID LANGUAGE plpgsql AS $F$ + BEGIN + RAISE EXCEPTION 'This test should die, but not halt execution. +Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself.' + USING DETAIL = 'DETAIL'; + END; + $F$; + $E$; + END IF; + EXECUTE 'SELECT __die();'; END; $$ LANGUAGE plpgsql; diff --git a/test/sql/runtests.sql b/test/sql/runtests.sql index a8c426c16414..cd5ba9702f08 100644 --- a/test/sql/runtests.sql +++ b/test/sql/runtests.sql @@ -65,17 +65,34 @@ BEGIN RETURN NEXT pass( 'plpgsql simple 2' ); INSERT INTO whatever.foo VALUES(1); RETURN NEXT is( MAX(id), 1, 'Should be a 1 in the test table') FROM whatever.foo; - RAISE EXCEPTION 'This test should die, but not halt execution. + IF pg_version_num() >= 90300 THEN + EXECUTE $E$ + CREATE OR REPLACE FUNCTION __die() RETURNS VOID LANGUAGE plpgsql AS $F$ + BEGIN + RAISE EXCEPTION 'This test should die, but not halt execution. Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself.' - USING - -- Weird formatting helps errors show up - DETAIL = 'DETAIL' - , COLUMN = 'COLUMN' - , CONSTRAINT = 'CONSTRAINT' - , DATATYPE = 'TYPE' - , TABLE = 'TABLE' - , SCHEMA = 'SCHEMA' - ; + USING + DETAIL = 'DETAIL', + COLUMN = 'COLUMN', + CONSTRAINT = 'CONSTRAINT', + DATATYPE = 'TYPE', + TABLE = 'TABLE', + SCHEMA = 'SCHEMA'; + END; + $F$; + $E$; + ELSE + EXECUTE $E$ + CREATE OR REPLACE FUNCTION __die() RETURNS VOID LANGUAGE plpgsql AS $F$ + BEGIN + RAISE EXCEPTION 'This test should die, but not halt execution. +Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself.' + USING DETAIL = 'DETAIL'; + END; + $F$; + $E$; + END IF; + EXECUTE 'SELECT __die();'; END; $$ LANGUAGE plpgsql; From a9ce1a07b47300adc58c2dfdb169466b1350c1b7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 13 May 2016 14:11:17 -0700 Subject: [PATCH 0888/1195] Add patch for 9.1 support. Removes stacked diagnostics call. Also update _error_diag() to use arument numbers, instead of names, since they can't be used on 9.1. --- Makefile | 4 ++++ compat/install-9.1.patch | 24 +++++++++++++++++++ doc/pgtap.mmd | 6 +++++ sql/pgtap.sql.in | 18 +++++++------- test/expected/runjusttests_3.out | 41 ++++++++++++++++++++++++++++++++ test/expected/runtests_2.out | 13 ---------- test/sql/throwtap.sql | 6 ++--- 7 files changed, 87 insertions(+), 25 deletions(-) create mode 100644 compat/install-9.1.patch create mode 100644 test/expected/runjusttests_3.out diff --git a/Makefile b/Makefile index c94d6ca98c50..4db481bf5f1f 100644 --- a/Makefile +++ b/Makefile @@ -100,6 +100,9 @@ sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql ifeq ($(shell echo $(VERSION) | grep -qE "9[.][012]|8[.][1234]" && echo yes || echo no),yes) patch -p0 < compat/install-9.2.patch endif +ifeq ($(shell echo $(VERSION) | grep -qE "9[.][01]|8[.][1234]" && echo yes || echo no),yes) + patch -p0 < compat/install-9.1.patch +endif ifeq ($(shell echo $(VERSION) | grep -qE "9[.]0|8[.][1234]" && echo yes || echo no),yes) patch -p0 < compat/install-9.0.patch endif @@ -138,6 +141,7 @@ sql/pgtap-core.sql: sql/pgtap.sql.in sql/pgtap-schema.sql: sql/pgtap.sql.in cp $< $@ sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-9.2.patch | patch -p0 + sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-9.1.patch | patch -p0 sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-9.0.patch | patch -p0 sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-8.4.patch | patch -p0 sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-8.3.patch | patch -p0 diff --git a/compat/install-9.1.patch b/compat/install-9.1.patch new file mode 100644 index 000000000000..a8631e943cd2 --- /dev/null +++ b/compat/install-9.1.patch @@ -0,0 +1,24 @@ +--- sql/pgtap.sql ++++ sql/pgtap.sql +@@ -791,10 +791,6 @@ + RETURN ok( TRUE, descr ); + EXCEPTION WHEN OTHERS THEN + -- There should have been no exception. +- GET STACKED DIAGNOSTICS +- detail = PG_EXCEPTION_DETAIL, +- hint = PG_EXCEPTION_HINT, +- context = PG_EXCEPTION_CONTEXT; + RETURN ok( FALSE, descr ) || E'\n' || diag( + ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) + ); +@@ -6160,10 +6156,6 @@ + -- 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; + END; + + -- Always raise an exception to rollback any changes. diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 244b354c8328..17fc66650c11 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7612,6 +7612,12 @@ No changes. Everything should just work. include schema, table, column, data type, or constraint information, since such diagnostics were not introduced until 9.3. +9.1 and Down +------------ +* Diagnostic output from `lives_ok()` and xUnit function exceptions will not + error context or details, since such diagnostics were not introduced until + 9.2. + 9.0 and Down ------------ * The `foreign_table_owner_is()` function will not work, because, of course, diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index c696c35114bd..4d9b6403b6d8 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -756,19 +756,19 @@ CREATE OR REPLACE FUNCTION _error_diag( typname TEXT DEFAULT NULL ) RETURNS TEXT AS $$ SELECT COALESCE( - COALESCE( NULLIF(sqlstate, '') || ': ', '' ) || COALESCE( NULLIF(errmsg, ''), '' ), + COALESCE( NULLIF($1, '') || ': ', '' ) || COALESCE( NULLIF($2, ''), '' ), 'NO ERROR FOUND' ) - || COALESCE(E'\n DETAIL: ' || nullif(detail, ''), '') - || COALESCE(E'\n HINT: ' || nullif(hint, ''), '') - || COALESCE(E'\n SCHEMA: ' || nullif(schname, ''), '') - || COALESCE(E'\n TABLE: ' || nullif(tabname, ''), '') - || COALESCE(E'\n COLUMN: ' || nullif(colname, ''), '') - || COALESCE(E'\n CONSTRAINT: ' || nullif(chkname, ''), '') - || COALESCE(E'\n TYPE: ' || nullif(typname, ''), '') + || 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( context, ''), '^', ' ', 'gn' + || regexp_replace(NULLIF( $5, ''), '^', ' ', 'gn' ), ''); $$ LANGUAGE sql IMMUTABLE; diff --git a/test/expected/runjusttests_3.out b/test/expected/runjusttests_3.out new file mode 100644 index 000000000000..767b0ab63ecf --- /dev/null +++ b/test/expected/runjusttests_3.out @@ -0,0 +1,41 @@ +\unset ECHO + # Subtest: whatever."test ident"() + ok 1 - ident + ok 2 - ident 2 + 1..2 +ok 1 - whatever."test ident" + # Subtest: whatever.testnada() + 1..0 + # No tests run! +not ok 2 - whatever.testnada +# Failed test 2: "whatever.testnada" + # Subtest: whatever.testplpgsql() + ok 1 - plpgsql simple + ok 2 - plpgsql simple 2 + ok 3 - Should be a 1 in the test table + 1..3 +ok 3 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. +not ok 4 - whatever.testplpgsqldie +# Failed test 4: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - simple pass + ok 2 - another simple pass + 1..2 +ok 5 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - pass + not ok 2 - this test intentionally fails + # Failed test 2: "this test intentionally fails" + 1..2 + # Looks like you failed 1 tests of 2 +not ok 6 - whatever.testy +# Failed test 6: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - Late test should find nothing in the test table + 1..1 +ok 7 - whatever.testz +1..7 +# Looks like you failed 3 tests of 7 diff --git a/test/expected/runtests_2.out b/test/expected/runtests_2.out index af097face9e8..79c65048e69a 100644 --- a/test/expected/runtests_2.out +++ b/test/expected/runtests_2.out @@ -28,13 +28,6 @@ ok 4 - whatever.testplpgsql ok 3 - setup more # Test died: P0001: This test should die, but not halt execution. # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. - # DETAIL: DETAIL - # CONTEXT: - # SQL statement "SELECT __die();" - # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement - # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement - # SQL function "runtests" statement 1 - # SQL function "runtests" statement 1 not ok 5 - whatever.testplpgsqldie # Failed test 5: "whatever.testplpgsqldie" # Subtest: whatever.testthis() @@ -101,12 +94,6 @@ ok 4 - whatever.testplpgsql ok 3 - setup more # Test died: P0001: This test should die, but not halt execution. # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. - # DETAIL: DETAIL - # CONTEXT: - # SQL statement "SELECT __die();" - # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement - # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement - # SQL function "runtests" statement 1 not ok 5 - whatever.testplpgsqldie # Failed test 5: "whatever.testplpgsqldie" # Subtest: whatever.testthis() diff --git a/test/sql/throwtap.sql b/test/sql/throwtap.sql index 73fe271c598b..a415410f551e 100644 --- a/test/sql/throwtap.sql +++ b/test/sql/throwtap.sql @@ -122,11 +122,11 @@ SELECT * FROM check_test( false, 'lives_ok failure diagnostics', '', - ' died: P0001: todo_end() called without todo_start() - CONTEXT: + ' died: P0001: todo_end() called without todo_start()' + || CASE WHEN pg_version_num() < 90200 THEN '' ELSE ' CONTEXT: SQL statement "SELECT * FROM todo_end()" PL/pgSQL function lives_ok(text,text) line 14 at EXECUTE' - || CASE WHEN pg_version_num() >= 90500 THEN '' ELSE ' statement' END + || CASE WHEN pg_version_num() >= 90500 THEN '' ELSE ' statement' END END ); /****************************************************************************/ From 8eb5df99d37241adafe8f52c0a4e9dc1023a4abe Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 13 May 2016 15:17:30 -0700 Subject: [PATCH 0889/1195] Update for 9.1. Mostly just need to remove has_extensions(). --- compat/install-9.0.patch | 59 ++++++++++++++++++++++++++++++++++++++-- doc/pgtap.mmd | 4 +-- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/compat/install-9.0.patch b/compat/install-9.0.patch index 269c4a0af5f2..94f86af7bc66 100644 --- a/compat/install-9.0.patch +++ b/compat/install-9.0.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -3585,7 +3585,7 @@ RETURNS TEXT AS $$ +@@ -3622,7 +3622,7 @@ RETURNS TEXT AS $$ AND n.nspname = $1 AND t.typname = $2 AND t.typtype = 'e' @@ -9,7 +9,7 @@ ), $3, $4 -@@ -3613,7 +3613,7 @@ RETURNS TEXT AS $$ +@@ -3650,7 +3650,7 @@ RETURNS TEXT AS $$ AND pg_catalog.pg_type_is_visible(t.oid) AND t.typname = $1 AND t.typtype = 'e' @@ -18,3 +18,58 @@ ), $2, $3 +@@ -9484,54 +9484,3 @@ + + 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; diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 17fc66650c11..c595adfd6215 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7622,8 +7622,8 @@ No changes. Everything should just work. ------------ * The `foreign_table_owner_is()` function will not work, because, of course, there were no foreign tables until 9.1. -* The `extensions_are()` functions will not work, because extensions were not - introduced until 9.1. +* The `extensions_are()` functions are not available, because extensions were + not introduced until 9.1. 8.4 and Down ------------ From a765aff543f951d4047c7386f6978db93b96ef76 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 13 May 2016 16:02:14 -0700 Subject: [PATCH 0890/1195] Update for 8.4. Includes a new patch to remove the `DROP FUNCTION file has_exceptions() statements from the uninstall file. Speaking of which, we add those statemetns to the uninstall file and update it with other stuff that has changed since who knows when. As a result, drop a duplicate function declarationfor _get_schema_privs() from the install file and make the layout of the performs_within() and _error_diag() functions the same as all the others, so it's easy to regenerate the uninstall file. --- Makefile | 3 +++ compat/install-8.4.patch | 8 +++--- compat/uninstall-8.4.patch | 12 +++++++++ sql/pgtap.sql.in | 46 ++++++++-------------------------- sql/uninstall_pgtap.sql.in | 51 +++++++++++++++++++++++++++++++------- test/sql/privs.sql | 4 +-- 6 files changed, 73 insertions(+), 51 deletions(-) create mode 100644 compat/uninstall-8.4.patch diff --git a/Makefile b/Makefile index 4db481bf5f1f..429e80bd3ce3 100644 --- a/Makefile +++ b/Makefile @@ -123,6 +123,9 @@ endif sql/uninstall_pgtap.sql: sql/uninstall_pgtap.sql.in test/setup.sql cp sql/uninstall_pgtap.sql.in sql/uninstall_pgtap.sql +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][1234]" && echo yes || echo no),yes) + patch -p0 < compat/uninstall-8.4.patch +endif ifeq ($(shell echo $(VERSION) | grep -qE "8[.][123]" && echo yes || echo no),yes) patch -p0 < compat/uninstall-8.3.patch endif diff --git a/compat/install-8.4.patch b/compat/install-8.4.patch index e6a6d39b95f9..5f8424bd06ab 100644 --- a/compat/install-8.4.patch +++ b/compat/install-8.4.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -7418,7 +7418,6 @@ +@@ -7473,7 +7473,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -8,7 +8,7 @@ EXCEPT SELECT $3[i] FROM generate_series(1, array_upper($3, 1)) s(i) -@@ -7433,7 +7432,6 @@ +@@ -7488,7 +7487,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -16,7 +16,7 @@ ), $4 ); -@@ -7457,7 +7455,6 @@ +@@ -7512,7 +7510,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = $1 AND n.nspname NOT IN ('pg_catalog', 'information_schema') @@ -24,7 +24,7 @@ EXCEPT SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) -@@ -7471,7 +7468,6 @@ +@@ -7526,7 +7523,6 @@ 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') diff --git a/compat/uninstall-8.4.patch b/compat/uninstall-8.4.patch new file mode 100644 index 000000000000..192960a2ea6d --- /dev/null +++ b/compat/uninstall-8.4.patch @@ -0,0 +1,12 @@ +--- sql/uninstall_pgtap.sql ++++ sql/uninstall_pgtap.sql +@@ -1,9 +1,3 @@ +-DROP FUNCTION extensions_are( NAME[] ) +-DROP FUNCTION extensions_are( NAME[], TEXT ); +-DROP FUNCTION extensions_are( NAME, NAME[] ); +-DROP FUNCTION extensions_are( NAME, NAME[], TEXT ); +-DROP FUNCTION _extensions(); +-DROP FUNCTION _extensions( NAME ); + DROP FUNCTION foreign_tables_are ( NAME[] ); + DROP FUNCTION foreign_tables_are ( NAME, NAME[] ); + DROP FUNCTION foreign_tables_are ( NAME[], TEXT ); diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 4d9b6403b6d8..0134b3b12194 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -743,18 +743,8 @@ RETURNS TEXT AS $$ SELECT throws_ok( $1, $2::char(5), NULL, NULL ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _error_diag( - sqlstate TEXT DEFAULT NULL, - errmsg TEXT DEFAULT NULL, - detail TEXT DEFAULT NULL, - hint TEXT DEFAULT NULL, - context TEXT DEFAULT NULL, - schname TEXT DEFAULT NULL, - tabname TEXT DEFAULT NULL, - colname TEXT DEFAULT NULL, - chkname TEXT DEFAULT NULL, - typname TEXT DEFAULT NULL -) RETURNS TEXT AS $$ +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' @@ -888,7 +878,8 @@ 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 $$ +CREATE OR REPLACE FUNCTION performs_within(TEXT, NUMERIC, NUMERIC, INT, TEXT) +RETURNS TEXT AS $$ DECLARE query TEXT := _query($1); expected_avg ALIAS FOR $2; @@ -906,20 +897,23 @@ END; $$ LANGUAGE plpgsql; -- performs_within( sql, average_milliseconds, within, iterations ) -CREATE OR REPLACE FUNCTION performs_within(TEXT, NUMERIC, NUMERIC, INT) RETURNS TEXT AS $$ +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 $$ +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 $$ +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'); @@ -9283,26 +9277,6 @@ RETURNS TEXT AS $$ ); $$ 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; - CREATE OR REPLACE FUNCTION _get_server_privs (NAME, TEXT) RETURNS TEXT[] AS $$ BEGIN diff --git a/sql/uninstall_pgtap.sql.in b/sql/uninstall_pgtap.sql.in index a7d6389911e2..d4667345bd26 100644 --- a/sql/uninstall_pgtap.sql.in +++ b/sql/uninstall_pgtap.sql.in @@ -1,3 +1,27 @@ +DROP FUNCTION extensions_are( NAME[] ) +DROP FUNCTION extensions_are( NAME[], TEXT ); +DROP FUNCTION extensions_are( NAME, NAME[] ); +DROP FUNCTION extensions_are( NAME, NAME[], TEXT ); +DROP FUNCTION _extensions(); +DROP FUNCTION _extensions( NAME ); +DROP FUNCTION foreign_tables_are ( NAME[] ); +DROP FUNCTION foreign_tables_are ( NAME, NAME[] ); +DROP FUNCTION foreign_tables_are ( NAME[], TEXT ); +DROP FUNCTION foreign_tables_are ( NAME, NAME[], TEXT ); +DROP FUNCTION hasnt_materialized_view ( NAME ); +DROP FUNCTION hasnt_materialized_view ( NAME, TEXT ); +DROP FUNCTION hasnt_materialized_view ( NAME, NAME, TEXT ); +DROP FUNCTION has_materialized_view ( NAME ); +DROP FUNCTION has_materialized_view ( NAME, TEXT ); +DROP FUNCTION has_materialized_view ( NAME, NAME, TEXT ); +DROP FUNCTION materialized_view_owner_is ( NAME, NAME ); +DROP FUNCTION materialized_view_owner_is ( NAME, NAME, TEXT ); +DROP FUNCTION materialized_view_owner_is ( NAME, NAME, NAME ); +DROP FUNCTION materialized_view_owner_is ( NAME, NAME, NAME, TEXT ); +DROP FUNCTION materialized_views_are ( NAME[] ); +DROP FUNCTION materialized_views_are ( NAME, NAME[] ); +DROP FUNCTION materialized_views_are ( NAME[], TEXT ); +DROP FUNCTION materialized_views_are ( NAME, NAME[], TEXT ); DROP FUNCTION server_privs_are ( NAME, NAME, NAME[] ); DROP FUNCTION server_privs_are ( NAME, NAME, NAME[], TEXT ); DROP FUNCTION _get_server_privs (NAME, TEXT); @@ -25,7 +49,6 @@ DROP FUNCTION tablespace_privs_are ( NAME, NAME, NAME[], TEXT ); DROP FUNCTION _get_tablespaceprivs (NAME, TEXT); DROP FUNCTION schema_privs_are ( NAME, NAME, NAME[] ); DROP FUNCTION schema_privs_are ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _get_schema_privs(NAME, TEXT); DROP FUNCTION language_privs_are ( NAME, NAME, NAME[] ); DROP FUNCTION language_privs_are ( NAME, NAME, NAME[], TEXT ); DROP FUNCTION _get_lang_privs (NAME, TEXT); @@ -33,8 +56,8 @@ DROP FUNCTION function_privs_are ( NAME, NAME[], NAME, NAME[] ); DROP FUNCTION function_privs_are ( NAME, NAME[], NAME, NAME[], TEXT ); DROP FUNCTION function_privs_are ( NAME, NAME, NAME[], NAME, NAME[] ); DROP FUNCTION function_privs_are ( NAME, NAME, NAME[], NAME, NAME[], TEXT ); -DROP FUNCTION _fprivs_are ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _get_func_privs(NAME, TEXT); +DROP FUNCTION _fprivs_are ( TEXT, NAME, NAME[], TEXT ); +DROP FUNCTION _get_func_privs(TEXT, TEXT); DROP FUNCTION database_privs_are ( NAME, NAME, NAME[] ); DROP FUNCTION database_privs_are ( NAME, NAME, NAME[], TEXT ); DROP FUNCTION _get_db_privs(NAME, TEXT); @@ -250,7 +273,9 @@ DROP FUNCTION do_tap( name, text ); DROP FUNCTION _is_verbose(); DROP FUNCTION _runem( text[], boolean ); DROP FUNCTION findfuncs( TEXT ); +DROP FUNCTION findfuncs( TEXT, TEXT ); DROP FUNCTION findfuncs( NAME, TEXT ); +DROP FUNCTION findfuncs( NAME, TEXT, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); @@ -627,6 +652,8 @@ DROP FUNCTION col_is_unique ( NAME, NAME, TEXT ); DROP FUNCTION col_is_unique ( NAME, NAME, NAME, TEXT ); DROP FUNCTION col_is_unique ( NAME, NAME[] ); DROP FUNCTION col_is_unique ( NAME, NAME[], TEXT ); +DROP FUNCTION col_is_unique ( NAME, NAME, NAME ); +DROP FUNCTION col_is_unique ( NAME, NAME, NAME[] ); DROP FUNCTION col_is_unique ( NAME, NAME, NAME[], TEXT ); DROP FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ); DROP FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ); @@ -733,15 +760,18 @@ DROP FUNCTION has_composite ( NAME, TEXT ); DROP FUNCTION has_composite ( NAME, NAME, TEXT ); DROP FUNCTION hasnt_foreign_table ( NAME ); DROP FUNCTION hasnt_foreign_table ( NAME, TEXT ); +DROP FUNCTION hasnt_foreign_table ( NAME, NAME ); DROP FUNCTION hasnt_foreign_table ( NAME, NAME, TEXT ); DROP FUNCTION has_foreign_table ( NAME ); DROP FUNCTION has_foreign_table ( NAME, TEXT ); +DROP FUNCTION has_foreign_table ( NAME, NAME ); DROP FUNCTION has_foreign_table ( NAME, NAME, TEXT ); DROP FUNCTION hasnt_sequence ( NAME ); DROP FUNCTION hasnt_sequence ( NAME, TEXT ); DROP FUNCTION hasnt_sequence ( NAME, NAME, TEXT ); DROP FUNCTION has_sequence ( NAME ); DROP FUNCTION has_sequence ( NAME, TEXT ); +DROP FUNCTION has_sequence ( NAME, NAME ); DROP FUNCTION has_sequence ( NAME, NAME, TEXT ); DROP FUNCTION hasnt_view ( NAME ); DROP FUNCTION hasnt_view ( NAME, TEXT ); @@ -751,9 +781,11 @@ DROP FUNCTION has_view ( NAME, TEXT ); DROP FUNCTION has_view ( NAME, NAME, TEXT ); DROP FUNCTION hasnt_table ( NAME ); DROP FUNCTION hasnt_table ( NAME, TEXT ); +DROP FUNCTION hasnt_table ( NAME, NAME ); DROP FUNCTION hasnt_table ( NAME, NAME, TEXT ); DROP FUNCTION has_table ( NAME ); DROP FUNCTION has_table ( NAME, TEXT ); +DROP FUNCTION has_table ( NAME, NAME ); DROP FUNCTION has_table ( NAME, NAME, TEXT ); DROP FUNCTION _rexists ( CHAR, NAME ); DROP FUNCTION _rexists ( CHAR, NAME, NAME ); @@ -765,15 +797,16 @@ DROP FUNCTION has_relation ( NAME, TEXT ); DROP FUNCTION has_relation ( NAME, NAME, TEXT ); DROP FUNCTION _relexists ( NAME ); DROP FUNCTION _relexists ( NAME, NAME ); +DROP FUNCTION performs_within(TEXT, NUMERIC, NUMERIC); +DROP FUNCTION performs_within(TEXT, NUMERIC, NUMERIC, TEXT); +DROP FUNCTION performs_within(TEXT, NUMERIC, NUMERIC, INT); +DROP FUNCTION performs_within(TEXT, NUMERIC, NUMERIC, INT, TEXT); +DROP FUNCTION _time_trials(TEXT, INT, NUMERIC); DROP FUNCTION performs_ok ( TEXT, NUMERIC ); DROP FUNCTION performs_ok ( TEXT, NUMERIC, TEXT ); -DROP FUNCTION performs_within ( TEXT, NUMERIC, NUMERIC, INT, TEXT ); -DROP FUNCTION performs_within ( TEXT, NUMERIC, NUMERIC, TEXT ); -DROP FUNCTION performs_within ( TEXT, NUMERIC, NUMERIC, INT ); -DROP FUNCTION performs_within ( TEXT, NUMERIC, NUMERIC ); -DROP FUNCTION _time_trials( TEXT, INT, NUMERIC ); DROP FUNCTION lives_ok ( TEXT ); DROP FUNCTION lives_ok ( TEXT, TEXT ); +DROP FUNCTION _error_diag( TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT ); DROP FUNCTION throws_ok ( TEXT, int4 ); DROP FUNCTION throws_ok ( TEXT, int4, TEXT ); DROP FUNCTION throws_ok ( TEXT, int4, TEXT, TEXT ); @@ -830,7 +863,7 @@ DROP FUNCTION diag( VARIADIC text[] ); DROP FUNCTION diag ( msg anyelement ); DROP FUNCTION diag ( msg text ); DROP FUNCTION finish (); -DROP FUNCTION _finish ( INTEGER, INTEGER, INTEGER); +DROP FUNCTION _finish (INTEGER, INTEGER, INTEGER); DROP FUNCTION num_failed (); DROP FUNCTION add_result ( bool, bool, text, text, text ); DROP FUNCTION _add ( text, integer ); diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 576defe88db4..a9d3f688f3ed 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -948,7 +948,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( pass('whatever'), true, - 'sequence_privs_are(LOL, ASeq, role, privs)', + 'sequence_privs_are(sch, seq, role, privs)', 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -980,7 +980,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( pass('whatever'), true, - 'sequence_privs_are(ASeq, role, privs)', + 'sequence_privs_are(seq, role, privs)', 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; From 6eebb81aee46cd868d19165dadc09907a0128be9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 13 May 2016 16:15:02 -0700 Subject: [PATCH 0891/1195] Auto-generate the uninstall file. This allows us to drop a bunch of boilerplate and patches, and always have a much more accurate and up-to-date uninstall file. Just depends on the format of the CREATE OR REPLACE functions always being correct in the pgtap.sql file. Which it now is. --- Changes | 3 + Makefile | 13 +- compat/uninstall-8.2.patch | 39 -- compat/uninstall-8.3.patch | 20 - compat/uninstall-8.4.patch | 12 - sql/pgtap.sql.in | 2 +- sql/uninstall_pgtap.sql.in | 884 ------------------------------------- 7 files changed, 6 insertions(+), 967 deletions(-) delete mode 100644 compat/uninstall-8.2.patch delete mode 100644 compat/uninstall-8.3.patch delete mode 100644 compat/uninstall-8.4.patch delete mode 100644 sql/uninstall_pgtap.sql.in diff --git a/Changes b/Changes index b0ee5ec90d24..ca1560818465 100644 --- a/Changes +++ b/Changes @@ -17,6 +17,9 @@ Revision history for pgTAP * Fixed a test failure due to the use of a hash index on PostgresSQL 9.5. * Added `extensions_are()`, prompted by a pull request from Grégoire HUBERT (Issue #98). +* The `sql/unininstall_pgtap.sql` file is now generated by `make` from the + `sql/pgtap.sql` file. This should make it much more up-to-date and accurate + in the future. 0.95.0 2015-03-20T20:31:41Z --------------------------- diff --git a/Makefile b/Makefile index 429e80bd3ce3..3a4d388f853f 100644 --- a/Makefile +++ b/Makefile @@ -121,17 +121,8 @@ endif sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap.sql > sql/pgtap.tmp mv sql/pgtap.tmp sql/pgtap.sql -sql/uninstall_pgtap.sql: sql/uninstall_pgtap.sql.in test/setup.sql - cp sql/uninstall_pgtap.sql.in sql/uninstall_pgtap.sql -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][1234]" && echo yes || echo no),yes) - patch -p0 < compat/uninstall-8.4.patch -endif -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][123]" && echo yes || echo no),yes) - patch -p0 < compat/uninstall-8.3.patch -endif -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][12]" && echo yes || echo no),yes) - patch -p0 < compat/uninstall-8.2.patch -endif +sql/uninstall_pgtap.sql: sql/pgtap.sql test/setup.sql + grep 'CREATE OR REPLACE ' sql/pgtap.sql | $(PERL) -e 'for (reverse ) { chomp; s/CREATE OR REPLACE/DROP/; print "$$_;\n" }' > sql/uninstall_pgtap.sql sql/pgtap-core.sql: sql/pgtap.sql.in cp $< $@ diff --git a/compat/uninstall-8.2.patch b/compat/uninstall-8.2.patch deleted file mode 100644 index 042951ffa2c4..000000000000 --- a/compat/uninstall-8.2.patch +++ /dev/null @@ -1,39 +0,0 @@ ---- sql/uninstall_pgtap.sql -+++ sql/uninstall_pgtap.sql -@@ -490,10 +490,6 @@ - DROP FUNCTION has_role( NAME ); - DROP FUNCTION has_role( NAME, TEXT ); - DROP FUNCTION _has_role( NAME ); --DROP FUNCTION enum_has_labels( NAME, NAME[] ); --DROP FUNCTION enum_has_labels( NAME, NAME[], TEXT ); --DROP FUNCTION enum_has_labels( NAME, NAME, NAME[] ); --DROP FUNCTION enum_has_labels( NAME, NAME, NAME[], TEXT ); - DROP FUNCTION hasnt_enum( NAME ); - DROP FUNCTION hasnt_enum( NAME, TEXT ); - DROP FUNCTION hasnt_enum( NAME, NAME ); -@@ -824,9 +820,6 @@ - DROP FUNCTION is (anyelement, anyelement, text); - DROP FUNCTION ok ( boolean ); - DROP FUNCTION ok ( boolean, text ); --DROP FUNCTION diag( VARIADIC anyarray ); --DROP FUNCTION diag( VARIADIC text[] ); --DROP FUNCTION diag ( msg anyelement ); - DROP FUNCTION diag ( msg text ); - DROP FUNCTION finish (); - DROP FUNCTION _finish ( INTEGER, INTEGER, INTEGER); -@@ -849,3 +842,15 @@ - DROP FUNCTION os_name(); - DROP FUNCTION pg_version_num(); - DROP FUNCTION pg_version(); -+DROP CAST (regtype AS text); -+DROP FUNCTION regtypetext(regtype); -+DROP OPERATOR <> ( name[], name[] ); -+DROP FUNCTION namearray_ne( name[], name[] ); -+DROP OPERATOR = ( name[], name[] ); -+DROP FUNCTION namearray_eq( name[], name[] ); -+DROP CAST (name[] AS text); -+DROP FUNCTION namearray_text(name[]); -+DROP CAST (text[] AS text); -+DROP FUNCTION textarray_text(text[]); -+DROP CAST (boolean AS char(1)); -+DROP FUNCTION booltext(boolean); diff --git a/compat/uninstall-8.3.patch b/compat/uninstall-8.3.patch deleted file mode 100644 index 94c74934081c..000000000000 --- a/compat/uninstall-8.3.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- sql/uninstall_pgtap.sql -+++ sql/uninstall_pgtap.sql -@@ -169,8 +169,7 @@ - DROP FUNCTION throws_like ( TEXT, TEXT ); - DROP FUNCTION throws_like ( TEXT, TEXT, TEXT ); - DROP FUNCTION _tlike ( BOOLEAN, TEXT, TEXT, TEXT ); --DROP FUNCTION collect_tap( VARCHAR[] ); --DROP FUNCTION collect_tap( VARIADIC text[] ); -+DROP FUNCTION collect_tap( text[] ); - DROP FUNCTION isnt_empty( TEXT ); - DROP FUNCTION isnt_empty( TEXT, TEXT ); - DROP FUNCTION is_empty( TEXT ); -@@ -845,6 +844,7 @@ - DROP FUNCTION _get ( text ); - DROP FUNCTION no_plan(); - DROP FUNCTION plan( integer ); -+DROP FUNCTION pg_typeof("any"); - DROP FUNCTION pgtap_version(); - DROP FUNCTION os_name(); - DROP FUNCTION pg_version_num(); diff --git a/compat/uninstall-8.4.patch b/compat/uninstall-8.4.patch deleted file mode 100644 index 192960a2ea6d..000000000000 --- a/compat/uninstall-8.4.patch +++ /dev/null @@ -1,12 +0,0 @@ ---- sql/uninstall_pgtap.sql -+++ sql/uninstall_pgtap.sql -@@ -1,9 +1,3 @@ --DROP FUNCTION extensions_are( NAME[] ) --DROP FUNCTION extensions_are( NAME[], TEXT ); --DROP FUNCTION extensions_are( NAME, NAME[] ); --DROP FUNCTION extensions_are( NAME, NAME[], TEXT ); --DROP FUNCTION _extensions(); --DROP FUNCTION _extensions( NAME ); - DROP FUNCTION foreign_tables_are ( NAME[] ); - DROP FUNCTION foreign_tables_are ( NAME, NAME[] ); - DROP FUNCTION foreign_tables_are ( NAME[], TEXT ); diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 0134b3b12194..17ddc5791bdc 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -743,7 +743,7 @@ 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) +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, ''), '' ), diff --git a/sql/uninstall_pgtap.sql.in b/sql/uninstall_pgtap.sql.in deleted file mode 100644 index d4667345bd26..000000000000 --- a/sql/uninstall_pgtap.sql.in +++ /dev/null @@ -1,884 +0,0 @@ -DROP FUNCTION extensions_are( NAME[] ) -DROP FUNCTION extensions_are( NAME[], TEXT ); -DROP FUNCTION extensions_are( NAME, NAME[] ); -DROP FUNCTION extensions_are( NAME, NAME[], TEXT ); -DROP FUNCTION _extensions(); -DROP FUNCTION _extensions( NAME ); -DROP FUNCTION foreign_tables_are ( NAME[] ); -DROP FUNCTION foreign_tables_are ( NAME, NAME[] ); -DROP FUNCTION foreign_tables_are ( NAME[], TEXT ); -DROP FUNCTION foreign_tables_are ( NAME, NAME[], TEXT ); -DROP FUNCTION hasnt_materialized_view ( NAME ); -DROP FUNCTION hasnt_materialized_view ( NAME, TEXT ); -DROP FUNCTION hasnt_materialized_view ( NAME, NAME, TEXT ); -DROP FUNCTION has_materialized_view ( NAME ); -DROP FUNCTION has_materialized_view ( NAME, TEXT ); -DROP FUNCTION has_materialized_view ( NAME, NAME, TEXT ); -DROP FUNCTION materialized_view_owner_is ( NAME, NAME ); -DROP FUNCTION materialized_view_owner_is ( NAME, NAME, TEXT ); -DROP FUNCTION materialized_view_owner_is ( NAME, NAME, NAME ); -DROP FUNCTION materialized_view_owner_is ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION materialized_views_are ( NAME[] ); -DROP FUNCTION materialized_views_are ( NAME, NAME[] ); -DROP FUNCTION materialized_views_are ( NAME[], TEXT ); -DROP FUNCTION materialized_views_are ( NAME, NAME[], TEXT ); -DROP FUNCTION server_privs_are ( NAME, NAME, NAME[] ); -DROP FUNCTION server_privs_are ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _get_server_privs (NAME, TEXT); -DROP FUNCTION _get_schema_privs(NAME, TEXT); -DROP FUNCTION fdw_privs_are ( NAME, NAME, NAME[] ); -DROP FUNCTION fdw_privs_are ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _get_fdw_privs (NAME, TEXT); -DROP FUNCTION column_privs_are ( NAME, NAME, NAME, NAME[] ); -DROP FUNCTION column_privs_are ( NAME, NAME, NAME, NAME[], TEXT ); -DROP FUNCTION column_privs_are ( NAME, NAME, NAME, NAME, NAME[] ); -DROP FUNCTION column_privs_are ( NAME, NAME, NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _get_col_privs(NAME, TEXT, NAME); -DROP FUNCTION any_column_privs_are ( NAME, NAME, NAME[] ); -DROP FUNCTION any_column_privs_are ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION any_column_privs_are ( NAME, NAME, NAME, NAME[] ); -DROP FUNCTION any_column_privs_are ( NAME, NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _get_ac_privs(NAME, TEXT); -DROP FUNCTION sequence_privs_are ( NAME, NAME, NAME[] ); -DROP FUNCTION sequence_privs_are ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION sequence_privs_are ( NAME, NAME, NAME, NAME[] ); -DROP FUNCTION sequence_privs_are ( NAME, NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _get_sequence_privs(NAME, TEXT); -DROP FUNCTION tablespace_privs_are ( NAME, NAME, NAME[] ); -DROP FUNCTION tablespace_privs_are ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _get_tablespaceprivs (NAME, TEXT); -DROP FUNCTION schema_privs_are ( NAME, NAME, NAME[] ); -DROP FUNCTION schema_privs_are ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION language_privs_are ( NAME, NAME, NAME[] ); -DROP FUNCTION language_privs_are ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _get_lang_privs (NAME, TEXT); -DROP FUNCTION function_privs_are ( NAME, NAME[], NAME, NAME[] ); -DROP FUNCTION function_privs_are ( NAME, NAME[], NAME, NAME[], TEXT ); -DROP FUNCTION function_privs_are ( NAME, NAME, NAME[], NAME, NAME[] ); -DROP FUNCTION function_privs_are ( NAME, NAME, NAME[], NAME, NAME[], TEXT ); -DROP FUNCTION _fprivs_are ( TEXT, NAME, NAME[], TEXT ); -DROP FUNCTION _get_func_privs(TEXT, TEXT); -DROP FUNCTION database_privs_are ( NAME, NAME, NAME[] ); -DROP FUNCTION database_privs_are ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _get_db_privs(NAME, TEXT); -DROP FUNCTION _db_privs(); -DROP FUNCTION table_privs_are ( NAME, NAME, NAME[] ); -DROP FUNCTION table_privs_are ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION table_privs_are ( NAME, NAME, NAME, NAME[] ); -DROP FUNCTION table_privs_are ( NAME, NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _table_privs(); -DROP FUNCTION _get_table_privs(NAME, TEXT); -DROP FUNCTION _assets_are ( text, text[], text[], TEXT ); -DROP FUNCTION type_owner_is ( NAME, NAME ); -DROP FUNCTION type_owner_is ( NAME, NAME, TEXT ); -DROP FUNCTION type_owner_is ( NAME, NAME, NAME ); -DROP FUNCTION type_owner_is ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION _get_type_owner ( NAME ); -DROP FUNCTION _get_type_owner ( NAME, NAME ); -DROP FUNCTION opclass_owner_is( NAME, NAME ); -DROP FUNCTION opclass_owner_is ( NAME, NAME, TEXT ); -DROP FUNCTION opclass_owner_is( NAME, NAME, NAME ); -DROP FUNCTION opclass_owner_is ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION _get_opclass_owner ( NAME ); -DROP FUNCTION _get_opclass_owner ( NAME, NAME ); -DROP FUNCTION language_owner_is ( NAME, NAME ); -DROP FUNCTION language_owner_is ( NAME, NAME, TEXT ); -DROP FUNCTION _get_language_owner( NAME ); -DROP FUNCTION index_owner_is ( NAME, NAME, NAME ); -DROP FUNCTION index_owner_is ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION index_owner_is ( NAME, NAME, NAME, NAME ); -DROP FUNCTION index_owner_is ( NAME, NAME, NAME, NAME, TEXT ); -DROP FUNCTION _get_index_owner( NAME, NAME ); -DROP FUNCTION _get_index_owner( NAME, NAME, NAME ); -DROP FUNCTION tablespace_owner_is ( NAME, NAME ); -DROP FUNCTION tablespace_owner_is ( NAME, NAME, TEXT ); -DROP FUNCTION _get_tablespace_owner( NAME ); -DROP FUNCTION function_owner_is( NAME, NAME[], NAME ); -DROP FUNCTION function_owner_is ( NAME, NAME[], NAME, TEXT ); -DROP FUNCTION function_owner_is( NAME, NAME, NAME[], NAME ); -DROP FUNCTION function_owner_is ( NAME, NAME, NAME[], NAME, TEXT ); -DROP FUNCTION _get_func_owner ( NAME, NAME[] ); -DROP FUNCTION _get_func_owner ( NAME, NAME, NAME[] ); -DROP FUNCTION foreign_table_owner_is ( NAME, NAME ); -DROP FUNCTION foreign_table_owner_is ( NAME, NAME, TEXT ); -DROP FUNCTION foreign_table_owner_is ( NAME, NAME, NAME ); -DROP FUNCTION foreign_table_owner_is ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION composite_owner_is ( NAME, NAME ); -DROP FUNCTION composite_owner_is ( NAME, NAME, TEXT ); -DROP FUNCTION composite_owner_is ( NAME, NAME, NAME ); -DROP FUNCTION composite_owner_is ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION sequence_owner_is ( NAME, NAME ); -DROP FUNCTION sequence_owner_is ( NAME, NAME, TEXT ); -DROP FUNCTION sequence_owner_is ( NAME, NAME, NAME ); -DROP FUNCTION sequence_owner_is ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION view_owner_is ( NAME, NAME ); -DROP FUNCTION view_owner_is ( NAME, NAME, TEXT ); -DROP FUNCTION view_owner_is ( NAME, NAME, NAME ); -DROP FUNCTION view_owner_is ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION table_owner_is ( NAME, NAME ); -DROP FUNCTION table_owner_is ( NAME, NAME, TEXT ); -DROP FUNCTION table_owner_is ( NAME, NAME, NAME ); -DROP FUNCTION table_owner_is ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION _get_rel_owner ( CHAR, NAME ); -DROP FUNCTION _get_rel_owner ( CHAR, NAME, NAME ); -DROP FUNCTION relation_owner_is ( NAME, NAME ); -DROP FUNCTION relation_owner_is ( NAME, NAME, TEXT ); -DROP FUNCTION relation_owner_is ( NAME, NAME, NAME ); -DROP FUNCTION relation_owner_is ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION _get_rel_owner ( NAME ); -DROP FUNCTION _get_rel_owner ( NAME, NAME ); -DROP FUNCTION schema_owner_is ( NAME, NAME ); -DROP FUNCTION schema_owner_is ( NAME, NAME, TEXT ); -DROP FUNCTION _get_schema_owner( NAME ); -DROP FUNCTION db_owner_is ( NAME, NAME ); -DROP FUNCTION db_owner_is ( NAME, NAME, TEXT ); -DROP FUNCTION _get_db_owner( NAME ); -DROP FUNCTION columns_are( NAME, NAME[] ); -DROP FUNCTION columns_are( NAME, NAME[], TEXT ); -DROP FUNCTION columns_are( NAME, NAME, NAME[] ); -DROP FUNCTION columns_are( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION operators_are ( TEXT[] ); -DROP FUNCTION operators_are( TEXT[], TEXT ); -DROP FUNCTION operators_are ( NAME, TEXT[] ); -DROP FUNCTION operators_are( NAME, TEXT[], TEXT ); -DROP FUNCTION display_oper ( NAME, OID ); -DROP FUNCTION casts_are ( TEXT[] ); -DROP FUNCTION casts_are ( TEXT[], TEXT ); -DROP FUNCTION _areni ( text, text[], text[], TEXT ); -DROP FUNCTION triggers_are( NAME, NAME[] ); -DROP FUNCTION triggers_are( NAME, NAME[], TEXT ); -DROP FUNCTION triggers_are( NAME, NAME, NAME[] ); -DROP FUNCTION triggers_are( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION row_eq( TEXT, anyelement ); -DROP FUNCTION row_eq( TEXT, anyelement, TEXT ); -DROP FUNCTION domain_type_isnt( TEXT, TEXT ); -DROP FUNCTION domain_type_isnt( TEXT, TEXT, TEXT ); -DROP FUNCTION domain_type_isnt( NAME, TEXT, TEXT ); -DROP FUNCTION domain_type_isnt( NAME, TEXT, TEXT, TEXT ); -DROP FUNCTION domain_type_isnt( NAME, TEXT, NAME, TEXT ); -DROP FUNCTION domain_type_isnt( NAME, TEXT, NAME, TEXT, TEXT ); -DROP FUNCTION domain_type_is( TEXT, TEXT ); -DROP FUNCTION domain_type_is( TEXT, TEXT, TEXT ); -DROP FUNCTION domain_type_is( NAME, TEXT, TEXT ); -DROP FUNCTION domain_type_is( NAME, TEXT, TEXT, TEXT ); -DROP FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT ); -DROP FUNCTION domain_type_is( NAME, TEXT, NAME, TEXT, TEXT ); -DROP FUNCTION _get_dtype( NAME ); -DROP FUNCTION _get_dtype( NAME, TEXT, BOOLEAN ); -DROP FUNCTION _dexists ( NAME ); -DROP FUNCTION _dexists ( NAME, NAME ); -DROP FUNCTION enums_are ( NAME[] ); -DROP FUNCTION enums_are ( NAME[], TEXT ); -DROP FUNCTION enums_are ( NAME, NAME[] ); -DROP FUNCTION enums_are ( NAME, NAME[], TEXT ); -DROP FUNCTION domains_are ( NAME[] ); -DROP FUNCTION domains_are ( NAME[], TEXT ); -DROP FUNCTION domains_are ( NAME, NAME[] ); -DROP FUNCTION domains_are ( NAME, NAME[], TEXT ); -DROP FUNCTION types_are ( NAME[] ); -DROP FUNCTION types_are ( NAME[], TEXT ); -DROP FUNCTION _types_are ( NAME[], TEXT, CHAR[] ); -DROP FUNCTION types_are ( NAME, NAME[] ); -DROP FUNCTION types_are ( NAME, NAME[], TEXT ); -DROP FUNCTION _types_are ( NAME, NAME[], TEXT, CHAR[] ); -DROP FUNCTION roles_are( NAME[] ); -DROP FUNCTION roles_are( NAME[], TEXT ); -DROP FUNCTION throws_imatching ( TEXT, TEXT ); -DROP FUNCTION throws_imatching ( TEXT, TEXT, TEXT ); -DROP FUNCTION throws_matching ( TEXT, TEXT ); -DROP FUNCTION throws_matching ( TEXT, TEXT, TEXT ); -DROP FUNCTION throws_ilike ( TEXT, TEXT ); -DROP FUNCTION throws_ilike ( TEXT, TEXT, TEXT ); -DROP FUNCTION throws_like ( TEXT, TEXT ); -DROP FUNCTION throws_like ( TEXT, TEXT, TEXT ); -DROP FUNCTION _tlike ( BOOLEAN, TEXT, TEXT, TEXT ); -DROP FUNCTION collect_tap( VARCHAR[] ); -DROP FUNCTION collect_tap( VARIADIC text[] ); -DROP FUNCTION isnt_empty( TEXT ); -DROP FUNCTION isnt_empty( TEXT, TEXT ); -DROP FUNCTION is_empty( TEXT ); -DROP FUNCTION is_empty( TEXT, TEXT ); -DROP FUNCTION isa_ok( anyelement, regtype ); -DROP FUNCTION isa_ok( anyelement, regtype, TEXT ); -DROP FUNCTION results_ne( refcursor, anyarray ); -DROP FUNCTION results_ne( refcursor, anyarray, TEXT ); -DROP FUNCTION results_ne( refcursor, TEXT ); -DROP FUNCTION results_ne( refcursor, TEXT, TEXT ); -DROP FUNCTION results_ne( TEXT, refcursor ); -DROP FUNCTION results_ne( TEXT, refcursor, TEXT ); -DROP FUNCTION results_ne( TEXT, anyarray ); -DROP FUNCTION results_ne( TEXT, anyarray, TEXT ); -DROP FUNCTION results_ne( TEXT, TEXT ); -DROP FUNCTION results_ne( TEXT, TEXT, TEXT ); -DROP FUNCTION results_ne( refcursor, refcursor ); -DROP FUNCTION results_ne( refcursor, refcursor, text ); -DROP FUNCTION results_eq( refcursor, anyarray ); -DROP FUNCTION results_eq( refcursor, anyarray, TEXT ); -DROP FUNCTION results_eq( refcursor, TEXT ); -DROP FUNCTION results_eq( refcursor, TEXT, TEXT ); -DROP FUNCTION results_eq( TEXT, refcursor ); -DROP FUNCTION results_eq( TEXT, refcursor, TEXT ); -DROP FUNCTION results_eq( TEXT, anyarray ); -DROP FUNCTION results_eq( TEXT, anyarray, TEXT ); -DROP FUNCTION results_eq( TEXT, TEXT ); -DROP FUNCTION results_eq( TEXT, TEXT, TEXT ); -DROP FUNCTION results_eq( refcursor, refcursor ); -DROP FUNCTION results_eq( refcursor, refcursor, text ); -DROP FUNCTION bag_hasnt( TEXT, TEXT ); -DROP FUNCTION bag_hasnt( TEXT, TEXT, TEXT ); -DROP FUNCTION set_hasnt( TEXT, TEXT ); -DROP FUNCTION set_hasnt( TEXT, TEXT, TEXT ); -DROP FUNCTION bag_has( TEXT, TEXT ); -DROP FUNCTION bag_has( TEXT, TEXT, TEXT ); -DROP FUNCTION set_has( TEXT, TEXT ); -DROP FUNCTION set_has( TEXT, TEXT, TEXT ); -DROP FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION bag_ne( TEXT, anyarray ); -DROP FUNCTION bag_ne( TEXT, anyarray, TEXT ); -DROP FUNCTION bag_ne( TEXT, TEXT ); -DROP FUNCTION bag_ne( TEXT, TEXT, TEXT ); -DROP FUNCTION set_ne( TEXT, anyarray ); -DROP FUNCTION set_ne( TEXT, anyarray, TEXT ); -DROP FUNCTION set_ne( TEXT, TEXT ); -DROP FUNCTION set_ne( TEXT, TEXT, TEXT ); -DROP FUNCTION _relne( TEXT, anyarray, TEXT, TEXT ); -DROP FUNCTION _relne( TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION _do_ne( TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION bag_eq( TEXT, anyarray ); -DROP FUNCTION bag_eq( TEXT, anyarray, TEXT ); -DROP FUNCTION bag_eq( TEXT, TEXT ); -DROP FUNCTION bag_eq( TEXT, TEXT, TEXT ); -DROP FUNCTION set_eq( TEXT, anyarray ); -DROP FUNCTION set_eq( TEXT, anyarray, TEXT ); -DROP FUNCTION set_eq( TEXT, TEXT ); -DROP FUNCTION set_eq( TEXT, TEXT, TEXT ); -DROP FUNCTION _relcomp( TEXT, anyarray, TEXT, TEXT ); -DROP FUNCTION _relcomp( TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION _docomp( TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION _temptypes( TEXT ); -DROP FUNCTION _temptable ( anyarray, TEXT ); -DROP FUNCTION _temptable ( TEXT, TEXT ); -DROP FUNCTION runtests( ); -DROP FUNCTION runtests( TEXT ); -DROP FUNCTION runtests( NAME ); -DROP FUNCTION runtests( NAME, TEXT ); -DROP FUNCTION _runner( text[], text[], text[], text[], text[] ); -DROP FUNCTION diag_test_name(TEXT); -DROP FUNCTION _cleanup(); -DROP FUNCTION _currtest(); -DROP FUNCTION do_tap( ); -DROP FUNCTION do_tap( text ); -DROP FUNCTION do_tap( name ); -DROP FUNCTION do_tap( name, text ); -DROP FUNCTION _is_verbose(); -DROP FUNCTION _runem( text[], boolean ); -DROP FUNCTION findfuncs( TEXT ); -DROP FUNCTION findfuncs( TEXT, TEXT ); -DROP FUNCTION findfuncs( NAME, TEXT ); -DROP FUNCTION findfuncs( NAME, TEXT, TEXT ); -DROP FUNCTION check_test( TEXT, BOOLEAN ); -DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT ); -DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT ); -DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT ); -DROP FUNCTION check_test( TEXT, BOOLEAN, TEXT, TEXT, TEXT, BOOLEAN ); -DROP FUNCTION volatility_is( NAME, TEXT ); -DROP FUNCTION volatility_is( NAME, TEXT, TEXT ); -DROP FUNCTION volatility_is( NAME, NAME[], TEXT ); -DROP FUNCTION volatility_is( NAME, NAME[], TEXT, TEXT ); -DROP FUNCTION volatility_is( NAME, NAME, TEXT ); -DROP FUNCTION volatility_is( NAME, NAME, TEXT, TEXT ); -DROP FUNCTION volatility_is( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION volatility_is( NAME, NAME, NAME[], TEXT, TEXT ); -DROP FUNCTION _vol ( NAME ); -DROP FUNCTION _vol ( NAME, NAME[] ); -DROP FUNCTION _vol ( NAME, NAME ); -DROP FUNCTION _vol ( NAME, NAME, NAME[] ); -DROP FUNCTION _refine_vol( text ); -DROP FUNCTION _expand_vol( char ); -DROP FUNCTION isnt_strict( NAME ); -DROP FUNCTION isnt_strict( NAME, TEXT ); -DROP FUNCTION isnt_strict( NAME, NAME[] ); -DROP FUNCTION isnt_strict ( NAME, NAME[], TEXT ); -DROP FUNCTION isnt_strict( NAME, NAME ); -DROP FUNCTION isnt_strict ( NAME, NAME, TEXT ); -DROP FUNCTION isnt_strict( NAME, NAME, NAME[] ); -DROP FUNCTION isnt_strict ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION is_strict( NAME ); -DROP FUNCTION is_strict( NAME, TEXT ); -DROP FUNCTION is_strict( NAME, NAME[] ); -DROP FUNCTION is_strict ( NAME, NAME[], TEXT ); -DROP FUNCTION is_strict( NAME, NAME ); -DROP FUNCTION is_strict ( NAME, NAME, TEXT ); -DROP FUNCTION is_strict( NAME, NAME, NAME[] ); -DROP FUNCTION is_strict ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _strict ( NAME ); -DROP FUNCTION _strict ( NAME, NAME[] ); -DROP FUNCTION _strict ( NAME, NAME ); -DROP FUNCTION _strict ( NAME, NAME, NAME[] ); -DROP FUNCTION is_aggregate( NAME ); -DROP FUNCTION is_aggregate( NAME, TEXT ); -DROP FUNCTION is_aggregate( NAME, NAME[] ); -DROP FUNCTION is_aggregate ( NAME, NAME[], TEXT ); -DROP FUNCTION is_aggregate( NAME, NAME ); -DROP FUNCTION is_aggregate ( NAME, NAME, TEXT ); -DROP FUNCTION is_aggregate( NAME, NAME, NAME[] ); -DROP FUNCTION is_aggregate ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _agg ( NAME ); -DROP FUNCTION _agg ( NAME, NAME[] ); -DROP FUNCTION _agg ( NAME, NAME ); -DROP FUNCTION _agg ( NAME, NAME, NAME[] ); -DROP FUNCTION is_definer( NAME ); -DROP FUNCTION is_definer( NAME, TEXT ); -DROP FUNCTION is_definer( NAME, NAME[] ); -DROP FUNCTION is_definer ( NAME, NAME[], TEXT ); -DROP FUNCTION is_definer( NAME, NAME ); -DROP FUNCTION is_definer ( NAME, NAME, TEXT ); -DROP FUNCTION is_definer( NAME, NAME, NAME[] ); -DROP FUNCTION is_definer ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _definer ( NAME ); -DROP FUNCTION _definer ( NAME, NAME[] ); -DROP FUNCTION _definer ( NAME, NAME ); -DROP FUNCTION _definer ( NAME, NAME, NAME[] ); -DROP FUNCTION function_returns( NAME, TEXT ); -DROP FUNCTION function_returns( NAME, TEXT, TEXT ); -DROP FUNCTION function_returns( NAME, NAME[], TEXT ); -DROP FUNCTION function_returns( NAME, NAME[], TEXT, TEXT ); -DROP FUNCTION function_returns( NAME, NAME, TEXT ); -DROP FUNCTION function_returns( NAME, NAME, TEXT, TEXT ); -DROP FUNCTION function_returns( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION function_returns( NAME, NAME, NAME[], TEXT, TEXT ); -DROP FUNCTION _returns ( NAME ); -DROP FUNCTION _returns ( NAME, NAME[] ); -DROP FUNCTION _returns ( NAME, NAME ); -DROP FUNCTION _returns ( NAME, NAME, NAME[] ); -DROP FUNCTION function_lang_is( NAME, NAME ); -DROP FUNCTION function_lang_is( NAME, NAME, TEXT ); -DROP FUNCTION function_lang_is( NAME, NAME[], NAME ); -DROP FUNCTION function_lang_is( NAME, NAME[], NAME, TEXT ); -DROP FUNCTION function_lang_is( NAME, NAME, NAME ); -DROP FUNCTION function_lang_is( NAME, NAME, NAME, TEXT ); -DROP FUNCTION function_lang_is( NAME, NAME, NAME[], NAME ); -DROP FUNCTION function_lang_is( NAME, NAME, NAME[], NAME, TEXT ); -DROP FUNCTION _lang ( NAME ); -DROP FUNCTION _lang ( NAME, NAME[] ); -DROP FUNCTION _lang ( NAME, NAME ); -DROP FUNCTION _lang ( NAME, NAME, NAME[] ); -DROP FUNCTION _func_compare( NAME, NAME, boolean, TEXT); -DROP FUNCTION _func_compare( NAME, NAME, anyelement, anyelement, TEXT); -DROP FUNCTION _func_compare( NAME, NAME, NAME[], boolean, TEXT); -DROP FUNCTION _func_compare( NAME, NAME, NAME[], anyelement, anyelement, TEXT); -DROP FUNCTION _nosuch( NAME, NAME, NAME[]); -DROP FUNCTION rule_is_on( NAME, NAME, TEXT ); -DROP FUNCTION rule_is_on( NAME, NAME, TEXT, TEXT ); -DROP FUNCTION rule_is_on( NAME, NAME, NAME, TEXT ); -DROP FUNCTION rule_is_on( NAME, NAME, NAME, TEXT, TEXT ); -DROP FUNCTION _rule_on( NAME, NAME ); -DROP FUNCTION _rule_on( NAME, NAME, NAME ); -DROP FUNCTION _contract_on( TEXT ); -DROP FUNCTION _expand_on( char ); -DROP FUNCTION rule_is_instead( NAME, NAME ); -DROP FUNCTION rule_is_instead( NAME, NAME, TEXT ); -DROP FUNCTION rule_is_instead( NAME, NAME, NAME ); -DROP FUNCTION rule_is_instead( NAME, NAME, NAME, TEXT ); -DROP FUNCTION hasnt_rule( NAME, NAME ); -DROP FUNCTION hasnt_rule( NAME, NAME, TEXT ); -DROP FUNCTION hasnt_rule( NAME, NAME, NAME ); -DROP FUNCTION hasnt_rule( NAME, NAME, NAME, TEXT ); -DROP FUNCTION has_rule( NAME, NAME ); -DROP FUNCTION has_rule( NAME, NAME, TEXT ); -DROP FUNCTION has_rule( NAME, NAME, NAME ); -DROP FUNCTION has_rule( NAME, NAME, NAME, TEXT ); -DROP FUNCTION _is_instead( NAME, NAME ); -DROP FUNCTION _is_instead( NAME, NAME, NAME ); -DROP FUNCTION rules_are( NAME, NAME[] ); -DROP FUNCTION rules_are( NAME, NAME[], TEXT ); -DROP FUNCTION rules_are( NAME, NAME, NAME[] ); -DROP FUNCTION rules_are( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION opclasses_are ( NAME[] ); -DROP FUNCTION opclasses_are ( NAME[], TEXT ); -DROP FUNCTION opclasses_are ( NAME, NAME[] ); -DROP FUNCTION opclasses_are ( NAME, NAME[], TEXT ); -DROP FUNCTION hasnt_opclass( NAME ); -DROP FUNCTION hasnt_opclass( NAME, TEXT ); -DROP FUNCTION hasnt_opclass( NAME, NAME ); -DROP FUNCTION hasnt_opclass( NAME, NAME, TEXT ); -DROP FUNCTION has_opclass( NAME ); -DROP FUNCTION has_opclass( NAME, TEXT ); -DROP FUNCTION has_opclass( NAME, NAME ); -DROP FUNCTION has_opclass( NAME, NAME, TEXT ); -DROP FUNCTION _opc_exists( NAME ); -DROP FUNCTION _opc_exists( NAME, NAME ); -DROP FUNCTION language_is_trusted( NAME ); -DROP FUNCTION language_is_trusted( NAME, TEXT ); -DROP FUNCTION hasnt_language( NAME ); -DROP FUNCTION hasnt_language( NAME, TEXT ); -DROP FUNCTION has_language( NAME ); -DROP FUNCTION has_language( NAME, TEXT ); -DROP FUNCTION _is_trusted( NAME ); -DROP FUNCTION languages_are( NAME[] ); -DROP FUNCTION languages_are( NAME[], TEXT ); -DROP FUNCTION groups_are( NAME[] ); -DROP FUNCTION groups_are( NAME[], TEXT ); -DROP FUNCTION users_are( NAME[] ); -DROP FUNCTION users_are( NAME[], TEXT ); -DROP FUNCTION indexes_are( NAME, NAME[] ); -DROP FUNCTION indexes_are( NAME, NAME[], TEXT ); -DROP FUNCTION indexes_are( NAME, NAME, NAME[] ); -DROP FUNCTION indexes_are( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION functions_are ( NAME[] ); -DROP FUNCTION functions_are ( NAME[], TEXT ); -DROP FUNCTION functions_are ( NAME, NAME[] ); -DROP FUNCTION functions_are ( NAME, NAME[], TEXT ); -DROP FUNCTION sequences_are ( NAME[] ); -DROP FUNCTION sequences_are ( NAME, NAME[] ); -DROP FUNCTION sequences_are ( NAME[], TEXT ); -DROP FUNCTION sequences_are ( NAME, NAME[], TEXT ); -DROP FUNCTION views_are ( NAME[] ); -DROP FUNCTION views_are ( NAME, NAME[] ); -DROP FUNCTION views_are ( NAME[], TEXT ); -DROP FUNCTION views_are ( NAME, NAME[], TEXT ); -DROP FUNCTION tables_are ( NAME[] ); -DROP FUNCTION tables_are ( NAME, NAME[] ); -DROP FUNCTION tables_are ( NAME[], TEXT ); -DROP FUNCTION tables_are ( NAME, NAME[], TEXT ); -DROP FUNCTION _missing ( CHAR, NAME[] ); -DROP FUNCTION _missing ( CHAR, NAME, NAME[] ); -DROP FUNCTION _extras ( CHAR, NAME[] ); -DROP FUNCTION _extras ( CHAR, NAME, NAME[] ); -DROP FUNCTION schemas_are ( NAME[] ); -DROP FUNCTION schemas_are ( NAME[], TEXT ); -DROP FUNCTION tablespaces_are ( NAME[] ); -DROP FUNCTION tablespaces_are ( NAME[], TEXT ); -DROP FUNCTION _are ( text, name[], name[], TEXT ); -DROP FUNCTION has_rightop ( NAME, NAME ); -DROP FUNCTION has_rightop ( NAME, NAME, TEXT ); -DROP FUNCTION has_rightop ( NAME, NAME, NAME ); -DROP FUNCTION has_rightop ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION has_rightop ( NAME, NAME, NAME, NAME ); -DROP FUNCTION has_rightop ( NAME, NAME, NAME, NAME, TEXT ); -DROP FUNCTION has_leftop ( NAME, NAME ); -DROP FUNCTION has_leftop ( NAME, NAME, TEXT ); -DROP FUNCTION has_leftop ( NAME, NAME, NAME ); -DROP FUNCTION has_leftop ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION has_leftop ( NAME, NAME, NAME, NAME ); -DROP FUNCTION has_leftop ( NAME, NAME, NAME, NAME, TEXT ); -DROP FUNCTION has_operator ( NAME, NAME, NAME ); -DROP FUNCTION has_operator ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION has_operator ( NAME, NAME, NAME, NAME ); -DROP FUNCTION has_operator ( NAME, NAME, NAME, NAME, TEXT ); -DROP FUNCTION has_operator ( NAME, NAME, NAME, NAME, NAME ); -DROP FUNCTION has_operator ( NAME, NAME, NAME, NAME, NAME, TEXT ); -DROP FUNCTION _op_exists ( NAME, NAME, NAME ); -DROP FUNCTION _op_exists ( NAME, NAME, NAME, NAME ); -DROP FUNCTION _op_exists ( NAME, NAME, NAME, NAME, NAME ); -DROP FUNCTION cast_context_is( NAME, NAME, TEXT ); -DROP FUNCTION cast_context_is( NAME, NAME, TEXT, TEXT ); -DROP FUNCTION _get_context( NAME, NAME ); -DROP FUNCTION _expand_context( char ); -DROP FUNCTION hasnt_cast ( NAME, NAME ); -DROP FUNCTION hasnt_cast ( NAME, NAME, TEXT ); -DROP FUNCTION hasnt_cast ( NAME, NAME, NAME ); -DROP FUNCTION hasnt_cast ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION hasnt_cast ( NAME, NAME, NAME, NAME ); -DROP FUNCTION hasnt_cast ( NAME, NAME, NAME, NAME, TEXT ); -DROP FUNCTION has_cast ( NAME, NAME ); -DROP FUNCTION has_cast ( NAME, NAME, TEXT ); -DROP FUNCTION has_cast ( NAME, NAME, NAME ); -DROP FUNCTION has_cast ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION has_cast ( NAME, NAME, NAME, NAME ); -DROP FUNCTION has_cast ( NAME, NAME, NAME, NAME, TEXT ); -DROP FUNCTION _cast_exists ( NAME, NAME ); -DROP FUNCTION _cast_exists ( NAME, NAME, NAME ); -DROP FUNCTION _cast_exists ( NAME, NAME, NAME, NAME ); -DROP FUNCTION _cmp_types(oid, name); -DROP FUNCTION is_member_of( NAME, NAME ); -DROP FUNCTION is_member_of( NAME, NAME[] ); -DROP FUNCTION is_member_of( NAME, NAME, TEXT ); -DROP FUNCTION is_member_of( NAME, NAME[], TEXT ); -DROP FUNCTION _grolist ( NAME ); -DROP FUNCTION hasnt_group( NAME ); -DROP FUNCTION hasnt_group( NAME, TEXT ); -DROP FUNCTION has_group( NAME ); -DROP FUNCTION has_group( NAME, TEXT ); -DROP FUNCTION _has_group( NAME ); -DROP FUNCTION isnt_superuser( NAME ); -DROP FUNCTION isnt_superuser( NAME, TEXT ); -DROP FUNCTION is_superuser( NAME ); -DROP FUNCTION is_superuser( NAME, TEXT ); -DROP FUNCTION _is_super( NAME ); -DROP FUNCTION hasnt_user( NAME ); -DROP FUNCTION hasnt_user( NAME, TEXT ); -DROP FUNCTION has_user( NAME ); -DROP FUNCTION has_user( NAME, TEXT ); -DROP FUNCTION _has_user( NAME ); -DROP FUNCTION hasnt_role( NAME ); -DROP FUNCTION hasnt_role( NAME, TEXT ); -DROP FUNCTION has_role( NAME ); -DROP FUNCTION has_role( NAME, TEXT ); -DROP FUNCTION _has_role( NAME ); -DROP FUNCTION enum_has_labels( NAME, NAME[] ); -DROP FUNCTION enum_has_labels( NAME, NAME[], TEXT ); -DROP FUNCTION enum_has_labels( NAME, NAME, NAME[] ); -DROP FUNCTION enum_has_labels( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION hasnt_enum( NAME ); -DROP FUNCTION hasnt_enum( NAME, TEXT ); -DROP FUNCTION hasnt_enum( NAME, NAME ); -DROP FUNCTION hasnt_enum( NAME, NAME, TEXT ); -DROP FUNCTION has_enum( NAME ); -DROP FUNCTION has_enum( NAME, TEXT ); -DROP FUNCTION has_enum( NAME, NAME ); -DROP FUNCTION has_enum( NAME, NAME, TEXT ); -DROP FUNCTION hasnt_domain( NAME ); -DROP FUNCTION hasnt_domain( NAME, TEXT ); -DROP FUNCTION hasnt_domain( NAME, NAME ); -DROP FUNCTION hasnt_domain( NAME, NAME, TEXT ); -DROP FUNCTION has_domain( NAME ); -DROP FUNCTION has_domain( NAME, TEXT ); -DROP FUNCTION has_domain( NAME, NAME ); -DROP FUNCTION has_domain( NAME, NAME, TEXT ); -DROP FUNCTION hasnt_type( NAME ); -DROP FUNCTION hasnt_type( NAME, TEXT ); -DROP FUNCTION hasnt_type( NAME, NAME ); -DROP FUNCTION hasnt_type( NAME, NAME, TEXT ); -DROP FUNCTION has_type( NAME ); -DROP FUNCTION has_type( NAME, TEXT ); -DROP FUNCTION has_type( NAME, NAME ); -DROP FUNCTION has_type( NAME, NAME, TEXT ); -DROP FUNCTION _has_type( NAME, CHAR[] ); -DROP FUNCTION _has_type( NAME, NAME, CHAR[] ); -DROP FUNCTION hasnt_tablespace( NAME ); -DROP FUNCTION hasnt_tablespace( NAME, TEXT ); -DROP FUNCTION has_tablespace( NAME ); -DROP FUNCTION has_tablespace( NAME, TEXT ); -DROP FUNCTION has_tablespace( NAME, TEXT, TEXT ); -DROP FUNCTION hasnt_schema( NAME ); -DROP FUNCTION hasnt_schema( NAME, TEXT ); -DROP FUNCTION has_schema( NAME ); -DROP FUNCTION has_schema( NAME, TEXT ); -DROP FUNCTION trigger_is ( NAME, NAME, NAME ); -DROP FUNCTION trigger_is ( NAME, NAME, NAME, text ); -DROP FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME ); -DROP FUNCTION trigger_is ( NAME, NAME, NAME, NAME, NAME, text ); -DROP FUNCTION hasnt_trigger ( NAME, NAME ); -DROP FUNCTION hasnt_trigger ( NAME, NAME, TEXT ); -DROP FUNCTION hasnt_trigger ( NAME, NAME, NAME ); -DROP FUNCTION hasnt_trigger ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION has_trigger ( NAME, NAME ); -DROP FUNCTION has_trigger ( NAME, NAME, TEXT ); -DROP FUNCTION has_trigger ( NAME, NAME, NAME ); -DROP FUNCTION has_trigger ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION _trig ( NAME, NAME ); -DROP FUNCTION _trig ( NAME, NAME, NAME ); -DROP FUNCTION index_is_type ( NAME, NAME ); -DROP FUNCTION index_is_type ( NAME, NAME, NAME ); -DROP FUNCTION index_is_type ( NAME, NAME, NAME, NAME ); -DROP FUNCTION index_is_type ( NAME, NAME, NAME, NAME, text ); -DROP FUNCTION is_clustered ( NAME ); -DROP FUNCTION is_clustered ( NAME, NAME ); -DROP FUNCTION is_clustered ( NAME, NAME, NAME ); -DROP FUNCTION is_clustered ( NAME, NAME, NAME, text ); -DROP FUNCTION index_is_primary ( NAME ); -DROP FUNCTION index_is_primary ( NAME, NAME ); -DROP FUNCTION index_is_primary ( NAME, NAME, NAME ); -DROP FUNCTION index_is_primary ( NAME, NAME, NAME, text ); -DROP FUNCTION index_is_unique ( NAME ); -DROP FUNCTION index_is_unique ( NAME, NAME ); -DROP FUNCTION index_is_unique ( NAME, NAME, NAME ); -DROP FUNCTION index_is_unique ( NAME, NAME, NAME, text ); -DROP FUNCTION hasnt_index ( NAME, NAME ); -DROP FUNCTION hasnt_index ( NAME, NAME, TEXT ); -DROP FUNCTION hasnt_index ( NAME, NAME, NAME ); -DROP FUNCTION hasnt_index ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION has_index ( NAME, NAME ); -DROP FUNCTION has_index ( NAME, NAME, text ); -DROP FUNCTION has_index ( NAME, NAME, NAME ); -DROP FUNCTION has_index ( NAME, NAME, NAME, text ); -DROP FUNCTION _is_schema( NAME ); -DROP FUNCTION has_index ( NAME, NAME, NAME[] ); -DROP FUNCTION has_index ( NAME, NAME, NAME[], text ); -DROP FUNCTION has_index ( NAME, NAME, NAME, NAME ); -DROP FUNCTION has_index ( NAME, NAME, NAME, NAME, text ); -DROP FUNCTION has_index ( NAME, NAME, NAME, NAME[] ); -DROP FUNCTION has_index ( NAME, NAME, NAME, NAME[], text ); -DROP FUNCTION _have_index( NAME, NAME); -DROP FUNCTION _have_index( NAME, NAME, NAME); -DROP FUNCTION _ikeys( NAME, NAME); -DROP FUNCTION _ikeys( NAME, NAME, NAME); -DROP FUNCTION can ( NAME[] ); -DROP FUNCTION can ( NAME[], TEXT ); -DROP FUNCTION can ( NAME, NAME[] ); -DROP FUNCTION can ( NAME, NAME[], TEXT ); -DROP FUNCTION _pg_sv_type_array( OID[] ); -DROP FUNCTION hasnt_function( NAME ); -DROP FUNCTION hasnt_function( NAME, TEXT ); -DROP FUNCTION hasnt_function( NAME, NAME[] ); -DROP FUNCTION hasnt_function ( NAME, NAME[], TEXT ); -DROP FUNCTION hasnt_function( NAME, NAME ); -DROP FUNCTION hasnt_function ( NAME, NAME, TEXT ); -DROP FUNCTION hasnt_function( NAME, NAME, NAME[] ); -DROP FUNCTION hasnt_function ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION has_function( NAME ); -DROP FUNCTION has_function( NAME, TEXT ); -DROP FUNCTION has_function( NAME, NAME[] ); -DROP FUNCTION has_function ( NAME, NAME[], TEXT ); -DROP FUNCTION has_function( NAME, NAME ); -DROP FUNCTION has_function ( NAME, NAME, TEXT ); -DROP FUNCTION has_function( NAME, NAME, NAME[] ); -DROP FUNCTION has_function ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _got_func ( NAME ); -DROP FUNCTION _got_func ( NAME, NAME[] ); -DROP FUNCTION _got_func ( NAME, NAME ); -DROP FUNCTION _got_func ( NAME, NAME, NAME[] ); -DROP VIEW tap_funky; -DROP FUNCTION fk_ok ( NAME, NAME, NAME, NAME ); -DROP FUNCTION fk_ok ( NAME, NAME, NAME, NAME, TEXT ); -DROP FUNCTION fk_ok ( NAME, NAME, NAME, NAME, NAME, TEXT ); -DROP FUNCTION fk_ok ( NAME, NAME, NAME, NAME, NAME, NAME, TEXT ); -DROP FUNCTION fk_ok ( NAME, NAME[], NAME, NAME[] ); -DROP FUNCTION fk_ok ( NAME, NAME, NAME[], NAME, NAME, NAME[] ); -DROP FUNCTION fk_ok ( NAME, NAME[], NAME, NAME[], TEXT ); -DROP FUNCTION fk_ok ( NAME, NAME, NAME[], NAME, NAME, NAME[], TEXT ); -DROP FUNCTION col_has_check ( NAME, NAME ); -DROP FUNCTION col_has_check ( NAME, NAME, TEXT ); -DROP FUNCTION col_has_check ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION col_has_check ( NAME, NAME[] ); -DROP FUNCTION col_has_check ( NAME, NAME[], TEXT ); -DROP FUNCTION col_has_check ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION has_check ( NAME ); -DROP FUNCTION has_check ( NAME, TEXT ); -DROP FUNCTION has_check ( NAME, NAME, TEXT ); -DROP FUNCTION col_is_unique ( NAME, NAME ); -DROP FUNCTION col_is_unique ( NAME, NAME, TEXT ); -DROP FUNCTION col_is_unique ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION col_is_unique ( NAME, NAME[] ); -DROP FUNCTION col_is_unique ( NAME, NAME[], TEXT ); -DROP FUNCTION col_is_unique ( NAME, NAME, NAME ); -DROP FUNCTION col_is_unique ( NAME, NAME, NAME[] ); -DROP FUNCTION col_is_unique ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ); -DROP FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ); -DROP FUNCTION has_unique ( TEXT ); -DROP FUNCTION has_unique ( TEXT, TEXT ); -DROP FUNCTION has_unique ( TEXT, TEXT, TEXT ); -DROP FUNCTION col_isnt_fk ( NAME, NAME ); -DROP FUNCTION col_isnt_fk ( NAME, NAME, TEXT ); -DROP FUNCTION col_isnt_fk ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION col_isnt_fk ( NAME, NAME[] ); -DROP FUNCTION col_isnt_fk ( NAME, NAME[], TEXT ); -DROP FUNCTION col_isnt_fk ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION col_is_fk ( NAME, NAME ); -DROP FUNCTION col_is_fk ( NAME, NAME, TEXT ); -DROP FUNCTION col_is_fk ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION col_is_fk ( NAME, NAME[] ); -DROP FUNCTION col_is_fk ( NAME, NAME[], TEXT ); -DROP FUNCTION col_is_fk ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _fkexists ( NAME, NAME[] ); -DROP FUNCTION _fkexists ( NAME, NAME, NAME[] ); -DROP FUNCTION hasnt_fk ( NAME ); -DROP FUNCTION hasnt_fk ( NAME, TEXT ); -DROP FUNCTION hasnt_fk ( NAME, NAME, TEXT ); -DROP FUNCTION has_fk ( NAME ); -DROP FUNCTION has_fk ( NAME, TEXT ); -DROP FUNCTION has_fk ( NAME, NAME, TEXT ); -DROP FUNCTION col_isnt_pk ( NAME, NAME ); -DROP FUNCTION col_isnt_pk ( NAME, NAME, TEXT ); -DROP FUNCTION col_isnt_pk ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION col_isnt_pk ( NAME, NAME[] ); -DROP FUNCTION col_isnt_pk ( NAME, NAME[], TEXT ); -DROP FUNCTION col_isnt_pk ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION col_is_pk ( NAME, NAME ); -DROP FUNCTION col_is_pk ( NAME, NAME, TEXT ); -DROP FUNCTION col_is_pk ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION col_is_pk ( NAME, NAME[] ); -DROP FUNCTION col_is_pk ( NAME, NAME[], TEXT ); -DROP FUNCTION col_is_pk ( NAME, NAME, NAME[], TEXT ); -DROP FUNCTION _ckeys ( NAME, CHAR ); -DROP FUNCTION _ckeys ( NAME, NAME, CHAR ); -DROP FUNCTION _keys ( NAME, CHAR ); -DROP FUNCTION _keys ( NAME, NAME, CHAR ); -DROP VIEW pg_all_foreign_keys; -DROP FUNCTION _pg_sv_table_accessible( OID, OID ); -DROP FUNCTION _pg_sv_column_array( OID, SMALLINT[] ); -DROP FUNCTION _ident_array_to_string( name[], text ); -DROP FUNCTION hasnt_pk ( NAME ); -DROP FUNCTION hasnt_pk ( NAME, TEXT ); -DROP FUNCTION hasnt_pk ( NAME, NAME, TEXT ); -DROP FUNCTION has_pk ( NAME ); -DROP FUNCTION has_pk ( NAME, TEXT ); -DROP FUNCTION has_pk ( NAME, NAME, TEXT ); -DROP FUNCTION _hasc ( NAME, CHAR ); -DROP FUNCTION _hasc ( NAME, NAME, CHAR ); -DROP FUNCTION col_default_is ( NAME, NAME, text ); -DROP FUNCTION col_default_is ( NAME, NAME, anyelement ); -DROP FUNCTION col_default_is ( NAME, NAME, TEXT, TEXT ); -DROP FUNCTION col_default_is ( NAME, NAME, anyelement, TEXT ); -DROP FUNCTION col_default_is ( NAME, NAME, NAME, TEXT, TEXT ); -DROP FUNCTION col_default_is ( NAME, NAME, NAME, anyelement, TEXT ); -DROP FUNCTION _cdi ( NAME, NAME, anyelement ); -DROP FUNCTION _cdi ( NAME, NAME, anyelement, TEXT ); -DROP FUNCTION _cdi ( NAME, NAME, NAME, anyelement, TEXT ); -DROP FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT ); -DROP FUNCTION col_hasnt_default ( NAME, NAME ); -DROP FUNCTION col_hasnt_default ( NAME, NAME, TEXT ); -DROP FUNCTION col_hasnt_default ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION col_has_default ( NAME, NAME ); -DROP FUNCTION col_has_default ( NAME, NAME, TEXT ); -DROP FUNCTION col_has_default ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION _has_def ( NAME, NAME ); -DROP FUNCTION _has_def ( NAME, NAME, NAME ); -DROP FUNCTION col_type_is ( NAME, NAME, TEXT ); -DROP FUNCTION col_type_is ( NAME, NAME, TEXT, TEXT ); -DROP FUNCTION col_type_is ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION col_type_is ( NAME, NAME, NAME, TEXT, TEXT ); -DROP FUNCTION col_type_is ( NAME, NAME, NAME, NAME, TEXT ); -DROP FUNCTION col_type_is ( NAME, NAME, NAME, NAME, TEXT, TEXT ); -DROP FUNCTION _quote_ident_like(TEXT, TEXT); -DROP FUNCTION _get_col_ns_type ( NAME, NAME, NAME ); -DROP FUNCTION _get_col_type ( NAME, NAME ); -DROP FUNCTION _get_col_type ( NAME, NAME, NAME ); -DROP FUNCTION col_is_null ( NAME, NAME ); -DROP FUNCTION col_is_null ( NAME, NAME, NAME ); -DROP FUNCTION col_is_null ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION col_not_null ( NAME, NAME ); -DROP FUNCTION col_not_null ( NAME, NAME, TEXT ); -DROP FUNCTION col_not_null ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION _col_is_null ( NAME, NAME, TEXT, bool ); -DROP FUNCTION _col_is_null ( NAME, NAME, NAME, TEXT, bool ); -DROP FUNCTION hasnt_column ( NAME, NAME ); -DROP FUNCTION hasnt_column ( NAME, NAME, TEXT ); -DROP FUNCTION hasnt_column ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION has_column ( NAME, NAME ); -DROP FUNCTION has_column ( NAME, NAME, TEXT ); -DROP FUNCTION has_column ( NAME, NAME, NAME, TEXT ); -DROP FUNCTION _cexists ( NAME, NAME ); -DROP FUNCTION _cexists ( NAME, NAME, NAME ); -DROP FUNCTION hasnt_composite ( NAME ); -DROP FUNCTION hasnt_composite ( NAME, TEXT ); -DROP FUNCTION hasnt_composite ( NAME, NAME, TEXT ); -DROP FUNCTION has_composite ( NAME ); -DROP FUNCTION has_composite ( NAME, TEXT ); -DROP FUNCTION has_composite ( NAME, NAME, TEXT ); -DROP FUNCTION hasnt_foreign_table ( NAME ); -DROP FUNCTION hasnt_foreign_table ( NAME, TEXT ); -DROP FUNCTION hasnt_foreign_table ( NAME, NAME ); -DROP FUNCTION hasnt_foreign_table ( NAME, NAME, TEXT ); -DROP FUNCTION has_foreign_table ( NAME ); -DROP FUNCTION has_foreign_table ( NAME, TEXT ); -DROP FUNCTION has_foreign_table ( NAME, NAME ); -DROP FUNCTION has_foreign_table ( NAME, NAME, TEXT ); -DROP FUNCTION hasnt_sequence ( NAME ); -DROP FUNCTION hasnt_sequence ( NAME, TEXT ); -DROP FUNCTION hasnt_sequence ( NAME, NAME, TEXT ); -DROP FUNCTION has_sequence ( NAME ); -DROP FUNCTION has_sequence ( NAME, TEXT ); -DROP FUNCTION has_sequence ( NAME, NAME ); -DROP FUNCTION has_sequence ( NAME, NAME, TEXT ); -DROP FUNCTION hasnt_view ( NAME ); -DROP FUNCTION hasnt_view ( NAME, TEXT ); -DROP FUNCTION hasnt_view ( NAME, NAME, TEXT ); -DROP FUNCTION has_view ( NAME ); -DROP FUNCTION has_view ( NAME, TEXT ); -DROP FUNCTION has_view ( NAME, NAME, TEXT ); -DROP FUNCTION hasnt_table ( NAME ); -DROP FUNCTION hasnt_table ( NAME, TEXT ); -DROP FUNCTION hasnt_table ( NAME, NAME ); -DROP FUNCTION hasnt_table ( NAME, NAME, TEXT ); -DROP FUNCTION has_table ( NAME ); -DROP FUNCTION has_table ( NAME, TEXT ); -DROP FUNCTION has_table ( NAME, NAME ); -DROP FUNCTION has_table ( NAME, NAME, TEXT ); -DROP FUNCTION _rexists ( CHAR, NAME ); -DROP FUNCTION _rexists ( CHAR, NAME, NAME ); -DROP FUNCTION hasnt_relation ( NAME ); -DROP FUNCTION hasnt_relation ( NAME, TEXT ); -DROP FUNCTION hasnt_relation ( NAME, NAME, TEXT ); -DROP FUNCTION has_relation ( NAME ); -DROP FUNCTION has_relation ( NAME, TEXT ); -DROP FUNCTION has_relation ( NAME, NAME, TEXT ); -DROP FUNCTION _relexists ( NAME ); -DROP FUNCTION _relexists ( NAME, NAME ); -DROP FUNCTION performs_within(TEXT, NUMERIC, NUMERIC); -DROP FUNCTION performs_within(TEXT, NUMERIC, NUMERIC, TEXT); -DROP FUNCTION performs_within(TEXT, NUMERIC, NUMERIC, INT); -DROP FUNCTION performs_within(TEXT, NUMERIC, NUMERIC, INT, TEXT); -DROP FUNCTION _time_trials(TEXT, INT, NUMERIC); -DROP FUNCTION performs_ok ( TEXT, NUMERIC ); -DROP FUNCTION performs_ok ( TEXT, NUMERIC, TEXT ); -DROP FUNCTION lives_ok ( TEXT ); -DROP FUNCTION lives_ok ( TEXT, TEXT ); -DROP FUNCTION _error_diag( TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT ); -DROP FUNCTION throws_ok ( TEXT, int4 ); -DROP FUNCTION throws_ok ( TEXT, int4, TEXT ); -DROP FUNCTION throws_ok ( TEXT, int4, TEXT, TEXT ); -DROP FUNCTION throws_ok ( TEXT ); -DROP FUNCTION throws_ok ( TEXT, TEXT ); -DROP FUNCTION throws_ok ( TEXT, TEXT, TEXT ); -DROP FUNCTION throws_ok ( TEXT, CHAR(5), TEXT, TEXT ); -DROP FUNCTION _query( TEXT ); -DROP FUNCTION skip( int ); -DROP FUNCTION skip( int, text ); -DROP FUNCTION skip ( text ); -DROP FUNCTION skip ( why text, how_many int ); -DROP FUNCTION _todo(); -DROP FUNCTION todo_end (); -DROP FUNCTION in_todo (); -DROP FUNCTION todo_start (); -DROP FUNCTION todo_start (text); -DROP FUNCTION todo ( how_many int ); -DROP FUNCTION todo ( why text ); -DROP FUNCTION todo ( how_many int, why text ); -DROP FUNCTION todo ( why text, how_many int ); -DROP FUNCTION fail (); -DROP FUNCTION fail ( text ); -DROP FUNCTION pass (); -DROP FUNCTION pass ( text ); -DROP FUNCTION cmp_ok (anyelement, text, anyelement); -DROP FUNCTION cmp_ok (anyelement, text, anyelement, text); -DROP FUNCTION unialike ( anyelement, text ); -DROP FUNCTION unialike ( anyelement, text, text ); -DROP FUNCTION unalike ( anyelement, text ); -DROP FUNCTION unalike ( anyelement, text, text ); -DROP FUNCTION doesnt_imatch ( anyelement, text ); -DROP FUNCTION doesnt_imatch ( anyelement, text, text ); -DROP FUNCTION doesnt_match ( anyelement, text ); -DROP FUNCTION doesnt_match ( anyelement, text, text ); -DROP FUNCTION _unalike ( BOOLEAN, ANYELEMENT, TEXT, TEXT ); -DROP FUNCTION ialike ( anyelement, text ); -DROP FUNCTION ialike ( anyelement, text, text ); -DROP FUNCTION alike ( anyelement, text ); -DROP FUNCTION alike ( anyelement, text, text ); -DROP FUNCTION imatches ( anyelement, text ); -DROP FUNCTION imatches ( anyelement, text, text ); -DROP FUNCTION matches ( anyelement, text ); -DROP FUNCTION matches ( anyelement, text, text ); -DROP FUNCTION _alike ( BOOLEAN, ANYELEMENT, TEXT, TEXT ); -DROP FUNCTION isnt (anyelement, anyelement); -DROP FUNCTION isnt (anyelement, anyelement, text); -DROP FUNCTION is (anyelement, anyelement); -DROP FUNCTION is (anyelement, anyelement, text); -DROP FUNCTION ok ( boolean ); -DROP FUNCTION ok ( boolean, text ); -DROP FUNCTION diag( VARIADIC anyarray ); -DROP FUNCTION diag( VARIADIC text[] ); -DROP FUNCTION diag ( msg anyelement ); -DROP FUNCTION diag ( msg text ); -DROP FUNCTION finish (); -DROP FUNCTION _finish (INTEGER, INTEGER, INTEGER); -DROP FUNCTION num_failed (); -DROP FUNCTION add_result ( bool, bool, text, text, text ); -DROP FUNCTION _add ( text, integer ); -DROP FUNCTION _add ( text, integer, text ); -DROP FUNCTION _set ( integer, integer ); -DROP FUNCTION _set ( text, integer ); -DROP FUNCTION _set ( text, integer, text ); -DROP FUNCTION _get_note ( integer ); -DROP FUNCTION _get_note ( text ); -DROP FUNCTION _get_latest ( text, integer ); -DROP FUNCTION _get_latest ( text ); -DROP FUNCTION _get ( text ); -DROP FUNCTION no_plan(); -DROP FUNCTION plan( integer ); -DROP FUNCTION pgtap_version(); -DROP FUNCTION os_name(); -DROP FUNCTION pg_version_num(); -DROP FUNCTION pg_version(); From 0211d0543e789361c598a84652ab03a25fe40d83 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 13 May 2016 16:19:29 -0700 Subject: [PATCH 0892/1195] Make uninstall file creation more acccurate. We were missing a custom data type. --- Makefile | 2 +- sql/pgtap.sql.in | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 3a4d388f853f..34267dbf53cc 100644 --- a/Makefile +++ b/Makefile @@ -122,7 +122,7 @@ endif mv sql/pgtap.tmp sql/pgtap.sql sql/uninstall_pgtap.sql: sql/pgtap.sql test/setup.sql - grep 'CREATE OR REPLACE ' sql/pgtap.sql | $(PERL) -e 'for (reverse ) { chomp; s/CREATE OR REPLACE/DROP/; print "$$_;\n" }' > sql/uninstall_pgtap.sql + grep '^CREATE ' sql/pgtap.sql | $(PERL) -e 'for (reverse ) { chomp; s/CREATE (OR REPLACE)?/DROP/; print "$$_;\n" }' > sql/uninstall_pgtap.sql sql/pgtap-core.sql: sql/pgtap.sql.in cp $< $@ diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 17ddc5791bdc..962b59abba7f 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -839,7 +839,8 @@ $$ LANGUAGE sql; -- 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 TYPE _time_trial_type +AS (a_time NUMERIC); CREATE OR REPLACE FUNCTION _time_trials(TEXT, INT, NUMERIC) RETURNS SETOF _time_trial_type AS $$ DECLARE From 652b212e5e0f868780f969c2e7d54682dd72959e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 13 May 2016 16:21:43 -0700 Subject: [PATCH 0893/1195] Update 8.4 patch offsets. --- compat/install-8.4.patch | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compat/install-8.4.patch b/compat/install-8.4.patch index 5f8424bd06ab..12c11384ff72 100644 --- a/compat/install-8.4.patch +++ b/compat/install-8.4.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -7473,7 +7473,6 @@ +@@ -7474,7 +7474,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -8,7 +8,7 @@ EXCEPT SELECT $3[i] FROM generate_series(1, array_upper($3, 1)) s(i) -@@ -7488,7 +7487,6 @@ +@@ -7489,7 +7488,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -16,7 +16,7 @@ ), $4 ); -@@ -7512,7 +7510,6 @@ +@@ -7513,7 +7511,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = $1 AND n.nspname NOT IN ('pg_catalog', 'information_schema') @@ -24,7 +24,7 @@ EXCEPT SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) -@@ -7526,7 +7523,6 @@ +@@ -7527,7 +7524,6 @@ 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') From cc1719b8e88ceb81a76017f0e3ca1f598320091d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 13 May 2016 16:40:37 -0700 Subject: [PATCH 0894/1195] Update for 8.3. In particular, deal with exception raising (no detail) and fix more inconsistencies in priv test output. --- compat/install-8.3.patch | 8 ++++---- test/expected/privs.out | 12 ++++++------ test/sql/extension.sql | 2 +- test/sql/privs.sql | 8 ++++---- test/sql/runjusttests.sql | 11 ++++++++++- test/sql/runtests.sql | 11 ++++++++++- 6 files changed, 35 insertions(+), 17 deletions(-) diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index 623e3e4d24d9..0767c845e485 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -12,7 +12,7 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT s.a[1]::int * 10000 -@@ -6544,7 +6549,7 @@ +@@ -6600,7 +6605,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -21,7 +21,7 @@ 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' || -@@ -6701,7 +6706,7 @@ +@@ -6757,7 +6762,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -30,7 +30,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6910,13 +6915,7 @@ +@@ -6966,13 +6971,7 @@ $$ LANGUAGE sql; -- collect_tap( tap, tap, tap ) @@ -45,7 +45,7 @@ RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); $$ LANGUAGE sql; -@@ -7392,7 +7391,7 @@ +@@ -7448,7 +7447,7 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; diff --git a/test/expected/privs.out b/test/expected/privs.out index c5b34d4ab379..bdcbf1f27cf8 100644 --- a/test/expected/privs.out +++ b/test/expected/privs.out @@ -279,9 +279,9 @@ ok 276 - any_column_privs_are(tab, role, no privs) should have the proper diagno ok 277 - column_privs_are(sch, tab, col, role, privs, desc) should pass ok 278 - column_privs_are(sch, tab, col, role, privs, desc) should have the proper description ok 279 - column_privs_are(sch, tab, col, role, privs, desc) should have the proper diagnostics -ok 280 - column_privs_are(LOL, ATable, AColumn role, privs, desc) should pass -ok 281 - column_privs_are(LOL, ATable, AColumn role, privs, desc) should have the proper description -ok 282 - column_privs_are(LOL, ATable, AColumn role, privs, desc) should have the proper diagnostics +ok 280 - column_privs_are(LOL, ATable, AColumn, role, privs, desc) should pass +ok 281 - column_privs_are(LOL, ATable, AColumn, role, privs, desc) should have the proper description +ok 282 - column_privs_are(LOL, ATable, AColumn, role, privs, desc) should have the proper diagnostics ok 283 - column_privs_are(sch, tab, col, role, privs) should pass ok 284 - column_privs_are(sch, tab, col, role, privs) should have the proper description ok 285 - column_privs_are(sch, tab, col, role, privs) should have the proper diagnostics @@ -357,9 +357,9 @@ ok 354 - server_privs_are(SomeServer, role, privs, desc) should have the proper ok 355 - server_privs_are(server, role, privs, desc) should pass ok 356 - server_privs_are(server, role, privs, desc) should have the proper description ok 357 - server_privs_are(server, role, privs, desc) should have the proper diagnostics -ok 358 - server_privs_are(SomeSrver, role, privs, desc) should pass -ok 359 - server_privs_are(SomeSrver, role, privs, desc) should have the proper description -ok 360 - server_privs_are(SomeSrver, role, privs, desc) should have the proper diagnostics +ok 358 - server_privs_are(SomeServer, role, privs, desc) should pass +ok 359 - server_privs_are(SomeServer, role, privs, desc) should have the proper description +ok 360 - server_privs_are(SomeServer, role, privs, desc) should have the proper diagnostics ok 361 - server_privs_are(non-server, role, privs, desc) should fail ok 362 - server_privs_are(non-server, role, privs, desc) should have the proper description ok 363 - server_privs_are(non-server, role, privs, desc) should have the proper diagnostics diff --git a/test/sql/extension.sql b/test/sql/extension.sql index ea1fc734a95e..0be9d2acb5b5 100644 --- a/test/sql/extension.sql +++ b/test/sql/extension.sql @@ -62,7 +62,7 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT* FROM check_test( - extensions_are( 'empty schema', ARRAY[]::name[] ), + extensions_are( 'empty schema', '{}'::name[] ), true, 'extensions_are(non-sch, exts)', 'Schema "empty schema" should have the correct extensions', diff --git a/test/sql/privs.sql b/test/sql/privs.sql index a9d3f688f3ed..0b0d841d88be 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -1334,7 +1334,7 @@ BEGIN 'INSERT', 'REFERENCES', 'SELECT', 'UPDATE' ], 'whatever' ), true, - 'column_privs_are(LOL, ATable, AColumn role, privs, desc)', + 'column_privs_are(LOL, ATable, AColumn, role, privs, desc)', 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1543,7 +1543,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( pass('whatever'), true, - 'column_privs_are(LOL, ATable, AColumn, role, privs, desc)', + 'column_privs_are(tab, col, role, privs, desc)', 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1559,7 +1559,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( pass('whatever'), true, - 'column_privs_are(LOL, ATable, AColumn, role, privs)', + 'column_privs_are(tab, col, role, privs)', 'whatever', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1833,7 +1833,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( server_privs_are( 'SomeServer', current_user, '{USAGE}' ), true, - 'server_privs_are(SomeSrver, role, privs, desc)', + 'server_privs_are(SomeServer, role, privs, desc)', 'Role ' || current_user || ' should be granted USAGE on server "SomeServer"', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; diff --git a/test/sql/runjusttests.sql b/test/sql/runjusttests.sql index 1b611da6a01e..a51d8c6d0078 100644 --- a/test/sql/runjusttests.sql +++ b/test/sql/runjusttests.sql @@ -46,7 +46,7 @@ Note that in some cases we get what appears to be a duplicate context message, b END; $F$; $E$; - ELSE + ELSIF pg_version_num() >= 80400 THEN EXECUTE $E$ CREATE OR REPLACE FUNCTION __die() RETURNS VOID LANGUAGE plpgsql AS $F$ BEGIN @@ -56,6 +56,15 @@ Note that in some cases we get what appears to be a duplicate context message, b END; $F$; $E$; + ELSE + EXECUTE $E$ + CREATE OR REPLACE FUNCTION __die() RETURNS VOID LANGUAGE plpgsql AS $F$ + BEGIN + RAISE EXCEPTION 'This test should die, but not halt execution. +Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself.'; + END; + $F$; + $E$; END IF; EXECUTE 'SELECT __die();'; END; diff --git a/test/sql/runtests.sql b/test/sql/runtests.sql index cd5ba9702f08..4bb4d6c4b5be 100644 --- a/test/sql/runtests.sql +++ b/test/sql/runtests.sql @@ -81,7 +81,7 @@ Note that in some cases we get what appears to be a duplicate context message, b END; $F$; $E$; - ELSE + ELSIF pg_version_num() >= 80400 THEN EXECUTE $E$ CREATE OR REPLACE FUNCTION __die() RETURNS VOID LANGUAGE plpgsql AS $F$ BEGIN @@ -91,6 +91,15 @@ Note that in some cases we get what appears to be a duplicate context message, b END; $F$; $E$; + ELSE + EXECUTE $E$ + CREATE OR REPLACE FUNCTION __die() RETURNS VOID LANGUAGE plpgsql AS $F$ + BEGIN + RAISE EXCEPTION 'This test should die, but not halt execution. +Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself.'; + END; + $F$; + $E$; END IF; EXECUTE 'SELECT __die();'; END; From d2055b1ef44a49447fa3f46c8b6e7861ad6eaee7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 13 May 2016 18:50:57 -0700 Subject: [PATCH 0895/1195] Update for 8.2. --- compat/install-8.2.patch | 62 +++++++++++++++++++++------------ test/expected/runtests_3.out | 66 ++++++++++++++++++++++++++++++++++++ test/sql/do_tap.sql | 2 +- test/sql/runtests.sql | 14 +++----- 4 files changed, 111 insertions(+), 33 deletions(-) create mode 100644 test/expected/runtests_3.out diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 82f8e980a7a2..3f21b04fdc64 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -94,7 +94,16 @@ INTO result; output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( -@@ -2441,7 +2479,7 @@ +@@ -763,7 +801,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; + +@@ -2473,7 +2511,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 @@ -103,7 +112,7 @@ p.prolang AS langoid, p.proisstrict AS is_strict, p.proisagg AS is_agg, -@@ -3577,63 +3615,6 @@ +@@ -3609,63 +3647,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -167,7 +176,7 @@ CREATE OR REPLACE FUNCTION _has_role( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( -@@ -6065,17 +6046,17 @@ +@@ -6118,17 +6099,17 @@ BEGIN -- Run the setup functions. FOR tap IN SELECT * FROM _runem(setup, false) LOOP @@ -188,7 +197,16 @@ END LOOP; -- Emit the plan. -@@ -6224,13 +6205,13 @@ +@@ -6167,7 +6148,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; +@@ -6280,13 +6261,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -204,7 +222,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6454,7 +6435,7 @@ +@@ -6510,7 +6491,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -213,7 +231,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6549,11 +6530,11 @@ +@@ -6605,11 +6586,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -228,7 +246,7 @@ ); END IF; rownum = rownum + 1; -@@ -6568,9 +6549,9 @@ +@@ -6624,9 +6605,9 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Number of columns or their types differ between the queries' || @@ -241,7 +259,7 @@ END ); END; -@@ -6706,7 +6687,7 @@ +@@ -6762,7 +6743,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -250,7 +268,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6720,8 +6701,8 @@ +@@ -6776,8 +6757,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -261,7 +279,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6846,9 +6827,9 @@ +@@ -6902,9 +6883,9 @@ DECLARE typeof regtype := pg_typeof($1); BEGIN @@ -274,7 +292,7 @@ END; $$ LANGUAGE plpgsql; -@@ -6869,7 +6850,7 @@ +@@ -6925,7 +6906,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -283,7 +301,7 @@ END LOOP; -- What extra records do we have? -@@ -7037,7 +7018,7 @@ +@@ -7093,7 +7074,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -292,7 +310,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT -@@ -7055,7 +7036,7 @@ +@@ -7111,7 +7092,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -301,7 +319,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) ), -@@ -7088,7 +7069,7 @@ +@@ -7144,7 +7125,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -310,7 +328,7 @@ 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']) ) -@@ -7107,7 +7088,7 @@ +@@ -7163,7 +7144,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -319,7 +337,7 @@ 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']) ) -@@ -7391,10 +7372,12 @@ +@@ -7447,10 +7428,12 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; @@ -335,7 +353,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7541,7 +7524,7 @@ +@@ -7597,7 +7580,7 @@ CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) RETURNS TEXT AS $$ @@ -344,7 +362,7 @@ $$ LANGUAGE SQL; -- operators_are( schema, operators[], description ) -@@ -7550,7 +7533,7 @@ +@@ -7606,7 +7589,7 @@ SELECT _areni( 'operators', ARRAY( @@ -353,7 +371,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7562,7 +7545,7 @@ +@@ -7618,7 +7601,7 @@ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT @@ -362,7 +380,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7583,7 +7566,7 @@ +@@ -7639,7 +7622,7 @@ SELECT _areni( 'operators', ARRAY( @@ -371,7 +389,7 @@ 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) -@@ -7596,7 +7579,7 @@ +@@ -7652,7 +7635,7 @@ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT @@ -380,7 +398,7 @@ 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) -@@ -8299,40 +8282,6 @@ +@@ -8355,40 +8338,6 @@ ); $$ LANGUAGE sql; diff --git a/test/expected/runtests_3.out b/test/expected/runtests_3.out new file mode 100644 index 000000000000..6cfda37662bc --- /dev/null +++ b/test/expected/runtests_3.out @@ -0,0 +1,66 @@ +\unset ECHO +ok 1 - starting up +ok 2 - starting up some more + # Subtest: whatever."test ident"() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - ident + ok 5 - ident 2 + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 3 - whatever."test ident" + # Subtest: whatever.testplpgsql() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - plpgsql simple + ok 5 - plpgsql simple 2 + ok 6 - Should be a 1 in the test table + ok 7 - teardown + ok 8 - teardown more + 1..8 +ok 4 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: P0001: This test should die, but not halt execution +not ok 5 - whatever.testplpgsqldie +# Failed test 5: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - simple pass + ok 5 - another simple pass + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 6 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + not ok 4 - this test intentionally fails + # Failed test 4: "this test intentionally fails" + ok 5 - teardown + ok 6 - teardown more + 1..6 + # Looks like you failed 1 test of 6 +not ok 7 - whatever.testy +# Failed test 7: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - Late test should find nothing in the test table + ok 5 - teardown + ok 6 - teardown more + 1..6 +ok 8 - whatever.testz +ok 9 - shutting down +ok 10 - shutting down more +1..10 +# Looks like you failed 2 tests of 10 diff --git a/test/sql/do_tap.sql b/test/sql/do_tap.sql index b67f6547121b..e352753f03bb 100644 --- a/test/sql/do_tap.sql +++ b/test/sql/do_tap.sql @@ -53,7 +53,7 @@ SELECT is( SELECT is( findfuncs('foo'), - '{}'::text[], + CASE WHEN pg_version_num() < 80300 THEN NULL ELSE '{}'::text[] END, 'findfuncs(unknown) should find no tests' ); diff --git a/test/sql/runtests.sql b/test/sql/runtests.sql index 4bb4d6c4b5be..5f42e988bcda 100644 --- a/test/sql/runtests.sql +++ b/test/sql/runtests.sql @@ -81,6 +81,7 @@ Note that in some cases we get what appears to be a duplicate context message, b END; $F$; $E$; + EXECUTE 'SELECT __die();'; ELSIF pg_version_num() >= 80400 THEN EXECUTE $E$ CREATE OR REPLACE FUNCTION __die() RETURNS VOID LANGUAGE plpgsql AS $F$ @@ -91,17 +92,10 @@ Note that in some cases we get what appears to be a duplicate context message, b END; $F$; $E$; + EXECUTE 'SELECT __die();'; ELSE - EXECUTE $E$ - CREATE OR REPLACE FUNCTION __die() RETURNS VOID LANGUAGE plpgsql AS $F$ - BEGIN - RAISE EXCEPTION 'This test should die, but not halt execution. -Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself.'; - END; - $F$; - $E$; + RAISE EXCEPTION 'This test should die, but not halt execution'; END IF; - EXECUTE 'SELECT __die();'; END; $$ LANGUAGE plpgsql; @@ -124,6 +118,6 @@ $$ LANGUAGE plpgsql; SELECT * FROM runtests('whatever'::name); -- Verify that startup, shutdown, etc aren't run as normal tests -SELECT * FROM runtests('whatever'::name, '.*'); +SELECT * FROM runtests('whatever'::name, '.*') WHERE pg_version_num() >= 80300; ROLLBACK; From d5c8c3957c60da7de6cac1b27ba677c2aeb87ad4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 13 May 2016 19:02:48 -0700 Subject: [PATCH 0896/1195] Update for 8.1. At least I hope so. I don't have an 8.1 to run anymore. --- compat/install-8.1.patch | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index 24f1df7876b8..e60ae121f36e 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -2226,13 +2226,13 @@ +@@ -2258,13 +2258,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -18,7 +18,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $6 || ' constraints'; -@@ -2250,13 +2250,13 @@ +@@ -2282,13 +2282,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -36,7 +36,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $5 || ' constraints'; -@@ -5929,7 +5929,7 @@ +@@ -5971,7 +5971,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -45,7 +45,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -5937,8 +5937,8 @@ +@@ -5979,8 +5979,8 @@ -- 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. @@ -56,7 +56,7 @@ END LOOP; END LOOP; RETURN; -@@ -6007,7 +6007,7 @@ +@@ -6049,7 +6049,7 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -65,7 +65,7 @@ tfaild INTEGER := 0; ffaild INTEGER := 0; tnumb INTEGER := 0; -@@ -6018,7 +6018,7 @@ +@@ -6059,7 +6059,7 @@ BEGIN -- No plan support. PERFORM * FROM no_plan(); @@ -74,7 +74,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -6045,18 +6045,18 @@ +@@ -6098,18 +6098,18 @@ BEGIN BEGIN -- Run the setup functions. @@ -99,7 +99,7 @@ END LOOP; -- Emit the plan. -@@ -6109,11 +6109,11 @@ +@@ -6165,11 +6165,11 @@ END LOOP; -- Run the shutdown functions. @@ -114,7 +114,7 @@ END LOOP; -- Clean up and return. -@@ -7371,7 +7371,7 @@ +@@ -7427,7 +7427,7 @@ DECLARE rec RECORD; BEGIN From baf190e385662b7253d8b11128bd9a157ec7a191 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 16 May 2016 12:00:16 -0700 Subject: [PATCH 0897/1195] Cover runtests output on 8.3. --- test/expected/runtests_3.out | 65 +++++++++++++++++++++++++++++++++++ test/expected/runtests_4.out | 66 ++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 test/expected/runtests_4.out diff --git a/test/expected/runtests_3.out b/test/expected/runtests_3.out index 6cfda37662bc..c46f2c792ec0 100644 --- a/test/expected/runtests_3.out +++ b/test/expected/runtests_3.out @@ -64,3 +64,68 @@ ok 9 - shutting down ok 10 - shutting down more 1..10 # Looks like you failed 2 tests of 10 +ok 1 - starting up +ok 2 - starting up some more + # Subtest: whatever."test ident"() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - ident + ok 5 - ident 2 + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 3 - whatever."test ident" + # Subtest: whatever.testplpgsql() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - plpgsql simple + ok 5 - plpgsql simple 2 + ok 6 - Should be a 1 in the test table + ok 7 - teardown + ok 8 - teardown more + 1..8 +ok 4 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: P0001: This test should die, but not halt execution +not ok 5 - whatever.testplpgsqldie +# Failed test 5: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - simple pass + ok 5 - another simple pass + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 6 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + not ok 4 - this test intentionally fails + # Failed test 4: "this test intentionally fails" + ok 5 - teardown + ok 6 - teardown more + 1..6 + # Looks like you failed 1 test of 6 +not ok 7 - whatever.testy +# Failed test 7: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - Late test should find nothing in the test table + ok 5 - teardown + ok 6 - teardown more + 1..6 +ok 8 - whatever.testz +ok 9 - shutting down +ok 10 - shutting down more +1..10 +# Looks like you failed 2 tests of 10 diff --git a/test/expected/runtests_4.out b/test/expected/runtests_4.out new file mode 100644 index 000000000000..6cfda37662bc --- /dev/null +++ b/test/expected/runtests_4.out @@ -0,0 +1,66 @@ +\unset ECHO +ok 1 - starting up +ok 2 - starting up some more + # Subtest: whatever."test ident"() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - ident + ok 5 - ident 2 + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 3 - whatever."test ident" + # Subtest: whatever.testplpgsql() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - plpgsql simple + ok 5 - plpgsql simple 2 + ok 6 - Should be a 1 in the test table + ok 7 - teardown + ok 8 - teardown more + 1..8 +ok 4 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: P0001: This test should die, but not halt execution +not ok 5 - whatever.testplpgsqldie +# Failed test 5: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - simple pass + ok 5 - another simple pass + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 6 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + not ok 4 - this test intentionally fails + # Failed test 4: "this test intentionally fails" + ok 5 - teardown + ok 6 - teardown more + 1..6 + # Looks like you failed 1 test of 6 +not ok 7 - whatever.testy +# Failed test 7: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - Late test should find nothing in the test table + ok 5 - teardown + ok 6 - teardown more + 1..6 +ok 8 - whatever.testz +ok 9 - shutting down +ok 10 - shutting down more +1..10 +# Looks like you failed 2 tests of 10 From e6d5f35d9cc3326df2aeda3ebfd392ce063d3f28 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 16 May 2016 13:29:34 -0700 Subject: [PATCH 0898/1195] Upate 9.x patches, expected output. --- compat/install-9.0.patch | 6 +-- compat/install-9.1.patch | 4 +- compat/install-9.2.patch | 4 +- test/expected/runjusttests.out | 2 +- test/expected/runjusttests_1.out | 2 +- test/expected/runjusttests_2.out | 2 +- test/expected/runjusttests_3.out | 7 ++++ test/expected/runjusttests_4.out | 41 ++++++++++++++++++++ test/expected/runtests.out | 4 +- test/expected/runtests_1.out | 4 +- test/expected/runtests_2.out | 13 +++++++ test/expected/runtests_3.out | 6 ++- test/expected/runtests_4.out | 65 +++++++++++++++++++++++++++++++ test/expected/runtests_5.out | 66 ++++++++++++++++++++++++++++++++ test/sql/throwtap.sql | 3 +- 15 files changed, 212 insertions(+), 17 deletions(-) create mode 100644 test/expected/runjusttests_4.out create mode 100644 test/expected/runtests_5.out diff --git a/compat/install-9.0.patch b/compat/install-9.0.patch index 94f86af7bc66..a6f9222616ab 100644 --- a/compat/install-9.0.patch +++ b/compat/install-9.0.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -3622,7 +3622,7 @@ RETURNS TEXT AS $$ +@@ -3617,7 +3617,7 @@ AND n.nspname = $1 AND t.typname = $2 AND t.typtype = 'e' @@ -9,7 +9,7 @@ ), $3, $4 -@@ -3650,7 +3650,7 @@ RETURNS TEXT AS $$ +@@ -3645,7 +3645,7 @@ AND pg_catalog.pg_type_is_visible(t.oid) AND t.typname = $1 AND t.typtype = 'e' @@ -18,7 +18,7 @@ ), $2, $3 -@@ -9484,54 +9484,3 @@ +@@ -9459,54 +9459,3 @@ GRANT SELECT ON tap_funky TO PUBLIC; GRANT SELECT ON pg_all_foreign_keys TO PUBLIC; diff --git a/compat/install-9.1.patch b/compat/install-9.1.patch index a8631e943cd2..0160b7c7a17b 100644 --- a/compat/install-9.1.patch +++ b/compat/install-9.1.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -791,10 +791,6 @@ +@@ -781,10 +781,6 @@ RETURN ok( TRUE, descr ); EXCEPTION WHEN OTHERS THEN -- There should have been no exception. @@ -11,7 +11,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6160,10 +6156,6 @@ +@@ -6155,10 +6151,6 @@ -- Something went wrong. Record that fact. errstate := SQLSTATE; errmsg := SQLERRM; diff --git a/compat/install-9.2.patch b/compat/install-9.2.patch index d144eae7b71e..f3869ea0df9f 100644 --- a/compat/install-9.2.patch +++ b/compat/install-9.2.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -794,12 +794,7 @@ +@@ -784,12 +784,7 @@ GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, hint = PG_EXCEPTION_HINT, @@ -14,7 +14,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6168,12 +6163,7 @@ +@@ -6163,12 +6158,7 @@ GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, hint = PG_EXCEPTION_HINT, diff --git a/test/expected/runjusttests.out b/test/expected/runjusttests.out index 58b703812ba5..1885d9c11cf5 100644 --- a/test/expected/runjusttests.out +++ b/test/expected/runjusttests.out @@ -26,7 +26,7 @@ ok 3 - whatever.testplpgsql # TYPE: TYPE # CONTEXT: # SQL statement "SELECT __die();" - # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE + # PL/pgSQL function whatever.testplpgsqldie() line 43 at EXECUTE # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement # SQL function "runtests" statement 1 # SQL function "runtests" statement 1 diff --git a/test/expected/runjusttests_1.out b/test/expected/runjusttests_1.out index 65f99c9f0b89..89f40bb4cee7 100644 --- a/test/expected/runjusttests_1.out +++ b/test/expected/runjusttests_1.out @@ -26,7 +26,7 @@ ok 3 - whatever.testplpgsql # TYPE: TYPE # CONTEXT: # SQL statement "SELECT __die();" - # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement + # PL/pgSQL function whatever.testplpgsqldie() line 43 at EXECUTE statement # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement # SQL function "runtests" statement 1 # SQL function "runtests" statement 1 diff --git a/test/expected/runjusttests_2.out b/test/expected/runjusttests_2.out index 3814a9ef9405..0b45fdce3fb9 100644 --- a/test/expected/runjusttests_2.out +++ b/test/expected/runjusttests_2.out @@ -21,7 +21,7 @@ ok 3 - whatever.testplpgsql # DETAIL: DETAIL # CONTEXT: # SQL statement "SELECT __die();" - # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement + # PL/pgSQL function whatever.testplpgsqldie() line 43 at EXECUTE statement # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement # SQL function "runtests" statement 1 # SQL function "runtests" statement 1 diff --git a/test/expected/runjusttests_3.out b/test/expected/runjusttests_3.out index 767b0ab63ecf..3814a9ef9405 100644 --- a/test/expected/runjusttests_3.out +++ b/test/expected/runjusttests_3.out @@ -18,6 +18,13 @@ ok 3 - whatever.testplpgsql # Subtest: whatever.testplpgsqldie() # Test died: P0001: This test should die, but not halt execution. # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. + # DETAIL: DETAIL + # CONTEXT: + # SQL statement "SELECT __die();" + # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 + # SQL function "runtests" statement 1 not ok 4 - whatever.testplpgsqldie # Failed test 4: "whatever.testplpgsqldie" # Subtest: whatever.testthis() diff --git a/test/expected/runjusttests_4.out b/test/expected/runjusttests_4.out new file mode 100644 index 000000000000..767b0ab63ecf --- /dev/null +++ b/test/expected/runjusttests_4.out @@ -0,0 +1,41 @@ +\unset ECHO + # Subtest: whatever."test ident"() + ok 1 - ident + ok 2 - ident 2 + 1..2 +ok 1 - whatever."test ident" + # Subtest: whatever.testnada() + 1..0 + # No tests run! +not ok 2 - whatever.testnada +# Failed test 2: "whatever.testnada" + # Subtest: whatever.testplpgsql() + ok 1 - plpgsql simple + ok 2 - plpgsql simple 2 + ok 3 - Should be a 1 in the test table + 1..3 +ok 3 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. +not ok 4 - whatever.testplpgsqldie +# Failed test 4: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - simple pass + ok 2 - another simple pass + 1..2 +ok 5 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - pass + not ok 2 - this test intentionally fails + # Failed test 2: "this test intentionally fails" + 1..2 + # Looks like you failed 1 tests of 2 +not ok 6 - whatever.testy +# Failed test 6: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - Late test should find nothing in the test table + 1..1 +ok 7 - whatever.testz +1..7 +# Looks like you failed 3 tests of 7 diff --git a/test/expected/runtests.out b/test/expected/runtests.out index 04a68d00a275..38e2e7dc51e7 100644 --- a/test/expected/runtests.out +++ b/test/expected/runtests.out @@ -36,7 +36,7 @@ ok 4 - whatever.testplpgsql # TYPE: TYPE # CONTEXT: # SQL statement "SELECT __die();" - # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE + # PL/pgSQL function whatever.testplpgsqldie() line 23 at EXECUTE # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement # SQL function "runtests" statement 1 # SQL function "runtests" statement 1 @@ -114,7 +114,7 @@ ok 4 - whatever.testplpgsql # TYPE: TYPE # CONTEXT: # SQL statement "SELECT __die();" - # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE + # PL/pgSQL function whatever.testplpgsqldie() line 23 at EXECUTE # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement # SQL function "runtests" statement 1 not ok 5 - whatever.testplpgsqldie diff --git a/test/expected/runtests_1.out b/test/expected/runtests_1.out index 8913db0d6797..a2e106611157 100644 --- a/test/expected/runtests_1.out +++ b/test/expected/runtests_1.out @@ -36,7 +36,7 @@ ok 4 - whatever.testplpgsql # TYPE: TYPE # CONTEXT: # SQL statement "SELECT __die();" - # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement + # PL/pgSQL function whatever.testplpgsqldie() line 23 at EXECUTE statement # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement # SQL function "runtests" statement 1 # SQL function "runtests" statement 1 @@ -114,7 +114,7 @@ ok 4 - whatever.testplpgsql # TYPE: TYPE # CONTEXT: # SQL statement "SELECT __die();" - # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement + # PL/pgSQL function whatever.testplpgsqldie() line 23 at EXECUTE statement # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement # SQL function "runtests" statement 1 not ok 5 - whatever.testplpgsqldie diff --git a/test/expected/runtests_2.out b/test/expected/runtests_2.out index 79c65048e69a..af097face9e8 100644 --- a/test/expected/runtests_2.out +++ b/test/expected/runtests_2.out @@ -28,6 +28,13 @@ ok 4 - whatever.testplpgsql ok 3 - setup more # Test died: P0001: This test should die, but not halt execution. # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. + # DETAIL: DETAIL + # CONTEXT: + # SQL statement "SELECT __die();" + # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 + # SQL function "runtests" statement 1 not ok 5 - whatever.testplpgsqldie # Failed test 5: "whatever.testplpgsqldie" # Subtest: whatever.testthis() @@ -94,6 +101,12 @@ ok 4 - whatever.testplpgsql ok 3 - setup more # Test died: P0001: This test should die, but not halt execution. # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. + # DETAIL: DETAIL + # CONTEXT: + # SQL statement "SELECT __die();" + # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 not ok 5 - whatever.testplpgsqldie # Failed test 5: "whatever.testplpgsqldie" # Subtest: whatever.testthis() diff --git a/test/expected/runtests_3.out b/test/expected/runtests_3.out index c46f2c792ec0..79c65048e69a 100644 --- a/test/expected/runtests_3.out +++ b/test/expected/runtests_3.out @@ -26,7 +26,8 @@ ok 4 - whatever.testplpgsql ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more - # Test died: P0001: This test should die, but not halt execution + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. not ok 5 - whatever.testplpgsqldie # Failed test 5: "whatever.testplpgsqldie" # Subtest: whatever.testthis() @@ -91,7 +92,8 @@ ok 4 - whatever.testplpgsql ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more - # Test died: P0001: This test should die, but not halt execution + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. not ok 5 - whatever.testplpgsqldie # Failed test 5: "whatever.testplpgsqldie" # Subtest: whatever.testthis() diff --git a/test/expected/runtests_4.out b/test/expected/runtests_4.out index 6cfda37662bc..c46f2c792ec0 100644 --- a/test/expected/runtests_4.out +++ b/test/expected/runtests_4.out @@ -64,3 +64,68 @@ ok 9 - shutting down ok 10 - shutting down more 1..10 # Looks like you failed 2 tests of 10 +ok 1 - starting up +ok 2 - starting up some more + # Subtest: whatever."test ident"() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - ident + ok 5 - ident 2 + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 3 - whatever."test ident" + # Subtest: whatever.testplpgsql() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - plpgsql simple + ok 5 - plpgsql simple 2 + ok 6 - Should be a 1 in the test table + ok 7 - teardown + ok 8 - teardown more + 1..8 +ok 4 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: P0001: This test should die, but not halt execution +not ok 5 - whatever.testplpgsqldie +# Failed test 5: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - simple pass + ok 5 - another simple pass + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 6 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + not ok 4 - this test intentionally fails + # Failed test 4: "this test intentionally fails" + ok 5 - teardown + ok 6 - teardown more + 1..6 + # Looks like you failed 1 test of 6 +not ok 7 - whatever.testy +# Failed test 7: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - Late test should find nothing in the test table + ok 5 - teardown + ok 6 - teardown more + 1..6 +ok 8 - whatever.testz +ok 9 - shutting down +ok 10 - shutting down more +1..10 +# Looks like you failed 2 tests of 10 diff --git a/test/expected/runtests_5.out b/test/expected/runtests_5.out new file mode 100644 index 000000000000..6cfda37662bc --- /dev/null +++ b/test/expected/runtests_5.out @@ -0,0 +1,66 @@ +\unset ECHO +ok 1 - starting up +ok 2 - starting up some more + # Subtest: whatever."test ident"() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - ident + ok 5 - ident 2 + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 3 - whatever."test ident" + # Subtest: whatever.testplpgsql() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - plpgsql simple + ok 5 - plpgsql simple 2 + ok 6 - Should be a 1 in the test table + ok 7 - teardown + ok 8 - teardown more + 1..8 +ok 4 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: P0001: This test should die, but not halt execution +not ok 5 - whatever.testplpgsqldie +# Failed test 5: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - simple pass + ok 5 - another simple pass + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 6 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + not ok 4 - this test intentionally fails + # Failed test 4: "this test intentionally fails" + ok 5 - teardown + ok 6 - teardown more + 1..6 + # Looks like you failed 1 test of 6 +not ok 7 - whatever.testy +# Failed test 7: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - Late test should find nothing in the test table + ok 5 - teardown + ok 6 - teardown more + 1..6 +ok 8 - whatever.testz +ok 9 - shutting down +ok 10 - shutting down more +1..10 +# Looks like you failed 2 tests of 10 diff --git a/test/sql/throwtap.sql b/test/sql/throwtap.sql index a415410f551e..ba6553b08416 100644 --- a/test/sql/throwtap.sql +++ b/test/sql/throwtap.sql @@ -123,7 +123,8 @@ SELECT * FROM check_test( 'lives_ok failure diagnostics', '', ' died: P0001: todo_end() called without todo_start()' - || CASE WHEN pg_version_num() < 90200 THEN '' ELSE ' CONTEXT: + || CASE WHEN pg_version_num() < 90200 THEN '' ELSE ' + CONTEXT: SQL statement "SELECT * FROM todo_end()" PL/pgSQL function lives_ok(text,text) line 14 at EXECUTE' || CASE WHEN pg_version_num() >= 90500 THEN '' ELSE ' statement' END END From 26cf5ee9319c1c34ed106541aff930dc7597082e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 16 May 2016 13:50:54 -0700 Subject: [PATCH 0899/1195] Note addition of diagnostic output. --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index ca1560818465..ceec75c3cb9b 100644 --- a/Changes +++ b/Changes @@ -20,6 +20,8 @@ Revision history for pgTAP * The `sql/unininstall_pgtap.sql` file is now generated by `make` from the `sql/pgtap.sql` file. This should make it much more up-to-date and accurate in the future. +* Added full stack diagnostic output to the `lives_ok()` failures, as well as + xUnit test function errors. Patch from Jim Nasby. 0.95.0 2015-03-20T20:31:41Z --------------------------- From 1ae4abb4460f565af3de35b07175aeaeb0b3d9af Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 16 May 2016 13:52:23 -0700 Subject: [PATCH 0900/1195] Increment to v0.96.0. --- Changes | 2 +- META.json | 8 ++++---- README.md | 2 +- doc/pgtap.mmd | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Changes b/Changes index ceec75c3cb9b..7b83010073d0 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.95.1 +0.96.0 --------------------------- * Added an optional `:exclude_pattern` parameter to `findfuncs()` to prevent matching of some functions. This is useful to prevent matching diff --git a/META.json b/META.json index bf87bf780ddd..0e256d48309f 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "0.95.1", + "version": "0.96.0", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "0.95.0" + "version": "0.96.0" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "0.95.0" + "version": "0.96.0" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "0.95.0" + "version": "0.96.0" } }, "resources": { diff --git a/README.md b/README.md index c4fd057dea7b..3c0d9c061750 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 0.95.1 +pgTAP 0.96.0 ============ [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index c595adfd6215..02814ee2c71c 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 0.95.1 +pgTAP 0.96.0 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and From f5166c7af53b4d640823e8626168891796c6f1d8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 16 May 2016 13:52:53 -0700 Subject: [PATCH 0901/1195] Update copyright date. --- README.md | 2 +- doc/pgtap.mmd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3c0d9c061750..44b0be6cb073 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ full use of its API. It also requires PL/pgSQL. Copyright and License --------------------- -Copyright (c) 2008-2015 David E. Wheeler. Some rights reserved. +Copyright (c) 2008-2016 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 02814ee2c71c..6b298484f970 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7707,7 +7707,7 @@ Credits Copyright and License --------------------- -Copyright (c) 2008-2015 David E. Wheeler. Some rights reserved. +Copyright (c) 2008-2016 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is From fe52f3a8abff1ad681879eae0a090cca3ad0dd96 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 16 May 2016 13:53:11 -0700 Subject: [PATCH 0902/1195] Timestamp v0.96.0. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 7b83010073d0..4ea7170a8428 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.96.0 +0.96.0 2016-05-16T20:53:57Z --------------------------- * Added an optional `:exclude_pattern` parameter to `findfuncs()` to prevent matching of some functions. This is useful to prevent matching From d0661e2e7dcd07c93686d00a7f8095922969cb50 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 16 May 2016 13:58:06 -0700 Subject: [PATCH 0903/1195] Increment to v0.96.1. --- Changes | 3 +++ META.json | 8 ++++---- README.md | 2 +- doc/pgtap.mmd | 2 +- sql/pgtap--0.96.0--0.97.0.sql | 1 + 5 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 sql/pgtap--0.96.0--0.97.0.sql diff --git a/Changes b/Changes index 4ea7170a8428..84ea2cfbdceb 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Revision history for pgTAP ========================== +0.96.1 +--------------------------- + 0.96.0 2016-05-16T20:53:57Z --------------------------- * Added an optional `:exclude_pattern` parameter to `findfuncs()` to prevent diff --git a/META.json b/META.json index 0e256d48309f..d3ec61c70add 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "0.96.0", + "version": "0.96.1", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "0.96.0" + "version": "0.96.1" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "0.96.0" + "version": "0.96.1" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "0.96.0" + "version": "0.96.1" } }, "resources": { diff --git a/README.md b/README.md index 44b0be6cb073..14480f1d071f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 0.96.0 +pgTAP 0.96.1 ============ [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 6b298484f970..1b821abbfe76 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 0.96.0 +pgTAP 0.96.1 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/sql/pgtap--0.96.0--0.97.0.sql b/sql/pgtap--0.96.0--0.97.0.sql new file mode 100644 index 000000000000..dc13e04b9aab --- /dev/null +++ b/sql/pgtap--0.96.0--0.97.0.sql @@ -0,0 +1 @@ +-- No changes yet. From cd6a0c6db076b82afd5155e97fed0f387b81d73a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Qui=C3=A9deville?= Date: Thu, 23 Jun 2016 15:45:23 +0200 Subject: [PATCH 0904/1195] Fix wrong message opclass --- sql/pgtap.sql.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 962b59abba7f..d6275531bae3 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -4810,7 +4810,7 @@ $$ 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 exist' ); + SELECT ok( NOT _opc_exists( $1, $2 ), 'Operator class ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' ); $$ LANGUAGE SQL; -- hasnt_opclass( name, description ) @@ -4822,7 +4822,7 @@ $$ 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 exist' ); + SELECT ok( NOT _opc_exists( $1 ), 'Operator class ' || quote_ident($1) || ' should not exist' ); $$ LANGUAGE SQL; -- opclasses_are( schema, opclasses[], description ) From 81590bb919147e39a3d925ccd116a2c82e001f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Qui=C3=A9deville?= Date: Thu, 23 Jun 2016 15:57:58 +0200 Subject: [PATCH 0905/1195] Fix unit tests for hasnt_opclass --- test/sql/hastap.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index 4441e7ef6b62..69c146c6af68 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1735,7 +1735,7 @@ SELECT * FROM check_test( hasnt_opclass( 'pg_catalog', 'int4_ops'::name ), false, 'hasnt_opclass( schema, name )', - 'Operator class pg_catalog.int4_ops should exist', + 'Operator class pg_catalog.int4_ops should not exist', '' ); @@ -1751,7 +1751,7 @@ SELECT * FROM check_test( hasnt_opclass( 'int4_ops' ), false, 'hasnt_opclass( name )', - 'Operator class int4_ops should exist', + 'Operator class int4_ops should not exist', '' ); From 517c1c8522dd9f05bef0a2f4572230b2b330fe85 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Jun 2016 11:13:00 -0700 Subject: [PATCH 0906/1195] Note fix to hasnt_opclass() description. --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 84ea2cfbdceb..6251788964d9 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,8 @@ Revision history for pgTAP 0.96.1 --------------------------- +* Fixed the default description for `hasnt_opclass()` to say "should not + exist" instead of "should exist", thanks to Rodolphe Quiédeville (PR #99). 0.96.0 2016-05-16T20:53:57Z --------------------------- From 57e70cf4cce25536b31ad882046898eb618d3039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Qui=C3=A9deville?= Date: Fri, 24 Jun 2016 12:18:51 +0200 Subject: [PATCH 0907/1195] Add has_extension functions --- sql/pgtap.sql.in | 73 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index d6275531bae3..93ac60da139d 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -9528,3 +9528,76 @@ 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($1) || '.' || quote_ident($2) || ' should exist' ); +$$ 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; From 8b0499b10e2190036ff0e6c4e875a83558fcca1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Qui=C3=A9deville?= Date: Mon, 27 Jun 2016 11:21:31 +0200 Subject: [PATCH 0908/1195] Reindent, add unit tests --- sql/pgtap.sql.in | 21 ++- test/expected/extension.out | 50 ++++++- test/sql/extension.sql | 275 +++++++++++++++++++++++++++++++++++- 3 files changed, 337 insertions(+), 9 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 93ac60da139d..126e6372e2b9 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -9560,10 +9560,10 @@ $$ 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) ); + SELECT ok( + _ext_exists( $1, $2 ), + 'Extension ' || quote_ident($2) + || ' should exist in schema ' || quote_ident($1) ); $$ LANGUAGE SQL; -- has_extension( name, description ) @@ -9575,7 +9575,9 @@ $$ 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' ); + SELECT ok( + _ext_exists( $1 ), + 'Extension ' || quote_ident($1) || ' should exist' ); $$ LANGUAGE SQL; -- hasnt_extension( schema, name, description ) @@ -9587,7 +9589,10 @@ $$ 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($1) || '.' || quote_ident($2) || ' should exist' ); + 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 ) @@ -9599,5 +9604,7 @@ $$ 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' ); + SELECT ok( + NOT _ext_exists( $1 ), + 'Extension ' || quote_ident($1) || ' should not exist' ); $$ LANGUAGE SQL; diff --git a/test/expected/extension.out b/test/expected/extension.out index c12a734df592..c31ecf60467e 100644 --- a/test/expected/extension.out +++ b/test/expected/extension.out @@ -1,5 +1,5 @@ \unset ECHO -1..24 +1..72 ok 1 - extensions_are(sch, exts, desc) should pass ok 2 - extensions_are(sch, exts, desc) should have the proper description ok 3 - extensions_are(sch, exts, desc) should have the proper diagnostics @@ -24,3 +24,51 @@ ok 21 - extensions_are(sch, good/bad, desc) should have the proper diagnostics ok 22 - extensions_are(someexts) should fail ok 23 - extensions_are(someexts) should have the proper description ok 24 - extensions_are(someexts) should have the proper diagnostics +ok 25 - has_extension( schema, name, desc ) should pass +ok 26 - has_extension( schema, name, desc ) should have the proper description +ok 27 - has_extension( schema, name, desc ) should have the proper diagnostics +ok 28 - has_extension( schema, name ) should pass +ok 29 - has_extension( schema, name ) should have the proper description +ok 30 - has_extension( schema, name ) should have the proper diagnostics +ok 31 - has_extension( name, desc ) should pass +ok 32 - has_extension( name, desc ) should have the proper description +ok 33 - has_extension( name, desc ) should have the proper diagnostics +ok 34 - has_extension( name ) should pass +ok 35 - has_extension( name ) should have the proper description +ok 36 - has_extension( name ) should have the proper diagnostics +ok 37 - has_extension( schema, name, desc ) fail should fail +ok 38 - has_extension( schema, name, desc ) fail should have the proper description +ok 39 - has_extension( schema, name, desc ) fail should have the proper diagnostics +ok 40 - has_extension( schema, name ) fail should fail +ok 41 - has_extension( schema, name ) fail should have the proper description +ok 42 - has_extension( schema, name ) fail should have the proper diagnostics +ok 43 - has_extension( name, desc ) fail should fail +ok 44 - has_extension( name, desc ) fail should have the proper description +ok 45 - has_extension( name, desc ) fail should have the proper diagnostics +ok 46 - has_extension( name ) fail should fail +ok 47 - has_extension( name ) fail should have the proper description +ok 48 - has_extension( name ) fail should have the proper diagnostics +ok 49 - hasnt_extension( schema, name, desc ) should pass +ok 50 - hasnt_extension( schema, name, desc ) should have the proper description +ok 51 - hasnt_extension( schema, name, desc ) should have the proper diagnostics +ok 52 - hasnt_extension( schema, name ) should pass +ok 53 - hasnt_extension( schema, name ) should have the proper description +ok 54 - hasnt_extension( schema, name ) should have the proper diagnostics +ok 55 - hasnt_extension( name, desc ) should pass +ok 56 - hasnt_extension( name, desc ) should have the proper description +ok 57 - hasnt_extension( name, desc ) should have the proper diagnostics +ok 58 - hasnt_extension( name ) should pass +ok 59 - hasnt_extension( name ) should have the proper description +ok 60 - hasnt_extension( name ) should have the proper diagnostics +ok 61 - hasnt_extension( schema, name, desc ) should fail +ok 62 - hasnt_extension( schema, name, desc ) should have the proper description +ok 63 - hasnt_extension( schema, name, desc ) should have the proper diagnostics +ok 64 - hasnt_extension( schema, name ) should fail +ok 65 - hasnt_extension( schema, name ) should have the proper description +ok 66 - hasnt_extension( schema, name ) should have the proper diagnostics +ok 67 - hasnt_extension( name, desc ) should fail +ok 68 - hasnt_extension( name, desc ) should have the proper description +ok 69 - hasnt_extension( name, desc ) should have the proper diagnostics +ok 70 - hasnt_extension( name ) should fail +ok 71 - hasnt_extension( name ) should have the proper description +ok 72 - hasnt_extension( name ) should have the proper diagnostics diff --git a/test/sql/extension.sql b/test/sql/extension.sql index 0be9d2acb5b5..0269a3dbca94 100644 --- a/test/sql/extension.sql +++ b/test/sql/extension.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(24); +SELECT plan(72); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -92,6 +92,142 @@ BEGIN Missing extensions: nonesuch' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + /********************************************************************/ + -- Test has_extension(). + -- 8 tests + + FOR tap IN SELECT * FROM check_test( + has_extension( 'public', 'citext', 'desc' ), + true, + 'has_extension( schema, name, desc )', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_extension( 'public', 'citext'::name ), + true, + 'has_extension( schema, name )', + 'Extension citext should exist in schema public', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_extension( 'citext'::name, 'desc' ), + true, + 'has_extension( name, desc )', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_extension( 'citext' ), + true, + 'has_extension( name )', + 'Extension citext should exist', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_extension( 'public'::name, '__NON_EXISTS__'::name, 'desc' ), + false, + 'has_extension( schema, name, desc ) fail', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_extension( 'public'::name, '__NON_EXISTS__'::name ), + false, + 'has_extension( schema, name ) fail', + 'Extension "__NON_EXISTS__" should exist in schema public', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_extension( '__NON_EXISTS__'::name, 'desc' ), + false, + 'has_extension( name, desc ) fail', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_extension( '__NON_EXISTS__'::name ), + false, + 'has_extension( name ) fail', + 'Extension "__NON_EXISTS__" should exist', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + /********************************************************************/ + -- Test hasnt_extension(). + -- 8 tests + + FOR tap IN SELECT * FROM check_test( + hasnt_extension( 'public', '__NON_EXISTS__', 'desc' ), + true, + 'hasnt_extension( schema, name, desc )', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_extension( 'public', '__NON_EXISTS__'::name ), + true, + 'hasnt_extension( schema, name )', + 'Extension "__NON_EXISTS__" should not exist in schema public', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_extension( '__NON_EXISTS__'::name, 'desc' ), + true, + 'hasnt_extension( name, desc )', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_extension( '__NON_EXISTS__' ), + true, + 'hasnt_extension( name )', + 'Extension "__NON_EXISTS__" should not exist', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_extension( 'public', 'citext', 'desc' ), + false, + 'hasnt_extension( schema, name, desc )', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_extension( 'public', 'citext'::name ), + false, + 'hasnt_extension( schema, name )', + 'Extension citext should not exist in schema public', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_extension( 'citext', 'desc' ), + false, + 'hasnt_extension( name, desc )', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_extension( 'citext' ), + false, + 'hasnt_extension( name )', + 'Extension citext should not exist', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; ELSE FOR tap IN SELECT * FROM check_test( pass('mumble'), @@ -156,6 +292,143 @@ BEGIN 'mumble', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + /********************************************************************/ + -- Test has_extension(). + -- 8 tests + + FOR tap IN SELECT * FROM check_test( + pass('mumble'), + true, + 'has_extension( schema, name, desc )', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('mumble'), + true, + 'has_extension( schema, name )', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('mumble'), + true, + 'has_extension( name, desc )', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('mumble'), + true, + 'has_extension( name )', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('mumble'), + false, + 'has_extension( schema, name, desc ) fail', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('mumble'), + false, + 'has_extension( schema, name ) fail', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('mumble'), + false, + 'has_extension( name, desc ) fail', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('mumble'), + false, + 'has_extension( name ) fail', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + /********************************************************************/ + -- Test hasnt_extension(). + -- 8 tests + + FOR tap IN SELECT * FROM check_test( + pass('mumble'), + true, + 'hasnt_extension( schema, name, desc )', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('mumble'), + true, + 'hasnt_extension( schema, name )', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('mumble'), + true, + 'hasnt_extension( name, desc )', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass('mumble'), + true, + 'hasnt_extension( name )', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('mumble'), + false, + 'hasnt_extension( schema, name, desc )', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('mumble'), + false, + 'hasnt_extension( schema, name )', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('mumble'), + false, + 'hasnt_extension( name, desc )', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail('mumble'), + false, + 'hasnt_extension( name )', + 'mumble', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + END IF; RETURN; END; From 8e20236df8efd90b756499ba766e9e0294ee7137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Qui=C3=A9deville?= Date: Mon, 27 Jun 2016 11:33:29 +0200 Subject: [PATCH 0909/1195] Add doc for has_extension --- doc/pgtap.mmd | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 1b821abbfe76..0ce2ed16f7b8 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -3826,6 +3826,55 @@ omitted, it will default to "Procedural language `:language` should exist". The inverse of `has_language()`, this function tests for the *absence* of a procedural language. +### `has_extension()` ### + + SELECT has_extension( :schema, :extension, :description ); + SELECT has_extension( :schema, :extension ); + SELECT has_extension( :extension, :description ); + SELECT has_extension( :extension ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:extension` +: Name of an extension. + +`:description` +: A short description of the test. + +This function tests whether or not an extension exists in the +database. The first argument is the schema name, the second the +extension name, and the third the test description. If the schema is +omitted, the extension must be visible in the search path. If the test +description is omitted, it will be set to "Extension `:extension` +should exist". +Example: + + SELECT has_extension('public', 'pgtap'); + +### `hasnt_extension()` ### + + SELECT hasnt_extension( :schema, :extension, :description ); + SELECT hasnt_extension( :schema, :extension ); + SELECT hasnt_extension( :extension, :description ); + SELECT hasnt_extension( :extension ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:extension` +: Name of an extension. + +`:description` +: A short description of the test. + +This function is the inverse of `has_extension()`. The test passes if the +specified extension does *not* exist. + Table For One ------------- From dacc209a7967bd5386499fb736f5dda33aee0f79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Qui=C3=A9deville?= Date: Mon, 27 Jun 2016 12:08:24 +0200 Subject: [PATCH 0910/1195] Add new functions in compat file --- compat/install-9.0.patch | 82 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/compat/install-9.0.patch b/compat/install-9.0.patch index a6f9222616ab..430e0bdfd250 100644 --- a/compat/install-9.0.patch +++ b/compat/install-9.0.patch @@ -18,7 +18,7 @@ ), $2, $3 -@@ -9459,54 +9459,3 @@ +@@ -9459,134 +9459,3 @@ GRANT SELECT ON tap_funky TO PUBLIC; GRANT SELECT ON pg_all_foreign_keys TO PUBLIC; @@ -73,3 +73,83 @@ -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; From b99c111844dcd1ba64da38c0b1db593248ba4891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Qui=C3=A9deville?= Date: Mon, 27 Jun 2016 15:19:20 +0200 Subject: [PATCH 0911/1195] Fix comment --- sql/pgtap.sql.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index d6275531bae3..63100a17f54e 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2832,7 +2832,7 @@ BEGIN END; $$ LANGUAGE plpgsql; --- has_index( table, index, columns[], description ) +-- 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' ); From 1f0c4b2e5e4667d5554e33cc15cdf2ea816b55fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Qui=C3=A9deville?= Date: Mon, 27 Jun 2016 18:49:43 +0200 Subject: [PATCH 0912/1195] Add first functions for is_indexed --- sql/pgtap.sql.in | 78 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 63100a17f54e..63e3b0c01408 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2923,6 +2923,84 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; +CREATE OR REPLACE FUNCTION _is_indexed( NAME, NAME, NAME[]) +RETURNS BOOLEAN AS $$ + WITH cols AS ( + SELECT x.indexrelid, x.indrelid, unnest(x.indkey) as colid + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class r ON r.oid = x.indrelid + JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace + WHERE n.nspname = $1 + AND r.relname = $2), + colsdef AS ( + SELECT cols.indexrelid, cols.indrelid, array_agg(a.attname) as cols + FROM cols + JOIN pg_catalog.pg_attribute a ON (a.attrelid = cols.indrelid + AND a.attnum = cols.colid) + GROUP BY 1, 2) + SELECT EXISTS ( + SELECT TRUE + FROM colsdef + WHERE colsdef.cols::NAME[] = $3 + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _is_indexed( NAME, NAME[]) +RETURNS BOOLEAN AS $$ + WITH cols AS ( + SELECT x.indexrelid, x.indrelid, unnest(x.indkey) as colid + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class r ON r.oid = x.indrelid + WHERE r.relname = $1), + colsdef AS ( + SELECT cols.indexrelid, cols.indrelid, array_agg(a.attname) as cols + FROM cols + JOIN pg_catalog.pg_attribute a ON (a.attrelid = cols.indrelid + AND a.attnum = cols.colid) + GROUP BY 1, 2) + SELECT EXISTS ( + SELECT TRUE + FROM colsdef + WHERE colsdef.cols::NAME[] = $2 + ); +$$ 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), 'An index on ' || quote_ident($1) || '.' || quote_ident($2) || ' with ' || $3::text || ' should exist'); +$$ LANGUAGE sql; + +-- is_indexed( table, columns[], description ) +CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT ok ( _is_indexed( $1, $2), $3); +$$ LANGUAGE sql; + +-- is_indexed( table, columns[] ) +CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok ( _is_indexed( $1, $2), 'An index on ' || quote_ident($1) || ' with ' || $2::text || ' should exist'); +$$ LANGUAGE sql; + +-- is_indexed( schema, table, column ) +CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok ( _is_indexed( $1, $2, ARRAY[$3]::NAME[]), 'An index on ' || quote_ident($1) || '.' || quote_ident($2) || ' on ' || quote_ident($3) || ' should exist'); +$$ 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; + -- index_is_unique( schema, table, index, description ) CREATE OR REPLACE FUNCTION index_is_unique ( NAME, NAME, NAME, text ) RETURNS TEXT AS $$ From 1a924f2ae15bda7478f43ea6716e186267537193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Qui=C3=A9deville?= Date: Tue, 28 Jun 2016 21:32:41 +0200 Subject: [PATCH 0913/1195] Add doc and unit tests --- doc/pgtap.mmd | 30 ++++++++++++++++++ sql/pgtap.sql.in | 28 +++++++++++++---- test/expected/index.out | 26 +++++++++++++++- test/sql/index.sql | 69 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 145 insertions(+), 8 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 1b821abbfe76..de038056e685 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4644,6 +4644,36 @@ an index when the SQL command `CLUSTER TABLE INDEXNAME` has been executed. Clustering reorganizes the table tuples so that they are stored on disk in the order defined by the index. +### `is_indexed()` ### + + SELECT is_indexed( :schema, :table, :columns, :description ); + SELECT is_indexed( :schema, :table, :columns ); + SELECT is_indexed( :table, :columns, :description ); + SELECT is_indexed( :table, :columns ); + SELECT is_indexed( :schema, :table, :column, :description ); + SELECT is_indexed( :schema, :table, :column ); + SELECT is_indexed( :table, :column, :description ); + SELECT is_indexed( :table, :column ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table containing the index. + +`:column` +: Name of the column. + +`:columns` +: Array of columns name. + +`:description` +: A short description of the test. + +Tests an index exists on table for a set of columns. Order of column matters. + ### `index_is_type()` ### SELECT index_is_type( :schema, :table, :index, :type, :description ); diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 63e3b0c01408..fdc94cf42b7c 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2799,13 +2799,13 @@ RETURNS TEXT AS $$ SELECT has_index( $1, $2, $3, $4, 'Index ' || quote_ident($3) || ' should exist' ); $$ LANGUAGE sql; --- has_index( schema, table, index, column/expression, description ) +-- 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, columns/expression ) +-- 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' ); @@ -2989,16 +2989,32 @@ RETURNS TEXT AS $$ SELECT ok ( _is_indexed( $1, $2), 'An index on ' || quote_ident($1) || ' with ' || $2::text || ' should exist'); $$ 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 ) CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME, NAME ) RETURNS TEXT AS $$ - SELECT ok ( _is_indexed( $1, $2, ARRAY[$3]::NAME[]), 'An index on ' || quote_ident($1) || '.' || quote_ident($2) || ' on ' || quote_ident($3) || ' should exist'); + SELECT CASE WHEN _is_schema( $1 ) THEN + -- Looking for schema.table index. + ok ( _is_indexed( $1, $2, ARRAY[$3]::NAME[]), + 'An index on ' || quote_ident($1) || '.' || quote_ident($2) + || ' on column ' || quote_ident($3) || ' should exist') + ELSE + -- Looking for particular columns. + ok ( _is_indexed( $1, ARRAY[$2]::NAME[]), $3) + END; $$ LANGUAGE sql; --- is_indexed( schema, table, column, description ) -CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME, NAME, TEXT ) +-- is_indexed( table, column ) +CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT ok ( _is_indexed( $1, $2, ARRAY[$3]::NAME[]), $4); + SELECT ok ( _is_indexed( $1, ARRAY[$2]::NAME[]), + 'An index on ' || quote_ident($1) || ' on column ' + || $2::text || ' should exist'); $$ LANGUAGE sql; -- index_is_unique( schema, table, index, description ) diff --git a/test/expected/index.out b/test/expected/index.out index 8a4365160640..c8d5bcb57412 100644 --- a/test/expected/index.out +++ b/test/expected/index.out @@ -1,5 +1,5 @@ \unset ECHO -1..234 +1..258 ok 1 - has_index() single column should pass ok 2 - has_index() single column should have the proper description ok 3 - has_index() single column should have the proper diagnostics @@ -234,3 +234,27 @@ ok 231 - index_is_type() no table fail should have the proper diagnostics ok 232 - index_is_type() hash should pass ok 233 - index_is_type() hash should have the proper description ok 234 - index_is_type() hash should have the proper diagnostics +ok 235 - is_indexed( schema, table, columns[], description ) should pass +ok 236 - is_indexed( schema, table, columns[], description ) should have the proper description +ok 237 - is_indexed( schema, table, columns[], description ) should have the proper diagnostics +ok 238 - is_indexed( schema, table, columns[] ) should pass +ok 239 - is_indexed( schema, table, columns[] ) should have the proper description +ok 240 - is_indexed( schema, table, columns[] ) should have the proper diagnostics +ok 241 - is_indexed( table, columns[], description ) should pass +ok 242 - is_indexed( table, columns[], description ) should have the proper description +ok 243 - is_indexed( table, columns[], description ) should have the proper diagnostics +ok 244 - is_indexed( table, columns[] ) should pass +ok 245 - is_indexed( table, columns[] ) should have the proper description +ok 246 - is_indexed( table, columns[] ) should have the proper diagnostics +ok 247 - is_indexed( schema, table, column, description ) should pass +ok 248 - is_indexed( schema, table, column, description ) should have the proper description +ok 249 - is_indexed( schema, table, column, description ) should have the proper diagnostics +ok 250 - is_indexed( schema, table, column ) should pass +ok 251 - is_indexed( schema, table, column ) should have the proper description +ok 252 - is_indexed( schema, table, column ) should have the proper diagnostics +ok 253 - is_indexed( schema, table, column ) fail should fail +ok 254 - is_indexed( schema, table, column ) fail should have the proper description +ok 255 - is_indexed( schema, table, column ) fail should have the proper diagnostics +ok 256 - is_indexed( schema, table, columns[] ) fail, column order matters should fail +ok 257 - is_indexed( schema, table, columns[] ) fail, column order matters should have the proper description +ok 258 - is_indexed( schema, table, columns[] ) fail, column order matters should have the proper diagnostics diff --git a/test/sql/index.sql b/test/sql/index.sql index 31da0f6e6e0f..1925585074d3 100644 --- a/test/sql/index.sql +++ b/test/sql/index.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(234); +SELECT plan(258); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -20,6 +20,7 @@ CREATE INDEX idx_bar ON public.sometab(numb, name); CREATE UNIQUE INDEX idx_baz ON public.sometab(LOWER(name)); CREATE INDEX idx_mul ON public.sometab(numb, LOWER(name)); CREATE INDEX idx_expr ON public.sometab(UPPER(name), numb, LOWER(name)); +CREATE INDEX idx_double ON public.sometab(numb, myint); RESET client_min_messages; /****************************************************************************/ @@ -674,6 +675,72 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test is_indexed(). +SELECT * FROM check_test( + is_indexed( 'public', 'sometab', ARRAY['numb','name']::name[], 'desc' ), + true, + 'is_indexed( schema, table, columns[], description )', + 'desc', + '' +); + +SELECT * FROM check_test( + is_indexed( 'public', 'sometab', ARRAY['numb','name']::name[] ), + true, + 'is_indexed( schema, table, columns[] )', + 'An index on public.sometab with {numb,name} should exist', + '' +); + +SELECT * FROM check_test( + is_indexed( 'sometab', ARRAY['numb','name']::name[], 'desc' ), + true, + 'is_indexed( table, columns[], description )', + 'desc', + '' +); + +SELECT * FROM check_test( + is_indexed( 'sometab', ARRAY['numb','name']::name[] ), + true, + 'is_indexed( table, columns[] )', + 'An index on sometab with {numb,name} should exist', + '' +); + +SELECT * FROM check_test( + is_indexed( 'public', 'sometab', 'numb', 'desc' ), + true, + 'is_indexed( schema, table, column, description )', + 'desc', + '' +); + +SELECT * FROM check_test( + is_indexed( 'public', 'sometab'::name, 'numb'::name ), + true, + 'is_indexed( schema, table, column )', + 'An index on public.sometab on column numb should exist', + '' +); + +SELECT * FROM check_test( + is_indexed( 'public', 'sometab'::name, 'myint'::name ), + false, + 'is_indexed( schema, table, column ) fail', + 'An index on public.sometab on column myint should exist', + '' +); + +SELECT * FROM check_test( + is_indexed( 'public', 'sometab', ARRAY['name','numb']::name[] ), + false, + 'is_indexed( schema, table, columns[] ) fail, column order matters', + 'An index on public.sometab with {name,numb} should exist', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From fb203a7bc1364884dff363a73d8b35a2ccdff0da Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 29 Jun 2016 23:17:24 +0100 Subject: [PATCH 0914/1195] Add is_indexed() to upgrade script. And record it in Changes. --- Changes | 4 +- sql/pgtap--0.96.0--0.97.0.sql | 94 ++++++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 6251788964d9..28f31136a297 100644 --- a/Changes +++ b/Changes @@ -5,7 +5,9 @@ Revision history for pgTAP --------------------------- * Fixed the default description for `hasnt_opclass()` to say "should not exist" instead of "should exist", thanks to Rodolphe Quiédeville (PR #99). - +* Added `is_indexed()`, which test to see that specific table columns are + indexed, thanks to Rodolphe Quiédeville (PR #103). + 0.96.0 2016-05-16T20:53:57Z --------------------------- * Added an optional `:exclude_pattern` parameter to `findfuncs()` to prevent diff --git a/sql/pgtap--0.96.0--0.97.0.sql b/sql/pgtap--0.96.0--0.97.0.sql index dc13e04b9aab..6c39d2429413 100644 --- a/sql/pgtap--0.96.0--0.97.0.sql +++ b/sql/pgtap--0.96.0--0.97.0.sql @@ -1 +1,93 @@ --- No changes yet. +CREATE OR REPLACE FUNCTION _is_indexed( NAME, NAME, NAME[]) +RETURNS BOOLEAN AS $$ + WITH cols AS ( + SELECT x.indexrelid, x.indrelid, unnest(x.indkey) as colid + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class r ON r.oid = x.indrelid + JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace + WHERE n.nspname = $1 + AND r.relname = $2), + colsdef AS ( + SELECT cols.indexrelid, cols.indrelid, array_agg(a.attname) as cols + FROM cols + JOIN pg_catalog.pg_attribute a ON (a.attrelid = cols.indrelid + AND a.attnum = cols.colid) + GROUP BY 1, 2) + SELECT EXISTS ( + SELECT TRUE + FROM colsdef + WHERE colsdef.cols::NAME[] = $3 + ); +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION _is_indexed( NAME, NAME[]) +RETURNS BOOLEAN AS $$ + WITH cols AS ( + SELECT x.indexrelid, x.indrelid, unnest(x.indkey) as colid + FROM pg_catalog.pg_index x + JOIN pg_catalog.pg_class r ON r.oid = x.indrelid + WHERE r.relname = $1), + colsdef AS ( + SELECT cols.indexrelid, cols.indrelid, array_agg(a.attname) as cols + FROM cols + JOIN pg_catalog.pg_attribute a ON (a.attrelid = cols.indrelid + AND a.attnum = cols.colid) + GROUP BY 1, 2) + SELECT EXISTS ( + SELECT TRUE + FROM colsdef + WHERE colsdef.cols::NAME[] = $2 + ); +$$ 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), 'An index on ' || quote_ident($1) || '.' || quote_ident($2) || ' with ' || $3::text || ' should exist'); +$$ LANGUAGE sql; + +-- is_indexed( table, columns[], description ) +CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME[], TEXT ) +RETURNS TEXT AS $$ + SELECT ok ( _is_indexed( $1, $2), $3); +$$ LANGUAGE sql; + +-- is_indexed( table, columns[] ) +CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok ( _is_indexed( $1, $2), 'An index on ' || quote_ident($1) || ' with ' || $2::text || ' should exist'); +$$ 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 ) +CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT CASE WHEN _is_schema( $1 ) THEN + -- Looking for schema.table index. + ok ( _is_indexed( $1, $2, ARRAY[$3]::NAME[]), + 'An index on ' || quote_ident($1) || '.' || quote_ident($2) + || ' on column ' || quote_ident($3) || ' should exist') + ELSE + -- Looking for particular columns. + ok ( _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( $1, ARRAY[$2]::NAME[]), + 'An index on ' || quote_ident($1) || ' on column ' + || $2::text || ' should exist'); +$$ LANGUAGE sql; \ No newline at end of file From e3c9ab2b7df20258fb3dd4827b74eacbb7583b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Qui=C3=A9deville?= Date: Thu, 30 Jun 2016 16:29:20 +0200 Subject: [PATCH 0915/1195] Add functions isnt_definer(), isnt_aggregate() and related tests --- sql/pgtap--0.96.0--0.97.0.sql | 120 +++++- sql/pgtap.sql.in | 118 ++++++ test/expected/functap.out | 664 ++++++++++++++++++++-------------- test/sql/functap.sql | 294 ++++++++++++++- 4 files changed, 914 insertions(+), 282 deletions(-) diff --git a/sql/pgtap--0.96.0--0.97.0.sql b/sql/pgtap--0.96.0--0.97.0.sql index 6c39d2429413..b3e64a86dd4e 100644 --- a/sql/pgtap--0.96.0--0.97.0.sql +++ b/sql/pgtap--0.96.0--0.97.0.sql @@ -90,4 +90,122 @@ RETURNS TEXT AS $$ SELECT ok ( _is_indexed( $1, ARRAY[$2]::NAME[]), 'An index on ' || quote_ident($1) || ' on column ' || $2::text || ' should exist'); -$$ LANGUAGE sql; \ No newline at end of file +$$ 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 be not 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 be not 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 be not 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 be not security definer' ); +$$ 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 _agg($1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- isnt_aggregate( schema, function, args[] ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _agg($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 _agg($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_aggregate( schema, function ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _agg($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 _agg($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_aggregate( function, args[] ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _agg($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 _agg($1), $2 ); +$$ LANGUAGE sql; + +-- isnt_aggregate( function ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _agg($1), 'Function ' || quote_ident($1) || '() should not be an aggregate function' ); +$$ LANGUAGE sql; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index fdc94cf42b7c..9e93ced68ede 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -5588,6 +5588,65 @@ 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; + CREATE OR REPLACE FUNCTION _agg ( NAME, NAME, NAME[] ) RETURNS BOOLEAN AS $$ SELECT is_agg @@ -5675,6 +5734,65 @@ RETURNS TEXT AS $$ SELECT ok( _agg($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 _agg($1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- isnt_aggregate( schema, function, args[] ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _agg($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 _agg($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_aggregate( schema, function ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _agg($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 _agg($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_aggregate( function, args[] ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _agg($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 _agg($1), $2 ); +$$ LANGUAGE sql; + +-- isnt_aggregate( function ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _agg($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 diff --git a/test/expected/functap.out b/test/expected/functap.out index 7f024018d11d..2b6704c2ef59 100644 --- a/test/expected/functap.out +++ b/test/expected/functap.out @@ -1,5 +1,5 @@ \unset ECHO -1..520 +1..628 ok 1 - simple function should pass ok 2 - simple function should have the proper description ok 3 - simple function should have the proper diagnostics @@ -243,280 +243,388 @@ ok 240 - function_returns(func, setof bool) should have the proper diagnostics ok 241 - is_definer(schema, func, 0 args, desc) should pass ok 242 - is_definer(schema, func, 0 args, desc) should have the proper description ok 243 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics -ok 244 - is_definer(schema, func, 0 args) should pass -ok 245 - is_definer(schema, func, 0 args) should have the proper description -ok 246 - is_definer(schema, func, 0 args) should have the proper diagnostics -ok 247 - is_definer(schema, func, args, desc) should fail -ok 248 - is_definer(schema, func, args, desc) should have the proper description -ok 249 - is_definer(schema, func, args, desc) should have the proper diagnostics -ok 250 - is_definer(schema, func, args) should fail -ok 251 - is_definer(schema, func, args) should have the proper description -ok 252 - is_definer(schema, func, args) should have the proper diagnostics -ok 253 - is_definer(schema, func, desc) should pass -ok 254 - is_definer(schema, func, desc) should have the proper description -ok 255 - is_definer(schema, func, desc) should have the proper diagnostics -ok 256 - is_definer(schema, func) should pass -ok 257 - is_definer(schema, func) should have the proper description -ok 258 - is_definer(schema, func) should have the proper diagnostics -ok 259 - is_definer(schema, func, 0 args, desc) should pass -ok 260 - is_definer(schema, func, 0 args, desc) should have the proper description -ok 261 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics -ok 262 - is_definer(schema, func, 0 args) should pass -ok 263 - is_definer(schema, func, 0 args) should have the proper description -ok 264 - is_definer(schema, func, 0 args) should have the proper diagnostics -ok 265 - is_definer(schema, func, args, desc) should fail -ok 266 - is_definer(schema, func, args, desc) should have the proper description -ok 267 - is_definer(schema, func, args, desc) should have the proper diagnostics -ok 268 - is_definer(schema, func, args) should fail -ok 269 - is_definer(schema, func, args) should have the proper description -ok 270 - is_definer(schema, func, args) should have the proper diagnostics -ok 271 - is_definer(schema, func, desc) should pass -ok 272 - is_definer(schema, func, desc) should have the proper description -ok 273 - is_definer(schema, func, desc) should have the proper diagnostics -ok 274 - is_definer(schema, func) should pass -ok 275 - is_definer(schema, func) should have the proper description -ok 276 - is_definer(schema, func) should have the proper diagnostics -ok 277 - is_definer(func, 0 args, desc) should pass -ok 278 - is_definer(func, 0 args, desc) should have the proper description -ok 279 - is_definer(func, 0 args, desc) should have the proper diagnostics -ok 280 - is_definer(func, 0 args) should pass -ok 281 - is_definer(func, 0 args) should have the proper description -ok 282 - is_definer(func, 0 args) should have the proper diagnostics -ok 283 - is_definer(func, args, desc) should fail -ok 284 - is_definer(func, args, desc) should have the proper description -ok 285 - is_definer(func, args, desc) should have the proper diagnostics -ok 286 - is_definer(func, args) should fail -ok 287 - is_definer(func, args) should have the proper description -ok 288 - is_definer(func, args) should have the proper diagnostics -ok 289 - is_definer(func, desc) should pass -ok 290 - is_definer(func, desc) should have the proper description -ok 291 - is_definer(func, desc) should have the proper diagnostics -ok 292 - is_definer(func) should pass -ok 293 - is_definer(func) should have the proper description -ok 294 - is_definer(func) should have the proper diagnostics -ok 295 - is_aggregate(schema, func, arg, desc) should pass -ok 296 - is_aggregate(schema, func, arg, desc) should have the proper description -ok 297 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics -ok 298 - is_aggregate(schema, func, arg) should pass -ok 299 - is_aggregate(schema, func, arg) should have the proper description -ok 300 - is_aggregate(schema, func, arg) should have the proper diagnostics -ok 301 - is_aggregate(schema, func, args, desc) should fail -ok 302 - is_aggregate(schema, func, args, desc) should have the proper description -ok 303 - is_aggregate(schema, func, args, desc) should have the proper diagnostics -ok 304 - is_aggregate(schema, func, args) should fail -ok 305 - is_aggregate(schema, func, args) should have the proper description -ok 306 - is_aggregate(schema, func, args) should have the proper diagnostics -ok 307 - is_aggregate(schema, func, desc) should pass -ok 308 - is_aggregate(schema, func, desc) should have the proper description -ok 309 - is_aggregate(schema, func, desc) should have the proper diagnostics -ok 310 - is_aggregate(schema, func) should pass -ok 311 - is_aggregate(schema, func) should have the proper description -ok 312 - is_aggregate(schema, func) should have the proper diagnostics -ok 313 - is_aggregate(schema, func, arg, desc) should pass -ok 314 - is_aggregate(schema, func, arg, desc) should have the proper description -ok 315 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics -ok 316 - is_aggregate(schema, func, arg) should pass -ok 317 - is_aggregate(schema, func, arg) should have the proper description -ok 318 - is_aggregate(schema, func, arg) should have the proper diagnostics -ok 319 - is_aggregate(schema, func, args, desc) should fail -ok 320 - is_aggregate(schema, func, args, desc) should have the proper description -ok 321 - is_aggregate(schema, func, args, desc) should have the proper diagnostics -ok 322 - is_aggregate(schema, func, args) should fail -ok 323 - is_aggregate(schema, func, args) should have the proper description -ok 324 - is_aggregate(schema, func, args) should have the proper diagnostics -ok 325 - is_aggregate(schema, func, desc) should pass -ok 326 - is_aggregate(schema, func, desc) should have the proper description -ok 327 - is_aggregate(schema, func, desc) should have the proper diagnostics -ok 328 - is_aggregate(schema, func) should pass -ok 329 - is_aggregate(schema, func) should have the proper description -ok 330 - is_aggregate(schema, func) should have the proper diagnostics -ok 331 - is_aggregate(func, arg, desc) should pass -ok 332 - is_aggregate(func, arg, desc) should have the proper description -ok 333 - is_aggregate(func, arg, desc) should have the proper diagnostics -ok 334 - is_aggregate(func, arg) should pass -ok 335 - is_aggregate(func, arg) should have the proper description -ok 336 - is_aggregate(func, arg) should have the proper diagnostics -ok 337 - is_aggregate(func, args, desc) should fail -ok 338 - is_aggregate(func, args, desc) should have the proper description -ok 339 - is_aggregate(func, args, desc) should have the proper diagnostics -ok 340 - is_aggregate(func, args) should fail -ok 341 - is_aggregate(func, args) should have the proper description -ok 342 - is_aggregate(func, args) should have the proper diagnostics -ok 343 - is_aggregate(func, desc) should pass -ok 344 - is_aggregate(func, desc) should have the proper description -ok 345 - is_aggregate(func, desc) should have the proper diagnostics -ok 346 - is_aggregate(func) should pass -ok 347 - is_aggregate(func) should have the proper description -ok 348 - is_aggregate(func) should have the proper diagnostics -ok 349 - is_strict(schema, func, 0 args, desc) should pass -ok 350 - is_strict(schema, func, 0 args, desc) should have the proper description -ok 351 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 352 - isnt_strict(schema, func, 0 args, desc) should fail -ok 353 - isnt_strict(schema, func, 0 args, desc) should have the proper description -ok 354 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 355 - is_strict(schema, func, 0 args) should pass -ok 356 - is_strict(schema, func, 0 args) should have the proper description -ok 357 - is_strict(schema, func, 0 args) should have the proper diagnostics -ok 358 - isnt_strict(schema, func, 0 args) should fail -ok 359 - isnt_strict(schema, func, 0 args) should have the proper description -ok 360 - isnt_strict(schema, func, 0 args) should have the proper diagnostics -ok 361 - is_strict(schema, func, args, desc) should fail -ok 362 - is_strict(schema, func, args, desc) should have the proper description -ok 363 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 364 - is_strict(schema, func, args, desc) should pass -ok 365 - is_strict(schema, func, args, desc) should have the proper description -ok 366 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 367 - is_strict(schema, func, args) should fail -ok 368 - is_strict(schema, func, args) should have the proper description -ok 369 - is_strict(schema, func, args) should have the proper diagnostics -ok 370 - is_strict(schema, func, args) should pass -ok 371 - is_strict(schema, func, args) should have the proper description -ok 372 - is_strict(schema, func, args) should have the proper diagnostics -ok 373 - is_strict(schema, func, desc) should pass -ok 374 - is_strict(schema, func, desc) should have the proper description -ok 375 - is_strict(schema, func, desc) should have the proper diagnostics -ok 376 - isnt_strict(schema, func, desc) should fail -ok 377 - isnt_strict(schema, func, desc) should have the proper description -ok 378 - isnt_strict(schema, func, desc) should have the proper diagnostics -ok 379 - is_strict(schema, func) should pass -ok 380 - is_strict(schema, func) should have the proper description -ok 381 - is_strict(schema, func) should have the proper diagnostics -ok 382 - isnt_strict(schema, func) should fail -ok 383 - isnt_strict(schema, func) should have the proper description -ok 384 - isnt_strict(schema, func) should have the proper diagnostics -ok 385 - is_strict(schema, func, 0 args, desc) should pass -ok 386 - is_strict(schema, func, 0 args, desc) should have the proper description -ok 387 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 388 - isnt_strict(schema, func, 0 args, desc) should fail -ok 389 - isnt_strict(schema, func, 0 args, desc) should have the proper description -ok 390 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 391 - is_strict(schema, func, 0 args) should pass -ok 392 - is_strict(schema, func, 0 args) should have the proper description -ok 393 - is_strict(schema, func, 0 args) should have the proper diagnostics -ok 394 - isnt_strict(schema, func, 0 args) should fail -ok 395 - isnt_strict(schema, func, 0 args) should have the proper description -ok 396 - isnt_strict(schema, func, 0 args) should have the proper diagnostics -ok 397 - is_strict(schema, func, args, desc) should fail -ok 398 - is_strict(schema, func, args, desc) should have the proper description -ok 399 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 400 - isnt_strict(schema, func, args, desc) should pass -ok 401 - isnt_strict(schema, func, args, desc) should have the proper description -ok 402 - isnt_strict(schema, func, args, desc) should have the proper diagnostics -ok 403 - is_strict(schema, func, args) should fail -ok 404 - is_strict(schema, func, args) should have the proper description -ok 405 - is_strict(schema, func, args) should have the proper diagnostics -ok 406 - isnt_strict(schema, func, args) should pass -ok 407 - isnt_strict(schema, func, args) should have the proper description -ok 408 - isnt_strict(schema, func, args) should have the proper diagnostics -ok 409 - is_strict(schema, func, desc) should pass -ok 410 - is_strict(schema, func, desc) should have the proper description -ok 411 - is_strict(schema, func, desc) should have the proper diagnostics -ok 412 - isnt_strict(schema, func, desc) should fail -ok 413 - isnt_strict(schema, func, desc) should have the proper description -ok 414 - isnt_strict(schema, func, desc) should have the proper diagnostics -ok 415 - is_strict(schema, func) should pass -ok 416 - is_strict(schema, func) should have the proper description -ok 417 - is_strict(schema, func) should have the proper diagnostics -ok 418 - isnt_strict(schema, func) should fail -ok 419 - isnt_strict(schema, func) should have the proper description -ok 420 - isnt_strict(schema, func) should have the proper diagnostics -ok 421 - is_strict(func, 0 args, desc) should pass -ok 422 - is_strict(func, 0 args, desc) should have the proper description -ok 423 - is_strict(func, 0 args, desc) should have the proper diagnostics -ok 424 - isnt_strict(func, 0 args, desc) should fail -ok 425 - isnt_strict(func, 0 args, desc) should have the proper description -ok 426 - isnt_strict(func, 0 args, desc) should have the proper diagnostics -ok 427 - is_strict(func, 0 args) should pass -ok 428 - is_strict(func, 0 args) should have the proper description -ok 429 - is_strict(func, 0 args) should have the proper diagnostics -ok 430 - isnt_strict(func, 0 args) should fail -ok 431 - isnt_strict(func, 0 args) should have the proper description -ok 432 - isnt_strict(func, 0 args) should have the proper diagnostics -ok 433 - is_strict(func, args, desc) should fail -ok 434 - is_strict(func, args, desc) should have the proper description -ok 435 - is_strict(func, args, desc) should have the proper diagnostics -ok 436 - isnt_strict(func, args, desc) should pass -ok 437 - isnt_strict(func, args, desc) should have the proper description -ok 438 - isnt_strict(func, args, desc) should have the proper diagnostics -ok 439 - is_strict(func, args) should fail -ok 440 - is_strict(func, args) should have the proper description -ok 441 - is_strict(func, args) should have the proper diagnostics -ok 442 - isnt_strict(func, args) should pass -ok 443 - isnt_strict(func, args) should have the proper description -ok 444 - isnt_strict(func, args) should have the proper diagnostics -ok 445 - is_strict(func, desc) should pass -ok 446 - is_strict(func, desc) should have the proper description -ok 447 - is_strict(func, desc) should have the proper diagnostics -ok 448 - isnt_strict(func, desc) should fail -ok 449 - isnt_strict(func, desc) should have the proper description -ok 450 - isnt_strict(func, desc) should have the proper diagnostics -ok 451 - is_strict(func) should pass -ok 452 - is_strict(func) should have the proper description -ok 453 - is_strict(func) should have the proper diagnostics -ok 454 - isnt_strict(func) should fail -ok 455 - isnt_strict(func) should have the proper description -ok 456 - isnt_strict(func) should have the proper diagnostics -ok 457 - function_volatility(schema, func, 0 args, volatile, desc) should pass -ok 458 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper description -ok 459 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper diagnostics -ok 460 - function_volatility(schema, func, 0 args, VOLATILE, desc) should pass -ok 461 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper description -ok 462 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper diagnostics -ok 463 - function_volatility(schema, func, 0 args, v, desc) should pass -ok 464 - function_volatility(schema, func, 0 args, v, desc) should have the proper description -ok 465 - function_volatility(schema, func, 0 args, v, desc) should have the proper diagnostics -ok 466 - function_volatility(schema, func, args, immutable, desc) should pass -ok 467 - function_volatility(schema, func, args, immutable, desc) should have the proper description -ok 468 - function_volatility(schema, func, args, immutable, desc) should have the proper diagnostics -ok 469 - function_volatility(schema, func, 0 args, stable, desc) should pass -ok 470 - function_volatility(schema, func, 0 args, stable, desc) should have the proper description -ok 471 - function_volatility(schema, func, 0 args, stable, desc) should have the proper diagnostics -ok 472 - function_volatility(schema, func, 0 args, volatile) should pass -ok 473 - function_volatility(schema, func, 0 args, volatile) should have the proper description -ok 474 - function_volatility(schema, func, 0 args, volatile) should have the proper diagnostics -ok 475 - function_volatility(schema, func, args, immutable) should pass -ok 476 - function_volatility(schema, func, args, immutable) should have the proper description -ok 477 - function_volatility(schema, func, volatile, desc) should pass -ok 478 - function_volatility(schema, func, volatile, desc) should have the proper description -ok 479 - function_volatility(schema, func, volatile, desc) should have the proper diagnostics -ok 480 - function_volatility(schema, func, volatile) should pass -ok 481 - function_volatility(schema, func, volatile) should have the proper description -ok 482 - function_volatility(schema, func, volatile) should have the proper diagnostics -ok 483 - function_volatility(schema, func, immutable, desc) should pass -ok 484 - function_volatility(schema, func, immutable, desc) should have the proper description -ok 485 - function_volatility(schema, func, immutable, desc) should have the proper diagnostics -ok 486 - function_volatility(schema, func, stable, desc) should pass -ok 487 - function_volatility(schema, func, stable, desc) should have the proper description -ok 488 - function_volatility(schema, func, stable, desc) should have the proper diagnostics -ok 489 - function_volatility(func, 0 args, volatile, desc) should pass -ok 490 - function_volatility(func, 0 args, volatile, desc) should have the proper description -ok 491 - function_volatility(func, 0 args, volatile, desc) should have the proper diagnostics -ok 492 - function_volatility(func, 0 args, VOLATILE, desc) should pass -ok 493 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper description -ok 494 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper diagnostics -ok 495 - function_volatility(func, 0 args, v, desc) should pass -ok 496 - function_volatility(func, 0 args, v, desc) should have the proper description -ok 497 - function_volatility(func, 0 args, v, desc) should have the proper diagnostics -ok 498 - function_volatility(func, args, immutable, desc) should pass -ok 499 - function_volatility(func, args, immutable, desc) should have the proper description -ok 500 - function_volatility(func, args, immutable, desc) should have the proper diagnostics -ok 501 - function_volatility(func, 0 args, stable, desc) should pass -ok 502 - function_volatility(func, 0 args, stable, desc) should have the proper description -ok 503 - function_volatility(func, 0 args, stable, desc) should have the proper diagnostics -ok 504 - function_volatility(func, 0 args, volatile) should pass -ok 505 - function_volatility(func, 0 args, volatile) should have the proper description -ok 506 - function_volatility(func, 0 args, volatile) should have the proper diagnostics -ok 507 - function_volatility(func, args, immutable) should pass -ok 508 - function_volatility(func, args, immutable) should have the proper description -ok 509 - function_volatility(func, volatile, desc) should pass -ok 510 - function_volatility(func, volatile, desc) should have the proper description -ok 511 - function_volatility(func, volatile, desc) should have the proper diagnostics -ok 512 - function_volatility(func, volatile) should pass -ok 513 - function_volatility(func, volatile) should have the proper description -ok 514 - function_volatility(func, volatile) should have the proper diagnostics -ok 515 - function_volatility(func, immutable, desc) should pass -ok 516 - function_volatility(func, immutable, desc) should have the proper description -ok 517 - function_volatility(func, immutable, desc) should have the proper diagnostics -ok 518 - function_volatility(func, stable, desc) should pass -ok 519 - function_volatility(func, stable, desc) should have the proper description -ok 520 - function_volatility(func, stable, desc) should have the proper diagnostics +ok 244 - isnt_definer(schema, func, 0 args, desc) should fail +ok 245 - isnt_definer(schema, func, 0 args, desc) should have the proper description +ok 246 - isnt_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 247 - is_definer(schema, func, 0 args) should pass +ok 248 - is_definer(schema, func, 0 args) should have the proper description +ok 249 - is_definer(schema, func, 0 args) should have the proper diagnostics +ok 250 - isnt_definer(schema, func, 0 args) should fail +ok 251 - isnt_definer(schema, func, 0 args) should have the proper description +ok 252 - isnt_definer(schema, func, 0 args) should have the proper diagnostics +ok 253 - is_definer(schema, func, args, desc) should fail +ok 254 - is_definer(schema, func, args, desc) should have the proper description +ok 255 - is_definer(schema, func, args, desc) should have the proper diagnostics +ok 256 - isnt_definer(schema, func, args, desc) should pass +ok 257 - isnt_definer(schema, func, args, desc) should have the proper description +ok 258 - isnt_definer(schema, func, args, desc) should have the proper diagnostics +ok 259 - is_definer(schema, func, args) should fail +ok 260 - is_definer(schema, func, args) should have the proper description +ok 261 - is_definer(schema, func, args) should have the proper diagnostics +ok 262 - isnt_definer(schema, func, args) should pass +ok 263 - isnt_definer(schema, func, args) should have the proper description +ok 264 - isnt_definer(schema, func, args) should have the proper diagnostics +ok 265 - is_definer(schema, func, desc) should pass +ok 266 - is_definer(schema, func, desc) should have the proper description +ok 267 - is_definer(schema, func, desc) should have the proper diagnostics +ok 268 - isnt_definer(schema, func, desc) should fail +ok 269 - isnt_definer(schema, func, desc) should have the proper description +ok 270 - isnt_definer(schema, func, desc) should have the proper diagnostics +ok 271 - is_definer(schema, func) should pass +ok 272 - is_definer(schema, func) should have the proper description +ok 273 - is_definer(schema, func) should have the proper diagnostics +ok 274 - isnt_definer(schema, func) should fail +ok 275 - isnt_definer(schema, func) should have the proper description +ok 276 - isnt_definer(schema, func) should have the proper diagnostics +ok 277 - is_definer(schema, func, 0 args, desc) should pass +ok 278 - is_definer(schema, func, 0 args, desc) should have the proper description +ok 279 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 280 - isnt_definer(schema, func, 0 args, desc) should fail +ok 281 - isnt_definer(schema, func, 0 args, desc) should have the proper description +ok 282 - isnt_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 283 - is_definer(schema, func, 0 args) should pass +ok 284 - is_definer(schema, func, 0 args) should have the proper description +ok 285 - is_definer(schema, func, 0 args) should have the proper diagnostics +ok 286 - isnt_definer(schema, func, 0 args) should fail +ok 287 - isnt_definer(schema, func, 0 args) should have the proper description +ok 288 - isnt_definer(schema, func, 0 args) should have the proper diagnostics +ok 289 - is_definer(schema, func, args, desc) should fail +ok 290 - is_definer(schema, func, args, desc) should have the proper description +ok 291 - is_definer(schema, func, args, desc) should have the proper diagnostics +ok 292 - isnt_definer(schema, func, args, desc) should pass +ok 293 - isnt_definer(schema, func, args, desc) should have the proper description +ok 294 - isnt_definer(schema, func, args, desc) should have the proper diagnostics +ok 295 - is_definer(schema, func, args) should fail +ok 296 - is_definer(schema, func, args) should have the proper description +ok 297 - is_definer(schema, func, args) should have the proper diagnostics +ok 298 - isnt_definer(schema, func, args) should pass +ok 299 - isnt_definer(schema, func, args) should have the proper description +ok 300 - isnt_definer(schema, func, args) should have the proper diagnostics +ok 301 - is_definer(schema, func, desc) should pass +ok 302 - is_definer(schema, func, desc) should have the proper description +ok 303 - is_definer(schema, func, desc) should have the proper diagnostics +ok 304 - isnt_definer(schema, func, desc) should fail +ok 305 - isnt_definer(schema, func, desc) should have the proper description +ok 306 - isnt_definer(schema, func, desc) should have the proper diagnostics +ok 307 - is_definer(schema, func) should pass +ok 308 - is_definer(schema, func) should have the proper description +ok 309 - is_definer(schema, func) should have the proper diagnostics +ok 310 - isnt_definer(schema, func) should fail +ok 311 - isnt_definer(schema, func) should have the proper description +ok 312 - isnt_definer(schema, func) should have the proper diagnostics +ok 313 - is_definer(func, 0 args, desc) should pass +ok 314 - is_definer(func, 0 args, desc) should have the proper description +ok 315 - is_definer(func, 0 args, desc) should have the proper diagnostics +ok 316 - isnt_definer(func, 0 args, desc) should fail +ok 317 - isnt_definer(func, 0 args, desc) should have the proper description +ok 318 - isnt_definer(func, 0 args, desc) should have the proper diagnostics +ok 319 - is_definer(func, 0 args) should pass +ok 320 - is_definer(func, 0 args) should have the proper description +ok 321 - is_definer(func, 0 args) should have the proper diagnostics +ok 322 - isnt_definer(func, 0 args) should fail +ok 323 - isnt_definer(func, 0 args) should have the proper description +ok 324 - isnt_definer(func, 0 args) should have the proper diagnostics +ok 325 - is_definer(func, args, desc) should fail +ok 326 - is_definer(func, args, desc) should have the proper description +ok 327 - is_definer(func, args, desc) should have the proper diagnostics +ok 328 - isnt_definer(func, args, desc) should pass +ok 329 - isnt_definer(func, args, desc) should have the proper description +ok 330 - isnt_definer(func, args, desc) should have the proper diagnostics +ok 331 - is_definer(func, args) should fail +ok 332 - is_definer(func, args) should have the proper description +ok 333 - is_definer(func, args) should have the proper diagnostics +ok 334 - isnt_definer(func, args) should pass +ok 335 - isnt_definer(func, args) should have the proper description +ok 336 - isnt_definer(func, args) should have the proper diagnostics +ok 337 - is_definer(func, desc) should pass +ok 338 - is_definer(func, desc) should have the proper description +ok 339 - is_definer(func, desc) should have the proper diagnostics +ok 340 - isnt_definer(func, desc) should fail +ok 341 - isnt_definer(func, desc) should have the proper description +ok 342 - isnt_definer(func, desc) should have the proper diagnostics +ok 343 - is_definer(func) should pass +ok 344 - is_definer(func) should have the proper description +ok 345 - is_definer(func) should have the proper diagnostics +ok 346 - isnt_definer(func) should fail +ok 347 - isnt_definer(func) should have the proper description +ok 348 - isnt_definer(func) should have the proper diagnostics +ok 349 - is_aggregate(schema, func, arg, desc) should pass +ok 350 - is_aggregate(schema, func, arg, desc) should have the proper description +ok 351 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 352 - isnt_aggregate(schema, func, arg, desc) should fail +ok 353 - isnt_aggregate(schema, func, arg, desc) should have the proper description +ok 354 - isnt_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 355 - is_aggregate(schema, func, arg) should pass +ok 356 - is_aggregate(schema, func, arg) should have the proper description +ok 357 - is_aggregate(schema, func, arg) should have the proper diagnostics +ok 358 - isnt_aggregate(schema, func, arg) should fail +ok 359 - isnt_aggregate(schema, func, arg) should have the proper description +ok 360 - isnt_aggregate(schema, func, arg) should have the proper diagnostics +ok 361 - is_aggregate(schema, func, args, desc) should fail +ok 362 - is_aggregate(schema, func, args, desc) should have the proper description +ok 363 - is_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 364 - isnt_aggregate(schema, func, args, desc) should pass +ok 365 - isnt_aggregate(schema, func, args, desc) should have the proper description +ok 366 - isnt_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 367 - is_aggregate(schema, func, args) should fail +ok 368 - is_aggregate(schema, func, args) should have the proper description +ok 369 - is_aggregate(schema, func, args) should have the proper diagnostics +ok 370 - isnt_aggregate(schema, func, args) should pass +ok 371 - isnt_aggregate(schema, func, args) should have the proper description +ok 372 - isnt_aggregate(schema, func, args) should have the proper diagnostics +ok 373 - is_aggregate(schema, func, desc) should pass +ok 374 - is_aggregate(schema, func, desc) should have the proper description +ok 375 - is_aggregate(schema, func, desc) should have the proper diagnostics +ok 376 - isnt_aggregate(schema, func, desc) should fail +ok 377 - isnt_aggregate(schema, func, desc) should have the proper description +ok 378 - isnt_aggregate(schema, func, desc) should have the proper diagnostics +ok 379 - is_aggregate(schema, func) should pass +ok 380 - is_aggregate(schema, func) should have the proper description +ok 381 - is_aggregate(schema, func) should have the proper diagnostics +ok 382 - isnt_aggregate(schema, func) should fail +ok 383 - isnt_aggregate(schema, func) should have the proper description +ok 384 - isnt_aggregate(schema, func) should have the proper diagnostics +ok 385 - is_aggregate(schema, func, arg, desc) should pass +ok 386 - is_aggregate(schema, func, arg, desc) should have the proper description +ok 387 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 388 - isnt_aggregate(schema, func, arg, desc) should fail +ok 389 - isnt_aggregate(schema, func, arg, desc) should have the proper description +ok 390 - isnt_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 391 - is_aggregate(schema, func, arg) should pass +ok 392 - is_aggregate(schema, func, arg) should have the proper description +ok 393 - is_aggregate(schema, func, arg) should have the proper diagnostics +ok 394 - isnt_aggregate(schema, func, arg) should fail +ok 395 - isnt_aggregate(schema, func, arg) should have the proper description +ok 396 - isnt_aggregate(schema, func, arg) should have the proper diagnostics +ok 397 - is_aggregate(schema, func, args, desc) should fail +ok 398 - is_aggregate(schema, func, args, desc) should have the proper description +ok 399 - is_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 400 - isnt_aggregate(schema, func, args, desc) should pass +ok 401 - isnt_aggregate(schema, func, args, desc) should have the proper description +ok 402 - isnt_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 403 - is_aggregate(schema, func, args) should fail +ok 404 - is_aggregate(schema, func, args) should have the proper description +ok 405 - is_aggregate(schema, func, args) should have the proper diagnostics +ok 406 - isnt_aggregate(schema, func, args) should pass +ok 407 - isnt_aggregate(schema, func, args) should have the proper description +ok 408 - isnt_aggregate(schema, func, args) should have the proper diagnostics +ok 409 - is_aggregate(schema, func, desc) should pass +ok 410 - is_aggregate(schema, func, desc) should have the proper description +ok 411 - is_aggregate(schema, func, desc) should have the proper diagnostics +ok 412 - isnt_aggregate(schema, func, desc) should fail +ok 413 - isnt_aggregate(schema, func, desc) should have the proper description +ok 414 - isnt_aggregate(schema, func, desc) should have the proper diagnostics +ok 415 - is_aggregate(schema, func) should pass +ok 416 - is_aggregate(schema, func) should have the proper description +ok 417 - is_aggregate(schema, func) should have the proper diagnostics +ok 418 - isnt_aggregate(schema, func) should fail +ok 419 - isnt_aggregate(schema, func) should have the proper description +ok 420 - isnt_aggregate(schema, func) should have the proper diagnostics +ok 421 - is_aggregate(func, arg, desc) should pass +ok 422 - is_aggregate(func, arg, desc) should have the proper description +ok 423 - is_aggregate(func, arg, desc) should have the proper diagnostics +ok 424 - isnt_aggregate(func, arg, desc) should fail +ok 425 - isnt_aggregate(func, arg, desc) should have the proper description +ok 426 - isnt_aggregate(func, arg, desc) should have the proper diagnostics +ok 427 - is_aggregate(func, arg) should pass +ok 428 - is_aggregate(func, arg) should have the proper description +ok 429 - is_aggregate(func, arg) should have the proper diagnostics +ok 430 - isnt_aggregate(func, arg) should fail +ok 431 - isnt_aggregate(func, arg) should have the proper description +ok 432 - isnt_aggregate(func, arg) should have the proper diagnostics +ok 433 - is_aggregate(func, args, desc) should fail +ok 434 - is_aggregate(func, args, desc) should have the proper description +ok 435 - is_aggregate(func, args, desc) should have the proper diagnostics +ok 436 - isnt_aggregate(func, args, desc) should pass +ok 437 - isnt_aggregate(func, args, desc) should have the proper description +ok 438 - isnt_aggregate(func, args, desc) should have the proper diagnostics +ok 439 - is_aggregate(func, args) should fail +ok 440 - is_aggregate(func, args) should have the proper description +ok 441 - is_aggregate(func, args) should have the proper diagnostics +ok 442 - isnt_aggregate(func, args) should pass +ok 443 - isnt_aggregate(func, args) should have the proper description +ok 444 - isnt_aggregate(func, args) should have the proper diagnostics +ok 445 - is_aggregate(func, desc) should pass +ok 446 - is_aggregate(func, desc) should have the proper description +ok 447 - is_aggregate(func, desc) should have the proper diagnostics +ok 448 - isnt_aggregate(func, desc) should fail +ok 449 - isnt_aggregate(func, desc) should have the proper description +ok 450 - isnt_aggregate(func, desc) should have the proper diagnostics +ok 451 - is_aggregate(func) should pass +ok 452 - is_aggregate(func) should have the proper description +ok 453 - is_aggregate(func) should have the proper diagnostics +ok 454 - isnt_aggregate(func) should fail +ok 455 - isnt_aggregate(func) should have the proper description +ok 456 - isnt_aggregate(func) should have the proper diagnostics +ok 457 - is_strict(schema, func, 0 args, desc) should pass +ok 458 - is_strict(schema, func, 0 args, desc) should have the proper description +ok 459 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 460 - isnt_strict(schema, func, 0 args, desc) should fail +ok 461 - isnt_strict(schema, func, 0 args, desc) should have the proper description +ok 462 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 463 - is_strict(schema, func, 0 args) should pass +ok 464 - is_strict(schema, func, 0 args) should have the proper description +ok 465 - is_strict(schema, func, 0 args) should have the proper diagnostics +ok 466 - isnt_strict(schema, func, 0 args) should fail +ok 467 - isnt_strict(schema, func, 0 args) should have the proper description +ok 468 - isnt_strict(schema, func, 0 args) should have the proper diagnostics +ok 469 - is_strict(schema, func, args, desc) should fail +ok 470 - is_strict(schema, func, args, desc) should have the proper description +ok 471 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 472 - is_strict(schema, func, args, desc) should pass +ok 473 - is_strict(schema, func, args, desc) should have the proper description +ok 474 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 475 - is_strict(schema, func, args) should fail +ok 476 - is_strict(schema, func, args) should have the proper description +ok 477 - is_strict(schema, func, args) should have the proper diagnostics +ok 478 - is_strict(schema, func, args) should pass +ok 479 - is_strict(schema, func, args) should have the proper description +ok 480 - is_strict(schema, func, args) should have the proper diagnostics +ok 481 - is_strict(schema, func, desc) should pass +ok 482 - is_strict(schema, func, desc) should have the proper description +ok 483 - is_strict(schema, func, desc) should have the proper diagnostics +ok 484 - isnt_strict(schema, func, desc) should fail +ok 485 - isnt_strict(schema, func, desc) should have the proper description +ok 486 - isnt_strict(schema, func, desc) should have the proper diagnostics +ok 487 - is_strict(schema, func) should pass +ok 488 - is_strict(schema, func) should have the proper description +ok 489 - is_strict(schema, func) should have the proper diagnostics +ok 490 - isnt_strict(schema, func) should fail +ok 491 - isnt_strict(schema, func) should have the proper description +ok 492 - isnt_strict(schema, func) should have the proper diagnostics +ok 493 - is_strict(schema, func, 0 args, desc) should pass +ok 494 - is_strict(schema, func, 0 args, desc) should have the proper description +ok 495 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 496 - isnt_strict(schema, func, 0 args, desc) should fail +ok 497 - isnt_strict(schema, func, 0 args, desc) should have the proper description +ok 498 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 499 - is_strict(schema, func, 0 args) should pass +ok 500 - is_strict(schema, func, 0 args) should have the proper description +ok 501 - is_strict(schema, func, 0 args) should have the proper diagnostics +ok 502 - isnt_strict(schema, func, 0 args) should fail +ok 503 - isnt_strict(schema, func, 0 args) should have the proper description +ok 504 - isnt_strict(schema, func, 0 args) should have the proper diagnostics +ok 505 - is_strict(schema, func, args, desc) should fail +ok 506 - is_strict(schema, func, args, desc) should have the proper description +ok 507 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 508 - isnt_strict(schema, func, args, desc) should pass +ok 509 - isnt_strict(schema, func, args, desc) should have the proper description +ok 510 - isnt_strict(schema, func, args, desc) should have the proper diagnostics +ok 511 - is_strict(schema, func, args) should fail +ok 512 - is_strict(schema, func, args) should have the proper description +ok 513 - is_strict(schema, func, args) should have the proper diagnostics +ok 514 - isnt_strict(schema, func, args) should pass +ok 515 - isnt_strict(schema, func, args) should have the proper description +ok 516 - isnt_strict(schema, func, args) should have the proper diagnostics +ok 517 - is_strict(schema, func, desc) should pass +ok 518 - is_strict(schema, func, desc) should have the proper description +ok 519 - is_strict(schema, func, desc) should have the proper diagnostics +ok 520 - isnt_strict(schema, func, desc) should fail +ok 521 - isnt_strict(schema, func, desc) should have the proper description +ok 522 - isnt_strict(schema, func, desc) should have the proper diagnostics +ok 523 - is_strict(schema, func) should pass +ok 524 - is_strict(schema, func) should have the proper description +ok 525 - is_strict(schema, func) should have the proper diagnostics +ok 526 - isnt_strict(schema, func) should fail +ok 527 - isnt_strict(schema, func) should have the proper description +ok 528 - isnt_strict(schema, func) should have the proper diagnostics +ok 529 - is_strict(func, 0 args, desc) should pass +ok 530 - is_strict(func, 0 args, desc) should have the proper description +ok 531 - is_strict(func, 0 args, desc) should have the proper diagnostics +ok 532 - isnt_strict(func, 0 args, desc) should fail +ok 533 - isnt_strict(func, 0 args, desc) should have the proper description +ok 534 - isnt_strict(func, 0 args, desc) should have the proper diagnostics +ok 535 - is_strict(func, 0 args) should pass +ok 536 - is_strict(func, 0 args) should have the proper description +ok 537 - is_strict(func, 0 args) should have the proper diagnostics +ok 538 - isnt_strict(func, 0 args) should fail +ok 539 - isnt_strict(func, 0 args) should have the proper description +ok 540 - isnt_strict(func, 0 args) should have the proper diagnostics +ok 541 - is_strict(func, args, desc) should fail +ok 542 - is_strict(func, args, desc) should have the proper description +ok 543 - is_strict(func, args, desc) should have the proper diagnostics +ok 544 - isnt_strict(func, args, desc) should pass +ok 545 - isnt_strict(func, args, desc) should have the proper description +ok 546 - isnt_strict(func, args, desc) should have the proper diagnostics +ok 547 - is_strict(func, args) should fail +ok 548 - is_strict(func, args) should have the proper description +ok 549 - is_strict(func, args) should have the proper diagnostics +ok 550 - isnt_strict(func, args) should pass +ok 551 - isnt_strict(func, args) should have the proper description +ok 552 - isnt_strict(func, args) should have the proper diagnostics +ok 553 - is_strict(func, desc) should pass +ok 554 - is_strict(func, desc) should have the proper description +ok 555 - is_strict(func, desc) should have the proper diagnostics +ok 556 - isnt_strict(func, desc) should fail +ok 557 - isnt_strict(func, desc) should have the proper description +ok 558 - isnt_strict(func, desc) should have the proper diagnostics +ok 559 - is_strict(func) should pass +ok 560 - is_strict(func) should have the proper description +ok 561 - is_strict(func) should have the proper diagnostics +ok 562 - isnt_strict(func) should fail +ok 563 - isnt_strict(func) should have the proper description +ok 564 - isnt_strict(func) should have the proper diagnostics +ok 565 - function_volatility(schema, func, 0 args, volatile, desc) should pass +ok 566 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper description +ok 567 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper diagnostics +ok 568 - function_volatility(schema, func, 0 args, VOLATILE, desc) should pass +ok 569 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper description +ok 570 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper diagnostics +ok 571 - function_volatility(schema, func, 0 args, v, desc) should pass +ok 572 - function_volatility(schema, func, 0 args, v, desc) should have the proper description +ok 573 - function_volatility(schema, func, 0 args, v, desc) should have the proper diagnostics +ok 574 - function_volatility(schema, func, args, immutable, desc) should pass +ok 575 - function_volatility(schema, func, args, immutable, desc) should have the proper description +ok 576 - function_volatility(schema, func, args, immutable, desc) should have the proper diagnostics +ok 577 - function_volatility(schema, func, 0 args, stable, desc) should pass +ok 578 - function_volatility(schema, func, 0 args, stable, desc) should have the proper description +ok 579 - function_volatility(schema, func, 0 args, stable, desc) should have the proper diagnostics +ok 580 - function_volatility(schema, func, 0 args, volatile) should pass +ok 581 - function_volatility(schema, func, 0 args, volatile) should have the proper description +ok 582 - function_volatility(schema, func, 0 args, volatile) should have the proper diagnostics +ok 583 - function_volatility(schema, func, args, immutable) should pass +ok 584 - function_volatility(schema, func, args, immutable) should have the proper description +ok 585 - function_volatility(schema, func, volatile, desc) should pass +ok 586 - function_volatility(schema, func, volatile, desc) should have the proper description +ok 587 - function_volatility(schema, func, volatile, desc) should have the proper diagnostics +ok 588 - function_volatility(schema, func, volatile) should pass +ok 589 - function_volatility(schema, func, volatile) should have the proper description +ok 590 - function_volatility(schema, func, volatile) should have the proper diagnostics +ok 591 - function_volatility(schema, func, immutable, desc) should pass +ok 592 - function_volatility(schema, func, immutable, desc) should have the proper description +ok 593 - function_volatility(schema, func, immutable, desc) should have the proper diagnostics +ok 594 - function_volatility(schema, func, stable, desc) should pass +ok 595 - function_volatility(schema, func, stable, desc) should have the proper description +ok 596 - function_volatility(schema, func, stable, desc) should have the proper diagnostics +ok 597 - function_volatility(func, 0 args, volatile, desc) should pass +ok 598 - function_volatility(func, 0 args, volatile, desc) should have the proper description +ok 599 - function_volatility(func, 0 args, volatile, desc) should have the proper diagnostics +ok 600 - function_volatility(func, 0 args, VOLATILE, desc) should pass +ok 601 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper description +ok 602 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper diagnostics +ok 603 - function_volatility(func, 0 args, v, desc) should pass +ok 604 - function_volatility(func, 0 args, v, desc) should have the proper description +ok 605 - function_volatility(func, 0 args, v, desc) should have the proper diagnostics +ok 606 - function_volatility(func, args, immutable, desc) should pass +ok 607 - function_volatility(func, args, immutable, desc) should have the proper description +ok 608 - function_volatility(func, args, immutable, desc) should have the proper diagnostics +ok 609 - function_volatility(func, 0 args, stable, desc) should pass +ok 610 - function_volatility(func, 0 args, stable, desc) should have the proper description +ok 611 - function_volatility(func, 0 args, stable, desc) should have the proper diagnostics +ok 612 - function_volatility(func, 0 args, volatile) should pass +ok 613 - function_volatility(func, 0 args, volatile) should have the proper description +ok 614 - function_volatility(func, 0 args, volatile) should have the proper diagnostics +ok 615 - function_volatility(func, args, immutable) should pass +ok 616 - function_volatility(func, args, immutable) should have the proper description +ok 617 - function_volatility(func, volatile, desc) should pass +ok 618 - function_volatility(func, volatile, desc) should have the proper description +ok 619 - function_volatility(func, volatile, desc) should have the proper diagnostics +ok 620 - function_volatility(func, volatile) should pass +ok 621 - function_volatility(func, volatile) should have the proper description +ok 622 - function_volatility(func, volatile) should have the proper diagnostics +ok 623 - function_volatility(func, immutable, desc) should pass +ok 624 - function_volatility(func, immutable, desc) should have the proper description +ok 625 - function_volatility(func, immutable, desc) should have the proper diagnostics +ok 626 - function_volatility(func, stable, desc) should pass +ok 627 - function_volatility(func, stable, desc) should have the proper description +ok 628 - function_volatility(func, stable, desc) should have the proper diagnostics diff --git a/test/sql/functap.sql b/test/sql/functap.sql index 41f7d62f24b5..8d6c5e6045cc 100644 --- a/test/sql/functap.sql +++ b/test/sql/functap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(520); +SELECT plan(628); --SELECT * FROM no_plan(); CREATE SCHEMA someschema; @@ -691,7 +691,7 @@ SELECT * FROM check_test( ); /****************************************************************************/ --- Test is_definer(). +-- Test is_definer() isnt_definer(). SELECT * FROM check_test( is_definer( 'public', 'yay', '{}'::name[], 'whatever' ), true, @@ -700,6 +700,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'yay', '{}'::name[], 'whatever' ), + false, + 'isnt_definer(schema, func, 0 args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'yay', '{}'::name[] ), true, @@ -708,6 +716,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'yay', '{}'::name[] ), + false, + 'isnt_definer(schema, func, 0 args)', + 'Function public.yay() should not be security definer', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), false, @@ -716,6 +732,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_definer(schema, func, args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'oww', ARRAY['integer', 'text'] ), false, @@ -724,6 +748,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'oww', ARRAY['integer', 'text'] ), + true, + 'isnt_definer(schema, func, args)', + 'Function public.oww(integer, text) should not be security definer', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'yay', 'whatever' ), true, @@ -732,6 +764,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'yay', 'whatever' ), + false, + 'isnt_definer(schema, func, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'yay'::name ), true, @@ -740,6 +780,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'yay'::name ), + false, + 'isnt_definer(schema, func)', + 'Function public.yay() should not be security definer', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'yay', '{}'::name[], 'whatever' ), true, @@ -748,6 +796,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'yay', '{}'::name[], 'whatever' ), + false, + 'isnt_definer(schema, func, 0 args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'yay', '{}'::name[] ), true, @@ -756,6 +812,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'yay', '{}'::name[] ), + false, + 'isnt_definer(schema, func, 0 args)', + 'Function public.yay() should not be security definer', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), false, @@ -764,6 +828,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_definer(schema, func, args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'oww', ARRAY['integer', 'text'] ), false, @@ -772,6 +844,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'oww', ARRAY['integer', 'text'] ), + true, + 'isnt_definer(schema, func, args)', + 'Function public.oww(integer, text) should not be security definer', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'yay', 'whatever' ), true, @@ -780,6 +860,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'yay', 'whatever' ), + false, + 'isnt_definer(schema, func, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'yay'::name ), true, @@ -788,6 +876,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'yay'::name ), + false, + 'isnt_definer(schema, func)', + 'Function public.yay() should not be security definer', + '' +); + SELECT * FROM check_test( is_definer( 'yay', '{}'::name[], 'whatever' ), true, @@ -796,6 +892,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'yay', '{}'::name[], 'whatever' ), + false, + 'isnt_definer(func, 0 args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'yay', '{}'::name[] ), true, @@ -804,6 +908,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'yay', '{}'::name[] ), + false, + 'isnt_definer(func, 0 args)', + 'Function yay() should not be security definer', + '' +); + SELECT * FROM check_test( is_definer( 'oww', ARRAY['integer', 'text'], 'whatever' ), false, @@ -812,6 +924,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_definer(func, args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'oww', ARRAY['integer', 'text'] ), false, @@ -820,6 +940,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'oww', ARRAY['integer', 'text'] ), + true, + 'isnt_definer(func, args)', + 'Function oww(integer, text) should not be security definer', + '' +); + SELECT * FROM check_test( is_definer( 'yay', 'whatever' ), true, @@ -828,6 +956,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'yay', 'whatever' ), + false, + 'isnt_definer(func, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'yay'::name ), true, @@ -836,8 +972,16 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'yay'::name ), + false, + 'isnt_definer(func)', + 'Function yay() should not be security definer', + '' +); + /****************************************************************************/ --- Test is_aggregate(). +-- Test is_aggregate() and isnt_aggregate(). SELECT * FROM check_test( is_aggregate( 'public', 'tap_accum', ARRAY['anyelement'], 'whatever' ), true, @@ -846,6 +990,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum', ARRAY['anyelement'], 'whatever' ), + false, + 'isnt_aggregate(schema, func, arg, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'tap_accum', ARRAY['anyelement'] ), true, @@ -854,6 +1006,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum', ARRAY['anyelement'] ), + false, + 'isnt_aggregate(schema, func, arg)', + 'Function public.tap_accum(anyelement) should not be an aggregate function', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), false, @@ -862,6 +1022,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_aggregate(schema, func, args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), false, @@ -870,6 +1038,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), + true, + 'isnt_aggregate(schema, func, args)', + 'Function public.oww(integer, text) should not be an aggregate function', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'tap_accum', 'whatever' ), true, @@ -878,6 +1054,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum', 'whatever' ), + false, + 'isnt_aggregate(schema, func, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'tap_accum'::name ), true, @@ -886,6 +1070,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum'::name ), + false, + 'isnt_aggregate(schema, func)', + 'Function public.tap_accum() should not be an aggregate function', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'tap_accum', ARRAY['anyelement'], 'whatever' ), true, @@ -894,6 +1086,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum', ARRAY['anyelement'], 'whatever' ), + false, + 'isnt_aggregate(schema, func, arg, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'tap_accum', ARRAY['anyelement'] ), true, @@ -902,6 +1102,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum', ARRAY['anyelement'] ), + false, + 'isnt_aggregate(schema, func, arg)', + 'Function public.tap_accum(anyelement) should not be an aggregate function', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), false, @@ -910,6 +1118,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_aggregate(schema, func, args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), false, @@ -918,6 +1134,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), + true, + 'isnt_aggregate(schema, func, args)', + 'Function public.oww(integer, text) should not be an aggregate function', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'tap_accum', 'whatever' ), true, @@ -926,6 +1150,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum', 'whatever' ), + false, + 'isnt_aggregate(schema, func, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'tap_accum'::name ), true, @@ -934,6 +1166,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum'::name ), + false, + 'isnt_aggregate(schema, func)', + 'Function public.tap_accum() should not be an aggregate function', + '' +); + SELECT * FROM check_test( is_aggregate( 'tap_accum', ARRAY['anyelement'], 'whatever' ), true, @@ -942,6 +1182,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'tap_accum', ARRAY['anyelement'], 'whatever' ), + false, + 'isnt_aggregate(func, arg, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'tap_accum', ARRAY['anyelement'] ), true, @@ -950,6 +1198,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'tap_accum', ARRAY['anyelement'] ), + false, + 'isnt_aggregate(func, arg)', + 'Function tap_accum(anyelement) should not be an aggregate function', + '' +); + SELECT * FROM check_test( is_aggregate( 'oww', ARRAY['integer', 'text'], 'whatever' ), false, @@ -958,6 +1214,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_aggregate(func, args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'oww', ARRAY['integer', 'text'] ), false, @@ -966,6 +1230,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'oww', ARRAY['integer', 'text'] ), + true, + 'isnt_aggregate(func, args)', + 'Function oww(integer, text) should not be an aggregate function', + '' +); + SELECT * FROM check_test( is_aggregate( 'tap_accum', 'whatever' ), true, @@ -974,6 +1246,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'tap_accum', 'whatever' ), + false, + 'isnt_aggregate(func, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'tap_accum'::name ), true, @@ -982,6 +1262,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'tap_accum'::name ), + false, + 'isnt_aggregate(func)', + 'Function tap_accum() should not be an aggregate function', + '' +); + /****************************************************************************/ -- Test is_strict() and isnt_strict(). SELECT * FROM check_test( From bf8a89a7981f9bec54af36befd1164bc3a5f4cfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Qui=C3=A9deville?= Date: Thu, 30 Jun 2016 16:37:22 +0200 Subject: [PATCH 0916/1195] Add doc on new functions isnt_definer() and isnt_aggregate(). Fix a little typo in is_aggregate() doc --- doc/pgtap.mmd | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index de038056e685..2aed35ecd9bc 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4921,6 +4921,42 @@ If the function does not exist, a handy diagnostic message will let you know: But then you check with `has_function()` first, right? +### `isnt_definer()` ### + + SELECT isnt_definer( :schema, :function, :args, :description ); + SELECT isnt_definer( :schema, :function, :args ); + SELECT isnt_definer( :schema, :function, :description ); + SELECT isnt_definer( :schema, :function ); + SELECT isnt_definer( :function, :args, :description ); + SELECT isnt_definer( :function, :args ); + SELECT isnt_definer( :function, :description ); + SELECT isnt_definer( :function ); + +**Parameters** + +`:schema` +: Schema in which to find the function. + +`:function` +: Function name. + +`:args` +: Array of data types for the function arguments. + +`:description` +: A short description of the test. + + +This function is the inverse of `is_definer()`. The test passes if the specified +function is not a security definer. + +If the function does not exist, a handy diagnostic message will let you know: + + # Failed test 290: "Function nasty() should not be security definer" + # Function nasty() does not exist + +But then you check with `has_function()` first, right? + ### `is_strict()` ### SELECT is_strict( :schema, :function, :args, :description ); @@ -5041,7 +5077,42 @@ Examples: If the function does not exist, a handy diagnostic message will let you know: - # Failed test 290: "Function nasty() should be strict" + # Failed test 290: "Function nasty() should be an aggregate function" + # Function nasty() does not exist + +But then you check with `has_function()` first, right? + +### `isnt_aggregate()` ### + + SELECT isnt_aggregate( :schema, :function, :args, :description ); + SELECT isnt_aggregate( :schema, :function, :args ); + SELECT isnt_aggregate( :schema, :function, :description ); + SELECT isnt_aggregate( :schema, :function ); + SELECT isnt_aggregate( :function, :args, :description ); + SELECT isnt_aggregate( :function, :args ); + SELECT isnt_aggregate( :function, :description ); + SELECT isnt_aggregate( :function ); + +**Parameters** + +`:schema` +: Schema in which to find the function. + +`:function` +: Function name. + +`:args` +: Array of data types for the function arguments. + +`:description` +: A short description of the test. + +This function is the inverse of `is_aggregate()`. The test passes if the specified +function is not an aggregate function. + +If the function does not exist, a handy diagnostic message will let you know: + + # Failed test 290: "Function nasty() should not be an aggregate function" # Function nasty() does not exist But then you check with `has_function()` first, right? From 5ad76e25067463a863b12a13cfd3203f82595f9f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 24 Sep 2016 21:28:20 -0700 Subject: [PATCH 0917/1195] Fix version num parsing for pg 10. --- Changes | 2 ++ sql/pgtap--0.96.0--0.97.0.sql | 12 +++++++++++- sql/pgtap.sql.in | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 28f31136a297..946c2e2d2546 100644 --- a/Changes +++ b/Changes @@ -7,6 +7,8 @@ Revision history for pgTAP exist" instead of "should exist", thanks to Rodolphe Quiédeville (PR #99). * Added `is_indexed()`, which test to see that specific table columns are indexed, thanks to Rodolphe Quiédeville (PR #103). +* Fixed `pg_version_num()` to work with the new version format coming in + PostgreSQL 10. 0.96.0 2016-05-16T20:53:57Z --------------------------- diff --git a/sql/pgtap--0.96.0--0.97.0.sql b/sql/pgtap--0.96.0--0.97.0.sql index 6c39d2429413..8d0fbb76e730 100644 --- a/sql/pgtap--0.96.0--0.97.0.sql +++ b/sql/pgtap--0.96.0--0.97.0.sql @@ -90,4 +90,14 @@ RETURNS TEXT AS $$ SELECT ok ( _is_indexed( $1, ARRAY[$2]::NAME[]), 'An index on ' || quote_ident($1) || ' on column ' || $2::text || ' should exist'); -$$ LANGUAGE sql; \ No newline at end of file +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION pg_version_num() +RETURNS integer AS $$ + 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index fdc94cf42b7c..5a0fa9c6955e 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -11,7 +11,7 @@ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ - SELECT s.a[1]::int * 10000 + 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 ( From 01b47d7e54c10470358614ff8e46b3e467c0fbb9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 26 Sep 2016 14:11:24 -0700 Subject: [PATCH 0918/1195] Index test doc tweaks. --- doc/pgtap.mmd | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index de038056e685..fe2172c3b44b 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -3211,12 +3211,12 @@ enum does *not* exist. : Array of the columns and/or expressions in the index. `:column` -: Idexed column name or expression. +: Indexed column name or expression. `:description` : A short description of the test. -Checks for the existence of an index associated with the named table. The +Checks for the existence of a named index associated with the named table. The `:schema` argument is optional, as is the column name or names or expression, and the description. The columns argument may be a string naming one column or expression, or an array of column names and/or expressions. For expressions, @@ -4672,7 +4672,8 @@ order defined by the index. `:description` : A short description of the test. -Tests an index exists on table for a set of columns. Order of column matters. +Checks for the existence of an index associated with the named table. Differs +from `has_index()` in not requiring an index name. ### `index_is_type()` ### From d7ce18a71ff2333573e2c12516bbbb093c5ceb62 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 26 Sep 2016 17:05:21 -0700 Subject: [PATCH 0919/1195] Teach is_indexed() to understand column expressions. Completes the work started in #103. --- Changes | 5 +- doc/pgtap.mmd | 14 +++--- sql/pgtap--0.96.0--0.97.0.sql | 92 +++++++++++++++-------------------- sql/pgtap.sql.in | 92 +++++++++++++++-------------------- test/expected/index.out | 14 +++++- test/sql/index.sql | 54 +++++++++++++++++--- 6 files changed, 151 insertions(+), 120 deletions(-) diff --git a/Changes b/Changes index 946c2e2d2546..53eea98f8cc8 100644 --- a/Changes +++ b/Changes @@ -6,7 +6,10 @@ Revision history for pgTAP * Fixed the default description for `hasnt_opclass()` to say "should not exist" instead of "should exist", thanks to Rodolphe Quiédeville (PR #99). * Added `is_indexed()`, which test to see that specific table columns are - indexed, thanks to Rodolphe Quiédeville (PR #103). + indexed. It's effectively the same as `has_index()` except that it doesn't + require an index name and does require one or more column names or + expressions in the defined for the index. Thanks to Rodolphe Quiédeville + (PR #103). * Fixed `pg_version_num()` to work with the new version format coming in PostgreSQL 10. diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index fe2172c3b44b..79b467362e54 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4663,17 +4663,19 @@ order defined by the index. `:table` : Name of a table containing the index. -`:column` -: Name of the column. - `:columns` -: Array of columns name. +: Array of the columns and/or expressions in the index. + +`:column` +: Indexed column name or expression. `:description` : A short description of the test. -Checks for the existence of an index associated with the named table. Differs -from `has_index()` in not requiring an index name. +Checks that the specified columns or expressions are contained in a single +index on the named table. Effectively like `has_index()` except that it +doesn't require an index name and does require one or more column names or +expressions in the defined for the index. ### `index_is_type()` ### diff --git a/sql/pgtap--0.96.0--0.97.0.sql b/sql/pgtap--0.96.0--0.97.0.sql index 8d0fbb76e730..028ead57cbc9 100644 --- a/sql/pgtap--0.96.0--0.97.0.sql +++ b/sql/pgtap--0.96.0--0.97.0.sql @@ -1,67 +1,56 @@ -CREATE OR REPLACE FUNCTION _is_indexed( NAME, NAME, NAME[]) -RETURNS BOOLEAN AS $$ - WITH cols AS ( - SELECT x.indexrelid, x.indrelid, unnest(x.indkey) as colid - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class r ON r.oid = x.indrelid - JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace - WHERE n.nspname = $1 - AND r.relname = $2), - colsdef AS ( - SELECT cols.indexrelid, cols.indrelid, array_agg(a.attname) as cols - FROM cols - JOIN pg_catalog.pg_attribute a ON (a.attrelid = cols.indrelid - AND a.attnum = cols.colid) - GROUP BY 1, 2) - SELECT EXISTS ( - SELECT TRUE - FROM colsdef - WHERE colsdef.cols::NAME[] = $3 - ); +CREATE OR REPLACE FUNCTION _is_indexed( NAME, NAME, TEXT[] ) +RETURNS BOOL AS $$ + WITH icols AS ( + SELECT _ikeys($1, $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 n.nspname = $1 + AND ct.relname = $2 + ) SELECT EXISTS( SELECT TRUE from icols WHERE cols = $3 ) $$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION _is_indexed( NAME, NAME[]) -RETURNS BOOLEAN AS $$ - WITH cols AS ( - SELECT x.indexrelid, x.indrelid, unnest(x.indkey) as colid - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class r ON r.oid = x.indrelid - WHERE r.relname = $1), - colsdef AS ( - SELECT cols.indexrelid, cols.indrelid, array_agg(a.attname) as cols - FROM cols - JOIN pg_catalog.pg_attribute a ON (a.attrelid = cols.indrelid - AND a.attnum = cols.colid) - GROUP BY 1, 2) - SELECT EXISTS ( - SELECT TRUE - FROM colsdef - WHERE colsdef.cols::NAME[] = $2 - ); +CREATE OR REPLACE FUNCTION _is_indexed( NAME, TEXT[] ) +RETURNS BOOL AS $$ + WITH icols AS ( + SELECT _ikeys($1, 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 + WHERE ct.relname = $1 + AND pg_catalog.pg_table_is_visible(ct.oid) + ) SELECT EXISTS( SELECT TRUE from icols WHERE cols = $2 ) $$ 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); + 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), 'An index on ' || quote_ident($1) || '.' || quote_ident($2) || ' with ' || $3::text || ' should exist'); + 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( $1, $2), $3); + SELECT ok( _is_indexed($1, $2), $3 ); $$ LANGUAGE sql; -- is_indexed( table, columns[] ) CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME[] ) RETURNS TEXT AS $$ - SELECT ok ( _is_indexed( $1, $2), 'An index on ' || quote_ident($1) || ' with ' || $2::text || ' should exist'); + SELECT ok( + _is_indexed($1, $2), + 'Should have an index on ' || quote_ident($1) || '(' || array_to_string( $2, ', ' ) || ')' + ); $$ LANGUAGE sql; -- is_indexed( schema, table, column, description ) @@ -71,25 +60,22 @@ RETURNS TEXT AS $$ $$ 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. - ok ( _is_indexed( $1, $2, ARRAY[$3]::NAME[]), - 'An index on ' || quote_ident($1) || '.' || quote_ident($2) - || ' on column ' || quote_ident($3) || ' should exist') - ELSE - -- Looking for particular columns. - ok ( _is_indexed( $1, ARRAY[$2]::NAME[]), $3) - END; + -- 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( $1, ARRAY[$2]::NAME[]), - 'An index on ' || quote_ident($1) || ' on column ' - || $2::text || ' should exist'); + SELECT ok ( _is_indexed( $1, ARRAY[$2]::NAME[]) ); $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION pg_version_num() diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 5a0fa9c6955e..4f25f016882d 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2923,70 +2923,59 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _is_indexed( NAME, NAME, NAME[]) -RETURNS BOOLEAN AS $$ - WITH cols AS ( - SELECT x.indexrelid, x.indrelid, unnest(x.indkey) as colid - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class r ON r.oid = x.indrelid - JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace - WHERE n.nspname = $1 - AND r.relname = $2), - colsdef AS ( - SELECT cols.indexrelid, cols.indrelid, array_agg(a.attname) as cols - FROM cols - JOIN pg_catalog.pg_attribute a ON (a.attrelid = cols.indrelid - AND a.attnum = cols.colid) - GROUP BY 1, 2) - SELECT EXISTS ( - SELECT TRUE - FROM colsdef - WHERE colsdef.cols::NAME[] = $3 - ); +CREATE OR REPLACE FUNCTION _is_indexed( NAME, NAME, TEXT[] ) +RETURNS BOOL AS $$ + WITH icols AS ( + SELECT _ikeys($1, $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 n.nspname = $1 + AND ct.relname = $2 + ) SELECT EXISTS( SELECT TRUE from icols WHERE cols = $3 ) $$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION _is_indexed( NAME, NAME[]) -RETURNS BOOLEAN AS $$ - WITH cols AS ( - SELECT x.indexrelid, x.indrelid, unnest(x.indkey) as colid - FROM pg_catalog.pg_index x - JOIN pg_catalog.pg_class r ON r.oid = x.indrelid - WHERE r.relname = $1), - colsdef AS ( - SELECT cols.indexrelid, cols.indrelid, array_agg(a.attname) as cols - FROM cols - JOIN pg_catalog.pg_attribute a ON (a.attrelid = cols.indrelid - AND a.attnum = cols.colid) - GROUP BY 1, 2) - SELECT EXISTS ( - SELECT TRUE - FROM colsdef - WHERE colsdef.cols::NAME[] = $2 - ); +CREATE OR REPLACE FUNCTION _is_indexed( NAME, TEXT[] ) +RETURNS BOOL AS $$ + WITH icols AS ( + SELECT _ikeys($1, 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 + WHERE ct.relname = $1 + AND pg_catalog.pg_table_is_visible(ct.oid) + ) SELECT EXISTS( SELECT TRUE from icols WHERE cols = $2 ) $$ 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); + 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), 'An index on ' || quote_ident($1) || '.' || quote_ident($2) || ' with ' || $3::text || ' should exist'); + 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( $1, $2), $3); + SELECT ok( _is_indexed($1, $2), $3 ); $$ LANGUAGE sql; -- is_indexed( table, columns[] ) CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME[] ) RETURNS TEXT AS $$ - SELECT ok ( _is_indexed( $1, $2), 'An index on ' || quote_ident($1) || ' with ' || $2::text || ' should exist'); + SELECT ok( + _is_indexed($1, $2), + 'Should have an index on ' || quote_ident($1) || '(' || array_to_string( $2, ', ' ) || ')' + ); $$ LANGUAGE sql; -- is_indexed( schema, table, column, description ) @@ -2996,25 +2985,22 @@ RETURNS TEXT AS $$ $$ 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. - ok ( _is_indexed( $1, $2, ARRAY[$3]::NAME[]), - 'An index on ' || quote_ident($1) || '.' || quote_ident($2) - || ' on column ' || quote_ident($3) || ' should exist') - ELSE - -- Looking for particular columns. - ok ( _is_indexed( $1, ARRAY[$2]::NAME[]), $3) - END; + -- 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( $1, ARRAY[$2]::NAME[]), - 'An index on ' || quote_ident($1) || ' on column ' - || $2::text || ' should exist'); + SELECT ok ( _is_indexed( $1, ARRAY[$2]::NAME[]) ); $$ LANGUAGE sql; -- index_is_unique( schema, table, index, description ) diff --git a/test/expected/index.out b/test/expected/index.out index c8d5bcb57412..ded52109c8e2 100644 --- a/test/expected/index.out +++ b/test/expected/index.out @@ -1,5 +1,5 @@ \unset ECHO -1..258 +1..270 ok 1 - has_index() single column should pass ok 2 - has_index() single column should have the proper description ok 3 - has_index() single column should have the proper diagnostics @@ -258,3 +258,15 @@ ok 255 - is_indexed( schema, table, column ) fail should have the proper diagnos ok 256 - is_indexed( schema, table, columns[] ) fail, column order matters should fail ok 257 - is_indexed( schema, table, columns[] ) fail, column order matters should have the proper description ok 258 - is_indexed( schema, table, columns[] ) fail, column order matters should have the proper diagnostics +ok 259 - is_indexed(schema, table, expressions) should pass +ok 260 - is_indexed(schema, table, expressions) should have the proper description +ok 261 - is_indexed(schema, table, expressions) should have the proper diagnostics +ok 262 - is_indexed(schema, table, expression) should pass +ok 263 - is_indexed(schema, table, expression) should have the proper description +ok 264 - is_indexed(schema, table, expression) should have the proper diagnostics +ok 265 - is_indexed(table, expressions) should pass +ok 266 - is_indexed(table, expressions) should have the proper description +ok 267 - is_indexed(table, expressions) should have the proper diagnostics +ok 268 - is_indexed( table, expression) should pass +ok 269 - is_indexed( table, expression) should have the proper description +ok 270 - is_indexed( table, expression) should have the proper diagnostics diff --git a/test/sql/index.sql b/test/sql/index.sql index 1925585074d3..941407bfe2ed 100644 --- a/test/sql/index.sql +++ b/test/sql/index.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(258); +SELECT plan(270); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -689,7 +689,7 @@ SELECT * FROM check_test( is_indexed( 'public', 'sometab', ARRAY['numb','name']::name[] ), true, 'is_indexed( schema, table, columns[] )', - 'An index on public.sometab with {numb,name} should exist', + 'Should have an index on public.sometab(numb, name)', '' ); @@ -705,7 +705,7 @@ SELECT * FROM check_test( is_indexed( 'sometab', ARRAY['numb','name']::name[] ), true, 'is_indexed( table, columns[] )', - 'An index on sometab with {numb,name} should exist', + 'Should have an index on sometab(numb, name)', '' ); @@ -721,7 +721,7 @@ SELECT * FROM check_test( is_indexed( 'public', 'sometab'::name, 'numb'::name ), true, 'is_indexed( schema, table, column )', - 'An index on public.sometab on column numb should exist', + 'Should have an index on public.sometab(numb)', '' ); @@ -729,7 +729,7 @@ SELECT * FROM check_test( is_indexed( 'public', 'sometab'::name, 'myint'::name ), false, 'is_indexed( schema, table, column ) fail', - 'An index on public.sometab on column myint should exist', + 'Should have an index on public.sometab(myint)', '' ); @@ -737,7 +737,49 @@ SELECT * FROM check_test( is_indexed( 'public', 'sometab', ARRAY['name','numb']::name[] ), false, 'is_indexed( schema, table, columns[] ) fail, column order matters', - 'An index on public.sometab with {name,numb} should exist', + 'Should have an index on public.sometab(name, numb)', + '' +); + + +-- Make sure index expressions are supported. +SELECT * FROM check_test( + is_indexed( + 'public', 'sometab', + ARRAY['upper(name)', 'numb', 'lower(name)'], + 'whatever' + ), + true, + 'is_indexed(schema, table, expressions)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_indexed( 'public', 'sometab', 'lower(name)', 'whatever' ), + true, + 'is_indexed(schema, table, expression)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_indexed( + 'sometab', + ARRAY['upper(name)', 'numb', 'lower(name)'], + 'whatever' + ), + true, + 'is_indexed(table, expressions)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_indexed( 'sometab', 'lower(name)'::name, 'whatever' ), + true, + 'is_indexed( table, expression)', + 'whatever', '' ); From 9375febfb321ca4ec1226b2fd02c992604be5d14 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Sun, 9 Oct 2016 17:03:54 -0500 Subject: [PATCH 0920/1195] Force C collation for finding functions --- sql/pgtap.sql.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 4f25f016882d..223478af4648 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -6040,7 +6040,7 @@ $$ 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) AS pname + SELECT DISTINCT (quote_ident(n.nspname) || '.' || quote_ident(p.proname)) COLLATION "C" AS pname FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = $1 @@ -6057,7 +6057,7 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION findfuncs( TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( - SELECT DISTINCT quote_ident(n.nspname) || '.' || quote_ident(p.proname) AS pname + SELECT DISTINCT (quote_ident(n.nspname) || '.' || quote_ident(p.proname)) COLLATION "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) From 069f3c096de9cc6308ce4aa8e9fe68d4441b2bdf Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Tue, 11 Oct 2016 00:15:27 -0500 Subject: [PATCH 0921/1195] Fix syntax --- sql/pgtap.sql.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 223478af4648..269d78929cd2 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -6040,7 +6040,7 @@ $$ 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)) COLLATION "C" AS pname + 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 @@ -6057,7 +6057,7 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION findfuncs( TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( - SELECT DISTINCT (quote_ident(n.nspname) || '.' || quote_ident(p.proname)) COLLATION "C" AS pname + 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) From f68a77f670baba61c352ebed2be58dd3e0fd335a Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Sat, 15 Oct 2016 19:18:45 -0500 Subject: [PATCH 0922/1195] Update compatibility patch --- compat/install-9.0.patch | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/compat/install-9.0.patch b/compat/install-9.0.patch index a6f9222616ab..87f4d997d51d 100644 --- a/compat/install-9.0.patch +++ b/compat/install-9.0.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -3617,7 +3617,7 @@ +@@ -3697,7 +3697,7 @@ AND n.nspname = $1 AND t.typname = $2 AND t.typtype = 'e' @@ -9,7 +9,7 @@ ), $3, $4 -@@ -3645,7 +3645,7 @@ +@@ -3725,7 +3725,7 @@ AND pg_catalog.pg_type_is_visible(t.oid) AND t.typname = $1 AND t.typtype = 'e' @@ -18,7 +18,25 @@ ), $2, $3 -@@ -9459,54 +9459,3 @@ +@@ -6031,7 +6031,7 @@ + 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 ++ SELECT DISTINCT (quote_ident(n.nspname) || '.' || quote_ident(p.proname)) AS pname + FROM pg_catalog.pg_proc p + JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid + WHERE n.nspname = $1 +@@ -6048,7 +6048,7 @@ + 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 ++ SELECT DISTINCT (quote_ident(n.nspname) || '.' || quote_ident(p.proname)) 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) +@@ -9539,54 +9539,3 @@ GRANT SELECT ON tap_funky TO PUBLIC; GRANT SELECT ON pg_all_foreign_keys TO PUBLIC; From ea3572573646196faa90c5b3cf0997f69fb4a7f9 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Mon, 17 Oct 2016 20:31:32 -0500 Subject: [PATCH 0923/1195] Properly handle plpgsql ASSERT; lives_ok() and throws_ok() need to explicitly trap ASSERT_FAILURE in an EXCEPTION block, because it's not included as part of OTHERS. --- Makefile | 3 ++ compat/install-9.4.patch | 20 +++++++ doc/pgtap.mmd | 7 ++- sql/pgtap.sql.in | 4 +- test/expected/throwtap.out | 15 +++++- test/sql/throwtap.sql | 103 ++++++++++++++++++++++++++++++++++++- 6 files changed, 147 insertions(+), 5 deletions(-) create mode 100644 compat/install-9.4.patch diff --git a/Makefile b/Makefile index 34267dbf53cc..dcfaf177dd20 100644 --- a/Makefile +++ b/Makefile @@ -97,6 +97,9 @@ endif sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql cp $< $@ +ifeq ($(shell echo $(VERSION) | grep -qE "9[.][01234]|8[.][1234]" && echo yes || echo no),yes) + patch -p0 < compat/install-9.4.patch +endif ifeq ($(shell echo $(VERSION) | grep -qE "9[.][012]|8[.][1234]" && echo yes || echo no),yes) patch -p0 < compat/install-9.2.patch endif diff --git a/compat/install-9.4.patch b/compat/install-9.4.patch new file mode 100644 index 000000000000..31a89c7072dc --- /dev/null +++ b/compat/install-9.4.patch @@ -0,0 +1,20 @@ +--- sql/pgtap.sql ++++ sql/pgtap.sql +@@ -675,7 +675,7 @@ + ' caught: no exception' || + E'\n wanted: ' || COALESCE( errcode, 'an exception' ) + ); +-EXCEPTION WHEN OTHERS OR ASSERT_FAILURE THEN ++EXCEPTION WHEN OTHERS THEN + IF (errcode IS NULL OR SQLSTATE = errcode) + AND ( errmsg IS NULL OR SQLERRM = errmsg) + THEN +@@ -779,7 +779,7 @@ + BEGIN + EXECUTE code; + RETURN ok( TRUE, descr ); +-EXCEPTION WHEN OTHERS OR ASSERT_FAILURE THEN ++EXCEPTION WHEN OTHERS THEN + -- There should have been no exception. + GET STACKED DIAGNOSTICS + detail = PG_EXCEPTION_DETAIL, diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 79b467362e54..00f290455add 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7634,11 +7634,16 @@ instead: To see the specifics for each version of PostgreSQL, consult the files in the `compat/` directory in the pgTAP distribution. -9.3 and Up +9.5 and Up ---------- No changes. Everything should just work. +9.4 and Down +------------ +* lives_ok() and throws_ok() will not trap ASSERT_FAILURE, since asserts do not + exist prior to 9.5. + 9.2 and Down ------------ * Diagnostic output from `lives_ok()` and xUnit function exceptions will not diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 4f25f016882d..01ca2b5f17db 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -675,7 +675,7 @@ BEGIN ' caught: no exception' || E'\n wanted: ' || COALESCE( errcode, 'an exception' ) ); -EXCEPTION WHEN OTHERS THEN +EXCEPTION WHEN OTHERS OR ASSERT_FAILURE THEN IF (errcode IS NULL OR SQLSTATE = errcode) AND ( errmsg IS NULL OR SQLERRM = errmsg) THEN @@ -779,7 +779,7 @@ DECLARE BEGIN EXECUTE code; RETURN ok( TRUE, descr ); -EXCEPTION WHEN OTHERS THEN +EXCEPTION WHEN OTHERS OR ASSERT_FAILURE THEN -- There should have been no exception. GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, diff --git a/test/expected/throwtap.out b/test/expected/throwtap.out index 67a4933a637f..456a8cbe5b2b 100644 --- a/test/expected/throwtap.out +++ b/test/expected/throwtap.out @@ -1,5 +1,5 @@ \unset ECHO -1..84 +1..97 ok 1 - four-argument form should pass ok 2 - four-argument form should have the proper description ok 3 - four-argument form should have the proper diagnostics @@ -84,3 +84,16 @@ ok 81 - throws_imatching(sql, regex, desc) should have the proper diagnostics ok 82 - throws_imatching(valid sql, regex, desc) should fail ok 83 - throws_imatching(valid sql, regex, desc) should have the proper description ok 84 - throws_imatching(valid sql, regex, desc) should have the proper diagnostics +ok 85 - Create check_assert function +ok 86 - throws_ok catches assert should pass +ok 87 - throws_ok catches assert should have the proper description +ok 88 - throws_ok catches assert should have the proper diagnostics +ok 89 - throws_ok does not accept passing assert should fail +ok 90 - throws_ok does not accept passing assert should have the proper description +ok 91 - throws_ok does not accept passing assert should have the proper diagnostics +ok 92 - lives_ok calling check_assert(true) should pass +ok 93 - lives_ok calling check_assert(true) should have the proper description +ok 94 - lives_ok calling check_assert(true) should have the proper diagnostics +ok 95 - lives_ok with check_assert(false) should fail +ok 96 - lives_ok with check_assert(false) should have the proper description +ok 97 - lives_ok with check_assert(false) should have the proper diagnostics diff --git a/test/sql/throwtap.sql b/test/sql/throwtap.sql index ba6553b08416..5ccb0beb1920 100644 --- a/test/sql/throwtap.sql +++ b/test/sql/throwtap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(84); +SELECT plan(97); --SELECT * FROM no_plan(); /****************************************************************************/ @@ -300,6 +300,107 @@ SELECT * FROM check_test( ' no exception thrown' ); +/****************************************************************************/ +-- Test ASSERTs +SELECT lives_ok( + CASE WHEN pg_version_num() < 90500 THEN $exec$ +CREATE FUNCTION check_assert(b boolean) RETURNS void LANGUAGE plpgsql AS $body$ +BEGIN + RAISE 'this code should never be called!'; +END +$body$; +$exec$ + ELSE $exec$ +CREATE FUNCTION check_assert(b boolean) RETURNS void LANGUAGE plpgsql AS $body$ +BEGIN + ASSERT b IS TRUE, 'assert description'; +END +$body$; +$exec$ + END + , 'Create check_assert function' +); + +CREATE FUNCTION test_assert() RETURNS SETOF text LANGUAGE plpgsql AS $body$ +BEGIN +IF pg_version_num() >= 90500 THEN +RETURN QUERY SELECT * FROM check_test( + throws_ok( 'SELECT check_assert(false)', 'P0004', 'assert description' ), + true, + 'throws_ok catches assert', + 'threw P0004: assert description', + '' +); + +RETURN QUERY SELECT * FROM check_test( + throws_ok( 'SELECT check_assert(true)', 'P0004' ), + false, + 'throws_ok does not accept passing assert', + 'threw P0004', + ' caught: no exception + wanted: P0004' +); + +RETURN QUERY SELECT * FROM check_test( + lives_ok( 'SELECT check_assert(true)' ), + true, + 'lives_ok calling check_assert(true)', + '', + '' +); + +-- Check its diagnostics when there is an exception. +RETURN QUERY SELECT * FROM check_test( + lives_ok( 'SELECT check_assert(false)' ), + false, + 'lives_ok with check_assert(false)', + '', + ' died: P0004: assert description + CONTEXT: + PL/pgSQL function check_assert(boolean) line 3 at ASSERT + SQL statement "SELECT check_assert(false)" + PL/pgSQL function lives_ok(text,text) line 14 at EXECUTE + PL/pgSQL function test_assert() line 30 at RETURN QUERY' +); +ELSE +RETURN QUERY SELECT * FROM check_test( + pass(''), + true, + 'throws_ok catches assert', + '', + '' +); + +RETURN QUERY SELECT * FROM check_test( + fail(''), + false, + 'throws_ok does not accept passing assert', + '', + '' +); + +RETURN QUERY SELECT * FROM check_test( + pass(''), + true, + 'lives_ok calling check_assert(true)', + '', + '' +); + +-- Check its diagnostics when there is an exception. +RETURN QUERY SELECT * FROM check_test( + fail(''), + false, + 'lives_ok with check_assert(false)', + '', + '' +); +END IF; +END +$body$; + +SELECT test_assert(); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 526dbbe584d3bce517a8a00a01d4b60e766d584d Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Wed, 19 Oct 2016 18:05:47 -0500 Subject: [PATCH 0924/1195] Fix typo --- sql/pgtap.sql.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 3c7e9a3d4ee2..e3bd63a51577 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -6040,7 +6040,7 @@ $$ 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)) COLLATION "C" AS pname + 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 @@ -6057,7 +6057,7 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION findfuncs( TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( - SELECT DISTINCT (quote_ident(n.nspname) || '.' || quote_ident(p.proname)) COLLATION "C" AS pname + 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) From 04d18d32735a813d46a3b162ca43daf7ff26789e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Qui=C3=A9deville?= Date: Thu, 30 Jun 2016 16:29:20 +0200 Subject: [PATCH 0925/1195] Add functions isnt_definer(), isnt_aggregate() and related tests --- sql/pgtap--0.96.0--0.97.0.sql | 120 ++++++ sql/pgtap.sql.in | 118 ++++++ test/expected/functap.out | 664 ++++++++++++++++++++-------------- test/sql/functap.sql | 294 ++++++++++++++- 4 files changed, 915 insertions(+), 281 deletions(-) diff --git a/sql/pgtap--0.96.0--0.97.0.sql b/sql/pgtap--0.96.0--0.97.0.sql index 028ead57cbc9..4c6a1b797e22 100644 --- a/sql/pgtap--0.96.0--0.97.0.sql +++ b/sql/pgtap--0.96.0--0.97.0.sql @@ -78,6 +78,7 @@ RETURNS TEXT AS $$ SELECT ok ( _is_indexed( $1, ARRAY[$2]::NAME[]) ); $$ LANGUAGE sql; +-- pg_version_num() CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT substring(s.a[1] FROM '[[:digit:]]+')::int * 10000 @@ -87,3 +88,122 @@ RETURNS integer AS $$ SELECT string_to_array(current_setting('server_version'), '.') AS a ) AS s; $$ LANGUAGE SQL IMMUTABLE; + +-- 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 be not 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 be not 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 be not 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 be not security definer' ); +$$ 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 _agg($1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- isnt_aggregate( schema, function, args[] ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _agg($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 _agg($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_aggregate( schema, function ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _agg($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 _agg($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_aggregate( function, args[] ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _agg($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 _agg($1), $2 ); +$$ LANGUAGE sql; + +-- isnt_aggregate( function ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _agg($1), 'Function ' || quote_ident($1) || '() should not be an aggregate function' ); +$$ LANGUAGE sql; + diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 190c48e43f4d..52b3e0d9622b 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -5574,6 +5574,65 @@ 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; + CREATE OR REPLACE FUNCTION _agg ( NAME, NAME, NAME[] ) RETURNS BOOLEAN AS $$ SELECT is_agg @@ -5661,6 +5720,65 @@ RETURNS TEXT AS $$ SELECT ok( _agg($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 _agg($1, $2, $3), $4 ); +$$ LANGUAGE SQL; + +-- isnt_aggregate( schema, function, args[] ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _agg($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 _agg($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_aggregate( schema, function ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _agg($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 _agg($1, $2), $3 ); +$$ LANGUAGE SQL; + +-- isnt_aggregate( function, args[] ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, NAME[] ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _agg($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 _agg($1), $2 ); +$$ LANGUAGE sql; + +-- isnt_aggregate( function ) +CREATE OR REPLACE FUNCTION isnt_aggregate( NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _agg($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 diff --git a/test/expected/functap.out b/test/expected/functap.out index 7f024018d11d..2b6704c2ef59 100644 --- a/test/expected/functap.out +++ b/test/expected/functap.out @@ -1,5 +1,5 @@ \unset ECHO -1..520 +1..628 ok 1 - simple function should pass ok 2 - simple function should have the proper description ok 3 - simple function should have the proper diagnostics @@ -243,280 +243,388 @@ ok 240 - function_returns(func, setof bool) should have the proper diagnostics ok 241 - is_definer(schema, func, 0 args, desc) should pass ok 242 - is_definer(schema, func, 0 args, desc) should have the proper description ok 243 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics -ok 244 - is_definer(schema, func, 0 args) should pass -ok 245 - is_definer(schema, func, 0 args) should have the proper description -ok 246 - is_definer(schema, func, 0 args) should have the proper diagnostics -ok 247 - is_definer(schema, func, args, desc) should fail -ok 248 - is_definer(schema, func, args, desc) should have the proper description -ok 249 - is_definer(schema, func, args, desc) should have the proper diagnostics -ok 250 - is_definer(schema, func, args) should fail -ok 251 - is_definer(schema, func, args) should have the proper description -ok 252 - is_definer(schema, func, args) should have the proper diagnostics -ok 253 - is_definer(schema, func, desc) should pass -ok 254 - is_definer(schema, func, desc) should have the proper description -ok 255 - is_definer(schema, func, desc) should have the proper diagnostics -ok 256 - is_definer(schema, func) should pass -ok 257 - is_definer(schema, func) should have the proper description -ok 258 - is_definer(schema, func) should have the proper diagnostics -ok 259 - is_definer(schema, func, 0 args, desc) should pass -ok 260 - is_definer(schema, func, 0 args, desc) should have the proper description -ok 261 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics -ok 262 - is_definer(schema, func, 0 args) should pass -ok 263 - is_definer(schema, func, 0 args) should have the proper description -ok 264 - is_definer(schema, func, 0 args) should have the proper diagnostics -ok 265 - is_definer(schema, func, args, desc) should fail -ok 266 - is_definer(schema, func, args, desc) should have the proper description -ok 267 - is_definer(schema, func, args, desc) should have the proper diagnostics -ok 268 - is_definer(schema, func, args) should fail -ok 269 - is_definer(schema, func, args) should have the proper description -ok 270 - is_definer(schema, func, args) should have the proper diagnostics -ok 271 - is_definer(schema, func, desc) should pass -ok 272 - is_definer(schema, func, desc) should have the proper description -ok 273 - is_definer(schema, func, desc) should have the proper diagnostics -ok 274 - is_definer(schema, func) should pass -ok 275 - is_definer(schema, func) should have the proper description -ok 276 - is_definer(schema, func) should have the proper diagnostics -ok 277 - is_definer(func, 0 args, desc) should pass -ok 278 - is_definer(func, 0 args, desc) should have the proper description -ok 279 - is_definer(func, 0 args, desc) should have the proper diagnostics -ok 280 - is_definer(func, 0 args) should pass -ok 281 - is_definer(func, 0 args) should have the proper description -ok 282 - is_definer(func, 0 args) should have the proper diagnostics -ok 283 - is_definer(func, args, desc) should fail -ok 284 - is_definer(func, args, desc) should have the proper description -ok 285 - is_definer(func, args, desc) should have the proper diagnostics -ok 286 - is_definer(func, args) should fail -ok 287 - is_definer(func, args) should have the proper description -ok 288 - is_definer(func, args) should have the proper diagnostics -ok 289 - is_definer(func, desc) should pass -ok 290 - is_definer(func, desc) should have the proper description -ok 291 - is_definer(func, desc) should have the proper diagnostics -ok 292 - is_definer(func) should pass -ok 293 - is_definer(func) should have the proper description -ok 294 - is_definer(func) should have the proper diagnostics -ok 295 - is_aggregate(schema, func, arg, desc) should pass -ok 296 - is_aggregate(schema, func, arg, desc) should have the proper description -ok 297 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics -ok 298 - is_aggregate(schema, func, arg) should pass -ok 299 - is_aggregate(schema, func, arg) should have the proper description -ok 300 - is_aggregate(schema, func, arg) should have the proper diagnostics -ok 301 - is_aggregate(schema, func, args, desc) should fail -ok 302 - is_aggregate(schema, func, args, desc) should have the proper description -ok 303 - is_aggregate(schema, func, args, desc) should have the proper diagnostics -ok 304 - is_aggregate(schema, func, args) should fail -ok 305 - is_aggregate(schema, func, args) should have the proper description -ok 306 - is_aggregate(schema, func, args) should have the proper diagnostics -ok 307 - is_aggregate(schema, func, desc) should pass -ok 308 - is_aggregate(schema, func, desc) should have the proper description -ok 309 - is_aggregate(schema, func, desc) should have the proper diagnostics -ok 310 - is_aggregate(schema, func) should pass -ok 311 - is_aggregate(schema, func) should have the proper description -ok 312 - is_aggregate(schema, func) should have the proper diagnostics -ok 313 - is_aggregate(schema, func, arg, desc) should pass -ok 314 - is_aggregate(schema, func, arg, desc) should have the proper description -ok 315 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics -ok 316 - is_aggregate(schema, func, arg) should pass -ok 317 - is_aggregate(schema, func, arg) should have the proper description -ok 318 - is_aggregate(schema, func, arg) should have the proper diagnostics -ok 319 - is_aggregate(schema, func, args, desc) should fail -ok 320 - is_aggregate(schema, func, args, desc) should have the proper description -ok 321 - is_aggregate(schema, func, args, desc) should have the proper diagnostics -ok 322 - is_aggregate(schema, func, args) should fail -ok 323 - is_aggregate(schema, func, args) should have the proper description -ok 324 - is_aggregate(schema, func, args) should have the proper diagnostics -ok 325 - is_aggregate(schema, func, desc) should pass -ok 326 - is_aggregate(schema, func, desc) should have the proper description -ok 327 - is_aggregate(schema, func, desc) should have the proper diagnostics -ok 328 - is_aggregate(schema, func) should pass -ok 329 - is_aggregate(schema, func) should have the proper description -ok 330 - is_aggregate(schema, func) should have the proper diagnostics -ok 331 - is_aggregate(func, arg, desc) should pass -ok 332 - is_aggregate(func, arg, desc) should have the proper description -ok 333 - is_aggregate(func, arg, desc) should have the proper diagnostics -ok 334 - is_aggregate(func, arg) should pass -ok 335 - is_aggregate(func, arg) should have the proper description -ok 336 - is_aggregate(func, arg) should have the proper diagnostics -ok 337 - is_aggregate(func, args, desc) should fail -ok 338 - is_aggregate(func, args, desc) should have the proper description -ok 339 - is_aggregate(func, args, desc) should have the proper diagnostics -ok 340 - is_aggregate(func, args) should fail -ok 341 - is_aggregate(func, args) should have the proper description -ok 342 - is_aggregate(func, args) should have the proper diagnostics -ok 343 - is_aggregate(func, desc) should pass -ok 344 - is_aggregate(func, desc) should have the proper description -ok 345 - is_aggregate(func, desc) should have the proper diagnostics -ok 346 - is_aggregate(func) should pass -ok 347 - is_aggregate(func) should have the proper description -ok 348 - is_aggregate(func) should have the proper diagnostics -ok 349 - is_strict(schema, func, 0 args, desc) should pass -ok 350 - is_strict(schema, func, 0 args, desc) should have the proper description -ok 351 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 352 - isnt_strict(schema, func, 0 args, desc) should fail -ok 353 - isnt_strict(schema, func, 0 args, desc) should have the proper description -ok 354 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 355 - is_strict(schema, func, 0 args) should pass -ok 356 - is_strict(schema, func, 0 args) should have the proper description -ok 357 - is_strict(schema, func, 0 args) should have the proper diagnostics -ok 358 - isnt_strict(schema, func, 0 args) should fail -ok 359 - isnt_strict(schema, func, 0 args) should have the proper description -ok 360 - isnt_strict(schema, func, 0 args) should have the proper diagnostics -ok 361 - is_strict(schema, func, args, desc) should fail -ok 362 - is_strict(schema, func, args, desc) should have the proper description -ok 363 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 364 - is_strict(schema, func, args, desc) should pass -ok 365 - is_strict(schema, func, args, desc) should have the proper description -ok 366 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 367 - is_strict(schema, func, args) should fail -ok 368 - is_strict(schema, func, args) should have the proper description -ok 369 - is_strict(schema, func, args) should have the proper diagnostics -ok 370 - is_strict(schema, func, args) should pass -ok 371 - is_strict(schema, func, args) should have the proper description -ok 372 - is_strict(schema, func, args) should have the proper diagnostics -ok 373 - is_strict(schema, func, desc) should pass -ok 374 - is_strict(schema, func, desc) should have the proper description -ok 375 - is_strict(schema, func, desc) should have the proper diagnostics -ok 376 - isnt_strict(schema, func, desc) should fail -ok 377 - isnt_strict(schema, func, desc) should have the proper description -ok 378 - isnt_strict(schema, func, desc) should have the proper diagnostics -ok 379 - is_strict(schema, func) should pass -ok 380 - is_strict(schema, func) should have the proper description -ok 381 - is_strict(schema, func) should have the proper diagnostics -ok 382 - isnt_strict(schema, func) should fail -ok 383 - isnt_strict(schema, func) should have the proper description -ok 384 - isnt_strict(schema, func) should have the proper diagnostics -ok 385 - is_strict(schema, func, 0 args, desc) should pass -ok 386 - is_strict(schema, func, 0 args, desc) should have the proper description -ok 387 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 388 - isnt_strict(schema, func, 0 args, desc) should fail -ok 389 - isnt_strict(schema, func, 0 args, desc) should have the proper description -ok 390 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 391 - is_strict(schema, func, 0 args) should pass -ok 392 - is_strict(schema, func, 0 args) should have the proper description -ok 393 - is_strict(schema, func, 0 args) should have the proper diagnostics -ok 394 - isnt_strict(schema, func, 0 args) should fail -ok 395 - isnt_strict(schema, func, 0 args) should have the proper description -ok 396 - isnt_strict(schema, func, 0 args) should have the proper diagnostics -ok 397 - is_strict(schema, func, args, desc) should fail -ok 398 - is_strict(schema, func, args, desc) should have the proper description -ok 399 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 400 - isnt_strict(schema, func, args, desc) should pass -ok 401 - isnt_strict(schema, func, args, desc) should have the proper description -ok 402 - isnt_strict(schema, func, args, desc) should have the proper diagnostics -ok 403 - is_strict(schema, func, args) should fail -ok 404 - is_strict(schema, func, args) should have the proper description -ok 405 - is_strict(schema, func, args) should have the proper diagnostics -ok 406 - isnt_strict(schema, func, args) should pass -ok 407 - isnt_strict(schema, func, args) should have the proper description -ok 408 - isnt_strict(schema, func, args) should have the proper diagnostics -ok 409 - is_strict(schema, func, desc) should pass -ok 410 - is_strict(schema, func, desc) should have the proper description -ok 411 - is_strict(schema, func, desc) should have the proper diagnostics -ok 412 - isnt_strict(schema, func, desc) should fail -ok 413 - isnt_strict(schema, func, desc) should have the proper description -ok 414 - isnt_strict(schema, func, desc) should have the proper diagnostics -ok 415 - is_strict(schema, func) should pass -ok 416 - is_strict(schema, func) should have the proper description -ok 417 - is_strict(schema, func) should have the proper diagnostics -ok 418 - isnt_strict(schema, func) should fail -ok 419 - isnt_strict(schema, func) should have the proper description -ok 420 - isnt_strict(schema, func) should have the proper diagnostics -ok 421 - is_strict(func, 0 args, desc) should pass -ok 422 - is_strict(func, 0 args, desc) should have the proper description -ok 423 - is_strict(func, 0 args, desc) should have the proper diagnostics -ok 424 - isnt_strict(func, 0 args, desc) should fail -ok 425 - isnt_strict(func, 0 args, desc) should have the proper description -ok 426 - isnt_strict(func, 0 args, desc) should have the proper diagnostics -ok 427 - is_strict(func, 0 args) should pass -ok 428 - is_strict(func, 0 args) should have the proper description -ok 429 - is_strict(func, 0 args) should have the proper diagnostics -ok 430 - isnt_strict(func, 0 args) should fail -ok 431 - isnt_strict(func, 0 args) should have the proper description -ok 432 - isnt_strict(func, 0 args) should have the proper diagnostics -ok 433 - is_strict(func, args, desc) should fail -ok 434 - is_strict(func, args, desc) should have the proper description -ok 435 - is_strict(func, args, desc) should have the proper diagnostics -ok 436 - isnt_strict(func, args, desc) should pass -ok 437 - isnt_strict(func, args, desc) should have the proper description -ok 438 - isnt_strict(func, args, desc) should have the proper diagnostics -ok 439 - is_strict(func, args) should fail -ok 440 - is_strict(func, args) should have the proper description -ok 441 - is_strict(func, args) should have the proper diagnostics -ok 442 - isnt_strict(func, args) should pass -ok 443 - isnt_strict(func, args) should have the proper description -ok 444 - isnt_strict(func, args) should have the proper diagnostics -ok 445 - is_strict(func, desc) should pass -ok 446 - is_strict(func, desc) should have the proper description -ok 447 - is_strict(func, desc) should have the proper diagnostics -ok 448 - isnt_strict(func, desc) should fail -ok 449 - isnt_strict(func, desc) should have the proper description -ok 450 - isnt_strict(func, desc) should have the proper diagnostics -ok 451 - is_strict(func) should pass -ok 452 - is_strict(func) should have the proper description -ok 453 - is_strict(func) should have the proper diagnostics -ok 454 - isnt_strict(func) should fail -ok 455 - isnt_strict(func) should have the proper description -ok 456 - isnt_strict(func) should have the proper diagnostics -ok 457 - function_volatility(schema, func, 0 args, volatile, desc) should pass -ok 458 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper description -ok 459 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper diagnostics -ok 460 - function_volatility(schema, func, 0 args, VOLATILE, desc) should pass -ok 461 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper description -ok 462 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper diagnostics -ok 463 - function_volatility(schema, func, 0 args, v, desc) should pass -ok 464 - function_volatility(schema, func, 0 args, v, desc) should have the proper description -ok 465 - function_volatility(schema, func, 0 args, v, desc) should have the proper diagnostics -ok 466 - function_volatility(schema, func, args, immutable, desc) should pass -ok 467 - function_volatility(schema, func, args, immutable, desc) should have the proper description -ok 468 - function_volatility(schema, func, args, immutable, desc) should have the proper diagnostics -ok 469 - function_volatility(schema, func, 0 args, stable, desc) should pass -ok 470 - function_volatility(schema, func, 0 args, stable, desc) should have the proper description -ok 471 - function_volatility(schema, func, 0 args, stable, desc) should have the proper diagnostics -ok 472 - function_volatility(schema, func, 0 args, volatile) should pass -ok 473 - function_volatility(schema, func, 0 args, volatile) should have the proper description -ok 474 - function_volatility(schema, func, 0 args, volatile) should have the proper diagnostics -ok 475 - function_volatility(schema, func, args, immutable) should pass -ok 476 - function_volatility(schema, func, args, immutable) should have the proper description -ok 477 - function_volatility(schema, func, volatile, desc) should pass -ok 478 - function_volatility(schema, func, volatile, desc) should have the proper description -ok 479 - function_volatility(schema, func, volatile, desc) should have the proper diagnostics -ok 480 - function_volatility(schema, func, volatile) should pass -ok 481 - function_volatility(schema, func, volatile) should have the proper description -ok 482 - function_volatility(schema, func, volatile) should have the proper diagnostics -ok 483 - function_volatility(schema, func, immutable, desc) should pass -ok 484 - function_volatility(schema, func, immutable, desc) should have the proper description -ok 485 - function_volatility(schema, func, immutable, desc) should have the proper diagnostics -ok 486 - function_volatility(schema, func, stable, desc) should pass -ok 487 - function_volatility(schema, func, stable, desc) should have the proper description -ok 488 - function_volatility(schema, func, stable, desc) should have the proper diagnostics -ok 489 - function_volatility(func, 0 args, volatile, desc) should pass -ok 490 - function_volatility(func, 0 args, volatile, desc) should have the proper description -ok 491 - function_volatility(func, 0 args, volatile, desc) should have the proper diagnostics -ok 492 - function_volatility(func, 0 args, VOLATILE, desc) should pass -ok 493 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper description -ok 494 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper diagnostics -ok 495 - function_volatility(func, 0 args, v, desc) should pass -ok 496 - function_volatility(func, 0 args, v, desc) should have the proper description -ok 497 - function_volatility(func, 0 args, v, desc) should have the proper diagnostics -ok 498 - function_volatility(func, args, immutable, desc) should pass -ok 499 - function_volatility(func, args, immutable, desc) should have the proper description -ok 500 - function_volatility(func, args, immutable, desc) should have the proper diagnostics -ok 501 - function_volatility(func, 0 args, stable, desc) should pass -ok 502 - function_volatility(func, 0 args, stable, desc) should have the proper description -ok 503 - function_volatility(func, 0 args, stable, desc) should have the proper diagnostics -ok 504 - function_volatility(func, 0 args, volatile) should pass -ok 505 - function_volatility(func, 0 args, volatile) should have the proper description -ok 506 - function_volatility(func, 0 args, volatile) should have the proper diagnostics -ok 507 - function_volatility(func, args, immutable) should pass -ok 508 - function_volatility(func, args, immutable) should have the proper description -ok 509 - function_volatility(func, volatile, desc) should pass -ok 510 - function_volatility(func, volatile, desc) should have the proper description -ok 511 - function_volatility(func, volatile, desc) should have the proper diagnostics -ok 512 - function_volatility(func, volatile) should pass -ok 513 - function_volatility(func, volatile) should have the proper description -ok 514 - function_volatility(func, volatile) should have the proper diagnostics -ok 515 - function_volatility(func, immutable, desc) should pass -ok 516 - function_volatility(func, immutable, desc) should have the proper description -ok 517 - function_volatility(func, immutable, desc) should have the proper diagnostics -ok 518 - function_volatility(func, stable, desc) should pass -ok 519 - function_volatility(func, stable, desc) should have the proper description -ok 520 - function_volatility(func, stable, desc) should have the proper diagnostics +ok 244 - isnt_definer(schema, func, 0 args, desc) should fail +ok 245 - isnt_definer(schema, func, 0 args, desc) should have the proper description +ok 246 - isnt_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 247 - is_definer(schema, func, 0 args) should pass +ok 248 - is_definer(schema, func, 0 args) should have the proper description +ok 249 - is_definer(schema, func, 0 args) should have the proper diagnostics +ok 250 - isnt_definer(schema, func, 0 args) should fail +ok 251 - isnt_definer(schema, func, 0 args) should have the proper description +ok 252 - isnt_definer(schema, func, 0 args) should have the proper diagnostics +ok 253 - is_definer(schema, func, args, desc) should fail +ok 254 - is_definer(schema, func, args, desc) should have the proper description +ok 255 - is_definer(schema, func, args, desc) should have the proper diagnostics +ok 256 - isnt_definer(schema, func, args, desc) should pass +ok 257 - isnt_definer(schema, func, args, desc) should have the proper description +ok 258 - isnt_definer(schema, func, args, desc) should have the proper diagnostics +ok 259 - is_definer(schema, func, args) should fail +ok 260 - is_definer(schema, func, args) should have the proper description +ok 261 - is_definer(schema, func, args) should have the proper diagnostics +ok 262 - isnt_definer(schema, func, args) should pass +ok 263 - isnt_definer(schema, func, args) should have the proper description +ok 264 - isnt_definer(schema, func, args) should have the proper diagnostics +ok 265 - is_definer(schema, func, desc) should pass +ok 266 - is_definer(schema, func, desc) should have the proper description +ok 267 - is_definer(schema, func, desc) should have the proper diagnostics +ok 268 - isnt_definer(schema, func, desc) should fail +ok 269 - isnt_definer(schema, func, desc) should have the proper description +ok 270 - isnt_definer(schema, func, desc) should have the proper diagnostics +ok 271 - is_definer(schema, func) should pass +ok 272 - is_definer(schema, func) should have the proper description +ok 273 - is_definer(schema, func) should have the proper diagnostics +ok 274 - isnt_definer(schema, func) should fail +ok 275 - isnt_definer(schema, func) should have the proper description +ok 276 - isnt_definer(schema, func) should have the proper diagnostics +ok 277 - is_definer(schema, func, 0 args, desc) should pass +ok 278 - is_definer(schema, func, 0 args, desc) should have the proper description +ok 279 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 280 - isnt_definer(schema, func, 0 args, desc) should fail +ok 281 - isnt_definer(schema, func, 0 args, desc) should have the proper description +ok 282 - isnt_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 283 - is_definer(schema, func, 0 args) should pass +ok 284 - is_definer(schema, func, 0 args) should have the proper description +ok 285 - is_definer(schema, func, 0 args) should have the proper diagnostics +ok 286 - isnt_definer(schema, func, 0 args) should fail +ok 287 - isnt_definer(schema, func, 0 args) should have the proper description +ok 288 - isnt_definer(schema, func, 0 args) should have the proper diagnostics +ok 289 - is_definer(schema, func, args, desc) should fail +ok 290 - is_definer(schema, func, args, desc) should have the proper description +ok 291 - is_definer(schema, func, args, desc) should have the proper diagnostics +ok 292 - isnt_definer(schema, func, args, desc) should pass +ok 293 - isnt_definer(schema, func, args, desc) should have the proper description +ok 294 - isnt_definer(schema, func, args, desc) should have the proper diagnostics +ok 295 - is_definer(schema, func, args) should fail +ok 296 - is_definer(schema, func, args) should have the proper description +ok 297 - is_definer(schema, func, args) should have the proper diagnostics +ok 298 - isnt_definer(schema, func, args) should pass +ok 299 - isnt_definer(schema, func, args) should have the proper description +ok 300 - isnt_definer(schema, func, args) should have the proper diagnostics +ok 301 - is_definer(schema, func, desc) should pass +ok 302 - is_definer(schema, func, desc) should have the proper description +ok 303 - is_definer(schema, func, desc) should have the proper diagnostics +ok 304 - isnt_definer(schema, func, desc) should fail +ok 305 - isnt_definer(schema, func, desc) should have the proper description +ok 306 - isnt_definer(schema, func, desc) should have the proper diagnostics +ok 307 - is_definer(schema, func) should pass +ok 308 - is_definer(schema, func) should have the proper description +ok 309 - is_definer(schema, func) should have the proper diagnostics +ok 310 - isnt_definer(schema, func) should fail +ok 311 - isnt_definer(schema, func) should have the proper description +ok 312 - isnt_definer(schema, func) should have the proper diagnostics +ok 313 - is_definer(func, 0 args, desc) should pass +ok 314 - is_definer(func, 0 args, desc) should have the proper description +ok 315 - is_definer(func, 0 args, desc) should have the proper diagnostics +ok 316 - isnt_definer(func, 0 args, desc) should fail +ok 317 - isnt_definer(func, 0 args, desc) should have the proper description +ok 318 - isnt_definer(func, 0 args, desc) should have the proper diagnostics +ok 319 - is_definer(func, 0 args) should pass +ok 320 - is_definer(func, 0 args) should have the proper description +ok 321 - is_definer(func, 0 args) should have the proper diagnostics +ok 322 - isnt_definer(func, 0 args) should fail +ok 323 - isnt_definer(func, 0 args) should have the proper description +ok 324 - isnt_definer(func, 0 args) should have the proper diagnostics +ok 325 - is_definer(func, args, desc) should fail +ok 326 - is_definer(func, args, desc) should have the proper description +ok 327 - is_definer(func, args, desc) should have the proper diagnostics +ok 328 - isnt_definer(func, args, desc) should pass +ok 329 - isnt_definer(func, args, desc) should have the proper description +ok 330 - isnt_definer(func, args, desc) should have the proper diagnostics +ok 331 - is_definer(func, args) should fail +ok 332 - is_definer(func, args) should have the proper description +ok 333 - is_definer(func, args) should have the proper diagnostics +ok 334 - isnt_definer(func, args) should pass +ok 335 - isnt_definer(func, args) should have the proper description +ok 336 - isnt_definer(func, args) should have the proper diagnostics +ok 337 - is_definer(func, desc) should pass +ok 338 - is_definer(func, desc) should have the proper description +ok 339 - is_definer(func, desc) should have the proper diagnostics +ok 340 - isnt_definer(func, desc) should fail +ok 341 - isnt_definer(func, desc) should have the proper description +ok 342 - isnt_definer(func, desc) should have the proper diagnostics +ok 343 - is_definer(func) should pass +ok 344 - is_definer(func) should have the proper description +ok 345 - is_definer(func) should have the proper diagnostics +ok 346 - isnt_definer(func) should fail +ok 347 - isnt_definer(func) should have the proper description +ok 348 - isnt_definer(func) should have the proper diagnostics +ok 349 - is_aggregate(schema, func, arg, desc) should pass +ok 350 - is_aggregate(schema, func, arg, desc) should have the proper description +ok 351 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 352 - isnt_aggregate(schema, func, arg, desc) should fail +ok 353 - isnt_aggregate(schema, func, arg, desc) should have the proper description +ok 354 - isnt_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 355 - is_aggregate(schema, func, arg) should pass +ok 356 - is_aggregate(schema, func, arg) should have the proper description +ok 357 - is_aggregate(schema, func, arg) should have the proper diagnostics +ok 358 - isnt_aggregate(schema, func, arg) should fail +ok 359 - isnt_aggregate(schema, func, arg) should have the proper description +ok 360 - isnt_aggregate(schema, func, arg) should have the proper diagnostics +ok 361 - is_aggregate(schema, func, args, desc) should fail +ok 362 - is_aggregate(schema, func, args, desc) should have the proper description +ok 363 - is_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 364 - isnt_aggregate(schema, func, args, desc) should pass +ok 365 - isnt_aggregate(schema, func, args, desc) should have the proper description +ok 366 - isnt_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 367 - is_aggregate(schema, func, args) should fail +ok 368 - is_aggregate(schema, func, args) should have the proper description +ok 369 - is_aggregate(schema, func, args) should have the proper diagnostics +ok 370 - isnt_aggregate(schema, func, args) should pass +ok 371 - isnt_aggregate(schema, func, args) should have the proper description +ok 372 - isnt_aggregate(schema, func, args) should have the proper diagnostics +ok 373 - is_aggregate(schema, func, desc) should pass +ok 374 - is_aggregate(schema, func, desc) should have the proper description +ok 375 - is_aggregate(schema, func, desc) should have the proper diagnostics +ok 376 - isnt_aggregate(schema, func, desc) should fail +ok 377 - isnt_aggregate(schema, func, desc) should have the proper description +ok 378 - isnt_aggregate(schema, func, desc) should have the proper diagnostics +ok 379 - is_aggregate(schema, func) should pass +ok 380 - is_aggregate(schema, func) should have the proper description +ok 381 - is_aggregate(schema, func) should have the proper diagnostics +ok 382 - isnt_aggregate(schema, func) should fail +ok 383 - isnt_aggregate(schema, func) should have the proper description +ok 384 - isnt_aggregate(schema, func) should have the proper diagnostics +ok 385 - is_aggregate(schema, func, arg, desc) should pass +ok 386 - is_aggregate(schema, func, arg, desc) should have the proper description +ok 387 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 388 - isnt_aggregate(schema, func, arg, desc) should fail +ok 389 - isnt_aggregate(schema, func, arg, desc) should have the proper description +ok 390 - isnt_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 391 - is_aggregate(schema, func, arg) should pass +ok 392 - is_aggregate(schema, func, arg) should have the proper description +ok 393 - is_aggregate(schema, func, arg) should have the proper diagnostics +ok 394 - isnt_aggregate(schema, func, arg) should fail +ok 395 - isnt_aggregate(schema, func, arg) should have the proper description +ok 396 - isnt_aggregate(schema, func, arg) should have the proper diagnostics +ok 397 - is_aggregate(schema, func, args, desc) should fail +ok 398 - is_aggregate(schema, func, args, desc) should have the proper description +ok 399 - is_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 400 - isnt_aggregate(schema, func, args, desc) should pass +ok 401 - isnt_aggregate(schema, func, args, desc) should have the proper description +ok 402 - isnt_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 403 - is_aggregate(schema, func, args) should fail +ok 404 - is_aggregate(schema, func, args) should have the proper description +ok 405 - is_aggregate(schema, func, args) should have the proper diagnostics +ok 406 - isnt_aggregate(schema, func, args) should pass +ok 407 - isnt_aggregate(schema, func, args) should have the proper description +ok 408 - isnt_aggregate(schema, func, args) should have the proper diagnostics +ok 409 - is_aggregate(schema, func, desc) should pass +ok 410 - is_aggregate(schema, func, desc) should have the proper description +ok 411 - is_aggregate(schema, func, desc) should have the proper diagnostics +ok 412 - isnt_aggregate(schema, func, desc) should fail +ok 413 - isnt_aggregate(schema, func, desc) should have the proper description +ok 414 - isnt_aggregate(schema, func, desc) should have the proper diagnostics +ok 415 - is_aggregate(schema, func) should pass +ok 416 - is_aggregate(schema, func) should have the proper description +ok 417 - is_aggregate(schema, func) should have the proper diagnostics +ok 418 - isnt_aggregate(schema, func) should fail +ok 419 - isnt_aggregate(schema, func) should have the proper description +ok 420 - isnt_aggregate(schema, func) should have the proper diagnostics +ok 421 - is_aggregate(func, arg, desc) should pass +ok 422 - is_aggregate(func, arg, desc) should have the proper description +ok 423 - is_aggregate(func, arg, desc) should have the proper diagnostics +ok 424 - isnt_aggregate(func, arg, desc) should fail +ok 425 - isnt_aggregate(func, arg, desc) should have the proper description +ok 426 - isnt_aggregate(func, arg, desc) should have the proper diagnostics +ok 427 - is_aggregate(func, arg) should pass +ok 428 - is_aggregate(func, arg) should have the proper description +ok 429 - is_aggregate(func, arg) should have the proper diagnostics +ok 430 - isnt_aggregate(func, arg) should fail +ok 431 - isnt_aggregate(func, arg) should have the proper description +ok 432 - isnt_aggregate(func, arg) should have the proper diagnostics +ok 433 - is_aggregate(func, args, desc) should fail +ok 434 - is_aggregate(func, args, desc) should have the proper description +ok 435 - is_aggregate(func, args, desc) should have the proper diagnostics +ok 436 - isnt_aggregate(func, args, desc) should pass +ok 437 - isnt_aggregate(func, args, desc) should have the proper description +ok 438 - isnt_aggregate(func, args, desc) should have the proper diagnostics +ok 439 - is_aggregate(func, args) should fail +ok 440 - is_aggregate(func, args) should have the proper description +ok 441 - is_aggregate(func, args) should have the proper diagnostics +ok 442 - isnt_aggregate(func, args) should pass +ok 443 - isnt_aggregate(func, args) should have the proper description +ok 444 - isnt_aggregate(func, args) should have the proper diagnostics +ok 445 - is_aggregate(func, desc) should pass +ok 446 - is_aggregate(func, desc) should have the proper description +ok 447 - is_aggregate(func, desc) should have the proper diagnostics +ok 448 - isnt_aggregate(func, desc) should fail +ok 449 - isnt_aggregate(func, desc) should have the proper description +ok 450 - isnt_aggregate(func, desc) should have the proper diagnostics +ok 451 - is_aggregate(func) should pass +ok 452 - is_aggregate(func) should have the proper description +ok 453 - is_aggregate(func) should have the proper diagnostics +ok 454 - isnt_aggregate(func) should fail +ok 455 - isnt_aggregate(func) should have the proper description +ok 456 - isnt_aggregate(func) should have the proper diagnostics +ok 457 - is_strict(schema, func, 0 args, desc) should pass +ok 458 - is_strict(schema, func, 0 args, desc) should have the proper description +ok 459 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 460 - isnt_strict(schema, func, 0 args, desc) should fail +ok 461 - isnt_strict(schema, func, 0 args, desc) should have the proper description +ok 462 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 463 - is_strict(schema, func, 0 args) should pass +ok 464 - is_strict(schema, func, 0 args) should have the proper description +ok 465 - is_strict(schema, func, 0 args) should have the proper diagnostics +ok 466 - isnt_strict(schema, func, 0 args) should fail +ok 467 - isnt_strict(schema, func, 0 args) should have the proper description +ok 468 - isnt_strict(schema, func, 0 args) should have the proper diagnostics +ok 469 - is_strict(schema, func, args, desc) should fail +ok 470 - is_strict(schema, func, args, desc) should have the proper description +ok 471 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 472 - is_strict(schema, func, args, desc) should pass +ok 473 - is_strict(schema, func, args, desc) should have the proper description +ok 474 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 475 - is_strict(schema, func, args) should fail +ok 476 - is_strict(schema, func, args) should have the proper description +ok 477 - is_strict(schema, func, args) should have the proper diagnostics +ok 478 - is_strict(schema, func, args) should pass +ok 479 - is_strict(schema, func, args) should have the proper description +ok 480 - is_strict(schema, func, args) should have the proper diagnostics +ok 481 - is_strict(schema, func, desc) should pass +ok 482 - is_strict(schema, func, desc) should have the proper description +ok 483 - is_strict(schema, func, desc) should have the proper diagnostics +ok 484 - isnt_strict(schema, func, desc) should fail +ok 485 - isnt_strict(schema, func, desc) should have the proper description +ok 486 - isnt_strict(schema, func, desc) should have the proper diagnostics +ok 487 - is_strict(schema, func) should pass +ok 488 - is_strict(schema, func) should have the proper description +ok 489 - is_strict(schema, func) should have the proper diagnostics +ok 490 - isnt_strict(schema, func) should fail +ok 491 - isnt_strict(schema, func) should have the proper description +ok 492 - isnt_strict(schema, func) should have the proper diagnostics +ok 493 - is_strict(schema, func, 0 args, desc) should pass +ok 494 - is_strict(schema, func, 0 args, desc) should have the proper description +ok 495 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 496 - isnt_strict(schema, func, 0 args, desc) should fail +ok 497 - isnt_strict(schema, func, 0 args, desc) should have the proper description +ok 498 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 499 - is_strict(schema, func, 0 args) should pass +ok 500 - is_strict(schema, func, 0 args) should have the proper description +ok 501 - is_strict(schema, func, 0 args) should have the proper diagnostics +ok 502 - isnt_strict(schema, func, 0 args) should fail +ok 503 - isnt_strict(schema, func, 0 args) should have the proper description +ok 504 - isnt_strict(schema, func, 0 args) should have the proper diagnostics +ok 505 - is_strict(schema, func, args, desc) should fail +ok 506 - is_strict(schema, func, args, desc) should have the proper description +ok 507 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 508 - isnt_strict(schema, func, args, desc) should pass +ok 509 - isnt_strict(schema, func, args, desc) should have the proper description +ok 510 - isnt_strict(schema, func, args, desc) should have the proper diagnostics +ok 511 - is_strict(schema, func, args) should fail +ok 512 - is_strict(schema, func, args) should have the proper description +ok 513 - is_strict(schema, func, args) should have the proper diagnostics +ok 514 - isnt_strict(schema, func, args) should pass +ok 515 - isnt_strict(schema, func, args) should have the proper description +ok 516 - isnt_strict(schema, func, args) should have the proper diagnostics +ok 517 - is_strict(schema, func, desc) should pass +ok 518 - is_strict(schema, func, desc) should have the proper description +ok 519 - is_strict(schema, func, desc) should have the proper diagnostics +ok 520 - isnt_strict(schema, func, desc) should fail +ok 521 - isnt_strict(schema, func, desc) should have the proper description +ok 522 - isnt_strict(schema, func, desc) should have the proper diagnostics +ok 523 - is_strict(schema, func) should pass +ok 524 - is_strict(schema, func) should have the proper description +ok 525 - is_strict(schema, func) should have the proper diagnostics +ok 526 - isnt_strict(schema, func) should fail +ok 527 - isnt_strict(schema, func) should have the proper description +ok 528 - isnt_strict(schema, func) should have the proper diagnostics +ok 529 - is_strict(func, 0 args, desc) should pass +ok 530 - is_strict(func, 0 args, desc) should have the proper description +ok 531 - is_strict(func, 0 args, desc) should have the proper diagnostics +ok 532 - isnt_strict(func, 0 args, desc) should fail +ok 533 - isnt_strict(func, 0 args, desc) should have the proper description +ok 534 - isnt_strict(func, 0 args, desc) should have the proper diagnostics +ok 535 - is_strict(func, 0 args) should pass +ok 536 - is_strict(func, 0 args) should have the proper description +ok 537 - is_strict(func, 0 args) should have the proper diagnostics +ok 538 - isnt_strict(func, 0 args) should fail +ok 539 - isnt_strict(func, 0 args) should have the proper description +ok 540 - isnt_strict(func, 0 args) should have the proper diagnostics +ok 541 - is_strict(func, args, desc) should fail +ok 542 - is_strict(func, args, desc) should have the proper description +ok 543 - is_strict(func, args, desc) should have the proper diagnostics +ok 544 - isnt_strict(func, args, desc) should pass +ok 545 - isnt_strict(func, args, desc) should have the proper description +ok 546 - isnt_strict(func, args, desc) should have the proper diagnostics +ok 547 - is_strict(func, args) should fail +ok 548 - is_strict(func, args) should have the proper description +ok 549 - is_strict(func, args) should have the proper diagnostics +ok 550 - isnt_strict(func, args) should pass +ok 551 - isnt_strict(func, args) should have the proper description +ok 552 - isnt_strict(func, args) should have the proper diagnostics +ok 553 - is_strict(func, desc) should pass +ok 554 - is_strict(func, desc) should have the proper description +ok 555 - is_strict(func, desc) should have the proper diagnostics +ok 556 - isnt_strict(func, desc) should fail +ok 557 - isnt_strict(func, desc) should have the proper description +ok 558 - isnt_strict(func, desc) should have the proper diagnostics +ok 559 - is_strict(func) should pass +ok 560 - is_strict(func) should have the proper description +ok 561 - is_strict(func) should have the proper diagnostics +ok 562 - isnt_strict(func) should fail +ok 563 - isnt_strict(func) should have the proper description +ok 564 - isnt_strict(func) should have the proper diagnostics +ok 565 - function_volatility(schema, func, 0 args, volatile, desc) should pass +ok 566 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper description +ok 567 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper diagnostics +ok 568 - function_volatility(schema, func, 0 args, VOLATILE, desc) should pass +ok 569 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper description +ok 570 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper diagnostics +ok 571 - function_volatility(schema, func, 0 args, v, desc) should pass +ok 572 - function_volatility(schema, func, 0 args, v, desc) should have the proper description +ok 573 - function_volatility(schema, func, 0 args, v, desc) should have the proper diagnostics +ok 574 - function_volatility(schema, func, args, immutable, desc) should pass +ok 575 - function_volatility(schema, func, args, immutable, desc) should have the proper description +ok 576 - function_volatility(schema, func, args, immutable, desc) should have the proper diagnostics +ok 577 - function_volatility(schema, func, 0 args, stable, desc) should pass +ok 578 - function_volatility(schema, func, 0 args, stable, desc) should have the proper description +ok 579 - function_volatility(schema, func, 0 args, stable, desc) should have the proper diagnostics +ok 580 - function_volatility(schema, func, 0 args, volatile) should pass +ok 581 - function_volatility(schema, func, 0 args, volatile) should have the proper description +ok 582 - function_volatility(schema, func, 0 args, volatile) should have the proper diagnostics +ok 583 - function_volatility(schema, func, args, immutable) should pass +ok 584 - function_volatility(schema, func, args, immutable) should have the proper description +ok 585 - function_volatility(schema, func, volatile, desc) should pass +ok 586 - function_volatility(schema, func, volatile, desc) should have the proper description +ok 587 - function_volatility(schema, func, volatile, desc) should have the proper diagnostics +ok 588 - function_volatility(schema, func, volatile) should pass +ok 589 - function_volatility(schema, func, volatile) should have the proper description +ok 590 - function_volatility(schema, func, volatile) should have the proper diagnostics +ok 591 - function_volatility(schema, func, immutable, desc) should pass +ok 592 - function_volatility(schema, func, immutable, desc) should have the proper description +ok 593 - function_volatility(schema, func, immutable, desc) should have the proper diagnostics +ok 594 - function_volatility(schema, func, stable, desc) should pass +ok 595 - function_volatility(schema, func, stable, desc) should have the proper description +ok 596 - function_volatility(schema, func, stable, desc) should have the proper diagnostics +ok 597 - function_volatility(func, 0 args, volatile, desc) should pass +ok 598 - function_volatility(func, 0 args, volatile, desc) should have the proper description +ok 599 - function_volatility(func, 0 args, volatile, desc) should have the proper diagnostics +ok 600 - function_volatility(func, 0 args, VOLATILE, desc) should pass +ok 601 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper description +ok 602 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper diagnostics +ok 603 - function_volatility(func, 0 args, v, desc) should pass +ok 604 - function_volatility(func, 0 args, v, desc) should have the proper description +ok 605 - function_volatility(func, 0 args, v, desc) should have the proper diagnostics +ok 606 - function_volatility(func, args, immutable, desc) should pass +ok 607 - function_volatility(func, args, immutable, desc) should have the proper description +ok 608 - function_volatility(func, args, immutable, desc) should have the proper diagnostics +ok 609 - function_volatility(func, 0 args, stable, desc) should pass +ok 610 - function_volatility(func, 0 args, stable, desc) should have the proper description +ok 611 - function_volatility(func, 0 args, stable, desc) should have the proper diagnostics +ok 612 - function_volatility(func, 0 args, volatile) should pass +ok 613 - function_volatility(func, 0 args, volatile) should have the proper description +ok 614 - function_volatility(func, 0 args, volatile) should have the proper diagnostics +ok 615 - function_volatility(func, args, immutable) should pass +ok 616 - function_volatility(func, args, immutable) should have the proper description +ok 617 - function_volatility(func, volatile, desc) should pass +ok 618 - function_volatility(func, volatile, desc) should have the proper description +ok 619 - function_volatility(func, volatile, desc) should have the proper diagnostics +ok 620 - function_volatility(func, volatile) should pass +ok 621 - function_volatility(func, volatile) should have the proper description +ok 622 - function_volatility(func, volatile) should have the proper diagnostics +ok 623 - function_volatility(func, immutable, desc) should pass +ok 624 - function_volatility(func, immutable, desc) should have the proper description +ok 625 - function_volatility(func, immutable, desc) should have the proper diagnostics +ok 626 - function_volatility(func, stable, desc) should pass +ok 627 - function_volatility(func, stable, desc) should have the proper description +ok 628 - function_volatility(func, stable, desc) should have the proper diagnostics diff --git a/test/sql/functap.sql b/test/sql/functap.sql index 41f7d62f24b5..8d6c5e6045cc 100644 --- a/test/sql/functap.sql +++ b/test/sql/functap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(520); +SELECT plan(628); --SELECT * FROM no_plan(); CREATE SCHEMA someschema; @@ -691,7 +691,7 @@ SELECT * FROM check_test( ); /****************************************************************************/ --- Test is_definer(). +-- Test is_definer() isnt_definer(). SELECT * FROM check_test( is_definer( 'public', 'yay', '{}'::name[], 'whatever' ), true, @@ -700,6 +700,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'yay', '{}'::name[], 'whatever' ), + false, + 'isnt_definer(schema, func, 0 args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'yay', '{}'::name[] ), true, @@ -708,6 +716,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'yay', '{}'::name[] ), + false, + 'isnt_definer(schema, func, 0 args)', + 'Function public.yay() should not be security definer', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), false, @@ -716,6 +732,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_definer(schema, func, args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'oww', ARRAY['integer', 'text'] ), false, @@ -724,6 +748,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'oww', ARRAY['integer', 'text'] ), + true, + 'isnt_definer(schema, func, args)', + 'Function public.oww(integer, text) should not be security definer', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'yay', 'whatever' ), true, @@ -732,6 +764,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'yay', 'whatever' ), + false, + 'isnt_definer(schema, func, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'yay'::name ), true, @@ -740,6 +780,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'yay'::name ), + false, + 'isnt_definer(schema, func)', + 'Function public.yay() should not be security definer', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'yay', '{}'::name[], 'whatever' ), true, @@ -748,6 +796,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'yay', '{}'::name[], 'whatever' ), + false, + 'isnt_definer(schema, func, 0 args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'yay', '{}'::name[] ), true, @@ -756,6 +812,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'yay', '{}'::name[] ), + false, + 'isnt_definer(schema, func, 0 args)', + 'Function public.yay() should not be security definer', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), false, @@ -764,6 +828,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_definer(schema, func, args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'oww', ARRAY['integer', 'text'] ), false, @@ -772,6 +844,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'oww', ARRAY['integer', 'text'] ), + true, + 'isnt_definer(schema, func, args)', + 'Function public.oww(integer, text) should not be security definer', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'yay', 'whatever' ), true, @@ -780,6 +860,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'yay', 'whatever' ), + false, + 'isnt_definer(schema, func, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'public', 'yay'::name ), true, @@ -788,6 +876,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'public', 'yay'::name ), + false, + 'isnt_definer(schema, func)', + 'Function public.yay() should not be security definer', + '' +); + SELECT * FROM check_test( is_definer( 'yay', '{}'::name[], 'whatever' ), true, @@ -796,6 +892,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'yay', '{}'::name[], 'whatever' ), + false, + 'isnt_definer(func, 0 args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'yay', '{}'::name[] ), true, @@ -804,6 +908,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'yay', '{}'::name[] ), + false, + 'isnt_definer(func, 0 args)', + 'Function yay() should not be security definer', + '' +); + SELECT * FROM check_test( is_definer( 'oww', ARRAY['integer', 'text'], 'whatever' ), false, @@ -812,6 +924,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_definer(func, args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'oww', ARRAY['integer', 'text'] ), false, @@ -820,6 +940,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'oww', ARRAY['integer', 'text'] ), + true, + 'isnt_definer(func, args)', + 'Function oww(integer, text) should not be security definer', + '' +); + SELECT * FROM check_test( is_definer( 'yay', 'whatever' ), true, @@ -828,6 +956,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'yay', 'whatever' ), + false, + 'isnt_definer(func, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_definer( 'yay'::name ), true, @@ -836,8 +972,16 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_definer( 'yay'::name ), + false, + 'isnt_definer(func)', + 'Function yay() should not be security definer', + '' +); + /****************************************************************************/ --- Test is_aggregate(). +-- Test is_aggregate() and isnt_aggregate(). SELECT * FROM check_test( is_aggregate( 'public', 'tap_accum', ARRAY['anyelement'], 'whatever' ), true, @@ -846,6 +990,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum', ARRAY['anyelement'], 'whatever' ), + false, + 'isnt_aggregate(schema, func, arg, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'tap_accum', ARRAY['anyelement'] ), true, @@ -854,6 +1006,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum', ARRAY['anyelement'] ), + false, + 'isnt_aggregate(schema, func, arg)', + 'Function public.tap_accum(anyelement) should not be an aggregate function', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), false, @@ -862,6 +1022,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_aggregate(schema, func, args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), false, @@ -870,6 +1038,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), + true, + 'isnt_aggregate(schema, func, args)', + 'Function public.oww(integer, text) should not be an aggregate function', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'tap_accum', 'whatever' ), true, @@ -878,6 +1054,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum', 'whatever' ), + false, + 'isnt_aggregate(schema, func, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'tap_accum'::name ), true, @@ -886,6 +1070,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum'::name ), + false, + 'isnt_aggregate(schema, func)', + 'Function public.tap_accum() should not be an aggregate function', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'tap_accum', ARRAY['anyelement'], 'whatever' ), true, @@ -894,6 +1086,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum', ARRAY['anyelement'], 'whatever' ), + false, + 'isnt_aggregate(schema, func, arg, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'tap_accum', ARRAY['anyelement'] ), true, @@ -902,6 +1102,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum', ARRAY['anyelement'] ), + false, + 'isnt_aggregate(schema, func, arg)', + 'Function public.tap_accum(anyelement) should not be an aggregate function', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), false, @@ -910,6 +1118,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_aggregate(schema, func, args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), false, @@ -918,6 +1134,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), + true, + 'isnt_aggregate(schema, func, args)', + 'Function public.oww(integer, text) should not be an aggregate function', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'tap_accum', 'whatever' ), true, @@ -926,6 +1150,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum', 'whatever' ), + false, + 'isnt_aggregate(schema, func, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'public', 'tap_accum'::name ), true, @@ -934,6 +1166,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum'::name ), + false, + 'isnt_aggregate(schema, func)', + 'Function public.tap_accum() should not be an aggregate function', + '' +); + SELECT * FROM check_test( is_aggregate( 'tap_accum', ARRAY['anyelement'], 'whatever' ), true, @@ -942,6 +1182,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'tap_accum', ARRAY['anyelement'], 'whatever' ), + false, + 'isnt_aggregate(func, arg, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'tap_accum', ARRAY['anyelement'] ), true, @@ -950,6 +1198,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'tap_accum', ARRAY['anyelement'] ), + false, + 'isnt_aggregate(func, arg)', + 'Function tap_accum(anyelement) should not be an aggregate function', + '' +); + SELECT * FROM check_test( is_aggregate( 'oww', ARRAY['integer', 'text'], 'whatever' ), false, @@ -958,6 +1214,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_aggregate(func, args, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'oww', ARRAY['integer', 'text'] ), false, @@ -966,6 +1230,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'oww', ARRAY['integer', 'text'] ), + true, + 'isnt_aggregate(func, args)', + 'Function oww(integer, text) should not be an aggregate function', + '' +); + SELECT * FROM check_test( is_aggregate( 'tap_accum', 'whatever' ), true, @@ -974,6 +1246,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'tap_accum', 'whatever' ), + false, + 'isnt_aggregate(func, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( is_aggregate( 'tap_accum'::name ), true, @@ -982,6 +1262,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_aggregate( 'tap_accum'::name ), + false, + 'isnt_aggregate(func)', + 'Function tap_accum() should not be an aggregate function', + '' +); + /****************************************************************************/ -- Test is_strict() and isnt_strict(). SELECT * FROM check_test( From 15e12d436ffc2b4e29410204e351417b351a54ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Qui=C3=A9deville?= Date: Thu, 30 Jun 2016 16:37:22 +0200 Subject: [PATCH 0926/1195] Add doc on new functions isnt_definer() and isnt_aggregate(). Fix a little typo in is_aggregate() doc --- doc/pgtap.mmd | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index fcbcdeaf84aa..a37692cb6800 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4973,6 +4973,42 @@ If the function does not exist, a handy diagnostic message will let you know: But then you check with `has_function()` first, right? +### `isnt_definer()` ### + + SELECT isnt_definer( :schema, :function, :args, :description ); + SELECT isnt_definer( :schema, :function, :args ); + SELECT isnt_definer( :schema, :function, :description ); + SELECT isnt_definer( :schema, :function ); + SELECT isnt_definer( :function, :args, :description ); + SELECT isnt_definer( :function, :args ); + SELECT isnt_definer( :function, :description ); + SELECT isnt_definer( :function ); + +**Parameters** + +`:schema` +: Schema in which to find the function. + +`:function` +: Function name. + +`:args` +: Array of data types for the function arguments. + +`:description` +: A short description of the test. + + +This function is the inverse of `is_definer()`. The test passes if the specified +function is not a security definer. + +If the function does not exist, a handy diagnostic message will let you know: + + # Failed test 290: "Function nasty() should not be security definer" + # Function nasty() does not exist + +But then you check with `has_function()` first, right? + ### `is_strict()` ### SELECT is_strict( :schema, :function, :args, :description ); @@ -5048,7 +5084,42 @@ function is not strict. If the function does not exist, a handy diagnostic message will let you know: - # Failed test 290: "Function nasty() should be strict" + # Failed test 290: "Function nasty() should be an aggregate function" + # Function nasty() does not exist + +But then you check with `has_function()` first, right? + +### `isnt_aggregate()` ### + + SELECT isnt_aggregate( :schema, :function, :args, :description ); + SELECT isnt_aggregate( :schema, :function, :args ); + SELECT isnt_aggregate( :schema, :function, :description ); + SELECT isnt_aggregate( :schema, :function ); + SELECT isnt_aggregate( :function, :args, :description ); + SELECT isnt_aggregate( :function, :args ); + SELECT isnt_aggregate( :function, :description ); + SELECT isnt_aggregate( :function ); + +**Parameters** + +`:schema` +: Schema in which to find the function. + +`:function` +: Function name. + +`:args` +: Array of data types for the function arguments. + +`:description` +: A short description of the test. + +This function is the inverse of `is_aggregate()`. The test passes if the specified +function is not an aggregate function. + +If the function does not exist, a handy diagnostic message will let you know: + + # Failed test 290: "Function nasty() should not be an aggregate function" # Function nasty() does not exist But then you check with `has_function()` first, right? From 1f59221818528396451e1727039f7a0709a2df67 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Wed, 26 Oct 2016 15:17:50 -0500 Subject: [PATCH 0927/1195] New upgrade scripts for 0.97.0 --- .gitignore | 2 + Makefile | 30 ++- compat/9.0/pgtap--0.96.0--0.97.0.patch | 88 +++++++++ compat/9.2/pgtap--0.95.0--0.96.0.patch | 186 ++++++++++++++++++ compat/9.4/pgtap--0.96.0--0.97.0.patch | 81 ++++++++ ....96.0.sql => pgtap--0.95.0--0.96.0.sql.in} | 0 ....97.0.sql => pgtap--0.96.0--0.97.0.sql.in} | 181 ++++++++++++++++- 7 files changed, 561 insertions(+), 7 deletions(-) create mode 100644 compat/9.0/pgtap--0.96.0--0.97.0.patch create mode 100644 compat/9.2/pgtap--0.95.0--0.96.0.patch create mode 100644 compat/9.4/pgtap--0.96.0--0.97.0.patch rename sql/{pgtap--0.95.0--0.96.0.sql => pgtap--0.95.0--0.96.0.sql.in} (100%) rename sql/{pgtap--0.96.0--0.97.0.sql => pgtap--0.96.0--0.97.0.sql.in} (57%) diff --git a/.gitignore b/.gitignore index 12ae109db8ad..c723a9894a1b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ bbin /sql/pgtap--?.??.?.sql /sql/pgtap-core--* /sql/pgtap-schema--* +/sql/pgtap--0.95.0--0.96.0.sql +/sql/pgtap--0.96.0--0.97.0.sql *.sql.orig diff --git a/Makefile b/Makefile index dcfaf177dd20..652773a09695 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,8 @@ EXTENSION = $(MAINEXT) EXTVERSION = $(shell grep default_version $(MAINEXT).control | \ sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") NUMVERSION = $(shell echo $(EXTVERSION) | sed -e 's/\([[:digit:]]*[.][[:digit:]]*\).*/\1/') -DATA = $(wildcard sql/*--*.sql) +_IN_FILES = $(wildcard sql/*--*.sql.in) +_IN_PATCHED = $(_IN_FILES:.in=) TESTS = $(wildcard test/sql/*.sql) EXTRA_CLEAN = sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql doc/*.html DOCS = doc/pgtap.mmd @@ -11,6 +12,9 @@ REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --inputdir=test --load-language=plpgsql PG_CONFIG ?= pg_config +# sort is necessary to remove dupes so install won't complain +DATA = $(sort $(wildcard sql/*--*.sql) $(_IN_PATCHED)) # NOTE! This gets reset below! + ifdef NO_PGXS top_builddir = ../.. PG_CONFIG := $(top_builddir)/src/bin/pg_config/pg_config @@ -76,7 +80,8 @@ endif OSNAME := $(shell $(SHELL) ./getos.sh) # Make sure we build these. -all: sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql +EXTRA_CLEAN += $(_IN_PATCHED) +all: $(_IN_PATCHED) sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql # Add extension build targets on 9.1 and up. ifeq ($(shell echo $(VERSION) | grep -qE "8[.]|9[.]0" && echo no || echo yes),yes) @@ -91,7 +96,9 @@ sql/$(MAINEXT)-core--$(EXTVERSION).sql: sql/$(MAINEXT)-core.sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql: sql/$(MAINEXT)-schema.sql cp $< $@ -DATA = $(wildcard sql/*--*.sql) +# I think this can go away... +DATA = $(sort $(wildcard sql/*--*.sql) $(_IN_PATCHED)) + EXTRA_CLEAN += sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql endif @@ -124,6 +131,23 @@ endif sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap.sql > sql/pgtap.tmp mv sql/pgtap.tmp sql/pgtap.sql +# Ugly hack for now... +EXCRA_CLEAN += sql/pgtap--0.96.0--0.97.0.sql +sql/pgtap--0.96.0--0.97.0.sql: sql/pgtap--0.96.0--0.97.0.sql.in + cp $< $@ +ifeq ($(shell echo $(VERSION) | grep -qE "9[.][01234]|8[.][1234]" && echo yes || echo no),yes) + patch -p0 < compat/9.4/pgtap--0.96.0--0.97.0.patch +endif +ifeq ($(shell echo $(VERSION) | grep -qE "9[.]0|8[.][1234]" && echo yes || echo no),yes) + patch -p0 < compat/9.0/pgtap--0.96.0--0.97.0.patch +endif +EXCRA_CLEAN += sql/pgtap--0.95.0--0.96.0.sql +sql/pgtap--0.95.0--0.96.0.sql: sql/pgtap--0.95.0--0.96.0.sql.in + cp $< $@ +ifeq ($(shell echo $(VERSION) | grep -qE "9[.][012]|8[.][1234]" && echo yes || echo no),yes) + patch -p0 < compat/9.2/pgtap--0.95.0--0.96.0.patch +endif + sql/uninstall_pgtap.sql: sql/pgtap.sql test/setup.sql grep '^CREATE ' sql/pgtap.sql | $(PERL) -e 'for (reverse ) { chomp; s/CREATE (OR REPLACE)?/DROP/; print "$$_;\n" }' > sql/uninstall_pgtap.sql diff --git a/compat/9.0/pgtap--0.96.0--0.97.0.patch b/compat/9.0/pgtap--0.96.0--0.97.0.patch new file mode 100644 index 000000000000..dee803b33bfe --- /dev/null +++ b/compat/9.0/pgtap--0.96.0--0.97.0.patch @@ -0,0 +1,88 @@ +--- sql/pgtap--0.96.0--0.97.0.sql ++++ sql/pgtap--0.96.0--0.97.0.sql +@@ -225,85 +225,3 @@ + SELECT ok( NOT _opc_exists( $1 ), 'Operator class ' || quote_ident($1) || ' should not exist' ); + $$ LANGUAGE SQL; + +--- https://github.com/theory/pgtap/pull/101 +- +--- 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; +- diff --git a/compat/9.2/pgtap--0.95.0--0.96.0.patch b/compat/9.2/pgtap--0.95.0--0.96.0.patch new file mode 100644 index 000000000000..1bfbe5b2a82b --- /dev/null +++ b/compat/9.2/pgtap--0.95.0--0.96.0.patch @@ -0,0 +1,186 @@ +--- sql/pgtap--0.95.0--0.96.0.sql ++++ sql/pgtap--0.95.0--0.96.0.sql +@@ -233,183 +233,3 @@ CREATE OR REPLACE FUNCTION _error_diag( + ), ''); + $$ 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 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; +- +-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 tfaild WHEN 1 THEN '' ELSE 's' END +- || ' of ' || fnumb +- ); +- END IF; +- END IF; +- +- EXCEPTION WHEN raise_exception 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; +- diff --git a/compat/9.4/pgtap--0.96.0--0.97.0.patch b/compat/9.4/pgtap--0.96.0--0.97.0.patch new file mode 100644 index 000000000000..b1d9784216bc --- /dev/null +++ b/compat/9.4/pgtap--0.96.0--0.97.0.patch @@ -0,0 +1,81 @@ +--- sql/pgtap--0.96.0--0.97.0.sql ++++ sql/pgtap--0.96.0--0.97.0.sql +@@ -307,78 +307,3 @@ RETURNS TEXT AS $$ + 'Extension ' || quote_ident($1) || ' should not exist' ); + $$ LANGUAGE SQL; + +--- https://github.com/theory/pgtap/pull/119 +- +--- 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; +- +--- 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; +- diff --git a/sql/pgtap--0.95.0--0.96.0.sql b/sql/pgtap--0.95.0--0.96.0.sql.in similarity index 100% rename from sql/pgtap--0.95.0--0.96.0.sql rename to sql/pgtap--0.95.0--0.96.0.sql.in diff --git a/sql/pgtap--0.96.0--0.97.0.sql b/sql/pgtap--0.96.0--0.97.0.sql.in similarity index 57% rename from sql/pgtap--0.96.0--0.97.0.sql rename to sql/pgtap--0.96.0--0.97.0.sql.in index 6b1b57ec185c..373c289a6b68 100644 --- a/sql/pgtap--0.96.0--0.97.0.sql +++ b/sql/pgtap--0.96.0--0.97.0.sql.in @@ -104,7 +104,7 @@ RETURNS TEXT AS $$ SELECT ok( NOT _definer($1, $2, $3), 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '(' || - array_to_string($3, ', ') || ') should be not security definer' + array_to_string($3, ', ') || ') should not be security definer' ); $$ LANGUAGE sql; @@ -119,7 +119,7 @@ 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 be not security definer' + 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should not be security definer' ); $$ LANGUAGE sql; @@ -135,7 +135,7 @@ RETURNS TEXT AS $$ SELECT ok( NOT _definer($1, $2), 'Function ' || quote_ident($1) || '(' || - array_to_string($2, ', ') || ') should be not security definer' + array_to_string($2, ', ') || ') should not be security definer' ); $$ LANGUAGE sql; @@ -148,7 +148,7 @@ $$ 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 be not security definer' ); + SELECT ok( NOT _definer($1), 'Function ' || quote_ident($1) || '() should not be security definer' ); $$ LANGUAGE sql; -- isnt_aggregate( schema, function, args[], description ) @@ -209,3 +209,176 @@ CREATE OR REPLACE FUNCTION isnt_aggregate( NAME ) RETURNS TEXT AS $$ SELECT ok( NOT _agg($1), 'Function ' || quote_ident($1) || '() should not be an aggregate function' ); $$ LANGUAGE sql; + + +-- https://github.com/theory/pgtap/pull/99 + +-- 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 ) +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; + +-- https://github.com/theory/pgtap/pull/101 + +-- 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; + +-- https://github.com/theory/pgtap/pull/119 + +-- 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; + +-- 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; + From b4cef7707fe160a9f8923ed356c69f094f79d59d Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Wed, 26 Oct 2016 16:00:08 -0500 Subject: [PATCH 0928/1195] Update Changes --- Changes | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 53eea98f8cc8..fde2d2dcad23 100644 --- a/Changes +++ b/Changes @@ -5,13 +5,18 @@ Revision history for pgTAP --------------------------- * Fixed the default description for `hasnt_opclass()` to say "should not exist" instead of "should exist", thanks to Rodolphe Quiédeville (PR #99). -* Added `is_indexed()`, which test to see that specific table columns are +* Added `is_indexed()`, which tests to see if specific table columns are indexed. It's effectively the same as `has_index()` except that it doesn't - require an index name and does require one or more column names or - expressions in the defined for the index. Thanks to Rodolphe Quiédeville - (PR #103). + require an index name and does require one or more column names or index + expressions. Thanks to Rodolphe Quiédeville (PR #103). * Fixed `pg_version_num()` to work with the new version format coming in PostgreSQL 10. +* Fix lives_ok() and throws_ok() to also trap ASSERT_FAILURE (PR #119). +* Add negation functions isnt_aggregate() and isnt_definer(), which return the + opposite of is_aggregate() and is_definer(). Thanks to Rodolphe Quiédeville + (PR #106). +* Add has_extension() and hasnt_extension(). Thanks to Rodolphe Quiédeville (PR + #101). 0.96.0 2016-05-16T20:53:57Z --------------------------- From 9a0f5bd45ec818156b0a85764eed88fdf8ffc60c Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Wed, 2 Nov 2016 15:41:37 -0500 Subject: [PATCH 0929/1195] Fix typos in Makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 652773a09695..3ff4149a558e 100644 --- a/Makefile +++ b/Makefile @@ -132,7 +132,7 @@ endif mv sql/pgtap.tmp sql/pgtap.sql # Ugly hack for now... -EXCRA_CLEAN += sql/pgtap--0.96.0--0.97.0.sql +EXTRA_CLEAN += sql/pgtap--0.96.0--0.97.0.sql sql/pgtap--0.96.0--0.97.0.sql: sql/pgtap--0.96.0--0.97.0.sql.in cp $< $@ ifeq ($(shell echo $(VERSION) | grep -qE "9[.][01234]|8[.][1234]" && echo yes || echo no),yes) @@ -141,7 +141,7 @@ endif ifeq ($(shell echo $(VERSION) | grep -qE "9[.]0|8[.][1234]" && echo yes || echo no),yes) patch -p0 < compat/9.0/pgtap--0.96.0--0.97.0.patch endif -EXCRA_CLEAN += sql/pgtap--0.95.0--0.96.0.sql +EXTRA_CLEAN += sql/pgtap--0.95.0--0.96.0.sql sql/pgtap--0.95.0--0.96.0.sql: sql/pgtap--0.95.0--0.96.0.sql.in cp $< $@ ifeq ($(shell echo $(VERSION) | grep -qE "9[.][012]|8[.][1234]" && echo yes || echo no),yes) From 097bb7da2780eaf7e75e314bcb010e47f1726cbc Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Wed, 9 Nov 2016 12:55:10 -0600 Subject: [PATCH 0930/1195] Merge additional version support and travis testing Fixes is_indexed() for older versions Fix unit tests on older versions Add 8.2-8.4 and 9.6 to Travis Squashed commit of the following: commit 9a8c9db6d8e36cf6d99b29aedfe629a589b4c872 Author: Jim Nasby Date: Mon Nov 7 21:40:40 2016 -0600 Enable 9.6 in travis commit a41a0821f0194995137abe66427a7f49327d1daf Author: Jim Nasby Date: Mon Nov 7 21:40:18 2016 -0600 Add alternative output for 9.6 commit ebfc1ee343a473b0b2cb31bfbd1e22a061be9ea3 Merge: c79f048 9ad1685 Author: Jim Nasby Date: Mon Nov 7 21:37:39 2016 -0600 Merge branch 'master' into more_travis commit c79f048d64a48e7dacf6900cdd7d9d0695508771 Author: Jim Nasby Date: Mon Nov 7 15:42:47 2016 -0600 Add fix for 9.6 commit 46921c479d00f92415b8e9ffe96ccc9a44281f2d Author: Jim Nasby Date: Mon Nov 7 15:32:48 2016 -0600 8.1 doesn't work on travis commit e337147df2073c8cb50c4f7b4052bb84d7f82adb Author: Jim Nasby Date: Mon Nov 7 15:32:30 2016 -0600 Fix 9.5 commit a32a8be03431f9af3a8e4fbd56f26bb12e351ad3 Author: Jim Nasby Date: Mon Nov 7 15:12:04 2016 -0600 There's a chance this will work on 8.1 commit 3ca77ad9086014e5bec4f4dd3c387be285742341 Author: Jim Nasby Date: Mon Nov 7 15:11:48 2016 -0600 Fixes for 8.2 commit 0923abf2797fb236dbd3d393fae92ebac948f9ce Author: Jim Nasby Date: Mon Nov 7 14:48:57 2016 -0600 Fix unit test for older versions commit b350483c191ea7bd3ce8457286574d44ce15d07b Author: Jim Nasby Date: Mon Nov 7 14:40:48 2016 -0600 Fix upgrade script commit 57ab54ad4a30ad51184fa78514e192035a9c68a9 Author: Jim Nasby Date: Fri Nov 4 16:23:40 2016 -0500 Fix 8.3 error commit 97072f4a3a3bdbfbac23c254a66cd46c6f3d24d6 Merge: c373d0c 9a0f5bd Author: Jim Nasby Date: Fri Nov 4 15:54:37 2016 -0500 Merge branch 'master' into more_travis commit c373d0ceb516bd5e2c1eb0c4b2f512eed405b455 Merge: 8854c00 367f66e Author: Jim Nasby Date: Sun Oct 23 10:43:43 2016 -0500 Merge branch 'master' into more_travis commit 367f66e283a3c00b19c9fbc7375f89d4b59db4b7 Author: Jim Nasby Date: Sun Oct 23 10:08:10 2016 -0500 Add 8.2-8.4 to travis This requires changing _is_indexed() not not use WITH There's still some version-related cleanup to do, like updating the minimum-version test in the Makefile, updating META.json, and maybe the docs. There's also a questionable pg73 test. But merging this in now will make it easier to detect problems via automated travis testing. Squashed commit of the following: commit 8854c0036589ae28fbda3d5b7a680357297a5f5b Author: Jim Nasby Date: Sat Oct 15 19:10:06 2016 -0500 Remove 8.0 and 8.1 from travis commit b1f5c2bf51e9b9edfd842b89615362f04c485d71 Author: Jim Nasby Date: Sat Oct 15 19:01:08 2016 -0500 Remove use of WITH commit d74d0adbb4cb78d37d89bd70b2f3abfb8e28986f Author: Jim Nasby Date: Sat Oct 15 18:38:53 2016 -0500 Get rid of WITH commit 8854c0036589ae28fbda3d5b7a680357297a5f5b Author: Jim Nasby Date: Sat Oct 15 19:10:06 2016 -0500 Remove 8.0 and 8.1 from travis commit b1f5c2bf51e9b9edfd842b89615362f04c485d71 Author: Jim Nasby Date: Sat Oct 15 19:01:08 2016 -0500 Remove use of WITH commit d74d0adbb4cb78d37d89bd70b2f3abfb8e28986f Author: Jim Nasby Date: Sat Oct 15 18:38:53 2016 -0500 Get rid of WITH --- .travis.yml | 4 + sql/pgtap--0.96.0--0.97.0.sql.in | 51 ++++------ sql/pgtap.sql.in | 27 ++---- test/expected/runjusttests_5.out | 54 +++++++++++ test/expected/runtests_6.out | 158 +++++++++++++++++++++++++++++++ test/sql/throwtap.sql | 61 ++++++++---- 6 files changed, 284 insertions(+), 71 deletions(-) create mode 100644 test/expected/runjusttests_5.out create mode 100644 test/expected/runtests_6.out diff --git a/.travis.yml b/.travis.yml index 3eac6f93afe5..b41ccae9dcb0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,10 +5,14 @@ before_install: - sudo sh ./apt.postgresql.org.sh - sudo rm -vf /etc/apt/sources.list.d/pgdg-source.list env: + - PGVERSION=8.2 + - PGVERSION=8.3 + - PGVERSION=8.4 - PGVERSION=9.0 - PGVERSION=9.1 - PGVERSION=9.2 - PGVERSION=9.3 - PGVERSION=9.4 - PGVERSION=9.5 + - PGVERSION=9.6 script: bash ./pg-travis-test.sh diff --git a/sql/pgtap--0.96.0--0.97.0.sql.in b/sql/pgtap--0.96.0--0.97.0.sql.in index 373c289a6b68..b5f8fb62db77 100644 --- a/sql/pgtap--0.96.0--0.97.0.sql.in +++ b/sql/pgtap--0.96.0--0.97.0.sql.in @@ -1,26 +1,26 @@ +-- pg_version_num() +CREATE OR REPLACE FUNCTION pg_version_num() +RETURNS integer AS $$ + 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 _is_indexed( NAME, NAME, TEXT[] ) RETURNS BOOL AS $$ - WITH icols AS ( - SELECT _ikeys($1, $2, ci.relname) AS cols +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 n.nspname = $1 + WHERE ($1 IS NULL OR n.nspname = $1) AND ct.relname = $2 - ) SELECT EXISTS( SELECT TRUE from icols WHERE cols = $3 ) -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _is_indexed( NAME, TEXT[] ) -RETURNS BOOL AS $$ - WITH icols AS ( - SELECT _ikeys($1, 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 - WHERE ct.relname = $1 - AND pg_catalog.pg_table_is_visible(ct.oid) - ) SELECT EXISTS( SELECT TRUE from icols WHERE cols = $2 ) + ) icols + WHERE cols = $3 ) $$ LANGUAGE sql; -- is_indexed( schema, table, columns[], description ) @@ -41,14 +41,14 @@ $$ LANGUAGE sql; -- is_indexed( table, columns[], description ) CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ - SELECT ok( _is_indexed($1, $2), $3 ); + 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($1, $2), + _is_indexed(NULL, $1, $2), 'Should have an index on ' || quote_ident($1) || '(' || array_to_string( $2, ', ' ) || ')' ); $$ LANGUAGE sql; @@ -75,22 +75,9 @@ $$ LANGUAGE sql; -- is_indexed( table, column ) CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT ok ( _is_indexed( $1, ARRAY[$2]::NAME[]), - 'An index on ' || quote_ident($1) || ' on column ' - || $2::text || ' should exist'); + SELECT ok ( _is_indexed( NULL, $1, ARRAY[$2]::NAME[]) ); $$ LANGUAGE sql; --- pg_version_num() -CREATE OR REPLACE FUNCTION pg_version_num() -RETURNS integer AS $$ - 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; - -- isnt_definer( schema, function, args[], description ) CREATE OR REPLACE FUNCTION isnt_definer ( NAME, NAME, NAME[], TEXT ) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 52b3e0d9622b..198ad7ab2d8e 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2925,27 +2925,16 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _is_indexed( NAME, NAME, TEXT[] ) RETURNS BOOL AS $$ - WITH icols AS ( - SELECT _ikeys($1, $2, ci.relname) AS cols +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 n.nspname = $1 + WHERE ($1 IS NULL OR n.nspname = $1) AND ct.relname = $2 - ) SELECT EXISTS( SELECT TRUE from icols WHERE cols = $3 ) -$$ LANGUAGE sql; - -CREATE OR REPLACE FUNCTION _is_indexed( NAME, TEXT[] ) -RETURNS BOOL AS $$ - WITH icols AS ( - SELECT _ikeys($1, 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 - WHERE ct.relname = $1 - AND pg_catalog.pg_table_is_visible(ct.oid) - ) SELECT EXISTS( SELECT TRUE from icols WHERE cols = $2 ) + ) icols + WHERE cols = $3 ) $$ LANGUAGE sql; -- is_indexed( schema, table, columns[], description ) @@ -2966,14 +2955,14 @@ $$ LANGUAGE sql; -- is_indexed( table, columns[], description ) CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ - SELECT ok( _is_indexed($1, $2), $3 ); + 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($1, $2), + _is_indexed(NULL, $1, $2), 'Should have an index on ' || quote_ident($1) || '(' || array_to_string( $2, ', ' ) || ')' ); $$ LANGUAGE sql; @@ -3000,7 +2989,7 @@ $$ LANGUAGE sql; -- is_indexed( table, column ) CREATE OR REPLACE FUNCTION is_indexed ( NAME, NAME ) RETURNS TEXT AS $$ - SELECT ok ( _is_indexed( $1, ARRAY[$2]::NAME[]) ); + SELECT ok ( _is_indexed( NULL, $1, ARRAY[$2]::NAME[]) ); $$ LANGUAGE sql; -- index_is_unique( schema, table, index, description ) diff --git a/test/expected/runjusttests_5.out b/test/expected/runjusttests_5.out new file mode 100644 index 000000000000..54862df7e430 --- /dev/null +++ b/test/expected/runjusttests_5.out @@ -0,0 +1,54 @@ +\unset ECHO + # Subtest: whatever."test ident"() + ok 1 - ident + ok 2 - ident 2 + 1..2 +ok 1 - whatever."test ident" + # Subtest: whatever.testnada() + 1..0 + # No tests run! +not ok 2 - whatever.testnada +# Failed test 2: "whatever.testnada" + # Subtest: whatever.testplpgsql() + ok 1 - plpgsql simple + ok 2 - plpgsql simple 2 + ok 3 - Should be a 1 in the test table + 1..3 +ok 3 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. + # DETAIL: DETAIL + # SCHEMA: SCHEMA + # TABLE: TABLE + # COLUMN: COLUMN + # CONSTRAINT: CONSTRAINT + # TYPE: TYPE + # CONTEXT: + # PL/pgSQL function __die() line 3 at RAISE + # SQL statement "SELECT __die();" + # PL/pgSQL function whatever.testplpgsqldie() line 43 at EXECUTE + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 + # SQL function "runtests" statement 1 +not ok 4 - whatever.testplpgsqldie +# Failed test 4: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - simple pass + ok 2 - another simple pass + 1..2 +ok 5 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - pass + not ok 2 - this test intentionally fails + # Failed test 2: "this test intentionally fails" + 1..2 + # Looks like you failed 1 tests of 2 +not ok 6 - whatever.testy +# Failed test 6: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - Late test should find nothing in the test table + 1..1 +ok 7 - whatever.testz +1..7 +# Looks like you failed 3 tests of 7 diff --git a/test/expected/runtests_6.out b/test/expected/runtests_6.out new file mode 100644 index 000000000000..20fadf630f6d --- /dev/null +++ b/test/expected/runtests_6.out @@ -0,0 +1,158 @@ +\unset ECHO +ok 1 - starting up +ok 2 - starting up some more + # Subtest: whatever."test ident"() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - ident + ok 5 - ident 2 + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 3 - whatever."test ident" + # Subtest: whatever.testplpgsql() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - plpgsql simple + ok 5 - plpgsql simple 2 + ok 6 - Should be a 1 in the test table + ok 7 - teardown + ok 8 - teardown more + 1..8 +ok 4 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. + # DETAIL: DETAIL + # SCHEMA: SCHEMA + # TABLE: TABLE + # COLUMN: COLUMN + # CONSTRAINT: CONSTRAINT + # TYPE: TYPE + # CONTEXT: + # PL/pgSQL function __die() line 3 at RAISE + # SQL statement "SELECT __die();" + # PL/pgSQL function whatever.testplpgsqldie() line 23 at EXECUTE + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 + # SQL function "runtests" statement 1 +not ok 5 - whatever.testplpgsqldie +# Failed test 5: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - simple pass + ok 5 - another simple pass + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 6 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + not ok 4 - this test intentionally fails + # Failed test 4: "this test intentionally fails" + ok 5 - teardown + ok 6 - teardown more + 1..6 + # Looks like you failed 1 test of 6 +not ok 7 - whatever.testy +# Failed test 7: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - Late test should find nothing in the test table + ok 5 - teardown + ok 6 - teardown more + 1..6 +ok 8 - whatever.testz +ok 9 - shutting down +ok 10 - shutting down more +1..10 +# Looks like you failed 2 tests of 10 +ok 1 - starting up +ok 2 - starting up some more + # Subtest: whatever."test ident"() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - ident + ok 5 - ident 2 + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 3 - whatever."test ident" + # Subtest: whatever.testplpgsql() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - plpgsql simple + ok 5 - plpgsql simple 2 + ok 6 - Should be a 1 in the test table + ok 7 - teardown + ok 8 - teardown more + 1..8 +ok 4 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. + # DETAIL: DETAIL + # SCHEMA: SCHEMA + # TABLE: TABLE + # COLUMN: COLUMN + # CONSTRAINT: CONSTRAINT + # TYPE: TYPE + # CONTEXT: + # PL/pgSQL function __die() line 3 at RAISE + # SQL statement "SELECT __die();" + # PL/pgSQL function whatever.testplpgsqldie() line 23 at EXECUTE + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 +not ok 5 - whatever.testplpgsqldie +# Failed test 5: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - simple pass + ok 5 - another simple pass + ok 6 - teardown + ok 7 - teardown more + 1..7 +ok 6 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + not ok 4 - this test intentionally fails + # Failed test 4: "this test intentionally fails" + ok 5 - teardown + ok 6 - teardown more + 1..6 + # Looks like you failed 1 test of 6 +not ok 7 - whatever.testy +# Failed test 7: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + ok 4 - Late test should find nothing in the test table + ok 5 - teardown + ok 6 - teardown more + 1..6 +ok 8 - whatever.testz +ok 9 - shutting down +ok 10 - shutting down more +1..10 +# Looks like you failed 2 tests of 10 diff --git a/test/sql/throwtap.sql b/test/sql/throwtap.sql index 5ccb0beb1920..ad71f9694a1c 100644 --- a/test/sql/throwtap.sql +++ b/test/sql/throwtap.sql @@ -124,7 +124,10 @@ SELECT * FROM check_test( '', ' died: P0001: todo_end() called without todo_start()' || CASE WHEN pg_version_num() < 90200 THEN '' ELSE ' - CONTEXT: + CONTEXT:' + || CASE WHEN pg_version_num() < 90600 THEN '' ELSE ' + PL/pgSQL function todo_end() line 7 at RAISE' END + || ' SQL statement "SELECT * FROM todo_end()" PL/pgSQL function lives_ok(text,text) line 14 at EXECUTE' || CASE WHEN pg_version_num() >= 90500 THEN '' ELSE ' statement' END END @@ -306,7 +309,7 @@ SELECT lives_ok( CASE WHEN pg_version_num() < 90500 THEN $exec$ CREATE FUNCTION check_assert(b boolean) RETURNS void LANGUAGE plpgsql AS $body$ BEGIN - RAISE 'this code should never be called!'; + RAISE EXCEPTION 'this code should never be called!'; END $body$; $exec$ @@ -322,35 +325,43 @@ $exec$ ); CREATE FUNCTION test_assert() RETURNS SETOF text LANGUAGE plpgsql AS $body$ +DECLARE + t text; BEGIN IF pg_version_num() >= 90500 THEN -RETURN QUERY SELECT * FROM check_test( +FOR t IN SELECT * FROM check_test( throws_ok( 'SELECT check_assert(false)', 'P0004', 'assert description' ), true, 'throws_ok catches assert', 'threw P0004: assert description', '' -); +) LOOP + RETURN NEXT t; +END LOOP; -RETURN QUERY SELECT * FROM check_test( +FOR t IN SELECT * FROM check_test( throws_ok( 'SELECT check_assert(true)', 'P0004' ), false, 'throws_ok does not accept passing assert', 'threw P0004', ' caught: no exception wanted: P0004' -); +) LOOP + RETURN NEXT t; +END LOOP; -RETURN QUERY SELECT * FROM check_test( +FOR t IN SELECT * FROM check_test( lives_ok( 'SELECT check_assert(true)' ), true, 'lives_ok calling check_assert(true)', '', '' -); +) LOOP + RETURN NEXT t; +END LOOP; -- Check its diagnostics when there is an exception. -RETURN QUERY SELECT * FROM check_test( +FOR t IN SELECT * FROM check_test( lives_ok( 'SELECT check_assert(false)' ), false, 'lives_ok with check_assert(false)', @@ -360,46 +371,56 @@ RETURN QUERY SELECT * FROM check_test( PL/pgSQL function check_assert(boolean) line 3 at ASSERT SQL statement "SELECT check_assert(false)" PL/pgSQL function lives_ok(text,text) line 14 at EXECUTE - PL/pgSQL function test_assert() line 30 at RETURN QUERY' -); + PL/pgSQL function test_assert() line 38 at FOR over SELECT rows' +) LOOP + RETURN NEXT t; +END LOOP; ELSE -RETURN QUERY SELECT * FROM check_test( +FOR t IN SELECT * FROM check_test( pass(''), true, 'throws_ok catches assert', '', '' -); +) LOOP + RETURN NEXT t; +END LOOP; -RETURN QUERY SELECT * FROM check_test( +FOR t IN SELECT * FROM check_test( fail(''), false, 'throws_ok does not accept passing assert', '', '' -); +) LOOP + RETURN NEXT t; +END LOOP; -RETURN QUERY SELECT * FROM check_test( +FOR t IN SELECT * FROM check_test( pass(''), true, 'lives_ok calling check_assert(true)', '', '' -); +) LOOP + RETURN NEXT t; +END LOOP; -- Check its diagnostics when there is an exception. -RETURN QUERY SELECT * FROM check_test( +FOR t IN SELECT * FROM check_test( fail(''), false, 'lives_ok with check_assert(false)', '', '' -); +) LOOP + RETURN NEXT t; +END LOOP; END IF; END $body$; -SELECT test_assert(); +SELECT * FROM test_assert(); /****************************************************************************/ -- Finish the tests and clean up. From fec6b5f49aa4ef09afe94dd35165395e932e427b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 22 Nov 2016 13:14:23 -0800 Subject: [PATCH 0931/1195] Update compat patches. Just fix line numbers; no other changes required. --- compat/9.0/pgtap--0.96.0--0.97.0.patch | 2 +- compat/9.4/pgtap--0.96.0--0.97.0.patch | 2 +- compat/install-8.1.patch | 14 ++++---- compat/install-8.2.patch | 44 +++++++++++++------------- compat/install-8.3.patch | 10 +++--- compat/install-8.4.patch | 8 ++--- compat/install-9.0.patch | 10 +++--- compat/install-9.1.patch | 2 +- compat/install-9.2.patch | 2 +- 9 files changed, 47 insertions(+), 47 deletions(-) diff --git a/compat/9.0/pgtap--0.96.0--0.97.0.patch b/compat/9.0/pgtap--0.96.0--0.97.0.patch index dee803b33bfe..8f40f0c99286 100644 --- a/compat/9.0/pgtap--0.96.0--0.97.0.patch +++ b/compat/9.0/pgtap--0.96.0--0.97.0.patch @@ -1,6 +1,6 @@ --- sql/pgtap--0.96.0--0.97.0.sql +++ sql/pgtap--0.96.0--0.97.0.sql -@@ -225,85 +225,3 @@ +@@ -212,85 +212,3 @@ SELECT ok( NOT _opc_exists( $1 ), 'Operator class ' || quote_ident($1) || ' should not exist' ); $$ LANGUAGE SQL; diff --git a/compat/9.4/pgtap--0.96.0--0.97.0.patch b/compat/9.4/pgtap--0.96.0--0.97.0.patch index b1d9784216bc..f8fea308bffb 100644 --- a/compat/9.4/pgtap--0.96.0--0.97.0.patch +++ b/compat/9.4/pgtap--0.96.0--0.97.0.patch @@ -1,6 +1,6 @@ --- sql/pgtap--0.96.0--0.97.0.sql +++ sql/pgtap--0.96.0--0.97.0.sql -@@ -307,78 +307,3 @@ RETURNS TEXT AS $$ +@@ -294,78 +294,3 @@ 'Extension ' || quote_ident($1) || ' should not exist' ); $$ LANGUAGE SQL; diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index e60ae121f36e..9c5ba53a86e0 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -36,7 +36,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $5 || ' constraints'; -@@ -5971,7 +5971,7 @@ +@@ -6158,7 +6158,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -45,7 +45,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -5979,8 +5979,8 @@ +@@ -6166,8 +6166,8 @@ -- 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. @@ -56,7 +56,7 @@ END LOOP; END LOOP; RETURN; -@@ -6049,7 +6049,7 @@ +@@ -6236,7 +6236,7 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -65,7 +65,7 @@ tfaild INTEGER := 0; ffaild INTEGER := 0; tnumb INTEGER := 0; -@@ -6059,7 +6059,7 @@ +@@ -6246,7 +6246,7 @@ BEGIN -- No plan support. PERFORM * FROM no_plan(); @@ -74,7 +74,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -6098,18 +6098,18 @@ +@@ -6285,18 +6285,18 @@ BEGIN BEGIN -- Run the setup functions. @@ -99,7 +99,7 @@ END LOOP; -- Emit the plan. -@@ -6165,11 +6165,11 @@ +@@ -6352,11 +6352,11 @@ END LOOP; -- Run the shutdown functions. @@ -114,7 +114,7 @@ END LOOP; -- Clean up and return. -@@ -7427,7 +7427,7 @@ +@@ -7614,7 +7614,7 @@ DECLARE rec RECORD; BEGIN diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 3f21b04fdc64..b481a30df67d 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -112,7 +112,7 @@ p.prolang AS langoid, p.proisstrict AS is_strict, p.proisagg AS is_agg, -@@ -3609,63 +3647,6 @@ +@@ -3678,63 +3716,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -176,7 +176,7 @@ CREATE OR REPLACE FUNCTION _has_role( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( -@@ -6118,17 +6099,17 @@ +@@ -6305,17 +6286,17 @@ BEGIN -- Run the setup functions. FOR tap IN SELECT * FROM _runem(setup, false) LOOP @@ -197,7 +197,7 @@ END LOOP; -- Emit the plan. -@@ -6167,7 +6148,7 @@ +@@ -6354,7 +6335,7 @@ tok := FALSE; RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname @@ -206,7 +206,7 @@ errmsg := NULL; END IF; END; -@@ -6280,13 +6261,13 @@ +@@ -6467,13 +6448,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -222,7 +222,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6510,7 +6491,7 @@ +@@ -6697,7 +6678,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -231,7 +231,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6605,11 +6586,11 @@ +@@ -6792,11 +6773,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -246,7 +246,7 @@ ); END IF; rownum = rownum + 1; -@@ -6624,9 +6605,9 @@ +@@ -6811,9 +6792,9 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Number of columns or their types differ between the queries' || @@ -259,7 +259,7 @@ END ); END; -@@ -6762,7 +6743,7 @@ +@@ -6949,7 +6930,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -268,7 +268,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6776,8 +6757,8 @@ +@@ -6963,8 +6944,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -279,7 +279,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -6902,9 +6883,9 @@ +@@ -7089,9 +7070,9 @@ DECLARE typeof regtype := pg_typeof($1); BEGIN @@ -292,7 +292,7 @@ END; $$ LANGUAGE plpgsql; -@@ -6925,7 +6906,7 @@ +@@ -7112,7 +7093,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -301,7 +301,7 @@ END LOOP; -- What extra records do we have? -@@ -7093,7 +7074,7 @@ +@@ -7280,7 +7261,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -310,7 +310,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT -@@ -7111,7 +7092,7 @@ +@@ -7298,7 +7279,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -319,7 +319,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) ), -@@ -7144,7 +7125,7 @@ +@@ -7331,7 +7312,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -328,7 +328,7 @@ 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']) ) -@@ -7163,7 +7144,7 @@ +@@ -7350,7 +7331,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -337,7 +337,7 @@ 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']) ) -@@ -7447,10 +7428,12 @@ +@@ -7634,10 +7615,12 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; @@ -353,7 +353,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7597,7 +7580,7 @@ +@@ -7784,7 +7767,7 @@ CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) RETURNS TEXT AS $$ @@ -362,7 +362,7 @@ $$ LANGUAGE SQL; -- operators_are( schema, operators[], description ) -@@ -7606,7 +7589,7 @@ +@@ -7793,7 +7776,7 @@ SELECT _areni( 'operators', ARRAY( @@ -371,7 +371,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7618,7 +7601,7 @@ +@@ -7805,7 +7788,7 @@ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT @@ -380,7 +380,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7639,7 +7622,7 @@ +@@ -7826,7 +7809,7 @@ SELECT _areni( 'operators', ARRAY( @@ -389,7 +389,7 @@ 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) -@@ -7652,7 +7635,7 @@ +@@ -7839,7 +7822,7 @@ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT @@ -398,7 +398,7 @@ 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) -@@ -8355,40 +8338,6 @@ +@@ -8542,40 +8525,6 @@ ); $$ LANGUAGE sql; diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index 0767c845e485..d392c4c41f4f 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -11,8 +11,8 @@ + CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ - SELECT s.a[1]::int * 10000 -@@ -6600,7 +6605,7 @@ + SELECT substring(s.a[1] FROM '[[:digit:]]+')::int * 10000 +@@ -6787,7 +6792,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -21,7 +21,7 @@ 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' || -@@ -6757,7 +6762,7 @@ +@@ -6944,7 +6949,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -30,7 +30,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6966,13 +6971,7 @@ +@@ -7153,13 +7158,7 @@ $$ LANGUAGE sql; -- collect_tap( tap, tap, tap ) @@ -45,7 +45,7 @@ RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); $$ LANGUAGE sql; -@@ -7448,7 +7447,7 @@ +@@ -7635,7 +7634,7 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; diff --git a/compat/install-8.4.patch b/compat/install-8.4.patch index 12c11384ff72..872b66fb1067 100644 --- a/compat/install-8.4.patch +++ b/compat/install-8.4.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -7474,7 +7474,6 @@ +@@ -7661,7 +7661,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -8,7 +8,7 @@ EXCEPT SELECT $3[i] FROM generate_series(1, array_upper($3, 1)) s(i) -@@ -7489,7 +7488,6 @@ +@@ -7676,7 +7675,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -16,7 +16,7 @@ ), $4 ); -@@ -7513,7 +7511,6 @@ +@@ -7700,7 +7698,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = $1 AND n.nspname NOT IN ('pg_catalog', 'information_schema') @@ -24,7 +24,7 @@ EXCEPT SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) -@@ -7527,7 +7524,6 @@ +@@ -7714,7 +7711,6 @@ 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') diff --git a/compat/install-9.0.patch b/compat/install-9.0.patch index b4b57fee4ddc..13cc9af2c134 100644 --- a/compat/install-9.0.patch +++ b/compat/install-9.0.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -3697,7 +3697,7 @@ +@@ -3686,7 +3686,7 @@ AND n.nspname = $1 AND t.typname = $2 AND t.typtype = 'e' @@ -9,7 +9,7 @@ ), $3, $4 -@@ -3725,7 +3725,7 @@ +@@ -3714,7 +3714,7 @@ AND pg_catalog.pg_type_is_visible(t.oid) AND t.typname = $1 AND t.typtype = 'e' @@ -18,7 +18,7 @@ ), $2, $3 -@@ -6031,7 +6031,7 @@ +@@ -6138,7 +6138,7 @@ CREATE OR REPLACE FUNCTION findfuncs( NAME, TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( @@ -27,7 +27,7 @@ FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = $1 -@@ -6048,7 +6048,7 @@ +@@ -6155,7 +6155,7 @@ CREATE OR REPLACE FUNCTION findfuncs( TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( @@ -36,7 +36,7 @@ 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) -@@ -9459,134 +9459,3 @@ +@@ -9646,134 +9646,3 @@ GRANT SELECT ON tap_funky TO PUBLIC; GRANT SELECT ON pg_all_foreign_keys TO PUBLIC; diff --git a/compat/install-9.1.patch b/compat/install-9.1.patch index 0160b7c7a17b..211d05d15488 100644 --- a/compat/install-9.1.patch +++ b/compat/install-9.1.patch @@ -11,7 +11,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6155,10 +6151,6 @@ +@@ -6342,10 +6338,6 @@ -- Something went wrong. Record that fact. errstate := SQLSTATE; errmsg := SQLERRM; diff --git a/compat/install-9.2.patch b/compat/install-9.2.patch index f3869ea0df9f..82da5a497a86 100644 --- a/compat/install-9.2.patch +++ b/compat/install-9.2.patch @@ -14,7 +14,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6163,12 +6158,7 @@ +@@ -6350,12 +6345,7 @@ GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, hint = PG_EXCEPTION_HINT, From 2aab972ed986fb6409d593ac0f7d2242b56355e3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 28 Nov 2016 13:58:02 -0800 Subject: [PATCH 0932/1195] Increment to v0.97.0. --- Changes | 2 +- META.json | 8 ++++---- README.md | 2 +- doc/pgtap.mmd | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Changes b/Changes index fde2d2dcad23..cb273ce244f3 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.96.1 +0.97.0 --------------------------- * Fixed the default description for `hasnt_opclass()` to say "should not exist" instead of "should exist", thanks to Rodolphe Quiédeville (PR #99). diff --git a/META.json b/META.json index d3ec61c70add..816730c18e5b 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "0.96.1", + "version": "0.97.0", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "0.96.1" + "version": "0.97.0" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "0.96.1" + "version": "0.97.0" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "0.96.1" + "version": "0.97.0" } }, "resources": { diff --git a/README.md b/README.md index 14480f1d071f..5905786da9a2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 0.96.1 +pgTAP 0.97.0 ============ [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 1ce9011cd1a8..a107da00586a 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 0.96.1 +pgTAP 0.97.0 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and From e10b6b068af394585f3cb5436f259d4eb6388e7f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 28 Nov 2016 14:04:06 -0800 Subject: [PATCH 0933/1195] Make sure version is correct. --- pgtap.control | 2 +- sql/pgtap--0.96.0--0.97.0.sql.in | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pgtap.control b/pgtap.control index 5321d6016fe9..ce85da974b94 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '0.95.0' +default_version = '0.97.0' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true diff --git a/sql/pgtap--0.96.0--0.97.0.sql.in b/sql/pgtap--0.96.0--0.97.0.sql.in index b5f8fb62db77..b23e271c7097 100644 --- a/sql/pgtap--0.96.0--0.97.0.sql.in +++ b/sql/pgtap--0.96.0--0.97.0.sql.in @@ -1,3 +1,7 @@ +CREATE OR REPLACE FUNCTION pgtap_version() +RETURNS NUMERIC AS 'SELECT 0.97;' +LANGUAGE SQL IMMUTABLE; + -- pg_version_num() CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ From 6821a3232ae29a81c39e2f2353f30b375712c070 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 28 Nov 2016 14:15:58 -0800 Subject: [PATCH 0934/1195] Update diff offsets. --- compat/9.0/pgtap--0.96.0--0.97.0.patch | 2 +- compat/9.4/pgtap--0.96.0--0.97.0.patch | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compat/9.0/pgtap--0.96.0--0.97.0.patch b/compat/9.0/pgtap--0.96.0--0.97.0.patch index 8f40f0c99286..c86930c0decd 100644 --- a/compat/9.0/pgtap--0.96.0--0.97.0.patch +++ b/compat/9.0/pgtap--0.96.0--0.97.0.patch @@ -1,6 +1,6 @@ --- sql/pgtap--0.96.0--0.97.0.sql +++ sql/pgtap--0.96.0--0.97.0.sql -@@ -212,85 +212,3 @@ +@@ -216,85 +216,3 @@ SELECT ok( NOT _opc_exists( $1 ), 'Operator class ' || quote_ident($1) || ' should not exist' ); $$ LANGUAGE SQL; diff --git a/compat/9.4/pgtap--0.96.0--0.97.0.patch b/compat/9.4/pgtap--0.96.0--0.97.0.patch index f8fea308bffb..e213f63c7b15 100644 --- a/compat/9.4/pgtap--0.96.0--0.97.0.patch +++ b/compat/9.4/pgtap--0.96.0--0.97.0.patch @@ -1,6 +1,6 @@ --- sql/pgtap--0.96.0--0.97.0.sql +++ sql/pgtap--0.96.0--0.97.0.sql -@@ -294,78 +294,3 @@ +@@ -298,78 +298,3 @@ 'Extension ' || quote_ident($1) || ' should not exist' ); $$ LANGUAGE SQL; From 7efc3bffa7e4c7238ccb361d7169950e17889d99 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 28 Nov 2016 14:17:19 -0800 Subject: [PATCH 0935/1195] Timestamp v0.97.0. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index cb273ce244f3..6e11b9a42159 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.97.0 +0.97.0 2016-11-28T22:18:29Z --------------------------- * Fixed the default description for `hasnt_opclass()` to say "should not exist" instead of "should exist", thanks to Rodolphe Quiédeville (PR #99). From 7067f1e8e2db08d01d0700d6fefcb9e33d43964c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 28 Nov 2016 14:37:03 -0800 Subject: [PATCH 0936/1195] Increment to v0.98.0. --- Changes | 3 +++ META.json | 8 ++++---- README.md | 2 +- doc/pgtap.mmd | 2 +- pgtap.control | 2 +- sql/pgtap--0.97.0--0.98.0.sql | 3 +++ 6 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 sql/pgtap--0.97.0--0.98.0.sql diff --git a/Changes b/Changes index 6e11b9a42159..4a3ccb94d684 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Revision history for pgTAP ========================== +0.98.0 +--------------------------- + 0.97.0 2016-11-28T22:18:29Z --------------------------- * Fixed the default description for `hasnt_opclass()` to say "should not diff --git a/META.json b/META.json index 816730c18e5b..dee7fc8952f0 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "0.97.0", + "version": "0.98.0", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "0.97.0" + "version": "0.98.0" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "0.97.0" + "version": "0.98.0" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "0.97.0" + "version": "0.98.0" } }, "resources": { diff --git a/README.md b/README.md index 5905786da9a2..2f32f5d6d072 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 0.97.0 +pgTAP 0.98.0 ============ [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index a107da00586a..c3415585256d 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 0.97.0 +pgTAP 0.98.0 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/pgtap.control b/pgtap.control index ce85da974b94..d24cb0849498 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '0.97.0' +default_version = '0.98.0' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true diff --git a/sql/pgtap--0.97.0--0.98.0.sql b/sql/pgtap--0.97.0--0.98.0.sql new file mode 100644 index 000000000000..e4d5eead59a7 --- /dev/null +++ b/sql/pgtap--0.97.0--0.98.0.sql @@ -0,0 +1,3 @@ +CREATE OR REPLACE FUNCTION pgtap_version() +RETURNS NUMERIC AS 'SELECT 0.98;' +LANGUAGE SQL IMMUTABLE; From e2d3e0b79cdb6f9c444cc1ca93c6836b084b2562 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Thu, 8 Dec 2016 11:26:08 -0600 Subject: [PATCH 0937/1195] Switch to using CREATE EXTENSION A simple \i sql/pgtap.sql won't show a problem if extension installation failed. --- test/setup.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/setup.sql b/test/setup.sql index e79981c3b53d..bbd7c10ae113 100644 --- a/test/setup.sql +++ b/test/setup.sql @@ -19,4 +19,5 @@ BEGIN; -- CREATE SCHEMA tap; SET search_path TO tap,public; -- Load the TAP functions. -\i sql/pgtap.sql +--\i sql/pgtap.sql +CREATE EXTENSION pgtap; From 3458f41623692c52dddb260b9f76e63a875796e5 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Thu, 8 Dec 2016 11:35:41 -0600 Subject: [PATCH 0938/1195] Ensure versioned files get installed --- Makefile | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 3ff4149a558e..60770e25df4a 100644 --- a/Makefile +++ b/Makefile @@ -3,17 +3,18 @@ EXTENSION = $(MAINEXT) EXTVERSION = $(shell grep default_version $(MAINEXT).control | \ sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") NUMVERSION = $(shell echo $(EXTVERSION) | sed -e 's/\([[:digit:]]*[.][[:digit:]]*\).*/\1/') +VERSION_FILES = sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql _IN_FILES = $(wildcard sql/*--*.sql.in) _IN_PATCHED = $(_IN_FILES:.in=) TESTS = $(wildcard test/sql/*.sql) -EXTRA_CLEAN = sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql doc/*.html +EXTRA_CLEAN = $(VERSION_FILES) sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql doc/*.html DOCS = doc/pgtap.mmd REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --inputdir=test --load-language=plpgsql PG_CONFIG ?= pg_config # sort is necessary to remove dupes so install won't complain -DATA = $(sort $(wildcard sql/*--*.sql) $(_IN_PATCHED)) # NOTE! This gets reset below! +DATA = $(sort $(wildcard sql/*--*.sql) $(VERSION_FILES) $(_IN_PATCHED)) # NOTE! This gets reset below! ifdef NO_PGXS top_builddir = ../.. @@ -97,9 +98,7 @@ sql/$(MAINEXT)-schema--$(EXTVERSION).sql: sql/$(MAINEXT)-schema.sql cp $< $@ # I think this can go away... -DATA = $(sort $(wildcard sql/*--*.sql) $(_IN_PATCHED)) - -EXTRA_CLEAN += sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql +DATA = $(sort $(wildcard sql/*--*.sql) $(VERSION_FILES) $(_IN_PATCHED)) endif sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql @@ -171,7 +170,7 @@ sql/pgtap-schema.sql: sql/pgtap.sql.in rm sql/pgtap-schema.tmp # Make sure that we build the regression tests. -installcheck: test/setup.sql +installcheck: test/setup.sql install # In addition to installcheck, one can also run the tests through pg_prove. test: test/setup.sql @@ -181,3 +180,6 @@ html: multimarkdown doc/pgtap.mmd > doc/pgtap.html ./tocgen doc/pgtap.html 2> doc/toc.html perl -MPod::Simple::XHTML -E "my \$$p = Pod::Simple::XHTML->new; \$$p->html_header_tags(''); \$$p->strip_verbatim_indent(sub { my \$$l = shift; (my \$$i = \$$l->[0]) =~ s/\\S.*//; \$$i }); \$$p->parse_from_file('`perldoc -l pg_prove`')" > doc/pg_prove.html + +# To use this, do make print-VARIABLE_NAME +print-% : ; $(info $* is $(flavor $*) variable set to "$($*)") @true From 6ef877ff29323ff21f2de74dd3773b681634c47a Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Thu, 8 Dec 2016 11:46:22 -0600 Subject: [PATCH 0939/1195] Revert CREATE EXTENSION so things will work in older versions --- test/setup.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/setup.sql b/test/setup.sql index bbd7c10ae113..6cbbc964a5cc 100644 --- a/test/setup.sql +++ b/test/setup.sql @@ -19,5 +19,5 @@ BEGIN; -- CREATE SCHEMA tap; SET search_path TO tap,public; -- Load the TAP functions. ---\i sql/pgtap.sql -CREATE EXTENSION pgtap; +\i sql/pgtap.sql +--CREATE EXTENSION pgtap; From b173e64bf78eac5e041e165e78890e67d76c2546 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Thu, 8 Dec 2016 11:47:22 -0600 Subject: [PATCH 0940/1195] installcheck depending on install breaks travis --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 60770e25df4a..847f1a2f21c9 100644 --- a/Makefile +++ b/Makefile @@ -170,7 +170,7 @@ sql/pgtap-schema.sql: sql/pgtap.sql.in rm sql/pgtap-schema.tmp # Make sure that we build the regression tests. -installcheck: test/setup.sql install +installcheck: test/setup.sql # In addition to installcheck, one can also run the tests through pg_prove. test: test/setup.sql From cd2027e913cfb2f486cd13a69cd34a46e96000df Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Tue, 13 Dec 2016 13:32:36 -0600 Subject: [PATCH 0941/1195] Clean up patch *.orig files --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 847f1a2f21c9..02d097ddb29e 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ _IN_FILES = $(wildcard sql/*--*.sql.in) _IN_PATCHED = $(_IN_FILES:.in=) TESTS = $(wildcard test/sql/*.sql) EXTRA_CLEAN = $(VERSION_FILES) sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql doc/*.html +EXTRA_CLEAN += $(wildcard sql/*.orig) # These are files left behind by patch DOCS = doc/pgtap.mmd REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --inputdir=test --load-language=plpgsql From ced4ecde6b4a175e755aa87910c541dfacffd309 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Tue, 13 Dec 2016 15:30:09 -0600 Subject: [PATCH 0942/1195] Install base files regardless of PG version --- Makefile | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 02d097ddb29e..c0ebf3b4a941 100644 --- a/Makefile +++ b/Makefile @@ -83,12 +83,9 @@ OSNAME := $(shell $(SHELL) ./getos.sh) # Make sure we build these. EXTRA_CLEAN += $(_IN_PATCHED) -all: $(_IN_PATCHED) sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql - -# Add extension build targets on 9.1 and up. -ifeq ($(shell echo $(VERSION) | grep -qE "8[.]|9[.]0" && echo no || echo yes),yes) -all: sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql +all: $(_IN_PATCHED) sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql +# Note: The targets for these dependencies are below sql/$(MAINEXT)--$(EXTVERSION).sql: sql/$(MAINEXT).sql cp $< $@ @@ -98,10 +95,7 @@ sql/$(MAINEXT)-core--$(EXTVERSION).sql: sql/$(MAINEXT)-core.sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql: sql/$(MAINEXT)-schema.sql cp $< $@ -# I think this can go away... -DATA = $(sort $(wildcard sql/*--*.sql) $(VERSION_FILES) $(_IN_PATCHED)) -endif - +# TODO: replace pgtap with $(MAINEXT) or just stop using $(MAINEXT) sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql cp $< $@ ifeq ($(shell echo $(VERSION) | grep -qE "9[.][01234]|8[.][1234]" && echo yes || echo no),yes) From e2c62a86d01a5a5654c8a647038b55559cf841ad Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Tue, 13 Dec 2016 15:38:23 -0600 Subject: [PATCH 0943/1195] Fix pre-9.1 install --- Makefile | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index c0ebf3b4a941..45838a165bd7 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ EXTVERSION = $(shell grep default_version $(MAINEXT).control | \ sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") NUMVERSION = $(shell echo $(EXTVERSION) | sed -e 's/\([[:digit:]]*[.][[:digit:]]*\).*/\1/') VERSION_FILES = sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql +BASE_FILES = $(subst --$(EXTVERSION),,$(VERSION_FILES)) _IN_FILES = $(wildcard sql/*--*.sql.in) _IN_PATCHED = $(_IN_FILES:.in=) TESTS = $(wildcard test/sql/*.sql) @@ -15,7 +16,7 @@ REGRESS_OPTS = --inputdir=test --load-language=plpgsql PG_CONFIG ?= pg_config # sort is necessary to remove dupes so install won't complain -DATA = $(sort $(wildcard sql/*--*.sql) $(VERSION_FILES) $(_IN_PATCHED)) # NOTE! This gets reset below! +DATA = $(BASE_FILES) # Set to something to make PGXS happy ifdef NO_PGXS top_builddir = ../.. @@ -83,9 +84,12 @@ OSNAME := $(shell $(SHELL) ./getos.sh) # Make sure we build these. EXTRA_CLEAN += $(_IN_PATCHED) -all: $(_IN_PATCHED) sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql +all: $(_IN_PATCHED) sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql + +# Add extension build targets on 9.1 and up. +ifeq ($(shell echo $(VERSION) | grep -qE "8[.]|9[.]0" && echo no || echo yes),yes) +all: sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql -# Note: The targets for these dependencies are below sql/$(MAINEXT)--$(EXTVERSION).sql: sql/$(MAINEXT).sql cp $< $@ @@ -95,7 +99,9 @@ sql/$(MAINEXT)-core--$(EXTVERSION).sql: sql/$(MAINEXT)-core.sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql: sql/$(MAINEXT)-schema.sql cp $< $@ -# TODO: replace pgtap with $(MAINEXT) or just stop using $(MAINEXT) +DATA = $(sort $(wildcard sql/*--*.sql) $(BASE_FILES) $(VERSION_FILES) $(_IN_PATCHED)) +endif + sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql cp $< $@ ifeq ($(shell echo $(VERSION) | grep -qE "9[.][01234]|8[.][1234]" && echo yes || echo no),yes) From b02815ff24b85cc9e9f22468284a43cab23c8cf9 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Tue, 13 Dec 2016 15:51:09 -0600 Subject: [PATCH 0944/1195] Need to install the uninstall file too. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 45838a165bd7..d137670c65b3 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ EXTVERSION = $(shell grep default_version $(MAINEXT).control | \ sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") NUMVERSION = $(shell echo $(EXTVERSION) | sed -e 's/\([[:digit:]]*[.][[:digit:]]*\).*/\1/') VERSION_FILES = sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql -BASE_FILES = $(subst --$(EXTVERSION),,$(VERSION_FILES)) +BASE_FILES = $(subst --$(EXTVERSION),,$(VERSION_FILES)) sql/uninstall_$(MAINEXT).sql _IN_FILES = $(wildcard sql/*--*.sql.in) _IN_PATCHED = $(_IN_FILES:.in=) TESTS = $(wildcard test/sql/*.sql) From 41bda8dcc8cf057cbe42e5284e216816fb70fb27 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 4 Mar 2017 15:19:29 -0800 Subject: [PATCH 0945/1195] Note fix to close #131. --- Changes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changes b/Changes index 4a3ccb94d684..ff6e7b021e0a 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,9 @@ Revision history for pgTAP 0.98.0 --------------------------- +* `make install` once again properly installs the versioned SQL file required + by `CREATE EXTENSION`. Thanks to Keith Fiske for the report (Issue #131) and + Jim Nasby for the fix (Issue #132). 0.97.0 2016-11-28T22:18:29Z --------------------------- From 69c7a82cec0a30764acd13bddd4ffc7c29f9e772 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 24 Jul 2017 11:42:48 -0400 Subject: [PATCH 0946/1195] Remove references to Epic. It appears to have disappeared from the internet. Closes #140. --- Changes | 3 +++ doc/pgtap.mmd | 15 +++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Changes b/Changes index ff6e7b021e0a..f08ed1a56799 100644 --- a/Changes +++ b/Changes @@ -6,6 +6,9 @@ Revision history for pgTAP * `make install` once again properly installs the versioned SQL file required by `CREATE EXTENSION`. Thanks to Keith Fiske for the report (Issue #131) and Jim Nasby for the fix (Issue #132). +* Removed references to the Epic test framework, since its domain + (`epictest.org`) has expired and the project appears to no longer be + available online. Thanks to Andreas Brekken for the report (#140). 0.97.0 2016-11-28T22:18:29Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index c3415585256d..242997768eee 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -315,12 +315,12 @@ What a sweet unit! If you're used to xUnit testing frameworks, you can collect all of your tests into database functions and run them all at once with `runtests()`. This is -similar to how [PGUnit](http://en.dklab.ru/lib/dklab_pgunit/) and -[Epic](http://www.epictest.org/) work. The `runtests()` function does all the -work of finding and running your test functions in individual transactions. It -even supports setup and teardown functions. To use it, write your unit test -functions so that they return a set of text results, and then use the pgTAP -assertion functions to return TAP values, like so: +similar to how [PGUnit](http://en.dklab.ru/lib/dklab_pgunit/)s. The +`runtests()` function does all the work of finding and running your test +functions in individual transactions. It even supports setup and teardown +functions. To use it, write your unit test functions so that they return a set +of text results, and then use the pgTAP assertion functions to return TAP +values, like so: CREATE OR REPLACE FUNCTION setup_insert( ) RETURNS SETOF TEXT AS $$ @@ -7523,8 +7523,7 @@ failing tests. If you'd like pgTAP to plan, run all of your test functions, and finish, all in one fell swoop, use `runtests()`. This most closely emulates the xUnit testing environment, similar to the functionality of -[PGUnit](http://en.dklab.ru/lib/dklab_pgunit/) and -[Epic](http://www.epictest.org/). Example: +[PGUnit](http://en.dklab.ru/lib/dklab_pgunit/). Example: SELECT * FROM runtests( 'testschema', '^test' ); From 5c23cf854f73fa76d0ff252f85d7d2dfd40f6c17 Mon Sep 17 00:00:00 2001 From: Erik Rijkers Date: Tue, 15 Aug 2017 14:09:04 -0400 Subject: [PATCH 0947/1195] Fix version tests on v10. --- Changes | 2 ++ test/sql/util.sql | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index f08ed1a56799..7f9c965e5492 100644 --- a/Changes +++ b/Changes @@ -9,6 +9,8 @@ Revision history for pgTAP * Removed references to the Epic test framework, since its domain (`epictest.org`) has expired and the project appears to no longer be available online. Thanks to Andreas Brekken for the report (#140). +* Fix failing version tests on PostgreSQL v10. Thanks to Erik Rijkers + for the patch. 0.97.0 2016-11-28T22:18:29Z --------------------------- diff --git a/test/sql/util.sql b/test/sql/util.sql index 2f46507d1c82..600be48540b3 100644 --- a/test/sql/util.sql +++ b/test/sql/util.sql @@ -16,7 +16,10 @@ SELECT is( ); SELECT matches( pg_version(), - '^[89][.][[:digit:]]{1,2}([.][[:digit:]]{1,2}|devel|(alpha|beta|rc)[[:digit:]]+)$', + '(?x)' + '(?: ^[89][.][[:digit:]]{1,2}([.][[:digit:]]{1,2}|devel|(alpha|beta|rc)[[:digit:]]+) )' + '|' + '(?: ^1[[:digit:]] (?: [.] [[:digit:]]{1,2} | devel | (?:alpha|beta|rc)[[:digit:]]+))', 'pg_version() should work' ); @@ -36,7 +39,7 @@ SELECT is( ); SELECT matches( pg_version_num()::text, - '^[89][[:digit:]]{4}$', + '^[89][[:digit:]]{4}$|^1[[:digit:]]{5}$', 'pg_version_num() should be correct' ); From 5af71cd6f73497ff0b6784409e5cbcf48bc0c1e1 Mon Sep 17 00:00:00 2001 From: Erik Rijkers Date: Tue, 15 Aug 2017 14:11:41 -0400 Subject: [PATCH 0948/1195] Account for varying row order in foreign table test. Also, fix a pasto in the hastap tests. --- Changes | 2 ++ test/sql/aretap.sql | 4 ++-- test/sql/hastap.sql | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 7f9c965e5492..83eca99bf756 100644 --- a/Changes +++ b/Changes @@ -11,6 +11,8 @@ Revision history for pgTAP available online. Thanks to Andreas Brekken for the report (#140). * Fix failing version tests on PostgreSQL v10. Thanks to Erik Rijkers for the patch. +* Improved foreign tables test to account for varying message order for + diagnostic output. Thanks to Erik Rijkers for the patch. 0.97.0 2016-11-28T22:18:29Z --------------------------- diff --git a/test/sql/aretap.sql b/test/sql/aretap.sql index afc253db1fe4..e3c63d547a70 100644 --- a/test/sql/aretap.sql +++ b/test/sql/aretap.sql @@ -1837,8 +1837,8 @@ BEGIN 'foreign_tables_are(schema, tables) extra and missing', 'Schema public should have the correct foreign tables', ' Extra foreign tables: - ft_foo - ft_bar + ft_(?:bar|foo) + ft_(?:foo|bar) Missing foreign tables: ba[rz] ba[rz]', diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index 69c146c6af68..c7fa4f3a85f4 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -2469,7 +2469,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( has_materialized_view( 'mview', 'yowza' ), true, - 'has_materialized_view(materialized_viewiew, desc)', + 'has_materialized_view(materialized_view, desc)', 'yowza', '' ) AS b LOOP @@ -2583,7 +2583,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( hasnt_materialized_view( 'mview', 'yowza' ), false, - 'hasnt_materialized_view(materialized_viewiew, desc)', + 'hasnt_materialized_view(materialized_view, desc)', 'yowza', '' ) AS b LOOP From e5b6d6e18a7132b3cabc6a135c2a8d4ce9317727 Mon Sep 17 00:00:00 2001 From: Glen Huang Date: Fri, 17 Mar 2017 11:06:57 +0800 Subject: [PATCH 0949/1195] Update install-9.1.patch --- compat/install-9.1.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compat/install-9.1.patch b/compat/install-9.1.patch index 211d05d15488..c9a85fc78b69 100644 --- a/compat/install-9.1.patch +++ b/compat/install-9.1.patch @@ -2,7 +2,7 @@ +++ sql/pgtap.sql @@ -781,10 +781,6 @@ RETURN ok( TRUE, descr ); - EXCEPTION WHEN OTHERS THEN + EXCEPTION WHEN OTHERS OR ASSERT_FAILURE THEN -- There should have been no exception. - GET STACKED DIAGNOSTICS - detail = PG_EXCEPTION_DETAIL, From 82898acfcec24e780bd3b083c66066d41b8846ea Mon Sep 17 00:00:00 2001 From: "Aaron W. Swenson" Date: Sun, 5 Nov 2017 09:08:03 -0500 Subject: [PATCH 0950/1195] PGVERSION List Refresh Travis CI/Ubuntu no longer has 8.2 and 8.3 available resulting in build failures. Added the latest stable release: 10. --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b41ccae9dcb0..e265e6ac174e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,6 @@ before_install: - sudo sh ./apt.postgresql.org.sh - sudo rm -vf /etc/apt/sources.list.d/pgdg-source.list env: - - PGVERSION=8.2 - - PGVERSION=8.3 - PGVERSION=8.4 - PGVERSION=9.0 - PGVERSION=9.1 @@ -15,4 +13,5 @@ env: - PGVERSION=9.4 - PGVERSION=9.5 - PGVERSION=9.6 + - PGVERSION=10 script: bash ./pg-travis-test.sh From ec2c82388cc3488ed0841b694647a50edcd927bf Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 5 Nov 2017 10:53:11 -0500 Subject: [PATCH 0951/1195] Clarify use of :schema testing extensions. Resolves #121. --- Changes | 4 ++++ doc/pgtap.mmd | 32 +++++++++++++++++--------------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Changes b/Changes index 83eca99bf756..5ed4c31128c2 100644 --- a/Changes +++ b/Changes @@ -13,6 +13,10 @@ Revision history for pgTAP for the patch. * Improved foreign tables test to account for varying message order for diagnostic output. Thanks to Erik Rijkers for the patch. +* Clarified the use of the `:schema` parameter to the extension-testing + functions in the documentation. Required since extensions do not belong to + schemas, but their objects may be specifically created in a schema. Thanks to + @decibel for the report (#121)! 0.97.0 2016-11-28T22:18:29Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 242997768eee..b02322a39ae0 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2560,7 +2560,7 @@ missing operators, like so: **Parameters** `:schema` -: Name of a schema in which to find extensions. +: Name of a schema associated with the extensions. `:extensions` : An array of extension names. @@ -2568,11 +2568,14 @@ missing operators, like so: `:description` : A short description of the test. -This function tests that all of the extensions in the named schema, or that -are visible in the search path, are only the extensions that *should* be -there. If the `:schema` argument is omitted, all extensions will be checked. -If the description is omitted, a generally useful default description will be -generated. Example: +This function tests all of the extensions that should be present. If `:schema` +is specified, it will test only for extensions associated the named schema (via +the `schema` parameter in the extension's control file, ov the `WITH SCHEMA` +clause of the +[CREATE EXTENSION](https://www.postgresql.org/docs/current/static/extend-extensions.html) +statement). Otherwise it will check for all extension in the database, +including pgTAP itself. If the description is omitted, a generally useful +default description will be generated. Example: SELECT extensions_are( 'myschema', @@ -3836,7 +3839,7 @@ procedural language. **Parameters** `:schema` -: Schema in which to find the table. +: Schema in which the extension's objects were installed. `:extension` : Name of an extension. @@ -3844,13 +3847,12 @@ procedural language. `:description` : A short description of the test. -This function tests whether or not an extension exists in the -database. The first argument is the schema name, the second the -extension name, and the third the test description. If the schema is -omitted, the extension must be visible in the search path. If the test -description is omitted, it will be set to "Extension `:extension` -should exist". -Example: +This function tests whether or not an extension exists in the database. The +first argument is the schema in which the extension objects were installed, the +second the extension name, and the third the test description. If the schema is +omitted, the may be associated with any schema or no schema. If the test +description is omitted, it will be set to "Extension `:extension` should +exist". Example: SELECT has_extension('public', 'pgtap'); @@ -3864,7 +3866,7 @@ Example: **Parameters** `:schema` -: Schema in which to find the table. +: Schema in which the extension's objects would be installed. `:extension` : Name of an extension. From 36a77cbeba5cb6dbedb687a75f387f6bd81e6dea Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 5 Nov 2017 11:15:11 -0500 Subject: [PATCH 0952/1195] Make the test target .PHONY. Resolves #112. --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index d137670c65b3..31289b31f5b2 100644 --- a/Makefile +++ b/Makefile @@ -82,6 +82,8 @@ endif # Determine the OS. Borrowed from Perl's Configure. OSNAME := $(shell $(SHELL) ./getos.sh) +.PHONY: test + # Make sure we build these. EXTRA_CLEAN += $(_IN_PATCHED) all: $(_IN_PATCHED) sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql From eb21ab7172ab5173c46da033b45348530f3459ef Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 5 Nov 2017 13:25:49 -0500 Subject: [PATCH 0953/1195] Recognize partitioned tables. Just like any other table. Required for Postgres 10. --- Changes | 7 +- doc/pgtap.mmd | 74 +- sql/pgtap--0.97.0--0.98.0.sql | 231 +++++ sql/pgtap.sql.in | 96 ++- test/expected/hastap.out | 1498 +++++++++++++++++---------------- test/expected/ownership.out | 707 ++++++++-------- test/sql/aretap.sql | 19 +- test/sql/hastap.sql | 53 +- test/sql/ownership.sql | 95 ++- 9 files changed, 1625 insertions(+), 1155 deletions(-) diff --git a/Changes b/Changes index 5ed4c31128c2..449702dfd681 100644 --- a/Changes +++ b/Changes @@ -17,6 +17,11 @@ Revision history for pgTAP functions in the documentation. Required since extensions do not belong to schemas, but their objects may be specifically created in a schema. Thanks to @decibel for the report (#121)! +* Updated the table-testing functions to recognized partitioned tables: + + `has_table()` + + `hasnt_table()` + + `tables_are()` + + `table_owner_is()` 0.97.0 2016-11-28T22:18:29Z --------------------------- @@ -59,7 +64,7 @@ Revision history for pgTAP 0.95.0 2015-03-20T20:31:41Z --------------------------- -* Added materialized-view testing assertions functions: +* Added materialized view-testing assertion functions: + `materialized_views_are()` + `has_materialized_view()` + `hasnt_materialized_view()` diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index b02322a39ae0..3bdc90952f08 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1937,11 +1937,12 @@ missing views, like so: `:description` : A short description of the test. -This function tests that all of the materialized views in the named schema, or that are -visible in the search path, are only the materialized views that *should* be there. If the -`:schema` argument is omitted, materialized views will be sought in the search path, -excluding `pg_catalog` and `information_schema` If the description is omitted, -a generally useful default description will be generated. Example: +This function tests that all of the materialized views in the named schema, or +that are visible in the search path, are only the materialized views that +*should* be there. If the `:schema` argument is omitted, materialized views +will be sought in the search path, excluding `pg_catalog` and +`information_schema` If the description is omitted, a generally useful default +description will be generated. Example: SELECT materialized_views_are( 'myschema', @@ -2333,10 +2334,10 @@ missing opclasses, like so: : A short description of the test. This function tests that all of the rules on the named relation are only the -rules that *should* be on that relation (a table, view or a materialized view). If the `:schema` -argument is omitted, the rules must be visible in the search path, excluding -`pg_catalog` and `information_schema`. If the description is omitted, a -generally useful default description will be generated. Example: +rules that *should* be on that relation (a table, view or a materialized view). +If the `:schema` argument is omitted, the rules must be visible in the search +path, excluding `pg_catalog` and `information_schema`. If the description is +omitted, a generally useful default description will be generated. Example: SELECT rules_are( 'myschema', @@ -2695,11 +2696,11 @@ specified schema does *not* exist. `:description` : A short description of the test. -This function tests whether or not a relation exists in the database. -Relations are tables, views, materialized views, seqences, composite types, foreign tables, and -toast tables. The first argument is a schema name, the second is a relation -name, and the third is the test description. If you omit the schema, the -relation must be visible in the search path. Example: +This function tests whether or not a relation exists in the database. Relations +are tables, views, materialized views, seqences, composite types, foreign +tables, and toast tables. The first argument is a schema name, the second is a +relation name, and the third is the test description. If you omit the schema, +the relation must be visible in the search path. Example: SELECT has_relation('myschema', 'somerelation'); @@ -2842,10 +2843,10 @@ specified view does *not* exist. `:description` : A short description of the test. -This function tests whether or not a materialized view exists in the database. The first -argument is a schema name, the second is a materialized view name, and the third is the -test description. If you omit the schema, the materialized view must be visible in the -search path. Example: +This function tests whether or not a materialized view exists in the database. +The first argument is a schema name, the second is a materialized view name, +and the third is the test description. If you omit the schema, the materialized +view must be visible in the search path. Example: SELECT has_materialized_view('myschema', 'some_materialized_view'); @@ -3907,10 +3908,10 @@ So we have the assertions to validate 'em. `:description` : A short description of the test. -Tests whether or not a column exists in a given table, view, materialized view or composite -type. The first argument is the schema name, the second the table name, the -third the column name, and the fourth is the test description. If the schema -is omitted, the table must be visible in the search path. If the test +Tests whether or not a column exists in a given table, view, materialized view +or composite type. The first argument is the schema name, the second the table +name, the third the column name, and the fourth is the test description. If the +schema is omitted, the table must be visible in the search path. If the test description is omitted, it will be set to "Column `:table.:column` should exist". @@ -3935,8 +3936,8 @@ exist". : A short description of the test. This function is the inverse of `has_column()`. The test passes if the -specified column does *not* exist in the specified table, view, materialized view or composite -type. +specified column does *not* exist in the specified table, view, materialized +view or composite type. ### `col_not_null()` ### @@ -5798,9 +5799,10 @@ the diagnostics will look something like: `:description` : A short description of the test. -Tests the ownership of a relation. Relations are tables, views, materialized views, seqences, -composite types, foreign tables, and toast tables. If the `:description` -argument is omitted, an appropriate description will be created. Examples: +Tests the ownership of a relation. Relations are tables, views, materialized +views, sequences, composite types, foreign tables, and toast tables. If the +`:description` argument is omitted, an appropriate description will be created. +Examples: SELECT relation_owner_is( 'public', 'mytable', 'someuser', @@ -5934,24 +5936,24 @@ diagnostics will look something like: `:description` : A short description of the test. -Tests the ownership of a materialized view. If the `:description` argument is omitted, an -appropriate description will be created. Examples: +Tests the ownership of a materialized view. If the `:description` argument is +omitted, an appropriate description will be created. Examples: SELECT view_owner_is( - 'public', 'mymaterializedview', 'someuser', - 'mymaterializedview should be owned by someuser' + 'public', 'my_matview', 'someuser', + 'my_matview should be owned by someuser' ); SELECT materialized_view_owner_is( 'widgets', current_user ); -In the event that the test fails because the materialized view in question does not -actually exist or is not visible, you will see an appropriate diagnostic such -as: +In the event that the test fails because the materialized view in question does +not actually exist or is not visible, you will see an appropriate diagnostic +such as: # Failed test 16: "Materialized view foo should be owned by www" # Materialized view foo does not exist -If the test fails because the materialized view is not owned by the specified user, the -diagnostics will look something like: +If the test fails because the materialized view is not owned by the specified +user, the diagnostics will look something like: # Failed test 17: "Materialized view bar should be owned by root" # have: postgres diff --git a/sql/pgtap--0.97.0--0.98.0.sql b/sql/pgtap--0.97.0--0.98.0.sql index e4d5eead59a7..60878ff6c9ea 100644 --- a/sql/pgtap--0.97.0--0.98.0.sql +++ b/sql/pgtap--0.97.0--0.98.0.sql @@ -1,3 +1,234 @@ CREATE OR REPLACE FUNCTION pgtap_version() RETURNS NUMERIC AS 'SELECT 0.98;' LANGUAGE SQL IMMUTABLE; + +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; + +-- 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; + +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; + +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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 198ad7ab2d8e..8b31811f5b73 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -977,40 +977,50 @@ RETURNS TEXT AS $$ SELECT hasnt_relation( $1, 'Relation ' || quote_ident($1) || ' should not exist' ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _rexists ( CHAR, NAME, NAME ) +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 = $1 + WHERE c.relkind = ANY($1) AND n.nspname = $2 AND c.relname = $3 ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _rexists ( CHAR, NAME ) +CREATE OR REPLACE FUNCTION _rexists ( CHAR[], NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( SELECT true FROM pg_catalog.pg_class c - WHERE c.relkind = $1 + 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', $1, $2 ), $3 ); + 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', $1, $2 ), + _rexists( '{r,p}'::char[], $1, $2 ), 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' ); $$ LANGUAGE SQL; @@ -1018,7 +1028,7 @@ $$ LANGUAGE SQL; -- has_table( table, description ) CREATE OR REPLACE FUNCTION has_table ( NAME, TEXT ) RETURNS TEXT AS $$ - SELECT ok( _rexists( 'r', $1 ), $2 ); + SELECT ok( _rexists( '{r,p}'::char[], $1 ), $2 ); $$ LANGUAGE SQL; -- has_table( table ) @@ -1030,14 +1040,14 @@ $$ LANGUAGE SQL; -- hasnt_table( schema, table, description ) CREATE OR REPLACE FUNCTION hasnt_table ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ - SELECT ok( NOT _rexists( 'r', $1, $2 ), $3 ); + 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', $1, $2 ), + NOT _rexists( '{r,p}'::char[], $1, $2 ), 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' ); $$ LANGUAGE SQL; @@ -1045,7 +1055,7 @@ $$ LANGUAGE SQL; -- hasnt_table( table, description ) CREATE OR REPLACE FUNCTION hasnt_table ( NAME, TEXT ) RETURNS TEXT AS $$ - SELECT ok( NOT _rexists( 'r', $1 ), $2 ); + SELECT ok( NOT _rexists( '{r,p}'::char[], $1 ), $2 ); $$ LANGUAGE SQL; -- hasnt_table( table ) @@ -4402,13 +4412,13 @@ RETURNS TEXT AS $$ SELECT schemas_are( $1, 'There should be the correct schemas' ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _extras ( CHAR, NAME, NAME[] ) +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 = $1 + 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 @@ -4417,7 +4427,7 @@ RETURNS NAME[] AS $$ ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _extras ( CHAR, NAME[] ) +CREATE OR REPLACE FUNCTION _extras ( CHAR[], NAME[] ) RETURNS NAME[] AS $$ SELECT ARRAY( SELECT c.relname @@ -4425,7 +4435,7 @@ RETURNS NAME[] AS $$ 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 = $1 + 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] @@ -4433,7 +4443,17 @@ RETURNS NAME[] AS $$ ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _missing ( CHAR, NAME, NAME[] ) +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] @@ -4442,12 +4462,12 @@ RETURNS NAME[] AS $$ SELECT c.relname FROM pg_catalog.pg_namespace n JOIN pg_catalog.pg_class c ON n.oid = c.relnamespace - WHERE c.relkind = $1 + WHERE c.relkind = ANY($1) AND n.nspname = $2 ); $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _missing ( CHAR, NAME[] ) +CREATE OR REPLACE FUNCTION _missing ( CHAR[], NAME[] ) RETURNS NAME[] AS $$ SELECT ARRAY( SELECT $2[i] @@ -4458,27 +4478,37 @@ RETURNS NAME[] AS $$ 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 = $1 + 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', $1, $2), _missing('r', $1, $2), $3); + 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', $1), _missing('r', $1), $2); + 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', $1, $2), _missing('r', $1, $2), + 'tables', _extras('{r,p}'::char[], $1, $2), _missing('{r,p}'::char[], $1, $2), 'Schema ' || quote_ident($1) || ' should have the correct tables' ); $$ LANGUAGE SQL; @@ -4487,7 +4517,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION tables_are ( NAME[] ) RETURNS TEXT AS $$ SELECT _are( - 'tables', _extras('r', $1), _missing('r', $1), + '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; @@ -8099,30 +8129,40 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION _get_rel_owner ( CHAR, NAME, NAME ) +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 = $1 + WHERE c.relkind = ANY($1) AND n.nspname = $2 AND c.relname = $3 $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _get_rel_owner ( CHAR, NAME ) +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 = $1 + 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'::char, $1, $2); + owner NAME := _get_rel_owner('{r,p}'::char[], $1, $2); BEGIN -- Make sure the table exists. IF owner IS NULL THEN @@ -8148,7 +8188,7 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION table_owner_is ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ DECLARE - owner NAME := _get_rel_owner('r'::char, $1); + owner NAME := _get_rel_owner('{r,p}'::char[], $1); BEGIN -- Make sure the table exists. IF owner IS NULL THEN diff --git a/test/expected/hastap.out b/test/expected/hastap.out index 5e96a3486fb7..61c64569a860 100644 --- a/test/expected/hastap.out +++ b/test/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..830 +1..842 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -72,761 +72,773 @@ ok 69 - has_table(sch, view, desc) should have the proper diagnostics ok 70 - has_table(type, desc) should fail ok 71 - has_table(type, desc) should have the proper description ok 72 - has_table(type, desc) should have the proper diagnostics -ok 73 - hasnt_table(non-existent table) should pass -ok 74 - hasnt_table(non-existent table) should have the proper description -ok 75 - hasnt_table(non-existent table) should have the proper diagnostics -ok 76 - hasnt_table(non-existent schema, tab) should pass -ok 77 - hasnt_table(non-existent schema, tab) should have the proper description -ok 78 - hasnt_table(non-existent schema, tab) should have the proper diagnostics -ok 79 - hasnt_table(non-existent table, desc) should pass -ok 80 - hasnt_table(non-existent table, desc) should have the proper description -ok 81 - hasnt_table(non-existent table, desc) should have the proper diagnostics -ok 82 - hasnt_table(sch, non-existent tab, desc) should pass -ok 83 - hasnt_table(sch, non-existent tab, desc) should have the proper description -ok 84 - hasnt_table(sch, non-existent tab, desc) should have the proper diagnostics -ok 85 - hasnt_table(tab, desc) should fail -ok 86 - hasnt_table(tab, desc) should have the proper description -ok 87 - hasnt_table(tab, desc) should have the proper diagnostics -ok 88 - hasnt_table(sch, tab, desc) should fail -ok 89 - hasnt_table(sch, tab, desc) should have the proper description -ok 90 - hasnt_table(sch, tab, desc) should have the proper diagnostics -ok 91 - has_view(non-existent view) should fail -ok 92 - has_view(non-existent view) should have the proper description -ok 93 - has_view(non-existent view) should have the proper diagnostics -ok 94 - has_view(non-existent view, desc) should fail -ok 95 - has_view(non-existent view, desc) should have the proper description -ok 96 - has_view(non-existent view, desc) should have the proper diagnostics -ok 97 - has_view(sch, non-existtent view, desc) should fail -ok 98 - has_view(sch, non-existtent view, desc) should have the proper description -ok 99 - has_view(sch, non-existtent view, desc) should have the proper diagnostics -ok 100 - has_view(view, desc) should pass -ok 101 - has_view(view, desc) should have the proper description -ok 102 - has_view(view, desc) should have the proper diagnostics -ok 103 - has_view(sch, view, desc) should pass -ok 104 - has_view(sch, view, desc) should have the proper description -ok 105 - has_view(sch, view, desc) should have the proper diagnostics -ok 106 - hasnt_view(non-existent view) should pass -ok 107 - hasnt_view(non-existent view) should have the proper description -ok 108 - hasnt_view(non-existent view) should have the proper diagnostics -ok 109 - hasnt_view(non-existent view, desc) should pass -ok 110 - hasnt_view(non-existent view, desc) should have the proper description -ok 111 - hasnt_view(non-existent view, desc) should have the proper diagnostics -ok 112 - hasnt_view(sch, non-existtent view, desc) should pass -ok 113 - hasnt_view(sch, non-existtent view, desc) should have the proper description -ok 114 - hasnt_view(sch, non-existtent view, desc) should have the proper diagnostics -ok 115 - hasnt_view(view, desc) should fail -ok 116 - hasnt_view(view, desc) should have the proper description -ok 117 - hasnt_view(view, desc) should have the proper diagnostics -ok 118 - hasnt_view(sch, view, desc) should fail -ok 119 - hasnt_view(sch, view, desc) should have the proper description -ok 120 - hasnt_view(sch, view, desc) should have the proper diagnostics -ok 121 - has_sequence(non-existent sequence) should fail -ok 122 - has_sequence(non-existent sequence) should have the proper description -ok 123 - has_sequence(non-existent sequence) should have the proper diagnostics -ok 124 - has_sequence(non-existent sequence, desc) should fail -ok 125 - has_sequence(non-existent sequence, desc) should have the proper description -ok 126 - has_sequence(non-existent sequence, desc) should have the proper diagnostics -ok 127 - has_sequence(sch, non-existtent sequence, desc) should fail -ok 128 - has_sequence(sch, non-existtent sequence, desc) should have the proper description -ok 129 - has_sequence(sch, non-existtent sequence, desc) should have the proper diagnostics -ok 130 - has_sequence(sequence, desc) should pass -ok 131 - has_sequence(sequence, desc) should have the proper description -ok 132 - has_sequence(sequence, desc) should have the proper diagnostics -ok 133 - has_sequence(sch, sequence, desc) should pass -ok 134 - has_sequence(sch, sequence, desc) should have the proper description -ok 135 - has_sequence(sch, sequence, desc) should have the proper diagnostics -ok 136 - has_sequence(sch, sequence) should pass -ok 137 - has_sequence(sch, sequence) should have the proper description -ok 138 - hasnt_sequence(non-existent sequence) should pass -ok 139 - hasnt_sequence(non-existent sequence) should have the proper description -ok 140 - hasnt_sequence(non-existent sequence) should have the proper diagnostics -ok 141 - hasnt_sequence(non-existent sequence, desc) should pass -ok 142 - hasnt_sequence(non-existent sequence, desc) should have the proper description -ok 143 - hasnt_sequence(non-existent sequence, desc) should have the proper diagnostics -ok 144 - hasnt_sequence(sch, non-existtent sequence, desc) should pass -ok 145 - hasnt_sequence(sch, non-existtent sequence, desc) should have the proper description -ok 146 - hasnt_sequence(sch, non-existtent sequence, desc) should have the proper diagnostics -ok 147 - hasnt_sequence(sequence, desc) should fail -ok 148 - hasnt_sequence(sequence, desc) should have the proper description -ok 149 - hasnt_sequence(sequence, desc) should have the proper diagnostics -ok 150 - hasnt_sequence(sch, sequence, desc) should fail -ok 151 - hasnt_sequence(sch, sequence, desc) should have the proper description -ok 152 - hasnt_sequence(sch, sequence, desc) should have the proper diagnostics -ok 153 - has_composite(non-existent composite type) should fail -ok 154 - has_composite(non-existent composite type) should have the proper description -ok 155 - has_composite(non-existent composite type) should have the proper diagnostics -ok 156 - has_composite(non-existent schema, tab) should fail -ok 157 - has_composite(non-existent schema, tab) should have the proper description -ok 158 - has_composite(non-existent schema, tab) should have the proper diagnostics -ok 159 - has_composite(sch, non-existent composite type, desc) should fail -ok 160 - has_composite(sch, non-existent composite type, desc) should have the proper description -ok 161 - has_composite(sch, non-existent composite type, desc) should have the proper diagnostics -ok 162 - has_composite(tab, desc) should pass -ok 163 - has_composite(tab, desc) should have the proper description -ok 164 - has_composite(tab, desc) should have the proper diagnostics -ok 165 - has_composite(sch, tab, desc) should pass -ok 166 - has_composite(sch, tab, desc) should have the proper description -ok 167 - has_composite(sch, tab, desc) should have the proper diagnostics -ok 168 - has_composite(sch, view, desc) should fail -ok 169 - has_composite(sch, view, desc) should have the proper description -ok 170 - has_composite(sch, view, desc) should have the proper diagnostics -ok 171 - has_composite(type, desc) should fail -ok 172 - has_composite(type, desc) should have the proper description -ok 173 - has_composite(type, desc) should have the proper diagnostics -ok 174 - hasnt_composite(non-existent composite type) should pass -ok 175 - hasnt_composite(non-existent composite type) should have the proper description -ok 176 - hasnt_composite(non-existent composite type) should have the proper diagnostics -ok 177 - hasnt_composite(non-existent schema, tab) should pass -ok 178 - hasnt_composite(non-existent schema, tab) should have the proper description -ok 179 - hasnt_composite(non-existent schema, tab) should have the proper diagnostics -ok 180 - hasnt_composite(sch, non-existent tab, desc) should pass -ok 181 - hasnt_composite(sch, non-existent tab, desc) should have the proper description -ok 182 - hasnt_composite(sch, non-existent tab, desc) should have the proper diagnostics -ok 183 - hasnt_composite(tab, desc) should fail -ok 184 - hasnt_composite(tab, desc) should have the proper description -ok 185 - hasnt_composite(tab, desc) should have the proper diagnostics -ok 186 - hasnt_composite(sch, tab, desc) should fail -ok 187 - hasnt_composite(sch, tab, desc) should have the proper description -ok 188 - hasnt_composite(sch, tab, desc) should have the proper diagnostics -ok 189 - has_type(type) should pass -ok 190 - has_type(type) should have the proper description -ok 191 - has_type(type) should have the proper diagnostics -ok 192 - has_type(type, desc) should pass -ok 193 - has_type(type, desc) should have the proper description -ok 194 - has_type(type, desc) should have the proper diagnostics -ok 195 - has_type(scheam, type) should pass -ok 196 - has_type(scheam, type) should have the proper description -ok 197 - has_type(scheam, type) should have the proper diagnostics -ok 198 - has_type(schema, type, desc) should pass -ok 199 - has_type(schema, type, desc) should have the proper description -ok 200 - has_type(schema, type, desc) should have the proper diagnostics -ok 201 - has_type(myType) should pass -ok 202 - has_type(myType) should have the proper description -ok 203 - has_type(myType) should have the proper diagnostics -ok 204 - has_type(myType, desc) should pass -ok 205 - has_type(myType, desc) should have the proper description -ok 206 - has_type(myType, desc) should have the proper diagnostics -ok 207 - has_type(scheam, myType) should pass -ok 208 - has_type(scheam, myType) should have the proper description -ok 209 - has_type(scheam, myType) should have the proper diagnostics -ok 210 - has_type(schema, myType, desc) should pass -ok 211 - has_type(schema, myType, desc) should have the proper description -ok 212 - has_type(schema, myType, desc) should have the proper diagnostics -ok 213 - has_type(type) should fail -ok 214 - has_type(type) should have the proper description -ok 215 - has_type(type) should have the proper diagnostics -ok 216 - has_type(type, desc) should fail -ok 217 - has_type(type, desc) should have the proper description -ok 218 - has_type(type, desc) should have the proper diagnostics -ok 219 - has_type(scheam, type) should fail -ok 220 - has_type(scheam, type) should have the proper description -ok 221 - has_type(scheam, type) should have the proper diagnostics -ok 222 - has_type(schema, type, desc) should fail -ok 223 - has_type(schema, type, desc) should have the proper description -ok 224 - has_type(schema, type, desc) should have the proper diagnostics -ok 225 - has_type(domain) should pass -ok 226 - has_type(domain) should have the proper description -ok 227 - has_type(domain) should have the proper diagnostics -ok 228 - has_type(myDomain) should pass -ok 229 - has_type(myDomain) should have the proper description -ok 230 - has_type(myDomain) should have the proper diagnostics -ok 231 - hasnt_type(type) should pass -ok 232 - hasnt_type(type) should have the proper description -ok 233 - hasnt_type(type) should have the proper diagnostics -ok 234 - hasnt_type(type, desc) should pass -ok 235 - hasnt_type(type, desc) should have the proper description -ok 236 - hasnt_type(type, desc) should have the proper diagnostics -ok 237 - hasnt_type(scheam, type) should pass -ok 238 - hasnt_type(scheam, type) should have the proper description -ok 239 - hasnt_type(scheam, type) should have the proper diagnostics -ok 240 - hasnt_type(schema, type, desc) should pass -ok 241 - hasnt_type(schema, type, desc) should have the proper description -ok 242 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 243 - hasnt_type(type) should fail +ok 73 - has_table(sch, part, desc) should pass +ok 74 - has_table(sch, part, desc) should have the proper description +ok 75 - has_table(sch, part, desc) should have the proper diagnostics +ok 76 - has_table(part, desc) should pass +ok 77 - has_table(part, desc) should have the proper description +ok 78 - has_table(part, desc) should have the proper diagnostics +ok 79 - hasnt_table(non-existent table) should pass +ok 80 - hasnt_table(non-existent table) should have the proper description +ok 81 - hasnt_table(non-existent table) should have the proper diagnostics +ok 82 - hasnt_table(non-existent schema, tab) should pass +ok 83 - hasnt_table(non-existent schema, tab) should have the proper description +ok 84 - hasnt_table(non-existent schema, tab) should have the proper diagnostics +ok 85 - hasnt_table(non-existent table, desc) should pass +ok 86 - hasnt_table(non-existent table, desc) should have the proper description +ok 87 - hasnt_table(non-existent table, desc) should have the proper diagnostics +ok 88 - hasnt_table(sch, non-existent tab, desc) should pass +ok 89 - hasnt_table(sch, non-existent tab, desc) should have the proper description +ok 90 - hasnt_table(sch, non-existent tab, desc) should have the proper diagnostics +ok 91 - hasnt_table(tab, desc) should fail +ok 92 - hasnt_table(tab, desc) should have the proper description +ok 93 - hasnt_table(tab, desc) should have the proper diagnostics +ok 94 - hasnt_table(sch, tab, desc) should fail +ok 95 - hasnt_table(sch, tab, desc) should have the proper description +ok 96 - hasnt_table(sch, tab, desc) should have the proper diagnostics +ok 97 - hasnt_table(part, desc) should fail +ok 98 - hasnt_table(part, desc) should have the proper description +ok 99 - hasnt_table(part, desc) should have the proper diagnostics +ok 100 - hasnt_table(sch, part, desc) should fail +ok 101 - hasnt_table(sch, part, desc) should have the proper description +ok 102 - hasnt_table(sch, part, desc) should have the proper diagnostics +ok 103 - has_view(non-existent view) should fail +ok 104 - has_view(non-existent view) should have the proper description +ok 105 - has_view(non-existent view) should have the proper diagnostics +ok 106 - has_view(non-existent view, desc) should fail +ok 107 - has_view(non-existent view, desc) should have the proper description +ok 108 - has_view(non-existent view, desc) should have the proper diagnostics +ok 109 - has_view(sch, non-existtent view, desc) should fail +ok 110 - has_view(sch, non-existtent view, desc) should have the proper description +ok 111 - has_view(sch, non-existtent view, desc) should have the proper diagnostics +ok 112 - has_view(view, desc) should pass +ok 113 - has_view(view, desc) should have the proper description +ok 114 - has_view(view, desc) should have the proper diagnostics +ok 115 - has_view(sch, view, desc) should pass +ok 116 - has_view(sch, view, desc) should have the proper description +ok 117 - has_view(sch, view, desc) should have the proper diagnostics +ok 118 - hasnt_view(non-existent view) should pass +ok 119 - hasnt_view(non-existent view) should have the proper description +ok 120 - hasnt_view(non-existent view) should have the proper diagnostics +ok 121 - hasnt_view(non-existent view, desc) should pass +ok 122 - hasnt_view(non-existent view, desc) should have the proper description +ok 123 - hasnt_view(non-existent view, desc) should have the proper diagnostics +ok 124 - hasnt_view(sch, non-existtent view, desc) should pass +ok 125 - hasnt_view(sch, non-existtent view, desc) should have the proper description +ok 126 - hasnt_view(sch, non-existtent view, desc) should have the proper diagnostics +ok 127 - hasnt_view(view, desc) should fail +ok 128 - hasnt_view(view, desc) should have the proper description +ok 129 - hasnt_view(view, desc) should have the proper diagnostics +ok 130 - hasnt_view(sch, view, desc) should fail +ok 131 - hasnt_view(sch, view, desc) should have the proper description +ok 132 - hasnt_view(sch, view, desc) should have the proper diagnostics +ok 133 - has_sequence(non-existent sequence) should fail +ok 134 - has_sequence(non-existent sequence) should have the proper description +ok 135 - has_sequence(non-existent sequence) should have the proper diagnostics +ok 136 - has_sequence(non-existent sequence, desc) should fail +ok 137 - has_sequence(non-existent sequence, desc) should have the proper description +ok 138 - has_sequence(non-existent sequence, desc) should have the proper diagnostics +ok 139 - has_sequence(sch, non-existtent sequence, desc) should fail +ok 140 - has_sequence(sch, non-existtent sequence, desc) should have the proper description +ok 141 - has_sequence(sch, non-existtent sequence, desc) should have the proper diagnostics +ok 142 - has_sequence(sequence, desc) should pass +ok 143 - has_sequence(sequence, desc) should have the proper description +ok 144 - has_sequence(sequence, desc) should have the proper diagnostics +ok 145 - has_sequence(sch, sequence, desc) should pass +ok 146 - has_sequence(sch, sequence, desc) should have the proper description +ok 147 - has_sequence(sch, sequence, desc) should have the proper diagnostics +ok 148 - has_sequence(sch, sequence) should pass +ok 149 - has_sequence(sch, sequence) should have the proper description +ok 150 - hasnt_sequence(non-existent sequence) should pass +ok 151 - hasnt_sequence(non-existent sequence) should have the proper description +ok 152 - hasnt_sequence(non-existent sequence) should have the proper diagnostics +ok 153 - hasnt_sequence(non-existent sequence, desc) should pass +ok 154 - hasnt_sequence(non-existent sequence, desc) should have the proper description +ok 155 - hasnt_sequence(non-existent sequence, desc) should have the proper diagnostics +ok 156 - hasnt_sequence(sch, non-existtent sequence, desc) should pass +ok 157 - hasnt_sequence(sch, non-existtent sequence, desc) should have the proper description +ok 158 - hasnt_sequence(sch, non-existtent sequence, desc) should have the proper diagnostics +ok 159 - hasnt_sequence(sequence, desc) should fail +ok 160 - hasnt_sequence(sequence, desc) should have the proper description +ok 161 - hasnt_sequence(sequence, desc) should have the proper diagnostics +ok 162 - hasnt_sequence(sch, sequence, desc) should fail +ok 163 - hasnt_sequence(sch, sequence, desc) should have the proper description +ok 164 - hasnt_sequence(sch, sequence, desc) should have the proper diagnostics +ok 165 - has_composite(non-existent composite type) should fail +ok 166 - has_composite(non-existent composite type) should have the proper description +ok 167 - has_composite(non-existent composite type) should have the proper diagnostics +ok 168 - has_composite(non-existent schema, tab) should fail +ok 169 - has_composite(non-existent schema, tab) should have the proper description +ok 170 - has_composite(non-existent schema, tab) should have the proper diagnostics +ok 171 - has_composite(sch, non-existent composite type, desc) should fail +ok 172 - has_composite(sch, non-existent composite type, desc) should have the proper description +ok 173 - has_composite(sch, non-existent composite type, desc) should have the proper diagnostics +ok 174 - has_composite(tab, desc) should pass +ok 175 - has_composite(tab, desc) should have the proper description +ok 176 - has_composite(tab, desc) should have the proper diagnostics +ok 177 - has_composite(sch, tab, desc) should pass +ok 178 - has_composite(sch, tab, desc) should have the proper description +ok 179 - has_composite(sch, tab, desc) should have the proper diagnostics +ok 180 - has_composite(sch, view, desc) should fail +ok 181 - has_composite(sch, view, desc) should have the proper description +ok 182 - has_composite(sch, view, desc) should have the proper diagnostics +ok 183 - has_composite(type, desc) should fail +ok 184 - has_composite(type, desc) should have the proper description +ok 185 - has_composite(type, desc) should have the proper diagnostics +ok 186 - hasnt_composite(non-existent composite type) should pass +ok 187 - hasnt_composite(non-existent composite type) should have the proper description +ok 188 - hasnt_composite(non-existent composite type) should have the proper diagnostics +ok 189 - hasnt_composite(non-existent schema, tab) should pass +ok 190 - hasnt_composite(non-existent schema, tab) should have the proper description +ok 191 - hasnt_composite(non-existent schema, tab) should have the proper diagnostics +ok 192 - hasnt_composite(sch, non-existent tab, desc) should pass +ok 193 - hasnt_composite(sch, non-existent tab, desc) should have the proper description +ok 194 - hasnt_composite(sch, non-existent tab, desc) should have the proper diagnostics +ok 195 - hasnt_composite(tab, desc) should fail +ok 196 - hasnt_composite(tab, desc) should have the proper description +ok 197 - hasnt_composite(tab, desc) should have the proper diagnostics +ok 198 - hasnt_composite(sch, tab, desc) should fail +ok 199 - hasnt_composite(sch, tab, desc) should have the proper description +ok 200 - hasnt_composite(sch, tab, desc) should have the proper diagnostics +ok 201 - has_type(type) should pass +ok 202 - has_type(type) should have the proper description +ok 203 - has_type(type) should have the proper diagnostics +ok 204 - has_type(type, desc) should pass +ok 205 - has_type(type, desc) should have the proper description +ok 206 - has_type(type, desc) should have the proper diagnostics +ok 207 - has_type(scheam, type) should pass +ok 208 - has_type(scheam, type) should have the proper description +ok 209 - has_type(scheam, type) should have the proper diagnostics +ok 210 - has_type(schema, type, desc) should pass +ok 211 - has_type(schema, type, desc) should have the proper description +ok 212 - has_type(schema, type, desc) should have the proper diagnostics +ok 213 - has_type(myType) should pass +ok 214 - has_type(myType) should have the proper description +ok 215 - has_type(myType) should have the proper diagnostics +ok 216 - has_type(myType, desc) should pass +ok 217 - has_type(myType, desc) should have the proper description +ok 218 - has_type(myType, desc) should have the proper diagnostics +ok 219 - has_type(scheam, myType) should pass +ok 220 - has_type(scheam, myType) should have the proper description +ok 221 - has_type(scheam, myType) should have the proper diagnostics +ok 222 - has_type(schema, myType, desc) should pass +ok 223 - has_type(schema, myType, desc) should have the proper description +ok 224 - has_type(schema, myType, desc) should have the proper diagnostics +ok 225 - has_type(type) should fail +ok 226 - has_type(type) should have the proper description +ok 227 - has_type(type) should have the proper diagnostics +ok 228 - has_type(type, desc) should fail +ok 229 - has_type(type, desc) should have the proper description +ok 230 - has_type(type, desc) should have the proper diagnostics +ok 231 - has_type(scheam, type) should fail +ok 232 - has_type(scheam, type) should have the proper description +ok 233 - has_type(scheam, type) should have the proper diagnostics +ok 234 - has_type(schema, type, desc) should fail +ok 235 - has_type(schema, type, desc) should have the proper description +ok 236 - has_type(schema, type, desc) should have the proper diagnostics +ok 237 - has_type(domain) should pass +ok 238 - has_type(domain) should have the proper description +ok 239 - has_type(domain) should have the proper diagnostics +ok 240 - has_type(myDomain) should pass +ok 241 - has_type(myDomain) should have the proper description +ok 242 - has_type(myDomain) should have the proper diagnostics +ok 243 - hasnt_type(type) should pass ok 244 - hasnt_type(type) should have the proper description ok 245 - hasnt_type(type) should have the proper diagnostics -ok 246 - hasnt_type(type, desc) should fail +ok 246 - hasnt_type(type, desc) should pass ok 247 - hasnt_type(type, desc) should have the proper description ok 248 - hasnt_type(type, desc) should have the proper diagnostics -ok 249 - hasnt_type(scheam, type) should fail +ok 249 - hasnt_type(scheam, type) should pass ok 250 - hasnt_type(scheam, type) should have the proper description ok 251 - hasnt_type(scheam, type) should have the proper diagnostics -ok 252 - hasnt_type(schema, type, desc) should fail +ok 252 - hasnt_type(schema, type, desc) should pass ok 253 - hasnt_type(schema, type, desc) should have the proper description ok 254 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 255 - has_domain(domain) should pass -ok 256 - has_domain(domain) should have the proper description -ok 257 - has_domain(domain) should have the proper diagnostics -ok 258 - has_domain(domain, desc) should pass -ok 259 - has_domain(domain, desc) should have the proper description -ok 260 - has_domain(domain, desc) should have the proper diagnostics -ok 261 - has_domain(scheam, domain) should pass -ok 262 - has_domain(scheam, domain) should have the proper description -ok 263 - has_domain(scheam, domain) should have the proper diagnostics -ok 264 - has_domain(schema, domain, desc) should pass -ok 265 - has_domain(schema, domain, desc) should have the proper description -ok 266 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 267 - has_domain(myDomain) should pass -ok 268 - has_domain(myDomain) should have the proper description -ok 269 - has_domain(myDomain) should have the proper diagnostics -ok 270 - has_domain(myDomain, desc) should pass -ok 271 - has_domain(myDomain, desc) should have the proper description -ok 272 - has_domain(myDomain, desc) should have the proper diagnostics -ok 273 - has_domain(scheam, myDomain) should pass -ok 274 - has_domain(scheam, myDomain) should have the proper description -ok 275 - has_domain(scheam, myDomain) should have the proper diagnostics -ok 276 - has_domain(schema, myDomain, desc) should pass -ok 277 - has_domain(schema, myDomain, desc) should have the proper description -ok 278 - has_domain(schema, myDomain, desc) should have the proper diagnostics -ok 279 - has_domain(domain) should fail -ok 280 - has_domain(domain) should have the proper description -ok 281 - has_domain(domain) should have the proper diagnostics -ok 282 - has_domain(domain, desc) should fail -ok 283 - has_domain(domain, desc) should have the proper description -ok 284 - has_domain(domain, desc) should have the proper diagnostics -ok 285 - has_domain(scheam, domain) should fail -ok 286 - has_domain(scheam, domain) should have the proper description -ok 287 - has_domain(scheam, domain) should have the proper diagnostics -ok 288 - has_domain(schema, domain, desc) should fail -ok 289 - has_domain(schema, domain, desc) should have the proper description -ok 290 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 291 - hasnt_domain(domain) should pass -ok 292 - hasnt_domain(domain) should have the proper description -ok 293 - hasnt_domain(domain) should have the proper diagnostics -ok 294 - hasnt_domain(domain, desc) should pass -ok 295 - hasnt_domain(domain, desc) should have the proper description -ok 296 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 297 - hasnt_domain(scheam, domain) should pass -ok 298 - hasnt_domain(scheam, domain) should have the proper description -ok 299 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 300 - hasnt_domain(schema, domain, desc) should pass -ok 301 - hasnt_domain(schema, domain, desc) should have the proper description -ok 302 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 303 - hasnt_domain(domain) should fail +ok 255 - hasnt_type(type) should fail +ok 256 - hasnt_type(type) should have the proper description +ok 257 - hasnt_type(type) should have the proper diagnostics +ok 258 - hasnt_type(type, desc) should fail +ok 259 - hasnt_type(type, desc) should have the proper description +ok 260 - hasnt_type(type, desc) should have the proper diagnostics +ok 261 - hasnt_type(scheam, type) should fail +ok 262 - hasnt_type(scheam, type) should have the proper description +ok 263 - hasnt_type(scheam, type) should have the proper diagnostics +ok 264 - hasnt_type(schema, type, desc) should fail +ok 265 - hasnt_type(schema, type, desc) should have the proper description +ok 266 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 267 - has_domain(domain) should pass +ok 268 - has_domain(domain) should have the proper description +ok 269 - has_domain(domain) should have the proper diagnostics +ok 270 - has_domain(domain, desc) should pass +ok 271 - has_domain(domain, desc) should have the proper description +ok 272 - has_domain(domain, desc) should have the proper diagnostics +ok 273 - has_domain(scheam, domain) should pass +ok 274 - has_domain(scheam, domain) should have the proper description +ok 275 - has_domain(scheam, domain) should have the proper diagnostics +ok 276 - has_domain(schema, domain, desc) should pass +ok 277 - has_domain(schema, domain, desc) should have the proper description +ok 278 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 279 - has_domain(myDomain) should pass +ok 280 - has_domain(myDomain) should have the proper description +ok 281 - has_domain(myDomain) should have the proper diagnostics +ok 282 - has_domain(myDomain, desc) should pass +ok 283 - has_domain(myDomain, desc) should have the proper description +ok 284 - has_domain(myDomain, desc) should have the proper diagnostics +ok 285 - has_domain(scheam, myDomain) should pass +ok 286 - has_domain(scheam, myDomain) should have the proper description +ok 287 - has_domain(scheam, myDomain) should have the proper diagnostics +ok 288 - has_domain(schema, myDomain, desc) should pass +ok 289 - has_domain(schema, myDomain, desc) should have the proper description +ok 290 - has_domain(schema, myDomain, desc) should have the proper diagnostics +ok 291 - has_domain(domain) should fail +ok 292 - has_domain(domain) should have the proper description +ok 293 - has_domain(domain) should have the proper diagnostics +ok 294 - has_domain(domain, desc) should fail +ok 295 - has_domain(domain, desc) should have the proper description +ok 296 - has_domain(domain, desc) should have the proper diagnostics +ok 297 - has_domain(scheam, domain) should fail +ok 298 - has_domain(scheam, domain) should have the proper description +ok 299 - has_domain(scheam, domain) should have the proper diagnostics +ok 300 - has_domain(schema, domain, desc) should fail +ok 301 - has_domain(schema, domain, desc) should have the proper description +ok 302 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 303 - hasnt_domain(domain) should pass ok 304 - hasnt_domain(domain) should have the proper description ok 305 - hasnt_domain(domain) should have the proper diagnostics -ok 306 - hasnt_domain(domain, desc) should fail +ok 306 - hasnt_domain(domain, desc) should pass ok 307 - hasnt_domain(domain, desc) should have the proper description ok 308 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 309 - hasnt_domain(scheam, domain) should fail +ok 309 - hasnt_domain(scheam, domain) should pass ok 310 - hasnt_domain(scheam, domain) should have the proper description ok 311 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 312 - hasnt_domain(schema, domain, desc) should fail +ok 312 - hasnt_domain(schema, domain, desc) should pass ok 313 - hasnt_domain(schema, domain, desc) should have the proper description ok 314 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 315 - has_column(non-existent tab, col) should fail -ok 316 - has_column(non-existent tab, col) should have the proper description -ok 317 - has_column(non-existent tab, col) should have the proper diagnostics -ok 318 - has_column(non-existent tab, col, desc) should fail -ok 319 - has_column(non-existent tab, col, desc) should have the proper description -ok 320 - has_column(non-existent tab, col, desc) should have the proper diagnostics -ok 321 - has_column(non-existent sch, tab, col, desc) should fail -ok 322 - has_column(non-existent sch, tab, col, desc) should have the proper description -ok 323 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 324 - has_column(table, column) should pass -ok 325 - has_column(table, column) should have the proper description -ok 326 - has_column(table, column) should have the proper diagnostics -ok 327 - has_column(sch, tab, col, desc) should pass -ok 328 - has_column(sch, tab, col, desc) should have the proper description -ok 329 - has_column(sch, tab, col, desc) should have the proper diagnostics -ok 330 - has_column(table, camleCase column) should pass -ok 331 - has_column(table, camleCase column) should have the proper description -ok 332 - has_column(table, camleCase column) should have the proper diagnostics -ok 333 - has_column(view, column) should pass -ok 334 - has_column(view, column) should have the proper description -ok 335 - has_column(view, column) should have the proper diagnostics -ok 336 - has_column(type, column) should pass -ok 337 - has_column(type, column) should have the proper description -ok 338 - has_column(type, column) should have the proper diagnostics -ok 339 - hasnt_column(non-existent tab, col) should pass -ok 340 - hasnt_column(non-existent tab, col) should have the proper description -ok 341 - hasnt_column(non-existent tab, col) should have the proper diagnostics -ok 342 - hasnt_column(non-existent tab, col, desc) should pass -ok 343 - hasnt_column(non-existent tab, col, desc) should have the proper description -ok 344 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics -ok 345 - hasnt_column(non-existent sch, tab, col, desc) should pass -ok 346 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description -ok 347 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 348 - hasnt_column(table, column) should fail -ok 349 - hasnt_column(table, column) should have the proper description -ok 350 - hasnt_column(table, column) should have the proper diagnostics -ok 351 - hasnt_column(sch, tab, col, desc) should fail -ok 352 - hasnt_column(sch, tab, col, desc) should have the proper description -ok 353 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics -ok 354 - hasnt_column(view, column) should pass -ok 355 - hasnt_column(view, column) should have the proper description -ok 356 - hasnt_column(view, column) should have the proper diagnostics -ok 357 - hasnt_column(type, column) should pass -ok 358 - hasnt_column(type, column) should have the proper description -ok 359 - hasnt_column(type, column) should have the proper diagnostics -ok 360 - has_cast( src, targ, schema, func, desc) should pass -ok 361 - has_cast( src, targ, schema, func, desc) should have the proper description -ok 362 - has_cast( src, targ, schema, func, desc) should have the proper diagnostics -ok 363 - has_cast( src, targ, schema, func ) should pass -ok 364 - has_cast( src, targ, schema, func ) should have the proper description -ok 365 - has_cast( src, targ, schema, func ) should have the proper diagnostics -ok 366 - has_cast( src, targ, func, desc ) should pass -ok 367 - has_cast( src, targ, func, desc ) should have the proper description -ok 368 - has_cast( src, targ, func, desc ) should have the proper diagnostics -ok 369 - has_cast( src, targ, func) should pass -ok 370 - has_cast( src, targ, func) should have the proper description -ok 371 - has_cast( src, targ, func) should have the proper diagnostics -ok 372 - has_cast( src, targ, desc ) should pass -ok 373 - has_cast( src, targ, desc ) should have the proper description -ok 374 - has_cast( src, targ, desc ) should have the proper diagnostics -ok 375 - has_cast( src, targ ) should pass -ok 376 - has_cast( src, targ ) should have the proper description -ok 377 - has_cast( src, targ ) should have the proper diagnostics -ok 378 - has_cast( src, targ, schema, func, desc) fail should fail -ok 379 - has_cast( src, targ, schema, func, desc) fail should have the proper description -ok 380 - has_cast( src, targ, schema, func, desc) fail should have the proper diagnostics -ok 381 - has_cast( src, targ, func, desc ) fail should fail -ok 382 - has_cast( src, targ, func, desc ) fail should have the proper description -ok 383 - has_cast( src, targ, func, desc ) fail should have the proper diagnostics -ok 384 - has_cast( src, targ, desc ) fail should fail -ok 385 - has_cast( src, targ, desc ) fail should have the proper description -ok 386 - has_cast( src, targ, desc ) fail should have the proper diagnostics -ok 387 - hasnt_cast( src, targ, schema, func, desc) should fail -ok 388 - hasnt_cast( src, targ, schema, func, desc) should have the proper description -ok 389 - hasnt_cast( src, targ, schema, func, desc) should have the proper diagnostics -ok 390 - hasnt_cast( src, targ, schema, func ) should fail -ok 391 - hasnt_cast( src, targ, schema, func ) should have the proper description -ok 392 - hasnt_cast( src, targ, schema, func ) should have the proper diagnostics -ok 393 - hasnt_cast( src, targ, func, desc ) should fail -ok 394 - hasnt_cast( src, targ, func, desc ) should have the proper description -ok 395 - hasnt_cast( src, targ, func, desc ) should have the proper diagnostics -ok 396 - hasnt_cast( src, targ, func) should fail -ok 397 - hasnt_cast( src, targ, func) should have the proper description -ok 398 - hasnt_cast( src, targ, func) should have the proper diagnostics -ok 399 - hasnt_cast( src, targ, desc ) should fail -ok 400 - hasnt_cast( src, targ, desc ) should have the proper description -ok 401 - hasnt_cast( src, targ, desc ) should have the proper diagnostics -ok 402 - hasnt_cast( src, targ ) should fail -ok 403 - hasnt_cast( src, targ ) should have the proper description -ok 404 - hasnt_cast( src, targ ) should have the proper diagnostics -ok 405 - hasnt_cast( src, targ, schema, func, desc) fail should pass -ok 406 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper description -ok 407 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper diagnostics -ok 408 - hasnt_cast( src, targ, func, desc ) fail should pass -ok 409 - hasnt_cast( src, targ, func, desc ) fail should have the proper description -ok 410 - hasnt_cast( src, targ, func, desc ) fail should have the proper diagnostics -ok 411 - hasnt_cast( src, targ, desc ) fail should pass -ok 412 - hasnt_cast( src, targ, desc ) fail should have the proper description -ok 413 - hasnt_cast( src, targ, desc ) fail should have the proper diagnostics -ok 414 - cast_context_is( src, targ, context, desc ) should pass -ok 415 - cast_context_is( src, targ, context, desc ) should have the proper description -ok 416 - cast_context_is( src, targ, context, desc ) should have the proper diagnostics -ok 417 - cast_context_is( src, targ, context ) should pass -ok 418 - cast_context_is( src, targ, context ) should have the proper description -ok 419 - cast_context_is( src, targ, context ) should have the proper diagnostics -ok 420 - cast_context_is( src, targ, i, desc ) should pass -ok 421 - cast_context_is( src, targ, i, desc ) should have the proper description -ok 422 - cast_context_is( src, targ, i, desc ) should have the proper diagnostics -ok 423 - cast_context_is( src, targ, IMPL, desc ) should pass -ok 424 - cast_context_is( src, targ, IMPL, desc ) should have the proper description -ok 425 - cast_context_is( src, targ, IMPL, desc ) should have the proper diagnostics -ok 426 - cast_context_is( src, targ, assignment, desc ) should pass -ok 427 - cast_context_is( src, targ, assignment, desc ) should have the proper description -ok 428 - cast_context_is( src, targ, assignment, desc ) should have the proper diagnostics -ok 429 - cast_context_is( src, targ, a, desc ) should pass -ok 430 - cast_context_is( src, targ, a, desc ) should have the proper description -ok 431 - cast_context_is( src, targ, a, desc ) should have the proper diagnostics -ok 432 - cast_context_is( src, targ, ASS, desc ) should pass -ok 433 - cast_context_is( src, targ, ASS, desc ) should have the proper description -ok 434 - cast_context_is( src, targ, ASS, desc ) should have the proper diagnostics -ok 435 - cast_context_is( src, targ, explicit, desc ) should pass -ok 436 - cast_context_is( src, targ, explicit, desc ) should have the proper description -ok 437 - cast_context_is( src, targ, explicit, desc ) should have the proper diagnostics -ok 438 - cast_context_is( src, targ, e, desc ) should pass -ok 439 - cast_context_is( src, targ, e, desc ) should have the proper description -ok 440 - cast_context_is( src, targ, e, desc ) should have the proper diagnostics -ok 441 - cast_context_is( src, targ, EX, desc ) should pass -ok 442 - cast_context_is( src, targ, EX, desc ) should have the proper description -ok 443 - cast_context_is( src, targ, EX, desc ) should have the proper diagnostics -ok 444 - cast_context_is( src, targ, context, desc ) fail should fail -ok 445 - cast_context_is( src, targ, context, desc ) fail should have the proper description -ok 446 - cast_context_is( src, targ, context, desc ) fail should have the proper diagnostics -ok 447 - cast_context_is( src, targ, context ) fail should fail -ok 448 - cast_context_is( src, targ, context ) fail should have the proper description -ok 449 - cast_context_is( src, targ, context ) fail should have the proper diagnostics -ok 450 - cast_context_is( src, targ, context, desc ) noexist should fail -ok 451 - cast_context_is( src, targ, context, desc ) noexist should have the proper description -ok 452 - cast_context_is( src, targ, context, desc ) noexist should have the proper diagnostics -ok 453 - has_operator( left, schema, name, right, result, desc ) should pass -ok 454 - has_operator( left, schema, name, right, result, desc ) should have the proper description -ok 455 - has_operator( left, schema, name, right, result, desc ) should have the proper diagnostics -ok 456 - has_operator( left, schema, name, right, result ) should pass -ok 457 - has_operator( left, schema, name, right, result ) should have the proper description -ok 458 - has_operator( left, schema, name, right, result ) should have the proper diagnostics -ok 459 - has_operator( left, name, right, result, desc ) should pass -ok 460 - has_operator( left, name, right, result, desc ) should have the proper description -ok 461 - has_operator( left, name, right, result, desc ) should have the proper diagnostics -ok 462 - has_operator( left, name, right, result ) should pass -ok 463 - has_operator( left, name, right, result ) should have the proper description -ok 464 - has_operator( left, name, right, result ) should have the proper diagnostics -ok 465 - has_operator( left, name, right, desc ) should pass -ok 466 - has_operator( left, name, right, desc ) should have the proper description -ok 467 - has_operator( left, name, right, desc ) should have the proper diagnostics -ok 468 - has_operator( left, name, right ) should pass -ok 469 - has_operator( left, name, right ) should have the proper description -ok 470 - has_operator( left, name, right ) should have the proper diagnostics -ok 471 - has_operator( left, schema, name, right, result, desc ) fail should fail -ok 472 - has_operator( left, schema, name, right, result, desc ) fail should have the proper description -ok 473 - has_operator( left, schema, name, right, result, desc ) fail should have the proper diagnostics -ok 474 - has_operator( left, schema, name, right, result ) fail should fail -ok 475 - has_operator( left, schema, name, right, result ) fail should have the proper description -ok 476 - has_operator( left, schema, name, right, result ) fail should have the proper diagnostics -ok 477 - has_operator( left, name, right, result, desc ) fail should fail -ok 478 - has_operator( left, name, right, result, desc ) fail should have the proper description -ok 479 - has_operator( left, name, right, result, desc ) fail should have the proper diagnostics -ok 480 - has_operator( left, name, right, result ) fail should fail -ok 481 - has_operator( left, name, right, result ) fail should have the proper description -ok 482 - has_operator( left, name, right, result ) fail should have the proper diagnostics -ok 483 - has_operator( left, name, right, desc ) fail should fail -ok 484 - has_operator( left, name, right, desc ) fail should have the proper description -ok 485 - has_operator( left, name, right, desc ) fail should have the proper diagnostics -ok 486 - has_operator( left, name, right ) fail should fail -ok 487 - has_operator( left, name, right ) fail should have the proper description -ok 488 - has_operator( left, name, right ) fail should have the proper diagnostics -ok 489 - has_leftop( schema, name, right, result, desc ) should pass -ok 490 - has_leftop( schema, name, right, result, desc ) should have the proper description -ok 491 - has_leftop( schema, name, right, result, desc ) should have the proper diagnostics -ok 492 - has_leftop( schema, name, right, result ) should pass -ok 493 - has_leftop( schema, name, right, result ) should have the proper description -ok 494 - has_leftop( schema, name, right, result ) should have the proper diagnostics -ok 495 - has_leftop( name, right, result, desc ) should pass -ok 496 - has_leftop( name, right, result, desc ) should have the proper description -ok 497 - has_leftop( name, right, result, desc ) should have the proper diagnostics -ok 498 - has_leftop( name, right, result ) should pass -ok 499 - has_leftop( name, right, result ) should have the proper description -ok 500 - has_leftop( name, right, result ) should have the proper diagnostics -ok 501 - has_leftop( name, right, desc ) should pass -ok 502 - has_leftop( name, right, desc ) should have the proper description -ok 503 - has_leftop( name, right, desc ) should have the proper diagnostics -ok 504 - has_leftop( name, right ) should pass -ok 505 - has_leftop( name, right ) should have the proper description -ok 506 - has_leftop( name, right ) should have the proper diagnostics -ok 507 - has_leftop( schema, name, right, result, desc ) fail should fail -ok 508 - has_leftop( schema, name, right, result, desc ) fail should have the proper description -ok 509 - has_leftop( schema, name, right, result, desc ) fail should have the proper diagnostics -ok 510 - has_leftop( schema, name, right, result ) fail should fail -ok 511 - has_leftop( schema, name, right, result ) fail should have the proper description -ok 512 - has_leftop( schema, name, right, result ) fail should have the proper diagnostics -ok 513 - has_leftop( name, right, result, desc ) fail should fail -ok 514 - has_leftop( name, right, result, desc ) fail should have the proper description -ok 515 - has_leftop( name, right, result, desc ) fail should have the proper diagnostics -ok 516 - has_leftop( name, right, result ) fail should fail -ok 517 - has_leftop( name, right, result ) fail should have the proper description -ok 518 - has_leftop( name, right, result ) fail should have the proper diagnostics -ok 519 - has_leftop( name, right, desc ) fail should fail -ok 520 - has_leftop( name, right, desc ) fail should have the proper description -ok 521 - has_leftop( name, right, desc ) fail should have the proper diagnostics -ok 522 - has_leftop( name, right ) fail should fail -ok 523 - has_leftop( name, right ) fail should have the proper description -ok 524 - has_leftop( name, right ) fail should have the proper diagnostics -ok 525 - has_rightop( left, schema, name, result, desc ) should pass -ok 526 - has_rightop( left, schema, name, result, desc ) should have the proper description -ok 527 - has_rightop( left, schema, name, result, desc ) should have the proper diagnostics -ok 528 - has_rightop( left, schema, name, result ) should pass -ok 529 - has_rightop( left, schema, name, result ) should have the proper description -ok 530 - has_rightop( left, schema, name, result ) should have the proper diagnostics -ok 531 - has_rightop( left, name, result, desc ) should pass -ok 532 - has_rightop( left, name, result, desc ) should have the proper description -ok 533 - has_rightop( left, name, result, desc ) should have the proper diagnostics -ok 534 - has_rightop( left, name, result ) should pass -ok 535 - has_rightop( left, name, result ) should have the proper description -ok 536 - has_rightop( left, name, result ) should have the proper diagnostics -ok 537 - has_rightop( left, name, desc ) should pass -ok 538 - has_rightop( left, name, desc ) should have the proper description -ok 539 - has_rightop( left, name, desc ) should have the proper diagnostics -ok 540 - has_rightop( left, name ) should pass -ok 541 - has_rightop( left, name ) should have the proper description -ok 542 - has_rightop( left, name ) should have the proper diagnostics -ok 543 - has_rightop( left, schema, name, result, desc ) fail should fail -ok 544 - has_rightop( left, schema, name, result, desc ) fail should have the proper description -ok 545 - has_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics -ok 546 - has_rightop( left, schema, name, result ) fail should fail -ok 547 - has_rightop( left, schema, name, result ) fail should have the proper description -ok 548 - has_rightop( left, schema, name, result ) fail should have the proper diagnostics -ok 549 - has_rightop( left, name, result, desc ) fail should fail -ok 550 - has_rightop( left, name, result, desc ) fail should have the proper description -ok 551 - has_rightop( left, name, result, desc ) fail should have the proper diagnostics -ok 552 - has_rightop( left, name, result ) fail should fail -ok 553 - has_rightop( left, name, result ) fail should have the proper description -ok 554 - has_rightop( left, name, result ) fail should have the proper diagnostics -ok 555 - has_rightop( left, name, desc ) fail should fail -ok 556 - has_rightop( left, name, desc ) fail should have the proper description -ok 557 - has_rightop( left, name, desc ) fail should have the proper diagnostics -ok 558 - has_rightop( left, name ) fail should fail -ok 559 - has_rightop( left, name ) fail should have the proper description -ok 560 - has_rightop( left, name ) fail should have the proper diagnostics -ok 561 - has_language(language) should pass -ok 562 - has_language(language) should have the proper description -ok 563 - has_language(language) should have the proper diagnostics -ok 564 - has_language(language, desc) should pass -ok 565 - has_language(language, desc) should have the proper description -ok 566 - has_language(language, desc) should have the proper diagnostics -ok 567 - has_language(nonexistent language) should fail -ok 568 - has_language(nonexistent language) should have the proper description -ok 569 - has_language(nonexistent language) should have the proper diagnostics -ok 570 - has_language(nonexistent language, desc) should fail -ok 571 - has_language(nonexistent language, desc) should have the proper description -ok 572 - has_language(nonexistent language, desc) should have the proper diagnostics -ok 573 - hasnt_language(language) should fail -ok 574 - hasnt_language(language) should have the proper description -ok 575 - hasnt_language(language) should have the proper diagnostics -ok 576 - hasnt_language(language, desc) should fail -ok 577 - hasnt_language(language, desc) should have the proper description -ok 578 - hasnt_language(language, desc) should have the proper diagnostics -ok 579 - hasnt_language(nonexistent language) should pass -ok 580 - hasnt_language(nonexistent language) should have the proper description -ok 581 - hasnt_language(nonexistent language) should have the proper diagnostics -ok 582 - hasnt_language(nonexistent language, desc) should pass -ok 583 - hasnt_language(nonexistent language, desc) should have the proper description -ok 584 - hasnt_language(nonexistent language, desc) should have the proper diagnostics -ok 585 - language_is_trusted(language, desc) should pass -ok 586 - language_is_trusted(language, desc) should have the proper description -ok 587 - language_is_trusted(language, desc) should have the proper diagnostics -ok 588 - language_is_trusted(language) should pass -ok 589 - language_is_trusted(language) should have the proper description -ok 590 - language_is_trusted(language) should have the proper diagnostics -ok 591 - language_is_trusted(language, desc) fail should fail -ok 592 - language_is_trusted(language, desc) fail should have the proper description -ok 593 - language_is_trusted(language, desc) fail should have the proper diagnostics -ok 594 - language_is_trusted(language, desc) non-existent should fail -ok 595 - language_is_trusted(language, desc) non-existent should have the proper description -ok 596 - language_is_trusted(language, desc) non-existent should have the proper diagnostics -ok 597 - has_opclass( schema, name, desc ) should pass -ok 598 - has_opclass( schema, name, desc ) should have the proper description -ok 599 - has_opclass( schema, name, desc ) should have the proper diagnostics -ok 600 - has_opclass( schema, name ) should pass -ok 601 - has_opclass( schema, name ) should have the proper description -ok 602 - has_opclass( schema, name ) should have the proper diagnostics -ok 603 - has_opclass( name, desc ) should pass -ok 604 - has_opclass( name, desc ) should have the proper description -ok 605 - has_opclass( name, desc ) should have the proper diagnostics -ok 606 - has_opclass( name ) should pass -ok 607 - has_opclass( name ) should have the proper description -ok 608 - has_opclass( name ) should have the proper diagnostics -ok 609 - has_opclass( schema, name, desc ) fail should fail -ok 610 - has_opclass( schema, name, desc ) fail should have the proper description -ok 611 - has_opclass( schema, name, desc ) fail should have the proper diagnostics -ok 612 - has_opclass( name, desc ) fail should fail -ok 613 - has_opclass( name, desc ) fail should have the proper description -ok 614 - has_opclass( name, desc ) fail should have the proper diagnostics -ok 615 - hasnt_opclass( schema, name, desc ) should fail -ok 616 - hasnt_opclass( schema, name, desc ) should have the proper description -ok 617 - hasnt_opclass( schema, name, desc ) should have the proper diagnostics -ok 618 - hasnt_opclass( schema, name ) should fail -ok 619 - hasnt_opclass( schema, name ) should have the proper description -ok 620 - hasnt_opclass( schema, name ) should have the proper diagnostics -ok 621 - hasnt_opclass( name, desc ) should fail -ok 622 - hasnt_opclass( name, desc ) should have the proper description -ok 623 - hasnt_opclass( name, desc ) should have the proper diagnostics -ok 624 - hasnt_opclass( name ) should fail -ok 625 - hasnt_opclass( name ) should have the proper description -ok 626 - hasnt_opclass( name ) should have the proper diagnostics -ok 627 - hasnt_opclass( schema, name, desc ) fail should pass -ok 628 - hasnt_opclass( schema, name, desc ) fail should have the proper description -ok 629 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnostics -ok 630 - hasnt_opclass( name, desc ) fail should pass -ok 631 - hasnt_opclass( name, desc ) fail should have the proper description -ok 632 - hasnt_opclass( name, desc ) fail should have the proper diagnostics -ok 633 - domain_type_is(schema, domain, schema, type, desc) should pass -ok 634 - domain_type_is(schema, domain, schema, type, desc) should have the proper description -ok 635 - domain_type_is(schema, domain, schema, type, desc) should have the proper diagnostics -ok 636 - domain_type_is(schema, domain, schema, type) should pass -ok 637 - domain_type_is(schema, domain, schema, type) should have the proper description -ok 638 - domain_type_is(schema, domain, schema, type) should have the proper diagnostics -ok 639 - domain_type_is(schema, domain, schema, type, desc) fail should fail -ok 640 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper description -ok 641 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper diagnostics -ok 642 - domain_type_is(schema, nondomain, schema, type, desc) should fail -ok 643 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper description -ok 644 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper diagnostics -ok 645 - domain_type_is(schema, type, schema, type, desc) fail should fail -ok 646 - domain_type_is(schema, type, schema, type, desc) fail should have the proper description -ok 647 - domain_type_is(schema, type, schema, type, desc) fail should have the proper diagnostics -ok 648 - domain_type_is(schema, domain, type, desc) should pass -ok 649 - domain_type_is(schema, domain, type, desc) should have the proper description -ok 650 - domain_type_is(schema, domain, type, desc) should have the proper diagnostics -ok 651 - domain_type_is(schema, domain, type) should pass -ok 652 - domain_type_is(schema, domain, type) should have the proper description -ok 653 - domain_type_is(schema, domain, type) should have the proper diagnostics -ok 654 - domain_type_is(schema, domain, type, desc) fail should fail -ok 655 - domain_type_is(schema, domain, type, desc) fail should have the proper description -ok 656 - domain_type_is(schema, domain, type, desc) fail should have the proper diagnostics -ok 657 - domain_type_is(schema, nondomain, type, desc) should fail -ok 658 - domain_type_is(schema, nondomain, type, desc) should have the proper description -ok 659 - domain_type_is(schema, nondomain, type, desc) should have the proper diagnostics -ok 660 - domain_type_is(schema, type, type, desc) fail should fail -ok 661 - domain_type_is(schema, type, type, desc) fail should have the proper description -ok 662 - domain_type_is(schema, type, type, desc) fail should have the proper diagnostics -ok 663 - domain_type_is(domain, type, desc) should pass -ok 664 - domain_type_is(domain, type, desc) should have the proper description -ok 665 - domain_type_is(domain, type, desc) should have the proper diagnostics -ok 666 - domain_type_is(domain, type) should pass -ok 667 - domain_type_is(domain, type) should have the proper description -ok 668 - domain_type_is(domain, type) should have the proper diagnostics -ok 669 - domain_type_is(domain, type, desc) fail should fail -ok 670 - domain_type_is(domain, type, desc) fail should have the proper description -ok 671 - domain_type_is(domain, type, desc) fail should have the proper diagnostics -ok 672 - domain_type_is(nondomain, type, desc) should fail -ok 673 - domain_type_is(nondomain, type, desc) should have the proper description -ok 674 - domain_type_is(nondomain, type, desc) should have the proper diagnostics -ok 675 - domain_type_is(type, type, desc) fail should fail -ok 676 - domain_type_is(type, type, desc) fail should have the proper description -ok 677 - domain_type_is(type, type, desc) fail should have the proper diagnostics -ok 678 - domain_type_isnt(schema, domain, schema, type, desc) should pass -ok 679 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper description -ok 680 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper diagnostics -ok 681 - domain_type_isnt(schema, domain, schema, type) should pass -ok 682 - domain_type_isnt(schema, domain, schema, type) should have the proper description -ok 683 - domain_type_isnt(schema, domain, schema, type) should have the proper diagnostics -ok 684 - domain_type_isnt(schema, domain, schema, type, desc) fail should fail -ok 685 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper description -ok 686 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper diagnostics -ok 687 - domain_type_isnt(schema, nondomain, schema, type, desc) should fail -ok 688 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper description -ok 689 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper diagnostics -ok 690 - domain_type_isnt(schema, type, schema, type, desc) should fail -ok 691 - domain_type_isnt(schema, type, schema, type, desc) should have the proper description -ok 692 - domain_type_isnt(schema, type, schema, type, desc) should have the proper diagnostics -ok 693 - domain_type_isnt(schema, domain, type, desc) should pass -ok 694 - domain_type_isnt(schema, domain, type, desc) should have the proper description -ok 695 - domain_type_isnt(schema, domain, type, desc) should have the proper diagnostics -ok 696 - domain_type_isnt(schema, domain, type) should pass -ok 697 - domain_type_isnt(schema, domain, type) should have the proper description -ok 698 - domain_type_isnt(schema, domain, type) should have the proper diagnostics -ok 699 - domain_type_isnt(schema, domain, type, desc) fail should fail -ok 700 - domain_type_isnt(schema, domain, type, desc) fail should have the proper description -ok 701 - domain_type_isnt(schema, domain, type, desc) fail should have the proper diagnostics -ok 702 - domain_type_isnt(schema, nondomain, type, desc) should fail -ok 703 - domain_type_isnt(schema, nondomain, type, desc) should have the proper description -ok 704 - domain_type_isnt(schema, nondomain, type, desc) should have the proper diagnostics -ok 705 - domain_type_isnt(schema, type, type, desc) should fail -ok 706 - domain_type_isnt(schema, type, type, desc) should have the proper description -ok 707 - domain_type_isnt(schema, type, type, desc) should have the proper diagnostics -ok 708 - domain_type_isnt(domain, type, desc) should pass -ok 709 - domain_type_isnt(domain, type, desc) should have the proper description -ok 710 - domain_type_isnt(domain, type, desc) should have the proper diagnostics -ok 711 - domain_type_isnt(domain, type) should pass -ok 712 - domain_type_isnt(domain, type) should have the proper description -ok 713 - domain_type_isnt(domain, type) should have the proper diagnostics -ok 714 - domain_type_isnt(domain, type, desc) fail should fail -ok 715 - domain_type_isnt(domain, type, desc) fail should have the proper description -ok 716 - domain_type_isnt(domain, type, desc) fail should have the proper diagnostics -ok 717 - domain_type_isnt(nondomain, type, desc) should fail -ok 718 - domain_type_isnt(nondomain, type, desc) should have the proper description -ok 719 - domain_type_isnt(nondomain, type, desc) should have the proper diagnostics -ok 720 - domain_type_isnt(type, type, desc) should fail -ok 721 - domain_type_isnt(type, type, desc) should have the proper description -ok 722 - domain_type_isnt(type, type, desc) should have the proper diagnostics -ok 723 - has_relation(non-existent relation) should fail -ok 724 - has_relation(non-existent relation) should have the proper description -ok 725 - has_relation(non-existent relation) should have the proper diagnostics -ok 726 - has_relation(non-existent schema, tab) should fail -ok 727 - has_relation(non-existent schema, tab) should have the proper description -ok 728 - has_relation(non-existent schema, tab) should have the proper diagnostics -ok 729 - has_relation(sch, non-existent relation, desc) should fail -ok 730 - has_relation(sch, non-existent relation, desc) should have the proper description -ok 731 - has_relation(sch, non-existent relation, desc) should have the proper diagnostics -ok 732 - has_relation(tab, desc) should pass -ok 733 - has_relation(tab, desc) should have the proper description -ok 734 - has_relation(tab, desc) should have the proper diagnostics -ok 735 - has_relation(sch, tab, desc) should pass -ok 736 - has_relation(sch, tab, desc) should have the proper description -ok 737 - has_relation(sch, tab, desc) should have the proper diagnostics -ok 738 - has_relation(sch, view, desc) should pass -ok 739 - has_relation(sch, view, desc) should have the proper description -ok 740 - has_relation(sch, view, desc) should have the proper diagnostics -ok 741 - has_relation(type, desc) should pass -ok 742 - has_relation(type, desc) should have the proper description -ok 743 - has_relation(type, desc) should have the proper diagnostics -ok 744 - hasnt_relation(non-existent relation) should pass -ok 745 - hasnt_relation(non-existent relation) should have the proper description -ok 746 - hasnt_relation(non-existent relation) should have the proper diagnostics -ok 747 - hasnt_relation(non-existent schema, tab) should pass -ok 748 - hasnt_relation(non-existent schema, tab) should have the proper description -ok 749 - hasnt_relation(non-existent schema, tab) should have the proper diagnostics -ok 750 - hasnt_relation(sch, non-existent tab, desc) should pass -ok 751 - hasnt_relation(sch, non-existent tab, desc) should have the proper description -ok 752 - hasnt_relation(sch, non-existent tab, desc) should have the proper diagnostics -ok 753 - hasnt_relation(tab, desc) should fail -ok 754 - hasnt_relation(tab, desc) should have the proper description -ok 755 - hasnt_relation(tab, desc) should have the proper diagnostics -ok 756 - hasnt_relation(sch, tab, desc) should fail -ok 757 - hasnt_relation(sch, tab, desc) should have the proper description -ok 758 - hasnt_relation(sch, tab, desc) should have the proper diagnostics -ok 759 - has_foreign_table(non-existent table) should fail -ok 760 - has_foreign_table(non-existent table) should have the proper description -ok 761 - has_foreign_table(non-existent table) should have the proper diagnostics -ok 762 - has_foreign_table(non-existent schema, tab) should fail -ok 763 - has_foreign_table(non-existent schema, tab) should have the proper description -ok 764 - has_foreign_table(non-existent schema, tab) should have the proper diagnostics -ok 765 - has_foreign_table(non-existent table, desc) should fail -ok 766 - has_foreign_table(non-existent table, desc) should have the proper description -ok 767 - has_foreign_table(non-existent table, desc) should have the proper diagnostics -ok 768 - has_foreign_table(sch, non-existent table, desc) should fail -ok 769 - has_foreign_table(sch, non-existent table, desc) should have the proper description -ok 770 - has_foreign_table(sch, non-existent table, desc) should have the proper diagnostics -ok 771 - has_foreign_table(tab, desc) should pass -ok 772 - has_foreign_table(tab, desc) should have the proper description -ok 773 - has_foreign_table(tab, desc) should have the proper diagnostics -ok 774 - has_foreign_table(sch, tab, desc) should pass -ok 775 - has_foreign_table(sch, tab, desc) should have the proper description -ok 776 - has_foreign_table(sch, tab, desc) should have the proper diagnostics -ok 777 - has_foreign_table(sch, view, desc) should fail -ok 778 - has_foreign_table(sch, view, desc) should have the proper description -ok 779 - has_foreign_table(sch, view, desc) should have the proper diagnostics -ok 780 - has_foreign_table(type, desc) should fail -ok 781 - has_foreign_table(type, desc) should have the proper description -ok 782 - has_foreign_table(type, desc) should have the proper diagnostics -ok 783 - hasnt_foreign_table(non-existent table) should pass -ok 784 - hasnt_foreign_table(non-existent table) should have the proper description -ok 785 - hasnt_foreign_table(non-existent table) should have the proper diagnostics -ok 786 - hasnt_foreign_table(non-existent schema, tab) should pass -ok 787 - hasnt_foreign_table(non-existent schema, tab) should have the proper description -ok 788 - hasnt_foreign_table(non-existent schema, tab) should have the proper diagnostics -ok 789 - hasnt_foreign_table(non-existent table, desc) should pass -ok 790 - hasnt_foreign_table(non-existent table, desc) should have the proper description -ok 791 - hasnt_foreign_table(non-existent table, desc) should have the proper diagnostics -ok 792 - hasnt_foreign_table(sch, non-existent tab, desc) should pass -ok 793 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper description -ok 794 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper diagnostics -ok 795 - hasnt_foreign_table(tab, desc) should fail -ok 796 - hasnt_foreign_table(tab, desc) should have the proper description -ok 797 - hasnt_foreign_table(tab, desc) should have the proper diagnostics -ok 798 - hasnt_foreign_table(sch, tab, desc) should fail -ok 799 - hasnt_foreign_table(sch, tab, desc) should have the proper description -ok 800 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics -ok 801 - has_materialized_view(non-existent materialized_view) should fail -ok 802 - has_materialized_view(non-existent materialized_view) should have the proper description -ok 803 - has_materialized_view(non-existent materialized_view) should have the proper diagnostics -ok 804 - has_materialized_view(non-existent materialized_view, desc) should fail -ok 805 - has_materialized_view(non-existent materialized_view, desc) should have the proper description -ok 806 - has_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics -ok 807 - has_materialized_view(sch, non-existtent materialized_view, desc) should fail -ok 808 - has_materialized_view(sch, non-existtent materialized_view, desc) should have the proper description -ok 809 - has_materialized_view(sch, non-existtent materialized_view, desc) should have the proper diagnostics -ok 810 - has_materialized_view(materialized_view, desc) should pass -ok 811 - has_materialized_view(materialized_view, desc) should have the proper description -ok 812 - has_materialized_view(materialized_view, desc) should have the proper diagnostics -ok 813 - has_materialized_view(sch, materialized_view, desc) should pass -ok 814 - has_materialized_view(sch, materialized_view, desc) should have the proper description -ok 815 - has_materialized_view(sch, materialized_view, desc) should have the proper diagnostics -ok 816 - hasnt_materialized_view(non-existent materialized_view) should pass -ok 817 - hasnt_materialized_view(non-existent materialized_view) should have the proper description -ok 818 - hasnt_materialized_view(non-existent materialized_view) should have the proper diagnostics -ok 819 - hasnt_materialized_view(non-existent materialized_view, desc) should pass -ok 820 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper description -ok 821 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics -ok 822 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should pass -ok 823 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should have the proper description -ok 824 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should have the proper diagnostics -ok 825 - hasnt_materialized_view(materialized_view, desc) should fail -ok 826 - hasnt_materialized_view(materialized_view, desc) should have the proper description -ok 827 - hasnt_materialized_view(materialized_view, desc) should have the proper diagnostics -ok 828 - hasnt_materialized_view(sch, materialized_view, desc) should fail -ok 829 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper description -ok 830 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper diagnostics +ok 315 - hasnt_domain(domain) should fail +ok 316 - hasnt_domain(domain) should have the proper description +ok 317 - hasnt_domain(domain) should have the proper diagnostics +ok 318 - hasnt_domain(domain, desc) should fail +ok 319 - hasnt_domain(domain, desc) should have the proper description +ok 320 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 321 - hasnt_domain(scheam, domain) should fail +ok 322 - hasnt_domain(scheam, domain) should have the proper description +ok 323 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 324 - hasnt_domain(schema, domain, desc) should fail +ok 325 - hasnt_domain(schema, domain, desc) should have the proper description +ok 326 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 327 - has_column(non-existent tab, col) should fail +ok 328 - has_column(non-existent tab, col) should have the proper description +ok 329 - has_column(non-existent tab, col) should have the proper diagnostics +ok 330 - has_column(non-existent tab, col, desc) should fail +ok 331 - has_column(non-existent tab, col, desc) should have the proper description +ok 332 - has_column(non-existent tab, col, desc) should have the proper diagnostics +ok 333 - has_column(non-existent sch, tab, col, desc) should fail +ok 334 - has_column(non-existent sch, tab, col, desc) should have the proper description +ok 335 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 336 - has_column(table, column) should pass +ok 337 - has_column(table, column) should have the proper description +ok 338 - has_column(table, column) should have the proper diagnostics +ok 339 - has_column(sch, tab, col, desc) should pass +ok 340 - has_column(sch, tab, col, desc) should have the proper description +ok 341 - has_column(sch, tab, col, desc) should have the proper diagnostics +ok 342 - has_column(table, camleCase column) should pass +ok 343 - has_column(table, camleCase column) should have the proper description +ok 344 - has_column(table, camleCase column) should have the proper diagnostics +ok 345 - has_column(view, column) should pass +ok 346 - has_column(view, column) should have the proper description +ok 347 - has_column(view, column) should have the proper diagnostics +ok 348 - has_column(type, column) should pass +ok 349 - has_column(type, column) should have the proper description +ok 350 - has_column(type, column) should have the proper diagnostics +ok 351 - hasnt_column(non-existent tab, col) should pass +ok 352 - hasnt_column(non-existent tab, col) should have the proper description +ok 353 - hasnt_column(non-existent tab, col) should have the proper diagnostics +ok 354 - hasnt_column(non-existent tab, col, desc) should pass +ok 355 - hasnt_column(non-existent tab, col, desc) should have the proper description +ok 356 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics +ok 357 - hasnt_column(non-existent sch, tab, col, desc) should pass +ok 358 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description +ok 359 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 360 - hasnt_column(table, column) should fail +ok 361 - hasnt_column(table, column) should have the proper description +ok 362 - hasnt_column(table, column) should have the proper diagnostics +ok 363 - hasnt_column(sch, tab, col, desc) should fail +ok 364 - hasnt_column(sch, tab, col, desc) should have the proper description +ok 365 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics +ok 366 - hasnt_column(view, column) should pass +ok 367 - hasnt_column(view, column) should have the proper description +ok 368 - hasnt_column(view, column) should have the proper diagnostics +ok 369 - hasnt_column(type, column) should pass +ok 370 - hasnt_column(type, column) should have the proper description +ok 371 - hasnt_column(type, column) should have the proper diagnostics +ok 372 - has_cast( src, targ, schema, func, desc) should pass +ok 373 - has_cast( src, targ, schema, func, desc) should have the proper description +ok 374 - has_cast( src, targ, schema, func, desc) should have the proper diagnostics +ok 375 - has_cast( src, targ, schema, func ) should pass +ok 376 - has_cast( src, targ, schema, func ) should have the proper description +ok 377 - has_cast( src, targ, schema, func ) should have the proper diagnostics +ok 378 - has_cast( src, targ, func, desc ) should pass +ok 379 - has_cast( src, targ, func, desc ) should have the proper description +ok 380 - has_cast( src, targ, func, desc ) should have the proper diagnostics +ok 381 - has_cast( src, targ, func) should pass +ok 382 - has_cast( src, targ, func) should have the proper description +ok 383 - has_cast( src, targ, func) should have the proper diagnostics +ok 384 - has_cast( src, targ, desc ) should pass +ok 385 - has_cast( src, targ, desc ) should have the proper description +ok 386 - has_cast( src, targ, desc ) should have the proper diagnostics +ok 387 - has_cast( src, targ ) should pass +ok 388 - has_cast( src, targ ) should have the proper description +ok 389 - has_cast( src, targ ) should have the proper diagnostics +ok 390 - has_cast( src, targ, schema, func, desc) fail should fail +ok 391 - has_cast( src, targ, schema, func, desc) fail should have the proper description +ok 392 - has_cast( src, targ, schema, func, desc) fail should have the proper diagnostics +ok 393 - has_cast( src, targ, func, desc ) fail should fail +ok 394 - has_cast( src, targ, func, desc ) fail should have the proper description +ok 395 - has_cast( src, targ, func, desc ) fail should have the proper diagnostics +ok 396 - has_cast( src, targ, desc ) fail should fail +ok 397 - has_cast( src, targ, desc ) fail should have the proper description +ok 398 - has_cast( src, targ, desc ) fail should have the proper diagnostics +ok 399 - hasnt_cast( src, targ, schema, func, desc) should fail +ok 400 - hasnt_cast( src, targ, schema, func, desc) should have the proper description +ok 401 - hasnt_cast( src, targ, schema, func, desc) should have the proper diagnostics +ok 402 - hasnt_cast( src, targ, schema, func ) should fail +ok 403 - hasnt_cast( src, targ, schema, func ) should have the proper description +ok 404 - hasnt_cast( src, targ, schema, func ) should have the proper diagnostics +ok 405 - hasnt_cast( src, targ, func, desc ) should fail +ok 406 - hasnt_cast( src, targ, func, desc ) should have the proper description +ok 407 - hasnt_cast( src, targ, func, desc ) should have the proper diagnostics +ok 408 - hasnt_cast( src, targ, func) should fail +ok 409 - hasnt_cast( src, targ, func) should have the proper description +ok 410 - hasnt_cast( src, targ, func) should have the proper diagnostics +ok 411 - hasnt_cast( src, targ, desc ) should fail +ok 412 - hasnt_cast( src, targ, desc ) should have the proper description +ok 413 - hasnt_cast( src, targ, desc ) should have the proper diagnostics +ok 414 - hasnt_cast( src, targ ) should fail +ok 415 - hasnt_cast( src, targ ) should have the proper description +ok 416 - hasnt_cast( src, targ ) should have the proper diagnostics +ok 417 - hasnt_cast( src, targ, schema, func, desc) fail should pass +ok 418 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper description +ok 419 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper diagnostics +ok 420 - hasnt_cast( src, targ, func, desc ) fail should pass +ok 421 - hasnt_cast( src, targ, func, desc ) fail should have the proper description +ok 422 - hasnt_cast( src, targ, func, desc ) fail should have the proper diagnostics +ok 423 - hasnt_cast( src, targ, desc ) fail should pass +ok 424 - hasnt_cast( src, targ, desc ) fail should have the proper description +ok 425 - hasnt_cast( src, targ, desc ) fail should have the proper diagnostics +ok 426 - cast_context_is( src, targ, context, desc ) should pass +ok 427 - cast_context_is( src, targ, context, desc ) should have the proper description +ok 428 - cast_context_is( src, targ, context, desc ) should have the proper diagnostics +ok 429 - cast_context_is( src, targ, context ) should pass +ok 430 - cast_context_is( src, targ, context ) should have the proper description +ok 431 - cast_context_is( src, targ, context ) should have the proper diagnostics +ok 432 - cast_context_is( src, targ, i, desc ) should pass +ok 433 - cast_context_is( src, targ, i, desc ) should have the proper description +ok 434 - cast_context_is( src, targ, i, desc ) should have the proper diagnostics +ok 435 - cast_context_is( src, targ, IMPL, desc ) should pass +ok 436 - cast_context_is( src, targ, IMPL, desc ) should have the proper description +ok 437 - cast_context_is( src, targ, IMPL, desc ) should have the proper diagnostics +ok 438 - cast_context_is( src, targ, assignment, desc ) should pass +ok 439 - cast_context_is( src, targ, assignment, desc ) should have the proper description +ok 440 - cast_context_is( src, targ, assignment, desc ) should have the proper diagnostics +ok 441 - cast_context_is( src, targ, a, desc ) should pass +ok 442 - cast_context_is( src, targ, a, desc ) should have the proper description +ok 443 - cast_context_is( src, targ, a, desc ) should have the proper diagnostics +ok 444 - cast_context_is( src, targ, ASS, desc ) should pass +ok 445 - cast_context_is( src, targ, ASS, desc ) should have the proper description +ok 446 - cast_context_is( src, targ, ASS, desc ) should have the proper diagnostics +ok 447 - cast_context_is( src, targ, explicit, desc ) should pass +ok 448 - cast_context_is( src, targ, explicit, desc ) should have the proper description +ok 449 - cast_context_is( src, targ, explicit, desc ) should have the proper diagnostics +ok 450 - cast_context_is( src, targ, e, desc ) should pass +ok 451 - cast_context_is( src, targ, e, desc ) should have the proper description +ok 452 - cast_context_is( src, targ, e, desc ) should have the proper diagnostics +ok 453 - cast_context_is( src, targ, EX, desc ) should pass +ok 454 - cast_context_is( src, targ, EX, desc ) should have the proper description +ok 455 - cast_context_is( src, targ, EX, desc ) should have the proper diagnostics +ok 456 - cast_context_is( src, targ, context, desc ) fail should fail +ok 457 - cast_context_is( src, targ, context, desc ) fail should have the proper description +ok 458 - cast_context_is( src, targ, context, desc ) fail should have the proper diagnostics +ok 459 - cast_context_is( src, targ, context ) fail should fail +ok 460 - cast_context_is( src, targ, context ) fail should have the proper description +ok 461 - cast_context_is( src, targ, context ) fail should have the proper diagnostics +ok 462 - cast_context_is( src, targ, context, desc ) noexist should fail +ok 463 - cast_context_is( src, targ, context, desc ) noexist should have the proper description +ok 464 - cast_context_is( src, targ, context, desc ) noexist should have the proper diagnostics +ok 465 - has_operator( left, schema, name, right, result, desc ) should pass +ok 466 - has_operator( left, schema, name, right, result, desc ) should have the proper description +ok 467 - has_operator( left, schema, name, right, result, desc ) should have the proper diagnostics +ok 468 - has_operator( left, schema, name, right, result ) should pass +ok 469 - has_operator( left, schema, name, right, result ) should have the proper description +ok 470 - has_operator( left, schema, name, right, result ) should have the proper diagnostics +ok 471 - has_operator( left, name, right, result, desc ) should pass +ok 472 - has_operator( left, name, right, result, desc ) should have the proper description +ok 473 - has_operator( left, name, right, result, desc ) should have the proper diagnostics +ok 474 - has_operator( left, name, right, result ) should pass +ok 475 - has_operator( left, name, right, result ) should have the proper description +ok 476 - has_operator( left, name, right, result ) should have the proper diagnostics +ok 477 - has_operator( left, name, right, desc ) should pass +ok 478 - has_operator( left, name, right, desc ) should have the proper description +ok 479 - has_operator( left, name, right, desc ) should have the proper diagnostics +ok 480 - has_operator( left, name, right ) should pass +ok 481 - has_operator( left, name, right ) should have the proper description +ok 482 - has_operator( left, name, right ) should have the proper diagnostics +ok 483 - has_operator( left, schema, name, right, result, desc ) fail should fail +ok 484 - has_operator( left, schema, name, right, result, desc ) fail should have the proper description +ok 485 - has_operator( left, schema, name, right, result, desc ) fail should have the proper diagnostics +ok 486 - has_operator( left, schema, name, right, result ) fail should fail +ok 487 - has_operator( left, schema, name, right, result ) fail should have the proper description +ok 488 - has_operator( left, schema, name, right, result ) fail should have the proper diagnostics +ok 489 - has_operator( left, name, right, result, desc ) fail should fail +ok 490 - has_operator( left, name, right, result, desc ) fail should have the proper description +ok 491 - has_operator( left, name, right, result, desc ) fail should have the proper diagnostics +ok 492 - has_operator( left, name, right, result ) fail should fail +ok 493 - has_operator( left, name, right, result ) fail should have the proper description +ok 494 - has_operator( left, name, right, result ) fail should have the proper diagnostics +ok 495 - has_operator( left, name, right, desc ) fail should fail +ok 496 - has_operator( left, name, right, desc ) fail should have the proper description +ok 497 - has_operator( left, name, right, desc ) fail should have the proper diagnostics +ok 498 - has_operator( left, name, right ) fail should fail +ok 499 - has_operator( left, name, right ) fail should have the proper description +ok 500 - has_operator( left, name, right ) fail should have the proper diagnostics +ok 501 - has_leftop( schema, name, right, result, desc ) should pass +ok 502 - has_leftop( schema, name, right, result, desc ) should have the proper description +ok 503 - has_leftop( schema, name, right, result, desc ) should have the proper diagnostics +ok 504 - has_leftop( schema, name, right, result ) should pass +ok 505 - has_leftop( schema, name, right, result ) should have the proper description +ok 506 - has_leftop( schema, name, right, result ) should have the proper diagnostics +ok 507 - has_leftop( name, right, result, desc ) should pass +ok 508 - has_leftop( name, right, result, desc ) should have the proper description +ok 509 - has_leftop( name, right, result, desc ) should have the proper diagnostics +ok 510 - has_leftop( name, right, result ) should pass +ok 511 - has_leftop( name, right, result ) should have the proper description +ok 512 - has_leftop( name, right, result ) should have the proper diagnostics +ok 513 - has_leftop( name, right, desc ) should pass +ok 514 - has_leftop( name, right, desc ) should have the proper description +ok 515 - has_leftop( name, right, desc ) should have the proper diagnostics +ok 516 - has_leftop( name, right ) should pass +ok 517 - has_leftop( name, right ) should have the proper description +ok 518 - has_leftop( name, right ) should have the proper diagnostics +ok 519 - has_leftop( schema, name, right, result, desc ) fail should fail +ok 520 - has_leftop( schema, name, right, result, desc ) fail should have the proper description +ok 521 - has_leftop( schema, name, right, result, desc ) fail should have the proper diagnostics +ok 522 - has_leftop( schema, name, right, result ) fail should fail +ok 523 - has_leftop( schema, name, right, result ) fail should have the proper description +ok 524 - has_leftop( schema, name, right, result ) fail should have the proper diagnostics +ok 525 - has_leftop( name, right, result, desc ) fail should fail +ok 526 - has_leftop( name, right, result, desc ) fail should have the proper description +ok 527 - has_leftop( name, right, result, desc ) fail should have the proper diagnostics +ok 528 - has_leftop( name, right, result ) fail should fail +ok 529 - has_leftop( name, right, result ) fail should have the proper description +ok 530 - has_leftop( name, right, result ) fail should have the proper diagnostics +ok 531 - has_leftop( name, right, desc ) fail should fail +ok 532 - has_leftop( name, right, desc ) fail should have the proper description +ok 533 - has_leftop( name, right, desc ) fail should have the proper diagnostics +ok 534 - has_leftop( name, right ) fail should fail +ok 535 - has_leftop( name, right ) fail should have the proper description +ok 536 - has_leftop( name, right ) fail should have the proper diagnostics +ok 537 - has_rightop( left, schema, name, result, desc ) should pass +ok 538 - has_rightop( left, schema, name, result, desc ) should have the proper description +ok 539 - has_rightop( left, schema, name, result, desc ) should have the proper diagnostics +ok 540 - has_rightop( left, schema, name, result ) should pass +ok 541 - has_rightop( left, schema, name, result ) should have the proper description +ok 542 - has_rightop( left, schema, name, result ) should have the proper diagnostics +ok 543 - has_rightop( left, name, result, desc ) should pass +ok 544 - has_rightop( left, name, result, desc ) should have the proper description +ok 545 - has_rightop( left, name, result, desc ) should have the proper diagnostics +ok 546 - has_rightop( left, name, result ) should pass +ok 547 - has_rightop( left, name, result ) should have the proper description +ok 548 - has_rightop( left, name, result ) should have the proper diagnostics +ok 549 - has_rightop( left, name, desc ) should pass +ok 550 - has_rightop( left, name, desc ) should have the proper description +ok 551 - has_rightop( left, name, desc ) should have the proper diagnostics +ok 552 - has_rightop( left, name ) should pass +ok 553 - has_rightop( left, name ) should have the proper description +ok 554 - has_rightop( left, name ) should have the proper diagnostics +ok 555 - has_rightop( left, schema, name, result, desc ) fail should fail +ok 556 - has_rightop( left, schema, name, result, desc ) fail should have the proper description +ok 557 - has_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics +ok 558 - has_rightop( left, schema, name, result ) fail should fail +ok 559 - has_rightop( left, schema, name, result ) fail should have the proper description +ok 560 - has_rightop( left, schema, name, result ) fail should have the proper diagnostics +ok 561 - has_rightop( left, name, result, desc ) fail should fail +ok 562 - has_rightop( left, name, result, desc ) fail should have the proper description +ok 563 - has_rightop( left, name, result, desc ) fail should have the proper diagnostics +ok 564 - has_rightop( left, name, result ) fail should fail +ok 565 - has_rightop( left, name, result ) fail should have the proper description +ok 566 - has_rightop( left, name, result ) fail should have the proper diagnostics +ok 567 - has_rightop( left, name, desc ) fail should fail +ok 568 - has_rightop( left, name, desc ) fail should have the proper description +ok 569 - has_rightop( left, name, desc ) fail should have the proper diagnostics +ok 570 - has_rightop( left, name ) fail should fail +ok 571 - has_rightop( left, name ) fail should have the proper description +ok 572 - has_rightop( left, name ) fail should have the proper diagnostics +ok 573 - has_language(language) should pass +ok 574 - has_language(language) should have the proper description +ok 575 - has_language(language) should have the proper diagnostics +ok 576 - has_language(language, desc) should pass +ok 577 - has_language(language, desc) should have the proper description +ok 578 - has_language(language, desc) should have the proper diagnostics +ok 579 - has_language(nonexistent language) should fail +ok 580 - has_language(nonexistent language) should have the proper description +ok 581 - has_language(nonexistent language) should have the proper diagnostics +ok 582 - has_language(nonexistent language, desc) should fail +ok 583 - has_language(nonexistent language, desc) should have the proper description +ok 584 - has_language(nonexistent language, desc) should have the proper diagnostics +ok 585 - hasnt_language(language) should fail +ok 586 - hasnt_language(language) should have the proper description +ok 587 - hasnt_language(language) should have the proper diagnostics +ok 588 - hasnt_language(language, desc) should fail +ok 589 - hasnt_language(language, desc) should have the proper description +ok 590 - hasnt_language(language, desc) should have the proper diagnostics +ok 591 - hasnt_language(nonexistent language) should pass +ok 592 - hasnt_language(nonexistent language) should have the proper description +ok 593 - hasnt_language(nonexistent language) should have the proper diagnostics +ok 594 - hasnt_language(nonexistent language, desc) should pass +ok 595 - hasnt_language(nonexistent language, desc) should have the proper description +ok 596 - hasnt_language(nonexistent language, desc) should have the proper diagnostics +ok 597 - language_is_trusted(language, desc) should pass +ok 598 - language_is_trusted(language, desc) should have the proper description +ok 599 - language_is_trusted(language, desc) should have the proper diagnostics +ok 600 - language_is_trusted(language) should pass +ok 601 - language_is_trusted(language) should have the proper description +ok 602 - language_is_trusted(language) should have the proper diagnostics +ok 603 - language_is_trusted(language, desc) fail should fail +ok 604 - language_is_trusted(language, desc) fail should have the proper description +ok 605 - language_is_trusted(language, desc) fail should have the proper diagnostics +ok 606 - language_is_trusted(language, desc) non-existent should fail +ok 607 - language_is_trusted(language, desc) non-existent should have the proper description +ok 608 - language_is_trusted(language, desc) non-existent should have the proper diagnostics +ok 609 - has_opclass( schema, name, desc ) should pass +ok 610 - has_opclass( schema, name, desc ) should have the proper description +ok 611 - has_opclass( schema, name, desc ) should have the proper diagnostics +ok 612 - has_opclass( schema, name ) should pass +ok 613 - has_opclass( schema, name ) should have the proper description +ok 614 - has_opclass( schema, name ) should have the proper diagnostics +ok 615 - has_opclass( name, desc ) should pass +ok 616 - has_opclass( name, desc ) should have the proper description +ok 617 - has_opclass( name, desc ) should have the proper diagnostics +ok 618 - has_opclass( name ) should pass +ok 619 - has_opclass( name ) should have the proper description +ok 620 - has_opclass( name ) should have the proper diagnostics +ok 621 - has_opclass( schema, name, desc ) fail should fail +ok 622 - has_opclass( schema, name, desc ) fail should have the proper description +ok 623 - has_opclass( schema, name, desc ) fail should have the proper diagnostics +ok 624 - has_opclass( name, desc ) fail should fail +ok 625 - has_opclass( name, desc ) fail should have the proper description +ok 626 - has_opclass( name, desc ) fail should have the proper diagnostics +ok 627 - hasnt_opclass( schema, name, desc ) should fail +ok 628 - hasnt_opclass( schema, name, desc ) should have the proper description +ok 629 - hasnt_opclass( schema, name, desc ) should have the proper diagnostics +ok 630 - hasnt_opclass( schema, name ) should fail +ok 631 - hasnt_opclass( schema, name ) should have the proper description +ok 632 - hasnt_opclass( schema, name ) should have the proper diagnostics +ok 633 - hasnt_opclass( name, desc ) should fail +ok 634 - hasnt_opclass( name, desc ) should have the proper description +ok 635 - hasnt_opclass( name, desc ) should have the proper diagnostics +ok 636 - hasnt_opclass( name ) should fail +ok 637 - hasnt_opclass( name ) should have the proper description +ok 638 - hasnt_opclass( name ) should have the proper diagnostics +ok 639 - hasnt_opclass( schema, name, desc ) fail should pass +ok 640 - hasnt_opclass( schema, name, desc ) fail should have the proper description +ok 641 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnostics +ok 642 - hasnt_opclass( name, desc ) fail should pass +ok 643 - hasnt_opclass( name, desc ) fail should have the proper description +ok 644 - hasnt_opclass( name, desc ) fail should have the proper diagnostics +ok 645 - domain_type_is(schema, domain, schema, type, desc) should pass +ok 646 - domain_type_is(schema, domain, schema, type, desc) should have the proper description +ok 647 - domain_type_is(schema, domain, schema, type, desc) should have the proper diagnostics +ok 648 - domain_type_is(schema, domain, schema, type) should pass +ok 649 - domain_type_is(schema, domain, schema, type) should have the proper description +ok 650 - domain_type_is(schema, domain, schema, type) should have the proper diagnostics +ok 651 - domain_type_is(schema, domain, schema, type, desc) fail should fail +ok 652 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper description +ok 653 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 654 - domain_type_is(schema, nondomain, schema, type, desc) should fail +ok 655 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper description +ok 656 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 657 - domain_type_is(schema, type, schema, type, desc) fail should fail +ok 658 - domain_type_is(schema, type, schema, type, desc) fail should have the proper description +ok 659 - domain_type_is(schema, type, schema, type, desc) fail should have the proper diagnostics +ok 660 - domain_type_is(schema, domain, type, desc) should pass +ok 661 - domain_type_is(schema, domain, type, desc) should have the proper description +ok 662 - domain_type_is(schema, domain, type, desc) should have the proper diagnostics +ok 663 - domain_type_is(schema, domain, type) should pass +ok 664 - domain_type_is(schema, domain, type) should have the proper description +ok 665 - domain_type_is(schema, domain, type) should have the proper diagnostics +ok 666 - domain_type_is(schema, domain, type, desc) fail should fail +ok 667 - domain_type_is(schema, domain, type, desc) fail should have the proper description +ok 668 - domain_type_is(schema, domain, type, desc) fail should have the proper diagnostics +ok 669 - domain_type_is(schema, nondomain, type, desc) should fail +ok 670 - domain_type_is(schema, nondomain, type, desc) should have the proper description +ok 671 - domain_type_is(schema, nondomain, type, desc) should have the proper diagnostics +ok 672 - domain_type_is(schema, type, type, desc) fail should fail +ok 673 - domain_type_is(schema, type, type, desc) fail should have the proper description +ok 674 - domain_type_is(schema, type, type, desc) fail should have the proper diagnostics +ok 675 - domain_type_is(domain, type, desc) should pass +ok 676 - domain_type_is(domain, type, desc) should have the proper description +ok 677 - domain_type_is(domain, type, desc) should have the proper diagnostics +ok 678 - domain_type_is(domain, type) should pass +ok 679 - domain_type_is(domain, type) should have the proper description +ok 680 - domain_type_is(domain, type) should have the proper diagnostics +ok 681 - domain_type_is(domain, type, desc) fail should fail +ok 682 - domain_type_is(domain, type, desc) fail should have the proper description +ok 683 - domain_type_is(domain, type, desc) fail should have the proper diagnostics +ok 684 - domain_type_is(nondomain, type, desc) should fail +ok 685 - domain_type_is(nondomain, type, desc) should have the proper description +ok 686 - domain_type_is(nondomain, type, desc) should have the proper diagnostics +ok 687 - domain_type_is(type, type, desc) fail should fail +ok 688 - domain_type_is(type, type, desc) fail should have the proper description +ok 689 - domain_type_is(type, type, desc) fail should have the proper diagnostics +ok 690 - domain_type_isnt(schema, domain, schema, type, desc) should pass +ok 691 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper description +ok 692 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper diagnostics +ok 693 - domain_type_isnt(schema, domain, schema, type) should pass +ok 694 - domain_type_isnt(schema, domain, schema, type) should have the proper description +ok 695 - domain_type_isnt(schema, domain, schema, type) should have the proper diagnostics +ok 696 - domain_type_isnt(schema, domain, schema, type, desc) fail should fail +ok 697 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper description +ok 698 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 699 - domain_type_isnt(schema, nondomain, schema, type, desc) should fail +ok 700 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper description +ok 701 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 702 - domain_type_isnt(schema, type, schema, type, desc) should fail +ok 703 - domain_type_isnt(schema, type, schema, type, desc) should have the proper description +ok 704 - domain_type_isnt(schema, type, schema, type, desc) should have the proper diagnostics +ok 705 - domain_type_isnt(schema, domain, type, desc) should pass +ok 706 - domain_type_isnt(schema, domain, type, desc) should have the proper description +ok 707 - domain_type_isnt(schema, domain, type, desc) should have the proper diagnostics +ok 708 - domain_type_isnt(schema, domain, type) should pass +ok 709 - domain_type_isnt(schema, domain, type) should have the proper description +ok 710 - domain_type_isnt(schema, domain, type) should have the proper diagnostics +ok 711 - domain_type_isnt(schema, domain, type, desc) fail should fail +ok 712 - domain_type_isnt(schema, domain, type, desc) fail should have the proper description +ok 713 - domain_type_isnt(schema, domain, type, desc) fail should have the proper diagnostics +ok 714 - domain_type_isnt(schema, nondomain, type, desc) should fail +ok 715 - domain_type_isnt(schema, nondomain, type, desc) should have the proper description +ok 716 - domain_type_isnt(schema, nondomain, type, desc) should have the proper diagnostics +ok 717 - domain_type_isnt(schema, type, type, desc) should fail +ok 718 - domain_type_isnt(schema, type, type, desc) should have the proper description +ok 719 - domain_type_isnt(schema, type, type, desc) should have the proper diagnostics +ok 720 - domain_type_isnt(domain, type, desc) should pass +ok 721 - domain_type_isnt(domain, type, desc) should have the proper description +ok 722 - domain_type_isnt(domain, type, desc) should have the proper diagnostics +ok 723 - domain_type_isnt(domain, type) should pass +ok 724 - domain_type_isnt(domain, type) should have the proper description +ok 725 - domain_type_isnt(domain, type) should have the proper diagnostics +ok 726 - domain_type_isnt(domain, type, desc) fail should fail +ok 727 - domain_type_isnt(domain, type, desc) fail should have the proper description +ok 728 - domain_type_isnt(domain, type, desc) fail should have the proper diagnostics +ok 729 - domain_type_isnt(nondomain, type, desc) should fail +ok 730 - domain_type_isnt(nondomain, type, desc) should have the proper description +ok 731 - domain_type_isnt(nondomain, type, desc) should have the proper diagnostics +ok 732 - domain_type_isnt(type, type, desc) should fail +ok 733 - domain_type_isnt(type, type, desc) should have the proper description +ok 734 - domain_type_isnt(type, type, desc) should have the proper diagnostics +ok 735 - has_relation(non-existent relation) should fail +ok 736 - has_relation(non-existent relation) should have the proper description +ok 737 - has_relation(non-existent relation) should have the proper diagnostics +ok 738 - has_relation(non-existent schema, tab) should fail +ok 739 - has_relation(non-existent schema, tab) should have the proper description +ok 740 - has_relation(non-existent schema, tab) should have the proper diagnostics +ok 741 - has_relation(sch, non-existent relation, desc) should fail +ok 742 - has_relation(sch, non-existent relation, desc) should have the proper description +ok 743 - has_relation(sch, non-existent relation, desc) should have the proper diagnostics +ok 744 - has_relation(tab, desc) should pass +ok 745 - has_relation(tab, desc) should have the proper description +ok 746 - has_relation(tab, desc) should have the proper diagnostics +ok 747 - has_relation(sch, tab, desc) should pass +ok 748 - has_relation(sch, tab, desc) should have the proper description +ok 749 - has_relation(sch, tab, desc) should have the proper diagnostics +ok 750 - has_relation(sch, view, desc) should pass +ok 751 - has_relation(sch, view, desc) should have the proper description +ok 752 - has_relation(sch, view, desc) should have the proper diagnostics +ok 753 - has_relation(type, desc) should pass +ok 754 - has_relation(type, desc) should have the proper description +ok 755 - has_relation(type, desc) should have the proper diagnostics +ok 756 - hasnt_relation(non-existent relation) should pass +ok 757 - hasnt_relation(non-existent relation) should have the proper description +ok 758 - hasnt_relation(non-existent relation) should have the proper diagnostics +ok 759 - hasnt_relation(non-existent schema, tab) should pass +ok 760 - hasnt_relation(non-existent schema, tab) should have the proper description +ok 761 - hasnt_relation(non-existent schema, tab) should have the proper diagnostics +ok 762 - hasnt_relation(sch, non-existent tab, desc) should pass +ok 763 - hasnt_relation(sch, non-existent tab, desc) should have the proper description +ok 764 - hasnt_relation(sch, non-existent tab, desc) should have the proper diagnostics +ok 765 - hasnt_relation(tab, desc) should fail +ok 766 - hasnt_relation(tab, desc) should have the proper description +ok 767 - hasnt_relation(tab, desc) should have the proper diagnostics +ok 768 - hasnt_relation(sch, tab, desc) should fail +ok 769 - hasnt_relation(sch, tab, desc) should have the proper description +ok 770 - hasnt_relation(sch, tab, desc) should have the proper diagnostics +ok 771 - has_foreign_table(non-existent table) should fail +ok 772 - has_foreign_table(non-existent table) should have the proper description +ok 773 - has_foreign_table(non-existent table) should have the proper diagnostics +ok 774 - has_foreign_table(non-existent schema, tab) should fail +ok 775 - has_foreign_table(non-existent schema, tab) should have the proper description +ok 776 - has_foreign_table(non-existent schema, tab) should have the proper diagnostics +ok 777 - has_foreign_table(non-existent table, desc) should fail +ok 778 - has_foreign_table(non-existent table, desc) should have the proper description +ok 779 - has_foreign_table(non-existent table, desc) should have the proper diagnostics +ok 780 - has_foreign_table(sch, non-existent table, desc) should fail +ok 781 - has_foreign_table(sch, non-existent table, desc) should have the proper description +ok 782 - has_foreign_table(sch, non-existent table, desc) should have the proper diagnostics +ok 783 - has_foreign_table(tab, desc) should pass +ok 784 - has_foreign_table(tab, desc) should have the proper description +ok 785 - has_foreign_table(tab, desc) should have the proper diagnostics +ok 786 - has_foreign_table(sch, tab, desc) should pass +ok 787 - has_foreign_table(sch, tab, desc) should have the proper description +ok 788 - has_foreign_table(sch, tab, desc) should have the proper diagnostics +ok 789 - has_foreign_table(sch, view, desc) should fail +ok 790 - has_foreign_table(sch, view, desc) should have the proper description +ok 791 - has_foreign_table(sch, view, desc) should have the proper diagnostics +ok 792 - has_foreign_table(type, desc) should fail +ok 793 - has_foreign_table(type, desc) should have the proper description +ok 794 - has_foreign_table(type, desc) should have the proper diagnostics +ok 795 - hasnt_foreign_table(non-existent table) should pass +ok 796 - hasnt_foreign_table(non-existent table) should have the proper description +ok 797 - hasnt_foreign_table(non-existent table) should have the proper diagnostics +ok 798 - hasnt_foreign_table(non-existent schema, tab) should pass +ok 799 - hasnt_foreign_table(non-existent schema, tab) should have the proper description +ok 800 - hasnt_foreign_table(non-existent schema, tab) should have the proper diagnostics +ok 801 - hasnt_foreign_table(non-existent table, desc) should pass +ok 802 - hasnt_foreign_table(non-existent table, desc) should have the proper description +ok 803 - hasnt_foreign_table(non-existent table, desc) should have the proper diagnostics +ok 804 - hasnt_foreign_table(sch, non-existent tab, desc) should pass +ok 805 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper description +ok 806 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper diagnostics +ok 807 - hasnt_foreign_table(tab, desc) should fail +ok 808 - hasnt_foreign_table(tab, desc) should have the proper description +ok 809 - hasnt_foreign_table(tab, desc) should have the proper diagnostics +ok 810 - hasnt_foreign_table(sch, tab, desc) should fail +ok 811 - hasnt_foreign_table(sch, tab, desc) should have the proper description +ok 812 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics +ok 813 - has_materialized_view(non-existent materialized_view) should fail +ok 814 - has_materialized_view(non-existent materialized_view) should have the proper description +ok 815 - has_materialized_view(non-existent materialized_view) should have the proper diagnostics +ok 816 - has_materialized_view(non-existent materialized_view, desc) should fail +ok 817 - has_materialized_view(non-existent materialized_view, desc) should have the proper description +ok 818 - has_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics +ok 819 - has_materialized_view(sch, non-existtent materialized_view, desc) should fail +ok 820 - has_materialized_view(sch, non-existtent materialized_view, desc) should have the proper description +ok 821 - has_materialized_view(sch, non-existtent materialized_view, desc) should have the proper diagnostics +ok 822 - has_materialized_view(materialized_view, desc) should pass +ok 823 - has_materialized_view(materialized_view, desc) should have the proper description +ok 824 - has_materialized_view(materialized_view, desc) should have the proper diagnostics +ok 825 - has_materialized_view(sch, materialized_view, desc) should pass +ok 826 - has_materialized_view(sch, materialized_view, desc) should have the proper description +ok 827 - has_materialized_view(sch, materialized_view, desc) should have the proper diagnostics +ok 828 - hasnt_materialized_view(non-existent materialized_view) should pass +ok 829 - hasnt_materialized_view(non-existent materialized_view) should have the proper description +ok 830 - hasnt_materialized_view(non-existent materialized_view) should have the proper diagnostics +ok 831 - hasnt_materialized_view(non-existent materialized_view, desc) should pass +ok 832 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper description +ok 833 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics +ok 834 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should pass +ok 835 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should have the proper description +ok 836 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should have the proper diagnostics +ok 837 - hasnt_materialized_view(materialized_view, desc) should fail +ok 838 - hasnt_materialized_view(materialized_view, desc) should have the proper description +ok 839 - hasnt_materialized_view(materialized_view, desc) should have the proper diagnostics +ok 840 - hasnt_materialized_view(sch, materialized_view, desc) should fail +ok 841 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper description +ok 842 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper diagnostics diff --git a/test/expected/ownership.out b/test/expected/ownership.out index 6c84101dec28..b62b5f4dd42e 100644 --- a/test/expected/ownership.out +++ b/test/expected/ownership.out @@ -1,5 +1,5 @@ \unset ECHO -1..384 +1..411 ok 1 - db_owner_is(db, user, desc) should pass ok 2 - db_owner_is(db, user, desc) should have the proper description ok 3 - db_owner_is(db, user, desc) should have the proper diagnostics @@ -45,342 +45,369 @@ ok 42 - relation_owner_is(tab, user) should have the proper diagnostics ok 43 - relation_owner_is(non-tab, user) should fail ok 44 - relation_owner_is(non-tab, user) should have the proper description ok 45 - relation_owner_is(non-tab, user) should have the proper diagnostics -ok 46 - relation_owner_is(sch, seq, user, desc) should pass -ok 47 - relation_owner_is(sch, seq, user, desc) should have the proper description -ok 48 - relation_owner_is(sch, seq, user, desc) should have the proper diagnostics -ok 49 - relation_owner_is(sch, seq, user) should pass -ok 50 - relation_owner_is(sch, seq, user) should have the proper description -ok 51 - relation_owner_is(sch, seq, user) should have the proper diagnostics -ok 52 - relation_owner_is(non-sch, seq, user) should fail -ok 53 - relation_owner_is(non-sch, seq, user) should have the proper description -ok 54 - relation_owner_is(non-sch, seq, user) should have the proper diagnostics -ok 55 - relation_owner_is(sch, non-seq, user) should fail -ok 56 - relation_owner_is(sch, non-seq, user) should have the proper description -ok 57 - relation_owner_is(sch, non-seq, user) should have the proper diagnostics -ok 58 - relation_owner_is(seq, user, desc) should pass -ok 59 - relation_owner_is(seq, user, desc) should have the proper description -ok 60 - relation_owner_is(seq, user, desc) should have the proper diagnostics -ok 61 - relation_owner_is(seq, user) should pass -ok 62 - relation_owner_is(seq, user) should have the proper description -ok 63 - relation_owner_is(seq, user) should have the proper diagnostics -ok 64 - relation_owner_is(non-seq, user) should fail -ok 65 - relation_owner_is(non-seq, user) should have the proper description -ok 66 - relation_owner_is(non-seq, user) should have the proper diagnostics -ok 67 - table_owner_is(sch, tab, user, desc) should pass -ok 68 - table_owner_is(sch, tab, user, desc) should have the proper description -ok 69 - table_owner_is(sch, tab, user, desc) should have the proper diagnostics -ok 70 - table_owner_is(sch, tab, user) should pass -ok 71 - table_owner_is(sch, tab, user) should have the proper description -ok 72 - table_owner_is(sch, tab, user) should have the proper diagnostics -ok 73 - table_owner_is(non-sch, tab, user) should fail -ok 74 - table_owner_is(non-sch, tab, user) should have the proper description -ok 75 - table_owner_is(non-sch, tab, user) should have the proper diagnostics -ok 76 - table_owner_is(sch, non-tab, user) should fail -ok 77 - table_owner_is(sch, non-tab, user) should have the proper description -ok 78 - table_owner_is(sch, non-tab, user) should have the proper diagnostics -ok 79 - table_owner_is(tab, user, desc) should pass -ok 80 - table_owner_is(tab, user, desc) should have the proper description -ok 81 - table_owner_is(tab, user, desc) should have the proper diagnostics -ok 82 - table_owner_is(tab, user) should pass -ok 83 - table_owner_is(tab, user) should have the proper description -ok 84 - table_owner_is(tab, user) should have the proper diagnostics -ok 85 - table_owner_is(non-tab, user) should fail -ok 86 - table_owner_is(non-tab, user) should have the proper description -ok 87 - table_owner_is(non-tab, user) should have the proper diagnostics -ok 88 - table_owner_is(sch, seq, user, desc) should fail -ok 89 - table_owner_is(sch, seq, user, desc) should have the proper description -ok 90 - table_owner_is(sch, seq, user, desc) should have the proper diagnostics -ok 91 - table_owner_is(seq, user, desc) should fail -ok 92 - table_owner_is(seq, user, desc) should have the proper description -ok 93 - table_owner_is(seq, user, desc) should have the proper diagnostics -ok 94 - view_owner_is(sch, view, user, desc) should pass -ok 95 - view_owner_is(sch, view, user, desc) should have the proper description -ok 96 - view_owner_is(sch, view, user, desc) should have the proper diagnostics -ok 97 - view_owner_is(sch, view, user) should pass -ok 98 - view_owner_is(sch, view, user) should have the proper description -ok 99 - view_owner_is(sch, view, user) should have the proper diagnostics -ok 100 - view_owner_is(non-sch, view, user) should fail -ok 101 - view_owner_is(non-sch, view, user) should have the proper description -ok 102 - view_owner_is(non-sch, view, user) should have the proper diagnostics -ok 103 - view_owner_is(sch, non-view, user) should fail -ok 104 - view_owner_is(sch, non-view, user) should have the proper description -ok 105 - view_owner_is(sch, non-view, user) should have the proper diagnostics -ok 106 - view_owner_is(view, user, desc) should pass -ok 107 - view_owner_is(view, user, desc) should have the proper description -ok 108 - view_owner_is(view, user, desc) should have the proper diagnostics -ok 109 - view_owner_is(view, user) should pass -ok 110 - view_owner_is(view, user) should have the proper description -ok 111 - view_owner_is(view, user) should have the proper diagnostics -ok 112 - view_owner_is(non-view, user) should fail -ok 113 - view_owner_is(non-view, user) should have the proper description -ok 114 - view_owner_is(non-view, user) should have the proper diagnostics -ok 115 - view_owner_is(sch, seq, user, desc) should fail -ok 116 - view_owner_is(sch, seq, user, desc) should have the proper description -ok 117 - view_owner_is(sch, seq, user, desc) should have the proper diagnostics -ok 118 - view_owner_is(seq, user, desc) should fail -ok 119 - view_owner_is(seq, user, desc) should have the proper description -ok 120 - view_owner_is(seq, user, desc) should have the proper diagnostics -ok 121 - sequence_owner_is(sch, sequence, user, desc) should pass -ok 122 - sequence_owner_is(sch, sequence, user, desc) should have the proper description -ok 123 - sequence_owner_is(sch, sequence, user, desc) should have the proper diagnostics -ok 124 - sequence_owner_is(sch, sequence, user) should pass -ok 125 - sequence_owner_is(sch, sequence, user) should have the proper description -ok 126 - sequence_owner_is(sch, sequence, user) should have the proper diagnostics -ok 127 - sequence_owner_is(non-sch, sequence, user) should fail -ok 128 - sequence_owner_is(non-sch, sequence, user) should have the proper description -ok 129 - sequence_owner_is(non-sch, sequence, user) should have the proper diagnostics -ok 130 - sequence_owner_is(sch, non-sequence, user) should fail -ok 131 - sequence_owner_is(sch, non-sequence, user) should have the proper description -ok 132 - sequence_owner_is(sch, non-sequence, user) should have the proper diagnostics -ok 133 - sequence_owner_is(sequence, user, desc) should pass -ok 134 - sequence_owner_is(sequence, user, desc) should have the proper description -ok 135 - sequence_owner_is(sequence, user, desc) should have the proper diagnostics -ok 136 - sequence_owner_is(sequence, user) should pass -ok 137 - sequence_owner_is(sequence, user) should have the proper description -ok 138 - sequence_owner_is(sequence, user) should have the proper diagnostics -ok 139 - sequence_owner_is(non-sequence, user) should fail -ok 140 - sequence_owner_is(non-sequence, user) should have the proper description -ok 141 - sequence_owner_is(non-sequence, user) should have the proper diagnostics -ok 142 - sequence_owner_is(sch, view, user, desc) should fail -ok 143 - sequence_owner_is(sch, view, user, desc) should have the proper description -ok 144 - sequence_owner_is(sch, view, user, desc) should have the proper diagnostics -ok 145 - sequence_owner_is(view, user, desc) should fail -ok 146 - sequence_owner_is(view, user, desc) should have the proper description -ok 147 - sequence_owner_is(view, user, desc) should have the proper diagnostics -ok 148 - composite_owner_is(sch, composite, user, desc) should pass -ok 149 - composite_owner_is(sch, composite, user, desc) should have the proper description -ok 150 - composite_owner_is(sch, composite, user, desc) should have the proper diagnostics -ok 151 - composite_owner_is(sch, composite, user) should pass -ok 152 - composite_owner_is(sch, composite, user) should have the proper description -ok 153 - composite_owner_is(sch, composite, user) should have the proper diagnostics -ok 154 - composite_owner_is(non-sch, composite, user) should fail -ok 155 - composite_owner_is(non-sch, composite, user) should have the proper description -ok 156 - composite_owner_is(non-sch, composite, user) should have the proper diagnostics -ok 157 - composite_owner_is(sch, non-composite, user) should fail -ok 158 - composite_owner_is(sch, non-composite, user) should have the proper description -ok 159 - composite_owner_is(sch, non-composite, user) should have the proper diagnostics -ok 160 - composite_owner_is(composite, user, desc) should pass -ok 161 - composite_owner_is(composite, user, desc) should have the proper description -ok 162 - composite_owner_is(composite, user, desc) should have the proper diagnostics -ok 163 - composite_owner_is(composite, user) should pass -ok 164 - composite_owner_is(composite, user) should have the proper description -ok 165 - composite_owner_is(composite, user) should have the proper diagnostics -ok 166 - composite_owner_is(non-composite, user) should fail -ok 167 - composite_owner_is(non-composite, user) should have the proper description -ok 168 - composite_owner_is(non-composite, user) should have the proper diagnostics -ok 169 - composite_owner_is(sch, view, user, desc) should fail -ok 170 - composite_owner_is(sch, view, user, desc) should have the proper description -ok 171 - composite_owner_is(sch, view, user, desc) should have the proper diagnostics -ok 172 - composite_owner_is(view, user, desc) should fail -ok 173 - composite_owner_is(view, user, desc) should have the proper description -ok 174 - composite_owner_is(view, user, desc) should have the proper diagnostics -ok 175 - foreign_table_owner_is(sch, tab, user, desc) should pass -ok 176 - foreign_table_owner_is(sch, tab, user, desc) should have the proper description -ok 177 - foreign_table_owner_is(sch, tab, user, desc) should have the proper diagnostics -ok 178 - foreign_table_owner_is(sch, tab, user) should pass -ok 179 - foreign_table_owner_is(sch, tab, user) should have the proper description -ok 180 - foreign_table_owner_is(sch, tab, user) should have the proper diagnostics -ok 181 - foreign_table_owner_is(non-sch, tab, user) should fail -ok 182 - foreign_table_owner_is(non-sch, tab, user) should have the proper description -ok 183 - foreign_table_owner_is(non-sch, tab, user) should have the proper diagnostics -ok 184 - foreign_table_owner_is(sch, non-tab, user) should fail -ok 185 - foreign_table_owner_is(sch, non-tab, user) should have the proper description -ok 186 - foreign_table_owner_is(sch, non-tab, user) should have the proper diagnostics -ok 187 - foreign_table_owner_is(tab, user, desc) should pass -ok 188 - foreign_table_owner_is(tab, user, desc) should have the proper description -ok 189 - foreign_table_owner_is(tab, user, desc) should have the proper diagnostics -ok 190 - foreign_table_owner_is(tab, user) should pass -ok 191 - foreign_table_owner_is(tab, user) should have the proper description -ok 192 - foreign_table_owner_is(tab, user) should have the proper diagnostics -ok 193 - foreign_table_owner_is(non-tab, user) should fail -ok 194 - foreign_table_owner_is(non-tab, user) should have the proper description -ok 195 - foreign_table_owner_is(non-tab, user) should have the proper diagnostics -ok 196 - foreign_table_owner_is(sch, tab, user, desc) should fail -ok 197 - foreign_table_owner_is(sch, tab, user, desc) should have the proper description -ok 198 - foreign_table_owner_is(sch, tab, user, desc) should have the proper diagnostics -ok 199 - foreign_table_owner_is(tab, user, desc) should fail -ok 200 - foreign_table_owner_is(tab, user, desc) should have the proper description -ok 201 - foreign_table_owner_is(tab, user, desc) should have the proper diagnostics -ok 202 - function_owner_is(sch, function, args[integer], user, desc) should pass -ok 203 - function_owner_is(sch, function, args[integer], user, desc) should have the proper description -ok 204 - function_owner_is(sch, function, args[integer], user, desc) should have the proper diagnostics -ok 205 - function_owner_is(sch, function, args[integer], user) should pass -ok 206 - function_owner_is(sch, function, args[integer], user) should have the proper description -ok 207 - function_owner_is(sch, function, args[integer], user) should have the proper diagnostics -ok 208 - function_owner_is(sch, function, args[], user, desc) should pass -ok 209 - function_owner_is(sch, function, args[], user, desc) should have the proper description -ok 210 - function_owner_is(sch, function, args[], user, desc) should have the proper diagnostics -ok 211 - function_owner_is(sch, function, args[], user) should pass -ok 212 - function_owner_is(sch, function, args[], user) should have the proper description -ok 213 - function_owner_is(sch, function, args[], user) should have the proper diagnostics -ok 214 - function_owner_is(function, args[integer], user, desc) should pass -ok 215 - function_owner_is(function, args[integer], user, desc) should have the proper description -ok 216 - function_owner_is(function, args[integer], user, desc) should have the proper diagnostics -ok 217 - function_owner_is(function, args[integer], user) should pass -ok 218 - function_owner_is(function, args[integer], user) should have the proper description -ok 219 - function_owner_is(function, args[integer], user) should have the proper diagnostics -ok 220 - function_owner_is(function, args[], user, desc) should pass -ok 221 - function_owner_is(function, args[], user, desc) should have the proper description -ok 222 - function_owner_is(function, args[], user, desc) should have the proper diagnostics -ok 223 - function_owner_is(function, args[], user) should pass -ok 224 - function_owner_is(function, args[], user) should have the proper description -ok 225 - function_owner_is(function, args[], user) should have the proper diagnostics -ok 226 - function_owner_is(sch, non-function, args[integer], user, desc) should fail -ok 227 - function_owner_is(sch, non-function, args[integer], user, desc) should have the proper description -ok 228 - function_owner_is(sch, non-function, args[integer], user, desc) should have the proper diagnostics -ok 229 - function_owner_is(non-sch, function, args[integer], user, desc) should fail -ok 230 - function_owner_is(non-sch, function, args[integer], user, desc) should have the proper description -ok 231 - function_owner_is(non-sch, function, args[integer], user, desc) should have the proper diagnostics -ok 232 - function_owner_is(non-function, args[integer], user, desc) should fail -ok 233 - function_owner_is(non-function, args[integer], user, desc) should have the proper description -ok 234 - function_owner_is(non-function, args[integer], user, desc) should have the proper diagnostics -ok 235 - function_owner_is(sch, function, args[integer], non-user, desc) should fail -ok 236 - function_owner_is(sch, function, args[integer], non-user, desc) should have the proper description -ok 237 - function_owner_is(sch, function, args[integer], non-user, desc) should have the proper diagnostics -ok 238 - function_owner_is(function, args[integer], non-user, desc) should fail -ok 239 - function_owner_is(function, args[integer], non-user, desc) should have the proper description -ok 240 - function_owner_is(function, args[integer], non-user, desc) should have the proper diagnostics -ok 241 - tablespace_owner_is(tablespace, user, desc) should pass -ok 242 - tablespace_owner_is(tablespace, user, desc) should have the proper description -ok 243 - tablespace_owner_is(tablespace, user, desc) should have the proper diagnostics -ok 244 - tablespace_owner_is(tablespace, user) should pass -ok 245 - tablespace_owner_is(tablespace, user) should have the proper description -ok 246 - tablespace_owner_is(tablespace, user) should have the proper diagnostics -ok 247 - tablespace_owner_is(non-tablespace, user) should fail -ok 248 - tablespace_owner_is(non-tablespace, user) should have the proper description -ok 249 - tablespace_owner_is(non-tablespace, user) should have the proper diagnostics -ok 250 - tablespace_owner_is(tablespace, non-user) should fail -ok 251 - tablespace_owner_is(tablespace, non-user) should have the proper description -ok 252 - tablespace_owner_is(tablespace, non-user) should have the proper diagnostics -ok 253 - index_owner_is(schema, table, index, user, desc) should pass -ok 254 - index_owner_is(schema, table, index, user, desc) should have the proper description -ok 255 - index_owner_is(schema, table, index, user, desc) should have the proper diagnostics -ok 256 - index_owner_is(schema, table, index, user) should pass -ok 257 - index_owner_is(schema, table, index, user) should have the proper description -ok 258 - index_owner_is(schema, table, index, user) should have the proper diagnostics -ok 259 - index_owner_is(schema, table, non-index, user, desc) should fail -ok 260 - index_owner_is(schema, table, non-index, user, desc) should have the proper description -ok 261 - index_owner_is(schema, table, non-index, user, desc) should have the proper diagnostics -ok 262 - index_owner_is(schema, non-table, index, user, desc) should fail -ok 263 - index_owner_is(schema, non-table, index, user, desc) should have the proper description -ok 264 - index_owner_is(schema, non-table, index, user, desc) should have the proper diagnostics -ok 265 - index_owner_is(non-schema, table, index, user, desc) should fail -ok 266 - index_owner_is(non-schema, table, index, user, desc) should have the proper description -ok 267 - index_owner_is(non-schema, table, index, user, desc) should have the proper diagnostics -ok 268 - index_owner_is(schema, table, index, non-user, desc) should fail -ok 269 - index_owner_is(schema, table, index, non-user, desc) should have the proper description -ok 270 - index_owner_is(schema, table, index, non-user, desc) should have the proper diagnostics -ok 271 - index_owner_is(invisible-table, index, user, desc) should fail -ok 272 - index_owner_is(invisible-table, index, user, desc) should have the proper description -ok 273 - index_owner_is(invisible-table, index, user, desc) should have the proper diagnostics -ok 274 - index_owner_is(table, index, user, desc) should pass -ok 275 - index_owner_is(table, index, user, desc) should have the proper description -ok 276 - index_owner_is(table, index, user, desc) should have the proper diagnostics -ok 277 - index_owner_is(table, index, user) should pass -ok 278 - index_owner_is(table, index, user) should have the proper description -ok 279 - index_owner_is(table, index, user) should have the proper diagnostics -ok 280 - index_owner_is(non-table, index, user) should fail -ok 281 - index_owner_is(non-table, index, user) should have the proper description -ok 282 - index_owner_is(non-table, index, user) should have the proper diagnostics -ok 283 - index_owner_is(table, non-index, user) should fail -ok 284 - index_owner_is(table, non-index, user) should have the proper description -ok 285 - index_owner_is(table, non-index, user) should have the proper diagnostics -ok 286 - index_owner_is(table, index, non-user) should fail -ok 287 - index_owner_is(table, index, non-user) should have the proper description -ok 288 - index_owner_is(table, index, non-user) should have the proper diagnostics -ok 289 - language_owner_is(language, user, desc) should pass -ok 290 - language_owner_is(language, user, desc) should have the proper description -ok 291 - language_owner_is(language, user, desc) should have the proper diagnostics -ok 292 - language_owner_is(language, user) should pass -ok 293 - language_owner_is(language, user) should have the proper description -ok 294 - language_owner_is(language, user) should have the proper diagnostics -ok 295 - language_owner_is(non-language, user) should fail -ok 296 - language_owner_is(non-language, user) should have the proper description -ok 297 - language_owner_is(non-language, user) should have the proper diagnostics -ok 298 - language_owner_is(language, non-user) should fail -ok 299 - language_owner_is(language, non-user) should have the proper description -ok 300 - language_owner_is(language, non-user) should have the proper diagnostics -ok 301 - opclass_owner_is(schema, opclass, user, desc) should pass -ok 302 - opclass_owner_is(schema, opclass, user, desc) should have the proper description -ok 303 - opclass_owner_is(schema, opclass, user, desc) should have the proper diagnostics -ok 304 - opclass_owner_is(schema, opclass, user) should pass -ok 305 - opclass_owner_is(schema, opclass, user) should have the proper description -ok 306 - opclass_owner_is(schema, opclass, user) should have the proper diagnostics -ok 307 - opclass_owner_is(non-schema, opclass, user, desc) should fail -ok 308 - opclass_owner_is(non-schema, opclass, user, desc) should have the proper description -ok 309 - opclass_owner_is(non-schema, opclass, user, desc) should have the proper diagnostics -ok 310 - opclass_owner_is(schema, not-opclass, user, desc) should fail -ok 311 - opclass_owner_is(schema, not-opclass, user, desc) should have the proper description -ok 312 - opclass_owner_is(schema, not-opclass, user, desc) should have the proper diagnostics -ok 313 - opclass_owner_is(schema, opclass, non-user, desc) should fail -ok 314 - opclass_owner_is(schema, opclass, non-user, desc) should have the proper description -ok 315 - opclass_owner_is(schema, opclass, non-user, desc) should have the proper diagnostics -ok 316 - opclass_owner_is(opclass, user, desc) should pass -ok 317 - opclass_owner_is(opclass, user, desc) should have the proper description -ok 318 - opclass_owner_is(opclass, user, desc) should have the proper diagnostics -ok 319 - opclass_owner_is(opclass, user) should pass -ok 320 - opclass_owner_is(opclass, user) should have the proper description -ok 321 - opclass_owner_is(opclass, user) should have the proper diagnostics -ok 322 - opclass_owner_is(non-opclass, user, desc) should fail -ok 323 - opclass_owner_is(non-opclass, user, desc) should have the proper description -ok 324 - opclass_owner_is(non-opclass, user, desc) should have the proper diagnostics -ok 325 - opclass_owner_is(opclass, non-user, desc) should fail -ok 326 - opclass_owner_is(opclass, non-user, desc) should have the proper description -ok 327 - opclass_owner_is(opclass, non-user, desc) should have the proper diagnostics -ok 328 - type_owner_is(schema, type, user, desc) should pass -ok 329 - type_owner_is(schema, type, user, desc) should have the proper description -ok 330 - type_owner_is(schema, type, user, desc) should have the proper diagnostics -ok 331 - type_owner_is(schema, type, user) should pass -ok 332 - type_owner_is(schema, type, user) should have the proper description -ok 333 - type_owner_is(schema, type, user) should have the proper diagnostics -ok 334 - type_owner_is(non-schema, type, user, desc) should fail -ok 335 - type_owner_is(non-schema, type, user, desc) should have the proper description -ok 336 - type_owner_is(non-schema, type, user, desc) should have the proper diagnostics -ok 337 - type_owner_is(schema, non-type, user, desc) should fail -ok 338 - type_owner_is(schema, non-type, user, desc) should have the proper description -ok 339 - type_owner_is(schema, non-type, user, desc) should have the proper diagnostics -ok 340 - type_owner_is(schema, type, non-user, desc) should fail -ok 341 - type_owner_is(schema, type, non-user, desc) should have the proper description -ok 342 - type_owner_is(schema, type, non-user, desc) should have the proper diagnostics -ok 343 - type_owner_is( invisible-type, user, desc) should fail -ok 344 - type_owner_is( invisible-type, user, desc) should have the proper description -ok 345 - type_owner_is( invisible-type, user, desc) should have the proper diagnostics -ok 346 - type_owner_is(type, user, desc) should pass -ok 347 - type_owner_is(type, user, desc) should have the proper description -ok 348 - type_owner_is(type, user, desc) should have the proper diagnostics -ok 349 - type_owner_is(type, user) should pass -ok 350 - type_owner_is(type, user) should have the proper description -ok 351 - type_owner_is(type, user) should have the proper diagnostics -ok 352 - type_owner_is(non-type, user, desc) should fail -ok 353 - type_owner_is(non-type, user, desc) should have the proper description -ok 354 - type_owner_is(non-type, user, desc) should have the proper diagnostics -ok 355 - type_owner_is(type, non-user, desc) should fail -ok 356 - type_owner_is(type, non-user, desc) should have the proper description -ok 357 - type_owner_is(type, non-user, desc) should have the proper diagnostics -ok 358 - materialized_view_owner_is(sch, materialized_view, user, desc) should pass -ok 359 - materialized_view_owner_is(sch, materialized_view, user, desc) should have the proper description -ok 360 - materialized_view_owner_is(sch, materialized_view, user, desc) should have the proper diagnostics -ok 361 - materialized_view_owner_is(sch, materialized_view, user) should pass -ok 362 - materialized_view_owner_is(sch, materialized_view, user) should have the proper description -ok 363 - materialized_view_owner_is(sch, materialized_view, user) should have the proper diagnostics -ok 364 - materialized_view_owner_is(non-sch, materialized_view, user) should fail -ok 365 - materialized_view_owner_is(non-sch, materialized_view, user) should have the proper description -ok 366 - materialized_view_owner_is(non-sch, materialized_view, user) should have the proper diagnostics -ok 367 - materialized_view_owner_is(sch, non-materialized_view, user) should fail -ok 368 - materialized_view_owner_is(sch, non-materialized_view, user) should have the proper description -ok 369 - materialized_view_owner_is(sch, non-materialized_view, user) should have the proper diagnostics -ok 370 - materialized_view_owner_is(materialized_view, user, desc) should pass -ok 371 - materialized_view_owner_is(materialized_view, user, desc) should have the proper description -ok 372 - materialized_view_owner_is(materialized_view, user, desc) should have the proper diagnostics -ok 373 - materialized_view_owner_is(view, user) should pass -ok 374 - materialized_view_owner_is(view, user) should have the proper description -ok 375 - materialized_view_owner_is(view, user) should have the proper diagnostics -ok 376 - materialized_view_owner_is(non-materialized_view, user) should fail -ok 377 - materialized_view_owner_is(non-materialized_view, user) should have the proper description -ok 378 - materialized_view_owner_is(non-materialized_view, user) should have the proper diagnostics -ok 379 - materialized_view_owner_is(sch, seq, user, desc) should fail -ok 380 - materialized_view_owner_is(sch, seq, user, desc) should have the proper description -ok 381 - materialized_view_owner_is(sch, seq, user, desc) should have the proper diagnostics -ok 382 - materialized_view_owner_is(seq, user, desc) should fail -ok 383 - materialized_view_owner_is(seq, user, desc) should have the proper description -ok 384 - materialized_view_owner_is(seq, user, desc) should have the proper diagnostics +ok 46 - relation_owner_is(sch, part, user, desc) should pass +ok 47 - relation_owner_is(sch, part, user, desc) should have the proper description +ok 48 - relation_owner_is(sch, part, user, desc) should have the proper diagnostics +ok 49 - relation_owner_is(sch, part, user) should pass +ok 50 - relation_owner_is(sch, part, user) should have the proper description +ok 51 - relation_owner_is(sch, part, user) should have the proper diagnostics +ok 52 - relation_owner_is(non-sch, part, user) should fail +ok 53 - relation_owner_is(non-sch, part, user) should have the proper description +ok 54 - relation_owner_is(non-sch, part, user) should have the proper diagnostics +ok 55 - relation_owner_is(sch, non-part, user) should fail +ok 56 - relation_owner_is(sch, non-part, user) should have the proper description +ok 57 - relation_owner_is(sch, non-part, user) should have the proper diagnostics +ok 58 - relation_owner_is(part, user, desc) should pass +ok 59 - relation_owner_is(part, user, desc) should have the proper description +ok 60 - relation_owner_is(part, user, desc) should have the proper diagnostics +ok 61 - relation_owner_is(part, user) should pass +ok 62 - relation_owner_is(part, user) should have the proper description +ok 63 - relation_owner_is(part, user) should have the proper diagnostics +ok 64 - relation_owner_is(non-part, user) should fail +ok 65 - relation_owner_is(non-part, user) should have the proper description +ok 66 - relation_owner_is(non-part, user) should have the proper diagnostics +ok 67 - relation_owner_is(sch, seq, user, desc) should pass +ok 68 - relation_owner_is(sch, seq, user, desc) should have the proper description +ok 69 - relation_owner_is(sch, seq, user, desc) should have the proper diagnostics +ok 70 - relation_owner_is(sch, seq, user) should pass +ok 71 - relation_owner_is(sch, seq, user) should have the proper description +ok 72 - relation_owner_is(sch, seq, user) should have the proper diagnostics +ok 73 - relation_owner_is(non-sch, seq, user) should fail +ok 74 - relation_owner_is(non-sch, seq, user) should have the proper description +ok 75 - relation_owner_is(non-sch, seq, user) should have the proper diagnostics +ok 76 - relation_owner_is(sch, non-seq, user) should fail +ok 77 - relation_owner_is(sch, non-seq, user) should have the proper description +ok 78 - relation_owner_is(sch, non-seq, user) should have the proper diagnostics +ok 79 - relation_owner_is(seq, user, desc) should pass +ok 80 - relation_owner_is(seq, user, desc) should have the proper description +ok 81 - relation_owner_is(seq, user, desc) should have the proper diagnostics +ok 82 - relation_owner_is(seq, user) should pass +ok 83 - relation_owner_is(seq, user) should have the proper description +ok 84 - relation_owner_is(seq, user) should have the proper diagnostics +ok 85 - relation_owner_is(non-seq, user) should fail +ok 86 - relation_owner_is(non-seq, user) should have the proper description +ok 87 - relation_owner_is(non-seq, user) should have the proper diagnostics +ok 88 - table_owner_is(sch, tab, user, desc) should pass +ok 89 - table_owner_is(sch, tab, user, desc) should have the proper description +ok 90 - table_owner_is(sch, tab, user, desc) should have the proper diagnostics +ok 91 - table_owner_is(sch, tab, user) should pass +ok 92 - table_owner_is(sch, tab, user) should have the proper description +ok 93 - table_owner_is(sch, tab, user) should have the proper diagnostics +ok 94 - table_owner_is(non-sch, tab, user) should fail +ok 95 - table_owner_is(non-sch, tab, user) should have the proper description +ok 96 - table_owner_is(non-sch, tab, user) should have the proper diagnostics +ok 97 - table_owner_is(sch, non-tab, user) should fail +ok 98 - table_owner_is(sch, non-tab, user) should have the proper description +ok 99 - table_owner_is(sch, non-tab, user) should have the proper diagnostics +ok 100 - table_owner_is(tab, user, desc) should pass +ok 101 - table_owner_is(tab, user, desc) should have the proper description +ok 102 - table_owner_is(tab, user, desc) should have the proper diagnostics +ok 103 - table_owner_is(tab, user) should pass +ok 104 - table_owner_is(tab, user) should have the proper description +ok 105 - table_owner_is(tab, user) should have the proper diagnostics +ok 106 - table_owner_is(non-tab, user) should fail +ok 107 - table_owner_is(non-tab, user) should have the proper description +ok 108 - table_owner_is(non-tab, user) should have the proper diagnostics +ok 109 - table_owner_is(sch, seq, user, desc) should fail +ok 110 - table_owner_is(sch, seq, user, desc) should have the proper description +ok 111 - table_owner_is(sch, seq, user, desc) should have the proper diagnostics +ok 112 - table_owner_is(seq, user, desc) should fail +ok 113 - table_owner_is(seq, user, desc) should have the proper description +ok 114 - table_owner_is(seq, user, desc) should have the proper diagnostics +ok 115 - table_owner_is(sch, part, user, desc) should pass +ok 116 - table_owner_is(sch, part, user, desc) should have the proper description +ok 117 - table_owner_is(sch, part, user, desc) should have the proper diagnostics +ok 118 - table_owner_is(part, user, desc) should pass +ok 119 - table_owner_is(part, user, desc) should have the proper description +ok 120 - table_owner_is(part, user, desc) should have the proper diagnostics +ok 121 - view_owner_is(sch, view, user, desc) should pass +ok 122 - view_owner_is(sch, view, user, desc) should have the proper description +ok 123 - view_owner_is(sch, view, user, desc) should have the proper diagnostics +ok 124 - view_owner_is(sch, view, user) should pass +ok 125 - view_owner_is(sch, view, user) should have the proper description +ok 126 - view_owner_is(sch, view, user) should have the proper diagnostics +ok 127 - view_owner_is(non-sch, view, user) should fail +ok 128 - view_owner_is(non-sch, view, user) should have the proper description +ok 129 - view_owner_is(non-sch, view, user) should have the proper diagnostics +ok 130 - view_owner_is(sch, non-view, user) should fail +ok 131 - view_owner_is(sch, non-view, user) should have the proper description +ok 132 - view_owner_is(sch, non-view, user) should have the proper diagnostics +ok 133 - view_owner_is(view, user, desc) should pass +ok 134 - view_owner_is(view, user, desc) should have the proper description +ok 135 - view_owner_is(view, user, desc) should have the proper diagnostics +ok 136 - view_owner_is(view, user) should pass +ok 137 - view_owner_is(view, user) should have the proper description +ok 138 - view_owner_is(view, user) should have the proper diagnostics +ok 139 - view_owner_is(non-view, user) should fail +ok 140 - view_owner_is(non-view, user) should have the proper description +ok 141 - view_owner_is(non-view, user) should have the proper diagnostics +ok 142 - view_owner_is(sch, seq, user, desc) should fail +ok 143 - view_owner_is(sch, seq, user, desc) should have the proper description +ok 144 - view_owner_is(sch, seq, user, desc) should have the proper diagnostics +ok 145 - view_owner_is(seq, user, desc) should fail +ok 146 - view_owner_is(seq, user, desc) should have the proper description +ok 147 - view_owner_is(seq, user, desc) should have the proper diagnostics +ok 148 - sequence_owner_is(sch, sequence, user, desc) should pass +ok 149 - sequence_owner_is(sch, sequence, user, desc) should have the proper description +ok 150 - sequence_owner_is(sch, sequence, user, desc) should have the proper diagnostics +ok 151 - sequence_owner_is(sch, sequence, user) should pass +ok 152 - sequence_owner_is(sch, sequence, user) should have the proper description +ok 153 - sequence_owner_is(sch, sequence, user) should have the proper diagnostics +ok 154 - sequence_owner_is(non-sch, sequence, user) should fail +ok 155 - sequence_owner_is(non-sch, sequence, user) should have the proper description +ok 156 - sequence_owner_is(non-sch, sequence, user) should have the proper diagnostics +ok 157 - sequence_owner_is(sch, non-sequence, user) should fail +ok 158 - sequence_owner_is(sch, non-sequence, user) should have the proper description +ok 159 - sequence_owner_is(sch, non-sequence, user) should have the proper diagnostics +ok 160 - sequence_owner_is(sequence, user, desc) should pass +ok 161 - sequence_owner_is(sequence, user, desc) should have the proper description +ok 162 - sequence_owner_is(sequence, user, desc) should have the proper diagnostics +ok 163 - sequence_owner_is(sequence, user) should pass +ok 164 - sequence_owner_is(sequence, user) should have the proper description +ok 165 - sequence_owner_is(sequence, user) should have the proper diagnostics +ok 166 - sequence_owner_is(non-sequence, user) should fail +ok 167 - sequence_owner_is(non-sequence, user) should have the proper description +ok 168 - sequence_owner_is(non-sequence, user) should have the proper diagnostics +ok 169 - sequence_owner_is(sch, view, user, desc) should fail +ok 170 - sequence_owner_is(sch, view, user, desc) should have the proper description +ok 171 - sequence_owner_is(sch, view, user, desc) should have the proper diagnostics +ok 172 - sequence_owner_is(view, user, desc) should fail +ok 173 - sequence_owner_is(view, user, desc) should have the proper description +ok 174 - sequence_owner_is(view, user, desc) should have the proper diagnostics +ok 175 - composite_owner_is(sch, composite, user, desc) should pass +ok 176 - composite_owner_is(sch, composite, user, desc) should have the proper description +ok 177 - composite_owner_is(sch, composite, user, desc) should have the proper diagnostics +ok 178 - composite_owner_is(sch, composite, user) should pass +ok 179 - composite_owner_is(sch, composite, user) should have the proper description +ok 180 - composite_owner_is(sch, composite, user) should have the proper diagnostics +ok 181 - composite_owner_is(non-sch, composite, user) should fail +ok 182 - composite_owner_is(non-sch, composite, user) should have the proper description +ok 183 - composite_owner_is(non-sch, composite, user) should have the proper diagnostics +ok 184 - composite_owner_is(sch, non-composite, user) should fail +ok 185 - composite_owner_is(sch, non-composite, user) should have the proper description +ok 186 - composite_owner_is(sch, non-composite, user) should have the proper diagnostics +ok 187 - composite_owner_is(composite, user, desc) should pass +ok 188 - composite_owner_is(composite, user, desc) should have the proper description +ok 189 - composite_owner_is(composite, user, desc) should have the proper diagnostics +ok 190 - composite_owner_is(composite, user) should pass +ok 191 - composite_owner_is(composite, user) should have the proper description +ok 192 - composite_owner_is(composite, user) should have the proper diagnostics +ok 193 - composite_owner_is(non-composite, user) should fail +ok 194 - composite_owner_is(non-composite, user) should have the proper description +ok 195 - composite_owner_is(non-composite, user) should have the proper diagnostics +ok 196 - composite_owner_is(sch, view, user, desc) should fail +ok 197 - composite_owner_is(sch, view, user, desc) should have the proper description +ok 198 - composite_owner_is(sch, view, user, desc) should have the proper diagnostics +ok 199 - composite_owner_is(view, user, desc) should fail +ok 200 - composite_owner_is(view, user, desc) should have the proper description +ok 201 - composite_owner_is(view, user, desc) should have the proper diagnostics +ok 202 - foreign_table_owner_is(sch, tab, user, desc) should pass +ok 203 - foreign_table_owner_is(sch, tab, user, desc) should have the proper description +ok 204 - foreign_table_owner_is(sch, tab, user, desc) should have the proper diagnostics +ok 205 - foreign_table_owner_is(sch, tab, user) should pass +ok 206 - foreign_table_owner_is(sch, tab, user) should have the proper description +ok 207 - foreign_table_owner_is(sch, tab, user) should have the proper diagnostics +ok 208 - foreign_table_owner_is(non-sch, tab, user) should fail +ok 209 - foreign_table_owner_is(non-sch, tab, user) should have the proper description +ok 210 - foreign_table_owner_is(non-sch, tab, user) should have the proper diagnostics +ok 211 - foreign_table_owner_is(sch, non-tab, user) should fail +ok 212 - foreign_table_owner_is(sch, non-tab, user) should have the proper description +ok 213 - foreign_table_owner_is(sch, non-tab, user) should have the proper diagnostics +ok 214 - foreign_table_owner_is(tab, user, desc) should pass +ok 215 - foreign_table_owner_is(tab, user, desc) should have the proper description +ok 216 - foreign_table_owner_is(tab, user, desc) should have the proper diagnostics +ok 217 - foreign_table_owner_is(tab, user) should pass +ok 218 - foreign_table_owner_is(tab, user) should have the proper description +ok 219 - foreign_table_owner_is(tab, user) should have the proper diagnostics +ok 220 - foreign_table_owner_is(non-tab, user) should fail +ok 221 - foreign_table_owner_is(non-tab, user) should have the proper description +ok 222 - foreign_table_owner_is(non-tab, user) should have the proper diagnostics +ok 223 - foreign_table_owner_is(sch, tab, user, desc) should fail +ok 224 - foreign_table_owner_is(sch, tab, user, desc) should have the proper description +ok 225 - foreign_table_owner_is(sch, tab, user, desc) should have the proper diagnostics +ok 226 - foreign_table_owner_is(tab, user, desc) should fail +ok 227 - foreign_table_owner_is(tab, user, desc) should have the proper description +ok 228 - foreign_table_owner_is(tab, user, desc) should have the proper diagnostics +ok 229 - function_owner_is(sch, function, args[integer], user, desc) should pass +ok 230 - function_owner_is(sch, function, args[integer], user, desc) should have the proper description +ok 231 - function_owner_is(sch, function, args[integer], user, desc) should have the proper diagnostics +ok 232 - function_owner_is(sch, function, args[integer], user) should pass +ok 233 - function_owner_is(sch, function, args[integer], user) should have the proper description +ok 234 - function_owner_is(sch, function, args[integer], user) should have the proper diagnostics +ok 235 - function_owner_is(sch, function, args[], user, desc) should pass +ok 236 - function_owner_is(sch, function, args[], user, desc) should have the proper description +ok 237 - function_owner_is(sch, function, args[], user, desc) should have the proper diagnostics +ok 238 - function_owner_is(sch, function, args[], user) should pass +ok 239 - function_owner_is(sch, function, args[], user) should have the proper description +ok 240 - function_owner_is(sch, function, args[], user) should have the proper diagnostics +ok 241 - function_owner_is(function, args[integer], user, desc) should pass +ok 242 - function_owner_is(function, args[integer], user, desc) should have the proper description +ok 243 - function_owner_is(function, args[integer], user, desc) should have the proper diagnostics +ok 244 - function_owner_is(function, args[integer], user) should pass +ok 245 - function_owner_is(function, args[integer], user) should have the proper description +ok 246 - function_owner_is(function, args[integer], user) should have the proper diagnostics +ok 247 - function_owner_is(function, args[], user, desc) should pass +ok 248 - function_owner_is(function, args[], user, desc) should have the proper description +ok 249 - function_owner_is(function, args[], user, desc) should have the proper diagnostics +ok 250 - function_owner_is(function, args[], user) should pass +ok 251 - function_owner_is(function, args[], user) should have the proper description +ok 252 - function_owner_is(function, args[], user) should have the proper diagnostics +ok 253 - function_owner_is(sch, non-function, args[integer], user, desc) should fail +ok 254 - function_owner_is(sch, non-function, args[integer], user, desc) should have the proper description +ok 255 - function_owner_is(sch, non-function, args[integer], user, desc) should have the proper diagnostics +ok 256 - function_owner_is(non-sch, function, args[integer], user, desc) should fail +ok 257 - function_owner_is(non-sch, function, args[integer], user, desc) should have the proper description +ok 258 - function_owner_is(non-sch, function, args[integer], user, desc) should have the proper diagnostics +ok 259 - function_owner_is(non-function, args[integer], user, desc) should fail +ok 260 - function_owner_is(non-function, args[integer], user, desc) should have the proper description +ok 261 - function_owner_is(non-function, args[integer], user, desc) should have the proper diagnostics +ok 262 - function_owner_is(sch, function, args[integer], non-user, desc) should fail +ok 263 - function_owner_is(sch, function, args[integer], non-user, desc) should have the proper description +ok 264 - function_owner_is(sch, function, args[integer], non-user, desc) should have the proper diagnostics +ok 265 - function_owner_is(function, args[integer], non-user, desc) should fail +ok 266 - function_owner_is(function, args[integer], non-user, desc) should have the proper description +ok 267 - function_owner_is(function, args[integer], non-user, desc) should have the proper diagnostics +ok 268 - tablespace_owner_is(tablespace, user, desc) should pass +ok 269 - tablespace_owner_is(tablespace, user, desc) should have the proper description +ok 270 - tablespace_owner_is(tablespace, user, desc) should have the proper diagnostics +ok 271 - tablespace_owner_is(tablespace, user) should pass +ok 272 - tablespace_owner_is(tablespace, user) should have the proper description +ok 273 - tablespace_owner_is(tablespace, user) should have the proper diagnostics +ok 274 - tablespace_owner_is(non-tablespace, user) should fail +ok 275 - tablespace_owner_is(non-tablespace, user) should have the proper description +ok 276 - tablespace_owner_is(non-tablespace, user) should have the proper diagnostics +ok 277 - tablespace_owner_is(tablespace, non-user) should fail +ok 278 - tablespace_owner_is(tablespace, non-user) should have the proper description +ok 279 - tablespace_owner_is(tablespace, non-user) should have the proper diagnostics +ok 280 - index_owner_is(schema, table, index, user, desc) should pass +ok 281 - index_owner_is(schema, table, index, user, desc) should have the proper description +ok 282 - index_owner_is(schema, table, index, user, desc) should have the proper diagnostics +ok 283 - index_owner_is(schema, table, index, user) should pass +ok 284 - index_owner_is(schema, table, index, user) should have the proper description +ok 285 - index_owner_is(schema, table, index, user) should have the proper diagnostics +ok 286 - index_owner_is(schema, table, non-index, user, desc) should fail +ok 287 - index_owner_is(schema, table, non-index, user, desc) should have the proper description +ok 288 - index_owner_is(schema, table, non-index, user, desc) should have the proper diagnostics +ok 289 - index_owner_is(schema, non-table, index, user, desc) should fail +ok 290 - index_owner_is(schema, non-table, index, user, desc) should have the proper description +ok 291 - index_owner_is(schema, non-table, index, user, desc) should have the proper diagnostics +ok 292 - index_owner_is(non-schema, table, index, user, desc) should fail +ok 293 - index_owner_is(non-schema, table, index, user, desc) should have the proper description +ok 294 - index_owner_is(non-schema, table, index, user, desc) should have the proper diagnostics +ok 295 - index_owner_is(schema, table, index, non-user, desc) should fail +ok 296 - index_owner_is(schema, table, index, non-user, desc) should have the proper description +ok 297 - index_owner_is(schema, table, index, non-user, desc) should have the proper diagnostics +ok 298 - index_owner_is(invisible-table, index, user, desc) should fail +ok 299 - index_owner_is(invisible-table, index, user, desc) should have the proper description +ok 300 - index_owner_is(invisible-table, index, user, desc) should have the proper diagnostics +ok 301 - index_owner_is(table, index, user, desc) should pass +ok 302 - index_owner_is(table, index, user, desc) should have the proper description +ok 303 - index_owner_is(table, index, user, desc) should have the proper diagnostics +ok 304 - index_owner_is(table, index, user) should pass +ok 305 - index_owner_is(table, index, user) should have the proper description +ok 306 - index_owner_is(table, index, user) should have the proper diagnostics +ok 307 - index_owner_is(non-table, index, user) should fail +ok 308 - index_owner_is(non-table, index, user) should have the proper description +ok 309 - index_owner_is(non-table, index, user) should have the proper diagnostics +ok 310 - index_owner_is(table, non-index, user) should fail +ok 311 - index_owner_is(table, non-index, user) should have the proper description +ok 312 - index_owner_is(table, non-index, user) should have the proper diagnostics +ok 313 - index_owner_is(table, index, non-user) should fail +ok 314 - index_owner_is(table, index, non-user) should have the proper description +ok 315 - index_owner_is(table, index, non-user) should have the proper diagnostics +ok 316 - language_owner_is(language, user, desc) should pass +ok 317 - language_owner_is(language, user, desc) should have the proper description +ok 318 - language_owner_is(language, user, desc) should have the proper diagnostics +ok 319 - language_owner_is(language, user) should pass +ok 320 - language_owner_is(language, user) should have the proper description +ok 321 - language_owner_is(language, user) should have the proper diagnostics +ok 322 - language_owner_is(non-language, user) should fail +ok 323 - language_owner_is(non-language, user) should have the proper description +ok 324 - language_owner_is(non-language, user) should have the proper diagnostics +ok 325 - language_owner_is(language, non-user) should fail +ok 326 - language_owner_is(language, non-user) should have the proper description +ok 327 - language_owner_is(language, non-user) should have the proper diagnostics +ok 328 - opclass_owner_is(schema, opclass, user, desc) should pass +ok 329 - opclass_owner_is(schema, opclass, user, desc) should have the proper description +ok 330 - opclass_owner_is(schema, opclass, user, desc) should have the proper diagnostics +ok 331 - opclass_owner_is(schema, opclass, user) should pass +ok 332 - opclass_owner_is(schema, opclass, user) should have the proper description +ok 333 - opclass_owner_is(schema, opclass, user) should have the proper diagnostics +ok 334 - opclass_owner_is(non-schema, opclass, user, desc) should fail +ok 335 - opclass_owner_is(non-schema, opclass, user, desc) should have the proper description +ok 336 - opclass_owner_is(non-schema, opclass, user, desc) should have the proper diagnostics +ok 337 - opclass_owner_is(schema, not-opclass, user, desc) should fail +ok 338 - opclass_owner_is(schema, not-opclass, user, desc) should have the proper description +ok 339 - opclass_owner_is(schema, not-opclass, user, desc) should have the proper diagnostics +ok 340 - opclass_owner_is(schema, opclass, non-user, desc) should fail +ok 341 - opclass_owner_is(schema, opclass, non-user, desc) should have the proper description +ok 342 - opclass_owner_is(schema, opclass, non-user, desc) should have the proper diagnostics +ok 343 - opclass_owner_is(opclass, user, desc) should pass +ok 344 - opclass_owner_is(opclass, user, desc) should have the proper description +ok 345 - opclass_owner_is(opclass, user, desc) should have the proper diagnostics +ok 346 - opclass_owner_is(opclass, user) should pass +ok 347 - opclass_owner_is(opclass, user) should have the proper description +ok 348 - opclass_owner_is(opclass, user) should have the proper diagnostics +ok 349 - opclass_owner_is(non-opclass, user, desc) should fail +ok 350 - opclass_owner_is(non-opclass, user, desc) should have the proper description +ok 351 - opclass_owner_is(non-opclass, user, desc) should have the proper diagnostics +ok 352 - opclass_owner_is(opclass, non-user, desc) should fail +ok 353 - opclass_owner_is(opclass, non-user, desc) should have the proper description +ok 354 - opclass_owner_is(opclass, non-user, desc) should have the proper diagnostics +ok 355 - type_owner_is(schema, type, user, desc) should pass +ok 356 - type_owner_is(schema, type, user, desc) should have the proper description +ok 357 - type_owner_is(schema, type, user, desc) should have the proper diagnostics +ok 358 - type_owner_is(schema, type, user) should pass +ok 359 - type_owner_is(schema, type, user) should have the proper description +ok 360 - type_owner_is(schema, type, user) should have the proper diagnostics +ok 361 - type_owner_is(non-schema, type, user, desc) should fail +ok 362 - type_owner_is(non-schema, type, user, desc) should have the proper description +ok 363 - type_owner_is(non-schema, type, user, desc) should have the proper diagnostics +ok 364 - type_owner_is(schema, non-type, user, desc) should fail +ok 365 - type_owner_is(schema, non-type, user, desc) should have the proper description +ok 366 - type_owner_is(schema, non-type, user, desc) should have the proper diagnostics +ok 367 - type_owner_is(schema, type, non-user, desc) should fail +ok 368 - type_owner_is(schema, type, non-user, desc) should have the proper description +ok 369 - type_owner_is(schema, type, non-user, desc) should have the proper diagnostics +ok 370 - type_owner_is( invisible-type, user, desc) should fail +ok 371 - type_owner_is( invisible-type, user, desc) should have the proper description +ok 372 - type_owner_is( invisible-type, user, desc) should have the proper diagnostics +ok 373 - type_owner_is(type, user, desc) should pass +ok 374 - type_owner_is(type, user, desc) should have the proper description +ok 375 - type_owner_is(type, user, desc) should have the proper diagnostics +ok 376 - type_owner_is(type, user) should pass +ok 377 - type_owner_is(type, user) should have the proper description +ok 378 - type_owner_is(type, user) should have the proper diagnostics +ok 379 - type_owner_is(non-type, user, desc) should fail +ok 380 - type_owner_is(non-type, user, desc) should have the proper description +ok 381 - type_owner_is(non-type, user, desc) should have the proper diagnostics +ok 382 - type_owner_is(type, non-user, desc) should fail +ok 383 - type_owner_is(type, non-user, desc) should have the proper description +ok 384 - type_owner_is(type, non-user, desc) should have the proper diagnostics +ok 385 - materialized_view_owner_is(sch, materialized_view, user, desc) should pass +ok 386 - materialized_view_owner_is(sch, materialized_view, user, desc) should have the proper description +ok 387 - materialized_view_owner_is(sch, materialized_view, user, desc) should have the proper diagnostics +ok 388 - materialized_view_owner_is(sch, materialized_view, user) should pass +ok 389 - materialized_view_owner_is(sch, materialized_view, user) should have the proper description +ok 390 - materialized_view_owner_is(sch, materialized_view, user) should have the proper diagnostics +ok 391 - materialized_view_owner_is(non-sch, materialized_view, user) should fail +ok 392 - materialized_view_owner_is(non-sch, materialized_view, user) should have the proper description +ok 393 - materialized_view_owner_is(non-sch, materialized_view, user) should have the proper diagnostics +ok 394 - materialized_view_owner_is(sch, non-materialized_view, user) should fail +ok 395 - materialized_view_owner_is(sch, non-materialized_view, user) should have the proper description +ok 396 - materialized_view_owner_is(sch, non-materialized_view, user) should have the proper diagnostics +ok 397 - materialized_view_owner_is(materialized_view, user, desc) should pass +ok 398 - materialized_view_owner_is(materialized_view, user, desc) should have the proper description +ok 399 - materialized_view_owner_is(materialized_view, user, desc) should have the proper diagnostics +ok 400 - materialized_view_owner_is(view, user) should pass +ok 401 - materialized_view_owner_is(view, user) should have the proper description +ok 402 - materialized_view_owner_is(view, user) should have the proper diagnostics +ok 403 - materialized_view_owner_is(non-materialized_view, user) should fail +ok 404 - materialized_view_owner_is(non-materialized_view, user) should have the proper description +ok 405 - materialized_view_owner_is(non-materialized_view, user) should have the proper diagnostics +ok 406 - materialized_view_owner_is(sch, seq, user, desc) should fail +ok 407 - materialized_view_owner_is(sch, seq, user, desc) should have the proper description +ok 408 - materialized_view_owner_is(sch, seq, user, desc) should have the proper diagnostics +ok 409 - materialized_view_owner_is(seq, user, desc) should fail +ok 410 - materialized_view_owner_is(seq, user, desc) should have the proper description +ok 411 - materialized_view_owner_is(seq, user, desc) should have the proper diagnostics diff --git a/test/sql/aretap.sql b/test/sql/aretap.sql index e3c63d547a70..b8f3da040a20 100644 --- a/test/sql/aretap.sql +++ b/test/sql/aretap.sql @@ -13,9 +13,22 @@ CREATE TABLE public.fou( numb NUMERIC(10, 2), "myInt" NUMERIC(8) ); -CREATE TABLE public.foo( - id INT NOT NULL PRIMARY KEY -); +-- Create a partition. +CREATE FUNCTION mkpart() RETURNS SETOF TEXT AS $$ +BEGIN + IF pg_version_num() >= 100000 THEN + EXECUTE $E$ + CREATE TABLE public.foo (dt DATE NOT NULL) PARTITION BY RANGE (dt); + $E$; + ELSE + EXECUTE $E$ + CREATE TABLE public.foo (dt DATE NOT NULL); + $E$; + END IF; + RETURN; +END; +$$ LANGUAGE plpgsql; +SELECT * FROM mkpart(); CREATE RULE ins_me AS ON INSERT TO public.fou DO NOTHING; CREATE RULE upd_me AS ON UPDATE TO public.fou DO NOTHING; diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index c7fa4f3a85f4..cffac11c8743 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(830); +SELECT plan(842); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -13,6 +13,23 @@ CREATE TABLE public.sometab( "myInt" NUMERIC(8) ); +-- Create a partition. +CREATE FUNCTION mkpart() RETURNS SETOF TEXT AS $$ +BEGIN + IF pg_version_num() >= 100000 THEN + EXECUTE $E$ + CREATE TABLE public.apart (dt DATE NOT NULL) PARTITION BY RANGE (dt); + $E$; + ELSE + EXECUTE $E$ + CREATE TABLE public.apart (dt DATE NOT NULL); + $E$; + END IF; + RETURN; +END; +$$ LANGUAGE plpgsql; +SELECT * FROM mkpart(); + CREATE TYPE public.sometype AS ( id INT, name TEXT @@ -231,6 +248,23 @@ SELECT * FROM check_test( '' ); +-- But not partitions. +SELECT * FROM check_test( + has_table( 'public', 'apart', 'have apart' ), + true, + 'has_table(sch, part, desc)', + 'have apart', + '' +); + +SELECT * FROM check_test( + has_table( 'apart', 'have apart' ), + true, + 'has_table(part, desc)', + 'have apart', + '' +); + /****************************************************************************/ -- Test hasnt_table(). @@ -282,6 +316,22 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + hasnt_table( 'apart', 'got apart' ), + false, + 'hasnt_table(part, desc)', + 'got apart', + '' +); + +SELECT * FROM check_test( + hasnt_table( 'public', 'apart', 'got apart' ), + false, + 'hasnt_table(sch, part, desc)', + 'got apart', + '' +); + /****************************************************************************/ -- Test has_view(). @@ -2662,4 +2712,3 @@ SELECT * FROm test_hasnt_materialized_views_are(); -- Finish the tests and clean up. SELECT * FROM finish(); ROLLBACK; - diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index a058d6c8cb6a..531024ad1e1c 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(384); +SELECT plan(411); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -15,6 +15,23 @@ CREATE TABLE public.sometab( CREATE INDEX idx_hey ON public.sometab(numb); +-- Create a partition. +CREATE FUNCTION mkpart() RETURNS SETOF TEXT AS $$ +BEGIN + IF pg_version_num() >= 100000 THEN + EXECUTE $E$ + CREATE TABLE public.apart (dt DATE NOT NULL) PARTITION BY RANGE (dt); + $E$; + ELSE + EXECUTE $E$ + CREATE TABLE public.apart (dt DATE NOT NULL); + $E$; + END IF; + RETURN; +END; +$$ LANGUAGE plpgsql; +SELECT * FROM mkpart(); + CREATE VIEW public.someview AS SELECT * FROM public.sometab; CREATE TYPE public.sometype AS ( @@ -168,6 +185,64 @@ SELECT * FROM check_test( ' Relation __not__sometab does not exist' ); +/****************************************************************************/ +-- Test relation_owner_is() with a partition. +SELECT * FROM check_test( + relation_owner_is('public', 'apart', current_user, 'mumble'), + true, + 'relation_owner_is(sch, part, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + relation_owner_is('public', 'apart', current_user), + true, + 'relation_owner_is(sch, part, user)', + 'Relation public.apart should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + relation_owner_is('__not__public', 'apart', current_user, 'mumble'), + false, + 'relation_owner_is(non-sch, part, user)', + 'mumble', + ' Relation __not__public.apart does not exist' +); + +SELECT * FROM check_test( + relation_owner_is('public', '__not__apart', current_user, 'mumble'), + false, + 'relation_owner_is(sch, non-part, user)', + 'mumble', + ' Relation public.__not__apart does not exist' +); + +SELECT * FROM check_test( + relation_owner_is('apart', current_user, 'mumble'), + true, + 'relation_owner_is(part, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + relation_owner_is('apart', current_user), + true, + 'relation_owner_is(part, user)', + 'Relation apart should be owned by ' || current_user, + '' +); + +SELECT * FROM check_test( + relation_owner_is('__not__apart', current_user, 'mumble'), + false, + 'relation_owner_is(non-part, user)', + 'mumble', + ' Relation __not__apart does not exist' +); + /****************************************************************************/ -- Test relation_owner_is() with a schema. SELECT * FROM check_test( @@ -301,6 +376,23 @@ SELECT * FROM check_test( ' Table someseq does not exist' ); +-- But not a partition. +SELECT * FROM check_test( + table_owner_is('public', 'apart', current_user, 'mumble'), + true, + 'table_owner_is(sch, part, user, desc)', + 'mumble', + '' +); + +SELECT * FROM check_test( + table_owner_is('apart', current_user, 'mumble'), + true, + 'table_owner_is(part, user, desc)', + 'mumble', + '' +); + /****************************************************************************/ -- Test view_owner_is(). SELECT * FROM check_test( @@ -1397,4 +1489,3 @@ SELECT * from test_materialized_view_owner_is(); -- Finish the tests and clean up. SELECT * FROM finish(); ROLLBACK; - From 7d67ff20a86b9302a917ec5bf2955783d6ba74b8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 5 Nov 2017 19:49:16 -0500 Subject: [PATCH 0954/1195] Add is_partitioned(). Also isnt_partitioned(). --- Changes | 3 + doc/pgtap.mmd | 46 +++++ sql/pgtap--0.97.0--0.98.0.sql | 60 ++++++ sql/pgtap.sql.in | 60 ++++++ test/expected/hastap.out | 44 ++++- test/sql/hastap.sql | 334 ++++++++++++++++++++++++++++++++-- 6 files changed, 535 insertions(+), 12 deletions(-) diff --git a/Changes b/Changes index 449702dfd681..418033510515 100644 --- a/Changes +++ b/Changes @@ -22,6 +22,9 @@ Revision history for pgTAP + `hasnt_table()` + `tables_are()` + `table_owner_is()` +* Added partition-testing assert functions: + + `is_partitioned()` + + `isnt_partitioned()` 0.97.0 2016-11-28T22:18:29Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 3bdc90952f08..0fe8659ff7f2 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4670,6 +4670,52 @@ Tests whether an index is unique. Tests whether an index is on a primary key. +### `is_partitioned()` ### + + SELECT is_partitioned( :schema, :table, :description ); + SELECT is_partitioned( :schema, :table ); + SELECT is_partitioned( :table, :description); + SELECT is_partitioned( :table ); + +**Parameters** + +`:schema` +: Schema in which to find the partitioned table. + +`:table` +: Name of a partitioned table. + +`:description` +: A short description of the test. + +Tests whether a table is partitioned. The first argument is the schema name, +the second the table name, the the third is the test description. If the schema +is omitted, the table must be visible in the search path. If the test +description is omitted, it will be set to "Table `:table` should be +partitioned". Note that this test will fail if the table in question does not +exist. + +### `isnt_partitioned()` ### + + SELECT isnt_partitioned( :schema, :table, :description ); + SELECT isnt_partitioned( :schema, :table ); + SELECT isnt_partitioned( :table, :description); + SELECT isnt_partitioned( :table ); + +**Parameters** + +`:schema` +: Schema in which to find the table. + +`:table` +: Name of a table. + +`:description` +: A short description of the test. + +This function is the inverse of `is_partitioned()`. The test passes if the +specified table is *not* partitioned, or if it does not exist. + ### `is_clustered()` ### SELECT is_clustered( :schema, :table, :index, :description ); diff --git a/sql/pgtap--0.97.0--0.98.0.sql b/sql/pgtap--0.97.0--0.98.0.sql index 60878ff6c9ea..f2cbc93e845e 100644 --- a/sql/pgtap--0.97.0--0.98.0.sql +++ b/sql/pgtap--0.97.0--0.98.0.sql @@ -232,3 +232,63 @@ BEGIN RETURN is(owner, $3, $4); END; $$ LANGUAGE plpgsql; + +-- 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 8b31811f5b73..645cf73c418a 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -9835,3 +9835,63 @@ RETURNS TEXT AS $$ 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; diff --git a/test/expected/hastap.out b/test/expected/hastap.out index 61c64569a860..69fe7a4002da 100644 --- a/test/expected/hastap.out +++ b/test/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..842 +1..884 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -842,3 +842,45 @@ ok 839 - hasnt_materialized_view(materialized_view, desc) should have the proper ok 840 - hasnt_materialized_view(sch, materialized_view, desc) should fail ok 841 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper description ok 842 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper diagnostics +ok 843 - is_partitioned(non-existent part) should fail +ok 844 - is_partitioned(non-existent part) should have the proper description +ok 845 - is_partitioned(non-existent part) should have the proper diagnostics +ok 846 - is_partitioned(non-existent part, desc) should fail +ok 847 - is_partitioned(non-existent part, desc) should have the proper description +ok 848 - is_partitioned(non-existent part, desc) should have the proper diagnostics +ok 849 - is_partitioned(sch, non-existtent part, desc) should fail +ok 850 - is_partitioned(sch, non-existtent part, desc) should have the proper description +ok 851 - is_partitioned(sch, non-existtent part, desc) should have the proper diagnostics +ok 852 - is_partitioned(sch, part, desc) should pass +ok 853 - is_partitioned(sch, part, desc) should have the proper description +ok 854 - is_partitioned(sch, part, desc) should have the proper diagnostics +ok 855 - is_partitioned(sch, part) should pass +ok 856 - is_partitioned(sch, part) should have the proper description +ok 857 - is_partitioned(sch, part) should have the proper diagnostics +ok 858 - is_partitioned(part, desc) should pass +ok 859 - is_partitioned(part, desc) should have the proper description +ok 860 - is_partitioned(part, desc) should have the proper diagnostics +ok 861 - is_partitioned(part) should pass +ok 862 - is_partitioned(part) should have the proper description +ok 863 - is_partitioned(part) should have the proper diagnostics +ok 864 - isnt_partitioned(non-existent partition) should pass +ok 865 - isnt_partitioned(non-existent partition) should have the proper description +ok 866 - isnt_partitioned(non-existent partition) should have the proper diagnostics +ok 867 - isnt_partitioned(non-existent partition, desc) should pass +ok 868 - isnt_partitioned(non-existent partition, desc) should have the proper description +ok 869 - isnt_partitioned(non-existent partition, desc) should have the proper diagnostics +ok 870 - isnt_partitioned(sch, non-existtent partition, desc) should pass +ok 871 - isnt_partitioned(sch, non-existtent partition, desc) should have the proper description +ok 872 - isnt_partitioned(sch, non-existtent partition, desc) should have the proper diagnostics +ok 873 - isnt_partitioned(sch, part, desc) should fail +ok 874 - isnt_partitioned(sch, part, desc) should have the proper description +ok 875 - isnt_partitioned(sch, part, desc) should have the proper diagnostics +ok 876 - isnt_partitioned(sch, part) should fail +ok 877 - isnt_partitioned(sch, part) should have the proper description +ok 878 - isnt_partitioned(sch, part) should have the proper diagnostics +ok 879 - isnt_partitioned(part, desc) should fail +ok 880 - isnt_partitioned(part, desc) should have the proper description +ok 881 - isnt_partitioned(part, desc) should have the proper diagnostics +ok 882 - isnt_partitioned(part) should fail +ok 883 - isnt_partitioned(part) should have the proper description +ok 884 - isnt_partitioned(part) should have the proper diagnostics diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index cffac11c8743..2667ccb18a14 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(842); +SELECT plan(884); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -2586,11 +2586,10 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; - - end if; - - return; -end; $$language PLPGSQL; + END IF; + RETURN; +END; +$$ language PLPGSQL; /****************************************************************************/ -- Test hasnt_materialized_view(). @@ -2699,15 +2698,328 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; - end if; - - return; -end; $$language PLPGSQL; + END IF; + RETURN; +END; +$$ language PLPGSQL; SELECT * FROM test_has_materialized_view(); - SELECT * FROm test_hasnt_materialized_views_are(); +/****************************************************************************/ +-- Test is_partitioned(). +CREATE FUNCTION test_is_partitioned() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() >= 100000 THEN + EXECUTE $E$ + CREATE TABLE public.somepart ( + logdate date not null, + peaktemp int, + unitsales int + ) PARTITION BY RANGE (logdate); + $E$; + + FOR tap IN SELECT * FROM check_test( + is_partitioned( '__SDFSDFD__' ), + false, + 'is_partitioned(non-existent part)', + 'Table "__SDFSDFD__" should be partitioned', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + is_partitioned( '__SDFSDFD__', 'howdy' ), + false, + 'is_partitioned(non-existent part, desc)', + 'howdy', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + is_partitioned( 'foo', '__SDFSDFD__', 'desc' ), + false, + 'is_partitioned(sch, non-existtent part, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + is_partitioned( 'public', 'somepart', 'desc' ), + true, + 'is_partitioned(sch, part, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + is_partitioned( 'public', 'somepart'::name ), + true, + 'is_partitioned(sch, part)', + 'Table public.somepart should be partitioned', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + is_partitioned( 'somepart', 'yowza' ), + true, + 'is_partitioned(part, desc)', + 'yowza', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + is_partitioned( 'somepart' ), + true, + 'is_partitioned(part)', + 'Table somepart should be partitioned', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + ELSE + FOR tap IN SELECT * FROM check_test( + has_view( '__SDFSDFD__' ), + false, + 'is_partitioned(non-existent part)', + 'View "__SDFSDFD__" should exist', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_view( '__SDFSDFD__', 'howdy' ), + false, + 'is_partitioned(non-existent part, desc)', + 'howdy', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_view( 'foo', '__SDFSDFD__', 'desc' ), + false, + 'is_partitioned(sch, non-existent part, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_view( 'information_schema', 'tables', 'desc' ), + true, + 'is_partitioned(sch, part, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_view( 'information_schema', 'tables', 'desc' ), + true, + 'is_partitioned(sch, part)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_view( 'information_schema', 'tables', 'desc' ), + true, + 'is_partitioned(part, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_view( 'pg_tables' ), + true, + 'is_partitioned(part)', + 'View pg_tables should exist' + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + END IF; + RETURN; +END; +$$ language PLPGSQL; + +/****************************************************************************/ +-- Test isnt_partitioned(). +CREATE FUNCTION test_isnt_partitioned() RETURNS SETOF TEXT AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() >= 100000 THEN + FOR tap IN SELECT * FROM check_test( + isnt_partitioned( '__SDFSDFD__' ), + true, + 'isnt_partitioned(non-existent partition)', + 'Table "__SDFSDFD__" should not be partitioned', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + isnt_partitioned( '__SDFSDFD__', 'howdy' ), + true, + 'isnt_partitioned(non-existent partition, desc)', + 'howdy', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + isnt_partitioned( 'foo', '__SDFSDFD__', 'desc' ), + true, + 'isnt_partitioned(sch, non-existtent partition, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + isnt_partitioned( 'public', 'somepart', 'desc' ), + false, + 'isnt_partitioned(sch, part, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + isnt_partitioned( 'public', 'somepart'::name ), + false, + 'isnt_partitioned(sch, part)', + 'Table public.somepart should not be partitioned', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + isnt_partitioned( 'somepart', 'yowza'::text ), + false, + 'isnt_partitioned(part, desc)', + 'yowza', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + isnt_partitioned( 'somepart' ), + false, + 'isnt_partitioned(part)', + 'Table somepart should not be partitioned', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + else + FOR tap IN SELECT * FROM check_test( + hasnt_view( '__SDFSDFD__' ), + true, + 'isnt_partitioned(non-existent part)', + 'View "__SDFSDFD__" should not exist', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_view( '__SDFSDFD__', 'howdy' ), + true, + 'isnt_partitioned(non-existent part, desc)', + 'howdy', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_view( 'foo', '__SDFSDFD__', 'desc' ), + true, + 'isnt_partitioned(sch, non-existent part, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_view( 'information_schema', 'tables', 'desc' ), + false, + 'isnt_partitioned(sch, part, desc)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_view( 'information_schema', 'tables', 'desc' ), + false, + 'isnt_partitioned(sch, part)', + 'desc', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_view( 'pg_tables', 'yowza' ), + false, + 'isnt_partitioned(part, desc)', + 'yowza', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + hasnt_view( 'pg_tables', 'yowza' ), + false, + 'isnt_partitioned(part)', + 'yowza', + '' + ) AS b LOOP + RETURN NEXT tap.b; + END LOOP; + END IF; + RETURN; +END; +$$ language PLPGSQL; + +SELECT * FROM test_is_partitioned(); +SELECT * FROM test_isnt_partitioned(); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From e18fd1459589015a8797f5d8885f58c5bea23b81 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 5 Nov 2017 20:00:01 -0500 Subject: [PATCH 0955/1195] Make tests work with and without partitions. --- test/expected/hastap.out | 60 ++++++++++++++++++++-------------------- test/sql/hastap.sql | 58 +++++++++++++++++--------------------- 2 files changed, 55 insertions(+), 63 deletions(-) diff --git a/test/expected/hastap.out b/test/expected/hastap.out index 69fe7a4002da..0008ce5715dd 100644 --- a/test/expected/hastap.out +++ b/test/expected/hastap.out @@ -108,9 +108,9 @@ ok 105 - has_view(non-existent view) should have the proper diagnostics ok 106 - has_view(non-existent view, desc) should fail ok 107 - has_view(non-existent view, desc) should have the proper description ok 108 - has_view(non-existent view, desc) should have the proper diagnostics -ok 109 - has_view(sch, non-existtent view, desc) should fail -ok 110 - has_view(sch, non-existtent view, desc) should have the proper description -ok 111 - has_view(sch, non-existtent view, desc) should have the proper diagnostics +ok 109 - has_view(sch, non-existent view, desc) should fail +ok 110 - has_view(sch, non-existent view, desc) should have the proper description +ok 111 - has_view(sch, non-existent view, desc) should have the proper diagnostics ok 112 - has_view(view, desc) should pass ok 113 - has_view(view, desc) should have the proper description ok 114 - has_view(view, desc) should have the proper diagnostics @@ -123,9 +123,9 @@ ok 120 - hasnt_view(non-existent view) should have the proper diagnostics ok 121 - hasnt_view(non-existent view, desc) should pass ok 122 - hasnt_view(non-existent view, desc) should have the proper description ok 123 - hasnt_view(non-existent view, desc) should have the proper diagnostics -ok 124 - hasnt_view(sch, non-existtent view, desc) should pass -ok 125 - hasnt_view(sch, non-existtent view, desc) should have the proper description -ok 126 - hasnt_view(sch, non-existtent view, desc) should have the proper diagnostics +ok 124 - hasnt_view(sch, non-existent view, desc) should pass +ok 125 - hasnt_view(sch, non-existent view, desc) should have the proper description +ok 126 - hasnt_view(sch, non-existent view, desc) should have the proper diagnostics ok 127 - hasnt_view(view, desc) should fail ok 128 - hasnt_view(view, desc) should have the proper description ok 129 - hasnt_view(view, desc) should have the proper diagnostics @@ -138,9 +138,9 @@ ok 135 - has_sequence(non-existent sequence) should have the proper diagnostics ok 136 - has_sequence(non-existent sequence, desc) should fail ok 137 - has_sequence(non-existent sequence, desc) should have the proper description ok 138 - has_sequence(non-existent sequence, desc) should have the proper diagnostics -ok 139 - has_sequence(sch, non-existtent sequence, desc) should fail -ok 140 - has_sequence(sch, non-existtent sequence, desc) should have the proper description -ok 141 - has_sequence(sch, non-existtent sequence, desc) should have the proper diagnostics +ok 139 - has_sequence(sch, non-existent sequence, desc) should fail +ok 140 - has_sequence(sch, non-existent sequence, desc) should have the proper description +ok 141 - has_sequence(sch, non-existent sequence, desc) should have the proper diagnostics ok 142 - has_sequence(sequence, desc) should pass ok 143 - has_sequence(sequence, desc) should have the proper description ok 144 - has_sequence(sequence, desc) should have the proper diagnostics @@ -155,9 +155,9 @@ ok 152 - hasnt_sequence(non-existent sequence) should have the proper diagnostic ok 153 - hasnt_sequence(non-existent sequence, desc) should pass ok 154 - hasnt_sequence(non-existent sequence, desc) should have the proper description ok 155 - hasnt_sequence(non-existent sequence, desc) should have the proper diagnostics -ok 156 - hasnt_sequence(sch, non-existtent sequence, desc) should pass -ok 157 - hasnt_sequence(sch, non-existtent sequence, desc) should have the proper description -ok 158 - hasnt_sequence(sch, non-existtent sequence, desc) should have the proper diagnostics +ok 156 - hasnt_sequence(sch, non-existent sequence, desc) should pass +ok 157 - hasnt_sequence(sch, non-existent sequence, desc) should have the proper description +ok 158 - hasnt_sequence(sch, non-existent sequence, desc) should have the proper diagnostics ok 159 - hasnt_sequence(sequence, desc) should fail ok 160 - hasnt_sequence(sequence, desc) should have the proper description ok 161 - hasnt_sequence(sequence, desc) should have the proper diagnostics @@ -818,9 +818,9 @@ ok 815 - has_materialized_view(non-existent materialized_view) should have the p ok 816 - has_materialized_view(non-existent materialized_view, desc) should fail ok 817 - has_materialized_view(non-existent materialized_view, desc) should have the proper description ok 818 - has_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics -ok 819 - has_materialized_view(sch, non-existtent materialized_view, desc) should fail -ok 820 - has_materialized_view(sch, non-existtent materialized_view, desc) should have the proper description -ok 821 - has_materialized_view(sch, non-existtent materialized_view, desc) should have the proper diagnostics +ok 819 - has_materialized_view(sch, non-existent materialized_view, desc) should fail +ok 820 - has_materialized_view(sch, non-existent materialized_view, desc) should have the proper description +ok 821 - has_materialized_view(sch, non-existent materialized_view, desc) should have the proper diagnostics ok 822 - has_materialized_view(materialized_view, desc) should pass ok 823 - has_materialized_view(materialized_view, desc) should have the proper description ok 824 - has_materialized_view(materialized_view, desc) should have the proper diagnostics @@ -833,9 +833,9 @@ ok 830 - hasnt_materialized_view(non-existent materialized_view) should have the ok 831 - hasnt_materialized_view(non-existent materialized_view, desc) should pass ok 832 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper description ok 833 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics -ok 834 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should pass -ok 835 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should have the proper description -ok 836 - hasnt_materialized_view(sch, non-existtent materialized_view, desc) should have the proper diagnostics +ok 834 - hasnt_materialized_view(sch, non-existent materialized_view, desc) should pass +ok 835 - hasnt_materialized_view(sch, non-existent materialized_view, desc) should have the proper description +ok 836 - hasnt_materialized_view(sch, non-existent materialized_view, desc) should have the proper diagnostics ok 837 - hasnt_materialized_view(materialized_view, desc) should fail ok 838 - hasnt_materialized_view(materialized_view, desc) should have the proper description ok 839 - hasnt_materialized_view(materialized_view, desc) should have the proper diagnostics @@ -848,9 +848,9 @@ ok 845 - is_partitioned(non-existent part) should have the proper diagnostics ok 846 - is_partitioned(non-existent part, desc) should fail ok 847 - is_partitioned(non-existent part, desc) should have the proper description ok 848 - is_partitioned(non-existent part, desc) should have the proper diagnostics -ok 849 - is_partitioned(sch, non-existtent part, desc) should fail -ok 850 - is_partitioned(sch, non-existtent part, desc) should have the proper description -ok 851 - is_partitioned(sch, non-existtent part, desc) should have the proper diagnostics +ok 849 - is_partitioned(sch, non-existent part, desc) should fail +ok 850 - is_partitioned(sch, non-existent part, desc) should have the proper description +ok 851 - is_partitioned(sch, non-existent part, desc) should have the proper diagnostics ok 852 - is_partitioned(sch, part, desc) should pass ok 853 - is_partitioned(sch, part, desc) should have the proper description ok 854 - is_partitioned(sch, part, desc) should have the proper diagnostics @@ -863,15 +863,15 @@ ok 860 - is_partitioned(part, desc) should have the proper diagnostics ok 861 - is_partitioned(part) should pass ok 862 - is_partitioned(part) should have the proper description ok 863 - is_partitioned(part) should have the proper diagnostics -ok 864 - isnt_partitioned(non-existent partition) should pass -ok 865 - isnt_partitioned(non-existent partition) should have the proper description -ok 866 - isnt_partitioned(non-existent partition) should have the proper diagnostics -ok 867 - isnt_partitioned(non-existent partition, desc) should pass -ok 868 - isnt_partitioned(non-existent partition, desc) should have the proper description -ok 869 - isnt_partitioned(non-existent partition, desc) should have the proper diagnostics -ok 870 - isnt_partitioned(sch, non-existtent partition, desc) should pass -ok 871 - isnt_partitioned(sch, non-existtent partition, desc) should have the proper description -ok 872 - isnt_partitioned(sch, non-existtent partition, desc) should have the proper diagnostics +ok 864 - isnt_partitioned(non-existent part) should pass +ok 865 - isnt_partitioned(non-existent part) should have the proper description +ok 866 - isnt_partitioned(non-existent part) should have the proper diagnostics +ok 867 - isnt_partitioned(non-existent part, desc) should pass +ok 868 - isnt_partitioned(non-existent part, desc) should have the proper description +ok 869 - isnt_partitioned(non-existent part, desc) should have the proper diagnostics +ok 870 - isnt_partitioned(sch, non-existent part, desc) should pass +ok 871 - isnt_partitioned(sch, non-existent part, desc) should have the proper description +ok 872 - isnt_partitioned(sch, non-existent part, desc) should have the proper diagnostics ok 873 - isnt_partitioned(sch, part, desc) should fail ok 874 - isnt_partitioned(sch, part, desc) should have the proper description ok 875 - isnt_partitioned(sch, part, desc) should have the proper diagnostics diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index 2667ccb18a14..8d55f652a5f0 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -354,7 +354,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( has_view( 'foo', '__SDFSDFD__', 'desc' ), false, - 'has_view(sch, non-existtent view, desc)', + 'has_view(sch, non-existent view, desc)', 'desc', '' ); @@ -397,7 +397,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( hasnt_view( 'foo', '__SDFSDFD__', 'desc' ), true, - 'hasnt_view(sch, non-existtent view, desc)', + 'hasnt_view(sch, non-existent view, desc)', 'desc', '' ); @@ -440,7 +440,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( has_sequence( 'foo', '__SDFSDFD__', 'desc' ), false, - 'has_sequence(sch, non-existtent sequence, desc)', + 'has_sequence(sch, non-existent sequence, desc)', 'desc', '' ); @@ -491,7 +491,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( hasnt_sequence( 'foo', '__SDFSDFD__', 'desc' ), true, - 'hasnt_sequence(sch, non-existtent sequence, desc)', + 'hasnt_sequence(sch, non-existent sequence, desc)', 'desc', '' ); @@ -2509,7 +2509,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( has_materialized_view( 'foo', '__SDFSDFD__', 'desc' ), false, - 'has_materialized_view(sch, non-existtent materialized_view, desc)', + 'has_materialized_view(sch, non-existent materialized_view, desc)', 'desc', '' ) AS b LOOP @@ -2560,7 +2560,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( has_view( 'foo', '__SDFSDFD__', 'desc' ), false, - 'has_materialized_view(sch, non-existtent materialized_view, desc)', + 'has_materialized_view(sch, non-existent materialized_view, desc)', 'desc', '' ) AS b LOOP @@ -2622,7 +2622,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( hasnt_materialized_view( 'foo', '__SDFSDFD__', 'desc' ), true, - 'hasnt_materialized_view(sch, non-existtent materialized_view, desc)', + 'hasnt_materialized_view(sch, non-existent materialized_view, desc)', 'desc', '' ) AS b LOOP @@ -2672,7 +2672,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( hasnt_view( 'foo', '__SDFSDFD__', 'desc' ), true, - 'hasnt_materialized_view(sch, non-existtent materialized_view, desc)', + 'hasnt_materialized_view(sch, non-existent materialized_view, desc)', 'desc', '' ) AS b LOOP @@ -2713,14 +2713,6 @@ DECLARE tap record; BEGIN IF pg_version_num() >= 100000 THEN - EXECUTE $E$ - CREATE TABLE public.somepart ( - logdate date not null, - peaktemp int, - unitsales int - ) PARTITION BY RANGE (logdate); - $E$; - FOR tap IN SELECT * FROM check_test( is_partitioned( '__SDFSDFD__' ), false, @@ -2744,7 +2736,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( is_partitioned( 'foo', '__SDFSDFD__', 'desc' ), false, - 'is_partitioned(sch, non-existtent part, desc)', + 'is_partitioned(sch, non-existent part, desc)', 'desc', '' ) AS b LOOP @@ -2752,7 +2744,7 @@ BEGIN END LOOP; FOR tap IN SELECT * FROM check_test( - is_partitioned( 'public', 'somepart', 'desc' ), + is_partitioned( 'public', 'apart', 'desc' ), true, 'is_partitioned(sch, part, desc)', 'desc', @@ -2762,17 +2754,17 @@ BEGIN END LOOP; FOR tap IN SELECT * FROM check_test( - is_partitioned( 'public', 'somepart'::name ), + is_partitioned( 'public', 'apart'::name ), true, 'is_partitioned(sch, part)', - 'Table public.somepart should be partitioned', + 'Table public.apart should be partitioned', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - is_partitioned( 'somepart', 'yowza' ), + is_partitioned( 'apart', 'yowza' ), true, 'is_partitioned(part, desc)', 'yowza', @@ -2782,10 +2774,10 @@ BEGIN END LOOP; FOR tap IN SELECT * FROM check_test( - is_partitioned( 'somepart' ), + is_partitioned( 'apart' ), true, 'is_partitioned(part)', - 'Table somepart should be partitioned', + 'Table apart should be partitioned', '' ) AS b LOOP RETURN NEXT tap.b; @@ -2856,7 +2848,7 @@ BEGIN has_view( 'pg_tables' ), true, 'is_partitioned(part)', - 'View pg_tables should exist' + 'View pg_tables should exist', '' ) AS b LOOP RETURN NEXT tap.b; @@ -2876,7 +2868,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( isnt_partitioned( '__SDFSDFD__' ), true, - 'isnt_partitioned(non-existent partition)', + 'isnt_partitioned(non-existent part)', 'Table "__SDFSDFD__" should not be partitioned', '' ) AS b LOOP @@ -2886,7 +2878,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( isnt_partitioned( '__SDFSDFD__', 'howdy' ), true, - 'isnt_partitioned(non-existent partition, desc)', + 'isnt_partitioned(non-existent part, desc)', 'howdy', '' ) AS b LOOP @@ -2896,7 +2888,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( isnt_partitioned( 'foo', '__SDFSDFD__', 'desc' ), true, - 'isnt_partitioned(sch, non-existtent partition, desc)', + 'isnt_partitioned(sch, non-existent part, desc)', 'desc', '' ) AS b LOOP @@ -2904,7 +2896,7 @@ BEGIN END LOOP; FOR tap IN SELECT * FROM check_test( - isnt_partitioned( 'public', 'somepart', 'desc' ), + isnt_partitioned( 'public', 'apart', 'desc' ), false, 'isnt_partitioned(sch, part, desc)', 'desc', @@ -2914,17 +2906,17 @@ BEGIN END LOOP; FOR tap IN SELECT * FROM check_test( - isnt_partitioned( 'public', 'somepart'::name ), + isnt_partitioned( 'public', 'apart'::name ), false, 'isnt_partitioned(sch, part)', - 'Table public.somepart should not be partitioned', + 'Table public.apart should not be partitioned', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - isnt_partitioned( 'somepart', 'yowza'::text ), + isnt_partitioned( 'apart', 'yowza'::text ), false, 'isnt_partitioned(part, desc)', 'yowza', @@ -2934,10 +2926,10 @@ BEGIN END LOOP; FOR tap IN SELECT * FROM check_test( - isnt_partitioned( 'somepart' ), + isnt_partitioned( 'apart' ), false, 'isnt_partitioned(part)', - 'Table somepart should not be partitioned', + 'Table apart should not be partitioned', '' ) AS b LOOP RETURN NEXT tap.b; From 12865bafeaed0b0b53bdc16fab3e864dc7f4318f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 5 Nov 2017 20:37:34 -0500 Subject: [PATCH 0956/1195] Fix 9.0 patch. --- compat/install-9.0.patch | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/compat/install-9.0.patch b/compat/install-9.0.patch index 13cc9af2c134..7301a4c2e1bd 100644 --- a/compat/install-9.0.patch +++ b/compat/install-9.0.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -3686,7 +3686,7 @@ +@@ -3696,7 +3696,7 @@ AND n.nspname = $1 AND t.typname = $2 AND t.typtype = 'e' @@ -9,7 +9,7 @@ ), $3, $4 -@@ -3714,7 +3714,7 @@ +@@ -3724,7 +3724,7 @@ AND pg_catalog.pg_type_is_visible(t.oid) AND t.typname = $1 AND t.typtype = 'e' @@ -18,7 +18,7 @@ ), $2, $3 -@@ -6138,7 +6138,7 @@ +@@ -6168,7 +6168,7 @@ CREATE OR REPLACE FUNCTION findfuncs( NAME, TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( @@ -27,7 +27,7 @@ FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = $1 -@@ -6155,7 +6155,7 @@ +@@ -6185,7 +6185,7 @@ CREATE OR REPLACE FUNCTION findfuncs( TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( @@ -36,11 +36,10 @@ 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) -@@ -9646,134 +9646,3 @@ - +@@ -9687,137 +9687,6 @@ 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 $$ @@ -171,3 +170,7 @@ - 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 $$ From 9b6994a23b9bb990e2387cb0a54d8881a3adea46 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 5 Nov 2017 21:12:28 -0500 Subject: [PATCH 0957/1195] Sketch out is_partition_of(). Not even tested yet, but this should be a good start to it. --- sql/pgtap.sql.in | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 645cf73c418a..b6bbf7d0304c 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -9895,3 +9895,70 @@ RETURNS TEXT AS $$ '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; From c1a40f92d63326fa4b0c39643df5e87c35aea10d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 10:51:47 -0500 Subject: [PATCH 0958/1195] Test is_partition_of(). --- Changes | 1 + Makefile | 6 + sql/pgtap.sql.in | 4 +- test/expected/partitions.out | 68 +++++++++++ test/sql/partitions.sql | 225 +++++++++++++++++++++++++++++++++++ 5 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 test/expected/partitions.out create mode 100644 test/sql/partitions.sql diff --git a/Changes b/Changes index 418033510515..09933d4b43db 100644 --- a/Changes +++ b/Changes @@ -25,6 +25,7 @@ Revision history for pgTAP * Added partition-testing assert functions: + `is_partitioned()` + `isnt_partitioned()` + + `is_partition_of()` 0.97.0 2016-11-28T22:18:29Z --------------------------- diff --git a/Makefile b/Makefile index 31289b31f5b2..ca355c7ab560 100644 --- a/Makefile +++ b/Makefile @@ -79,6 +79,12 @@ TESTS := $(filter-out test/sql/enumtap.sql sql/valueset.sql,$(TESTS)) REGRESS := $(filter-out enumtap valueset,$(REGRESS)) endif +# Partition tests tests not supported by 9.x and earlier. +ifeq ($(shell echo $(VERSION) | grep -qE "[89][.]" && echo yes || echo no),yes) +TESTS := $(filter-out test/sql/partitions.sql,$(TESTS)) +REGRESS := $(filter-out partitions,$(REGRESS)) +endif + # Determine the OS. Borrowed from Perl's Configure. OSNAME := $(shell $(SHELL) ./getos.sh) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index b6bbf7d0304c..6d9342f40320 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -9943,7 +9943,7 @@ 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' + 'Table ' || quote_ident($1) || '.' || quote_ident($2) || ' should be a partition of ' || quote_ident($3) || '.' || quote_ident($4) ); $$ LANGUAGE sql; @@ -9959,6 +9959,6 @@ 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) + 'Table ' || quote_ident($1) || ' should be a partition of ' || quote_ident($2) ); $$ LANGUAGE sql; diff --git a/test/expected/partitions.out b/test/expected/partitions.out new file mode 100644 index 000000000000..10a423106179 --- /dev/null +++ b/test/expected/partitions.out @@ -0,0 +1,68 @@ +\unset ECHO +1..66 +ok 1 - is_partition_of( csch, ctab, psch, ptab, desc ) should pass +ok 2 - is_partition_of( csch, ctab, psch, ptab, desc ) should have the proper description +ok 3 - is_partition_of( csch, ctab, psch, ptab, desc ) should have the proper diagnostics +ok 4 - is_partition_of( csch, ctab, psch, ptab ) should pass +ok 5 - is_partition_of( csch, ctab, psch, ptab ) should have the proper description +ok 6 - is_partition_of( csch, ctab, psch, ptab ) should have the proper diagnostics +ok 7 - is_partition_of( ctab, ptab, desc ) should pass +ok 8 - is_partition_of( ctab, ptab, desc ) should have the proper description +ok 9 - is_partition_of( ctab, ptab, desc ) should have the proper diagnostics +ok 10 - is_partition_of( ctab, ptab ) should pass +ok 11 - is_partition_of( ctab, ptab ) should have the proper description +ok 12 - is_partition_of( ctab, ptab ) should have the proper diagnostics +ok 13 - is_partition_of( csch, non-part ctab, psch, non-part ptab, desc ) should fail +ok 14 - is_partition_of( csch, non-part ctab, psch, non-part ptab, desc ) should have the proper description +ok 15 - is_partition_of( csch, non-part ctab, psch, non-part ptab, desc ) should have the proper diagnostics +ok 16 - is_partition_of( non-part ctab, non-part ptab, desc ) should fail +ok 17 - is_partition_of( non-part ctab, non-part ptab, desc ) should have the proper description +ok 18 - is_partition_of( non-part ctab, non-part ptab, desc ) should have the proper diagnostics +ok 19 - is_partition_of( csch, non-part ctab, psch, ptab, desc ) should fail +ok 20 - is_partition_of( csch, non-part ctab, psch, ptab, desc ) should have the proper description +ok 21 - is_partition_of( csch, non-part ctab, psch, ptab, desc ) should have the proper diagnostics +ok 22 - is_partition_of( non-part ctab, ptab, desc ) should fail +ok 23 - is_partition_of( non-part ctab, ptab, desc ) should have the proper description +ok 24 - is_partition_of( non-part ctab, ptab, desc ) should have the proper diagnostics +ok 25 - is_partition_of( csch, ctab, psch, non-part ptab, desc ) should fail +ok 26 - is_partition_of( csch, ctab, psch, non-part ptab, desc ) should have the proper description +ok 27 - is_partition_of( csch, ctab, psch, non-part ptab, desc ) should have the proper diagnostics +ok 28 - is_partition_of( ctab, non-part ptab, desc ) should fail +ok 29 - is_partition_of( ctab, non-part ptab, desc ) should have the proper description +ok 30 - is_partition_of( ctab, non-part ptab, desc ) should have the proper diagnostics +ok 31 - is_partition_of( priv csch, ctab, priv psch, ptab, desc ) should pass +ok 32 - is_partition_of( priv csch, ctab, priv psch, ptab, desc ) should have the proper description +ok 33 - is_partition_of( priv csch, ctab, priv psch, ptab, desc ) should have the proper diagnostics +ok 34 - is_partition_of( priv ctab, priv ptab, desc ) should fail +ok 35 - is_partition_of( priv ctab, priv ptab, desc ) should have the proper description +ok 36 - is_partition_of( priv ctab, priv ptab, desc ) should have the proper diagnostics +ok 37 - is_partition_of( priv csch, ctab, psch, ptab, desc ) should pass +ok 38 - is_partition_of( priv csch, ctab, psch, ptab, desc ) should have the proper description +ok 39 - is_partition_of( priv csch, ctab, psch, ptab, desc ) should have the proper diagnostics +ok 40 - is_partition_of( priv ctab, ptab, desc ) should fail +ok 41 - is_partition_of( priv ctab, ptab, desc ) should have the proper description +ok 42 - is_partition_of( priv ctab, ptab, desc ) should have the proper diagnostics +ok 43 - is_partition_of( csch, ctab, priv psch, ptab, desc ) should pass +ok 44 - is_partition_of( csch, ctab, priv psch, ptab, desc ) should have the proper description +ok 45 - is_partition_of( csch, ctab, priv psch, ptab, desc ) should have the proper diagnostics +ok 46 - is_partition_of( ctab, priv ptab, desc ) should fail +ok 47 - is_partition_of( ctab, priv ptab, desc ) should have the proper description +ok 48 - is_partition_of( ctab, priv ptab, desc ) should have the proper diagnostics +ok 49 - is_partition_of( csch, non-ctab, psch, non-ptab, desc ) should fail +ok 50 - is_partition_of( csch, non-ctab, psch, non-ptab, desc ) should have the proper description +ok 51 - is_partition_of( csch, non-ctab, psch, non-ptab, desc ) should have the proper diagnostics +ok 52 - is_partition_of( non-ctab, non-ptab, desc ) should fail +ok 53 - is_partition_of( non-ctab, non-ptab, desc ) should have the proper description +ok 54 - is_partition_of( non-ctab, non-ptab, desc ) should have the proper diagnostics +ok 55 - is_partition_of( csch, ctab, psch, non-ptab, desc ) should fail +ok 56 - is_partition_of( csch, ctab, psch, non-ptab, desc ) should have the proper description +ok 57 - is_partition_of( csch, ctab, psch, non-ptab, desc ) should have the proper diagnostics +ok 58 - is_partition_of( ctab, non-ptab, desc ) should fail +ok 59 - is_partition_of( ctab, non-ptab, desc ) should have the proper description +ok 60 - is_partition_of( ctab, non-ptab, desc ) should have the proper diagnostics +ok 61 - is_partition_of( csch, non-ctab, psch, ptab, desc ) should fail +ok 62 - is_partition_of( csch, non-ctab, psch, ptab, desc ) should have the proper description +ok 63 - is_partition_of( csch, non-ctab, psch, ptab, desc ) should have the proper diagnostics +ok 64 - is_partition_of( non-ctab, ptab, desc ) should fail +ok 65 - is_partition_of( non-ctab, ptab, desc ) should have the proper description +ok 66 - is_partition_of( non-ctab, ptab, desc ) should have the proper diagnostics diff --git a/test/sql/partitions.sql b/test/sql/partitions.sql new file mode 100644 index 000000000000..2cd47b83efcf --- /dev/null +++ b/test/sql/partitions.sql @@ -0,0 +1,225 @@ +\unset ECHO +\i test/setup.sql + +SELECT plan(66); +--SELECT * FROM no_plan(); + +-- This will be rolled back. :-) +SET client_min_messages = warning; + +-- Create inherited tables (not partitions). +CREATE TABLE public.parent(id INT PRIMARY KEY); +CREATE TABLE public.child(id INT PRIMARY KEY) INHERITS (public.parent); + +-- Create a partitioned table with two partitions. +CREATE TABLE public.parted(id INT NOT NULL) PARTITION BY RANGE (id); +CREATE TABLE public.part1 PARTITION OF public.parted FOR VALUES FROM (1) TO (10); +CREATE TABLE public.part2 PARTITION OF public.parted FOR VALUES FROM (11) TO (20); + +-- Create partitions outside of search path. +CREATE SCHEMA hide; +CREATE TABLE hide.hidden_parted(id INT NOT NULL) PARTITION BY RANGE (id); +CREATE TABLE hide.hidden_part1 PARTITION OF hide.hidden_parted FOR VALUES FROM (1) TO (10); +CREATE TABLE hide.hidden_part2 PARTITION OF hide.hidden_parted FOR VALUES FROM (11) TO (20); + +-- Put a partition for the public table in the hidden schema. +CREATE TABLE hide.part3 PARTITION OF public.parted FOR VALUES FROM (21) TO (30); + +-- Put a partition for the hidden table in the public schema. +CREATE TABLE public.not_hidden_part3 PARTITION OF hide.hidden_parted + FOR VALUES FROM (21) TO (30); + +RESET client_min_messages; + +/****************************************************************************/ +-- Test is_partition_of(). +SELECT * FROM check_test( + is_partition_of( 'public', 'part1', 'public', 'parted', 'whatevs' ), + true, + 'is_partition_of( csch, ctab, psch, ptab, desc )', + 'whatevs', + '' +); + +SELECT * FROM check_test( + is_partition_of( 'public', 'part1', 'public', 'parted' ), + true, + 'is_partition_of( csch, ctab, psch, ptab )', + 'Table public.part1 should be a partition of public.parted', + '' +); + +SELECT * FROM check_test( + is_partition_of( 'part1', 'parted', 'whatevs' ), + true, + 'is_partition_of( ctab, ptab, desc )', + 'whatevs', + '' +); + +SELECT * FROM check_test( + is_partition_of( 'part1', 'parted' ), + true, + 'is_partition_of( ctab, ptab )', + 'Table part1 should be a partition of parted', + '' +); + +-- is_partition_of() should fail for inherited but not partitioned tables. +SELECT * FROM check_test( + is_partition_of( 'public', 'child', 'public', 'parent', 'whatevs' ), + false, + 'is_partition_of( csch, non-part ctab, psch, non-part ptab, desc )', + 'whatevs', + '' +); + +SELECT * FROM check_test( + is_partition_of( 'child', 'parent', 'whatevs' ), + false, + 'is_partition_of( non-part ctab, non-part ptab, desc )', + 'whatevs', + '' +); + +-- is_partition_of() should fail for parted table and non-part child. +SELECT * FROM check_test( + is_partition_of( 'public', 'child', 'public', 'parted', 'whatevs' ), + false, + 'is_partition_of( csch, non-part ctab, psch, ptab, desc )', + 'whatevs', + '' +); + +SELECT * FROM check_test( + is_partition_of( 'child', 'parted', 'whatevs' ), + false, + 'is_partition_of( non-part ctab, ptab, desc )', + 'whatevs', + '' +); + +-- is_partition_of() should fail for partition child but wrong parent. +SELECT * FROM check_test( + is_partition_of( 'public', 'part1', 'public', 'parent', 'whatevs' ), + false, + 'is_partition_of( csch, ctab, psch, non-part ptab, desc )', + 'whatevs', + '' +); + +SELECT * FROM check_test( + is_partition_of( 'part1', 'parent', 'whatevs' ), + false, + 'is_partition_of( ctab, non-part ptab, desc )', + 'whatevs', + '' +); + +-- Should find tables outside search path for explicit schema. +SELECT * FROM check_test( + is_partition_of( 'hide', 'hidden_part1', 'hide', 'hidden_parted', 'whatevs' ), + true, + 'is_partition_of( priv csch, ctab, priv psch, ptab, desc )', + 'whatevs', + '' +); + +-- But not when the schema is not specified. +SELECT * FROM check_test( + is_partition_of( 'hidden_part1', 'hidden_parted', 'whatevs' ), + false, + 'is_partition_of( priv ctab, priv ptab, desc )', + 'whatevs', + '' +); + +-- Should find explicit hidden table for public partition. +SELECT * FROM check_test( + is_partition_of( 'hide', 'part3', 'public', 'parted', 'whatevs' ), + true, + 'is_partition_of( priv csch, ctab, psch, ptab, desc )', + 'whatevs', + '' +); + +-- But still not when schemas not specified. +SELECT * FROM check_test( + is_partition_of( 'part3', 'hidden', 'whatevs' ), + false, + 'is_partition_of( priv ctab, ptab, desc )', + 'whatevs', + '' +); + +-- Should find public partition for hidden parent. +SELECT * FROM check_test( + is_partition_of( 'public', 'not_hidden_part3', 'hide', 'hidden_parted', 'whatevs' ), + true, + 'is_partition_of( csch, ctab, priv psch, ptab, desc )', + 'whatevs', + '' +); + +-- But not if no schemas are specified. +SELECT * FROM check_test( + is_partition_of( 'not_hidden_part3', 'hidden_parted', 'whatevs' ), + false, + 'is_partition_of( ctab, priv ptab, desc )', + 'whatevs', + '' +); + +-- And of course, it should not work for nonexistent partitions. +SELECT * FROM check_test( + is_partition_of( 'public', 'nonesuch', 'public', 'nothing', 'whatevs' ), + false, + 'is_partition_of( csch, non-ctab, psch, non-ptab, desc )', + 'whatevs', + '' +); + +SELECT * FROM check_test( + is_partition_of( 'nonesuch', 'nothing', 'whatevs' ), + false, + 'is_partition_of( non-ctab, non-ptab, desc )', + 'whatevs', + '' +); + +SELECT * FROM check_test( + is_partition_of( 'public', 'part1', 'public', 'nothing', 'whatevs' ), + false, + 'is_partition_of( csch, ctab, psch, non-ptab, desc )', + 'whatevs', + '' +); + +SELECT * FROM check_test( + is_partition_of( 'nonesuch', 'part1', 'whatevs' ), + false, + 'is_partition_of( ctab, non-ptab, desc )', + 'whatevs', + '' +); + +SELECT * FROM check_test( + is_partition_of( 'public', 'nonesuch', 'public', 'parted', 'whatevs' ), + false, + 'is_partition_of( csch, non-ctab, psch, ptab, desc )', + 'whatevs', + '' +); + +SELECT * FROM check_test( + is_partition_of( 'nonesuch', 'parted', 'whatevs' ), + false, + 'is_partition_of( non-ctab, ptab, desc )', + 'whatevs', + '' +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From fa2cfda84ad56ddc4dd98015962a8a1c11590205 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 10:59:50 -0500 Subject: [PATCH 0959/1195] Document is_partition_of. --- doc/pgtap.mmd | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 0fe8659ff7f2..26c1d023ac13 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4716,6 +4716,37 @@ exist. This function is the inverse of `is_partitioned()`. The test passes if the specified table is *not* partitioned, or if it does not exist. +### `is_partition_of()` ### + + SELECT is_parent( :child_schema, :child, :parent_schema, :parent_table, :description ); + SELECT is_parent( :child_schema, :child, :parent_schema, :parent_table ); + SELECT is_parent( :child, :parent_table, :description ); + SELECT is_parent( :child, :parent_table ); + +**Parameters** + +`:child_schema` +: Schema in which to find the partition table. + +`:child` +: Name of a partition table. + +`:parent_schema` +: Schema in which to find the partitioned table. + +`:parent_table` +: Name of a partitioned table. + +`:description` +: A short description of the test. + +Tests that one table is a partition of another table. The partition (or child) +table is specified first, the partitioned (or parent) table second. Without the +schema parameters, both tables must be visible in the search path. If the +test description is omitted, it will be set to "Table `:child_table` should be +a partition of `:parent_table`". Note that this test will fail if either table +does not exist. + ### `is_clustered()` ### SELECT is_clustered( :schema, :table, :index, :description ); From b5fa9b685b43b51bb366c358541f469300d1539e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 11:24:21 -0500 Subject: [PATCH 0960/1195] Sketch out partitions_are(). --- sql/pgtap.sql.in | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 6d9342f40320..3c07d3b6a878 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -9962,3 +9962,64 @@ RETURNS TEXT AS $$ '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 +$$ 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 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[], TEXT ) +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; From 9c72811490c9b159b7200af1c1b9112ed2114146 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 11:26:20 -0500 Subject: [PATCH 0961/1195] Tweak Changes. --- Changes | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Changes b/Changes index 09933d4b43db..a5b7cd5502ba 100644 --- a/Changes +++ b/Changes @@ -22,10 +22,11 @@ Revision history for pgTAP + `hasnt_table()` + `tables_are()` + `table_owner_is()` -* Added partition-testing assert functions: +* Added partition-testing assertion functions: + `is_partitioned()` + `isnt_partitioned()` + `is_partition_of()` + + `partitions_are()` 0.97.0 2016-11-28T22:18:29Z --------------------------- @@ -37,12 +38,12 @@ Revision history for pgTAP expressions. Thanks to Rodolphe Quiédeville (PR #103). * Fixed `pg_version_num()` to work with the new version format coming in PostgreSQL 10. -* Fix lives_ok() and throws_ok() to also trap ASSERT_FAILURE (PR #119). -* Add negation functions isnt_aggregate() and isnt_definer(), which return the - opposite of is_aggregate() and is_definer(). Thanks to Rodolphe Quiédeville - (PR #106). -* Add has_extension() and hasnt_extension(). Thanks to Rodolphe Quiédeville (PR - #101). +* Fix `lives_ok()` and `throws_ok()` to also trap `ASSERT_FAILURE` (PR #119). +* Add negation functions `isnt_aggregate()` and `isnt_definer()`, which return + the opposite of `is_aggregate()` and `is_definer()`. Thanks to Rodolphe + Quiédeville (PR #106). +* Add `has_extension()` and `hasnt_extension()`. Thanks to Rodolphe Quiédeville + (PR #101). 0.96.0 2016-05-16T20:53:57Z --------------------------- From fdf582b48c010fc0c596993517faa1d8c382066b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 11:42:38 -0500 Subject: [PATCH 0962/1195] Test partitions_are(). --- sql/pgtap.sql.in | 4 +- test/expected/partitions.out | 38 ++++++++++- test/sql/partitions.sql | 126 ++++++++++++++++++++++++++++++++++- 3 files changed, 165 insertions(+), 3 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 3c07d3b6a878..fce5da9d8c0f 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -9972,6 +9972,7 @@ RETURNS SETOF NAME AS $$ 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) @@ -9981,6 +9982,7 @@ RETURNS SETOF NAME AS $$ 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; @@ -9996,7 +9998,7 @@ RETURNS TEXT AS $$ $$ LANGUAGE SQL; -- partitions_are( schema, table, partitions ) -CREATE OR REPLACE FUNCTION partitions_are( NAME, NAME, NAME[], TEXT ) +CREATE OR REPLACE FUNCTION partitions_are( NAME, NAME, NAME[] ) RETURNS TEXT AS $$ SELECT partitions_are( $1, $2, $3, diff --git a/test/expected/partitions.out b/test/expected/partitions.out index 10a423106179..1aab350a3dd5 100644 --- a/test/expected/partitions.out +++ b/test/expected/partitions.out @@ -1,5 +1,5 @@ \unset ECHO -1..66 +1..102 ok 1 - is_partition_of( csch, ctab, psch, ptab, desc ) should pass ok 2 - is_partition_of( csch, ctab, psch, ptab, desc ) should have the proper description ok 3 - is_partition_of( csch, ctab, psch, ptab, desc ) should have the proper diagnostics @@ -66,3 +66,39 @@ ok 63 - is_partition_of( csch, non-ctab, psch, ptab, desc ) should have the prop ok 64 - is_partition_of( non-ctab, ptab, desc ) should fail ok 65 - is_partition_of( non-ctab, ptab, desc ) should have the proper description ok 66 - is_partition_of( non-ctab, ptab, desc ) should have the proper diagnostics +ok 67 - partitions_are( sch, tab, parts, desc ) should pass +ok 68 - partitions_are( sch, tab, parts, desc ) should have the proper description +ok 69 - partitions_are( sch, tab, parts, desc ) should have the proper diagnostics +ok 70 - partitions_are( sch, tab, parts ) should pass +ok 71 - partitions_are( sch, tab, parts ) should have the proper description +ok 72 - partitions_are( sch, tab, parts ) should have the proper diagnostics +ok 73 - partitions_are( tab, parts, desc ) should pass +ok 74 - partitions_are( tab, parts, desc ) should have the proper description +ok 75 - partitions_are( tab, parts, desc ) should have the proper diagnostics +ok 76 - partitions_are( tab, parts ) should pass +ok 77 - partitions_are( tab, parts ) should have the proper description +ok 78 - partitions_are( tab, parts ) should have the proper diagnostics +ok 79 - partitions_are( sch, tab, bad parts, desc ) should fail +ok 80 - partitions_are( sch, tab, bad parts, desc ) should have the proper description +ok 81 - partitions_are( sch, tab, bad parts, desc ) should have the proper diagnostics +ok 82 - partitions_are( tab, bad parts, desc ) should fail +ok 83 - partitions_are( tab, bad parts, desc ) should have the proper description +ok 84 - partitions_are( tab, bad parts, desc ) should have the proper diagnostics +ok 85 - partitions_are( hidden sch, tab, parts, desc ) should pass +ok 86 - partitions_are( hidden sch, tab, parts, desc ) should have the proper description +ok 87 - partitions_are( hidden sch, tab, parts, desc ) should have the proper diagnostics +ok 88 - partitions_are( hidden tab, parts, desc ) should fail +ok 89 - partitions_are( hidden tab, parts, desc ) should have the proper description +ok 90 - partitions_are( hidden tab, parts, desc ) should have the proper diagnostics +ok 91 - partitions_are( sch, non-parted tab, inherited tab, desc ) should fail +ok 92 - partitions_are( sch, non-parted tab, inherited tab, desc ) should have the proper description +ok 93 - partitions_are( sch, non-parted tab, inherited tab, desc ) should have the proper diagnostics +ok 94 - partitions_are( non-parted tab, inherited tab, desc ) should fail +ok 95 - partitions_are( non-parted tab, inherited tab, desc ) should have the proper description +ok 96 - partitions_are( non-parted tab, inherited tab, desc ) should have the proper diagnostics +ok 97 - partitions_are( sch, non-existent tab, parts, desc ) should fail +ok 98 - partitions_are( sch, non-existent tab, parts, desc ) should have the proper description +ok 99 - partitions_are( sch, non-existent tab, parts, desc ) should have the proper diagnostics +ok 100 - partitions_are( non-existent tab, parts, desc ) should fail +ok 101 - partitions_are( non-existent tab, parts, desc ) should have the proper description +ok 102 - partitions_are( non-existent tab, parts, desc ) should have the proper diagnostics diff --git a/test/sql/partitions.sql b/test/sql/partitions.sql index 2cd47b83efcf..237733efae81 100644 --- a/test/sql/partitions.sql +++ b/test/sql/partitions.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(66); +SELECT plan(102); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -219,6 +219,130 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test partitions_are(). +SELECT * FROM check_test( + partitions_are( 'public', 'parted', '{part1,part2,hide.part3}', 'hi' ), + true, + 'partitions_are( sch, tab, parts, desc )', + 'hi', + '' +); + +SELECT * FROM check_test( + partitions_are( 'public', 'parted', '{part1,part2,hide.part3}'::name[] ), + true, + 'partitions_are( sch, tab, parts )', + 'Table public.parted should have the correct partitions', + '' +); + +SELECT * FROM check_test( + partitions_are( 'parted', '{part1,part2,hide.part3}'::name[], 'hi' ), + true, + 'partitions_are( tab, parts, desc )', + 'hi', + '' +); + +SELECT * FROM check_test( + partitions_are( 'parted', '{part1,part2,hide.part3}' ), + true, + 'partitions_are( tab, parts )', + 'Table parted should have the correct partitions', + '' +); + +-- Test diagnostics. +SELECT * FROM check_test( + partitions_are( 'public', 'parted', '{part1,part2of2,hide.part3}', 'hi' ), + false, + 'partitions_are( sch, tab, bad parts, desc )', + 'hi', + ' Extra partitions: + part2 + Missing partitions: + part2of2' +); + +SELECT * FROM check_test( + partitions_are( 'parted', '{part1,part2of2,hide.part3}'::name[], 'hi' ), + false, + 'partitions_are( tab, bad parts, desc )', + 'hi', + ' Extra partitions: + part2 + Missing partitions: + part2of2' +); + +-- Test with the hidden schema. +SELECT * FROM check_test( + partitions_are( + 'hide', 'hidden_parted', + '{hide.hidden_part1,hide.hidden_part2,not_hidden_part3}', + 'hi' + ), + true, + 'partitions_are( hidden sch, tab, parts, desc )', + 'hi', + '' +); + +-- Should fail for partitioned table outside search path. +SELECT * FROM check_test( + partitions_are( + 'hidden_parted', + '{hide.hidden_part1,hide.hidden_part2,not_hidden_part3}'::name[], + 'hi' + ), + false, + 'partitions_are( hidden tab, parts, desc )', + 'hi', + ' Missing partitions: + not_hidden_part3 + "hide.hidden_part2" + "hide.hidden_part1"' +); + +-- Should not work for unpartitioned but inherited table +SELECT * FROM check_test( + partitions_are( 'public', 'parent', '{child}', 'hi' ), + false, + 'partitions_are( sch, non-parted tab, inherited tab, desc )', + 'hi', + ' Missing partitions: + child' +); + +SELECT * FROM check_test( + partitions_are( 'parent', '{child}'::name[], 'hi' ), + false, + 'partitions_are( non-parted tab, inherited tab, desc )', + 'hi', + ' Missing partitions: + child' +); + +-- Should not work for non-existent table. +SELECT * FROM check_test( + partitions_are( 'public', 'nonesuch', '{part1}', 'hi' ), + false, + 'partitions_are( sch, non-existent tab, parts, desc )', + 'hi', + ' Missing partitions: + part1' +); + +SELECT * FROM check_test( + partitions_are( 'nonesuch', '{part1}'::name[], 'hi' ), + false, + 'partitions_are( non-existent tab, parts, desc )', + 'hi', + ' Missing partitions: + part1' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 36cef747ed7986cd0eb86bfc411ce2e004c18117 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 11:53:03 -0500 Subject: [PATCH 0963/1195] Document partitions_are(). --- doc/pgtap.mmd | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 26c1d023ac13..13d04d42b729 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1838,6 +1838,60 @@ missing tables, like so: # users # widgets +### `partitions_are()` ### + + SELECT partitions_are( :schema, :table, :partitions :description ); + SELECT partitions_are( :schema, :table, :partitions ); + SELECT partitions_are( :table, :partitions :description ); + SELECT partitions_are( :table, :partitions ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the partitioned table. + +`:table` +: Name of a partitioned table. + +`:partitions` +: An array of partition table names. + +`:description` +: A short description of the test. + +This function tests that the named table has all of the specified partitions, +and those are its only partitions. Partition names should be specified relative +to the search path; those in the search path should not be schema-qualified, +while those outside the search path should be schema-qualified. Partition names +and schemas should be appropriately quoted as identifiers where appropriate. + +If the `:schema` argument is omitted, the partitioned table must be visible the +search path. If the description is omitted, a generally useful default +description will be generated. Example: + + SELECT partitions_are( + 'myschema', 'mylog', + ARRAY[ 'log1', 'log2', 'log3', 'log4' ] + ); + +Example for partitions outside the search path and requiring identifier-quoting: + + SELECT partitions_are( + 'myschema', 'MyLog', + ARRAY[ 'hidden."Log 1"', 'hidden."Log 2"' ] + ); + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing partitions, like so: + + # Failed test 12: "Table myschema should have the correct partitions" + # Extra partitions: + # part4 + # hidden."Part 5" + # Missing partitions: + # part5 + # part6 + ### `foreign_tables_are()` ### SELECT foreign_tables_are( :schema, :foreign_tables, :description ); From 93c872ad63ff576559c97dc3b1efb1328eb49cc6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 11:54:30 -0500 Subject: [PATCH 0964/1195] Note use of regclass. --- doc/pgtap.mmd | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 13d04d42b729..3d5344ebb4b0 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1860,10 +1860,11 @@ missing tables, like so: : A short description of the test. This function tests that the named table has all of the specified partitions, -and those are its only partitions. Partition names should be specified relative -to the search path; those in the search path should not be schema-qualified, -while those outside the search path should be schema-qualified. Partition names -and schemas should be appropriately quoted as identifiers where appropriate. +and those are its only partitions. The test casts partition names to the +`regclass` type; therefore, partition names should be specified relative to the +search path. Those in the search path should not be schema-qualified, while +those outside the search path should be schema-qualified. Partition names and +schemas should be appropriately quoted as identifiers where appropriate. If the `:schema` argument is omitted, the partitioned table must be visible the search path. If the description is omitted, a generally useful default From e37367b695972eab4e3d1b5c42838d52db0c8e23 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 11:55:55 -0500 Subject: [PATCH 0965/1195] Add partitions_are() to upgrade script. --- sql/pgtap--0.97.0--0.98.0.sql | 130 ++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/sql/pgtap--0.97.0--0.98.0.sql b/sql/pgtap--0.97.0--0.98.0.sql index f2cbc93e845e..a921eaf62271 100644 --- a/sql/pgtap--0.97.0--0.98.0.sql +++ b/sql/pgtap--0.97.0--0.98.0.sql @@ -292,3 +292,133 @@ RETURNS TEXT AS $$ '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; From 8515fd52d18af51feefb3344424726c4e35e829e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 12:55:07 -0500 Subject: [PATCH 0966/1195] Restore 9.6 compat. Add a patch to remove most of the partition-testing functions on 9.6 and ealier. --- Makefile | 3 + compat/install-9.6.patch | 136 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 compat/install-9.6.patch diff --git a/Makefile b/Makefile index ca355c7ab560..4de13b92f7b7 100644 --- a/Makefile +++ b/Makefile @@ -112,6 +112,9 @@ endif sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql cp $< $@ +ifeq ($(shell echo $(VERSION) | grep -qE "[98][.]" && echo yes || echo no),yes) + patch -p0 < compat/install-9.6.patch +endif ifeq ($(shell echo $(VERSION) | grep -qE "9[.][01234]|8[.][1234]" && echo yes || echo no),yes) patch -p0 < compat/install-9.4.patch endif diff --git a/compat/install-9.6.patch b/compat/install-9.6.patch new file mode 100644 index 000000000000..c9e7dacd25e1 --- /dev/null +++ b/compat/install-9.6.patch @@ -0,0 +1,136 @@ +--- sql/pgtap.sql ++++ sql/pgtap.sql +@@ -9895,133 +9895,3 @@ + '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; From 47d71796112e020d858cbda60cbe12444aeba1ca Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 13:08:24 -0500 Subject: [PATCH 0967/1195] Add patch for 0.98 upgrade. --- .gitignore | 1 + Makefile | 10 +- compat/9.6/pgtap--0.97.0--0.98.0.patch | 136 ++++++++++++++++++ ....98.0.sql => pgtap--0.97.0--0.98.0.sql.in} | 0 4 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 compat/9.6/pgtap--0.97.0--0.98.0.patch rename sql/{pgtap--0.97.0--0.98.0.sql => pgtap--0.97.0--0.98.0.sql.in} (100%) diff --git a/.gitignore b/.gitignore index c723a9894a1b..de59f465cf28 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ bbin /sql/pgtap-schema--* /sql/pgtap--0.95.0--0.96.0.sql /sql/pgtap--0.96.0--0.97.0.sql +/sql/pgtap--0.97.0--0.98.0.sql *.sql.orig diff --git a/Makefile b/Makefile index 4de13b92f7b7..342b9709a659 100644 --- a/Makefile +++ b/Makefile @@ -142,7 +142,14 @@ endif sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap.sql > sql/pgtap.tmp mv sql/pgtap.tmp sql/pgtap.sql -# Ugly hack for now... +# Ugly hacks for now... +EXTRA_CLEAN += sql/pgtap--0.97.0--0.98.0.sql +sql/pgtap--0.97.0--0.98.0.sql: sql/pgtap--0.97.0--0.98.0.sql.in + cp $< $@ +ifeq ($(shell echo $(VERSION) | grep -qE "[89][.]" && echo yes || echo no),yes) + patch -p0 < compat/9.6/pgtap--0.97.0--0.98.0.patch +endif + EXTRA_CLEAN += sql/pgtap--0.96.0--0.97.0.sql sql/pgtap--0.96.0--0.97.0.sql: sql/pgtap--0.96.0--0.97.0.sql.in cp $< $@ @@ -152,6 +159,7 @@ endif ifeq ($(shell echo $(VERSION) | grep -qE "9[.]0|8[.][1234]" && echo yes || echo no),yes) patch -p0 < compat/9.0/pgtap--0.96.0--0.97.0.patch endif + EXTRA_CLEAN += sql/pgtap--0.95.0--0.96.0.sql sql/pgtap--0.95.0--0.96.0.sql: sql/pgtap--0.95.0--0.96.0.sql.in cp $< $@ diff --git a/compat/9.6/pgtap--0.97.0--0.98.0.patch b/compat/9.6/pgtap--0.97.0--0.98.0.patch new file mode 100644 index 000000000000..8494c8a0062a --- /dev/null +++ b/compat/9.6/pgtap--0.97.0--0.98.0.patch @@ -0,0 +1,136 @@ +--- sql/pgtap--0.97.0--0.98.0.sql ++++ sql/pgtap--0.97.0--0.98.0.sql +@@ -292,133 +292,3 @@ + '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; diff --git a/sql/pgtap--0.97.0--0.98.0.sql b/sql/pgtap--0.97.0--0.98.0.sql.in similarity index 100% rename from sql/pgtap--0.97.0--0.98.0.sql rename to sql/pgtap--0.97.0--0.98.0.sql.in From 60755c6db9735bf537abf846b4f11001475931e6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 13:17:30 -0500 Subject: [PATCH 0968/1195] Add the materialized view functions to the v0.95.0 upgrade script. --- Changes | 2 + sql/pgtap--0.94.0--0.95.0.sql | 118 ++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/Changes b/Changes index a5b7cd5502ba..4cad25baa5d7 100644 --- a/Changes +++ b/Changes @@ -27,6 +27,8 @@ Revision history for pgTAP + `isnt_partitioned()` + `is_partition_of()` + `partitions_are()` +* Added the materialized view-testing assertion functions to the v0.95.0 upgrade + script; they were inadvertently omitted from the v0.95.0 release. 0.97.0 2016-11-28T22:18:29Z --------------------------- diff --git a/sql/pgtap--0.94.0--0.95.0.sql b/sql/pgtap--0.94.0--0.95.0.sql index 8423315eed05..90ab56ad6e9b 100644 --- a/sql/pgtap--0.94.0--0.95.0.sql +++ b/sql/pgtap--0.94.0--0.95.0.sql @@ -133,6 +133,124 @@ RETURNS TEXT AS $$ SELECT _are( 'foreign tables', _extras('f', $1, $2), _missing('f', $1, $2), $3); $$ 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( tables, description ) CREATE OR REPLACE FUNCTION foreign_tables_are ( NAME[], TEXT ) RETURNS TEXT AS $$ From 9cd80f3987f33f1e10019a224ed5de1d547b33d3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 14:28:45 -0500 Subject: [PATCH 0969/1195] Patch for core and schema only once. --- .gitignore | 1 + Makefile | 34 +++++++++++++++++----------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index de59f465cf28..1c64cb6dd881 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ bbin /sql/pgtap--0.95.0--0.96.0.sql /sql/pgtap--0.96.0--0.97.0.sql /sql/pgtap--0.97.0--0.98.0.sql +/sql/pgtap-static.sql *.sql.orig diff --git a/Makefile b/Makefile index 342b9709a659..91318120ddb5 100644 --- a/Makefile +++ b/Makefile @@ -170,24 +170,24 @@ endif sql/uninstall_pgtap.sql: sql/pgtap.sql test/setup.sql grep '^CREATE ' sql/pgtap.sql | $(PERL) -e 'for (reverse ) { chomp; s/CREATE (OR REPLACE)?/DROP/; print "$$_;\n" }' > sql/uninstall_pgtap.sql -sql/pgtap-core.sql: sql/pgtap.sql.in +sql/pgtap-static.sql: sql/pgtap.sql.in cp $< $@ - sed -e 's,sql/pgtap,sql/pgtap-core,g' compat/install-8.4.patch | patch -p0 - sed -e 's,sql/pgtap,sql/pgtap-core,g' compat/install-8.3.patch | patch -p0 - sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap-core.sql > sql/pgtap-core.tmp - $(PERL) compat/gencore 0 sql/pgtap-core.tmp > sql/pgtap-core.sql - rm sql/pgtap-core.tmp - -sql/pgtap-schema.sql: sql/pgtap.sql.in - cp $< $@ - sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-9.2.patch | patch -p0 - sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-9.1.patch | patch -p0 - sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-9.0.patch | patch -p0 - sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-8.4.patch | patch -p0 - sed -e 's,sql/pgtap,sql/pgtap-schema,g' compat/install-8.3.patch | patch -p0 - sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap-schema.sql > sql/pgtap-schema.tmp - $(PERL) compat/gencore 1 sql/pgtap-schema.tmp > sql/pgtap-schema.sql - rm sql/pgtap-schema.tmp + sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-9.6.patch | patch -p0 + sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-9.4.patch | patch -p0 + sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-9.2.patch | patch -p0 + sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-9.1.patch | patch -p0 + sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-9.0.patch | patch -p0 + sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-8.4.patch | patch -p0 + sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-8.3.patch | patch -p0 + sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' $@ > sql/pgtap-static.tmp + mv sql/pgtap-static.tmp $@ +EXTRA_CLEAN += sql/pgtap-static.sql + +sql/pgtap-core.sql: sql/pgtap-static.sql + $(PERL) compat/gencore 0 sql/pgtap-static.sql > sql/pgtap-core.sql + +sql/pgtap-schema.sql: sql/pgtap-static.sql + $(PERL) compat/gencore 1 sql/pgtap-static.sql > sql/pgtap-schema.sql # Make sure that we build the regression tests. installcheck: test/setup.sql From 31cac42d9cf0bbcf4d62295b64fc42d205964d7e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 14:53:47 -0500 Subject: [PATCH 0970/1195] Set DATA before loading PGXS. Otherwise it gets ignored on 9.4 and earlier. Resolves #143. --- Makefile | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/Makefile b/Makefile index 91318120ddb5..ecba1a438ce6 100644 --- a/Makefile +++ b/Makefile @@ -15,9 +15,6 @@ REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --inputdir=test --load-language=plpgsql PG_CONFIG ?= pg_config -# sort is necessary to remove dupes so install won't complain -DATA = $(BASE_FILES) # Set to something to make PGXS happy - ifdef NO_PGXS top_builddir = ../.. PG_CONFIG := $(top_builddir)/src/bin/pg_config/pg_config @@ -39,6 +36,30 @@ ifeq ($(shell echo $(VERSION) | grep -qE "8[.][123]" && echo yes || echo no),yes MODULES = src/pgtap endif +# Make sure we build these. +EXTRA_CLEAN += $(_IN_PATCHED) +all: $(_IN_PATCHED) sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql + +# Add extension build targets on 9.1 and up. +ifeq ($(shell echo $(VERSION) | grep -qE "8[.]|9[.]0" && echo no || echo yes),yes) +all: sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql + +sql/$(MAINEXT)--$(EXTVERSION).sql: sql/$(MAINEXT).sql + cp $< $@ + +sql/$(MAINEXT)-core--$(EXTVERSION).sql: sql/$(MAINEXT)-core.sql + cp $< $@ + +sql/$(MAINEXT)-schema--$(EXTVERSION).sql: sql/$(MAINEXT)-schema.sql + cp $< $@ + +# sort is necessary to remove dupes so install won't complain +DATA = $(sort $(wildcard sql/*--*.sql) $(BASE_FILES) $(VERSION_FILES) $(_IN_PATCHED)) +else +# No extension support, just install the base files. +DATA = $(BASE_FILES) +endif + # Load PGXS now that we've set all the variables it might need. ifdef NO_PGXS include $(top_builddir)/src/Makefile.global @@ -90,26 +111,6 @@ OSNAME := $(shell $(SHELL) ./getos.sh) .PHONY: test -# Make sure we build these. -EXTRA_CLEAN += $(_IN_PATCHED) -all: $(_IN_PATCHED) sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql - -# Add extension build targets on 9.1 and up. -ifeq ($(shell echo $(VERSION) | grep -qE "8[.]|9[.]0" && echo no || echo yes),yes) -all: sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql - -sql/$(MAINEXT)--$(EXTVERSION).sql: sql/$(MAINEXT).sql - cp $< $@ - -sql/$(MAINEXT)-core--$(EXTVERSION).sql: sql/$(MAINEXT)-core.sql - cp $< $@ - -sql/$(MAINEXT)-schema--$(EXTVERSION).sql: sql/$(MAINEXT)-schema.sql - cp $< $@ - -DATA = $(sort $(wildcard sql/*--*.sql) $(BASE_FILES) $(VERSION_FILES) $(_IN_PATCHED)) -endif - sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql cp $< $@ ifeq ($(shell echo $(VERSION) | grep -qE "[98][.]" && echo yes || echo no),yes) From 9b5195a940b4eac05319ac6562db092c2488ff4f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 15:43:14 -0500 Subject: [PATCH 0971/1195] Update compat patches. --- compat/install-8.1.patch | 18 ++++++++-------- compat/install-8.2.patch | 46 ++++++++++++++++++++-------------------- compat/install-8.3.patch | 8 +++---- compat/install-8.4.patch | 8 +++---- compat/install-9.1.patch | 4 ++-- compat/install-9.2.patch | 2 +- 6 files changed, 43 insertions(+), 43 deletions(-) diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index 9c5ba53a86e0..bafeffc26cc4 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -2258,13 +2258,13 @@ +@@ -2268,13 +2268,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -18,7 +18,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $6 || ' constraints'; -@@ -2282,13 +2282,13 @@ +@@ -2292,13 +2292,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -36,7 +36,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $5 || ' constraints'; -@@ -6158,7 +6158,7 @@ +@@ -6188,7 +6188,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -45,7 +45,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -6166,8 +6166,8 @@ +@@ -6196,8 +6196,8 @@ -- 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. @@ -56,7 +56,7 @@ END LOOP; END LOOP; RETURN; -@@ -6236,7 +6236,7 @@ +@@ -6266,7 +6266,7 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -65,7 +65,7 @@ tfaild INTEGER := 0; ffaild INTEGER := 0; tnumb INTEGER := 0; -@@ -6246,7 +6246,7 @@ +@@ -6276,7 +6276,7 @@ BEGIN -- No plan support. PERFORM * FROM no_plan(); @@ -74,7 +74,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -6285,18 +6285,18 @@ +@@ -6315,18 +6315,18 @@ BEGIN BEGIN -- Run the setup functions. @@ -99,7 +99,7 @@ END LOOP; -- Emit the plan. -@@ -6352,11 +6352,11 @@ +@@ -6382,11 +6382,11 @@ END LOOP; -- Run the shutdown functions. @@ -114,7 +114,7 @@ END LOOP; -- Clean up and return. -@@ -7614,7 +7614,7 @@ +@@ -7644,7 +7644,7 @@ DECLARE rec RECORD; BEGIN diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index b481a30df67d..ff83cdf4b8cf 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -103,7 +103,7 @@ ), ''); $$ LANGUAGE sql IMMUTABLE; -@@ -2473,7 +2511,7 @@ +@@ -2483,7 +2521,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 @@ -112,7 +112,7 @@ p.prolang AS langoid, p.proisstrict AS is_strict, p.proisagg AS is_agg, -@@ -3678,63 +3716,6 @@ +@@ -3688,63 +3726,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -176,7 +176,7 @@ CREATE OR REPLACE FUNCTION _has_role( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( -@@ -6305,17 +6286,17 @@ +@@ -6335,17 +6316,17 @@ BEGIN -- Run the setup functions. FOR tap IN SELECT * FROM _runem(setup, false) LOOP @@ -197,7 +197,7 @@ END LOOP; -- Emit the plan. -@@ -6354,7 +6335,7 @@ +@@ -6384,7 +6365,7 @@ tok := FALSE; RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname @@ -206,7 +206,7 @@ errmsg := NULL; END IF; END; -@@ -6467,13 +6448,13 @@ +@@ -6497,13 +6478,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -222,7 +222,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6697,7 +6678,7 @@ +@@ -6727,7 +6708,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -231,7 +231,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6792,11 +6773,11 @@ +@@ -6822,11 +6803,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -246,7 +246,7 @@ ); END IF; rownum = rownum + 1; -@@ -6811,9 +6792,9 @@ +@@ -6841,9 +6822,9 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Number of columns or their types differ between the queries' || @@ -259,7 +259,7 @@ END ); END; -@@ -6949,7 +6930,7 @@ +@@ -6979,7 +6960,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -268,7 +268,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6963,8 +6944,8 @@ +@@ -6993,8 +6974,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -279,7 +279,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7089,9 +7070,9 @@ +@@ -7119,9 +7100,9 @@ DECLARE typeof regtype := pg_typeof($1); BEGIN @@ -292,7 +292,7 @@ END; $$ LANGUAGE plpgsql; -@@ -7112,7 +7093,7 @@ +@@ -7142,7 +7123,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -301,7 +301,7 @@ END LOOP; -- What extra records do we have? -@@ -7280,7 +7261,7 @@ +@@ -7310,7 +7291,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -310,7 +310,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT -@@ -7298,7 +7279,7 @@ +@@ -7328,7 +7309,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -319,7 +319,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) ), -@@ -7331,7 +7312,7 @@ +@@ -7361,7 +7342,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -328,7 +328,7 @@ 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']) ) -@@ -7350,7 +7331,7 @@ +@@ -7380,7 +7361,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -337,7 +337,7 @@ 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']) ) -@@ -7634,10 +7615,12 @@ +@@ -7664,10 +7645,12 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; @@ -353,7 +353,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7784,7 +7767,7 @@ +@@ -7814,7 +7797,7 @@ CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) RETURNS TEXT AS $$ @@ -362,7 +362,7 @@ $$ LANGUAGE SQL; -- operators_are( schema, operators[], description ) -@@ -7793,7 +7776,7 @@ +@@ -7823,7 +7806,7 @@ SELECT _areni( 'operators', ARRAY( @@ -371,7 +371,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7805,7 +7788,7 @@ +@@ -7835,7 +7818,7 @@ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT @@ -380,7 +380,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7826,7 +7809,7 @@ +@@ -7856,7 +7839,7 @@ SELECT _areni( 'operators', ARRAY( @@ -389,7 +389,7 @@ 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) -@@ -7839,7 +7822,7 @@ +@@ -7869,7 +7852,7 @@ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT @@ -398,7 +398,7 @@ 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) -@@ -8542,40 +8525,6 @@ +@@ -8582,40 +8565,6 @@ ); $$ LANGUAGE sql; diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index d392c4c41f4f..7ae5ef2c7e1e 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -12,7 +12,7 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT substring(s.a[1] FROM '[[:digit:]]+')::int * 10000 -@@ -6787,7 +6792,7 @@ +@@ -6817,7 +6822,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -21,7 +21,7 @@ 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' || -@@ -6944,7 +6949,7 @@ +@@ -6974,7 +6979,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -30,7 +30,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -7153,13 +7158,7 @@ +@@ -7183,13 +7188,7 @@ $$ LANGUAGE sql; -- collect_tap( tap, tap, tap ) @@ -45,7 +45,7 @@ RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); $$ LANGUAGE sql; -@@ -7635,7 +7634,7 @@ +@@ -7665,7 +7664,7 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; diff --git a/compat/install-8.4.patch b/compat/install-8.4.patch index 872b66fb1067..d7cb517f0c68 100644 --- a/compat/install-8.4.patch +++ b/compat/install-8.4.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -7661,7 +7661,6 @@ +@@ -7691,7 +7691,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -8,7 +8,7 @@ EXCEPT SELECT $3[i] FROM generate_series(1, array_upper($3, 1)) s(i) -@@ -7676,7 +7675,6 @@ +@@ -7706,7 +7705,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -16,7 +16,7 @@ ), $4 ); -@@ -7700,7 +7698,6 @@ +@@ -7730,7 +7728,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = $1 AND n.nspname NOT IN ('pg_catalog', 'information_schema') @@ -24,7 +24,7 @@ EXCEPT SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) -@@ -7714,7 +7711,6 @@ +@@ -7744,7 +7741,6 @@ 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') diff --git a/compat/install-9.1.patch b/compat/install-9.1.patch index c9a85fc78b69..ea6b68d78a92 100644 --- a/compat/install-9.1.patch +++ b/compat/install-9.1.patch @@ -2,7 +2,7 @@ +++ sql/pgtap.sql @@ -781,10 +781,6 @@ RETURN ok( TRUE, descr ); - EXCEPTION WHEN OTHERS OR ASSERT_FAILURE THEN + EXCEPTION WHEN OTHERS THEN -- There should have been no exception. - GET STACKED DIAGNOSTICS - detail = PG_EXCEPTION_DETAIL, @@ -11,7 +11,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6342,10 +6338,6 @@ +@@ -6372,10 +6368,6 @@ -- Something went wrong. Record that fact. errstate := SQLSTATE; errmsg := SQLERRM; diff --git a/compat/install-9.2.patch b/compat/install-9.2.patch index 82da5a497a86..44985237f334 100644 --- a/compat/install-9.2.patch +++ b/compat/install-9.2.patch @@ -14,7 +14,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6350,12 +6345,7 @@ +@@ -6380,12 +6375,7 @@ GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, hint = PG_EXCEPTION_HINT, From 56fe3ad35a827c427399c562f9d0d62a57a12fd4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 16:00:08 -0500 Subject: [PATCH 0972/1195] Fix throwtap test on 8.1. --- test/sql/throwtap.sql | 182 +++++++++++++++++++++--------------------- 1 file changed, 91 insertions(+), 91 deletions(-) diff --git a/test/sql/throwtap.sql b/test/sql/throwtap.sql index ad71f9694a1c..1a7c9831b796 100644 --- a/test/sql/throwtap.sql +++ b/test/sql/throwtap.sql @@ -326,98 +326,98 @@ $exec$ CREATE FUNCTION test_assert() RETURNS SETOF text LANGUAGE plpgsql AS $body$ DECLARE - t text; + tap record; BEGIN -IF pg_version_num() >= 90500 THEN -FOR t IN SELECT * FROM check_test( - throws_ok( 'SELECT check_assert(false)', 'P0004', 'assert description' ), - true, - 'throws_ok catches assert', - 'threw P0004: assert description', - '' -) LOOP - RETURN NEXT t; -END LOOP; - -FOR t IN SELECT * FROM check_test( - throws_ok( 'SELECT check_assert(true)', 'P0004' ), - false, - 'throws_ok does not accept passing assert', - 'threw P0004', - ' caught: no exception - wanted: P0004' -) LOOP - RETURN NEXT t; -END LOOP; - -FOR t IN SELECT * FROM check_test( - lives_ok( 'SELECT check_assert(true)' ), - true, - 'lives_ok calling check_assert(true)', - '', - '' -) LOOP - RETURN NEXT t; -END LOOP; - --- Check its diagnostics when there is an exception. -FOR t IN SELECT * FROM check_test( - lives_ok( 'SELECT check_assert(false)' ), - false, - 'lives_ok with check_assert(false)', - '', - ' died: P0004: assert description - CONTEXT: - PL/pgSQL function check_assert(boolean) line 3 at ASSERT - SQL statement "SELECT check_assert(false)" - PL/pgSQL function lives_ok(text,text) line 14 at EXECUTE - PL/pgSQL function test_assert() line 38 at FOR over SELECT rows' -) LOOP - RETURN NEXT t; -END LOOP; -ELSE -FOR t IN SELECT * FROM check_test( - pass(''), - true, - 'throws_ok catches assert', - '', - '' -) LOOP - RETURN NEXT t; -END LOOP; - -FOR t IN SELECT * FROM check_test( - fail(''), - false, - 'throws_ok does not accept passing assert', - '', - '' -) LOOP - RETURN NEXT t; -END LOOP; - -FOR t IN SELECT * FROM check_test( - pass(''), - true, - 'lives_ok calling check_assert(true)', - '', - '' -) LOOP - RETURN NEXT t; -END LOOP; - --- Check its diagnostics when there is an exception. -FOR t IN SELECT * FROM check_test( - fail(''), - false, - 'lives_ok with check_assert(false)', - '', - '' -) LOOP - RETURN NEXT t; -END LOOP; -END IF; -END + IF pg_version_num() >= 90500 THEN + FOR tap IN SELECT * FROM check_test( + throws_ok( 'SELECT check_assert(false)', 'P0004', 'assert description' ), + true, + 'throws_ok catches assert', + 'threw P0004: assert description', + '' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + throws_ok( 'SELECT check_assert(true)', 'P0004' ), + false, + 'throws_ok does not accept passing assert', + 'threw P0004', + ' caught: no exception + wanted: P0004' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + lives_ok( 'SELECT check_assert(true)' ), + true, + 'lives_ok calling check_assert(true)', + '', + '' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + -- Check its diagnostics when there is an exception. + FOR tap IN SELECT * FROM check_test( + lives_ok( 'SELECT check_assert(false)' ), + false, + 'lives_ok with check_assert(false)', + '', + ' died: P0004: assert description + CONTEXT: + PL/pgSQL function check_assert(boolean) line 3 at ASSERT + SQL statement "SELECT check_assert(false)" + PL/pgSQL function lives_ok(text,text) line 14 at EXECUTE + PL/pgSQL function test_assert() line 38 at FOR over SELECT rows' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + ELSE + FOR tap IN SELECT * FROM check_test( + pass(''), + true, + 'throws_ok catches assert', + '', + '' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + fail(''), + false, + 'throws_ok does not accept passing assert', + '', + '' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + pass(''), + true, + 'lives_ok calling check_assert(true)', + '', + '' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + -- Check its diagnostics when there is an exception. + FOR tap IN SELECT * FROM check_test( + fail(''), + false, + 'lives_ok with check_assert(false)', + '', + '' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + END IF; +END; $body$; SELECT * FROM test_assert(); From d4b9f0020b3d1184836e484ba869b10bbb5209a7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 16:30:26 -0500 Subject: [PATCH 0973/1195] Fix diagnostic test indentation for 9.5. --- test/sql/throwtap.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/sql/throwtap.sql b/test/sql/throwtap.sql index 1a7c9831b796..f20b805745cb 100644 --- a/test/sql/throwtap.sql +++ b/test/sql/throwtap.sql @@ -345,7 +345,7 @@ BEGIN 'throws_ok does not accept passing assert', 'threw P0004', ' caught: no exception - wanted: P0004' + wanted: P0004' ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; @@ -367,11 +367,11 @@ BEGIN 'lives_ok with check_assert(false)', '', ' died: P0004: assert description - CONTEXT: - PL/pgSQL function check_assert(boolean) line 3 at ASSERT - SQL statement "SELECT check_assert(false)" - PL/pgSQL function lives_ok(text,text) line 14 at EXECUTE - PL/pgSQL function test_assert() line 38 at FOR over SELECT rows' + CONTEXT: + PL/pgSQL function check_assert(boolean) line 3 at ASSERT + SQL statement "SELECT check_assert(false)" + PL/pgSQL function lives_ok(text,text) line 14 at EXECUTE + PL/pgSQL function test_assert() line 38 at FOR over SELECT rows' ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; From d1277d71b6016aff589f71b84425e6df26539256 Mon Sep 17 00:00:00 2001 From: James Marca Date: Mon, 6 Nov 2017 13:06:27 -0800 Subject: [PATCH 0974/1195] trivial fix of typo: bar to bare bar record should be bare record --- doc/pgtap.mmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 3d5344ebb4b0..c86e08e43fd3 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1643,7 +1643,7 @@ test fails. : A short description of the test. Compares the contents of a single row to a record. Due to the limitations of -non-C functions in PostgreSQL, a bar `RECORD` value cannot be passed to the +non-C functions in PostgreSQL, a bare `RECORD` value cannot be passed to the function. You must instead pass in a valid composite type value, and cast the record argument (the second argument) to the same type. Both explicitly created composite types and table types are supported. Thus, you can do this: From d9898947f2ce9629707ad2bef56bd466d038cd26 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 16:48:37 -0500 Subject: [PATCH 0975/1195] Tweak RPM spec file. --- Changes | 1 + contrib/pgtap.spec | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 4cad25baa5d7..d2589b80c935 100644 --- a/Changes +++ b/Changes @@ -29,6 +29,7 @@ Revision history for pgTAP + `partitions_are()` * Added the materialized view-testing assertion functions to the v0.95.0 upgrade script; they were inadvertently omitted from the v0.95.0 release. +* Fixed faling tests on Postgres 8.1. 0.97.0 2016-11-28T22:18:29Z --------------------------- diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 255f42589d4f..5b414064cc33 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,13 +1,16 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 0.93.0 +Version: 0.98.0 Release: 1%{?dist} Group: Applications/Databases License: PostgreSQL URL: http://pgtap.org/ Source0: http://master.pgxn.org/dist/pgtap/%{version}/pgtap-%{version}.zip BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root - +Provides: %{name} +Provides: %{name}-core +Provides: %{name}-schema + %description pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and PL/SQL. It includes a comprehensive collection of TAP-emitting assertion @@ -16,6 +19,7 @@ frameworks. It can also be used in the xUnit testing style. %define postgresver %(pg_config --version|awk '{print $2}'| cut -d. -f1,2) Requires: postgresql-server = %{postgresver}, perl-Test-Harness >= 3.0 +Requires: perl(TAP::Parser::SourceHandler::pgTAP) BuildRequires: postgresql-devel = %{postgresver} %if "%{postgresver}" != "8.4" @@ -44,12 +48,17 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog -* Tue Jan 15 2013 David E. Wheeler 0.92.0-1 -- Upgraded to pgTAP 0.92.0 +* Mon Nov 6 2017 David E. Wheeler 0.98.0-1 +- Update to v0.98.0. +- Added pgTAP harness Perl module requirement. +- Added explicit list of provided features. * Mon Jan 28 2013 David Wheeler 0.93.0 - Upgraded to pgTAP 0.93.0 +* Tue Jan 15 2013 David E. Wheeler 0.92.0-1 +- Upgraded to pgTAP 0.92.0 + * Tue Aug 23 2011 David Wheeler 0.91.0 - Removed USE_PGXS from Makefile; it has not been supported in some time. - Removed TAPSCHEMA from Makefile; use PGOPTIONS=--search_path=tap with From 22e65a4a8063d1d2dbc62f8e2b44ced35f268e2a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 16:58:36 -0500 Subject: [PATCH 0976/1195] Note 9.6 compat changes. --- doc/pgtap.mmd | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index c86e08e43fd3..e4cd73924f86 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7924,11 +7924,16 @@ instead: To see the specifics for each version of PostgreSQL, consult the files in the `compat/` directory in the pgTAP distribution. -9.5 and Up ----------- +10 and Up +--------- No changes. Everything should just work. +9.6 and Down +------------ +* The partition-testing functions are not available, because partitions were + not introduced until 10. + 9.4 and Down ------------ * lives_ok() and throws_ok() will not trap ASSERT_FAILURE, since asserts do not @@ -8024,7 +8029,7 @@ comments, suggestions, and bug reports are welcomed there. Author ------ -[David E. Wheeler](http://justatheory.com/) +[David E. Wheeler](http://theory.pm/) Credits ------- From 3e7b57ae12316b211757e7b77e82dfd059e48cb6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 17:30:07 -0500 Subject: [PATCH 0977/1195] Timestamp v0.98.0. --- Changes | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index d2589b80c935..9a76140bdee3 100644 --- a/Changes +++ b/Changes @@ -1,11 +1,11 @@ Revision history for pgTAP ========================== -0.98.0 +0.98.0 2017-11-06T22:30:54Z --------------------------- * `make install` once again properly installs the versioned SQL file required - by `CREATE EXTENSION`. Thanks to Keith Fiske for the report (Issue #131) and - Jim Nasby for the fix (Issue #132). + by `CREATE EXTENSION`. Thanks to Keith Fiske for the reports (Issue #131, + Issue #143) and Jim Nasby for the fix (Issue #132). * Removed references to the Epic test framework, since its domain (`epictest.org`) has expired and the project appears to no longer be available online. Thanks to Andreas Brekken for the report (#140). @@ -16,8 +16,8 @@ Revision history for pgTAP * Clarified the use of the `:schema` parameter to the extension-testing functions in the documentation. Required since extensions do not belong to schemas, but their objects may be specifically created in a schema. Thanks to - @decibel for the report (#121)! -* Updated the table-testing functions to recognized partitioned tables: + Jim Nasby for the report (#121)! +* Updated the table-testing functions to also recognize partitioned tables: + `has_table()` + `hasnt_table()` + `tables_are()` @@ -28,7 +28,7 @@ Revision history for pgTAP + `is_partition_of()` + `partitions_are()` * Added the materialized view-testing assertion functions to the v0.95.0 upgrade - script; they were inadvertently omitted from the v0.95.0 release. + script; they were inadvertently omitted in the v0.95.0 release. * Fixed faling tests on Postgres 8.1. 0.97.0 2016-11-28T22:18:29Z From 1a68a2cd13413243f6722a9348cdc7e806d247d4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 17:37:39 -0500 Subject: [PATCH 0978/1195] Do not archive .travis.yml --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index c496927a6cbc..b5815f6bce13 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ .gitignore export-ignore .gitattributes export-ignore tocgen export-ignore +.travis.yml export-ignore From d8ffb6bc8cc5c12083ccc845af1a419243064fd3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 17:56:14 -0500 Subject: [PATCH 0979/1195] Increment to v0.99.0. --- Changes | 3 +++ META.json | 8 ++++---- README.md | 2 +- contrib/pgtap.spec | 2 +- doc/pgtap.mmd | 2 +- pgtap.control | 2 +- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Changes b/Changes index 9a76140bdee3..0bea9cda992d 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Revision history for pgTAP ========================== +0.99.0 +--------------------------- + 0.98.0 2017-11-06T22:30:54Z --------------------------- * `make install` once again properly installs the versioned SQL file required diff --git a/META.json b/META.json index dee7fc8952f0..87b3096b66ab 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "0.98.0", + "version": "0.99.0", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "0.98.0" + "version": "0.99.0" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "0.98.0" + "version": "0.99.0" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "0.98.0" + "version": "0.99.0" } }, "resources": { diff --git a/README.md b/README.md index 2f32f5d6d072..051b9d842c31 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 0.98.0 +pgTAP 0.99.0 ============ [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 5b414064cc33..b6962999eb3b 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 0.98.0 +Version: 0.99.0 Release: 1%{?dist} Group: Applications/Databases License: PostgreSQL diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index e4cd73924f86..9cfbc72fc63d 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 0.98.0 +pgTAP 0.99.0 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/pgtap.control b/pgtap.control index d24cb0849498..f701687dbf8a 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '0.98.0' +default_version = '0.99.0' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true From c1b0dd38939050d36d777585856ad7be759ff270 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 18:03:40 -0500 Subject: [PATCH 0980/1195] Add release-making notes. --- .gitattributes | 1 + release.mmd | 120 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 release.mmd diff --git a/.gitattributes b/.gitattributes index b5815f6bce13..834f849cc33f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,4 @@ .gitattributes export-ignore tocgen export-ignore .travis.yml export-ignore +.release.mmd export-ignore diff --git a/release.mmd b/release.mmd new file mode 100644 index 000000000000..fcc2783e5915 --- /dev/null +++ b/release.mmd @@ -0,0 +1,120 @@ +pgTAP Release Management +======================== + +Here are the steps to take to make a release of pgTAP: + +* Test on all supported PostgreSQL versions, starting with the latest version + (10) and moving backward in order (9.6, 9.5, 9.4, etc.). For each version, + ensure that: + + + Patches apply cleanly (try to eliminate Hunk warnings for patches to + `pgtap.sql` itself, usually by fixing line numbers) + + + All files are installed (on 8.3 and earlier that includes pgtap.so). + + + `ALTER EXTENSION pgtap UPDATE;` works on 9.1 and higher. + + + `CREATE EXTENSION pgtap;` works on 9.1 and higer. + + + All tests pass in `make installcheck` (on 8.1, move the pgtap source + dir into the postgres source contrib directory and run + `make NO_PGXS=1 installcheck`) + +* If you've made any significant changes, test all supported version again in + foreward order (8.1, 8.2, 8.3, etc.) to make sure the changes didn't break + any later versions. + +* Review the documentation in `doc/pgtap.mmd`, and make any necessary changes, + including to the list of PostgreSQL version-compatibility notes at the end + of the document. + +* Add an item to the top of the `%changelog` section of `contrib/pgtap.spec`. + It should use the version you're about to release, as well as the date and + your name and email address. Add at least one bullet mentioning the + upgrade. + +* Run `make html` (you'll need + [MultiMarkdown](http://fletcherpenney.net/multimarkdown/) in your path and + the [Pod::Simple::XHTML](https://metacpan.org/module/Pod::Simple::XHTML) + Perl module installed), then checkout the `gh-pages` branch and make these + changes: + + + Open `documentatoin.html` and delete all the lines between these "DOC" + comments, until the main div looks like this: + +
    + + +
    + + +
    + + +
    + + + Copy the first `

    ` and `

    ` from `doc/pgtap.html` into the + `DOC INTRO HERE` section. + + + Copy the rest of `doc/pgtap.html` into the + `DOCS HERE, WITH INTRO MOVED ABOVE` section. + + + Copy the entire contents of `doc/toc.html` into the + `DOC SANS pgTAP x.xx` section, and then remove the first `

  • ` element that + says "pgTAP x.xx". + + + Sanity-check that everything looks right; use `git diff` to make sure + nothing important was lost. It should mainly be additions. + + + Commit the changes, but don't push them yet. + +* Go back to the `master` branch and proofread the additions to the `Changes` + file since the last release. Make sure all relevant changes are recorded + there, and that any typos or formatting errors are corrected. + +* Timestamp the `Changes` file. I generate the timestamp like so: + + perl -MDateTime -e 'print DateTime->now->datetime . "Z"' + + Paste that into the line with the new version, maybe increment by a minute + to account for the time you'll need to actually do the release. + +* Commit the timestamp and tag it: + + git ci -m 'Timestamp v0.98.0.' + git tag -am 'Tag v0.98.0.' v0.98.0 + +* Package the source and submit to [PGXN](http://manager.pgxn.org/). + + gem install pgxn_utils + git archive --format zip --prefix=pgtap-0.98.0/ \ + --output pgtap-0.98.0.zip master + +* Push all the changes to GitHub. + + git push + git push --tags + +* Ask one of the nice folks at [PGX](https://pgexperts.com/) to pull the + changes into [their fork](https://github.com/pgexperts/pgtap); It's their + repo that serves [pgxn.org](http://pgxn.org/). Once it's merged, check + that the changes to the + [documentation page](http://pgxn.org/documentation.html) were properly + updated. + +* Increment the version to kick off development for the next release. The + version should be added to the `Changes` file, and incremented in the + following files: + + + `META.json` (including for the three parts of the `provides` section) + + `README.md` + + `contrib/pgtap.spec` + + `doc/pgtap.mmd` + + `pgtap.control` + +* Commit that change and push it. + + git ci -m 'Increment to v0.99.0.' + git push + +* Start hacking on the next version! From 0199dc2a1aa6874aa52eda86b6c7aa0cadec2aaa Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 6 Nov 2017 18:04:22 -0500 Subject: [PATCH 0981/1195] Regular markdown, notnhing special. --- release.mmd => release.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename release.mmd => release.md (100%) diff --git a/release.mmd b/release.md similarity index 100% rename from release.mmd rename to release.md From 0c31b33b976dfee2c097e9bee7310cb7cdcc0847 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 8 Nov 2017 09:43:41 -0500 Subject: [PATCH 0982/1195] Update copyright; fix m-dashes. Also a typo. --- README.md | 2 +- doc/pgtap.mmd | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 051b9d842c31..8c19067170da 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ full use of its API. It also requires PL/pgSQL. Copyright and License --------------------- -Copyright (c) 2008-2016 David E. Wheeler. Some rights reserved. +Copyright (c) 2008-2017 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 9cfbc72fc63d..a1b173ac2fbc 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -383,16 +383,19 @@ By default pgTAP displays this names as "comment", but you're able to change thi : A test name. This will show + # test: my_example_test_function_name instead of + # my_example_test_function_name() + This makes easy handling test name and differing test names from comments. I'm ok, you're not ok --------------------- -The basic purpose of pgTAP--and of any TAP-emitting test framework, for that -matter--is to print out either "ok #" or "not ok #", depending on whether a +The basic purpose of pgTAP---and of any TAP-emitting test framework, for that +matter---is to print out either "ok #" or "not ok #", depending on whether a given test succeeded or failed. Everything else is just gravy. All of the following functions return "ok" or "not ok" depending on whether @@ -1419,7 +1422,7 @@ are *not* the same. The two queries can as usual be the names of prepared statements or strings containing an SQL query (see the [summary](#Pursuing+Your+Query) for query argument details), or even one of each. The two queries, however, must return results that are directly -comparable -- that is, with the same number and types of columns in the same +comparable --- that is, with the same number and types of columns in the same orders. If it happens that the query you're testing returns a single column, the second argument may be an array. @@ -1536,7 +1539,7 @@ are *not* the same, including duplicates. The two queries can as usual be the names of prepared statements or strings containing an SQL query (see the [summary](#Pursuing+Your+Query) for query argument details), or even one of each. The two queries, however, must return results that are directly -comparable -- that is, with the same number and types of columns in the same +comparable --- that is, with the same number and types of columns in the same orders. If it happens that the query you're testing returns a single column, the second argument may be an array. @@ -4935,7 +4938,7 @@ defined in `:schema` are not exactly the functions defined in `:functions`. If `:schema` is omitted, then `can()` will look for functions defined in schemas defined in the search path. No matter how many functions are listed in `:functions`, a single call to `can()` counts as one test. If you want -otherwise, call `can()` once for each function -- or better yet, use +otherwise, call `can()` once for each function --- or better yet, use `has_function()`. Example: SELECT can( 'pg_catalog', ARRAY['upper', 'lower'] ); @@ -6441,8 +6444,8 @@ user, the diagnostics will look something like: # have: postgres # want: root -Prvileged Access ----------------- +Privileged Access +----------------- So we know who owns the objects. But what about other roles? Can they access database objects? Let's find out! @@ -8040,7 +8043,7 @@ Credits Copyright and License --------------------- -Copyright (c) 2008-2016 David E. Wheeler. Some rights reserved. +Copyright (c) 2008-2017 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is From 7d2a20e1a5430693e952fd330fcbe3b4565519ab Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 8 Nov 2017 10:06:43 -0500 Subject: [PATCH 0983/1195] Update introductory documentation. A lot has changed over the years! --- Changes | 1 + doc/pgtap.mmd | 113 +++++++++++++++++++++++--------------------------- 2 files changed, 52 insertions(+), 62 deletions(-) diff --git a/Changes b/Changes index 0bea9cda992d..aa7f1f40a420 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,7 @@ Revision history for pgTAP 0.99.0 --------------------------- +* Updated introductory documentation. A lot has changed over the years! 0.98.0 2017-11-06T22:30:54Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index a1b173ac2fbc..2417453da835 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -10,6 +10,8 @@ used in the xUnit testing style. Synopsis ======== + CREATE EXTENSION IF NOT EXISTS pgtap; + SELECT plan( 23 ); -- or SELECT * from no_plan(); @@ -96,28 +98,6 @@ You need to run the test suite using a super user, such as the default make installcheck PGUSER=postgres -Once pgTAP is installed, you can add it to a database. If you're running -PostgreSQL 9.1.0 or greater, it's a simple as connecting to a database as a -super user and running: - - CREATE EXTENSION pgtap; - -If you've upgraded your cluster to PostgreSQL 9.1 and already had pgTAP -installed, you can upgrade it to a properly packaged extension with: - - CREATE EXTENSION pgtap FROM unpackaged; - -For versions of PostgreSQL less than 9.1.0, you'll need to run the -installation script: - - psql -d mydb -f /path/to/pgsql/share/contrib/pgtap.sql - -If you want to install pgTAP and all of its supporting objects into a -specific schema, use the `PGOPTIONS` environment variable to specify the -schema, like so: - - PGOPTIONS=--search_path=tap psql -d mydb -f pgTAP.sql - Testing pgTAP with pgTAP ------------------------ @@ -142,15 +122,36 @@ You can use it to run the test suite as a database super user like so: Adding pgTAP to a Database -------------------------- -Once pgTAP has been built and tested, you can install it into a -PL/pgSQL-enabled database: +Once pgTAP is installed, you can add it to a database. If you're running +PostgreSQL 9.1.0 or greater, it's a simple as connecting to a database as a +super user and running: + + CREATE EXTENSION IF NOT EXISTS pgtap; - psql -d dbname -f pgtap.sql +If you've upgraded your cluster to PostgreSQL 9.1 and already had pgTAP +installed, you can upgrade it to a properly packaged extension with: + + CREATE EXTENSION pgtap FROM unpackaged; If you want pgTAP to be available to all new databases, install it into the "template1" database: - psql -d template1 -f pgtap.sql + psql -d template1 -C "CREATE EXTENSION pgtap" + +To uninstall pgTAP, use `DROP EXTENSION`: + + DROP EXTENSION IF EXISTS pgtap; + +For versions of PostgreSQL less than 9.1.0, you'll need to run the +installation script: + + psql -d mydb -f /path/to/pgsql/share/contrib/pgtap.sql + +If you want to install pgTAP and all of its supporting objects into a +specific schema, use the `PGOPTIONS` environment variable to specify the +schema, like so: + + PGOPTIONS=--search_path=tap psql -d mydb -f pgTAP.sql If you want to remove pgTAP from a database, run the `uninstall_pgtap.sql` script: @@ -175,23 +176,22 @@ variables to keep the tests quiet, start a transaction, load the functions in your test script, and then rollback the transaction at the end of the script. Here's an example: - \set ECHO + \unset ECHO \set QUIET 1 -- Turn off echo and keep things quiet. -- Format the output for nice TAP. \pset format unaligned \pset tuples_only true - \pset pager + \pset pager off -- Revert all changes on failure. \set ON_ERROR_ROLLBACK 1 \set ON_ERROR_STOP true - \set QUIET 1 -- Load the TAP functions. BEGIN; - \i pgtap.sql + \i pgtap.sql -- Plan the tests. SELECT plan(1); @@ -203,16 +203,6 @@ Here's an example: SELECT * FROM finish(); ROLLBACK; -Of course, if you already have the pgTAP functions in your testing database, -you should skip `\i pgtap.sql` at the beginning of the script. - -The only other limitation is that the `pg_typeof()` function, which is -written in C, will not be available in 8.3 and lower. You'll want to -comment out its declaration in the bundled copy of `pgtap.sql` and then avoid -using `cmp_ok()`, since that function relies on `pg_typeof()`. Note that -`pg_typeof()` is included in PostgreSQL 8.4, so you won't need to avoid it on -that version or higher. - Now you're ready to run your test script! % psql -d try -Xf test.sql @@ -270,12 +260,12 @@ Using pgTAP =========== The purpose of pgTAP is to provide a wide range of testing utilities that -output TAP. TAP, or the "Test Anything Protocol", is an emerging standard for -representing the output from unit tests. It owes its success to its format as -a simple text-based interface that allows for practical machine parsing and -high legibility for humans. TAP started life as part of the test harness for -Perl but now has implementations in C/C++, Python, PHP, JavaScript, Perl, and -now PostgreSQL. +output TAP. TAP, or the "Test Anything Protocol", is a standard for +representing the output from unit tests. It owes its success to its format as a +simple text-based interface that allows for practical machine parsing and high +legibility for humans. TAP started life as part of the test harness for Perl +but now has implementations in C/C++, Python, PHP, JavaScript, Perl, and, of +course, PostgreSQL. There are two ways to use pgTAP: 1) In simple test scripts that use a plan to describe the tests in the script; or 2) In xUnit-style test functions that you @@ -291,7 +281,7 @@ many tests your script is going to run to protect against premature failure. The preferred way to do this is to declare a plan by calling the `plan()` function: - SELECT plan( 42 ); + SELECT plan(42); There are rare cases when you will not know beforehand how many tests your script is going to run. In this case, you can declare that you have no plan. @@ -344,12 +334,12 @@ Each test function will run within its own transaction, and rolled back when the function completes (or after any teardown functions have run). The TAP results will be sent to your client. -Test names ----------- +Test Descriptions +----------------- By convention, each test is assigned a number in order. This is largely done -automatically for you. However, it's often very useful to assign a name to -each test. Would you rather see this? +automatically for you. However, it's often very useful to deascribe each test. +Would you rather see this? ok 4 not ok 5 @@ -364,32 +354,31 @@ Or this? The latter gives you some idea of what failed. It also makes it easier to find the test in your script, simply search for "simple exponential". -All test functions take a name argument. It's optional, but highly suggested -that you use it. +All test functions take a description argument. It's optional, but highly +suggested that you use it. -Sometimes it's useful to extract test function names from pgtap output, especially when using xUnit style with Continuous Integration Server like Hudson or TeamCity. -By default pgTAP displays this names as "comment", but you're able to change this behavior by overriding function `diag_test_name`: +### xUnit Function Names ### -### `diag_test_name()` ### +Sometimes it's useful to extract xUnit test function names from TAP output, +especially when using xUnit style with Continuous Integration Server like +Hudson or TeamCity. By default pgTAP displays these names as comments, but +you're able to change this behavior by overriding the function `diag_test_name`. +For example: CREATE OR REPLACE FUNCTION diag_test_name(TEXT) RETURNS TEXT AS $$ SELECT diag('test: ' || $1 ); $$ LANGUAGE SQL; -**Parameters** - -`:test_name` -: A test name. - This will show # test: my_example_test_function_name + instead of # my_example_test_function_name() -This makes easy handling test name and differing test names from comments. +This simplifies parsing test names from TAP comments. I'm ok, you're not ok --------------------- From eb276053852dc3ebf0b5d3dbf34504ab537c8006 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 8 Nov 2017 10:10:08 -0500 Subject: [PATCH 0984/1195] Spellcheck. --- doc/pgtap.mmd | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 2417453da835..6345baf3d3d2 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -338,7 +338,7 @@ Test Descriptions ----------------- By convention, each test is assigned a number in order. This is largely done -automatically for you. However, it's often very useful to deascribe each test. +automatically for you. However, it's often very useful to describe each test. Would you rather see this? ok 4 @@ -449,7 +449,7 @@ additional diagnostic: : Value to test. `:want` -: Value that `:have` is expcted to be. Must be the same data type. +: Value that `:have` is expected to be. Must be the same data type. `:description` : A short description of the test. @@ -494,7 +494,7 @@ Will produce something like this: So you can figure out what went wrong without re-running the test. You are encouraged to use `is()` and `isnt()` over `ok()` where possible. You -can even use them to compar records in PostgreSQL 8.4 and later: +can even use them to compare records in PostgreSQL 8.4 and later: SELECT is( users.*, ROW(1, 'theory', true)::users ) FROM users @@ -716,7 +716,7 @@ Use these functions very, very, very sparingly. Checks to see if the given value is of a particular type. The description and diagnostics of this test normally just refer to "the value". If you'd like them to be more specific, you can supply a `:name`. For example you might say -"the return value" when yo're examing the result of a function call: +"the return value" when you're examining the result of a function call: SELECT isa_ok( length('foo'), 'integer', 'The return value from length()' ); @@ -2524,7 +2524,7 @@ This function tests that all of the casts in the database are only the casts that *should* be in that database. Casts are specified as strings in a syntax similarly to how they're declared via `CREATE CAST`. The pattern is `:source_type AS :target_type`. If either type was created with double-quotes -to force mixed case or special characers, then you must use double quotes in +to force mixed case or special characters, then you must use double quotes in the cast strings. Example: SELECT casts_are(ARRAY[ @@ -2744,7 +2744,7 @@ specified schema does *not* exist. : A short description of the test. This function tests whether or not a relation exists in the database. Relations -are tables, views, materialized views, seqences, composite types, foreign +are tables, views, materialized views, sequences, composite types, foreign tables, and toast tables. The first argument is a schema name, the second is a relation name, and the third is the test description. If you omit the schema, the relation must be visible in the search path. Example: @@ -4133,7 +4133,7 @@ description. If the table schema is omitted, the table must be visible in the search path. If the type schema is omitted, it must be visible in the search path; otherwise, the diagnostics will report the schema it's actually in. The schema -can optinally be included in the `:type` argument, e.g., `'contrib.citext`. +can optionally be included in the `:type` argument, e.g., `'contrib.citext`. If the test description is omitted, it will be set to "Column `:schema.:table.:column` should be type `:schema.:type`". Note that this test @@ -5575,7 +5575,7 @@ diagnostics that tell you so: The inverse of `domain_type_is()`, this function tests that a domain does *not* extend a particular data type. For example, a US postal code domain -should probably extned the `text` type, not `integer`, since leading 0s are +should probably extend the `text` type, not `integer`, since leading 0s are valid and required. Example: SELECT domain_type_isnt( @@ -6662,7 +6662,7 @@ table privileges are: * TRUNCATE * UPDATE -Note that the privilege RULE is not available aftter PostgreSQL 8.1, and that +Note that the privilege RULE is not available after PostgreSQL 8.1, and that TRIGGER was added in 8.4. If the `:description` argument is omitted, an appropriate description will be @@ -6736,7 +6736,7 @@ sequence privileges are: * USAGE Note that sequence privileges were added in PostgreSQL 9.0, so this function -will likley throw an exception on earlier versions. +will likely throw an exception on earlier versions. If the `:description` argument is omitted, an appropriate description will be created. Examples: @@ -6809,7 +6809,7 @@ The available column privileges are: * UPDATE Note that column privileges were added in PostgreSQL 8.4, so this function -will likley throw an exception on earlier versions. +will likely throw an exception on earlier versions. If the `:description` argument is omitted, an appropriate description will be created. Examples: @@ -6885,7 +6885,7 @@ available column privileges are: * UPDATE Note that column privileges were added in PostgreSQL 8.4, so this function -will likley throw an exception on earlier versions. +will likely throw an exception on earlier versions. If the `:description` argument is omitted, an appropriate description will be created. Examples: @@ -7084,7 +7084,7 @@ available function privileges are: * USAGE Note that foreign data wrapper privileges were added in PostgreSQL 8.4, so -this function will likley throw an exception on earlier versions. +this function will likely throw an exception on earlier versions. If the `:description` argument is omitted, an appropriate description will be created. Examples: @@ -7103,13 +7103,13 @@ will list the extra permissions, like so: # USAGE Likewise if the role is not granted some of the specified permissions on the -fdw: +FDW: # Failed test 15: "Role kurk should be granted USAGE on odbc" # Missing privileges: # USAGE -In the event that the test fails because the fdw in question does not +In the event that the test fails because the FDW in question does not actually exist or is not visible, you will see an appropriate diagnostic such as: @@ -7147,7 +7147,7 @@ function privileges are: * USAGE Note that server privileges were added in PostgreSQL 8.4, so this function -will likley throw an exception on earlier versions. +will likely throw an exception on earlier versions. If the `:description` argument is omitted, an appropriate description will be created. Examples: @@ -7498,7 +7498,7 @@ On PostgreSQL 8.4 and higher, it can take any number of arguments. Lower than `:operoid` : Operator OID. -Similar to casting an operator OID to regoperator, only the schema is not +Similar to casting an operator OID to `regoperator`, only the schema is not included in the display. For example: SELECT display_oper(oprname, oid ) FROM pg_operator; From 1374245cc91f95bb89362fb66e5082e8192d2d40 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 8 Nov 2017 10:27:51 -0500 Subject: [PATCH 0985/1195] Add tips and tricks to the docs. --- Changes | 2 ++ doc/pgtap.mmd | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/Changes b/Changes index aa7f1f40a420..ca9bba91579f 100644 --- a/Changes +++ b/Changes @@ -4,6 +4,8 @@ Revision history for pgTAP 0.99.0 --------------------------- * Updated introductory documentation. A lot has changed over the years! +* Added the "Secrets of the pgTAP Mavens" section of the documentation. + Contributions wanted! 0.98.0 2017-11-06T22:30:54Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 6345baf3d3d2..6b0a456cdabe 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7701,6 +7701,37 @@ database in largely the same condition as it was in when you started it (the one exception I'm aware of being sequences, which are not rolled back to the value used at the beginning of a rolled-back transaction). +Secrets of the pgTAP Mavens +=========================== + +Over the years, a number of techniques have evolved to make all of our pgTAP +testing lives easier. Here are some of them. + +Relational-style Loops +---------------------- + +Need to test a bunch of objects and find yourself looking for some kind of +`for` loop to [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) off +with? SQL doesn't have one, of course, but that's because it doesn't need one: +the whole language is built around doing things to a bunch of rows. So take +advantage of it: build relations with the +[`VALUES`](https://www.postgresql.org/docs/current/static/sql-values.html) +command! For example, to make sure you have a table in a defined list of +schemas, try something like this: + + SELECT has_table(sch, 'widgets', format('Has %I.widgets', sch)) + FROM (VALUES('amazon'), ('starbucks'), ('boeing')) F(sch); + +Note the use of the +[`format` function](https://www.postgresql.org/docs/current/static/functions-string.html#functions-string-format) +to make a nice test description, too. Here's a more complicated example that +uses a cross join to test that various columns are `NOT NULL` in a specific +table in a bunch of schemas: + + SELECT col_not_null(sch, 'table1', col) + FROM (VALUES('schema1'), ('schema1')) AS stmp (sch) + CROSS JOIN (VALUES('col_pk'), ('col2'), ('col3')) AS ctmp (col); + Compose Yourself ================ From a5dd26f41c0b947128687f1c7d64b1349258377b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 13 Nov 2017 15:35:20 -0500 Subject: [PATCH 0986/1195] Make pg_version_num() consistent with sever_version_num. Unless building on 8.2 or earlier. Includes an extension upgrade script that updates the function, too. Also tweak the test description to reflecta that it's the `_num` variant that's being tested. Patch changes are to get line numbers re-aligned, as well as to go back to the old version of `pg_version_num()` on 8.2. Resolves #148. --- Changes | 2 ++ compat/install-8.2.patch | 66 +++++++++++++++++++++-------------- compat/install-8.3.patch | 10 +++--- compat/install-8.4.patch | 8 ++--- compat/install-9.0.patch | 10 +++--- compat/install-9.1.patch | 4 +-- compat/install-9.2.patch | 4 +-- compat/install-9.4.patch | 4 +-- compat/install-9.6.patch | 2 +- sql/pgtap--0.98.0--0.99.0.sql | 8 +++++ sql/pgtap.sql.in | 7 +--- test/expected/util.out | 2 +- test/sql/util.sql | 6 ++-- 13 files changed, 76 insertions(+), 57 deletions(-) create mode 100644 sql/pgtap--0.98.0--0.99.0.sql diff --git a/Changes b/Changes index ca9bba91579f..ca79465270d9 100644 --- a/Changes +++ b/Changes @@ -6,6 +6,8 @@ Revision history for pgTAP * Updated introductory documentation. A lot has changed over the years! * Added the "Secrets of the pgTAP Mavens" section of the documentation. Contributions wanted! +* Fixed `pg_version_num()` to always return the value of `server_version_num` + on PostreSQL 8.3 and higher, thus fixing a test failure on 10.1. 0.98.0 2017-11-06T22:30:54Z --------------------------- diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index ff83cdf4b8cf..7ed8fd1742f1 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -60,7 +60,21 @@ CREATE OR REPLACE FUNCTION pg_version() RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; -@@ -250,21 +303,6 @@ +@@ -16,7 +69,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 +303,6 @@ ); $$ LANGUAGE sql strict; @@ -82,7 +96,7 @@ CREATE OR REPLACE FUNCTION ok ( boolean, text ) RETURNS TEXT AS $$ DECLARE -@@ -477,9 +515,9 @@ +@@ -472,9 +515,9 @@ output TEXT; BEGIN EXECUTE 'SELECT ' || @@ -94,7 +108,7 @@ INTO result; output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( -@@ -763,7 +801,7 @@ +@@ -758,7 +801,7 @@ || COALESCE(E'\n TYPE: ' || nullif($10, ''), '') -- We need to manually indent all the context lines || COALESCE(E'\n CONTEXT:\n' @@ -103,7 +117,7 @@ ), ''); $$ LANGUAGE sql IMMUTABLE; -@@ -2483,7 +2521,7 @@ +@@ -2478,7 +2521,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 @@ -112,7 +126,7 @@ p.prolang AS langoid, p.proisstrict AS is_strict, p.proisagg AS is_agg, -@@ -3688,63 +3726,6 @@ +@@ -3683,63 +3726,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -176,7 +190,7 @@ CREATE OR REPLACE FUNCTION _has_role( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( -@@ -6335,17 +6316,17 @@ +@@ -6330,17 +6316,17 @@ BEGIN -- Run the setup functions. FOR tap IN SELECT * FROM _runem(setup, false) LOOP @@ -197,7 +211,7 @@ END LOOP; -- Emit the plan. -@@ -6384,7 +6365,7 @@ +@@ -6379,7 +6365,7 @@ tok := FALSE; RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname @@ -206,7 +220,7 @@ errmsg := NULL; END IF; END; -@@ -6497,13 +6478,13 @@ +@@ -6492,13 +6478,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -222,7 +236,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6727,7 +6708,7 @@ +@@ -6722,7 +6708,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -231,7 +245,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6822,11 +6803,11 @@ +@@ -6817,11 +6803,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -246,7 +260,7 @@ ); END IF; rownum = rownum + 1; -@@ -6841,9 +6822,9 @@ +@@ -6836,9 +6822,9 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Number of columns or their types differ between the queries' || @@ -259,7 +273,7 @@ END ); END; -@@ -6979,7 +6960,7 @@ +@@ -6974,7 +6960,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -268,7 +282,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6993,8 +6974,8 @@ +@@ -6988,8 +6974,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -279,7 +293,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7119,9 +7100,9 @@ +@@ -7114,9 +7100,9 @@ DECLARE typeof regtype := pg_typeof($1); BEGIN @@ -292,7 +306,7 @@ END; $$ LANGUAGE plpgsql; -@@ -7142,7 +7123,7 @@ +@@ -7137,7 +7123,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -301,7 +315,7 @@ END LOOP; -- What extra records do we have? -@@ -7310,7 +7291,7 @@ +@@ -7305,7 +7291,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -310,7 +324,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT -@@ -7328,7 +7309,7 @@ +@@ -7323,7 +7309,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -319,7 +333,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) ), -@@ -7361,7 +7342,7 @@ +@@ -7356,7 +7342,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -328,7 +342,7 @@ 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']) ) -@@ -7380,7 +7361,7 @@ +@@ -7375,7 +7361,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -337,7 +351,7 @@ 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']) ) -@@ -7664,10 +7645,12 @@ +@@ -7659,10 +7645,12 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; @@ -353,7 +367,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7814,7 +7797,7 @@ +@@ -7809,7 +7797,7 @@ CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) RETURNS TEXT AS $$ @@ -362,7 +376,7 @@ $$ LANGUAGE SQL; -- operators_are( schema, operators[], description ) -@@ -7823,7 +7806,7 @@ +@@ -7818,7 +7806,7 @@ SELECT _areni( 'operators', ARRAY( @@ -371,7 +385,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7835,7 +7818,7 @@ +@@ -7830,7 +7818,7 @@ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT @@ -380,7 +394,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7856,7 +7839,7 @@ +@@ -7851,7 +7839,7 @@ SELECT _areni( 'operators', ARRAY( @@ -389,7 +403,7 @@ 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) -@@ -7869,7 +7852,7 @@ +@@ -7864,7 +7852,7 @@ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT @@ -398,7 +412,7 @@ 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) -@@ -8582,40 +8565,6 @@ +@@ -8577,40 +8565,6 @@ ); $$ LANGUAGE sql; diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index 7ae5ef2c7e1e..0f6635cc677e 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -11,8 +11,8 @@ + CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ - SELECT substring(s.a[1] FROM '[[:digit:]]+')::int * 10000 -@@ -6817,7 +6822,7 @@ + SELECT current_setting('server_version_num')::integer; +@@ -6812,7 +6817,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -21,7 +21,7 @@ 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' || -@@ -6974,7 +6979,7 @@ +@@ -6969,7 +6974,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -30,7 +30,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -7183,13 +7188,7 @@ +@@ -7178,13 +7183,7 @@ $$ LANGUAGE sql; -- collect_tap( tap, tap, tap ) @@ -45,7 +45,7 @@ RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); $$ LANGUAGE sql; -@@ -7665,7 +7664,7 @@ +@@ -7660,7 +7659,7 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; diff --git a/compat/install-8.4.patch b/compat/install-8.4.patch index d7cb517f0c68..6740590cd472 100644 --- a/compat/install-8.4.patch +++ b/compat/install-8.4.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -7691,7 +7691,6 @@ +@@ -7686,7 +7686,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -8,7 +8,7 @@ EXCEPT SELECT $3[i] FROM generate_series(1, array_upper($3, 1)) s(i) -@@ -7706,7 +7705,6 @@ +@@ -7701,7 +7700,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -16,7 +16,7 @@ ), $4 ); -@@ -7730,7 +7728,6 @@ +@@ -7725,7 +7723,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = $1 AND n.nspname NOT IN ('pg_catalog', 'information_schema') @@ -24,7 +24,7 @@ EXCEPT SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) -@@ -7744,7 +7741,6 @@ +@@ -7739,7 +7736,6 @@ 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') diff --git a/compat/install-9.0.patch b/compat/install-9.0.patch index 7301a4c2e1bd..2433abf23b54 100644 --- a/compat/install-9.0.patch +++ b/compat/install-9.0.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -3696,7 +3696,7 @@ +@@ -3691,7 +3691,7 @@ AND n.nspname = $1 AND t.typname = $2 AND t.typtype = 'e' @@ -9,7 +9,7 @@ ), $3, $4 -@@ -3724,7 +3724,7 @@ +@@ -3719,7 +3719,7 @@ AND pg_catalog.pg_type_is_visible(t.oid) AND t.typname = $1 AND t.typtype = 'e' @@ -18,7 +18,7 @@ ), $2, $3 -@@ -6168,7 +6168,7 @@ +@@ -6163,7 +6163,7 @@ CREATE OR REPLACE FUNCTION findfuncs( NAME, TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( @@ -27,7 +27,7 @@ FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = $1 -@@ -6185,7 +6185,7 @@ +@@ -6180,7 +6180,7 @@ CREATE OR REPLACE FUNCTION findfuncs( TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( @@ -36,7 +36,7 @@ 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) -@@ -9687,137 +9687,6 @@ +@@ -9682,137 +9682,6 @@ GRANT SELECT ON tap_funky TO PUBLIC; GRANT SELECT ON pg_all_foreign_keys TO PUBLIC; diff --git a/compat/install-9.1.patch b/compat/install-9.1.patch index ea6b68d78a92..054f1df67657 100644 --- a/compat/install-9.1.patch +++ b/compat/install-9.1.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -781,10 +781,6 @@ +@@ -776,10 +776,6 @@ RETURN ok( TRUE, descr ); EXCEPTION WHEN OTHERS THEN -- There should have been no exception. @@ -11,7 +11,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6372,10 +6368,6 @@ +@@ -6367,10 +6363,6 @@ -- Something went wrong. Record that fact. errstate := SQLSTATE; errmsg := SQLERRM; diff --git a/compat/install-9.2.patch b/compat/install-9.2.patch index 44985237f334..8d8c7caa9b0e 100644 --- a/compat/install-9.2.patch +++ b/compat/install-9.2.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -784,12 +784,7 @@ +@@ -779,12 +779,7 @@ GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, hint = PG_EXCEPTION_HINT, @@ -14,7 +14,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6380,12 +6375,7 @@ +@@ -6375,12 +6370,7 @@ GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, hint = PG_EXCEPTION_HINT, diff --git a/compat/install-9.4.patch b/compat/install-9.4.patch index 31a89c7072dc..7c337c2d26a2 100644 --- a/compat/install-9.4.patch +++ b/compat/install-9.4.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -675,7 +675,7 @@ +@@ -670,7 +670,7 @@ ' caught: no exception' || E'\n wanted: ' || COALESCE( errcode, 'an exception' ) ); @@ -9,7 +9,7 @@ IF (errcode IS NULL OR SQLSTATE = errcode) AND ( errmsg IS NULL OR SQLERRM = errmsg) THEN -@@ -779,7 +779,7 @@ +@@ -774,7 +774,7 @@ BEGIN EXECUTE code; RETURN ok( TRUE, descr ); diff --git a/compat/install-9.6.patch b/compat/install-9.6.patch index c9e7dacd25e1..3472d11c095f 100644 --- a/compat/install-9.6.patch +++ b/compat/install-9.6.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -9895,133 +9895,3 @@ +@@ -9890,133 +9890,3 @@ 'Table ' || quote_ident($1) || ' should not be partitioned' ); $$ LANGUAGE sql; diff --git a/sql/pgtap--0.98.0--0.99.0.sql b/sql/pgtap--0.98.0--0.99.0.sql new file mode 100644 index 000000000000..eda651e42650 --- /dev/null +++ b/sql/pgtap--0.98.0--0.99.0.sql @@ -0,0 +1,8 @@ +CREATE OR REPLACE FUNCTION pgtap_version() +RETURNS NUMERIC AS 'SELECT 0.99;' +LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE FUNCTION pg_version_num() +RETURNS integer AS $$ + SELECT current_setting('server_version_num')::integer; +$$ LANGUAGE SQL IMMUTABLE; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index fce5da9d8c0f..784a44c5b536 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -11,12 +11,7 @@ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ - 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; + SELECT current_setting('server_version_num')::integer; $$ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION os_name() diff --git a/test/expected/util.out b/test/expected/util.out index 2fca170c4cf8..a22e4535b960 100644 --- a/test/expected/util.out +++ b/test/expected/util.out @@ -6,7 +6,7 @@ ok 3 - pg_type(text) should work ok 4 - pg_version() should return text ok 5 - pg_version() should return same as "server_version" setting ok 6 - pg_version() should work -ok 7 - pg_version() should return same as "server_version" setting +ok 7 - pg_version_num() should return same as "server_version_num" setting ok 8 - pg_version_num() should return integer ok 9 - pg_version_num() should be correct ok 10 - os_name() should output something like an OS name diff --git a/test/sql/util.sql b/test/sql/util.sql index 600be48540b3..56193efcb696 100644 --- a/test/sql/util.sql +++ b/test/sql/util.sql @@ -24,11 +24,11 @@ SELECT matches( ); SELECT CASE WHEN pg_version_num() < 81000 - THEN pass( 'pg_version() should return same as "server_version" setting' ) + THEN pass( 'pg_version_num() should return same as "server_version_num" setting' ) ELSE is( pg_version_num(), - current_setting( 'server_version_num')::integer, - 'pg_version() should return same as "server_version" setting' + current_setting('server_version_num')::integer, + 'pg_version_num() should return same as "server_version_num" setting' ) END; From 5b015b59259730b14de34c5c6d5235eeeeb53838 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 13 Nov 2017 15:45:18 -0500 Subject: [PATCH 0987/1195] Credit Christoph. --- Changes | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index ca79465270d9..549a92104756 100644 --- a/Changes +++ b/Changes @@ -7,7 +7,8 @@ Revision history for pgTAP * Added the "Secrets of the pgTAP Mavens" section of the documentation. Contributions wanted! * Fixed `pg_version_num()` to always return the value of `server_version_num` - on PostreSQL 8.3 and higher, thus fixing a test failure on 10.1. + on PostreSQL 8.3 and higher, thus fixing a test failure on 10.1. Thanks to + Christoph Berg for the report (#148)! 0.98.0 2017-11-06T22:30:54Z --------------------------- From 2ede30f48353fe4653c086143cedfc8e3d63ab81 Mon Sep 17 00:00:00 2001 From: Anton Lvov Date: Sun, 21 Jan 2018 22:49:41 +0500 Subject: [PATCH 0988/1195] Documentation typo fix (is/isnt functions description) --- doc/pgtap.mmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 6b0a456cdabe..e348b63f052d 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -466,8 +466,8 @@ the result of that to determine if the test succeeded or failed. So these: are similar to these: - SELECT ok( ultimate_answer() = 42, 'Meaning of Life' ); - SELECT isnt( foo() <> '', 'Got some foo' ); + SELECT ok( ultimate_answer() = 42, 'Meaning of Life' ); + SELECT ok( foo() <> '', 'Got some foo' ); (Mnemonic: "This is that." "This isn't that.") From 57c6cb2f2a145b276f8d82be8ce7ad421ce25f96 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 26 Mar 2018 15:14:38 -0400 Subject: [PATCH 0989/1195] Update mail list URL. --- doc/pgtap.mmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index e348b63f052d..f72bc403a679 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -8046,7 +8046,7 @@ Mail List --------- Join the pgTAP community by subscribing to the [pgtap-users mail -list](http://pgfoundry.org/mailman/listinfo/pgtap-users). All questions, +list](https://groups.google.com/forum/#!forum/pgtap-users). All questions, comments, suggestions, and bug reports are welcomed there. Author From e633b44143f1cbd38479bcb85f6a36826fa8ebd8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 26 Mar 2018 23:33:16 -0400 Subject: [PATCH 0990/1195] Sort extras and missing. By their values, rather than their placement in the array. Fixes #158. --- Changes | 6 ++- sql/pgtap--0.98.0--0.99.0.sql | 77 +++++++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 26 ++++++++++-- test/sql/partitions.sql | 4 +- 4 files changed, 106 insertions(+), 7 deletions(-) diff --git a/Changes b/Changes index 549a92104756..9c7fd652f571 100644 --- a/Changes +++ b/Changes @@ -7,8 +7,12 @@ Revision history for pgTAP * Added the "Secrets of the pgTAP Mavens" section of the documentation. Contributions wanted! * Fixed `pg_version_num()` to always return the value of `server_version_num` - on PostreSQL 8.3 and higher, thus fixing a test failure on 10.1. Thanks to + on PostgreSQL 8.3 and higher, thus fixing a test failure on 10.1. Thanks to Christoph Berg for the report (#148)! +* Changed the diagnostic output of extra and missing values from the `*_are()` + functions to sort the extra and missing values. Fixes a test failure on + PostgreSQL 10, and generally makes the diagnostics more deterministic and + easier to read and. Thanks to Michael Glaesemann for the report (#158)! 0.98.0 2017-11-06T22:30:54Z --------------------------- diff --git a/sql/pgtap--0.98.0--0.99.0.sql b/sql/pgtap--0.98.0--0.99.0.sql index eda651e42650..36ad40a42673 100644 --- a/sql/pgtap--0.98.0--0.99.0.sql +++ b/sql/pgtap--0.98.0--0.99.0.sql @@ -6,3 +6,80 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT current_setting('server_version_num')::integer; $$ LANGUAGE SQL IMMUTABLE; + +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; + +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; + +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; + diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 784a44c5b536..352943820061 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -4330,14 +4330,14 @@ BEGIN res = FALSE; msg := E'\n' || diag( ' Extra ' || what || E':\n ' - || _ident_array_to_string( extras, 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_string( missing, E'\n ' ) + || _ident_array_to_sorted_string( missing, E'\n ' ) ); END IF; @@ -7783,14 +7783,14 @@ BEGIN res = FALSE; msg := E'\n' || diag( ' Extra ' || what || E':\n ' - || array_to_string( extras, 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_string( missing, E'\n ' ) + || _array_to_sorted_string( missing, E'\n ' ) ); END IF; @@ -10020,3 +10020,21 @@ RETURNS TEXT AS $$ '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; \ No newline at end of file diff --git a/test/sql/partitions.sql b/test/sql/partitions.sql index 237733efae81..54c704bed79d 100644 --- a/test/sql/partitions.sql +++ b/test/sql/partitions.sql @@ -300,9 +300,9 @@ SELECT * FROM check_test( 'partitions_are( hidden tab, parts, desc )', 'hi', ' Missing partitions: - not_hidden_part3 + "hide.hidden_part1" "hide.hidden_part2" - "hide.hidden_part1"' + not_hidden_part3' ); -- Should not work for unpartitioned but inherited table From ba2236cfd0bbb89094b1232159df3bcdc1f0de83 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 26 Mar 2018 23:41:03 -0400 Subject: [PATCH 0991/1195] Update 9.6 patch. --- compat/install-9.6.patch | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/compat/install-9.6.patch b/compat/install-9.6.patch index 3472d11c095f..fb3d14ce5053 100644 --- a/compat/install-9.6.patch +++ b/compat/install-9.6.patch @@ -1,10 +1,9 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -9890,133 +9890,3 @@ - 'Table ' || quote_ident($1) || ' should not be partitioned' +@@ -9891,136 +9891,6 @@ ); $$ LANGUAGE sql; -- + --- _partof( child_schema, child_table, parent_schema, parent_table ) -CREATE OR REPLACE FUNCTION _partof ( NAME, NAME, NAME, NAME ) -RETURNS BOOLEAN AS $$ @@ -134,3 +133,7 @@ - '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( From 112fdeee1337b13065f96c340547bbb93aac3fcb Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 2 Apr 2018 09:44:56 -0400 Subject: [PATCH 0992/1195] Add missing BEING/END to PL/pgSQL function. And add a comment about the hypothetical `users` table. Resolves #163. --- doc/pgtap.mmd | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index f72bc403a679..323f10e1bbf6 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -305,17 +305,19 @@ What a sweet unit! If you're used to xUnit testing frameworks, you can collect all of your tests into database functions and run them all at once with `runtests()`. This is -similar to how [PGUnit](http://en.dklab.ru/lib/dklab_pgunit/)s. The +similar to how [PGUnit](http://en.dklab.ru/lib/dklab_pgunit/) works. The `runtests()` function does all the work of finding and running your test functions in individual transactions. It even supports setup and teardown functions. To use it, write your unit test functions so that they return a set of text results, and then use the pgTAP assertion functions to return TAP -values, like so: +values. Here's an example, testing a hypothetical `users` table: CREATE OR REPLACE FUNCTION setup_insert( ) RETURNS SETOF TEXT AS $$ + BEGIN RETURN NEXT is( MAX(nick), NULL, 'Should have no users') FROM users; INSERT INTO users (nick) VALUES ('theory'); + END; $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION test_user( From 49b2efa43028a0b81e078d16d021c502dbb50bcd Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 26 Jul 2018 18:21:29 -0400 Subject: [PATCH 0993/1195] Use pg_index to see if a table has a primary key. Fixes an issue on PostgreSQL 11, where the pg_class.relhaspkey column has been removed. Closes #174. --- sql/pgtap--0.98.0--0.99.0.sql | 30 ++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 6 ++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/sql/pgtap--0.98.0--0.99.0.sql b/sql/pgtap--0.98.0--0.99.0.sql index 36ad40a42673..970021811cd0 100644 --- a/sql/pgtap--0.98.0--0.99.0.sql +++ b/sql/pgtap--0.98.0--0.99.0.sql @@ -83,3 +83,33 @@ BEGIN END; $$ LANGUAGE plpgsql; +-- _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 + JOIN pg_catalog.pg_index i ON c.oid = i.indrelid + WHERE i.indisprimary = true + AND 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 + JOIN pg_catalog.pg_index i ON c.oid = i.indrelid + WHERE i.indisprimary = true + AND pg_table_is_visible(c.oid) + AND c.relname = $1 + AND x.contype = $2 + ); +$$ LANGUAGE sql; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 352943820061..c35fcc1ba2b9 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1759,7 +1759,8 @@ RETURNS BOOLEAN AS $$ 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 c.relhaspkey = true + JOIN pg_catalog.pg_index i ON c.oid = i.indrelid + WHERE i.indisprimary = true AND n.nspname = $1 AND c.relname = $2 AND x.contype = $3 @@ -1773,7 +1774,8 @@ RETURNS BOOLEAN AS $$ SELECT true FROM pg_catalog.pg_class c JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid - WHERE c.relhaspkey = true + JOIN pg_catalog.pg_index i ON c.oid = i.indrelid + WHERE i.indisprimary = true AND pg_table_is_visible(c.oid) AND c.relname = $1 AND x.contype = $2 From 081fe1a9ba2ab4dde1ce75c4f8cb321d9b6b4f0d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 26 Jul 2018 18:22:34 -0400 Subject: [PATCH 0994/1195] Note fix for bug #174. --- Changes | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Changes b/Changes index 9c7fd652f571..a312b717e2d0 100644 --- a/Changes +++ b/Changes @@ -13,6 +13,10 @@ Revision history for pgTAP functions to sort the extra and missing values. Fixes a test failure on PostgreSQL 10, and generally makes the diagnostics more deterministic and easier to read and. Thanks to Michael Glaesemann for the report (#158)! +* Fixed an issue on PostgreSQL 11, where the `pg_class.relhaspkey` column has + been removed. The primary key testing functions now refer to + `pg_index.indisprimary`, instead. Thanks to Keith Fiske for the report + (#174)! 0.98.0 2017-11-06T22:30:54Z --------------------------- From e7f76863290705c7155501c72152ea1593ed562d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 26 Jul 2018 19:01:20 -0400 Subject: [PATCH 0995/1195] Adjust patch offsets. --- compat/install-8.1.patch | 18 ++++++++-------- compat/install-8.2.patch | 46 ++++++++++++++++++++-------------------- compat/install-8.3.patch | 8 +++---- compat/install-8.4.patch | 8 +++---- compat/install-9.0.patch | 10 ++++----- compat/install-9.1.patch | 2 +- compat/install-9.2.patch | 2 +- compat/install-9.6.patch | 2 +- 8 files changed, 48 insertions(+), 48 deletions(-) diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index bafeffc26cc4..2bd27f4ad565 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -2268,13 +2268,13 @@ +@@ -2270,13 +2270,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -18,7 +18,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $6 || ' constraints'; -@@ -2292,13 +2292,13 @@ +@@ -2294,13 +2294,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -36,7 +36,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $5 || ' constraints'; -@@ -6188,7 +6188,7 @@ +@@ -6190,7 +6190,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -45,7 +45,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -6196,8 +6196,8 @@ +@@ -6198,8 +6198,8 @@ -- 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. @@ -56,7 +56,7 @@ END LOOP; END LOOP; RETURN; -@@ -6266,7 +6266,7 @@ +@@ -6268,7 +6268,7 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -65,7 +65,7 @@ tfaild INTEGER := 0; ffaild INTEGER := 0; tnumb INTEGER := 0; -@@ -6276,7 +6276,7 @@ +@@ -6278,7 +6278,7 @@ BEGIN -- No plan support. PERFORM * FROM no_plan(); @@ -74,7 +74,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -6315,18 +6315,18 @@ +@@ -6317,18 +6317,18 @@ BEGIN BEGIN -- Run the setup functions. @@ -99,7 +99,7 @@ END LOOP; -- Emit the plan. -@@ -6382,11 +6382,11 @@ +@@ -6384,11 +6384,11 @@ END LOOP; -- Run the shutdown functions. @@ -114,7 +114,7 @@ END LOOP; -- Clean up and return. -@@ -7644,7 +7644,7 @@ +@@ -7646,7 +7646,7 @@ DECLARE rec RECORD; BEGIN diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 7ed8fd1742f1..881e41bf485f 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -117,7 +117,7 @@ ), ''); $$ LANGUAGE sql IMMUTABLE; -@@ -2478,7 +2521,7 @@ +@@ -2480,7 +2523,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 @@ -126,7 +126,7 @@ p.prolang AS langoid, p.proisstrict AS is_strict, p.proisagg AS is_agg, -@@ -3683,63 +3726,6 @@ +@@ -3685,63 +3728,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -190,7 +190,7 @@ CREATE OR REPLACE FUNCTION _has_role( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( -@@ -6330,17 +6316,17 @@ +@@ -6332,17 +6318,17 @@ BEGIN -- Run the setup functions. FOR tap IN SELECT * FROM _runem(setup, false) LOOP @@ -211,7 +211,7 @@ END LOOP; -- Emit the plan. -@@ -6379,7 +6365,7 @@ +@@ -6381,7 +6367,7 @@ tok := FALSE; RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname @@ -220,7 +220,7 @@ errmsg := NULL; END IF; END; -@@ -6492,13 +6478,13 @@ +@@ -6494,13 +6480,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -236,7 +236,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6722,7 +6708,7 @@ +@@ -6724,7 +6710,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -245,7 +245,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6817,11 +6803,11 @@ +@@ -6819,11 +6805,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -260,7 +260,7 @@ ); END IF; rownum = rownum + 1; -@@ -6836,9 +6822,9 @@ +@@ -6838,9 +6824,9 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Number of columns or their types differ between the queries' || @@ -273,7 +273,7 @@ END ); END; -@@ -6974,7 +6960,7 @@ +@@ -6976,7 +6962,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -282,7 +282,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6988,8 +6974,8 @@ +@@ -6990,8 +6976,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -293,7 +293,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7114,9 +7100,9 @@ +@@ -7116,9 +7102,9 @@ DECLARE typeof regtype := pg_typeof($1); BEGIN @@ -306,7 +306,7 @@ END; $$ LANGUAGE plpgsql; -@@ -7137,7 +7123,7 @@ +@@ -7139,7 +7125,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -315,7 +315,7 @@ END LOOP; -- What extra records do we have? -@@ -7305,7 +7291,7 @@ +@@ -7307,7 +7293,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -324,7 +324,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT -@@ -7323,7 +7309,7 @@ +@@ -7325,7 +7311,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -333,7 +333,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) ), -@@ -7356,7 +7342,7 @@ +@@ -7358,7 +7344,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -342,7 +342,7 @@ 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']) ) -@@ -7375,7 +7361,7 @@ +@@ -7377,7 +7363,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -351,7 +351,7 @@ 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']) ) -@@ -7659,10 +7645,12 @@ +@@ -7661,10 +7647,12 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; @@ -367,7 +367,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7809,7 +7797,7 @@ +@@ -7811,7 +7799,7 @@ CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) RETURNS TEXT AS $$ @@ -376,7 +376,7 @@ $$ LANGUAGE SQL; -- operators_are( schema, operators[], description ) -@@ -7818,7 +7806,7 @@ +@@ -7820,7 +7808,7 @@ SELECT _areni( 'operators', ARRAY( @@ -385,7 +385,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7830,7 +7818,7 @@ +@@ -7832,7 +7820,7 @@ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT @@ -394,7 +394,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7851,7 +7839,7 @@ +@@ -7853,7 +7841,7 @@ SELECT _areni( 'operators', ARRAY( @@ -403,7 +403,7 @@ 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) -@@ -7864,7 +7852,7 @@ +@@ -7866,7 +7854,7 @@ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT @@ -412,7 +412,7 @@ 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) -@@ -8577,40 +8565,6 @@ +@@ -8579,40 +8567,6 @@ ); $$ LANGUAGE sql; diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index 0f6635cc677e..969d95d298f1 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -12,7 +12,7 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT current_setting('server_version_num')::integer; -@@ -6812,7 +6817,7 @@ +@@ -6814,7 +6819,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -21,7 +21,7 @@ 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' || -@@ -6969,7 +6974,7 @@ +@@ -6971,7 +6976,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -30,7 +30,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -7178,13 +7183,7 @@ +@@ -7180,13 +7185,7 @@ $$ LANGUAGE sql; -- collect_tap( tap, tap, tap ) @@ -45,7 +45,7 @@ RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); $$ LANGUAGE sql; -@@ -7660,7 +7659,7 @@ +@@ -7662,7 +7661,7 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; diff --git a/compat/install-8.4.patch b/compat/install-8.4.patch index 6740590cd472..13b2521e800c 100644 --- a/compat/install-8.4.patch +++ b/compat/install-8.4.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -7686,7 +7686,6 @@ +@@ -7688,7 +7688,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -8,7 +8,7 @@ EXCEPT SELECT $3[i] FROM generate_series(1, array_upper($3, 1)) s(i) -@@ -7701,7 +7700,6 @@ +@@ -7703,7 +7702,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -16,7 +16,7 @@ ), $4 ); -@@ -7725,7 +7723,6 @@ +@@ -7727,7 +7725,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = $1 AND n.nspname NOT IN ('pg_catalog', 'information_schema') @@ -24,7 +24,7 @@ EXCEPT SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) -@@ -7739,7 +7736,6 @@ +@@ -7741,7 +7738,6 @@ 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') diff --git a/compat/install-9.0.patch b/compat/install-9.0.patch index 2433abf23b54..ed252feddb3d 100644 --- a/compat/install-9.0.patch +++ b/compat/install-9.0.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -3691,7 +3691,7 @@ +@@ -3693,7 +3693,7 @@ AND n.nspname = $1 AND t.typname = $2 AND t.typtype = 'e' @@ -9,7 +9,7 @@ ), $3, $4 -@@ -3719,7 +3719,7 @@ +@@ -3721,7 +3721,7 @@ AND pg_catalog.pg_type_is_visible(t.oid) AND t.typname = $1 AND t.typtype = 'e' @@ -18,7 +18,7 @@ ), $2, $3 -@@ -6163,7 +6163,7 @@ +@@ -6165,7 +6165,7 @@ CREATE OR REPLACE FUNCTION findfuncs( NAME, TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( @@ -27,7 +27,7 @@ FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = $1 -@@ -6180,7 +6180,7 @@ +@@ -6182,7 +6182,7 @@ CREATE OR REPLACE FUNCTION findfuncs( TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( @@ -36,7 +36,7 @@ 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) -@@ -9682,137 +9682,6 @@ +@@ -9684,137 +9684,6 @@ GRANT SELECT ON tap_funky TO PUBLIC; GRANT SELECT ON pg_all_foreign_keys TO PUBLIC; diff --git a/compat/install-9.1.patch b/compat/install-9.1.patch index 054f1df67657..22992665e6ab 100644 --- a/compat/install-9.1.patch +++ b/compat/install-9.1.patch @@ -11,7 +11,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6367,10 +6363,6 @@ +@@ -6369,10 +6365,6 @@ -- Something went wrong. Record that fact. errstate := SQLSTATE; errmsg := SQLERRM; diff --git a/compat/install-9.2.patch b/compat/install-9.2.patch index 8d8c7caa9b0e..59ff9fa5b87c 100644 --- a/compat/install-9.2.patch +++ b/compat/install-9.2.patch @@ -14,7 +14,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6375,12 +6370,7 @@ +@@ -6377,12 +6372,7 @@ GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, hint = PG_EXCEPTION_HINT, diff --git a/compat/install-9.6.patch b/compat/install-9.6.patch index fb3d14ce5053..b7af3c71a281 100644 --- a/compat/install-9.6.patch +++ b/compat/install-9.6.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -9891,136 +9891,6 @@ +@@ -9893,136 +9893,6 @@ ); $$ LANGUAGE sql; From 15858e222776b904c2c852ec4cba751182738288 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 28 Jul 2018 10:05:03 -0400 Subject: [PATCH 0996/1195] Use pg_proc.prokind on Postgres 11. And add a patch to restore pre-11 compatability. Closes #174. --- Changes | 3 +++ Makefile | 6 ++++- compat/install-10.patch | 44 ++++++++++++++++++++++++++++++++ sql/pgtap--0.98.0--0.99.0.sql | 47 +++++++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 10 ++++---- 5 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 compat/install-10.patch diff --git a/Changes b/Changes index a312b717e2d0..34871f976b60 100644 --- a/Changes +++ b/Changes @@ -17,6 +17,9 @@ Revision history for pgTAP been removed. The primary key testing functions now refer to `pg_index.indisprimary`, instead. Thanks to Keith Fiske for the report (#174)! +* Fixed an issue on PostgreSQL 11, where the `pg_proc.proisagg` column has + been replaced with the more generically useful `pg_proc.prokind`. Thanks to + Keith Fiske for the report (#174)! 0.98.0 2017-11-06T22:30:54Z --------------------------- diff --git a/Makefile b/Makefile index ecba1a438ce6..fad798fa25e7 100644 --- a/Makefile +++ b/Makefile @@ -113,7 +113,10 @@ OSNAME := $(shell $(SHELL) ./getos.sh) sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql cp $< $@ -ifeq ($(shell echo $(VERSION) | grep -qE "[98][.]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "([98]|10)[.]" && echo yes || echo no),yes) + patch -p0 < compat/install-10.patch +endif +ifeq ($(shell echo $(VERSION) | grep -qE "[98][012345][.]" && echo yes || echo no),yes) patch -p0 < compat/install-9.6.patch endif ifeq ($(shell echo $(VERSION) | grep -qE "9[.][01234]|8[.][1234]" && echo yes || echo no),yes) @@ -173,6 +176,7 @@ sql/uninstall_pgtap.sql: sql/pgtap.sql test/setup.sql sql/pgtap-static.sql: sql/pgtap.sql.in cp $< $@ + sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-10.patch | patch -p0 sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-9.6.patch | patch -p0 sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-9.4.patch | patch -p0 sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-9.2.patch | patch -p0 diff --git a/compat/install-10.patch b/compat/install-10.patch new file mode 100644 index 000000000000..efe6375177ce --- /dev/null +++ b/compat/install-10.patch @@ -0,0 +1,44 @@ +--- sql/pgtap.sql ++++ sql/pgtap.sql +@@ -2487,7 +2487,7 @@ + || p.prorettype::regtype AS returns, + p.prolang AS langoid, + p.proisstrict AS is_strict, +- p.prokind AS kind, ++ p.proisagg AS is_agg, + p.prosecdef AS is_definer, + p.proretset AS returns_set, + p.provolatile::char AS volatility, +@@ -5651,7 +5651,7 @@ + + CREATE OR REPLACE FUNCTION _agg ( NAME, NAME, NAME[] ) + RETURNS BOOLEAN AS $$ +- SELECT kind = 'a' ++ SELECT is_agg + FROM tap_funky + WHERE schema = $1 + AND name = $2 +@@ -5660,12 +5660,12 @@ + + CREATE OR REPLACE FUNCTION _agg ( NAME, NAME ) + RETURNS BOOLEAN AS $$ +- SELECT kind = 'a' FROM tap_funky WHERE schema = $1 AND name = $2 ++ SELECT is_agg FROM tap_funky WHERE schema = $1 AND name = $2 + $$ LANGUAGE SQL; + + CREATE OR REPLACE FUNCTION _agg ( NAME, NAME[] ) + RETURNS BOOLEAN AS $$ +- SELECT kind = 'a' ++ SELECT is_agg + FROM tap_funky + WHERE name = $1 + AND args = array_to_string($2, ',') +@@ -5674,7 +5674,7 @@ + + CREATE OR REPLACE FUNCTION _agg ( NAME ) + RETURNS BOOLEAN AS $$ +- SELECT kind = 'a' FROM tap_funky WHERE name = $1 AND is_visible; ++ SELECT is_agg FROM tap_funky WHERE name = $1 AND is_visible; + $$ LANGUAGE SQL; + + -- is_aggregate( schema, function, args[], description ) diff --git a/sql/pgtap--0.98.0--0.99.0.sql b/sql/pgtap--0.98.0--0.99.0.sql index 970021811cd0..7775221b9e05 100644 --- a/sql/pgtap--0.98.0--0.99.0.sql +++ b/sql/pgtap--0.98.0--0.99.0.sql @@ -113,3 +113,50 @@ RETURNS BOOLEAN AS $$ AND x.contype = $2 ); $$ LANGUAGE sql; + +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, + p.prokind 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 _agg ( NAME, NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT kind = 'a' + FROM tap_funky + WHERE schema = $1 + AND name = $2 + AND args = array_to_string($3, ',') +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _agg ( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT kind = 'a' FROM tap_funky WHERE schema = $1 AND name = $2 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _agg ( NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT kind = 'a' + FROM tap_funky + WHERE name = $1 + AND args = array_to_string($2, ',') + AND is_visible; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _agg ( NAME ) +RETURNS BOOLEAN AS $$ + SELECT kind = 'a' FROM tap_funky WHERE name = $1 AND is_visible; +$$ LANGUAGE SQL; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index c35fcc1ba2b9..daa3934d8db4 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2487,7 +2487,7 @@ CREATE OR REPLACE VIEW tap_funky || p.prorettype::regtype AS returns, p.prolang AS langoid, p.proisstrict AS is_strict, - p.proisagg AS is_agg, + p.prokind AS kind, p.prosecdef AS is_definer, p.proretset AS returns_set, p.provolatile::char AS volatility, @@ -5651,7 +5651,7 @@ $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _agg ( NAME, NAME, NAME[] ) RETURNS BOOLEAN AS $$ - SELECT is_agg + SELECT kind = 'a' FROM tap_funky WHERE schema = $1 AND name = $2 @@ -5660,12 +5660,12 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _agg ( NAME, NAME ) RETURNS BOOLEAN AS $$ - SELECT is_agg FROM tap_funky WHERE schema = $1 AND name = $2 + SELECT kind = 'a' FROM tap_funky WHERE schema = $1 AND name = $2 $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _agg ( NAME, NAME[] ) RETURNS BOOLEAN AS $$ - SELECT is_agg + SELECT kind = 'a' FROM tap_funky WHERE name = $1 AND args = array_to_string($2, ',') @@ -5674,7 +5674,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _agg ( NAME ) RETURNS BOOLEAN AS $$ - SELECT is_agg FROM tap_funky WHERE name = $1 AND is_visible; + SELECT kind = 'a' FROM tap_funky WHERE name = $1 AND is_visible; $$ LANGUAGE SQL; -- is_aggregate( schema, function, args[], description ) From 4f7bbcf3ab948c9a5af758b9c69f47fffe65c3e9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 28 Jul 2018 11:52:47 -0400 Subject: [PATCH 0997/1195] Fix failing Postgres 11 test with bare RECORD argument. It is now supported for PL/pgSQL functions. So test for that, and document it. Also, remove old 8.0 tests from the record testing, since we no longer support 8.0. --- Changes | 2 + doc/pgtap.mmd | 15 ++-- test/expected/resultset.out | 5 +- test/sql/resultset.sql | 170 ++++++++++++++++-------------------- 4 files changed, 93 insertions(+), 99 deletions(-) diff --git a/Changes b/Changes index 34871f976b60..906dc5bc3369 100644 --- a/Changes +++ b/Changes @@ -20,6 +20,8 @@ Revision history for pgTAP * Fixed an issue on PostgreSQL 11, where the `pg_proc.proisagg` column has been replaced with the more generically useful `pg_proc.prokind`. Thanks to Keith Fiske for the report (#174)! +* Documented that a bar `RECORD` can be passed to `row_eq()` on PostgreSQL 11, + and fixed the tests to match this new functionality. 0.98.0 2017-11-06T22:30:54Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 323f10e1bbf6..20e671bd2ee5 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1636,11 +1636,16 @@ test fails. `:description` : A short description of the test. -Compares the contents of a single row to a record. Due to the limitations of -non-C functions in PostgreSQL, a bare `RECORD` value cannot be passed to the -function. You must instead pass in a valid composite type value, and cast the -record argument (the second argument) to the same type. Both explicitly -created composite types and table types are supported. Thus, you can do this: +Compares the contents of a single row to a record. On PostgreSQL 11 and later, a +bare `RECORD` value may be passed: + + SELECT row_eq( $$ SELECT 1, 'foo' $$, ROW(1, 'foo') ); + +ue to the limitations of non-C functions in earlier versions of PostgreSQL, a +bare `RECORD` value cannot be passed to the function. You must instead pass in a +valid composite type value, and cast the record argument (the second argument) +to the same type. Both explicitly created composite types and table types are +supported. Thus, you can do this: CREATE TYPE sometype AS ( id INT, diff --git a/test/expected/resultset.out b/test/expected/resultset.out index 5b994441d917..42ce58bcb45d 100644 --- a/test/expected/resultset.out +++ b/test/expected/resultset.out @@ -1,5 +1,5 @@ \unset ECHO -1..542 +1..545 ok 1 - Should create temp table with simple query ok 2 - Table __foonames__ should exist ok 3 - Should create a temp table for a prepared statement @@ -542,3 +542,6 @@ ok 539 - row_eq(sqlrow, sometype, desc) should pass ok 540 - row_eq(sqlrow, sometype, desc) should have the proper description ok 541 - row_eq(sqlrow, sometype, desc) should have the proper diagnostics ok 542 - threw 0A000 +ok 543 - row_eq(qry, record) should pass +ok 544 - row_eq(qry, record) should have the proper description +ok 545 - row_eq(qry, record) should have the proper diagnostics diff --git a/test/sql/resultset.sql b/test/sql/resultset.sql index 3970cf385e46..094764b9b997 100644 --- a/test/sql/resultset.sql +++ b/test/sql/resultset.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(542); +SELECT plan(545); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -2440,106 +2440,90 @@ CREATE FUNCTION test_row_eq() RETURNS SETOF TEXT AS $$ DECLARE tap record; BEGIN - IF pg_version_num() < 80100 THEN - -- Can't do shit with records on 8.0 - RETURN NEXT pass('row_eq(prepared, record, desc) should pass'); - RETURN NEXT pass('row_eq(prepared, record, desc) should have the proper description'); - RETURN NEXT pass('row_eq(prepared, record, desc) should have the proper diagnostics'); - RETURN NEXT pass('row_eq(sql, record, desc) should pass'); - RETURN NEXT pass('row_eq(sql, record, desc) should have the proper description'); - RETURN NEXT pass('row_eq(sql, record, desc) should have the proper diagnostics'); - RETURN NEXT pass('row_eq(prepared, record, desc) should pass'); - RETURN NEXT pass('row_eq(prepared, record, desc) should have the proper description'); - RETURN NEXT pass('row_eq(prepared, record, desc) should have the proper diagnostics'); - RETURN NEXT pass('row_eq(prepared, record, desc) should fail'); - RETURN NEXT pass('row_eq(prepared, record, desc) should have the proper description'); - RETURN NEXT pass('row_eq(prepared, record, desc) should have the proper diagnostics'); - RETURN NEXT pass('row_eq(prepared, sometype, desc) should pass'); - RETURN NEXT pass('row_eq(prepared, sometype, desc) should have the proper description'); - RETURN NEXT pass('row_eq(prepared, sometype, desc) should have the proper diagnostics'); - RETURN NEXT pass('row_eq(sqlrow, sometype, desc) should pass'); - RETURN NEXT pass('row_eq(sqlrow, sometype, desc) should have the proper description'); - RETURN NEXT pass('row_eq(sqlrow, sometype, desc) should have the proper diagnostics'); - RETURN NEXT pass('threw 0A000'); - ELSE - FOR tap IN SELECT * FROM check_test( - row_eq('arow', ROW(1, 'Jacob')::names, 'whatever'), - true, - 'row_eq(prepared, record, desc)', - 'whatever', - '' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - - FOR tap IN SELECT * FROM check_test( - row_eq('SELECT id, name FROM names WHERE id = 1', ROW(1, 'Jacob')::names, 'whatever'), - true, - 'row_eq(sql, record, desc)', - 'whatever', - '' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - - FOR tap IN SELECT * FROM check_test( - row_eq('arow', ROW(1, 'Jacob')::names), - true, - 'row_eq(prepared, record, desc)', - '', - '' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - - FOR tap IN SELECT * FROM check_test( - row_eq('arow', ROW(1, 'Larry')::names), - false, - 'row_eq(prepared, record, desc)', - '', - ' have: (1,Jacob) + FOR tap IN SELECT * FROM check_test( + row_eq('arow', ROW(1, 'Jacob')::names, 'whatever'), + true, + 'row_eq(prepared, record, desc)', + 'whatever', + '' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + row_eq('SELECT id, name FROM names WHERE id = 1', ROW(1, 'Jacob')::names, 'whatever'), + true, + 'row_eq(sql, record, desc)', + 'whatever', + '' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + row_eq('arow', ROW(1, 'Jacob')::names), + true, + 'row_eq(prepared, record, desc)', + '', + '' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + row_eq('arow', ROW(1, 'Larry')::names), + false, + 'row_eq(prepared, record, desc)', + '', + ' have: (1,Jacob) want: (1,Larry)' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - - FOR tap IN SELECT * FROM check_test( - row_eq('arow', ROW(1, 'Jacob')::sometype), - true, - 'row_eq(prepared, sometype, desc)', - '', - '' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + row_eq('arow', ROW(1, 'Jacob')::sometype), + true, + 'row_eq(prepared, sometype, desc)', + '', + '' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + FOR tap IN SELECT * FROM check_test( + row_eq('SELECT 1, ''Jacob''::text', ROW(1, 'Jacob')::sometype), + true, + 'row_eq(sqlrow, sometype, desc)', + '', + '' + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; + + INSERT INTO someat (ts) values ('2009-12-04T07:22:52'); + IF pg_version_num() < 110000 THEN + -- Prior to 11, one cannot pass a bare RECORD. + RETURN NEXT throws_ok( + 'SELECT row_eq( ''SELECT id, ts FROM someat'', ROW(1, ''2009-12-04T07:22:52''::timestamp) )', + '0A000' + -- 'PL/pgSQL functions cannot accept type record' + ); + RETURN NEXT pass('row_eq(qry, record) should pass'); + RETURN NEXT pass('row_eq(qry, record) should have the proper description'); + RETURN NEXT pass('row_eq(qry, record) should have the proper diagnostics'); + ELSE + -- Postgres 11 supports record arguments! + RETURN NEXT pass('threw 0A000'); FOR tap IN SELECT * FROM check_test( - row_eq('SELECT 1, ''Jacob''::text', ROW(1, 'Jacob')::sometype), + row_eq('SELECT id, ts FROM someat', ROW(1, '2009-12-04T07:22:52'::timestamp)), true, - 'row_eq(sqlrow, sometype, desc)', + 'row_eq(qry, record)', '', '' ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; - - INSERT INTO someat (ts) values ('2009-12-04T07:22:52'); - RETURN NEXT throws_ok( - 'SELECT row_eq( ''SELECT id, ts FROM someat'', ROW(1, ''2009-12-04T07:22:52'') )', - '0A000' - -- 'PL/pgSQL functions cannot accept type record' - ); - - -- FOR tap IN SELECT * FROM check_test( - -- row_eq( 'SELECT id, ts FROM someat', ROW(1, '2009-12-04T07:22:52') ), - -- true, - -- 'row_eq(sql, rec)', - -- '', - -- '' - -- ) AS a(b) LOOP - -- RETURN NEXT tap.b; - -- END LOOP; - END IF; RETURN; END; From f9fdfe82a1cdcfa58be26c629ece7cf95a1a1f40 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 28 Jul 2018 12:00:25 -0400 Subject: [PATCH 0998/1195] Remove 8.0-specific tests. --- Changes | 4 +++- doc/pgtap.mmd | 2 +- test/sql/istap.sql | 14 +----------- test/sql/resultset.sql | 50 +++++++++++++++++------------------------- test/sql/todotap.sql | 17 ++++++-------- 5 files changed, 32 insertions(+), 55 deletions(-) diff --git a/Changes b/Changes index 906dc5bc3369..fe16a847e586 100644 --- a/Changes +++ b/Changes @@ -22,6 +22,8 @@ Revision history for pgTAP Keith Fiske for the report (#174)! * Documented that a bar `RECORD` can be passed to `row_eq()` on PostgreSQL 11, and fixed the tests to match this new functionality. +* Removed tests that only ran on 8.0, support for which was discontinued in + v0.90.0. 0.98.0 2017-11-06T22:30:54Z --------------------------- @@ -234,7 +236,7 @@ Revision history for pgTAP 0.90.0 2011-12-03T20:18:16Z --------------------------- * Removed the "Supported Versions" section of the documentation. Suffice it to - say that pgTAP supports PostgreSQL 8.0 or higher. + say that pgTAP supports PostgreSQL 8.1 or higher. * Added a build target to create a portable copy of pgTAP that can be included in any distribution and should just work for tesing on PostgreSQL 8.3+. The new files are `pgtap-core.sql`, which contains the core functionality, and diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 20e671bd2ee5..7a8270b83cf0 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7429,7 +7429,7 @@ pgTAP was compiled. This function is useful for determining whether or not certain tests should be run or skipped (using `skip()`) depending on the version of PostgreSQL. For example: - SELECT CASE WHEN pg_version_num() < 80100 + SELECT CASE WHEN pg_version_num() < 80300 THEN skip('has_enum() not supported before 8.3' ) ELSE has_enum( 'bug_status', 'mydesc' ) END; diff --git a/test/sql/istap.sql b/test/sql/istap.sql index 87b5dc182761..b7208c6ac016 100644 --- a/test/sql/istap.sql +++ b/test/sql/istap.sql @@ -65,19 +65,7 @@ CREATE FUNCTION test_records() RETURNS SETOF TEXT AS $$ DECLARE tap record; BEGIN - IF pg_version_num() < 80100 THEN - -- Can't do shit with records on 8.0 - RETURN NEXT pass('with records!'); - RETURN NEXT pass( 'is(mumble, row) fail should fail'); - RETURN NEXT pass( 'is(mumble, row) fail should have the proper description'); - RETURN NEXT pass( 'is(mumble, row) fail should have the proper diagnostics'); - RETURN NEXT pass( 'is(mumble, row) fail with NULL should fail'); - RETURN NEXT pass( 'is(mumble, row) fail with NULL should have the proper description'); - RETURN NEXT pass( 'is(mumble, row) fail with NULL should have the proper diagnostics'); - RETURN NEXT pass( 'is(mumble, NULL) should fail'); - RETURN NEXT pass( 'is(mumble, NULL) should have the proper description'); - RETURN NEXT pass( 'is(mumble, NULL) should have the proper diagnostics'); - ELSIF pg_version_num() >= 80400 THEN + IF pg_version_num() >= 80400 THEN RETURN NEXT is( mumble.*, ROW(1, 'hey')::mumble, 'with records!' ) FROM mumble; diff --git a/test/sql/resultset.sql b/test/sql/resultset.sql index 094764b9b997..8c5d12853268 100644 --- a/test/sql/resultset.sql +++ b/test/sql/resultset.sql @@ -2321,41 +2321,31 @@ CREATE FUNCTION test_empty_fail() RETURNS SETOF TEXT AS $$ DECLARE tap record; BEGIN - IF pg_version_num() < 80100 THEN - -- Can't do shit with records on 8.0 - RETURN NEXT pass('is_empty(prepared, desc) fail should fail'); - RETURN NEXT pass('is_empty(prepared, desc) fail should have the proper description'); - RETURN NEXT pass('is_empty(prepared, desc) fail should have the proper diagnostics'); - RETURN NEXT pass('is_empty(prepared) fail should fail'); - RETURN NEXT pass('is_empty(prepared) fail should have the proper description'); - RETURN NEXT pass('is_empty(prepared) fail should have the proper diagnostics'); - ELSE - PREPARE notempty AS SELECT id, name FROM names WHERE name IN ('Jacob', 'Emily') - ORDER BY ID; - FOR tap IN SELECT * FROM check_test( - is_empty( 'notempty', 'whatever' ), - false, - 'is_empty(prepared, desc) fail', - 'whatever', - ' Unexpected records: + PREPARE notempty AS SELECT id, name FROM names WHERE name IN ('Jacob', 'Emily') + ORDER BY ID; + FOR tap IN SELECT * FROM check_test( + is_empty( 'notempty', 'whatever' ), + false, + 'is_empty(prepared, desc) fail', + 'whatever', + ' Unexpected records: (1,Jacob) (2,Emily)' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; - FOR tap IN SELECT * FROM check_test( - is_empty( 'notempty' ), - false, - 'is_empty(prepared) fail', - '', - ' Unexpected records: + FOR tap IN SELECT * FROM check_test( + is_empty( 'notempty' ), + false, + 'is_empty(prepared) fail', + '', + ' Unexpected records: (1,Jacob) (2,Emily)' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - END IF; + ) AS a(b) LOOP + RETURN NEXT tap.b; + END LOOP; RETURN; END; $$ LANGUAGE PLPGSQL; diff --git a/test/sql/todotap.sql b/test/sql/todotap.sql index b85b7b339e48..ef268e096fb2 100644 --- a/test/sql/todotap.sql +++ b/test/sql/todotap.sql @@ -172,16 +172,13 @@ not ok 34 - Another todo test # TODO just because 'Should be able to revers the arguments to todo()' ); --- Test the exception when throws_ok() is available. -SELECT CASE WHEN pg_version_num() < 80100 - THEN pass('Should get an exception when todo_end() is called without todo_start()') - ELSE throws_ok( - 'SELECT todo_end()', - 'P0001', - 'todo_end() called without todo_start()', - 'Should get an exception when todo_end() is called without todo_start()' - ) - END; +-- Test the exception. +SELECT throws_ok( + 'SELECT todo_end()', + 'P0001', + 'todo_end() called without todo_start()', + 'Should get an exception when todo_end() is called without todo_start()' +); /****************************************************************************/ -- Finish the tests and clean up. From da005fb998dc2115a060ad67bf28c046e1f88bb9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 28 Jul 2018 12:04:58 -0400 Subject: [PATCH 0999/1195] Properly apply v9.6 patch. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index fad798fa25e7..5e6221ad8c08 100644 --- a/Makefile +++ b/Makefile @@ -116,7 +116,7 @@ sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql ifeq ($(shell echo $(VERSION) | grep -qE "([98]|10)[.]" && echo yes || echo no),yes) patch -p0 < compat/install-10.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE "[98][012345][.]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "9[.][0123456]|8[.][1234]" && echo yes || echo no),yes) patch -p0 < compat/install-9.6.patch endif ifeq ($(shell echo $(VERSION) | grep -qE "9[.][01234]|8[.][1234]" && echo yes || echo no),yes) From a8c740035b35193a9acd04f93ab4bfeb4f900692 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 30 Aug 2018 11:26:02 -0400 Subject: [PATCH 1000/1195] Match versions at start of string. --- Makefile | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 5e6221ad8c08..6ab5d41c29b7 100644 --- a/Makefile +++ b/Makefile @@ -27,12 +27,12 @@ endif VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') # We support 8.1 and later. -ifeq ($(shell echo $(VERSION) | grep -qE " 7[.]|8[.]0" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^(7[.]|8[.]0)" && echo yes || echo no),yes) $(error pgTAP requires PostgreSQL 8.1 or later. This is $(VERSION)) endif # Compile the C code only if we're on 8.3 or older. -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][123]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^8[.][123]" && echo yes || echo no),yes) MODULES = src/pgtap endif @@ -41,7 +41,7 @@ EXTRA_CLEAN += $(_IN_PATCHED) all: $(_IN_PATCHED) sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql # Add extension build targets on 9.1 and up. -ifeq ($(shell echo $(VERSION) | grep -qE "8[.]|9[.]0" && echo no || echo yes),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^(8[.]|9[.]0)" && echo no || echo yes),yes) all: sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql sql/$(MAINEXT)--$(EXTVERSION).sql: sql/$(MAINEXT).sql @@ -89,19 +89,19 @@ $(warning cpan TAP::Parser::SourceHandler::pgTAP) endif # Enum tests not supported by 8.2 and earlier. -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][12]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^8[.][12]" && echo yes || echo no),yes) TESTS := $(filter-out test/sql/enumtap.sql,$(TESTS)) REGRESS := $(filter-out enumtap,$(REGRESS)) endif # Values tests not supported by 8.1 and earlier. -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][1]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^8[.][1]" && echo yes || echo no),yes) TESTS := $(filter-out test/sql/enumtap.sql sql/valueset.sql,$(TESTS)) REGRESS := $(filter-out enumtap valueset,$(REGRESS)) endif # Partition tests tests not supported by 9.x and earlier. -ifeq ($(shell echo $(VERSION) | grep -qE "[89][.]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^[89][.]" && echo yes || echo no),yes) TESTS := $(filter-out test/sql/partitions.sql,$(TESTS)) REGRESS := $(filter-out partitions,$(REGRESS)) endif @@ -113,34 +113,34 @@ OSNAME := $(shell $(SHELL) ./getos.sh) sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql cp $< $@ -ifeq ($(shell echo $(VERSION) | grep -qE "([98]|10)[.]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^([98]|10)[.]" && echo yes || echo no),yes) patch -p0 < compat/install-10.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE "9[.][0123456]|8[.][1234]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][0123456]|8[.][1234])" && echo yes || echo no),yes) patch -p0 < compat/install-9.6.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE "9[.][01234]|8[.][1234]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][01234]|8[.][1234])" && echo yes || echo no),yes) patch -p0 < compat/install-9.4.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE "9[.][012]|8[.][1234]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][012]|8[.][1234])" && echo yes || echo no),yes) patch -p0 < compat/install-9.2.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE "9[.][01]|8[.][1234]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][01]|8[.][1234])" && echo yes || echo no),yes) patch -p0 < compat/install-9.1.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE "9[.]0|8[.][1234]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.]0|8[.][1234])" && echo yes || echo no),yes) patch -p0 < compat/install-9.0.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][1234]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^8[.][1234]" && echo yes || echo no),yes) patch -p0 < compat/install-8.4.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][123]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^8[.][123]" && echo yes || echo no),yes) patch -p0 < compat/install-8.3.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][12]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^8[.][12]" && echo yes || echo no),yes) patch -p0 < compat/install-8.2.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][1]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^8[.][1]" && echo yes || echo no),yes) patch -p0 < compat/install-8.1.patch endif sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap.sql > sql/pgtap.tmp @@ -150,24 +150,24 @@ endif EXTRA_CLEAN += sql/pgtap--0.97.0--0.98.0.sql sql/pgtap--0.97.0--0.98.0.sql: sql/pgtap--0.97.0--0.98.0.sql.in cp $< $@ -ifeq ($(shell echo $(VERSION) | grep -qE "[89][.]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^[89][.]" && echo yes || echo no),yes) patch -p0 < compat/9.6/pgtap--0.97.0--0.98.0.patch endif EXTRA_CLEAN += sql/pgtap--0.96.0--0.97.0.sql sql/pgtap--0.96.0--0.97.0.sql: sql/pgtap--0.96.0--0.97.0.sql.in cp $< $@ -ifeq ($(shell echo $(VERSION) | grep -qE "9[.][01234]|8[.][1234]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][01234]|8[.][1234])" && echo yes || echo no),yes) patch -p0 < compat/9.4/pgtap--0.96.0--0.97.0.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE "9[.]0|8[.][1234]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.]0|8[.][1234])" && echo yes || echo no),yes) patch -p0 < compat/9.0/pgtap--0.96.0--0.97.0.patch endif EXTRA_CLEAN += sql/pgtap--0.95.0--0.96.0.sql sql/pgtap--0.95.0--0.96.0.sql: sql/pgtap--0.95.0--0.96.0.sql.in cp $< $@ -ifeq ($(shell echo $(VERSION) | grep -qE "9[.][012]|8[.][1234]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][012]|8[.][1234])" && echo yes || echo no),yes) patch -p0 < compat/9.2/pgtap--0.95.0--0.96.0.patch endif From b50c63450eeb6d4333306eddf3bbd560669e7eca Mon Sep 17 00:00:00 2001 From: Tobias Bengtsson Date: Tue, 4 Sep 2018 09:03:20 +0800 Subject: [PATCH 1001/1195] Fix 'has_function' documentation error. --- doc/pgtap.mmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 7a8270b83cf0..9a3bcbd69564 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -3446,7 +3446,7 @@ rule does *not* exist. **Parameters** `:schema` -: Name of a schema in which to not find the function. +: Name of a schema in which to find the function. `:function` : Name of a function. From dd76816e7473697004655a4da664ac0ee336ec2c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 16 Sep 2018 15:42:26 -0400 Subject: [PATCH 1002/1195] Omit failing tests from `make test`. These tests fail on purpose; their intent is to validate that the failure output is correct. They must always be tested by `make installcheck`, but having `make test` run them only confuses things. Resolves #172. --- Changes | 5 +++++ Makefile | 3 ++- test/sql/runjusttests.sql | 4 ++-- test/sql/runtests.sql | 4 ++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index fe16a847e586..c0946f1e1191 100644 --- a/Changes +++ b/Changes @@ -24,6 +24,11 @@ Revision history for pgTAP and fixed the tests to match this new functionality. * Removed tests that only ran on 8.0, support for which was discontinued in v0.90.0. +* Removed `test/sql/run*` tests that intentionally fail from the `test` target + in the Makefile. It's not meaningful to test them via `pg_prove`; they must + instead be tested by `make installcheck`, so their output can be compared + against output that varies by PostgreSQL version. Thanks to Jim Nasby for + the report (#172). 0.98.0 2017-11-06T22:30:54Z --------------------------- diff --git a/Makefile b/Makefile index 6ab5d41c29b7..26e7d6026be4 100644 --- a/Makefile +++ b/Makefile @@ -199,7 +199,8 @@ installcheck: test/setup.sql # In addition to installcheck, one can also run the tests through pg_prove. test: test/setup.sql - pg_prove --pset tuples_only=1 $(TESTS) + # Filter-out tests that intentionally fail. They should be tested by installcheck. + pg_prove --pset tuples_only=1 $(filter-out test/sql/run%,$(TESTS)) html: multimarkdown doc/pgtap.mmd > doc/pgtap.html diff --git a/test/sql/runjusttests.sql b/test/sql/runjusttests.sql index a51d8c6d0078..60df67a038c8 100644 --- a/test/sql/runjusttests.sql +++ b/test/sql/runjusttests.sql @@ -26,8 +26,8 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION whatever.testplpgsqldie() RETURNS SETOF TEXT AS $$ BEGIN - RETURN NEXT pass( 'plpgsql simple' ); - RETURN NEXT pass( 'plpgsql simple 2' ); + RETURN NEXT pass( 'plpgsql simple' ); -- Won't appear in results. + RETURN NEXT pass( 'plpgsql simple 2' ); -- Won't appear in results. INSERT INTO whatever.foo VALUES(1); RETURN NEXT is( MAX(id), 1, 'Should be a 1 in the test table') FROM whatever.foo; IF pg_version_num() >= 90300 THEN diff --git a/test/sql/runtests.sql b/test/sql/runtests.sql index 5f42e988bcda..8832fefd80b4 100644 --- a/test/sql/runtests.sql +++ b/test/sql/runtests.sql @@ -61,8 +61,8 @@ $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION whatever.testplpgsqldie() RETURNS SETOF TEXT AS $$ BEGIN - RETURN NEXT pass( 'plpgsql simple' ); - RETURN NEXT pass( 'plpgsql simple 2' ); + RETURN NEXT pass( 'plpgsql simple' ); -- Won't appear in results. + RETURN NEXT pass( 'plpgsql simple 2' ); -- Won't appear in results. INSERT INTO whatever.foo VALUES(1); RETURN NEXT is( MAX(id), 1, 'Should be a 1 in the test table') FROM whatever.foo; IF pg_version_num() >= 90300 THEN From f415187158954a187bedc651916fbe39ce1e2556 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 16 Sep 2018 16:16:48 -0400 Subject: [PATCH 1003/1195] Don't require primary key in _hasc(). It causes tests to fail when checking for some other constraint against a table with no primary key. Fixes #171. --- Changes | 2 + compat/install-10.patch | 8 +- compat/install-8.1.patch | 18 +-- compat/install-8.2.patch | 46 +++--- compat/install-8.3.patch | 8 +- compat/install-8.4.patch | 8 +- compat/install-9.0.patch | 10 +- compat/install-9.1.patch | 2 +- compat/install-9.2.patch | 2 +- compat/install-9.6.patch | 2 +- sql/pgtap--0.98.0--0.99.0.sql | 27 ++++ sql/pgtap.sql.in | 8 +- test/expected/fktap.out | 254 +++++++++++++++++----------------- test/sql/fktap.sql | 29 +++- 14 files changed, 238 insertions(+), 186 deletions(-) diff --git a/Changes b/Changes index c0946f1e1191..c316f2ab0e0f 100644 --- a/Changes +++ b/Changes @@ -29,6 +29,8 @@ Revision history for pgTAP instead be tested by `make installcheck`, so their output can be compared against output that varies by PostgreSQL version. Thanks to Jim Nasby for the report (#172). +* Fixed a bug where `have_fk()` would fail if the table had no primary key. + Thanks to @bigmodem for the report! (#171). 0.98.0 2017-11-06T22:30:54Z --------------------------- diff --git a/compat/install-10.patch b/compat/install-10.patch index efe6375177ce..94261f7ed007 100644 --- a/compat/install-10.patch +++ b/compat/install-10.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -2487,7 +2487,7 @@ +@@ -2483,7 +2483,7 @@ || p.prorettype::regtype AS returns, p.prolang AS langoid, p.proisstrict AS is_strict, @@ -9,7 +9,7 @@ p.prosecdef AS is_definer, p.proretset AS returns_set, p.provolatile::char AS volatility, -@@ -5651,7 +5651,7 @@ +@@ -5647,7 +5647,7 @@ CREATE OR REPLACE FUNCTION _agg ( NAME, NAME, NAME[] ) RETURNS BOOLEAN AS $$ @@ -18,7 +18,7 @@ FROM tap_funky WHERE schema = $1 AND name = $2 -@@ -5660,12 +5660,12 @@ +@@ -5656,12 +5656,12 @@ CREATE OR REPLACE FUNCTION _agg ( NAME, NAME ) RETURNS BOOLEAN AS $$ @@ -33,7 +33,7 @@ FROM tap_funky WHERE name = $1 AND args = array_to_string($2, ',') -@@ -5674,7 +5674,7 @@ +@@ -5670,7 +5670,7 @@ CREATE OR REPLACE FUNCTION _agg ( NAME ) RETURNS BOOLEAN AS $$ diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index 2bd27f4ad565..8603e218c3ae 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -2270,13 +2270,13 @@ +@@ -2266,13 +2266,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -18,7 +18,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $6 || ' constraints'; -@@ -2294,13 +2294,13 @@ +@@ -2290,13 +2290,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -36,7 +36,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $5 || ' constraints'; -@@ -6190,7 +6190,7 @@ +@@ -6186,7 +6186,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -45,7 +45,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -6198,8 +6198,8 @@ +@@ -6194,8 +6194,8 @@ -- 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. @@ -56,7 +56,7 @@ END LOOP; END LOOP; RETURN; -@@ -6268,7 +6268,7 @@ +@@ -6264,7 +6264,7 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -65,7 +65,7 @@ tfaild INTEGER := 0; ffaild INTEGER := 0; tnumb INTEGER := 0; -@@ -6278,7 +6278,7 @@ +@@ -6274,7 +6274,7 @@ BEGIN -- No plan support. PERFORM * FROM no_plan(); @@ -74,7 +74,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -6317,18 +6317,18 @@ +@@ -6313,18 +6313,18 @@ BEGIN BEGIN -- Run the setup functions. @@ -99,7 +99,7 @@ END LOOP; -- Emit the plan. -@@ -6384,11 +6384,11 @@ +@@ -6380,11 +6380,11 @@ END LOOP; -- Run the shutdown functions. @@ -114,7 +114,7 @@ END LOOP; -- Clean up and return. -@@ -7646,7 +7646,7 @@ +@@ -7642,7 +7642,7 @@ DECLARE rec RECORD; BEGIN diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 881e41bf485f..1191c6f19191 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -117,7 +117,7 @@ ), ''); $$ LANGUAGE sql IMMUTABLE; -@@ -2480,7 +2523,7 @@ +@@ -2476,7 +2519,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 @@ -126,7 +126,7 @@ p.prolang AS langoid, p.proisstrict AS is_strict, p.proisagg AS is_agg, -@@ -3685,63 +3728,6 @@ +@@ -3681,63 +3724,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -190,7 +190,7 @@ CREATE OR REPLACE FUNCTION _has_role( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( -@@ -6332,17 +6318,17 @@ +@@ -6328,17 +6314,17 @@ BEGIN -- Run the setup functions. FOR tap IN SELECT * FROM _runem(setup, false) LOOP @@ -211,7 +211,7 @@ END LOOP; -- Emit the plan. -@@ -6381,7 +6367,7 @@ +@@ -6377,7 +6363,7 @@ tok := FALSE; RETURN NEXT regexp_replace( diag('Test died: ' || _error_diag( errstate, errmsg, detail, hint, context, schname, tabname, colname, chkname, typname @@ -220,7 +220,7 @@ errmsg := NULL; END IF; END; -@@ -6494,13 +6480,13 @@ +@@ -6490,13 +6476,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -236,7 +236,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6724,7 +6710,7 @@ +@@ -6720,7 +6706,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -245,7 +245,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6819,11 +6805,11 @@ +@@ -6815,11 +6801,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -260,7 +260,7 @@ ); END IF; rownum = rownum + 1; -@@ -6838,9 +6824,9 @@ +@@ -6834,9 +6820,9 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Number of columns or their types differ between the queries' || @@ -273,7 +273,7 @@ END ); END; -@@ -6976,7 +6962,7 @@ +@@ -6972,7 +6958,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -282,7 +282,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6990,8 +6976,8 @@ +@@ -6986,8 +6972,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -293,7 +293,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7116,9 +7102,9 @@ +@@ -7112,9 +7098,9 @@ DECLARE typeof regtype := pg_typeof($1); BEGIN @@ -306,7 +306,7 @@ END; $$ LANGUAGE plpgsql; -@@ -7139,7 +7125,7 @@ +@@ -7135,7 +7121,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -315,7 +315,7 @@ END LOOP; -- What extra records do we have? -@@ -7307,7 +7293,7 @@ +@@ -7303,7 +7289,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -324,7 +324,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT -@@ -7325,7 +7311,7 @@ +@@ -7321,7 +7307,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -333,7 +333,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) ), -@@ -7358,7 +7344,7 @@ +@@ -7354,7 +7340,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -342,7 +342,7 @@ 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']) ) -@@ -7377,7 +7363,7 @@ +@@ -7373,7 +7359,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -351,7 +351,7 @@ 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']) ) -@@ -7661,10 +7647,12 @@ +@@ -7657,10 +7643,12 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; @@ -367,7 +367,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7811,7 +7799,7 @@ +@@ -7807,7 +7795,7 @@ CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) RETURNS TEXT AS $$ @@ -376,7 +376,7 @@ $$ LANGUAGE SQL; -- operators_are( schema, operators[], description ) -@@ -7820,7 +7808,7 @@ +@@ -7816,7 +7804,7 @@ SELECT _areni( 'operators', ARRAY( @@ -385,7 +385,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7832,7 +7820,7 @@ +@@ -7828,7 +7816,7 @@ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT @@ -394,7 +394,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7853,7 +7841,7 @@ +@@ -7849,7 +7837,7 @@ SELECT _areni( 'operators', ARRAY( @@ -403,7 +403,7 @@ 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) -@@ -7866,7 +7854,7 @@ +@@ -7862,7 +7850,7 @@ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT @@ -412,7 +412,7 @@ 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) -@@ -8579,40 +8567,6 @@ +@@ -8575,40 +8563,6 @@ ); $$ LANGUAGE sql; diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index 969d95d298f1..d98880e67f98 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -12,7 +12,7 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT current_setting('server_version_num')::integer; -@@ -6814,7 +6819,7 @@ +@@ -6810,7 +6815,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -21,7 +21,7 @@ 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' || -@@ -6971,7 +6976,7 @@ +@@ -6967,7 +6972,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -30,7 +30,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -7180,13 +7185,7 @@ +@@ -7176,13 +7181,7 @@ $$ LANGUAGE sql; -- collect_tap( tap, tap, tap ) @@ -45,7 +45,7 @@ RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); $$ LANGUAGE sql; -@@ -7662,7 +7661,7 @@ +@@ -7658,7 +7657,7 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; diff --git a/compat/install-8.4.patch b/compat/install-8.4.patch index 13b2521e800c..3df72a391855 100644 --- a/compat/install-8.4.patch +++ b/compat/install-8.4.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -7688,7 +7688,6 @@ +@@ -7684,7 +7684,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -8,7 +8,7 @@ EXCEPT SELECT $3[i] FROM generate_series(1, array_upper($3, 1)) s(i) -@@ -7703,7 +7702,6 @@ +@@ -7699,7 +7698,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -16,7 +16,7 @@ ), $4 ); -@@ -7727,7 +7725,6 @@ +@@ -7723,7 +7721,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = $1 AND n.nspname NOT IN ('pg_catalog', 'information_schema') @@ -24,7 +24,7 @@ EXCEPT SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) -@@ -7741,7 +7738,6 @@ +@@ -7737,7 +7734,6 @@ 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') diff --git a/compat/install-9.0.patch b/compat/install-9.0.patch index ed252feddb3d..ec8864fea13b 100644 --- a/compat/install-9.0.patch +++ b/compat/install-9.0.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -3693,7 +3693,7 @@ +@@ -3689,7 +3689,7 @@ AND n.nspname = $1 AND t.typname = $2 AND t.typtype = 'e' @@ -9,7 +9,7 @@ ), $3, $4 -@@ -3721,7 +3721,7 @@ +@@ -3717,7 +3717,7 @@ AND pg_catalog.pg_type_is_visible(t.oid) AND t.typname = $1 AND t.typtype = 'e' @@ -18,7 +18,7 @@ ), $2, $3 -@@ -6165,7 +6165,7 @@ +@@ -6161,7 +6161,7 @@ CREATE OR REPLACE FUNCTION findfuncs( NAME, TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( @@ -27,7 +27,7 @@ FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = $1 -@@ -6182,7 +6182,7 @@ +@@ -6178,7 +6178,7 @@ CREATE OR REPLACE FUNCTION findfuncs( TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( @@ -36,7 +36,7 @@ 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) -@@ -9684,137 +9684,6 @@ +@@ -9680,137 +9680,6 @@ GRANT SELECT ON tap_funky TO PUBLIC; GRANT SELECT ON pg_all_foreign_keys TO PUBLIC; diff --git a/compat/install-9.1.patch b/compat/install-9.1.patch index 22992665e6ab..14bc2d81fd52 100644 --- a/compat/install-9.1.patch +++ b/compat/install-9.1.patch @@ -11,7 +11,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6369,10 +6365,6 @@ +@@ -6365,10 +6361,6 @@ -- Something went wrong. Record that fact. errstate := SQLSTATE; errmsg := SQLERRM; diff --git a/compat/install-9.2.patch b/compat/install-9.2.patch index 59ff9fa5b87c..bbf5fecb1e6a 100644 --- a/compat/install-9.2.patch +++ b/compat/install-9.2.patch @@ -14,7 +14,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6377,12 +6372,7 @@ +@@ -6373,12 +6388,7 @@ GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, hint = PG_EXCEPTION_HINT, diff --git a/compat/install-9.6.patch b/compat/install-9.6.patch index b7af3c71a281..70ff4f95b9a2 100644 --- a/compat/install-9.6.patch +++ b/compat/install-9.6.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -9893,136 +9893,6 @@ +@@ -9889,136 +9889,6 @@ ); $$ LANGUAGE sql; diff --git a/sql/pgtap--0.98.0--0.99.0.sql b/sql/pgtap--0.98.0--0.99.0.sql index 7775221b9e05..e165e17bbad0 100644 --- a/sql/pgtap--0.98.0--0.99.0.sql +++ b/sql/pgtap--0.98.0--0.99.0.sql @@ -160,3 +160,30 @@ CREATE OR REPLACE FUNCTION _agg ( NAME ) RETURNS BOOLEAN AS $$ SELECT kind = 'a' FROM tap_funky WHERE name = $1 AND is_visible; $$ 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index daa3934d8db4..484223224f3a 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1759,9 +1759,7 @@ RETURNS BOOLEAN AS $$ 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 - JOIN pg_catalog.pg_index i ON c.oid = i.indrelid - WHERE i.indisprimary = true - AND n.nspname = $1 + WHERE n.nspname = $1 AND c.relname = $2 AND x.contype = $3 ); @@ -1774,9 +1772,7 @@ RETURNS BOOLEAN AS $$ SELECT true FROM pg_catalog.pg_class c JOIN pg_catalog.pg_constraint x ON c.oid = x.conrelid - JOIN pg_catalog.pg_index i ON c.oid = i.indrelid - WHERE i.indisprimary = true - AND pg_table_is_visible(c.oid) + WHERE pg_table_is_visible(c.oid) AND c.relname = $1 AND x.contype = $2 ); diff --git a/test/expected/fktap.out b/test/expected/fktap.out index 0844b71b04fe..675455f9c440 100644 --- a/test/expected/fktap.out +++ b/test/expected/fktap.out @@ -1,130 +1,134 @@ \unset ECHO -1..128 +1..132 ok 1 - has_fk( schema, table, description ) should pass ok 2 - has_fk( schema, table, description ) should have the proper description ok 3 - has_fk( table, description ) should pass ok 4 - has_fk( table, description ) should have the proper description -ok 5 - has_fk( table ) should pass -ok 6 - has_fk( table ) should have the proper description -ok 7 - has_fk( schema, table, description ) fail should fail -ok 8 - has_fk( schema, table, description ) fail should have the proper description -ok 9 - has_fk( table, description ) fail should fail -ok 10 - has_fk( table, description ) fail should have the proper description -ok 11 - hasnt_fk( schema, table, description ) should fail -ok 12 - hasnt_fk( schema, table, description ) should have the proper description -ok 13 - hasnt_fk( table, description ) should fail -ok 14 - hasnt_fk( table, description ) should have the proper description -ok 15 - hasnt_fk( table ) should fail -ok 16 - hasnt_fk( table ) should have the proper description -ok 17 - hasnt_fk( schema, table, description ) pass should pass -ok 18 - hasnt_fk( schema, table, description ) pass should have the proper description -ok 19 - hasnt_fk( table, description ) pass should pass -ok 20 - hasnt_fk( table, description ) pass should have the proper description -ok 21 - col_is_fk( schema, table, column, description ) should pass -ok 22 - col_is_fk( schema, table, column, description ) should have the proper description -ok 23 - col_is_fk( table, column, description ) should pass -ok 24 - col_is_fk( table, column, description ) should have the proper description -ok 25 - col_is_fk( table, column ) should pass -ok 26 - col_is_fk( table, column ) should have the proper description -ok 27 - col_is_fk( schema, table, column, description ) should fail -ok 28 - col_is_fk( schema, table, column, description ) should have the proper description -ok 29 - col_is_fk( schema, table, column, description ) should have the proper diagnostics -ok 30 - col_is_fk( table, column, description ) should fail -ok 31 - col_is_fk( table, column, description ) should have the proper description -ok 32 - col_is_fk( table, column, description ) should have the proper diagnostics -ok 33 - multi-fk col_is_fk test should pass -ok 34 - multi-fk col_is_fk test should have the proper description -ok 35 - col_is_fk with no FKs should fail -ok 36 - col_is_fk with no FKs should have the proper description -ok 37 - col_is_fk with no FKs should have the proper diagnostics -ok 38 - col_is_fk with no FKs should fail -ok 39 - col_is_fk with no FKs should have the proper description -ok 40 - col_is_fk with no FKs should have the proper diagnostics -ok 41 - col_is_fk( schema, table, column[], description ) should pass -ok 42 - col_is_fk( schema, table, column[], description ) should have the proper description -ok 43 - col_is_fk( table, column[], description ) should pass -ok 44 - col_is_fk( table, column[], description ) should have the proper description -ok 45 - col_is_fk( table, column[] ) should pass -ok 46 - col_is_fk( table, column[] ) should have the proper description -ok 47 - col_isnt_fk( schema, table, column, description ) should fail -ok 48 - col_isnt_fk( schema, table, column, description ) should have the proper description -ok 49 - col_isnt_fk( schema, table, column, description ) should have the proper diagnostics -ok 50 - col_isnt_fk( table, column, description ) should fail -ok 51 - col_isnt_fk( table, column, description ) should have the proper description -ok 52 - col_isnt_fk( table, column, description ) should have the proper diagnostics -ok 53 - col_isnt_fk( table, column ) should fail -ok 54 - col_isnt_fk( table, column ) should have the proper description -ok 55 - col_isnt_fk( table, column ) should have the proper diagnostics -ok 56 - col_isnt_fk( schema, table, column, description ) should pass -ok 57 - col_isnt_fk( schema, table, column, description ) should have the proper description -ok 58 - col_isnt_fk( schema, table, column, description ) should have the proper diagnostics -ok 59 - col_isnt_fk( table, column, description ) should pass -ok 60 - col_isnt_fk( table, column, description ) should have the proper description -ok 61 - col_isnt_fk( table, column, description ) should have the proper diagnostics -ok 62 - multi-fk col_isnt_fk test should fail -ok 63 - multi-fk col_isnt_fk test should have the proper description -ok 64 - multi-fk col_isnt_fk test should have the proper diagnostics -ok 65 - col_isnt_fk with no FKs should pass -ok 66 - col_isnt_fk with no FKs should have the proper description -ok 67 - col_isnt_fk with no FKs should have the proper diagnostics -ok 68 - col_isnt_fk with no FKs should pass -ok 69 - col_isnt_fk with no FKs should have the proper description -ok 70 - col_isnt_fk with no FKs should have the proper diagnostics -ok 71 - col_isnt_fk( schema, table, column[], description ) should fail -ok 72 - col_isnt_fk( schema, table, column[], description ) should have the proper description -ok 73 - col_isnt_fk( table, column[], description ) should fail -ok 74 - col_isnt_fk( table, column[], description ) should have the proper description -ok 75 - col_isnt_fk( table, column[] ) should fail -ok 76 - col_isnt_fk( table, column[] ) should have the proper description -ok 77 - full fk_ok array should pass -ok 78 - full fk_ok array should have the proper description -ok 79 - multiple fk fk_ok desc should pass -ok 80 - multiple fk fk_ok desc should have the proper description -ok 81 - fk_ok array desc should pass -ok 82 - fk_ok array desc should have the proper description -ok 83 - fk_ok array noschema desc should pass -ok 84 - fk_ok array noschema desc should have the proper description -ok 85 - multiple fk fk_ok noschema desc should pass -ok 86 - multiple fk fk_ok noschema desc should have the proper description -ok 87 - fk_ok array noschema should pass -ok 88 - fk_ok array noschema should have the proper description -ok 89 - basic fk_ok should pass -ok 90 - basic fk_ok should have the proper description -ok 91 - basic fk_ok desc should pass -ok 92 - basic fk_ok desc should have the proper description -ok 93 - basic fk_ok noschema should pass -ok 94 - basic fk_ok noschema should have the proper description -ok 95 - basic fk_ok noschema desc should pass -ok 96 - basic fk_ok noschema desc should have the proper description -ok 97 - basic fk_ok noschema desc should have the proper diagnostics -ok 98 - Test should pass -ok 99 - fk_ok fail should fail -ok 100 - fk_ok fail should have the proper description -ok 101 - fk_ok fail should have the proper diagnostics -ok 102 - fk_ok fail desc should fail -ok 103 - fk_ok fail desc should have the proper description -ok 104 - fk_ok fail desc should have the proper diagnostics -ok 105 - fk_ok fail no schema should fail -ok 106 - fk_ok fail no schema should have the proper description -ok 107 - fk_ok fail no schema should have the proper diagnostics -ok 108 - fk_ok fail no schema desc should fail -ok 109 - fk_ok fail no schema desc should have the proper description -ok 110 - fk_ok fail no schema desc should have the proper diagnostics -ok 111 - fk_ok bad PK test should fail -ok 112 - fk_ok bad PK test should have the proper description -ok 113 - fk_ok bad PK test should have the proper diagnostics -ok 114 - double fk schema test should pass -ok 115 - double fk schema test should have the proper description -ok 116 - double fk schema test should have the proper diagnostics -ok 117 - double fk test should pass -ok 118 - double fk test should have the proper description -ok 119 - double fk test should have the proper diagnostics -ok 120 - double fk and col schema test should pass -ok 121 - double fk and col schema test should have the proper description -ok 122 - double fk and col schema test should have the proper diagnostics -ok 123 - missing fk test should fail -ok 124 - missing fk test should have the proper description -ok 125 - missing fk test should have the proper diagnostics -ok 126 - bad FK column test should fail -ok 127 - bad FK column test should have the proper description -ok 128 - bad FK column test should have the proper diagnostics +ok 5 - has_fk( table4, description ) should pass +ok 6 - has_fk( table4, description ) should have the proper description +ok 7 - has_fk( schema, table4, description ) should pass +ok 8 - has_fk( schema, table4, description ) should have the proper description +ok 9 - has_fk( table ) should pass +ok 10 - has_fk( table ) should have the proper description +ok 11 - has_fk( schema, table, description ) fail should fail +ok 12 - has_fk( schema, table, description ) fail should have the proper description +ok 13 - has_fk( table, description ) fail should fail +ok 14 - has_fk( table, description ) fail should have the proper description +ok 15 - hasnt_fk( schema, table, description ) should fail +ok 16 - hasnt_fk( schema, table, description ) should have the proper description +ok 17 - hasnt_fk( table, description ) should fail +ok 18 - hasnt_fk( table, description ) should have the proper description +ok 19 - hasnt_fk( table ) should fail +ok 20 - hasnt_fk( table ) should have the proper description +ok 21 - hasnt_fk( schema, table, description ) pass should pass +ok 22 - hasnt_fk( schema, table, description ) pass should have the proper description +ok 23 - hasnt_fk( table, description ) pass should pass +ok 24 - hasnt_fk( table, description ) pass should have the proper description +ok 25 - col_is_fk( schema, table, column, description ) should pass +ok 26 - col_is_fk( schema, table, column, description ) should have the proper description +ok 27 - col_is_fk( table, column, description ) should pass +ok 28 - col_is_fk( table, column, description ) should have the proper description +ok 29 - col_is_fk( table, column ) should pass +ok 30 - col_is_fk( table, column ) should have the proper description +ok 31 - col_is_fk( schema, table, column, description ) should fail +ok 32 - col_is_fk( schema, table, column, description ) should have the proper description +ok 33 - col_is_fk( schema, table, column, description ) should have the proper diagnostics +ok 34 - col_is_fk( table, column, description ) should fail +ok 35 - col_is_fk( table, column, description ) should have the proper description +ok 36 - col_is_fk( table, column, description ) should have the proper diagnostics +ok 37 - multi-fk col_is_fk test should pass +ok 38 - multi-fk col_is_fk test should have the proper description +ok 39 - col_is_fk with no FKs should fail +ok 40 - col_is_fk with no FKs should have the proper description +ok 41 - col_is_fk with no FKs should have the proper diagnostics +ok 42 - col_is_fk with no FKs should fail +ok 43 - col_is_fk with no FKs should have the proper description +ok 44 - col_is_fk with no FKs should have the proper diagnostics +ok 45 - col_is_fk( schema, table, column[], description ) should pass +ok 46 - col_is_fk( schema, table, column[], description ) should have the proper description +ok 47 - col_is_fk( table, column[], description ) should pass +ok 48 - col_is_fk( table, column[], description ) should have the proper description +ok 49 - col_is_fk( table, column[] ) should pass +ok 50 - col_is_fk( table, column[] ) should have the proper description +ok 51 - col_isnt_fk( schema, table, column, description ) should fail +ok 52 - col_isnt_fk( schema, table, column, description ) should have the proper description +ok 53 - col_isnt_fk( schema, table, column, description ) should have the proper diagnostics +ok 54 - col_isnt_fk( table, column, description ) should fail +ok 55 - col_isnt_fk( table, column, description ) should have the proper description +ok 56 - col_isnt_fk( table, column, description ) should have the proper diagnostics +ok 57 - col_isnt_fk( table, column ) should fail +ok 58 - col_isnt_fk( table, column ) should have the proper description +ok 59 - col_isnt_fk( table, column ) should have the proper diagnostics +ok 60 - col_isnt_fk( schema, table, column, description ) should pass +ok 61 - col_isnt_fk( schema, table, column, description ) should have the proper description +ok 62 - col_isnt_fk( schema, table, column, description ) should have the proper diagnostics +ok 63 - col_isnt_fk( table, column, description ) should pass +ok 64 - col_isnt_fk( table, column, description ) should have the proper description +ok 65 - col_isnt_fk( table, column, description ) should have the proper diagnostics +ok 66 - multi-fk col_isnt_fk test should fail +ok 67 - multi-fk col_isnt_fk test should have the proper description +ok 68 - multi-fk col_isnt_fk test should have the proper diagnostics +ok 69 - col_isnt_fk with no FKs should pass +ok 70 - col_isnt_fk with no FKs should have the proper description +ok 71 - col_isnt_fk with no FKs should have the proper diagnostics +ok 72 - col_isnt_fk with no FKs should pass +ok 73 - col_isnt_fk with no FKs should have the proper description +ok 74 - col_isnt_fk with no FKs should have the proper diagnostics +ok 75 - col_isnt_fk( schema, table, column[], description ) should fail +ok 76 - col_isnt_fk( schema, table, column[], description ) should have the proper description +ok 77 - col_isnt_fk( table, column[], description ) should fail +ok 78 - col_isnt_fk( table, column[], description ) should have the proper description +ok 79 - col_isnt_fk( table, column[] ) should fail +ok 80 - col_isnt_fk( table, column[] ) should have the proper description +ok 81 - full fk_ok array should pass +ok 82 - full fk_ok array should have the proper description +ok 83 - multiple fk fk_ok desc should pass +ok 84 - multiple fk fk_ok desc should have the proper description +ok 85 - fk_ok array desc should pass +ok 86 - fk_ok array desc should have the proper description +ok 87 - fk_ok array noschema desc should pass +ok 88 - fk_ok array noschema desc should have the proper description +ok 89 - multiple fk fk_ok noschema desc should pass +ok 90 - multiple fk fk_ok noschema desc should have the proper description +ok 91 - fk_ok array noschema should pass +ok 92 - fk_ok array noschema should have the proper description +ok 93 - basic fk_ok should pass +ok 94 - basic fk_ok should have the proper description +ok 95 - basic fk_ok desc should pass +ok 96 - basic fk_ok desc should have the proper description +ok 97 - basic fk_ok noschema should pass +ok 98 - basic fk_ok noschema should have the proper description +ok 99 - basic fk_ok noschema desc should pass +ok 100 - basic fk_ok noschema desc should have the proper description +ok 101 - basic fk_ok noschema desc should have the proper diagnostics +ok 102 - Test should pass +ok 103 - fk_ok fail should fail +ok 104 - fk_ok fail should have the proper description +ok 105 - fk_ok fail should have the proper diagnostics +ok 106 - fk_ok fail desc should fail +ok 107 - fk_ok fail desc should have the proper description +ok 108 - fk_ok fail desc should have the proper diagnostics +ok 109 - fk_ok fail no schema should fail +ok 110 - fk_ok fail no schema should have the proper description +ok 111 - fk_ok fail no schema should have the proper diagnostics +ok 112 - fk_ok fail no schema desc should fail +ok 113 - fk_ok fail no schema desc should have the proper description +ok 114 - fk_ok fail no schema desc should have the proper diagnostics +ok 115 - fk_ok bad PK test should fail +ok 116 - fk_ok bad PK test should have the proper description +ok 117 - fk_ok bad PK test should have the proper diagnostics +ok 118 - double fk schema test should pass +ok 119 - double fk schema test should have the proper description +ok 120 - double fk schema test should have the proper diagnostics +ok 121 - double fk test should pass +ok 122 - double fk test should have the proper description +ok 123 - double fk test should have the proper diagnostics +ok 124 - double fk and col schema test should pass +ok 125 - double fk and col schema test should have the proper description +ok 126 - double fk and col schema test should have the proper diagnostics +ok 127 - missing fk test should fail +ok 128 - missing fk test should have the proper description +ok 129 - missing fk test should have the proper diagnostics +ok 130 - bad FK column test should fail +ok 131 - bad FK column test should have the proper description +ok 132 - bad FK column test should have the proper diagnostics diff --git a/test/sql/fktap.sql b/test/sql/fktap.sql index 0722a0f8d877..0ba65edfb723 100644 --- a/test/sql/fktap.sql +++ b/test/sql/fktap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(128); +SELECT plan(132); --SELECT * from no_plan(); -- These will be rolled back. :-) @@ -17,7 +17,7 @@ CREATE TABLE public.fk ( ); CREATE TABLE public.pk2 ( - num int NOT NULL, + num int NOT NULL UNIQUE, dot int NOT NULL, PRIMARY KEY (num, dot) ); @@ -25,7 +25,7 @@ CREATE TABLE public.pk2 ( CREATE TABLE public.fk2 ( pk2_num int NOT NULL, pk2_dot int NOT NULL, - FOREIGN KEY(pk2_num, pk2_dot) REFERENCES pk2( num, dot) + FOREIGN KEY(pk2_num, pk2_dot) REFERENCES pk2(num, dot) ); CREATE TABLE public.fk3( @@ -36,6 +36,15 @@ CREATE TABLE public.fk3( foo_id INT NOT NULL, FOREIGN KEY(pk2_num, pk2_dot) REFERENCES pk2( num, dot) ); + +CREATE TABLE public.pk3( + id INT UNIQUE +); + +CREATE TABLE public.fk4 ( + id INT REFERENCES pk3(id) +); + RESET client_min_messages; /****************************************************************************/ @@ -54,6 +63,20 @@ SELECT * FROM check_test( 'fk should have an fk' ); +SELECT * FROM check_test( + has_fk( 'fk4', 'fk4 should have an fk' ), + 'true', + 'has_fk( table4, description )', + 'fk4 should have an fk' +); + +SELECT * FROM check_test( + has_fk( 'public', 'fk4', 'fk4 should have an fk' ), + 'true', + 'has_fk( schema, table4, description )', + 'fk4 should have an fk' +); + SELECT * FROM check_test( has_fk( 'fk' ), true, From c5474ee9c4fd5582c5e5abede371b0dc05d06fad Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 16 Sep 2018 16:41:18 -0400 Subject: [PATCH 1004/1195] Tweak docs. --- Changes | 14 +++++++------- doc/pgtap.mmd | 2 +- release.md | 15 ++++++++------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Changes b/Changes index c316f2ab0e0f..bb701aa23316 100644 --- a/Changes +++ b/Changes @@ -12,7 +12,7 @@ Revision history for pgTAP * Changed the diagnostic output of extra and missing values from the `*_are()` functions to sort the extra and missing values. Fixes a test failure on PostgreSQL 10, and generally makes the diagnostics more deterministic and - easier to read and. Thanks to Michael Glaesemann for the report (#158)! + easier to read. Thanks to Michael Glaesemann for the report (#158)! * Fixed an issue on PostgreSQL 11, where the `pg_class.relhaspkey` column has been removed. The primary key testing functions now refer to `pg_index.indisprimary`, instead. Thanks to Keith Fiske for the report @@ -20,15 +20,15 @@ Revision history for pgTAP * Fixed an issue on PostgreSQL 11, where the `pg_proc.proisagg` column has been replaced with the more generically useful `pg_proc.prokind`. Thanks to Keith Fiske for the report (#174)! -* Documented that a bar `RECORD` can be passed to `row_eq()` on PostgreSQL 11, +* Documented that a `RECORD` can be passed to `row_eq()` on PostgreSQL 11, and fixed the tests to match this new functionality. * Removed tests that only ran on 8.0, support for which was discontinued in v0.90.0. -* Removed `test/sql/run*` tests that intentionally fail from the `test` target - in the Makefile. It's not meaningful to test them via `pg_prove`; they must - instead be tested by `make installcheck`, so their output can be compared - against output that varies by PostgreSQL version. Thanks to Jim Nasby for - the report (#172). +* Removed `test/sql/run*` tests, which intentionally fail, from the `test` + target in the `Makefile`. It's not meaningful to test them via `pg_prove`; + they must instead be tested by `make installcheck`, so their output can be + compared against output that varies by PostgreSQL version. Thanks to Jim Nasby + for the report (#172). * Fixed a bug where `have_fk()` would fail if the table had no primary key. Thanks to @bigmodem for the report! (#171). diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 9a3bcbd69564..693aee6f8984 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -8059,7 +8059,7 @@ comments, suggestions, and bug reports are welcomed there. Author ------ -[David E. Wheeler](http://theory.pm/) +[David E. Wheeler](http://justatheory.com/) Credits ------- diff --git a/release.md b/release.md index fcc2783e5915..4beff9fa9da5 100644 --- a/release.md +++ b/release.md @@ -4,8 +4,9 @@ pgTAP Release Management Here are the steps to take to make a release of pgTAP: * Test on all supported PostgreSQL versions, starting with the latest version - (10) and moving backward in order (9.6, 9.5, 9.4, etc.). For each version, - ensure that: + (10) and moving backward in order (9.6, 9.5, 9.4, etc.). + [pgenv](https://github.com/theory/pgenv/) is a handy tool for installing and + switching between versions. For each version, ensure that: + Patches apply cleanly (try to eliminate Hunk warnings for patches to `pgtap.sql` itself, usually by fixing line numbers) @@ -14,15 +15,15 @@ Here are the steps to take to make a release of pgTAP: + `ALTER EXTENSION pgtap UPDATE;` works on 9.1 and higher. - + `CREATE EXTENSION pgtap;` works on 9.1 and higer. + + `CREATE EXTENSION pgtap;` works on 9.1 and higher. + All tests pass in `make installcheck` (on 8.1, move the pgtap source - dir into the postgres source contrib directory and run + dir into the postgres source `contrib` directory and run `make NO_PGXS=1 installcheck`) -* If you've made any significant changes, test all supported version again in - foreward order (8.1, 8.2, 8.3, etc.) to make sure the changes didn't break - any later versions. +* If you've made any significant changes while testing versions backward, test + them again in forward order (8.1, 8.2, 8.3, etc.) to make sure the changes + didn't break any later versions. * Review the documentation in `doc/pgtap.mmd`, and make any necessary changes, including to the list of PostgreSQL version-compatibility notes at the end From 5649543267b562c64860c93d6e911d625c9d5817 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 16 Sep 2018 16:43:29 -0400 Subject: [PATCH 1005/1195] Update dates. --- README.md | 2 +- contrib/pgtap.spec | 3 +++ doc/pgtap.mmd | 2 +- release.md | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8c19067170da..92bbaeaadf75 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ full use of its API. It also requires PL/pgSQL. Copyright and License --------------------- -Copyright (c) 2008-2017 David E. Wheeler. Some rights reserved. +Copyright (c) 2008-2018 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index b6962999eb3b..5949d1453036 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -48,6 +48,9 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog +* Sun Sep 16 2018 David E. Wheeler 0.99.0-1 +- Update to v0.99.0. + * Mon Nov 6 2017 David E. Wheeler 0.98.0-1 - Update to v0.98.0. - Added pgTAP harness Perl module requirement. diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 693aee6f8984..a6f96fb54d90 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -8070,7 +8070,7 @@ Credits Copyright and License --------------------- -Copyright (c) 2008-2017 David E. Wheeler. Some rights reserved. +Copyright (c) 2008-2018 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is diff --git a/release.md b/release.md index 4beff9fa9da5..3c7c2251711e 100644 --- a/release.md +++ b/release.md @@ -40,7 +40,7 @@ Here are the steps to take to make a release of pgTAP: Perl module installed), then checkout the `gh-pages` branch and make these changes: - + Open `documentatoin.html` and delete all the lines between these "DOC" + + Open `documentation.html` and delete all the lines between these "DOC" comments, until the main div looks like this:
    From d2f34763409cb42a0893128510c63ce78019f34d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 16 Sep 2018 16:55:06 -0400 Subject: [PATCH 1006/1195] Timestamp v0.99.0. --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index bb701aa23316..c22130121747 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.99.0 +0.99.0 2018-09-16T20:55:41Z --------------------------- * Updated introductory documentation. A lot has changed over the years! * Added the "Secrets of the pgTAP Mavens" section of the documentation. From 3ed6d52a5ce5da2af9c566660ea83e4777b6a3e1 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 16 Sep 2018 17:02:42 -0400 Subject: [PATCH 1007/1195] Increment to v0.99.1. Starting dev toward 1.0.0. --- Changes | 3 +++ META.json | 8 ++++---- README.md | 2 +- contrib/pgtap.spec | 2 +- doc/pgtap.mmd | 2 +- pgtap.control | 2 +- release.md | 15 ++++----------- 7 files changed, 15 insertions(+), 19 deletions(-) diff --git a/Changes b/Changes index c22130121747..fc0eeefeda73 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Revision history for pgTAP ========================== +0.99.1 (Dev toward 1.0.0) +-------------------------- + 0.99.0 2018-09-16T20:55:41Z --------------------------- * Updated introductory documentation. A lot has changed over the years! diff --git a/META.json b/META.json index 87b3096b66ab..efdba4a566ea 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "0.99.0", + "version": "0.99.1", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "0.99.0" + "version": "0.99.1" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "0.99.0" + "version": "0.99.1" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "0.99.0" + "version": "0.99.1" } }, "resources": { diff --git a/README.md b/README.md index 92bbaeaadf75..84068424ee39 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 0.99.0 +pgTAP 0.99.1 ============ [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 5949d1453036..5066b83fa5a6 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 0.99.0 +Version: 0.99.1 Release: 1%{?dist} Group: Applications/Databases License: PostgreSQL diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index a6f96fb54d90..5e02103da7b8 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 0.99.0 +pgTAP 0.99.1 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/pgtap.control b/pgtap.control index f701687dbf8a..c2df5e05d5c1 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '0.99.0' +default_version = '0.99.1' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true diff --git a/release.md b/release.md index 3c7c2251711e..44ca4030dfdf 100644 --- a/release.md +++ b/release.md @@ -83,7 +83,7 @@ Here are the steps to take to make a release of pgTAP: * Commit the timestamp and tag it: git ci -m 'Timestamp v0.98.0.' - git tag -am 'Tag v0.98.0.' v0.98.0 + git tag -sm 'Tag v0.98.0.' v0.98.0 * Package the source and submit to [PGXN](http://manager.pgxn.org/). @@ -96,15 +96,8 @@ Here are the steps to take to make a release of pgTAP: git push git push --tags -* Ask one of the nice folks at [PGX](https://pgexperts.com/) to pull the - changes into [their fork](https://github.com/pgexperts/pgtap); It's their - repo that serves [pgxn.org](http://pgxn.org/). Once it's merged, check - that the changes to the - [documentation page](http://pgxn.org/documentation.html) were properly - updated. - -* Increment the version to kick off development for the next release. The - version should be added to the `Changes` file, and incremented in the +* Increment the minor version to kick off development for the next release. + The version should be added to the `Changes` file, and incremented in the following files: + `META.json` (including for the three parts of the `provides` section) @@ -115,7 +108,7 @@ Here are the steps to take to make a release of pgTAP: * Commit that change and push it. - git ci -m 'Increment to v0.99.0.' + git ci -m 'Increment to v0.98.1.' git push * Start hacking on the next version! From 2d3e07e329d9e53ef4e6e8890808e97067f4b96c Mon Sep 17 00:00:00 2001 From: dianhenglau Date: Mon, 17 Sep 2018 05:12:34 +0800 Subject: [PATCH 1008/1195] Fix documentation. (#166) * Fix signatures of `has_index`. * Change `:Type` to `:type`. Remove duplicated `isnt_aggregate` section. --- doc/pgtap.mmd | 43 ++++--------------------------------------- 1 file changed, 4 insertions(+), 39 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 5e02103da7b8..879e8fdd04e0 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -3245,12 +3245,12 @@ enum does *not* exist. SELECT has_index( :schema, :table, :index, :columns ); SELECT has_index( :schema, :table, :index, :column, :description ); SELECT has_index( :schema, :table, :index, :column ); + SELECT has_index( :schema, :table, :index, :description ); + SELECT has_index( :schema, :table, :index ); SELECT has_index( :table, :index, :columns, :description ); - SELECT has_index( :table, :index, :columns, :description ); + SELECT has_index( :table, :index, :columns ); SELECT has_index( :table, :index, :column, :description ); - SELECT has_index( :schema, :table, :index, :column ); SELECT has_index( :table, :index, :column ); - SELECT has_index( :schema, :table, :index ); SELECT has_index( :table, :index, :description ); SELECT has_index( :table, :index ); @@ -5023,7 +5023,7 @@ But then you check with `has_function()` first, right? `:args` : Array of data types for the function arguments. -`:Type` +`:type` : Return value data type. `:description` @@ -5223,41 +5223,6 @@ If the function does not exist, a handy diagnostic message will let you know: But then you check with `has_function()` first, right? -### `isnt_aggregate()` ### - - SELECT isnt_aggregate( :schema, :function, :args, :description ); - SELECT isnt_aggregate( :schema, :function, :args ); - SELECT isnt_aggregate( :schema, :function, :description ); - SELECT isnt_aggregate( :schema, :function ); - SELECT isnt_aggregate( :function, :args, :description ); - SELECT isnt_aggregate( :function, :args ); - SELECT isnt_aggregate( :function, :description ); - SELECT isnt_aggregate( :function ); - -**Parameters** - -`:schema` -: Schema in which to find the function. - -`:function` -: Function name. - -`:args` -: Array of data types for the function arguments. - -`:description` -: A short description of the test. - -This function is the inverse of `is_aggregate()`. The test passes if the specified -function is not an aggregate function. - -If the function does not exist, a handy diagnostic message will let you know: - - # Failed test 290: "Function nasty() should not be an aggregate function" - # Function nasty() does not exist - -But then you check with `has_function()` first, right? - ### `is_aggregate()` ### SELECT is_aggregate( :schema, :function, :args, :description ); From 0f2d0f55fe337993cab1bf8b8e62daea2396ea09 Mon Sep 17 00:00:00 2001 From: Marc Cousin Date: Sun, 16 Sep 2018 23:13:18 +0200 Subject: [PATCH 1009/1195] Fix tocgen for Linux The shebang is not split on linux, so #!/usr/bin/env perl -n -pi searches for "perl -n -pi" as an executable. --- tocgen | 63 +++++++++++++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/tocgen b/tocgen index f769562adea0..2dd1e1cea8d8 100755 --- a/tocgen +++ b/tocgen @@ -1,4 +1,8 @@ -#!/usr/bin/env perl -n -pi +#!/usr/bin/env perl + +use English; +$INPLACE_EDIT=1; + our $prevn; @@ -7,33 +11,38 @@ BEGIN { print STDERR "

    Contents

    \n
    \n" } From 267081f50b304be95e22e02eeab22236c0cf83af Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 16 Sep 2018 17:14:12 -0400 Subject: [PATCH 1010/1195] Fix bugs and merge PRs before release. --- release.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/release.md b/release.md index 44ca4030dfdf..0a6bed6ccf3d 100644 --- a/release.md +++ b/release.md @@ -3,6 +3,12 @@ pgTAP Release Management Here are the steps to take to make a release of pgTAP: +* Review and fix any open bugs in the + [issue tracker](https://github.com/theory/pgtap/issues). + +* Review and merge any appropriate + [pull requests](https://github.com/theory/pgtap/pulls). + * Test on all supported PostgreSQL versions, starting with the latest version (10) and moving backward in order (9.6, 9.5, 9.4, etc.). [pgenv](https://github.com/theory/pgenv/) is a handy tool for installing and From 024754675b63d5e369d12b268b7caa40763f77ba Mon Sep 17 00:00:00 2001 From: Kamen Naydenov Date: Tue, 16 Oct 2018 17:40:00 +0300 Subject: [PATCH 1011/1195] fix: generation of uninstall script Current regexp replace generates 'DROPTYPE' in uninstall script for '_time_trial_type'. Adding ' ' to both pattern and replacement fix this. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 26e7d6026be4..b5680200f773 100644 --- a/Makefile +++ b/Makefile @@ -172,7 +172,7 @@ ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][012]|8[.][1234])" && echo yes | endif sql/uninstall_pgtap.sql: sql/pgtap.sql test/setup.sql - grep '^CREATE ' sql/pgtap.sql | $(PERL) -e 'for (reverse ) { chomp; s/CREATE (OR REPLACE)?/DROP/; print "$$_;\n" }' > sql/uninstall_pgtap.sql + grep '^CREATE ' sql/pgtap.sql | $(PERL) -e 'for (reverse ) { chomp; s/CREATE (OR REPLACE )?/DROP /; print "$$_;\n" }' > sql/uninstall_pgtap.sql sql/pgtap-static.sql: sql/pgtap.sql.in cp $< $@ From e1e11915e15198e9994cb293a382ce27ebe1a5a2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 17 Oct 2018 09:13:51 -0400 Subject: [PATCH 1012/1195] Note fix from @pau4o #181. --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index fc0eeefeda73..667caa52cd45 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,8 @@ Revision history for pgTAP 0.99.1 (Dev toward 1.0.0) -------------------------- +* Fixed uninstall script generation to properly emit `DROP TYPE` statements. + Thanks to Kamen Naydenov for the PR (#181)! 0.99.0 2018-09-16T20:55:41Z --------------------------- From 651f95c3a560ea33ec751722be34c4c592b3091e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 31 Oct 2018 08:16:47 -0400 Subject: [PATCH 1013/1195] Test on Postgres 11. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index e265e6ac174e..02ca1e4bee70 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,4 +14,5 @@ env: - PGVERSION=9.5 - PGVERSION=9.6 - PGVERSION=10 + - PGVERSION=11 script: bash ./pg-travis-test.sh From 2682ab691ea34af46fc0071c0d235c748c59bc7e Mon Sep 17 00:00:00 2001 From: Kamen Naydenov Date: Mon, 5 Nov 2018 22:21:20 +0200 Subject: [PATCH 1014/1195] Add `policies_are()` Functions to test that polices attached to the named table are only those listed in function arguments. --- sql/pgtap.sql.in | 75 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 484223224f3a..3320bc80be29 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -10035,4 +10035,77 @@ RETURNS text AS $$ FROM generate_series(1, array_upper($1, 1)) s(i) ORDER BY $1[i] ), $2); -$$ LANGUAGE SQL immutable; \ No newline at end of file +$$ 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; From 7493f7d7c5cbdab4e1603816a2084bfbaef14662 Mon Sep 17 00:00:00 2001 From: Kamen Naydenov Date: Mon, 5 Nov 2018 22:59:56 +0200 Subject: [PATCH 1015/1195] Add documentation for `policies_are()` --- doc/pgtap.mmd | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 879e8fdd04e0..2f6c07bc57b7 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7157,6 +7157,48 @@ something like: # Failed test 17: "Role slim should be granted USAGE on oltp" # Role slim does not exist +### `policies_are()` ### + + SELECT policies_are( :schema, :table, :policies, :description ); + SELECT policies_are( :schema, :table, :policies ); + SELECT policies_are( :table, :policies, :description ); + SELECT policies_are( :table, :policies ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the `:table`. + +`:table` +: Name of a table in which to find policies. + +`:policies` +: An array of policy names. + +`:description` +: A short description of the test. + +This function tests that all of the policies on the named table are only the +policies that *should* be on that table. If the `:schema` argument is omitted, +the table must be visible in the search path, excluding `pg_catalog` and +`information_schema`. If the description is omitted, a generally useful +default description will be generated. Example: + + SELECT policies_are( + 'myschema', + 'atable', + ARRAY[ 'atable_policy_one', 'atable_policy_two' ] + ); + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing policies, like so: + + # Failed test 13: "Table myschema.atable should have the correct policies" + # Extra policies: + # policy_for_atable + # Missing policies: + # atable_policy_two + No Test for the Wicked ====================== From 318d4a85f49e808fe12968b569355cf819a24ef0 Mon Sep 17 00:00:00 2001 From: Kamen Naydenov Date: Mon, 12 Nov 2018 13:40:00 +0200 Subject: [PATCH 1016/1195] Add tests for `policies_are()` --- test/expected/policy.out | 32 +++++++++ test/sql/policy.sql | 151 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 test/expected/policy.out create mode 100644 test/sql/policy.sql diff --git a/test/expected/policy.out b/test/expected/policy.out new file mode 100644 index 000000000000..40c0c9a6562d --- /dev/null +++ b/test/expected/policy.out @@ -0,0 +1,32 @@ +\unset ECHO +1..30 +ok 1 - policies_are(schema, table, policies, desc) should pass +ok 2 - policies_are(schema, table, policies, desc) should have the proper description +ok 3 - policies_are(schema, table, policies, desc) should have the proper diagnostics +ok 4 - policies_are(schema, table, policies) should pass +ok 5 - policies_are(schema, table, policies) should have the proper description +ok 6 - policies_are(schema, table, policies) should have the proper diagnostics +ok 7 - policies_are(schema, table, policies) + extra should fail +ok 8 - policies_are(schema, table, policies) + extra should have the proper description +ok 9 - policies_are(schema, table, policies) + extra should have the proper diagnostics +ok 10 - policies_are(schema, table, policies) + missing should fail +ok 11 - policies_are(schema, table, policies) + missing should have the proper description +ok 12 - policies_are(schema, table, policies) + missing should have the proper diagnostics +ok 13 - policies_are(schema, table, policies) + extra & missing should fail +ok 14 - policies_are(schema, table, policies) + extra & missing should have the proper description +ok 15 - policies_are(schema, table, policies) + extra & missing should have the proper diagnostics +ok 16 - policies_are(table, policies, desc) should pass +ok 17 - policies_are(table, policies, desc) should have the proper description +ok 18 - policies_are(table, policies, desc) should have the proper diagnostics +ok 19 - policies_are(table, policies) should pass +ok 20 - policies_are(table, policies) should have the proper description +ok 21 - policies_are(table, policies) should have the proper diagnostics +ok 22 - policies_are(table, policies) + extra should fail +ok 23 - policies_are(table, policies) + extra should have the proper description +ok 24 - policies_are(table, policies) + extra should have the proper diagnostics +ok 25 - policies_are(table, policies) + missing should fail +ok 26 - policies_are(table, policies) + missing should have the proper description +ok 27 - policies_are(table, policies) + missing should have the proper diagnostics +ok 28 - policies_are(table, policies) + extra & missing should fail +ok 29 - policies_are(table, policies) + extra & missing should have the proper description +ok 30 - policies_are(table, policies) + extra & missing should have the proper diagnostics diff --git a/test/sql/policy.sql b/test/sql/policy.sql new file mode 100644 index 000000000000..b9b7ce95d2b0 --- /dev/null +++ b/test/sql/policy.sql @@ -0,0 +1,151 @@ +\unset ECHO +\i test/setup.sql + +SELECT plan(30); +--SELECT * FROM no_plan(); + +-- This will be rolled back. :-) +SET client_min_messages = warning; +CREATE TABLE public.passwd( + user_name TEXT UNIQUE NOT NULL, + pwhash TEXT NOT NULL, + uid INT PRIMARY KEY, + gid INT NOT NULL, + real_name TEXT NOT NULL, + home_phone TEXT, + extra_info TEXT, + home_dir TEXT NOT NULL, + shell TEXT NOT NULL +); + +CREATE ROLE root; +CREATE ROLE bob; +CREATE ROLE alice; +CREATE ROLE daemon; + +INSERT INTO public.passwd VALUES + ('admin','xxx',0,0,'Admin','111-222-3333',null,'/root','/bin/dash'); +INSERT INTO public.passwd VALUES + ('bob','xxx',1,1,'Bob','123-456-7890',null,'/home/bob','/bin/zsh'); +INSERT INTO public.passwd VALUES + ('alice','xxx',2,1,'Alice','098-765-4321',null,'/home/alice','/bin/zsh'); +INSERT INTO public.passwd VALUES + ('daemon','xxx',3,2,'daemon server',null,null,'/var/lib/daemon','/usr/sbin/nologin'); + +ALTER TABLE public.passwd ENABLE ROW LEVEL SECURITY; + +-- Create policies +-- Administrator can see all rows and add any rows +CREATE POLICY root_all ON public.passwd TO root USING (true) WITH CHECK (true); +-- Normal users can view all rows +CREATE POLICY all_view ON public.passwd FOR SELECT USING (true); +-- Normal users can update their own records, but +-- limit which shells a normal user is allowed to set +CREATE POLICY user_mod ON public.passwd FOR UPDATE + USING (current_user = user_name) + WITH CHECK ( + current_user = user_name AND + shell IN ('/bin/bash','/bin/sh','/bin/dash','/bin/zsh','/bin/tcsh') + ); +-- Daemons can insert and delete records with gid > 100 +CREATE POLICY daemon_insert ON public.passwd FOR INSERT TO daemon WITH CHECK (gid > 100); +CREATE POLICY daemon_delete ON public.passwd FOR DELETE TO daemon USING (gid > 100); + +RESET client_min_messages; + +/****************************************************************************/ +-- Test policies_are(). +SELECT * FROM check_test( + policies_are( 'public', 'passwd', ARRAY['root_all', 'all_view', 'user_mod', 'daemon_insert', 'daemon_delete'], 'whatever' ), + true, + 'policies_are(schema, table, policies, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + policies_are( 'public', 'passwd', ARRAY['root_all', 'all_view', 'user_mod', 'daemon_insert', 'daemon_delete'] ), + true, + 'policies_are(schema, table, policies)', + 'Table public.passwd should have the correct policies', + '' +); + +SELECT * FROM check_test( + policies_are( 'public', 'passwd', ARRAY['all_view', 'user_mod', 'daemon_insert', 'daemon_delete'] ), + false, + 'policies_are(schema, table, policies) + extra', + 'Table public.passwd should have the correct policies', + ' Extra policies: + root_all' +); + +SELECT * FROM check_test( + policies_are( 'public', 'passwd', ARRAY['root_all', 'all_view', 'user_mod', 'daemon_insert', 'daemon_delete', 'extra_policy'] ), + false, + 'policies_are(schema, table, policies) + missing', + 'Table public.passwd should have the correct policies', + ' Missing policies: + extra_policy' +); + +SELECT * FROM check_test( + policies_are( 'public', 'passwd', ARRAY['all_view', 'user_mod', 'daemon_insert', 'daemon_delete', 'extra_policy'] ), + false, + 'policies_are(schema, table, policies) + extra & missing', + 'Table public.passwd should have the correct policies', + ' Extra policies: + root_all + Missing policies: + extra_policy' +); + +SELECT * FROM check_test( + policies_are( 'passwd', ARRAY['root_all', 'all_view', 'user_mod', 'daemon_insert', 'daemon_delete'], 'whatever' ), + true, + 'policies_are(table, policies, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + policies_are( 'passwd', ARRAY['root_all', 'all_view', 'user_mod', 'daemon_insert', 'daemon_delete'] ), + true, + 'policies_are(table, policies)', + 'Table passwd should have the correct policies', + '' +); + +SELECT * FROM check_test( + policies_are( 'passwd', ARRAY['all_view', 'user_mod', 'daemon_insert', 'daemon_delete'] ), + false, + 'policies_are(table, policies) + extra', + 'Table passwd should have the correct policies', + ' Extra policies: + root_all' +); + +SELECT * FROM check_test( + policies_are( 'passwd', ARRAY['root_all', 'all_view', 'user_mod', 'daemon_insert', 'daemon_delete', 'extra_policy'] ), + false, + 'policies_are(table, policies) + missing', + 'Table passwd should have the correct policies', + ' Missing policies: + extra_policy' +); + +SELECT * FROM check_test( + policies_are( 'passwd', ARRAY['all_view', 'user_mod', 'daemon_insert', 'daemon_delete', 'extra_policy'] ), + false, + 'policies_are(table, policies) + extra & missing', + 'Table passwd should have the correct policies', + ' Extra policies: + root_all + Missing policies: + extra_policy' +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From 47eb9d7f4a7c379025b92dabbcef2be49cf61df1 Mon Sep 17 00:00:00 2001 From: Kamen Naydenov Date: Thu, 15 Nov 2018 13:35:36 +0200 Subject: [PATCH 1017/1195] Add `policy_roles_are()` Functions that test whether the roles to which policy applies are just those listed in the function arguments. --- sql/pgtap.sql.in | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 3320bc80be29..a8696d868f6d 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -10109,3 +10109,85 @@ 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; From e2af12cc5ee7985cf67b347df87f0d4fca4e02ff Mon Sep 17 00:00:00 2001 From: Kamen Naydenov Date: Thu, 15 Nov 2018 14:05:07 +0200 Subject: [PATCH 1018/1195] Add documentation for `policy_roles_are()` --- doc/pgtap.mmd | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 2f6c07bc57b7..ce29dcfe59bb 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7199,6 +7199,52 @@ missing policies, like so: # Missing policies: # atable_policy_two +### `policy_roles_are()` ### + + SELECT policy_roles_are( :schema, :table, :policy, :roles, :description ); + SELECT policy_roles_are( :schema, :table, :policy, :roles ); + SELECT policy_roles_are( :table, :policy, :roles, :description ); + SELECT policy_roles_are( :table, :policy, :roles ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the `:table`. + +`:table` +: Name of a table to which `:policy` applies. + +`:policy` +: Name of a policy. + +`:roles` +: An array of role names to which `:policy` is applied. + +`:description` +: A short description of the test. + +This function tests whether the roles to which policy applies are only the +roles that *should* be on that policy. If the `:schema` argument is omitted, +the table must be visible in the search path, excluding `pg_catalog` and +`information_schema`. If the description is omitted, a generally useful +default description will be generated. Example: + + SELECT policy_roles_are( + 'myschema', + 'atable', + 'apolicy' + ARRAY[ 'atable_apolicy_role_one', 'atable_apolicy_role_two' ] + ); + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing policy roles, like so: + + # Failed test 13: "Policy apolicy for table myschema.atable should have the correct roles" + # Extra policy roles: + # arole_one + # Missing policy roles: + # atable_apolicy_role_one + No Test for the Wicked ====================== From 538b8f379e0d5d4943cc1581f18c360c8a21a9a2 Mon Sep 17 00:00:00 2001 From: Kamen Naydenov Date: Thu, 15 Nov 2018 23:50:52 +0200 Subject: [PATCH 1019/1195] Add tests for `policy_roles_are()` --- test/expected/policy.out | 32 +++++++++++++- test/sql/policy.sql | 96 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 125 insertions(+), 3 deletions(-) diff --git a/test/expected/policy.out b/test/expected/policy.out index 40c0c9a6562d..5d2c084628a1 100644 --- a/test/expected/policy.out +++ b/test/expected/policy.out @@ -1,5 +1,5 @@ \unset ECHO -1..30 +1..60 ok 1 - policies_are(schema, table, policies, desc) should pass ok 2 - policies_are(schema, table, policies, desc) should have the proper description ok 3 - policies_are(schema, table, policies, desc) should have the proper diagnostics @@ -30,3 +30,33 @@ ok 27 - policies_are(table, policies) + missing should have the proper diagnosti ok 28 - policies_are(table, policies) + extra & missing should fail ok 29 - policies_are(table, policies) + extra & missing should have the proper description ok 30 - policies_are(table, policies) + extra & missing should have the proper diagnostics +ok 31 - policy_roles_are(schema, table, policy, roles, desc) should pass +ok 32 - policy_roles_are(schema, table, policy, roles, desc) should have the proper description +ok 33 - policy_roles_are(schema, table, policy, roles, desc) should have the proper diagnostics +ok 34 - policy_roles_are(schema, table, policy, roles) should pass +ok 35 - policy_roles_are(schema, table, policy, roles) should have the proper description +ok 36 - policy_roles_are(schema, table, policy, roles) should have the proper diagnostics +ok 37 - policy_roles_are(schema, table, policy, roles) + extra should fail +ok 38 - policy_roles_are(schema, table, policy, roles) + extra should have the proper description +ok 39 - policy_roles_are(schema, table, policy, roles) + extra should have the proper diagnostics +ok 40 - policy_roles_are(schema, table, policy, roles) + missing should fail +ok 41 - policy_roles_are(schema, table, policy, roles) + missing should have the proper description +ok 42 - policy_roles_are(schema, table, policy, roles) + missing should have the proper diagnostics +ok 43 - policy_roles_are(schema, table, policy, roles) + extra & missing should fail +ok 44 - policy_roles_are(schema, table, policy, roles) + extra & missing should have the proper description +ok 45 - policy_roles_are(schema, table, policy, roles) + extra & missing should have the proper diagnostics +ok 46 - policy_roles_are(table, policy, roles, desc) should pass +ok 47 - policy_roles_are(table, policy, roles, desc) should have the proper description +ok 48 - policy_roles_are(table, policy, roles, desc) should have the proper diagnostics +ok 49 - policy_roles_are(table, policy, roles) should pass +ok 50 - policy_roles_are(table, policy, roles) should have the proper description +ok 51 - policy_roles_are(table, policy, roles) should have the proper diagnostics +ok 52 - policy_roles_are(table, policy, roles) + extra should fail +ok 53 - policy_roles_are(table, policy, roles) + extra should have the proper description +ok 54 - policy_roles_are(table, policy, roles) + extra should have the proper diagnostics +ok 55 - policy_roles_are(table, policy, roles) + missing should fail +ok 56 - policy_roles_are(table, policy, roles) + missing should have the proper description +ok 57 - policy_roles_are(table, policy, roles) + missing should have the proper diagnostics +ok 58 - policy_roles_are(table, policy, roles) + extra & missing should fail +ok 59 - policy_roles_are(table, policy, roles) + extra & missing should have the proper description +ok 60 - policy_roles_are(table, policy, roles) + extra & missing should have the proper diagnostics diff --git a/test/sql/policy.sql b/test/sql/policy.sql index b9b7ce95d2b0..eb88f1dcc41d 100644 --- a/test/sql/policy.sql +++ b/test/sql/policy.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(30); +SELECT plan(60); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -38,7 +38,7 @@ ALTER TABLE public.passwd ENABLE ROW LEVEL SECURITY; -- Administrator can see all rows and add any rows CREATE POLICY root_all ON public.passwd TO root USING (true) WITH CHECK (true); -- Normal users can view all rows -CREATE POLICY all_view ON public.passwd FOR SELECT USING (true); +CREATE POLICY all_view ON public.passwd FOR SELECT TO root, bob, daemon USING (true); -- Normal users can update their own records, but -- limit which shells a normal user is allowed to set CREATE POLICY user_mod ON public.passwd FOR UPDATE @@ -145,6 +145,98 @@ SELECT * FROM check_test( extra_policy' ); +/****************************************************************************/ +-- Test policy_roles_are(). +SELECT * FROM check_test( + policy_roles_are( 'public', 'passwd', 'all_view', ARRAY['root', 'bob', 'daemon'], 'whatever' ), + true, + 'policy_roles_are(schema, table, policy, roles, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + policy_roles_are( 'public', 'passwd', 'all_view', ARRAY['root', 'bob', 'daemon'] ), + true, + 'policy_roles_are(schema, table, policy, roles)', + 'Policy all_view for table public.passwd should have the correct roles', + '' +); + +SELECT * FROM check_test( + policy_roles_are( 'public', 'passwd', 'all_view', ARRAY['bob', 'daemon'] ), + false, + 'policy_roles_are(schema, table, policy, roles) + extra', + 'Policy all_view for table public.passwd should have the correct roles', + ' Extra policy roles: + root' +); + +SELECT * FROM check_test( + policy_roles_are( 'public', 'passwd', 'all_view', ARRAY['root', 'bob', 'daemon', 'alice'] ), + false, + 'policy_roles_are(schema, table, policy, roles) + missing', + 'Policy all_view for table public.passwd should have the correct roles', + ' Missing policy roles: + alice' +); + +SELECT * FROM check_test( + policy_roles_are( 'public', 'passwd', 'all_view', ARRAY['bob', 'daemon', 'alice'] ), + false, + 'policy_roles_are(schema, table, policy, roles) + extra & missing', + 'Policy all_view for table public.passwd should have the correct roles', + ' Extra policy roles: + root + Missing policy roles: + alice' +); + +SELECT * FROM check_test( + policy_roles_are( 'passwd', 'all_view', ARRAY['root', 'bob', 'daemon'], 'whatever' ), + true, + 'policy_roles_are(table, policy, roles, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + policy_roles_are( 'passwd', 'all_view', ARRAY['root', 'bob', 'daemon'] ), + true, + 'policy_roles_are(table, policy, roles)', + 'Policy all_view for table passwd should have the correct roles', + '' +); + +SELECT * FROM check_test( + policy_roles_are( 'passwd', 'all_view', ARRAY['bob', 'daemon'] ), + false, + 'policy_roles_are(table, policy, roles) + extra', + 'Policy all_view for table passwd should have the correct roles', + ' Extra policy roles: + root' +); + +SELECT * FROM check_test( + policy_roles_are( 'passwd', 'all_view', ARRAY['root', 'bob', 'daemon', 'alice'] ), + false, + 'policy_roles_are(table, policy, roles) + missing', + 'Policy all_view for table passwd should have the correct roles', + ' Missing policy roles: + alice' +); + +SELECT * FROM check_test( + policy_roles_are( 'passwd', 'all_view', ARRAY['bob', 'daemon', 'alice'] ), + false, + 'policy_roles_are(table, policy, roles) + extra & missing', + 'Policy all_view for table passwd should have the correct roles', + ' Extra policy roles: + root + Missing policy roles: + alice' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 74bd9fc8f071080469c43de46a8f89a99ea8a7c4 Mon Sep 17 00:00:00 2001 From: Kamen Naydenov Date: Fri, 16 Nov 2018 11:28:39 +0200 Subject: [PATCH 1020/1195] Add `policy_cmd_is()` Functions to test that table policy apply to correct command. --- sql/pgtap.sql.in | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index a8696d868f6d..34e5f452deeb 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -10191,3 +10191,75 @@ 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; From 356903f330c8a520f5940b211d8e798f4456dfac Mon Sep 17 00:00:00 2001 From: Kamen Naydenov Date: Fri, 16 Nov 2018 13:03:55 +0200 Subject: [PATCH 1021/1195] Add documentation for `policy_cmd_is()` --- doc/pgtap.mmd | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index ce29dcfe59bb..37a0ba813d6a 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -7245,6 +7245,60 @@ missing policy roles, like so: # Missing policy roles: # atable_apolicy_role_one +### `policy_cmd_is()` ### + + SELECT policy_cmd_is( :schema, :table, :policy, :command, :description ); + SELECT policy_cmd_is( :schema, :table, :policy, :command ); + SELECT policy_cmd_is( :table, :policy, :command, :description ); + SELECT policy_cmd_is( :table, :policy, :command ); + +**Parameters** + +`:schema` +: Name of a schema in which to find the `:table`. + +`:table` +: Name of a table to which `:policy` applies. + +`:policy` +: Name of a policy. + +`:command` +: The command type to which the `:policy` is applied. + +`:description` +: A short description of the test. + +This function tests whether the command to which policy applies is same as +command that is given in function arguments. + +The available policy `:command` types are: + +* SELECT +* INSERT +* UPDATE +* DELETE +* ALL + +If the `:schema` argument is omitted, the table must be visible in the search +path, excluding `pg_catalog` and `information_schema`. If the `:description` is +omitted (but `:schema` is present, be sure to cast `:policy` to name), a +generally useful default description will be generated. Example: + + SELECT policy_cmd_is( + 'myschema', + 'atable', + 'apolicy'::NAME + 'all' + ); + +In the event of a failure, you'll see diagnostics listing the extra and/or +missing policy command, like so: + + # Failed test 13: "Policy apolicy for table myschema.atable should apply to ALL command" + # have: INSERT + # want: ALL + No Test for the Wicked ====================== From fdd8bea66ddbefbb77fba2e10714485c052b8e71 Mon Sep 17 00:00:00 2001 From: Kamen Naydenov Date: Fri, 16 Nov 2018 14:32:49 +0200 Subject: [PATCH 1022/1195] Add tests for `policy_cmd_is()` --- test/expected/policy.out | 42 ++++- test/sql/policy.sql | 344 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 384 insertions(+), 2 deletions(-) diff --git a/test/expected/policy.out b/test/expected/policy.out index 5d2c084628a1..f505275c713b 100644 --- a/test/expected/policy.out +++ b/test/expected/policy.out @@ -1,5 +1,5 @@ \unset ECHO -1..60 +1..100 ok 1 - policies_are(schema, table, policies, desc) should pass ok 2 - policies_are(schema, table, policies, desc) should have the proper description ok 3 - policies_are(schema, table, policies, desc) should have the proper diagnostics @@ -60,3 +60,43 @@ ok 57 - policy_roles_are(table, policy, roles) + missing should have the proper ok 58 - policy_roles_are(table, policy, roles) + extra & missing should fail ok 59 - policy_roles_are(table, policy, roles) + extra & missing should have the proper description ok 60 - policy_roles_are(table, policy, roles) + extra & missing should have the proper diagnostics +ok 61 - policy_cmd_is(schema, table, policy, command, desc) for SELECT +ok 62 - policy_cmd_is(schema, table, policy, command) for SELECT +ok 63 - policy_cmd_is(table, policy, command, desc) for SELECT +ok 64 - policy_cmd_is(table, policy, command) for SELECT +ok 65 - policy_cmd_is(schema, table, policy, command, desc) for SELECT should fail +ok 66 - policy_cmd_is(schema, table, policy, command) for SELECT should fail +ok 67 - policy_cmd_is(table, policy, command, desc) for SELECT should fail +ok 68 - policy_cmd_is(table, policy, command) for SELECT should fail +ok 69 - policy_cmd_is(schema, table, policy, command, desc) for INSERT +ok 70 - policy_cmd_is(schema, table, policy, command) for INSERT +ok 71 - policy_cmd_is(table, policy, command, desc) for INSERT +ok 72 - policy_cmd_is(table, policy, command) for INSERT +ok 73 - policy_cmd_is(schema, table, policy, command, desc) for INSERT should fail +ok 74 - policy_cmd_is(schema, table, policy, command) for INSERT should fail +ok 75 - policy_cmd_is(table, policy, command, desc) for INSERT should fail +ok 76 - policy_cmd_is(table, policy, command) for INSERT should fail +ok 77 - policy_cmd_is(schema, table, policy, command, desc) for UPDATE +ok 78 - policy_cmd_is(schema, table, policy, command) for UPDATE +ok 79 - policy_cmd_is(table, policy, command, desc) for UPDATE +ok 80 - policy_cmd_is(table, policy, command) for UPDATE +ok 81 - policy_cmd_is(schema, table, policy, command, desc) for UPDATE should fail +ok 82 - policy_cmd_is(schema, table, policy, command) for UPDATE should fail +ok 83 - policy_cmd_is(table, policy, command, desc) for UPDATE should fail +ok 84 - policy_cmd_is(table, policy, command) for UPDATE should fail +ok 85 - policy_cmd_is(schema, table, policy, command, desc) for DELETE +ok 86 - policy_cmd_is(schema, table, policy, command) for DELETE +ok 87 - policy_cmd_is(table, policy, command, desc) for DELETE +ok 88 - policy_cmd_is(table, policy, command) for DELETE +ok 89 - policy_cmd_is(schema, table, policy, command, desc) for DELETE should fail +ok 90 - policy_cmd_is(schema, table, policy, command) for DELETE should fail +ok 91 - policy_cmd_is(table, policy, command, desc) for DELETE should fail +ok 92 - policy_cmd_is(table, policy, command) for DELETE should fail +ok 93 - policy_cmd_is(schema, table, policy, command, desc) for ALL +ok 94 - policy_cmd_is(schema, table, policy, command) for ALL +ok 95 - policy_cmd_is(table, policy, command, desc) for ALL +ok 96 - policy_cmd_is(table, policy, command) for ALL +ok 97 - policy_cmd_is(schema, table, policy, command, desc) for ALL should fail +ok 98 - policy_cmd_is(schema, table, policy, command) for ALL should fail +ok 99 - policy_cmd_is(table, policy, command, desc) for ALL should fail +ok 100 - policy_cmd_is(table, policy, command) for ALL should fail \ No newline at end of file diff --git a/test/sql/policy.sql b/test/sql/policy.sql index eb88f1dcc41d..59f34c318781 100644 --- a/test/sql/policy.sql +++ b/test/sql/policy.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(60); +SELECT plan(180); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -237,6 +237,348 @@ SELECT * FROM check_test( alice' ); +/****************************************************************************/ +-- Test policy_cmd_is(). +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'all_view', 'select', 'whatever' ), + true, + 'policy_cmd_is(schema, table, policy, command, desc) for SELECT', + 'whatever', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'all_view'::NAME, 'select' ), + true, + 'policy_cmd_is(schema, table, policy, command) for SELECT', + 'Policy all_view for table public.passwd should apply to SELECT command', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'all_view', 'select', 'whatever' ), + true, + 'policy_cmd_is(table, policy, command, desc) for SELECT', + 'whatever', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'all_view', 'select' ), + true, + 'policy_cmd_is(table, policy, command) for SELECT', + 'Policy all_view for table passwd should apply to SELECT command', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'all_view', 'delete', 'whatever' ), + false, + 'policy_cmd_is(schema, table, policy, command, desc) for SELECT should fail', + 'whatever', + ' have: SELECT + want: DELETE' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'all_view'::NAME, 'delete' ), + false, + 'policy_cmd_is(schema, table, policy, command) for SELECT should fail', + 'Policy all_view for table public.passwd should apply to DELETE command', + ' have: SELECT + want: DELETE' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'all_view', 'delete', 'whatever' ), + false, + 'policy_cmd_is(table, policy, command, desc) for SELECT should fail', + 'whatever', + ' have: SELECT + want: DELETE' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'all_view', 'delete' ), + false, + 'policy_cmd_is(table, policy, command) for SELECT should fail', + 'Policy all_view for table passwd should apply to DELETE command', + ' have: SELECT + want: DELETE' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'daemon_insert', 'insert', 'whatever' ), + true, + 'policy_cmd_is(schema, table, policy, command, desc) for INSERT', + 'whatever', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'daemon_insert'::NAME, 'insert' ), + true, + 'policy_cmd_is(schema, table, policy, command) for INSERT', + 'Policy daemon_insert for table public.passwd should apply to INSERT command', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'daemon_insert', 'insert', 'whatever' ), + true, + 'policy_cmd_is(table, policy, command, desc) for INSERT', + 'whatever', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'daemon_insert', 'insert' ), + true, + 'policy_cmd_is(table, policy, command) for INSERT', + 'Policy daemon_insert for table passwd should apply to INSERT command', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'daemon_insert', 'delete', 'whatever' ), + false, + 'policy_cmd_is(schema, table, policy, command, desc) for INSERT should fail', + 'whatever', + ' have: INSERT + want: DELETE' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'daemon_insert'::NAME, 'delete' ), + false, + 'policy_cmd_is(schema, table, policy, command) for INSERT should fail', + 'Policy daemon_insert for table public.passwd should apply to DELETE command', + ' have: INSERT + want: DELETE' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'daemon_insert', 'delete', 'whatever' ), + false, + 'policy_cmd_is(table, policy, command, desc) for INSERT should fail', + 'whatever', + ' have: INSERT + want: DELETE' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'daemon_insert', 'delete' ), + false, + 'policy_cmd_is(table, policy, command) for INSERT should fail', + 'Policy daemon_insert for table passwd should apply to DELETE command', + ' have: INSERT + want: DELETE' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'user_mod', 'update', 'whatever' ), + true, + 'policy_cmd_is(schema, table, policy, command, desc) for UPDATE', + 'whatever', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'user_mod'::NAME, 'update' ), + true, + 'policy_cmd_is(schema, table, policy, command) for UPDATE', + 'Policy user_mod for table public.passwd should apply to UPDATE command', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'user_mod', 'update', 'whatever' ), + true, + 'policy_cmd_is(table, policy, command, desc) for UPDATE', + 'whatever', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'user_mod', 'update' ), + true, + 'policy_cmd_is(table, policy, command) for UPDATE', + 'Policy user_mod for table passwd should apply to UPDATE command', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'user_mod', 'delete', 'whatever' ), + false, + 'policy_cmd_is(schema, table, policy, command, desc) for UPDATE should fail', + 'whatever', + ' have: UPDATE + want: DELETE' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'user_mod'::NAME, 'all' ), + false, + 'policy_cmd_is(schema, table, policy, command) for UPDATE should fail', + 'Policy user_mod for table public.passwd should apply to ALL command', + ' have: UPDATE + want: ALL' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'user_mod', 'all', 'whatever' ), + false, + 'policy_cmd_is(table, policy, command, desc) for UPDATE should fail', + 'whatever', + ' have: UPDATE + want: ALL' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'user_mod', 'all' ), + false, + 'policy_cmd_is(table, policy, command) for UPDATE should fail', + 'Policy user_mod for table passwd should apply to ALL command', + ' have: UPDATE + want: ALL' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'daemon_delete', 'delete', 'whatever' ), + true, + 'policy_cmd_is(schema, table, policy, command, desc) for DELETE', + 'whatever', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'daemon_delete'::NAME, 'delete' ), + true, + 'policy_cmd_is(schema, table, policy, command) for DELETE', + 'Policy daemon_delete for table public.passwd should apply to DELETE command', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'daemon_delete', 'delete', 'whatever' ), + true, + 'policy_cmd_is(table, policy, command, desc) for DELETE', + 'whatever', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'daemon_delete', 'delete' ), + true, + 'policy_cmd_is(table, policy, command) for DELETE', + 'Policy daemon_delete for table passwd should apply to DELETE command', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'daemon_delete', 'all', 'whatever' ), + false, + 'policy_cmd_is(schema, table, policy, command, desc) for DELETE should fail', + 'whatever', + ' have: DELETE + want: ALL' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'daemon_delete'::NAME, 'all' ), + false, + 'policy_cmd_is(schema, table, policy, command) for DELETE should fail', + 'Policy daemon_delete for table public.passwd should apply to ALL command', + ' have: DELETE + want: ALL' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'daemon_delete', 'all', 'whatever' ), + false, + 'policy_cmd_is(table, policy, command, desc) for DELETE should fail', + 'whatever', + ' have: DELETE + want: ALL' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'daemon_delete', 'all' ), + false, + 'policy_cmd_is(table, policy, command) for DELETE should fail', + 'Policy daemon_delete for table passwd should apply to ALL command', + ' have: DELETE + want: ALL' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'root_all', 'all', 'whatever' ), + true, + 'policy_cmd_is(schema, table, policy, command, desc) for ALL', + 'whatever', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'root_all'::NAME, 'all' ), + true, + 'policy_cmd_is(schema, table, policy, command) for ALL', + 'Policy root_all for table public.passwd should apply to ALL command', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'root_all', 'all', 'whatever' ), + true, + 'policy_cmd_is(table, policy, command, desc) for ALL', + 'whatever', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'root_all', 'all' ), + true, + 'policy_cmd_is(table, policy, command) for ALL', + 'Policy root_all for table passwd should apply to ALL command', + '' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'root_all', 'delete', 'whatever' ), + false, + 'policy_cmd_is(schema, table, policy, command, desc) for ALL should fail', + 'whatever', + ' have: ALL + want: DELETE' +); + +SELECT * FROM check_test( + policy_cmd_is( 'public', 'passwd', 'root_all'::NAME, 'delete' ), + false, + 'policy_cmd_is(schema, table, policy, command) for ALL should fail', + 'Policy root_all for table public.passwd should apply to DELETE command', + ' have: ALL + want: DELETE' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'root_all', 'delete', 'whatever' ), + false, + 'policy_cmd_is(table, policy, command, desc) for ALL should fail', + 'whatever', + ' have: ALL + want: DELETE' +); + +SELECT * FROM check_test( + policy_cmd_is( 'passwd', 'root_all', 'delete' ), + false, + 'policy_cmd_is(table, policy, command) for ALL should fail', + 'Policy root_all for table passwd should apply to DELETE command', + ' have: ALL + want: DELETE' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 3ad51464e7a21ea7c4a606aed92af7ce62670561 Mon Sep 17 00:00:00 2001 From: Kamen Naydenov Date: Fri, 16 Nov 2018 17:49:28 +0200 Subject: [PATCH 1023/1195] fix: expected output for policy tests --- test/expected/policy.out | 162 +++++++++++++++++++++++++++++---------- 1 file changed, 121 insertions(+), 41 deletions(-) diff --git a/test/expected/policy.out b/test/expected/policy.out index f505275c713b..ff51445ca3c6 100644 --- a/test/expected/policy.out +++ b/test/expected/policy.out @@ -1,5 +1,5 @@ \unset ECHO -1..100 +1..180 ok 1 - policies_are(schema, table, policies, desc) should pass ok 2 - policies_are(schema, table, policies, desc) should have the proper description ok 3 - policies_are(schema, table, policies, desc) should have the proper diagnostics @@ -60,43 +60,123 @@ ok 57 - policy_roles_are(table, policy, roles) + missing should have the proper ok 58 - policy_roles_are(table, policy, roles) + extra & missing should fail ok 59 - policy_roles_are(table, policy, roles) + extra & missing should have the proper description ok 60 - policy_roles_are(table, policy, roles) + extra & missing should have the proper diagnostics -ok 61 - policy_cmd_is(schema, table, policy, command, desc) for SELECT -ok 62 - policy_cmd_is(schema, table, policy, command) for SELECT -ok 63 - policy_cmd_is(table, policy, command, desc) for SELECT -ok 64 - policy_cmd_is(table, policy, command) for SELECT -ok 65 - policy_cmd_is(schema, table, policy, command, desc) for SELECT should fail -ok 66 - policy_cmd_is(schema, table, policy, command) for SELECT should fail -ok 67 - policy_cmd_is(table, policy, command, desc) for SELECT should fail -ok 68 - policy_cmd_is(table, policy, command) for SELECT should fail -ok 69 - policy_cmd_is(schema, table, policy, command, desc) for INSERT -ok 70 - policy_cmd_is(schema, table, policy, command) for INSERT -ok 71 - policy_cmd_is(table, policy, command, desc) for INSERT -ok 72 - policy_cmd_is(table, policy, command) for INSERT -ok 73 - policy_cmd_is(schema, table, policy, command, desc) for INSERT should fail -ok 74 - policy_cmd_is(schema, table, policy, command) for INSERT should fail -ok 75 - policy_cmd_is(table, policy, command, desc) for INSERT should fail -ok 76 - policy_cmd_is(table, policy, command) for INSERT should fail -ok 77 - policy_cmd_is(schema, table, policy, command, desc) for UPDATE -ok 78 - policy_cmd_is(schema, table, policy, command) for UPDATE -ok 79 - policy_cmd_is(table, policy, command, desc) for UPDATE -ok 80 - policy_cmd_is(table, policy, command) for UPDATE -ok 81 - policy_cmd_is(schema, table, policy, command, desc) for UPDATE should fail -ok 82 - policy_cmd_is(schema, table, policy, command) for UPDATE should fail -ok 83 - policy_cmd_is(table, policy, command, desc) for UPDATE should fail -ok 84 - policy_cmd_is(table, policy, command) for UPDATE should fail -ok 85 - policy_cmd_is(schema, table, policy, command, desc) for DELETE -ok 86 - policy_cmd_is(schema, table, policy, command) for DELETE -ok 87 - policy_cmd_is(table, policy, command, desc) for DELETE -ok 88 - policy_cmd_is(table, policy, command) for DELETE -ok 89 - policy_cmd_is(schema, table, policy, command, desc) for DELETE should fail -ok 90 - policy_cmd_is(schema, table, policy, command) for DELETE should fail -ok 91 - policy_cmd_is(table, policy, command, desc) for DELETE should fail -ok 92 - policy_cmd_is(table, policy, command) for DELETE should fail -ok 93 - policy_cmd_is(schema, table, policy, command, desc) for ALL -ok 94 - policy_cmd_is(schema, table, policy, command) for ALL -ok 95 - policy_cmd_is(table, policy, command, desc) for ALL -ok 96 - policy_cmd_is(table, policy, command) for ALL -ok 97 - policy_cmd_is(schema, table, policy, command, desc) for ALL should fail -ok 98 - policy_cmd_is(schema, table, policy, command) for ALL should fail -ok 99 - policy_cmd_is(table, policy, command, desc) for ALL should fail -ok 100 - policy_cmd_is(table, policy, command) for ALL should fail \ No newline at end of file +ok 61 - policy_cmd_is(schema, table, policy, command, desc) for SELECT should pass +ok 62 - policy_cmd_is(schema, table, policy, command, desc) for SELECT should have the proper description +ok 63 - policy_cmd_is(schema, table, policy, command, desc) for SELECT should have the proper diagnostics +ok 64 - policy_cmd_is(schema, table, policy, command) for SELECT should pass +ok 65 - policy_cmd_is(schema, table, policy, command) for SELECT should have the proper description +ok 66 - policy_cmd_is(schema, table, policy, command) for SELECT should have the proper diagnostics +ok 67 - policy_cmd_is(table, policy, command, desc) for SELECT should pass +ok 68 - policy_cmd_is(table, policy, command, desc) for SELECT should have the proper description +ok 69 - policy_cmd_is(table, policy, command, desc) for SELECT should have the proper diagnostics +ok 70 - policy_cmd_is(table, policy, command) for SELECT should pass +ok 71 - policy_cmd_is(table, policy, command) for SELECT should have the proper description +ok 72 - policy_cmd_is(table, policy, command) for SELECT should have the proper diagnostics +ok 73 - policy_cmd_is(schema, table, policy, command, desc) for SELECT should fail should fail +ok 74 - policy_cmd_is(schema, table, policy, command, desc) for SELECT should fail should have the proper description +ok 75 - policy_cmd_is(schema, table, policy, command, desc) for SELECT should fail should have the proper diagnostics +ok 76 - policy_cmd_is(schema, table, policy, command) for SELECT should fail should fail +ok 77 - policy_cmd_is(schema, table, policy, command) for SELECT should fail should have the proper description +ok 78 - policy_cmd_is(schema, table, policy, command) for SELECT should fail should have the proper diagnostics +ok 79 - policy_cmd_is(table, policy, command, desc) for SELECT should fail should fail +ok 80 - policy_cmd_is(table, policy, command, desc) for SELECT should fail should have the proper description +ok 81 - policy_cmd_is(table, policy, command, desc) for SELECT should fail should have the proper diagnostics +ok 82 - policy_cmd_is(table, policy, command) for SELECT should fail should fail +ok 83 - policy_cmd_is(table, policy, command) for SELECT should fail should have the proper description +ok 84 - policy_cmd_is(table, policy, command) for SELECT should fail should have the proper diagnostics +ok 85 - policy_cmd_is(schema, table, policy, command, desc) for INSERT should pass +ok 86 - policy_cmd_is(schema, table, policy, command, desc) for INSERT should have the proper description +ok 87 - policy_cmd_is(schema, table, policy, command, desc) for INSERT should have the proper diagnostics +ok 88 - policy_cmd_is(schema, table, policy, command) for INSERT should pass +ok 89 - policy_cmd_is(schema, table, policy, command) for INSERT should have the proper description +ok 90 - policy_cmd_is(schema, table, policy, command) for INSERT should have the proper diagnostics +ok 91 - policy_cmd_is(table, policy, command, desc) for INSERT should pass +ok 92 - policy_cmd_is(table, policy, command, desc) for INSERT should have the proper description +ok 93 - policy_cmd_is(table, policy, command, desc) for INSERT should have the proper diagnostics +ok 94 - policy_cmd_is(table, policy, command) for INSERT should pass +ok 95 - policy_cmd_is(table, policy, command) for INSERT should have the proper description +ok 96 - policy_cmd_is(table, policy, command) for INSERT should have the proper diagnostics +ok 97 - policy_cmd_is(schema, table, policy, command, desc) for INSERT should fail should fail +ok 98 - policy_cmd_is(schema, table, policy, command, desc) for INSERT should fail should have the proper description +ok 99 - policy_cmd_is(schema, table, policy, command, desc) for INSERT should fail should have the proper diagnostics +ok 100 - policy_cmd_is(schema, table, policy, command) for INSERT should fail should fail +ok 101 - policy_cmd_is(schema, table, policy, command) for INSERT should fail should have the proper description +ok 102 - policy_cmd_is(schema, table, policy, command) for INSERT should fail should have the proper diagnostics +ok 103 - policy_cmd_is(table, policy, command, desc) for INSERT should fail should fail +ok 104 - policy_cmd_is(table, policy, command, desc) for INSERT should fail should have the proper description +ok 105 - policy_cmd_is(table, policy, command, desc) for INSERT should fail should have the proper diagnostics +ok 106 - policy_cmd_is(table, policy, command) for INSERT should fail should fail +ok 107 - policy_cmd_is(table, policy, command) for INSERT should fail should have the proper description +ok 108 - policy_cmd_is(table, policy, command) for INSERT should fail should have the proper diagnostics +ok 109 - policy_cmd_is(schema, table, policy, command, desc) for UPDATE should pass +ok 110 - policy_cmd_is(schema, table, policy, command, desc) for UPDATE should have the proper description +ok 111 - policy_cmd_is(schema, table, policy, command, desc) for UPDATE should have the proper diagnostics +ok 112 - policy_cmd_is(schema, table, policy, command) for UPDATE should pass +ok 113 - policy_cmd_is(schema, table, policy, command) for UPDATE should have the proper description +ok 114 - policy_cmd_is(schema, table, policy, command) for UPDATE should have the proper diagnostics +ok 115 - policy_cmd_is(table, policy, command, desc) for UPDATE should pass +ok 116 - policy_cmd_is(table, policy, command, desc) for UPDATE should have the proper description +ok 117 - policy_cmd_is(table, policy, command, desc) for UPDATE should have the proper diagnostics +ok 118 - policy_cmd_is(table, policy, command) for UPDATE should pass +ok 119 - policy_cmd_is(table, policy, command) for UPDATE should have the proper description +ok 120 - policy_cmd_is(table, policy, command) for UPDATE should have the proper diagnostics +ok 121 - policy_cmd_is(schema, table, policy, command, desc) for UPDATE should fail should fail +ok 122 - policy_cmd_is(schema, table, policy, command, desc) for UPDATE should fail should have the proper description +ok 123 - policy_cmd_is(schema, table, policy, command, desc) for UPDATE should fail should have the proper diagnostics +ok 124 - policy_cmd_is(schema, table, policy, command) for UPDATE should fail should fail +ok 125 - policy_cmd_is(schema, table, policy, command) for UPDATE should fail should have the proper description +ok 126 - policy_cmd_is(schema, table, policy, command) for UPDATE should fail should have the proper diagnostics +ok 127 - policy_cmd_is(table, policy, command, desc) for UPDATE should fail should fail +ok 128 - policy_cmd_is(table, policy, command, desc) for UPDATE should fail should have the proper description +ok 129 - policy_cmd_is(table, policy, command, desc) for UPDATE should fail should have the proper diagnostics +ok 130 - policy_cmd_is(table, policy, command) for UPDATE should fail should fail +ok 131 - policy_cmd_is(table, policy, command) for UPDATE should fail should have the proper description +ok 132 - policy_cmd_is(table, policy, command) for UPDATE should fail should have the proper diagnostics +ok 133 - policy_cmd_is(schema, table, policy, command, desc) for DELETE should pass +ok 134 - policy_cmd_is(schema, table, policy, command, desc) for DELETE should have the proper description +ok 135 - policy_cmd_is(schema, table, policy, command, desc) for DELETE should have the proper diagnostics +ok 136 - policy_cmd_is(schema, table, policy, command) for DELETE should pass +ok 137 - policy_cmd_is(schema, table, policy, command) for DELETE should have the proper description +ok 138 - policy_cmd_is(schema, table, policy, command) for DELETE should have the proper diagnostics +ok 139 - policy_cmd_is(table, policy, command, desc) for DELETE should pass +ok 140 - policy_cmd_is(table, policy, command, desc) for DELETE should have the proper description +ok 141 - policy_cmd_is(table, policy, command, desc) for DELETE should have the proper diagnostics +ok 142 - policy_cmd_is(table, policy, command) for DELETE should pass +ok 143 - policy_cmd_is(table, policy, command) for DELETE should have the proper description +ok 144 - policy_cmd_is(table, policy, command) for DELETE should have the proper diagnostics +ok 145 - policy_cmd_is(schema, table, policy, command, desc) for DELETE should fail should fail +ok 146 - policy_cmd_is(schema, table, policy, command, desc) for DELETE should fail should have the proper description +ok 147 - policy_cmd_is(schema, table, policy, command, desc) for DELETE should fail should have the proper diagnostics +ok 148 - policy_cmd_is(schema, table, policy, command) for DELETE should fail should fail +ok 149 - policy_cmd_is(schema, table, policy, command) for DELETE should fail should have the proper description +ok 150 - policy_cmd_is(schema, table, policy, command) for DELETE should fail should have the proper diagnostics +ok 151 - policy_cmd_is(table, policy, command, desc) for DELETE should fail should fail +ok 152 - policy_cmd_is(table, policy, command, desc) for DELETE should fail should have the proper description +ok 153 - policy_cmd_is(table, policy, command, desc) for DELETE should fail should have the proper diagnostics +ok 154 - policy_cmd_is(table, policy, command) for DELETE should fail should fail +ok 155 - policy_cmd_is(table, policy, command) for DELETE should fail should have the proper description +ok 156 - policy_cmd_is(table, policy, command) for DELETE should fail should have the proper diagnostics +ok 157 - policy_cmd_is(schema, table, policy, command, desc) for ALL should pass +ok 158 - policy_cmd_is(schema, table, policy, command, desc) for ALL should have the proper description +ok 159 - policy_cmd_is(schema, table, policy, command, desc) for ALL should have the proper diagnostics +ok 160 - policy_cmd_is(schema, table, policy, command) for ALL should pass +ok 161 - policy_cmd_is(schema, table, policy, command) for ALL should have the proper description +ok 162 - policy_cmd_is(schema, table, policy, command) for ALL should have the proper diagnostics +ok 163 - policy_cmd_is(table, policy, command, desc) for ALL should pass +ok 164 - policy_cmd_is(table, policy, command, desc) for ALL should have the proper description +ok 165 - policy_cmd_is(table, policy, command, desc) for ALL should have the proper diagnostics +ok 166 - policy_cmd_is(table, policy, command) for ALL should pass +ok 167 - policy_cmd_is(table, policy, command) for ALL should have the proper description +ok 168 - policy_cmd_is(table, policy, command) for ALL should have the proper diagnostics +ok 169 - policy_cmd_is(schema, table, policy, command, desc) for ALL should fail should fail +ok 170 - policy_cmd_is(schema, table, policy, command, desc) for ALL should fail should have the proper description +ok 171 - policy_cmd_is(schema, table, policy, command, desc) for ALL should fail should have the proper diagnostics +ok 172 - policy_cmd_is(schema, table, policy, command) for ALL should fail should fail +ok 173 - policy_cmd_is(schema, table, policy, command) for ALL should fail should have the proper description +ok 174 - policy_cmd_is(schema, table, policy, command) for ALL should fail should have the proper diagnostics +ok 175 - policy_cmd_is(table, policy, command, desc) for ALL should fail should fail +ok 176 - policy_cmd_is(table, policy, command, desc) for ALL should fail should have the proper description +ok 177 - policy_cmd_is(table, policy, command, desc) for ALL should fail should have the proper diagnostics +ok 178 - policy_cmd_is(table, policy, command) for ALL should fail should fail +ok 179 - policy_cmd_is(table, policy, command) for ALL should fail should have the proper description +ok 180 - policy_cmd_is(table, policy, command) for ALL should fail should have the proper diagnostics From 3b3f478723b6b1758100c640f6c2901528534945 Mon Sep 17 00:00:00 2001 From: Kamen Naydenov Date: Sat, 17 Nov 2018 12:45:09 +0200 Subject: [PATCH 1024/1195] Update 9.4 compat. Add a patch to remove functions that test row security policies as they are not available on version 9.4 and earlier. --- compat/install-9.4.patch | 233 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) diff --git a/compat/install-9.4.patch b/compat/install-9.4.patch index 7c337c2d26a2..6adfc492a209 100644 --- a/compat/install-9.4.patch +++ b/compat/install-9.4.patch @@ -18,3 +18,236 @@ -- There should have been no exception. GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, +@@ -9905,231 +9905,4 @@ + 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; ++$$ LANGUAGE SQL immutable; From cf0823f6e95bd1aee37892a67fad35ae5492766c Mon Sep 17 00:00:00 2001 From: Kamen Naydenov Date: Sat, 17 Nov 2018 13:04:23 +0200 Subject: [PATCH 1025/1195] Skip test for policies for versions earlier than 9.5 --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index b5680200f773..f799e0f94abf 100644 --- a/Makefile +++ b/Makefile @@ -106,6 +106,12 @@ TESTS := $(filter-out test/sql/partitions.sql,$(TESTS)) REGRESS := $(filter-out partitions,$(REGRESS)) endif +# Row security policy tests not supported by 9.4 and earlier. +ifeq ($(shell echo $(VERSION) | grep -qE "^9[.][01234]|8[.]" && echo yes || echo no),yes) +TESTS := $(filter-out test/sql/policy.sql,$(TESTS)) +REGRESS := $(filter-out policy,$(REGRESS)) +endif + # Determine the OS. Borrowed from Perl's Configure. OSNAME := $(shell $(SHELL) ./getos.sh) From 8592673e9f57b9e5f1690611c08262967ed1e43f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 09:39:35 -0500 Subject: [PATCH 1026/1195] Record policy functions in Changes. --- Changes | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Changes b/Changes index 667caa52cd45..a2221835ee50 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,11 @@ Revision history for pgTAP -------------------------- * Fixed uninstall script generation to properly emit `DROP TYPE` statements. Thanks to Kamen Naydenov for the PR (#181)! +* Added policy test assertion functions for PostgreSQL 9.5 and higher: + + `policies_are()` + + `policy_roles_are()` + + `policy_cmd_is()` + Thanks to Kamen Naydenov for the PR (#185). 0.99.0 2018-09-16T20:55:41Z --------------------------- From c1b1f59284b1737d491754138408cad54345bfac Mon Sep 17 00:00:00 2001 From: Sandro Santilli Date: Thu, 4 Oct 2018 16:51:16 +0200 Subject: [PATCH 1027/1195] Fix typos in test descriptions --- test/expected/functap.out | 12 ++++++------ test/sql/functap.sql | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/expected/functap.out b/test/expected/functap.out index 2b6704c2ef59..bb6fb69584d7 100644 --- a/test/expected/functap.out +++ b/test/expected/functap.out @@ -21,9 +21,9 @@ ok 18 - simple schema.func with 0 args should have the proper diagnostics ok 19 - simple schema.func with desc should pass ok 20 - simple schema.func with desc should have the proper description ok 21 - simple schema.func with desc should have the proper diagnostics -ok 22 - simple scchma.func with 0 args, desc should pass -ok 23 - simple scchma.func with 0 args, desc should have the proper description -ok 24 - simple scchma.func with 0 args, desc should have the proper diagnostics +ok 22 - simple schema.func with 0 args, desc should pass +ok 23 - simple schema.func with 0 args, desc should have the proper description +ok 24 - simple schema.func with 0 args, desc should have the proper diagnostics ok 25 - simple function with 1 arg should pass ok 26 - simple function with 1 arg should have the proper description ok 27 - simple function with 1 arg should have the proper diagnostics @@ -63,9 +63,9 @@ ok 60 - simple schema.func with 0 args should have the proper diagnostics ok 61 - simple schema.func with desc should fail ok 62 - simple schema.func with desc should have the proper description ok 63 - simple schema.func with desc should have the proper diagnostics -ok 64 - simple scchma.func with 0 args, desc should fail -ok 65 - simple scchma.func with 0 args, desc should have the proper description -ok 66 - simple scchma.func with 0 args, desc should have the proper diagnostics +ok 64 - simple schema.func with 0 args, desc should fail +ok 65 - simple schema.func with 0 args, desc should have the proper description +ok 66 - simple schema.func with 0 args, desc should have the proper diagnostics ok 67 - simple function with 1 arg should fail ok 68 - simple function with 1 arg should have the proper description ok 69 - simple function with 1 arg should have the proper diagnostics diff --git a/test/sql/functap.sql b/test/sql/functap.sql index 8d6c5e6045cc..0a9f01f5f9e3 100644 --- a/test/sql/functap.sql +++ b/test/sql/functap.sql @@ -83,7 +83,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( has_function( 'pg_catalog', 'now', '{}'::name[], 'whatever' ), true, - 'simple scchma.func with 0 args, desc', + 'simple schema.func with 0 args, desc', 'whatever', '' ); @@ -208,7 +208,7 @@ SELECT * FROM check_test( SELECT * FROM check_test( hasnt_function( 'pg_catalog', 'now', '{}'::name[], 'whatever' ), false, - 'simple scchma.func with 0 args, desc', + 'simple schema.func with 0 args, desc', 'whatever', '' ); From b281cfb1be6346037d15104b3c9ac143495c66e6 Mon Sep 17 00:00:00 2001 From: Sandro Santilli Date: Thu, 4 Oct 2018 12:26:04 +0200 Subject: [PATCH 1028/1195] Re-qualify argument type names to be immune of search_path Closes #179 --- sql/pgtap.sql.in | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 34e5f452deeb..297dc8998c08 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2499,7 +2499,12 @@ RETURNS BOOLEAN AS $$ FROM tap_funky WHERE schema = $1 AND name = $2 - AND args = array_to_string($3, ',') + AND args = ( + SELECT COALESCE(array_to_string( + array_agg(COALESCE(NULLIF(x, '')::regtype::text,'')), ',' + ), '') + FROM unnest($3) x + ) ); $$ LANGUAGE SQL; @@ -2514,7 +2519,12 @@ RETURNS BOOLEAN AS $$ SELECT TRUE FROM tap_funky WHERE name = $1 - AND args = array_to_string($2, ',') + AND args = ( + SELECT COALESCE(array_to_string( + array_agg(COALESCE(NULLIF(x, '')::regtype::text,'')), ',' + ), '') + FROM unnest($2) x + ) AND is_visible ); $$ LANGUAGE SQL; From 795e0ad046047bd4074e47badb40e412cf87d376 Mon Sep 17 00:00:00 2001 From: Sandro Santilli Date: Thu, 4 Oct 2018 17:38:13 +0200 Subject: [PATCH 1029/1195] Add test for testing existance of function taking complex arg in search_path Tests both the qualified and unqualified function schema Ref #179 --- test/expected/functap.out | 1185 +++++++++++++++++++------------------ test/sql/functap.sql | 34 ++ 2 files changed, 633 insertions(+), 586 deletions(-) diff --git a/test/expected/functap.out b/test/expected/functap.out index bb6fb69584d7..56efdc21a73b 100644 --- a/test/expected/functap.out +++ b/test/expected/functap.out @@ -39,592 +39,605 @@ ok 36 - custom array function should have the proper diagnostics ok 37 - custom numeric function should pass ok 38 - custom numeric function should have the proper description ok 39 - custom numeric function should have the proper diagnostics -ok 40 - failure output should fail -ok 41 - failure output should have the proper description -ok 42 - failure output should have the proper diagnostics -ok 43 - simple function should fail -ok 44 - simple function should have the proper description -ok 45 - simple function should have the proper diagnostics -ok 46 - simple schema.function should fail -ok 47 - simple schema.function should have the proper description -ok 48 - simple schema.function should have the proper diagnostics -ok 49 - simple function desc should fail -ok 50 - simple function desc should have the proper description -ok 51 - simple function desc should have the proper diagnostics -ok 52 - simple with 0 args should fail -ok 53 - simple with 0 args should have the proper description -ok 54 - simple with 0 args should have the proper diagnostics -ok 55 - simple with 0 args desc should fail -ok 56 - simple with 0 args desc should have the proper description -ok 57 - simple with 0 args desc should have the proper diagnostics -ok 58 - simple schema.func with 0 args should fail -ok 59 - simple schema.func with 0 args should have the proper description -ok 60 - simple schema.func with 0 args should have the proper diagnostics -ok 61 - simple schema.func with desc should fail -ok 62 - simple schema.func with desc should have the proper description -ok 63 - simple schema.func with desc should have the proper diagnostics -ok 64 - simple schema.func with 0 args, desc should fail -ok 65 - simple schema.func with 0 args, desc should have the proper description -ok 66 - simple schema.func with 0 args, desc should have the proper diagnostics -ok 67 - simple function with 1 arg should fail -ok 68 - simple function with 1 arg should have the proper description -ok 69 - simple function with 1 arg should have the proper diagnostics -ok 70 - simple function with 2 args should fail -ok 71 - simple function with 2 args should have the proper description -ok 72 - simple function with 2 args should have the proper diagnostics -ok 73 - simple array function should fail -ok 74 - simple array function should have the proper description -ok 75 - simple array function should have the proper diagnostics -ok 76 - custom array function should fail -ok 77 - custom array function should have the proper description -ok 78 - custom array function should have the proper diagnostics -ok 79 - custom numeric function should fail -ok 80 - custom numeric function should have the proper description -ok 81 - custom numeric function should have the proper diagnostics -ok 82 - can(schema) with desc should pass -ok 83 - can(schema) with desc should have the proper description -ok 84 - can(schema) with desc should have the proper diagnostics -ok 85 - can(schema) should pass -ok 86 - can(schema) should have the proper description -ok 87 - can(schema) should have the proper diagnostics -ok 88 - fail can(schema) with desc should fail -ok 89 - fail can(schema) with desc should have the proper description -ok 90 - fail can(schema) with desc should have the proper diagnostics -ok 91 - fail can(someschema) with desc should fail -ok 92 - fail can(someschema) with desc should have the proper description -ok 93 - fail can(someschema) with desc should have the proper diagnostics -ok 94 - can() with desc should pass -ok 95 - can() with desc should have the proper description -ok 96 - can() with desc should have the proper diagnostics +ok 40 - custom unqualified function with complex unqualified argument should pass +ok 41 - custom unqualified function with complex unqualified argument should have the proper description +ok 42 - custom unqualified function with complex unqualified argument should have the proper diagnostics +ok 43 - custom unqualified function with complex qualified argument should pass +ok 44 - custom unqualified function with complex qualified argument should have the proper description +ok 45 - custom unqualified function with complex qualified argument should have the proper diagnostics +ok 46 - custom qualified function with complex unqualified argument should pass +ok 47 - custom qualified function with complex unqualified argument should have the proper description +ok 48 - custom qualified function with complex unqualified argument should have the proper diagnostics +ok 49 - custom qualified function with complex qualified argument should pass +ok 50 - custom qualified function with complex qualified argument should have the proper description +ok 51 - custom qualified function with complex qualified argument should have the proper diagnostics +ok 52 - failure output should fail +ok 53 - failure output should have the proper description +ok 54 - failure output should have the proper diagnostics +ok 55 - simple function should fail +ok 56 - simple function should have the proper description +ok 57 - simple function should have the proper diagnostics +ok 58 - simple schema.function should fail +ok 59 - simple schema.function should have the proper description +ok 60 - simple schema.function should have the proper diagnostics +ok 61 - simple function desc should fail +ok 62 - simple function desc should have the proper description +ok 63 - simple function desc should have the proper diagnostics +ok 64 - simple with 0 args should fail +ok 65 - simple with 0 args should have the proper description +ok 66 - simple with 0 args should have the proper diagnostics +ok 67 - simple with 0 args desc should fail +ok 68 - simple with 0 args desc should have the proper description +ok 69 - simple with 0 args desc should have the proper diagnostics +ok 70 - simple schema.func with 0 args should fail +ok 71 - simple schema.func with 0 args should have the proper description +ok 72 - simple schema.func with 0 args should have the proper diagnostics +ok 73 - simple schema.func with desc should fail +ok 74 - simple schema.func with desc should have the proper description +ok 75 - simple schema.func with desc should have the proper diagnostics +ok 76 - simple schema.func with 0 args, desc should fail +ok 77 - simple schema.func with 0 args, desc should have the proper description +ok 78 - simple schema.func with 0 args, desc should have the proper diagnostics +ok 79 - simple function with 1 arg should fail +ok 80 - simple function with 1 arg should have the proper description +ok 81 - simple function with 1 arg should have the proper diagnostics +ok 82 - simple function with 2 args should fail +ok 83 - simple function with 2 args should have the proper description +ok 84 - simple function with 2 args should have the proper diagnostics +ok 85 - simple array function should fail +ok 86 - simple array function should have the proper description +ok 87 - simple array function should have the proper diagnostics +ok 88 - custom array function should fail +ok 89 - custom array function should have the proper description +ok 90 - custom array function should have the proper diagnostics +ok 91 - custom numeric function should fail +ok 92 - custom numeric function should have the proper description +ok 93 - custom numeric function should have the proper diagnostics +ok 94 - can(schema) with desc should pass +ok 95 - can(schema) with desc should have the proper description +ok 96 - can(schema) with desc should have the proper diagnostics ok 97 - can(schema) should pass ok 98 - can(schema) should have the proper description ok 99 - can(schema) should have the proper diagnostics -ok 100 - fail can() with desc should fail -ok 101 - fail can() with desc should have the proper description -ok 102 - fail can() with desc should have the proper diagnostics -ok 103 - function_lang_is(schema, func, 0 args, sql, desc) should pass -ok 104 - function_lang_is(schema, func, 0 args, sql, desc) should have the proper description -ok 105 - function_lang_is(schema, func, 0 args, sql, desc) should have the proper diagnostics -ok 106 - function_lang_is(schema, func, 0 args, sql) should pass -ok 107 - function_lang_is(schema, func, 0 args, sql) should have the proper description -ok 108 - function_lang_is(schema, func, 0 args, sql) should have the proper diagnostics -ok 109 - function_lang_is(schema, func, args, plpgsql, desc) should pass -ok 110 - function_lang_is(schema, func, args, plpgsql, desc) should have the proper description -ok 111 - function_lang_is(schema, func, args, plpgsql, desc) should have the proper diagnostics -ok 112 - function_lang_is(schema, func, args, plpgsql) should pass -ok 113 - function_lang_is(schema, func, args, plpgsql) should have the proper description -ok 114 - function_lang_is(schema, func, args, plpgsql) should have the proper diagnostics -ok 115 - function_lang_is(schema, func, 0 args, perl, desc) should fail -ok 116 - function_lang_is(schema, func, 0 args, perl, desc) should have the proper description -ok 117 - function_lang_is(schema, func, 0 args, perl, desc) should have the proper diagnostics -ok 118 - function_lang_is(schema, non-func, 0 args, sql, desc) should fail -ok 119 - function_lang_is(schema, non-func, 0 args, sql, desc) should have the proper description -ok 120 - function_lang_is(schema, non-func, 0 args, sql, desc) should have the proper diagnostics -ok 121 - function_lang_is(schema, func, args, plpgsql) should fail -ok 122 - function_lang_is(schema, func, args, plpgsql) should have the proper description -ok 123 - function_lang_is(schema, func, args, plpgsql) should have the proper diagnostics -ok 124 - function_lang_is(schema, func, sql, desc) should pass -ok 125 - function_lang_is(schema, func, sql, desc) should have the proper description -ok 126 - function_lang_is(schema, func, sql, desc) should have the proper diagnostics -ok 127 - function_lang_is(schema, func, sql) should pass -ok 128 - function_lang_is(schema, func, sql) should have the proper description -ok 129 - function_lang_is(schema, func, sql) should have the proper diagnostics -ok 130 - function_lang_is(schema, func, perl, desc) should fail -ok 131 - function_lang_is(schema, func, perl, desc) should have the proper description -ok 132 - function_lang_is(schema, func, perl, desc) should have the proper diagnostics -ok 133 - function_lang_is(schema, non-func, sql, desc) should fail -ok 134 - function_lang_is(schema, non-func, sql, desc) should have the proper description -ok 135 - function_lang_is(schema, non-func, sql, desc) should have the proper diagnostics -ok 136 - function_lang_is(func, 0 args, sql, desc) should pass -ok 137 - function_lang_is(func, 0 args, sql, desc) should have the proper description -ok 138 - function_lang_is(func, 0 args, sql, desc) should have the proper diagnostics -ok 139 - function_lang_is(func, 0 args, sql) should pass -ok 140 - function_lang_is(func, 0 args, sql) should have the proper description -ok 141 - function_lang_is(func, 0 args, sql) should have the proper diagnostics -ok 142 - function_lang_is(func, args, plpgsql, desc) should pass -ok 143 - function_lang_is(func, args, plpgsql, desc) should have the proper description -ok 144 - function_lang_is(func, args, plpgsql, desc) should have the proper diagnostics -ok 145 - function_lang_is(func, args, plpgsql) should pass -ok 146 - function_lang_is(func, args, plpgsql) should have the proper description -ok 147 - function_lang_is(func, args, plpgsql) should have the proper diagnostics -ok 148 - function_lang_is(func, 0 args, perl, desc) should fail -ok 149 - function_lang_is(func, 0 args, perl, desc) should have the proper description -ok 150 - function_lang_is(func, 0 args, perl, desc) should have the proper diagnostics -ok 151 - function_lang_is(non-func, 0 args, sql, desc) should fail -ok 152 - function_lang_is(non-func, 0 args, sql, desc) should have the proper description -ok 153 - function_lang_is(non-func, 0 args, sql, desc) should have the proper diagnostics -ok 154 - function_lang_is(func, args, plpgsql) should fail -ok 155 - function_lang_is(func, args, plpgsql) should have the proper description -ok 156 - function_lang_is(func, args, plpgsql) should have the proper diagnostics -ok 157 - function_lang_is(func, sql, desc) should pass -ok 158 - function_lang_is(func, sql, desc) should have the proper description -ok 159 - function_lang_is(func, sql, desc) should have the proper diagnostics -ok 160 - function_lang_is(func, sql) should pass -ok 161 - function_lang_is(func, sql) should have the proper description -ok 162 - function_lang_is(func, sql) should have the proper diagnostics -ok 163 - function_lang_is(func, perl, desc) should fail -ok 164 - function_lang_is(func, perl, desc) should have the proper description -ok 165 - function_lang_is(func, perl, desc) should have the proper diagnostics -ok 166 - function_lang_is(non-func, sql, desc) should fail -ok 167 - function_lang_is(non-func, sql, desc) should have the proper description -ok 168 - function_lang_is(non-func, sql, desc) should have the proper diagnostics -ok 169 - function_returns(schema, func, 0 args, bool, desc) should pass -ok 170 - function_returns(schema, func, 0 args, bool, desc) should have the proper description -ok 171 - function_returns(schema, func, 0 args, bool, desc) should have the proper diagnostics -ok 172 - function_returns(schema, func, 0 args, bool) should pass -ok 173 - function_returns(schema, func, 0 args, bool) should have the proper description -ok 174 - function_returns(schema, func, 0 args, bool) should have the proper diagnostics -ok 175 - function_returns(schema, func, args, bool, false) should pass -ok 176 - function_returns(schema, func, args, bool, false) should have the proper description -ok 177 - function_returns(schema, func, args, bool, false) should have the proper diagnostics -ok 178 - function_returns(schema, func, args, bool) should pass -ok 179 - function_returns(schema, func, args, bool) should have the proper description -ok 180 - function_returns(schema, func, args, bool) should have the proper diagnostics -ok 181 - function_returns(schema, func, 0 args, setof bool, desc) should pass -ok 182 - function_returns(schema, func, 0 args, setof bool, desc) should have the proper description -ok 183 - function_returns(schema, func, 0 args, setof bool, desc) should have the proper diagnostics -ok 184 - function_returns(schema, func, 0 args, setof bool) should pass -ok 185 - function_returns(schema, func, 0 args, setof bool) should have the proper description -ok 186 - function_returns(schema, func, 0 args, setof bool) should have the proper diagnostics -ok 187 - function_returns(schema, func, bool, desc) should pass -ok 188 - function_returns(schema, func, bool, desc) should have the proper description -ok 189 - function_returns(schema, func, bool, desc) should have the proper diagnostics -ok 190 - function_returns(schema, func, bool) should pass -ok 191 - function_returns(schema, func, bool) should have the proper description -ok 192 - function_returns(schema, func, bool) should have the proper diagnostics -ok 193 - function_returns(schema, other func, bool, false) should pass -ok 194 - function_returns(schema, other func, bool, false) should have the proper description -ok 195 - function_returns(schema, other func, bool, false) should have the proper diagnostics -ok 196 - function_returns(schema, other func, bool) should pass -ok 197 - function_returns(schema, other func, bool) should have the proper description -ok 198 - function_returns(schema, other func, bool) should have the proper diagnostics -ok 199 - function_returns(schema, func, setof bool, desc) should pass -ok 200 - function_returns(schema, func, setof bool, desc) should have the proper description -ok 201 - function_returns(schema, func, setof bool, desc) should have the proper diagnostics -ok 202 - function_returns(schema, func, setof bool) should pass -ok 203 - function_returns(schema, func, setof bool) should have the proper description -ok 204 - function_returns(schema, func, setof bool) should have the proper diagnostics -ok 205 - function_returns(func, 0 args, bool, desc) should pass -ok 206 - function_returns(func, 0 args, bool, desc) should have the proper description -ok 207 - function_returns(func, 0 args, bool, desc) should have the proper diagnostics -ok 208 - function_returns(func, 0 args, bool) should pass -ok 209 - function_returns(func, 0 args, bool) should have the proper description -ok 210 - function_returns(func, 0 args, bool) should have the proper diagnostics -ok 211 - function_returns(func, args, bool, false) should pass -ok 212 - function_returns(func, args, bool, false) should have the proper description -ok 213 - function_returns(func, args, bool, false) should have the proper diagnostics -ok 214 - function_returns(func, args, bool) should pass -ok 215 - function_returns(func, args, bool) should have the proper description -ok 216 - function_returns(func, args, bool) should have the proper diagnostics -ok 217 - function_returns(func, 0 args, setof bool, desc) should pass -ok 218 - function_returns(func, 0 args, setof bool, desc) should have the proper description -ok 219 - function_returns(func, 0 args, setof bool, desc) should have the proper diagnostics -ok 220 - function_returns(func, 0 args, setof bool) should pass -ok 221 - function_returns(func, 0 args, setof bool) should have the proper description -ok 222 - function_returns(func, 0 args, setof bool) should have the proper diagnostics -ok 223 - function_returns(func, bool, desc) should pass -ok 224 - function_returns(func, bool, desc) should have the proper description -ok 225 - function_returns(func, bool, desc) should have the proper diagnostics -ok 226 - function_returns(func, bool) should pass -ok 227 - function_returns(func, bool) should have the proper description -ok 228 - function_returns(func, bool) should have the proper diagnostics -ok 229 - function_returns(other func, bool, false) should pass -ok 230 - function_returns(other func, bool, false) should have the proper description -ok 231 - function_returns(other func, bool, false) should have the proper diagnostics -ok 232 - function_returns(other func, bool) should pass -ok 233 - function_returns(other func, bool) should have the proper description -ok 234 - function_returns(other func, bool) should have the proper diagnostics -ok 235 - function_returns(func, setof bool, desc) should pass -ok 236 - function_returns(func, setof bool, desc) should have the proper description -ok 237 - function_returns(func, setof bool, desc) should have the proper diagnostics -ok 238 - function_returns(func, setof bool) should pass -ok 239 - function_returns(func, setof bool) should have the proper description -ok 240 - function_returns(func, setof bool) should have the proper diagnostics -ok 241 - is_definer(schema, func, 0 args, desc) should pass -ok 242 - is_definer(schema, func, 0 args, desc) should have the proper description -ok 243 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics -ok 244 - isnt_definer(schema, func, 0 args, desc) should fail -ok 245 - isnt_definer(schema, func, 0 args, desc) should have the proper description -ok 246 - isnt_definer(schema, func, 0 args, desc) should have the proper diagnostics -ok 247 - is_definer(schema, func, 0 args) should pass -ok 248 - is_definer(schema, func, 0 args) should have the proper description -ok 249 - is_definer(schema, func, 0 args) should have the proper diagnostics -ok 250 - isnt_definer(schema, func, 0 args) should fail -ok 251 - isnt_definer(schema, func, 0 args) should have the proper description -ok 252 - isnt_definer(schema, func, 0 args) should have the proper diagnostics -ok 253 - is_definer(schema, func, args, desc) should fail -ok 254 - is_definer(schema, func, args, desc) should have the proper description -ok 255 - is_definer(schema, func, args, desc) should have the proper diagnostics -ok 256 - isnt_definer(schema, func, args, desc) should pass -ok 257 - isnt_definer(schema, func, args, desc) should have the proper description -ok 258 - isnt_definer(schema, func, args, desc) should have the proper diagnostics -ok 259 - is_definer(schema, func, args) should fail -ok 260 - is_definer(schema, func, args) should have the proper description -ok 261 - is_definer(schema, func, args) should have the proper diagnostics -ok 262 - isnt_definer(schema, func, args) should pass -ok 263 - isnt_definer(schema, func, args) should have the proper description -ok 264 - isnt_definer(schema, func, args) should have the proper diagnostics -ok 265 - is_definer(schema, func, desc) should pass -ok 266 - is_definer(schema, func, desc) should have the proper description -ok 267 - is_definer(schema, func, desc) should have the proper diagnostics -ok 268 - isnt_definer(schema, func, desc) should fail -ok 269 - isnt_definer(schema, func, desc) should have the proper description -ok 270 - isnt_definer(schema, func, desc) should have the proper diagnostics -ok 271 - is_definer(schema, func) should pass -ok 272 - is_definer(schema, func) should have the proper description -ok 273 - is_definer(schema, func) should have the proper diagnostics -ok 274 - isnt_definer(schema, func) should fail -ok 275 - isnt_definer(schema, func) should have the proper description -ok 276 - isnt_definer(schema, func) should have the proper diagnostics -ok 277 - is_definer(schema, func, 0 args, desc) should pass -ok 278 - is_definer(schema, func, 0 args, desc) should have the proper description -ok 279 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics -ok 280 - isnt_definer(schema, func, 0 args, desc) should fail -ok 281 - isnt_definer(schema, func, 0 args, desc) should have the proper description -ok 282 - isnt_definer(schema, func, 0 args, desc) should have the proper diagnostics -ok 283 - is_definer(schema, func, 0 args) should pass -ok 284 - is_definer(schema, func, 0 args) should have the proper description -ok 285 - is_definer(schema, func, 0 args) should have the proper diagnostics -ok 286 - isnt_definer(schema, func, 0 args) should fail -ok 287 - isnt_definer(schema, func, 0 args) should have the proper description -ok 288 - isnt_definer(schema, func, 0 args) should have the proper diagnostics -ok 289 - is_definer(schema, func, args, desc) should fail -ok 290 - is_definer(schema, func, args, desc) should have the proper description -ok 291 - is_definer(schema, func, args, desc) should have the proper diagnostics -ok 292 - isnt_definer(schema, func, args, desc) should pass -ok 293 - isnt_definer(schema, func, args, desc) should have the proper description -ok 294 - isnt_definer(schema, func, args, desc) should have the proper diagnostics -ok 295 - is_definer(schema, func, args) should fail -ok 296 - is_definer(schema, func, args) should have the proper description -ok 297 - is_definer(schema, func, args) should have the proper diagnostics -ok 298 - isnt_definer(schema, func, args) should pass -ok 299 - isnt_definer(schema, func, args) should have the proper description -ok 300 - isnt_definer(schema, func, args) should have the proper diagnostics -ok 301 - is_definer(schema, func, desc) should pass -ok 302 - is_definer(schema, func, desc) should have the proper description -ok 303 - is_definer(schema, func, desc) should have the proper diagnostics -ok 304 - isnt_definer(schema, func, desc) should fail -ok 305 - isnt_definer(schema, func, desc) should have the proper description -ok 306 - isnt_definer(schema, func, desc) should have the proper diagnostics -ok 307 - is_definer(schema, func) should pass -ok 308 - is_definer(schema, func) should have the proper description -ok 309 - is_definer(schema, func) should have the proper diagnostics -ok 310 - isnt_definer(schema, func) should fail -ok 311 - isnt_definer(schema, func) should have the proper description -ok 312 - isnt_definer(schema, func) should have the proper diagnostics -ok 313 - is_definer(func, 0 args, desc) should pass -ok 314 - is_definer(func, 0 args, desc) should have the proper description -ok 315 - is_definer(func, 0 args, desc) should have the proper diagnostics -ok 316 - isnt_definer(func, 0 args, desc) should fail -ok 317 - isnt_definer(func, 0 args, desc) should have the proper description -ok 318 - isnt_definer(func, 0 args, desc) should have the proper diagnostics -ok 319 - is_definer(func, 0 args) should pass -ok 320 - is_definer(func, 0 args) should have the proper description -ok 321 - is_definer(func, 0 args) should have the proper diagnostics -ok 322 - isnt_definer(func, 0 args) should fail -ok 323 - isnt_definer(func, 0 args) should have the proper description -ok 324 - isnt_definer(func, 0 args) should have the proper diagnostics -ok 325 - is_definer(func, args, desc) should fail -ok 326 - is_definer(func, args, desc) should have the proper description -ok 327 - is_definer(func, args, desc) should have the proper diagnostics -ok 328 - isnt_definer(func, args, desc) should pass -ok 329 - isnt_definer(func, args, desc) should have the proper description -ok 330 - isnt_definer(func, args, desc) should have the proper diagnostics -ok 331 - is_definer(func, args) should fail -ok 332 - is_definer(func, args) should have the proper description -ok 333 - is_definer(func, args) should have the proper diagnostics -ok 334 - isnt_definer(func, args) should pass -ok 335 - isnt_definer(func, args) should have the proper description -ok 336 - isnt_definer(func, args) should have the proper diagnostics -ok 337 - is_definer(func, desc) should pass -ok 338 - is_definer(func, desc) should have the proper description -ok 339 - is_definer(func, desc) should have the proper diagnostics -ok 340 - isnt_definer(func, desc) should fail -ok 341 - isnt_definer(func, desc) should have the proper description -ok 342 - isnt_definer(func, desc) should have the proper diagnostics -ok 343 - is_definer(func) should pass -ok 344 - is_definer(func) should have the proper description -ok 345 - is_definer(func) should have the proper diagnostics -ok 346 - isnt_definer(func) should fail -ok 347 - isnt_definer(func) should have the proper description -ok 348 - isnt_definer(func) should have the proper diagnostics -ok 349 - is_aggregate(schema, func, arg, desc) should pass -ok 350 - is_aggregate(schema, func, arg, desc) should have the proper description -ok 351 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics -ok 352 - isnt_aggregate(schema, func, arg, desc) should fail -ok 353 - isnt_aggregate(schema, func, arg, desc) should have the proper description -ok 354 - isnt_aggregate(schema, func, arg, desc) should have the proper diagnostics -ok 355 - is_aggregate(schema, func, arg) should pass -ok 356 - is_aggregate(schema, func, arg) should have the proper description -ok 357 - is_aggregate(schema, func, arg) should have the proper diagnostics -ok 358 - isnt_aggregate(schema, func, arg) should fail -ok 359 - isnt_aggregate(schema, func, arg) should have the proper description -ok 360 - isnt_aggregate(schema, func, arg) should have the proper diagnostics -ok 361 - is_aggregate(schema, func, args, desc) should fail -ok 362 - is_aggregate(schema, func, args, desc) should have the proper description -ok 363 - is_aggregate(schema, func, args, desc) should have the proper diagnostics -ok 364 - isnt_aggregate(schema, func, args, desc) should pass -ok 365 - isnt_aggregate(schema, func, args, desc) should have the proper description -ok 366 - isnt_aggregate(schema, func, args, desc) should have the proper diagnostics -ok 367 - is_aggregate(schema, func, args) should fail -ok 368 - is_aggregate(schema, func, args) should have the proper description -ok 369 - is_aggregate(schema, func, args) should have the proper diagnostics -ok 370 - isnt_aggregate(schema, func, args) should pass -ok 371 - isnt_aggregate(schema, func, args) should have the proper description -ok 372 - isnt_aggregate(schema, func, args) should have the proper diagnostics -ok 373 - is_aggregate(schema, func, desc) should pass -ok 374 - is_aggregate(schema, func, desc) should have the proper description -ok 375 - is_aggregate(schema, func, desc) should have the proper diagnostics -ok 376 - isnt_aggregate(schema, func, desc) should fail -ok 377 - isnt_aggregate(schema, func, desc) should have the proper description -ok 378 - isnt_aggregate(schema, func, desc) should have the proper diagnostics -ok 379 - is_aggregate(schema, func) should pass -ok 380 - is_aggregate(schema, func) should have the proper description -ok 381 - is_aggregate(schema, func) should have the proper diagnostics -ok 382 - isnt_aggregate(schema, func) should fail -ok 383 - isnt_aggregate(schema, func) should have the proper description -ok 384 - isnt_aggregate(schema, func) should have the proper diagnostics -ok 385 - is_aggregate(schema, func, arg, desc) should pass -ok 386 - is_aggregate(schema, func, arg, desc) should have the proper description -ok 387 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics -ok 388 - isnt_aggregate(schema, func, arg, desc) should fail -ok 389 - isnt_aggregate(schema, func, arg, desc) should have the proper description -ok 390 - isnt_aggregate(schema, func, arg, desc) should have the proper diagnostics -ok 391 - is_aggregate(schema, func, arg) should pass -ok 392 - is_aggregate(schema, func, arg) should have the proper description -ok 393 - is_aggregate(schema, func, arg) should have the proper diagnostics -ok 394 - isnt_aggregate(schema, func, arg) should fail -ok 395 - isnt_aggregate(schema, func, arg) should have the proper description -ok 396 - isnt_aggregate(schema, func, arg) should have the proper diagnostics -ok 397 - is_aggregate(schema, func, args, desc) should fail -ok 398 - is_aggregate(schema, func, args, desc) should have the proper description -ok 399 - is_aggregate(schema, func, args, desc) should have the proper diagnostics -ok 400 - isnt_aggregate(schema, func, args, desc) should pass -ok 401 - isnt_aggregate(schema, func, args, desc) should have the proper description -ok 402 - isnt_aggregate(schema, func, args, desc) should have the proper diagnostics -ok 403 - is_aggregate(schema, func, args) should fail -ok 404 - is_aggregate(schema, func, args) should have the proper description -ok 405 - is_aggregate(schema, func, args) should have the proper diagnostics -ok 406 - isnt_aggregate(schema, func, args) should pass -ok 407 - isnt_aggregate(schema, func, args) should have the proper description -ok 408 - isnt_aggregate(schema, func, args) should have the proper diagnostics -ok 409 - is_aggregate(schema, func, desc) should pass -ok 410 - is_aggregate(schema, func, desc) should have the proper description -ok 411 - is_aggregate(schema, func, desc) should have the proper diagnostics -ok 412 - isnt_aggregate(schema, func, desc) should fail -ok 413 - isnt_aggregate(schema, func, desc) should have the proper description -ok 414 - isnt_aggregate(schema, func, desc) should have the proper diagnostics -ok 415 - is_aggregate(schema, func) should pass -ok 416 - is_aggregate(schema, func) should have the proper description -ok 417 - is_aggregate(schema, func) should have the proper diagnostics -ok 418 - isnt_aggregate(schema, func) should fail -ok 419 - isnt_aggregate(schema, func) should have the proper description -ok 420 - isnt_aggregate(schema, func) should have the proper diagnostics -ok 421 - is_aggregate(func, arg, desc) should pass -ok 422 - is_aggregate(func, arg, desc) should have the proper description -ok 423 - is_aggregate(func, arg, desc) should have the proper diagnostics -ok 424 - isnt_aggregate(func, arg, desc) should fail -ok 425 - isnt_aggregate(func, arg, desc) should have the proper description -ok 426 - isnt_aggregate(func, arg, desc) should have the proper diagnostics -ok 427 - is_aggregate(func, arg) should pass -ok 428 - is_aggregate(func, arg) should have the proper description -ok 429 - is_aggregate(func, arg) should have the proper diagnostics -ok 430 - isnt_aggregate(func, arg) should fail -ok 431 - isnt_aggregate(func, arg) should have the proper description -ok 432 - isnt_aggregate(func, arg) should have the proper diagnostics -ok 433 - is_aggregate(func, args, desc) should fail -ok 434 - is_aggregate(func, args, desc) should have the proper description -ok 435 - is_aggregate(func, args, desc) should have the proper diagnostics -ok 436 - isnt_aggregate(func, args, desc) should pass -ok 437 - isnt_aggregate(func, args, desc) should have the proper description -ok 438 - isnt_aggregate(func, args, desc) should have the proper diagnostics -ok 439 - is_aggregate(func, args) should fail -ok 440 - is_aggregate(func, args) should have the proper description -ok 441 - is_aggregate(func, args) should have the proper diagnostics -ok 442 - isnt_aggregate(func, args) should pass -ok 443 - isnt_aggregate(func, args) should have the proper description -ok 444 - isnt_aggregate(func, args) should have the proper diagnostics -ok 445 - is_aggregate(func, desc) should pass -ok 446 - is_aggregate(func, desc) should have the proper description -ok 447 - is_aggregate(func, desc) should have the proper diagnostics -ok 448 - isnt_aggregate(func, desc) should fail -ok 449 - isnt_aggregate(func, desc) should have the proper description -ok 450 - isnt_aggregate(func, desc) should have the proper diagnostics -ok 451 - is_aggregate(func) should pass -ok 452 - is_aggregate(func) should have the proper description -ok 453 - is_aggregate(func) should have the proper diagnostics -ok 454 - isnt_aggregate(func) should fail -ok 455 - isnt_aggregate(func) should have the proper description -ok 456 - isnt_aggregate(func) should have the proper diagnostics -ok 457 - is_strict(schema, func, 0 args, desc) should pass -ok 458 - is_strict(schema, func, 0 args, desc) should have the proper description -ok 459 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 460 - isnt_strict(schema, func, 0 args, desc) should fail -ok 461 - isnt_strict(schema, func, 0 args, desc) should have the proper description -ok 462 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 463 - is_strict(schema, func, 0 args) should pass -ok 464 - is_strict(schema, func, 0 args) should have the proper description -ok 465 - is_strict(schema, func, 0 args) should have the proper diagnostics -ok 466 - isnt_strict(schema, func, 0 args) should fail -ok 467 - isnt_strict(schema, func, 0 args) should have the proper description -ok 468 - isnt_strict(schema, func, 0 args) should have the proper diagnostics -ok 469 - is_strict(schema, func, args, desc) should fail -ok 470 - is_strict(schema, func, args, desc) should have the proper description -ok 471 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 472 - is_strict(schema, func, args, desc) should pass -ok 473 - is_strict(schema, func, args, desc) should have the proper description -ok 474 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 475 - is_strict(schema, func, args) should fail -ok 476 - is_strict(schema, func, args) should have the proper description -ok 477 - is_strict(schema, func, args) should have the proper diagnostics -ok 478 - is_strict(schema, func, args) should pass -ok 479 - is_strict(schema, func, args) should have the proper description -ok 480 - is_strict(schema, func, args) should have the proper diagnostics -ok 481 - is_strict(schema, func, desc) should pass -ok 482 - is_strict(schema, func, desc) should have the proper description -ok 483 - is_strict(schema, func, desc) should have the proper diagnostics -ok 484 - isnt_strict(schema, func, desc) should fail -ok 485 - isnt_strict(schema, func, desc) should have the proper description -ok 486 - isnt_strict(schema, func, desc) should have the proper diagnostics -ok 487 - is_strict(schema, func) should pass -ok 488 - is_strict(schema, func) should have the proper description -ok 489 - is_strict(schema, func) should have the proper diagnostics -ok 490 - isnt_strict(schema, func) should fail -ok 491 - isnt_strict(schema, func) should have the proper description -ok 492 - isnt_strict(schema, func) should have the proper diagnostics -ok 493 - is_strict(schema, func, 0 args, desc) should pass -ok 494 - is_strict(schema, func, 0 args, desc) should have the proper description -ok 495 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 496 - isnt_strict(schema, func, 0 args, desc) should fail -ok 497 - isnt_strict(schema, func, 0 args, desc) should have the proper description -ok 498 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 499 - is_strict(schema, func, 0 args) should pass -ok 500 - is_strict(schema, func, 0 args) should have the proper description -ok 501 - is_strict(schema, func, 0 args) should have the proper diagnostics -ok 502 - isnt_strict(schema, func, 0 args) should fail -ok 503 - isnt_strict(schema, func, 0 args) should have the proper description -ok 504 - isnt_strict(schema, func, 0 args) should have the proper diagnostics -ok 505 - is_strict(schema, func, args, desc) should fail -ok 506 - is_strict(schema, func, args, desc) should have the proper description -ok 507 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 508 - isnt_strict(schema, func, args, desc) should pass -ok 509 - isnt_strict(schema, func, args, desc) should have the proper description -ok 510 - isnt_strict(schema, func, args, desc) should have the proper diagnostics -ok 511 - is_strict(schema, func, args) should fail -ok 512 - is_strict(schema, func, args) should have the proper description -ok 513 - is_strict(schema, func, args) should have the proper diagnostics -ok 514 - isnt_strict(schema, func, args) should pass -ok 515 - isnt_strict(schema, func, args) should have the proper description -ok 516 - isnt_strict(schema, func, args) should have the proper diagnostics -ok 517 - is_strict(schema, func, desc) should pass -ok 518 - is_strict(schema, func, desc) should have the proper description -ok 519 - is_strict(schema, func, desc) should have the proper diagnostics -ok 520 - isnt_strict(schema, func, desc) should fail -ok 521 - isnt_strict(schema, func, desc) should have the proper description -ok 522 - isnt_strict(schema, func, desc) should have the proper diagnostics -ok 523 - is_strict(schema, func) should pass -ok 524 - is_strict(schema, func) should have the proper description -ok 525 - is_strict(schema, func) should have the proper diagnostics -ok 526 - isnt_strict(schema, func) should fail -ok 527 - isnt_strict(schema, func) should have the proper description -ok 528 - isnt_strict(schema, func) should have the proper diagnostics -ok 529 - is_strict(func, 0 args, desc) should pass -ok 530 - is_strict(func, 0 args, desc) should have the proper description -ok 531 - is_strict(func, 0 args, desc) should have the proper diagnostics -ok 532 - isnt_strict(func, 0 args, desc) should fail -ok 533 - isnt_strict(func, 0 args, desc) should have the proper description -ok 534 - isnt_strict(func, 0 args, desc) should have the proper diagnostics -ok 535 - is_strict(func, 0 args) should pass -ok 536 - is_strict(func, 0 args) should have the proper description -ok 537 - is_strict(func, 0 args) should have the proper diagnostics -ok 538 - isnt_strict(func, 0 args) should fail -ok 539 - isnt_strict(func, 0 args) should have the proper description -ok 540 - isnt_strict(func, 0 args) should have the proper diagnostics -ok 541 - is_strict(func, args, desc) should fail -ok 542 - is_strict(func, args, desc) should have the proper description -ok 543 - is_strict(func, args, desc) should have the proper diagnostics -ok 544 - isnt_strict(func, args, desc) should pass -ok 545 - isnt_strict(func, args, desc) should have the proper description -ok 546 - isnt_strict(func, args, desc) should have the proper diagnostics -ok 547 - is_strict(func, args) should fail -ok 548 - is_strict(func, args) should have the proper description -ok 549 - is_strict(func, args) should have the proper diagnostics -ok 550 - isnt_strict(func, args) should pass -ok 551 - isnt_strict(func, args) should have the proper description -ok 552 - isnt_strict(func, args) should have the proper diagnostics -ok 553 - is_strict(func, desc) should pass -ok 554 - is_strict(func, desc) should have the proper description -ok 555 - is_strict(func, desc) should have the proper diagnostics -ok 556 - isnt_strict(func, desc) should fail -ok 557 - isnt_strict(func, desc) should have the proper description -ok 558 - isnt_strict(func, desc) should have the proper diagnostics -ok 559 - is_strict(func) should pass -ok 560 - is_strict(func) should have the proper description -ok 561 - is_strict(func) should have the proper diagnostics -ok 562 - isnt_strict(func) should fail -ok 563 - isnt_strict(func) should have the proper description -ok 564 - isnt_strict(func) should have the proper diagnostics -ok 565 - function_volatility(schema, func, 0 args, volatile, desc) should pass -ok 566 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper description -ok 567 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper diagnostics -ok 568 - function_volatility(schema, func, 0 args, VOLATILE, desc) should pass -ok 569 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper description -ok 570 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper diagnostics -ok 571 - function_volatility(schema, func, 0 args, v, desc) should pass -ok 572 - function_volatility(schema, func, 0 args, v, desc) should have the proper description -ok 573 - function_volatility(schema, func, 0 args, v, desc) should have the proper diagnostics -ok 574 - function_volatility(schema, func, args, immutable, desc) should pass -ok 575 - function_volatility(schema, func, args, immutable, desc) should have the proper description -ok 576 - function_volatility(schema, func, args, immutable, desc) should have the proper diagnostics -ok 577 - function_volatility(schema, func, 0 args, stable, desc) should pass -ok 578 - function_volatility(schema, func, 0 args, stable, desc) should have the proper description -ok 579 - function_volatility(schema, func, 0 args, stable, desc) should have the proper diagnostics -ok 580 - function_volatility(schema, func, 0 args, volatile) should pass -ok 581 - function_volatility(schema, func, 0 args, volatile) should have the proper description -ok 582 - function_volatility(schema, func, 0 args, volatile) should have the proper diagnostics -ok 583 - function_volatility(schema, func, args, immutable) should pass -ok 584 - function_volatility(schema, func, args, immutable) should have the proper description -ok 585 - function_volatility(schema, func, volatile, desc) should pass -ok 586 - function_volatility(schema, func, volatile, desc) should have the proper description -ok 587 - function_volatility(schema, func, volatile, desc) should have the proper diagnostics -ok 588 - function_volatility(schema, func, volatile) should pass -ok 589 - function_volatility(schema, func, volatile) should have the proper description -ok 590 - function_volatility(schema, func, volatile) should have the proper diagnostics -ok 591 - function_volatility(schema, func, immutable, desc) should pass -ok 592 - function_volatility(schema, func, immutable, desc) should have the proper description -ok 593 - function_volatility(schema, func, immutable, desc) should have the proper diagnostics -ok 594 - function_volatility(schema, func, stable, desc) should pass -ok 595 - function_volatility(schema, func, stable, desc) should have the proper description -ok 596 - function_volatility(schema, func, stable, desc) should have the proper diagnostics -ok 597 - function_volatility(func, 0 args, volatile, desc) should pass -ok 598 - function_volatility(func, 0 args, volatile, desc) should have the proper description -ok 599 - function_volatility(func, 0 args, volatile, desc) should have the proper diagnostics -ok 600 - function_volatility(func, 0 args, VOLATILE, desc) should pass -ok 601 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper description -ok 602 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper diagnostics -ok 603 - function_volatility(func, 0 args, v, desc) should pass -ok 604 - function_volatility(func, 0 args, v, desc) should have the proper description -ok 605 - function_volatility(func, 0 args, v, desc) should have the proper diagnostics -ok 606 - function_volatility(func, args, immutable, desc) should pass -ok 607 - function_volatility(func, args, immutable, desc) should have the proper description -ok 608 - function_volatility(func, args, immutable, desc) should have the proper diagnostics -ok 609 - function_volatility(func, 0 args, stable, desc) should pass -ok 610 - function_volatility(func, 0 args, stable, desc) should have the proper description -ok 611 - function_volatility(func, 0 args, stable, desc) should have the proper diagnostics -ok 612 - function_volatility(func, 0 args, volatile) should pass -ok 613 - function_volatility(func, 0 args, volatile) should have the proper description -ok 614 - function_volatility(func, 0 args, volatile) should have the proper diagnostics -ok 615 - function_volatility(func, args, immutable) should pass -ok 616 - function_volatility(func, args, immutable) should have the proper description -ok 617 - function_volatility(func, volatile, desc) should pass -ok 618 - function_volatility(func, volatile, desc) should have the proper description -ok 619 - function_volatility(func, volatile, desc) should have the proper diagnostics -ok 620 - function_volatility(func, volatile) should pass -ok 621 - function_volatility(func, volatile) should have the proper description -ok 622 - function_volatility(func, volatile) should have the proper diagnostics -ok 623 - function_volatility(func, immutable, desc) should pass -ok 624 - function_volatility(func, immutable, desc) should have the proper description -ok 625 - function_volatility(func, immutable, desc) should have the proper diagnostics -ok 626 - function_volatility(func, stable, desc) should pass -ok 627 - function_volatility(func, stable, desc) should have the proper description -ok 628 - function_volatility(func, stable, desc) should have the proper diagnostics +ok 100 - fail can(schema) with desc should fail +ok 101 - fail can(schema) with desc should have the proper description +ok 102 - fail can(schema) with desc should have the proper diagnostics +ok 103 - fail can(someschema) with desc should fail +ok 104 - fail can(someschema) with desc should have the proper description +ok 105 - fail can(someschema) with desc should have the proper diagnostics +ok 106 - can() with desc should pass +ok 107 - can() with desc should have the proper description +ok 108 - can() with desc should have the proper diagnostics +ok 109 - can(schema) should pass +ok 110 - can(schema) should have the proper description +ok 111 - can(schema) should have the proper diagnostics +ok 112 - fail can() with desc should fail +ok 113 - fail can() with desc should have the proper description +ok 114 - fail can() with desc should have the proper diagnostics +ok 115 - function_lang_is(schema, func, 0 args, sql, desc) should pass +ok 116 - function_lang_is(schema, func, 0 args, sql, desc) should have the proper description +ok 117 - function_lang_is(schema, func, 0 args, sql, desc) should have the proper diagnostics +ok 118 - function_lang_is(schema, func, 0 args, sql) should pass +ok 119 - function_lang_is(schema, func, 0 args, sql) should have the proper description +ok 120 - function_lang_is(schema, func, 0 args, sql) should have the proper diagnostics +ok 121 - function_lang_is(schema, func, args, plpgsql, desc) should pass +ok 122 - function_lang_is(schema, func, args, plpgsql, desc) should have the proper description +ok 123 - function_lang_is(schema, func, args, plpgsql, desc) should have the proper diagnostics +ok 124 - function_lang_is(schema, func, args, plpgsql) should pass +ok 125 - function_lang_is(schema, func, args, plpgsql) should have the proper description +ok 126 - function_lang_is(schema, func, args, plpgsql) should have the proper diagnostics +ok 127 - function_lang_is(schema, func, 0 args, perl, desc) should fail +ok 128 - function_lang_is(schema, func, 0 args, perl, desc) should have the proper description +ok 129 - function_lang_is(schema, func, 0 args, perl, desc) should have the proper diagnostics +ok 130 - function_lang_is(schema, non-func, 0 args, sql, desc) should fail +ok 131 - function_lang_is(schema, non-func, 0 args, sql, desc) should have the proper description +ok 132 - function_lang_is(schema, non-func, 0 args, sql, desc) should have the proper diagnostics +ok 133 - function_lang_is(schema, func, args, plpgsql) should fail +ok 134 - function_lang_is(schema, func, args, plpgsql) should have the proper description +ok 135 - function_lang_is(schema, func, args, plpgsql) should have the proper diagnostics +ok 136 - function_lang_is(schema, func, sql, desc) should pass +ok 137 - function_lang_is(schema, func, sql, desc) should have the proper description +ok 138 - function_lang_is(schema, func, sql, desc) should have the proper diagnostics +ok 139 - function_lang_is(schema, func, sql) should pass +ok 140 - function_lang_is(schema, func, sql) should have the proper description +ok 141 - function_lang_is(schema, func, sql) should have the proper diagnostics +ok 142 - function_lang_is(schema, func, perl, desc) should fail +ok 143 - function_lang_is(schema, func, perl, desc) should have the proper description +ok 144 - function_lang_is(schema, func, perl, desc) should have the proper diagnostics +ok 145 - function_lang_is(schema, non-func, sql, desc) should fail +ok 146 - function_lang_is(schema, non-func, sql, desc) should have the proper description +ok 147 - function_lang_is(schema, non-func, sql, desc) should have the proper diagnostics +ok 148 - function_lang_is(func, 0 args, sql, desc) should pass +ok 149 - function_lang_is(func, 0 args, sql, desc) should have the proper description +ok 150 - function_lang_is(func, 0 args, sql, desc) should have the proper diagnostics +ok 151 - function_lang_is(func, 0 args, sql) should pass +ok 152 - function_lang_is(func, 0 args, sql) should have the proper description +ok 153 - function_lang_is(func, 0 args, sql) should have the proper diagnostics +ok 154 - function_lang_is(func, args, plpgsql, desc) should pass +ok 155 - function_lang_is(func, args, plpgsql, desc) should have the proper description +ok 156 - function_lang_is(func, args, plpgsql, desc) should have the proper diagnostics +ok 157 - function_lang_is(func, args, plpgsql) should pass +ok 158 - function_lang_is(func, args, plpgsql) should have the proper description +ok 159 - function_lang_is(func, args, plpgsql) should have the proper diagnostics +ok 160 - function_lang_is(func, 0 args, perl, desc) should fail +ok 161 - function_lang_is(func, 0 args, perl, desc) should have the proper description +ok 162 - function_lang_is(func, 0 args, perl, desc) should have the proper diagnostics +ok 163 - function_lang_is(non-func, 0 args, sql, desc) should fail +ok 164 - function_lang_is(non-func, 0 args, sql, desc) should have the proper description +ok 165 - function_lang_is(non-func, 0 args, sql, desc) should have the proper diagnostics +ok 166 - function_lang_is(func, args, plpgsql) should fail +ok 167 - function_lang_is(func, args, plpgsql) should have the proper description +ok 168 - function_lang_is(func, args, plpgsql) should have the proper diagnostics +ok 169 - function_lang_is(func, sql, desc) should pass +ok 170 - function_lang_is(func, sql, desc) should have the proper description +ok 171 - function_lang_is(func, sql, desc) should have the proper diagnostics +ok 172 - function_lang_is(func, sql) should pass +ok 173 - function_lang_is(func, sql) should have the proper description +ok 174 - function_lang_is(func, sql) should have the proper diagnostics +ok 175 - function_lang_is(func, perl, desc) should fail +ok 176 - function_lang_is(func, perl, desc) should have the proper description +ok 177 - function_lang_is(func, perl, desc) should have the proper diagnostics +ok 178 - function_lang_is(non-func, sql, desc) should fail +ok 179 - function_lang_is(non-func, sql, desc) should have the proper description +ok 180 - function_lang_is(non-func, sql, desc) should have the proper diagnostics +ok 181 - function_returns(schema, func, 0 args, bool, desc) should pass +ok 182 - function_returns(schema, func, 0 args, bool, desc) should have the proper description +ok 183 - function_returns(schema, func, 0 args, bool, desc) should have the proper diagnostics +ok 184 - function_returns(schema, func, 0 args, bool) should pass +ok 185 - function_returns(schema, func, 0 args, bool) should have the proper description +ok 186 - function_returns(schema, func, 0 args, bool) should have the proper diagnostics +ok 187 - function_returns(schema, func, args, bool, false) should pass +ok 188 - function_returns(schema, func, args, bool, false) should have the proper description +ok 189 - function_returns(schema, func, args, bool, false) should have the proper diagnostics +ok 190 - function_returns(schema, func, args, bool) should pass +ok 191 - function_returns(schema, func, args, bool) should have the proper description +ok 192 - function_returns(schema, func, args, bool) should have the proper diagnostics +ok 193 - function_returns(schema, func, 0 args, setof bool, desc) should pass +ok 194 - function_returns(schema, func, 0 args, setof bool, desc) should have the proper description +ok 195 - function_returns(schema, func, 0 args, setof bool, desc) should have the proper diagnostics +ok 196 - function_returns(schema, func, 0 args, setof bool) should pass +ok 197 - function_returns(schema, func, 0 args, setof bool) should have the proper description +ok 198 - function_returns(schema, func, 0 args, setof bool) should have the proper diagnostics +ok 199 - function_returns(schema, func, bool, desc) should pass +ok 200 - function_returns(schema, func, bool, desc) should have the proper description +ok 201 - function_returns(schema, func, bool, desc) should have the proper diagnostics +ok 202 - function_returns(schema, func, bool) should pass +ok 203 - function_returns(schema, func, bool) should have the proper description +ok 204 - function_returns(schema, func, bool) should have the proper diagnostics +ok 205 - function_returns(schema, other func, bool, false) should pass +ok 206 - function_returns(schema, other func, bool, false) should have the proper description +ok 207 - function_returns(schema, other func, bool, false) should have the proper diagnostics +ok 208 - function_returns(schema, other func, bool) should pass +ok 209 - function_returns(schema, other func, bool) should have the proper description +ok 210 - function_returns(schema, other func, bool) should have the proper diagnostics +ok 211 - function_returns(schema, func, setof bool, desc) should pass +ok 212 - function_returns(schema, func, setof bool, desc) should have the proper description +ok 213 - function_returns(schema, func, setof bool, desc) should have the proper diagnostics +ok 214 - function_returns(schema, func, setof bool) should pass +ok 215 - function_returns(schema, func, setof bool) should have the proper description +ok 216 - function_returns(schema, func, setof bool) should have the proper diagnostics +ok 217 - function_returns(func, 0 args, bool, desc) should pass +ok 218 - function_returns(func, 0 args, bool, desc) should have the proper description +ok 219 - function_returns(func, 0 args, bool, desc) should have the proper diagnostics +ok 220 - function_returns(func, 0 args, bool) should pass +ok 221 - function_returns(func, 0 args, bool) should have the proper description +ok 222 - function_returns(func, 0 args, bool) should have the proper diagnostics +ok 223 - function_returns(func, args, bool, false) should pass +ok 224 - function_returns(func, args, bool, false) should have the proper description +ok 225 - function_returns(func, args, bool, false) should have the proper diagnostics +ok 226 - function_returns(func, args, bool) should pass +ok 227 - function_returns(func, args, bool) should have the proper description +ok 228 - function_returns(func, args, bool) should have the proper diagnostics +ok 229 - function_returns(func, 0 args, setof bool, desc) should pass +ok 230 - function_returns(func, 0 args, setof bool, desc) should have the proper description +ok 231 - function_returns(func, 0 args, setof bool, desc) should have the proper diagnostics +ok 232 - function_returns(func, 0 args, setof bool) should pass +ok 233 - function_returns(func, 0 args, setof bool) should have the proper description +ok 234 - function_returns(func, 0 args, setof bool) should have the proper diagnostics +ok 235 - function_returns(func, bool, desc) should pass +ok 236 - function_returns(func, bool, desc) should have the proper description +ok 237 - function_returns(func, bool, desc) should have the proper diagnostics +ok 238 - function_returns(func, bool) should pass +ok 239 - function_returns(func, bool) should have the proper description +ok 240 - function_returns(func, bool) should have the proper diagnostics +ok 241 - function_returns(other func, bool, false) should pass +ok 242 - function_returns(other func, bool, false) should have the proper description +ok 243 - function_returns(other func, bool, false) should have the proper diagnostics +ok 244 - function_returns(other func, bool) should pass +ok 245 - function_returns(other func, bool) should have the proper description +ok 246 - function_returns(other func, bool) should have the proper diagnostics +ok 247 - function_returns(func, setof bool, desc) should pass +ok 248 - function_returns(func, setof bool, desc) should have the proper description +ok 249 - function_returns(func, setof bool, desc) should have the proper diagnostics +ok 250 - function_returns(func, setof bool) should pass +ok 251 - function_returns(func, setof bool) should have the proper description +ok 252 - function_returns(func, setof bool) should have the proper diagnostics +ok 253 - is_definer(schema, func, 0 args, desc) should pass +ok 254 - is_definer(schema, func, 0 args, desc) should have the proper description +ok 255 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 256 - isnt_definer(schema, func, 0 args, desc) should fail +ok 257 - isnt_definer(schema, func, 0 args, desc) should have the proper description +ok 258 - isnt_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 259 - is_definer(schema, func, 0 args) should pass +ok 260 - is_definer(schema, func, 0 args) should have the proper description +ok 261 - is_definer(schema, func, 0 args) should have the proper diagnostics +ok 262 - isnt_definer(schema, func, 0 args) should fail +ok 263 - isnt_definer(schema, func, 0 args) should have the proper description +ok 264 - isnt_definer(schema, func, 0 args) should have the proper diagnostics +ok 265 - is_definer(schema, func, args, desc) should fail +ok 266 - is_definer(schema, func, args, desc) should have the proper description +ok 267 - is_definer(schema, func, args, desc) should have the proper diagnostics +ok 268 - isnt_definer(schema, func, args, desc) should pass +ok 269 - isnt_definer(schema, func, args, desc) should have the proper description +ok 270 - isnt_definer(schema, func, args, desc) should have the proper diagnostics +ok 271 - is_definer(schema, func, args) should fail +ok 272 - is_definer(schema, func, args) should have the proper description +ok 273 - is_definer(schema, func, args) should have the proper diagnostics +ok 274 - isnt_definer(schema, func, args) should pass +ok 275 - isnt_definer(schema, func, args) should have the proper description +ok 276 - isnt_definer(schema, func, args) should have the proper diagnostics +ok 277 - is_definer(schema, func, desc) should pass +ok 278 - is_definer(schema, func, desc) should have the proper description +ok 279 - is_definer(schema, func, desc) should have the proper diagnostics +ok 280 - isnt_definer(schema, func, desc) should fail +ok 281 - isnt_definer(schema, func, desc) should have the proper description +ok 282 - isnt_definer(schema, func, desc) should have the proper diagnostics +ok 283 - is_definer(schema, func) should pass +ok 284 - is_definer(schema, func) should have the proper description +ok 285 - is_definer(schema, func) should have the proper diagnostics +ok 286 - isnt_definer(schema, func) should fail +ok 287 - isnt_definer(schema, func) should have the proper description +ok 288 - isnt_definer(schema, func) should have the proper diagnostics +ok 289 - is_definer(schema, func, 0 args, desc) should pass +ok 290 - is_definer(schema, func, 0 args, desc) should have the proper description +ok 291 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 292 - isnt_definer(schema, func, 0 args, desc) should fail +ok 293 - isnt_definer(schema, func, 0 args, desc) should have the proper description +ok 294 - isnt_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 295 - is_definer(schema, func, 0 args) should pass +ok 296 - is_definer(schema, func, 0 args) should have the proper description +ok 297 - is_definer(schema, func, 0 args) should have the proper diagnostics +ok 298 - isnt_definer(schema, func, 0 args) should fail +ok 299 - isnt_definer(schema, func, 0 args) should have the proper description +ok 300 - isnt_definer(schema, func, 0 args) should have the proper diagnostics +ok 301 - is_definer(schema, func, args, desc) should fail +ok 302 - is_definer(schema, func, args, desc) should have the proper description +ok 303 - is_definer(schema, func, args, desc) should have the proper diagnostics +ok 304 - isnt_definer(schema, func, args, desc) should pass +ok 305 - isnt_definer(schema, func, args, desc) should have the proper description +ok 306 - isnt_definer(schema, func, args, desc) should have the proper diagnostics +ok 307 - is_definer(schema, func, args) should fail +ok 308 - is_definer(schema, func, args) should have the proper description +ok 309 - is_definer(schema, func, args) should have the proper diagnostics +ok 310 - isnt_definer(schema, func, args) should pass +ok 311 - isnt_definer(schema, func, args) should have the proper description +ok 312 - isnt_definer(schema, func, args) should have the proper diagnostics +ok 313 - is_definer(schema, func, desc) should pass +ok 314 - is_definer(schema, func, desc) should have the proper description +ok 315 - is_definer(schema, func, desc) should have the proper diagnostics +ok 316 - isnt_definer(schema, func, desc) should fail +ok 317 - isnt_definer(schema, func, desc) should have the proper description +ok 318 - isnt_definer(schema, func, desc) should have the proper diagnostics +ok 319 - is_definer(schema, func) should pass +ok 320 - is_definer(schema, func) should have the proper description +ok 321 - is_definer(schema, func) should have the proper diagnostics +ok 322 - isnt_definer(schema, func) should fail +ok 323 - isnt_definer(schema, func) should have the proper description +ok 324 - isnt_definer(schema, func) should have the proper diagnostics +ok 325 - is_definer(func, 0 args, desc) should pass +ok 326 - is_definer(func, 0 args, desc) should have the proper description +ok 327 - is_definer(func, 0 args, desc) should have the proper diagnostics +ok 328 - isnt_definer(func, 0 args, desc) should fail +ok 329 - isnt_definer(func, 0 args, desc) should have the proper description +ok 330 - isnt_definer(func, 0 args, desc) should have the proper diagnostics +ok 331 - is_definer(func, 0 args) should pass +ok 332 - is_definer(func, 0 args) should have the proper description +ok 333 - is_definer(func, 0 args) should have the proper diagnostics +ok 334 - isnt_definer(func, 0 args) should fail +ok 335 - isnt_definer(func, 0 args) should have the proper description +ok 336 - isnt_definer(func, 0 args) should have the proper diagnostics +ok 337 - is_definer(func, args, desc) should fail +ok 338 - is_definer(func, args, desc) should have the proper description +ok 339 - is_definer(func, args, desc) should have the proper diagnostics +ok 340 - isnt_definer(func, args, desc) should pass +ok 341 - isnt_definer(func, args, desc) should have the proper description +ok 342 - isnt_definer(func, args, desc) should have the proper diagnostics +ok 343 - is_definer(func, args) should fail +ok 344 - is_definer(func, args) should have the proper description +ok 345 - is_definer(func, args) should have the proper diagnostics +ok 346 - isnt_definer(func, args) should pass +ok 347 - isnt_definer(func, args) should have the proper description +ok 348 - isnt_definer(func, args) should have the proper diagnostics +ok 349 - is_definer(func, desc) should pass +ok 350 - is_definer(func, desc) should have the proper description +ok 351 - is_definer(func, desc) should have the proper diagnostics +ok 352 - isnt_definer(func, desc) should fail +ok 353 - isnt_definer(func, desc) should have the proper description +ok 354 - isnt_definer(func, desc) should have the proper diagnostics +ok 355 - is_definer(func) should pass +ok 356 - is_definer(func) should have the proper description +ok 357 - is_definer(func) should have the proper diagnostics +ok 358 - isnt_definer(func) should fail +ok 359 - isnt_definer(func) should have the proper description +ok 360 - isnt_definer(func) should have the proper diagnostics +ok 361 - is_aggregate(schema, func, arg, desc) should pass +ok 362 - is_aggregate(schema, func, arg, desc) should have the proper description +ok 363 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 364 - isnt_aggregate(schema, func, arg, desc) should fail +ok 365 - isnt_aggregate(schema, func, arg, desc) should have the proper description +ok 366 - isnt_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 367 - is_aggregate(schema, func, arg) should pass +ok 368 - is_aggregate(schema, func, arg) should have the proper description +ok 369 - is_aggregate(schema, func, arg) should have the proper diagnostics +ok 370 - isnt_aggregate(schema, func, arg) should fail +ok 371 - isnt_aggregate(schema, func, arg) should have the proper description +ok 372 - isnt_aggregate(schema, func, arg) should have the proper diagnostics +ok 373 - is_aggregate(schema, func, args, desc) should fail +ok 374 - is_aggregate(schema, func, args, desc) should have the proper description +ok 375 - is_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 376 - isnt_aggregate(schema, func, args, desc) should pass +ok 377 - isnt_aggregate(schema, func, args, desc) should have the proper description +ok 378 - isnt_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 379 - is_aggregate(schema, func, args) should fail +ok 380 - is_aggregate(schema, func, args) should have the proper description +ok 381 - is_aggregate(schema, func, args) should have the proper diagnostics +ok 382 - isnt_aggregate(schema, func, args) should pass +ok 383 - isnt_aggregate(schema, func, args) should have the proper description +ok 384 - isnt_aggregate(schema, func, args) should have the proper diagnostics +ok 385 - is_aggregate(schema, func, desc) should pass +ok 386 - is_aggregate(schema, func, desc) should have the proper description +ok 387 - is_aggregate(schema, func, desc) should have the proper diagnostics +ok 388 - isnt_aggregate(schema, func, desc) should fail +ok 389 - isnt_aggregate(schema, func, desc) should have the proper description +ok 390 - isnt_aggregate(schema, func, desc) should have the proper diagnostics +ok 391 - is_aggregate(schema, func) should pass +ok 392 - is_aggregate(schema, func) should have the proper description +ok 393 - is_aggregate(schema, func) should have the proper diagnostics +ok 394 - isnt_aggregate(schema, func) should fail +ok 395 - isnt_aggregate(schema, func) should have the proper description +ok 396 - isnt_aggregate(schema, func) should have the proper diagnostics +ok 397 - is_aggregate(schema, func, arg, desc) should pass +ok 398 - is_aggregate(schema, func, arg, desc) should have the proper description +ok 399 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 400 - isnt_aggregate(schema, func, arg, desc) should fail +ok 401 - isnt_aggregate(schema, func, arg, desc) should have the proper description +ok 402 - isnt_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 403 - is_aggregate(schema, func, arg) should pass +ok 404 - is_aggregate(schema, func, arg) should have the proper description +ok 405 - is_aggregate(schema, func, arg) should have the proper diagnostics +ok 406 - isnt_aggregate(schema, func, arg) should fail +ok 407 - isnt_aggregate(schema, func, arg) should have the proper description +ok 408 - isnt_aggregate(schema, func, arg) should have the proper diagnostics +ok 409 - is_aggregate(schema, func, args, desc) should fail +ok 410 - is_aggregate(schema, func, args, desc) should have the proper description +ok 411 - is_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 412 - isnt_aggregate(schema, func, args, desc) should pass +ok 413 - isnt_aggregate(schema, func, args, desc) should have the proper description +ok 414 - isnt_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 415 - is_aggregate(schema, func, args) should fail +ok 416 - is_aggregate(schema, func, args) should have the proper description +ok 417 - is_aggregate(schema, func, args) should have the proper diagnostics +ok 418 - isnt_aggregate(schema, func, args) should pass +ok 419 - isnt_aggregate(schema, func, args) should have the proper description +ok 420 - isnt_aggregate(schema, func, args) should have the proper diagnostics +ok 421 - is_aggregate(schema, func, desc) should pass +ok 422 - is_aggregate(schema, func, desc) should have the proper description +ok 423 - is_aggregate(schema, func, desc) should have the proper diagnostics +ok 424 - isnt_aggregate(schema, func, desc) should fail +ok 425 - isnt_aggregate(schema, func, desc) should have the proper description +ok 426 - isnt_aggregate(schema, func, desc) should have the proper diagnostics +ok 427 - is_aggregate(schema, func) should pass +ok 428 - is_aggregate(schema, func) should have the proper description +ok 429 - is_aggregate(schema, func) should have the proper diagnostics +ok 430 - isnt_aggregate(schema, func) should fail +ok 431 - isnt_aggregate(schema, func) should have the proper description +ok 432 - isnt_aggregate(schema, func) should have the proper diagnostics +ok 433 - is_aggregate(func, arg, desc) should pass +ok 434 - is_aggregate(func, arg, desc) should have the proper description +ok 435 - is_aggregate(func, arg, desc) should have the proper diagnostics +ok 436 - isnt_aggregate(func, arg, desc) should fail +ok 437 - isnt_aggregate(func, arg, desc) should have the proper description +ok 438 - isnt_aggregate(func, arg, desc) should have the proper diagnostics +ok 439 - is_aggregate(func, arg) should pass +ok 440 - is_aggregate(func, arg) should have the proper description +ok 441 - is_aggregate(func, arg) should have the proper diagnostics +ok 442 - isnt_aggregate(func, arg) should fail +ok 443 - isnt_aggregate(func, arg) should have the proper description +ok 444 - isnt_aggregate(func, arg) should have the proper diagnostics +ok 445 - is_aggregate(func, args, desc) should fail +ok 446 - is_aggregate(func, args, desc) should have the proper description +ok 447 - is_aggregate(func, args, desc) should have the proper diagnostics +ok 448 - isnt_aggregate(func, args, desc) should pass +ok 449 - isnt_aggregate(func, args, desc) should have the proper description +ok 450 - isnt_aggregate(func, args, desc) should have the proper diagnostics +ok 451 - is_aggregate(func, args) should fail +ok 452 - is_aggregate(func, args) should have the proper description +ok 453 - is_aggregate(func, args) should have the proper diagnostics +ok 454 - isnt_aggregate(func, args) should pass +ok 455 - isnt_aggregate(func, args) should have the proper description +ok 456 - isnt_aggregate(func, args) should have the proper diagnostics +ok 457 - is_aggregate(func, desc) should pass +ok 458 - is_aggregate(func, desc) should have the proper description +ok 459 - is_aggregate(func, desc) should have the proper diagnostics +ok 460 - isnt_aggregate(func, desc) should fail +ok 461 - isnt_aggregate(func, desc) should have the proper description +ok 462 - isnt_aggregate(func, desc) should have the proper diagnostics +ok 463 - is_aggregate(func) should pass +ok 464 - is_aggregate(func) should have the proper description +ok 465 - is_aggregate(func) should have the proper diagnostics +ok 466 - isnt_aggregate(func) should fail +ok 467 - isnt_aggregate(func) should have the proper description +ok 468 - isnt_aggregate(func) should have the proper diagnostics +ok 469 - is_strict(schema, func, 0 args, desc) should pass +ok 470 - is_strict(schema, func, 0 args, desc) should have the proper description +ok 471 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 472 - isnt_strict(schema, func, 0 args, desc) should fail +ok 473 - isnt_strict(schema, func, 0 args, desc) should have the proper description +ok 474 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 475 - is_strict(schema, func, 0 args) should pass +ok 476 - is_strict(schema, func, 0 args) should have the proper description +ok 477 - is_strict(schema, func, 0 args) should have the proper diagnostics +ok 478 - isnt_strict(schema, func, 0 args) should fail +ok 479 - isnt_strict(schema, func, 0 args) should have the proper description +ok 480 - isnt_strict(schema, func, 0 args) should have the proper diagnostics +ok 481 - is_strict(schema, func, args, desc) should fail +ok 482 - is_strict(schema, func, args, desc) should have the proper description +ok 483 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 484 - is_strict(schema, func, args, desc) should pass +ok 485 - is_strict(schema, func, args, desc) should have the proper description +ok 486 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 487 - is_strict(schema, func, args) should fail +ok 488 - is_strict(schema, func, args) should have the proper description +ok 489 - is_strict(schema, func, args) should have the proper diagnostics +ok 490 - is_strict(schema, func, args) should pass +ok 491 - is_strict(schema, func, args) should have the proper description +ok 492 - is_strict(schema, func, args) should have the proper diagnostics +ok 493 - is_strict(schema, func, desc) should pass +ok 494 - is_strict(schema, func, desc) should have the proper description +ok 495 - is_strict(schema, func, desc) should have the proper diagnostics +ok 496 - isnt_strict(schema, func, desc) should fail +ok 497 - isnt_strict(schema, func, desc) should have the proper description +ok 498 - isnt_strict(schema, func, desc) should have the proper diagnostics +ok 499 - is_strict(schema, func) should pass +ok 500 - is_strict(schema, func) should have the proper description +ok 501 - is_strict(schema, func) should have the proper diagnostics +ok 502 - isnt_strict(schema, func) should fail +ok 503 - isnt_strict(schema, func) should have the proper description +ok 504 - isnt_strict(schema, func) should have the proper diagnostics +ok 505 - is_strict(schema, func, 0 args, desc) should pass +ok 506 - is_strict(schema, func, 0 args, desc) should have the proper description +ok 507 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 508 - isnt_strict(schema, func, 0 args, desc) should fail +ok 509 - isnt_strict(schema, func, 0 args, desc) should have the proper description +ok 510 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 511 - is_strict(schema, func, 0 args) should pass +ok 512 - is_strict(schema, func, 0 args) should have the proper description +ok 513 - is_strict(schema, func, 0 args) should have the proper diagnostics +ok 514 - isnt_strict(schema, func, 0 args) should fail +ok 515 - isnt_strict(schema, func, 0 args) should have the proper description +ok 516 - isnt_strict(schema, func, 0 args) should have the proper diagnostics +ok 517 - is_strict(schema, func, args, desc) should fail +ok 518 - is_strict(schema, func, args, desc) should have the proper description +ok 519 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 520 - isnt_strict(schema, func, args, desc) should pass +ok 521 - isnt_strict(schema, func, args, desc) should have the proper description +ok 522 - isnt_strict(schema, func, args, desc) should have the proper diagnostics +ok 523 - is_strict(schema, func, args) should fail +ok 524 - is_strict(schema, func, args) should have the proper description +ok 525 - is_strict(schema, func, args) should have the proper diagnostics +ok 526 - isnt_strict(schema, func, args) should pass +ok 527 - isnt_strict(schema, func, args) should have the proper description +ok 528 - isnt_strict(schema, func, args) should have the proper diagnostics +ok 529 - is_strict(schema, func, desc) should pass +ok 530 - is_strict(schema, func, desc) should have the proper description +ok 531 - is_strict(schema, func, desc) should have the proper diagnostics +ok 532 - isnt_strict(schema, func, desc) should fail +ok 533 - isnt_strict(schema, func, desc) should have the proper description +ok 534 - isnt_strict(schema, func, desc) should have the proper diagnostics +ok 535 - is_strict(schema, func) should pass +ok 536 - is_strict(schema, func) should have the proper description +ok 537 - is_strict(schema, func) should have the proper diagnostics +ok 538 - isnt_strict(schema, func) should fail +ok 539 - isnt_strict(schema, func) should have the proper description +ok 540 - isnt_strict(schema, func) should have the proper diagnostics +ok 541 - is_strict(func, 0 args, desc) should pass +ok 542 - is_strict(func, 0 args, desc) should have the proper description +ok 543 - is_strict(func, 0 args, desc) should have the proper diagnostics +ok 544 - isnt_strict(func, 0 args, desc) should fail +ok 545 - isnt_strict(func, 0 args, desc) should have the proper description +ok 546 - isnt_strict(func, 0 args, desc) should have the proper diagnostics +ok 547 - is_strict(func, 0 args) should pass +ok 548 - is_strict(func, 0 args) should have the proper description +ok 549 - is_strict(func, 0 args) should have the proper diagnostics +ok 550 - isnt_strict(func, 0 args) should fail +ok 551 - isnt_strict(func, 0 args) should have the proper description +ok 552 - isnt_strict(func, 0 args) should have the proper diagnostics +ok 553 - is_strict(func, args, desc) should fail +ok 554 - is_strict(func, args, desc) should have the proper description +ok 555 - is_strict(func, args, desc) should have the proper diagnostics +ok 556 - isnt_strict(func, args, desc) should pass +ok 557 - isnt_strict(func, args, desc) should have the proper description +ok 558 - isnt_strict(func, args, desc) should have the proper diagnostics +ok 559 - is_strict(func, args) should fail +ok 560 - is_strict(func, args) should have the proper description +ok 561 - is_strict(func, args) should have the proper diagnostics +ok 562 - isnt_strict(func, args) should pass +ok 563 - isnt_strict(func, args) should have the proper description +ok 564 - isnt_strict(func, args) should have the proper diagnostics +ok 565 - is_strict(func, desc) should pass +ok 566 - is_strict(func, desc) should have the proper description +ok 567 - is_strict(func, desc) should have the proper diagnostics +ok 568 - isnt_strict(func, desc) should fail +ok 569 - isnt_strict(func, desc) should have the proper description +ok 570 - isnt_strict(func, desc) should have the proper diagnostics +ok 571 - is_strict(func) should pass +ok 572 - is_strict(func) should have the proper description +ok 573 - is_strict(func) should have the proper diagnostics +ok 574 - isnt_strict(func) should fail +ok 575 - isnt_strict(func) should have the proper description +ok 576 - isnt_strict(func) should have the proper diagnostics +ok 577 - function_volatility(schema, func, 0 args, volatile, desc) should pass +ok 578 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper description +ok 579 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper diagnostics +ok 580 - function_volatility(schema, func, 0 args, VOLATILE, desc) should pass +ok 581 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper description +ok 582 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper diagnostics +ok 583 - function_volatility(schema, func, 0 args, v, desc) should pass +ok 584 - function_volatility(schema, func, 0 args, v, desc) should have the proper description +ok 585 - function_volatility(schema, func, 0 args, v, desc) should have the proper diagnostics +ok 586 - function_volatility(schema, func, args, immutable, desc) should pass +ok 587 - function_volatility(schema, func, args, immutable, desc) should have the proper description +ok 588 - function_volatility(schema, func, args, immutable, desc) should have the proper diagnostics +ok 589 - function_volatility(schema, func, 0 args, stable, desc) should pass +ok 590 - function_volatility(schema, func, 0 args, stable, desc) should have the proper description +ok 591 - function_volatility(schema, func, 0 args, stable, desc) should have the proper diagnostics +ok 592 - function_volatility(schema, func, 0 args, volatile) should pass +ok 593 - function_volatility(schema, func, 0 args, volatile) should have the proper description +ok 594 - function_volatility(schema, func, 0 args, volatile) should have the proper diagnostics +ok 595 - function_volatility(schema, func, args, immutable) should pass +ok 596 - function_volatility(schema, func, args, immutable) should have the proper description +ok 597 - function_volatility(schema, func, volatile, desc) should pass +ok 598 - function_volatility(schema, func, volatile, desc) should have the proper description +ok 599 - function_volatility(schema, func, volatile, desc) should have the proper diagnostics +ok 600 - function_volatility(schema, func, volatile) should pass +ok 601 - function_volatility(schema, func, volatile) should have the proper description +ok 602 - function_volatility(schema, func, volatile) should have the proper diagnostics +ok 603 - function_volatility(schema, func, immutable, desc) should pass +ok 604 - function_volatility(schema, func, immutable, desc) should have the proper description +ok 605 - function_volatility(schema, func, immutable, desc) should have the proper diagnostics +ok 606 - function_volatility(schema, func, stable, desc) should pass +ok 607 - function_volatility(schema, func, stable, desc) should have the proper description +ok 608 - function_volatility(schema, func, stable, desc) should have the proper diagnostics +ok 609 - function_volatility(func, 0 args, volatile, desc) should pass +ok 610 - function_volatility(func, 0 args, volatile, desc) should have the proper description +ok 611 - function_volatility(func, 0 args, volatile, desc) should have the proper diagnostics +ok 612 - function_volatility(func, 0 args, VOLATILE, desc) should pass +ok 613 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper description +ok 614 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper diagnostics +ok 615 - function_volatility(func, 0 args, v, desc) should pass +ok 616 - function_volatility(func, 0 args, v, desc) should have the proper description +ok 617 - function_volatility(func, 0 args, v, desc) should have the proper diagnostics +ok 618 - function_volatility(func, args, immutable, desc) should pass +ok 619 - function_volatility(func, args, immutable, desc) should have the proper description +ok 620 - function_volatility(func, args, immutable, desc) should have the proper diagnostics +ok 621 - function_volatility(func, 0 args, stable, desc) should pass +ok 622 - function_volatility(func, 0 args, stable, desc) should have the proper description +ok 623 - function_volatility(func, 0 args, stable, desc) should have the proper diagnostics +ok 624 - function_volatility(func, 0 args, volatile) should pass +ok 625 - function_volatility(func, 0 args, volatile) should have the proper description +ok 626 - function_volatility(func, 0 args, volatile) should have the proper diagnostics +ok 627 - function_volatility(func, args, immutable) should pass +ok 628 - function_volatility(func, args, immutable) should have the proper description +ok 629 - function_volatility(func, volatile, desc) should pass +ok 630 - function_volatility(func, volatile, desc) should have the proper description +ok 631 - function_volatility(func, volatile, desc) should have the proper diagnostics +ok 632 - function_volatility(func, volatile) should pass +ok 633 - function_volatility(func, volatile) should have the proper description +ok 634 - function_volatility(func, volatile) should have the proper diagnostics +ok 635 - function_volatility(func, immutable, desc) should pass +ok 636 - function_volatility(func, immutable, desc) should have the proper description +ok 637 - function_volatility(func, immutable, desc) should have the proper diagnostics +ok 638 - function_volatility(func, stable, desc) should pass +ok 639 - function_volatility(func, stable, desc) should have the proper description +ok 640 - function_volatility(func, stable, desc) should have the proper diagnostics +# Looks like you planned 628 tests but ran 640 diff --git a/test/sql/functap.sql b/test/sql/functap.sql index 0a9f01f5f9e3..5aa7cccec01b 100644 --- a/test/sql/functap.sql +++ b/test/sql/functap.sql @@ -138,6 +138,40 @@ SELECT * FROM check_test( '' ); +-- Check a custom function with a complex argument +CREATE TABLE public.complex(a int); +CREATE FUNCTION __cat__(public.complex) RETURNS BOOLEAN +AS 'SELECT TRUE' +LANGUAGE SQL; +SELECT * FROM check_test( + has_function( '__cat__', '{complex}'::name[] ), + true, + 'custom unqualified function with complex unqualified argument', + 'Function __cat__(complex) should exist', + '' +); +SELECT * FROM check_test( + has_function( '__cat__', '{public.complex}'::name[] ), + true, + 'custom unqualified function with complex qualified argument', + 'Function __cat__(public.complex) should exist', + '' +); +SELECT * FROM check_test( + has_function( 'public', '__cat__', '{complex}'::name[] ), + true, + 'custom qualified function with complex unqualified argument', + 'Function public.__cat__(complex) should exist', + '' +); +SELECT * FROM check_test( + has_function( 'public', '__cat__', '{public.complex}'::name[] ), + true, + 'custom qualified function with complex qualified argument', + 'Function public.__cat__(public.complex) should exist', + '' +); + -- Check failure output. SELECT * FROM check_test( has_function( '__cat__', '{varchar[]}'::name[] ), From 256f12f335522a7c1e2e06ee3a90f213bdaeeff0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 10:20:54 -0500 Subject: [PATCH 1030/1195] Simpler fix for qualified function args. Just cast the array to `regtype[]`, same as the `tap_funky` view does. Also, remove references to a "complex" type in the tests, which is a red herring. The issue is schema qualification. So use a simple domain instead of a table. Also, fix the test plan. --- sql/pgtap.sql.in | 14 ++------------ test/expected/functap.out | 27 +++++++++++++-------------- test/sql/functap.sql | 37 +++++++++++++++++++------------------ 3 files changed, 34 insertions(+), 44 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 297dc8998c08..4df69bd4bba5 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2499,12 +2499,7 @@ RETURNS BOOLEAN AS $$ FROM tap_funky WHERE schema = $1 AND name = $2 - AND args = ( - SELECT COALESCE(array_to_string( - array_agg(COALESCE(NULLIF(x, '')::regtype::text,'')), ',' - ), '') - FROM unnest($3) x - ) + AND args = array_to_string($3::regtype[], ',') ); $$ LANGUAGE SQL; @@ -2519,12 +2514,7 @@ RETURNS BOOLEAN AS $$ SELECT TRUE FROM tap_funky WHERE name = $1 - AND args = ( - SELECT COALESCE(array_to_string( - array_agg(COALESCE(NULLIF(x, '')::regtype::text,'')), ',' - ), '') - FROM unnest($2) x - ) + AND args = array_to_string($2::regtype[], ',') AND is_visible ); $$ LANGUAGE SQL; diff --git a/test/expected/functap.out b/test/expected/functap.out index 56efdc21a73b..200c46ec4008 100644 --- a/test/expected/functap.out +++ b/test/expected/functap.out @@ -1,5 +1,5 @@ \unset ECHO -1..628 +1..640 ok 1 - simple function should pass ok 2 - simple function should have the proper description ok 3 - simple function should have the proper diagnostics @@ -39,18 +39,18 @@ ok 36 - custom array function should have the proper diagnostics ok 37 - custom numeric function should pass ok 38 - custom numeric function should have the proper description ok 39 - custom numeric function should have the proper diagnostics -ok 40 - custom unqualified function with complex unqualified argument should pass -ok 41 - custom unqualified function with complex unqualified argument should have the proper description -ok 42 - custom unqualified function with complex unqualified argument should have the proper diagnostics -ok 43 - custom unqualified function with complex qualified argument should pass -ok 44 - custom unqualified function with complex qualified argument should have the proper description -ok 45 - custom unqualified function with complex qualified argument should have the proper diagnostics -ok 46 - custom qualified function with complex unqualified argument should pass -ok 47 - custom qualified function with complex unqualified argument should have the proper description -ok 48 - custom qualified function with complex unqualified argument should have the proper diagnostics -ok 49 - custom qualified function with complex qualified argument should pass -ok 50 - custom qualified function with complex qualified argument should have the proper description -ok 51 - custom qualified function with complex qualified argument should have the proper diagnostics +ok 40 - custom unqualified function with intword unqualified argument should pass +ok 41 - custom unqualified function with intword unqualified argument should have the proper description +ok 42 - custom unqualified function with intword unqualified argument should have the proper diagnostics +ok 43 - custom unqualified function with intword qualified argument should pass +ok 44 - custom unqualified function with intword qualified argument should have the proper description +ok 45 - custom unqualified function with intword qualified argument should have the proper diagnostics +ok 46 - custom qualified function with intword unqualified argument should pass +ok 47 - custom qualified function with intword unqualified argument should have the proper description +ok 48 - custom qualified function with intword unqualified argument should have the proper diagnostics +ok 49 - custom qualified function with intword qualified argument should pass +ok 50 - custom qualified function with intword qualified argument should have the proper description +ok 51 - custom qualified function with intword qualified argument should have the proper diagnostics ok 52 - failure output should fail ok 53 - failure output should have the proper description ok 54 - failure output should have the proper diagnostics @@ -640,4 +640,3 @@ ok 637 - function_volatility(func, immutable, desc) should have the proper diagn ok 638 - function_volatility(func, stable, desc) should pass ok 639 - function_volatility(func, stable, desc) should have the proper description ok 640 - function_volatility(func, stable, desc) should have the proper diagnostics -# Looks like you planned 628 tests but ran 640 diff --git a/test/sql/functap.sql b/test/sql/functap.sql index 5aa7cccec01b..45f23fc67afa 100644 --- a/test/sql/functap.sql +++ b/test/sql/functap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(628); +SELECT plan(640); --SELECT * FROM no_plan(); CREATE SCHEMA someschema; @@ -138,37 +138,38 @@ SELECT * FROM check_test( '' ); --- Check a custom function with a complex argument -CREATE TABLE public.complex(a int); -CREATE FUNCTION __cat__(public.complex) RETURNS BOOLEAN -AS 'SELECT TRUE' -LANGUAGE SQL; +-- Check a custom function with a schema-qualified arugment. +CREATE DOMAIN public.intword AS TEXT CHECK (VALUE IN ('one', 'two', 'three')); +CREATE FUNCTION __cat__(intword) RETURNS BOOLEAN AS 'SELECT TRUE' LANGUAGE SQL; SELECT * FROM check_test( - has_function( '__cat__', '{complex}'::name[] ), + has_function( '__cat__', '{intword}'::name[] ), true, - 'custom unqualified function with complex unqualified argument', - 'Function __cat__(complex) should exist', + 'custom unqualified function with intword unqualified argument', + 'Function __cat__(intword) should exist', '' ); + SELECT * FROM check_test( - has_function( '__cat__', '{public.complex}'::name[] ), + has_function( '__cat__', '{public.intword}'::name[] ), true, - 'custom unqualified function with complex qualified argument', - 'Function __cat__(public.complex) should exist', + 'custom unqualified function with intword qualified argument', + 'Function __cat__(public.intword) should exist', '' ); + SELECT * FROM check_test( - has_function( 'public', '__cat__', '{complex}'::name[] ), + has_function( 'public', '__cat__', '{intword}'::name[] ), true, - 'custom qualified function with complex unqualified argument', - 'Function public.__cat__(complex) should exist', + 'custom qualified function with intword unqualified argument', + 'Function public.__cat__(intword) should exist', '' ); + SELECT * FROM check_test( - has_function( 'public', '__cat__', '{public.complex}'::name[] ), + has_function( 'public', '__cat__', '{public.intword}'::text[] ), true, - 'custom qualified function with complex qualified argument', - 'Function public.__cat__(public.complex) should exist', + 'custom qualified function with intword qualified argument', + 'Function public.__cat__(public.intword) should exist', '' ); From 9c182989a78277504d9ac922f1651e4a04539da7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 10:25:08 -0500 Subject: [PATCH 1031/1195] Add 1.0.0 upgrade script. --- sql/pgtap--0.99.0--1.0.0.sql | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 sql/pgtap--0.99.0--1.0.0.sql diff --git a/sql/pgtap--0.99.0--1.0.0.sql b/sql/pgtap--0.99.0--1.0.0.sql new file mode 100644 index 000000000000..82e3a902c6ff --- /dev/null +++ b/sql/pgtap--0.99.0--1.0.0.sql @@ -0,0 +1,21 @@ +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 = array_to_string($3::regtype[], ',') + ); +$$ 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 = array_to_string($2::regtype[], ',') + AND is_visible + ); +$$ LANGUAGE SQL; From 2888bb8717f94b7f188809fc7449156fd5a1336d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 11:30:42 -0500 Subject: [PATCH 1032/1195] Add _funcargs(). Works back as far as 8.3. --- Changes | 3 +++ compat/install-10.patch | 6 +++--- compat/install-8.3.patch | 8 ++++---- compat/install-8.4.patch | 8 ++++---- compat/install-9.0.patch | 10 +++++----- compat/install-9.1.patch | 2 +- compat/install-9.2.patch | 2 +- compat/install-9.4.patch | 2 +- compat/install-9.6.patch | 2 +- sql/pgtap--0.99.0--1.0.0.sql | 13 +++++++++++-- sql/pgtap.sql.in | 13 +++++++++++-- 11 files changed, 45 insertions(+), 24 deletions(-) diff --git a/Changes b/Changes index a2221835ee50..b60347858e43 100644 --- a/Changes +++ b/Changes @@ -10,6 +10,9 @@ Revision history for pgTAP + `policy_roles_are()` + `policy_cmd_is()` Thanks to Kamen Naydenov for the PR (#185). +* Fixed an issue with `has_function()` where a test would fail if arguments + are schema-qualified even though the schema is in the search path. Thanks to + Sandro Santilli for the bug report (#179) and PR (#180). 0.99.0 2018-09-16T20:55:41Z --------------------------- diff --git a/compat/install-10.patch b/compat/install-10.patch index 94261f7ed007..391705b54947 100644 --- a/compat/install-10.patch +++ b/compat/install-10.patch @@ -9,7 +9,7 @@ p.prosecdef AS is_definer, p.proretset AS returns_set, p.provolatile::char AS volatility, -@@ -5647,7 +5647,7 @@ +@@ -5656,7 +5656,7 @@ CREATE OR REPLACE FUNCTION _agg ( NAME, NAME, NAME[] ) RETURNS BOOLEAN AS $$ @@ -18,7 +18,7 @@ FROM tap_funky WHERE schema = $1 AND name = $2 -@@ -5656,12 +5656,12 @@ +@@ -5665,12 +5665,12 @@ CREATE OR REPLACE FUNCTION _agg ( NAME, NAME ) RETURNS BOOLEAN AS $$ @@ -33,7 +33,7 @@ FROM tap_funky WHERE name = $1 AND args = array_to_string($2, ',') -@@ -5670,7 +5670,7 @@ +@@ -5679,7 +5679,7 @@ CREATE OR REPLACE FUNCTION _agg ( NAME ) RETURNS BOOLEAN AS $$ diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index d98880e67f98..6bb95387107b 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -12,7 +12,7 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT current_setting('server_version_num')::integer; -@@ -6810,7 +6815,7 @@ +@@ -6819,7 +6824,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -21,7 +21,7 @@ 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' || -@@ -6967,7 +6972,7 @@ +@@ -6976,7 +6981,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -30,7 +30,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -7176,13 +7181,7 @@ +@@ -7185,13 +7190,7 @@ $$ LANGUAGE sql; -- collect_tap( tap, tap, tap ) @@ -45,7 +45,7 @@ RETURNS TEXT AS $$ SELECT array_to_string($1, E'\n'); $$ LANGUAGE sql; -@@ -7658,7 +7657,7 @@ +@@ -7667,7 +7666,7 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; diff --git a/compat/install-8.4.patch b/compat/install-8.4.patch index 3df72a391855..ee4ae73fa43a 100644 --- a/compat/install-8.4.patch +++ b/compat/install-8.4.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -7684,7 +7684,6 @@ +@@ -7693,7 +7693,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -8,7 +8,7 @@ EXCEPT SELECT $3[i] FROM generate_series(1, array_upper($3, 1)) s(i) -@@ -7699,7 +7698,6 @@ +@@ -7708,7 +7707,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 @@ -16,7 +16,7 @@ ), $4 ); -@@ -7723,7 +7721,6 @@ +@@ -7732,7 +7730,6 @@ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = $1 AND n.nspname NOT IN ('pg_catalog', 'information_schema') @@ -24,7 +24,7 @@ EXCEPT SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) -@@ -7737,7 +7734,6 @@ +@@ -7746,7 +7743,6 @@ 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') diff --git a/compat/install-9.0.patch b/compat/install-9.0.patch index ec8864fea13b..57cc71ad97b9 100644 --- a/compat/install-9.0.patch +++ b/compat/install-9.0.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -3689,7 +3689,7 @@ +@@ -3698,7 +3698,7 @@ AND n.nspname = $1 AND t.typname = $2 AND t.typtype = 'e' @@ -9,7 +9,7 @@ ), $3, $4 -@@ -3717,7 +3717,7 @@ +@@ -3726,7 +3726,7 @@ AND pg_catalog.pg_type_is_visible(t.oid) AND t.typname = $1 AND t.typtype = 'e' @@ -18,7 +18,7 @@ ), $2, $3 -@@ -6161,7 +6161,7 @@ +@@ -6170,7 +6170,7 @@ CREATE OR REPLACE FUNCTION findfuncs( NAME, TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( @@ -27,7 +27,7 @@ FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = $1 -@@ -6178,7 +6178,7 @@ +@@ -6187,7 +6187,7 @@ CREATE OR REPLACE FUNCTION findfuncs( TEXT, TEXT ) RETURNS TEXT[] AS $$ SELECT ARRAY( @@ -36,7 +36,7 @@ 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) -@@ -9680,137 +9680,6 @@ +@@ -9689,137 +9689,6 @@ GRANT SELECT ON tap_funky TO PUBLIC; GRANT SELECT ON pg_all_foreign_keys TO PUBLIC; diff --git a/compat/install-9.1.patch b/compat/install-9.1.patch index 14bc2d81fd52..0047e2a655bb 100644 --- a/compat/install-9.1.patch +++ b/compat/install-9.1.patch @@ -11,7 +11,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6365,10 +6361,6 @@ +@@ -6374,10 +6370,6 @@ -- Something went wrong. Record that fact. errstate := SQLSTATE; errmsg := SQLERRM; diff --git a/compat/install-9.2.patch b/compat/install-9.2.patch index bbf5fecb1e6a..c71bf65a607a 100644 --- a/compat/install-9.2.patch +++ b/compat/install-9.2.patch @@ -14,7 +14,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6373,12 +6388,7 @@ +@@ -6382,12 +6397,7 @@ GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, hint = PG_EXCEPTION_HINT, diff --git a/compat/install-9.4.patch b/compat/install-9.4.patch index 6adfc492a209..728e1d64ef3b 100644 --- a/compat/install-9.4.patch +++ b/compat/install-9.4.patch @@ -18,7 +18,7 @@ -- There should have been no exception. GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, -@@ -9905,231 +9905,4 @@ +@@ -9914,231 +9914,4 @@ FROM generate_series(1, array_upper($1, 1)) s(i) ORDER BY $1[i] ), $2); diff --git a/compat/install-9.6.patch b/compat/install-9.6.patch index 70ff4f95b9a2..3ce5b460bc99 100644 --- a/compat/install-9.6.patch +++ b/compat/install-9.6.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -9889,136 +9889,6 @@ +@@ -9898,136 +9898,6 @@ ); $$ LANGUAGE sql; diff --git a/sql/pgtap--0.99.0--1.0.0.sql b/sql/pgtap--0.99.0--1.0.0.sql index 82e3a902c6ff..84ece256a313 100644 --- a/sql/pgtap--0.99.0--1.0.0.sql +++ b/sql/pgtap--0.99.0--1.0.0.sql @@ -1,3 +1,12 @@ +CREATE OR REPLACE FUNCTION _funkargs ( TEXT[] ) +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( @@ -5,7 +14,7 @@ RETURNS BOOLEAN AS $$ FROM tap_funky WHERE schema = $1 AND name = $2 - AND args = array_to_string($3::regtype[], ',') + AND args = _funkargs($3) ); $$ LANGUAGE SQL; @@ -15,7 +24,7 @@ RETURNS BOOLEAN AS $$ SELECT TRUE FROM tap_funky WHERE name = $1 - AND args = array_to_string($2::regtype[], ',') + AND args = _funkargs($2) AND is_visible ); $$ LANGUAGE SQL; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 4df69bd4bba5..2f2b470d4561 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2492,6 +2492,15 @@ CREATE OR REPLACE VIEW tap_funky JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid ; +CREATE OR REPLACE FUNCTION _funkargs ( TEXT[] ) +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( @@ -2499,7 +2508,7 @@ RETURNS BOOLEAN AS $$ FROM tap_funky WHERE schema = $1 AND name = $2 - AND args = array_to_string($3::regtype[], ',') + AND args = _funkargs($3) ); $$ LANGUAGE SQL; @@ -2514,7 +2523,7 @@ RETURNS BOOLEAN AS $$ SELECT TRUE FROM tap_funky WHERE name = $1 - AND args = array_to_string($2::regtype[], ',') + AND args = _funkargs($2) AND is_visible ); $$ LANGUAGE SQL; From 80ae103759838386edfd0455183f24aa1d117cb8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 11:49:52 -0500 Subject: [PATCH 1033/1195] Get arg checking working on 8.2. --- compat/install-8.2.patch | 72 +++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 1191c6f19191..09469137d2d8 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,6 +1,6 @@ ---- sql/pgtap.sql -+++ sql/pgtap.sql -@@ -5,6 +5,59 @@ +--- sql/pgtap.sql 2018-11-20 11:45:47.000000000 -0500 ++++ sql/pgtap.sql.saf 2018-11-20 11:45:32.000000000 -0500 +@@ -5,6 +5,71 @@ -- -- http://pgtap.org/ @@ -31,6 +31,18 @@ +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[], @@ -60,7 +72,7 @@ CREATE OR REPLACE FUNCTION pg_version() RETURNS text AS 'SELECT current_setting(''server_version'')' LANGUAGE SQL IMMUTABLE; -@@ -16,7 +69,12 @@ +@@ -16,7 +81,12 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ @@ -74,7 +86,7 @@ $$ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION os_name() -@@ -245,21 +303,6 @@ +@@ -245,21 +315,6 @@ ); $$ LANGUAGE sql strict; @@ -96,7 +108,7 @@ CREATE OR REPLACE FUNCTION ok ( boolean, text ) RETURNS TEXT AS $$ DECLARE -@@ -472,9 +515,9 @@ +@@ -472,9 +527,9 @@ output TEXT; BEGIN EXECUTE 'SELECT ' || @@ -108,7 +120,7 @@ INTO result; output := ok( COALESCE(result, FALSE), descr ); RETURN output || CASE result WHEN TRUE THEN '' ELSE E'\n' || diag( -@@ -758,7 +801,7 @@ +@@ -758,7 +813,7 @@ || COALESCE(E'\n TYPE: ' || nullif($10, ''), '') -- We need to manually indent all the context lines || COALESCE(E'\n CONTEXT:\n' @@ -117,7 +129,7 @@ ), ''); $$ LANGUAGE sql IMMUTABLE; -@@ -2476,7 +2519,7 @@ +@@ -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 @@ -126,7 +138,7 @@ p.prolang AS langoid, p.proisstrict AS is_strict, p.proisagg AS is_agg, -@@ -3681,63 +3724,6 @@ +@@ -3690,63 +3745,6 @@ SELECT ok( NOT _has_type( $1, ARRAY['e'] ), ('Enum ' || quote_ident($1) || ' should not exist')::text ); $$ LANGUAGE sql; @@ -190,7 +202,7 @@ CREATE OR REPLACE FUNCTION _has_role( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( -@@ -6328,17 +6314,17 @@ +@@ -6337,17 +6335,17 @@ BEGIN -- Run the setup functions. FOR tap IN SELECT * FROM _runem(setup, false) LOOP @@ -211,7 +223,7 @@ END LOOP; -- Emit the plan. -@@ -6377,7 +6363,7 @@ +@@ -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 @@ -220,7 +232,7 @@ errmsg := NULL; END IF; END; -@@ -6490,13 +6476,13 @@ +@@ -6499,13 +6497,13 @@ -- Find extra records. FOR rec in EXECUTE 'SELECT * FROM ' || have || ' EXCEPT ' || $4 || 'SELECT * FROM ' || want LOOP @@ -236,7 +248,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6720,7 +6706,7 @@ +@@ -6729,7 +6727,7 @@ -- Find relevant records. FOR rec in EXECUTE 'SELECT * FROM ' || want || ' ' || $4 || ' SELECT * FROM ' || have LOOP @@ -245,7 +257,7 @@ END LOOP; -- Drop the temporary tables. -@@ -6815,11 +6801,11 @@ +@@ -6824,11 +6822,11 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -260,7 +272,7 @@ ); END IF; rownum = rownum + 1; -@@ -6834,9 +6820,9 @@ +@@ -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' || @@ -273,7 +285,7 @@ END ); END; -@@ -6972,7 +6958,7 @@ +@@ -6981,7 +6979,7 @@ FETCH want INTO want_rec; want_found := FOUND; WHILE have_found OR want_found LOOP @@ -282,7 +294,7 @@ RETURN ok( true, $3 ); ELSE FETCH have INTO have_rec; -@@ -6986,8 +6972,8 @@ +@@ -6995,8 +6993,8 @@ WHEN datatype_mismatch THEN RETURN ok( false, $3 ) || E'\n' || diag( E' Columns differ between queries:\n' || @@ -293,7 +305,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7112,9 +7098,9 @@ +@@ -7121,9 +7119,9 @@ DECLARE typeof regtype := pg_typeof($1); BEGIN @@ -306,7 +318,7 @@ END; $$ LANGUAGE plpgsql; -@@ -7135,7 +7121,7 @@ +@@ -7144,7 +7142,7 @@ BEGIN -- Find extra records. FOR rec in EXECUTE _query($1) LOOP @@ -315,7 +327,7 @@ END LOOP; -- What extra records do we have? -@@ -7303,7 +7289,7 @@ +@@ -7312,7 +7310,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -324,7 +336,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT -@@ -7321,7 +7307,7 @@ +@@ -7330,7 +7328,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -333,7 +345,7 @@ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) ), -@@ -7354,7 +7340,7 @@ +@@ -7363,7 +7361,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -342,7 +354,7 @@ 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']) ) -@@ -7373,7 +7359,7 @@ +@@ -7382,7 +7380,7 @@ t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) ) @@ -351,7 +363,7 @@ 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']) ) -@@ -7657,10 +7643,12 @@ +@@ -7666,10 +7664,12 @@ rec RECORD; BEGIN EXECUTE _query($1) INTO rec; @@ -367,7 +379,7 @@ ); END; $$ LANGUAGE plpgsql; -@@ -7807,7 +7795,7 @@ +@@ -7816,7 +7816,7 @@ CREATE OR REPLACE FUNCTION display_oper ( NAME, OID ) RETURNS TEXT AS $$ @@ -376,7 +388,7 @@ $$ LANGUAGE SQL; -- operators_are( schema, operators[], description ) -@@ -7816,7 +7804,7 @@ +@@ -7825,7 +7825,7 @@ SELECT _areni( 'operators', ARRAY( @@ -385,7 +397,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7828,7 +7816,7 @@ +@@ -7837,7 +7837,7 @@ SELECT $2[i] FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT @@ -394,7 +406,7 @@ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON o.oprnamespace = n.oid WHERE n.nspname = $1 -@@ -7849,7 +7837,7 @@ +@@ -7858,7 +7858,7 @@ SELECT _areni( 'operators', ARRAY( @@ -403,7 +415,7 @@ 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) -@@ -7862,7 +7850,7 @@ +@@ -7871,7 +7871,7 @@ SELECT $1[i] FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT @@ -412,7 +424,7 @@ 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) -@@ -8575,40 +8563,6 @@ +@@ -8584,40 +8584,6 @@ ); $$ LANGUAGE sql; From 947fa2fbf9c86b2e2f694f46bd026494cfbc0961 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 11:56:52 -0500 Subject: [PATCH 1034/1195] Update patch for 8.1. --- compat/install-8.1.patch | 18 +++++++++--------- compat/install-8.2.patch | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch index 8603e218c3ae..911994f98607 100644 --- a/compat/install-8.1.patch +++ b/compat/install-8.1.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -2266,13 +2266,13 @@ +@@ -2278,13 +2278,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -18,7 +18,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $6 || ' constraints'; -@@ -2290,13 +2290,13 @@ +@@ -2302,13 +2302,13 @@ CREATE OR REPLACE FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ) RETURNS TEXT AS $$ DECLARE @@ -36,7 +36,7 @@ END LOOP; IF array_upper(keys, 0) = 1 THEN have := 'No ' || $5 || ' constraints'; -@@ -6186,7 +6186,7 @@ +@@ -6207,7 +6207,7 @@ CREATE OR REPLACE FUNCTION _runem( text[], boolean ) RETURNS SETOF TEXT AS $$ DECLARE @@ -45,7 +45,7 @@ lbound int := array_lower($1, 1); BEGIN IF lbound IS NULL THEN RETURN; END IF; -@@ -6194,8 +6194,8 @@ +@@ -6215,8 +6215,8 @@ -- 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. @@ -56,7 +56,7 @@ END LOOP; END LOOP; RETURN; -@@ -6264,7 +6264,7 @@ +@@ -6285,7 +6285,7 @@ setup ALIAS FOR $3; teardown ALIAS FOR $4; tests ALIAS FOR $5; @@ -65,7 +65,7 @@ tfaild INTEGER := 0; ffaild INTEGER := 0; tnumb INTEGER := 0; -@@ -6274,7 +6274,7 @@ +@@ -6295,7 +6295,7 @@ BEGIN -- No plan support. PERFORM * FROM no_plan(); @@ -74,7 +74,7 @@ EXCEPTION -- Catch all exceptions and simply rethrow custom exceptions. This -- will roll back everything in the above block. -@@ -6313,18 +6313,18 @@ +@@ -6334,18 +6334,18 @@ BEGIN BEGIN -- Run the setup functions. @@ -99,7 +99,7 @@ END LOOP; -- Emit the plan. -@@ -6380,11 +6380,11 @@ +@@ -6401,11 +6401,11 @@ END LOOP; -- Run the shutdown functions. @@ -114,7 +114,7 @@ END LOOP; -- Clean up and return. -@@ -7642,7 +7642,7 @@ +@@ -7663,7 +7663,7 @@ DECLARE rec RECORD; BEGIN diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch index 09469137d2d8..98b17cc89c1a 100644 --- a/compat/install-8.2.patch +++ b/compat/install-8.2.patch @@ -1,5 +1,5 @@ ---- sql/pgtap.sql 2018-11-20 11:45:47.000000000 -0500 -+++ sql/pgtap.sql.saf 2018-11-20 11:45:32.000000000 -0500 +--- sql/pgtap.sql ++++ sql/pgtap.sql @@ -5,6 +5,71 @@ -- -- http://pgtap.org/ From 417863299954bbd1b311c890ccef7fa4aad3498b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 12:03:25 -0500 Subject: [PATCH 1035/1195] Use name[] until 8.3. --- compat/install-8.3.patch | 9 +++++++++ sql/pgtap--0.99.0--1.0.0.sql | 2 +- sql/pgtap.sql.in | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index 6bb95387107b..757cd4fa0a3f 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -12,6 +12,15 @@ CREATE OR REPLACE FUNCTION pg_version_num() RETURNS integer AS $$ SELECT current_setting('server_version_num')::integer; +@@ -2483,7 +2488,7 @@ + JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid + ; + +-CREATE OR REPLACE FUNCTION _funkargs ( NAME[] ) ++CREATE OR REPLACE FUNCTION _funkargs ( TEXT[] ) + RETURNS TEXT AS $$ + BEGIN + RETURN array_to_string($1::regtype[], ','); @@ -6819,7 +6824,7 @@ FETCH want INTO want_rec; want_found := FOUND; diff --git a/sql/pgtap--0.99.0--1.0.0.sql b/sql/pgtap--0.99.0--1.0.0.sql index 84ece256a313..406da0ef18cb 100644 --- a/sql/pgtap--0.99.0--1.0.0.sql +++ b/sql/pgtap--0.99.0--1.0.0.sql @@ -1,4 +1,4 @@ -CREATE OR REPLACE FUNCTION _funkargs ( TEXT[] ) +CREATE OR REPLACE FUNCTION _funkargs ( NAME[] ) RETURNS TEXT AS $$ BEGIN RETURN array_to_string($1::regtype[], ','); diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 2f2b470d4561..2d2d8c46d3f6 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2492,7 +2492,7 @@ CREATE OR REPLACE VIEW tap_funky JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid ; -CREATE OR REPLACE FUNCTION _funkargs ( TEXT[] ) +CREATE OR REPLACE FUNCTION _funkargs ( NAME[] ) RETURNS TEXT AS $$ BEGIN RETURN array_to_string($1::regtype[], ','); From 453af7db93bf08c3cefdaffa27929ed9762cdc93 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 12:07:33 -0500 Subject: [PATCH 1036/1195] Add policy test functions to upgrade script. --- sql/pgtap--0.99.0--1.0.0.sql | 236 +++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) diff --git a/sql/pgtap--0.99.0--1.0.0.sql b/sql/pgtap--0.99.0--1.0.0.sql index 406da0ef18cb..6305b021f017 100644 --- a/sql/pgtap--0.99.0--1.0.0.sql +++ b/sql/pgtap--0.99.0--1.0.0.sql @@ -1,3 +1,239 @@ +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; + CREATE OR REPLACE FUNCTION _funkargs ( NAME[] ) RETURNS TEXT AS $$ BEGIN From d111472da7971f60fe8893882b444ca77062bd6d Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Mon, 1 Oct 2018 15:08:29 +0200 Subject: [PATCH 1037/1195] First implementation of tests for inheritance. Adds two families of functions: - has_inherited_tables - hasnt_inherited_tables Both accept a table name and ensures the table has (or hasn't) a inherited chain (i.e., pg_class.relhassubclass is true). See discussion and ticket . --- sql/pgtap.sql.in | 105 +++++++++++++++++++++++++++++++++++++++ test/sql/inheritance.sql | 52 +++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 test/sql/inheritance.sql diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 2d2d8c46d3f6..68873e604877 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1095,6 +1095,111 @@ RETURNS TEXT AS $$ SELECT hasnt_view( $1, 'View ' || quote_ident($1) || ' should not exist' ); $$ LANGUAGE SQL; + + +/******************** INHERITANCE ***********************************************/ + + +/* + * Internal function to test if the specified table in the + * specified schema does have 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 if a specific table + * in the search_path has a 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, -- schema + $2 ), -- table + $3 ); -- error message +$$ LANGUAGE SQL; + +-- has_inherited_tables( schema, table ) +CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT has_inherited_tables( $1, -- schema + $2, -- table + format( 'Table(s) inherit from table %I.%I', $1, $2 ) ); +$$ LANGUAGE SQL; + + +-- has_inherited_tables( table, description ) +CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _inherited( $1 ), -- table + $2 ); -- description +$$ LANGUAGE SQL; + + +-- has_inherited_tables( table ) +CREATE OR REPLACE FUNCTION has_inherited_tables( NAME ) +RETURNS TEXT AS $$ + SELECT has_inherited_tables( $1::NAME, -- table + format( 'Table(s) inherit from table %I', $1 ) ); +$$ 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 ), + format( 'Table %I.%I cannot have inherited tables', $1, $2 ) ); +$$ 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 ), + format( 'Table %I cannot have inherited tables', $1 ) ); +$$ LANGUAGE SQL; + + +/**************************************************************************/ + -- has_sequence( schema, sequence, description ) CREATE OR REPLACE FUNCTION has_sequence ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ diff --git a/test/sql/inheritance.sql b/test/sql/inheritance.sql new file mode 100644 index 000000000000..bc7026e93c70 --- /dev/null +++ b/test/sql/inheritance.sql @@ -0,0 +1,52 @@ +\unset ECHO +--\i test/setup.sql + +SELECT plan( 4 ); + +-- Create inherited tables +CREATE TABLE public.parent( id INT PRIMARY KEY ); +CREATE TABLE public.child( id INT PRIMARY KEY ) INHERITS ( public.parent ); + +-- Create inherited tables in another schema +CREATE SCHEMA hide; +CREATE TABLE hide.h_parent( id INT PRIMARY KEY ); +CREATE TABLE hide.h_child( id INT PRIMARY KEY ) INHERITS ( hide.h_parent ); + + +-- test has_inhereted_tables +SELECT * FROM check_test( + has_inherited_tables( 'hide'::name, 'h_parent'::name ) + , true -- expected value + , 'hide.h_parent is supposed to be parent of other tables' + ); + +-- test hasnt_inherited_tables +SELECT * FROM check_test( + hasnt_inherited_tables( 'hide'::name, 'h_child'::name ) + , true -- expected value + , 'hide.h_child is supposed not to have children' +); + + +-- test has_inhereted_tables +SELECT * FROM check_test( + has_inherited_tables( 'parent'::name ) + , true -- expected value + , 'public.parent is supposed to be parent of other tables' +); + +-- test hasnt_inherited_tables +SELECT * FROM check_test( + hasnt_inherited_tables( 'child'::name ) + , true -- expected value + , 'public.child is supposed not to have children' +); + + + + + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From 6dccf1ad4df5a172b1f77f75a1d7920a26314bf5 Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Mon, 1 Oct 2018 16:00:45 +0200 Subject: [PATCH 1038/1195] Implementation of the 'is_parent_of' and 'is_child_of' inheritance tests. The idea is to specify a table parent of another. The check is performed using the pg_inherits system catalog. The 'is_parent_of' group of functions use the first table as the parent one and the second as the child one, the 'is_child_of' group of functions do the opposite. So for instance: is_parent_of( 'my_parent_table', 'my_child_table' ); is_child_of( 'my_child_table', 'my_parent_table' ); are the same. Negated functions (e.g., 'isnt_child_of') exist. Documentation and internal tests updated. --- doc/pgtap.mmd | 152 +++++++++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 160 +++++++++++++++++++++++++++++++++++++++ test/sql/inheritance.sql | 68 ++++++++++++++++- 3 files changed, 379 insertions(+), 1 deletion(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 37a0ba813d6a..3e9a2db23f9e 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2927,6 +2927,158 @@ exist". This function is the inverse of `has_view()`. The test passes if the specified materialized view does *not* exist. +### `has_inherited_tables()` + + SELECT has_inherited_tables( :schema, :table, :description ); + SELECT has_inherited_tables( :schema, :table ); + SELECT has_inherited_tables( :table, :description ); + SELECT has_inherited_tables( :table ); + +**Parameters** + +`:schema` +: Name of a schema in which to search for the table that has children. + +`:table` +: Name of the table that must have children. + +`:description +: A description of the test. + +This function checks that the specified table has at least one other table that +inherits from it. + +### `hasnt_inherited_tables()` + + SELECT hasnt_inherited_tables( :schema, :table, :description ); + SELECT hasnt_inherited_tables( :schema, :table ); + SELECT hasnt_inherited_tables( :table, :description ); + SELECT hasnt_inherited_tables( :table ); + +**Parameters** + +`:schema` +: Name of a schema in which to search for the table that must not have children. + +`:table` +: Name of the table that must not have children. + +`:description +: A description of the test. + +This function checks that the specified table has no tables inheriting from it. +It is the opposite of the function `has_inherited_tables()`. + +### `is_parent_of()` + + SELECT is_parent_of( :parent_schema, :parent_table, :child_schema, :child_table, :description ); + SELECT is_parent_of( :parent_schema, :parent_table, :child_schema, :child_table ); + SELECT is_parent_of( :parent_table, :child_table, :description ); + SELECT is_parent_of( :parent_table, :child_table ); + +**Parameters** + +`:parent_schema` +: Name of the schema in which the parent table must be found. + +`:parent_table` +: Name of the table that plays the parent role. + +`:child_schema` +: Name of the schema in which the child table must be. + +`:child_table` +: Name of the table that is supposed to be child of the other one. + +`:description` +: Description of the test. + +This function checks if the table marked as "parent" is effectively a table from which the "child" table +inherits from, that is there is an inheritance chain between the two tables. + + +### `isnt_parent_of()` + + SELECT isnt_parent_of( :parent_schema, :parent_table, :child_schema, :child_table, :description ); + SELECT isnt_parent_of( :parent_schema, :parent_table, :child_schema, :child_table ); + SELECT isnt_parent_of( :parent_table, :child_table, :description ); + SELECT isnt_parent_of( :parent_table, :child_table ); + +**Parameters** + +`:parent_schema` +: Name of the schema in which the parent table must be found. + +`:parent_table` +: Name of the table that plays the parent role. + +`:child_schema` +: Name of the schema in which the child table must be. + +`:child_table` +: Name of the table that is supposed to be child of the other one. + +`:description` +: Description of the test. + +This function ensures that the table marked as "parent" is not a table from which "child" inherits from, that +is there is not an inheritance chain between the two tables. +The function does the opposite of `is_parent_of()`. + + +### `is_child_of()` + + SELECT is_child_of( :child_schema, :child_table, :parent_schema, :parent_table, :description ); + SELECT is_child_of( :child_schema, :child_table, :parent_schema, :parent_table ); + SELECT is_child_of( :child_table, :parent_table, :description ); + SELECT is_child_of( :child_table, :parent_table ); + +**Parameters** + +`:parent_schema` +: Name of the schema in which the parent table must be found. + +`:parent_table` +: Name of the table that plays the parent role. + +`:child_schema` +: Name of the schema in which the child table must be. + +`:child_table` +: Name of the table that is supposed to be child of the other one. + +`:description` +: Description of the test. + +This function does the same of `is_parent_of()`, with swapped arguments. + + +### `isnt_child_of()` + + SELECT isnt_child_of( :child_schema, :child_table, :parent_schema, :parent_table, :description ); + SELECT isnt_child_of( :child_schema, :child_table, :parent_schema, :parent_table ); + SELECT isnt_child_of( :child_table, :parent_table, :description ); + SELECT isnt_child_of( :child_table, :parent_table ); + +**Parameters** + +`:parent_schema` +: Name of the schema in which the parent table must be found. + +`:parent_table` +: Name of the table that plays the parent role. + +`:child_schema` +: Name of the schema in which the child table must be. + +`:child_table` +: Name of the table that is supposed to be child of the other one. + +`:description` +: Description of the test. + +This function does the same of `isnt_parent_of()`, with swapped arguments. + ### `has_sequence()` ### SELECT has_sequence( :schema, :sequence, :description ); diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 68873e604877..6828b870a207 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1198,6 +1198,166 @@ RETURNS TEXT AS $$ $$ LANGUAGE SQL; + + +/* +* Internal function to test if the qualified table is aprent of +* the other qualified table. +*/ +CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_inherits i + 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 ) + AND i.inhparent = ( SELECT c2.oid + FROM pg_catalog.pg_class c2 + JOIN pg_catalog.pg_namespace n2 + ON c2.relnamespace = n2.oid + WHERE c2.relname = $2 + AND n2.nspname = $1 ) + ); + +$$ LANGUAGE SQL; + + +/* + * Internal function to check if not-qualified tables + * within the search_path are connected by an inheritance chain. + */ +CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT EXISTS( + SELECT true + FROM pg_catalog.pg_inherits i + WHERE i.inhrelid = ( SELECT c1.oid + FROM pg_catalog.pg_class c1 + WHERE c1.relname = $2 + AND pg_catalog.pg_table_is_visible( c1.oid ) ) + AND i.inhparent = ( SELECT c2.oid + FROM pg_catalog.pg_class c2 + WHERE c2.relname = $1 + AND pg_catalog.pg_table_is_visible( c2.oid ) ) + ); +$$ LANGUAGE SQL; + + + + + +-- is_parent_of( schema, table, schema, table, description ) +CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _parent_of( $1, $2, $3, $4 ), + $5 ); +$$ LANGUAGE SQL; + +-- is_child_of( schema, table, schema, table, description ) +CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT is_parent_of( $3, $4, $1, $2, $5 ); +$$ LANGUAGE SQL; + +-- is_parent_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT is_parent_of( $1, $2, + $3, $4, + format( 'Table %I.%I is parent of %I.%I', $1, $2, $3, $4 ) ); +$$ LANGUAGE SQL; + +-- is_child_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT is_parent_of( $3, $4, $1, $2 ); +$$ LANGUAGE SQL; + +-- is_parent_of( table, table, description ) +CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _parent_of( $1, $2 ), $3 ); +$$ LANGUAGE SQL; + +-- is_child_of( table, table, description ) +CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT is_parent_of( $2, $1, $3 ); +$$ LANGUAGE SQL; + +-- is_parent_of( table, table ) +CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( _parent_of( $1, $2 ), + format( 'Table %I is parent of %I', $1, $2 ) ); +$$ LANGUAGE SQL; + +-- is_child_of( table, table ) +CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT is_parent_of( $2, $1 ); +$$ LANGUAGE SQL; + + +-- isnt_parent_of( schema, table, schema, table, description ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _parent_of( $1, $2, $3, $4 ), + $5 ); +$$ LANGUAGE SQL; + +-- isnt_child_of( schema, table, schema, table, description ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT isnt_parent_of( $3, $4, $1, $2, $5 ); +$$ LANGUAGE SQL; + +-- isnt_parent_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT isnt_parent_of( $1, $2, + $3, $4, + format( 'Table %I.%I is not parent of %I.%I', $1, $2, $3, $4 ) ); +$$LANGUAGE SQL; + + +-- isnt_child_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT isnt_parent_of( $3, $4, $1, $2 ); +$$LANGUAGE SQL; + + +-- isnt_parent_of( table, table, description ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _parent_of( $1, $2 ), $3 ); +$$ LANGUAGE SQL; + + +-- isnt_child_of( table, table, description ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, TEXT ) +RETURNS TEXT AS $$ + SELECT isnt_parent_of( $2, $1, $3 ); +$$ LANGUAGE SQL; + +-- isnt_parent_of( table, table ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( NOT _parent_of( $1, $2 ), + format( 'Table %I is not parent of %I', $1, $2 ) ); +$$ LANGUAGE SQL; + +-- isnt_child_of( table, table ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT isnt_parent_of( $2, $1 ); +$$ LANGUAGE SQL; + /**************************************************************************/ -- has_sequence( schema, sequence, description ) diff --git a/test/sql/inheritance.sql b/test/sql/inheritance.sql index bc7026e93c70..68a4a1861aa9 100644 --- a/test/sql/inheritance.sql +++ b/test/sql/inheritance.sql @@ -1,7 +1,7 @@ \unset ECHO --\i test/setup.sql -SELECT plan( 4 ); +SELECT plan( 14 ); -- Create inherited tables CREATE TABLE public.parent( id INT PRIMARY KEY ); @@ -45,6 +45,72 @@ SELECT * FROM check_test( +SELECT * FROM check_test( + is_parent_of( 'hide', 'h_parent', 'hide', 'h_child' ) + , true -- expected value + , 'hide.h_parent is father of hide.h_child' +); + +SELECT * FROM check_test( + is_parent_of( 'parent', 'child' ) + , true -- expected value + , 'child inherits from parent' +); + + +SELECT * FROM check_test( + is_parent_of( 'hide', 'h_parent', 'public', 'child' ) + , false -- expected value + , 'hide.h_parent is not father of public.child' +); + +SELECT * FROM check_test( + is_parent_of( 'public', 'parent', 'public', 'child' ) + , true -- expected value + , 'public.parent is not father of public.child' +); + + + +SELECT * FROM check_test( + isnt_child_of( 'hide', 'h_parent', 'public', 'child' ) + , true -- expected value + , 'hide.h_parent is not father of public.child' +); + +SELECT * FROM check_test( + isnt_parent_of( 'parent', 'child' ) + , false -- expected value + , 'parent is not father of public.child' +); + + +SELECT * FROM check_test( + isnt_child_of( 'parent', 'child' ) + , true -- expected value + , 'parent is not child' +); + +SELECT * FROM check_test( + isnt_child_of( 'child', 'parent' ) + , false -- expected value + , 'child inherits from parent' +); + + +SELECT * FROM check_test( + isnt_child_of( 'hide', 'h_parent', 'hide', 'h_child' ) + , true -- expected value + , 'hide.h_parent is not child of hide.h_child' +); + +SELECT * FROM check_test( + isnt_child_of( 'hide', 'h_child', 'hide', 'h_parent' ) + , false -- expected value + , 'hide.h_child inherits from hide.h_parent' +); + + /****************************************************************************/ -- Finish the tests and clean up. From d6274e78711921ebda420f52044127956892eed1 Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Mon, 1 Oct 2018 18:06:38 +0200 Subject: [PATCH 1039/1195] Refactoring of '_parent_of' function to accept a inheritance depth. The idea is to test recursively against the catalog pg_inherits to check for an inheritance chain between two tables. For a direct relationship, the depth is 1 (default value), for deeper inheritance chains it must be specified. Since this uses a recursive CTE, it could have some problems in porting to old PostgreSQL versions. --- sql/pgtap.sql.in | 195 ++++++++++++++++++++++++++++----------- test/sql/inheritance.sql | 68 ++++++++------ 2 files changed, 184 insertions(+), 79 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 6828b870a207..c543deb52cc2 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1202,27 +1202,59 @@ $$ LANGUAGE SQL; /* * Internal function to test if the qualified table is aprent of -* the other qualified table. +* the other qualified table. The integer value is the +* length of the inheritance chain: a direct child has a 1 value. */ -CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, NAME, NAME ) +CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, NAME, NAME, INT DEFAULT 1 ) RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_inherits i + WITH RECURSIVE inheritance_chain AS ( + -- select the parent tuple + SELECT i.inhrelid AS child_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 childrens + SELECT i.inhrelid AS child_id + , p.inheritance_level + 1 AS inheritance_level + FROM pg_catalog.pg_inherits i + JOIN inheritance_chain p + ON p.child_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 ) - AND i.inhparent = ( SELECT c2.oid - FROM pg_catalog.pg_class c2 - JOIN pg_catalog.pg_namespace n2 - ON c2.relnamespace = n2.oid - WHERE c2.relname = $2 - AND n2.nspname = $1 ) - ); + ) + SELECT EXISTS( SELECT * + FROM inheritance_chain + WHERE inheritance_level = $5 + AND child_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 STRICT; +/* + * Internal function to test for a direct child (i.e., inheritance chain + * length of 1). + */ +CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT _parent_of( $1, $2, $3, $4, 1 ); $$ LANGUAGE SQL; @@ -1230,37 +1262,85 @@ $$ LANGUAGE SQL; * Internal function to check if not-qualified tables * within the search_path are connected by an inheritance chain. */ -CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME ) +CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, INT DEFAULT 1 ) RETURNS BOOLEAN AS $$ - SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_inherits i - WHERE i.inhrelid = ( SELECT c1.oid + WITH RECURSIVE inheritance_chain AS ( + -- select the parent tuple + SELECT i.inhrelid AS child_id + , 1 AS inheritance_level + FROM pg_catalog.pg_inherits i + WHERE i.inhparent = ( SELECT c1.oid FROM pg_catalog.pg_class c1 - WHERE c1.relname = $2 - AND pg_catalog.pg_table_is_visible( c1.oid ) ) - AND i.inhparent = ( SELECT c2.oid - FROM pg_catalog.pg_class c2 - WHERE c2.relname = $1 - AND pg_catalog.pg_table_is_visible( c2.oid ) ) - ); + WHERE c1.relname = $1 + AND pg_catalog.pg_table_is_visible( c1.oid ) ) + + + UNION + + -- select the childrens + SELECT i.inhrelid AS child_id + , p.inheritance_level + 1 AS inheritance_level + FROM pg_catalog.pg_inherits i + JOIN inheritance_chain p + ON p.child_id = i.inhparent + WHERE i.inhrelid = ( SELECT c1.oid + FROM pg_catalog.pg_class c1 + WHERE c1.relname = $1 + AND pg_catalog.pg_table_is_visible( c1.oid ) ) + ) + SELECT EXISTS( SELECT * + FROM inheritance_chain + WHERE inheritance_level = $3 + AND child_id = ( SELECT c1.oid + FROM pg_catalog.pg_class c1 + WHERE c1.relname = $2 + AND pg_catalog.pg_table_is_visible( c1.oid ) ) + ); + + $$ LANGUAGE SQL; +-- is_parent_of( schema, table, schema, table, depth, description ) +CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _parent_of( $1, $2, $3, $4, $5 ), + $6 ); +$$ LANGUAGE SQL; + + +-- is_parent_of( schema, table, schema, table, depth ) +CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME, INT ) +RETURNS TEXT AS $$ + SELECT ok( _parent_of( $1, $2, $3, $4, $5 ), + format( 'Table %I.%I is a parent of %I.%I', $1, $2, $3, $4 ) ); +$$ LANGUAGE SQL; -- is_parent_of( schema, table, schema, table, description ) CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME, TEXT ) RETURNS TEXT AS $$ - SELECT ok( _parent_of( $1, $2, $3, $4 ), - $5 ); + SELECT is_parent_of( $1, $2, $3, $4, 1, $5 ); +$$ LANGUAGE SQL; + + +-- is_child_of( schema, table, schema, table, depth, description ) +CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT is_parent_of( $3, $4, $1, $2, $5, $6 ); +$$ LANGUAGE SQL; + +-- is_child_of( schema, table, schema, table, depth ) +CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME, INT ) +RETURNS TEXT AS $$ + SELECT is_parent_of( $3, $4, $1, $2, $5 ); $$ LANGUAGE SQL; -- is_child_of( schema, table, schema, table, description ) CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME, TEXT ) RETURNS TEXT AS $$ - SELECT is_parent_of( $3, $4, $1, $2, $5 ); + SELECT is_child_of( $1, $2, $3, $4, 1, $5 ); $$ LANGUAGE SQL; -- is_parent_of( schema, table, schema, table ) @@ -1268,31 +1348,33 @@ CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME ) RETURNS TEXT AS $$ SELECT is_parent_of( $1, $2, $3, $4, + 1, format( 'Table %I.%I is parent of %I.%I', $1, $2, $3, $4 ) ); $$ LANGUAGE SQL; -- is_child_of( schema, table, schema, table ) CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME ) RETURNS TEXT AS $$ - SELECT is_parent_of( $3, $4, $1, $2 ); + SELECT is_child_of( $1, $2, $3, $4, 1, + format( 'Table %I.%I is child of %I.%I', $1, $2, $3, $4 ) ); $$ LANGUAGE SQL; -- is_parent_of( table, table, description ) -CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, TEXT ) +CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, INT, TEXT ) RETURNS TEXT AS $$ - SELECT ok( _parent_of( $1, $2 ), $3 ); + SELECT ok( _parent_of( $1, $2, $3 ), $4 ); $$ LANGUAGE SQL; -- is_child_of( table, table, description ) CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, TEXT ) RETURNS TEXT AS $$ - SELECT is_parent_of( $2, $1, $3 ); + SELECT is_parent_of( $2, $1, 1, $3 ); $$ LANGUAGE SQL; -- is_parent_of( table, table ) CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME ) RETURNS TEXT AS $$ - SELECT ok( _parent_of( $1, $2 ), + SELECT ok( _parent_of( $1, $2, 1 ), format( 'Table %I is parent of %I', $1, $2 ) ); $$ LANGUAGE SQL; @@ -1303,59 +1385,68 @@ RETURNS TEXT AS $$ $$ LANGUAGE SQL; --- isnt_parent_of( schema, table, schema, table, description ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME, TEXT ) +-- isnt_parent_of( schema, table, schema, table, depth, description ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME, INT, TEXT ) RETURNS TEXT AS $$ - SELECT ok( NOT _parent_of( $1, $2, $3, $4 ), - $5 ); + SELECT ok( NOT _parent_of( $1, $2, $3, $4, $5 ), + $6 ); $$ LANGUAGE SQL; --- isnt_child_of( schema, table, schema, table, description ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME, TEXT ) +-- isnt_child_of( schema, table, schema, table, depth, description ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME, INT, TEXT ) RETURNS TEXT AS $$ - SELECT isnt_parent_of( $3, $4, $1, $2, $5 ); + SELECT isnt_parent_of( $3, $4, $1, $2, $5, $6 ); $$ LANGUAGE SQL; --- isnt_parent_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME ) +-- isnt_parent_of( schema, table, schema, table, depth ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME, INT ) RETURNS TEXT AS $$ SELECT isnt_parent_of( $1, $2, $3, $4, + $5, format( 'Table %I.%I is not parent of %I.%I', $1, $2, $3, $4 ) ); -$$LANGUAGE SQL; +$$ LANGUAGE SQL; + + +-- isnt_child_of( schema, table, schema, table, depth ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME, INT ) +RETURNS TEXT AS $$ + SELECT isnt_parent_of( $3, $4, $1, $2, $5 ); +$$ LANGUAGE SQL; -- isnt_child_of( schema, table, schema, table ) CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME ) RETURNS TEXT AS $$ - SELECT isnt_parent_of( $3, $4, $1, $2 ); -$$LANGUAGE SQL; + SELECT isnt_parent_of( $3, $4, $1, $2, 1 ); +$$ LANGUAGE SQL; -- isnt_parent_of( table, table, description ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, TEXT ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, INT, TEXT ) RETURNS TEXT AS $$ - SELECT ok( NOT _parent_of( $1, $2 ), $3 ); + SELECT ok( NOT _parent_of( $1, $2, $3 ), $4 ); $$ LANGUAGE SQL; -- isnt_child_of( table, table, description ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, TEXT ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, INT, TEXT ) RETURNS TEXT AS $$ - SELECT isnt_parent_of( $2, $1, $3 ); + SELECT isnt_parent_of( $2, $1, $3, $4 ); $$ LANGUAGE SQL; -- isnt_parent_of( table, table ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, INT DEFAULT 1 ) RETURNS TEXT AS $$ - SELECT ok( NOT _parent_of( $1, $2 ), + SELECT ok( NOT _parent_of( $1, $2, $3 ), format( 'Table %I is not parent of %I', $1, $2 ) ); $$ LANGUAGE SQL; -- isnt_child_of( table, table ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, INT DEFAULT 1 ) RETURNS TEXT AS $$ - SELECT isnt_parent_of( $2, $1 ); + SELECT isnt_child_of( $1, $2, $3, + format( 'Table %I is not child of %I', $1, $2 ) ); $$ LANGUAGE SQL; /**************************************************************************/ diff --git a/test/sql/inheritance.sql b/test/sql/inheritance.sql index 68a4a1861aa9..fb4011677cf8 100644 --- a/test/sql/inheritance.sql +++ b/test/sql/inheritance.sql @@ -1,16 +1,18 @@ \unset ECHO --\i test/setup.sql -SELECT plan( 14 ); +SELECT plan( 16 ); -- Create inherited tables CREATE TABLE public.parent( id INT PRIMARY KEY ); -CREATE TABLE public.child( id INT PRIMARY KEY ) INHERITS ( public.parent ); +CREATE TABLE public.child1( id INT PRIMARY KEY ) INHERITS ( public.parent ); +CREATE TABLE public.child2( id INT PRIMARY KEY ) INHERITS ( public.child1 ); -- Create inherited tables in another schema CREATE SCHEMA hide; CREATE TABLE hide.h_parent( id INT PRIMARY KEY ); -CREATE TABLE hide.h_child( id INT PRIMARY KEY ) INHERITS ( hide.h_parent ); +CREATE TABLE hide.h_child1( id INT PRIMARY KEY ) INHERITS ( hide.h_parent ); +CREATE TABLE hide.h_child2( id INT PRIMARY KEY ) INHERITS ( hide.h_child1 ); -- test has_inhereted_tables @@ -22,9 +24,9 @@ SELECT * FROM check_test( -- test hasnt_inherited_tables SELECT * FROM check_test( - hasnt_inherited_tables( 'hide'::name, 'h_child'::name ) + hasnt_inherited_tables( 'hide'::name, 'h_child2'::name ) , true -- expected value - , 'hide.h_child is supposed not to have children' + , 'hide.h_child2 is not supposed to have children' ); @@ -37,77 +39,89 @@ SELECT * FROM check_test( -- test hasnt_inherited_tables SELECT * FROM check_test( - hasnt_inherited_tables( 'child'::name ) + hasnt_inherited_tables( 'child2'::name ) , true -- expected value - , 'public.child is supposed not to have children' + , 'public.child2 is supposed not to have children' ); SELECT * FROM check_test( - is_parent_of( 'hide', 'h_parent', 'hide', 'h_child' ) + is_parent_of( 'hide'::name, 'h_parent'::name, 'hide'::name, 'h_child1'::name, 1 ) , true -- expected value - , 'hide.h_parent is father of hide.h_child' + , 'hide.h_parent direct is father of hide.h_child1' ); SELECT * FROM check_test( - is_parent_of( 'parent', 'child' ) + is_parent_of( 'hide', 'h_child1', 'hide', 'h_child2', 1 ) , true -- expected value - , 'child inherits from parent' + , 'hide.h_child1 direct is father of hide.h_child2' +); + +SELECT * FROM check_test( + is_parent_of( 'hide', 'h_parent', 'hide', 'h_child2', 2 ) + , true -- expected value + , 'hide.h_parent is father of hide.h_child2' +); + +SELECT * FROM check_test( + is_parent_of( 'parent', 'child1' ) + , true -- expected value + , 'child1 inherits from parent' ); SELECT * FROM check_test( - is_parent_of( 'hide', 'h_parent', 'public', 'child' ) + is_parent_of( 'hide'::name, 'h_parent'::name, 'public'::name, 'child1'::name ) , false -- expected value - , 'hide.h_parent is not father of public.child' + , 'hide.h_parent is not father of public.child1' ); SELECT * FROM check_test( - is_parent_of( 'public', 'parent', 'public', 'child' ) + is_parent_of( 'public'::name, 'parent'::name, 'public'::name, 'child1'::name ) , true -- expected value - , 'public.parent is not father of public.child' + , 'public.parent is not father of public.child1' ); SELECT * FROM check_test( - isnt_child_of( 'hide', 'h_parent', 'public', 'child' ) + isnt_child_of( 'hide'::name, 'h_parent'::name, 'public'::name, 'child1'::name ) , true -- expected value - , 'hide.h_parent is not father of public.child' + , 'hide.h_parent is not father of public.child1' ); SELECT * FROM check_test( - isnt_parent_of( 'parent', 'child' ) + isnt_parent_of( 'parent', 'child1' ) , false -- expected value - , 'parent is not father of public.child' + , 'parent is not father of public.child1' ); SELECT * FROM check_test( - isnt_child_of( 'parent', 'child' ) + isnt_child_of( 'parent', 'child1' ) , true -- expected value - , 'parent is not child' + , 'parent is not child1' ); SELECT * FROM check_test( - isnt_child_of( 'child', 'parent' ) + isnt_child_of( 'child1', 'parent' ) , false -- expected value - , 'child inherits from parent' + , 'child1 inherits from parent' ); SELECT * FROM check_test( - isnt_child_of( 'hide', 'h_parent', 'hide', 'h_child' ) + isnt_child_of( 'hide'::name, 'h_parent'::name, 'hide'::name, 'h_child1'::name ) , true -- expected value - , 'hide.h_parent is not child of hide.h_child' + , 'hide.h_parent is not child of hide.h_child1' ); SELECT * FROM check_test( - isnt_child_of( 'hide', 'h_child', 'hide', 'h_parent' ) + isnt_child_of( 'hide'::name, 'h_child1'::name, 'hide'::name, 'h_parent'::name ) , false -- expected value - , 'hide.h_child inherits from hide.h_parent' + , 'hide.h_child1 inherits from hide.h_parent' ); From f8392d74b7efb4cec74981a6e5b30a7b8f239607 Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Tue, 2 Oct 2018 08:47:23 +0200 Subject: [PATCH 1040/1195] Refactoring of the 'is_parent_of()', 'is_child_of()', 'has_inherited_tables()'. Now use default arguments to keep the number of functions manageable and avoid overloading. Use 'format()' and 'coalesce()' to get rid of null text description. Update documentation. Fix test numbers and transaction boundaries. --- doc/pgtap.mmd | 38 ++++++-- sql/pgtap.sql.in | 184 +++++++++------------------------------ test/sql/inheritance.sql | 23 +++-- 3 files changed, 88 insertions(+), 157 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 3e9a2db23f9e..28ca6b31dbf6 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2971,9 +2971,11 @@ It is the opposite of the function `has_inherited_tables()`. ### `is_parent_of()` - SELECT is_parent_of( :parent_schema, :parent_table, :child_schema, :child_table, :description ); + SELECT is_parent_of( :parent_schema, :parent_table, :child_schema, :child_table, :depth, :description ); + SELECT is_parent_of( :parent_schema, :parent_table, :child_schema, :child_table, :depth ); SELECT is_parent_of( :parent_schema, :parent_table, :child_schema, :child_table ); - SELECT is_parent_of( :parent_table, :child_table, :description ); + SELECT is_parent_of( :parent_table, :child_table, :depth, :description ); + SELECT is_parent_of( :parent_table, :child_table, :depth ); SELECT is_parent_of( :parent_table, :child_table ); **Parameters** @@ -2993,15 +2995,21 @@ It is the opposite of the function `has_inherited_tables()`. `:description` : Description of the test. +`:depth` +: The position in the inheritance chain of the child table, by default 1 (a direct child). + This function checks if the table marked as "parent" is effectively a table from which the "child" table -inherits from, that is there is an inheritance chain between the two tables. +inherits from, that is there is an inheritance chain between the two tables. The depth argument +allows to specifiy at which position in the inheritance chain the child table must be found. ### `isnt_parent_of()` - SELECT isnt_parent_of( :parent_schema, :parent_table, :child_schema, :child_table, :description ); + SELECT isnt_parent_of( :parent_schema, :parent_table, :child_schema, :child_table, :depth, :description ); + SELECT isnt_parent_of( :parent_schema, :parent_table, :child_schema, :child_table, :depth ); SELECT isnt_parent_of( :parent_schema, :parent_table, :child_schema, :child_table ); - SELECT isnt_parent_of( :parent_table, :child_table, :description ); + SELECT isnt_parent_of( :parent_table, :child_table, :depth, :description ); + SELECT isnt_parent_of( :parent_table, :child_table, :depth ); SELECT isnt_parent_of( :parent_table, :child_table ); **Parameters** @@ -3021,6 +3029,9 @@ inherits from, that is there is an inheritance chain between the two tables. `:description` : Description of the test. +`:depth` +: The position within the inheritance chain where the child table is expected to be (by default 1). + This function ensures that the table marked as "parent" is not a table from which "child" inherits from, that is there is not an inheritance chain between the two tables. The function does the opposite of `is_parent_of()`. @@ -3028,9 +3039,11 @@ The function does the opposite of `is_parent_of()`. ### `is_child_of()` + SELECT is_child_of( :child_schema, :child_table, :parent_schema, :parent_table, :depth, :description ); SELECT is_child_of( :child_schema, :child_table, :parent_schema, :parent_table, :description ); SELECT is_child_of( :child_schema, :child_table, :parent_schema, :parent_table ); - SELECT is_child_of( :child_table, :parent_table, :description ); + SELECT is_child_of( :child_table, :parent_table, :depth, :description ); + SELECT is_child_of( :child_table, :parent_table, :depth ); SELECT is_child_of( :child_table, :parent_table ); **Parameters** @@ -3050,14 +3063,19 @@ The function does the opposite of `is_parent_of()`. `:description` : Description of the test. +`:depth` +: The position where the child table is expected to be in the inheritance chain (by default 1 for a direct son). + This function does the same of `is_parent_of()`, with swapped arguments. ### `isnt_child_of()` - SELECT isnt_child_of( :child_schema, :child_table, :parent_schema, :parent_table, :description ); + SELECT isnt_child_of( :child_schema, :child_table, :parent_schema, :parent_table, :depth, :description ); + SELECT isnt_child_of( :child_schema, :child_table, :parent_schema, :parent_table, :depth ); SELECT isnt_child_of( :child_schema, :child_table, :parent_schema, :parent_table ); - SELECT isnt_child_of( :child_table, :parent_table, :description ); + SELECT isnt_child_of( :child_table, :parent_table, :depth, :description ); + SELECT isnt_child_of( :child_table, :parent_table, :depth ); SELECT isnt_child_of( :child_table, :parent_table ); **Parameters** @@ -3077,6 +3095,10 @@ This function does the same of `is_parent_of()`, with swapped arguments. `:description` : Description of the test. +`:depth` +: The position where the inheriting table is expected to be along the inheritance chain with respect to the +parent table (by default 1 for a direct child). + This function does the same of `isnt_parent_of()`, with swapped arguments. ### `has_sequence()` ### diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index c543deb52cc2..fa6880c69c4e 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1137,69 +1137,41 @@ RETURNS BOOLEAN AS $$ $$ LANGUAGE SQL; -- has_inherited_tables( schema, table, description ) -CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, NAME, TEXT ) +-- has_inherited_tables( schema, table ) +CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, NAME, TEXT DEFAULT NULL ) RETURNS TEXT AS $$ SELECT ok( _inherited( $1, -- schema $2 ), -- table - $3 ); -- error message -$$ LANGUAGE SQL; - --- has_inherited_tables( schema, table ) -CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT has_inherited_tables( $1, -- schema - $2, -- table - format( 'Table(s) inherit from table %I.%I', $1, $2 ) ); + coalesce( $3, format( 'Table %I.%I has children', $1, $2 ) ) ); $$ LANGUAGE SQL; -- has_inherited_tables( table, description ) -CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _inherited( $1 ), -- table - $2 ); -- description -$$ LANGUAGE SQL; - - -- has_inherited_tables( table ) -CREATE OR REPLACE FUNCTION has_inherited_tables( NAME ) +CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, TEXT DEFAULT NULL ) RETURNS TEXT AS $$ - SELECT has_inherited_tables( $1::NAME, -- table - format( 'Table(s) inherit from table %I', $1 ) ); + SELECT ok( _inherited( $1 ), -- table + coalesce( $2, format( 'Table %I has children', $1 ) ) ); $$ 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 ) +CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME, NAME, TEXT DEFAULT NULL ) RETURNS TEXT AS $$ - SELECT ok( NOT _inherited( $1, $2 ), - format( 'Table %I.%I cannot have inherited tables', $1, $2 ) ); + SELECT ok( NOT _inherited( $1, $2 ), + coalesce( $3, format( 'Table %I.%I does not have children', $1, $2 ) ) ); $$ 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 ) +CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME, TEXT DEFAULT NULL ) RETURNS TEXT AS $$ - SELECT ok( NOT _inherited( $1 ), - format( 'Table %I cannot have inherited tables', $1 ) ); + SELECT ok( NOT _inherited( $1 ), + coalesce( $2, format( 'Table %I does not have children', $1 ) ) ); $$ LANGUAGE SQL; - - /* * Internal function to test if the qualified table is aprent of * the other qualified table. The integer value is the @@ -1303,150 +1275,80 @@ $$ LANGUAGE SQL; -- is_parent_of( schema, table, schema, table, depth, description ) -CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME, INT, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _parent_of( $1, $2, $3, $4, $5 ), - $6 ); -$$ LANGUAGE SQL; - - -- is_parent_of( schema, table, schema, table, depth ) -CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME, INT ) +-- is_parent_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) RETURNS TEXT AS $$ SELECT ok( _parent_of( $1, $2, $3, $4, $5 ), - format( 'Table %I.%I is a parent of %I.%I', $1, $2, $3, $4 ) ); + coalesce( $6, + format( 'Table %I.%I is a %s-parent of %I.%I', $1, $2, $5, $3, $4 ) ) + ); $$ LANGUAGE SQL; --- is_parent_of( schema, table, schema, table, description ) -CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT is_parent_of( $1, $2, $3, $4, 1, $5 ); -$$ LANGUAGE SQL; - -- is_child_of( schema, table, schema, table, depth, description ) -CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME, INT, TEXT ) -RETURNS TEXT AS $$ - SELECT is_parent_of( $3, $4, $1, $2, $5, $6 ); -$$ LANGUAGE SQL; - -- is_child_of( schema, table, schema, table, depth ) -CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME, INT ) -RETURNS TEXT AS $$ - SELECT is_parent_of( $3, $4, $1, $2, $5 ); -$$ LANGUAGE SQL; - --- is_child_of( schema, table, schema, table, description ) -CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT is_child_of( $1, $2, $3, $4, 1, $5 ); -$$ LANGUAGE SQL; - --- is_parent_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT is_parent_of( $1, $2, - $3, $4, - 1, - format( 'Table %I.%I is parent of %I.%I', $1, $2, $3, $4 ) ); -$$ LANGUAGE SQL; - -- is_child_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME ) +CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) RETURNS TEXT AS $$ - SELECT is_child_of( $1, $2, $3, $4, 1, - format( 'Table %I.%I is child of %I.%I', $1, $2, $3, $4 ) ); + SELECT is_parent_of( $3, $4, $1, $2, $5, + coalesce( $6, + format( 'Table %I.%I is a %s-child of %I.%I', $1, $2, $5, $3, $4 ) ) + ); $$ LANGUAGE SQL; --- is_parent_of( table, table, description ) -CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, INT, TEXT ) -RETURNS TEXT AS $$ - SELECT ok( _parent_of( $1, $2, $3 ), $4 ); -$$ LANGUAGE SQL; - --- is_child_of( table, table, description ) -CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT is_parent_of( $2, $1, 1, $3 ); -$$ LANGUAGE SQL; +-- is_parent_of( table, table, depth, description ) +-- is_parent_of( table, table, depth ) -- is_parent_of( table, table ) -CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME ) +CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) RETURNS TEXT AS $$ - SELECT ok( _parent_of( $1, $2, 1 ), - format( 'Table %I is parent of %I', $1, $2 ) ); + SELECT ok( _parent_of( $1, $2, $3 ), $4 ); $$ LANGUAGE SQL; +-- is_child_of( table, table, depth, description ) +-- is_child_of( table, table, depth ) -- is_child_of( table, table ) -CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME ) +CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) RETURNS TEXT AS $$ - SELECT is_parent_of( $2, $1 ); + SELECT is_parent_of( $2, $1, $3, $4 ); $$ LANGUAGE SQL; -- isnt_parent_of( schema, table, schema, table, depth, description ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME, INT, TEXT ) +-- isnt_parent_of( schema, table, schema, table, depth ) +-- isnt_parent_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) RETURNS TEXT AS $$ SELECT ok( NOT _parent_of( $1, $2, $3, $4, $5 ), $6 ); $$ LANGUAGE SQL; -- isnt_child_of( schema, table, schema, table, depth, description ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME, INT, TEXT ) -RETURNS TEXT AS $$ - SELECT isnt_parent_of( $3, $4, $1, $2, $5, $6 ); -$$ LANGUAGE SQL; - --- isnt_parent_of( schema, table, schema, table, depth ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME, INT ) -RETURNS TEXT AS $$ - SELECT isnt_parent_of( $1, $2, - $3, $4, - $5, - format( 'Table %I.%I is not parent of %I.%I', $1, $2, $3, $4 ) ); -$$ LANGUAGE SQL; - - -- isnt_child_of( schema, table, schema, table, depth ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME, INT ) -RETURNS TEXT AS $$ - SELECT isnt_parent_of( $3, $4, $1, $2, $5 ); -$$ LANGUAGE SQL; - - -- isnt_child_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) RETURNS TEXT AS $$ - SELECT isnt_parent_of( $3, $4, $1, $2, 1 ); + SELECT isnt_parent_of( $3, $4, $1, $2, $5, $6 ); $$ LANGUAGE SQL; --- isnt_parent_of( table, table, description ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, INT, TEXT ) +-- isnt_parent_of( table, table, depth, description ) +-- isnt_parent_of( table, table, depth ) +-- isnt_parent_of( table, table ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) RETURNS TEXT AS $$ SELECT ok( NOT _parent_of( $1, $2, $3 ), $4 ); $$ LANGUAGE SQL; --- isnt_child_of( table, table, description ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, INT, TEXT ) -RETURNS TEXT AS $$ - SELECT isnt_parent_of( $2, $1, $3, $4 ); -$$ LANGUAGE SQL; - --- isnt_parent_of( table, table ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, INT DEFAULT 1 ) -RETURNS TEXT AS $$ - SELECT ok( NOT _parent_of( $1, $2, $3 ), - format( 'Table %I is not parent of %I', $1, $2 ) ); -$$ LANGUAGE SQL; - +-- isnt_child_of( table, table, depth, description ) +-- isnt_child_of( table, table, depth ) -- isnt_child_of( table, table ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, INT DEFAULT 1 ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, INT DEFAULT 1 , TEXT DEFAULT NULL ) RETURNS TEXT AS $$ - SELECT isnt_child_of( $1, $2, $3, - format( 'Table %I is not child of %I', $1, $2 ) ); + SELECT isnt_parent_of( $2, $1, $3, $4 ); $$ LANGUAGE SQL; /**************************************************************************/ diff --git a/test/sql/inheritance.sql b/test/sql/inheritance.sql index fb4011677cf8..0c0031ef79f6 100644 --- a/test/sql/inheritance.sql +++ b/test/sql/inheritance.sql @@ -1,18 +1,18 @@ -\unset ECHO +--\unset ECHO --\i test/setup.sql - -SELECT plan( 16 ); +BEGIN; +SELECT plan( 17 ); -- Create inherited tables CREATE TABLE public.parent( id INT PRIMARY KEY ); -CREATE TABLE public.child1( id INT PRIMARY KEY ) INHERITS ( public.parent ); -CREATE TABLE public.child2( id INT PRIMARY KEY ) INHERITS ( public.child1 ); +CREATE TABLE public.child1( id INT PRIMARY KEY ) INHERITS ( public.parent ); +CREATE TABLE public.child2( id INT PRIMARY KEY ) INHERITS ( public.child1 ); -- Create inherited tables in another schema CREATE SCHEMA hide; CREATE TABLE hide.h_parent( id INT PRIMARY KEY ); -CREATE TABLE hide.h_child1( id INT PRIMARY KEY ) INHERITS ( hide.h_parent ); -CREATE TABLE hide.h_child2( id INT PRIMARY KEY ) INHERITS ( hide.h_child1 ); +CREATE TABLE hide.h_child1( id INT PRIMARY KEY ) INHERITS ( hide.h_parent ); +CREATE TABLE hide.h_child2( id INT PRIMARY KEY ) INHERITS ( hide.h_child1 ); -- test has_inhereted_tables @@ -48,7 +48,14 @@ SELECT * FROM check_test( SELECT * FROM check_test( - is_parent_of( 'hide'::name, 'h_parent'::name, 'hide'::name, 'h_child1'::name, 1 ) + is_parent_of( 'hide', 'h_parent', 'hide', 'h_child1', 1, 'Test hide.h_parent->hide.h_child1' ) + , true -- expected value + , 'hide.h_parent direct is father of hide.h_child1' +); + + +SELECT * FROM check_test( + is_parent_of( 'hide', 'h_parent', 'hide', 'h_child1', 1 ) , true -- expected value , 'hide.h_parent direct is father of hide.h_child1' ); From 78c677546d609423388aeb69ced57756c9eb256e Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Wed, 3 Oct 2018 08:11:12 +0200 Subject: [PATCH 1041/1195] Remove format(). The format() function has been introduced within 9.1 or so, and its usage breaks compataibility with older PostgreSQL versions. --- sql/pgtap.sql.in | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index fa6880c69c4e..25d520b4b862 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1142,7 +1142,8 @@ CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, NAME, TEXT DEFAULT NULL ) RETURNS TEXT AS $$ SELECT ok( _inherited( $1, -- schema $2 ), -- table - coalesce( $3, format( 'Table %I.%I has children', $1, $2 ) ) ); + coalesce( $3, + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || ' must have children' ) ); $$ LANGUAGE SQL; @@ -1159,7 +1160,8 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME, NAME, TEXT DEFAULT NULL ) RETURNS TEXT AS $$ SELECT ok( NOT _inherited( $1, $2 ), - coalesce( $3, format( 'Table %I.%I does not have children', $1, $2 ) ) ); + coalesce( $3, + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) 'must not have children' ) ); $$ LANGUAGE SQL; @@ -1168,7 +1170,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME, TEXT DEFAULT NULL ) RETURNS TEXT AS $$ SELECT ok( NOT _inherited( $1 ), - coalesce( $2, format( 'Table %I does not have children', $1 ) ) ); + coalesce( $2, 'Table ' || quote_idente( $1 ) || 'must not have children' ) ); $$ LANGUAGE SQL; @@ -1281,8 +1283,8 @@ CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, RETURNS TEXT AS $$ SELECT ok( _parent_of( $1, $2, $3, $4, $5 ), coalesce( $6, - format( 'Table %I.%I is a %s-parent of %I.%I', $1, $2, $5, $3, $4 ) ) - ); + 'Table ' || quote_idente( $1 ) || '.' || quote_idente( $2 ) + || ' is a ' || $5 || '-parent of ' || quote_idente( $3 ) || '.' || quote_idente( $4 ) ); $$ LANGUAGE SQL; @@ -1294,8 +1296,8 @@ CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, T RETURNS TEXT AS $$ SELECT is_parent_of( $3, $4, $1, $2, $5, coalesce( $6, - format( 'Table %I.%I is a %s-child of %I.%I', $1, $2, $5, $3, $4 ) ) - ); + 'Table ' || quote_idente( $1 ) || '.' || quote_idente( $2 ) + || ' is a ' || $5 || '-child of ' || quote_idente( $3 ) || '.' || quote_idente( $4 ) ); $$ LANGUAGE SQL; From ced475813c3844c9f781e3f24b8633a20afbd808 Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Wed, 10 Oct 2018 08:53:54 +0200 Subject: [PATCH 1042/1195] Remove format() call. Spotted by test suite, see . Should have been fixed by commit 8dfbc22f6372d78 and is coherent with such. --- sql/pgtap.sql.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 25d520b4b862..c7b9aff766ec 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1152,7 +1152,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, TEXT DEFAULT NULL ) RETURNS TEXT AS $$ SELECT ok( _inherited( $1 ), -- table - coalesce( $2, format( 'Table %I has children', $1 ) ) ); + coalesce( $2, 'Table ' || quote_ident( $1 ) || ' has children' ) ); $$ LANGUAGE SQL; -- hasnt_inherited_tables( schema, table, description ) From fbcd8b3f64a495747c40d2e00c4127769b9af9a4 Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Wed, 10 Oct 2018 10:28:11 +0200 Subject: [PATCH 1043/1195] Fixed several mispelled quote_ident. --- sql/pgtap.sql.in | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index c7b9aff766ec..5d5c1b0ba8aa 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1161,7 +1161,7 @@ CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME, NAME, TEXT DEFAULT NULL RETURNS TEXT AS $$ SELECT ok( NOT _inherited( $1, $2 ), coalesce( $3, - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) 'must not have children' ) ); + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || 'must not have children' ) ); $$ LANGUAGE SQL; @@ -1170,7 +1170,7 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME, TEXT DEFAULT NULL ) RETURNS TEXT AS $$ SELECT ok( NOT _inherited( $1 ), - coalesce( $2, 'Table ' || quote_idente( $1 ) || 'must not have children' ) ); + coalesce( $2, 'Table ' || quote_ident( $1 ) || 'must not have children' ) ); $$ LANGUAGE SQL; @@ -1283,8 +1283,10 @@ CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, RETURNS TEXT AS $$ SELECT ok( _parent_of( $1, $2, $3, $4, $5 ), coalesce( $6, - 'Table ' || quote_idente( $1 ) || '.' || quote_idente( $2 ) - || ' is a ' || $5 || '-parent of ' || quote_idente( $3 ) || '.' || quote_idente( $4 ) ); + 'Table ' + || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' is a ' || $5 || '-parent of ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) ) ); $$ LANGUAGE SQL; @@ -1296,8 +1298,10 @@ CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, T RETURNS TEXT AS $$ SELECT is_parent_of( $3, $4, $1, $2, $5, coalesce( $6, - 'Table ' || quote_idente( $1 ) || '.' || quote_idente( $2 ) - || ' is a ' || $5 || '-child of ' || quote_idente( $3 ) || '.' || quote_idente( $4 ) ); + 'Table ' + || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' is a ' || $5 || '-child of ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) ) ); $$ LANGUAGE SQL; From 1d13f1224aa47cfb709273cb021e72ff78d34794 Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Wed, 10 Oct 2018 13:13:44 +0200 Subject: [PATCH 1044/1195] Output file for testing with CI and Travis. Produces running psql and pktap.sql. --- test/expected/inheritance.out | 85 +++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 test/expected/inheritance.out diff --git a/test/expected/inheritance.out b/test/expected/inheritance.out new file mode 100644 index 000000000000..2987b5ba9c96 --- /dev/null +++ b/test/expected/inheritance.out @@ -0,0 +1,85 @@ +1..84 +ok 1 - has_pk( schema, table, description ) should pass +ok 2 - has_pk( schema, table, description ) should have the proper description +ok 3 - has_pk( schema, table, description ) should have the proper diagnostics +ok 4 - has_pk( hideschema, hidetable, description ) should pass +ok 5 - has_pk( hideschema, hidetable, description ) should have the proper description +ok 6 - has_pk( hideschema, hidetable, description ) should have the proper diagnostics +ok 7 - has_pk( table, description ) should pass +ok 8 - has_pk( table, description ) should have the proper description +ok 9 - has_pk( table, description ) should have the proper diagnostics +ok 10 - has_pk( hidetable, description ) fail should fail +ok 11 - has_pk( hidetable, description ) fail should have the proper description +ok 12 - has_pk( hidetable, description ) fail should have the proper diagnostics +ok 13 - has_pk( table ) should pass +ok 14 - has_pk( table ) should have the proper description +ok 15 - has_pk( table ) should have the proper diagnostics +ok 16 - has_pk( schema, table, description ) fail should fail +ok 17 - has_pk( schema, table, description ) fail should have the proper description +ok 18 - has_pk( schema, table, description ) fail should have the proper diagnostics +ok 19 - has_pk( table, description ) fail should fail +ok 20 - has_pk( table, description ) fail should have the proper description +ok 21 - has_pk( table, description ) fail should have the proper diagnostics +ok 22 - hasnt_pk( schema, table, description ) should fail +ok 23 - hasnt_pk( schema, table, description ) should have the proper description +ok 24 - hasnt_pk( schema, table, description ) should have the proper diagnostics +ok 25 - hasnt_pk( table, description ) should fail +ok 26 - hasnt_pk( table, description ) should have the proper description +ok 27 - hasnt_pk( table, description ) should have the proper diagnostics +ok 28 - hasnt_pk( table ) should fail +ok 29 - hasnt_pk( table ) should have the proper description +ok 30 - hasnt_pk( table ) should have the proper diagnostics +ok 31 - hasnt_pk( schema, table, description ) pass should pass +ok 32 - hasnt_pk( schema, table, description ) pass should have the proper description +ok 33 - hasnt_pk( schema, table, description ) pass should have the proper diagnostics +ok 34 - hasnt_pk( table, description ) pass should pass +ok 35 - hasnt_pk( table, description ) pass should have the proper description +ok 36 - hasnt_pk( table, description ) pass should have the proper diagnostics +ok 37 - col_is_pk( schema, table, column, description ) should pass +ok 38 - col_is_pk( schema, table, column, description ) should have the proper description +ok 39 - col_is_pk( schema, table, column, description ) should have the proper diagnostics +ok 40 - col_is_pk( table, column, description ) should pass +ok 41 - col_is_pk( table, column, description ) should have the proper description +ok 42 - col_is_pk( table, column, description ) should have the proper diagnostics +ok 43 - col_is_pk( table, column ) should pass +ok 44 - col_is_pk( table, column ) should have the proper description +ok 45 - col_is_pk( table, column ) should have the proper diagnostics +ok 46 - col_is_pk( schema, table, column, description ) fail should fail +ok 47 - col_is_pk( schema, table, column, description ) fail should have the proper description +ok 48 - col_is_pk( schema, table, column, description ) fail should have the proper diagnostics +ok 49 - col_is_pk( table, column, description ) fail should fail +ok 50 - col_is_pk( table, column, description ) fail should have the proper description +ok 51 - col_is_pk( table, column, description ) fail should have the proper diagnostics +ok 52 - col_is_pk( schema, table, column[], description ) should pass +ok 53 - col_is_pk( schema, table, column[], description ) should have the proper description +ok 54 - col_is_pk( schema, table, column[], description ) should have the proper diagnostics +ok 55 - col_is_pk( table, column[], description ) should pass +ok 56 - col_is_pk( table, column[], description ) should have the proper description +ok 57 - col_is_pk( table, column[], description ) should have the proper diagnostics +ok 58 - col_is_pk( table, column[] ) should pass +ok 59 - col_is_pk( table, column[] ) should have the proper description +ok 60 - col_is_pk( table, column[] ) should have the proper diagnostics +ok 61 - col_isnt_pk( schema, table, column, description ) should fail +ok 62 - col_isnt_pk( schema, table, column, description ) should have the proper description +ok 63 - col_isnt_pk( schema, table, column, description ) should have the proper diagnostics +ok 64 - col_isnt_pk( table, column, description ) should fail +ok 65 - col_isnt_pk( table, column, description ) should have the proper description +ok 66 - col_isnt_pk( table, column, description ) should have the proper diagnostics +ok 67 - col_isnt_pk( table, column ) should fail +ok 68 - col_isnt_pk( table, column ) should have the proper description +ok 69 - col_isnt_pk( table, column ) should have the proper diagnostics +ok 70 - col_isnt_pk( schema, table, column, description ) pass should pass +ok 71 - col_isnt_pk( schema, table, column, description ) pass should have the proper description +ok 72 - col_isnt_pk( schema, table, column, description ) pass should have the proper diagnostics +ok 73 - col_isnt_pk( table, column, description ) pass should pass +ok 74 - col_isnt_pk( table, column, description ) pass should have the proper description +ok 75 - col_isnt_pk( table, column, description ) pass should have the proper diagnostics +ok 76 - col_isnt_pk( schema, table, column[], description ) should pass +ok 77 - col_isnt_pk( schema, table, column[], description ) should have the proper description +ok 78 - col_isnt_pk( schema, table, column[], description ) should have the proper diagnostics +ok 79 - col_isnt_pk( table, column[], description ) should pass +ok 80 - col_isnt_pk( table, column[], description ) should have the proper description +ok 81 - col_isnt_pk( table, column[], description ) should have the proper diagnostics +ok 82 - col_isnt_pk( table, column[] ) should pass +ok 83 - col_isnt_pk( table, column[] ) should have the proper description +ok 84 - col_isnt_pk( table, column[] ) should have the proper diagnostics From b25d2bcf5865c59252eac9389354e5e32bc4f127 Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Wed, 10 Oct 2018 16:16:13 +0200 Subject: [PATCH 1045/1195] Fix inheritance test and expected output. See Thanks to @theory for pointing out I was using the wrong test output in the expected file. --- test/expected/inheritance.out | 104 +++++++--------------------------- test/sql/inheritance.sql | 5 +- 2 files changed, 22 insertions(+), 87 deletions(-) diff --git a/test/expected/inheritance.out b/test/expected/inheritance.out index 2987b5ba9c96..b1dce04ae017 100644 --- a/test/expected/inheritance.out +++ b/test/expected/inheritance.out @@ -1,85 +1,19 @@ -1..84 -ok 1 - has_pk( schema, table, description ) should pass -ok 2 - has_pk( schema, table, description ) should have the proper description -ok 3 - has_pk( schema, table, description ) should have the proper diagnostics -ok 4 - has_pk( hideschema, hidetable, description ) should pass -ok 5 - has_pk( hideschema, hidetable, description ) should have the proper description -ok 6 - has_pk( hideschema, hidetable, description ) should have the proper diagnostics -ok 7 - has_pk( table, description ) should pass -ok 8 - has_pk( table, description ) should have the proper description -ok 9 - has_pk( table, description ) should have the proper diagnostics -ok 10 - has_pk( hidetable, description ) fail should fail -ok 11 - has_pk( hidetable, description ) fail should have the proper description -ok 12 - has_pk( hidetable, description ) fail should have the proper diagnostics -ok 13 - has_pk( table ) should pass -ok 14 - has_pk( table ) should have the proper description -ok 15 - has_pk( table ) should have the proper diagnostics -ok 16 - has_pk( schema, table, description ) fail should fail -ok 17 - has_pk( schema, table, description ) fail should have the proper description -ok 18 - has_pk( schema, table, description ) fail should have the proper diagnostics -ok 19 - has_pk( table, description ) fail should fail -ok 20 - has_pk( table, description ) fail should have the proper description -ok 21 - has_pk( table, description ) fail should have the proper diagnostics -ok 22 - hasnt_pk( schema, table, description ) should fail -ok 23 - hasnt_pk( schema, table, description ) should have the proper description -ok 24 - hasnt_pk( schema, table, description ) should have the proper diagnostics -ok 25 - hasnt_pk( table, description ) should fail -ok 26 - hasnt_pk( table, description ) should have the proper description -ok 27 - hasnt_pk( table, description ) should have the proper diagnostics -ok 28 - hasnt_pk( table ) should fail -ok 29 - hasnt_pk( table ) should have the proper description -ok 30 - hasnt_pk( table ) should have the proper diagnostics -ok 31 - hasnt_pk( schema, table, description ) pass should pass -ok 32 - hasnt_pk( schema, table, description ) pass should have the proper description -ok 33 - hasnt_pk( schema, table, description ) pass should have the proper diagnostics -ok 34 - hasnt_pk( table, description ) pass should pass -ok 35 - hasnt_pk( table, description ) pass should have the proper description -ok 36 - hasnt_pk( table, description ) pass should have the proper diagnostics -ok 37 - col_is_pk( schema, table, column, description ) should pass -ok 38 - col_is_pk( schema, table, column, description ) should have the proper description -ok 39 - col_is_pk( schema, table, column, description ) should have the proper diagnostics -ok 40 - col_is_pk( table, column, description ) should pass -ok 41 - col_is_pk( table, column, description ) should have the proper description -ok 42 - col_is_pk( table, column, description ) should have the proper diagnostics -ok 43 - col_is_pk( table, column ) should pass -ok 44 - col_is_pk( table, column ) should have the proper description -ok 45 - col_is_pk( table, column ) should have the proper diagnostics -ok 46 - col_is_pk( schema, table, column, description ) fail should fail -ok 47 - col_is_pk( schema, table, column, description ) fail should have the proper description -ok 48 - col_is_pk( schema, table, column, description ) fail should have the proper diagnostics -ok 49 - col_is_pk( table, column, description ) fail should fail -ok 50 - col_is_pk( table, column, description ) fail should have the proper description -ok 51 - col_is_pk( table, column, description ) fail should have the proper diagnostics -ok 52 - col_is_pk( schema, table, column[], description ) should pass -ok 53 - col_is_pk( schema, table, column[], description ) should have the proper description -ok 54 - col_is_pk( schema, table, column[], description ) should have the proper diagnostics -ok 55 - col_is_pk( table, column[], description ) should pass -ok 56 - col_is_pk( table, column[], description ) should have the proper description -ok 57 - col_is_pk( table, column[], description ) should have the proper diagnostics -ok 58 - col_is_pk( table, column[] ) should pass -ok 59 - col_is_pk( table, column[] ) should have the proper description -ok 60 - col_is_pk( table, column[] ) should have the proper diagnostics -ok 61 - col_isnt_pk( schema, table, column, description ) should fail -ok 62 - col_isnt_pk( schema, table, column, description ) should have the proper description -ok 63 - col_isnt_pk( schema, table, column, description ) should have the proper diagnostics -ok 64 - col_isnt_pk( table, column, description ) should fail -ok 65 - col_isnt_pk( table, column, description ) should have the proper description -ok 66 - col_isnt_pk( table, column, description ) should have the proper diagnostics -ok 67 - col_isnt_pk( table, column ) should fail -ok 68 - col_isnt_pk( table, column ) should have the proper description -ok 69 - col_isnt_pk( table, column ) should have the proper diagnostics -ok 70 - col_isnt_pk( schema, table, column, description ) pass should pass -ok 71 - col_isnt_pk( schema, table, column, description ) pass should have the proper description -ok 72 - col_isnt_pk( schema, table, column, description ) pass should have the proper diagnostics -ok 73 - col_isnt_pk( table, column, description ) pass should pass -ok 74 - col_isnt_pk( table, column, description ) pass should have the proper description -ok 75 - col_isnt_pk( table, column, description ) pass should have the proper diagnostics -ok 76 - col_isnt_pk( schema, table, column[], description ) should pass -ok 77 - col_isnt_pk( schema, table, column[], description ) should have the proper description -ok 78 - col_isnt_pk( schema, table, column[], description ) should have the proper diagnostics -ok 79 - col_isnt_pk( table, column[], description ) should pass -ok 80 - col_isnt_pk( table, column[], description ) should have the proper description -ok 81 - col_isnt_pk( table, column[], description ) should have the proper diagnostics -ok 82 - col_isnt_pk( table, column[] ) should pass -ok 83 - col_isnt_pk( table, column[] ) should have the proper description -ok 84 - col_isnt_pk( table, column[] ) should have the proper diagnostics +\unset ECHO +1..17 +ok 1 - hide.h_parent is supposed to be parent of other tables should pass +ok 2 - hide.h_child2 is not supposed to have children should pass +ok 3 - public.parent is supposed to be parent of other tables should pass +ok 4 - public.child2 is supposed not to have children should pass +ok 5 - hide.h_parent direct is father of hide.h_child1 should pass +ok 6 - hide.h_parent direct is father of hide.h_child1 should pass +ok 7 - hide.h_child1 direct is father of hide.h_child2 should pass +ok 8 - hide.h_parent is father of hide.h_child2 should pass +ok 9 - child1 inherits from parent should pass +ok 10 - hide.h_parent is not father of public.child1 should fail +ok 11 - public.parent is not father of public.child1 should pass +ok 12 - hide.h_parent is not father of public.child1 should pass +ok 13 - parent is not father of public.child1 should fail +ok 14 - parent is not child1 should pass +ok 15 - child1 inherits from parent should fail +ok 16 - hide.h_parent is not child of hide.h_child1 should pass +ok 17 - hide.h_child1 inherits from hide.h_parent should fail \ No newline at end of file diff --git a/test/sql/inheritance.sql b/test/sql/inheritance.sql index 0c0031ef79f6..db80ea7135d8 100644 --- a/test/sql/inheritance.sql +++ b/test/sql/inheritance.sql @@ -1,7 +1,8 @@ ---\unset ECHO ---\i test/setup.sql +\unset ECHO +\i test/setup.sql BEGIN; SELECT plan( 17 ); +SET client_min_messages = warning; -- Create inherited tables CREATE TABLE public.parent( id INT PRIMARY KEY ); From 4e73201e26780bfae0308ae498a85bbd1e01660d Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Wed, 10 Oct 2018 20:51:31 +0200 Subject: [PATCH 1046/1195] Remove extra transaction begin in test inheritance. Also add a new line in the expected output file. --- test/expected/inheritance.out | 2 +- test/sql/inheritance.sql | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/test/expected/inheritance.out b/test/expected/inheritance.out index b1dce04ae017..77ee597c9d40 100644 --- a/test/expected/inheritance.out +++ b/test/expected/inheritance.out @@ -16,4 +16,4 @@ ok 13 - parent is not father of public.child1 should fail ok 14 - parent is not child1 should pass ok 15 - child1 inherits from parent should fail ok 16 - hide.h_parent is not child of hide.h_child1 should pass -ok 17 - hide.h_child1 inherits from hide.h_parent should fail \ No newline at end of file +ok 17 - hide.h_child1 inherits from hide.h_parent should fail diff --git a/test/sql/inheritance.sql b/test/sql/inheritance.sql index db80ea7135d8..95834562ff6f 100644 --- a/test/sql/inheritance.sql +++ b/test/sql/inheritance.sql @@ -1,6 +1,5 @@ \unset ECHO \i test/setup.sql -BEGIN; SELECT plan( 17 ); SET client_min_messages = warning; From 2bff2ebf89c4ec4252ce2fa9871403005fb84629 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 13:25:07 -0500 Subject: [PATCH 1047/1195] Reformat and move inheritance functions. --- doc/pgtap.mmd | 30 +-- sql/pgtap.sql.in | 524 +++++++++++++++++++++++------------------------ 2 files changed, 276 insertions(+), 278 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 28ca6b31dbf6..e9c539702489 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2996,12 +2996,13 @@ It is the opposite of the function `has_inherited_tables()`. : Description of the test. `:depth` -: The position in the inheritance chain of the child table, by default 1 (a direct child). - -This function checks if the table marked as "parent" is effectively a table from which the "child" table -inherits from, that is there is an inheritance chain between the two tables. The depth argument -allows to specifiy at which position in the inheritance chain the child table must be found. +: The position in the inheritance chain of the child table, by default 1 (a + direct child). +This function checks if the table marked as "parent" is effectively a table from +which the "child" table inherits from, that is there is an inheritance chain +between the two tables. The depth argument allows to specify the level in the +inheritance chain in which the child table must be found. ### `isnt_parent_of()` @@ -3030,12 +3031,12 @@ allows to specifiy at which position in the inheritance chain the child table mu : Description of the test. `:depth` -: The position within the inheritance chain where the child table is expected to be (by default 1). - -This function ensures that the table marked as "parent" is not a table from which "child" inherits from, that -is there is not an inheritance chain between the two tables. -The function does the opposite of `is_parent_of()`. +: The position within the inheritance chain where the child table is expected + to be (by default 1). +This function ensures that the table marked as "parent" is not a table from +which "child" inherits from, that there no inheritance chain between the two +tables. The function does the opposite of `is_parent_of()`. ### `is_child_of()` @@ -3064,11 +3065,11 @@ The function does the opposite of `is_parent_of()`. : Description of the test. `:depth` -: The position where the child table is expected to be in the inheritance chain (by default 1 for a direct son). +: The position where the child table is expected to be in the inheritance chain + (by default 1 for a direct son). This function does the same of `is_parent_of()`, with swapped arguments. - ### `isnt_child_of()` SELECT isnt_child_of( :child_schema, :child_table, :parent_schema, :parent_table, :depth, :description ); @@ -3096,8 +3097,9 @@ This function does the same of `is_parent_of()`, with swapped arguments. : Description of the test. `:depth` -: The position where the inheriting table is expected to be along the inheritance chain with respect to the -parent table (by default 1 for a direct child). +: The position where the inheriting table is expected to be along the + inheritance chain with respect to the parent table (by default 1 for a direct + child). This function does the same of `isnt_parent_of()`, with swapped arguments. diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 5d5c1b0ba8aa..1ceea93be735 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1095,270 +1095,6 @@ RETURNS TEXT AS $$ SELECT hasnt_view( $1, 'View ' || quote_ident($1) || ' should not exist' ); $$ LANGUAGE SQL; - - -/******************** INHERITANCE ***********************************************/ - - -/* - * Internal function to test if the specified table in the - * specified schema does have 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 if a specific table - * in the search_path has a 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 ) --- has_inherited_tables( schema, table ) -CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, NAME, TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT ok( _inherited( $1, -- schema - $2 ), -- table - coalesce( $3, - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || ' must have children' ) ); -$$ LANGUAGE SQL; - - --- has_inherited_tables( table, description ) --- has_inherited_tables( table ) -CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT ok( _inherited( $1 ), -- table - coalesce( $2, 'Table ' || quote_ident( $1 ) || ' has children' ) ); -$$ LANGUAGE SQL; - --- hasnt_inherited_tables( schema, table, description ) --- hasnt_inherited_tables( schema, table ) -CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME, NAME, TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT ok( NOT _inherited( $1, $2 ), - coalesce( $3, - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || 'must not have children' ) ); -$$ LANGUAGE SQL; - - --- hasnt_inherited_tables( table, description ) --- hasnt_inherited_tables( table ) -CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME, TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT ok( NOT _inherited( $1 ), - coalesce( $2, 'Table ' || quote_ident( $1 ) || 'must not have children' ) ); -$$ LANGUAGE SQL; - - -/* -* Internal function to test if the qualified table is aprent of -* the other qualified table. The integer value is the -* length of the inheritance chain: a direct child has a 1 value. -*/ -CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, NAME, NAME, INT DEFAULT 1 ) -RETURNS BOOLEAN AS $$ - WITH RECURSIVE inheritance_chain AS ( - -- select the parent tuple - SELECT i.inhrelid AS child_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 childrens - SELECT i.inhrelid AS child_id - , p.inheritance_level + 1 AS inheritance_level - FROM pg_catalog.pg_inherits i - JOIN inheritance_chain p - ON p.child_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 * - FROM inheritance_chain - WHERE inheritance_level = $5 - AND child_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 STRICT; - -/* - * Internal function to test for a direct child (i.e., inheritance chain - * length of 1). - */ -CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT _parent_of( $1, $2, $3, $4, 1 ); -$$ LANGUAGE SQL; - - -/* - * Internal function to check if not-qualified tables - * within the search_path are connected by an inheritance chain. - */ -CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, INT DEFAULT 1 ) -RETURNS BOOLEAN AS $$ - WITH RECURSIVE inheritance_chain AS ( - -- select the parent tuple - SELECT i.inhrelid AS child_id - , 1 AS inheritance_level - FROM pg_catalog.pg_inherits i - WHERE i.inhparent = ( SELECT c1.oid - FROM pg_catalog.pg_class c1 - WHERE c1.relname = $1 - AND pg_catalog.pg_table_is_visible( c1.oid ) ) - - - UNION - - -- select the childrens - SELECT i.inhrelid AS child_id - , p.inheritance_level + 1 AS inheritance_level - FROM pg_catalog.pg_inherits i - JOIN inheritance_chain p - ON p.child_id = i.inhparent - WHERE i.inhrelid = ( SELECT c1.oid - FROM pg_catalog.pg_class c1 - WHERE c1.relname = $1 - AND pg_catalog.pg_table_is_visible( c1.oid ) ) - ) - SELECT EXISTS( SELECT * - FROM inheritance_chain - WHERE inheritance_level = $3 - AND child_id = ( SELECT c1.oid - FROM pg_catalog.pg_class c1 - WHERE c1.relname = $2 - AND pg_catalog.pg_table_is_visible( c1.oid ) ) - ); - - -$$ LANGUAGE SQL; - - - --- is_parent_of( schema, table, schema, table, depth, description ) --- is_parent_of( schema, table, schema, table, depth ) --- is_parent_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT ok( _parent_of( $1, $2, $3, $4, $5 ), - coalesce( $6, - 'Table ' - || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' is a ' || $5 || '-parent of ' - || quote_ident( $3 ) || '.' || quote_ident( $4 ) ) ); -$$ LANGUAGE SQL; - - - --- is_child_of( schema, table, schema, table, depth, description ) --- is_child_of( schema, table, schema, table, depth ) --- is_child_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT is_parent_of( $3, $4, $1, $2, $5, - coalesce( $6, - 'Table ' - || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' is a ' || $5 || '-child of ' - || quote_ident( $3 ) || '.' || quote_ident( $4 ) ) ); -$$ LANGUAGE SQL; - - --- is_parent_of( table, table, depth, description ) --- is_parent_of( table, table, depth ) --- is_parent_of( table, table ) -CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT ok( _parent_of( $1, $2, $3 ), $4 ); -$$ LANGUAGE SQL; - --- is_child_of( table, table, depth, description ) --- is_child_of( table, table, depth ) --- is_child_of( table, table ) -CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT is_parent_of( $2, $1, $3, $4 ); -$$ LANGUAGE SQL; - - --- isnt_parent_of( schema, table, schema, table, depth, description ) --- isnt_parent_of( schema, table, schema, table, depth ) --- isnt_parent_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT ok( NOT _parent_of( $1, $2, $3, $4, $5 ), - $6 ); -$$ LANGUAGE SQL; - --- isnt_child_of( schema, table, schema, table, depth, description ) --- isnt_child_of( schema, table, schema, table, depth ) --- isnt_child_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT isnt_parent_of( $3, $4, $1, $2, $5, $6 ); -$$ LANGUAGE SQL; - - --- isnt_parent_of( table, table, depth, description ) --- isnt_parent_of( table, table, depth ) --- isnt_parent_of( table, table ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT ok( NOT _parent_of( $1, $2, $3 ), $4 ); -$$ LANGUAGE SQL; - - --- isnt_child_of( table, table, depth, description ) --- isnt_child_of( table, table, depth ) --- isnt_child_of( table, table ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, INT DEFAULT 1 , TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT isnt_parent_of( $2, $1, $3, $4 ); -$$ LANGUAGE SQL; - -/**************************************************************************/ - -- has_sequence( schema, sequence, description ) CREATE OR REPLACE FUNCTION has_sequence ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ @@ -10536,3 +10272,263 @@ RETURNS TEXT AS $$ || ' 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 ) +-- has_inherited_tables( schema, table ) +CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, NAME, TEXT DEFAULT NULL ) +RETURNS TEXT AS $$ + SELECT ok( + _inherited( $1, $2 ), + COALESCE( + $3, + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || ' must have children' + ) + ); +$$ LANGUAGE SQL; + +-- has_inherited_tables( table, description ) +-- has_inherited_tables( table ) +CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, TEXT DEFAULT NULL ) +RETURNS TEXT AS $$ + SELECT ok( + _inherited( $1 ), + COALESCE( $2, 'Table ' || quote_ident( $1 ) || ' has children' ) + ); +$$ LANGUAGE SQL; + +-- hasnt_inherited_tables( schema, table, description ) +-- hasnt_inherited_tables( schema, table ) +CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME, NAME, TEXT DEFAULT NULL ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _inherited( $1, $2 ), + COALESCE( + $3, + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || 'must not have children' + ) + ); +$$ LANGUAGE SQL; + +-- hasnt_inherited_tables( table, description ) +-- hasnt_inherited_tables( table ) +CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME, TEXT DEFAULT NULL ) +RETURNS TEXT AS $$ + SELECT ok( + NOT _inherited( $1 ), + COALESCE( $2, 'Table ' || quote_ident( $1 ) || 'must not have children' ) + ); +$$ LANGUAGE SQL; + +/* +* Internal function to test whether the schema-qualified table is a parent of +* the other schema-qualified table. The integer value is the length of the +* inheritance chain: a direct child has a 1 value. +*/ +CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, NAME, NAME, INT DEFAULT 1 ) +RETURNS BOOLEAN AS $$ + WITH RECURSIVE inheritance_chain AS ( + -- select the parent tuple + SELECT i.inhrelid AS child_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 childrens + SELECT i.inhrelid AS child_id, + p.inheritance_level + 1 AS inheritance_level + FROM pg_catalog.pg_inherits i + JOIN inheritance_chain p + ON p.child_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 * + FROM inheritance_chain + WHERE inheritance_level = $5 + AND child_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 STRICT; + +/* + * Internal function to test for a direct child (i.e., inheritance chain + * length of 1). + */ +CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, NAME, NAME ) +RETURNS BOOLEAN AS $$ + SELECT _parent_of( $1, $2, $3, $4, 1 ); +$$ LANGUAGE SQL; + +/* + * Internal function to check if not-qualified tables + * within the search_path are connected by an inheritance chain. + */ +CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, INT DEFAULT 1 ) +RETURNS BOOLEAN AS $$ + WITH RECURSIVE inheritance_chain AS ( + -- select the parent tuple + SELECT i.inhrelid AS child_id, 1 AS inheritance_level + FROM pg_catalog.pg_inherits i + WHERE i.inhparent = ( + SELECT c1.oid + FROM pg_catalog.pg_class c1 + WHERE c1.relname = $1 + AND pg_catalog.pg_table_is_visible( c1.oid ) + ) + UNION + -- select the childrens + SELECT i.inhrelid AS child_id, + p.inheritance_level + 1 AS inheritance_level + FROM pg_catalog.pg_inherits i + JOIN inheritance_chain p + ON p.child_id = i.inhparent + WHERE i.inhrelid = ( + SELECT c1.oid + FROM pg_catalog.pg_class c1 + WHERE c1.relname = $1 + AND pg_catalog.pg_table_is_visible( c1.oid ) + ) + ) + SELECT EXISTS( + SELECT * + FROM inheritance_chain + WHERE inheritance_level = $3 + AND child_id = ( + SELECT c1.oid + FROM pg_catalog.pg_class c1 + WHERE c1.relname = $2 + AND pg_catalog.pg_table_is_visible( c1.oid ) + ) + ); +$$ LANGUAGE SQL; + +-- is_parent_of( schema, table, schema, table, depth, description ) +-- is_parent_of( schema, table, schema, table, depth ) +-- is_parent_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) +RETURNS TEXT AS $$ + SELECT ok( + _parent_of( $1, $2, $3, $4, $5 ), + COALESCE( + $6, + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' is a ' || $5 || '-parent of ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) + ) + ); +$$ LANGUAGE SQL; + +-- is_child_of( schema, table, schema, table, depth, description ) +-- is_child_of( schema, table, schema, table, depth ) +-- is_child_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) +RETURNS TEXT AS $$ + SELECT is_parent_of( + $3, $4, $1, $2, $5, + COALESCE( $6, + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' is a ' || $5 || '-child of ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) + ) + ); +$$ LANGUAGE SQL; +-- is_parent_of( table, table, depth, description ) +-- is_parent_of( table, table, depth ) +-- is_parent_of( table, table ) +CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) +RETURNS TEXT AS $$ + SELECT ok( _parent_of( $1, $2, $3 ), $4 ); +$$ LANGUAGE SQL; + +-- is_child_of( table, table, depth, description ) +-- is_child_of( table, table, depth ) +-- is_child_of( table, table ) +CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) +RETURNS TEXT AS $$ + SELECT is_parent_of( $2, $1, $3, $4 ); +$$ LANGUAGE SQL; + +-- isnt_parent_of( schema, table, schema, table, depth, description ) +-- isnt_parent_of( schema, table, schema, table, depth ) +-- isnt_parent_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) +RETURNS TEXT AS $$ + SELECT ok( NOT _parent_of( $1, $2, $3, $4, $5 ), $6 ); +$$ LANGUAGE SQL; + +-- isnt_child_of( schema, table, schema, table, depth, description ) +-- isnt_child_of( schema, table, schema, table, depth ) +-- isnt_child_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) +RETURNS TEXT AS $$ + SELECT isnt_parent_of( $3, $4, $1, $2, $5, $6 ); +$$ LANGUAGE SQL; + +-- isnt_parent_of( table, table, depth, description ) +-- isnt_parent_of( table, table, depth ) +-- isnt_parent_of( table, table ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) +RETURNS TEXT AS $$ + SELECT ok( NOT _parent_of( $1, $2, $3 ), $4 ); +$$ LANGUAGE SQL; + +-- isnt_child_of( table, table, depth, description ) +-- isnt_child_of( table, table, depth ) +-- isnt_child_of( table, table ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, INT DEFAULT 1 , TEXT DEFAULT NULL ) +RETURNS TEXT AS $$ + SELECT isnt_parent_of( $2, $1, $3, $4 ); +$$ LANGUAGE SQL; From 9383d6cc60e7d1cea993ebb90eb47a4480f93a54 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 13:36:33 -0500 Subject: [PATCH 1048/1195] Update 9.4 patch for inheritance functions. --- compat/install-9.4.patch | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/compat/install-9.4.patch b/compat/install-9.4.patch index 728e1d64ef3b..0ab9929b6b76 100644 --- a/compat/install-9.4.patch +++ b/compat/install-9.4.patch @@ -18,12 +18,10 @@ -- There should have been no exception. GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, -@@ -9914,231 +9914,4 @@ - FROM generate_series(1, array_upper($1, 1)) s(i) - ORDER BY $1[i] +@@ -9916,233 +9916,6 @@ ), $2); --$$ LANGUAGE SQL immutable; -- + $$ LANGUAGE SQL immutable; + --- policies_are( schema, table, policies[], description ) -CREATE OR REPLACE FUNCTION policies_are( NAME, NAME, NAME[], TEXT ) -RETURNS TEXT AS $$ @@ -250,4 +248,7 @@ - || ' should apply to ' || upper($3) || ' command' - ); -$$ LANGUAGE sql; -+$$ LANGUAGE SQL immutable; +- + /******************** INHERITANCE ***********************************************/ + /* + * Internal function to test whether the specified table in the specified schema From 78488517e0953185622c17c5746f4b9de3509203 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 14:03:18 -0500 Subject: [PATCH 1049/1195] Remove defaults. And continue reformatting. --- sql/pgtap.sql.in | 248 +++++++++++++++++++++++++++------------ test/sql/inheritance.sql | 117 ++++++++---------- 2 files changed, 226 insertions(+), 139 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 1ceea93be735..6d501a74108f 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -10280,14 +10280,14 @@ $$ LANGUAGE sql; */ 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 + 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; @@ -10298,59 +10298,73 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _inherited( NAME ) RETURNS BOOLEAN AS $$ SELECT EXISTS( - SELECT true - FROM pg_catalog.pg_class c - WHERE c.relkind = 'r' + 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 + 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, TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT ok( - _inherited( $1, $2 ), - COALESCE( - $3, - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || ' must have children' - ) - ); +CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _inherited( $1, $2 ), + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || ' must have children' + ); $$ 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, TEXT DEFAULT NULL ) +CREATE OR REPLACE FUNCTION has_inherited_tables( NAME ) RETURNS TEXT AS $$ - SELECT ok( - _inherited( $1 ), - COALESCE( $2, 'Table ' || quote_ident( $1 ) || ' has children' ) - ); + SELECT ok( + _inherited( $1 ), + 'Table ' || quote_ident( $1 ) || ' has children' + ); $$ 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, TEXT DEFAULT NULL ) -RETURNS TEXT AS $$ - SELECT ok( - NOT _inherited( $1, $2 ), - COALESCE( - $3, - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || 'must not have children' - ) - ); +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 ) || 'must not have children' + ); $$ 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, TEXT DEFAULT NULL ) +CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME ) RETURNS TEXT AS $$ - SELECT ok( - NOT _inherited( $1 ), - COALESCE( $2, 'Table ' || quote_ident( $1 ) || 'must not have children' ) - ); + SELECT ok( + NOT _inherited( $1 ), + 'Table ' || quote_ident( $1 ) || 'must not have children' + ); $$ LANGUAGE SQL; /* @@ -10358,7 +10372,7 @@ $$ LANGUAGE SQL; * the other schema-qualified table. The integer value is the length of the * inheritance chain: a direct child has a 1 value. */ -CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, NAME, NAME, INT DEFAULT 1 ) +CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, NAME, NAME, INT ) RETURNS BOOLEAN AS $$ WITH RECURSIVE inheritance_chain AS ( -- select the parent tuple @@ -10373,7 +10387,7 @@ RETURNS BOOLEAN AS $$ AND n1.nspname = $1 ) UNION - -- select the childrens + -- select the children SELECT i.inhrelid AS child_id, p.inheritance_level + 1 AS inheritance_level FROM pg_catalog.pg_inherits i @@ -10388,7 +10402,7 @@ RETURNS BOOLEAN AS $$ AND n1.nspname = $3 ) ) - SELECT EXISTS( + SELECT EXISTS( SELECT * FROM inheritance_chain WHERE inheritance_level = $5 @@ -10409,14 +10423,14 @@ $$ LANGUAGE SQL STRICT; */ CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, NAME, NAME ) RETURNS BOOLEAN AS $$ - SELECT _parent_of( $1, $2, $3, $4, 1 ); + SELECT _parent_of( $1, $2, $3, $4, 1 ); $$ LANGUAGE SQL; /* * Internal function to check if not-qualified tables * within the search_path are connected by an inheritance chain. */ -CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, INT DEFAULT 1 ) +CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, INT ) RETURNS BOOLEAN AS $$ WITH RECURSIVE inheritance_chain AS ( -- select the parent tuple @@ -10429,7 +10443,7 @@ RETURNS BOOLEAN AS $$ AND pg_catalog.pg_table_is_visible( c1.oid ) ) UNION - -- select the childrens + -- select the children SELECT i.inhrelid AS child_id, p.inheritance_level + 1 AS inheritance_level FROM pg_catalog.pg_inherits i @@ -10456,79 +10470,165 @@ RETURNS BOOLEAN AS $$ $$ LANGUAGE SQL; -- is_parent_of( schema, table, schema, table, depth, description ) +CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _parent_of( $1, $2, $3, $4, $5 ), $6 ); +$$ LANGUAGE SQL; + -- is_parent_of( schema, table, schema, table, depth ) --- is_parent_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) +CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME, INT ) RETURNS TEXT AS $$ SELECT ok( _parent_of( $1, $2, $3, $4, $5 ), - COALESCE( - $6, - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' is a ' || $5 || '-parent of ' - || quote_ident( $3 ) || '.' || quote_ident( $4 ) - ) + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' is a ' || $5 || '-parent of ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) + ); +$$ LANGUAGE SQL; + +-- is_parent_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT ok( + _parent_of( $1, $2, $3, $4, 1 ), + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' is a ' || '1-parent of ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) ); $$ LANGUAGE SQL; -- is_child_of( schema, table, schema, table, depth, description ) +CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT is_parent_of($3, $4, $1, $2, $5, $6); +$$ LANGUAGE SQL; + -- is_child_of( schema, table, schema, table, depth ) --- is_child_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) +CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME, INT ) RETURNS TEXT AS $$ SELECT is_parent_of( $3, $4, $1, $2, $5, - COALESCE( $6, - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' is a ' || $5 || '-child of ' - || quote_ident( $3 ) || '.' || quote_ident( $4 ) - ) + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' is a ' || $5 || '-child of ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) + ); +$$ LANGUAGE SQL; + +-- is_child_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME ) +RETURNS TEXT AS $$ + SELECT is_parent_of( + $3, $4, $1, $2, 1, + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' is a ' || '1-child of ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) ); $$ LANGUAGE SQL; + -- is_parent_of( table, table, depth, description ) +CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( _parent_of( $1, $2, $3 ), $4 ); +$$ LANGUAGE SQL; + -- is_parent_of( table, table, depth ) +CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, INT ) +RETURNS TEXT AS $$ + SELECT ok( _parent_of( $1, $2, $3 ), NULL ); +$$ LANGUAGE SQL; + -- is_parent_of( table, table ) -CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) +CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME ) RETURNS TEXT AS $$ - SELECT ok( _parent_of( $1, $2, $3 ), $4 ); + SELECT ok( _parent_of( $1, $2, 1 ), NULL ); $$ LANGUAGE SQL; -- is_child_of( table, table, depth, description ) +CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT is_parent_of( $2, $1, $3, $4 ); +$$ LANGUAGE SQL; + -- is_child_of( table, table, depth ) +CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, INT ) +RETURNS TEXT AS $$ + SELECT is_parent_of( $2, $1, $3, NULL ); +$$ LANGUAGE SQL; + -- is_child_of( table, table ) -CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) +CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME ) RETURNS TEXT AS $$ - SELECT is_parent_of( $2, $1, $3, $4 ); + SELECT is_parent_of( $2, $1, 1, NULL ); $$ LANGUAGE SQL; -- isnt_parent_of( schema, table, schema, table, depth, description ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _parent_of( $1, $2, $3, $4, $5 ), $6 ); +$$ LANGUAGE SQL; + -- isnt_parent_of( schema, table, schema, table, depth ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME, INT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _parent_of( $1, $2, $3, $4, $5 ), NULL ); +$$ LANGUAGE SQL; + -- isnt_parent_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME ) RETURNS TEXT AS $$ - SELECT ok( NOT _parent_of( $1, $2, $3, $4, $5 ), $6 ); + SELECT ok( NOT _parent_of( $1, $2, $3, $4, 1 ), NULL ); $$ LANGUAGE SQL; -- isnt_child_of( schema, table, schema, table, depth, description ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT isnt_parent_of( $3, $4, $1, $2, $5, $6 ); +$$ LANGUAGE SQL; + -- isnt_child_of( schema, table, schema, table, depth ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME, INT ) +RETURNS TEXT AS $$ + SELECT isnt_parent_of( $3, $4, $1, $2, $5, NULL ); +$$ LANGUAGE SQL; + -- isnt_child_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME ) RETURNS TEXT AS $$ - SELECT isnt_parent_of( $3, $4, $1, $2, $5, $6 ); + SELECT isnt_parent_of( $3, $4, $1, $2, 1, NULL ); $$ LANGUAGE SQL; -- isnt_parent_of( table, table, depth, description ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _parent_of( $1, $2, $3 ), $4 ); +$$ LANGUAGE SQL; + -- isnt_parent_of( table, table, depth ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, INT ) +RETURNS TEXT AS $$ + SELECT ok( NOT _parent_of( $1, $2, $3 ), NULL ); +$$ LANGUAGE SQL; + -- isnt_parent_of( table, table ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, INT DEFAULT 1, TEXT DEFAULT NULL ) +CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME ) RETURNS TEXT AS $$ - SELECT ok( NOT _parent_of( $1, $2, $3 ), $4 ); + SELECT ok( NOT _parent_of( $1, $2, 1 ), NULL ); $$ LANGUAGE SQL; -- isnt_child_of( table, table, depth, description ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, INT, TEXT ) +RETURNS TEXT AS $$ + SELECT isnt_parent_of( $2, $1, $3, $4 ); +$$ LANGUAGE SQL; + -- isnt_child_of( table, table, depth ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, INT ) +RETURNS TEXT AS $$ + SELECT isnt_parent_of( $2, $1, $3, NULL ); +$$ LANGUAGE SQL; + -- isnt_child_of( table, table ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, INT DEFAULT 1 , TEXT DEFAULT NULL ) +CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME ) RETURNS TEXT AS $$ - SELECT isnt_parent_of( $2, $1, $3, $4 ); + SELECT isnt_parent_of( $2, $1, 1, NULL ); $$ LANGUAGE SQL; diff --git a/test/sql/inheritance.sql b/test/sql/inheritance.sql index 95834562ff6f..b96cd508599f 100644 --- a/test/sql/inheritance.sql +++ b/test/sql/inheritance.sql @@ -14,125 +14,112 @@ CREATE TABLE hide.h_parent( id INT PRIMARY KEY ); CREATE TABLE hide.h_child1( id INT PRIMARY KEY ) INHERITS ( hide.h_parent ); CREATE TABLE hide.h_child2( id INT PRIMARY KEY ) INHERITS ( hide.h_child1 ); - -- test has_inhereted_tables SELECT * FROM check_test( - has_inherited_tables( 'hide'::name, 'h_parent'::name ) - , true -- expected value - , 'hide.h_parent is supposed to be parent of other tables' - ); + has_inherited_tables( 'hide'::name, 'h_parent'::name ), + true, -- expected value + 'hide.h_parent is supposed to be parent of other tables' +); -- test hasnt_inherited_tables SELECT * FROM check_test( - hasnt_inherited_tables( 'hide'::name, 'h_child2'::name ) - , true -- expected value - , 'hide.h_child2 is not supposed to have children' + hasnt_inherited_tables( 'hide'::name, 'h_child2'::name ), + true, -- expected value + 'hide.h_child2 is not supposed to have children' ); - -- test has_inhereted_tables SELECT * FROM check_test( - has_inherited_tables( 'parent'::name ) - , true -- expected value - , 'public.parent is supposed to be parent of other tables' + has_inherited_tables( 'parent'::name ), + true, -- expected value + 'public.parent is supposed to be parent of other tables' ); -- test hasnt_inherited_tables SELECT * FROM check_test( - hasnt_inherited_tables( 'child2'::name ) - , true -- expected value - , 'public.child2 is supposed not to have children' + hasnt_inherited_tables( 'child2'::name ), + true, -- expected value + 'public.child2 is supposed not to have children' ); - - - SELECT * FROM check_test( - is_parent_of( 'hide', 'h_parent', 'hide', 'h_child1', 1, 'Test hide.h_parent->hide.h_child1' ) - , true -- expected value - , 'hide.h_parent direct is father of hide.h_child1' + is_parent_of( 'hide', 'h_parent', 'hide', 'h_child1', 1, 'Test hide.h_parent->hide.h_child1' ), + true, -- expected value + 'hide.h_parent direct is father of hide.h_child1' ); - SELECT * FROM check_test( - is_parent_of( 'hide', 'h_parent', 'hide', 'h_child1', 1 ) - , true -- expected value - , 'hide.h_parent direct is father of hide.h_child1' + is_parent_of( 'hide', 'h_parent', 'hide', 'h_child1', 1 ), + true, -- expected value + 'hide.h_parent direct is father of hide.h_child1' ); SELECT * FROM check_test( - is_parent_of( 'hide', 'h_child1', 'hide', 'h_child2', 1 ) - , true -- expected value - , 'hide.h_child1 direct is father of hide.h_child2' + is_parent_of( 'hide', 'h_child1', 'hide', 'h_child2', 1 ), + true, -- expected value + 'hide.h_child1 direct is father of hide.h_child2' ); SELECT * FROM check_test( - is_parent_of( 'hide', 'h_parent', 'hide', 'h_child2', 2 ) - , true -- expected value - , 'hide.h_parent is father of hide.h_child2' + is_parent_of( 'hide', 'h_parent', 'hide', 'h_child2', 2 ), + true, -- expected value + 'hide.h_parent is father of hide.h_child2' ); SELECT * FROM check_test( - is_parent_of( 'parent', 'child1' ) - , true -- expected value - , 'child1 inherits from parent' + is_parent_of( 'parent', 'child1' ), + true, -- expected value + 'child1 inherits from parent' ); - SELECT * FROM check_test( - is_parent_of( 'hide'::name, 'h_parent'::name, 'public'::name, 'child1'::name ) - , false -- expected value - , 'hide.h_parent is not father of public.child1' + is_parent_of( 'hide'::name, 'h_parent'::name, 'public'::name, 'child1'::name ), + false, -- expected value + 'hide.h_parent is not father of public.child1' ); SELECT * FROM check_test( - is_parent_of( 'public'::name, 'parent'::name, 'public'::name, 'child1'::name ) - , true -- expected value - , 'public.parent is not father of public.child1' + is_parent_of( 'public'::name, 'parent'::name, 'public'::name, 'child1'::name ), + true, -- expected value + 'public.parent is not father of public.child1' ); - - SELECT * FROM check_test( - isnt_child_of( 'hide'::name, 'h_parent'::name, 'public'::name, 'child1'::name ) - , true -- expected value - , 'hide.h_parent is not father of public.child1' + isnt_child_of( 'hide'::name, 'h_parent'::name, 'public'::name, 'child1'::name ), + true, -- expected value + 'hide.h_parent is not father of public.child1' ); SELECT * FROM check_test( - isnt_parent_of( 'parent', 'child1' ) - , false -- expected value - , 'parent is not father of public.child1' + isnt_parent_of( 'parent', 'child1' ), + false, -- expected value + 'parent is not father of public.child1' ); - SELECT * FROM check_test( - isnt_child_of( 'parent', 'child1' ) - , true -- expected value - , 'parent is not child1' + isnt_child_of( 'parent', 'child1' ), + true, -- expected value + 'parent is not child1' ); SELECT * FROM check_test( - isnt_child_of( 'child1', 'parent' ) - , false -- expected value - , 'child1 inherits from parent' + isnt_child_of( 'child1', 'parent' ), + false, -- expected value + 'child1 inherits from parent' ); - SELECT * FROM check_test( - isnt_child_of( 'hide'::name, 'h_parent'::name, 'hide'::name, 'h_child1'::name ) - , true -- expected value - , 'hide.h_parent is not child of hide.h_child1' + isnt_child_of( 'hide'::name, 'h_parent'::name, 'hide'::name, 'h_child1'::name ), + true, -- expected value + 'hide.h_parent is not child of hide.h_child1' ); SELECT * FROM check_test( - isnt_child_of( 'hide'::name, 'h_child1'::name, 'hide'::name, 'h_parent'::name ) - , false -- expected value - , 'hide.h_child1 inherits from hide.h_parent' + isnt_child_of( 'hide'::name, 'h_child1'::name, 'hide'::name, 'h_parent'::name ), + false, -- expected value + 'hide.h_child1 inherits from hide.h_parent' ); - - /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From ca7b05de186a59d3ed886bee35eae3913115ee46 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 14:49:55 -0500 Subject: [PATCH 1050/1195] Fully test has_inheritance functions. --- sql/pgtap.sql.in | 16 +-- test/expected/inheritance.out | 104 +++++++++++++++--- test/sql/inheritance.sql | 201 +++++++++++++++++++++++++++++++--- 3 files changed, 280 insertions(+), 41 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 6d501a74108f..fb0af85956c6 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -10318,7 +10318,7 @@ CREATE OR REPLACE FUNCTION has_inherited_tables( NAME, NAME ) RETURNS TEXT AS $$ SELECT ok( _inherited( $1, $2 ), - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || ' must have children' + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || ' should have children' ); $$ LANGUAGE SQL; @@ -10333,7 +10333,7 @@ CREATE OR REPLACE FUNCTION has_inherited_tables( NAME ) RETURNS TEXT AS $$ SELECT ok( _inherited( $1 ), - 'Table ' || quote_ident( $1 ) || ' has children' + 'Table ' || quote_ident( $1 ) || ' should have children' ); $$ LANGUAGE SQL; @@ -10348,7 +10348,7 @@ 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 ) || 'must not have children' + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || ' should not have children' ); $$ LANGUAGE SQL; @@ -10363,7 +10363,7 @@ CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME ) RETURNS TEXT AS $$ SELECT ok( NOT _inherited( $1 ), - 'Table ' || quote_ident( $1 ) || 'must not have children' + 'Table ' || quote_ident( $1 ) || ' should not have children' ); $$ LANGUAGE SQL; @@ -10377,7 +10377,7 @@ RETURNS BOOLEAN AS $$ WITH RECURSIVE inheritance_chain AS ( -- select the parent tuple SELECT i.inhrelid AS child_id, 1 AS inheritance_level - FROM pg_catalog.pg_inherits i + FROM pg_catalog.pg_inherits i WHERE i.inhparent = ( SELECT c1.oid FROM pg_catalog.pg_class c1 @@ -10390,9 +10390,9 @@ RETURNS BOOLEAN AS $$ -- select the children SELECT i.inhrelid AS child_id, p.inheritance_level + 1 AS inheritance_level - FROM pg_catalog.pg_inherits i - JOIN inheritance_chain p - ON p.child_id = i.inhparent + FROM pg_catalog.pg_inherits i + JOIN inheritance_chain p + ON p.child_id = i.inhparent WHERE i.inhrelid = ( SELECT c1.oid FROM pg_catalog.pg_class c1 diff --git a/test/expected/inheritance.out b/test/expected/inheritance.out index 77ee597c9d40..b5ab0a268140 100644 --- a/test/expected/inheritance.out +++ b/test/expected/inheritance.out @@ -1,19 +1,87 @@ \unset ECHO -1..17 -ok 1 - hide.h_parent is supposed to be parent of other tables should pass -ok 2 - hide.h_child2 is not supposed to have children should pass -ok 3 - public.parent is supposed to be parent of other tables should pass -ok 4 - public.child2 is supposed not to have children should pass -ok 5 - hide.h_parent direct is father of hide.h_child1 should pass -ok 6 - hide.h_parent direct is father of hide.h_child1 should pass -ok 7 - hide.h_child1 direct is father of hide.h_child2 should pass -ok 8 - hide.h_parent is father of hide.h_child2 should pass -ok 9 - child1 inherits from parent should pass -ok 10 - hide.h_parent is not father of public.child1 should fail -ok 11 - public.parent is not father of public.child1 should pass -ok 12 - hide.h_parent is not father of public.child1 should pass -ok 13 - parent is not father of public.child1 should fail -ok 14 - parent is not child1 should pass -ok 15 - child1 inherits from parent should fail -ok 16 - hide.h_parent is not child of hide.h_child1 should pass -ok 17 - hide.h_child1 inherits from hide.h_parent should fail +1..85 +ok 1 - has_inherited_tables(sch, tab, desc) should pass +ok 2 - has_inherited_tables(sch, tab, desc) should have the proper description +ok 3 - has_inherited_tables(sch, tab, desc) should have the proper diagnostics +ok 4 - has_inherited_tables(sch, tab, desc) fail should fail +ok 5 - has_inherited_tables(sch, tab, desc) fail should have the proper description +ok 6 - has_inherited_tables(sch, tab, desc) fail should have the proper diagnostics +ok 7 - has_inherited_tables(sch, nonesuch, desc) should fail +ok 8 - has_inherited_tables(sch, nonesuch, desc) should have the proper description +ok 9 - has_inherited_tables(sch, nonesuch, desc) should have the proper diagnostics +ok 10 - has_inherited_tables(sch, tab) should pass +ok 11 - has_inherited_tables(sch, tab) should have the proper description +ok 12 - has_inherited_tables(sch, tab) should have the proper diagnostics +ok 13 - has_inherited_tables(sch, tab) fail should fail +ok 14 - has_inherited_tables(sch, tab) fail should have the proper description +ok 15 - has_inherited_tables(sch, tab) fail should have the proper diagnostics +ok 16 - has_inherited_tables(sch, nonesuch) should fail +ok 17 - has_inherited_tables(sch, nonesuch) should have the proper description +ok 18 - has_inherited_tables(sch, nonesuch) should have the proper diagnostics +ok 19 - has_inherited_tables(tab, desc) should pass +ok 20 - has_inherited_tables(tab, desc) should have the proper description +ok 21 - has_inherited_tables(tab, desc) should have the proper diagnostics +ok 22 - has_inherited_tables(tab, desc) fail should fail +ok 23 - has_inherited_tables(tab, desc) fail should have the proper description +ok 24 - has_inherited_tables(tab, desc) fail should have the proper diagnostics +ok 25 - has_inherited_tables(nonesuch, desc) should fail +ok 26 - has_inherited_tables(nonesuch, desc) should have the proper description +ok 27 - has_inherited_tables(nonesuch, desc) should have the proper diagnostics +ok 28 - has_inherited_tables(tab) should pass +ok 29 - has_inherited_tables(tab) should have the proper description +ok 30 - has_inherited_tables(tab) should have the proper diagnostics +ok 31 - has_inherited_tables(tab) fail should fail +ok 32 - has_inherited_tables(tab) fail should have the proper description +ok 33 - has_inherited_tables(tab) fail should have the proper diagnostics +ok 34 - has_inherited_tables(nonesuch) should fail +ok 35 - has_inherited_tables(nonesuch) should have the proper description +ok 36 - has_inherited_tables(nonesuch) should have the proper diagnostics +ok 37 - hasnt_inherited_tables(sch, tab, desc) should pass +ok 38 - hasnt_inherited_tables(sch, tab, desc) should have the proper description +ok 39 - hasnt_inherited_tables(sch, tab, desc) should have the proper diagnostics +ok 40 - hasnt_inherited_tables(sch, tab, desc) fail should fail +ok 41 - hasnt_inherited_tables(sch, tab, desc) fail should have the proper description +ok 42 - hasnt_inherited_tables(sch, tab, desc) fail should have the proper diagnostics +ok 43 - hasnt_inherited_tables(sch, nonesuch, desc) should pass +ok 44 - hasnt_inherited_tables(sch, nonesuch, desc) should have the proper description +ok 45 - hasnt_inherited_tables(sch, nonesuch, desc) should have the proper diagnostics +ok 46 - hasnt_inherited_tables(sch, tab) should pass +ok 47 - hasnt_inherited_tables(sch, tab) should have the proper description +ok 48 - hasnt_inherited_tables(sch, tab) should have the proper diagnostics +ok 49 - hasnt_inherited_tables(sch, tab) fail should fail +ok 50 - hasnt_inherited_tables(sch, tab) fail should have the proper description +ok 51 - hasnt_inherited_tables(sch, tab) fail should have the proper diagnostics +ok 52 - hasnt_inherited_tables(sch, nonesuch) should pass +ok 53 - hasnt_inherited_tables(sch, nonesuch) should have the proper description +ok 54 - hasnt_inherited_tables(sch, nonesuch) should have the proper diagnostics +ok 55 - hasnt_inherited_tables(tab, desc) should pass +ok 56 - hasnt_inherited_tables(tab, desc) should have the proper description +ok 57 - hasnt_inherited_tables(tab, desc) should have the proper diagnostics +ok 58 - hasnt_inherited_tables(tab, desc) fail should fail +ok 59 - hasnt_inherited_tables(tab, desc) fail should have the proper description +ok 60 - hasnt_inherited_tables(tab, desc) fail should have the proper diagnostics +ok 61 - hasnt_inherited_tables(nonesuch, desc) should pass +ok 62 - hasnt_inherited_tables(nonesuch, desc) should have the proper description +ok 63 - hasnt_inherited_tables(nonesuch, desc) should have the proper diagnostics +ok 64 - hasnt_inherited_tables(tab) should pass +ok 65 - hasnt_inherited_tables(tab) should have the proper description +ok 66 - hasnt_inherited_tables(tab) should have the proper diagnostics +ok 67 - hasnt_inherited_tables(tab) fail should fail +ok 68 - hasnt_inherited_tables(tab) fail should have the proper description +ok 69 - hasnt_inherited_tables(tab) fail should have the proper diagnostics +ok 70 - hasnt_inherited_tables(nonesuch) should pass +ok 71 - hasnt_inherited_tables(nonesuch) should have the proper description +ok 72 - hasnt_inherited_tables(nonesuch) should have the proper diagnostics +ok 73 - hide.h_parent direct is father of hide.h_child1 should pass +ok 74 - hide.h_parent direct is father of hide.h_child1 should pass +ok 75 - hide.h_child1 direct is father of hide.h_child2 should pass +ok 76 - hide.h_parent is father of hide.h_child2 should pass +ok 77 - child1 inherits from parent should pass +ok 78 - hide.h_parent is not father of public.child1 should fail +ok 79 - public.parent is not father of public.child1 should pass +ok 80 - hide.h_parent is not father of public.child1 should pass +ok 81 - parent is not father of public.child1 should fail +ok 82 - parent is not child1 should pass +ok 83 - child1 inherits from parent should fail +ok 84 - hide.h_parent is not child of hide.h_child1 should pass +ok 85 - hide.h_child1 inherits from hide.h_parent should fail diff --git a/test/sql/inheritance.sql b/test/sql/inheritance.sql index b96cd508599f..45edcebbfb11 100644 --- a/test/sql/inheritance.sql +++ b/test/sql/inheritance.sql @@ -1,6 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan( 17 ); +SELECT plan( 85 ); +--SELECT * FROM no_plan(); SET client_min_messages = warning; -- Create inherited tables @@ -16,32 +17,202 @@ CREATE TABLE hide.h_child2( id INT PRIMARY KEY ) INHERITS ( hide.h_child1 ); -- test has_inhereted_tables SELECT * FROM check_test( - has_inherited_tables( 'hide'::name, 'h_parent'::name ), - true, -- expected value - 'hide.h_parent is supposed to be parent of other tables' + has_inherited_tables( 'hide', 'h_parent', 'Gimme inheritance' ), + true, + 'has_inherited_tables(sch, tab, desc)', + 'Gimme inheritance', + '' ); --- test hasnt_inherited_tables SELECT * FROM check_test( - hasnt_inherited_tables( 'hide'::name, 'h_child2'::name ), - true, -- expected value - 'hide.h_child2 is not supposed to have children' + has_inherited_tables( 'hide', 'h_child2', 'Gimme inheritance' ), + false, + 'has_inherited_tables(sch, tab, desc) fail', + 'Gimme inheritance', + '' +); + +SELECT * FROM check_test( + has_inherited_tables( 'hide', 'nonesuch', 'Gimme inheritance' ), + false, + 'has_inherited_tables(sch, nonesuch, desc)', + 'Gimme inheritance', + '' +); + +SELECT * FROM check_test( + has_inherited_tables( 'hide', 'h_parent'::name ), + true, + 'has_inherited_tables(sch, tab)', + 'Table hide.h_parent should have children', + '' +); + +SELECT * FROM check_test( + has_inherited_tables( 'hide', 'h_child2'::name ), + false, + 'has_inherited_tables(sch, tab) fail', + 'Table hide.h_child2 should have children', + '' +); + +SELECT * FROM check_test( + has_inherited_tables( 'hide', 'nonesuch'::name ), + false, + 'has_inherited_tables(sch, nonesuch)', + 'Table hide.nonesuch should have children', + '' +); + +SELECT * FROM check_test( + has_inherited_tables( 'parent', 'Gimme more' ), + true, + 'has_inherited_tables(tab, desc)', + 'Gimme more', + '' ); --- test has_inhereted_tables SELECT * FROM check_test( - has_inherited_tables( 'parent'::name ), - true, -- expected value - 'public.parent is supposed to be parent of other tables' + has_inherited_tables( 'child2', 'Gimme more' ), + false, + 'has_inherited_tables(tab, desc) fail', + 'Gimme more', + '' +); + +SELECT * FROM check_test( + has_inherited_tables( 'nonesuch', 'Gimme more' ), + false, + 'has_inherited_tables(nonesuch, desc)', + 'Gimme more', + '' +); + +SELECT * FROM check_test( + has_inherited_tables( 'parent' ), + true, + 'has_inherited_tables(tab)', + 'Table parent should have children', + '' +); + +SELECT * FROM check_test( + has_inherited_tables( 'child2' ), + false, + 'has_inherited_tables(tab) fail', + 'Table child2 should have children', + '' +); + +SELECT * FROM check_test( + has_inherited_tables( 'nonesuch' ), + false, + 'has_inherited_tables(nonesuch)', + 'Table nonesuch should have children', + '' ); -- test hasnt_inherited_tables SELECT * FROM check_test( - hasnt_inherited_tables( 'child2'::name ), - true, -- expected value - 'public.child2 is supposed not to have children' + hasnt_inherited_tables( 'hide', 'h_child2', 'Gimme inheritance' ), + true, + 'hasnt_inherited_tables(sch, tab, desc)', + 'Gimme inheritance', + '' +); + +SELECT * FROM check_test( + hasnt_inherited_tables( 'hide', 'h_child1', 'Gimme inheritance' ), + false, + 'hasnt_inherited_tables(sch, tab, desc) fail', + 'Gimme inheritance', + '' +); + +SELECT * FROM check_test( + hasnt_inherited_tables( 'hide', 'nonesuch', 'Gimme inheritance' ), + true, + 'hasnt_inherited_tables(sch, nonesuch, desc)', + 'Gimme inheritance', + '' +); + +SELECT * FROM check_test( + hasnt_inherited_tables( 'hide', 'h_child2'::name ), + true, + 'hasnt_inherited_tables(sch, tab)', + 'Table hide.h_child2 should not have children', + '' +); + +SELECT * FROM check_test( + hasnt_inherited_tables( 'hide', 'h_child1'::name ), + false, + 'hasnt_inherited_tables(sch, tab) fail', + 'Table hide.h_child1 should not have children', + '' +); + +SELECT * FROM check_test( + hasnt_inherited_tables( 'hide', 'nonesuch'::name ), + true, + 'hasnt_inherited_tables(sch, nonesuch)', + 'Table hide.nonesuch should not have children', + '' +); + +SELECT * FROM check_test( + hasnt_inherited_tables( 'child2', 'Gimme inheritance' ), + true, + 'hasnt_inherited_tables(tab, desc)', + 'Gimme inheritance', + '' +); + +SELECT * FROM check_test( + hasnt_inherited_tables( 'child1', 'Gimme inheritance' ), + false, + 'hasnt_inherited_tables(tab, desc) fail', + 'Gimme inheritance', + '' ); +SELECT * FROM check_test( + hasnt_inherited_tables( 'nonesuch', 'Gimme inheritance' ), + true, + 'hasnt_inherited_tables(nonesuch, desc)', + 'Gimme inheritance', + '' +); + +SELECT * FROM check_test( + hasnt_inherited_tables( 'child2' ), + true, + 'hasnt_inherited_tables(tab)', + 'Table child2 should not have children', + '' +); + +SELECT * FROM check_test( + hasnt_inherited_tables( 'child1' ), + false, + 'hasnt_inherited_tables(tab) fail', + 'Table child1 should not have children', + '' +); + +SELECT * FROM check_test( + hasnt_inherited_tables( 'nonesuch' ), + true, + 'hasnt_inherited_tables(nonesuch)', + 'Table nonesuch should not have children', + '' +); + + + + + SELECT * FROM check_test( is_parent_of( 'hide', 'h_parent', 'hide', 'h_child1', 1, 'Test hide.h_parent->hide.h_child1' ), true, -- expected value From defa22951ced86df1f307e5197527369e3d8574c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 14:54:56 -0500 Subject: [PATCH 1051/1195] Document casting for has_inherted_tables(). --- doc/pgtap.mmd | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index e9c539702489..c2776202ca28 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2942,11 +2942,14 @@ specified materialized view does *not* exist. `:table` : Name of the table that must have children. -`:description +`:description` : A description of the test. -This function checks that the specified table has at least one other table that -inherits from it. +This function checks that the specified table has other tables that inherit from +it. If you find that the function call confuses the table name for a +description, cast the table to the `NAME` type: + + SELECT has_inherited_tables('myschema', 'sometable'::NAME); ### `hasnt_inherited_tables()` @@ -2963,11 +2966,15 @@ inherits from it. `:table` : Name of the table that must not have children. -`:description +`:description` : A description of the test. This function checks that the specified table has no tables inheriting from it. -It is the opposite of the function `has_inherited_tables()`. +It is the opposite of the function `has_inherited_tables()`. If you find that +the function call confuses the table name for a description, cast the table to +the `NAME` type: + + SELECT hasnt_inherited_tables('myschema', 'sometable'::NAME); ### `is_parent_of()` From 46443d1b76751ec42c98b1355a1d5c9c0cb2f286 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 16:12:44 -0500 Subject: [PATCH 1052/1195] Rename is_parent_of to is_ancestor_of. And test it fully. Also, if there is no depth, allow it to be any depth. --- doc/pgtap.mmd | 175 ++++++++++++----------- sql/pgtap.sql.in | 260 ++++++++++++++++++---------------- test/expected/inheritance.out | 99 +++++++++++-- test/sql/inheritance.sql | 247 +++++++++++++++++++++++++------- 4 files changed, 510 insertions(+), 271 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index c2776202ca28..53ff0124a8b0 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2976,139 +2976,146 @@ the `NAME` type: SELECT hasnt_inherited_tables('myschema', 'sometable'::NAME); -### `is_parent_of()` +### `is_ancestor_of()` - SELECT is_parent_of( :parent_schema, :parent_table, :child_schema, :child_table, :depth, :description ); - SELECT is_parent_of( :parent_schema, :parent_table, :child_schema, :child_table, :depth ); - SELECT is_parent_of( :parent_schema, :parent_table, :child_schema, :child_table ); - SELECT is_parent_of( :parent_table, :child_table, :depth, :description ); - SELECT is_parent_of( :parent_table, :child_table, :depth ); - SELECT is_parent_of( :parent_table, :child_table ); + SELECT is_ancestor_of( :ancestor_schema, :ancestor_table, :descendent_schema, :descendent_table, :depth, :description ); + SELECT is_ancestor_of( :ancestor_schema, :ancestor_table, :descendent_schema, :descendent_table, :depth ); + SELECT is_ancestor_of( :ancestor_schema, :ancestor_table, :descendent_schema, :descendent_table, :description ); + SELECT is_ancestor_of( :ancestor_schema, :ancestor_table, :descendent_schema, :descendent_table ); + SELECT is_ancestor_of( :ancestor_table, :descendent_table, :depth, :description ); + SELECT is_ancestor_of( :ancestor_table, :descendent_table, :depth ); + SELECT is_ancestor_of( :ancestor_table, :descendent_table, :description ); + SELECT is_ancestor_of( :ancestor_table, :descendent_table ); **Parameters** -`:parent_schema` -: Name of the schema in which the parent table must be found. +`:ancestor_schema` +: Name of the schema in which the ancestor table must be found. -`:parent_table` -: Name of the table that plays the parent role. +`:ancestor_table` +: Name of the table that plays the ancestor role. -`:child_schema` -: Name of the schema in which the child table must be. +`:descendent_schema` +: Name of the schema in which the descendent table must be. -`:child_table` -: Name of the table that is supposed to be child of the other one. +`:descendent_table` +: Name of the table that is supposed to be descendent of the other one. `:description` : Description of the test. `:depth` -: The position in the inheritance chain of the child table, by default 1 (a - direct child). +: The position in the inheritance chain of the descendent table. + +This function checks if the table marked as "ancestor" is effectively a table +from which the "descendent" table inherits --- that there is an inheritance +chain between the two tables. The optional depth argument specifies the length +of the inheritance chain between the tables; if not specified, the inheritance +distance may be of any length. + + If you find that the function call seems to be getting confused, cast the + sequence to the `NAME` type: -This function checks if the table marked as "parent" is effectively a table from -which the "child" table inherits from, that is there is an inheritance chain -between the two tables. The depth argument allows to specify the level in the -inheritance chain in which the child table must be found. + SELECT has_sequence('myschema', 'ancestor', 'myschema', 'descendent'::NAME); -### `isnt_parent_of()` +### `isnt_ancestor_of()` - SELECT isnt_parent_of( :parent_schema, :parent_table, :child_schema, :child_table, :depth, :description ); - SELECT isnt_parent_of( :parent_schema, :parent_table, :child_schema, :child_table, :depth ); - SELECT isnt_parent_of( :parent_schema, :parent_table, :child_schema, :child_table ); - SELECT isnt_parent_of( :parent_table, :child_table, :depth, :description ); - SELECT isnt_parent_of( :parent_table, :child_table, :depth ); - SELECT isnt_parent_of( :parent_table, :child_table ); + SELECT isnt_ancestor_of( :ancestor_schema, :ancestor_table, :descendent_schema, :descendent_table, :depth, :description ); + SELECT isnt_ancestor_of( :ancestor_schema, :ancestor_table, :descendent_schema, :descendent_table, :depth ); + SELECT isnt_ancestor_of( :ancestor_schema, :ancestor_table, :descendent_schema, :descendent_table ); + SELECT isnt_ancestor_of( :ancestor_table, :descendent_table, :depth, :description ); + SELECT isnt_ancestor_of( :ancestor_table, :descendent_table, :depth ); + SELECT isnt_ancestor_of( :ancestor_table, :descendent_table ); **Parameters** -`:parent_schema` -: Name of the schema in which the parent table must be found. +`:ancestor_schema` +: Name of the schema in which the ancestor table must be found. -`:parent_table` -: Name of the table that plays the parent role. +`:ancestor_table` +: Name of the table that plays the ancestor role. -`:child_schema` -: Name of the schema in which the child table must be. +`:descendent_schema` +: Name of the schema in which the descendent table must be. -`:child_table` -: Name of the table that is supposed to be child of the other one. +`:descendent_table` +: Name of the table that is supposed to be descendent of the other one. `:description` : Description of the test. `:depth` -: The position within the inheritance chain where the child table is expected +: The position within the inheritance chain where the descendent table is expected to be (by default 1). -This function ensures that the table marked as "parent" is not a table from -which "child" inherits from, that there no inheritance chain between the two -tables. The function does the opposite of `is_parent_of()`. +This function ensures that the table marked as "ancestor" is not a table from +which "descendent" inherits from, that there no inheritance chain between the two +tables. The function does the opposite of `is_ancestor_of()`. -### `is_child_of()` +### `is_descendent_of()` - SELECT is_child_of( :child_schema, :child_table, :parent_schema, :parent_table, :depth, :description ); - SELECT is_child_of( :child_schema, :child_table, :parent_schema, :parent_table, :description ); - SELECT is_child_of( :child_schema, :child_table, :parent_schema, :parent_table ); - SELECT is_child_of( :child_table, :parent_table, :depth, :description ); - SELECT is_child_of( :child_table, :parent_table, :depth ); - SELECT is_child_of( :child_table, :parent_table ); + SELECT is_descendent_of( :descendent_schema, :descendent_table, :ancestor_schema, :ancestor_table, :depth, :description ); + SELECT is_descendent_of( :descendent_schema, :descendent_table, :ancestor_schema, :ancestor_table, :description ); + SELECT is_descendent_of( :descendent_schema, :descendent_table, :ancestor_schema, :ancestor_table ); + SELECT is_descendent_of( :descendent_table, :ancestor_table, :depth, :description ); + SELECT is_descendent_of( :descendent_table, :ancestor_table, :depth ); + SELECT is_descendent_of( :descendent_table, :ancestor_table ); **Parameters** -`:parent_schema` -: Name of the schema in which the parent table must be found. +`:ancestor_schema` +: Name of the schema in which the ancestor table must be found. -`:parent_table` -: Name of the table that plays the parent role. +`:ancestor_table` +: Name of the table that plays the ancestor role. -`:child_schema` -: Name of the schema in which the child table must be. +`:descendent_schema` +: Name of the schema in which the descendent table must be. -`:child_table` -: Name of the table that is supposed to be child of the other one. +`:descendent_table` +: Name of the table that is supposed to be descendent of the other one. `:description` : Description of the test. `:depth` -: The position where the child table is expected to be in the inheritance chain +: The position where the descendent table is expected to be in the inheritance chain (by default 1 for a direct son). -This function does the same of `is_parent_of()`, with swapped arguments. +This function does the same of `is_ancestor_of()`, with swapped arguments. -### `isnt_child_of()` +### `isnt_descendent_of()` - SELECT isnt_child_of( :child_schema, :child_table, :parent_schema, :parent_table, :depth, :description ); - SELECT isnt_child_of( :child_schema, :child_table, :parent_schema, :parent_table, :depth ); - SELECT isnt_child_of( :child_schema, :child_table, :parent_schema, :parent_table ); - SELECT isnt_child_of( :child_table, :parent_table, :depth, :description ); - SELECT isnt_child_of( :child_table, :parent_table, :depth ); - SELECT isnt_child_of( :child_table, :parent_table ); + SELECT isnt_descendent_of( :descendent_schema, :descendent_table, :ancestor_schema, :ancestor_table, :depth, :description ); + SELECT isnt_descendent_of( :descendent_schema, :descendent_table, :ancestor_schema, :ancestor_table, :depth ); + SELECT isnt_descendent_of( :descendent_schema, :descendent_table, :ancestor_schema, :ancestor_table ); + SELECT isnt_descendent_of( :descendent_table, :ancestor_table, :depth, :description ); + SELECT isnt_descendent_of( :descendent_table, :ancestor_table, :depth ); + SELECT isnt_descendent_of( :descendent_table, :ancestor_table ); **Parameters** -`:parent_schema` -: Name of the schema in which the parent table must be found. +`:ancestor_schema` +: Name of the schema in which the ancestor table must be found. -`:parent_table` -: Name of the table that plays the parent role. +`:ancestor_table` +: Name of the table that plays the ancestor role. -`:child_schema` -: Name of the schema in which the child table must be. +`:descendent_schema` +: Name of the schema in which the descendent table must be. -`:child_table` -: Name of the table that is supposed to be child of the other one. +`:descendent_table` +: Name of the table that is supposed to be descendent of the other one. `:description` : Description of the test. `:depth` : The position where the inheriting table is expected to be along the - inheritance chain with respect to the parent table (by default 1 for a direct - child). + inheritance chain with respect to the ancestor table (by default 1 for a direct + descendent). -This function does the same of `isnt_parent_of()`, with swapped arguments. +This function does the same of `isnt_ancestor_of()`, with swapped arguments. ### `has_sequence()` ### @@ -3463,7 +3470,7 @@ and the description. The columns argument may be a string naming one column or expression, or an array of column names and/or expressions. For expressions, you must use lowercase for all SQL keywords and functions to properly compare to PostgreSQL's internal form of the expression. Non-functional expressions -should also be wrapped in parentheses. A few examples: +should also be wrapped in ancestorheses. A few examples: SELECT has_index( 'myschema', @@ -4955,10 +4962,10 @@ specified table is *not* partitioned, or if it does not exist. ### `is_partition_of()` ### - SELECT is_parent( :child_schema, :child, :parent_schema, :parent_table, :description ); - SELECT is_parent( :child_schema, :child, :parent_schema, :parent_table ); - SELECT is_parent( :child, :parent_table, :description ); - SELECT is_parent( :child, :parent_table ); + SELECT is_ancestor( :child_schema, :child, :ancestor_schema, :ancestor_table, :description ); + SELECT is_ancestor( :child_schema, :child, :ancestor_schema, :ancestor_table ); + SELECT is_ancestor( :child, :ancestor_table, :description ); + SELECT is_ancestor( :child, :ancestor_table ); **Parameters** @@ -4968,20 +4975,20 @@ specified table is *not* partitioned, or if it does not exist. `:child` : Name of a partition table. -`:parent_schema` +`:ancestor_schema` : Schema in which to find the partitioned table. -`:parent_table` +`:ancestor_table` : Name of a partitioned table. `:description` : A short description of the test. Tests that one table is a partition of another table. The partition (or child) -table is specified first, the partitioned (or parent) table second. Without the +table is specified first, the partitioned (or ancestor) table second. Without the schema parameters, both tables must be visible in the search path. If the test description is omitted, it will be set to "Table `:child_table` should be -a partition of `:parent_table`". Note that this test will fail if either table +a partition of `:ancestor_table`". Note that this test will fail if either table does not exist. ### `is_clustered()` ### diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index fb0af85956c6..63bbc6e0519e 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -10318,7 +10318,7 @@ 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 children' + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || ' should have descendents' ); $$ LANGUAGE SQL; @@ -10333,7 +10333,7 @@ CREATE OR REPLACE FUNCTION has_inherited_tables( NAME ) RETURNS TEXT AS $$ SELECT ok( _inherited( $1 ), - 'Table ' || quote_ident( $1 ) || ' should have children' + 'Table ' || quote_ident( $1 ) || ' should have descendents' ); $$ LANGUAGE SQL; @@ -10348,7 +10348,7 @@ 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 children' + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) || ' should not have descendents' ); $$ LANGUAGE SQL; @@ -10363,20 +10363,20 @@ CREATE OR REPLACE FUNCTION hasnt_inherited_tables( NAME ) RETURNS TEXT AS $$ SELECT ok( NOT _inherited( $1 ), - 'Table ' || quote_ident( $1 ) || ' should not have children' + 'Table ' || quote_ident( $1 ) || ' should not have descendents' ); $$ LANGUAGE SQL; /* -* Internal function to test whether the schema-qualified table is a parent of +* 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 child has a 1 value. +* inheritance chain: a direct ancestor has has a chain length of 1. */ -CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, NAME, NAME, INT ) +CREATE OR REPLACE FUNCTION _ancestor_of( NAME, NAME, NAME, NAME, INT ) RETURNS BOOLEAN AS $$ WITH RECURSIVE inheritance_chain AS ( - -- select the parent tuple - SELECT i.inhrelid AS child_id, 1 AS inheritance_level + -- 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 @@ -10387,12 +10387,12 @@ RETURNS BOOLEAN AS $$ AND n1.nspname = $1 ) UNION - -- select the children - SELECT i.inhrelid AS child_id, + -- 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.child_id = i.inhparent + ON p.descendent_id = i.inhparent WHERE i.inhrelid = ( SELECT c1.oid FROM pg_catalog.pg_class c1 @@ -10403,10 +10403,10 @@ RETURNS BOOLEAN AS $$ ) ) SELECT EXISTS( - SELECT * + SELECT true FROM inheritance_chain - WHERE inheritance_level = $5 - AND child_id = ( + 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 @@ -10415,220 +10415,238 @@ RETURNS BOOLEAN AS $$ AND n1.nspname = $3 ) ); -$$ LANGUAGE SQL STRICT; - -/* - * Internal function to test for a direct child (i.e., inheritance chain - * length of 1). - */ -CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, NAME, NAME ) -RETURNS BOOLEAN AS $$ - SELECT _parent_of( $1, $2, $3, $4, 1 ); $$ LANGUAGE SQL; /* * Internal function to check if not-qualified tables * within the search_path are connected by an inheritance chain. */ -CREATE OR REPLACE FUNCTION _parent_of( NAME, NAME, INT ) +CREATE OR REPLACE FUNCTION _ancestor_of( NAME, NAME, INT ) RETURNS BOOLEAN AS $$ WITH RECURSIVE inheritance_chain AS ( - -- select the parent tuple - SELECT i.inhrelid AS child_id, 1 AS inheritance_level + -- select the ancestor tuple + SELECT i.inhrelid AS descendent_id, 1 AS inheritance_level FROM pg_catalog.pg_inherits i - WHERE i.inhparent = ( + 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 children - SELECT i.inhrelid AS child_id, + -- 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.child_id = i.inhparent + ON p.descendent_id = i.inhparent WHERE i.inhrelid = ( SELECT c1.oid FROM pg_catalog.pg_class c1 - WHERE c1.relname = $1 + 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 * + SELECT true FROM inheritance_chain - WHERE inheritance_level = $3 - AND child_id = ( - SELECT c1.oid - FROM pg_catalog.pg_class c1 - WHERE c1.relname = $2 - AND pg_catalog.pg_table_is_visible( c1.oid ) + 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_parent_of( schema, table, schema, table, depth, description ) -CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME, INT, TEXT ) +-- 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( _parent_of( $1, $2, $3, $4, $5 ), $6 ); + SELECT ok( _ancestor_of( $1, $2, $3, $4, $5 ), $6 ); $$ LANGUAGE SQL; --- is_parent_of( schema, table, schema, table, depth ) -CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME, INT ) +-- 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( - _parent_of( $1, $2, $3, $4, $5 ), + _ancestor_of( $1, $2, $3, $4, $5 ), 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' is a ' || $5 || '-parent of ' + || ' should be ancestor ' || $5 || ' for ' || quote_ident( $3 ) || '.' || quote_ident( $4 ) ); $$ LANGUAGE SQL; --- is_parent_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, NAME, NAME ) +-- 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( - _parent_of( $1, $2, $3, $4, 1 ), + _ancestor_of( $1, $2, $3, $4, NULL ), 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' is a ' || '1-parent of ' + || ' should be an ancestor of ' || quote_ident( $3 ) || '.' || quote_ident( $4 ) ); $$ LANGUAGE SQL; --- is_child_of( schema, table, schema, table, depth, description ) -CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME, INT, TEXT ) +-- is_ancestor_of( table, table, depth, description ) +CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME, INT, TEXT ) RETURNS TEXT AS $$ - SELECT is_parent_of($3, $4, $1, $2, $5, $6); + SELECT ok( _ancestor_of( $1, $2, $3 ), $4 ); $$ LANGUAGE SQL; --- is_child_of( schema, table, schema, table, depth ) -CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME, INT ) +-- is_ancestor_of( table, table, depth ) +CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME, INT ) RETURNS TEXT AS $$ - SELECT is_parent_of( - $3, $4, $1, $2, $5, - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' is a ' || $5 || '-child of ' - || quote_ident( $3 ) || '.' || quote_ident( $4 ) + SELECT ok( + _ancestor_of( $1, $2, $3 ), + 'Table ' || quote_ident( $1 ) || ' should be ancestor ' || $3 || ' of ' || quote_ident( $2) ); $$ LANGUAGE SQL; --- is_child_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, NAME, NAME ) +-- is_ancestor_of( table, table, description ) +CREATE OR REPLACE FUNCTION is_ancestor_of( NAME, NAME, TEXT ) RETURNS TEXT AS $$ - SELECT is_parent_of( - $3, $4, $1, $2, 1, - 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' is a ' || '1-child of ' - || quote_ident( $3 ) || '.' || quote_ident( $4 ) + 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; --- is_parent_of( table, table, depth, description ) -CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, INT, TEXT ) +/* +-- 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( _parent_of( $1, $2, $3 ), $4 ); + SELECT is_ancestor_of($3, $4, $1, $2, $5, $6); $$ LANGUAGE SQL; --- is_parent_of( table, table, depth ) -CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME, INT ) +-- 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( _parent_of( $1, $2, $3 ), NULL ); + SELECT is_ancestor_of( + $3, $4, $1, $2, $5, + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' should be ancestor ' || $5 || ' for ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) + ); $$ LANGUAGE SQL; --- is_parent_of( table, table ) -CREATE OR REPLACE FUNCTION is_parent_of( NAME, NAME ) +-- is_descendent_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, NAME, NAME ) RETURNS TEXT AS $$ - SELECT ok( _parent_of( $1, $2, 1 ), NULL ); + SELECT is_ancestor_of( + $3, $4, $1, $2, 1, + 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) + || ' is a ' || '1-descendent of ' + || quote_ident( $3 ) || '.' || quote_ident( $4 ) + ); $$ LANGUAGE SQL; --- is_child_of( table, table, depth, description ) -CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, INT, TEXT ) +-- is_descendent_of( table, table, depth, description ) +CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, INT, TEXT ) RETURNS TEXT AS $$ - SELECT is_parent_of( $2, $1, $3, $4 ); + SELECT is_ancestor_of( $2, $1, $3, $4 ); $$ LANGUAGE SQL; --- is_child_of( table, table, depth ) -CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME, INT ) +-- is_descendent_of( table, table, depth ) +CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, INT ) RETURNS TEXT AS $$ - SELECT is_parent_of( $2, $1, $3, NULL ); + SELECT is_ancestor_of( $2, $1, $3, NULL ); $$ LANGUAGE SQL; --- is_child_of( table, table ) -CREATE OR REPLACE FUNCTION is_child_of( NAME, NAME ) +-- is_descendent_of( table, table ) +CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME ) RETURNS TEXT AS $$ - SELECT is_parent_of( $2, $1, 1, NULL ); + SELECT is_ancestor_of( $2, $1, 1, NULL ); $$ LANGUAGE SQL; --- isnt_parent_of( schema, table, schema, table, depth, description ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME, INT, TEXT ) +-- 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 _parent_of( $1, $2, $3, $4, $5 ), $6 ); + SELECT ok( NOT _ancestor_of( $1, $2, $3, $4, $5 ), $6 ); $$ LANGUAGE SQL; --- isnt_parent_of( schema, table, schema, table, depth ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME, INT ) +-- 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 _parent_of( $1, $2, $3, $4, $5 ), NULL ); + SELECT ok( NOT _ancestor_of( $1, $2, $3, $4, $5 ), NULL ); $$ LANGUAGE SQL; --- isnt_parent_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, NAME, NAME ) +-- isnt_ancestor_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME, NAME, NAME ) RETURNS TEXT AS $$ - SELECT ok( NOT _parent_of( $1, $2, $3, $4, 1 ), NULL ); + SELECT ok( NOT _ancestor_of( $1, $2, $3, $4, 1 ), NULL ); $$ LANGUAGE SQL; --- isnt_child_of( schema, table, schema, table, depth, description ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME, INT, TEXT ) +-- 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 isnt_parent_of( $3, $4, $1, $2, $5, $6 ); + SELECT isnt_ancestor_of( $3, $4, $1, $2, $5, $6 ); $$ LANGUAGE SQL; --- isnt_child_of( schema, table, schema, table, depth ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME, INT ) +-- isnt_descendent_of( schema, table, schema, table, depth ) +CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, NAME, NAME, INT ) RETURNS TEXT AS $$ - SELECT isnt_parent_of( $3, $4, $1, $2, $5, NULL ); + SELECT isnt_ancestor_of( $3, $4, $1, $2, $5, NULL ); $$ LANGUAGE SQL; --- isnt_child_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, NAME, NAME ) +-- isnt_descendent_of( schema, table, schema, table ) +CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, NAME, NAME ) RETURNS TEXT AS $$ - SELECT isnt_parent_of( $3, $4, $1, $2, 1, NULL ); + SELECT isnt_ancestor_of( $3, $4, $1, $2, 1, NULL ); $$ LANGUAGE SQL; --- isnt_parent_of( table, table, depth, description ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, INT, TEXT ) +-- isnt_ancestor_of( table, table, depth, description ) +CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME, INT, TEXT ) RETURNS TEXT AS $$ - SELECT ok( NOT _parent_of( $1, $2, $3 ), $4 ); + SELECT ok( NOT _ancestor_of( $1, $2, $3 ), $4 ); $$ LANGUAGE SQL; --- isnt_parent_of( table, table, depth ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME, INT ) +-- isnt_ancestor_of( table, table, depth ) +CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME, INT ) RETURNS TEXT AS $$ - SELECT ok( NOT _parent_of( $1, $2, $3 ), NULL ); + SELECT ok( NOT _ancestor_of( $1, $2, $3 ), NULL ); $$ LANGUAGE SQL; --- isnt_parent_of( table, table ) -CREATE OR REPLACE FUNCTION isnt_parent_of( NAME, NAME ) +-- isnt_ancestor_of( table, table ) +CREATE OR REPLACE FUNCTION isnt_ancestor_of( NAME, NAME ) RETURNS TEXT AS $$ - SELECT ok( NOT _parent_of( $1, $2, 1 ), NULL ); + SELECT ok( NOT _ancestor_of( $1, $2, 1 ), NULL ); $$ LANGUAGE SQL; --- isnt_child_of( table, table, depth, description ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, INT, TEXT ) +-- isnt_descendent_of( table, table, depth, description ) +CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, INT, TEXT ) RETURNS TEXT AS $$ - SELECT isnt_parent_of( $2, $1, $3, $4 ); + SELECT isnt_ancestor_of( $2, $1, $3, $4 ); $$ LANGUAGE SQL; --- isnt_child_of( table, table, depth ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME, INT ) +-- isnt_descendent_of( table, table, depth ) +CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, INT ) RETURNS TEXT AS $$ - SELECT isnt_parent_of( $2, $1, $3, NULL ); + SELECT isnt_ancestor_of( $2, $1, $3, NULL ); $$ LANGUAGE SQL; --- isnt_child_of( table, table ) -CREATE OR REPLACE FUNCTION isnt_child_of( NAME, NAME ) +-- isnt_descendent_of( table, table ) +CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME ) RETURNS TEXT AS $$ - SELECT isnt_parent_of( $2, $1, 1, NULL ); + SELECT isnt_ancestor_of( $2, $1, 1, NULL ); $$ LANGUAGE SQL; + +*/ diff --git a/test/expected/inheritance.out b/test/expected/inheritance.out index b5ab0a268140..b44c1b856fe5 100644 --- a/test/expected/inheritance.out +++ b/test/expected/inheritance.out @@ -1,5 +1,5 @@ \unset ECHO -1..85 +1..156 ok 1 - has_inherited_tables(sch, tab, desc) should pass ok 2 - has_inherited_tables(sch, tab, desc) should have the proper description ok 3 - has_inherited_tables(sch, tab, desc) should have the proper diagnostics @@ -72,16 +72,87 @@ ok 69 - hasnt_inherited_tables(tab) fail should have the proper diagnostics ok 70 - hasnt_inherited_tables(nonesuch) should pass ok 71 - hasnt_inherited_tables(nonesuch) should have the proper description ok 72 - hasnt_inherited_tables(nonesuch) should have the proper diagnostics -ok 73 - hide.h_parent direct is father of hide.h_child1 should pass -ok 74 - hide.h_parent direct is father of hide.h_child1 should pass -ok 75 - hide.h_child1 direct is father of hide.h_child2 should pass -ok 76 - hide.h_parent is father of hide.h_child2 should pass -ok 77 - child1 inherits from parent should pass -ok 78 - hide.h_parent is not father of public.child1 should fail -ok 79 - public.parent is not father of public.child1 should pass -ok 80 - hide.h_parent is not father of public.child1 should pass -ok 81 - parent is not father of public.child1 should fail -ok 82 - parent is not child1 should pass -ok 83 - child1 inherits from parent should fail -ok 84 - hide.h_parent is not child of hide.h_child1 should pass -ok 85 - hide.h_child1 inherits from hide.h_parent should fail +ok 73 - is_ancestor_of(psch, ptab, csch, ctab, 1, desc) should pass +ok 74 - is_ancestor_of(psch, ptab, csch, ctab, 1, desc) should have the proper description +ok 75 - is_ancestor_of(psch, ptab, csch, ctab, 1, desc) should have the proper diagnostics +ok 76 - is_ancestor_of(psch, ptab, csch, ctab, 2, desc) should pass +ok 77 - is_ancestor_of(psch, ptab, csch, ctab, 2, desc) should have the proper description +ok 78 - is_ancestor_of(psch, ptab, csch, ctab, 2, desc) should have the proper diagnostics +ok 79 - is_ancestor_of(psch, nope, csch, ctab, 1, desc) should fail +ok 80 - is_ancestor_of(psch, nope, csch, ctab, 1, desc) should have the proper description +ok 81 - is_ancestor_of(psch, nope, csch, ctab, 1, desc) should have the proper diagnostics +ok 82 - is_ancestor_of(psch, ptab, csch, nope, desc) should fail +ok 83 - is_ancestor_of(psch, ptab, csch, nope, desc) should have the proper description +ok 84 - is_ancestor_of(psch, ptab, csch, nope, desc) should have the proper diagnostics +ok 85 - is_ancestor_of(psch, ptab, csch, ctab, 1) should pass +ok 86 - is_ancestor_of(psch, ptab, csch, ctab, 1) should have the proper description +ok 87 - is_ancestor_of(psch, ptab, csch, ctab, 1) should have the proper diagnostics +ok 88 - is_ancestor_of(psch, nope, csch, ctab, 1) should fail +ok 89 - is_ancestor_of(psch, nope, csch, ctab, 1) should have the proper description +ok 90 - is_ancestor_of(psch, nope, csch, ctab, 1) should have the proper diagnostics +ok 91 - is_ancestor_of(psch, ptab, csch, nope, 1) should fail +ok 92 - is_ancestor_of(psch, ptab, csch, nope, 1) should have the proper description +ok 93 - is_ancestor_of(psch, ptab, csch, nope, 1) should have the proper diagnostics +ok 94 - is_ancestor_of(psch, ptab, csch, ctab, 2) should pass +ok 95 - is_ancestor_of(psch, ptab, csch, ctab, 2) should have the proper description +ok 96 - is_ancestor_of(psch, ptab, csch, ctab, 2) should have the proper diagnostics +ok 97 - is_ancestor_of(psch, nope, csch, ctab, 2) should fail +ok 98 - is_ancestor_of(psch, nope, csch, ctab, 2) should have the proper description +ok 99 - is_ancestor_of(psch, nope, csch, ctab, 2) should have the proper diagnostics +ok 100 - is_ancestor_of(psch, ptab, csch, nope, 2) should fail +ok 101 - is_ancestor_of(psch, ptab, csch, nope, 2) should have the proper description +ok 102 - is_ancestor_of(psch, ptab, csch, nope, 2) should have the proper diagnostics +ok 103 - is_ancestor_of(psch, ptab, csch, ctab, desc) should pass +ok 104 - is_ancestor_of(psch, ptab, csch, ctab, desc) should have the proper description +ok 105 - is_ancestor_of(psch, ptab, csch, ctab, desc) should have the proper diagnostics +ok 106 - is_ancestor_of(psch, ptab, csch, ctab2, desc) should pass +ok 107 - is_ancestor_of(psch, ptab, csch, ctab2, desc) should have the proper description +ok 108 - is_ancestor_of(psch, ptab, csch, ctab2, desc) should have the proper diagnostics +ok 109 - is_ancestor_of(psch, ptab, csch, ctab) should pass +ok 110 - is_ancestor_of(psch, ptab, csch, ctab) should have the proper description +ok 111 - is_ancestor_of(psch, ptab, csch, ctab) should have the proper diagnostics +ok 112 - is_ancestor_of(psch, ptab, csch, ctab2) should pass +ok 113 - is_ancestor_of(psch, ptab, csch, ctab2) should have the proper description +ok 114 - is_ancestor_of(psch, ptab, csch, ctab2) should have the proper diagnostics +ok 115 - is_ancestor_of(psch, nope, csch, ctab) should fail +ok 116 - is_ancestor_of(psch, nope, csch, ctab) should have the proper description +ok 117 - is_ancestor_of(psch, nope, csch, ctab) should have the proper diagnostics +ok 118 - is_ancestor_of(psch, ptab, csch, nope) should fail +ok 119 - is_ancestor_of(psch, ptab, csch, nope) should have the proper description +ok 120 - is_ancestor_of(psch, ptab, csch, nope) should have the proper diagnostics +ok 121 - is_ancestor_of(ptab, ctab, 1, desc) should pass +ok 122 - is_ancestor_of(ptab, ctab, 1, desc) should have the proper description +ok 123 - is_ancestor_of(ptab, ctab, 1, desc) should have the proper diagnostics +ok 124 - is_ancestor_of(ptab, ctab, 2, desc) should pass +ok 125 - is_ancestor_of(ptab, ctab, 2, desc) should have the proper description +ok 126 - is_ancestor_of(ptab, ctab, 2, desc) should have the proper diagnostics +ok 127 - is_ancestor_of(ptab, ctab, 1, desc) fail should fail +ok 128 - is_ancestor_of(ptab, ctab, 1, desc) fail should have the proper description +ok 129 - is_ancestor_of(ptab, ctab, 1, desc) fail should have the proper diagnostics +ok 130 - is_ancestor_of(ptab, nope, 1, desc) should fail +ok 131 - is_ancestor_of(ptab, nope, 1, desc) should have the proper description +ok 132 - is_ancestor_of(ptab, nope, 1, desc) should have the proper diagnostics +ok 133 - is_ancestor_of(ptab, ctab, 1) should pass +ok 134 - is_ancestor_of(ptab, ctab, 1) should have the proper description +ok 135 - is_ancestor_of(ptab, ctab, 1) should have the proper diagnostics +ok 136 - is_ancestor_of(ptab, ctab, 2) should pass +ok 137 - is_ancestor_of(ptab, ctab, 2) should have the proper description +ok 138 - is_ancestor_of(ptab, ctab, 2) should have the proper diagnostics +ok 139 - is_ancestor_of(ptab, ctab, 1) fail should fail +ok 140 - is_ancestor_of(ptab, ctab, 1) fail should have the proper description +ok 141 - is_ancestor_of(ptab, ctab, 1) fail should have the proper diagnostics +ok 142 - is_ancestor_of(ptab, nope, 1) should fail +ok 143 - is_ancestor_of(ptab, nope, 1) should have the proper description +ok 144 - is_ancestor_of(ptab, nope, 1) should have the proper diagnostics +ok 145 - is_ancestor_of(ptab, ctab) should pass +ok 146 - is_ancestor_of(ptab, ctab) should have the proper description +ok 147 - is_ancestor_of(ptab, ctab) should have the proper diagnostics +ok 148 - is_ancestor_of(ptab, ctab2) should pass +ok 149 - is_ancestor_of(ptab, ctab2) should have the proper description +ok 150 - is_ancestor_of(ptab, ctab2) should have the proper diagnostics +ok 151 - is_ancestor_of(ptab, nope) should fail +ok 152 - is_ancestor_of(ptab, nope) should have the proper description +ok 153 - is_ancestor_of(ptab, nope) should have the proper diagnostics +ok 154 - is_ancestor_of(nope, ctab2) should fail +ok 155 - is_ancestor_of(nope, ctab2) should have the proper description +ok 156 - is_ancestor_of(nope, ctab2) should have the proper diagnostics diff --git a/test/sql/inheritance.sql b/test/sql/inheritance.sql index 45edcebbfb11..05f6abfaa8d5 100644 --- a/test/sql/inheritance.sql +++ b/test/sql/inheritance.sql @@ -1,6 +1,6 @@ \unset ECHO \i test/setup.sql -SELECT plan( 85 ); +SELECT plan( 156 ); --SELECT * FROM no_plan(); SET client_min_messages = warning; @@ -44,7 +44,7 @@ SELECT * FROM check_test( has_inherited_tables( 'hide', 'h_parent'::name ), true, 'has_inherited_tables(sch, tab)', - 'Table hide.h_parent should have children', + 'Table hide.h_parent should have descendents', '' ); @@ -52,7 +52,7 @@ SELECT * FROM check_test( has_inherited_tables( 'hide', 'h_child2'::name ), false, 'has_inherited_tables(sch, tab) fail', - 'Table hide.h_child2 should have children', + 'Table hide.h_child2 should have descendents', '' ); @@ -60,7 +60,7 @@ SELECT * FROM check_test( has_inherited_tables( 'hide', 'nonesuch'::name ), false, 'has_inherited_tables(sch, nonesuch)', - 'Table hide.nonesuch should have children', + 'Table hide.nonesuch should have descendents', '' ); @@ -92,7 +92,7 @@ SELECT * FROM check_test( has_inherited_tables( 'parent' ), true, 'has_inherited_tables(tab)', - 'Table parent should have children', + 'Table parent should have descendents', '' ); @@ -100,7 +100,7 @@ SELECT * FROM check_test( has_inherited_tables( 'child2' ), false, 'has_inherited_tables(tab) fail', - 'Table child2 should have children', + 'Table child2 should have descendents', '' ); @@ -108,7 +108,7 @@ SELECT * FROM check_test( has_inherited_tables( 'nonesuch' ), false, 'has_inherited_tables(nonesuch)', - 'Table nonesuch should have children', + 'Table nonesuch should have descendents', '' ); @@ -141,7 +141,7 @@ SELECT * FROM check_test( hasnt_inherited_tables( 'hide', 'h_child2'::name ), true, 'hasnt_inherited_tables(sch, tab)', - 'Table hide.h_child2 should not have children', + 'Table hide.h_child2 should not have descendents', '' ); @@ -149,7 +149,7 @@ SELECT * FROM check_test( hasnt_inherited_tables( 'hide', 'h_child1'::name ), false, 'hasnt_inherited_tables(sch, tab) fail', - 'Table hide.h_child1 should not have children', + 'Table hide.h_child1 should not have descendents', '' ); @@ -157,7 +157,7 @@ SELECT * FROM check_test( hasnt_inherited_tables( 'hide', 'nonesuch'::name ), true, 'hasnt_inherited_tables(sch, nonesuch)', - 'Table hide.nonesuch should not have children', + 'Table hide.nonesuch should not have descendents', '' ); @@ -189,7 +189,7 @@ SELECT * FROM check_test( hasnt_inherited_tables( 'child2' ), true, 'hasnt_inherited_tables(tab)', - 'Table child2 should not have children', + 'Table child2 should not have descendents', '' ); @@ -197,7 +197,7 @@ SELECT * FROM check_test( hasnt_inherited_tables( 'child1' ), false, 'hasnt_inherited_tables(tab) fail', - 'Table child1 should not have children', + 'Table child1 should not have descendents', '' ); @@ -205,90 +205,233 @@ SELECT * FROM check_test( hasnt_inherited_tables( 'nonesuch' ), true, 'hasnt_inherited_tables(nonesuch)', - 'Table nonesuch should not have children', + 'Table nonesuch should not have descendents', '' ); +-- test is_ancestor_of +SELECT * FROM check_test( + is_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child1', 1, 'Lookie' ), + true, + 'is_ancestor_of(psch, ptab, csch, ctab, 1, desc)', + 'Lookie', + '' +); +SELECT * FROM check_test( + is_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child2', 2, 'Lookie' ), + true, + 'is_ancestor_of(psch, ptab, csch, ctab, 2, desc)', + 'Lookie', + '' +); +SELECT * FROM check_test( + is_ancestor_of( 'hide', 'h_parent', 'nope', 'h_child2', 1, 'Lookie' ), + false, + 'is_ancestor_of(psch, nope, csch, ctab, 1, desc)', + 'Lookie', + '' +); +SELECT * FROM check_test( + is_ancestor_of( 'hide', 'h_parent', 'hide', 'nope', 1, 'Lookie' ), + false, + 'is_ancestor_of(psch, ptab, csch, nope, desc)', + 'Lookie', + '' +); SELECT * FROM check_test( - is_parent_of( 'hide', 'h_parent', 'hide', 'h_child1', 1, 'Test hide.h_parent->hide.h_child1' ), - true, -- expected value - 'hide.h_parent direct is father of hide.h_child1' + is_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child1', 1 ), + true, + 'is_ancestor_of(psch, ptab, csch, ctab, 1)', + 'Table hide.h_parent should be ancestor 1 for hide.h_child1', + '' ); SELECT * FROM check_test( - is_parent_of( 'hide', 'h_parent', 'hide', 'h_child1', 1 ), - true, -- expected value - 'hide.h_parent direct is father of hide.h_child1' + is_ancestor_of( 'hide', 'nope', 'hide', 'h_child1', 1 ), + false, + 'is_ancestor_of(psch, nope, csch, ctab, 1)', + 'Table hide.nope should be ancestor 1 for hide.h_child1', + '' ); SELECT * FROM check_test( - is_parent_of( 'hide', 'h_child1', 'hide', 'h_child2', 1 ), - true, -- expected value - 'hide.h_child1 direct is father of hide.h_child2' + is_ancestor_of( 'hide', 'h_parent', 'hide', 'nope', 1 ), + false, + 'is_ancestor_of(psch, ptab, csch, nope, 1)', + 'Table hide.h_parent should be ancestor 1 for hide.nope', + '' ); SELECT * FROM check_test( - is_parent_of( 'hide', 'h_parent', 'hide', 'h_child2', 2 ), - true, -- expected value - 'hide.h_parent is father of hide.h_child2' + is_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child2', 2 ), + true, + 'is_ancestor_of(psch, ptab, csch, ctab, 2)', + 'Table hide.h_parent should be ancestor 2 for hide.h_child2', + '' ); SELECT * FROM check_test( - is_parent_of( 'parent', 'child1' ), - true, -- expected value - 'child1 inherits from parent' + is_ancestor_of( 'hide', 'nope', 'hide', 'h_child2', 2 ), + false, + 'is_ancestor_of(psch, nope, csch, ctab, 2)', + 'Table hide.nope should be ancestor 2 for hide.h_child2', + '' +); + +SELECT * FROM check_test( + is_ancestor_of( 'hide', 'h_parent', 'hide', 'nope', 2 ), + false, + 'is_ancestor_of(psch, ptab, csch, nope, 2)', + 'Table hide.h_parent should be ancestor 2 for hide.nope', + '' ); SELECT * FROM check_test( - is_parent_of( 'hide'::name, 'h_parent'::name, 'public'::name, 'child1'::name ), - false, -- expected value - 'hide.h_parent is not father of public.child1' + is_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child1', 'Howdy' ), + true, + 'is_ancestor_of(psch, ptab, csch, ctab, desc)', + 'Howdy', + '' ); SELECT * FROM check_test( - is_parent_of( 'public'::name, 'parent'::name, 'public'::name, 'child1'::name ), - true, -- expected value - 'public.parent is not father of public.child1' + is_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child2', 'Howdy' ), + true, + 'is_ancestor_of(psch, ptab, csch, ctab2, desc)', + 'Howdy', + '' ); SELECT * FROM check_test( - isnt_child_of( 'hide'::name, 'h_parent'::name, 'public'::name, 'child1'::name ), - true, -- expected value - 'hide.h_parent is not father of public.child1' + is_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child1'::name ), + true, + 'is_ancestor_of(psch, ptab, csch, ctab)', + 'Table hide.h_parent should be an ancestor of hide.h_child1', + '' ); SELECT * FROM check_test( - isnt_parent_of( 'parent', 'child1' ), - false, -- expected value - 'parent is not father of public.child1' + is_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child2'::name ), + true, + 'is_ancestor_of(psch, ptab, csch, ctab2)', + 'Table hide.h_parent should be an ancestor of hide.h_child2', + '' ); SELECT * FROM check_test( - isnt_child_of( 'parent', 'child1' ), - true, -- expected value - 'parent is not child1' + is_ancestor_of( 'hide', 'nope', 'hide', 'h_child1'::name ), + false, + 'is_ancestor_of(psch, nope, csch, ctab)', + 'Table hide.nope should be an ancestor of hide.h_child1', + '' ); SELECT * FROM check_test( - isnt_child_of( 'child1', 'parent' ), - false, -- expected value - 'child1 inherits from parent' + is_ancestor_of( 'hide', 'h_parent', 'hide', 'nope'::name ), + false, + 'is_ancestor_of(psch, ptab, csch, nope)', + 'Table hide.h_parent should be an ancestor of hide.nope', + '' ); SELECT * FROM check_test( - isnt_child_of( 'hide'::name, 'h_parent'::name, 'hide'::name, 'h_child1'::name ), - true, -- expected value - 'hide.h_parent is not child of hide.h_child1' + is_ancestor_of( 'parent', 'child1', 1, 'Howdy' ), + true, + 'is_ancestor_of(ptab, ctab, 1, desc)', + 'Howdy', + '' ); SELECT * FROM check_test( - isnt_child_of( 'hide'::name, 'h_child1'::name, 'hide'::name, 'h_parent'::name ), - false, -- expected value - 'hide.h_child1 inherits from hide.h_parent' + is_ancestor_of( 'parent', 'child2', 2, 'Howdy' ), + true, + 'is_ancestor_of(ptab, ctab, 2, desc)', + 'Howdy', + '' +); + +SELECT * FROM check_test( + is_ancestor_of( 'parent', 'child2', 1, 'Howdy' ), + false, + 'is_ancestor_of(ptab, ctab, 1, desc) fail', + 'Howdy', + '' +); + +SELECT * FROM check_test( + is_ancestor_of( 'parent', 'hope', 1, 'Howdy' ), + false, + 'is_ancestor_of(ptab, nope, 1, desc)', + 'Howdy', + '' +); + +SELECT * FROM check_test( + is_ancestor_of( 'parent', 'child1', 1 ), + true, + 'is_ancestor_of(ptab, ctab, 1)', + 'Table parent should be ancestor 1 of child1', + '' +); + +SELECT * FROM check_test( + is_ancestor_of( 'parent', 'child2', 2 ), + true, + 'is_ancestor_of(ptab, ctab, 2)', + 'Table parent should be ancestor 2 of child2', + '' +); + +SELECT * FROM check_test( + is_ancestor_of( 'parent', 'child2', 1 ), + false, + 'is_ancestor_of(ptab, ctab, 1) fail', + 'Table parent should be ancestor 1 of child2', + '' +); + +SELECT * FROM check_test( + is_ancestor_of( 'parent', 'nope', 1 ), + false, + 'is_ancestor_of(ptab, nope, 1)', + 'Table parent should be ancestor 1 of nope', + '' +); + +SELECT * FROM check_test( + is_ancestor_of( 'parent', 'child1' ), + true, + 'is_ancestor_of(ptab, ctab)', + 'Table parent should be an ancestor of child1', + '' +); + +SELECT * FROM check_test( + is_ancestor_of( 'parent', 'child2' ), + true, + 'is_ancestor_of(ptab, ctab2)', + 'Table parent should be an ancestor of child2', + '' +); + +SELECT * FROM check_test( + is_ancestor_of( 'parent', 'nope' ), + false, + 'is_ancestor_of(ptab, nope)', + 'Table parent should be an ancestor of nope', + '' +); + +SELECT * FROM check_test( + is_ancestor_of( 'nope', 'child1' ), + false, + 'is_ancestor_of(nope, ctab2)', + 'Table nope should be an ancestor of child1', + '' ); /****************************************************************************/ From 8f6f8e8dffced014f1c0d20a0f250fbb0be8c9b8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 16:50:39 -0500 Subject: [PATCH 1053/1195] Add is_descendent_of() And update the docs for both functions. --- doc/pgtap.mmd | 88 +++++++------ sql/pgtap.sql.in | 111 ++++------------ test/expected/inheritance.out | 86 ++++++++++++- test/sql/inheritance.sql | 229 +++++++++++++++++++++++++++++++++- 4 files changed, 389 insertions(+), 125 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 53ff0124a8b0..4f2389aae149 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2993,38 +2993,41 @@ the `NAME` type: : Name of the schema in which the ancestor table must be found. `:ancestor_table` -: Name of the table that plays the ancestor role. +: Name of the ancestor table. `:descendent_schema` -: Name of the schema in which the descendent table must be. +: Name of the schema in which the descendent table must be found. `:descendent_table` -: Name of the table that is supposed to be descendent of the other one. +: Name of the descendent table. + +`:depth` +: The inheritance distance between the two tables. `:description` : Description of the test. -`:depth` -: The position in the inheritance chain of the descendent table. - This function checks if the table marked as "ancestor" is effectively a table from which the "descendent" table inherits --- that there is an inheritance chain between the two tables. The optional depth argument specifies the length of the inheritance chain between the tables; if not specified, the inheritance -distance may be of any length. +distance may be of any length. If the `:description` is omitted, a reasonable +substitute will be created. If you find that the function call seems to be getting confused, cast the sequence to the `NAME` type: - SELECT has_sequence('myschema', 'ancestor', 'myschema', 'descendent'::NAME); + SELECT is_ancestor_of('myschema', 'ancestor', 'myschema', 'descendent'::NAME); ### `isnt_ancestor_of()` SELECT isnt_ancestor_of( :ancestor_schema, :ancestor_table, :descendent_schema, :descendent_table, :depth, :description ); SELECT isnt_ancestor_of( :ancestor_schema, :ancestor_table, :descendent_schema, :descendent_table, :depth ); + SELECT isnt_ancestor_of( :ancestor_schema, :ancestor_table, :descendent_schema, :descendent_table, :description ); SELECT isnt_ancestor_of( :ancestor_schema, :ancestor_table, :descendent_schema, :descendent_table ); SELECT isnt_ancestor_of( :ancestor_table, :descendent_table, :depth, :description ); SELECT isnt_ancestor_of( :ancestor_table, :descendent_table, :depth ); + SELECT isnt_ancestor_of( :ancestor_table, :descendent_table, :description ); SELECT isnt_ancestor_of( :ancestor_table, :descendent_table ); **Parameters** @@ -3033,64 +3036,74 @@ distance may be of any length. : Name of the schema in which the ancestor table must be found. `:ancestor_table` -: Name of the table that plays the ancestor role. +: Name of the ancestor table. `:descendent_schema` -: Name of the schema in which the descendent table must be. +: Name of the schema in which the descendent table must be found. `:descendent_table` -: Name of the table that is supposed to be descendent of the other one. +: Name of the descendent table. + +`:depth` +: The inheritance distance between the two tables. `:description` : Description of the test. -`:depth` -: The position within the inheritance chain where the descendent table is expected - to be (by default 1). - This function ensures that the table marked as "ancestor" is not a table from -which "descendent" inherits from, that there no inheritance chain between the two -tables. The function does the opposite of `is_ancestor_of()`. +which "descendent" inherits --- that there is no inheritance chain between the +two tables. If the optional depth argument is passed, the test enbsures only +that the two tablesa are not related at that distance; they still might be an +inheritance relationship between them. If the `:description` is omitted, a +reasonable substitute will be created. + + If you find that the function call seems to be getting confused, cast the + sequence to the `NAME` type: + + SELECT isnt_ancestor_of('myschema', 'ancestor', 'myschema', 'descendent'::NAME); ### `is_descendent_of()` SELECT is_descendent_of( :descendent_schema, :descendent_table, :ancestor_schema, :ancestor_table, :depth, :description ); + SELECT is_descendent_of( :descendent_schema, :descendent_table, :ancestor_schema, :ancestor_table, :depth ); SELECT is_descendent_of( :descendent_schema, :descendent_table, :ancestor_schema, :ancestor_table, :description ); SELECT is_descendent_of( :descendent_schema, :descendent_table, :ancestor_schema, :ancestor_table ); SELECT is_descendent_of( :descendent_table, :ancestor_table, :depth, :description ); SELECT is_descendent_of( :descendent_table, :ancestor_table, :depth ); + SELECT is_descendent_of( :descendent_table, :ancestor_table, :description ); SELECT is_descendent_of( :descendent_table, :ancestor_table ); **Parameters** +`:descendent_schema` +: Name of the schema in which the descendent table must be found. + +`:descendent_table` +: Name of the descendent table. + `:ancestor_schema` : Name of the schema in which the ancestor table must be found. `:ancestor_table` -: Name of the table that plays the ancestor role. +: Name of the ancestor table. -`:descendent_schema` -: Name of the schema in which the descendent table must be. - -`:descendent_table` -: Name of the table that is supposed to be descendent of the other one. +`:depth` +: The inheritance distance between the two tables. `:description` : Description of the test. -`:depth` -: The position where the descendent table is expected to be in the inheritance chain - (by default 1 for a direct son). - -This function does the same of `is_ancestor_of()`, with swapped arguments. +This function provide exactly the same functionality as `is_ancestor_of()`, but +with the ancestor and descendent arguments swapped. ### `isnt_descendent_of()` SELECT isnt_descendent_of( :descendent_schema, :descendent_table, :ancestor_schema, :ancestor_table, :depth, :description ); SELECT isnt_descendent_of( :descendent_schema, :descendent_table, :ancestor_schema, :ancestor_table, :depth ); - SELECT isnt_descendent_of( :descendent_schema, :descendent_table, :ancestor_schema, :ancestor_table ); + SELECT isnt_descendent_of( :descendent_schema, :descendent_table, :ancestor_schema, :ancestor_table, :description ); SELECT isnt_descendent_of( :descendent_table, :ancestor_table, :depth, :description ); SELECT isnt_descendent_of( :descendent_table, :ancestor_table, :depth ); + SELECT isnt_descendent_of( :descendent_table, :ancestor_table, :description ); SELECT isnt_descendent_of( :descendent_table, :ancestor_table ); **Parameters** @@ -3099,23 +3112,22 @@ This function does the same of `is_ancestor_of()`, with swapped arguments. : Name of the schema in which the ancestor table must be found. `:ancestor_table` -: Name of the table that plays the ancestor role. +: Name of the ancestor table. `:descendent_schema` -: Name of the schema in which the descendent table must be. +: Name of the schema in which the descendent table must be found. `:descendent_table` -: Name of the table that is supposed to be descendent of the other one. +: Name of the descendent table. + +`:depth` +: The inheritance distance between the two tables. `:description` : Description of the test. -`:depth` -: The position where the inheriting table is expected to be along the - inheritance chain with respect to the ancestor table (by default 1 for a direct - descendent). - -This function does the same of `isnt_ancestor_of()`, with swapped arguments. +This function provide exactly the same functionality as `isnt_ancestor_of()`, +but with the ancestor and descendent arguments swapped. ### `has_sequence()` ### diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 63bbc6e0519e..321b35c9c3f9 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -10530,31 +10530,36 @@ RETURNS TEXT AS $$ ); $$ 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 is_ancestor_of($3, $4, $1, $2, $5, $6); + 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 is_ancestor_of( - $3, $4, $1, $2, $5, + SELECT ok( + _ancestor_of( $3, $4, $1, $2, $5 ), 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' should be ancestor ' || $5 || ' for ' + || ' 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 is_ancestor_of( - $3, $4, $1, $2, 1, + SELECT ok( + _ancestor_of( $3, $4, $1, $2, NULL ), 'Table ' || quote_ident( $1 ) || '.' || quote_ident( $2 ) - || ' is a ' || '1-descendent of ' + || ' should be a descendent of ' || quote_ident( $3 ) || '.' || quote_ident( $4 ) ); $$ LANGUAGE SQL; @@ -10562,91 +10567,29 @@ $$ LANGUAGE SQL; -- is_descendent_of( table, table, depth, description ) CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, INT, TEXT ) RETURNS TEXT AS $$ - SELECT is_ancestor_of( $2, $1, $3, $4 ); + 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 is_ancestor_of( $2, $1, $3, NULL ); -$$ LANGUAGE SQL; - --- is_descendent_of( table, table ) -CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT is_ancestor_of( $2, $1, 1, NULL ); -$$ 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 ), NULL ); -$$ 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, 1 ), NULL ); -$$ 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 isnt_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 isnt_ancestor_of( $3, $4, $1, $2, $5, NULL ); -$$ LANGUAGE SQL; - --- isnt_descendent_of( schema, table, schema, table ) -CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, NAME, NAME ) -RETURNS TEXT AS $$ - SELECT isnt_ancestor_of( $3, $4, $1, $2, 1, NULL ); -$$ 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 ), NULL ); -$$ 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, 1 ), NULL ); -$$ LANGUAGE SQL; - --- isnt_descendent_of( table, table, depth, description ) -CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, INT, TEXT ) -RETURNS TEXT AS $$ - SELECT isnt_ancestor_of( $2, $1, $3, $4 ); + SELECT ok( + _ancestor_of( $2, $1, $3 ), + 'Table ' || quote_ident( $1 ) || ' should be descendent ' || $3 || ' from ' || quote_ident( $2) + ); $$ LANGUAGE SQL; --- isnt_descendent_of( table, table, depth ) -CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME, INT ) +-- is_descendent_of( table, table, description ) +CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME, TEXT ) RETURNS TEXT AS $$ - SELECT isnt_ancestor_of( $2, $1, $3, NULL ); + SELECT ok( _ancestor_of( $2, $1, NULL ), $3 ); $$ LANGUAGE SQL; --- isnt_descendent_of( table, table ) -CREATE OR REPLACE FUNCTION isnt_descendent_of( NAME, NAME ) +-- is_descendent_of( table, table ) +CREATE OR REPLACE FUNCTION is_descendent_of( NAME, NAME ) RETURNS TEXT AS $$ - SELECT isnt_ancestor_of( $2, $1, 1, NULL ); + SELECT ok( + _ancestor_of( $2, $1, NULL ), + 'Table ' || quote_ident( $1 ) || ' should be a descendent of ' || quote_ident( $2) + ); $$ LANGUAGE SQL; - -*/ diff --git a/test/expected/inheritance.out b/test/expected/inheritance.out index b44c1b856fe5..02a37643f62c 100644 --- a/test/expected/inheritance.out +++ b/test/expected/inheritance.out @@ -1,5 +1,5 @@ \unset ECHO -1..156 +1..240 ok 1 - has_inherited_tables(sch, tab, desc) should pass ok 2 - has_inherited_tables(sch, tab, desc) should have the proper description ok 3 - has_inherited_tables(sch, tab, desc) should have the proper diagnostics @@ -156,3 +156,87 @@ ok 153 - is_ancestor_of(ptab, nope) should have the proper diagnostics ok 154 - is_ancestor_of(nope, ctab2) should fail ok 155 - is_ancestor_of(nope, ctab2) should have the proper description ok 156 - is_ancestor_of(nope, ctab2) should have the proper diagnostics +ok 157 - is_descendent_of(csch, ctab, psch, ptab, 1, desc) should pass +ok 158 - is_descendent_of(csch, ctab, psch, ptab, 1, desc) should have the proper description +ok 159 - is_descendent_of(csch, ctab, psch, ptab, 1, desc) should have the proper diagnostics +ok 160 - is_descendent_of(csch, ctab, psch, ptab, 2, desc) should pass +ok 161 - is_descendent_of(csch, ctab, psch, ptab, 2, desc) should have the proper description +ok 162 - is_descendent_of(csch, ctab, psch, ptab, 2, desc) should have the proper diagnostics +ok 163 - is_descendent_of(csch, ctab, psch, nope, 1, desc) should fail +ok 164 - is_descendent_of(csch, ctab, psch, nope, 1, desc) should have the proper description +ok 165 - is_descendent_of(csch, ctab, psch, nope, 1, desc) should have the proper diagnostics +ok 166 - is_descendent_of(csch, nope, psch, ptab, desc) should fail +ok 167 - is_descendent_of(csch, nope, psch, ptab, desc) should have the proper description +ok 168 - is_descendent_of(csch, nope, psch, ptab, desc) should have the proper diagnostics +ok 169 - is_descendent_of(csch, ctab, psch, ptab, 1) should pass +ok 170 - is_descendent_of(csch, ctab, psch, ptab, 1) should have the proper description +ok 171 - is_descendent_of(csch, ctab, psch, ptab, 1) should have the proper diagnostics +ok 172 - is_descendent_of(csch, ctab, psch, nope, 1) should fail +ok 173 - is_descendent_of(csch, ctab, psch, nope, 1) should have the proper description +ok 174 - is_descendent_of(csch, ctab, psch, nope, 1) should have the proper diagnostics +ok 175 - is_descendent_of(csch, nope, psch, ptab, 1) should fail +ok 176 - is_descendent_of(csch, nope, psch, ptab, 1) should have the proper description +ok 177 - is_descendent_of(csch, nope, psch, ptab, 1) should have the proper diagnostics +ok 178 - is_descendent_of(csch, ctab, psch, ptab, 2) should pass +ok 179 - is_descendent_of(csch, ctab, psch, ptab, 2) should have the proper description +ok 180 - is_descendent_of(csch, ctab, psch, ptab, 2) should have the proper diagnostics +ok 181 - is_descendent_of(csch, ctab, psch, nope, 2) should fail +ok 182 - is_descendent_of(csch, ctab, psch, nope, 2) should have the proper description +ok 183 - is_descendent_of(csch, ctab, psch, nope, 2) should have the proper diagnostics +ok 184 - is_descendent_of(csch, nope, psch, ptab, 2) should fail +ok 185 - is_descendent_of(csch, nope, psch, ptab, 2) should have the proper description +ok 186 - is_descendent_of(csch, nope, psch, ptab, 2) should have the proper diagnostics +ok 187 - is_descendent_of(csch, ctab, psch, ptab, desc) should pass +ok 188 - is_descendent_of(csch, ctab, psch, ptab, desc) should have the proper description +ok 189 - is_descendent_of(csch, ctab, psch, ptab, desc) should have the proper diagnostics +ok 190 - is_descendent_of(csch, ctab2, psch, ptab, desc) should pass +ok 191 - is_descendent_of(csch, ctab2, psch, ptab, desc) should have the proper description +ok 192 - is_descendent_of(csch, ctab2, psch, ptab, desc) should have the proper diagnostics +ok 193 - is_descendent_of(csch, ctab, psch, ptab) should pass +ok 194 - is_descendent_of(csch, ctab, psch, ptab) should have the proper description +ok 195 - is_descendent_of(csch, ctab, psch, ptab) should have the proper diagnostics +ok 196 - is_descendent_of(csch, ctab2, psch, ptab) should pass +ok 197 - is_descendent_of(csch, ctab2, psch, ptab) should have the proper description +ok 198 - is_descendent_of(csch, ctab2, psch, ptab) should have the proper diagnostics +ok 199 - is_descendent_of(csch, ctab, psch, nope) should fail +ok 200 - is_descendent_of(csch, ctab, psch, nope) should have the proper description +ok 201 - is_descendent_of(csch, ctab, psch, nope) should have the proper diagnostics +ok 202 - is_descendent_of(csch, nope, psch, ptab) should fail +ok 203 - is_descendent_of(csch, nope, psch, ptab) should have the proper description +ok 204 - is_descendent_of(csch, nope, psch, ptab) should have the proper diagnostics +ok 205 - is_descendent_of(ctab, ptab, 1, desc) should pass +ok 206 - is_descendent_of(ctab, ptab, 1, desc) should have the proper description +ok 207 - is_descendent_of(ctab, ptab, 1, desc) should have the proper diagnostics +ok 208 - is_descendent_of(ctab, ptab, 2, desc) should pass +ok 209 - is_descendent_of(ctab, ptab, 2, desc) should have the proper description +ok 210 - is_descendent_of(ctab, ptab, 2, desc) should have the proper diagnostics +ok 211 - is_descendent_of(ctab, ptab, 1, desc) fail should fail +ok 212 - is_descendent_of(ctab, ptab, 1, desc) fail should have the proper description +ok 213 - is_descendent_of(ctab, ptab, 1, desc) fail should have the proper diagnostics +ok 214 - is_descendent_of(nope, ptab, 1, desc) should fail +ok 215 - is_descendent_of(nope, ptab, 1, desc) should have the proper description +ok 216 - is_descendent_of(nope, ptab, 1, desc) should have the proper diagnostics +ok 217 - is_descendent_of(ctab, ptab, 1) should pass +ok 218 - is_descendent_of(ctab, ptab, 1) should have the proper description +ok 219 - is_descendent_of(ctab, ptab, 1) should have the proper diagnostics +ok 220 - is_descendent_of(ctab, ptab, 2) should pass +ok 221 - is_descendent_of(ctab, ptab, 2) should have the proper description +ok 222 - is_descendent_of(ctab, ptab, 2) should have the proper diagnostics +ok 223 - is_descendent_of(ctab, ptab, 1) fail should fail +ok 224 - is_descendent_of(ctab, ptab, 1) fail should have the proper description +ok 225 - is_descendent_of(ctab, ptab, 1) fail should have the proper diagnostics +ok 226 - is_descendent_of(nope, ptab, 1) should fail +ok 227 - is_descendent_of(nope, ptab, 1) should have the proper description +ok 228 - is_descendent_of(nope, ptab, 1) should have the proper diagnostics +ok 229 - is_descendent_of(ctab, ptab) should pass +ok 230 - is_descendent_of(ctab, ptab) should have the proper description +ok 231 - is_descendent_of(ctab, ptab) should have the proper diagnostics +ok 232 - is_descendent_of( ctab2, ptab ) should pass +ok 233 - is_descendent_of( ctab2, ptab ) should have the proper description +ok 234 - is_descendent_of( ctab2, ptab ) should have the proper diagnostics +ok 235 - is_descendent_of(nope, ptab) should fail +ok 236 - is_descendent_of(nope, ptab) should have the proper description +ok 237 - is_descendent_of(nope, ptab) should have the proper diagnostics +ok 238 - is_descendent_of(ctab2, nope) should fail +ok 239 - is_descendent_of(ctab2, nope) should have the proper description +ok 240 - is_descendent_of(ctab2, nope) should have the proper diagnostics diff --git a/test/sql/inheritance.sql b/test/sql/inheritance.sql index 05f6abfaa8d5..cc30b07c2a95 100644 --- a/test/sql/inheritance.sql +++ b/test/sql/inheritance.sql @@ -1,6 +1,6 @@ \unset ECHO \i test/setup.sql -SELECT plan( 156 ); +SELECT plan( 240 ); --SELECT * FROM no_plan(); SET client_min_messages = warning; @@ -363,7 +363,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_ancestor_of( 'parent', 'hope', 1, 'Howdy' ), + is_ancestor_of( 'parent', 'nope', 1, 'Howdy' ), false, 'is_ancestor_of(ptab, nope, 1, desc)', 'Howdy', @@ -434,6 +434,231 @@ SELECT * FROM check_test( '' ); +-- test is_descendent_of +SELECT * FROM check_test( + is_descendent_of( 'hide', 'h_child1', 'hide', 'h_parent', 1, 'Lookie' ), + true, + 'is_descendent_of(csch, ctab, psch, ptab, 1, desc)', + 'Lookie', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'hide', 'h_child2', 'hide', 'h_parent', 2, 'Lookie' ), + true, + 'is_descendent_of(csch, ctab, psch, ptab, 2, desc)', + 'Lookie', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'nope', 'h_child2', 'hide', 'h_parent', 1, 'Lookie' ), + false, + 'is_descendent_of(csch, ctab, psch, nope, 1, desc)', + 'Lookie', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'hide', 'nope', 'hide', 'h_parent', 1, 'Lookie' ), + false, + 'is_descendent_of(csch, nope, psch, ptab, desc)', + 'Lookie', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'hide', 'h_child1', 'hide', 'h_parent', 1 ), + true, + 'is_descendent_of(csch, ctab, psch, ptab, 1)', + 'Table hide.h_child1 should be descendent 1 from hide.h_parent', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'hide', 'h_child1', 'hide', 'nope', 1 ), + false, + 'is_descendent_of(csch, ctab, psch, nope, 1)', + 'Table hide.h_child1 should be descendent 1 from hide.nope', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'hide', 'nope', 'hide', 'h_parent', 1 ), + false, + 'is_descendent_of(csch, nope, psch, ptab, 1)', + 'Table hide.nope should be descendent 1 from hide.h_parent', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'hide', 'h_child2', 'hide', 'h_parent', 2 ), + true, + 'is_descendent_of(csch, ctab, psch, ptab, 2)', + 'Table hide.h_child2 should be descendent 2 from hide.h_parent', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'hide', 'h_child2', 'hide', 'nope', 2 ), + false, + 'is_descendent_of(csch, ctab, psch, nope, 2)', + 'Table hide.h_child2 should be descendent 2 from hide.nope', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'hide', 'nope', 'hide', 'h_parent', 2 ), + false, + 'is_descendent_of(csch, nope, psch, ptab, 2)', + 'Table hide.nope should be descendent 2 from hide.h_parent', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'hide', 'h_child1', 'hide', 'h_parent', 'Howdy' ), + true, + 'is_descendent_of(csch, ctab, psch, ptab, desc)', + 'Howdy', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'hide', 'h_child2', 'hide', 'h_parent', 'Howdy' ), + true, + 'is_descendent_of(csch, ctab2, psch, ptab, desc)', + 'Howdy', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'hide', 'h_child1', 'hide', 'h_parent'::name ), + true, + 'is_descendent_of(csch, ctab, psch, ptab)', + 'Table hide.h_child1 should be a descendent of hide.h_parent', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'hide', 'h_child2', 'hide', 'h_parent'::name ), + true, + 'is_descendent_of(csch, ctab2, psch, ptab)', + 'Table hide.h_child2 should be a descendent of hide.h_parent', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'hide', 'h_child1', 'hide', 'nope'::name ), + false, + 'is_descendent_of(csch, ctab, psch, nope)', + 'Table hide.h_child1 should be a descendent of hide.nope', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'hide', 'nope', 'hide', 'h_parent'::name ), + false, + 'is_descendent_of(csch, nope, psch, ptab)', + 'Table hide.nope should be a descendent of hide.h_parent', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'child1', 'parent', 1, 'Howdy' ), + true, + 'is_descendent_of(ctab, ptab, 1, desc)', + 'Howdy', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'child2', 'parent', 2, 'Howdy' ), + true, + 'is_descendent_of(ctab, ptab, 2, desc)', + 'Howdy', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'child2', 'parent', 1, 'Howdy' ), + false, + 'is_descendent_of(ctab, ptab, 1, desc) fail', + 'Howdy', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'nope', 'parent', 1, 'Howdy' ), + false, + 'is_descendent_of(nope, ptab, 1, desc)', + 'Howdy', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'child1', 'parent', 1 ), + true, + 'is_descendent_of(ctab, ptab, 1)', + 'Table child1 should be descendent 1 from parent', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'child2', 'parent', 2 ), + true, + 'is_descendent_of(ctab, ptab, 2)', + 'Table child2 should be descendent 2 from parent', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'child2', 'parent', 1 ), + false, + 'is_descendent_of(ctab, ptab, 1) fail', + 'Table child2 should be descendent 1 from parent', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'nope', 'parent', 1 ), + false, + 'is_descendent_of(nope, ptab, 1)', + 'Table nope should be descendent 1 from parent', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'child1', 'parent' ), + true, + 'is_descendent_of(ctab, ptab)', + 'Table child1 should be a descendent of parent', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'child2', 'parent' ), + true, + 'is_descendent_of( ctab2, ptab )', + 'Table child2 should be a descendent of parent', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'nope', 'parent' ), + false, + 'is_descendent_of(nope, ptab)', + 'Table nope should be a descendent of parent', + '' +); + +SELECT * FROM check_test( + is_descendent_of( 'child1', 'nope' ), + false, + 'is_descendent_of(ctab2, nope)', + 'Table child1 should be a descendent of nope', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 4efc1f1fed6242c8da369aaa07d9f00958545d78 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 17:01:59 -0500 Subject: [PATCH 1054/1195] Add isnt_ancestor_of and isnt_descendent_of. --- sql/pgtap.sql.in | 128 ++++++++++ test/expected/inheritance.out | 338 ++++++++++++++++++------- test/sql/inheritance.sql | 454 +++++++++++++++++++++++++++++++++- 3 files changed, 833 insertions(+), 87 deletions(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 321b35c9c3f9..df06c68dbb18 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -10530,6 +10530,70 @@ RETURNS TEXT AS $$ ); $$ 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 $$ @@ -10593,3 +10657,67 @@ RETURNS TEXT AS $$ '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; diff --git a/test/expected/inheritance.out b/test/expected/inheritance.out index 02a37643f62c..0f9803dbb604 100644 --- a/test/expected/inheritance.out +++ b/test/expected/inheritance.out @@ -1,5 +1,5 @@ \unset ECHO -1..240 +1..408 ok 1 - has_inherited_tables(sch, tab, desc) should pass ok 2 - has_inherited_tables(sch, tab, desc) should have the proper description ok 3 - has_inherited_tables(sch, tab, desc) should have the proper diagnostics @@ -156,87 +156,255 @@ ok 153 - is_ancestor_of(ptab, nope) should have the proper diagnostics ok 154 - is_ancestor_of(nope, ctab2) should fail ok 155 - is_ancestor_of(nope, ctab2) should have the proper description ok 156 - is_ancestor_of(nope, ctab2) should have the proper diagnostics -ok 157 - is_descendent_of(csch, ctab, psch, ptab, 1, desc) should pass -ok 158 - is_descendent_of(csch, ctab, psch, ptab, 1, desc) should have the proper description -ok 159 - is_descendent_of(csch, ctab, psch, ptab, 1, desc) should have the proper diagnostics -ok 160 - is_descendent_of(csch, ctab, psch, ptab, 2, desc) should pass -ok 161 - is_descendent_of(csch, ctab, psch, ptab, 2, desc) should have the proper description -ok 162 - is_descendent_of(csch, ctab, psch, ptab, 2, desc) should have the proper diagnostics -ok 163 - is_descendent_of(csch, ctab, psch, nope, 1, desc) should fail -ok 164 - is_descendent_of(csch, ctab, psch, nope, 1, desc) should have the proper description -ok 165 - is_descendent_of(csch, ctab, psch, nope, 1, desc) should have the proper diagnostics -ok 166 - is_descendent_of(csch, nope, psch, ptab, desc) should fail -ok 167 - is_descendent_of(csch, nope, psch, ptab, desc) should have the proper description -ok 168 - is_descendent_of(csch, nope, psch, ptab, desc) should have the proper diagnostics -ok 169 - is_descendent_of(csch, ctab, psch, ptab, 1) should pass -ok 170 - is_descendent_of(csch, ctab, psch, ptab, 1) should have the proper description -ok 171 - is_descendent_of(csch, ctab, psch, ptab, 1) should have the proper diagnostics -ok 172 - is_descendent_of(csch, ctab, psch, nope, 1) should fail -ok 173 - is_descendent_of(csch, ctab, psch, nope, 1) should have the proper description -ok 174 - is_descendent_of(csch, ctab, psch, nope, 1) should have the proper diagnostics -ok 175 - is_descendent_of(csch, nope, psch, ptab, 1) should fail -ok 176 - is_descendent_of(csch, nope, psch, ptab, 1) should have the proper description -ok 177 - is_descendent_of(csch, nope, psch, ptab, 1) should have the proper diagnostics -ok 178 - is_descendent_of(csch, ctab, psch, ptab, 2) should pass -ok 179 - is_descendent_of(csch, ctab, psch, ptab, 2) should have the proper description -ok 180 - is_descendent_of(csch, ctab, psch, ptab, 2) should have the proper diagnostics -ok 181 - is_descendent_of(csch, ctab, psch, nope, 2) should fail -ok 182 - is_descendent_of(csch, ctab, psch, nope, 2) should have the proper description -ok 183 - is_descendent_of(csch, ctab, psch, nope, 2) should have the proper diagnostics -ok 184 - is_descendent_of(csch, nope, psch, ptab, 2) should fail -ok 185 - is_descendent_of(csch, nope, psch, ptab, 2) should have the proper description -ok 186 - is_descendent_of(csch, nope, psch, ptab, 2) should have the proper diagnostics -ok 187 - is_descendent_of(csch, ctab, psch, ptab, desc) should pass -ok 188 - is_descendent_of(csch, ctab, psch, ptab, desc) should have the proper description -ok 189 - is_descendent_of(csch, ctab, psch, ptab, desc) should have the proper diagnostics -ok 190 - is_descendent_of(csch, ctab2, psch, ptab, desc) should pass -ok 191 - is_descendent_of(csch, ctab2, psch, ptab, desc) should have the proper description -ok 192 - is_descendent_of(csch, ctab2, psch, ptab, desc) should have the proper diagnostics -ok 193 - is_descendent_of(csch, ctab, psch, ptab) should pass -ok 194 - is_descendent_of(csch, ctab, psch, ptab) should have the proper description -ok 195 - is_descendent_of(csch, ctab, psch, ptab) should have the proper diagnostics -ok 196 - is_descendent_of(csch, ctab2, psch, ptab) should pass -ok 197 - is_descendent_of(csch, ctab2, psch, ptab) should have the proper description -ok 198 - is_descendent_of(csch, ctab2, psch, ptab) should have the proper diagnostics -ok 199 - is_descendent_of(csch, ctab, psch, nope) should fail -ok 200 - is_descendent_of(csch, ctab, psch, nope) should have the proper description -ok 201 - is_descendent_of(csch, ctab, psch, nope) should have the proper diagnostics -ok 202 - is_descendent_of(csch, nope, psch, ptab) should fail -ok 203 - is_descendent_of(csch, nope, psch, ptab) should have the proper description -ok 204 - is_descendent_of(csch, nope, psch, ptab) should have the proper diagnostics -ok 205 - is_descendent_of(ctab, ptab, 1, desc) should pass -ok 206 - is_descendent_of(ctab, ptab, 1, desc) should have the proper description -ok 207 - is_descendent_of(ctab, ptab, 1, desc) should have the proper diagnostics -ok 208 - is_descendent_of(ctab, ptab, 2, desc) should pass -ok 209 - is_descendent_of(ctab, ptab, 2, desc) should have the proper description -ok 210 - is_descendent_of(ctab, ptab, 2, desc) should have the proper diagnostics -ok 211 - is_descendent_of(ctab, ptab, 1, desc) fail should fail -ok 212 - is_descendent_of(ctab, ptab, 1, desc) fail should have the proper description -ok 213 - is_descendent_of(ctab, ptab, 1, desc) fail should have the proper diagnostics -ok 214 - is_descendent_of(nope, ptab, 1, desc) should fail -ok 215 - is_descendent_of(nope, ptab, 1, desc) should have the proper description -ok 216 - is_descendent_of(nope, ptab, 1, desc) should have the proper diagnostics -ok 217 - is_descendent_of(ctab, ptab, 1) should pass -ok 218 - is_descendent_of(ctab, ptab, 1) should have the proper description -ok 219 - is_descendent_of(ctab, ptab, 1) should have the proper diagnostics -ok 220 - is_descendent_of(ctab, ptab, 2) should pass -ok 221 - is_descendent_of(ctab, ptab, 2) should have the proper description -ok 222 - is_descendent_of(ctab, ptab, 2) should have the proper diagnostics -ok 223 - is_descendent_of(ctab, ptab, 1) fail should fail -ok 224 - is_descendent_of(ctab, ptab, 1) fail should have the proper description -ok 225 - is_descendent_of(ctab, ptab, 1) fail should have the proper diagnostics -ok 226 - is_descendent_of(nope, ptab, 1) should fail -ok 227 - is_descendent_of(nope, ptab, 1) should have the proper description -ok 228 - is_descendent_of(nope, ptab, 1) should have the proper diagnostics -ok 229 - is_descendent_of(ctab, ptab) should pass -ok 230 - is_descendent_of(ctab, ptab) should have the proper description -ok 231 - is_descendent_of(ctab, ptab) should have the proper diagnostics -ok 232 - is_descendent_of( ctab2, ptab ) should pass -ok 233 - is_descendent_of( ctab2, ptab ) should have the proper description -ok 234 - is_descendent_of( ctab2, ptab ) should have the proper diagnostics -ok 235 - is_descendent_of(nope, ptab) should fail -ok 236 - is_descendent_of(nope, ptab) should have the proper description -ok 237 - is_descendent_of(nope, ptab) should have the proper diagnostics -ok 238 - is_descendent_of(ctab2, nope) should fail -ok 239 - is_descendent_of(ctab2, nope) should have the proper description -ok 240 - is_descendent_of(ctab2, nope) should have the proper diagnostics +ok 157 - isnt_ancestor_of(psch, ptab, csch, ctab, 1, desc) should fail +ok 158 - isnt_ancestor_of(psch, ptab, csch, ctab, 1, desc) should have the proper description +ok 159 - isnt_ancestor_of(psch, ptab, csch, ctab, 1, desc) should have the proper diagnostics +ok 160 - isnt_ancestor_of(psch, ptab, csch, ctab, 2, desc) should fail +ok 161 - isnt_ancestor_of(psch, ptab, csch, ctab, 2, desc) should have the proper description +ok 162 - isnt_ancestor_of(psch, ptab, csch, ctab, 2, desc) should have the proper diagnostics +ok 163 - isnt_ancestor_of(psch, nope, csch, ctab, 1, desc) should pass +ok 164 - isnt_ancestor_of(psch, nope, csch, ctab, 1, desc) should have the proper description +ok 165 - isnt_ancestor_of(psch, nope, csch, ctab, 1, desc) should have the proper diagnostics +ok 166 - isnt_ancestor_of(psch, ptab, csch, nope, desc) should pass +ok 167 - isnt_ancestor_of(psch, ptab, csch, nope, desc) should have the proper description +ok 168 - isnt_ancestor_of(psch, ptab, csch, nope, desc) should have the proper diagnostics +ok 169 - isnt_ancestor_of(psch, ptab, csch, ctab, 1) should fail +ok 170 - isnt_ancestor_of(psch, ptab, csch, ctab, 1) should have the proper description +ok 171 - isnt_ancestor_of(psch, ptab, csch, ctab, 1) should have the proper diagnostics +ok 172 - isnt_ancestor_of(psch, nope, csch, ctab, 1) should pass +ok 173 - isnt_ancestor_of(psch, nope, csch, ctab, 1) should have the proper description +ok 174 - isnt_ancestor_of(psch, nope, csch, ctab, 1) should have the proper diagnostics +ok 175 - isnt_ancestor_of(psch, ptab, csch, nope, 1) should pass +ok 176 - isnt_ancestor_of(psch, ptab, csch, nope, 1) should have the proper description +ok 177 - isnt_ancestor_of(psch, ptab, csch, nope, 1) should have the proper diagnostics +ok 178 - isnt_ancestor_of(psch, ptab, csch, ctab, 2) should fail +ok 179 - isnt_ancestor_of(psch, ptab, csch, ctab, 2) should have the proper description +ok 180 - isnt_ancestor_of(psch, ptab, csch, ctab, 2) should have the proper diagnostics +ok 181 - isnt_ancestor_of(psch, nope, csch, ctab, 2) should pass +ok 182 - isnt_ancestor_of(psch, nope, csch, ctab, 2) should have the proper description +ok 183 - isnt_ancestor_of(psch, nope, csch, ctab, 2) should have the proper diagnostics +ok 184 - isnt_ancestor_of(psch, ptab, csch, nope, 2) should pass +ok 185 - isnt_ancestor_of(psch, ptab, csch, nope, 2) should have the proper description +ok 186 - isnt_ancestor_of(psch, ptab, csch, nope, 2) should have the proper diagnostics +ok 187 - isnt_ancestor_of(psch, ptab, csch, ctab, desc) should fail +ok 188 - isnt_ancestor_of(psch, ptab, csch, ctab, desc) should have the proper description +ok 189 - isnt_ancestor_of(psch, ptab, csch, ctab, desc) should have the proper diagnostics +ok 190 - isnt_ancestor_of(psch, ptab, csch, ctab2, desc) should fail +ok 191 - isnt_ancestor_of(psch, ptab, csch, ctab2, desc) should have the proper description +ok 192 - isnt_ancestor_of(psch, ptab, csch, ctab2, desc) should have the proper diagnostics +ok 193 - isnt_ancestor_of(psch, ptab, csch, ctab) should fail +ok 194 - isnt_ancestor_of(psch, ptab, csch, ctab) should have the proper description +ok 195 - isnt_ancestor_of(psch, ptab, csch, ctab) should have the proper diagnostics +ok 196 - isnt_ancestor_of(psch, ptab, csch, ctab2) should fail +ok 197 - isnt_ancestor_of(psch, ptab, csch, ctab2) should have the proper description +ok 198 - isnt_ancestor_of(psch, ptab, csch, ctab2) should have the proper diagnostics +ok 199 - isnt_ancestor_of(psch, nope, csch, ctab) should pass +ok 200 - isnt_ancestor_of(psch, nope, csch, ctab) should have the proper description +ok 201 - isnt_ancestor_of(psch, nope, csch, ctab) should have the proper diagnostics +ok 202 - isnt_ancestor_of(psch, ptab, csch, nope) should pass +ok 203 - isnt_ancestor_of(psch, ptab, csch, nope) should have the proper description +ok 204 - isnt_ancestor_of(psch, ptab, csch, nope) should have the proper diagnostics +ok 205 - isnt_ancestor_of(ptab, ctab, 1, desc) should fail +ok 206 - isnt_ancestor_of(ptab, ctab, 1, desc) should have the proper description +ok 207 - isnt_ancestor_of(ptab, ctab, 1, desc) should have the proper diagnostics +ok 208 - isnt_ancestor_of(ptab, ctab, 2, desc) should fail +ok 209 - isnt_ancestor_of(ptab, ctab, 2, desc) should have the proper description +ok 210 - isnt_ancestor_of(ptab, ctab, 2, desc) should have the proper diagnostics +ok 211 - isnt_ancestor_of(ptab, ctab, 1, desc) fail should pass +ok 212 - isnt_ancestor_of(ptab, ctab, 1, desc) fail should have the proper description +ok 213 - isnt_ancestor_of(ptab, ctab, 1, desc) fail should have the proper diagnostics +ok 214 - isnt_ancestor_of(ptab, nope, 1, desc) should pass +ok 215 - isnt_ancestor_of(ptab, nope, 1, desc) should have the proper description +ok 216 - isnt_ancestor_of(ptab, nope, 1, desc) should have the proper diagnostics +ok 217 - isnt_ancestor_of(ptab, ctab, 1) should fail +ok 218 - isnt_ancestor_of(ptab, ctab, 1) should have the proper description +ok 219 - isnt_ancestor_of(ptab, ctab, 1) should have the proper diagnostics +ok 220 - isnt_ancestor_of(ptab, ctab, 2) should fail +ok 221 - isnt_ancestor_of(ptab, ctab, 2) should have the proper description +ok 222 - isnt_ancestor_of(ptab, ctab, 2) should have the proper diagnostics +ok 223 - isnt_ancestor_of(ptab, ctab, 1) fail should pass +ok 224 - isnt_ancestor_of(ptab, ctab, 1) fail should have the proper description +ok 225 - isnt_ancestor_of(ptab, ctab, 1) fail should have the proper diagnostics +ok 226 - isnt_ancestor_of(ptab, nope, 1) should pass +ok 227 - isnt_ancestor_of(ptab, nope, 1) should have the proper description +ok 228 - isnt_ancestor_of(ptab, nope, 1) should have the proper diagnostics +ok 229 - isnt_ancestor_of(ptab, ctab) should fail +ok 230 - isnt_ancestor_of(ptab, ctab) should have the proper description +ok 231 - isnt_ancestor_of(ptab, ctab) should have the proper diagnostics +ok 232 - isnt_ancestor_of(ptab, ctab2) should fail +ok 233 - isnt_ancestor_of(ptab, ctab2) should have the proper description +ok 234 - isnt_ancestor_of(ptab, ctab2) should have the proper diagnostics +ok 235 - isnt_ancestor_of(ptab, nope) should pass +ok 236 - isnt_ancestor_of(ptab, nope) should have the proper description +ok 237 - isnt_ancestor_of(ptab, nope) should have the proper diagnostics +ok 238 - isnt_ancestor_of(nope, ctab2) should pass +ok 239 - isnt_ancestor_of(nope, ctab2) should have the proper description +ok 240 - isnt_ancestor_of(nope, ctab2) should have the proper diagnostics +ok 241 - is_descendent_of(csch, ctab, psch, ptab, 1, desc) should pass +ok 242 - is_descendent_of(csch, ctab, psch, ptab, 1, desc) should have the proper description +ok 243 - is_descendent_of(csch, ctab, psch, ptab, 1, desc) should have the proper diagnostics +ok 244 - is_descendent_of(csch, ctab, psch, ptab, 2, desc) should pass +ok 245 - is_descendent_of(csch, ctab, psch, ptab, 2, desc) should have the proper description +ok 246 - is_descendent_of(csch, ctab, psch, ptab, 2, desc) should have the proper diagnostics +ok 247 - is_descendent_of(csch, ctab, psch, nope, 1, desc) should fail +ok 248 - is_descendent_of(csch, ctab, psch, nope, 1, desc) should have the proper description +ok 249 - is_descendent_of(csch, ctab, psch, nope, 1, desc) should have the proper diagnostics +ok 250 - is_descendent_of(csch, nope, psch, ptab, desc) should fail +ok 251 - is_descendent_of(csch, nope, psch, ptab, desc) should have the proper description +ok 252 - is_descendent_of(csch, nope, psch, ptab, desc) should have the proper diagnostics +ok 253 - is_descendent_of(csch, ctab, psch, ptab, 1) should pass +ok 254 - is_descendent_of(csch, ctab, psch, ptab, 1) should have the proper description +ok 255 - is_descendent_of(csch, ctab, psch, ptab, 1) should have the proper diagnostics +ok 256 - is_descendent_of(csch, ctab, psch, nope, 1) should fail +ok 257 - is_descendent_of(csch, ctab, psch, nope, 1) should have the proper description +ok 258 - is_descendent_of(csch, ctab, psch, nope, 1) should have the proper diagnostics +ok 259 - is_descendent_of(csch, nope, psch, ptab, 1) should fail +ok 260 - is_descendent_of(csch, nope, psch, ptab, 1) should have the proper description +ok 261 - is_descendent_of(csch, nope, psch, ptab, 1) should have the proper diagnostics +ok 262 - is_descendent_of(csch, ctab, psch, ptab, 2) should pass +ok 263 - is_descendent_of(csch, ctab, psch, ptab, 2) should have the proper description +ok 264 - is_descendent_of(csch, ctab, psch, ptab, 2) should have the proper diagnostics +ok 265 - is_descendent_of(csch, ctab, psch, nope, 2) should fail +ok 266 - is_descendent_of(csch, ctab, psch, nope, 2) should have the proper description +ok 267 - is_descendent_of(csch, ctab, psch, nope, 2) should have the proper diagnostics +ok 268 - is_descendent_of(csch, nope, psch, ptab, 2) should fail +ok 269 - is_descendent_of(csch, nope, psch, ptab, 2) should have the proper description +ok 270 - is_descendent_of(csch, nope, psch, ptab, 2) should have the proper diagnostics +ok 271 - is_descendent_of(csch, ctab, psch, ptab, desc) should pass +ok 272 - is_descendent_of(csch, ctab, psch, ptab, desc) should have the proper description +ok 273 - is_descendent_of(csch, ctab, psch, ptab, desc) should have the proper diagnostics +ok 274 - is_descendent_of(csch, ctab2, psch, ptab, desc) should pass +ok 275 - is_descendent_of(csch, ctab2, psch, ptab, desc) should have the proper description +ok 276 - is_descendent_of(csch, ctab2, psch, ptab, desc) should have the proper diagnostics +ok 277 - is_descendent_of(csch, ctab, psch, ptab) should pass +ok 278 - is_descendent_of(csch, ctab, psch, ptab) should have the proper description +ok 279 - is_descendent_of(csch, ctab, psch, ptab) should have the proper diagnostics +ok 280 - is_descendent_of(csch, ctab2, psch, ptab) should pass +ok 281 - is_descendent_of(csch, ctab2, psch, ptab) should have the proper description +ok 282 - is_descendent_of(csch, ctab2, psch, ptab) should have the proper diagnostics +ok 283 - is_descendent_of(csch, ctab, psch, nope) should fail +ok 284 - is_descendent_of(csch, ctab, psch, nope) should have the proper description +ok 285 - is_descendent_of(csch, ctab, psch, nope) should have the proper diagnostics +ok 286 - is_descendent_of(csch, nope, psch, ptab) should fail +ok 287 - is_descendent_of(csch, nope, psch, ptab) should have the proper description +ok 288 - is_descendent_of(csch, nope, psch, ptab) should have the proper diagnostics +ok 289 - is_descendent_of(ctab, ptab, 1, desc) should pass +ok 290 - is_descendent_of(ctab, ptab, 1, desc) should have the proper description +ok 291 - is_descendent_of(ctab, ptab, 1, desc) should have the proper diagnostics +ok 292 - is_descendent_of(ctab, ptab, 2, desc) should pass +ok 293 - is_descendent_of(ctab, ptab, 2, desc) should have the proper description +ok 294 - is_descendent_of(ctab, ptab, 2, desc) should have the proper diagnostics +ok 295 - is_descendent_of(ctab, ptab, 1, desc) fail should fail +ok 296 - is_descendent_of(ctab, ptab, 1, desc) fail should have the proper description +ok 297 - is_descendent_of(ctab, ptab, 1, desc) fail should have the proper diagnostics +ok 298 - is_descendent_of(nope, ptab, 1, desc) should fail +ok 299 - is_descendent_of(nope, ptab, 1, desc) should have the proper description +ok 300 - is_descendent_of(nope, ptab, 1, desc) should have the proper diagnostics +ok 301 - is_descendent_of(ctab, ptab, 1) should pass +ok 302 - is_descendent_of(ctab, ptab, 1) should have the proper description +ok 303 - is_descendent_of(ctab, ptab, 1) should have the proper diagnostics +ok 304 - is_descendent_of(ctab, ptab, 2) should pass +ok 305 - is_descendent_of(ctab, ptab, 2) should have the proper description +ok 306 - is_descendent_of(ctab, ptab, 2) should have the proper diagnostics +ok 307 - is_descendent_of(ctab, ptab, 1) fail should fail +ok 308 - is_descendent_of(ctab, ptab, 1) fail should have the proper description +ok 309 - is_descendent_of(ctab, ptab, 1) fail should have the proper diagnostics +ok 310 - is_descendent_of(nope, ptab, 1) should fail +ok 311 - is_descendent_of(nope, ptab, 1) should have the proper description +ok 312 - is_descendent_of(nope, ptab, 1) should have the proper diagnostics +ok 313 - is_descendent_of(ctab, ptab) should pass +ok 314 - is_descendent_of(ctab, ptab) should have the proper description +ok 315 - is_descendent_of(ctab, ptab) should have the proper diagnostics +ok 316 - is_descendent_of( ctab2, ptab ) should pass +ok 317 - is_descendent_of( ctab2, ptab ) should have the proper description +ok 318 - is_descendent_of( ctab2, ptab ) should have the proper diagnostics +ok 319 - is_descendent_of(nope, ptab) should fail +ok 320 - is_descendent_of(nope, ptab) should have the proper description +ok 321 - is_descendent_of(nope, ptab) should have the proper diagnostics +ok 322 - is_descendent_of(ctab2, nope) should fail +ok 323 - is_descendent_of(ctab2, nope) should have the proper description +ok 324 - is_descendent_of(ctab2, nope) should have the proper diagnostics +ok 325 - isnt_descendent_of(csch, ctab, psch, ptab, 1, desc) should fail +ok 326 - isnt_descendent_of(csch, ctab, psch, ptab, 1, desc) should have the proper description +ok 327 - isnt_descendent_of(csch, ctab, psch, ptab, 1, desc) should have the proper diagnostics +ok 328 - isnt_descendent_of(csch, ctab, psch, ptab, 2, desc) should fail +ok 329 - isnt_descendent_of(csch, ctab, psch, ptab, 2, desc) should have the proper description +ok 330 - isnt_descendent_of(csch, ctab, psch, ptab, 2, desc) should have the proper diagnostics +ok 331 - isnt_descendent_of(csch, ctab, psch, nope, 1, desc) should pass +ok 332 - isnt_descendent_of(csch, ctab, psch, nope, 1, desc) should have the proper description +ok 333 - isnt_descendent_of(csch, ctab, psch, nope, 1, desc) should have the proper diagnostics +ok 334 - isnt_descendent_of(csch, nope, psch, ptab, desc) should pass +ok 335 - isnt_descendent_of(csch, nope, psch, ptab, desc) should have the proper description +ok 336 - isnt_descendent_of(csch, nope, psch, ptab, desc) should have the proper diagnostics +ok 337 - isnt_descendent_of(csch, ctab, psch, ptab, 1) should fail +ok 338 - isnt_descendent_of(csch, ctab, psch, ptab, 1) should have the proper description +ok 339 - isnt_descendent_of(csch, ctab, psch, ptab, 1) should have the proper diagnostics +ok 340 - isnt_descendent_of(csch, ctab, psch, nope, 1) should pass +ok 341 - isnt_descendent_of(csch, ctab, psch, nope, 1) should have the proper description +ok 342 - isnt_descendent_of(csch, ctab, psch, nope, 1) should have the proper diagnostics +ok 343 - isnt_descendent_of(csch, nope, psch, ptab, 1) should pass +ok 344 - isnt_descendent_of(csch, nope, psch, ptab, 1) should have the proper description +ok 345 - isnt_descendent_of(csch, nope, psch, ptab, 1) should have the proper diagnostics +ok 346 - isnt_descendent_of(csch, ctab, psch, ptab, 2) should fail +ok 347 - isnt_descendent_of(csch, ctab, psch, ptab, 2) should have the proper description +ok 348 - isnt_descendent_of(csch, ctab, psch, ptab, 2) should have the proper diagnostics +ok 349 - isnt_descendent_of(csch, ctab, psch, nope, 2) should pass +ok 350 - isnt_descendent_of(csch, ctab, psch, nope, 2) should have the proper description +ok 351 - isnt_descendent_of(csch, ctab, psch, nope, 2) should have the proper diagnostics +ok 352 - isnt_descendent_of(csch, nope, psch, ptab, 2) should pass +ok 353 - isnt_descendent_of(csch, nope, psch, ptab, 2) should have the proper description +ok 354 - isnt_descendent_of(csch, nope, psch, ptab, 2) should have the proper diagnostics +ok 355 - isnt_descendent_of(csch, ctab, psch, ptab, desc) should fail +ok 356 - isnt_descendent_of(csch, ctab, psch, ptab, desc) should have the proper description +ok 357 - isnt_descendent_of(csch, ctab, psch, ptab, desc) should have the proper diagnostics +ok 358 - isnt_descendent_of(csch, ctab2, psch, ptab, desc) should fail +ok 359 - isnt_descendent_of(csch, ctab2, psch, ptab, desc) should have the proper description +ok 360 - isnt_descendent_of(csch, ctab2, psch, ptab, desc) should have the proper diagnostics +ok 361 - isnt_descendent_of(csch, ctab, psch, ptab) should fail +ok 362 - isnt_descendent_of(csch, ctab, psch, ptab) should have the proper description +ok 363 - isnt_descendent_of(csch, ctab, psch, ptab) should have the proper diagnostics +ok 364 - isnt_descendent_of(csch, ctab2, psch, ptab) should fail +ok 365 - isnt_descendent_of(csch, ctab2, psch, ptab) should have the proper description +ok 366 - isnt_descendent_of(csch, ctab2, psch, ptab) should have the proper diagnostics +ok 367 - isnt_descendent_of(csch, ctab, psch, nope) should pass +ok 368 - isnt_descendent_of(csch, ctab, psch, nope) should have the proper description +ok 369 - isnt_descendent_of(csch, ctab, psch, nope) should have the proper diagnostics +ok 370 - isnt_descendent_of(csch, nope, psch, ptab) should pass +ok 371 - isnt_descendent_of(csch, nope, psch, ptab) should have the proper description +ok 372 - isnt_descendent_of(csch, nope, psch, ptab) should have the proper diagnostics +ok 373 - isnt_descendent_of(ctab, ptab, 1, desc) should fail +ok 374 - isnt_descendent_of(ctab, ptab, 1, desc) should have the proper description +ok 375 - isnt_descendent_of(ctab, ptab, 1, desc) should have the proper diagnostics +ok 376 - isnt_descendent_of(ctab, ptab, 2, desc) should fail +ok 377 - isnt_descendent_of(ctab, ptab, 2, desc) should have the proper description +ok 378 - isnt_descendent_of(ctab, ptab, 2, desc) should have the proper diagnostics +ok 379 - isnt_descendent_of(ctab, ptab, 1, desc) fail should pass +ok 380 - isnt_descendent_of(ctab, ptab, 1, desc) fail should have the proper description +ok 381 - isnt_descendent_of(ctab, ptab, 1, desc) fail should have the proper diagnostics +ok 382 - isnt_descendent_of(nope, ptab, 1, desc) should pass +ok 383 - isnt_descendent_of(nope, ptab, 1, desc) should have the proper description +ok 384 - isnt_descendent_of(nope, ptab, 1, desc) should have the proper diagnostics +ok 385 - isnt_descendent_of(ctab, ptab, 1) should fail +ok 386 - isnt_descendent_of(ctab, ptab, 1) should have the proper description +ok 387 - isnt_descendent_of(ctab, ptab, 1) should have the proper diagnostics +ok 388 - isnt_descendent_of(ctab, ptab, 2) should fail +ok 389 - isnt_descendent_of(ctab, ptab, 2) should have the proper description +ok 390 - isnt_descendent_of(ctab, ptab, 2) should have the proper diagnostics +ok 391 - isnt_descendent_of(ctab, ptab, 1) fail should pass +ok 392 - isnt_descendent_of(ctab, ptab, 1) fail should have the proper description +ok 393 - isnt_descendent_of(ctab, ptab, 1) fail should have the proper diagnostics +ok 394 - isnt_descendent_of(nope, ptab, 1) should pass +ok 395 - isnt_descendent_of(nope, ptab, 1) should have the proper description +ok 396 - isnt_descendent_of(nope, ptab, 1) should have the proper diagnostics +ok 397 - isnt_descendent_of(ctab, ptab) should fail +ok 398 - isnt_descendent_of(ctab, ptab) should have the proper description +ok 399 - isnt_descendent_of(ctab, ptab) should have the proper diagnostics +ok 400 - isnt_descendent_of( ctab2, ptab ) should fail +ok 401 - isnt_descendent_of( ctab2, ptab ) should have the proper description +ok 402 - isnt_descendent_of( ctab2, ptab ) should have the proper diagnostics +ok 403 - isnt_descendent_of(nope, ptab) should pass +ok 404 - isnt_descendent_of(nope, ptab) should have the proper description +ok 405 - isnt_descendent_of(nope, ptab) should have the proper diagnostics +ok 406 - isnt_descendent_of(ctab2, nope) should pass +ok 407 - isnt_descendent_of(ctab2, nope) should have the proper description +ok 408 - isnt_descendent_of(ctab2, nope) should have the proper diagnostics diff --git a/test/sql/inheritance.sql b/test/sql/inheritance.sql index cc30b07c2a95..f4c113172d3f 100644 --- a/test/sql/inheritance.sql +++ b/test/sql/inheritance.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan( 240 ); ---SELECT * FROM no_plan(); +SELECT plan( 408 ); +-- SELECT * FROM no_plan(); SET client_min_messages = warning; -- Create inherited tables @@ -434,6 +434,231 @@ SELECT * FROM check_test( '' ); +-- test isnt_ancestor_of +SELECT * FROM check_test( + isnt_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child1', 1, 'Lookie' ), + false, + 'isnt_ancestor_of(psch, ptab, csch, ctab, 1, desc)', + 'Lookie', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child2', 2, 'Lookie' ), + false, + 'isnt_ancestor_of(psch, ptab, csch, ctab, 2, desc)', + 'Lookie', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'hide', 'h_parent', 'nope', 'h_child2', 1, 'Lookie' ), + true, + 'isnt_ancestor_of(psch, nope, csch, ctab, 1, desc)', + 'Lookie', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'hide', 'h_parent', 'hide', 'nope', 1, 'Lookie' ), + true, + 'isnt_ancestor_of(psch, ptab, csch, nope, desc)', + 'Lookie', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child1', 1 ), + false, + 'isnt_ancestor_of(psch, ptab, csch, ctab, 1)', + 'Table hide.h_parent should not be ancestor 1 for hide.h_child1', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'hide', 'nope', 'hide', 'h_child1', 1 ), + true, + 'isnt_ancestor_of(psch, nope, csch, ctab, 1)', + 'Table hide.nope should not be ancestor 1 for hide.h_child1', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'hide', 'h_parent', 'hide', 'nope', 1 ), + true, + 'isnt_ancestor_of(psch, ptab, csch, nope, 1)', + 'Table hide.h_parent should not be ancestor 1 for hide.nope', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child2', 2 ), + false, + 'isnt_ancestor_of(psch, ptab, csch, ctab, 2)', + 'Table hide.h_parent should not be ancestor 2 for hide.h_child2', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'hide', 'nope', 'hide', 'h_child2', 2 ), + true, + 'isnt_ancestor_of(psch, nope, csch, ctab, 2)', + 'Table hide.nope should not be ancestor 2 for hide.h_child2', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'hide', 'h_parent', 'hide', 'nope', 2 ), + true, + 'isnt_ancestor_of(psch, ptab, csch, nope, 2)', + 'Table hide.h_parent should not be ancestor 2 for hide.nope', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child1', 'Howdy' ), + false, + 'isnt_ancestor_of(psch, ptab, csch, ctab, desc)', + 'Howdy', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child2', 'Howdy' ), + false, + 'isnt_ancestor_of(psch, ptab, csch, ctab2, desc)', + 'Howdy', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child1'::name ), + false, + 'isnt_ancestor_of(psch, ptab, csch, ctab)', + 'Table hide.h_parent should not be an ancestor of hide.h_child1', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child2'::name ), + false, + 'isnt_ancestor_of(psch, ptab, csch, ctab2)', + 'Table hide.h_parent should not be an ancestor of hide.h_child2', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'hide', 'nope', 'hide', 'h_child1'::name ), + true, + 'isnt_ancestor_of(psch, nope, csch, ctab)', + 'Table hide.nope should not be an ancestor of hide.h_child1', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'hide', 'h_parent', 'hide', 'nope'::name ), + true, + 'isnt_ancestor_of(psch, ptab, csch, nope)', + 'Table hide.h_parent should not be an ancestor of hide.nope', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'parent', 'child1', 1, 'Howdy' ), + false, + 'isnt_ancestor_of(ptab, ctab, 1, desc)', + 'Howdy', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'parent', 'child2', 2, 'Howdy' ), + false, + 'isnt_ancestor_of(ptab, ctab, 2, desc)', + 'Howdy', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'parent', 'child2', 1, 'Howdy' ), + true, + 'isnt_ancestor_of(ptab, ctab, 1, desc) fail', + 'Howdy', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'parent', 'nope', 1, 'Howdy' ), + true, + 'isnt_ancestor_of(ptab, nope, 1, desc)', + 'Howdy', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'parent', 'child1', 1 ), + false, + 'isnt_ancestor_of(ptab, ctab, 1)', + 'Table parent should not be ancestor 1 of child1', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'parent', 'child2', 2 ), + false, + 'isnt_ancestor_of(ptab, ctab, 2)', + 'Table parent should not be ancestor 2 of child2', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'parent', 'child2', 1 ), + true, + 'isnt_ancestor_of(ptab, ctab, 1) fail', + 'Table parent should not be ancestor 1 of child2', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'parent', 'nope', 1 ), + true, + 'isnt_ancestor_of(ptab, nope, 1)', + 'Table parent should not be ancestor 1 of nope', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'parent', 'child1' ), + false, + 'isnt_ancestor_of(ptab, ctab)', + 'Table parent should not be an ancestor of child1', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'parent', 'child2' ), + false, + 'isnt_ancestor_of(ptab, ctab2)', + 'Table parent should not be an ancestor of child2', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'parent', 'nope' ), + true, + 'isnt_ancestor_of(ptab, nope)', + 'Table parent should not be an ancestor of nope', + '' +); + +SELECT * FROM check_test( + isnt_ancestor_of( 'nope', 'child1' ), + true, + 'isnt_ancestor_of(nope, ctab2)', + 'Table nope should not be an ancestor of child1', + '' +); + -- test is_descendent_of SELECT * FROM check_test( is_descendent_of( 'hide', 'h_child1', 'hide', 'h_parent', 1, 'Lookie' ), @@ -659,6 +884,231 @@ SELECT * FROM check_test( '' ); +-- test isnt_descendent_of +SELECT * FROM check_test( + isnt_descendent_of( 'hide', 'h_child1', 'hide', 'h_parent', 1, 'Lookie' ), + false, + 'isnt_descendent_of(csch, ctab, psch, ptab, 1, desc)', + 'Lookie', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'hide', 'h_child2', 'hide', 'h_parent', 2, 'Lookie' ), + false, + 'isnt_descendent_of(csch, ctab, psch, ptab, 2, desc)', + 'Lookie', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'nope', 'h_child2', 'hide', 'h_parent', 1, 'Lookie' ), + true, + 'isnt_descendent_of(csch, ctab, psch, nope, 1, desc)', + 'Lookie', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'hide', 'nope', 'hide', 'h_parent', 1, 'Lookie' ), + true, + 'isnt_descendent_of(csch, nope, psch, ptab, desc)', + 'Lookie', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'hide', 'h_child1', 'hide', 'h_parent', 1 ), + false, + 'isnt_descendent_of(csch, ctab, psch, ptab, 1)', + 'Table hide.h_child1 should not be descendent 1 from hide.h_parent', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'hide', 'h_child1', 'hide', 'nope', 1 ), + true, + 'isnt_descendent_of(csch, ctab, psch, nope, 1)', + 'Table hide.h_child1 should not be descendent 1 from hide.nope', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'hide', 'nope', 'hide', 'h_parent', 1 ), + true, + 'isnt_descendent_of(csch, nope, psch, ptab, 1)', + 'Table hide.nope should not be descendent 1 from hide.h_parent', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'hide', 'h_child2', 'hide', 'h_parent', 2 ), + false, + 'isnt_descendent_of(csch, ctab, psch, ptab, 2)', + 'Table hide.h_child2 should not be descendent 2 from hide.h_parent', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'hide', 'h_child2', 'hide', 'nope', 2 ), + true, + 'isnt_descendent_of(csch, ctab, psch, nope, 2)', + 'Table hide.h_child2 should not be descendent 2 from hide.nope', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'hide', 'nope', 'hide', 'h_parent', 2 ), + true, + 'isnt_descendent_of(csch, nope, psch, ptab, 2)', + 'Table hide.nope should not be descendent 2 from hide.h_parent', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'hide', 'h_child1', 'hide', 'h_parent', 'Howdy' ), + false, + 'isnt_descendent_of(csch, ctab, psch, ptab, desc)', + 'Howdy', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'hide', 'h_child2', 'hide', 'h_parent', 'Howdy' ), + false, + 'isnt_descendent_of(csch, ctab2, psch, ptab, desc)', + 'Howdy', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'hide', 'h_child1', 'hide', 'h_parent'::name ), + false, + 'isnt_descendent_of(csch, ctab, psch, ptab)', + 'Table hide.h_child1 should not be a descendent of hide.h_parent', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'hide', 'h_child2', 'hide', 'h_parent'::name ), + false, + 'isnt_descendent_of(csch, ctab2, psch, ptab)', + 'Table hide.h_child2 should not be a descendent of hide.h_parent', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'hide', 'h_child1', 'hide', 'nope'::name ), + true, + 'isnt_descendent_of(csch, ctab, psch, nope)', + 'Table hide.h_child1 should not be a descendent of hide.nope', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'hide', 'nope', 'hide', 'h_parent'::name ), + true, + 'isnt_descendent_of(csch, nope, psch, ptab)', + 'Table hide.nope should not be a descendent of hide.h_parent', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'child1', 'parent', 1, 'Howdy' ), + false, + 'isnt_descendent_of(ctab, ptab, 1, desc)', + 'Howdy', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'child2', 'parent', 2, 'Howdy' ), + false, + 'isnt_descendent_of(ctab, ptab, 2, desc)', + 'Howdy', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'child2', 'parent', 1, 'Howdy' ), + true, + 'isnt_descendent_of(ctab, ptab, 1, desc) fail', + 'Howdy', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'nope', 'parent', 1, 'Howdy' ), + true, + 'isnt_descendent_of(nope, ptab, 1, desc)', + 'Howdy', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'child1', 'parent', 1 ), + false, + 'isnt_descendent_of(ctab, ptab, 1)', + 'Table child1 should not be descendent 1 from parent', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'child2', 'parent', 2 ), + false, + 'isnt_descendent_of(ctab, ptab, 2)', + 'Table child2 should not be descendent 2 from parent', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'child2', 'parent', 1 ), + true, + 'isnt_descendent_of(ctab, ptab, 1) fail', + 'Table child2 should not be descendent 1 from parent', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'nope', 'parent', 1 ), + true, + 'isnt_descendent_of(nope, ptab, 1)', + 'Table nope should not be descendent 1 from parent', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'child1', 'parent' ), + false, + 'isnt_descendent_of(ctab, ptab)', + 'Table child1 should not be a descendent of parent', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'child2', 'parent' ), + false, + 'isnt_descendent_of( ctab2, ptab )', + 'Table child2 should not be a descendent of parent', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'nope', 'parent' ), + true, + 'isnt_descendent_of(nope, ptab)', + 'Table nope should not be a descendent of parent', + '' +); + +SELECT * FROM check_test( + isnt_descendent_of( 'child1', 'nope' ), + true, + 'isnt_descendent_of(ctab2, nope)', + 'Table child1 should not be a descendent of nope', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 05aa725e9d541e3c42c545faad8b6404775142ed Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 17:03:48 -0500 Subject: [PATCH 1055/1195] Add inheritannce functions to upgrade script. --- sql/pgtap--0.99.0--1.0.0.sql | 448 +++++++++++++++++++++++++++++++++++ 1 file changed, 448 insertions(+) diff --git a/sql/pgtap--0.99.0--1.0.0.sql b/sql/pgtap--0.99.0--1.0.0.sql index 6305b021f017..54b79a77f938 100644 --- a/sql/pgtap--0.99.0--1.0.0.sql +++ b/sql/pgtap--0.99.0--1.0.0.sql @@ -264,3 +264,451 @@ RETURNS BOOLEAN AS $$ AND is_visible ); $$ LANGUAGE SQL; + +/* + * 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; From 8522e7afd0f303f409b96cf58ca4c5837f649871 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 17:06:32 -0500 Subject: [PATCH 1056/1195] Record inheritance functions in Changes. --- Changes | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Changes b/Changes index b60347858e43..5ab164166b30 100644 --- a/Changes +++ b/Changes @@ -13,6 +13,14 @@ Revision history for pgTAP * Fixed an issue with `has_function()` where a test would fail if arguments are schema-qualified even though the schema is in the search path. Thanks to Sandro Santilli for the bug report (#179) and PR (#180). +* Added table inheritance testing functions: + + `has_inherited_tables()` + + `hasnt_inherited_tables()` + + `is_ancestor_of()` + + `isnt_ancestor_of()` + + `is_descendent_of()` + + `isnt_descendent_of()` + Thanks to Luca Ferrari for the inspiration. 0.99.0 2018-09-16T20:55:41Z --------------------------- From d04f723609b2899cce3dae6c42bfe5f9263b821d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 17:14:56 -0500 Subject: [PATCH 1057/1195] Remove ancestor functions on 8.3 --- compat/install-8.3.patch | 359 ++++++++++++++++++++++++++++++++ doc/pgtap.mmd | 16 +- test/expected/inheritance_1.out | 78 +++++++ 3 files changed, 449 insertions(+), 4 deletions(-) create mode 100644 test/expected/inheritance_1.out diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch index 757cd4fa0a3f..412e40bb0651 100644 --- a/compat/install-8.3.patch +++ b/compat/install-8.3.patch @@ -63,3 +63,362 @@ 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 +@@ -9856,358 +9855,3 @@ + '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; diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 4f2389aae149..fdd6bd74ece0 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -8321,10 +8321,18 @@ No changes. Everything should just work. * These permission-testing functions don't work, because one cannot grant permissions on the relevant objects until 8.4: - + `has_any_column_privilege()` - + `has_column_privilege()` - + `has_foreign_data_wrapper_privilege()` - + `has_server_privilege()` + + `has_any_column_privilege()` + + `has_column_privilege()` + + `has_foreign_data_wrapper_privilege()` + + `has_server_privilege()` + +* These inheritance-testing functions are not available, because internally + the use a recursive common table expression query not supported before 8.4: + + + `is_ancestor_of()` + + `isnt_ancestor_of()` + + `is_descendent_of()` + + `isnt_descendent_of()` 8.2 and Down ------------ diff --git a/test/expected/inheritance_1.out b/test/expected/inheritance_1.out new file mode 100644 index 000000000000..513c171443cc --- /dev/null +++ b/test/expected/inheritance_1.out @@ -0,0 +1,78 @@ +\unset ECHO +1..408 +ok 1 - has_inherited_tables(sch, tab, desc) should pass +ok 2 - has_inherited_tables(sch, tab, desc) should have the proper description +ok 3 - has_inherited_tables(sch, tab, desc) should have the proper diagnostics +ok 4 - has_inherited_tables(sch, tab, desc) fail should fail +ok 5 - has_inherited_tables(sch, tab, desc) fail should have the proper description +ok 6 - has_inherited_tables(sch, tab, desc) fail should have the proper diagnostics +ok 7 - has_inherited_tables(sch, nonesuch, desc) should fail +ok 8 - has_inherited_tables(sch, nonesuch, desc) should have the proper description +ok 9 - has_inherited_tables(sch, nonesuch, desc) should have the proper diagnostics +ok 10 - has_inherited_tables(sch, tab) should pass +ok 11 - has_inherited_tables(sch, tab) should have the proper description +ok 12 - has_inherited_tables(sch, tab) should have the proper diagnostics +ok 13 - has_inherited_tables(sch, tab) fail should fail +ok 14 - has_inherited_tables(sch, tab) fail should have the proper description +ok 15 - has_inherited_tables(sch, tab) fail should have the proper diagnostics +ok 16 - has_inherited_tables(sch, nonesuch) should fail +ok 17 - has_inherited_tables(sch, nonesuch) should have the proper description +ok 18 - has_inherited_tables(sch, nonesuch) should have the proper diagnostics +ok 19 - has_inherited_tables(tab, desc) should pass +ok 20 - has_inherited_tables(tab, desc) should have the proper description +ok 21 - has_inherited_tables(tab, desc) should have the proper diagnostics +ok 22 - has_inherited_tables(tab, desc) fail should fail +ok 23 - has_inherited_tables(tab, desc) fail should have the proper description +ok 24 - has_inherited_tables(tab, desc) fail should have the proper diagnostics +ok 25 - has_inherited_tables(nonesuch, desc) should fail +ok 26 - has_inherited_tables(nonesuch, desc) should have the proper description +ok 27 - has_inherited_tables(nonesuch, desc) should have the proper diagnostics +ok 28 - has_inherited_tables(tab) should pass +ok 29 - has_inherited_tables(tab) should have the proper description +ok 30 - has_inherited_tables(tab) should have the proper diagnostics +ok 31 - has_inherited_tables(tab) fail should fail +ok 32 - has_inherited_tables(tab) fail should have the proper description +ok 33 - has_inherited_tables(tab) fail should have the proper diagnostics +ok 34 - has_inherited_tables(nonesuch) should fail +ok 35 - has_inherited_tables(nonesuch) should have the proper description +ok 36 - has_inherited_tables(nonesuch) should have the proper diagnostics +ok 37 - hasnt_inherited_tables(sch, tab, desc) should pass +ok 38 - hasnt_inherited_tables(sch, tab, desc) should have the proper description +ok 39 - hasnt_inherited_tables(sch, tab, desc) should have the proper diagnostics +ok 40 - hasnt_inherited_tables(sch, tab, desc) fail should fail +ok 41 - hasnt_inherited_tables(sch, tab, desc) fail should have the proper description +ok 42 - hasnt_inherited_tables(sch, tab, desc) fail should have the proper diagnostics +ok 43 - hasnt_inherited_tables(sch, nonesuch, desc) should pass +ok 44 - hasnt_inherited_tables(sch, nonesuch, desc) should have the proper description +ok 45 - hasnt_inherited_tables(sch, nonesuch, desc) should have the proper diagnostics +ok 46 - hasnt_inherited_tables(sch, tab) should pass +ok 47 - hasnt_inherited_tables(sch, tab) should have the proper description +ok 48 - hasnt_inherited_tables(sch, tab) should have the proper diagnostics +ok 49 - hasnt_inherited_tables(sch, tab) fail should fail +ok 50 - hasnt_inherited_tables(sch, tab) fail should have the proper description +ok 51 - hasnt_inherited_tables(sch, tab) fail should have the proper diagnostics +ok 52 - hasnt_inherited_tables(sch, nonesuch) should pass +ok 53 - hasnt_inherited_tables(sch, nonesuch) should have the proper description +ok 54 - hasnt_inherited_tables(sch, nonesuch) should have the proper diagnostics +ok 55 - hasnt_inherited_tables(tab, desc) should pass +ok 56 - hasnt_inherited_tables(tab, desc) should have the proper description +ok 57 - hasnt_inherited_tables(tab, desc) should have the proper diagnostics +ok 58 - hasnt_inherited_tables(tab, desc) fail should fail +ok 59 - hasnt_inherited_tables(tab, desc) fail should have the proper description +ok 60 - hasnt_inherited_tables(tab, desc) fail should have the proper diagnostics +ok 61 - hasnt_inherited_tables(nonesuch, desc) should pass +ok 62 - hasnt_inherited_tables(nonesuch, desc) should have the proper description +ok 63 - hasnt_inherited_tables(nonesuch, desc) should have the proper diagnostics +ok 64 - hasnt_inherited_tables(tab) should pass +ok 65 - hasnt_inherited_tables(tab) should have the proper description +ok 66 - hasnt_inherited_tables(tab) should have the proper diagnostics +ok 67 - hasnt_inherited_tables(tab) fail should fail +ok 68 - hasnt_inherited_tables(tab) fail should have the proper description +ok 69 - hasnt_inherited_tables(tab) fail should have the proper diagnostics +ok 70 - hasnt_inherited_tables(nonesuch) should pass +ok 71 - hasnt_inherited_tables(nonesuch) should have the proper description +ok 72 - hasnt_inherited_tables(nonesuch) should have the proper diagnostics +ERROR: function is_ancestor_of(unknown, unknown, unknown, unknown, integer, unknown) does not exist +LINE 2: is_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child1', ... + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. From 2cce23374b0140180c6be7034aec1017333ac705 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 17:21:32 -0500 Subject: [PATCH 1058/1195] Add test output for inheritance on 8.1-8.2. --- test/expected/inheritance_2.out | 78 +++++++++++++++++++++++++++++++++ test/expected/inheritance_3.out | 76 ++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 test/expected/inheritance_2.out create mode 100644 test/expected/inheritance_3.out diff --git a/test/expected/inheritance_2.out b/test/expected/inheritance_2.out new file mode 100644 index 000000000000..fc8b08b16900 --- /dev/null +++ b/test/expected/inheritance_2.out @@ -0,0 +1,78 @@ +\unset ECHO +1..408 +ok 1 - has_inherited_tables(sch, tab, desc) should pass +ok 2 - has_inherited_tables(sch, tab, desc) should have the proper description +ok 3 - has_inherited_tables(sch, tab, desc) should have the proper diagnostics +ok 4 - has_inherited_tables(sch, tab, desc) fail should fail +ok 5 - has_inherited_tables(sch, tab, desc) fail should have the proper description +ok 6 - has_inherited_tables(sch, tab, desc) fail should have the proper diagnostics +ok 7 - has_inherited_tables(sch, nonesuch, desc) should fail +ok 8 - has_inherited_tables(sch, nonesuch, desc) should have the proper description +ok 9 - has_inherited_tables(sch, nonesuch, desc) should have the proper diagnostics +ok 10 - has_inherited_tables(sch, tab) should pass +ok 11 - has_inherited_tables(sch, tab) should have the proper description +ok 12 - has_inherited_tables(sch, tab) should have the proper diagnostics +ok 13 - has_inherited_tables(sch, tab) fail should fail +ok 14 - has_inherited_tables(sch, tab) fail should have the proper description +ok 15 - has_inherited_tables(sch, tab) fail should have the proper diagnostics +ok 16 - has_inherited_tables(sch, nonesuch) should fail +ok 17 - has_inherited_tables(sch, nonesuch) should have the proper description +ok 18 - has_inherited_tables(sch, nonesuch) should have the proper diagnostics +ok 19 - has_inherited_tables(tab, desc) should pass +ok 20 - has_inherited_tables(tab, desc) should have the proper description +ok 21 - has_inherited_tables(tab, desc) should have the proper diagnostics +ok 22 - has_inherited_tables(tab, desc) fail should fail +ok 23 - has_inherited_tables(tab, desc) fail should have the proper description +ok 24 - has_inherited_tables(tab, desc) fail should have the proper diagnostics +ok 25 - has_inherited_tables(nonesuch, desc) should fail +ok 26 - has_inherited_tables(nonesuch, desc) should have the proper description +ok 27 - has_inherited_tables(nonesuch, desc) should have the proper diagnostics +ok 28 - has_inherited_tables(tab) should pass +ok 29 - has_inherited_tables(tab) should have the proper description +ok 30 - has_inherited_tables(tab) should have the proper diagnostics +ok 31 - has_inherited_tables(tab) fail should fail +ok 32 - has_inherited_tables(tab) fail should have the proper description +ok 33 - has_inherited_tables(tab) fail should have the proper diagnostics +ok 34 - has_inherited_tables(nonesuch) should fail +ok 35 - has_inherited_tables(nonesuch) should have the proper description +ok 36 - has_inherited_tables(nonesuch) should have the proper diagnostics +ok 37 - hasnt_inherited_tables(sch, tab, desc) should pass +ok 38 - hasnt_inherited_tables(sch, tab, desc) should have the proper description +ok 39 - hasnt_inherited_tables(sch, tab, desc) should have the proper diagnostics +ok 40 - hasnt_inherited_tables(sch, tab, desc) fail should fail +ok 41 - hasnt_inherited_tables(sch, tab, desc) fail should have the proper description +ok 42 - hasnt_inherited_tables(sch, tab, desc) fail should have the proper diagnostics +ok 43 - hasnt_inherited_tables(sch, nonesuch, desc) should pass +ok 44 - hasnt_inherited_tables(sch, nonesuch, desc) should have the proper description +ok 45 - hasnt_inherited_tables(sch, nonesuch, desc) should have the proper diagnostics +ok 46 - hasnt_inherited_tables(sch, tab) should pass +ok 47 - hasnt_inherited_tables(sch, tab) should have the proper description +ok 48 - hasnt_inherited_tables(sch, tab) should have the proper diagnostics +ok 49 - hasnt_inherited_tables(sch, tab) fail should fail +ok 50 - hasnt_inherited_tables(sch, tab) fail should have the proper description +ok 51 - hasnt_inherited_tables(sch, tab) fail should have the proper diagnostics +ok 52 - hasnt_inherited_tables(sch, nonesuch) should pass +ok 53 - hasnt_inherited_tables(sch, nonesuch) should have the proper description +ok 54 - hasnt_inherited_tables(sch, nonesuch) should have the proper diagnostics +ok 55 - hasnt_inherited_tables(tab, desc) should pass +ok 56 - hasnt_inherited_tables(tab, desc) should have the proper description +ok 57 - hasnt_inherited_tables(tab, desc) should have the proper diagnostics +ok 58 - hasnt_inherited_tables(tab, desc) fail should fail +ok 59 - hasnt_inherited_tables(tab, desc) fail should have the proper description +ok 60 - hasnt_inherited_tables(tab, desc) fail should have the proper diagnostics +ok 61 - hasnt_inherited_tables(nonesuch, desc) should pass +ok 62 - hasnt_inherited_tables(nonesuch, desc) should have the proper description +ok 63 - hasnt_inherited_tables(nonesuch, desc) should have the proper diagnostics +ok 64 - hasnt_inherited_tables(tab) should pass +ok 65 - hasnt_inherited_tables(tab) should have the proper description +ok 66 - hasnt_inherited_tables(tab) should have the proper diagnostics +ok 67 - hasnt_inherited_tables(tab) fail should fail +ok 68 - hasnt_inherited_tables(tab) fail should have the proper description +ok 69 - hasnt_inherited_tables(tab) fail should have the proper diagnostics +ok 70 - hasnt_inherited_tables(nonesuch) should pass +ok 71 - hasnt_inherited_tables(nonesuch) should have the proper description +ok 72 - hasnt_inherited_tables(nonesuch) should have the proper diagnostics +ERROR: function is_ancestor_of("unknown", "unknown", "unknown", "unknown", integer, "unknown") does not exist +LINE 2: is_ancestor_of( 'hide', 'h_parent', 'hide', 'h_child1', ... + ^ +HINT: No function matches the given name and argument types. You may need to add explicit type casts. diff --git a/test/expected/inheritance_3.out b/test/expected/inheritance_3.out new file mode 100644 index 000000000000..280acce830b9 --- /dev/null +++ b/test/expected/inheritance_3.out @@ -0,0 +1,76 @@ +\unset ECHO +1..408 +ok 1 - has_inherited_tables(sch, tab, desc) should pass +ok 2 - has_inherited_tables(sch, tab, desc) should have the proper description +ok 3 - has_inherited_tables(sch, tab, desc) should have the proper diagnostics +ok 4 - has_inherited_tables(sch, tab, desc) fail should fail +ok 5 - has_inherited_tables(sch, tab, desc) fail should have the proper description +ok 6 - has_inherited_tables(sch, tab, desc) fail should have the proper diagnostics +ok 7 - has_inherited_tables(sch, nonesuch, desc) should fail +ok 8 - has_inherited_tables(sch, nonesuch, desc) should have the proper description +ok 9 - has_inherited_tables(sch, nonesuch, desc) should have the proper diagnostics +ok 10 - has_inherited_tables(sch, tab) should pass +ok 11 - has_inherited_tables(sch, tab) should have the proper description +ok 12 - has_inherited_tables(sch, tab) should have the proper diagnostics +ok 13 - has_inherited_tables(sch, tab) fail should fail +ok 14 - has_inherited_tables(sch, tab) fail should have the proper description +ok 15 - has_inherited_tables(sch, tab) fail should have the proper diagnostics +ok 16 - has_inherited_tables(sch, nonesuch) should fail +ok 17 - has_inherited_tables(sch, nonesuch) should have the proper description +ok 18 - has_inherited_tables(sch, nonesuch) should have the proper diagnostics +ok 19 - has_inherited_tables(tab, desc) should pass +ok 20 - has_inherited_tables(tab, desc) should have the proper description +ok 21 - has_inherited_tables(tab, desc) should have the proper diagnostics +ok 22 - has_inherited_tables(tab, desc) fail should fail +ok 23 - has_inherited_tables(tab, desc) fail should have the proper description +ok 24 - has_inherited_tables(tab, desc) fail should have the proper diagnostics +ok 25 - has_inherited_tables(nonesuch, desc) should fail +ok 26 - has_inherited_tables(nonesuch, desc) should have the proper description +ok 27 - has_inherited_tables(nonesuch, desc) should have the proper diagnostics +ok 28 - has_inherited_tables(tab) should pass +ok 29 - has_inherited_tables(tab) should have the proper description +ok 30 - has_inherited_tables(tab) should have the proper diagnostics +ok 31 - has_inherited_tables(tab) fail should fail +ok 32 - has_inherited_tables(tab) fail should have the proper description +ok 33 - has_inherited_tables(tab) fail should have the proper diagnostics +ok 34 - has_inherited_tables(nonesuch) should fail +ok 35 - has_inherited_tables(nonesuch) should have the proper description +ok 36 - has_inherited_tables(nonesuch) should have the proper diagnostics +ok 37 - hasnt_inherited_tables(sch, tab, desc) should pass +ok 38 - hasnt_inherited_tables(sch, tab, desc) should have the proper description +ok 39 - hasnt_inherited_tables(sch, tab, desc) should have the proper diagnostics +ok 40 - hasnt_inherited_tables(sch, tab, desc) fail should fail +ok 41 - hasnt_inherited_tables(sch, tab, desc) fail should have the proper description +ok 42 - hasnt_inherited_tables(sch, tab, desc) fail should have the proper diagnostics +ok 43 - hasnt_inherited_tables(sch, nonesuch, desc) should pass +ok 44 - hasnt_inherited_tables(sch, nonesuch, desc) should have the proper description +ok 45 - hasnt_inherited_tables(sch, nonesuch, desc) should have the proper diagnostics +ok 46 - hasnt_inherited_tables(sch, tab) should pass +ok 47 - hasnt_inherited_tables(sch, tab) should have the proper description +ok 48 - hasnt_inherited_tables(sch, tab) should have the proper diagnostics +ok 49 - hasnt_inherited_tables(sch, tab) fail should fail +ok 50 - hasnt_inherited_tables(sch, tab) fail should have the proper description +ok 51 - hasnt_inherited_tables(sch, tab) fail should have the proper diagnostics +ok 52 - hasnt_inherited_tables(sch, nonesuch) should pass +ok 53 - hasnt_inherited_tables(sch, nonesuch) should have the proper description +ok 54 - hasnt_inherited_tables(sch, nonesuch) should have the proper diagnostics +ok 55 - hasnt_inherited_tables(tab, desc) should pass +ok 56 - hasnt_inherited_tables(tab, desc) should have the proper description +ok 57 - hasnt_inherited_tables(tab, desc) should have the proper diagnostics +ok 58 - hasnt_inherited_tables(tab, desc) fail should fail +ok 59 - hasnt_inherited_tables(tab, desc) fail should have the proper description +ok 60 - hasnt_inherited_tables(tab, desc) fail should have the proper diagnostics +ok 61 - hasnt_inherited_tables(nonesuch, desc) should pass +ok 62 - hasnt_inherited_tables(nonesuch, desc) should have the proper description +ok 63 - hasnt_inherited_tables(nonesuch, desc) should have the proper diagnostics +ok 64 - hasnt_inherited_tables(tab) should pass +ok 65 - hasnt_inherited_tables(tab) should have the proper description +ok 66 - hasnt_inherited_tables(tab) should have the proper diagnostics +ok 67 - hasnt_inherited_tables(tab) fail should fail +ok 68 - hasnt_inherited_tables(tab) fail should have the proper description +ok 69 - hasnt_inherited_tables(tab) fail should have the proper diagnostics +ok 70 - hasnt_inherited_tables(nonesuch) should pass +ok 71 - hasnt_inherited_tables(nonesuch) should have the proper description +ok 72 - hasnt_inherited_tables(nonesuch) should have the proper diagnostics +ERROR: function is_ancestor_of("unknown", "unknown", "unknown", "unknown", integer, "unknown") does not exist +HINT: No function matches the given name and argument types. You may need to add explicit type casts. From 73fa3d32b481a68981c673cd3474b60672751b19 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 20 Nov 2018 17:35:04 -0500 Subject: [PATCH 1059/1195] Fix find-and-pastos. --- doc/pgtap.mmd | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index fdd6bd74ece0..a7c6c403d868 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -3482,7 +3482,7 @@ and the description. The columns argument may be a string naming one column or expression, or an array of column names and/or expressions. For expressions, you must use lowercase for all SQL keywords and functions to properly compare to PostgreSQL's internal form of the expression. Non-functional expressions -should also be wrapped in ancestorheses. A few examples: +should also be wrapped in parentheses. A few examples: SELECT has_index( 'myschema', @@ -4974,10 +4974,10 @@ specified table is *not* partitioned, or if it does not exist. ### `is_partition_of()` ### - SELECT is_ancestor( :child_schema, :child, :ancestor_schema, :ancestor_table, :description ); - SELECT is_ancestor( :child_schema, :child, :ancestor_schema, :ancestor_table ); - SELECT is_ancestor( :child, :ancestor_table, :description ); - SELECT is_ancestor( :child, :ancestor_table ); + SELECT is_parent( :child_schema, :child, :parent_schema, :parent_table, :description ); + SELECT is_parent( :child_schema, :child, :parent_schema, :parent_table ); + SELECT is_parent( :child, :parent_table, :description ); + SELECT is_parent( :child, :parent_table ); **Parameters** @@ -4987,20 +4987,20 @@ specified table is *not* partitioned, or if it does not exist. `:child` : Name of a partition table. -`:ancestor_schema` +`:parent_schema` : Schema in which to find the partitioned table. -`:ancestor_table` +`:parent_table` : Name of a partitioned table. `:description` : A short description of the test. Tests that one table is a partition of another table. The partition (or child) -table is specified first, the partitioned (or ancestor) table second. Without the +table is specified first, the partitioned (or parent) table second. Without the schema parameters, both tables must be visible in the search path. If the test description is omitted, it will be set to "Table `:child_table` should be -a partition of `:ancestor_table`". Note that this test will fail if either table +a partition of `:parent_table`". Note that this test will fail if either table does not exist. ### `is_clustered()` ### From 32d3748d932d552903fc246b0ada10bec60a1b9e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 30 Nov 2018 17:26:39 -0500 Subject: [PATCH 1060/1195] Fix handling of sql syntax functions on Postgres 10. --- Changes | 3 +++ doc/pgtap.mmd | 23 +++++++++++++++++++++++ sql/pgtap--0.99.0--1.0.0.sql | 19 +++++++++++++++++++ sql/pgtap.sql.in | 4 ++-- test/expected/coltap.out | 20 +++++++++++++++++++- test/sql/coltap.sql | 36 +++++++++++++++++++++++++++++++++++- 6 files changed, 101 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 5ab164166b30..add1dbfd2f77 100644 --- a/Changes +++ b/Changes @@ -21,6 +21,9 @@ Revision history for pgTAP + `is_descendent_of()` + `isnt_descendent_of()` Thanks to Luca Ferrari for the inspiration. +* Fixed `col_default_is()` to properly report special SQL syntax functions + like `CURRENT_USER` and `CURRENT_CATALOG` instead of evaluating them on + PostgreSQL 10 and higher. 0.99.0 2018-09-16T20:55:41Z --------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index a7c6c403d868..963fe0dbe80d 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4419,6 +4419,29 @@ string: SELECT col_default_is( 'user', 'created_at', 'now()' ); +But beware that the representation of special SQL syntax functions changed +in PostgreSQL 10. Where previously a default of `CURRENT_USER` and friends +used to be represented as functions: + + SELECT col_default_is( 'widgets', 'created_by', '"current_user"()' ); + +As of PostgreSQL 10, they comply with the SQL spec to appear in uppercase +and without trailining parentheses: + + SELECT col_default_is( 'widgets', 'created_by', 'CURRENT_USER' ); + +If you need to support both variants, use `pg_version_num()` to decide +which to use: + + SELECT col_default_is( + 'widgets', 'created_by', + CASE WHEN pg_version_num() >= 100000 THEN 'CURRENT_USER ELSE '"current_user"()' END + ); + +See the note in the +[System Information Functions](https://www.postgresql.org/docs/current/functions-info.html) +documentation for a complete list. + If the test fails, it will output useful diagnostics. For example, this test: SELECT col_default_is( diff --git a/sql/pgtap--0.99.0--1.0.0.sql b/sql/pgtap--0.99.0--1.0.0.sql index 54b79a77f938..9199b77188f5 100644 --- a/sql/pgtap--0.99.0--1.0.0.sql +++ b/sql/pgtap--0.99.0--1.0.0.sql @@ -712,3 +712,22 @@ RETURNS TEXT AS $$ 'Table ' || quote_ident( $1 ) || ' should not be a descendent of ' || quote_ident( $2) ); $$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION _def_is( TEXT, TEXT, anyelement, TEXT ) +RETURNS TEXT AS $$ +DECLARE + thing text; +BEGIN + -- Function or special SQL syntax. + IF $1 ~ '^[^'']+[(]' OR $1 = ANY('{CURRENT_CATALOG,CURRENT_ROLE,CURRENT_SCHEMA,CURRENT_USER,SESSION_USER,USER}') 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index df06c68dbb18..6c7705bfc5bd 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1625,8 +1625,8 @@ RETURNS TEXT AS $$ DECLARE thing text; BEGIN - IF $1 ~ '^[^'']+[(]' THEN - -- It's a functional default. + -- Function or special SQL syntax. + IF $1 ~ '^[^'']+[(]' OR $1 = ANY('{CURRENT_CATALOG,CURRENT_ROLE,CURRENT_SCHEMA,CURRENT_USER,SESSION_USER,USER}') THEN RETURN is( $1, $3, $4 ); END IF; diff --git a/test/expected/coltap.out b/test/expected/coltap.out index 7727ff2a1f4c..f3faa1c0bd88 100644 --- a/test/expected/coltap.out +++ b/test/expected/coltap.out @@ -1,5 +1,5 @@ \unset ECHO -1..204 +1..222 ok 1 - col_not_null( sch, tab, col, desc ) should pass ok 2 - col_not_null( sch, tab, col, desc ) should have the proper description ok 3 - col_not_null( sch, tab, col, desc ) should have the proper diagnostics @@ -204,3 +204,21 @@ ok 201 - col_default_is( tab, col, def, desc ) should have the proper diagnostic ok 202 - col_default_is( tab, col, def ) should fail ok 203 - col_default_is( tab, col, def ) should have the proper description ok 204 - col_default_is( tab, col, def ) should have the proper diagnostics +ok 205 - col_default_is( tab, col, CURRENT_CATALOG ) should pass +ok 206 - col_default_is( tab, col, CURRENT_CATALOG ) should have the proper description +ok 207 - col_default_is( tab, col, CURRENT_CATALOG ) should have the proper diagnostics +ok 208 - col_default_is( tab, col, CURRENT_ROLE ) should pass +ok 209 - col_default_is( tab, col, CURRENT_ROLE ) should have the proper description +ok 210 - col_default_is( tab, col, CURRENT_ROLE ) should have the proper diagnostics +ok 211 - col_default_is( tab, col, CURRENT_SCHEMA ) should pass +ok 212 - col_default_is( tab, col, CURRENT_SCHEMA ) should have the proper description +ok 213 - col_default_is( tab, col, CURRENT_SCHEMA ) should have the proper diagnostics +ok 214 - col_default_is( tab, col, CURRENT_USER ) should pass +ok 215 - col_default_is( tab, col, CURRENT_USER ) should have the proper description +ok 216 - col_default_is( tab, col, CURRENT_USER ) should have the proper diagnostics +ok 217 - col_default_is( tab, col, SESSION_USER ) should pass +ok 218 - col_default_is( tab, col, SESSION_USER ) should have the proper description +ok 219 - col_default_is( tab, col, SESSION_USER ) should have the proper diagnostics +ok 220 - col_default_is( tab, col, USER ) should pass +ok 221 - col_default_is( tab, col, USER ) should have the proper description +ok 222 - col_default_is( tab, col, USER ) should have the proper diagnostics diff --git a/test/sql/coltap.sql b/test/sql/coltap.sql index 39430a7ab153..4805af183171 100644 --- a/test/sql/coltap.sql +++ b/test/sql/coltap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(204); +SELECT plan(222); --SELECT * from no_plan(); CREATE TYPE public."myType" AS ( @@ -17,6 +17,12 @@ CREATE TABLE public.sometab( numb NUMERIC(10, 2) DEFAULT NULL, "myNum" NUMERIC(8) DEFAULT 24, myat TIMESTAMP DEFAULT NOW(), + cuser TEXT DEFAULT CURRENT_USER, + suser TEXT DEFAULT SESSION_USER, + auser TEXT DEFAULT USER, + crole TEXT DEFAULT CURRENT_ROLE, + csch TEXT DEFAULT CURRENT_SCHEMA, + ccat TEXT DEFAULT CURRENT_CATALOG, plain INTEGER, camel "myType" ); @@ -669,6 +675,34 @@ SELECT * FROM check_test( ' Column sometab.__asdfasdfs__ does not exist' ); +-- Make sure that it works when the default is a reserved SQL expression. +CREATE OR REPLACE FUNCTION ckreserve() RETURNS SETOF TEXT LANGUAGE PLPGSQL AS $$ +DECLARE + funcs text[] := '{CURRENT_CATALOG,CURRENT_ROLE,CURRENT_SCHEMA,CURRENT_USER,SESSION_USER,USER}'; + cols TEXT[] := '{ccat,crole,csch,cuser,suser,auser}'; + exp TEXT[] := funcs; + tap record; + last_index INTEGER; +BEGIN + last_index := array_upper(funcs, 1); + IF pg_version_num() < 100000 THEN + -- Prior to PostgreSQL 10, these wer functions rendered with paretheses. + exp := ARRAY['current_database()','"current_user"()','"current_schema"()','"current_user"()','"session_user"()','"current_user"()']; + END IF; + + FOR i IN 1..last_index LOOP + FOR tap IN SELECT * FROM check_test( + col_default_is( 'sometab', cols[i], exp[i], 'Test ' || funcs[i] ), + true, + 'col_default_is( tab, col, ' || funcs[i] || ' )', + 'Test ' || funcs[i], + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + END LOOP; +END; +$$; +SELECT * FROM ckreserve(); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 3b7edb899e1d732f2f9323ce009da8ab3ed08fa2 Mon Sep 17 00:00:00 2001 From: Sandro Santilli Date: Thu, 10 Jan 2019 14:35:50 +0100 Subject: [PATCH 1061/1195] Allow uninstall of partially installed pgtap --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f799e0f94abf..756ad79d6c69 100644 --- a/Makefile +++ b/Makefile @@ -178,7 +178,7 @@ ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][012]|8[.][1234])" && echo yes | endif sql/uninstall_pgtap.sql: sql/pgtap.sql test/setup.sql - grep '^CREATE ' sql/pgtap.sql | $(PERL) -e 'for (reverse ) { chomp; s/CREATE (OR REPLACE )?/DROP /; print "$$_;\n" }' > sql/uninstall_pgtap.sql + grep '^CREATE ' sql/pgtap.sql | $(PERL) -e 'for (reverse ) { chomp; s/CREATE (OR REPLACE )?/DROP /; print "$$_;\n" }' | sed 's/DROP \(FUNCTION\|VIEW\|TYPE\) /DROP \1 IF EXISTS /' > sql/uninstall_pgtap.sql sql/pgtap-static.sql: sql/pgtap.sql.in cp $< $@ From 5516f1337fc7a5da560649c5b3d2164c66435131 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 10 Jan 2019 10:12:56 -0500 Subject: [PATCH 1062/1195] Not uninstall change from @strk. --- Changes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changes b/Changes index add1dbfd2f77..8bc78738623e 100644 --- a/Changes +++ b/Changes @@ -24,6 +24,9 @@ Revision history for pgTAP * Fixed `col_default_is()` to properly report special SQL syntax functions like `CURRENT_USER` and `CURRENT_CATALOG` instead of evaluating them on PostgreSQL 10 and higher. +* Changed the uninstall script to use `DROP IF EXISTS` rather than just `DROP` + so that it works even with partial or failed installs. Thanks to Sandro + Santilli for the PR (#194). 0.99.0 2018-09-16T20:55:41Z --------------------------- From 4eec4bebb62e321ce2dad650095d00c251d1e0c7 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Tue, 19 Feb 2019 12:52:36 -0600 Subject: [PATCH 1063/1195] Stamp 1.0.0 --- .gitignore | 2 + Changes | 3 +- META.json | 10 +- Makefile | 7 + README.md | 2 +- compat/9.4/pgtap--0.99.0--1.0.0.patch | 236 ++++++++++++++++++ contrib/pgtap.spec | 5 +- doc/pgtap.mmd | 56 +---- pgtap.control | 2 +- ...-1.0.0.sql => pgtap--0.99.0--1.0.0.sql.in} | 0 test/sql/util.sql | 7 +- 11 files changed, 274 insertions(+), 56 deletions(-) create mode 100644 compat/9.4/pgtap--0.99.0--1.0.0.patch rename sql/{pgtap--0.99.0--1.0.0.sql => pgtap--0.99.0--1.0.0.sql.in} (100%) diff --git a/.gitignore b/.gitignore index 1c64cb6dd881..0efdf73dc023 100644 --- a/.gitignore +++ b/.gitignore @@ -10,10 +10,12 @@ regression.* *.html bbin /sql/pgtap--?.??.?.sql +/sql/pgtap--?.?.?.sql /sql/pgtap-core--* /sql/pgtap-schema--* /sql/pgtap--0.95.0--0.96.0.sql /sql/pgtap--0.96.0--0.97.0.sql /sql/pgtap--0.97.0--0.98.0.sql +/sql/pgtap--0.99.0--1.0.0.sql /sql/pgtap-static.sql *.sql.orig diff --git a/Changes b/Changes index 8bc78738623e..aa7b6b9d5fca 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -0.99.1 (Dev toward 1.0.0) +1.0.0 (Dev toward 1.0.0) -------------------------- * Fixed uninstall script generation to properly emit `DROP TYPE` statements. Thanks to Kamen Naydenov for the PR (#181)! @@ -27,6 +27,7 @@ Revision history for pgTAP * Changed the uninstall script to use `DROP IF EXISTS` rather than just `DROP` so that it works even with partial or failed installs. Thanks to Sandro Santilli for the PR (#194). +* Officially end support for PostgreSQL 8.2 and older. 0.99.0 2018-09-16T20:55:41Z --------------------------- diff --git a/META.json b/META.json index efdba4a566ea..5a428e9ad821 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "0.99.1", + "version": "1.0.0", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -14,7 +14,7 @@ "runtime": { "requires": { "plpgsql": 0, - "PostgreSQL": "8.1.0" + "PostgreSQL": "8.3.0" }, "recommends": { "PostgreSQL": "8.4.0" @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "0.99.1" + "version": "1.0.0" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "0.99.1" + "version": "1.0.0" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "0.99.1" + "version": "1.0.0" } }, "resources": { diff --git a/Makefile b/Makefile index 756ad79d6c69..d76257bc22fd 100644 --- a/Makefile +++ b/Makefile @@ -153,6 +153,13 @@ endif mv sql/pgtap.tmp sql/pgtap.sql # Ugly hacks for now... +EXTRA_CLEAN += sql/pgtap--0.99.0--1.0.0.sql +sql/pgtap--0.99.0--1.0.0.sql: sql/pgtap--0.99.0--1.0.0.sql.in + cp $< $@ +ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][01234]|8[.][1234])" && echo yes || echo no),yes) + patch -p0 < compat/9.4/pgtap--0.99.0--1.0.0.patch +endif + EXTRA_CLEAN += sql/pgtap--0.97.0--0.98.0.sql sql/pgtap--0.97.0--0.98.0.sql: sql/pgtap--0.97.0--0.98.0.sql.in cp $< $@ diff --git a/README.md b/README.md index 84068424ee39..65f4ab8faab5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 0.99.1 +pgTAP 1.0.0 ============ [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/compat/9.4/pgtap--0.99.0--1.0.0.patch b/compat/9.4/pgtap--0.99.0--1.0.0.patch new file mode 100644 index 000000000000..662524a0c352 --- /dev/null +++ b/compat/9.4/pgtap--0.99.0--1.0.0.patch @@ -0,0 +1,236 @@ +--- sql/pgtap--0.99.0--1.0.0.sql ++++ sql/pgtap--0.99.0--1.0.0.sql +@@ -7,233 +7,6 @@ RETURNS text AS $$ + ), $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; +- + CREATE OR REPLACE FUNCTION _funkargs ( NAME[] ) + RETURNS TEXT AS $$ + BEGIN diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 5066b83fa5a6..06f762a17459 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 0.99.1 +Version: 1.0.0 Release: 1%{?dist} Group: Applications/Databases License: PostgreSQL @@ -48,6 +48,9 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog +* Thu Feb 21 2019 Jim Nasby 1.0.0 +- Update to 1.0.0 + * Sun Sep 16 2018 David E. Wheeler 0.99.0-1 - Update to v0.99.0. diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 963fe0dbe80d..d70453681e00 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 0.99.1 +pgTAP 1.0.0 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and @@ -774,8 +774,8 @@ Since "my test" was declared with double quotes, it must be passed with double quotes. And since the call to "expect" included spaces (to keep it legible), the `EXECUTE` keyword was required. -In PostgreSQL 8.2 and up, you can also use a `VALUES` statement, both in -the query string or in a prepared statement. A useless example: +You can also use a `VALUES` statement, both in the query string or in a +prepared statement. A useless example: PREPARE myvals AS VALUES (1, 2), (3, 4); SELECT set_eq( @@ -1170,8 +1170,8 @@ expect, you might do something like this: 'active_users() should return active users' ); -Tip: If you're using PostgreSQL 8.2 and up and want to hard-code the values to -compare, use a `VALUES` statement instead of a query, like so: +Tip: If you want to hard-code the values to compare, use a `VALUES` statement +instead of a query, like so: SELECT results_eq( 'SELECT * FROM active_users()', @@ -1188,12 +1188,11 @@ second argument may be an array: ); In general, the use of prepared statements is highly recommended to keep your -test code SQLish (you can even use `VALUES` in prepared statements in -PostgreSQL 8.2 and up!). But note that, because `results_eq()` does a -row-by-row comparison, the results of the two query arguments must be in -exactly the same order, with exactly the same data types, in order to pass. In -practical terms, it means that you must make sure that your results are never -unambiguously ordered. +test code SQLish (you can even use `VALUES` in prepared statements). But note +that, because `results_eq()` does a row-by-row comparison, the results of the +two query arguments must be in exactly the same order, with exactly the same +data types, in order to pass. In practical terms, it means that you must make +sure that your results are never unambiguously ordered. For example, say that you want to compare queries against a `persons` table. The simplest way to sort is by `name`, as in: @@ -6662,8 +6661,6 @@ database privileges are: * CONNECT * TEMPORARY -Although CONNECT is not available before PostgreSQL 8.2. - If the `:description` argument is omitted, an appropriate description will be created. Examples: @@ -7579,7 +7576,7 @@ Conditional Tests Sometimes running a test under certain conditions will cause the test script or function to die. A certain function or feature isn't implemented (such as -`pg_sleep()` prior to PostgreSQL 8.2), some resource isn't available (like a +`sha256()` prior to PostgreSQL 11), some resource isn't available (like a procedural language), or a contrib module isn't available. In these cases it's necessary to skip tests, or declare that they are supposed to fail but will work in the future (a todo test). @@ -7769,14 +7766,7 @@ version of PostgreSQL. For example: The revision level is in the tens position, the minor version in the thousands position, and the major version in the ten thousands position and above (assuming PostgreSQL 10 is ever released, it will be in the hundred thousands -position). This value is the same as the `server_version_num` setting -available in PostgreSQL 8.2 and higher, but supported by this function back to -PostgreSQL 8.1: - - try=% select current_setting( 'server_version_num'), pg_version_num(); - current_setting | pg_version_num - -----------------+---------------- - 80304 | 80304 +position). This value is the same as the `server_version_num` setting. ### `os_name()` ### @@ -8357,28 +8347,6 @@ No changes. Everything should just work. + `is_descendent_of()` + `isnt_descendent_of()` -8.2 and Down ------------- - -* A patch is applied that removes `enum_has_labels()` and - `language_owner_is()`, since neither are testable before 8.3. - -* the `diag(anyelement)` function and `col_has_default()` cannot be used to - test for columns specified with `DEFAULT NULL` (even though that's the - implied default default). - -* The `has_enums()` function won't work. - -* A number of assignments casts are added to increase compatibility. The casts - are: - - + `boolean` to `text` - + `text[]` to `text` - + `name[]` to `text` - + `regtype` to `text` - -* Two operators, `=` and `<>`, are added to compare `name[]` values. - Metadata ======== diff --git a/pgtap.control b/pgtap.control index c2df5e05d5c1..a7b13814ac56 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '0.99.1' +default_version = '1.0.0' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true diff --git a/sql/pgtap--0.99.0--1.0.0.sql b/sql/pgtap--0.99.0--1.0.0.sql.in similarity index 100% rename from sql/pgtap--0.99.0--1.0.0.sql rename to sql/pgtap--0.99.0--1.0.0.sql.in diff --git a/test/sql/util.sql b/test/sql/util.sql index 56193efcb696..15f1e31cd231 100644 --- a/test/sql/util.sql +++ b/test/sql/util.sql @@ -55,9 +55,10 @@ SELECT is( 'findfincs() should return distinct values' ); -SELECT matches( - pgtap_version()::text, - '^0[.][[:digit:]]{2}$', +SELECT cmp_ok( + pgtap_version(), + '>=', + 1.0, 'pgtap_version() should work' ); From 24e2847a4eb9b73089d16b6f6fe75b0b57e72c26 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Thu, 21 Feb 2019 16:30:12 -0600 Subject: [PATCH 1064/1195] Timestamp 1.0.0 --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index aa7b6b9d5fca..f7a212ef57a3 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -1.0.0 (Dev toward 1.0.0) +1.0.0 2019-02-21T22:39:42Z -------------------------- * Fixed uninstall script generation to properly emit `DROP TYPE` statements. Thanks to Kamen Naydenov for the PR (#181)! From 660f372e4fb72ad53511f288e1a266ca110fcd67 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Thu, 21 Feb 2019 16:39:57 -0600 Subject: [PATCH 1065/1195] Increment to v1.0.1. --- META.json | 8 ++++---- README.md | 2 +- contrib/pgtap.spec | 2 +- doc/pgtap.mmd | 2 +- pgtap.control | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/META.json b/META.json index 5a428e9ad821..818624ccb80e 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "1.0.0", + "version": "1.1.0", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "1.0.0" + "version": "1.1.0" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "1.0.0" + "version": "1.1.0" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "1.0.0" + "version": "1.1.0" } }, "resources": { diff --git a/README.md b/README.md index 65f4ab8faab5..9839c83c5437 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 1.0.0 +pgTAP 1.1.0 ============ [pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 06f762a17459..18f5b9604474 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 1.0.0 +Version: 1.1.0 Release: 1%{?dist} Group: Applications/Databases License: PostgreSQL diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index d70453681e00..2edd26ff20ed 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 1.0.0 +pgTAP 1.1.0 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/pgtap.control b/pgtap.control index a7b13814ac56..497045846894 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '1.0.0' +default_version = '1.1.0' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true From 0322ca7161b1a77e841beca2cb71e123af97cb33 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Thu, 21 Feb 2019 17:00:10 -0600 Subject: [PATCH 1066/1195] Update release.md; add to .gitignore --- .gitignore | 6 +++++- release.md | 11 ++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 0efdf73dc023..2f5283baa0c1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ results pgtap.so regression.* *.html +*.html1 +*.sql.orig bbin /sql/pgtap--?.??.?.sql /sql/pgtap--?.?.?.sql @@ -18,4 +20,6 @@ bbin /sql/pgtap--0.97.0--0.98.0.sql /sql/pgtap--0.99.0--1.0.0.sql /sql/pgtap-static.sql -*.sql.orig + +# Misc mac crap +.DS_Store diff --git a/release.md b/release.md index 0a6bed6ccf3d..131e25697677 100644 --- a/release.md +++ b/release.md @@ -28,7 +28,7 @@ Here are the steps to take to make a release of pgTAP: `make NO_PGXS=1 installcheck`) * If you've made any significant changes while testing versions backward, test - them again in forward order (8.1, 8.2, 8.3, etc.) to make sure the changes + them again in forward order (9.1, 9.2, 9.3, etc.) to make sure the changes didn't break any later versions. * Review the documentation in `doc/pgtap.mmd`, and make any necessary changes, @@ -81,7 +81,7 @@ Here are the steps to take to make a release of pgTAP: * Timestamp the `Changes` file. I generate the timestamp like so: - perl -MDateTime -e 'print DateTime->now->datetime . "Z"' + perl -MDateTime -e 'print DateTime->now->datetime . "Z\n"' Paste that into the line with the new version, maybe increment by a minute to account for the time you'll need to actually do the release. @@ -94,13 +94,14 @@ Here are the steps to take to make a release of pgTAP: * Package the source and submit to [PGXN](http://manager.pgxn.org/). gem install pgxn_utils - git archive --format zip --prefix=pgtap-0.98.0/ \ - --output pgtap-0.98.0.zip master + git archive --format zip --prefix=pgtap-1.0.0/ \ + --output pgtap-1.0.0.zip master * Push all the changes to GitHub. git push git push --tags + git push origin up/gh-pages:gh-pages * Increment the minor version to kick off development for the next release. The version should be added to the `Changes` file, and incremented in the @@ -114,7 +115,7 @@ Here are the steps to take to make a release of pgTAP: * Commit that change and push it. - git ci -m 'Increment to v0.98.1.' + git ci -m 'Increment to v1.0.1.' git push * Start hacking on the next version! From d222da491d9e7eb2fdbf6712c6ee506df054e36b Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Mon, 7 Oct 2019 14:54:30 -0500 Subject: [PATCH 1067/1195] Fix pgtap_version() in update script for 1.0.0 (#214) Version 1.0.0 neglected to update pgtap_version(). This commit adds a 0.99.0--1.0.0 update script that does the correct thing (as well as creating a 1.0.0--1.1.0 update script). The 0.99.0--1.0.0 script might seem pointless, but at least it will allow anyone with the new extension package to do `ALTER EXTENSION pg_tap UPDATE TO 1.0` and get the correct results. Closes #209 --- Changes | 5 +++++ sql/pgtap--0.99.0--1.0.0.sql.in | 4 ++++ sql/pgtap--1.0.0--1.1.0.sql | 5 +++++ 3 files changed, 14 insertions(+) create mode 100644 sql/pgtap--1.0.0--1.1.0.sql diff --git a/Changes b/Changes index f7a212ef57a3..fe3990ef545a 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,11 @@ Revision history for pgTAP ========================== +1.1.0 +-------------------------- +* Fix pgtap_version(), which incorrectly returned 0.99 when upgrading from + version 0.99.0 to 1.0.0. + 1.0.0 2019-02-21T22:39:42Z -------------------------- * Fixed uninstall script generation to properly emit `DROP TYPE` statements. diff --git a/sql/pgtap--0.99.0--1.0.0.sql.in b/sql/pgtap--0.99.0--1.0.0.sql.in index 9199b77188f5..056064b5a664 100644 --- a/sql/pgtap--0.99.0--1.0.0.sql.in +++ b/sql/pgtap--0.99.0--1.0.0.sql.in @@ -1,3 +1,7 @@ +CREATE OR REPLACE FUNCTION pgtap_version() +RETURNS NUMERIC AS 'SELECT 1.0;' +LANGUAGE SQL IMMUTABLE; + CREATE OR REPLACE FUNCTION _array_to_sorted_string( name[], text ) RETURNS text AS $$ SELECT array_to_string(ARRAY( diff --git a/sql/pgtap--1.0.0--1.1.0.sql b/sql/pgtap--1.0.0--1.1.0.sql new file mode 100644 index 000000000000..e0653a2c5752 --- /dev/null +++ b/sql/pgtap--1.0.0--1.1.0.sql @@ -0,0 +1,5 @@ +CREATE OR REPLACE FUNCTION pgtap_version() +RETURNS NUMERIC AS 'SELECT 1.1;' +LANGUAGE SQL IMMUTABLE; + + From 7e93fa5c8251da3ab28a16cd41a57e56d2ce648d Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Mon, 7 Oct 2019 15:05:59 -0500 Subject: [PATCH 1068/1195] Fix tap funky (#215) This fixes an issue where using pg_upgrade to upgrade to PG 11 or newer with pgTap installed failed because the definition of `pg_proc` changed in version 11. This change replaces the direct reference to `pg_proc.prokind` (which used to be `proisagg`) with a pl/pgsql function that is version-aware. Closes #201 --- .gitignore | 1 + Changes | 1 + Makefile | 10 +++-- compat/10/pgtap--0.98.0--0.99.0.patch | 19 ++++++++ compat/install-10.patch | 44 ------------------- ....99.0.sql => pgtap--0.98.0--0.99.0.sql.in} | 0 sql/pgtap.sql.in | 22 +++++++++- 7 files changed, 49 insertions(+), 48 deletions(-) create mode 100644 compat/10/pgtap--0.98.0--0.99.0.patch delete mode 100644 compat/install-10.patch rename sql/{pgtap--0.98.0--0.99.0.sql => pgtap--0.98.0--0.99.0.sql.in} (100%) diff --git a/.gitignore b/.gitignore index 2f5283baa0c1..0f572728d000 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ bbin /sql/pgtap--0.95.0--0.96.0.sql /sql/pgtap--0.96.0--0.97.0.sql /sql/pgtap--0.97.0--0.98.0.sql +/sql/pgtap--0.98.0--0.99.0.sql /sql/pgtap--0.99.0--1.0.0.sql /sql/pgtap-static.sql diff --git a/Changes b/Changes index fe3990ef545a..c54c4a3ffc16 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,7 @@ Revision history for pgTAP -------------------------- * Fix pgtap_version(), which incorrectly returned 0.99 when upgrading from version 0.99.0 to 1.0.0. +* Fix issue with using pg_upgrade to Postgres 11+ with pgTap installed. 1.0.0 2019-02-21T22:39:42Z -------------------------- diff --git a/Makefile b/Makefile index d76257bc22fd..731a51f15462 100644 --- a/Makefile +++ b/Makefile @@ -119,9 +119,6 @@ OSNAME := $(shell $(SHELL) ./getos.sh) sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql cp $< $@ -ifeq ($(shell echo $(VERSION) | grep -qE "^([98]|10)[.]" && echo yes || echo no),yes) - patch -p0 < compat/install-10.patch -endif ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][0123456]|8[.][1234])" && echo yes || echo no),yes) patch -p0 < compat/install-9.6.patch endif @@ -160,6 +157,13 @@ ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][01234]|8[.][1234])" && echo yes patch -p0 < compat/9.4/pgtap--0.99.0--1.0.0.patch endif +EXTRA_CLEAN += sql/pgtap--0.98.0--0.99.0.sql +sql/pgtap--0.98.0--0.99.0.sql: sql/pgtap--0.98.0--0.99.0.sql.in + cp $< $@ +ifeq ($(shell echo $(VERSION) | grep -qE "^([89]|10)[.]" && echo yes || echo no),yes) + patch -p0 < compat/10/pgtap--0.98.0--0.99.0.patch +endif + EXTRA_CLEAN += sql/pgtap--0.97.0--0.98.0.sql sql/pgtap--0.97.0--0.98.0.sql: sql/pgtap--0.97.0--0.98.0.sql.in cp $< $@ diff --git a/compat/10/pgtap--0.98.0--0.99.0.patch b/compat/10/pgtap--0.98.0--0.99.0.patch new file mode 100644 index 000000000000..6fbc46c1e616 --- /dev/null +++ b/compat/10/pgtap--0.98.0--0.99.0.patch @@ -0,0 +1,19 @@ +--- sql/pgtap--0.98.0--0.99.0.sql ++++ sql/pgtap--0.98.0--0.99.0.sql +@@ -134,6 +134,7 @@ + ); + $$ LANGUAGE sql; + ++DROP VIEW tap_funky; + CREATE OR REPLACE VIEW tap_funky + AS SELECT p.oid AS oid, + n.nspname AS schema, +@@ -124,7 +124,7 @@ CREATE OR REPLACE VIEW tap_funky + || p.prorettype::regtype AS returns, + p.prolang AS langoid, + p.proisstrict AS is_strict, +- p.prokind AS kind, ++ CASE proisagg WHEN true THEN 'a' WHEN false THEN 'f' END AS kind, + p.prosecdef AS is_definer, + p.proretset AS returns_set, + p.provolatile::char AS volatility, diff --git a/compat/install-10.patch b/compat/install-10.patch deleted file mode 100644 index 391705b54947..000000000000 --- a/compat/install-10.patch +++ /dev/null @@ -1,44 +0,0 @@ ---- sql/pgtap.sql -+++ sql/pgtap.sql -@@ -2483,7 +2483,7 @@ - || p.prorettype::regtype AS returns, - p.prolang AS langoid, - p.proisstrict AS is_strict, -- p.prokind AS kind, -+ p.proisagg AS is_agg, - p.prosecdef AS is_definer, - p.proretset AS returns_set, - p.provolatile::char AS volatility, -@@ -5656,7 +5656,7 @@ - - CREATE OR REPLACE FUNCTION _agg ( NAME, NAME, NAME[] ) - RETURNS BOOLEAN AS $$ -- SELECT kind = 'a' -+ SELECT is_agg - FROM tap_funky - WHERE schema = $1 - AND name = $2 -@@ -5665,12 +5665,12 @@ - - CREATE OR REPLACE FUNCTION _agg ( NAME, NAME ) - RETURNS BOOLEAN AS $$ -- SELECT kind = 'a' FROM tap_funky WHERE schema = $1 AND name = $2 -+ SELECT is_agg FROM tap_funky WHERE schema = $1 AND name = $2 - $$ LANGUAGE SQL; - - CREATE OR REPLACE FUNCTION _agg ( NAME, NAME[] ) - RETURNS BOOLEAN AS $$ -- SELECT kind = 'a' -+ SELECT is_agg - FROM tap_funky - WHERE name = $1 - AND args = array_to_string($2, ',') -@@ -5679,7 +5679,7 @@ - - CREATE OR REPLACE FUNCTION _agg ( NAME ) - RETURNS BOOLEAN AS $$ -- SELECT kind = 'a' FROM tap_funky WHERE name = $1 AND is_visible; -+ SELECT is_agg FROM tap_funky WHERE name = $1 AND is_visible; - $$ LANGUAGE SQL; - - -- is_aggregate( schema, function, args[], description ) diff --git a/sql/pgtap--0.98.0--0.99.0.sql b/sql/pgtap--0.98.0--0.99.0.sql.in similarity index 100% rename from sql/pgtap--0.98.0--0.99.0.sql rename to sql/pgtap--0.98.0--0.99.0.sql.in diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 6c7705bfc5bd..766f2ee65b9a 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -2473,6 +2473,26 @@ 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 proisagg WHEN true THEN 'a' WHEN false THEN '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, @@ -2483,7 +2503,7 @@ CREATE OR REPLACE VIEW tap_funky || p.prorettype::regtype AS returns, p.prolang AS langoid, p.proisstrict AS is_strict, - p.prokind AS kind, + _prokind(p.oid) AS kind, p.prosecdef AS is_definer, p.proretset AS returns_set, p.provolatile::char AS volatility, From d60b8f56252dc384c3f71bec3f50300f8afbefc5 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Thu, 7 Nov 2019 08:50:59 -0600 Subject: [PATCH 1069/1195] WIP: Add support for testing upgrade scripts (#128) Add support for testing of pgTap update scripts. This commit adds several new make targets: - make uninstall-all: remove ALL installed pgtap code. Unlike `make unintall`, this removes pgtap*, not just our defined targets. Useful when testing multiple versions of pgtap. - make regress: run installcheck then print any diffs from expected output. - make updatecheck: install an older version of pgTap from PGXN (controlled by $UPDATE_FROM; 0.95.0 by default), update to the current version via ALTER EXTENSION, then run installcheck. - make results: runs `make test` and copies all result files to test/expected/. DO NOT RUN THIS UNLESS YOU'RE CERTAIN ALL YOUR TESTS ARE PASSING! In addition to these changes, `make installcheck` now runs as many tests as possible in parallel. This is much faster than running them sequentially. The degree of parallelism can be controlled via `$PARALLEL_CONN`. Setting `$PARALLEL_CONN` to `1` will go back to a serial test schedule. --- .gitignore | 8 +- .travis.yml | 17 +- Makefile | 348 +++++++++++++++++++++++++++---- README.md | 2 +- pg-travis-test.sh | 63 ++++++ sql/pgtap--0.97.0--0.98.0.sql.in | 17 ++ sql/pgtap--0.98.0--0.99.0.sql.in | 20 ++ test/expected/build.out | 1 + test/expected/create.out | 1 + test/expected/extension.out | 6 +- test/expected/runjusttests_6.out | 41 ++++ test/expected/update.out | 4 + test/psql.sql | 14 ++ test/schedule/build.sql | 10 + test/schedule/create.sql | 3 + test/schedule/main.sch | 2 + test/schedule/update.sch | 1 + test/schedule/update.sql | 20 ++ test/setup.sql | 22 +- test/sql/.gitignore | 3 + test/sql/extension.sql | 27 +-- test/sql/performs_ok.sql | 4 +- tools/parallel_conn.sh | 35 ++++ tools/psql_args.sh | 24 +++ 24 files changed, 602 insertions(+), 91 deletions(-) create mode 100644 pg-travis-test.sh create mode 100644 test/expected/build.out create mode 100644 test/expected/create.out create mode 100644 test/expected/runjusttests_6.out create mode 100644 test/expected/update.out create mode 100644 test/psql.sql create mode 100644 test/schedule/build.sql create mode 100644 test/schedule/create.sql create mode 100644 test/schedule/main.sch create mode 100644 test/schedule/update.sch create mode 100644 test/schedule/update.sql create mode 100644 test/sql/.gitignore create mode 100755 tools/parallel_conn.sh create mode 100755 tools/psql_args.sh diff --git a/.gitignore b/.gitignore index 0f572728d000..565d2934311d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ +# THERE IS ANOTHER .gitignore IN test/sql! + .*.swp pgtap.sql pgtap-core.sql pgtap-schema.sql uninstall_pgtap.sql -test/setup.sql results pgtap.so regression.* @@ -11,6 +12,7 @@ regression.* *.html1 *.sql.orig bbin + /sql/pgtap--?.??.?.sql /sql/pgtap--?.?.?.sql /sql/pgtap-core--* @@ -21,6 +23,10 @@ bbin /sql/pgtap--0.98.0--0.99.0.sql /sql/pgtap--0.99.0--1.0.0.sql /sql/pgtap-static.sql +/sql/pgtap-static.sql.tmp* +*.sql.orig + +test/build # Misc mac crap .DS_Store diff --git a/.travis.yml b/.travis.yml index 02ca1e4bee70..85c226e14451 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,12 @@ language: c before_install: - wget https://gist.githubusercontent.com/petere/5893799/raw/apt.postgresql.org.sh - - wget https://gist.githubusercontent.com/petere/6023944/raw/pg-travis-test.sh - sudo sh ./apt.postgresql.org.sh - sudo rm -vf /etc/apt/sources.list.d/pgdg-source.list +script: + - bash ./pg-travis-test.sh + env: - - PGVERSION=8.4 - - PGVERSION=9.0 - PGVERSION=9.1 - PGVERSION=9.2 - PGVERSION=9.3 @@ -14,5 +14,14 @@ env: - PGVERSION=9.5 - PGVERSION=9.6 - PGVERSION=10 - - PGVERSION=11 + - PGVERSION=11 UPDATE_FROM=0.99.0 + # Duplication below is via s/-/- PARALLEL_CONN=1/ + - PARALLEL_CONN=1 PGVERSION=9.1 + - PARALLEL_CONN=1 PGVERSION=9.2 + - PARALLEL_CONN=1 PGVERSION=9.3 + - PARALLEL_CONN=1 PGVERSION=9.4 + - PARALLEL_CONN=1 PGVERSION=9.5 + - PARALLEL_CONN=1 PGVERSION=9.6 + - PARALLEL_CONN=1 PGVERSION=10 + - PARALLEL_CONN=1 PGVERSION=11 UPDATE_FROM=0.99.0 script: bash ./pg-travis-test.sh diff --git a/Makefile b/Makefile index 731a51f15462..51177dfc6267 100644 --- a/Makefile +++ b/Makefile @@ -11,10 +11,56 @@ TESTS = $(wildcard test/sql/*.sql) EXTRA_CLEAN = $(VERSION_FILES) sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql doc/*.html EXTRA_CLEAN += $(wildcard sql/*.orig) # These are files left behind by patch DOCS = doc/pgtap.mmd -REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) -REGRESS_OPTS = --inputdir=test --load-language=plpgsql PG_CONFIG ?= pg_config +# +# Test configuration. This must be done BEFORE including PGXS +# + +# If you need to, you can manually pass options to pg_regress with this variable +REGRESS_CONF ?= + +# Set this to 1 to force serial test execution; otherwise it will be determined from Postgres max_connections +PARALLEL_CONN ?= + +# This controls what version to upgrade FROM when running updatecheck. +UPDATE_FROM ?= 0.95.0 + +# +# Setup test variables +# +# We use the contents of test/sql to create a parallel test schedule. Note that +# there is additional test setup below this; some variables must be set before +# loading PGXS, some must be set afterwards. +# + +# These are test files that need to end up in test/sql to make pg_regress +# happy, but these should NOT be treated as regular regression tests +SCHEDULE_TEST_FILES = $(wildcard test/schedule/*.sql) +SCHEDULE_DEST_FILES = $(subst test/schedule,test/sql,$(SCHEDULE_TEST_FILES)) +EXTRA_CLEAN += $(SCHEDULE_DEST_FILES) + +# The actual schedule files. Note that we'll build 2 additional files +SCHEDULE_FILES = $(wildcard test/schedule/*.sch) + +# These are our actual regression tests +TEST_FILES = $(filter-out $(SCHEDULE_DEST_FILES),$(wildcard test/sql/*.sql)) + +# Plain test names +TESTS = $(notdir $(TEST_FILES:.sql=)) + +# Some tests fail when run in parallel +SERIAL_TESTS = coltap hastap + +# This is a bit of a hack, but if REGRESS isn't set we can't installcheck, and +# it must be set BEFORE including pgxs. Note this gets set again below +REGRESS = --schedule $(TB_DIR)/run.sch + +# REMAINING TEST VARIABLES ARE DEFINED IN THE TEST SECTION +# sort is necessary to remove dupes so install won't complain +DATA = $(sort $(wildcard sql/*--*.sql) $(_IN_PATCHED)) # NOTE! This gets reset below! + +# Locate PGXS and pg_config ifdef NO_PGXS top_builddir = ../.. PG_CONFIG := $(top_builddir)/src/bin/pg_config/pg_config @@ -23,10 +69,14 @@ else PGXS := $(shell $(PG_CONFIG) --pgxs) endif -# We need to do various things with the PostgreSQLl version. +# We need to do various things with the PostgreSQL version. VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') -# We support 8.1 and later. +# +# Major version check +# +# TODO: update this +# TODO9.1: update the $(TB_DIR) target below when de-supporting 9.1 ifeq ($(shell echo $(VERSION) | grep -qE "^(7[.]|8[.]0)" && echo yes || echo no),yes) $(error pgTAP requires PostgreSQL 8.1 or later. This is $(VERSION)) endif @@ -68,6 +118,53 @@ else include $(PGXS) endif +# +# DISABLED TESTS +# + +# Row security policy tests not supported by 9.4 and earlier. +ifeq ($(shell echo $(VERSION) | grep -qE "^9[.][01234]|8[.]" && echo yes || echo no),yes) +EXCLUDE_TEST_FILES += test/sql/policy.sql +endif + +# Partition tests tests not supported by 9.x and earlier. +ifeq ($(shell echo $(VERSION) | grep -qE "[89][.]" && echo yes || echo no),yes) +EXCLUDE_TEST_FILES += test/sql/partitions.sql +endif + +# Enum tests not supported by 8.2 and earlier. +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][12]" && echo yes || echo no),yes) +EXCLUDE_TEST_FILES += test/sql/enumtap.sql +endif + +# Values tests not supported by 8.1 and earlier. +ifeq ($(shell echo $(VERSION) | grep -qE "8[.][1]" && echo yes || echo no),yes) +EXCLUDE_TEST_FILES += test/sql/valueset.sql +endif + +# +# Check for missing extensions +# +# NOTE! This currently MUST be after PGXS! The problem is that +# $(DESTDIR)$(datadir) aren't being expanded. +# +EXTENSION_DIR = $(DESTDIR)$(datadir)/extension +extension_control = $(shell file="$(EXTENSION_DIR)/$1.control"; [ -e "$$file" ] && echo "$$file") +ifeq (,$(call extension_control,citext)) +MISSING_EXTENSIONS += citext +endif +ifeq (,$(call extension_control,isn)) +MISSING_EXTENSIONS += isn +endif +ifeq (,$(call extension_control,ltree)) +MISSING_EXTENSIONS += ltree +endif +EXTENSION_TEST_FILES += test/sql/extension.sql +ifneq (,$(MISSING_EXTENSIONS)) +# NOTE: we emit a warning about this down below, but only when we're actually running a test. +EXCLUDE_TEST_FILES += $(EXTENSION_TEST_FILES) +endif + # We need Perl. ifneq (,$(findstring missing,$(PERL))) PERL := $(shell which perl) @@ -88,36 +185,23 @@ $(warning must be installed from CPAN. To do so, simply run:) $(warning cpan TAP::Parser::SourceHandler::pgTAP) endif -# Enum tests not supported by 8.2 and earlier. -ifeq ($(shell echo $(VERSION) | grep -qE "^8[.][12]" && echo yes || echo no),yes) -TESTS := $(filter-out test/sql/enumtap.sql,$(TESTS)) -REGRESS := $(filter-out enumtap,$(REGRESS)) -endif - -# Values tests not supported by 8.1 and earlier. -ifeq ($(shell echo $(VERSION) | grep -qE "^8[.][1]" && echo yes || echo no),yes) -TESTS := $(filter-out test/sql/enumtap.sql sql/valueset.sql,$(TESTS)) -REGRESS := $(filter-out enumtap valueset,$(REGRESS)) -endif - -# Partition tests tests not supported by 9.x and earlier. -ifeq ($(shell echo $(VERSION) | grep -qE "^[89][.]" && echo yes || echo no),yes) -TESTS := $(filter-out test/sql/partitions.sql,$(TESTS)) -REGRESS := $(filter-out partitions,$(REGRESS)) -endif - -# Row security policy tests not supported by 9.4 and earlier. -ifeq ($(shell echo $(VERSION) | grep -qE "^9[.][01234]|8[.]" && echo yes || echo no),yes) -TESTS := $(filter-out test/sql/policy.sql,$(TESTS)) -REGRESS := $(filter-out policy,$(REGRESS)) -endif +# Use a build directory to avoid cluttering up the main repo. (Maybe should just switch to VPATH builds?) +# WARNING! Not everything uses this! TODO: move all targets into $(B_DIR) +B_DIR ?= .build # Determine the OS. Borrowed from Perl's Configure. OSNAME := $(shell $(SHELL) ./getos.sh) .PHONY: test -sql/pgtap.sql: sql/pgtap.sql.in test/setup.sql +# TARGET uninstall-all: remove ALL installed versions of pgTap (rm pgtap*). +# Unlike `make unintall`, this removes pgtap*, not just our defined targets. +# Useful when testing multiple versions of pgtap. +uninstall-all: + rm -f $(EXTENSION_DIR)/pgtap* + +# TODO: switch this whole thing to a perl or shell script that understands the file naming convention and how to compare that to $VERSION. +sql/pgtap.sql: sql/pgtap.sql.in cp $< $@ ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][0123456]|8[.][1234])" && echo yes || echo no),yes) patch -p0 < compat/install-9.6.patch @@ -149,7 +233,7 @@ endif sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap.sql > sql/pgtap.tmp mv sql/pgtap.tmp sql/pgtap.sql -# Ugly hacks for now... +# Ugly hacks for now... TODO: script that understands $VERSION and will apply all the patch files for that version EXTRA_CLEAN += sql/pgtap--0.99.0--1.0.0.sql sql/pgtap--0.99.0--1.0.0.sql: sql/pgtap--0.99.0--1.0.0.sql.in cp $< $@ @@ -191,19 +275,20 @@ endif sql/uninstall_pgtap.sql: sql/pgtap.sql test/setup.sql grep '^CREATE ' sql/pgtap.sql | $(PERL) -e 'for (reverse ) { chomp; s/CREATE (OR REPLACE )?/DROP /; print "$$_;\n" }' | sed 's/DROP \(FUNCTION\|VIEW\|TYPE\) /DROP \1 IF EXISTS /' > sql/uninstall_pgtap.sql +# +# Support for static install files +# + +# The use of $@.tmp is to eliminate the possibility of leaving an invalid pgtap-static.sql in case the recipe fails part-way through. +# TODO: the sed command needs the equivalent of bash's PIPEFAIL; should just replace this with some perl magic sql/pgtap-static.sql: sql/pgtap.sql.in - cp $< $@ - sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-10.patch | patch -p0 - sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-9.6.patch | patch -p0 - sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-9.4.patch | patch -p0 - sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-9.2.patch | patch -p0 - sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-9.1.patch | patch -p0 - sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-9.0.patch | patch -p0 - sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-8.4.patch | patch -p0 - sed -e 's,sql/pgtap,sql/pgtap-static,g' compat/install-8.3.patch | patch -p0 - sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' $@ > sql/pgtap-static.tmp - mv sql/pgtap-static.tmp $@ -EXTRA_CLEAN += sql/pgtap-static.sql + cp $< $@.tmp + for p in `ls compat/install-*.patch | sort -rn`; do \ + echo; echo '***' "Patching pgtap-static.sql with $$p"; \ + sed -e 's#sql/pgtap.sql#sql/pgtap-static.sql.tmp#g' "$$p" | patch -p0; \ + done + sed -e 's#MODULE_PATHNAME#$$libdir/pgtap#g' -e 's#__OS__#$(OSNAME)#g' -e 's#__VERSION__#$(NUMVERSION)#g' $@.tmp > $@ +EXTRA_CLEAN += sql/pgtap-static.sql sql/pgtap-static.sql.tmp sql/pgtap-core.sql: sql/pgtap-static.sql $(PERL) compat/gencore 0 sql/pgtap-static.sql > sql/pgtap-core.sql @@ -211,18 +296,189 @@ sql/pgtap-core.sql: sql/pgtap-static.sql sql/pgtap-schema.sql: sql/pgtap-static.sql $(PERL) compat/gencore 1 sql/pgtap-static.sql > sql/pgtap-schema.sql +$(B_DIR)/static/: $(B_DIR) + mkdir -p $@ + +# We don't lump this in with the $(B_DIR)/static target because that would run the risk of a failure of the cp command leaving an empty directory behind +$(B_DIR)/static/%/: %/ $(B_DIR)/static + cp -R $< $@ + # Make sure that we build the regression tests. installcheck: test/setup.sql -# In addition to installcheck, one can also run the tests through pg_prove. -test: test/setup.sql - # Filter-out tests that intentionally fail. They should be tested by installcheck. - pg_prove --pset tuples_only=1 $(filter-out test/sql/run%,$(TESTS)) - html: multimarkdown doc/pgtap.mmd > doc/pgtap.html ./tocgen doc/pgtap.html 2> doc/toc.html perl -MPod::Simple::XHTML -E "my \$$p = Pod::Simple::XHTML->new; \$$p->html_header_tags(''); \$$p->strip_verbatim_indent(sub { my \$$l = shift; (my \$$i = \$$l->[0]) =~ s/\\S.*//; \$$i }); \$$p->parse_from_file('`perldoc -l pg_prove`')" > doc/pg_prove.html +# +# Actual test targets +# + +# TARGET regress: run installcheck then print any diffs from expected output. +.PHONY: regress +regress: installcheck_deps + $(MAKE) installcheck || ([ -e regression.diffs ] && $${PAGER:-cat} regression.diffs; exit 1) + +# TARGET updatecheck: install an older version of pgTap from PGXN (controlled by $UPDATE_FROM; 0.95.0 by default), update to the current version via ALTER EXTENSION, then run installcheck. +.PHONY: updatecheck +updatecheck: updatecheck_deps install + $(MAKE) updatecheck_run || ([ -e regression.diffs ] && $${PAGER:-cat} regression.diffs; exit 1) + +# General dependencies for installcheck. Note that several other places add themselves as dependencies. +.PHONY: installcheck_deps +installcheck_deps: $(SCHEDULE_DEST_FILES) extension_check set_parallel_conn # More dependencies below + +# In addition to installcheck, one can also run the tests through pg_prove. +test: extension_check + pg_prove --pset tuples_only=1 $(TEST_FILES) + +# +# General test support +# +# In order to support parallel testing, we need to have a test schedule file, +# which we build dynamically. Instead of cluttering the test directory, we use +# a test build directiory ($(TB_DIR)). We also use $(TB_DIR) to drop some +# additional artifacts, so that we can automatically determine if certain +# dependencies (such as excluded tests) have changed since the last time we +# ran. +TB_DIR = test/build +GENERATED_SCHEDULE_DEPS = $(TB_DIR)/tests $(TB_DIR)/exclude_tests +REGRESS = --schedule $(TB_DIR)/run.sch # Set this again just to be safe +REGRESS_OPTS = --inputdir=test --load-language=plpgsql --max-connections=$(PARALLEL_CONN) --schedule $(SETUP_SCH) $(REGRESS_CONF) +SETUP_SCH = test/schedule/main.sch # schedule to use for test setup; this can be forcibly changed by some targets! +IGNORE_TESTS = $(notdir $(EXCLUDE_TEST_FILES:.sql=)) +PARALLEL_TESTS = $(filter-out $(IGNORE_TESTS),$(filter-out $(SERIAL_TESTS),$(TESTS))) +GENERATED_SCHEDULES = $(TB_DIR)/serial.sch $(TB_DIR)/parallel.sch +installcheck: $(TB_DIR)/run.sch installcheck_deps + +# Parallel tests will use $(PARALLEL_TESTS) number of connections if we let it, +# but max_connections may not be set that high. You can set this manually to 1 +# for no parallelism +# +# This can be a bit expensive if we're not testing, so set it up as a +# dependency of installcheck +.PHONY: set_parallel_conn +set_parallel_conn: + $(eval PARALLEL_CONN = $(shell tools/parallel_conn.sh $(PARALLEL_CONN))) + @[ -n "$(PARALLEL_CONN)" ] + @echo "Using $(PARALLEL_CONN) parallel test connections" + +# Have to do this as a separate task to ensure the @[ -n ... ] test in set_parallel_conn actually runs +$(TB_DIR)/which_schedule: $(TB_DIR)/ set_parallel_conn + $(eval SCHEDULE = $(shell [ $(PARALLEL_CONN) -gt 1 ] && echo $(TB_DIR)/parallel.sch || echo $(TB_DIR)/serial.sch)) + @[ -n "$(SCHEDULE)" ] + @[ "`cat $@ 2>/dev/null`" = "$(SCHEDULE)" ] || (echo "Schedule changed to $(SCHEDULE)"; echo "$(SCHEDULE)" > $@) + +# Generated schedule files, one for serial one for parallel +.PHONY: $(TB_DIR)/tests # Need this target to force schedule rebuild if $(TEST) changes +$(TB_DIR)/tests: $(TB_DIR)/ + @[ "`cat $@ 2>/dev/null`" = "$(TEST)" ] || (echo "Rebuilding $@"; echo "$(TEST)" > $@) + +.PHONY: $(TB_DIR)/exclude_tests # Need this target to force schedule rebuild if $(EXCLUDE_TEST) changes +$(TB_DIR)/exclude_tests: $(TB_DIR)/ + @[ "`cat $@ 2>/dev/null`" = "$(EXCLUDE_TEST)" ] || (echo "Rebuilding $@"; echo "$(EXCLUDE_TEST)" > $@) + +$(TB_DIR)/serial.sch: $(GENERATED_SCHEDULE_DEPS) + @(for f in $(IGNORE_TESTS); do echo "ignore: $$f"; done; for f in $(TESTS); do echo "test: $$f"; done) > $@ + +$(TB_DIR)/parallel.sch: $(GENERATED_SCHEDULE_DEPS) + @( \ + for f in $(SERIAL_TESTS); do echo "test: $$f"; done; \ + ([ -z "$(IGNORE_TESTS)" ] || echo "ignore: $(IGNORE_TESTS)"); \ + ([ -z "$(PARALLEL_TESTS)" ] || echo "test: $(PARALLEL_TESTS)") \ + ) > $@ + +$(TB_DIR)/run.sch: $(TB_DIR)/which_schedule $(GENERATED_SCHEDULES) + cp `cat $<` $@ + +# Don't generate noise if we're not running tests... +.PHONY: extension_check +extension_check: + @[ -z "$(MISSING_EXTENSIONS)" ] || (echo; echo '***************************'; echo "WARNING: Some mandatory extensions ($(MISSING_EXTENSIONS)) are not installed; ignoring tests: $(EXTENSION_TEST_FILES)"; echo '***************************'; echo) + + +# These tests have specific dependencies +test/sql/build.sql: sql/pgtap.sql +test/sql/create.sql test/sql/update.sql: pgtap-version-$(EXTVERSION) + +test/sql/%.sql: test/schedule/%.sql + @(echo '\unset ECHO'; echo '-- GENERATED FILE! DO NOT EDIT!'; echo "-- Original file: $<"; cat $< ) > $@ + +# Prior to 9.2, EXTRA_CLEAN just does rm -f, which obviously won't work with a directory. +# TODO9.1: switch back to EXTRA_CLEAN when removing support for 9.1 +#EXTRA_CLEAN += $(TB_DIR)/ +clean: clean_tb_dir +.PHONY: clean_tb_dir +clean_tb_dir: + @rm -rf $(TB_DIR) +$(TB_DIR)/: + @mkdir -p $@ + +clean: clean_b_dir +.PHONY: clean_b_dir +clean_b_dir: + @rm -rf $(B_DIR) +$(B_DIR)/: + @mkdir -p $@ + + +# +# Update test support +# + +# If the specified version of pgtap doesn't exist, install it. Note that the +# real work is done by the $(EXTENSION_DIR)/pgtap--%.sql rule below. +pgtap-version-%: $(EXTENSION_DIR)/pgtap--%.sql + @true # Necessary to have a fake action here + + +# Travis will complain if we reinstall too quickly, so don't run make install +# unless actually necessary. +$(EXTENSION_DIR)/pgtap--$(EXTVERSION).sql: sql/pgtap--$(EXTVERSION).sql + $(MAKE) install + +# Need to explicitly exclude the current version. I wonder if there's a way to do this with % in the target? +# Note that we need to capture the test failure so the rule doesn't abort +$(EXTENSION_DIR)/pgtap--%.sql: + @ver=$(@:$(EXTENSION_DIR)/pgtap--%.sql=%); [ "$$ver" = "$(EXTVERSION)" ] || (echo Installing pgtap version $$ver from pgxn; pgxn install pgtap=$$ver) + +# This is separated out so it can be called before calling updatecheck_run +.PHONY: updatecheck_deps +updatecheck_deps: pgtap-version-$(UPDATE_FROM) test/sql/update.sql + +# We do this as a separate step to change SETUP_SCH before the main updatecheck +# recipe calls installcheck (which depends on SETUP_SCH being set correctly). +.PHONY: updatecheck_setup +# pg_regress --launcher not supported prior to 9.1 +# There are some other failures in 9.1 and 9.2 (see https://travis-ci.org/decibel/pgtap/builds/358206497). +# TODO: find something that can generically compare majors (ie: GE91 from +# https://github.com/decibel/pgxntool/blob/master/base.mk). +updatecheck_setup: updatecheck_deps + @if echo $(VERSION) | grep -qE "8[.]|9[.][012]"; then echo "updatecheck is not supported prior to 9.3"; exit 1; fi + $(eval SETUP_SCH = test/schedule/update.sch) + $(eval REGRESS_OPTS += --launcher "tools/psql_args.sh -v 'old_ver=$(UPDATE_FROM)' -v 'new_ver=$(EXTVERSION)'") + @echo + @echo "###################" + @echo "Testing upgrade from $(UPDATE_FROM) to $(EXTVERSION)" + @echo "###################" + @echo + +.PHONY: updatecheck_run +updatecheck_run: updatecheck_setup installcheck + +# +# STOLEN FROM pgxntool +# + +# TARGET results: runs `make test` and copies all result files to test/expected/. DO NOT RUN THIS UNLESS YOU'RE CERTAIN ALL YOUR TESTS ARE PASSING! +.PHONY: results +results: installcheck result-rsync + +.PHONY: +result-rsync: + rsync -rlpgovP results/ test/expected + + # To use this, do make print-VARIABLE_NAME print-% : ; $(info $* is $(flavor $*) variable set to "$($*)") @true diff --git a/README.md b/README.md index 9839c83c5437..f6db0622f0ba 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ documentation in `doc/pgtap.mmd` or To build it, just do this: make - make installcheck make install + make installcheck If you encounter an error such as: diff --git a/pg-travis-test.sh b/pg-travis-test.sh new file mode 100644 index 000000000000..c9784920d7da --- /dev/null +++ b/pg-travis-test.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +# Based on https://gist.github.com/petere/6023944 + +set -eux +failed='' + +sudo apt-get update + +packages="python-setuptools postgresql-$PGVERSION postgresql-server-dev-$PGVERSION postgresql-common" + +# bug: http://www.postgresql.org/message-id/20130508192711.GA9243@msgid.df7cb.de +sudo update-alternatives --remove-all postmaster.1.gz + +# stop all existing instances (because of https://github.com/travis-ci/travis-cookbooks/pull/221) +sudo service postgresql stop +# and make sure they don't come back +echo 'exit 0' | sudo tee /etc/init.d/postgresql +sudo chmod a+x /etc/init.d/postgresql + +sudo apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install $packages + +export PGPORT=55435 +export PGUSER=postgres +export PG_CONFIG=/usr/lib/postgresql/$PGVERSION/bin/pg_config +sudo pg_createcluster --start $PGVERSION test -p $PGPORT -- -A trust + +sudo easy_install pgxnclient + +test_make() { + set +ux + # Many tests depend on install, so just use sudo for all of them + if ! sudo make "$@"; then + echo + echo '!!!!!!!!!!!!!!!!' + echo "make $@ failed" + echo '!!!!!!!!!!!!!!!!' + echo + failed="$failed '$@'" + fi + set -ux +} + +test_make clean regress + +# pg_regress --launcher not supported prior to 9.1 +# There are some other failures in 9.1 and 9.2 (see https://travis-ci.org/decibel/pgtap/builds/358206497). +echo $PGVERSION | grep -qE "8[.]|9[.][012]" || test_make clean updatecheck + +# Explicitly test these other targets + +# TODO: install software necessary to allow testing the 'test' and 'html' targets +for t in all install ; do + test_make clean $t + test_make $t +done + +if [ -n "$failed" ]; then + set +ux + # $failed will have a leading space if it's not empty + echo "These test targets failed:$failed" + exit 1 +fi diff --git a/sql/pgtap--0.97.0--0.98.0.sql.in b/sql/pgtap--0.97.0--0.98.0.sql.in index a921eaf62271..d0a7b53f82d0 100644 --- a/sql/pgtap--0.97.0--0.98.0.sql.in +++ b/sql/pgtap--0.97.0--0.98.0.sql.in @@ -233,6 +233,23 @@ BEGIN END; $$ LANGUAGE plpgsql; +-- 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; + -- is_partitioned( schema, table, description ) CREATE OR REPLACE FUNCTION is_partitioned ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ diff --git a/sql/pgtap--0.98.0--0.99.0.sql.in b/sql/pgtap--0.98.0--0.99.0.sql.in index e165e17bbad0..1c957ac329ff 100644 --- a/sql/pgtap--0.98.0--0.99.0.sql.in +++ b/sql/pgtap--0.98.0--0.99.0.sql.in @@ -83,6 +83,26 @@ BEGIN END; $$ LANGUAGE plpgsql; +-- Note: this fixes a bug in the 97->98 upgrade script +-- 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; +*/ + -- _hasc( schema, table, constraint_type ) CREATE OR REPLACE FUNCTION _hasc ( NAME, NAME, CHAR ) RETURNS BOOLEAN AS $$ diff --git a/test/expected/build.out b/test/expected/build.out new file mode 100644 index 000000000000..f9861fe7708c --- /dev/null +++ b/test/expected/build.out @@ -0,0 +1 @@ +\unset ECHO diff --git a/test/expected/create.out b/test/expected/create.out new file mode 100644 index 000000000000..f9861fe7708c --- /dev/null +++ b/test/expected/create.out @@ -0,0 +1 @@ +\unset ECHO diff --git a/test/expected/extension.out b/test/expected/extension.out index c31ecf60467e..80d6fa05d589 100644 --- a/test/expected/extension.out +++ b/test/expected/extension.out @@ -12,9 +12,9 @@ ok 9 - extensions_are(exts, desc) should have the proper diagnostics ok 10 - extensions_are(exts) should pass ok 11 - extensions_are(exts) should have the proper description ok 12 - extensions_are(exts) should have the proper diagnostics -ok 13 - extensions_are(public, exts, desc) should pass -ok 14 - extensions_are(public, exts, desc) should have the proper description -ok 15 - extensions_are(public, exts, desc) should have the proper diagnostics +ok 13 - extensions_are(ci_schema, exts, desc) should pass +ok 14 - extensions_are(ci_schema, exts, desc) should have the proper description +ok 15 - extensions_are(ci_schema, exts, desc) should have the proper diagnostics ok 16 - extensions_are(non-sch, exts) should pass ok 17 - extensions_are(non-sch, exts) should have the proper description ok 18 - extensions_are(non-sch, exts) should have the proper diagnostics diff --git a/test/expected/runjusttests_6.out b/test/expected/runjusttests_6.out new file mode 100644 index 000000000000..5dac15ac91b3 --- /dev/null +++ b/test/expected/runjusttests_6.out @@ -0,0 +1,41 @@ +\unset ECHO + # Subtest: whatever."test ident"() + ok 1 - ident + ok 2 - ident 2 + 1..2 +ok 1 - whatever."test ident" + # Subtest: whatever.testnada() + 1..0 + # No tests run! +not ok 2 - whatever.testnada +# Failed test 2: "whatever.testnada" + # Subtest: whatever.testplpgsql() + ok 1 - plpgsql simple + ok 2 - plpgsql simple 2 + ok 3 - Should be a 1 in the test table + 1..3 +ok 3 - whatever.testplpgsql + # Subtest: whatever.testplpgsqldie() + # Test died: This test should die, but not halt execution. +# Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. +not ok 4 - whatever.testplpgsqldie +# Failed test 4: "whatever.testplpgsqldie" + # Subtest: whatever.testthis() + ok 1 - simple pass + ok 2 - another simple pass + 1..2 +ok 5 - whatever.testthis + # Subtest: whatever.testy() + ok 1 - pass + not ok 2 - this test intentionally fails + # Failed test 2: "this test intentionally fails" + 1..2 + # Looks like you failed 1 tests of 2 +not ok 6 - whatever.testy +# Failed test 6: "whatever.testy" + # Subtest: whatever.testz() + ok 1 - Late test should find nothing in the test table + 1..1 +ok 7 - whatever.testz +1..7 +# Looks like you failed 3 tests of 7 diff --git a/test/expected/update.out b/test/expected/update.out new file mode 100644 index 000000000000..4bd0c77c6d80 --- /dev/null +++ b/test/expected/update.out @@ -0,0 +1,4 @@ +\unset ECHO +1..2 +ok 1 - Old version of pgtap correctly installed +ok 2 - pgtap correctly updated diff --git a/test/psql.sql b/test/psql.sql new file mode 100644 index 000000000000..c147815d44f7 --- /dev/null +++ b/test/psql.sql @@ -0,0 +1,14 @@ +\set QUIET 1 + +-- +-- Tests for pgTAP. +-- +-- +-- Format the output for nice TAP. +\pset format unaligned +\pset tuples_only true +\pset pager + +-- Revert all changes on failure. +\set ON_ERROR_ROLLBACK 1 +\set ON_ERROR_STOP true diff --git a/test/schedule/build.sql b/test/schedule/build.sql new file mode 100644 index 000000000000..9365ba833bda --- /dev/null +++ b/test/schedule/build.sql @@ -0,0 +1,10 @@ +\unset ECHO +\i test/psql.sql + +/* + * Presumably no one is installing this way anymore, but this is a nice way to + * pick up any syntax errors during install. + */ +BEGIN; +\i sql/pgtap.sql +ROLLBACK; diff --git a/test/schedule/create.sql b/test/schedule/create.sql new file mode 100644 index 000000000000..ba355ed24dab --- /dev/null +++ b/test/schedule/create.sql @@ -0,0 +1,3 @@ +\unset ECHO +\i test/psql.sql +CREATE EXTENSION pgtap; diff --git a/test/schedule/main.sch b/test/schedule/main.sch new file mode 100644 index 000000000000..a8a5fbcaf964 --- /dev/null +++ b/test/schedule/main.sch @@ -0,0 +1,2 @@ +test: build +test: create diff --git a/test/schedule/update.sch b/test/schedule/update.sch new file mode 100644 index 000000000000..ea566141772f --- /dev/null +++ b/test/schedule/update.sch @@ -0,0 +1 @@ +test: update diff --git a/test/schedule/update.sql b/test/schedule/update.sql new file mode 100644 index 000000000000..be73d5174abf --- /dev/null +++ b/test/schedule/update.sql @@ -0,0 +1,20 @@ +\unset ECHO +\i test/psql.sql + +CREATE EXTENSION pgtap VERSION :'old_ver'; +SELECT plan(2); +SELECT is( + (SELECT extversion FROM pg_extension WHERE extname = 'pgtap') + , :'old_ver' + , 'Old version of pgtap correctly installed' +); + +ALTER EXTENSION pgtap UPDATE TO :'new_ver'; + +SELECT is( + (SELECT extversion FROM pg_extension WHERE extname = 'pgtap') + , :'new_ver' + , 'pgtap correctly updated' +); + +SELECT finish(); diff --git a/test/setup.sql b/test/setup.sql index 6cbbc964a5cc..492e91d83b5a 100644 --- a/test/setup.sql +++ b/test/setup.sql @@ -1,23 +1,3 @@ -\set QUIET 1 - --- --- Tests for pgTAP. --- --- --- Format the output for nice TAP. -\pset format unaligned -\pset tuples_only true -\pset pager - --- Revert all changes on failure. -\set ON_ERROR_ROLLBACK 1 -\set ON_ERROR_STOP true +\i test/psql.sql BEGIN; - --- Uncomment when testing with PGOPTIONS=--search_path=tap --- CREATE SCHEMA tap; SET search_path TO tap,public; - --- Load the TAP functions. -\i sql/pgtap.sql ---CREATE EXTENSION pgtap; diff --git a/test/sql/.gitignore b/test/sql/.gitignore new file mode 100644 index 000000000000..f7529a75b71a --- /dev/null +++ b/test/sql/.gitignore @@ -0,0 +1,3 @@ +build.sql +create.sql +update.sql diff --git a/test/sql/extension.sql b/test/sql/extension.sql index 0269a3dbca94..36f8ee94f7e5 100644 --- a/test/sql/extension.sql +++ b/test/sql/extension.sql @@ -15,8 +15,9 @@ BEGIN IF pg_version_num() >= 90100 THEN EXECUTE $E$ CREATE SCHEMA someschema; + CREATE SCHEMA ci_schema; CREATE SCHEMA "empty schema"; - CREATE EXTENSION IF NOT EXISTS citext; + CREATE EXTENSION IF NOT EXISTS citext SCHEMA ci_schema; CREATE EXTENSION IF NOT EXISTS isn SCHEMA someschema; CREATE EXTENSION IF NOT EXISTS ltree SCHEMA someschema; $E$; @@ -38,7 +39,7 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT* FROM check_test( - extensions_are( ARRAY['citext', 'isn', 'ltree', 'plpgsql'], 'Got em' ), + extensions_are( ARRAY['citext', 'isn', 'ltree', 'plpgsql', 'pgtap'], 'Got em' ), true, 'extensions_are(exts, desc)', 'Got em', @@ -46,7 +47,7 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT* FROM check_test( - extensions_are( ARRAY['citext', 'isn', 'ltree', 'plpgsql'] ), + extensions_are( ARRAY['citext', 'isn', 'ltree', 'plpgsql', 'pgtap'] ), true, 'extensions_are(exts)', 'Should have the correct extensions', @@ -54,9 +55,9 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT* FROM check_test( - extensions_are( 'public', ARRAY['citext'], 'Got em' ), + extensions_are( 'ci_schema', ARRAY['citext'], 'Got em' ), true, - 'extensions_are(public, exts, desc)', + 'extensions_are(ci_schema, exts, desc)', 'Got em', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -83,7 +84,7 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT* FROM check_test( - extensions_are( ARRAY['citext', 'isn', 'ltree', 'nonesuch'] ), + extensions_are( ARRAY['citext', 'isn', 'ltree', 'pgtap', 'nonesuch'] ), false, 'extensions_are(someexts)', 'Should have the correct extensions', @@ -98,7 +99,7 @@ BEGIN -- 8 tests FOR tap IN SELECT * FROM check_test( - has_extension( 'public', 'citext', 'desc' ), + has_extension( 'ci_schema', 'citext', 'desc' ), true, 'has_extension( schema, name, desc )', 'desc', @@ -106,10 +107,10 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - has_extension( 'public', 'citext'::name ), + has_extension( 'ci_schema', 'citext'::name ), true, 'has_extension( schema, name )', - 'Extension citext should exist in schema public', + 'Extension citext should exist in schema ci_schema', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -198,7 +199,7 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - hasnt_extension( 'public', 'citext', 'desc' ), + hasnt_extension( 'ci_schema', 'citext', 'desc' ), false, 'hasnt_extension( schema, name, desc )', 'desc', @@ -206,10 +207,10 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - hasnt_extension( 'public', 'citext'::name ), + hasnt_extension( 'ci_schema', 'citext'::name ), false, 'hasnt_extension( schema, name )', - 'Extension citext should not exist in schema public', + 'Extension citext should not exist in schema ci_schema', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -264,7 +265,7 @@ BEGIN FOR tap IN SELECT * FROM check_test( pass('mumble'), true, - 'extensions_are(public, exts, desc)', + 'extensions_are(ci_schema, exts, desc)', 'mumble', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; diff --git a/test/sql/performs_ok.sql b/test/sql/performs_ok.sql index 5f9c8df5ae60..a62b101e79f2 100644 --- a/test/sql/performs_ok.sql +++ b/test/sql/performs_ok.sql @@ -23,10 +23,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - performs_ok( 'SELECT TRUE', 99.99 ), + performs_ok( 'SELECT TRUE', 199.99 ), true, 'simple select numeric', - 'Should run in less than 99.99 ms', + 'Should run in less than 199.99 ms', '' ); diff --git a/tools/parallel_conn.sh b/tools/parallel_conn.sh new file mode 100755 index 000000000000..5cad67e8df10 --- /dev/null +++ b/tools/parallel_conn.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +# Find the maximum safe connections for parallel execution + +error () { + echo $@ 1>&2 +} +die () { + error $@ + exit 1 +} + +[ $# -le 1 ] || die "$0: Invalid number of arguments" + +PARALLEL_CONN=$1 + +if [ -n "$PARALLEL_CONN" ]; then + [ $PARALLEL_CONN -ge 1 ] 2>/dev/null || die "Invalid value for PARALLEL_CONN ($PARALLEL_CONN)" + echo $PARALLEL_CONN + exit +fi + +COMMAND="SELECT greatest(1, current_setting('max_connections')::int - current_setting('superuser_reserved_connections')::int - (SELECT count(*) FROM pg_stat_activity) - 2)" + +if PARALLEL_CONN=`psql -d ${PGDATABASE:-postgres} -qtc "$COMMAND" 2> /dev/null`; then + if [ $PARALLEL_CONN -ge 1 ] 2>/dev/null; then + # We know it's a number at this point + [ $PARALLEL_CONN -eq 1 ] && error "NOTICE: unable to run tests in parallel; not enough connections" + echo $PARALLEL_CONN + exit + fi +fi + +error "Problems encountered determining maximum parallel test connections; forcing serial mode" +echo 1 diff --git a/tools/psql_args.sh b/tools/psql_args.sh new file mode 100755 index 000000000000..e1f203f0984e --- /dev/null +++ b/tools/psql_args.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +# This script allows for passing args to psql using pg_regress's --launcher option + +while [ $# -gt 0 ]; do + case $1 in + */psql) + found=1 + break + ;; + *) + args="$args $1" + shift + ;; + esac +done + +if [ -z "$found" ]; then + echo "Error: psql not found in arguments" + exit 1 +fi + +#$* $args <&0 || exit $? +$* $args || exit $? From e9449ae765a1959e59a0651ac1627cc7011f5e9a Mon Sep 17 00:00:00 2001 From: nasbyj <45640492+nasbyj@users.noreply.github.com> Date: Mon, 18 Nov 2019 17:59:28 -0600 Subject: [PATCH 1070/1195] Add test for pg_upgrade (#218) Add test/test_MVU.sh, which will test running pg_upgrade in a cluster that has pgTap installed. To use this script, you need two different Postgres versions installed. Run test/test_MVU.sh old_port new_port old_version new_version old_path new_path where old_port and new_port are port numbers to use for the old and new clusters; old_version and new_version are the old and new *major* version numbers; and old_path and new_path are the location of the Postgres binaries for the old and new versions. --- .travis.yml | 22 ++-- Makefile | 12 +- pg-travis-test.sh | 81 +++++++++--- test/test_MVU.sh | 327 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 413 insertions(+), 29 deletions(-) create mode 100755 test/test_MVU.sh diff --git a/.travis.yml b/.travis.yml index 85c226e14451..b971009130c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,15 +7,17 @@ script: - bash ./pg-travis-test.sh env: - - PGVERSION=9.1 - - PGVERSION=9.2 - - PGVERSION=9.3 - - PGVERSION=9.4 - - PGVERSION=9.5 - - PGVERSION=9.6 - - PGVERSION=10 - - PGVERSION=11 UPDATE_FROM=0.99.0 - # Duplication below is via s/-/- PARALLEL_CONN=1/ + # WARNING! UPGRADE_TO tests pg_upgrade; UDPATE_FROM tests ALTER EXTENSION! Note UPGRADE vs UPDATE! + - UPGRADE_TO=9.2 PGVERSION=9.1 + - UPGRADE_TO=9.3 PGVERSION=9.2 + - UPGRADE_TO=9.4 PGVERSION=9.3 + - UPGRADE_TO=9.5 PGVERSION=9.4 + - UPGRADE_TO=9.6 PGVERSION=9.5 + - UPGRADE_TO=10 PGVERSION=9.6 + - UPGRADE_TO=11 PGVERSION=10 + - PGVERSION=11 UPDATE_FROM=0.99.0 + - UPGRADE_TO=11 PGVERSION=9.1 + # Duplication below is via s/-.*PGVERSION/- PARALLEL_CONN=1 PGVERSION/ - PARALLEL_CONN=1 PGVERSION=9.1 - PARALLEL_CONN=1 PGVERSION=9.2 - PARALLEL_CONN=1 PGVERSION=9.3 @@ -23,5 +25,5 @@ env: - PARALLEL_CONN=1 PGVERSION=9.5 - PARALLEL_CONN=1 PGVERSION=9.6 - PARALLEL_CONN=1 PGVERSION=10 - - PARALLEL_CONN=1 PGVERSION=11 UPDATE_FROM=0.99.0 + - PARALLEL_CONN=1 PGVERSION=11 UPDATE_FROM=0.99.0 script: bash ./pg-travis-test.sh diff --git a/Makefile b/Makefile index 51177dfc6267..267c27ccae1a 100644 --- a/Makefile +++ b/Makefile @@ -71,6 +71,9 @@ endif # We need to do various things with the PostgreSQL version. VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') +$(info ) +$(info GNUmake running against Postgres version $(VERSION), with pg_config located at $(shell dirname `which "$(PG_CONFIG)"`)) +$(info ) # # Major version check @@ -438,10 +441,13 @@ pgtap-version-%: $(EXTENSION_DIR)/pgtap--%.sql $(EXTENSION_DIR)/pgtap--$(EXTVERSION).sql: sql/pgtap--$(EXTVERSION).sql $(MAKE) install -# Need to explicitly exclude the current version. I wonder if there's a way to do this with % in the target? -# Note that we need to capture the test failure so the rule doesn't abort +# Install an old version of pgTap via pgxn. NOTE! This rule works in +# conjunction with the rule above, which handles installing our version. +# +# Note that we need to capture the test failure so the rule doesn't abort; +# that's why the test is written with || and not &&. $(EXTENSION_DIR)/pgtap--%.sql: - @ver=$(@:$(EXTENSION_DIR)/pgtap--%.sql=%); [ "$$ver" = "$(EXTVERSION)" ] || (echo Installing pgtap version $$ver from pgxn; pgxn install pgtap=$$ver) + @ver=$(@:$(EXTENSION_DIR)/pgtap--%.sql=%); [ "$$ver" = "$(EXTVERSION)" ] || (echo Installing pgtap version $$ver from pgxn; pgxn install --pg_config=$(PG_CONFIG) pgtap=$$ver) # This is separated out so it can be called before calling updatecheck_run .PHONY: updatecheck_deps diff --git a/pg-travis-test.sh b/pg-travis-test.sh index c9784920d7da..a408fb47d433 100644 --- a/pg-travis-test.sh +++ b/pg-travis-test.sh @@ -5,9 +5,57 @@ set -eux failed='' +#export DEBUG=9 +export UPGRADE_TO=${UPGRADE_TO:-} + sudo apt-get update -packages="python-setuptools postgresql-$PGVERSION postgresql-server-dev-$PGVERSION postgresql-common" +get_packages() { + echo "postgresql-$1 postgresql-server-dev-$1" +} +get_path() { + # See also test/test_MVU.sh + echo "/usr/lib/postgresql/$1/bin/" +} + +test_cmd() ( +if [ "$1" == '-s' ]; then + status="$2" + shift 2 +else + status="$1" +fi + +set +ux +echo +echo ############################################################################# +echo "PG-TRAVIS: running $@" +echo ############################################################################# +"$@" +rc=$? +set -ux +if [ $rc -ne 0 ]; then + echo + echo '!!!!!!!!!!!!!!!!' + echo "$@" + echo '!!!!!!!!!!!!!!!!' + echo + failed="$failed '$status'" +fi +) + +test_make() { + # Many tests depend on install, so just use sudo for all of them + test_cmd -s "$*" sudo make "$@" +} + +######################################################## +# Install packages +packages="python-setuptools postgresql-common $(get_packages $PGVERSION)" + +if [ -n "$UPGRADE_TO" ]; then + packages="$packages $(get_packages $UPGRADE_TO)" +fi # bug: http://www.postgresql.org/message-id/20130508192711.GA9243@msgid.df7cb.de sudo update-alternatives --remove-all postmaster.1.gz @@ -20,27 +68,21 @@ sudo chmod a+x /etc/init.d/postgresql sudo apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install $packages +# Need to explicitly set which pg_config we want to use +export PG_CONFIG="$(get_path $PGVERSION)pg_config" +[ "$PG_CONFIG" != 'pg_config' ] + +# Make life easier for test_MVU.sh +sudo usermod -a -G postgres $USER + + +# Setup cluster export PGPORT=55435 export PGUSER=postgres -export PG_CONFIG=/usr/lib/postgresql/$PGVERSION/bin/pg_config sudo pg_createcluster --start $PGVERSION test -p $PGPORT -- -A trust sudo easy_install pgxnclient -test_make() { - set +ux - # Many tests depend on install, so just use sudo for all of them - if ! sudo make "$@"; then - echo - echo '!!!!!!!!!!!!!!!!' - echo "make $@ failed" - echo '!!!!!!!!!!!!!!!!' - echo - failed="$failed '$@'" - fi - set -ux -} - test_make clean regress # pg_regress --launcher not supported prior to 9.1 @@ -55,6 +97,13 @@ for t in all install ; do test_make $t done +if [ -n "$UPGRADE_TO" ]; then + # We need to tell test_MVU.sh to run some steps via sudo since we're + # actually installing from pgxn into a system directory. We also use a + # different port number to avoid conflicting with existing clusters. + test_cmd test/test_MVU.sh -s 55667 55778 $PGVERSION $UPGRADE_TO "$(get_path $PGVERSION)" "$(get_path $UPGRADE_TO)" +fi + if [ -n "$failed" ]; then set +ux # $failed will have a leading space if it's not empty diff --git a/test/test_MVU.sh b/test/test_MVU.sh new file mode 100755 index 000000000000..3043151fd624 --- /dev/null +++ b/test/test_MVU.sh @@ -0,0 +1,327 @@ +#!/usr/bin/env bash + +# Test performing a Major Version Upgrade via pg_upgrade. +# +# MVU can be problematic due to catalog changes. For example, if the extension +# contains a view that references a catalog column that no longer exists, +# pg_upgrade itself will break. + +set -E -e -u -o pipefail + +rc=0 + +# ########### +# TODO: break these functions into a library shell script so they can be used elsewhere + +err_report() { + echo "errexit on line $(caller)" >&2 +} +trap err_report ERR + +error() { + echo "$@" >&2 +} + +die() { + local rc # Kinda silly here, but be safe... + rc=$1 + shift + error "$@" + exit $rc +} + +DEBUG=${DEBUG:-0} +debug() { + local level + level=$1 + shift + [ $level -gt $DEBUG ] || error "$@" +} + +debug_do() { + local level + level=$1 + shift + [ $level -gt $DEBUG ] || ( "$@" ) +} + +debug_ls() { +# Reverse test since we *exit* if we shouldn't debug! Also, note that unlike +# `exit`, `return` does not default to 0. +[ $1 -le $DEBUG ] || return 0 +( +level=$1 +shift + +# Look through each argument and see if more than one exist. If so, we don't +# need to print what it is we're listing. +location='' +for a in "$@"; do + if [ -e "$a" ]; then + if [ -n "$location" ]; then + location='' + break + else + location=$a + fi + fi +done + +error # blank line +[ -z "$location" ] || error "$location" +ls "$@" >&2 +) +} + +byte_len() ( +[ $# -eq 1 ] || die 99 "Expected 1 argument, not $# ($@)" +LANG=C LC_ALL=C +debug 99 "byte_len($@) = ${#1}" +echo ${#1} +) + +check_bin() { + for f in pg_ctl psql initdb; do + [ -x "$1/$f" ] || die 1 "$1/$f does not exist or is not executable" + done +} + +# mktemp on OS X results is a super-long path name that can cause problems, ie: +# connection to database failed: Unix-domain socket path "/private/var/folders/rp/mv0457r17cg0xqyw5j7701892tlc0h/T/test_pgtap_upgrade.upgrade.7W4BLF/.s.PGSQL.50432" is too long (maximum 103 bytes) +# +# This function looks for that condition and replaces the output with something more sane +short_tmpdir() ( +[ $# -eq 1 ] || die 99 "Expected 1 argument, not $# ($@)" +[ "$TMPDIR" != "" ] || die 99 '$TMPDIR not set' +out=$(mktemp -p '' -d $1.XXXXXX) +if echo "$out" | egrep -q '^(/private)?/var/folders'; then + newout=$(echo "$out" | sed -e "s#.*/$TMPDIR#$TMPDIR#") + debug 19 "replacing '$out' with '$newout'" +fi + +debug 9 "$0($@) = $out" +# Postgres blows up if this is too long. Technically the limit is 103 bytes, +# but need to account for the socket name, plus the fact that OS X might +# prepend '/private' to what we return. :( +[ $(byte_len "$out") -lt 75 ] || die 9 "short_tmpdir($@) returning a value >= 75 bytes ('$out')" +echo "$out" +) + +banner() { + echo + echo '###################################' + echo "$@" + echo '###################################' + echo +} + +find_at_path() ( +export PATH="$1:$PATH" # Unfortunately need to maintain old PATH to be able to find `which` :( +out=$(which $2) +[ -n "$out" ] || die 2 "unable to find $2" +echo $out +) + +modify_config() ( +# See below for definition of ctl_separator +if [ -z "$ctl_separator" ]; then + confDir=$PGDATA + conf=$confDir/postgresql.conf + debug 6 "$0: conf = $conf" + + debug 0 "Modifying NATIVE $conf" + + echo "port = $PGPORT" >> $conf +else + confDir="/etc/postgresql/$1/$cluster_name" + conf="$confDir/postgresql.conf" + debug 6 "$0: confDir = $confDir conf=$conf" + debug_ls 9 -la $confDir + + debug 0 "Modifying DEBIAN $confDir and $PGDATA" + + debug 2 ln -s $conf $PGDATA/ + ln -s $conf $PGDATA/ + # Some versions also have a conf.d ... + if [ -e "$confDir/conf.d" ]; then + debug 2 ln -s $confDir/conf.d $PGDATA/ + ln -s $confDir/conf.d $PGDATA/ + fi + debug_ls 8 -la $PGDATA + + # Shouldn't need to muck with PGPORT... + + # GUC changed somewhere between 9.1 and 9.5, so read config to figure out correct value + guc=$(grep unix_socket_director $conf | sed -e 's/^# *//' | cut -d ' ' -f 1) + debug 4 "$0: guc = $guc" + echo "$guc = '/tmp'" >> $conf +fi + +echo "synchronous_commit = off" >> $conf +) + +############################# +# Argument processing +keep='' +if [ "$1" == "-k" ]; then + debug 1 keeping results after exit + keep=1 + shift +fi + +sudo='' +if [ "$1" == '-s' ]; then + # Useful error if we can't find sudo + which sudo > /dev/null + sudo=$(which sudo) + debug 2 "sudo located at $sudo" + shift +fi + +OLD_PORT=$1 +NEW_PORT=$2 +OLD_VERSION=$3 +NEW_VERSION=$4 +OLD_PATH=$5 +NEW_PATH=$6 + +export PGDATABASE=test_pgtap_upgrade + +check_bin "$OLD_PATH" +check_bin "$NEW_PATH" + +export TMPDIR=${TMPDIR:-${TEMP:-${TMP:-/tmp}}} +debug 9 "\$TMPDIR=$TMPDIR" +[ $(byte_len "$TMPDIR") -lt 50 ] || die 9 "\$TMPDIR ('$TMPDIR') is too long; please set it" '(or $TEMP, or $TMP) to a value less than 50 bytes' +upgrade_dir=$(short_tmpdir test_pgtap_upgrade.upgrade) +old_dir=$(short_tmpdir test_pgtap_upgrade.old) +new_dir=$(short_tmpdir test_pgtap_upgrade.new) + +# Note: if this trap fires and removes the old directories with databases still +# running we'll get a bunch of spew on STDERR. It'd be nice to have a trap that +# knew what databases might actually be running. +exit_trap() { + # No point in stopping on error in here... + set +e + + # Force sudo on a debian system (see below) + [ -z "$ctl_separator" ] || sudo=$(which sudo) + + # Do not simply stick this command in the trap command; the quoting gets + # tricky, but the quoting is also damn critical to make sure rm -rf doesn't + # hose you if the temporary directory names have spaces in them! + $sudo rm -rf "$upgrade_dir" "$old_dir" "$new_dir" +} +[ -n "$keep" ] || trap exit_trap EXIT +debug 5 "traps: $(trap -p)" + +cluster_name=test_pg_upgrade +if which pg_ctlcluster > /dev/null 2>&1; then + # Looks like we're running in a apt / Debian / Ubuntu environment, so use their tooling + ctl_separator='--' + + # Force socket path to normal for pg_upgrade + export PGHOST=/tmp + + # And force current user + export PGUSER=$USER + + old_initdb="sudo pg_createcluster $OLD_VERSION $cluster_name -u $USER -p $OLD_PORT -d $old_dir -- -A trust" + new_initdb="sudo pg_createcluster $NEW_VERSION $cluster_name -u $USER -p $NEW_PORT -d $new_dir -- -A trust" + old_pg_ctl="sudo pg_ctlcluster $PGVERSION test_pg_upgrade" + new_pg_ctl=$old_pg_ctl + # See also ../pg-travis-test.sh + new_pg_upgrade=/usr/lib/postgresql/$NEW_VERSION/bin/pg_upgrade +else + ctl_separator='' + old_initdb="$(find_at_path "$OLD_PATH" initdb) -D $old_dir -N" + new_initdb="$(find_at_path "$NEW_PATH" initdb) -D $new_dir -N" + # s/initdb/pg_ctl/g + old_pg_ctl=$(find_at_path "$OLD_PATH" pg_ctl) + new_pg_ctl=$(find_at_path "$NEW_PATH" pg_ctl) + + new_pg_upgrade=$(find_at_path "$NEW_PATH" pg_upgrade) +fi + + +################################################################################################## +banner "Creating old version temporary installation at $old_dir on port $OLD_PORT (in the background)" +echo "Creating new version temporary installation at $new_dir on port $NEW_PORT (in the background)" +$old_initdb & +$new_initdb & + +echo Waiting... +wait + +################################################################################################## +banner "Starting OLD $OLD_VERSION postgres via $old_pg_ctl" +export PGDATA=$old_dir +export PGPORT=$OLD_PORT +modify_config $OLD_VERSION + +$old_pg_ctl start $ctl_separator -w # older versions don't support --wait + +echo "Creating database" +createdb # Note this uses PGPORT, so no need to wrap. + +echo "Installing pgtap" +# If user requested sudo then we need to use it for the install step. TODO: +# it'd be nice to move this into the Makefile, if the PGXS make stuff allows +# it... +( cd $(dirname $0)/.. && $sudo make clean install ) + + +banner "Loading extension" +psql -c 'CREATE EXTENSION pgtap' # Also uses PGPORT + +echo "Stopping OLD postgres via $old_pg_ctl" +$old_pg_ctl stop $ctl_separator -w # older versions don't support --wait + + + +################################################################################################## +banner "Running pg_upgrade" +export PGDATA=$new_dir +export PGPORT=$NEW_PORT +modify_config $NEW_VERSION + +cd $upgrade_dir +if [ $DEBUG -ge 9 ]; then + echo $old_dir; ls -la $old_dir; egrep 'director|unix|conf' $old_dir/postgresql.conf + echo $new_dir; ls -la $new_dir; egrep 'director|unix|conf' $new_dir/postgresql.conf +fi +echo $new_pg_upgrade -d "$old_dir" -D "$new_dir" -b "$OLD_PATH" -B "$NEW_PATH" +$new_pg_upgrade -d "$old_dir" -D "$new_dir" -b "$OLD_PATH" -B "$NEW_PATH" || rc=$? +if [ $rc -ne 0 ]; then + # Dump log, but only if we're not keeping the directory + if [ -z "$keep" ]; then + for f in `ls *.log`; do + echo; echo; echo; echo; echo; echo + echo "`pwd`/$f:" + cat "$f" + done + ls -la + else + error "pg_upgrade logs are at $upgrade_dir" + fi + die $rc "pg_upgrade returned $rc" +fi + + + +# TODO: turn this stuff on. It's pointless to test via `make regress` because +# that creates a new install; need to figure out the best way to test the new +# cluster +exit +################################################################################################## +banner "Testing UPGRADED cluster" + +# Run our tests against the upgraded cluster, but first make sure the old +# cluster is still down, to ensure there's no chance of testing it instead. +status=$($old_pg_ctl status) || die 3 "$old_pg_ctl status exited with $?" +debug 1 "$old_pg_ctl status returned $status" +[ "$status" == 'pg_ctl: no server running' ] || die 3 "old cluster is still running + +$old_pg_ctl status returned +$status" +( cd $(dirname $0)/..; $sudo make clean regress ) From a170aa4ba1bf2bea3a9bfe3fb2e513401a78ab05 Mon Sep 17 00:00:00 2001 From: Pete Date: Tue, 19 Nov 2019 22:17:28 +0000 Subject: [PATCH 1071/1195] fix test_user function example (#208) Fix syntax error in the docs. Thanks to PeteDevoy for the patch. --- doc/pgtap.mmd | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 2edd26ff20ed..d8f2781d6109 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -323,7 +323,6 @@ values. Here's an example, testing a hypothetical `users` table: CREATE OR REPLACE FUNCTION test_user( ) RETURNS SETOF TEXT AS $$ SELECT is( nick, 'theory', 'Should have nick') FROM users; - END; $$ LANGUAGE sql; See below for details on the pgTAP assertion functions. Once you've defined From 15975a09882c4ffb396a3304aab48566a51984e1 Mon Sep 17 00:00:00 2001 From: nasbyj <45640492+nasbyj@users.noreply.github.com> Date: Tue, 19 Nov 2019 17:50:37 -0600 Subject: [PATCH 1072/1195] Make description optional for col_not_null(schema, table, column) (#221) --- Changes | 1 + doc/pgtap.mmd | 2 + sql/pgtap--1.0.0--1.1.0.sql | 100 ++++++++ sql/pgtap.sql.in | 63 ++--- test/expected/coltap.out | 446 ++++++++++++++++++------------------ test/sql/coltap.sql | 17 +- 6 files changed, 382 insertions(+), 247 deletions(-) diff --git a/Changes b/Changes index c54c4a3ffc16..d9e286c87830 100644 --- a/Changes +++ b/Changes @@ -6,6 +6,7 @@ Revision history for pgTAP * Fix pgtap_version(), which incorrectly returned 0.99 when upgrading from version 0.99.0 to 1.0.0. * Fix issue with using pg_upgrade to Postgres 11+ with pgTap installed. +* Make description optional for col_not_null(). 1.0.0 2019-02-21T22:39:42Z -------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index d8f2781d6109..c6063e60eeb8 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4196,6 +4196,7 @@ view or composite type. ### `col_not_null()` ### SELECT col_not_null( :schema, :table, :column, :description ); + SELECT col_not_null( :schema, :table, :column ); SELECT col_not_null( :table, :column, :description ); SELECT col_not_null( :table, :column ); @@ -4225,6 +4226,7 @@ first, eh? ### `col_is_null()` ### SELECT col_is_null( :schema, :table, :column, :description ); + SELECT col_is_null( :schema, :table, :column ); SELECT col_is_null( :table, :column, :description ); SELECT col_is_null( :table, :column ); diff --git a/sql/pgtap--1.0.0--1.1.0.sql b/sql/pgtap--1.0.0--1.1.0.sql index e0653a2c5752..0a37b9439315 100644 --- a/sql/pgtap--1.0.0--1.1.0.sql +++ b/sql/pgtap--1.0.0--1.1.0.sql @@ -3,3 +3,103 @@ RETURNS NUMERIC AS 'SELECT 1.1;' LANGUAGE SQL IMMUTABLE; +-- These are now obsolete +DROP FUNCTION col_not_null ( NAME, NAME ); +DROP FUNCTION col_is_null ( NAME, NAME, NAME ); +DROP FUNCTION col_is_null ( NAME, NAME ); + +-- _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; + diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 766f2ee65b9a..3dc76da77df7 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1298,10 +1298,17 @@ $$ 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( $4 ) || E'\n' - || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || '.' || quote_ident($3) || ' does not exist' ); + RETURN fail( c_desc ) || E'\n' + || diag (' Column ' || qcol || ' does not exist' ); END IF; RETURN ok( EXISTS( @@ -1315,7 +1322,7 @@ BEGIN AND NOT a.attisdropped AND a.attname = $3 AND a.attnotnull = $5 - ), $4 + ), c_desc ); END; $$ LANGUAGE plpgsql; @@ -1323,10 +1330,17 @@ $$ 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( $3 ) || E'\n' - || diag (' Column ' || quote_ident($1) || '.' || quote_ident($2) || ' does not exist' ); + RETURN fail( c_desc ) || E'\n' + || diag (' Column ' || qcol || ' does not exist' ); END IF; RETURN ok( EXISTS( @@ -1339,46 +1353,43 @@ BEGIN AND NOT a.attisdropped AND a.attname = $2 AND a.attnotnull = $4 - ), $3 + ), c_desc ); END; $$ LANGUAGE plpgsql; -- col_not_null( schema, table, column, description ) -CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ +-- 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 ) -CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME, TEXT ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, $3, true ); -$$ LANGUAGE SQL; - -- col_not_null( table, column ) -CREATE OR REPLACE FUNCTION col_not_null ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should be NOT NULL', true ); +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 ) -CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME, TEXT ) -RETURNS TEXT AS $$ +-- 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( schema, table, column ) -CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME, NAME ) -RETURNS TEXT AS $$ +-- 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; --- col_is_null( table, column ) -CREATE OR REPLACE FUNCTION col_is_null ( NAME, NAME ) -RETURNS TEXT AS $$ - SELECT _col_is_null( $1, $2, 'Column ' || quote_ident($1) || '.' || quote_ident($2) || ' should allow NULL', false ); -$$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _get_col_type ( NAME, NAME, NAME ) RETURNS TEXT AS $$ diff --git a/test/expected/coltap.out b/test/expected/coltap.out index f3faa1c0bd88..7f84102b4a33 100644 --- a/test/expected/coltap.out +++ b/test/expected/coltap.out @@ -1,224 +1,230 @@ \unset ECHO -1..222 +1..228 ok 1 - col_not_null( sch, tab, col, desc ) should pass ok 2 - col_not_null( sch, tab, col, desc ) should have the proper description ok 3 - col_not_null( sch, tab, col, desc ) should have the proper diagnostics -ok 4 - col_not_null( tab, col, desc ) should pass -ok 5 - col_not_null( tab, col, desc ) should have the proper description -ok 6 - col_not_null( tab, col, desc ) should have the proper diagnostics -ok 7 - col_not_null( table, column ) should pass -ok 8 - col_not_null( table, column ) should have the proper description -ok 9 - col_not_null( table, column ) should have the proper diagnostics -ok 10 - col_not_null( table, column ) fail should fail -ok 11 - col_not_null( table, column ) fail should have the proper description -ok 12 - col_not_null( table, column ) fail should have the proper diagnostics -ok 13 - col_not_null( sch, tab, noncol, desc ) should fail -ok 14 - col_not_null( sch, tab, noncol, desc ) should have the proper description -ok 15 - col_not_null( sch, tab, noncol, desc ) should have the proper diagnostics -ok 16 - col_not_null( table, noncolumn ) fail should fail -ok 17 - col_not_null( table, noncolumn ) fail should have the proper description -ok 18 - col_not_null( table, noncolumn ) fail should have the proper diagnostics -ok 19 - col_is_null( sch, tab, col, desc ) should pass -ok 20 - col_is_null( sch, tab, col, desc ) should have the proper description -ok 21 - col_is_null( sch, tab, col, desc ) should have the proper diagnostics -ok 22 - col_is_null( tab, col, desc ) should pass -ok 23 - col_is_null( tab, col, desc ) should have the proper description -ok 24 - col_is_null( tab, col, desc ) should have the proper diagnostics -ok 25 - col_is_null( tab, col ) should pass -ok 26 - col_is_null( tab, col ) should have the proper description -ok 27 - col_is_null( tab, col ) should have the proper diagnostics -ok 28 - col_is_null( tab, col ) fail should fail -ok 29 - col_is_null( tab, col ) fail should have the proper description -ok 30 - col_is_null( tab, col ) fail should have the proper diagnostics -ok 31 - col_is_null( sch, tab, noncol, desc ) should fail -ok 32 - col_is_null( sch, tab, noncol, desc ) should have the proper description -ok 33 - col_is_null( sch, tab, noncol, desc ) should have the proper diagnostics -ok 34 - col_is_null( table, noncolumn ) fail should fail -ok 35 - col_is_null( table, noncolumn ) fail should have the proper description -ok 36 - col_is_null( table, noncolumn ) fail should have the proper diagnostics -ok 37 - col_type_is( sch, tab, col, sch, type, desc ) should pass -ok 38 - col_type_is( sch, tab, col, sch, type, desc ) should have the proper description -ok 39 - col_type_is( sch, tab, col, sch, type, desc ) should have the proper diagnostics -ok 40 - col_type_is( sch, tab, col, sch, type, desc ) should pass -ok 41 - col_type_is( sch, tab, col, sch, type, desc ) should have the proper description -ok 42 - col_type_is( sch, tab, col, sch, type, desc ) should have the proper diagnostics -ok 43 - col_type_is( sch, tab, myNum, sch, type, desc ) should pass -ok 44 - col_type_is( sch, tab, myNum, sch, type, desc ) should have the proper description -ok 45 - col_type_is( sch, tab, myNum, sch, type, desc ) should have the proper diagnostics -ok 46 - col_type_is( sch, tab, myNum, sch, type, desc ) should pass -ok 47 - col_type_is( sch, tab, myNum, sch, type, desc ) should have the proper description -ok 48 - col_type_is( sch, tab, myNum, sch, type, desc ) should have the proper diagnostics -ok 49 - col_type_is( sch, tab, camel, sch, type, desc ) should pass -ok 50 - col_type_is( sch, tab, camel, sch, type, desc ) should have the proper description -ok 51 - col_type_is( sch, tab, camel, sch, type, desc ) should have the proper diagnostics -ok 52 - col_type_is( sch, tab, camel, sch, type, desc ) should pass -ok 53 - col_type_is( sch, tab, camel, sch, type, desc ) should have the proper description -ok 54 - col_type_is( sch, tab, camel, sch, type, desc ) should have the proper diagnostics -ok 55 - col_type_is( sch, tab, camel, type, desc ) should pass -ok 56 - col_type_is( sch, tab, camel, type, desc ) should have the proper description -ok 57 - col_type_is( sch, tab, camel, type, desc ) should have the proper diagnostics -ok 58 - col_type_is( sch, tab, col, sch, type, desc ) fail should fail -ok 59 - col_type_is( sch, tab, col, sch, type, desc ) fail should have the proper description -ok 60 - col_type_is( sch, tab, col, sch, type, desc ) fail should have the proper diagnostics -ok 61 - col_type_is( sch, tab, col, sch, non-type, desc ) should fail -ok 62 - col_type_is( sch, tab, col, sch, non-type, desc ) should have the proper description -ok 63 - col_type_is( sch, tab, col, sch, non-type, desc ) should have the proper diagnostics -ok 64 - col_type_is( sch, tab, col, non-sch, type, desc ) should fail -ok 65 - col_type_is( sch, tab, col, non-sch, type, desc ) should have the proper description -ok 66 - col_type_is( sch, tab, col, non-sch, type, desc ) should have the proper diagnostics -ok 67 - col_type_is( sch, tab, non-col, sch, type, desc ) should fail -ok 68 - col_type_is( sch, tab, non-col, sch, type, desc ) should have the proper description -ok 69 - col_type_is( sch, tab, non-col, sch, type, desc ) should have the proper diagnostics -ok 70 - col_type_is( sch, tab, col, type, desc ) should pass -ok 71 - col_type_is( sch, tab, col, type, desc ) should have the proper description -ok 72 - col_type_is( sch, tab, col, type, desc ) should have the proper diagnostics -ok 73 - col_type_is( sch, tab, col, type ) should pass -ok 74 - col_type_is( sch, tab, col, type ) should have the proper description -ok 75 - col_type_is( sch, tab, col, type ) should have the proper diagnostics -ok 76 - col_type_is( tab, col, type, desc ) should pass -ok 77 - col_type_is( tab, col, type, desc ) should have the proper description -ok 78 - col_type_is( tab, col, type, desc ) should have the proper diagnostics -ok 79 - col_type_is( tab, col, type ) should pass -ok 80 - col_type_is( tab, col, type ) should have the proper description -ok 81 - col_type_is( tab, col, type ) should have the proper diagnostics -ok 82 - col_type_is( tab, col, type ) fail should fail -ok 83 - col_type_is( tab, col, type ) fail should have the proper description -ok 84 - col_type_is( tab, col, type ) fail should have the proper diagnostics -ok 85 - col_type_is( tab, noncol, type ) fail should fail -ok 86 - col_type_is( tab, noncol, type ) fail should have the proper description -ok 87 - col_type_is( tab, noncol, type ) fail should have the proper diagnostics -ok 88 - col_type_is( sch, tab, noncol, type, desc ) fail should fail -ok 89 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper description -ok 90 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper diagnostics -ok 91 - col_type_is with precision should pass -ok 92 - col_type_is with precision should have the proper description -ok 93 - col_type_is with precision should have the proper diagnostics -ok 94 - col_type_is precision fail should fail -ok 95 - col_type_is precision fail should have the proper description -ok 96 - col_type_is precision fail should have the proper diagnostics -ok 97 - col_has_default( sch, tab, col, desc ) should pass -ok 98 - col_has_default( sch, tab, col, desc ) should have the proper description -ok 99 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics -ok 100 - col_has_default( tab, col, desc ) should pass -ok 101 - col_has_default( tab, col, desc ) should have the proper description -ok 102 - col_has_default( tab, col, desc ) should have the proper diagnostics -ok 103 - col_has_default( tab, col ) should pass -ok 104 - col_has_default( tab, col ) should have the proper description -ok 105 - col_has_default( tab, col ) should have the proper diagnostics -ok 106 - col_has_default( sch, tab, col, desc ) should fail -ok 107 - col_has_default( sch, tab, col, desc ) should have the proper description -ok 108 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics -ok 109 - col_has_default( tab, col, desc ) should fail -ok 110 - col_has_default( tab, col, desc ) should have the proper description -ok 111 - col_has_default( tab, col, desc ) should have the proper diagnostics -ok 112 - col_has_default( tab, col ) should fail -ok 113 - col_has_default( tab, col ) should have the proper description -ok 114 - col_has_default( tab, col ) should have the proper diagnostics -ok 115 - col_has_default( sch, tab, col, desc ) should fail -ok 116 - col_has_default( sch, tab, col, desc ) should have the proper description -ok 117 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics -ok 118 - col_has_default( tab, col, desc ) should fail -ok 119 - col_has_default( tab, col, desc ) should have the proper description -ok 120 - col_has_default( tab, col, desc ) should have the proper diagnostics -ok 121 - col_has_default( tab, col ) should fail -ok 122 - col_has_default( tab, col ) should have the proper description -ok 123 - col_has_default( tab, col ) should have the proper diagnostics -ok 124 - col_hasnt_default( sch, tab, col, desc ) should fail -ok 125 - col_hasnt_default( sch, tab, col, desc ) should have the proper description -ok 126 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics -ok 127 - col_hasnt_default( tab, col, desc ) should fail -ok 128 - col_hasnt_default( tab, col, desc ) should have the proper description -ok 129 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics -ok 130 - col_hasnt_default( tab, col ) should fail -ok 131 - col_hasnt_default( tab, col ) should have the proper description -ok 132 - col_hasnt_default( tab, col ) should have the proper diagnostics -ok 133 - col_hasnt_default( sch, tab, col, desc ) should pass -ok 134 - col_hasnt_default( sch, tab, col, desc ) should have the proper description -ok 135 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics -ok 136 - col_hasnt_default( tab, col, desc ) should pass -ok 137 - col_hasnt_default( tab, col, desc ) should have the proper description -ok 138 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics -ok 139 - col_hasnt_default( tab, col ) should pass -ok 140 - col_hasnt_default( tab, col ) should have the proper description -ok 141 - col_hasnt_default( tab, col ) should have the proper diagnostics -ok 142 - col_hasnt_default( sch, tab, col, desc ) should fail -ok 143 - col_hasnt_default( sch, tab, col, desc ) should have the proper description -ok 144 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics -ok 145 - col_hasnt_default( tab, col, desc ) should fail -ok 146 - col_hasnt_default( tab, col, desc ) should have the proper description -ok 147 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics -ok 148 - col_hasnt_default( tab, col ) should fail -ok 149 - col_hasnt_default( tab, col ) should have the proper description -ok 150 - col_hasnt_default( tab, col ) should have the proper diagnostics -ok 151 - col_default_is( sch, tab, col, def, desc ) should pass -ok 152 - col_default_is( sch, tab, col, def, desc ) should have the proper description -ok 153 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics -ok 154 - col_default_is() fail should fail -ok 155 - col_default_is() fail should have the proper description -ok 156 - col_default_is() fail should have the proper diagnostics -ok 157 - col_default_is( tab, col, def, desc ) should pass -ok 158 - col_default_is( tab, col, def, desc ) should have the proper description -ok 159 - col_default_is( tab, col, def, desc ) should have the proper diagnostics -ok 160 - col_default_is( tab, col, def ) should pass -ok 161 - col_default_is( tab, col, def ) should have the proper description -ok 162 - col_default_is( tab, col, def ) should have the proper diagnostics -ok 163 - col_default_is( tab, col, int ) should pass -ok 164 - col_default_is( tab, col, int ) should have the proper description -ok 165 - col_default_is( tab, col, int ) should have the proper diagnostics -ok 166 - col_default_is( tab, col, NULL, desc ) should pass -ok 167 - col_default_is( tab, col, NULL, desc ) should have the proper description -ok 168 - col_default_is( tab, col, NULL, desc ) should have the proper diagnostics -ok 169 - col_default_is( tab, col, NULL ) should pass -ok 170 - col_default_is( tab, col, NULL ) should have the proper description -ok 171 - col_default_is( tab, col, NULL ) should have the proper diagnostics -ok 172 - col_default_is( tab, col, bogus, desc ) should fail -ok 173 - col_default_is( tab, col, bogus, desc ) should have the proper description -ok 174 - col_default_is( tab, col, bogus, desc ) should have the proper diagnostics -ok 175 - col_default_is( tab, col, bogus ) should fail -ok 176 - col_default_is( tab, col, bogus ) should have the proper description -ok 177 - col_default_is( tab, col, bogus ) should have the proper diagnostics -ok 178 - col_default_is( tab, col, expression ) should pass -ok 179 - col_default_is( tab, col, expression ) should have the proper description -ok 180 - col_default_is( tab, col, expression ) should have the proper diagnostics -ok 181 - col_default_is( tab, col, expression::text ) should pass -ok 182 - col_default_is( tab, col, expression::text ) should have the proper description -ok 183 - col_default_is( tab, col, expression::text ) should have the proper diagnostics -ok 184 - col_default_is( tab, col, expression, desc ) should pass -ok 185 - col_default_is( tab, col, expression, desc ) should have the proper description -ok 186 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics -ok 187 - col_default_is( tab, col, expression, desc ) should pass -ok 188 - col_default_is( tab, col, expression, desc ) should have the proper description -ok 189 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics -ok 190 - col_default_is( schema, tab, col, expression, desc ) should pass -ok 191 - col_default_is( schema, tab, col, expression, desc ) should have the proper description -ok 192 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics -ok 193 - col_default_is( schema, tab, col, expression, desc ) should pass -ok 194 - col_default_is( schema, tab, col, expression, desc ) should have the proper description -ok 195 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics -ok 196 - col_default_is( sch, tab, col, def, desc ) should fail -ok 197 - col_default_is( sch, tab, col, def, desc ) should have the proper description -ok 198 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics -ok 199 - col_default_is( tab, col, def, desc ) should fail -ok 200 - col_default_is( tab, col, def, desc ) should have the proper description -ok 201 - col_default_is( tab, col, def, desc ) should have the proper diagnostics -ok 202 - col_default_is( tab, col, def ) should fail -ok 203 - col_default_is( tab, col, def ) should have the proper description -ok 204 - col_default_is( tab, col, def ) should have the proper diagnostics -ok 205 - col_default_is( tab, col, CURRENT_CATALOG ) should pass -ok 206 - col_default_is( tab, col, CURRENT_CATALOG ) should have the proper description -ok 207 - col_default_is( tab, col, CURRENT_CATALOG ) should have the proper diagnostics -ok 208 - col_default_is( tab, col, CURRENT_ROLE ) should pass -ok 209 - col_default_is( tab, col, CURRENT_ROLE ) should have the proper description -ok 210 - col_default_is( tab, col, CURRENT_ROLE ) should have the proper diagnostics -ok 211 - col_default_is( tab, col, CURRENT_SCHEMA ) should pass -ok 212 - col_default_is( tab, col, CURRENT_SCHEMA ) should have the proper description -ok 213 - col_default_is( tab, col, CURRENT_SCHEMA ) should have the proper diagnostics -ok 214 - col_default_is( tab, col, CURRENT_USER ) should pass -ok 215 - col_default_is( tab, col, CURRENT_USER ) should have the proper description -ok 216 - col_default_is( tab, col, CURRENT_USER ) should have the proper diagnostics -ok 217 - col_default_is( tab, col, SESSION_USER ) should pass -ok 218 - col_default_is( tab, col, SESSION_USER ) should have the proper description -ok 219 - col_default_is( tab, col, SESSION_USER ) should have the proper diagnostics -ok 220 - col_default_is( tab, col, USER ) should pass -ok 221 - col_default_is( tab, col, USER ) should have the proper description -ok 222 - col_default_is( tab, col, USER ) should have the proper diagnostics +ok 4 - col_not_null( sch, tab, col::name ) should pass +ok 5 - col_not_null( sch, tab, col::name ) should have the proper description +ok 6 - col_not_null( sch, tab, col::name ) should have the proper diagnostics +ok 7 - col_not_null( tab, col, desc ) should pass +ok 8 - col_not_null( tab, col, desc ) should have the proper description +ok 9 - col_not_null( tab, col, desc ) should have the proper diagnostics +ok 10 - col_not_null( table, column ) should pass +ok 11 - col_not_null( table, column ) should have the proper description +ok 12 - col_not_null( table, column ) should have the proper diagnostics +ok 13 - col_not_null( table, column ) fail should fail +ok 14 - col_not_null( table, column ) fail should have the proper description +ok 15 - col_not_null( table, column ) fail should have the proper diagnostics +ok 16 - col_not_null( sch, tab, noncol, desc ) should fail +ok 17 - col_not_null( sch, tab, noncol, desc ) should have the proper description +ok 18 - col_not_null( sch, tab, noncol, desc ) should have the proper diagnostics +ok 19 - col_not_null( table, noncolumn ) fail should fail +ok 20 - col_not_null( table, noncolumn ) fail should have the proper description +ok 21 - col_not_null( table, noncolumn ) fail should have the proper diagnostics +ok 22 - col_is_null( sch, tab, col, desc ) should pass +ok 23 - col_is_null( sch, tab, col, desc ) should have the proper description +ok 24 - col_is_null( sch, tab, col, desc ) should have the proper diagnostics +ok 25 - col_is_null( sch, tab, col::name ) should pass +ok 26 - col_is_null( sch, tab, col::name ) should have the proper description +ok 27 - col_is_null( sch, tab, col::name ) should have the proper diagnostics +ok 28 - col_is_null( tab, col, desc ) should pass +ok 29 - col_is_null( tab, col, desc ) should have the proper description +ok 30 - col_is_null( tab, col, desc ) should have the proper diagnostics +ok 31 - col_is_null( tab, col ) should pass +ok 32 - col_is_null( tab, col ) should have the proper description +ok 33 - col_is_null( tab, col ) should have the proper diagnostics +ok 34 - col_is_null( tab, col ) fail should fail +ok 35 - col_is_null( tab, col ) fail should have the proper description +ok 36 - col_is_null( tab, col ) fail should have the proper diagnostics +ok 37 - col_is_null( sch, tab, noncol, desc ) should fail +ok 38 - col_is_null( sch, tab, noncol, desc ) should have the proper description +ok 39 - col_is_null( sch, tab, noncol, desc ) should have the proper diagnostics +ok 40 - col_is_null( table, noncolumn ) fail should fail +ok 41 - col_is_null( table, noncolumn ) fail should have the proper description +ok 42 - col_is_null( table, noncolumn ) fail should have the proper diagnostics +ok 43 - col_type_is( sch, tab, col, sch, type, desc ) should pass +ok 44 - col_type_is( sch, tab, col, sch, type, desc ) should have the proper description +ok 45 - col_type_is( sch, tab, col, sch, type, desc ) should have the proper diagnostics +ok 46 - col_type_is( sch, tab, col, sch, type, desc ) should pass +ok 47 - col_type_is( sch, tab, col, sch, type, desc ) should have the proper description +ok 48 - col_type_is( sch, tab, col, sch, type, desc ) should have the proper diagnostics +ok 49 - col_type_is( sch, tab, myNum, sch, type, desc ) should pass +ok 50 - col_type_is( sch, tab, myNum, sch, type, desc ) should have the proper description +ok 51 - col_type_is( sch, tab, myNum, sch, type, desc ) should have the proper diagnostics +ok 52 - col_type_is( sch, tab, myNum, sch, type, desc ) should pass +ok 53 - col_type_is( sch, tab, myNum, sch, type, desc ) should have the proper description +ok 54 - col_type_is( sch, tab, myNum, sch, type, desc ) should have the proper diagnostics +ok 55 - col_type_is( sch, tab, camel, sch, type, desc ) should pass +ok 56 - col_type_is( sch, tab, camel, sch, type, desc ) should have the proper description +ok 57 - col_type_is( sch, tab, camel, sch, type, desc ) should have the proper diagnostics +ok 58 - col_type_is( sch, tab, camel, sch, type, desc ) should pass +ok 59 - col_type_is( sch, tab, camel, sch, type, desc ) should have the proper description +ok 60 - col_type_is( sch, tab, camel, sch, type, desc ) should have the proper diagnostics +ok 61 - col_type_is( sch, tab, camel, type, desc ) should pass +ok 62 - col_type_is( sch, tab, camel, type, desc ) should have the proper description +ok 63 - col_type_is( sch, tab, camel, type, desc ) should have the proper diagnostics +ok 64 - col_type_is( sch, tab, col, sch, type, desc ) fail should fail +ok 65 - col_type_is( sch, tab, col, sch, type, desc ) fail should have the proper description +ok 66 - col_type_is( sch, tab, col, sch, type, desc ) fail should have the proper diagnostics +ok 67 - col_type_is( sch, tab, col, sch, non-type, desc ) should fail +ok 68 - col_type_is( sch, tab, col, sch, non-type, desc ) should have the proper description +ok 69 - col_type_is( sch, tab, col, sch, non-type, desc ) should have the proper diagnostics +ok 70 - col_type_is( sch, tab, col, non-sch, type, desc ) should fail +ok 71 - col_type_is( sch, tab, col, non-sch, type, desc ) should have the proper description +ok 72 - col_type_is( sch, tab, col, non-sch, type, desc ) should have the proper diagnostics +ok 73 - col_type_is( sch, tab, non-col, sch, type, desc ) should fail +ok 74 - col_type_is( sch, tab, non-col, sch, type, desc ) should have the proper description +ok 75 - col_type_is( sch, tab, non-col, sch, type, desc ) should have the proper diagnostics +ok 76 - col_type_is( sch, tab, col, type, desc ) should pass +ok 77 - col_type_is( sch, tab, col, type, desc ) should have the proper description +ok 78 - col_type_is( sch, tab, col, type, desc ) should have the proper diagnostics +ok 79 - col_type_is( sch, tab, col, type ) should pass +ok 80 - col_type_is( sch, tab, col, type ) should have the proper description +ok 81 - col_type_is( sch, tab, col, type ) should have the proper diagnostics +ok 82 - col_type_is( tab, col, type, desc ) should pass +ok 83 - col_type_is( tab, col, type, desc ) should have the proper description +ok 84 - col_type_is( tab, col, type, desc ) should have the proper diagnostics +ok 85 - col_type_is( tab, col, type ) should pass +ok 86 - col_type_is( tab, col, type ) should have the proper description +ok 87 - col_type_is( tab, col, type ) should have the proper diagnostics +ok 88 - col_type_is( tab, col, type ) fail should fail +ok 89 - col_type_is( tab, col, type ) fail should have the proper description +ok 90 - col_type_is( tab, col, type ) fail should have the proper diagnostics +ok 91 - col_type_is( tab, noncol, type ) fail should fail +ok 92 - col_type_is( tab, noncol, type ) fail should have the proper description +ok 93 - col_type_is( tab, noncol, type ) fail should have the proper diagnostics +ok 94 - col_type_is( sch, tab, noncol, type, desc ) fail should fail +ok 95 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper description +ok 96 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper diagnostics +ok 97 - col_type_is with precision should pass +ok 98 - col_type_is with precision should have the proper description +ok 99 - col_type_is with precision should have the proper diagnostics +ok 100 - col_type_is precision fail should fail +ok 101 - col_type_is precision fail should have the proper description +ok 102 - col_type_is precision fail should have the proper diagnostics +ok 103 - col_has_default( sch, tab, col, desc ) should pass +ok 104 - col_has_default( sch, tab, col, desc ) should have the proper description +ok 105 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics +ok 106 - col_has_default( tab, col, desc ) should pass +ok 107 - col_has_default( tab, col, desc ) should have the proper description +ok 108 - col_has_default( tab, col, desc ) should have the proper diagnostics +ok 109 - col_has_default( tab, col ) should pass +ok 110 - col_has_default( tab, col ) should have the proper description +ok 111 - col_has_default( tab, col ) should have the proper diagnostics +ok 112 - col_has_default( sch, tab, col, desc ) should fail +ok 113 - col_has_default( sch, tab, col, desc ) should have the proper description +ok 114 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics +ok 115 - col_has_default( tab, col, desc ) should fail +ok 116 - col_has_default( tab, col, desc ) should have the proper description +ok 117 - col_has_default( tab, col, desc ) should have the proper diagnostics +ok 118 - col_has_default( tab, col ) should fail +ok 119 - col_has_default( tab, col ) should have the proper description +ok 120 - col_has_default( tab, col ) should have the proper diagnostics +ok 121 - col_has_default( sch, tab, col, desc ) should fail +ok 122 - col_has_default( sch, tab, col, desc ) should have the proper description +ok 123 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics +ok 124 - col_has_default( tab, col, desc ) should fail +ok 125 - col_has_default( tab, col, desc ) should have the proper description +ok 126 - col_has_default( tab, col, desc ) should have the proper diagnostics +ok 127 - col_has_default( tab, col ) should fail +ok 128 - col_has_default( tab, col ) should have the proper description +ok 129 - col_has_default( tab, col ) should have the proper diagnostics +ok 130 - col_hasnt_default( sch, tab, col, desc ) should fail +ok 131 - col_hasnt_default( sch, tab, col, desc ) should have the proper description +ok 132 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics +ok 133 - col_hasnt_default( tab, col, desc ) should fail +ok 134 - col_hasnt_default( tab, col, desc ) should have the proper description +ok 135 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics +ok 136 - col_hasnt_default( tab, col ) should fail +ok 137 - col_hasnt_default( tab, col ) should have the proper description +ok 138 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 139 - col_hasnt_default( sch, tab, col, desc ) should pass +ok 140 - col_hasnt_default( sch, tab, col, desc ) should have the proper description +ok 141 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics +ok 142 - col_hasnt_default( tab, col, desc ) should pass +ok 143 - col_hasnt_default( tab, col, desc ) should have the proper description +ok 144 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics +ok 145 - col_hasnt_default( tab, col ) should pass +ok 146 - col_hasnt_default( tab, col ) should have the proper description +ok 147 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 148 - col_hasnt_default( sch, tab, col, desc ) should fail +ok 149 - col_hasnt_default( sch, tab, col, desc ) should have the proper description +ok 150 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics +ok 151 - col_hasnt_default( tab, col, desc ) should fail +ok 152 - col_hasnt_default( tab, col, desc ) should have the proper description +ok 153 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics +ok 154 - col_hasnt_default( tab, col ) should fail +ok 155 - col_hasnt_default( tab, col ) should have the proper description +ok 156 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 157 - col_default_is( sch, tab, col, def, desc ) should pass +ok 158 - col_default_is( sch, tab, col, def, desc ) should have the proper description +ok 159 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics +ok 160 - col_default_is() fail should fail +ok 161 - col_default_is() fail should have the proper description +ok 162 - col_default_is() fail should have the proper diagnostics +ok 163 - col_default_is( tab, col, def, desc ) should pass +ok 164 - col_default_is( tab, col, def, desc ) should have the proper description +ok 165 - col_default_is( tab, col, def, desc ) should have the proper diagnostics +ok 166 - col_default_is( tab, col, def ) should pass +ok 167 - col_default_is( tab, col, def ) should have the proper description +ok 168 - col_default_is( tab, col, def ) should have the proper diagnostics +ok 169 - col_default_is( tab, col, int ) should pass +ok 170 - col_default_is( tab, col, int ) should have the proper description +ok 171 - col_default_is( tab, col, int ) should have the proper diagnostics +ok 172 - col_default_is( tab, col, NULL, desc ) should pass +ok 173 - col_default_is( tab, col, NULL, desc ) should have the proper description +ok 174 - col_default_is( tab, col, NULL, desc ) should have the proper diagnostics +ok 175 - col_default_is( tab, col, NULL ) should pass +ok 176 - col_default_is( tab, col, NULL ) should have the proper description +ok 177 - col_default_is( tab, col, NULL ) should have the proper diagnostics +ok 178 - col_default_is( tab, col, bogus, desc ) should fail +ok 179 - col_default_is( tab, col, bogus, desc ) should have the proper description +ok 180 - col_default_is( tab, col, bogus, desc ) should have the proper diagnostics +ok 181 - col_default_is( tab, col, bogus ) should fail +ok 182 - col_default_is( tab, col, bogus ) should have the proper description +ok 183 - col_default_is( tab, col, bogus ) should have the proper diagnostics +ok 184 - col_default_is( tab, col, expression ) should pass +ok 185 - col_default_is( tab, col, expression ) should have the proper description +ok 186 - col_default_is( tab, col, expression ) should have the proper diagnostics +ok 187 - col_default_is( tab, col, expression::text ) should pass +ok 188 - col_default_is( tab, col, expression::text ) should have the proper description +ok 189 - col_default_is( tab, col, expression::text ) should have the proper diagnostics +ok 190 - col_default_is( tab, col, expression, desc ) should pass +ok 191 - col_default_is( tab, col, expression, desc ) should have the proper description +ok 192 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics +ok 193 - col_default_is( tab, col, expression, desc ) should pass +ok 194 - col_default_is( tab, col, expression, desc ) should have the proper description +ok 195 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics +ok 196 - col_default_is( schema, tab, col, expression, desc ) should pass +ok 197 - col_default_is( schema, tab, col, expression, desc ) should have the proper description +ok 198 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics +ok 199 - col_default_is( schema, tab, col, expression, desc ) should pass +ok 200 - col_default_is( schema, tab, col, expression, desc ) should have the proper description +ok 201 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics +ok 202 - col_default_is( sch, tab, col, def, desc ) should fail +ok 203 - col_default_is( sch, tab, col, def, desc ) should have the proper description +ok 204 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics +ok 205 - col_default_is( tab, col, def, desc ) should fail +ok 206 - col_default_is( tab, col, def, desc ) should have the proper description +ok 207 - col_default_is( tab, col, def, desc ) should have the proper diagnostics +ok 208 - col_default_is( tab, col, def ) should fail +ok 209 - col_default_is( tab, col, def ) should have the proper description +ok 210 - col_default_is( tab, col, def ) should have the proper diagnostics +ok 211 - col_default_is( tab, col, CURRENT_CATALOG ) should pass +ok 212 - col_default_is( tab, col, CURRENT_CATALOG ) should have the proper description +ok 213 - col_default_is( tab, col, CURRENT_CATALOG ) should have the proper diagnostics +ok 214 - col_default_is( tab, col, CURRENT_ROLE ) should pass +ok 215 - col_default_is( tab, col, CURRENT_ROLE ) should have the proper description +ok 216 - col_default_is( tab, col, CURRENT_ROLE ) should have the proper diagnostics +ok 217 - col_default_is( tab, col, CURRENT_SCHEMA ) should pass +ok 218 - col_default_is( tab, col, CURRENT_SCHEMA ) should have the proper description +ok 219 - col_default_is( tab, col, CURRENT_SCHEMA ) should have the proper diagnostics +ok 220 - col_default_is( tab, col, CURRENT_USER ) should pass +ok 221 - col_default_is( tab, col, CURRENT_USER ) should have the proper description +ok 222 - col_default_is( tab, col, CURRENT_USER ) should have the proper diagnostics +ok 223 - col_default_is( tab, col, SESSION_USER ) should pass +ok 224 - col_default_is( tab, col, SESSION_USER ) should have the proper description +ok 225 - col_default_is( tab, col, SESSION_USER ) should have the proper diagnostics +ok 226 - col_default_is( tab, col, USER ) should pass +ok 227 - col_default_is( tab, col, USER ) should have the proper description +ok 228 - col_default_is( tab, col, USER ) should have the proper diagnostics diff --git a/test/sql/coltap.sql b/test/sql/coltap.sql index 4805af183171..d0c463f2be04 100644 --- a/test/sql/coltap.sql +++ b/test/sql/coltap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(222); +SELECT plan(228); --SELECT * from no_plan(); CREATE TYPE public."myType" AS ( @@ -50,6 +50,13 @@ SELECT * FROM check_test( 'typname not null', '' ); +SELECT * FROM check_test( + col_not_null( 'pg_catalog', 'pg_type', 'typname'::name ), + true, + 'col_not_null( sch, tab, col::name )', + 'Column pg_catalog.pg_type.typname should be NOT NULL', + '' +); SELECT * FROM check_test( col_not_null( 'sometab', 'id', 'blah blah blah' ), @@ -103,6 +110,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + col_is_null( 'public', 'sometab', 'name'::name ), + true, + 'col_is_null( sch, tab, col::name )', + 'Column public.sometab.name should allow NULL', + '' +); + SELECT * FROM check_test( col_is_null( 'sometab', 'name', 'my desc' ), true, From 341de8505903766a7e5f39f43bc6fe22f75d1452 Mon Sep 17 00:00:00 2001 From: nasbyj <45640492+nasbyj@users.noreply.github.com> Date: Fri, 22 Nov 2019 16:29:15 -0600 Subject: [PATCH 1073/1195] Run tests after pg_upgrade (#222) Modify `test/test_MVU.sh` to run tests against pgTap after running `pg_upgrade`. Additionally: - Minor improvements to Makefile, including running pg_prove with multiple jobs for tests that support parallelism - Refactor `pg-travis-test.sh`; add explicit tests for `test`, `test-serial`, and `test-parallel`. - Make output ordering of `_keys()` deterministic. This was uncovered by multiple runs of the `test*` targets against the same database. --- Makefile | 40 +++++++++-- pg-travis-test.sh | 118 +++++++++++++++++++++++-------- sql/pgtap--1.0.0--1.1.0.sql | 25 +++++++ sql/pgtap.sql.in | 6 +- test/test_MVU.sh | 136 ++++++++++++++++++++++++++---------- 5 files changed, 250 insertions(+), 75 deletions(-) diff --git a/Makefile b/Makefile index 267c27ccae1a..2c74540c513d 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,6 @@ VERSION_FILES = sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERS BASE_FILES = $(subst --$(EXTVERSION),,$(VERSION_FILES)) sql/uninstall_$(MAINEXT).sql _IN_FILES = $(wildcard sql/*--*.sql.in) _IN_PATCHED = $(_IN_FILES:.in=) -TESTS = $(wildcard test/sql/*.sql) EXTRA_CLEAN = $(VERSION_FILES) sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap-core.sql sql/pgtap-schema.sql doc/*.html EXTRA_CLEAN += $(wildcard sql/*.orig) # These are files left behind by patch DOCS = doc/pgtap.mmd @@ -47,11 +46,17 @@ SCHEDULE_FILES = $(wildcard test/schedule/*.sch) TEST_FILES = $(filter-out $(SCHEDULE_DEST_FILES),$(wildcard test/sql/*.sql)) # Plain test names -TESTS = $(notdir $(TEST_FILES:.sql=)) +ALL_TESTS = $(notdir $(TEST_FILES:.sql=)) # Some tests fail when run in parallel SERIAL_TESTS = coltap hastap +# Some tests fail when run by pg_prove +# TODO: The first 2 of these fail because they have tests that intentionally +# fail, which makes pg_prove return a failure. Add a mode to these test files +# that will disable the failure tests. +PG_PROVE_EXCLUDE_TESTS = runjusttests runnotests runtests + # This is a bit of a hack, but if REGRESS isn't set we can't installcheck, and # it must be set BEFORE including pgxs. Note this gets set again below REGRESS = --schedule $(TB_DIR)/run.sch @@ -333,8 +338,23 @@ updatecheck: updatecheck_deps install installcheck_deps: $(SCHEDULE_DEST_FILES) extension_check set_parallel_conn # More dependencies below # In addition to installcheck, one can also run the tests through pg_prove. -test: extension_check - pg_prove --pset tuples_only=1 $(TEST_FILES) +.PHONY: test-serial +test-serial: extension_check + @echo Running pg_prove on SERIAL tests + pg_prove --pset tuples_only=1 \ + $(PG_PROVE_SERIAL_FILES) + +.PHONY: test-parallel +test-parallel: extension_check set_parallel_conn + @echo Running pg_prove on PARALLEL tests + pg_prove --pset tuples_only=1 \ + -j $(PARALLEL_CONN) \ + $(PG_PROVE_PARALLEL_FILES) + +.PHONY: test +test: test-serial test-parallel + @echo + @echo WARNING: these tests are EXCLUDED from pg_prove testing: $(PG_PROVE_EXCLUDE_TESTS) # # General test support @@ -351,8 +371,16 @@ REGRESS = --schedule $(TB_DIR)/run.sch # Set this again just to be safe REGRESS_OPTS = --inputdir=test --load-language=plpgsql --max-connections=$(PARALLEL_CONN) --schedule $(SETUP_SCH) $(REGRESS_CONF) SETUP_SCH = test/schedule/main.sch # schedule to use for test setup; this can be forcibly changed by some targets! IGNORE_TESTS = $(notdir $(EXCLUDE_TEST_FILES:.sql=)) -PARALLEL_TESTS = $(filter-out $(IGNORE_TESTS),$(filter-out $(SERIAL_TESTS),$(TESTS))) +PARALLEL_TESTS = $(filter-out $(IGNORE_TESTS),$(filter-out $(SERIAL_TESTS),$(ALL_TESTS))) +PG_PROVE_PARALLEL_TESTS = $(filter-out $(PG_PROVE_EXCLUDE_TESTS),$(PARALLEL_TESTS)) +PG_PROVE_SERIAL_TESTS = $(filter-out $(PG_PROVE_EXCLUDE_TESTS),$(SERIAL_TESTS)) +PG_PROVE_PARALLEL_FILES = $(call get_test_file,$(PG_PROVE_PARALLEL_TESTS)) +PG_PROVE_SERIAL_FILES = $(call get_test_file,$(PG_PROVE_SERIAL_TESTS)) GENERATED_SCHEDULES = $(TB_DIR)/serial.sch $(TB_DIR)/parallel.sch + +# Convert test name to file name +get_test_file = $(addprefix test/sql/,$(addsuffix .sql,$(1))) + installcheck: $(TB_DIR)/run.sch installcheck_deps # Parallel tests will use $(PARALLEL_TESTS) number of connections if we let it, @@ -383,7 +411,7 @@ $(TB_DIR)/exclude_tests: $(TB_DIR)/ @[ "`cat $@ 2>/dev/null`" = "$(EXCLUDE_TEST)" ] || (echo "Rebuilding $@"; echo "$(EXCLUDE_TEST)" > $@) $(TB_DIR)/serial.sch: $(GENERATED_SCHEDULE_DEPS) - @(for f in $(IGNORE_TESTS); do echo "ignore: $$f"; done; for f in $(TESTS); do echo "test: $$f"; done) > $@ + @(for f in $(IGNORE_TESTS); do echo "ignore: $$f"; done; for f in $(ALL_TESTS); do echo "test: $$f"; done) > $@ $(TB_DIR)/parallel.sch: $(GENERATED_SCHEDULE_DEPS) @( \ diff --git a/pg-travis-test.sh b/pg-travis-test.sh index a408fb47d433..3f96bda422f3 100644 --- a/pg-travis-test.sh +++ b/pg-travis-test.sh @@ -2,23 +2,34 @@ # Based on https://gist.github.com/petere/6023944 -set -eux -failed='' +set -E -e -u -o pipefail + +# +# NOTE: you can control what tests run by setting the TARGETS environment +# variable for a particular branch in the Travis console +# + +# You can set this to higher levels for more debug output +#export DEBUG=1 +#set -x -#export DEBUG=9 export UPGRADE_TO=${UPGRADE_TO:-} +failed='' +tests_run=0 sudo apt-get update get_packages() { - echo "postgresql-$1 postgresql-server-dev-$1" + echo "libtap-parser-sourcehandler-pgtap-perl postgresql-$1 postgresql-server-dev-$1" } get_path() { # See also test/test_MVU.sh echo "/usr/lib/postgresql/$1/bin/" } -test_cmd() ( +# Do NOT use () here; we depend on being able to set failed +test_cmd() { +#local status rc if [ "$1" == '-s' ]; then status="$2" shift 2 @@ -26,29 +37,76 @@ else status="$1" fi -set +ux echo echo ############################################################################# echo "PG-TRAVIS: running $@" echo ############################################################################# -"$@" -rc=$? -set -ux +tests_run=$((tests_run + 1)) +# Use || so as not to trip up -e, and a sub-shell to be safe. +rc=0 +( "$@" ) || rc=$? if [ $rc -ne 0 ]; then echo - echo '!!!!!!!!!!!!!!!!' - echo "$@" - echo '!!!!!!!!!!!!!!!!' + echo '!!!!!!!!!!!!!!!! FAILURE !!!!!!!!!!!!!!!!' + echo "$@" returned $rc + echo '!!!!!!!!!!!!!!!! FAILURE !!!!!!!!!!!!!!!!' echo failed="$failed '$status'" fi -) +} + +# Ensure test_cmd sets failed properly +test_cmd fail > /dev/null 2>&1 +if [ -z "$failed" ]; then + echo "code error: test_cmd() did not set \$failed" + exit 91 +fi +failed='' test_make() { # Many tests depend on install, so just use sudo for all of them test_cmd -s "$*" sudo make "$@" } +######################################################## +# TEST TARGETS +sanity() { + test_make clean regress +} + +update() { + # pg_regress --launcher not supported prior to 9.1 + # There are some other failures in 9.1 and 9.2 (see https://travis-ci.org/decibel/pgtap/builds/358206497). + echo $PGVERSION | grep -qE "8[.]|9[.][012]" || test_make clean updatecheck +} + +tests_run_by_target_all=11 # 1 + 5 * 2 +all() { + # the test* targets use pg_prove, which assumes it's making a default psql + # connection to a database that has pgTap installed, so we need to set that + # up. + test_cmd psql -Ec 'CREATE EXTENSION pgtap' + + # TODO: install software necessary to allow testing 'html' target + # UPDATE tests_run_by_target_all IF YOU ADD ANY TESTS HERE! + for t in all install test test-serial test-parallel ; do + # Test from a clean slate... + test_make uninstall clean $t + # And then test again + test_make $t + done +} + +upgrade() { +if [ -n "$UPGRADE_TO" ]; then + # We need to tell test_MVU.sh to run some steps via sudo since we're + # actually installing from pgxn into a system directory. We also use a + # different port number to avoid conflicting with existing clusters. + test_cmd test/test_MVU.sh -s 55667 55778 $PGVERSION $UPGRADE_TO "$(get_path $PGVERSION)" "$(get_path $UPGRADE_TO)" +fi +} + + ######################################################## # Install packages packages="python-setuptools postgresql-common $(get_packages $PGVERSION)" @@ -83,29 +141,27 @@ sudo pg_createcluster --start $PGVERSION test -p $PGPORT -- -A trust sudo easy_install pgxnclient -test_make clean regress - -# pg_regress --launcher not supported prior to 9.1 -# There are some other failures in 9.1 and 9.2 (see https://travis-ci.org/decibel/pgtap/builds/358206497). -echo $PGVERSION | grep -qE "8[.]|9[.][012]" || test_make clean updatecheck - -# Explicitly test these other targets - -# TODO: install software necessary to allow testing the 'test' and 'html' targets -for t in all install ; do - test_make clean $t - test_make $t +set +x +total_tests=$((3 + $tests_run_by_target_all)) +for t in ${TARGETS:-sanity update upgrade all}; do + $t done -if [ -n "$UPGRADE_TO" ]; then - # We need to tell test_MVU.sh to run some steps via sudo since we're - # actually installing from pgxn into a system directory. We also use a - # different port number to avoid conflicting with existing clusters. - test_cmd test/test_MVU.sh -s 55667 55778 $PGVERSION $UPGRADE_TO "$(get_path $PGVERSION)" "$(get_path $UPGRADE_TO)" +# You can use this to check tests that are failing pg_prove +pg_prove -f --pset tuples_only=1 test/sql/unique.sql test/sql/check.sql || true + +if [ $tests_run -eq $total_tests ]; then + echo Ran $tests_run tests +elif [ $tests_run -gt 0 ]; then + echo "WARNING! ONLY RAN $tests_run OUT OF $total_tests TESTS!" + # We don't consider this an error... +else + echo No tests were run! + exit 2 fi if [ -n "$failed" ]; then - set +ux + echo # $failed will have a leading space if it's not empty echo "These test targets failed:$failed" exit 1 diff --git a/sql/pgtap--1.0.0--1.1.0.sql b/sql/pgtap--1.0.0--1.1.0.sql index 0a37b9439315..8e211bb6d8e8 100644 --- a/sql/pgtap--1.0.0--1.1.0.sql +++ b/sql/pgtap--1.0.0--1.1.0.sql @@ -103,3 +103,28 @@ CREATE OR REPLACE FUNCTION col_is_null ( SELECT _col_is_null( $1, $2, $3, false ); $$ LANGUAGE SQL; +-- _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; + diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 3dc76da77df7..165f4dda9c96 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1930,24 +1930,26 @@ AS -- _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) + 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) + 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 ) diff --git a/test/test_MVU.sh b/test/test_MVU.sh index 3043151fd624..052852ab47e0 100755 --- a/test/test_MVU.sh +++ b/test/test_MVU.sh @@ -122,6 +122,11 @@ out=$(which $2) echo $out ) +run_make() ( +cd $(dirname $0)/.. +$sudo make $@ +) + modify_config() ( # See below for definition of ctl_separator if [ -z "$ctl_separator" ]; then @@ -207,6 +212,11 @@ exit_trap() { # Force sudo on a debian system (see below) [ -z "$ctl_separator" ] || sudo=$(which sudo) + # Attempt to shut down any running clusters, otherwise we'll get log spew + # when the temporary directories vanish. + $old_pg_ctl stop > /dev/null 2>&1 + $new_pg_ctl stop > /dev/null 2>&1 + # Do not simply stick this command in the trap command; the quoting gets # tricky, but the quoting is also damn critical to make sure rm -rf doesn't # hose you if the temporary directory names have spaces in them! @@ -227,17 +237,17 @@ if which pg_ctlcluster > /dev/null 2>&1; then export PGUSER=$USER old_initdb="sudo pg_createcluster $OLD_VERSION $cluster_name -u $USER -p $OLD_PORT -d $old_dir -- -A trust" + old_pg_ctl="sudo pg_ctlcluster $OLD_VERSION test_pg_upgrade" new_initdb="sudo pg_createcluster $NEW_VERSION $cluster_name -u $USER -p $NEW_PORT -d $new_dir -- -A trust" - old_pg_ctl="sudo pg_ctlcluster $PGVERSION test_pg_upgrade" - new_pg_ctl=$old_pg_ctl + new_pg_ctl="sudo pg_ctlcluster $NEW_VERSION test_pg_upgrade" + # See also ../pg-travis-test.sh new_pg_upgrade=/usr/lib/postgresql/$NEW_VERSION/bin/pg_upgrade else ctl_separator='' old_initdb="$(find_at_path "$OLD_PATH" initdb) -D $old_dir -N" - new_initdb="$(find_at_path "$NEW_PATH" initdb) -D $new_dir -N" - # s/initdb/pg_ctl/g old_pg_ctl=$(find_at_path "$OLD_PATH" pg_ctl) + new_initdb="$(find_at_path "$NEW_PATH" initdb) -D $new_dir -N" new_pg_ctl=$(find_at_path "$NEW_PATH" pg_ctl) new_pg_upgrade=$(find_at_path "$NEW_PATH" pg_upgrade) @@ -268,8 +278,7 @@ echo "Installing pgtap" # If user requested sudo then we need to use it for the install step. TODO: # it'd be nice to move this into the Makefile, if the PGXS make stuff allows # it... -( cd $(dirname $0)/.. && $sudo make clean install ) - +run_make clean install banner "Loading extension" psql -c 'CREATE EXTENSION pgtap' # Also uses PGPORT @@ -285,43 +294,98 @@ export PGDATA=$new_dir export PGPORT=$NEW_PORT modify_config $NEW_VERSION -cd $upgrade_dir -if [ $DEBUG -ge 9 ]; then - echo $old_dir; ls -la $old_dir; egrep 'director|unix|conf' $old_dir/postgresql.conf - echo $new_dir; ls -la $new_dir; egrep 'director|unix|conf' $new_dir/postgresql.conf -fi -echo $new_pg_upgrade -d "$old_dir" -D "$new_dir" -b "$OLD_PATH" -B "$NEW_PATH" -$new_pg_upgrade -d "$old_dir" -D "$new_dir" -b "$OLD_PATH" -B "$NEW_PATH" || rc=$? -if [ $rc -ne 0 ]; then - # Dump log, but only if we're not keeping the directory - if [ -z "$keep" ]; then - for f in `ls *.log`; do - echo; echo; echo; echo; echo; echo - echo "`pwd`/$f:" - cat "$f" - done - ls -la - else - error "pg_upgrade logs are at $upgrade_dir" +( + cd $upgrade_dir + if [ $DEBUG -ge 9 ]; then + echo $old_dir; ls -la $old_dir; egrep 'director|unix|conf' $old_dir/postgresql.conf + echo $new_dir; ls -la $new_dir; egrep 'director|unix|conf' $new_dir/postgresql.conf fi - die $rc "pg_upgrade returned $rc" -fi + echo $new_pg_upgrade -d "$old_dir" -D "$new_dir" -b "$OLD_PATH" -B "$NEW_PATH" + $new_pg_upgrade -d "$old_dir" -D "$new_dir" -b "$OLD_PATH" -B "$NEW_PATH" || rc=$? + if [ $rc -ne 0 ]; then + # Dump log, but only if we're not keeping the directory + if [ -z "$keep" ]; then + for f in `ls *.log`; do + echo; echo; echo; echo; echo; echo + echo "`pwd`/$f:" + cat "$f" + done + ls -la + else + error "pg_upgrade logs are at $upgrade_dir" + fi + die $rc "pg_upgrade returned $rc" + fi +) -# TODO: turn this stuff on. It's pointless to test via `make regress` because -# that creates a new install; need to figure out the best way to test the new -# cluster -exit ################################################################################################## banner "Testing UPGRADED cluster" # Run our tests against the upgraded cluster, but first make sure the old # cluster is still down, to ensure there's no chance of testing it instead. -status=$($old_pg_ctl status) || die 3 "$old_pg_ctl status exited with $?" -debug 1 "$old_pg_ctl status returned $status" -[ "$status" == 'pg_ctl: no server running' ] || die 3 "old cluster is still running +# Note that some versions of pg_ctl return different exit codes when the server +# isn't running. +echo ensuring OLD cluster is stopped +rc=0 +status=$($old_pg_ctl status) || rc=$? +[ "$status" == 'pg_ctl: no server running' ] || die 3 "$old_pg_ctl status returned '$status' and exited with $?" +debug 4 "$old_pg_ctl status exited with $rc" + +# TODO: send log output to a file so it doesn't mix in with STDOUT +echo starting NEW cluster +$new_pg_ctl start $ctl_separator -w || die $? "$new_pg_ctl start $ctl_separator -w returned $?" +$new_pg_ctl status # Should error if not running on most versions + +psql -E -c '\dx' +psql -E -c 'SELECT pgtap_version(), pg_version_num(), version();' + +# We want to make sure to use the NEW pg_config +export PG_CONFIG=$(find_at_path "$NEW_PATH" pg_config) +[ -x "$PG_CONFIG" ] || ( debug_ls 1 "$NEW_PATH"; die 4 "unable to find executable pg_config at $NEW_PATH" ) + +# When crossing certain upgrade boundaries we need to exclude some tests +# because they test functions not available in the previous version. +int_ver() { + local ver + ver=$(echo $1 | tr -d .) + # "multiply" versions less than 7.0 by 10 so that version 10.x becomes 100, + # 11 becomes 110, etc. + [ $ver -ge 70 ] || ver="${ver}0" + echo $ver +} +EXCLUDE_TEST_FILES='' +add_exclude() { + local old new + old=$(int_ver $1) + new=$(int_ver $2) + shift 2 + if [ $(int_ver $OLD_VERSION) -le $old -a $(int_ver $NEW_VERSION) -ge $new ]; then + EXCLUDE_TEST_FILES="$EXCLUDE_TEST_FILES $@" + fi +} + +add_exclude 9.1 9.2 test/sql/throwtap.sql +add_exclude 9.4 9.5 test/sql/policy.sql test/sql/throwtap.sql +add_exclude 9.6 10 test/sql/partitions.sql + +# Use this if there's a single test failing in Travis that you can't figure out... +#(cd $(dirname $0)/..; pg_prove -v --pset tuples_only=1 test/sql/throwtap.sql) + +export EXCLUDE_TEST_FILES +run_make clean test + +if [ -n "$EXCLUDE_TEST_FILES" ]; then + banner "Rerunning test after a reinstall due to version differences" + echo "Excluded tests: $EXCLUDE_TEST_FILES" + export EXCLUDED_TEST_FILES='' + + # Need to build with the new version, then install + run_make install + + psql -E -c 'DROP EXTENSION pgtap; CREATE EXTENSION pgtap;' + + run_make test +fi -$old_pg_ctl status returned -$status" -( cd $(dirname $0)/..; $sudo make clean regress ) From 1b30e27ce35271294b09e6a53906256930046033 Mon Sep 17 00:00:00 2001 From: nasbyj <45640492+nasbyj@users.noreply.github.com> Date: Fri, 22 Nov 2019 21:19:39 -0600 Subject: [PATCH 1074/1195] Add support for Postgres 12.0 (#223) Update Travis config. Also update META.json that we no longer support <= 9.1. --- .travis.yml | 6 ++++-- META.json | 5 +---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index b971009130c1..1e164877a696 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,8 +15,9 @@ env: - UPGRADE_TO=9.6 PGVERSION=9.5 - UPGRADE_TO=10 PGVERSION=9.6 - UPGRADE_TO=11 PGVERSION=10 - - PGVERSION=11 UPDATE_FROM=0.99.0 - - UPGRADE_TO=11 PGVERSION=9.1 + - UPGRADE_TO=12 PGVERSION=11 UPDATE_FROM=0.99.0 + - PGVERSION=12 UPDATE_FROM=0.99.0 + - UPGRADE_TO=12 PGVERSION=9.1 # Duplication below is via s/-.*PGVERSION/- PARALLEL_CONN=1 PGVERSION/ - PARALLEL_CONN=1 PGVERSION=9.1 - PARALLEL_CONN=1 PGVERSION=9.2 @@ -26,4 +27,5 @@ env: - PARALLEL_CONN=1 PGVERSION=9.6 - PARALLEL_CONN=1 PGVERSION=10 - PARALLEL_CONN=1 PGVERSION=11 UPDATE_FROM=0.99.0 + - PARALLEL_CONN=1 PGVERSION=12 UPDATE_FROM=0.99.0 script: bash ./pg-travis-test.sh diff --git a/META.json b/META.json index 818624ccb80e..fe5ae9f1110a 100644 --- a/META.json +++ b/META.json @@ -14,10 +14,7 @@ "runtime": { "requires": { "plpgsql": 0, - "PostgreSQL": "8.3.0" - }, - "recommends": { - "PostgreSQL": "8.4.0" + "PostgreSQL": "9.1.0" } } }, From 16880858a30ef6959ad656411fcc05a24ab0b9a9 Mon Sep 17 00:00:00 2001 From: nasbyj <45640492+nasbyj@users.noreply.github.com> Date: Sat, 23 Nov 2019 17:46:10 -0600 Subject: [PATCH 1075/1195] Fix pgtap-core and pgtap-static (#225) This also uncovered some issues due to patches for older versions, so remove all support for pre-9.1 --- Makefile | 38 +- compat/9.0/pgtap--0.96.0--0.97.0.patch | 88 ----- compat/gencore | 4 +- compat/install-8.1.patch | 125 ------- compat/install-8.2.patch | 467 ------------------------- compat/install-8.3.patch | 424 ---------------------- compat/install-8.4.patch | 34 -- compat/install-9.0.patch | 176 ---------- src/pgtap.c | 92 ----- 9 files changed, 7 insertions(+), 1441 deletions(-) delete mode 100644 compat/9.0/pgtap--0.96.0--0.97.0.patch delete mode 100644 compat/install-8.1.patch delete mode 100644 compat/install-8.2.patch delete mode 100644 compat/install-8.3.patch delete mode 100644 compat/install-8.4.patch delete mode 100644 compat/install-9.0.patch delete mode 100644 src/pgtap.c diff --git a/Makefile b/Makefile index 2c74540c513d..c80ee78a967c 100644 --- a/Makefile +++ b/Makefile @@ -85,13 +85,8 @@ $(info ) # # TODO: update this # TODO9.1: update the $(TB_DIR) target below when de-supporting 9.1 -ifeq ($(shell echo $(VERSION) | grep -qE "^(7[.]|8[.]0)" && echo yes || echo no),yes) -$(error pgTAP requires PostgreSQL 8.1 or later. This is $(VERSION)) -endif - -# Compile the C code only if we're on 8.3 or older. -ifeq ($(shell echo $(VERSION) | grep -qE "^8[.][123]" && echo yes || echo no),yes) -MODULES = src/pgtap +ifeq ($(shell echo $(VERSION) | grep -qE "^([78][.]|9[.]0)" && echo yes || echo no),yes) +$(error pgTAP requires PostgreSQL 9.1 or later. This is $(VERSION)) endif # Make sure we build these. @@ -140,16 +135,6 @@ ifeq ($(shell echo $(VERSION) | grep -qE "[89][.]" && echo yes || echo no),yes) EXCLUDE_TEST_FILES += test/sql/partitions.sql endif -# Enum tests not supported by 8.2 and earlier. -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][12]" && echo yes || echo no),yes) -EXCLUDE_TEST_FILES += test/sql/enumtap.sql -endif - -# Values tests not supported by 8.1 and earlier. -ifeq ($(shell echo $(VERSION) | grep -qE "8[.][1]" && echo yes || echo no),yes) -EXCLUDE_TEST_FILES += test/sql/valueset.sql -endif - # # Check for missing extensions # @@ -222,21 +207,6 @@ ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][012]|8[.][1234])" && echo yes | endif ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][01]|8[.][1234])" && echo yes || echo no),yes) patch -p0 < compat/install-9.1.patch -endif -ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.]0|8[.][1234])" && echo yes || echo no),yes) - patch -p0 < compat/install-9.0.patch -endif -ifeq ($(shell echo $(VERSION) | grep -qE "^8[.][1234]" && echo yes || echo no),yes) - patch -p0 < compat/install-8.4.patch -endif -ifeq ($(shell echo $(VERSION) | grep -qE "^8[.][123]" && echo yes || echo no),yes) - patch -p0 < compat/install-8.3.patch -endif -ifeq ($(shell echo $(VERSION) | grep -qE "^8[.][12]" && echo yes || echo no),yes) - patch -p0 < compat/install-8.2.patch -endif -ifeq ($(shell echo $(VERSION) | grep -qE "^8[.][1]" && echo yes || echo no),yes) - patch -p0 < compat/install-8.1.patch endif sed -e 's,MODULE_PATHNAME,$$libdir/pgtap,g' -e 's,__OS__,$(OSNAME),g' -e 's,__VERSION__,$(NUMVERSION),g' sql/pgtap.sql > sql/pgtap.tmp mv sql/pgtap.tmp sql/pgtap.sql @@ -298,10 +268,10 @@ sql/pgtap-static.sql: sql/pgtap.sql.in sed -e 's#MODULE_PATHNAME#$$libdir/pgtap#g' -e 's#__OS__#$(OSNAME)#g' -e 's#__VERSION__#$(NUMVERSION)#g' $@.tmp > $@ EXTRA_CLEAN += sql/pgtap-static.sql sql/pgtap-static.sql.tmp -sql/pgtap-core.sql: sql/pgtap-static.sql +sql/pgtap-core.sql: sql/pgtap-static.sql compat/gencore $(PERL) compat/gencore 0 sql/pgtap-static.sql > sql/pgtap-core.sql -sql/pgtap-schema.sql: sql/pgtap-static.sql +sql/pgtap-schema.sql: sql/pgtap-static.sql compat/gencore $(PERL) compat/gencore 1 sql/pgtap-static.sql > sql/pgtap-schema.sql $(B_DIR)/static/: $(B_DIR) diff --git a/compat/9.0/pgtap--0.96.0--0.97.0.patch b/compat/9.0/pgtap--0.96.0--0.97.0.patch deleted file mode 100644 index c86930c0decd..000000000000 --- a/compat/9.0/pgtap--0.96.0--0.97.0.patch +++ /dev/null @@ -1,88 +0,0 @@ ---- sql/pgtap--0.96.0--0.97.0.sql -+++ sql/pgtap--0.96.0--0.97.0.sql -@@ -216,85 +216,3 @@ - SELECT ok( NOT _opc_exists( $1 ), 'Operator class ' || quote_ident($1) || ' should not exist' ); - $$ LANGUAGE SQL; - ---- https://github.com/theory/pgtap/pull/101 -- ---- 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; -- diff --git a/compat/gencore b/compat/gencore index 093d8fa9d047..13f453a10604 100644 --- a/compat/gencore +++ b/compat/gencore @@ -10,7 +10,7 @@ my %keep = map { chomp; $_ => 1 } ; my ($name, $type) = $invert ? ('Schema', 'schema-testing') : ('Core', 'assertion'); print qq{ -- This file defines pgTAP $name, a portable collection of $type --- functions for TAP-based unit testing on PostgreSQL 8.3 or higher. It is +-- functions for TAP-based unit testing on PostgreSQL 9.1 or higher. It is -- distributed under the revised FreeBSD license. The home page for the pgTAP -- project is: @@ -85,7 +85,9 @@ performs_ok performs_within _time_trials _ident_array_to_string +_prokind tap_funky +_funkargs _got_func has_function hasnt_function diff --git a/compat/install-8.1.patch b/compat/install-8.1.patch deleted file mode 100644 index 911994f98607..000000000000 --- a/compat/install-8.1.patch +++ /dev/null @@ -1,125 +0,0 @@ ---- sql/pgtap.sql -+++ sql/pgtap.sql -@@ -2278,13 +2278,13 @@ - CREATE OR REPLACE FUNCTION _constraint ( NAME, NAME, CHAR, NAME[], TEXT, TEXT ) - RETURNS TEXT AS $$ - DECLARE -- akey NAME[]; -+ rec record; - 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; -+ FOR rec IN SELECT * FROM _keys($1, $2, $3) AS b(a) LOOP -+ IF rec.a = $4 THEN RETURN pass($5); END IF; -+ keys = keys || rec.a::text; - END LOOP; - IF array_upper(keys, 0) = 1 THEN - have := 'No ' || $6 || ' constraints'; -@@ -2302,13 +2302,13 @@ - CREATE OR REPLACE FUNCTION _constraint ( NAME, CHAR, NAME[], TEXT, TEXT ) - RETURNS TEXT AS $$ - DECLARE -- akey NAME[]; -+ rec record; - 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; -+ FOR rec IN SELECT * FROM _keys($1, $2) AS b(a) LOOP -+ IF rec.a = $3 THEN RETURN pass($4); END IF; -+ keys = keys || rec.a::text; - END LOOP; - IF array_upper(keys, 0) = 1 THEN - have := 'No ' || $5 || ' constraints'; -@@ -6207,7 +6207,7 @@ - CREATE OR REPLACE FUNCTION _runem( text[], boolean ) - RETURNS SETOF TEXT AS $$ - DECLARE -- tap text; -+ rec record; - lbound int := array_lower($1, 1); - BEGIN - IF lbound IS NULL THEN RETURN; END IF; -@@ -6215,8 +6215,8 @@ - -- 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; -+ FOR rec IN EXECUTE 'SELECT * FROM ' || $1[i] || '() AS b(a)' LOOP -+ RETURN NEXT rec.a; - END LOOP; - END LOOP; - RETURN; -@@ -6285,7 +6285,7 @@ - setup ALIAS FOR $3; - teardown ALIAS FOR $4; - tests ALIAS FOR $5; -- tap TEXT; -+ rec RECORD; - tfaild INTEGER := 0; - ffaild INTEGER := 0; - tnumb INTEGER := 0; -@@ -6295,7 +6295,7 @@ - BEGIN - -- No plan support. - PERFORM * FROM no_plan(); -- FOR tap IN SELECT * FROM _runem(startup, false) LOOP RETURN NEXT tap; END LOOP; -+ FOR rec IN SELECT * FROM _runem(startup, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; - EXCEPTION - -- Catch all exceptions and simply rethrow custom exceptions. This - -- will roll back everything in the above block. -@@ -6334,18 +6334,18 @@ - BEGIN - BEGIN - -- Run the setup functions. -- FOR tap IN SELECT * FROM _runem(setup, false) LOOP -- RETURN NEXT regexp_replace(tap, E'(^|\n)', E'\\1 ', 'g'); -+ FOR rec IN SELECT * FROM _runem(setup, false) AS b(a) LOOP -+ RETURN NEXT regexp_replace(rec.a, 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, E'(^|\n)', E'\\1 ', 'g'); -+ FOR rec IN EXECUTE 'SELECT * FROM ' || tests[i] || '() AS b(a)' LOOP -+ RETURN NEXT regexp_replace(rec.a, 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, E'(^|\n)', E'\\1 ', 'g'); -+ FOR rec IN SELECT * FROM _runem(teardown, false) AS b(a) LOOP -+ RETURN NEXT regexp_replace(rec.a, E'(^|\n)', E'\\1 ', 'g'); - END LOOP; - - -- Emit the plan. -@@ -6401,11 +6401,11 @@ - END LOOP; - - -- Run the shutdown functions. -- FOR tap IN SELECT * FROM _runem(shutdown, false) LOOP RETURN NEXT tap; END LOOP; -+ FOR rec IN SELECT * FROM _runem(shutdown, false) AS b(a) LOOP RETURN NEXT rec.a; END LOOP; - - -- Finish up. -- FOR tap IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) LOOP -- RETURN NEXT tap; -+ FOR rec IN SELECT * FROM _finish( COALESCE(_get('curr_test'), 0), 0, tfaild ) AS b(a) LOOP -+ RETURN NEXT rec.a; - END LOOP; - - -- Clean up and return. -@@ -7663,7 +7663,7 @@ - DECLARE - rec RECORD; - BEGIN -- EXECUTE _query($1) INTO rec; -+ FOR rec in EXECUTE _query($1) LOOP END LOOP; - IF NOT textin(record_out(rec)) IS DISTINCT FROM textin(record_out($2)) - THEN RETURN ok(true, $3); - END IF; diff --git a/compat/install-8.2.patch b/compat/install-8.2.patch deleted file mode 100644 index 98b17cc89c1a..000000000000 --- a/compat/install-8.2.patch +++ /dev/null @@ -1,467 +0,0 @@ ---- 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) diff --git a/compat/install-8.3.patch b/compat/install-8.3.patch deleted file mode 100644 index 412e40bb0651..000000000000 --- a/compat/install-8.3.patch +++ /dev/null @@ -1,424 +0,0 @@ ---- sql/pgtap.sql -+++ sql/pgtap.sql -@@ -9,6 +9,11 @@ - RETURNS text AS 'SELECT current_setting(''server_version'')' - LANGUAGE SQL IMMUTABLE; - -+CREATE OR REPLACE FUNCTION pg_typeof("any") -+RETURNS regtype -+AS '$libdir/pgtap' -+LANGUAGE C STABLE; -+ - CREATE OR REPLACE FUNCTION pg_version_num() - RETURNS integer AS $$ - SELECT current_setting('server_version_num')::integer; -@@ -2483,7 +2488,7 @@ - JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid - ; - --CREATE OR REPLACE FUNCTION _funkargs ( NAME[] ) -+CREATE OR REPLACE FUNCTION _funkargs ( TEXT[] ) - RETURNS TEXT AS $$ - BEGIN - RETURN array_to_string($1::regtype[], ','); -@@ -6819,7 +6824,7 @@ - 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 -+ IF have_rec::text IS DISTINCT FROM want_rec::text 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' || -@@ -6976,7 +6981,7 @@ - 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 -+ IF have_rec::text IS DISTINCT FROM want_rec::text OR have_found <> want_found THEN - RETURN ok( true, $3 ); - ELSE - FETCH have INTO have_rec; -@@ -7185,13 +7190,7 @@ - $$ 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[] ) -+CREATE OR REPLACE FUNCTION collect_tap( text[] ) - RETURNS TEXT AS $$ - SELECT array_to_string($1, E'\n'); - $$ LANGUAGE sql; -@@ -7667,7 +7666,7 @@ - rec RECORD; - BEGIN - EXECUTE _query($1) INTO rec; -- IF NOT rec IS DISTINCT FROM $2 THEN RETURN ok(true, $3); END IF; -+ IF NOT rec::text IS DISTINCT FROM $2::text 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 -@@ -9856,358 +9855,3 @@ - '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; diff --git a/compat/install-8.4.patch b/compat/install-8.4.patch deleted file mode 100644 index ee4ae73fa43a..000000000000 --- a/compat/install-8.4.patch +++ /dev/null @@ -1,34 +0,0 @@ ---- sql/pgtap.sql -+++ sql/pgtap.sql -@@ -7693,7 +7693,6 @@ - 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) -@@ -7708,7 +7707,6 @@ - JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE n.nspname = $1 - AND c.relname = $2 -- AND NOT t.tgisinternal - ), - $4 - ); -@@ -7732,7 +7730,6 @@ - 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) -@@ -7746,7 +7743,6 @@ - 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 - ); diff --git a/compat/install-9.0.patch b/compat/install-9.0.patch deleted file mode 100644 index 57cc71ad97b9..000000000000 --- a/compat/install-9.0.patch +++ /dev/null @@ -1,176 +0,0 @@ ---- sql/pgtap.sql -+++ sql/pgtap.sql -@@ -3698,7 +3698,7 @@ - AND n.nspname = $1 - AND t.typname = $2 - AND t.typtype = 'e' -- ORDER BY e.enumsortorder -+ ORDER BY e.oid - ), - $3, - $4 -@@ -3726,7 +3726,7 @@ - AND pg_catalog.pg_type_is_visible(t.oid) - AND t.typname = $1 - AND t.typtype = 'e' -- ORDER BY e.enumsortorder -+ ORDER BY e.oid - ), - $2, - $3 -@@ -6170,7 +6170,7 @@ - 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 -+ SELECT DISTINCT (quote_ident(n.nspname) || '.' || quote_ident(p.proname)) AS pname - FROM pg_catalog.pg_proc p - JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid - WHERE n.nspname = $1 -@@ -6187,7 +6187,7 @@ - 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 -+ SELECT DISTINCT (quote_ident(n.nspname) || '.' || quote_ident(p.proname)) 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) -@@ -9689,137 +9689,6 @@ - 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 $$ diff --git a/src/pgtap.c b/src/pgtap.c deleted file mode 100644 index f4d0a0513335..000000000000 --- a/src/pgtap.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * PostgreSQL utility functions for pgTAP. - */ - -#include "postgres.h" -#include "utils/builtins.h" - -#ifdef PG_MODULE_MAGIC -PG_MODULE_MAGIC; -#endif - -extern Datum pg_typeof (PG_FUNCTION_ARGS); -/* Switched to pure SQL. -extern Datum pg_version (PG_FUNCTION_ARGS); -extern Datum pg_version_num (PG_FUNCTION_ARGS); -*/ - -/* - * pg_typeof() - * Returns the OID for the data type of its first argument. The SQL function - * returns regtype, which magically makes it return text. - */ - -PG_FUNCTION_INFO_V1(pg_typeof); - -Datum -pg_typeof(PG_FUNCTION_ARGS) -{ - PG_RETURN_OID( get_fn_expr_argtype(fcinfo->flinfo, 0) ); -} - -/* - * pg_version() - * Returns the version number as output by version(), but without all the - * other crap. Code borrowed from version.c. - */ - -/* Switched to pure SQL. Kept here for posterity. - -PG_FUNCTION_INFO_V1(pg_version); - -Datum -pg_version(PG_FUNCTION_ARGS) -{ - int n = strlen(PG_VERSION); - text *ret = (text *) palloc(n + VARHDRSZ); - -#ifdef SET_VARSIZE - SET_VARSIZE(ret, n + VARHDRSZ); -#else - VARATT_SIZEP(ret) = n + VARHDRSZ; -#endif - memcpy(VARDATA(ret), PG_VERSION, n); - - PG_RETURN_TEXT_P(ret); -} - -*/ - -/* - * pg_version_num() - * Returns the version number as an integer. Support for pre-8.2 borrowed from - * dumputils.c. - */ - -/* Switched to pure SQL. Ketp here for posterity. -PG_FUNCTION_INFO_V1(pg_version_num); - -Datum -pg_version_num(PG_FUNCTION_ARGS) -{ -#ifdef PG_VERSION_NUM - PG_RETURN_INT32(PG_VERSION_NUM); -#else - int cnt; - int vmaj, - vmin, - vrev; - - cnt = sscanf(PG_VERSION, "%d.%d.%d", &vmaj, &vmin, &vrev); - - if (cnt < 2) - return -1; - - if (cnt == 2) - vrev = 0; - - PG_RETURN_INT32( (100 * vmaj + vmin) * 100 + vrev ); -#endif -} - -*/ From d61983db079e67f96c266431a9048495d6c8819c Mon Sep 17 00:00:00 2001 From: nasbyj <45640492+nasbyj@users.noreply.github.com> Date: Sun, 24 Nov 2019 18:22:02 -0600 Subject: [PATCH 1076/1195] Refactor shell scripts (#226) - Move several scripts into `tools/` - Refactor many shell functions into `tools/util.sh` --- .travis.yml | 28 ++-- Makefile | 7 +- test/test_MVU.sh | 74 +-------- getos.sh => tools/getos.sh | 0 tools/missing_extensions.sh | 29 ++++ pg-travis-test.sh => tools/pg-travis-test.sh | 40 ++++- tocgen => tools/tocgen | 0 tools/util.sh | 166 +++++++++++++++++++ 8 files changed, 254 insertions(+), 90 deletions(-) rename getos.sh => tools/getos.sh (100%) create mode 100755 tools/missing_extensions.sh rename pg-travis-test.sh => tools/pg-travis-test.sh (77%) rename tocgen => tools/tocgen (100%) create mode 100644 tools/util.sh diff --git a/.travis.yml b/.travis.yml index 1e164877a696..f8cec8370b6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,29 +3,33 @@ before_install: - wget https://gist.githubusercontent.com/petere/5893799/raw/apt.postgresql.org.sh - sudo sh ./apt.postgresql.org.sh - sudo rm -vf /etc/apt/sources.list.d/pgdg-source.list -script: - - bash ./pg-travis-test.sh env: # WARNING! UPGRADE_TO tests pg_upgrade; UDPATE_FROM tests ALTER EXTENSION! Note UPGRADE vs UPDATE! - - UPGRADE_TO=9.2 PGVERSION=9.1 - - UPGRADE_TO=9.3 PGVERSION=9.2 - - UPGRADE_TO=9.4 PGVERSION=9.3 + - UPGRADE_TO=9.2 PGVERSION=9.1 ALLOW_MISSING_EXTENSIONS=1 + - UPGRADE_TO=9.3 PGVERSION=9.2 ALLOW_MISSING_EXTENSIONS=1 + - UPGRADE_TO=9.4 PGVERSION=9.3 ALLOW_MISSING_EXTENSIONS=1 - UPGRADE_TO=9.5 PGVERSION=9.4 - UPGRADE_TO=9.6 PGVERSION=9.5 - UPGRADE_TO=10 PGVERSION=9.6 - UPGRADE_TO=11 PGVERSION=10 - - UPGRADE_TO=12 PGVERSION=11 UPDATE_FROM=0.99.0 + - UPGRADE_TO=12 PGVERSION=11 UPDATE_FROM=0.99.0 # Versions prior to 0.99.0 don't support Postgres 11 - PGVERSION=12 UPDATE_FROM=0.99.0 - - UPGRADE_TO=12 PGVERSION=9.1 + # Duplication below is via s/-.*PGVERSION/- PARALLEL_CONN=1 PGVERSION/ - - PARALLEL_CONN=1 PGVERSION=9.1 - - PARALLEL_CONN=1 PGVERSION=9.2 - - PARALLEL_CONN=1 PGVERSION=9.3 + - PARALLEL_CONN=1 PGVERSION=9.1 ALLOW_MISSING_EXTENSIONS=1 + - PARALLEL_CONN=1 PGVERSION=9.2 ALLOW_MISSING_EXTENSIONS=1 + - PARALLEL_CONN=1 PGVERSION=9.3 ALLOW_MISSING_EXTENSIONS=1 - PARALLEL_CONN=1 PGVERSION=9.4 - PARALLEL_CONN=1 PGVERSION=9.5 - PARALLEL_CONN=1 PGVERSION=9.6 - PARALLEL_CONN=1 PGVERSION=10 - - PARALLEL_CONN=1 PGVERSION=11 UPDATE_FROM=0.99.0 + - PARALLEL_CONN=1 PGVERSION=11 UPDATE_FROM=0.99.0 # Versions prior to 0.99.0 don't support Postgres 11 - PARALLEL_CONN=1 PGVERSION=12 UPDATE_FROM=0.99.0 -script: bash ./pg-travis-test.sh + + # Also test pg_upgrade across many versions + - UPGRADE_TO=12 PGVERSION=9.1 ALLOW_MISSING_EXTENSIONS=1 + - UPGRADE_TO=12 PGVERSION=9.4 +script: bash tools/pg-travis-test.sh + +# vi: noexpandtab ts=2 sw=2 diff --git a/Makefile b/Makefile index c80ee78a967c..8f01bd1d3730 100644 --- a/Makefile +++ b/Makefile @@ -183,7 +183,7 @@ endif B_DIR ?= .build # Determine the OS. Borrowed from Perl's Configure. -OSNAME := $(shell $(SHELL) ./getos.sh) +OSNAME := $(shell $(SHELL) tools/getos.sh) .PHONY: test @@ -286,7 +286,7 @@ installcheck: test/setup.sql html: multimarkdown doc/pgtap.mmd > doc/pgtap.html - ./tocgen doc/pgtap.html 2> doc/toc.html + tools/tocgen doc/pgtap.html 2> doc/toc.html perl -MPod::Simple::XHTML -E "my \$$p = Pod::Simple::XHTML->new; \$$p->html_header_tags(''); \$$p->strip_verbatim_indent(sub { my \$$l = shift; (my \$$i = \$$l->[0]) =~ s/\\S.*//; \$$i }); \$$p->parse_from_file('`perldoc -l pg_prove`')" > doc/pg_prove.html # @@ -396,7 +396,8 @@ $(TB_DIR)/run.sch: $(TB_DIR)/which_schedule $(GENERATED_SCHEDULES) # Don't generate noise if we're not running tests... .PHONY: extension_check extension_check: - @[ -z "$(MISSING_EXTENSIONS)" ] || (echo; echo '***************************'; echo "WARNING: Some mandatory extensions ($(MISSING_EXTENSIONS)) are not installed; ignoring tests: $(EXTENSION_TEST_FILES)"; echo '***************************'; echo) + @tools/missing_extensions.sh "$(MISSING_EXTENSIONS)" "$(EXTENSION_TEST_FILES)" + # These tests have specific dependencies diff --git a/test/test_MVU.sh b/test/test_MVU.sh index 052852ab47e0..f2301cca9c54 100755 --- a/test/test_MVU.sh +++ b/test/test_MVU.sh @@ -8,70 +8,17 @@ set -E -e -u -o pipefail -rc=0 - -# ########### -# TODO: break these functions into a library shell script so they can be used elsewhere - -err_report() { - echo "errexit on line $(caller)" >&2 -} +BASEDIR=`dirname $0` +if ! . $BASEDIR/../tools/util.sh; then + echo "FATAL: error sourcing $BASEDIR/../tools/util.sh" 1>&2 + exit 99 +fi trap err_report ERR -error() { - echo "$@" >&2 -} - -die() { - local rc # Kinda silly here, but be safe... - rc=$1 - shift - error "$@" - exit $rc -} - -DEBUG=${DEBUG:-0} -debug() { - local level - level=$1 - shift - [ $level -gt $DEBUG ] || error "$@" -} - -debug_do() { - local level - level=$1 - shift - [ $level -gt $DEBUG ] || ( "$@" ) -} +debug 19 "Arguments: $@" -debug_ls() { -# Reverse test since we *exit* if we shouldn't debug! Also, note that unlike -# `exit`, `return` does not default to 0. -[ $1 -le $DEBUG ] || return 0 -( -level=$1 -shift - -# Look through each argument and see if more than one exist. If so, we don't -# need to print what it is we're listing. -location='' -for a in "$@"; do - if [ -e "$a" ]; then - if [ -n "$location" ]; then - location='' - break - else - location=$a - fi - fi -done +rc=0 -error # blank line -[ -z "$location" ] || error "$location" -ls "$@" >&2 -) -} byte_len() ( [ $# -eq 1 ] || die 99 "Expected 1 argument, not $# ($@)" @@ -115,13 +62,6 @@ banner() { echo } -find_at_path() ( -export PATH="$1:$PATH" # Unfortunately need to maintain old PATH to be able to find `which` :( -out=$(which $2) -[ -n "$out" ] || die 2 "unable to find $2" -echo $out -) - run_make() ( cd $(dirname $0)/.. $sudo make $@ diff --git a/getos.sh b/tools/getos.sh similarity index 100% rename from getos.sh rename to tools/getos.sh diff --git a/tools/missing_extensions.sh b/tools/missing_extensions.sh new file mode 100755 index 000000000000..3d6586a4a3ad --- /dev/null +++ b/tools/missing_extensions.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +MISSING_EXTENSIONS=$1 +EXTENSION_TEST_FILES=$2 + +# Doesn't seem worth pulling all of util.sh in for just this, but if you need +# anything else please just pull it in! +stderr() { + echo "$@" >&2 +} + +if [ -n "$MISSING_EXTENSIONS" ]; then + if [ -n "$ALLOW_MISSING_EXTENSIONS" ]; then + stderr + stderr '***************************' + stderr "WARNING: Some mandatory extensions ($MISSING_EXTENSIONS) are not installed, ignoring tests: $EXTENSION_TEST_FILES" + stderr '***************************' + stderr + else + stderr + stderr '***************************' + stderr "ERROR: issing extensions required for testing: $MISSING_EXTENSIONS" + stderr + stderr "You may over-ride by setting \$ALLOW_MISSING_EXTENSIONS to a value." + stderr '***************************' + stderr + exit 1 + fi +fi diff --git a/pg-travis-test.sh b/tools/pg-travis-test.sh similarity index 77% rename from pg-travis-test.sh rename to tools/pg-travis-test.sh index 3f96bda422f3..832344dbbf83 100644 --- a/pg-travis-test.sh +++ b/tools/pg-travis-test.sh @@ -10,15 +10,24 @@ set -E -e -u -o pipefail # # You can set this to higher levels for more debug output -#export DEBUG=1 +#export DEBUG=9 #set -x +BASEDIR=`dirname $0` +if ! . $BASEDIR/util.sh; then + echo "FATAL: error sourcing $BASEDIR/util.sh" 1>&2 + exit 99 +fi +trap err_report ERR + +# For sanity sake, ensure that we run from the top level directory +cd "$BASEDIR"/.. || die 3 "Unable to cd to $BASEDIR/.." + export UPGRADE_TO=${UPGRADE_TO:-} +FAIL_FAST=${FAIL_FAST:-} failed='' tests_run=0 -sudo apt-get update - get_packages() { echo "libtap-parser-sourcehandler-pgtap-perl postgresql-$1 postgresql-server-dev-$1" } @@ -29,7 +38,7 @@ get_path() { # Do NOT use () here; we depend on being able to set failed test_cmd() { -#local status rc +local status rc if [ "$1" == '-s' ]; then status="$2" shift 2 @@ -37,6 +46,12 @@ else status="$1" fi +# NOTE! While this script is under tools/, we expect to be running from the main directory + +# NOTE: simply aliasing a local variable to "$@" does not work as desired, +# probably because by default the variable isn't an array. If it ever becomes +# an issue we can figure out how to do it. + echo echo ############################################################################# echo "PG-TRAVIS: running $@" @@ -48,20 +63,24 @@ rc=0 if [ $rc -ne 0 ]; then echo echo '!!!!!!!!!!!!!!!! FAILURE !!!!!!!!!!!!!!!!' - echo "$@" returned $rc + echo "$@ returned $rc" echo '!!!!!!!!!!!!!!!! FAILURE !!!!!!!!!!!!!!!!' echo failed="$failed '$status'" + [ -z "$FAIL_FAST" ] || die 1 "command failed and \$FAIL_FAST is not empty" fi } # Ensure test_cmd sets failed properly -test_cmd fail > /dev/null 2>&1 +old_FAST_FAIL=$FAIL_FAST +FAIL_FAST='' +test_cmd false > /dev/null # DO NOT redirect stderr, otherwise it's horrible to debug problems here! if [ -z "$failed" ]; then echo "code error: test_cmd() did not set \$failed" exit 91 fi failed='' +FAIL_FAST=$old_FAST_FAIL test_make() { # Many tests depend on install, so just use sudo for all of them @@ -82,10 +101,11 @@ update() { tests_run_by_target_all=11 # 1 + 5 * 2 all() { + local tests_run_start=$tests_run # the test* targets use pg_prove, which assumes it's making a default psql # connection to a database that has pgTap installed, so we need to set that # up. - test_cmd psql -Ec 'CREATE EXTENSION pgtap' + test_cmd -s "all(): psql create extension" psql -Ec 'CREATE EXTENSION pgtap' # TODO: install software necessary to allow testing 'html' target # UPDATE tests_run_by_target_all IF YOU ADD ANY TESTS HERE! @@ -95,6 +115,8 @@ all() { # And then test again test_make $t done + local commands_run=$(($tests_run - $tests_run_start)) + [ $commands_run -eq $tests_run_by_target_all ] || die 92 "all() expected to run $tests_run_by_target_all but actually ran $commands_run tests" } upgrade() { @@ -115,6 +137,8 @@ if [ -n "$UPGRADE_TO" ]; then packages="$packages $(get_packages $UPGRADE_TO)" fi +sudo apt-get update + # bug: http://www.postgresql.org/message-id/20130508192711.GA9243@msgid.df7cb.de sudo update-alternatives --remove-all postmaster.1.gz @@ -148,7 +172,7 @@ for t in ${TARGETS:-sanity update upgrade all}; do done # You can use this to check tests that are failing pg_prove -pg_prove -f --pset tuples_only=1 test/sql/unique.sql test/sql/check.sql || true +#pg_prove -f --pset tuples_only=1 test/sql/unique.sql test/sql/check.sql || true if [ $tests_run -eq $total_tests ]; then echo Ran $tests_run tests diff --git a/tocgen b/tools/tocgen similarity index 100% rename from tocgen rename to tools/tocgen diff --git a/tools/util.sh b/tools/util.sh new file mode 100644 index 000000000000..67dfd2b5b0d3 --- /dev/null +++ b/tools/util.sh @@ -0,0 +1,166 @@ +# Adapted from https://github.com/decibel/db_tools/blob/master/lib/util.sh + +ME=`basename $0` + +DEBUG=${DEBUG:-0} + +stderr() { + echo "$@" 1>&2 +} + +debug() { + local level=$1 + shift + if [ $level -le $DEBUG ] ; then + local oldIFS + oldIFS=$IFS + unset IFS + # Output debug level if it's over threashold + if [ $DEBUG -ge ${DEBUGEXTRA:-10} ]; then + stderr "${level}: $@" + else + stderr "$@" + fi + IFS=$oldIFS + fi +} + +debug_vars () { + level=$1 + shift + local out='' + local value='' + for variable in $*; do + eval value=\$$variable + out="$out $variable='$value'" + done + debug $level $out +} + +debug_do() { + local level + level=$1 + shift + [ $level -gt $DEBUG ] || ( "$@" ) +} + +debug_ls() { + # Reverse test since we *exit* if we shouldn't debug! Also, note that unlike + # `exit`, `return` does not default to 0. + [ $1 -le $DEBUG ] || return 0 + ( + level=$1 + shift + + # Look through each argument and see if more than one exist. If so, we don't + # need to print what it is we're listing. + location='' + for a in "$@"; do + if [ -e "$a" ]; then + if [ -n "$location" ]; then + location='' + break + else + location=$a + fi + fi + done + + stderr # blank line + [ -z "$location" ] || stderr "$location" + ls "$@" >&2 + ) +} + +error() { + local stack lineno + stack='' + lineno='' + while [ "$1" = "-s" -o "$1" = "-n" ]; do + if [ "$1" = "-n" ]; then + lineno=$2 + shift 2 + fi + if [ "$1" = "-s" ]; then + stack=1 + shift + fi + done + + stderr "$@" + + if [ -n "$stack" ]; then + stacktrace 1 # Skip our own frame + else + [ -z "$lineno" ] || echo "File \"$0\", line $lineno" 1>&2 + fi +} + +die() { + local return=$1 + debug_vars 99 return + shift + error "$@" + [ $DEBUG -le 0 ] || stacktrace 1 + if [ -n "${DIE_EXTRA:-}" ]; then + local lineno='' + error + error $DIE_EXTRA + fi + exit $return +} + +file_sanity() { + for file in "$@"; do + [ -e "$file" ] || die 1 "error: file '$file' does not exist" + [ -r "$file" ] || die 1 "error: file '$file' is not readable" + done +} + +db_exists() { + local exists + exists=`psql -qtc "SELECT EXISTS( SELECT 1 FROM pg_database WHERE datname = '$dbname' )" postgres $@ | tr -d ' '` + if [ "$exists" == "t" ]; then + return 0 + else + return 1 + fi +} + +stacktrace () { + debug 200 "stacktrace( $@ )" + local frame=${1:-0} + local line='' + local file='' + debug_vars 200 frame line file + + # NOTE the stderr redirect below! + ( + echo + echo Stacktrace: + while caller $frame; do + frame=$(( $frame + 1 )) + done | while read line function file; do + if [ -z "$function" -o "$function" = main ]; then + echo "$file: line $line" + else + echo "$file: line $line: function $function" + fi + done + ) 1>&2 +} + +# This is intended to be used by a trap, ie: +# trap err_report ERR +err_report() { + stderr "errexit on line $(caller)" >&2 +} + +find_at_path() ( +export PATH="$1:$PATH" # Unfortunately need to maintain old PATH to be able to find `which` :( +out=$(which $2) +[ -n "$out" ] || die 2 "unable to find $2" +echo $out +) + +# vi: noexpandtab ts=2 sw=2 From 18081cac9116e1212998f2c76f4c97bf6b443497 Mon Sep 17 00:00:00 2001 From: nasbyj <45640492+nasbyj@users.noreply.github.com> Date: Sun, 24 Nov 2019 18:51:19 -0600 Subject: [PATCH 1077/1195] Allow finish() to throw an exception (#227) Add a boolean argument to finish() that, when true, will cause it to throw an exception if any errors occurred. Requested by @singpolyma, most of the work done by @rodo. Closes #80, #104. --- Makefile | 17 +++--- doc/pgtap.mmd | 5 ++ sql/pgtap--1.0.0--1.1.0.sql | 56 ++++++++++++++++++++ sql/pgtap.sql.in | 11 ++-- test/expected/moretap.out | 102 +++++++++++++++++++----------------- test/sql/istap.sql | 2 +- test/sql/moretap.sql | 59 +++++++++++++++------ 7 files changed, 177 insertions(+), 75 deletions(-) diff --git a/Makefile b/Makefile index 8f01bd1d3730..26e117156d30 100644 --- a/Makefile +++ b/Makefile @@ -43,13 +43,16 @@ EXTRA_CLEAN += $(SCHEDULE_DEST_FILES) SCHEDULE_FILES = $(wildcard test/schedule/*.sch) # These are our actual regression tests -TEST_FILES = $(filter-out $(SCHEDULE_DEST_FILES),$(wildcard test/sql/*.sql)) +TEST_FILES ?= $(filter-out $(SCHEDULE_DEST_FILES),$(wildcard test/sql/*.sql)) # Plain test names ALL_TESTS = $(notdir $(TEST_FILES:.sql=)) # Some tests fail when run in parallel -SERIAL_TESTS = coltap hastap +SERIAL_TESTS ?= coltap hastap + +# Remove tests from SERIAL_TESTS that do not appear in ALL_TESTS +SERIAL_TESTS := $(foreach test,$(SERIAL_TESTS),$(findstring $(test),$(ALL_TESTS))) # Some tests fail when run by pg_prove # TODO: The first 2 of these fail because they have tests that intentionally @@ -336,7 +339,7 @@ test: test-serial test-parallel # dependencies (such as excluded tests) have changed since the last time we # ran. TB_DIR = test/build -GENERATED_SCHEDULE_DEPS = $(TB_DIR)/tests $(TB_DIR)/exclude_tests +GENERATED_SCHEDULE_DEPS = $(TB_DIR)/all_tests $(TB_DIR)/exclude_tests REGRESS = --schedule $(TB_DIR)/run.sch # Set this again just to be safe REGRESS_OPTS = --inputdir=test --load-language=plpgsql --max-connections=$(PARALLEL_CONN) --schedule $(SETUP_SCH) $(REGRESS_CONF) SETUP_SCH = test/schedule/main.sch # schedule to use for test setup; this can be forcibly changed by some targets! @@ -372,12 +375,12 @@ $(TB_DIR)/which_schedule: $(TB_DIR)/ set_parallel_conn @[ "`cat $@ 2>/dev/null`" = "$(SCHEDULE)" ] || (echo "Schedule changed to $(SCHEDULE)"; echo "$(SCHEDULE)" > $@) # Generated schedule files, one for serial one for parallel -.PHONY: $(TB_DIR)/tests # Need this target to force schedule rebuild if $(TEST) changes -$(TB_DIR)/tests: $(TB_DIR)/ - @[ "`cat $@ 2>/dev/null`" = "$(TEST)" ] || (echo "Rebuilding $@"; echo "$(TEST)" > $@) +.PHONY: $(TB_DIR)/all_tests # Need this target to force schedule rebuild if $(ALL_TESTS) changes +$(TB_DIR)/all_tests: $(TB_DIR)/ + @[ "`cat $@ 2>/dev/null`" = "$(ALL_TESTS)" ] || (echo "Rebuilding $@"; echo "$(ALL_TESTS)" > $@) .PHONY: $(TB_DIR)/exclude_tests # Need this target to force schedule rebuild if $(EXCLUDE_TEST) changes -$(TB_DIR)/exclude_tests: $(TB_DIR)/ +$(TB_DIR)/exclude_tests: $(TB_DIR)/ $(TB_DIR)/all_tests @[ "`cat $@ 2>/dev/null`" = "$(EXCLUDE_TEST)" ] || (echo "Rebuilding $@"; echo "$(EXCLUDE_TEST)" > $@) $(TB_DIR)/serial.sch: $(GENERATED_SCHEDULE_DEPS) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index c6063e60eeb8..7bdbda8baae0 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -300,6 +300,11 @@ discrepancy between the planned number of tests and the number actually run: SELECT * FROM finish(); +If you need to throw an exception if some test failed, you can pass an +option to `finish()`. + + SELECT * FROM finish(true); + What a sweet unit! ------------------ diff --git a/sql/pgtap--1.0.0--1.1.0.sql b/sql/pgtap--1.0.0--1.1.0.sql index 8e211bb6d8e8..0ae910d42b68 100644 --- a/sql/pgtap--1.0.0--1.1.0.sql +++ b/sql/pgtap--1.0.0--1.1.0.sql @@ -128,3 +128,59 @@ RETURNS SETOF NAME[] AS $$ ORDER BY 1 $$ LANGUAGE sql; + +-- finish +DROP FUNCTION finish(); +DROP FUNCTION _finish (INTEGER, INTEGER, INTEGER); + +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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 165f4dda9c96..9b191873fce5 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -180,13 +180,14 @@ RETURNS INTEGER AS $$ SELECT _get('failed'); $$ LANGUAGE SQL strict; -CREATE OR REPLACE FUNCTION _finish (INTEGER, INTEGER, INTEGER) +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; @@ -206,6 +207,9 @@ BEGIN 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 @@ -218,12 +222,13 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION finish () +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() + num_failed(), + $1 ); $$ LANGUAGE sql; diff --git a/test/expected/moretap.out b/test/expected/moretap.out index 3f07bf7beeb9..1e5ca0e90a84 100644 --- a/test/expected/moretap.out +++ b/test/expected/moretap.out @@ -1,48 +1,56 @@ \unset ECHO -1..46 -ok 1 - My pass() passed, w00t! -ok 2 - Testing fail() -ok 3 - We should get the proper output from fail() -ok 4 - The output of finish() should reflect the test failure -ok 5 - We should have one failure -ok 6 - We should now have no failures -ok 7 - diag() should work properly -ok 8 - multiline diag() should work properly -ok 9 - multiline diag() should work properly with existing comments -ok 10 - diag(int) -ok 11 - diag(numeric) -ok 12 - diag(timestamptz) -ok 13 - variadic text -ok 14 - variadic int -ok 15 - variadic unknown -ok 16 - no_plan() should have stored a plan of 0 -ok 17 - Set the plan to 4000 -ok 18 - The output of finish() should reflect a high test plan -ok 19 - Set the plan to 4 -ok 20 - The output of finish() should reflect a low test plan -ok 21 - Reset the plan -ok 22 - plan() should have stored the test count -ok 23 - ok(true) should pass -ok 24 - ok(true) should have the proper description -ok 25 - ok(true) should have the proper diagnostics -ok 26 - ok(true, '') should pass -ok 27 - ok(true, '') should have the proper description -ok 28 - ok(true, '') should have the proper diagnostics -ok 29 - ok(true, 'foo') should pass -ok 30 - ok(true, 'foo') should have the proper description -ok 31 - ok(true, 'foo') should have the proper diagnostics -ok 32 - ok(false) should fail -ok 33 - ok(false) should have the proper description -ok 34 - ok(false) should have the proper diagnostics -ok 35 - ok(false, '') should fail -ok 36 - ok(false, '') should have the proper description -ok 37 - ok(false, '') should have the proper diagnostics -ok 38 - ok(false, 'foo') should fail -ok 39 - ok(false, 'foo') should have the proper description -ok 40 - ok(false, 'foo') should have the proper diagnostics -ok 41 - ok(NULL, 'null') should fail -ok 42 - ok(NULL, 'null') should have the proper description -ok 43 - ok(NULL, 'null') should have the proper diagnostics -ok 44 - multiline desc should pass -ok 45 - multiline desc should have the proper description -ok 46 - multiline desc should have the proper diagnostics +1..54 +ok 1 - Modify internal plan value +ok 2 - My pass() passed, w00t! +ok 3 - Testing fail() +ok 4 - We should get the proper output from fail() +ok 5 - The output of finish() should reflect the test failure +ok 6 - Increase internal plan value after testing finish +ok 7 - The output of finish(false) should reflect the test failure +ok 8 - Increase internal plan value after testing finish +ok 9 - The output of finish(NULL) should reflect the test failure +ok 10 - Increase internal plan value after testing finish +ok 11 - finish(true) should throw an exception +ok 12 - We should have one failure +ok 13 - Reset internal failure count +ok 14 - We should now have no failures +ok 15 - diag() should work properly +ok 16 - multiline diag() should work properly +ok 17 - multiline diag() should work properly with existing comments +ok 18 - diag(int) +ok 19 - diag(numeric) +ok 20 - diag(timestamptz) +ok 21 - variadic text +ok 22 - variadic int +ok 23 - variadic unknown +ok 24 - no_plan() should have stored a plan of 0 +ok 25 - Set the plan to 4000 +ok 26 - The output of finish() should reflect a high test plan +ok 27 - Set the plan to 4 +ok 28 - The output of finish() should reflect a low test plan +ok 29 - Reset the plan +ok 30 - plan() should have stored the test count +ok 31 - ok(true) should pass +ok 32 - ok(true) should have the proper description +ok 33 - ok(true) should have the proper diagnostics +ok 34 - ok(true, '') should pass +ok 35 - ok(true, '') should have the proper description +ok 36 - ok(true, '') should have the proper diagnostics +ok 37 - ok(true, 'foo') should pass +ok 38 - ok(true, 'foo') should have the proper description +ok 39 - ok(true, 'foo') should have the proper diagnostics +ok 40 - ok(false) should fail +ok 41 - ok(false) should have the proper description +ok 42 - ok(false) should have the proper diagnostics +ok 43 - ok(false, '') should fail +ok 44 - ok(false, '') should have the proper description +ok 45 - ok(false, '') should have the proper diagnostics +ok 46 - ok(false, 'foo') should fail +ok 47 - ok(false, 'foo') should have the proper description +ok 48 - ok(false, 'foo') should have the proper diagnostics +ok 49 - ok(NULL, 'null') should fail +ok 50 - ok(NULL, 'null') should have the proper description +ok 51 - ok(NULL, 'null') should have the proper diagnostics +ok 52 - multiline desc should pass +ok 53 - multiline desc should have the proper description +ok 54 - multiline desc should have the proper diagnostics diff --git a/test/sql/istap.sql b/test/sql/istap.sql index b7208c6ac016..188a01fe3f83 100644 --- a/test/sql/istap.sql +++ b/test/sql/istap.sql @@ -147,5 +147,5 @@ SELECT * FROM test_records(); /****************************************************************************/ -- Finish the tests and clean up. -SELECT * FROM finish(); +SELECT * FROM finish(false); -- Arbitrarily decided to test `finish(false)` here... :) ROLLBACK; diff --git a/test/sql/moretap.sql b/test/sql/moretap.sql index 81607e4adf6d..0c0965e9bac3 100644 --- a/test/sql/moretap.sql +++ b/test/sql/moretap.sql @@ -1,35 +1,64 @@ \unset ECHO \i test/setup.sql -\set numb_tests 46 +\set numb_tests 54 SELECT plan(:numb_tests); -- Replace the internal record of the plan for a few tests. -UPDATE __tcache__ SET value = 3 WHERE label = 'plan'; +SELECT is( _set('plan', 4), 4, 'Modify internal plan value'); /****************************************************************************/ -- Test pass(). SELECT pass( 'My pass() passed, w00t!' ); -- Test fail(). -\set fail_numb 2 +\set fail_numb 3 \echo ok :fail_numb - Testing fail() SELECT is( - fail('oops'), - 'not ok 2 - oops -# Failed test 2: "oops"', 'We should get the proper output from fail()'); + fail('oops'), + format( E'not ok %1$s - oops\n# Failed test %1$s: "oops"', :fail_numb ), + 'We should get the proper output from fail()' +); + +/* + * NOTE: From this point until we call _set('failed') below we should always + * have *one* test failure, *BUT* if the tests themselves start failing then + * you'll have extra failures which will throw off all the successive counts! + */ --- Check the finish() output. +-- Check the finish() output with no value. SELECT is( (SELECT * FROM finish() LIMIT 1), - '# Looks like you failed 1 test of 3', + '# Looks like you failed 1 test of 4', 'The output of finish() should reflect the test failure' ); +-- Make sure that false and NULL work as well +SELECT is( _set('plan', 6), 6, 'Increase internal plan value after testing finish' ); +SELECT is( + (SELECT * FROM finish(false) LIMIT 1), + '# Looks like you failed 1 test of 6', + 'The output of finish(false) should reflect the test failure' +); +SELECT is( _set('plan', 8), 8, 'Increase internal plan value after testing finish' ); +SELECT is( + (SELECT * FROM finish(NULL) LIMIT 1), + '# Looks like you failed 1 test of 8', + 'The output of finish(NULL) should reflect the test failure' +); + +-- Verify that finish(true) works +SELECT is( _set('plan', 10), 10, 'Increase internal plan value after testing finish' ); +SELECT throws_ok( + $$SELECT finish(true)$$, + '1 test failed of 10', + 'finish(true) should throw an exception' +); + /****************************************************************************/ -- Check num_failed SELECT is( num_failed(), 1, 'We should have one failure' ); -UPDATE __tcache__ SET value = 0 WHERE label = 'failed'; +SELECT is( _set('failed', 0), 0, 'Reset internal failure count' ); SELECT is( num_failed(), 0, 'We should now have no failures' ); /****************************************************************************/ @@ -69,16 +98,14 @@ SELECT * FROM test_variadic(); -- Check no_plan. DELETE FROM __tcache__ WHERE label = 'plan'; SELECT * FROM no_plan(); -SELECT is( value, 0, 'no_plan() should have stored a plan of 0' ) - FROM __tcache__ - WHERE label = 'plan'; +SELECT is( _get('plan'), 0, 'no_plan() should have stored a plan of 0' ); -- Set the plan to a high number. DELETE FROM __tcache__ WHERE label = 'plan'; SELECT is( plan(4000), '1..4000', 'Set the plan to 4000' ); SELECT is( (SELECT * FROM finish() LIMIT 1), - '# Looks like you planned 4000 tests but ran 17', + '# Looks like you planned 4000 tests but ran 25', 'The output of finish() should reflect a high test plan' ); @@ -87,16 +114,14 @@ DELETE FROM __tcache__ WHERE label = 'plan'; SELECT is( plan(4), '1..4', 'Set the plan to 4' ); SELECT is( (SELECT * FROM finish() LIMIT 1), - '# Looks like you planned 4 tests but ran 19', + '# Looks like you planned 4 tests but ran 27', 'The output of finish() should reflect a low test plan' ); -- Reset the original plan. DELETE FROM __tcache__ WHERE label = 'plan'; SELECT is( plan(:numb_tests), '1..' || :numb_tests, 'Reset the plan' ); -SELECT is( value, :numb_tests, 'plan() should have stored the test count' ) - FROM __tcache__ - WHERE label = 'plan'; +SELECT is( _get('plan'), :numb_tests, 'plan() should have stored the test count' ); /****************************************************************************/ -- Test ok() From b5844d2591d3aeb56f3da6fa57b9975f1c16e7e8 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Mon, 25 Nov 2019 12:26:33 -0600 Subject: [PATCH 1078/1195] Recommend Postgres 9.4 in META.json --- META.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/META.json b/META.json index fe5ae9f1110a..c40a7f99b8f4 100644 --- a/META.json +++ b/META.json @@ -15,6 +15,9 @@ "requires": { "plpgsql": 0, "PostgreSQL": "9.1.0" + }, + "recommends": { + "PostgreSQL": "9.4.0" } } }, From a83e2cc21035abca37103adc7e48b651393752e8 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Mon, 25 Nov 2019 13:03:44 -0600 Subject: [PATCH 1079/1195] Timestamp v1.1.0 --- Changes | 22 +++++++++++++++---- contrib/pgtap.spec | 4 ++++ doc/pgtap.mmd | 55 +++++----------------------------------------- release.md | 25 +++++++++++---------- 4 files changed, 41 insertions(+), 65 deletions(-) diff --git a/Changes b/Changes index d9e286c87830..fce41be95f04 100644 --- a/Changes +++ b/Changes @@ -1,12 +1,26 @@ Revision history for pgTAP ========================== -1.1.0 +1.1.0 2019-11-25T19:05:38Z -------------------------- +* Remove support for PostgreSQL older than 9.1. Note that some automated tests + don't run on versions older than 9.4, so it's recommended you run PostgreSQL + 9.4 at minimum. +* Add a `throw_excepton` argument to `finish()`. `finish(true)` will raise an + exception if any tests failed. Thanks to Stephen Paul Weber for the idea + (#80), and Rodolphe Quiédeville for most of the work (#104)! +* Make description optional for col_not_null() (#221). +* Fix syntax of test_user function example in the docs (#208). Thanks to + PeteDevoy for the patch! +* Fix issue with pgtap-core referencing pg_proc.proisagg on PostgreSQL 11+ (#197) +* Add PostgreSQL 12.0 to automated tests (#223). * Fix pgtap_version(), which incorrectly returned 0.99 when upgrading from - version 0.99.0 to 1.0.0. -* Fix issue with using pg_upgrade to Postgres 11+ with pgTap installed. -* Make description optional for col_not_null(). + version 0.99.0 to 1.0.0 (#214). +* Fix issue with using pg_upgrade to PostgreSQL 11+ with pgTap installed (#201, + #215). +* Start using `DEFAULT` in functions. Previously +* Add automated testing of extension upgrade scripts (#128), as well as testing + of pg_upgrade with pgTap installed (#218, #222). 1.0.0 2019-02-21T22:39:42Z -------------------------- diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 18f5b9604474..1fb08be0f25e 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -48,6 +48,10 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog +* Mon Nov 25 2019 Jim Nasby 1.1.0 +- Update to 1.1.0 +- Remove support for PostgreSQL prior to 9.1 + * Thu Feb 21 2019 Jim Nasby 1.0.0 - Update to 1.0.0 diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 7bdbda8baae0..3beaea504eab 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -8273,14 +8273,9 @@ Compatibility ============= Here are some notes on how pgTAP is built for particular versions of -PostgreSQL. This helps you to understand any side-effects. If you'd rather not -have these changes in your schema, build `pgTAP` with a schema just for it, -instead: - - make TAPSCHEMA=tap - -To see the specifics for each version of PostgreSQL, consult the files in the -`compat/` directory in the pgTAP distribution. +PostgreSQL. This helps you to understand any side-effects. To see the specifics +for each version of PostgreSQL, consult the files in the `compat/` directory in +the pgTAP distribution. 10 and Up --------- @@ -8299,59 +8294,21 @@ No changes. Everything should just work. 9.2 and Down ------------ +* Lacks full automated testing. Recommend using 9.4 or higher. * Diagnostic output from `lives_ok()` and xUnit function exceptions will not include schema, table, column, data type, or constraint information, since such diagnostics were not introduced until 9.3. 9.1 and Down ------------ +* Lacks full automated testing. Recommend using 9.4 or higher. * Diagnostic output from `lives_ok()` and xUnit function exceptions will not error context or details, since such diagnostics were not introduced until 9.2. 9.0 and Down ------------ -* The `foreign_table_owner_is()` function will not work, because, of course, - there were no foreign tables until 9.1. -* The `extensions_are()` functions are not available, because extensions were - not introduced until 9.1. - -8.4 and Down ------------- -* The `sequence_privs_are()` function does not work, because privileges could - not be granted on sequences before 9.0. - -* The `triggers_are()` function does not ignore internally-created triggers. - -8.3 and Down ------------- -* A patch is applied to modify `results_eq()` and `row_eq()` to cast records - to text before comparing them. This means that things will mainly be - correct, but it also means that two queries with incompatible types that - convert to the same text string may be considered incorrectly equivalent. - -* A C function, `pg_typeof()`, is built and installed in a DSO. This is for - compatibility with the same function that ships in 8.4 core, and is required - for `cmp_ok()` and `isa_ok()` to work. - -* The variadic forms of `diag()` and `collect_tap()` are not available. - You can pass an array of TAP to `collect_tap()`, however. - -* These permission-testing functions don't work, because one cannot grant - permissions on the relevant objects until 8.4: - - + `has_any_column_privilege()` - + `has_column_privilege()` - + `has_foreign_data_wrapper_privilege()` - + `has_server_privilege()` - -* These inheritance-testing functions are not available, because internally - the use a recursive common table expression query not supported before 8.4: - - + `is_ancestor_of()` - + `isnt_ancestor_of()` - + `is_descendent_of()` - + `isnt_descendent_of()` +No longer supported. Metadata ======== diff --git a/release.md b/release.md index 131e25697677..0cdb7bb74de2 100644 --- a/release.md +++ b/release.md @@ -36,22 +36,23 @@ Here are the steps to take to make a release of pgTAP: of the document. * Add an item to the top of the `%changelog` section of `contrib/pgtap.spec`. - It should use the version you're about to release, as well as the date and - your name and email address. Add at least one bullet mentioning the - upgrade. + It should use the version you're about to release, as well as the date (use + `date +'%a %b %d %Y'`) and your name and email address. Add at least one + bullet mentioning the upgrade. * Run `make html` (you'll need - [MultiMarkdown](http://fletcherpenney.net/multimarkdown/) in your path and - the [Pod::Simple::XHTML](https://metacpan.org/module/Pod::Simple::XHTML) - Perl module installed), then checkout the `gh-pages` branch and make these - changes: + [MultiMarkdown](http://fletcherpenney.net/multimarkdown/) (Macports: port + install multimarkdown) in your path and the + [Pod::Simple::XHTML](https://metacpan.org/module/Pod::Simple::XHTML) + (Macports: port install p5-pod-simple) Perl module installed), then + checkout the `gh-pages` branch and make these changes: - + Open `documentation.html` and delete all the lines between these "DOC" - comments, until the main div looks like this: + + `cp .documentation.html.template documentation.html`. Edit + documentation.html, the main div should look like this:
    - - + +
    @@ -61,7 +62,7 @@ Here are the steps to take to make a release of pgTAP:
    + Copy the first `

    ` and `

    ` from `doc/pgtap.html` into the - `DOC INTRO HERE` section. + `DOCS INTRO HERE` section. + Copy the rest of `doc/pgtap.html` into the `DOCS HERE, WITH INTRO MOVED ABOVE` section. From 69401b54a73036ffaa24c3ceea9548dca4603e30 Mon Sep 17 00:00:00 2001 From: James Coleman Date: Wed, 8 Jan 2020 15:06:45 -0500 Subject: [PATCH 1080/1195] Fix docs typo (#231) --- doc/pgtap.mmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 3beaea504eab..38a2724f9716 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1644,7 +1644,7 @@ bare `RECORD` value may be passed: SELECT row_eq( $$ SELECT 1, 'foo' $$, ROW(1, 'foo') ); -ue to the limitations of non-C functions in earlier versions of PostgreSQL, a +Due to the limitations of non-C functions in earlier versions of PostgreSQL, a bare `RECORD` value cannot be passed to the function. You must instead pass in a valid composite type value, and cast the record argument (the second argument) to the same type. Both explicitly created composite types and table types are From e0db8e30deff39cf5f0deaefa1a814b3297dc3d4 Mon Sep 17 00:00:00 2001 From: Christoph Berg Date: Wed, 8 Jan 2020 21:08:23 +0100 Subject: [PATCH 1081/1195] Fix comment typo (#229) --- tools/missing_extensions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/missing_extensions.sh b/tools/missing_extensions.sh index 3d6586a4a3ad..9be5be65ad76 100755 --- a/tools/missing_extensions.sh +++ b/tools/missing_extensions.sh @@ -19,7 +19,7 @@ if [ -n "$MISSING_EXTENSIONS" ]; then else stderr stderr '***************************' - stderr "ERROR: issing extensions required for testing: $MISSING_EXTENSIONS" + stderr "ERROR: Missing extensions required for testing: $MISSING_EXTENSIONS" stderr stderr "You may over-ride by setting \$ALLOW_MISSING_EXTENSIONS to a value." stderr '***************************' From 0ec98407651d47682f5462296d806efdc297bd83 Mon Sep 17 00:00:00 2001 From: "Jim C. Nasby" Date: Thu, 16 Jan 2020 15:58:10 -0600 Subject: [PATCH 1082/1195] Partially bump to 1.2 --- pgtap.control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgtap.control b/pgtap.control index 497045846894..19213f0c9d69 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '1.1.0' +default_version = '1.2.0' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true From 8c8b4268c730253d1eabc645cf2682b95c19fb26 Mon Sep 17 00:00:00 2001 From: "Jim C. Nasby" Date: Thu, 16 Jan 2020 16:29:00 -0600 Subject: [PATCH 1083/1195] Add missing update script --- sql/pgtap--1.1.0--1.2.0.sql | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 sql/pgtap--1.1.0--1.2.0.sql diff --git a/sql/pgtap--1.1.0--1.2.0.sql b/sql/pgtap--1.1.0--1.2.0.sql new file mode 100644 index 000000000000..5d2ded6a23dd --- /dev/null +++ b/sql/pgtap--1.1.0--1.2.0.sql @@ -0,0 +1,3 @@ +CREATE OR REPLACE FUNCTION pgtap_version() +RETURNS NUMERIC AS 'SELECT 1.2;' +LANGUAGE SQL IMMUTABLE; From cd178fe78ef63fc10400b5d27a9c69ab8f8e21ab Mon Sep 17 00:00:00 2001 From: Godwottery Date: Fri, 17 Jan 2020 20:11:28 +0100 Subject: [PATCH 1084/1195] Add has_view(:schema, :name) and hasnt_view(:schema, :name) (#230) Adds functions that make the description field optional for has_view() and hasnt_view(). --- doc/pgtap.mmd | 2 + sql/pgtap--1.1.0--1.2.0.sql | 16 + sql/pgtap.sql.in | 17 + test/expected/hastap.out | 1516 ++++++++++++++++++----------------- test/sql/hastap.sql | 36 +- 5 files changed, 833 insertions(+), 754 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 38a2724f9716..8a043b0ffe64 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2839,6 +2839,7 @@ specified table does *not* exist. ### `has_view()` ### SELECT has_view( :schema, :view, :description ); + SELECT has_view( :schema, :view ); SELECT has_view( :view, :description ); SELECT has_view( :view ); @@ -2866,6 +2867,7 @@ exist". ### `hasnt_view()` ### SELECT hasnt_view( :schema, :view, :description ); + SELECT hasnt_view( :schema, :view ); SELECT hasnt_view( :view, :description ); SELECT hasnt_view( :view ); diff --git a/sql/pgtap--1.1.0--1.2.0.sql b/sql/pgtap--1.1.0--1.2.0.sql index 5d2ded6a23dd..32cdd15e470f 100644 --- a/sql/pgtap--1.1.0--1.2.0.sql +++ b/sql/pgtap--1.1.0--1.2.0.sql @@ -1,3 +1,19 @@ CREATE OR REPLACE FUNCTION pgtap_version() RETURNS NUMERIC AS 'SELECT 1.2;' LANGUAGE SQL IMMUTABLE; + +-- 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; + +-- 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 9b191873fce5..9745d7ce798d 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1070,6 +1070,14 @@ 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 $$ @@ -1088,6 +1096,15 @@ 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 $$ diff --git a/test/expected/hastap.out b/test/expected/hastap.out index 0008ce5715dd..7eabfa7821e3 100644 --- a/test/expected/hastap.out +++ b/test/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..884 +1..896 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -117,770 +117,782 @@ ok 114 - has_view(view, desc) should have the proper diagnostics ok 115 - has_view(sch, view, desc) should pass ok 116 - has_view(sch, view, desc) should have the proper description ok 117 - has_view(sch, view, desc) should have the proper diagnostics -ok 118 - hasnt_view(non-existent view) should pass -ok 119 - hasnt_view(non-existent view) should have the proper description -ok 120 - hasnt_view(non-existent view) should have the proper diagnostics -ok 121 - hasnt_view(non-existent view, desc) should pass -ok 122 - hasnt_view(non-existent view, desc) should have the proper description -ok 123 - hasnt_view(non-existent view, desc) should have the proper diagnostics -ok 124 - hasnt_view(sch, non-existent view, desc) should pass -ok 125 - hasnt_view(sch, non-existent view, desc) should have the proper description -ok 126 - hasnt_view(sch, non-existent view, desc) should have the proper diagnostics -ok 127 - hasnt_view(view, desc) should fail -ok 128 - hasnt_view(view, desc) should have the proper description -ok 129 - hasnt_view(view, desc) should have the proper diagnostics -ok 130 - hasnt_view(sch, view, desc) should fail -ok 131 - hasnt_view(sch, view, desc) should have the proper description -ok 132 - hasnt_view(sch, view, desc) should have the proper diagnostics -ok 133 - has_sequence(non-existent sequence) should fail -ok 134 - has_sequence(non-existent sequence) should have the proper description -ok 135 - has_sequence(non-existent sequence) should have the proper diagnostics -ok 136 - has_sequence(non-existent sequence, desc) should fail -ok 137 - has_sequence(non-existent sequence, desc) should have the proper description -ok 138 - has_sequence(non-existent sequence, desc) should have the proper diagnostics -ok 139 - has_sequence(sch, non-existent sequence, desc) should fail -ok 140 - has_sequence(sch, non-existent sequence, desc) should have the proper description -ok 141 - has_sequence(sch, non-existent sequence, desc) should have the proper diagnostics -ok 142 - has_sequence(sequence, desc) should pass -ok 143 - has_sequence(sequence, desc) should have the proper description -ok 144 - has_sequence(sequence, desc) should have the proper diagnostics -ok 145 - has_sequence(sch, sequence, desc) should pass -ok 146 - has_sequence(sch, sequence, desc) should have the proper description -ok 147 - has_sequence(sch, sequence, desc) should have the proper diagnostics -ok 148 - has_sequence(sch, sequence) should pass -ok 149 - has_sequence(sch, sequence) should have the proper description -ok 150 - hasnt_sequence(non-existent sequence) should pass -ok 151 - hasnt_sequence(non-existent sequence) should have the proper description -ok 152 - hasnt_sequence(non-existent sequence) should have the proper diagnostics -ok 153 - hasnt_sequence(non-existent sequence, desc) should pass -ok 154 - hasnt_sequence(non-existent sequence, desc) should have the proper description -ok 155 - hasnt_sequence(non-existent sequence, desc) should have the proper diagnostics -ok 156 - hasnt_sequence(sch, non-existent sequence, desc) should pass -ok 157 - hasnt_sequence(sch, non-existent sequence, desc) should have the proper description -ok 158 - hasnt_sequence(sch, non-existent sequence, desc) should have the proper diagnostics -ok 159 - hasnt_sequence(sequence, desc) should fail -ok 160 - hasnt_sequence(sequence, desc) should have the proper description -ok 161 - hasnt_sequence(sequence, desc) should have the proper diagnostics -ok 162 - hasnt_sequence(sch, sequence, desc) should fail -ok 163 - hasnt_sequence(sch, sequence, desc) should have the proper description -ok 164 - hasnt_sequence(sch, sequence, desc) should have the proper diagnostics -ok 165 - has_composite(non-existent composite type) should fail -ok 166 - has_composite(non-existent composite type) should have the proper description -ok 167 - has_composite(non-existent composite type) should have the proper diagnostics -ok 168 - has_composite(non-existent schema, tab) should fail -ok 169 - has_composite(non-existent schema, tab) should have the proper description -ok 170 - has_composite(non-existent schema, tab) should have the proper diagnostics -ok 171 - has_composite(sch, non-existent composite type, desc) should fail -ok 172 - has_composite(sch, non-existent composite type, desc) should have the proper description -ok 173 - has_composite(sch, non-existent composite type, desc) should have the proper diagnostics -ok 174 - has_composite(tab, desc) should pass -ok 175 - has_composite(tab, desc) should have the proper description -ok 176 - has_composite(tab, desc) should have the proper diagnostics -ok 177 - has_composite(sch, tab, desc) should pass -ok 178 - has_composite(sch, tab, desc) should have the proper description -ok 179 - has_composite(sch, tab, desc) should have the proper diagnostics -ok 180 - has_composite(sch, view, desc) should fail -ok 181 - has_composite(sch, view, desc) should have the proper description -ok 182 - has_composite(sch, view, desc) should have the proper diagnostics -ok 183 - has_composite(type, desc) should fail -ok 184 - has_composite(type, desc) should have the proper description -ok 185 - has_composite(type, desc) should have the proper diagnostics -ok 186 - hasnt_composite(non-existent composite type) should pass -ok 187 - hasnt_composite(non-existent composite type) should have the proper description -ok 188 - hasnt_composite(non-existent composite type) should have the proper diagnostics -ok 189 - hasnt_composite(non-existent schema, tab) should pass -ok 190 - hasnt_composite(non-existent schema, tab) should have the proper description -ok 191 - hasnt_composite(non-existent schema, tab) should have the proper diagnostics -ok 192 - hasnt_composite(sch, non-existent tab, desc) should pass -ok 193 - hasnt_composite(sch, non-existent tab, desc) should have the proper description -ok 194 - hasnt_composite(sch, non-existent tab, desc) should have the proper diagnostics -ok 195 - hasnt_composite(tab, desc) should fail -ok 196 - hasnt_composite(tab, desc) should have the proper description -ok 197 - hasnt_composite(tab, desc) should have the proper diagnostics -ok 198 - hasnt_composite(sch, tab, desc) should fail -ok 199 - hasnt_composite(sch, tab, desc) should have the proper description -ok 200 - hasnt_composite(sch, tab, desc) should have the proper diagnostics -ok 201 - has_type(type) should pass -ok 202 - has_type(type) should have the proper description -ok 203 - has_type(type) should have the proper diagnostics -ok 204 - has_type(type, desc) should pass -ok 205 - has_type(type, desc) should have the proper description -ok 206 - has_type(type, desc) should have the proper diagnostics -ok 207 - has_type(scheam, type) should pass -ok 208 - has_type(scheam, type) should have the proper description -ok 209 - has_type(scheam, type) should have the proper diagnostics -ok 210 - has_type(schema, type, desc) should pass -ok 211 - has_type(schema, type, desc) should have the proper description -ok 212 - has_type(schema, type, desc) should have the proper diagnostics -ok 213 - has_type(myType) should pass -ok 214 - has_type(myType) should have the proper description -ok 215 - has_type(myType) should have the proper diagnostics -ok 216 - has_type(myType, desc) should pass -ok 217 - has_type(myType, desc) should have the proper description -ok 218 - has_type(myType, desc) should have the proper diagnostics -ok 219 - has_type(scheam, myType) should pass -ok 220 - has_type(scheam, myType) should have the proper description -ok 221 - has_type(scheam, myType) should have the proper diagnostics -ok 222 - has_type(schema, myType, desc) should pass -ok 223 - has_type(schema, myType, desc) should have the proper description -ok 224 - has_type(schema, myType, desc) should have the proper diagnostics -ok 225 - has_type(type) should fail -ok 226 - has_type(type) should have the proper description -ok 227 - has_type(type) should have the proper diagnostics -ok 228 - has_type(type, desc) should fail -ok 229 - has_type(type, desc) should have the proper description -ok 230 - has_type(type, desc) should have the proper diagnostics -ok 231 - has_type(scheam, type) should fail -ok 232 - has_type(scheam, type) should have the proper description -ok 233 - has_type(scheam, type) should have the proper diagnostics -ok 234 - has_type(schema, type, desc) should fail -ok 235 - has_type(schema, type, desc) should have the proper description -ok 236 - has_type(schema, type, desc) should have the proper diagnostics -ok 237 - has_type(domain) should pass -ok 238 - has_type(domain) should have the proper description -ok 239 - has_type(domain) should have the proper diagnostics -ok 240 - has_type(myDomain) should pass -ok 241 - has_type(myDomain) should have the proper description -ok 242 - has_type(myDomain) should have the proper diagnostics -ok 243 - hasnt_type(type) should pass -ok 244 - hasnt_type(type) should have the proper description -ok 245 - hasnt_type(type) should have the proper diagnostics -ok 246 - hasnt_type(type, desc) should pass -ok 247 - hasnt_type(type, desc) should have the proper description -ok 248 - hasnt_type(type, desc) should have the proper diagnostics -ok 249 - hasnt_type(scheam, type) should pass -ok 250 - hasnt_type(scheam, type) should have the proper description -ok 251 - hasnt_type(scheam, type) should have the proper diagnostics -ok 252 - hasnt_type(schema, type, desc) should pass -ok 253 - hasnt_type(schema, type, desc) should have the proper description -ok 254 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 255 - hasnt_type(type) should fail +ok 118 - has_view(sch, view) should pass +ok 119 - has_view(sch, view) should have the proper description +ok 120 - has_view(sch, view) should have the proper diagnostics +ok 121 - has_view(sch, non-existent view, desc) should fail +ok 122 - has_view(sch, non-existent view, desc) should have the proper description +ok 123 - has_view(sch, non-existent view, desc) should have the proper diagnostics +ok 124 - hasnt_view(non-existent view) should pass +ok 125 - hasnt_view(non-existent view) should have the proper description +ok 126 - hasnt_view(non-existent view) should have the proper diagnostics +ok 127 - hasnt_view(non-existent view, desc) should pass +ok 128 - hasnt_view(non-existent view, desc) should have the proper description +ok 129 - hasnt_view(non-existent view, desc) should have the proper diagnostics +ok 130 - hasnt_view(sch, non-existent view, desc) should pass +ok 131 - hasnt_view(sch, non-existent view, desc) should have the proper description +ok 132 - hasnt_view(sch, non-existent view, desc) should have the proper diagnostics +ok 133 - hasnt_view(view, desc) should fail +ok 134 - hasnt_view(view, desc) should have the proper description +ok 135 - hasnt_view(view, desc) should have the proper diagnostics +ok 136 - hasnt_view(sch, view, desc) should fail +ok 137 - hasnt_view(sch, view, desc) should have the proper description +ok 138 - hasnt_view(sch, view, desc) should have the proper diagnostics +ok 139 - hasnt_view(sch, view) should fail +ok 140 - hasnt_view(sch, view) should have the proper description +ok 141 - hasnt_view(sch, view) should have the proper diagnostics +ok 142 - hasnt_view(sch, non-existent view) should pass +ok 143 - hasnt_view(sch, non-existent view) should have the proper description +ok 144 - hasnt_view(sch, non-existent view) should have the proper diagnostics +ok 145 - has_sequence(non-existent sequence) should fail +ok 146 - has_sequence(non-existent sequence) should have the proper description +ok 147 - has_sequence(non-existent sequence) should have the proper diagnostics +ok 148 - has_sequence(non-existent sequence, desc) should fail +ok 149 - has_sequence(non-existent sequence, desc) should have the proper description +ok 150 - has_sequence(non-existent sequence, desc) should have the proper diagnostics +ok 151 - has_sequence(sch, non-existent sequence, desc) should fail +ok 152 - has_sequence(sch, non-existent sequence, desc) should have the proper description +ok 153 - has_sequence(sch, non-existent sequence, desc) should have the proper diagnostics +ok 154 - has_sequence(sequence, desc) should pass +ok 155 - has_sequence(sequence, desc) should have the proper description +ok 156 - has_sequence(sequence, desc) should have the proper diagnostics +ok 157 - has_sequence(sch, sequence, desc) should pass +ok 158 - has_sequence(sch, sequence, desc) should have the proper description +ok 159 - has_sequence(sch, sequence, desc) should have the proper diagnostics +ok 160 - has_sequence(sch, sequence) should pass +ok 161 - has_sequence(sch, sequence) should have the proper description +ok 162 - hasnt_sequence(non-existent sequence) should pass +ok 163 - hasnt_sequence(non-existent sequence) should have the proper description +ok 164 - hasnt_sequence(non-existent sequence) should have the proper diagnostics +ok 165 - hasnt_sequence(non-existent sequence, desc) should pass +ok 166 - hasnt_sequence(non-existent sequence, desc) should have the proper description +ok 167 - hasnt_sequence(non-existent sequence, desc) should have the proper diagnostics +ok 168 - hasnt_sequence(sch, non-existent sequence, desc) should pass +ok 169 - hasnt_sequence(sch, non-existent sequence, desc) should have the proper description +ok 170 - hasnt_sequence(sch, non-existent sequence, desc) should have the proper diagnostics +ok 171 - hasnt_sequence(sequence, desc) should fail +ok 172 - hasnt_sequence(sequence, desc) should have the proper description +ok 173 - hasnt_sequence(sequence, desc) should have the proper diagnostics +ok 174 - hasnt_sequence(sch, sequence, desc) should fail +ok 175 - hasnt_sequence(sch, sequence, desc) should have the proper description +ok 176 - hasnt_sequence(sch, sequence, desc) should have the proper diagnostics +ok 177 - has_composite(non-existent composite type) should fail +ok 178 - has_composite(non-existent composite type) should have the proper description +ok 179 - has_composite(non-existent composite type) should have the proper diagnostics +ok 180 - has_composite(non-existent schema, tab) should fail +ok 181 - has_composite(non-existent schema, tab) should have the proper description +ok 182 - has_composite(non-existent schema, tab) should have the proper diagnostics +ok 183 - has_composite(sch, non-existent composite type, desc) should fail +ok 184 - has_composite(sch, non-existent composite type, desc) should have the proper description +ok 185 - has_composite(sch, non-existent composite type, desc) should have the proper diagnostics +ok 186 - has_composite(tab, desc) should pass +ok 187 - has_composite(tab, desc) should have the proper description +ok 188 - has_composite(tab, desc) should have the proper diagnostics +ok 189 - has_composite(sch, tab, desc) should pass +ok 190 - has_composite(sch, tab, desc) should have the proper description +ok 191 - has_composite(sch, tab, desc) should have the proper diagnostics +ok 192 - has_composite(sch, view, desc) should fail +ok 193 - has_composite(sch, view, desc) should have the proper description +ok 194 - has_composite(sch, view, desc) should have the proper diagnostics +ok 195 - has_composite(type, desc) should fail +ok 196 - has_composite(type, desc) should have the proper description +ok 197 - has_composite(type, desc) should have the proper diagnostics +ok 198 - hasnt_composite(non-existent composite type) should pass +ok 199 - hasnt_composite(non-existent composite type) should have the proper description +ok 200 - hasnt_composite(non-existent composite type) should have the proper diagnostics +ok 201 - hasnt_composite(non-existent schema, tab) should pass +ok 202 - hasnt_composite(non-existent schema, tab) should have the proper description +ok 203 - hasnt_composite(non-existent schema, tab) should have the proper diagnostics +ok 204 - hasnt_composite(sch, non-existent tab, desc) should pass +ok 205 - hasnt_composite(sch, non-existent tab, desc) should have the proper description +ok 206 - hasnt_composite(sch, non-existent tab, desc) should have the proper diagnostics +ok 207 - hasnt_composite(tab, desc) should fail +ok 208 - hasnt_composite(tab, desc) should have the proper description +ok 209 - hasnt_composite(tab, desc) should have the proper diagnostics +ok 210 - hasnt_composite(sch, tab, desc) should fail +ok 211 - hasnt_composite(sch, tab, desc) should have the proper description +ok 212 - hasnt_composite(sch, tab, desc) should have the proper diagnostics +ok 213 - has_type(type) should pass +ok 214 - has_type(type) should have the proper description +ok 215 - has_type(type) should have the proper diagnostics +ok 216 - has_type(type, desc) should pass +ok 217 - has_type(type, desc) should have the proper description +ok 218 - has_type(type, desc) should have the proper diagnostics +ok 219 - has_type(scheam, type) should pass +ok 220 - has_type(scheam, type) should have the proper description +ok 221 - has_type(scheam, type) should have the proper diagnostics +ok 222 - has_type(schema, type, desc) should pass +ok 223 - has_type(schema, type, desc) should have the proper description +ok 224 - has_type(schema, type, desc) should have the proper diagnostics +ok 225 - has_type(myType) should pass +ok 226 - has_type(myType) should have the proper description +ok 227 - has_type(myType) should have the proper diagnostics +ok 228 - has_type(myType, desc) should pass +ok 229 - has_type(myType, desc) should have the proper description +ok 230 - has_type(myType, desc) should have the proper diagnostics +ok 231 - has_type(scheam, myType) should pass +ok 232 - has_type(scheam, myType) should have the proper description +ok 233 - has_type(scheam, myType) should have the proper diagnostics +ok 234 - has_type(schema, myType, desc) should pass +ok 235 - has_type(schema, myType, desc) should have the proper description +ok 236 - has_type(schema, myType, desc) should have the proper diagnostics +ok 237 - has_type(type) should fail +ok 238 - has_type(type) should have the proper description +ok 239 - has_type(type) should have the proper diagnostics +ok 240 - has_type(type, desc) should fail +ok 241 - has_type(type, desc) should have the proper description +ok 242 - has_type(type, desc) should have the proper diagnostics +ok 243 - has_type(scheam, type) should fail +ok 244 - has_type(scheam, type) should have the proper description +ok 245 - has_type(scheam, type) should have the proper diagnostics +ok 246 - has_type(schema, type, desc) should fail +ok 247 - has_type(schema, type, desc) should have the proper description +ok 248 - has_type(schema, type, desc) should have the proper diagnostics +ok 249 - has_type(domain) should pass +ok 250 - has_type(domain) should have the proper description +ok 251 - has_type(domain) should have the proper diagnostics +ok 252 - has_type(myDomain) should pass +ok 253 - has_type(myDomain) should have the proper description +ok 254 - has_type(myDomain) should have the proper diagnostics +ok 255 - hasnt_type(type) should pass ok 256 - hasnt_type(type) should have the proper description ok 257 - hasnt_type(type) should have the proper diagnostics -ok 258 - hasnt_type(type, desc) should fail +ok 258 - hasnt_type(type, desc) should pass ok 259 - hasnt_type(type, desc) should have the proper description ok 260 - hasnt_type(type, desc) should have the proper diagnostics -ok 261 - hasnt_type(scheam, type) should fail +ok 261 - hasnt_type(scheam, type) should pass ok 262 - hasnt_type(scheam, type) should have the proper description ok 263 - hasnt_type(scheam, type) should have the proper diagnostics -ok 264 - hasnt_type(schema, type, desc) should fail +ok 264 - hasnt_type(schema, type, desc) should pass ok 265 - hasnt_type(schema, type, desc) should have the proper description ok 266 - hasnt_type(schema, type, desc) should have the proper diagnostics -ok 267 - has_domain(domain) should pass -ok 268 - has_domain(domain) should have the proper description -ok 269 - has_domain(domain) should have the proper diagnostics -ok 270 - has_domain(domain, desc) should pass -ok 271 - has_domain(domain, desc) should have the proper description -ok 272 - has_domain(domain, desc) should have the proper diagnostics -ok 273 - has_domain(scheam, domain) should pass -ok 274 - has_domain(scheam, domain) should have the proper description -ok 275 - has_domain(scheam, domain) should have the proper diagnostics -ok 276 - has_domain(schema, domain, desc) should pass -ok 277 - has_domain(schema, domain, desc) should have the proper description -ok 278 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 279 - has_domain(myDomain) should pass -ok 280 - has_domain(myDomain) should have the proper description -ok 281 - has_domain(myDomain) should have the proper diagnostics -ok 282 - has_domain(myDomain, desc) should pass -ok 283 - has_domain(myDomain, desc) should have the proper description -ok 284 - has_domain(myDomain, desc) should have the proper diagnostics -ok 285 - has_domain(scheam, myDomain) should pass -ok 286 - has_domain(scheam, myDomain) should have the proper description -ok 287 - has_domain(scheam, myDomain) should have the proper diagnostics -ok 288 - has_domain(schema, myDomain, desc) should pass -ok 289 - has_domain(schema, myDomain, desc) should have the proper description -ok 290 - has_domain(schema, myDomain, desc) should have the proper diagnostics -ok 291 - has_domain(domain) should fail -ok 292 - has_domain(domain) should have the proper description -ok 293 - has_domain(domain) should have the proper diagnostics -ok 294 - has_domain(domain, desc) should fail -ok 295 - has_domain(domain, desc) should have the proper description -ok 296 - has_domain(domain, desc) should have the proper diagnostics -ok 297 - has_domain(scheam, domain) should fail -ok 298 - has_domain(scheam, domain) should have the proper description -ok 299 - has_domain(scheam, domain) should have the proper diagnostics -ok 300 - has_domain(schema, domain, desc) should fail -ok 301 - has_domain(schema, domain, desc) should have the proper description -ok 302 - has_domain(schema, domain, desc) should have the proper diagnostics -ok 303 - hasnt_domain(domain) should pass -ok 304 - hasnt_domain(domain) should have the proper description -ok 305 - hasnt_domain(domain) should have the proper diagnostics -ok 306 - hasnt_domain(domain, desc) should pass -ok 307 - hasnt_domain(domain, desc) should have the proper description -ok 308 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 309 - hasnt_domain(scheam, domain) should pass -ok 310 - hasnt_domain(scheam, domain) should have the proper description -ok 311 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 312 - hasnt_domain(schema, domain, desc) should pass -ok 313 - hasnt_domain(schema, domain, desc) should have the proper description -ok 314 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 315 - hasnt_domain(domain) should fail +ok 267 - hasnt_type(type) should fail +ok 268 - hasnt_type(type) should have the proper description +ok 269 - hasnt_type(type) should have the proper diagnostics +ok 270 - hasnt_type(type, desc) should fail +ok 271 - hasnt_type(type, desc) should have the proper description +ok 272 - hasnt_type(type, desc) should have the proper diagnostics +ok 273 - hasnt_type(scheam, type) should fail +ok 274 - hasnt_type(scheam, type) should have the proper description +ok 275 - hasnt_type(scheam, type) should have the proper diagnostics +ok 276 - hasnt_type(schema, type, desc) should fail +ok 277 - hasnt_type(schema, type, desc) should have the proper description +ok 278 - hasnt_type(schema, type, desc) should have the proper diagnostics +ok 279 - has_domain(domain) should pass +ok 280 - has_domain(domain) should have the proper description +ok 281 - has_domain(domain) should have the proper diagnostics +ok 282 - has_domain(domain, desc) should pass +ok 283 - has_domain(domain, desc) should have the proper description +ok 284 - has_domain(domain, desc) should have the proper diagnostics +ok 285 - has_domain(scheam, domain) should pass +ok 286 - has_domain(scheam, domain) should have the proper description +ok 287 - has_domain(scheam, domain) should have the proper diagnostics +ok 288 - has_domain(schema, domain, desc) should pass +ok 289 - has_domain(schema, domain, desc) should have the proper description +ok 290 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 291 - has_domain(myDomain) should pass +ok 292 - has_domain(myDomain) should have the proper description +ok 293 - has_domain(myDomain) should have the proper diagnostics +ok 294 - has_domain(myDomain, desc) should pass +ok 295 - has_domain(myDomain, desc) should have the proper description +ok 296 - has_domain(myDomain, desc) should have the proper diagnostics +ok 297 - has_domain(scheam, myDomain) should pass +ok 298 - has_domain(scheam, myDomain) should have the proper description +ok 299 - has_domain(scheam, myDomain) should have the proper diagnostics +ok 300 - has_domain(schema, myDomain, desc) should pass +ok 301 - has_domain(schema, myDomain, desc) should have the proper description +ok 302 - has_domain(schema, myDomain, desc) should have the proper diagnostics +ok 303 - has_domain(domain) should fail +ok 304 - has_domain(domain) should have the proper description +ok 305 - has_domain(domain) should have the proper diagnostics +ok 306 - has_domain(domain, desc) should fail +ok 307 - has_domain(domain, desc) should have the proper description +ok 308 - has_domain(domain, desc) should have the proper diagnostics +ok 309 - has_domain(scheam, domain) should fail +ok 310 - has_domain(scheam, domain) should have the proper description +ok 311 - has_domain(scheam, domain) should have the proper diagnostics +ok 312 - has_domain(schema, domain, desc) should fail +ok 313 - has_domain(schema, domain, desc) should have the proper description +ok 314 - has_domain(schema, domain, desc) should have the proper diagnostics +ok 315 - hasnt_domain(domain) should pass ok 316 - hasnt_domain(domain) should have the proper description ok 317 - hasnt_domain(domain) should have the proper diagnostics -ok 318 - hasnt_domain(domain, desc) should fail +ok 318 - hasnt_domain(domain, desc) should pass ok 319 - hasnt_domain(domain, desc) should have the proper description ok 320 - hasnt_domain(domain, desc) should have the proper diagnostics -ok 321 - hasnt_domain(scheam, domain) should fail +ok 321 - hasnt_domain(scheam, domain) should pass ok 322 - hasnt_domain(scheam, domain) should have the proper description ok 323 - hasnt_domain(scheam, domain) should have the proper diagnostics -ok 324 - hasnt_domain(schema, domain, desc) should fail +ok 324 - hasnt_domain(schema, domain, desc) should pass ok 325 - hasnt_domain(schema, domain, desc) should have the proper description ok 326 - hasnt_domain(schema, domain, desc) should have the proper diagnostics -ok 327 - has_column(non-existent tab, col) should fail -ok 328 - has_column(non-existent tab, col) should have the proper description -ok 329 - has_column(non-existent tab, col) should have the proper diagnostics -ok 330 - has_column(non-existent tab, col, desc) should fail -ok 331 - has_column(non-existent tab, col, desc) should have the proper description -ok 332 - has_column(non-existent tab, col, desc) should have the proper diagnostics -ok 333 - has_column(non-existent sch, tab, col, desc) should fail -ok 334 - has_column(non-existent sch, tab, col, desc) should have the proper description -ok 335 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 336 - has_column(table, column) should pass -ok 337 - has_column(table, column) should have the proper description -ok 338 - has_column(table, column) should have the proper diagnostics -ok 339 - has_column(sch, tab, col, desc) should pass -ok 340 - has_column(sch, tab, col, desc) should have the proper description -ok 341 - has_column(sch, tab, col, desc) should have the proper diagnostics -ok 342 - has_column(table, camleCase column) should pass -ok 343 - has_column(table, camleCase column) should have the proper description -ok 344 - has_column(table, camleCase column) should have the proper diagnostics -ok 345 - has_column(view, column) should pass -ok 346 - has_column(view, column) should have the proper description -ok 347 - has_column(view, column) should have the proper diagnostics -ok 348 - has_column(type, column) should pass -ok 349 - has_column(type, column) should have the proper description -ok 350 - has_column(type, column) should have the proper diagnostics -ok 351 - hasnt_column(non-existent tab, col) should pass -ok 352 - hasnt_column(non-existent tab, col) should have the proper description -ok 353 - hasnt_column(non-existent tab, col) should have the proper diagnostics -ok 354 - hasnt_column(non-existent tab, col, desc) should pass -ok 355 - hasnt_column(non-existent tab, col, desc) should have the proper description -ok 356 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics -ok 357 - hasnt_column(non-existent sch, tab, col, desc) should pass -ok 358 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description -ok 359 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics -ok 360 - hasnt_column(table, column) should fail -ok 361 - hasnt_column(table, column) should have the proper description -ok 362 - hasnt_column(table, column) should have the proper diagnostics -ok 363 - hasnt_column(sch, tab, col, desc) should fail -ok 364 - hasnt_column(sch, tab, col, desc) should have the proper description -ok 365 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics -ok 366 - hasnt_column(view, column) should pass -ok 367 - hasnt_column(view, column) should have the proper description -ok 368 - hasnt_column(view, column) should have the proper diagnostics -ok 369 - hasnt_column(type, column) should pass -ok 370 - hasnt_column(type, column) should have the proper description -ok 371 - hasnt_column(type, column) should have the proper diagnostics -ok 372 - has_cast( src, targ, schema, func, desc) should pass -ok 373 - has_cast( src, targ, schema, func, desc) should have the proper description -ok 374 - has_cast( src, targ, schema, func, desc) should have the proper diagnostics -ok 375 - has_cast( src, targ, schema, func ) should pass -ok 376 - has_cast( src, targ, schema, func ) should have the proper description -ok 377 - has_cast( src, targ, schema, func ) should have the proper diagnostics -ok 378 - has_cast( src, targ, func, desc ) should pass -ok 379 - has_cast( src, targ, func, desc ) should have the proper description -ok 380 - has_cast( src, targ, func, desc ) should have the proper diagnostics -ok 381 - has_cast( src, targ, func) should pass -ok 382 - has_cast( src, targ, func) should have the proper description -ok 383 - has_cast( src, targ, func) should have the proper diagnostics -ok 384 - has_cast( src, targ, desc ) should pass -ok 385 - has_cast( src, targ, desc ) should have the proper description -ok 386 - has_cast( src, targ, desc ) should have the proper diagnostics -ok 387 - has_cast( src, targ ) should pass -ok 388 - has_cast( src, targ ) should have the proper description -ok 389 - has_cast( src, targ ) should have the proper diagnostics -ok 390 - has_cast( src, targ, schema, func, desc) fail should fail -ok 391 - has_cast( src, targ, schema, func, desc) fail should have the proper description -ok 392 - has_cast( src, targ, schema, func, desc) fail should have the proper diagnostics -ok 393 - has_cast( src, targ, func, desc ) fail should fail -ok 394 - has_cast( src, targ, func, desc ) fail should have the proper description -ok 395 - has_cast( src, targ, func, desc ) fail should have the proper diagnostics -ok 396 - has_cast( src, targ, desc ) fail should fail -ok 397 - has_cast( src, targ, desc ) fail should have the proper description -ok 398 - has_cast( src, targ, desc ) fail should have the proper diagnostics -ok 399 - hasnt_cast( src, targ, schema, func, desc) should fail -ok 400 - hasnt_cast( src, targ, schema, func, desc) should have the proper description -ok 401 - hasnt_cast( src, targ, schema, func, desc) should have the proper diagnostics -ok 402 - hasnt_cast( src, targ, schema, func ) should fail -ok 403 - hasnt_cast( src, targ, schema, func ) should have the proper description -ok 404 - hasnt_cast( src, targ, schema, func ) should have the proper diagnostics -ok 405 - hasnt_cast( src, targ, func, desc ) should fail -ok 406 - hasnt_cast( src, targ, func, desc ) should have the proper description -ok 407 - hasnt_cast( src, targ, func, desc ) should have the proper diagnostics -ok 408 - hasnt_cast( src, targ, func) should fail -ok 409 - hasnt_cast( src, targ, func) should have the proper description -ok 410 - hasnt_cast( src, targ, func) should have the proper diagnostics -ok 411 - hasnt_cast( src, targ, desc ) should fail -ok 412 - hasnt_cast( src, targ, desc ) should have the proper description -ok 413 - hasnt_cast( src, targ, desc ) should have the proper diagnostics -ok 414 - hasnt_cast( src, targ ) should fail -ok 415 - hasnt_cast( src, targ ) should have the proper description -ok 416 - hasnt_cast( src, targ ) should have the proper diagnostics -ok 417 - hasnt_cast( src, targ, schema, func, desc) fail should pass -ok 418 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper description -ok 419 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper diagnostics -ok 420 - hasnt_cast( src, targ, func, desc ) fail should pass -ok 421 - hasnt_cast( src, targ, func, desc ) fail should have the proper description -ok 422 - hasnt_cast( src, targ, func, desc ) fail should have the proper diagnostics -ok 423 - hasnt_cast( src, targ, desc ) fail should pass -ok 424 - hasnt_cast( src, targ, desc ) fail should have the proper description -ok 425 - hasnt_cast( src, targ, desc ) fail should have the proper diagnostics -ok 426 - cast_context_is( src, targ, context, desc ) should pass -ok 427 - cast_context_is( src, targ, context, desc ) should have the proper description -ok 428 - cast_context_is( src, targ, context, desc ) should have the proper diagnostics -ok 429 - cast_context_is( src, targ, context ) should pass -ok 430 - cast_context_is( src, targ, context ) should have the proper description -ok 431 - cast_context_is( src, targ, context ) should have the proper diagnostics -ok 432 - cast_context_is( src, targ, i, desc ) should pass -ok 433 - cast_context_is( src, targ, i, desc ) should have the proper description -ok 434 - cast_context_is( src, targ, i, desc ) should have the proper diagnostics -ok 435 - cast_context_is( src, targ, IMPL, desc ) should pass -ok 436 - cast_context_is( src, targ, IMPL, desc ) should have the proper description -ok 437 - cast_context_is( src, targ, IMPL, desc ) should have the proper diagnostics -ok 438 - cast_context_is( src, targ, assignment, desc ) should pass -ok 439 - cast_context_is( src, targ, assignment, desc ) should have the proper description -ok 440 - cast_context_is( src, targ, assignment, desc ) should have the proper diagnostics -ok 441 - cast_context_is( src, targ, a, desc ) should pass -ok 442 - cast_context_is( src, targ, a, desc ) should have the proper description -ok 443 - cast_context_is( src, targ, a, desc ) should have the proper diagnostics -ok 444 - cast_context_is( src, targ, ASS, desc ) should pass -ok 445 - cast_context_is( src, targ, ASS, desc ) should have the proper description -ok 446 - cast_context_is( src, targ, ASS, desc ) should have the proper diagnostics -ok 447 - cast_context_is( src, targ, explicit, desc ) should pass -ok 448 - cast_context_is( src, targ, explicit, desc ) should have the proper description -ok 449 - cast_context_is( src, targ, explicit, desc ) should have the proper diagnostics -ok 450 - cast_context_is( src, targ, e, desc ) should pass -ok 451 - cast_context_is( src, targ, e, desc ) should have the proper description -ok 452 - cast_context_is( src, targ, e, desc ) should have the proper diagnostics -ok 453 - cast_context_is( src, targ, EX, desc ) should pass -ok 454 - cast_context_is( src, targ, EX, desc ) should have the proper description -ok 455 - cast_context_is( src, targ, EX, desc ) should have the proper diagnostics -ok 456 - cast_context_is( src, targ, context, desc ) fail should fail -ok 457 - cast_context_is( src, targ, context, desc ) fail should have the proper description -ok 458 - cast_context_is( src, targ, context, desc ) fail should have the proper diagnostics -ok 459 - cast_context_is( src, targ, context ) fail should fail -ok 460 - cast_context_is( src, targ, context ) fail should have the proper description -ok 461 - cast_context_is( src, targ, context ) fail should have the proper diagnostics -ok 462 - cast_context_is( src, targ, context, desc ) noexist should fail -ok 463 - cast_context_is( src, targ, context, desc ) noexist should have the proper description -ok 464 - cast_context_is( src, targ, context, desc ) noexist should have the proper diagnostics -ok 465 - has_operator( left, schema, name, right, result, desc ) should pass -ok 466 - has_operator( left, schema, name, right, result, desc ) should have the proper description -ok 467 - has_operator( left, schema, name, right, result, desc ) should have the proper diagnostics -ok 468 - has_operator( left, schema, name, right, result ) should pass -ok 469 - has_operator( left, schema, name, right, result ) should have the proper description -ok 470 - has_operator( left, schema, name, right, result ) should have the proper diagnostics -ok 471 - has_operator( left, name, right, result, desc ) should pass -ok 472 - has_operator( left, name, right, result, desc ) should have the proper description -ok 473 - has_operator( left, name, right, result, desc ) should have the proper diagnostics -ok 474 - has_operator( left, name, right, result ) should pass -ok 475 - has_operator( left, name, right, result ) should have the proper description -ok 476 - has_operator( left, name, right, result ) should have the proper diagnostics -ok 477 - has_operator( left, name, right, desc ) should pass -ok 478 - has_operator( left, name, right, desc ) should have the proper description -ok 479 - has_operator( left, name, right, desc ) should have the proper diagnostics -ok 480 - has_operator( left, name, right ) should pass -ok 481 - has_operator( left, name, right ) should have the proper description -ok 482 - has_operator( left, name, right ) should have the proper diagnostics -ok 483 - has_operator( left, schema, name, right, result, desc ) fail should fail -ok 484 - has_operator( left, schema, name, right, result, desc ) fail should have the proper description -ok 485 - has_operator( left, schema, name, right, result, desc ) fail should have the proper diagnostics -ok 486 - has_operator( left, schema, name, right, result ) fail should fail -ok 487 - has_operator( left, schema, name, right, result ) fail should have the proper description -ok 488 - has_operator( left, schema, name, right, result ) fail should have the proper diagnostics -ok 489 - has_operator( left, name, right, result, desc ) fail should fail -ok 490 - has_operator( left, name, right, result, desc ) fail should have the proper description -ok 491 - has_operator( left, name, right, result, desc ) fail should have the proper diagnostics -ok 492 - has_operator( left, name, right, result ) fail should fail -ok 493 - has_operator( left, name, right, result ) fail should have the proper description -ok 494 - has_operator( left, name, right, result ) fail should have the proper diagnostics -ok 495 - has_operator( left, name, right, desc ) fail should fail -ok 496 - has_operator( left, name, right, desc ) fail should have the proper description -ok 497 - has_operator( left, name, right, desc ) fail should have the proper diagnostics -ok 498 - has_operator( left, name, right ) fail should fail -ok 499 - has_operator( left, name, right ) fail should have the proper description -ok 500 - has_operator( left, name, right ) fail should have the proper diagnostics -ok 501 - has_leftop( schema, name, right, result, desc ) should pass -ok 502 - has_leftop( schema, name, right, result, desc ) should have the proper description -ok 503 - has_leftop( schema, name, right, result, desc ) should have the proper diagnostics -ok 504 - has_leftop( schema, name, right, result ) should pass -ok 505 - has_leftop( schema, name, right, result ) should have the proper description -ok 506 - has_leftop( schema, name, right, result ) should have the proper diagnostics -ok 507 - has_leftop( name, right, result, desc ) should pass -ok 508 - has_leftop( name, right, result, desc ) should have the proper description -ok 509 - has_leftop( name, right, result, desc ) should have the proper diagnostics -ok 510 - has_leftop( name, right, result ) should pass -ok 511 - has_leftop( name, right, result ) should have the proper description -ok 512 - has_leftop( name, right, result ) should have the proper diagnostics -ok 513 - has_leftop( name, right, desc ) should pass -ok 514 - has_leftop( name, right, desc ) should have the proper description -ok 515 - has_leftop( name, right, desc ) should have the proper diagnostics -ok 516 - has_leftop( name, right ) should pass -ok 517 - has_leftop( name, right ) should have the proper description -ok 518 - has_leftop( name, right ) should have the proper diagnostics -ok 519 - has_leftop( schema, name, right, result, desc ) fail should fail -ok 520 - has_leftop( schema, name, right, result, desc ) fail should have the proper description -ok 521 - has_leftop( schema, name, right, result, desc ) fail should have the proper diagnostics -ok 522 - has_leftop( schema, name, right, result ) fail should fail -ok 523 - has_leftop( schema, name, right, result ) fail should have the proper description -ok 524 - has_leftop( schema, name, right, result ) fail should have the proper diagnostics -ok 525 - has_leftop( name, right, result, desc ) fail should fail -ok 526 - has_leftop( name, right, result, desc ) fail should have the proper description -ok 527 - has_leftop( name, right, result, desc ) fail should have the proper diagnostics -ok 528 - has_leftop( name, right, result ) fail should fail -ok 529 - has_leftop( name, right, result ) fail should have the proper description -ok 530 - has_leftop( name, right, result ) fail should have the proper diagnostics -ok 531 - has_leftop( name, right, desc ) fail should fail -ok 532 - has_leftop( name, right, desc ) fail should have the proper description -ok 533 - has_leftop( name, right, desc ) fail should have the proper diagnostics -ok 534 - has_leftop( name, right ) fail should fail -ok 535 - has_leftop( name, right ) fail should have the proper description -ok 536 - has_leftop( name, right ) fail should have the proper diagnostics -ok 537 - has_rightop( left, schema, name, result, desc ) should pass -ok 538 - has_rightop( left, schema, name, result, desc ) should have the proper description -ok 539 - has_rightop( left, schema, name, result, desc ) should have the proper diagnostics -ok 540 - has_rightop( left, schema, name, result ) should pass -ok 541 - has_rightop( left, schema, name, result ) should have the proper description -ok 542 - has_rightop( left, schema, name, result ) should have the proper diagnostics -ok 543 - has_rightop( left, name, result, desc ) should pass -ok 544 - has_rightop( left, name, result, desc ) should have the proper description -ok 545 - has_rightop( left, name, result, desc ) should have the proper diagnostics -ok 546 - has_rightop( left, name, result ) should pass -ok 547 - has_rightop( left, name, result ) should have the proper description -ok 548 - has_rightop( left, name, result ) should have the proper diagnostics -ok 549 - has_rightop( left, name, desc ) should pass -ok 550 - has_rightop( left, name, desc ) should have the proper description -ok 551 - has_rightop( left, name, desc ) should have the proper diagnostics -ok 552 - has_rightop( left, name ) should pass -ok 553 - has_rightop( left, name ) should have the proper description -ok 554 - has_rightop( left, name ) should have the proper diagnostics -ok 555 - has_rightop( left, schema, name, result, desc ) fail should fail -ok 556 - has_rightop( left, schema, name, result, desc ) fail should have the proper description -ok 557 - has_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics -ok 558 - has_rightop( left, schema, name, result ) fail should fail -ok 559 - has_rightop( left, schema, name, result ) fail should have the proper description -ok 560 - has_rightop( left, schema, name, result ) fail should have the proper diagnostics -ok 561 - has_rightop( left, name, result, desc ) fail should fail -ok 562 - has_rightop( left, name, result, desc ) fail should have the proper description -ok 563 - has_rightop( left, name, result, desc ) fail should have the proper diagnostics -ok 564 - has_rightop( left, name, result ) fail should fail -ok 565 - has_rightop( left, name, result ) fail should have the proper description -ok 566 - has_rightop( left, name, result ) fail should have the proper diagnostics -ok 567 - has_rightop( left, name, desc ) fail should fail -ok 568 - has_rightop( left, name, desc ) fail should have the proper description -ok 569 - has_rightop( left, name, desc ) fail should have the proper diagnostics -ok 570 - has_rightop( left, name ) fail should fail -ok 571 - has_rightop( left, name ) fail should have the proper description -ok 572 - has_rightop( left, name ) fail should have the proper diagnostics -ok 573 - has_language(language) should pass -ok 574 - has_language(language) should have the proper description -ok 575 - has_language(language) should have the proper diagnostics -ok 576 - has_language(language, desc) should pass -ok 577 - has_language(language, desc) should have the proper description -ok 578 - has_language(language, desc) should have the proper diagnostics -ok 579 - has_language(nonexistent language) should fail -ok 580 - has_language(nonexistent language) should have the proper description -ok 581 - has_language(nonexistent language) should have the proper diagnostics -ok 582 - has_language(nonexistent language, desc) should fail -ok 583 - has_language(nonexistent language, desc) should have the proper description -ok 584 - has_language(nonexistent language, desc) should have the proper diagnostics -ok 585 - hasnt_language(language) should fail -ok 586 - hasnt_language(language) should have the proper description -ok 587 - hasnt_language(language) should have the proper diagnostics -ok 588 - hasnt_language(language, desc) should fail -ok 589 - hasnt_language(language, desc) should have the proper description -ok 590 - hasnt_language(language, desc) should have the proper diagnostics -ok 591 - hasnt_language(nonexistent language) should pass -ok 592 - hasnt_language(nonexistent language) should have the proper description -ok 593 - hasnt_language(nonexistent language) should have the proper diagnostics -ok 594 - hasnt_language(nonexistent language, desc) should pass -ok 595 - hasnt_language(nonexistent language, desc) should have the proper description -ok 596 - hasnt_language(nonexistent language, desc) should have the proper diagnostics -ok 597 - language_is_trusted(language, desc) should pass -ok 598 - language_is_trusted(language, desc) should have the proper description -ok 599 - language_is_trusted(language, desc) should have the proper diagnostics -ok 600 - language_is_trusted(language) should pass -ok 601 - language_is_trusted(language) should have the proper description -ok 602 - language_is_trusted(language) should have the proper diagnostics -ok 603 - language_is_trusted(language, desc) fail should fail -ok 604 - language_is_trusted(language, desc) fail should have the proper description -ok 605 - language_is_trusted(language, desc) fail should have the proper diagnostics -ok 606 - language_is_trusted(language, desc) non-existent should fail -ok 607 - language_is_trusted(language, desc) non-existent should have the proper description -ok 608 - language_is_trusted(language, desc) non-existent should have the proper diagnostics -ok 609 - has_opclass( schema, name, desc ) should pass -ok 610 - has_opclass( schema, name, desc ) should have the proper description -ok 611 - has_opclass( schema, name, desc ) should have the proper diagnostics -ok 612 - has_opclass( schema, name ) should pass -ok 613 - has_opclass( schema, name ) should have the proper description -ok 614 - has_opclass( schema, name ) should have the proper diagnostics -ok 615 - has_opclass( name, desc ) should pass -ok 616 - has_opclass( name, desc ) should have the proper description -ok 617 - has_opclass( name, desc ) should have the proper diagnostics -ok 618 - has_opclass( name ) should pass -ok 619 - has_opclass( name ) should have the proper description -ok 620 - has_opclass( name ) should have the proper diagnostics -ok 621 - has_opclass( schema, name, desc ) fail should fail -ok 622 - has_opclass( schema, name, desc ) fail should have the proper description -ok 623 - has_opclass( schema, name, desc ) fail should have the proper diagnostics -ok 624 - has_opclass( name, desc ) fail should fail -ok 625 - has_opclass( name, desc ) fail should have the proper description -ok 626 - has_opclass( name, desc ) fail should have the proper diagnostics -ok 627 - hasnt_opclass( schema, name, desc ) should fail -ok 628 - hasnt_opclass( schema, name, desc ) should have the proper description -ok 629 - hasnt_opclass( schema, name, desc ) should have the proper diagnostics -ok 630 - hasnt_opclass( schema, name ) should fail -ok 631 - hasnt_opclass( schema, name ) should have the proper description -ok 632 - hasnt_opclass( schema, name ) should have the proper diagnostics -ok 633 - hasnt_opclass( name, desc ) should fail -ok 634 - hasnt_opclass( name, desc ) should have the proper description -ok 635 - hasnt_opclass( name, desc ) should have the proper diagnostics -ok 636 - hasnt_opclass( name ) should fail -ok 637 - hasnt_opclass( name ) should have the proper description -ok 638 - hasnt_opclass( name ) should have the proper diagnostics -ok 639 - hasnt_opclass( schema, name, desc ) fail should pass -ok 640 - hasnt_opclass( schema, name, desc ) fail should have the proper description -ok 641 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnostics -ok 642 - hasnt_opclass( name, desc ) fail should pass -ok 643 - hasnt_opclass( name, desc ) fail should have the proper description -ok 644 - hasnt_opclass( name, desc ) fail should have the proper diagnostics -ok 645 - domain_type_is(schema, domain, schema, type, desc) should pass -ok 646 - domain_type_is(schema, domain, schema, type, desc) should have the proper description -ok 647 - domain_type_is(schema, domain, schema, type, desc) should have the proper diagnostics -ok 648 - domain_type_is(schema, domain, schema, type) should pass -ok 649 - domain_type_is(schema, domain, schema, type) should have the proper description -ok 650 - domain_type_is(schema, domain, schema, type) should have the proper diagnostics -ok 651 - domain_type_is(schema, domain, schema, type, desc) fail should fail -ok 652 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper description -ok 653 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper diagnostics -ok 654 - domain_type_is(schema, nondomain, schema, type, desc) should fail -ok 655 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper description -ok 656 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper diagnostics -ok 657 - domain_type_is(schema, type, schema, type, desc) fail should fail -ok 658 - domain_type_is(schema, type, schema, type, desc) fail should have the proper description -ok 659 - domain_type_is(schema, type, schema, type, desc) fail should have the proper diagnostics -ok 660 - domain_type_is(schema, domain, type, desc) should pass -ok 661 - domain_type_is(schema, domain, type, desc) should have the proper description -ok 662 - domain_type_is(schema, domain, type, desc) should have the proper diagnostics -ok 663 - domain_type_is(schema, domain, type) should pass -ok 664 - domain_type_is(schema, domain, type) should have the proper description -ok 665 - domain_type_is(schema, domain, type) should have the proper diagnostics -ok 666 - domain_type_is(schema, domain, type, desc) fail should fail -ok 667 - domain_type_is(schema, domain, type, desc) fail should have the proper description -ok 668 - domain_type_is(schema, domain, type, desc) fail should have the proper diagnostics -ok 669 - domain_type_is(schema, nondomain, type, desc) should fail -ok 670 - domain_type_is(schema, nondomain, type, desc) should have the proper description -ok 671 - domain_type_is(schema, nondomain, type, desc) should have the proper diagnostics -ok 672 - domain_type_is(schema, type, type, desc) fail should fail -ok 673 - domain_type_is(schema, type, type, desc) fail should have the proper description -ok 674 - domain_type_is(schema, type, type, desc) fail should have the proper diagnostics -ok 675 - domain_type_is(domain, type, desc) should pass -ok 676 - domain_type_is(domain, type, desc) should have the proper description -ok 677 - domain_type_is(domain, type, desc) should have the proper diagnostics -ok 678 - domain_type_is(domain, type) should pass -ok 679 - domain_type_is(domain, type) should have the proper description -ok 680 - domain_type_is(domain, type) should have the proper diagnostics -ok 681 - domain_type_is(domain, type, desc) fail should fail -ok 682 - domain_type_is(domain, type, desc) fail should have the proper description -ok 683 - domain_type_is(domain, type, desc) fail should have the proper diagnostics -ok 684 - domain_type_is(nondomain, type, desc) should fail -ok 685 - domain_type_is(nondomain, type, desc) should have the proper description -ok 686 - domain_type_is(nondomain, type, desc) should have the proper diagnostics -ok 687 - domain_type_is(type, type, desc) fail should fail -ok 688 - domain_type_is(type, type, desc) fail should have the proper description -ok 689 - domain_type_is(type, type, desc) fail should have the proper diagnostics -ok 690 - domain_type_isnt(schema, domain, schema, type, desc) should pass -ok 691 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper description -ok 692 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper diagnostics -ok 693 - domain_type_isnt(schema, domain, schema, type) should pass -ok 694 - domain_type_isnt(schema, domain, schema, type) should have the proper description -ok 695 - domain_type_isnt(schema, domain, schema, type) should have the proper diagnostics -ok 696 - domain_type_isnt(schema, domain, schema, type, desc) fail should fail -ok 697 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper description -ok 698 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper diagnostics -ok 699 - domain_type_isnt(schema, nondomain, schema, type, desc) should fail -ok 700 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper description -ok 701 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper diagnostics -ok 702 - domain_type_isnt(schema, type, schema, type, desc) should fail -ok 703 - domain_type_isnt(schema, type, schema, type, desc) should have the proper description -ok 704 - domain_type_isnt(schema, type, schema, type, desc) should have the proper diagnostics -ok 705 - domain_type_isnt(schema, domain, type, desc) should pass -ok 706 - domain_type_isnt(schema, domain, type, desc) should have the proper description -ok 707 - domain_type_isnt(schema, domain, type, desc) should have the proper diagnostics -ok 708 - domain_type_isnt(schema, domain, type) should pass -ok 709 - domain_type_isnt(schema, domain, type) should have the proper description -ok 710 - domain_type_isnt(schema, domain, type) should have the proper diagnostics -ok 711 - domain_type_isnt(schema, domain, type, desc) fail should fail -ok 712 - domain_type_isnt(schema, domain, type, desc) fail should have the proper description -ok 713 - domain_type_isnt(schema, domain, type, desc) fail should have the proper diagnostics -ok 714 - domain_type_isnt(schema, nondomain, type, desc) should fail -ok 715 - domain_type_isnt(schema, nondomain, type, desc) should have the proper description -ok 716 - domain_type_isnt(schema, nondomain, type, desc) should have the proper diagnostics -ok 717 - domain_type_isnt(schema, type, type, desc) should fail -ok 718 - domain_type_isnt(schema, type, type, desc) should have the proper description -ok 719 - domain_type_isnt(schema, type, type, desc) should have the proper diagnostics -ok 720 - domain_type_isnt(domain, type, desc) should pass -ok 721 - domain_type_isnt(domain, type, desc) should have the proper description -ok 722 - domain_type_isnt(domain, type, desc) should have the proper diagnostics -ok 723 - domain_type_isnt(domain, type) should pass -ok 724 - domain_type_isnt(domain, type) should have the proper description -ok 725 - domain_type_isnt(domain, type) should have the proper diagnostics -ok 726 - domain_type_isnt(domain, type, desc) fail should fail -ok 727 - domain_type_isnt(domain, type, desc) fail should have the proper description -ok 728 - domain_type_isnt(domain, type, desc) fail should have the proper diagnostics -ok 729 - domain_type_isnt(nondomain, type, desc) should fail -ok 730 - domain_type_isnt(nondomain, type, desc) should have the proper description -ok 731 - domain_type_isnt(nondomain, type, desc) should have the proper diagnostics -ok 732 - domain_type_isnt(type, type, desc) should fail -ok 733 - domain_type_isnt(type, type, desc) should have the proper description -ok 734 - domain_type_isnt(type, type, desc) should have the proper diagnostics -ok 735 - has_relation(non-existent relation) should fail -ok 736 - has_relation(non-existent relation) should have the proper description -ok 737 - has_relation(non-existent relation) should have the proper diagnostics -ok 738 - has_relation(non-existent schema, tab) should fail -ok 739 - has_relation(non-existent schema, tab) should have the proper description -ok 740 - has_relation(non-existent schema, tab) should have the proper diagnostics -ok 741 - has_relation(sch, non-existent relation, desc) should fail -ok 742 - has_relation(sch, non-existent relation, desc) should have the proper description -ok 743 - has_relation(sch, non-existent relation, desc) should have the proper diagnostics -ok 744 - has_relation(tab, desc) should pass -ok 745 - has_relation(tab, desc) should have the proper description -ok 746 - has_relation(tab, desc) should have the proper diagnostics -ok 747 - has_relation(sch, tab, desc) should pass -ok 748 - has_relation(sch, tab, desc) should have the proper description -ok 749 - has_relation(sch, tab, desc) should have the proper diagnostics -ok 750 - has_relation(sch, view, desc) should pass -ok 751 - has_relation(sch, view, desc) should have the proper description -ok 752 - has_relation(sch, view, desc) should have the proper diagnostics -ok 753 - has_relation(type, desc) should pass -ok 754 - has_relation(type, desc) should have the proper description -ok 755 - has_relation(type, desc) should have the proper diagnostics -ok 756 - hasnt_relation(non-existent relation) should pass -ok 757 - hasnt_relation(non-existent relation) should have the proper description -ok 758 - hasnt_relation(non-existent relation) should have the proper diagnostics -ok 759 - hasnt_relation(non-existent schema, tab) should pass -ok 760 - hasnt_relation(non-existent schema, tab) should have the proper description -ok 761 - hasnt_relation(non-existent schema, tab) should have the proper diagnostics -ok 762 - hasnt_relation(sch, non-existent tab, desc) should pass -ok 763 - hasnt_relation(sch, non-existent tab, desc) should have the proper description -ok 764 - hasnt_relation(sch, non-existent tab, desc) should have the proper diagnostics -ok 765 - hasnt_relation(tab, desc) should fail -ok 766 - hasnt_relation(tab, desc) should have the proper description -ok 767 - hasnt_relation(tab, desc) should have the proper diagnostics -ok 768 - hasnt_relation(sch, tab, desc) should fail -ok 769 - hasnt_relation(sch, tab, desc) should have the proper description -ok 770 - hasnt_relation(sch, tab, desc) should have the proper diagnostics -ok 771 - has_foreign_table(non-existent table) should fail -ok 772 - has_foreign_table(non-existent table) should have the proper description -ok 773 - has_foreign_table(non-existent table) should have the proper diagnostics -ok 774 - has_foreign_table(non-existent schema, tab) should fail -ok 775 - has_foreign_table(non-existent schema, tab) should have the proper description -ok 776 - has_foreign_table(non-existent schema, tab) should have the proper diagnostics -ok 777 - has_foreign_table(non-existent table, desc) should fail -ok 778 - has_foreign_table(non-existent table, desc) should have the proper description -ok 779 - has_foreign_table(non-existent table, desc) should have the proper diagnostics -ok 780 - has_foreign_table(sch, non-existent table, desc) should fail -ok 781 - has_foreign_table(sch, non-existent table, desc) should have the proper description -ok 782 - has_foreign_table(sch, non-existent table, desc) should have the proper diagnostics -ok 783 - has_foreign_table(tab, desc) should pass -ok 784 - has_foreign_table(tab, desc) should have the proper description -ok 785 - has_foreign_table(tab, desc) should have the proper diagnostics -ok 786 - has_foreign_table(sch, tab, desc) should pass -ok 787 - has_foreign_table(sch, tab, desc) should have the proper description -ok 788 - has_foreign_table(sch, tab, desc) should have the proper diagnostics -ok 789 - has_foreign_table(sch, view, desc) should fail -ok 790 - has_foreign_table(sch, view, desc) should have the proper description -ok 791 - has_foreign_table(sch, view, desc) should have the proper diagnostics -ok 792 - has_foreign_table(type, desc) should fail -ok 793 - has_foreign_table(type, desc) should have the proper description -ok 794 - has_foreign_table(type, desc) should have the proper diagnostics -ok 795 - hasnt_foreign_table(non-existent table) should pass -ok 796 - hasnt_foreign_table(non-existent table) should have the proper description -ok 797 - hasnt_foreign_table(non-existent table) should have the proper diagnostics -ok 798 - hasnt_foreign_table(non-existent schema, tab) should pass -ok 799 - hasnt_foreign_table(non-existent schema, tab) should have the proper description -ok 800 - hasnt_foreign_table(non-existent schema, tab) should have the proper diagnostics -ok 801 - hasnt_foreign_table(non-existent table, desc) should pass -ok 802 - hasnt_foreign_table(non-existent table, desc) should have the proper description -ok 803 - hasnt_foreign_table(non-existent table, desc) should have the proper diagnostics -ok 804 - hasnt_foreign_table(sch, non-existent tab, desc) should pass -ok 805 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper description -ok 806 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper diagnostics -ok 807 - hasnt_foreign_table(tab, desc) should fail -ok 808 - hasnt_foreign_table(tab, desc) should have the proper description -ok 809 - hasnt_foreign_table(tab, desc) should have the proper diagnostics -ok 810 - hasnt_foreign_table(sch, tab, desc) should fail -ok 811 - hasnt_foreign_table(sch, tab, desc) should have the proper description -ok 812 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics -ok 813 - has_materialized_view(non-existent materialized_view) should fail -ok 814 - has_materialized_view(non-existent materialized_view) should have the proper description -ok 815 - has_materialized_view(non-existent materialized_view) should have the proper diagnostics -ok 816 - has_materialized_view(non-existent materialized_view, desc) should fail -ok 817 - has_materialized_view(non-existent materialized_view, desc) should have the proper description -ok 818 - has_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics -ok 819 - has_materialized_view(sch, non-existent materialized_view, desc) should fail -ok 820 - has_materialized_view(sch, non-existent materialized_view, desc) should have the proper description -ok 821 - has_materialized_view(sch, non-existent materialized_view, desc) should have the proper diagnostics -ok 822 - has_materialized_view(materialized_view, desc) should pass -ok 823 - has_materialized_view(materialized_view, desc) should have the proper description -ok 824 - has_materialized_view(materialized_view, desc) should have the proper diagnostics -ok 825 - has_materialized_view(sch, materialized_view, desc) should pass -ok 826 - has_materialized_view(sch, materialized_view, desc) should have the proper description -ok 827 - has_materialized_view(sch, materialized_view, desc) should have the proper diagnostics -ok 828 - hasnt_materialized_view(non-existent materialized_view) should pass -ok 829 - hasnt_materialized_view(non-existent materialized_view) should have the proper description -ok 830 - hasnt_materialized_view(non-existent materialized_view) should have the proper diagnostics -ok 831 - hasnt_materialized_view(non-existent materialized_view, desc) should pass -ok 832 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper description -ok 833 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics -ok 834 - hasnt_materialized_view(sch, non-existent materialized_view, desc) should pass -ok 835 - hasnt_materialized_view(sch, non-existent materialized_view, desc) should have the proper description -ok 836 - hasnt_materialized_view(sch, non-existent materialized_view, desc) should have the proper diagnostics -ok 837 - hasnt_materialized_view(materialized_view, desc) should fail -ok 838 - hasnt_materialized_view(materialized_view, desc) should have the proper description -ok 839 - hasnt_materialized_view(materialized_view, desc) should have the proper diagnostics -ok 840 - hasnt_materialized_view(sch, materialized_view, desc) should fail -ok 841 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper description -ok 842 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper diagnostics -ok 843 - is_partitioned(non-existent part) should fail -ok 844 - is_partitioned(non-existent part) should have the proper description -ok 845 - is_partitioned(non-existent part) should have the proper diagnostics -ok 846 - is_partitioned(non-existent part, desc) should fail -ok 847 - is_partitioned(non-existent part, desc) should have the proper description -ok 848 - is_partitioned(non-existent part, desc) should have the proper diagnostics -ok 849 - is_partitioned(sch, non-existent part, desc) should fail -ok 850 - is_partitioned(sch, non-existent part, desc) should have the proper description -ok 851 - is_partitioned(sch, non-existent part, desc) should have the proper diagnostics -ok 852 - is_partitioned(sch, part, desc) should pass -ok 853 - is_partitioned(sch, part, desc) should have the proper description -ok 854 - is_partitioned(sch, part, desc) should have the proper diagnostics -ok 855 - is_partitioned(sch, part) should pass -ok 856 - is_partitioned(sch, part) should have the proper description -ok 857 - is_partitioned(sch, part) should have the proper diagnostics -ok 858 - is_partitioned(part, desc) should pass -ok 859 - is_partitioned(part, desc) should have the proper description -ok 860 - is_partitioned(part, desc) should have the proper diagnostics -ok 861 - is_partitioned(part) should pass -ok 862 - is_partitioned(part) should have the proper description -ok 863 - is_partitioned(part) should have the proper diagnostics -ok 864 - isnt_partitioned(non-existent part) should pass -ok 865 - isnt_partitioned(non-existent part) should have the proper description -ok 866 - isnt_partitioned(non-existent part) should have the proper diagnostics -ok 867 - isnt_partitioned(non-existent part, desc) should pass -ok 868 - isnt_partitioned(non-existent part, desc) should have the proper description -ok 869 - isnt_partitioned(non-existent part, desc) should have the proper diagnostics -ok 870 - isnt_partitioned(sch, non-existent part, desc) should pass -ok 871 - isnt_partitioned(sch, non-existent part, desc) should have the proper description -ok 872 - isnt_partitioned(sch, non-existent part, desc) should have the proper diagnostics -ok 873 - isnt_partitioned(sch, part, desc) should fail -ok 874 - isnt_partitioned(sch, part, desc) should have the proper description -ok 875 - isnt_partitioned(sch, part, desc) should have the proper diagnostics -ok 876 - isnt_partitioned(sch, part) should fail -ok 877 - isnt_partitioned(sch, part) should have the proper description -ok 878 - isnt_partitioned(sch, part) should have the proper diagnostics -ok 879 - isnt_partitioned(part, desc) should fail -ok 880 - isnt_partitioned(part, desc) should have the proper description -ok 881 - isnt_partitioned(part, desc) should have the proper diagnostics -ok 882 - isnt_partitioned(part) should fail -ok 883 - isnt_partitioned(part) should have the proper description -ok 884 - isnt_partitioned(part) should have the proper diagnostics +ok 327 - hasnt_domain(domain) should fail +ok 328 - hasnt_domain(domain) should have the proper description +ok 329 - hasnt_domain(domain) should have the proper diagnostics +ok 330 - hasnt_domain(domain, desc) should fail +ok 331 - hasnt_domain(domain, desc) should have the proper description +ok 332 - hasnt_domain(domain, desc) should have the proper diagnostics +ok 333 - hasnt_domain(scheam, domain) should fail +ok 334 - hasnt_domain(scheam, domain) should have the proper description +ok 335 - hasnt_domain(scheam, domain) should have the proper diagnostics +ok 336 - hasnt_domain(schema, domain, desc) should fail +ok 337 - hasnt_domain(schema, domain, desc) should have the proper description +ok 338 - hasnt_domain(schema, domain, desc) should have the proper diagnostics +ok 339 - has_column(non-existent tab, col) should fail +ok 340 - has_column(non-existent tab, col) should have the proper description +ok 341 - has_column(non-existent tab, col) should have the proper diagnostics +ok 342 - has_column(non-existent tab, col, desc) should fail +ok 343 - has_column(non-existent tab, col, desc) should have the proper description +ok 344 - has_column(non-existent tab, col, desc) should have the proper diagnostics +ok 345 - has_column(non-existent sch, tab, col, desc) should fail +ok 346 - has_column(non-existent sch, tab, col, desc) should have the proper description +ok 347 - has_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 348 - has_column(table, column) should pass +ok 349 - has_column(table, column) should have the proper description +ok 350 - has_column(table, column) should have the proper diagnostics +ok 351 - has_column(sch, tab, col, desc) should pass +ok 352 - has_column(sch, tab, col, desc) should have the proper description +ok 353 - has_column(sch, tab, col, desc) should have the proper diagnostics +ok 354 - has_column(table, camleCase column) should pass +ok 355 - has_column(table, camleCase column) should have the proper description +ok 356 - has_column(table, camleCase column) should have the proper diagnostics +ok 357 - has_column(view, column) should pass +ok 358 - has_column(view, column) should have the proper description +ok 359 - has_column(view, column) should have the proper diagnostics +ok 360 - has_column(type, column) should pass +ok 361 - has_column(type, column) should have the proper description +ok 362 - has_column(type, column) should have the proper diagnostics +ok 363 - hasnt_column(non-existent tab, col) should pass +ok 364 - hasnt_column(non-existent tab, col) should have the proper description +ok 365 - hasnt_column(non-existent tab, col) should have the proper diagnostics +ok 366 - hasnt_column(non-existent tab, col, desc) should pass +ok 367 - hasnt_column(non-existent tab, col, desc) should have the proper description +ok 368 - hasnt_column(non-existent tab, col, desc) should have the proper diagnostics +ok 369 - hasnt_column(non-existent sch, tab, col, desc) should pass +ok 370 - hasnt_column(non-existent sch, tab, col, desc) should have the proper description +ok 371 - hasnt_column(non-existent sch, tab, col, desc) should have the proper diagnostics +ok 372 - hasnt_column(table, column) should fail +ok 373 - hasnt_column(table, column) should have the proper description +ok 374 - hasnt_column(table, column) should have the proper diagnostics +ok 375 - hasnt_column(sch, tab, col, desc) should fail +ok 376 - hasnt_column(sch, tab, col, desc) should have the proper description +ok 377 - hasnt_column(sch, tab, col, desc) should have the proper diagnostics +ok 378 - hasnt_column(view, column) should pass +ok 379 - hasnt_column(view, column) should have the proper description +ok 380 - hasnt_column(view, column) should have the proper diagnostics +ok 381 - hasnt_column(type, column) should pass +ok 382 - hasnt_column(type, column) should have the proper description +ok 383 - hasnt_column(type, column) should have the proper diagnostics +ok 384 - has_cast( src, targ, schema, func, desc) should pass +ok 385 - has_cast( src, targ, schema, func, desc) should have the proper description +ok 386 - has_cast( src, targ, schema, func, desc) should have the proper diagnostics +ok 387 - has_cast( src, targ, schema, func ) should pass +ok 388 - has_cast( src, targ, schema, func ) should have the proper description +ok 389 - has_cast( src, targ, schema, func ) should have the proper diagnostics +ok 390 - has_cast( src, targ, func, desc ) should pass +ok 391 - has_cast( src, targ, func, desc ) should have the proper description +ok 392 - has_cast( src, targ, func, desc ) should have the proper diagnostics +ok 393 - has_cast( src, targ, func) should pass +ok 394 - has_cast( src, targ, func) should have the proper description +ok 395 - has_cast( src, targ, func) should have the proper diagnostics +ok 396 - has_cast( src, targ, desc ) should pass +ok 397 - has_cast( src, targ, desc ) should have the proper description +ok 398 - has_cast( src, targ, desc ) should have the proper diagnostics +ok 399 - has_cast( src, targ ) should pass +ok 400 - has_cast( src, targ ) should have the proper description +ok 401 - has_cast( src, targ ) should have the proper diagnostics +ok 402 - has_cast( src, targ, schema, func, desc) fail should fail +ok 403 - has_cast( src, targ, schema, func, desc) fail should have the proper description +ok 404 - has_cast( src, targ, schema, func, desc) fail should have the proper diagnostics +ok 405 - has_cast( src, targ, func, desc ) fail should fail +ok 406 - has_cast( src, targ, func, desc ) fail should have the proper description +ok 407 - has_cast( src, targ, func, desc ) fail should have the proper diagnostics +ok 408 - has_cast( src, targ, desc ) fail should fail +ok 409 - has_cast( src, targ, desc ) fail should have the proper description +ok 410 - has_cast( src, targ, desc ) fail should have the proper diagnostics +ok 411 - hasnt_cast( src, targ, schema, func, desc) should fail +ok 412 - hasnt_cast( src, targ, schema, func, desc) should have the proper description +ok 413 - hasnt_cast( src, targ, schema, func, desc) should have the proper diagnostics +ok 414 - hasnt_cast( src, targ, schema, func ) should fail +ok 415 - hasnt_cast( src, targ, schema, func ) should have the proper description +ok 416 - hasnt_cast( src, targ, schema, func ) should have the proper diagnostics +ok 417 - hasnt_cast( src, targ, func, desc ) should fail +ok 418 - hasnt_cast( src, targ, func, desc ) should have the proper description +ok 419 - hasnt_cast( src, targ, func, desc ) should have the proper diagnostics +ok 420 - hasnt_cast( src, targ, func) should fail +ok 421 - hasnt_cast( src, targ, func) should have the proper description +ok 422 - hasnt_cast( src, targ, func) should have the proper diagnostics +ok 423 - hasnt_cast( src, targ, desc ) should fail +ok 424 - hasnt_cast( src, targ, desc ) should have the proper description +ok 425 - hasnt_cast( src, targ, desc ) should have the proper diagnostics +ok 426 - hasnt_cast( src, targ ) should fail +ok 427 - hasnt_cast( src, targ ) should have the proper description +ok 428 - hasnt_cast( src, targ ) should have the proper diagnostics +ok 429 - hasnt_cast( src, targ, schema, func, desc) fail should pass +ok 430 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper description +ok 431 - hasnt_cast( src, targ, schema, func, desc) fail should have the proper diagnostics +ok 432 - hasnt_cast( src, targ, func, desc ) fail should pass +ok 433 - hasnt_cast( src, targ, func, desc ) fail should have the proper description +ok 434 - hasnt_cast( src, targ, func, desc ) fail should have the proper diagnostics +ok 435 - hasnt_cast( src, targ, desc ) fail should pass +ok 436 - hasnt_cast( src, targ, desc ) fail should have the proper description +ok 437 - hasnt_cast( src, targ, desc ) fail should have the proper diagnostics +ok 438 - cast_context_is( src, targ, context, desc ) should pass +ok 439 - cast_context_is( src, targ, context, desc ) should have the proper description +ok 440 - cast_context_is( src, targ, context, desc ) should have the proper diagnostics +ok 441 - cast_context_is( src, targ, context ) should pass +ok 442 - cast_context_is( src, targ, context ) should have the proper description +ok 443 - cast_context_is( src, targ, context ) should have the proper diagnostics +ok 444 - cast_context_is( src, targ, i, desc ) should pass +ok 445 - cast_context_is( src, targ, i, desc ) should have the proper description +ok 446 - cast_context_is( src, targ, i, desc ) should have the proper diagnostics +ok 447 - cast_context_is( src, targ, IMPL, desc ) should pass +ok 448 - cast_context_is( src, targ, IMPL, desc ) should have the proper description +ok 449 - cast_context_is( src, targ, IMPL, desc ) should have the proper diagnostics +ok 450 - cast_context_is( src, targ, assignment, desc ) should pass +ok 451 - cast_context_is( src, targ, assignment, desc ) should have the proper description +ok 452 - cast_context_is( src, targ, assignment, desc ) should have the proper diagnostics +ok 453 - cast_context_is( src, targ, a, desc ) should pass +ok 454 - cast_context_is( src, targ, a, desc ) should have the proper description +ok 455 - cast_context_is( src, targ, a, desc ) should have the proper diagnostics +ok 456 - cast_context_is( src, targ, ASS, desc ) should pass +ok 457 - cast_context_is( src, targ, ASS, desc ) should have the proper description +ok 458 - cast_context_is( src, targ, ASS, desc ) should have the proper diagnostics +ok 459 - cast_context_is( src, targ, explicit, desc ) should pass +ok 460 - cast_context_is( src, targ, explicit, desc ) should have the proper description +ok 461 - cast_context_is( src, targ, explicit, desc ) should have the proper diagnostics +ok 462 - cast_context_is( src, targ, e, desc ) should pass +ok 463 - cast_context_is( src, targ, e, desc ) should have the proper description +ok 464 - cast_context_is( src, targ, e, desc ) should have the proper diagnostics +ok 465 - cast_context_is( src, targ, EX, desc ) should pass +ok 466 - cast_context_is( src, targ, EX, desc ) should have the proper description +ok 467 - cast_context_is( src, targ, EX, desc ) should have the proper diagnostics +ok 468 - cast_context_is( src, targ, context, desc ) fail should fail +ok 469 - cast_context_is( src, targ, context, desc ) fail should have the proper description +ok 470 - cast_context_is( src, targ, context, desc ) fail should have the proper diagnostics +ok 471 - cast_context_is( src, targ, context ) fail should fail +ok 472 - cast_context_is( src, targ, context ) fail should have the proper description +ok 473 - cast_context_is( src, targ, context ) fail should have the proper diagnostics +ok 474 - cast_context_is( src, targ, context, desc ) noexist should fail +ok 475 - cast_context_is( src, targ, context, desc ) noexist should have the proper description +ok 476 - cast_context_is( src, targ, context, desc ) noexist should have the proper diagnostics +ok 477 - has_operator( left, schema, name, right, result, desc ) should pass +ok 478 - has_operator( left, schema, name, right, result, desc ) should have the proper description +ok 479 - has_operator( left, schema, name, right, result, desc ) should have the proper diagnostics +ok 480 - has_operator( left, schema, name, right, result ) should pass +ok 481 - has_operator( left, schema, name, right, result ) should have the proper description +ok 482 - has_operator( left, schema, name, right, result ) should have the proper diagnostics +ok 483 - has_operator( left, name, right, result, desc ) should pass +ok 484 - has_operator( left, name, right, result, desc ) should have the proper description +ok 485 - has_operator( left, name, right, result, desc ) should have the proper diagnostics +ok 486 - has_operator( left, name, right, result ) should pass +ok 487 - has_operator( left, name, right, result ) should have the proper description +ok 488 - has_operator( left, name, right, result ) should have the proper diagnostics +ok 489 - has_operator( left, name, right, desc ) should pass +ok 490 - has_operator( left, name, right, desc ) should have the proper description +ok 491 - has_operator( left, name, right, desc ) should have the proper diagnostics +ok 492 - has_operator( left, name, right ) should pass +ok 493 - has_operator( left, name, right ) should have the proper description +ok 494 - has_operator( left, name, right ) should have the proper diagnostics +ok 495 - has_operator( left, schema, name, right, result, desc ) fail should fail +ok 496 - has_operator( left, schema, name, right, result, desc ) fail should have the proper description +ok 497 - has_operator( left, schema, name, right, result, desc ) fail should have the proper diagnostics +ok 498 - has_operator( left, schema, name, right, result ) fail should fail +ok 499 - has_operator( left, schema, name, right, result ) fail should have the proper description +ok 500 - has_operator( left, schema, name, right, result ) fail should have the proper diagnostics +ok 501 - has_operator( left, name, right, result, desc ) fail should fail +ok 502 - has_operator( left, name, right, result, desc ) fail should have the proper description +ok 503 - has_operator( left, name, right, result, desc ) fail should have the proper diagnostics +ok 504 - has_operator( left, name, right, result ) fail should fail +ok 505 - has_operator( left, name, right, result ) fail should have the proper description +ok 506 - has_operator( left, name, right, result ) fail should have the proper diagnostics +ok 507 - has_operator( left, name, right, desc ) fail should fail +ok 508 - has_operator( left, name, right, desc ) fail should have the proper description +ok 509 - has_operator( left, name, right, desc ) fail should have the proper diagnostics +ok 510 - has_operator( left, name, right ) fail should fail +ok 511 - has_operator( left, name, right ) fail should have the proper description +ok 512 - has_operator( left, name, right ) fail should have the proper diagnostics +ok 513 - has_leftop( schema, name, right, result, desc ) should pass +ok 514 - has_leftop( schema, name, right, result, desc ) should have the proper description +ok 515 - has_leftop( schema, name, right, result, desc ) should have the proper diagnostics +ok 516 - has_leftop( schema, name, right, result ) should pass +ok 517 - has_leftop( schema, name, right, result ) should have the proper description +ok 518 - has_leftop( schema, name, right, result ) should have the proper diagnostics +ok 519 - has_leftop( name, right, result, desc ) should pass +ok 520 - has_leftop( name, right, result, desc ) should have the proper description +ok 521 - has_leftop( name, right, result, desc ) should have the proper diagnostics +ok 522 - has_leftop( name, right, result ) should pass +ok 523 - has_leftop( name, right, result ) should have the proper description +ok 524 - has_leftop( name, right, result ) should have the proper diagnostics +ok 525 - has_leftop( name, right, desc ) should pass +ok 526 - has_leftop( name, right, desc ) should have the proper description +ok 527 - has_leftop( name, right, desc ) should have the proper diagnostics +ok 528 - has_leftop( name, right ) should pass +ok 529 - has_leftop( name, right ) should have the proper description +ok 530 - has_leftop( name, right ) should have the proper diagnostics +ok 531 - has_leftop( schema, name, right, result, desc ) fail should fail +ok 532 - has_leftop( schema, name, right, result, desc ) fail should have the proper description +ok 533 - has_leftop( schema, name, right, result, desc ) fail should have the proper diagnostics +ok 534 - has_leftop( schema, name, right, result ) fail should fail +ok 535 - has_leftop( schema, name, right, result ) fail should have the proper description +ok 536 - has_leftop( schema, name, right, result ) fail should have the proper diagnostics +ok 537 - has_leftop( name, right, result, desc ) fail should fail +ok 538 - has_leftop( name, right, result, desc ) fail should have the proper description +ok 539 - has_leftop( name, right, result, desc ) fail should have the proper diagnostics +ok 540 - has_leftop( name, right, result ) fail should fail +ok 541 - has_leftop( name, right, result ) fail should have the proper description +ok 542 - has_leftop( name, right, result ) fail should have the proper diagnostics +ok 543 - has_leftop( name, right, desc ) fail should fail +ok 544 - has_leftop( name, right, desc ) fail should have the proper description +ok 545 - has_leftop( name, right, desc ) fail should have the proper diagnostics +ok 546 - has_leftop( name, right ) fail should fail +ok 547 - has_leftop( name, right ) fail should have the proper description +ok 548 - has_leftop( name, right ) fail should have the proper diagnostics +ok 549 - has_rightop( left, schema, name, result, desc ) should pass +ok 550 - has_rightop( left, schema, name, result, desc ) should have the proper description +ok 551 - has_rightop( left, schema, name, result, desc ) should have the proper diagnostics +ok 552 - has_rightop( left, schema, name, result ) should pass +ok 553 - has_rightop( left, schema, name, result ) should have the proper description +ok 554 - has_rightop( left, schema, name, result ) should have the proper diagnostics +ok 555 - has_rightop( left, name, result, desc ) should pass +ok 556 - has_rightop( left, name, result, desc ) should have the proper description +ok 557 - has_rightop( left, name, result, desc ) should have the proper diagnostics +ok 558 - has_rightop( left, name, result ) should pass +ok 559 - has_rightop( left, name, result ) should have the proper description +ok 560 - has_rightop( left, name, result ) should have the proper diagnostics +ok 561 - has_rightop( left, name, desc ) should pass +ok 562 - has_rightop( left, name, desc ) should have the proper description +ok 563 - has_rightop( left, name, desc ) should have the proper diagnostics +ok 564 - has_rightop( left, name ) should pass +ok 565 - has_rightop( left, name ) should have the proper description +ok 566 - has_rightop( left, name ) should have the proper diagnostics +ok 567 - has_rightop( left, schema, name, result, desc ) fail should fail +ok 568 - has_rightop( left, schema, name, result, desc ) fail should have the proper description +ok 569 - has_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics +ok 570 - has_rightop( left, schema, name, result ) fail should fail +ok 571 - has_rightop( left, schema, name, result ) fail should have the proper description +ok 572 - has_rightop( left, schema, name, result ) fail should have the proper diagnostics +ok 573 - has_rightop( left, name, result, desc ) fail should fail +ok 574 - has_rightop( left, name, result, desc ) fail should have the proper description +ok 575 - has_rightop( left, name, result, desc ) fail should have the proper diagnostics +ok 576 - has_rightop( left, name, result ) fail should fail +ok 577 - has_rightop( left, name, result ) fail should have the proper description +ok 578 - has_rightop( left, name, result ) fail should have the proper diagnostics +ok 579 - has_rightop( left, name, desc ) fail should fail +ok 580 - has_rightop( left, name, desc ) fail should have the proper description +ok 581 - has_rightop( left, name, desc ) fail should have the proper diagnostics +ok 582 - has_rightop( left, name ) fail should fail +ok 583 - has_rightop( left, name ) fail should have the proper description +ok 584 - has_rightop( left, name ) fail should have the proper diagnostics +ok 585 - has_language(language) should pass +ok 586 - has_language(language) should have the proper description +ok 587 - has_language(language) should have the proper diagnostics +ok 588 - has_language(language, desc) should pass +ok 589 - has_language(language, desc) should have the proper description +ok 590 - has_language(language, desc) should have the proper diagnostics +ok 591 - has_language(nonexistent language) should fail +ok 592 - has_language(nonexistent language) should have the proper description +ok 593 - has_language(nonexistent language) should have the proper diagnostics +ok 594 - has_language(nonexistent language, desc) should fail +ok 595 - has_language(nonexistent language, desc) should have the proper description +ok 596 - has_language(nonexistent language, desc) should have the proper diagnostics +ok 597 - hasnt_language(language) should fail +ok 598 - hasnt_language(language) should have the proper description +ok 599 - hasnt_language(language) should have the proper diagnostics +ok 600 - hasnt_language(language, desc) should fail +ok 601 - hasnt_language(language, desc) should have the proper description +ok 602 - hasnt_language(language, desc) should have the proper diagnostics +ok 603 - hasnt_language(nonexistent language) should pass +ok 604 - hasnt_language(nonexistent language) should have the proper description +ok 605 - hasnt_language(nonexistent language) should have the proper diagnostics +ok 606 - hasnt_language(nonexistent language, desc) should pass +ok 607 - hasnt_language(nonexistent language, desc) should have the proper description +ok 608 - hasnt_language(nonexistent language, desc) should have the proper diagnostics +ok 609 - language_is_trusted(language, desc) should pass +ok 610 - language_is_trusted(language, desc) should have the proper description +ok 611 - language_is_trusted(language, desc) should have the proper diagnostics +ok 612 - language_is_trusted(language) should pass +ok 613 - language_is_trusted(language) should have the proper description +ok 614 - language_is_trusted(language) should have the proper diagnostics +ok 615 - language_is_trusted(language, desc) fail should fail +ok 616 - language_is_trusted(language, desc) fail should have the proper description +ok 617 - language_is_trusted(language, desc) fail should have the proper diagnostics +ok 618 - language_is_trusted(language, desc) non-existent should fail +ok 619 - language_is_trusted(language, desc) non-existent should have the proper description +ok 620 - language_is_trusted(language, desc) non-existent should have the proper diagnostics +ok 621 - has_opclass( schema, name, desc ) should pass +ok 622 - has_opclass( schema, name, desc ) should have the proper description +ok 623 - has_opclass( schema, name, desc ) should have the proper diagnostics +ok 624 - has_opclass( schema, name ) should pass +ok 625 - has_opclass( schema, name ) should have the proper description +ok 626 - has_opclass( schema, name ) should have the proper diagnostics +ok 627 - has_opclass( name, desc ) should pass +ok 628 - has_opclass( name, desc ) should have the proper description +ok 629 - has_opclass( name, desc ) should have the proper diagnostics +ok 630 - has_opclass( name ) should pass +ok 631 - has_opclass( name ) should have the proper description +ok 632 - has_opclass( name ) should have the proper diagnostics +ok 633 - has_opclass( schema, name, desc ) fail should fail +ok 634 - has_opclass( schema, name, desc ) fail should have the proper description +ok 635 - has_opclass( schema, name, desc ) fail should have the proper diagnostics +ok 636 - has_opclass( name, desc ) fail should fail +ok 637 - has_opclass( name, desc ) fail should have the proper description +ok 638 - has_opclass( name, desc ) fail should have the proper diagnostics +ok 639 - hasnt_opclass( schema, name, desc ) should fail +ok 640 - hasnt_opclass( schema, name, desc ) should have the proper description +ok 641 - hasnt_opclass( schema, name, desc ) should have the proper diagnostics +ok 642 - hasnt_opclass( schema, name ) should fail +ok 643 - hasnt_opclass( schema, name ) should have the proper description +ok 644 - hasnt_opclass( schema, name ) should have the proper diagnostics +ok 645 - hasnt_opclass( name, desc ) should fail +ok 646 - hasnt_opclass( name, desc ) should have the proper description +ok 647 - hasnt_opclass( name, desc ) should have the proper diagnostics +ok 648 - hasnt_opclass( name ) should fail +ok 649 - hasnt_opclass( name ) should have the proper description +ok 650 - hasnt_opclass( name ) should have the proper diagnostics +ok 651 - hasnt_opclass( schema, name, desc ) fail should pass +ok 652 - hasnt_opclass( schema, name, desc ) fail should have the proper description +ok 653 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnostics +ok 654 - hasnt_opclass( name, desc ) fail should pass +ok 655 - hasnt_opclass( name, desc ) fail should have the proper description +ok 656 - hasnt_opclass( name, desc ) fail should have the proper diagnostics +ok 657 - domain_type_is(schema, domain, schema, type, desc) should pass +ok 658 - domain_type_is(schema, domain, schema, type, desc) should have the proper description +ok 659 - domain_type_is(schema, domain, schema, type, desc) should have the proper diagnostics +ok 660 - domain_type_is(schema, domain, schema, type) should pass +ok 661 - domain_type_is(schema, domain, schema, type) should have the proper description +ok 662 - domain_type_is(schema, domain, schema, type) should have the proper diagnostics +ok 663 - domain_type_is(schema, domain, schema, type, desc) fail should fail +ok 664 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper description +ok 665 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 666 - domain_type_is(schema, nondomain, schema, type, desc) should fail +ok 667 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper description +ok 668 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 669 - domain_type_is(schema, type, schema, type, desc) fail should fail +ok 670 - domain_type_is(schema, type, schema, type, desc) fail should have the proper description +ok 671 - domain_type_is(schema, type, schema, type, desc) fail should have the proper diagnostics +ok 672 - domain_type_is(schema, domain, type, desc) should pass +ok 673 - domain_type_is(schema, domain, type, desc) should have the proper description +ok 674 - domain_type_is(schema, domain, type, desc) should have the proper diagnostics +ok 675 - domain_type_is(schema, domain, type) should pass +ok 676 - domain_type_is(schema, domain, type) should have the proper description +ok 677 - domain_type_is(schema, domain, type) should have the proper diagnostics +ok 678 - domain_type_is(schema, domain, type, desc) fail should fail +ok 679 - domain_type_is(schema, domain, type, desc) fail should have the proper description +ok 680 - domain_type_is(schema, domain, type, desc) fail should have the proper diagnostics +ok 681 - domain_type_is(schema, nondomain, type, desc) should fail +ok 682 - domain_type_is(schema, nondomain, type, desc) should have the proper description +ok 683 - domain_type_is(schema, nondomain, type, desc) should have the proper diagnostics +ok 684 - domain_type_is(schema, type, type, desc) fail should fail +ok 685 - domain_type_is(schema, type, type, desc) fail should have the proper description +ok 686 - domain_type_is(schema, type, type, desc) fail should have the proper diagnostics +ok 687 - domain_type_is(domain, type, desc) should pass +ok 688 - domain_type_is(domain, type, desc) should have the proper description +ok 689 - domain_type_is(domain, type, desc) should have the proper diagnostics +ok 690 - domain_type_is(domain, type) should pass +ok 691 - domain_type_is(domain, type) should have the proper description +ok 692 - domain_type_is(domain, type) should have the proper diagnostics +ok 693 - domain_type_is(domain, type, desc) fail should fail +ok 694 - domain_type_is(domain, type, desc) fail should have the proper description +ok 695 - domain_type_is(domain, type, desc) fail should have the proper diagnostics +ok 696 - domain_type_is(nondomain, type, desc) should fail +ok 697 - domain_type_is(nondomain, type, desc) should have the proper description +ok 698 - domain_type_is(nondomain, type, desc) should have the proper diagnostics +ok 699 - domain_type_is(type, type, desc) fail should fail +ok 700 - domain_type_is(type, type, desc) fail should have the proper description +ok 701 - domain_type_is(type, type, desc) fail should have the proper diagnostics +ok 702 - domain_type_isnt(schema, domain, schema, type, desc) should pass +ok 703 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper description +ok 704 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper diagnostics +ok 705 - domain_type_isnt(schema, domain, schema, type) should pass +ok 706 - domain_type_isnt(schema, domain, schema, type) should have the proper description +ok 707 - domain_type_isnt(schema, domain, schema, type) should have the proper diagnostics +ok 708 - domain_type_isnt(schema, domain, schema, type, desc) fail should fail +ok 709 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper description +ok 710 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 711 - domain_type_isnt(schema, nondomain, schema, type, desc) should fail +ok 712 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper description +ok 713 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 714 - domain_type_isnt(schema, type, schema, type, desc) should fail +ok 715 - domain_type_isnt(schema, type, schema, type, desc) should have the proper description +ok 716 - domain_type_isnt(schema, type, schema, type, desc) should have the proper diagnostics +ok 717 - domain_type_isnt(schema, domain, type, desc) should pass +ok 718 - domain_type_isnt(schema, domain, type, desc) should have the proper description +ok 719 - domain_type_isnt(schema, domain, type, desc) should have the proper diagnostics +ok 720 - domain_type_isnt(schema, domain, type) should pass +ok 721 - domain_type_isnt(schema, domain, type) should have the proper description +ok 722 - domain_type_isnt(schema, domain, type) should have the proper diagnostics +ok 723 - domain_type_isnt(schema, domain, type, desc) fail should fail +ok 724 - domain_type_isnt(schema, domain, type, desc) fail should have the proper description +ok 725 - domain_type_isnt(schema, domain, type, desc) fail should have the proper diagnostics +ok 726 - domain_type_isnt(schema, nondomain, type, desc) should fail +ok 727 - domain_type_isnt(schema, nondomain, type, desc) should have the proper description +ok 728 - domain_type_isnt(schema, nondomain, type, desc) should have the proper diagnostics +ok 729 - domain_type_isnt(schema, type, type, desc) should fail +ok 730 - domain_type_isnt(schema, type, type, desc) should have the proper description +ok 731 - domain_type_isnt(schema, type, type, desc) should have the proper diagnostics +ok 732 - domain_type_isnt(domain, type, desc) should pass +ok 733 - domain_type_isnt(domain, type, desc) should have the proper description +ok 734 - domain_type_isnt(domain, type, desc) should have the proper diagnostics +ok 735 - domain_type_isnt(domain, type) should pass +ok 736 - domain_type_isnt(domain, type) should have the proper description +ok 737 - domain_type_isnt(domain, type) should have the proper diagnostics +ok 738 - domain_type_isnt(domain, type, desc) fail should fail +ok 739 - domain_type_isnt(domain, type, desc) fail should have the proper description +ok 740 - domain_type_isnt(domain, type, desc) fail should have the proper diagnostics +ok 741 - domain_type_isnt(nondomain, type, desc) should fail +ok 742 - domain_type_isnt(nondomain, type, desc) should have the proper description +ok 743 - domain_type_isnt(nondomain, type, desc) should have the proper diagnostics +ok 744 - domain_type_isnt(type, type, desc) should fail +ok 745 - domain_type_isnt(type, type, desc) should have the proper description +ok 746 - domain_type_isnt(type, type, desc) should have the proper diagnostics +ok 747 - has_relation(non-existent relation) should fail +ok 748 - has_relation(non-existent relation) should have the proper description +ok 749 - has_relation(non-existent relation) should have the proper diagnostics +ok 750 - has_relation(non-existent schema, tab) should fail +ok 751 - has_relation(non-existent schema, tab) should have the proper description +ok 752 - has_relation(non-existent schema, tab) should have the proper diagnostics +ok 753 - has_relation(sch, non-existent relation, desc) should fail +ok 754 - has_relation(sch, non-existent relation, desc) should have the proper description +ok 755 - has_relation(sch, non-existent relation, desc) should have the proper diagnostics +ok 756 - has_relation(tab, desc) should pass +ok 757 - has_relation(tab, desc) should have the proper description +ok 758 - has_relation(tab, desc) should have the proper diagnostics +ok 759 - has_relation(sch, tab, desc) should pass +ok 760 - has_relation(sch, tab, desc) should have the proper description +ok 761 - has_relation(sch, tab, desc) should have the proper diagnostics +ok 762 - has_relation(sch, view, desc) should pass +ok 763 - has_relation(sch, view, desc) should have the proper description +ok 764 - has_relation(sch, view, desc) should have the proper diagnostics +ok 765 - has_relation(type, desc) should pass +ok 766 - has_relation(type, desc) should have the proper description +ok 767 - has_relation(type, desc) should have the proper diagnostics +ok 768 - hasnt_relation(non-existent relation) should pass +ok 769 - hasnt_relation(non-existent relation) should have the proper description +ok 770 - hasnt_relation(non-existent relation) should have the proper diagnostics +ok 771 - hasnt_relation(non-existent schema, tab) should pass +ok 772 - hasnt_relation(non-existent schema, tab) should have the proper description +ok 773 - hasnt_relation(non-existent schema, tab) should have the proper diagnostics +ok 774 - hasnt_relation(sch, non-existent tab, desc) should pass +ok 775 - hasnt_relation(sch, non-existent tab, desc) should have the proper description +ok 776 - hasnt_relation(sch, non-existent tab, desc) should have the proper diagnostics +ok 777 - hasnt_relation(tab, desc) should fail +ok 778 - hasnt_relation(tab, desc) should have the proper description +ok 779 - hasnt_relation(tab, desc) should have the proper diagnostics +ok 780 - hasnt_relation(sch, tab, desc) should fail +ok 781 - hasnt_relation(sch, tab, desc) should have the proper description +ok 782 - hasnt_relation(sch, tab, desc) should have the proper diagnostics +ok 783 - has_foreign_table(non-existent table) should fail +ok 784 - has_foreign_table(non-existent table) should have the proper description +ok 785 - has_foreign_table(non-existent table) should have the proper diagnostics +ok 786 - has_foreign_table(non-existent schema, tab) should fail +ok 787 - has_foreign_table(non-existent schema, tab) should have the proper description +ok 788 - has_foreign_table(non-existent schema, tab) should have the proper diagnostics +ok 789 - has_foreign_table(non-existent table, desc) should fail +ok 790 - has_foreign_table(non-existent table, desc) should have the proper description +ok 791 - has_foreign_table(non-existent table, desc) should have the proper diagnostics +ok 792 - has_foreign_table(sch, non-existent table, desc) should fail +ok 793 - has_foreign_table(sch, non-existent table, desc) should have the proper description +ok 794 - has_foreign_table(sch, non-existent table, desc) should have the proper diagnostics +ok 795 - has_foreign_table(tab, desc) should pass +ok 796 - has_foreign_table(tab, desc) should have the proper description +ok 797 - has_foreign_table(tab, desc) should have the proper diagnostics +ok 798 - has_foreign_table(sch, tab, desc) should pass +ok 799 - has_foreign_table(sch, tab, desc) should have the proper description +ok 800 - has_foreign_table(sch, tab, desc) should have the proper diagnostics +ok 801 - has_foreign_table(sch, view, desc) should fail +ok 802 - has_foreign_table(sch, view, desc) should have the proper description +ok 803 - has_foreign_table(sch, view, desc) should have the proper diagnostics +ok 804 - has_foreign_table(type, desc) should fail +ok 805 - has_foreign_table(type, desc) should have the proper description +ok 806 - has_foreign_table(type, desc) should have the proper diagnostics +ok 807 - hasnt_foreign_table(non-existent table) should pass +ok 808 - hasnt_foreign_table(non-existent table) should have the proper description +ok 809 - hasnt_foreign_table(non-existent table) should have the proper diagnostics +ok 810 - hasnt_foreign_table(non-existent schema, tab) should pass +ok 811 - hasnt_foreign_table(non-existent schema, tab) should have the proper description +ok 812 - hasnt_foreign_table(non-existent schema, tab) should have the proper diagnostics +ok 813 - hasnt_foreign_table(non-existent table, desc) should pass +ok 814 - hasnt_foreign_table(non-existent table, desc) should have the proper description +ok 815 - hasnt_foreign_table(non-existent table, desc) should have the proper diagnostics +ok 816 - hasnt_foreign_table(sch, non-existent tab, desc) should pass +ok 817 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper description +ok 818 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper diagnostics +ok 819 - hasnt_foreign_table(tab, desc) should fail +ok 820 - hasnt_foreign_table(tab, desc) should have the proper description +ok 821 - hasnt_foreign_table(tab, desc) should have the proper diagnostics +ok 822 - hasnt_foreign_table(sch, tab, desc) should fail +ok 823 - hasnt_foreign_table(sch, tab, desc) should have the proper description +ok 824 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics +ok 825 - has_materialized_view(non-existent materialized_view) should fail +ok 826 - has_materialized_view(non-existent materialized_view) should have the proper description +ok 827 - has_materialized_view(non-existent materialized_view) should have the proper diagnostics +ok 828 - has_materialized_view(non-existent materialized_view, desc) should fail +ok 829 - has_materialized_view(non-existent materialized_view, desc) should have the proper description +ok 830 - has_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics +ok 831 - has_materialized_view(sch, non-existent materialized_view, desc) should fail +ok 832 - has_materialized_view(sch, non-existent materialized_view, desc) should have the proper description +ok 833 - has_materialized_view(sch, non-existent materialized_view, desc) should have the proper diagnostics +ok 834 - has_materialized_view(materialized_view, desc) should pass +ok 835 - has_materialized_view(materialized_view, desc) should have the proper description +ok 836 - has_materialized_view(materialized_view, desc) should have the proper diagnostics +ok 837 - has_materialized_view(sch, materialized_view, desc) should pass +ok 838 - has_materialized_view(sch, materialized_view, desc) should have the proper description +ok 839 - has_materialized_view(sch, materialized_view, desc) should have the proper diagnostics +ok 840 - hasnt_materialized_view(non-existent materialized_view) should pass +ok 841 - hasnt_materialized_view(non-existent materialized_view) should have the proper description +ok 842 - hasnt_materialized_view(non-existent materialized_view) should have the proper diagnostics +ok 843 - hasnt_materialized_view(non-existent materialized_view, desc) should pass +ok 844 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper description +ok 845 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics +ok 846 - hasnt_materialized_view(sch, non-existent materialized_view, desc) should pass +ok 847 - hasnt_materialized_view(sch, non-existent materialized_view, desc) should have the proper description +ok 848 - hasnt_materialized_view(sch, non-existent materialized_view, desc) should have the proper diagnostics +ok 849 - hasnt_materialized_view(materialized_view, desc) should fail +ok 850 - hasnt_materialized_view(materialized_view, desc) should have the proper description +ok 851 - hasnt_materialized_view(materialized_view, desc) should have the proper diagnostics +ok 852 - hasnt_materialized_view(sch, materialized_view, desc) should fail +ok 853 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper description +ok 854 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper diagnostics +ok 855 - is_partitioned(non-existent part) should fail +ok 856 - is_partitioned(non-existent part) should have the proper description +ok 857 - is_partitioned(non-existent part) should have the proper diagnostics +ok 858 - is_partitioned(non-existent part, desc) should fail +ok 859 - is_partitioned(non-existent part, desc) should have the proper description +ok 860 - is_partitioned(non-existent part, desc) should have the proper diagnostics +ok 861 - is_partitioned(sch, non-existent part, desc) should fail +ok 862 - is_partitioned(sch, non-existent part, desc) should have the proper description +ok 863 - is_partitioned(sch, non-existent part, desc) should have the proper diagnostics +ok 864 - is_partitioned(sch, part, desc) should pass +ok 865 - is_partitioned(sch, part, desc) should have the proper description +ok 866 - is_partitioned(sch, part, desc) should have the proper diagnostics +ok 867 - is_partitioned(sch, part) should pass +ok 868 - is_partitioned(sch, part) should have the proper description +ok 869 - is_partitioned(sch, part) should have the proper diagnostics +ok 870 - is_partitioned(part, desc) should pass +ok 871 - is_partitioned(part, desc) should have the proper description +ok 872 - is_partitioned(part, desc) should have the proper diagnostics +ok 873 - is_partitioned(part) should pass +ok 874 - is_partitioned(part) should have the proper description +ok 875 - is_partitioned(part) should have the proper diagnostics +ok 876 - isnt_partitioned(non-existent part) should pass +ok 877 - isnt_partitioned(non-existent part) should have the proper description +ok 878 - isnt_partitioned(non-existent part) should have the proper diagnostics +ok 879 - isnt_partitioned(non-existent part, desc) should pass +ok 880 - isnt_partitioned(non-existent part, desc) should have the proper description +ok 881 - isnt_partitioned(non-existent part, desc) should have the proper diagnostics +ok 882 - isnt_partitioned(sch, non-existent part, desc) should pass +ok 883 - isnt_partitioned(sch, non-existent part, desc) should have the proper description +ok 884 - isnt_partitioned(sch, non-existent part, desc) should have the proper diagnostics +ok 885 - isnt_partitioned(sch, part, desc) should fail +ok 886 - isnt_partitioned(sch, part, desc) should have the proper description +ok 887 - isnt_partitioned(sch, part, desc) should have the proper diagnostics +ok 888 - isnt_partitioned(sch, part) should fail +ok 889 - isnt_partitioned(sch, part) should have the proper description +ok 890 - isnt_partitioned(sch, part) should have the proper diagnostics +ok 891 - isnt_partitioned(part, desc) should fail +ok 892 - isnt_partitioned(part, desc) should have the proper description +ok 893 - isnt_partitioned(part, desc) should have the proper diagnostics +ok 894 - isnt_partitioned(part) should fail +ok 895 - isnt_partitioned(part) should have the proper description +ok 896 - isnt_partitioned(part) should have the proper diagnostics diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index 8d55f652a5f0..344819056417 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(884); +SELECT plan(896); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -48,8 +48,8 @@ CREATE DOMAIN public."myDomain" AS TEXT CHECK(TRUE); CREATE SEQUENCE public.someseq; -CREATE SCHEMA someschema; +CREATE SCHEMA someschema; RESET client_min_messages; /****************************************************************************/ @@ -375,6 +375,22 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + has_view( 'information_schema', 'tables'::name ), + true, + 'has_view(sch, view)', + 'View information_schema.tables should exist', + '' +); + +SELECT * FROM check_test( + has_view( 'foo', '__SDFSDFD__'::name ), + false, + 'has_view(sch, non-existent view, desc)', + 'View foo."__SDFSDFD__" should exist', + '' +); + /****************************************************************************/ -- Test hasnt_view(). @@ -418,6 +434,22 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + hasnt_view( 'information_schema', 'tables'::name ), + false, + 'hasnt_view(sch, view)', + 'View information_schema.tables should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_view( 'foo', '__SDFSDFD__'::name ), + true, + 'hasnt_view(sch, non-existent view)', + 'View foo."__SDFSDFD__" should not exist', + '' +); + /****************************************************************************/ -- Test has_sequence(). From d3403466face2145c5244f2ab6503c358835abe8 Mon Sep 17 00:00:00 2001 From: "Jim C. Nasby" Date: Fri, 17 Jan 2020 13:15:40 -0600 Subject: [PATCH 1085/1195] Add item to Changes now since I forgot to credit Godwottery in my merge commit. --- Changes | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Changes b/Changes index fce41be95f04..1f94bdd553b1 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,11 @@ Revision history for pgTAP ========================== +1.2.0 +-------------------------- +* Make description field optional in has_view() and hasnt_view() when + specifying schema (#230). Thanks to Godwottery for the patch! + 1.1.0 2019-11-25T19:05:38Z -------------------------- * Remove support for PostgreSQL older than 9.1. Note that some automated tests From a1676457dbea93ca06cc22c18c934e8f45a77160 Mon Sep 17 00:00:00 2001 From: "Jim C. Nasby" Date: Tue, 28 Jan 2020 15:51:58 -0600 Subject: [PATCH 1086/1195] Fix typo --- doc/pgtap.mmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 8a043b0ffe64..9d3dc8e99e48 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -597,7 +597,7 @@ check if `:have` *does not* match the given pattern. `:description` : A short description of the test. -Similar to `matches()`, `alike()` matches `:hve` against the SQL `LIKE` +Similar to `matches()`, `alike()` matches `:have` against the SQL `LIKE` pattern `:like`. `ialike()` matches case-insensitively. So this: From 50c2585aebc7e25bc1b76210fe7ae475c6c66f52 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 29 Mar 2020 17:07:29 -0400 Subject: [PATCH 1087/1195] Add support for date/time keywords For col_default_is, which already looked for other special keyword functions. Resolves #244. --- Changes | 3 +++ sql/pgtap--1.1.0--1.2.0.sql | 19 +++++++++++++++++++ sql/pgtap.sql.in | 4 ++-- test/expected/coltap.out | 17 ++++++++++++++++- test/sql/coltap.sql | 15 ++++++++++----- 5 files changed, 50 insertions(+), 8 deletions(-) diff --git a/Changes b/Changes index 1f94bdd553b1..904826b7915d 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,9 @@ Revision history for pgTAP -------------------------- * Make description field optional in has_view() and hasnt_view() when specifying schema (#230). Thanks to Godwottery for the patch! +* Added support for the date and time keywords `CURRENT_DATE`, `CURRENT_TIME`, + `CURRENT_TIMESTAMP`, `LOCALTIME`, and `LOCALTIMESTAMP` to `col_default_is()`. + Thanks to Kevin Brannen for the report (#244)! 1.1.0 2019-11-25T19:05:38Z -------------------------- diff --git a/sql/pgtap--1.1.0--1.2.0.sql b/sql/pgtap--1.1.0--1.2.0.sql index 32cdd15e470f..5bebdc100221 100644 --- a/sql/pgtap--1.1.0--1.2.0.sql +++ b/sql/pgtap--1.1.0--1.2.0.sql @@ -17,3 +17,22 @@ RETURNS TEXT AS $$ 'View ' || quote_ident($1) || '.' || quote_ident($2) || ' should not exist' ); $$ 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 9745d7ce798d..1e26f873e7c7 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1658,8 +1658,8 @@ RETURNS TEXT AS $$ DECLARE thing text; BEGIN - -- Function or special SQL syntax. - IF $1 ~ '^[^'']+[(]' OR $1 = ANY('{CURRENT_CATALOG,CURRENT_ROLE,CURRENT_SCHEMA,CURRENT_USER,SESSION_USER,USER}') THEN + -- 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; diff --git a/test/expected/coltap.out b/test/expected/coltap.out index 7f84102b4a33..73beed97435b 100644 --- a/test/expected/coltap.out +++ b/test/expected/coltap.out @@ -1,5 +1,5 @@ \unset ECHO -1..228 +1..243 ok 1 - col_not_null( sch, tab, col, desc ) should pass ok 2 - col_not_null( sch, tab, col, desc ) should have the proper description ok 3 - col_not_null( sch, tab, col, desc ) should have the proper diagnostics @@ -228,3 +228,18 @@ ok 225 - col_default_is( tab, col, SESSION_USER ) should have the proper diagnos ok 226 - col_default_is( tab, col, USER ) should pass ok 227 - col_default_is( tab, col, USER ) should have the proper description ok 228 - col_default_is( tab, col, USER ) should have the proper diagnostics +ok 229 - col_default_is( tab, col, CURRENT_DATE ) should pass +ok 230 - col_default_is( tab, col, CURRENT_DATE ) should have the proper description +ok 231 - col_default_is( tab, col, CURRENT_DATE ) should have the proper diagnostics +ok 232 - col_default_is( tab, col, CURRENT_TIME ) should pass +ok 233 - col_default_is( tab, col, CURRENT_TIME ) should have the proper description +ok 234 - col_default_is( tab, col, CURRENT_TIME ) should have the proper diagnostics +ok 235 - col_default_is( tab, col, CURRENT_TIMESTAMP ) should pass +ok 236 - col_default_is( tab, col, CURRENT_TIMESTAMP ) should have the proper description +ok 237 - col_default_is( tab, col, CURRENT_TIMESTAMP ) should have the proper diagnostics +ok 238 - col_default_is( tab, col, LOCALTIME ) should pass +ok 239 - col_default_is( tab, col, LOCALTIME ) should have the proper description +ok 240 - col_default_is( tab, col, LOCALTIME ) should have the proper diagnostics +ok 241 - col_default_is( tab, col, LOCALTIMESTAMP ) should pass +ok 242 - col_default_is( tab, col, LOCALTIMESTAMP ) should have the proper description +ok 243 - col_default_is( tab, col, LOCALTIMESTAMP ) should have the proper diagnostics diff --git a/test/sql/coltap.sql b/test/sql/coltap.sql index d0c463f2be04..4b505a3a316b 100644 --- a/test/sql/coltap.sql +++ b/test/sql/coltap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(228); +SELECT plan(243); --SELECT * from no_plan(); CREATE TYPE public."myType" AS ( @@ -23,6 +23,11 @@ CREATE TABLE public.sometab( crole TEXT DEFAULT CURRENT_ROLE, csch TEXT DEFAULT CURRENT_SCHEMA, ccat TEXT DEFAULT CURRENT_CATALOG, + cdate DATE DEFAULT CURRENT_DATE, + ctime TIMETZ DEFAULT CURRENT_TIME, + ctstz TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + ltime TIME DEFAULT LOCALTIME, + ltstz TIMESTAMPTZ DEFAULT LOCALTIMESTAMP, plain INTEGER, camel "myType" ); @@ -693,16 +698,16 @@ SELECT * FROM check_test( -- Make sure that it works when the default is a reserved SQL expression. CREATE OR REPLACE FUNCTION ckreserve() RETURNS SETOF TEXT LANGUAGE PLPGSQL AS $$ DECLARE - funcs text[] := '{CURRENT_CATALOG,CURRENT_ROLE,CURRENT_SCHEMA,CURRENT_USER,SESSION_USER,USER}'; - cols TEXT[] := '{ccat,crole,csch,cuser,suser,auser}'; + funcs text[] := '{CURRENT_CATALOG,CURRENT_ROLE,CURRENT_SCHEMA,CURRENT_USER,SESSION_USER,USER,CURRENT_DATE,CURRENT_TIME,CURRENT_TIMESTAMP,LOCALTIME,LOCALTIMESTAMP}'; + cols TEXT[] := '{ccat,crole,csch,cuser,suser,auser,cdate,ctime,ctstz,ltime,ltstz}'; exp TEXT[] := funcs; tap record; last_index INTEGER; BEGIN last_index := array_upper(funcs, 1); IF pg_version_num() < 100000 THEN - -- Prior to PostgreSQL 10, these wer functions rendered with paretheses. - exp := ARRAY['current_database()','"current_user"()','"current_schema"()','"current_user"()','"session_user"()','"current_user"()']; + -- Prior to PostgreSQL 10, these were functions rendered with paretheses or as casts. + exp := ARRAY['current_database()','"current_user"()','"current_schema"()','"current_user"()','"session_user"()','"current_user"()','(''now''::text)::date','(''now''::text)::time with time zone','now()','(''now''::text)::time without time zone','(''now''::text)::timestamp without time zone']; END IF; FOR i IN 1..last_index LOOP From 99fdf949b8c3ea157fe078941c6e2af8c7dd7ae8 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 23 May 2020 12:24:04 -0400 Subject: [PATCH 1088/1195] Remove --load-language It has not been needed since 9.1, we support 9.1 and higher, and it has been removed from Postgres 13. Resolves #248. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 26e117156d30..b74520974a48 100644 --- a/Makefile +++ b/Makefile @@ -341,7 +341,7 @@ test: test-serial test-parallel TB_DIR = test/build GENERATED_SCHEDULE_DEPS = $(TB_DIR)/all_tests $(TB_DIR)/exclude_tests REGRESS = --schedule $(TB_DIR)/run.sch # Set this again just to be safe -REGRESS_OPTS = --inputdir=test --load-language=plpgsql --max-connections=$(PARALLEL_CONN) --schedule $(SETUP_SCH) $(REGRESS_CONF) +REGRESS_OPTS = --inputdir=test --max-connections=$(PARALLEL_CONN) --schedule $(SETUP_SCH) $(REGRESS_CONF) SETUP_SCH = test/schedule/main.sch # schedule to use for test setup; this can be forcibly changed by some targets! IGNORE_TESTS = $(notdir $(EXCLUDE_TEST_FILES:.sql=)) PARALLEL_TESTS = $(filter-out $(IGNORE_TESTS),$(filter-out $(SERIAL_TESTS),$(ALL_TESTS))) From 03636aebcd8354145ba7eada179e0f7ba751cd36 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 23 May 2020 13:02:26 -0400 Subject: [PATCH 1089/1195] Document need to quote index columns Due to the use of `pg_get_indexdef()`, which double-quotes them and we simply compare them. Resolves #247. --- Changes | 6 ++++++ doc/pgtap.mmd | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Changes b/Changes index 904826b7915d..38dccd727ab6 100644 --- a/Changes +++ b/Changes @@ -8,6 +8,12 @@ Revision history for pgTAP * Added support for the date and time keywords `CURRENT_DATE`, `CURRENT_TIME`, `CURRENT_TIMESTAMP`, `LOCALTIME`, and `LOCALTIMESTAMP` to `col_default_is()`. Thanks to Kevin Brannen for the report (#244)! +* Fixed failure to run tests on Postgres 13. Thanks to Christoph Berg for the + report (#248). +* Documented that mixed-case column names created with double quotes + must be double-quoted when passed to `has_index()` or `is_indexed()`, + sadly unlike other column arguments in pgTAP. Thanks to Keith Fiske for + the report (#247). 1.1.0 2019-11-25T19:05:38Z -------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 9d3dc8e99e48..c4cfc67192b5 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -3517,6 +3517,20 @@ incorrect, the diagnostics will look more like this: # have: "idx_baz" ON public.sometab(lower(name)) # want: "idx_baz" ON public.sometab(lower(lname)) +Note that unlike most other column parameter arguments in pgTAP, mixed-case +column mames crated with double-quotes must be double-quoted when passed +to `has_index()`, like so: + + SELECT has_index( + 'myschema', + 'sometable', + 'myindex', + ARRAY[ 'id', '"Name"', 'lower("foo-bar")' ] + ); + +This caveat applies only to column names, not to table and schema names, +which should omit double-quoting. + ### `hasnt_index()` ### SELECT hasnt_index( schema, table, index, description ); @@ -5092,6 +5106,15 @@ index on the named table. Effectively like `has_index()` except that it doesn't require an index name and does require one or more column names or expressions in the defined for the index. +Note that unlike most other column parameter arguments in pgTAP, mixed-case +column mames crated with double-quotes must be double-quoted when passed +to `is_indexed()`, like so: + + SELECT is_indexed( 'widgets', '"Name"' ); + +This caveat applies only to column names, not to table and schema names, +which should omit double-quoting. + ### `index_is_type()` ### SELECT index_is_type( :schema, :table, :index, :type, :description ); From 007ee2fc0ba3028e12b67e72c084845e021532d4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 31 May 2020 17:44:01 -0400 Subject: [PATCH 1090/1195] Quote current_user as an identifier Resolves #216. --- Changes | 2 ++ test/sql/aretap.sql | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 38dccd727ab6..302512b287d8 100644 --- a/Changes +++ b/Changes @@ -14,6 +14,8 @@ Revision history for pgTAP must be double-quoted when passed to `has_index()` or `is_indexed()`, sadly unlike other column arguments in pgTAP. Thanks to Keith Fiske for the report (#247). +* Fixed a test failure where the current username was not being quoted as an + identifier. Thanks to Matt DeLuco for the report (#216)! 1.1.0 2019-11-25T19:05:38Z -------------------------- diff --git a/test/sql/aretap.sql b/test/sql/aretap.sql index b8f3da040a20..8e2fe948aabc 100644 --- a/test/sql/aretap.sql +++ b/test/sql/aretap.sql @@ -728,7 +728,7 @@ SELECT * FROM check_test( 'users_are(users, desc) extras', 'whatever', ' Extra users: - ' || current_user + ' || quote_ident(current_user) ); SELECT * FROM check_test( @@ -737,7 +737,7 @@ SELECT * FROM check_test( 'users_are(users, desc) missing and extras', 'whatever', ' Extra users: - ' || current_user || ' + ' || quote_ident(current_user) || ' Missing users: __howdy__' ); From 8f8bb50fc8871dbbcf8dadd240069ae721678a7b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 7 Jun 2020 12:18:25 -0400 Subject: [PATCH 1091/1195] Fix the `col_not_null()` drop statements The code that creates sql/uninstall_pgtap.sql is in the target in the Makefile, and depends on the full function signature of the CREATE statement being on a single line. Resolves #252. --- Changes | 2 ++ sql/pgtap.sql.in | 20 ++++++++------------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Changes b/Changes index 302512b287d8..29317d78193c 100644 --- a/Changes +++ b/Changes @@ -16,6 +16,8 @@ Revision history for pgTAP the report (#247). * Fixed a test failure where the current username was not being quoted as an identifier. Thanks to Matt DeLuco for the report (#216)! +* Fixed the `col_not_null()` drop statements in the uninstall script. Thanks + to Kyle L. Jensen for the report (#252). 1.1.0 2019-11-25T19:05:38Z -------------------------- diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 1e26f873e7c7..4cda13c65810 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1382,33 +1382,29 @@ $$ 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 $$ +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 $$ +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 $$ +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 $$ +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; From 5bfd3c7ae7ea7392172b1e305e6e12a3a5524490 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sun, 25 Oct 2020 12:49:06 +0100 Subject: [PATCH 1092/1195] add pg13 to ci --- .travis.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index f8cec8370b6b..a148c4453abc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,8 @@ env: - UPGRADE_TO=10 PGVERSION=9.6 - UPGRADE_TO=11 PGVERSION=10 - UPGRADE_TO=12 PGVERSION=11 UPDATE_FROM=0.99.0 # Versions prior to 0.99.0 don't support Postgres 11 - - PGVERSION=12 UPDATE_FROM=0.99.0 + - UPGRADE_TO=13 PGVERSION=12 UPDATE_FROM=0.99.0 + - PGVERSION=13 UPDATE_FROM=0.99.0 # Duplication below is via s/-.*PGVERSION/- PARALLEL_CONN=1 PGVERSION/ - PARALLEL_CONN=1 PGVERSION=9.1 ALLOW_MISSING_EXTENSIONS=1 @@ -26,10 +27,11 @@ env: - PARALLEL_CONN=1 PGVERSION=10 - PARALLEL_CONN=1 PGVERSION=11 UPDATE_FROM=0.99.0 # Versions prior to 0.99.0 don't support Postgres 11 - PARALLEL_CONN=1 PGVERSION=12 UPDATE_FROM=0.99.0 + - PARALLEL_CONN=1 PGVERSION=13 UPDATE_FROM=0.99.0 # Also test pg_upgrade across many versions - - UPGRADE_TO=12 PGVERSION=9.1 ALLOW_MISSING_EXTENSIONS=1 - - UPGRADE_TO=12 PGVERSION=9.4 + - UPGRADE_TO=13 PGVERSION=9.1 ALLOW_MISSING_EXTENSIONS=1 + - UPGRADE_TO=13 PGVERSION=9.4 script: bash tools/pg-travis-test.sh # vi: noexpandtab ts=2 sw=2 From 608aca094c81faa626cf1f0740a02fd13551ef77 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 24 Oct 2020 17:03:55 -0400 Subject: [PATCH 1093/1195] Remove references to unsupported Postgres versions Remove all unnecessary references Postgres 9.0 and earlier, including conditional tests. Even the README and documentation file still referenced them quite a bit. --- README.md | 32 ++++----- contrib/pgtap.spec | 31 ++++---- doc/pgtap.mmd | 124 +++++++++++++------------------- release.md | 20 +++--- test/sql/cmpok.sql | 17 ----- test/sql/coltap.sql | 144 ++++++++++--------------------------- test/sql/istap.sql | 100 ++++++-------------------- test/sql/resultset.sql | 159 +++++++++++++---------------------------- test/sql/todotap.sql | 14 ++-- test/sql/valueset.sql | 129 +++++++++++---------------------- 10 files changed, 241 insertions(+), 529 deletions(-) diff --git a/README.md b/README.md index f6db0622f0ba..d6ef0dd32403 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ pgTAP 1.1.0 ============ -[pgTAP](http://pgtap.org) is a unit testing framework for PostgreSQL written +[pgTAP](https://pgtap.org) is a unit testing framework for PostgreSQL written in PL/pgSQL and PL/SQL. It includes a comprehensive collection of -[TAP](http://testanything.org)-emitting assertion functions, as well as the +[TAP](https://testanything.org)-emitting assertion functions, as well as the ability to integrate with other TAP-emitting test frameworks. It can also be used in the xUnit testing style. For detailed documentation, see the documentation in `doc/pgtap.mmd` or -[online](http://pgtap.org/documentation.html "Complete pgTAP Documentation"). +[online](https://pgtap.org/documentation.html "Complete pgTAP Documentation"). [![PGXN version](https://badge.fury.io/pg/pgtap.svg)](https://badge.fury.io/pg/pgtap) [![Build Status](https://travis-ci.org/theory/pgtap.png)](https://travis-ci.org/theory/pgtap) @@ -35,7 +35,7 @@ If you encounter an error such as: Or: - Makefile:52: *** pgTAP requires PostgreSQL 8.1 or later. This is . Stop. + Makefile:52: *** pgTAP requires PostgreSQL 9.1 or later. This is . Stop. Be sure that you have `pg_config` installed and in your path. If you used a package management system such as RPM to install PostgreSQL, be sure that the @@ -44,9 +44,9 @@ to find it: env PG_CONFIG=/path/to/pg_config make && make install && make installcheck -And finally, if all that fails (and if you're on PostgreSQL 8.1, it likely -will), copy the entire distribution directory to the `contrib/` subdirectory -of the PostgreSQL source tree and try it there without `pg_config`: +And finally, if all that fails, copy the entire distribution directory to the +`contrib/` subdirectory of the PostgreSQL source tree and try it there without +`pg_config`: env NO_PGXS=1 make && make install && make installcheck @@ -59,9 +59,8 @@ You need to run the test suite using a super user, such as the default make installcheck PGUSER=postgres -Once pgTAP is installed, you can add it to a database. If you're running -PostgreSQL 9.1.0 or greater, it's a simple as connecting to a database as a -super user and running: +Once pgTAP is installed, you can add it to a database by connecting as a super +user and running: CREATE EXTENSION pgtap; @@ -70,22 +69,21 @@ installed, you can upgrade it to a properly packaged extension with: CREATE EXTENSION pgtap FROM unpackaged; -For versions of PostgreSQL less than 9.1.0, you'll need to run the -installation script: - - psql -d mydb -f /path/to/pgsql/share/contrib/pgtap.sql - If you want to install pgTAP and all of its supporting objects into a specific schema, use the `PGOPTIONS` environment variable to specify the schema, like so: PGOPTIONS=--search_path=tap psql -d mydb -f pgTAP.sql +If you want to install pgTAP and all of its supporting objects into a specific +schema, use the `SCHEMA` clause to specify the schema, like so: + + CREATE EXTENSION pgtap SCHEMA tap; + Dependencies ------------ -pgTAP requires PostgreSQL 8.1 or higher, with 8.4 or higher recommended for -full use of its API. It also requires PL/pgSQL. +pgTAP requires PostgreSQL 9.1 or higher. Copyright and License --------------------- diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 1fb08be0f25e..91882f674599 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,11 +1,11 @@ -Summary: Unit testing suite for PostgreSQL -Name: pgtap -Version: 1.1.0 -Release: 1%{?dist} -Group: Applications/Databases -License: PostgreSQL -URL: http://pgtap.org/ -Source0: http://master.pgxn.org/dist/pgtap/%{version}/pgtap-%{version}.zip +Summary: Unit testing suite for PostgreSQL +Name: pgtap +Version: 1.1.0 +Release: 2%{?dist} +Group: Applications/Databases +License: PostgreSQL +URL: https://pgtap.org/ +Source0: https://master.pgxn.org/dist/pgtap/%{version}/pgtap-%{version}.zip BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root Provides: %{name} Provides: %{name}-core @@ -22,10 +22,6 @@ Requires: postgresql-server = %{postgresver}, perl-Test-Harness >= 3.0 Requires: perl(TAP::Parser::SourceHandler::pgTAP) BuildRequires: postgresql-devel = %{postgresver} -%if "%{postgresver}" != "8.4" -BuildArch: noarch -%endif - %prep %setup -q @@ -41,18 +37,19 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %files %defattr(-,root,root,-) -%if "%{postgresver}" == "8.3" -%{_libdir}/pgsql/pgtap.so -%endif %{_datadir}/pgsql/contrib/* %{_docdir}/pgsql/contrib/README.pgtap %changelog -* Mon Nov 25 2019 Jim Nasby 1.1.0 +* Sat Oct 24 2020 Jim Nasby 1.1.0-2 +- Remove last vestiges of pre-PostgreSQL 9.1 +- Use https for URLs + +* Mon Nov 25 2019 Jim Nasby 1.1.0-1 - Update to 1.1.0 - Remove support for PostgreSQL prior to 9.1 -* Thu Feb 21 2019 Jim Nasby 1.0.0 +* Thu Feb 21 2019 Jim Nasby 1.0.0-1 - Update to 1.0.0 * Sun Sep 16 2018 David E. Wheeler 0.99.0-1 diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index c4cfc67192b5..a2dbe594f10f 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -62,7 +62,7 @@ If you encounter an error such as: "Makefile", line 8: Need an operator You need to use GNU make, which may well be installed on your system as -'gmake': +`gmake`: gmake gmake install @@ -74,7 +74,7 @@ If you encounter an error such as: Or: - Makefile:52: *** pgTAP requires PostgreSQL 8.1 or later. This is . Stop. + Makefile:52: *** pgTAP requires PostgreSQL 9.1 or later. This is . Stop. Be sure that you have `pg_config` installed and in your path. If you used a package management system such as RPM to install PostgreSQL, be sure that the @@ -83,9 +83,9 @@ to find it: env PG_CONFIG=/path/to/pg_config make && make install && make installcheck -And finally, if all that fails (and if you're on PostgreSQL 8.1, it likely -will), copy the entire distribution directory to the `contrib/` subdirectory -of the PostgreSQL source tree and try it there without `pg_config`: +And finally, if all that fails, copy the entire distribution directory to the +`contrib/` subdirectory of the PostgreSQL source tree and try it there without +`pg_config`: env NO_PGXS=1 make && make install && make installcheck @@ -98,6 +98,27 @@ You need to run the test suite using a super user, such as the default make installcheck PGUSER=postgres +Once pgTAP is installed, you can add it to a database by connecting as a super +user and running: + + CREATE EXTENSION pgtap; + +If you've upgraded your cluster to PostgreSQL 9.1 and already had pgTAP +installed, you can upgrade it to a properly packaged extension with: + + CREATE EXTENSION pgtap FROM unpackaged; + +If you want to install pgTAP and all of its supporting objects into a +specific schema, use the `PGOPTIONS` environment variable to specify the +schema, like so: + + PGOPTIONS=--search_path=tap psql -d mydb -f pgTAP.sql + +If you want to install pgTAP and all of its supporting objects into a specific +schema, use the `SCHEMA` clause to specify the schema, like so: + + CREATE EXTENSION pgtap SCHEMA tap; + Testing pgTAP with pgTAP ------------------------ @@ -500,7 +521,7 @@ Will produce something like this: So you can figure out what went wrong without re-running the test. You are encouraged to use `is()` and `isnt()` over `ok()` where possible. You -can even use them to compare records in PostgreSQL 8.4 and later: +can even use them to compare records: SELECT is( users.*, ROW(1, 'theory', true)::users ) FROM users @@ -1273,26 +1294,14 @@ will be represented as "NULL": # have: (1,Anna) # want: NULL -On PostgreSQL 8.4 or higher, if the number of columns varies between result -sets, or if results are of different data types, you'll get diagnostics like -so: +If the number of columns varies between result sets, or if results are of +different data types, you'll get diagnostics like so: # Failed test 148 # Number of columns or their types differ between the queries: # have: (1) # want: (foo,1) -On PostgreSQL 8.3 and down, the rows are cast to text for comparison, rather -than compared as `record` objects. The downside to this necessity is that the -test cannot detect incompatibilities in column numbers or types, or -differences in columns that convert to the same text representation. For -example, a `NULL` column will be equivalent to an empty string. As a result, -pgTAP will not show the `have` and `want` values if they are the same, just -the error message, like so: - - # Failed test 149 - # Number of columns or their types differ between the queries - ### `results_ne()` ### SELECT results_ne( :sql, :sql, :description ); @@ -1331,9 +1340,6 @@ completeness and is kind of cute, so enjoy. If a `results_ne()` test fails, however, there will be no diagnostics, because, well, the results will be the same! -Note that the caveats for `results_ne()` on PostgreSQL 8.3 and down apply to -`results_ne()` as well. - ### `set_eq()` ### SELECT set_eq( :sql, :sql, :description ); @@ -2501,10 +2507,9 @@ missing domains, like so: : A short description of the test. Tests that all of the enums in the named schema are the only enums in that -schema. Enums are supported in PostgreSQL 8.3 and up. If the `:schema` -argument is omitted, the enums must be visible in the search path, excluding -`pg_catalog` and `information_schema`. If the description is omitted, a -generally useful default description will be generated. Example: +schema. If the `:schema` argument is omitted, the enums must be visible in the +search path, excluding `pg_catalog` and `information_schema`. If the description +is omitted, a generally useful default description will be generated. Example: SELECT enums_are('myschema', ARRAY[ 'timezone', 'state' ]); @@ -3413,11 +3418,11 @@ domain does *not* exist. `:description` : A short description of the test. -This function tests whether or not a enum exists in the database. Enums are -supported in PostgreSQL 8.3 or higher. The first argument is a schema name, -the second is the an enum name, and the third is the test description. If you -omit the schema, the enum must be visible in the search path. If you omit the -test description, it will be set to "Enum `:enum` should exist". Example: +This function tests whether or not a enum exists in the database. The first +argument is a schema name, the second is the an enum name, and the third is the +test description. If you omit the schema, the enum must be visible in the search +path. If you omit the test description, it will be set to "Enum `:enum` should +exist". Example: SELECT has_enum( 'myschema', 'someenum' ); @@ -4425,9 +4430,7 @@ that this test will fail if the table or column in question does not exist. The default argument must have an unambiguous type in order for the call to succeed. If you see an error such as 'ERROR: could not determine polymorphic type because input has type "unknown"', it's because you forgot to cast the -expected value, probably a `NULL` (which, by the way, you can only properly -test for in PostgreSQL 8.3 and later), to its proper type. IOW, this will -fail: +expected value, probably a `NULL`, to its proper type. IOW, this will fail: SELECT col_default_is( 'tab', age, NULL ); @@ -5707,10 +5710,9 @@ far. `:description` : A short description of the test. -This function tests that an enum consists of an expected list of labels. Enums -are supported in PostgreSQL 8.3 or higher. The first argument is a schema -name, the second an enum name, the third an array of enum labels, and the -fourth a description. Example: +This function tests that an enum consists of an expected list of labels.The +first argument is a schema name, the second an enum name, the third an array of +enum labels, and the fourth a description. Example: SELECT enum_has_labels( 'myschema', 'someenum', ARRAY['foo', 'bar'] ); @@ -6552,9 +6554,8 @@ diagnostics will look something like: `:description` : A short description of the test. -Tests the ownership of a procedural language. If the `:description` argument -is omitted, an appropriate description will be created. Works on PostgreSQL -8.3 and higher. Examples: +Tests the ownership of a procedural language. If the `:description` argument is +omitted, an appropriate description will be created. Examples: SELECT language_owner_is( 'plpgsql', 'larry', 'Larry should own plpgsql' ); SELECT language_owner_is( 'plperl', current_user ); @@ -6887,9 +6888,6 @@ table privileges are: * TRUNCATE * UPDATE -Note that the privilege RULE is not available after PostgreSQL 8.1, and that -TRIGGER was added in 8.4. - If the `:description` argument is omitted, an appropriate description will be created. Examples: @@ -7033,9 +7031,6 @@ The available column privileges are: * SELECT * UPDATE -Note that column privileges were added in PostgreSQL 8.4, so this function -will likely throw an exception on earlier versions. - If the `:description` argument is omitted, an appropriate description will be created. Examples: @@ -7109,9 +7104,6 @@ available column privileges are: * SELECT * UPDATE -Note that column privileges were added in PostgreSQL 8.4, so this function -will likely throw an exception on earlier versions. - If the `:description` argument is omitted, an appropriate description will be created. Examples: @@ -7308,9 +7300,6 @@ available function privileges are: * USAGE -Note that foreign data wrapper privileges were added in PostgreSQL 8.4, so -this function will likely throw an exception on earlier versions. - If the `:description` argument is omitted, an appropriate description will be created. Examples: @@ -7371,9 +7360,6 @@ function privileges are: * USAGE -Note that server privileges were added in PostgreSQL 8.4, so this function -will likely throw an exception on earlier versions. - If the `:description` argument is omitted, an appropriate description will be created. Examples: @@ -7597,10 +7583,9 @@ Which would produce: # but yours is set to en_US.ISO8859-1. # As a result, some tests may fail. YMMV. -You can pass data of any type to `diag()` on PostgreSQL 8.3 and higher and it -will all be converted to text for the diagnostics. On PostgreSQL 8.4 and -higher, you can pass any number of arguments (as long as they are all the same -data type) and they will be concatenated together. +You can pass data of any type to `diag()` and it will all be converted to text +for the diagnostics. You can als pass any number of arguments (as long as they +are all the same data type) and they will be concatenated together. Conditional Tests ----------------- @@ -7775,9 +7760,9 @@ the stringified version number displayed in the first part of the core `version()` function and stored in the "server_version" setting: try=% select current_setting( 'server_version'), pg_version(); - current_setting | pg_version + current_setting | pg_version -----------------+------------ - 8.3.4 | 8.3.4 + 12.2 | 12.2 (1 row) ### `pg_version_num()` ### @@ -7838,14 +7823,6 @@ when used in combination with `skip()`: skip('Collation-specific test', 4) END; -On PostgreSQL 8.4 and higher, it can take any number of arguments. Lower than -8.4 requires the explicit use of an array: - - SELECT collect_tap(ARRAY[ - ok(true, 'This should pass'), - ok(false, 'This should fail) - ]); - ### `display_oper()` ### SELECT display_oper( :opername, :operoid ); @@ -7884,11 +7861,6 @@ executing the comparison, but might be generally useful. -----------+----------- integer | numeric -*Note:* pgTAP does not build `pg_typeof()` on PostgreSQL 8.4 or higher, -because it's in core in 8.4. You only need to worry about this if you depend -on the function being in particular schema. It will always be in `pg_catalog` -in 8.4 and higher. - ### `findfuncs()` ### SELECT findfuncs( :schema, :pattern, :exclude_pattern ); diff --git a/release.md b/release.md index 0cdb7bb74de2..8327d3d00649 100644 --- a/release.md +++ b/release.md @@ -10,22 +10,20 @@ Here are the steps to take to make a release of pgTAP: [pull requests](https://github.com/theory/pgtap/pulls). * Test on all supported PostgreSQL versions, starting with the latest version - (10) and moving backward in order (9.6, 9.5, 9.4, etc.). + (12) and moving backward in order (9.6, 9.5, 9.4, etc.). [pgenv](https://github.com/theory/pgenv/) is a handy tool for installing and switching between versions. For each version, ensure that: + Patches apply cleanly (try to eliminate Hunk warnings for patches to `pgtap.sql` itself, usually by fixing line numbers) - + All files are installed (on 8.3 and earlier that includes pgtap.so). + + All files are installed. - + `ALTER EXTENSION pgtap UPDATE;` works on 9.1 and higher. + + `ALTER EXTENSION pgtap UPDATE;` works. - + `CREATE EXTENSION pgtap;` works on 9.1 and higher. + + `CREATE EXTENSION pgtap;` works. - + All tests pass in `make installcheck` (on 8.1, move the pgtap source - dir into the postgres source `contrib` directory and run - `make NO_PGXS=1 installcheck`) + + All tests pass in `make installcheck`. * If you've made any significant changes while testing versions backward, test them again in forward order (9.1, 9.2, 9.3, etc.) to make sure the changes @@ -41,11 +39,11 @@ Here are the steps to take to make a release of pgTAP: bullet mentioning the upgrade. * Run `make html` (you'll need - [MultiMarkdown](http://fletcherpenney.net/multimarkdown/) (Macports: port - install multimarkdown) in your path and the + [MultiMarkdown](https://fletcherpenney.net/multimarkdown/) (Homebrew: + `brew install multimarkdown`) in your path and the [Pod::Simple::XHTML](https://metacpan.org/module/Pod::Simple::XHTML) - (Macports: port install p5-pod-simple) Perl module installed), then - checkout the `gh-pages` branch and make these changes: + (Homebrew: `brew install cpanm && cpanm Pod::Simple::XHTML`) Perl module + installed), then checkout the `gh-pages` branch and make these changes: + `cp .documentation.html.template documentation.html`. Edit documentation.html, the main div should look like this: diff --git a/test/sql/cmpok.sql b/test/sql/cmpok.sql index 7522e6cb1392..f4782efcd0b2 100644 --- a/test/sql/cmpok.sql +++ b/test/sql/cmpok.sql @@ -3,23 +3,6 @@ SELECT plan(38); -/****************************************************************************/ - --- Set up some functions that are used only by this test, and aren't available --- in PostgreSQL 8.2 or older - -CREATE OR REPLACE FUNCTION quote_literal(polygon) -RETURNS TEXT AS 'SELECT '''''''' || textin(poly_out($1)) || ''''''''' -LANGUAGE SQL IMMUTABLE STRICT; - -CREATE OR REPLACE FUNCTION quote_literal(integer[]) -RETURNS TEXT AS 'SELECT '''''''' || textin(array_out($1)) || ''''''''' -LANGUAGE SQL IMMUTABLE STRICT; - -CREATE OR REPLACE FUNCTION quote_literal(inet[]) -RETURNS TEXT AS 'SELECT '''''''' || textin(array_out($1)) || ''''''''' -LANGUAGE SQL IMMUTABLE STRICT; - /****************************************************************************/ -- Test cmp_ok(). SELECT * FROM check_test( diff --git a/test/sql/coltap.sql b/test/sql/coltap.sql index 4b505a3a316b..a950a3434e40 100644 --- a/test/sql/coltap.sql +++ b/test/sql/coltap.sql @@ -516,117 +516,47 @@ SELECT * FROM check_test( ); -- Make sure it works with a NULL default. -CREATE OR REPLACE FUNCTION versiontests () RETURNS SETOF TEXT AS $$ -DECLARE - tap record; -BEGIN - IF pg_version_num() < 80300 THEN - -- Before 8.3, have to cast to text. - FOR tap IN SELECT * FROM check_test( - col_default_is( 'sometab', 'myNum', 24::text ), - true, - 'col_default_is( tab, col, int )', - 'Column sometab."myNum" should default to ''24''', - '' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - - -- Before 8.3, DEFAULT NULL was ignored. - FOR tap IN SELECT * FROM fakeout( - true, 'col_default_is( tab, col, NULL, desc )' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - - FOR tap IN SELECT * FROM fakeout( - true, 'col_default_is( tab, col, NULL )' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - - -- Make sure that it fails when there is no default. - -- Before 8.3, must cast values to text - FOR tap IN SELECT * FROM check_test( - col_default_is( 'sometab', 'plain', 1::text, 'desc' ), - false, - 'col_default_is( tab, col, bogus, desc )', - 'desc', - ' Column sometab.plain has no default' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - - FOR tap IN SELECT * FROM check_test( - col_default_is( 'sometab', 'plain', 1::text ), - false, - 'col_default_is( tab, col, bogus )', - 'Column sometab.plain should default to ''1''', - ' Column sometab.plain has no default' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - - ELSE - -- In 8.3 and later, can just use the raw value. - FOR tap IN SELECT * FROM check_test( - col_default_is( 'sometab', 'myNum', 24 ), - true, - 'col_default_is( tab, col, int )', - 'Column sometab."myNum" should default to ''24''', - '' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - - -- In 8.3 and later, we can handle DEFAULT NULL correctly. - FOR tap IN SELECT * FROM check_test( - col_default_is( 'sometab', 'numb', NULL::numeric, 'desc' ), - true, - 'col_default_is( tab, col, NULL, desc )', - 'desc', - '' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; +SELECT * FROM check_test( + col_default_is( 'sometab', 'myNum', 24 ), + true, + 'col_default_is( tab, col, int )', + 'Column sometab."myNum" should default to ''24''', + '' +); - FOR tap IN SELECT * FROM check_test( - col_default_is( 'sometab', 'numb', NULL::numeric ), - true, - 'col_default_is( tab, col, NULL )', - 'Column sometab.numb should default to NULL', - '' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; + -- We can handle DEFAULT NULL correctly. +SELECT * FROM check_test( + col_default_is( 'sometab', 'numb', NULL::numeric, 'desc' ), + true, + 'col_default_is( tab, col, NULL, desc )', + 'desc', + '' +); - -- Make sure that it fails when there is no default. - -- In 8.3 and later, can just use raw values. - FOR tap IN SELECT * FROM check_test( - col_default_is( 'sometab', 'plain', 1, 'desc' ), - false, - 'col_default_is( tab, col, bogus, desc )', - 'desc', - ' Column sometab.plain has no default' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; +SELECT * FROM check_test( + col_default_is( 'sometab', 'numb', NULL::numeric ), + true, + 'col_default_is( tab, col, NULL )', + 'Column sometab.numb should default to NULL', + '' +); - FOR tap IN SELECT * FROM check_test( - col_default_is( 'sometab', 'plain', 1 ), - false, - 'col_default_is( tab, col, bogus )', - 'Column sometab.plain should default to ''1''', - ' Column sometab.plain has no default' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; +-- Make sure that it fails when there is no default. +SELECT * FROM check_test( + col_default_is( 'sometab', 'plain', 1, 'desc' ), + false, + 'col_default_is( tab, col, bogus, desc )', + 'desc', + ' Column sometab.plain has no default' +); - END IF; - RETURN; -END; -$$ LANGUAGE plpgsql; -SELECT * FROM versiontests(); +SELECT * FROM check_test( + col_default_is( 'sometab', 'plain', 1 ), + false, + 'col_default_is( tab, col, bogus )', + 'Column sometab.plain should default to ''1''', + ' Column sometab.plain has no default' +); -- Make sure that it works when the default is an expression. SELECT * FROM check_test( diff --git a/test/sql/istap.sql b/test/sql/istap.sql index 188a01fe3f83..350385a6a643 100644 --- a/test/sql/istap.sql +++ b/test/sql/istap.sql @@ -61,89 +61,35 @@ CREATE TABLE mumble ( id int, name text ); RESET client_min_messages; INSERT INTO mumble VALUES (1, 'hey'); -CREATE FUNCTION test_records() RETURNS SETOF TEXT AS $$ -DECLARE - tap record; -BEGIN - IF pg_version_num() >= 80400 THEN - RETURN NEXT is( mumble.*, ROW(1, 'hey')::mumble, 'with records!' ) - FROM mumble; +SELECT is( mumble.*, ROW(1, 'hey')::mumble, 'with records!' ) +FROM mumble; - -- Before 8.3, have to cast to text. - FOR tap IN SELECT check_test( - is( mumble.*, ROW(1, 'HEY')::mumble ), - false, - 'is(mumble, row) fail', - '', - ' have: (1,hey) - want: (1,HEY)' - ) AS b FROM mumble LOOP - RETURN NEXT tap.b; - END LOOP; - - FOR tap IN SELECT check_test( - is( mumble.*, ROW(1, NULL)::mumble ), - false, - 'is(mumble, row) fail with NULL', - '', - ' have: (1,hey) - want: (1,)' - ) AS b FROM mumble LOOP - RETURN NEXT tap.b; - END LOOP; - - FOR tap IN SELECT check_test( - is( mumble.*, NULL::mumble ), - false, - 'is(mumble, NULL)', - '', - ' have: (1,hey) - want: NULL' - ) AS b FROM mumble LOOP - RETURN NEXT tap.b; - END LOOP; - ELSE - RETURN NEXT is( textin(record_out(mumble.*)), textin(record_out(ROW(1, 'hey'))), 'with records!' ) - FROM mumble; - - FOR tap IN SELECT check_test( - is( textin(record_out(mumble.*)), textin(record_out(ROW(1, 'HEY')))), - false, - 'is(mumble, row) fail', - '', - ' have: (1,hey) +SELECT check_test( + is( mumble.*, ROW(1, 'HEY')::mumble ), + false, + 'is(mumble, row) fail', + '', + ' have: (1,hey) want: (1,HEY)' - ) AS b FROM mumble LOOP - RETURN NEXT tap.b; - END LOOP; +) FROM mumble; - FOR tap IN SELECT check_test( - is( textin(record_out(mumble.*)), textin(record_out(ROW(1, NULL))) ), - false, - 'is(mumble, row) fail with NULL', - '', - ' have: (1,hey) +SELECT check_test( + is( mumble.*, ROW(1, NULL)::mumble ), + false, + 'is(mumble, row) fail with NULL', + '', + ' have: (1,hey) want: (1,)' - ) AS b FROM mumble LOOP - RETURN NEXT tap.b; - END LOOP; +) FROM mumble; - FOR tap IN SELECT check_test( - is( textin(record_out(mumble.*)), NULL::text ), - false, - 'is(mumble, NULL)', - '', - ' have: (1,hey) +SELECT check_test( + is( mumble.*, NULL::mumble ), + false, + 'is(mumble, NULL)', + '', + ' have: (1,hey) want: NULL' - ) AS b FROM mumble LOOP - RETURN NEXT tap.b; - END LOOP; - END IF; - RETURN; -END; -$$ LANGUAGE PLPGSQL; - -SELECT * FROM test_records(); +) FROM mumble; /****************************************************************************/ -- Finish the tests and clean up. diff --git a/test/sql/resultset.sql b/test/sql/resultset.sql index 8c5d12853268..32fef964edd0 100644 --- a/test/sql/resultset.sql +++ b/test/sql/resultset.sql @@ -986,60 +986,27 @@ SELECT * FROM check_test( want: (foo,1)' ); --- Handle failure due to more subtle column mismatch, valid only on 8.4. -CREATE OR REPLACE FUNCTION subtlefail() RETURNS SETOF TEXT AS $$ -DECLARE - tap record; -BEGIN - IF pg_version_num() < 80400 THEN - -- 8.3 and earlier cast records to text, so subtlety is out. - -- Fake out pg_regress by running equivalent tests with fail(). - FOR tap IN SELECT * FROM check_test( - fail('whatever'), - false, - 'results_eq(values, values) subtle mismatch', - 'whatever', - '' - ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; - - FOR tap IN SELECT * FROM check_test( - fail('whatever'), - false, - 'results_eq(values, values) integer type mismatch', - 'whatever', - '' - ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; - - ELSE - -- 8.4 does true record comparisions, yay! - FOR tap IN SELECT * FROM check_test( - results_eq( - 'VALUES (1, ''foo''::varchar), (2, ''bar''::varchar)', - 'VALUES (1, ''foo''), (2, ''bar'')' - ), - false, - 'results_eq(values, values) subtle mismatch', - '', - ' Number of columns or their types differ between the queries' ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; +SELECT * FROM check_test( + results_eq( + 'VALUES (1, ''foo''::varchar), (2, ''bar''::varchar)', + 'VALUES (1, ''foo''), (2, ''bar'')' + ), + false, + 'results_eq(values, values) subtle mismatch', + '', + ' Number of columns or their types differ between the queries' +); - FOR tap IN SELECT * FROM check_test( - results_eq( - 'VALUES (1::int), (2::int)', - 'VALUES (1::bigint), (2::bigint)' - ), - false, - 'results_eq(values, values) integer type mismatch', - '', - ' Number of columns or their types differ between the queries' ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - END IF; - RETURN; -END; -$$ LANGUAGE plpgsql; -SELECT * FROM subtlefail(); +SELECT * FROM check_test( + results_eq( + 'VALUES (1::int), (2::int)', + 'VALUES (1::bigint), (2::bigint)' + ), + false, + 'results_eq(values, values) integer type mismatch', + '', + ' Number of columns or their types differ between the queries' +); -- Handle failure due to column count mismatch. SELECT * FROM check_test( @@ -2162,71 +2129,41 @@ SELECT * FROM check_test( '' ); --- Handle failure due to more subtle column mismatch, valid only on 8.4. -CREATE OR REPLACE FUNCTION subtlefail() RETURNS SETOF TEXT AS $$ -DECLARE - tap record; -BEGIN - IF pg_version_num() < 80400 THEN - -- 8.3 and earlier cast records to text, so subtlety is out. - RETURN NEXT pass('results_ne(values, values) mismatch should fail'); - RETURN NEXT pass('results_ne(values, values) mismatch should have the proper description'); - RETURN NEXT pass('results_ne(values, values) mismatch should have the proper diagnostics'); - RETURN NEXT pass('results_ne(values, values) subtle mismatch should fail'); - RETURN NEXT pass('results_ne(values, values) subtle mismatch should have the proper description'); - RETURN NEXT pass('results_ne(values, values) subtle mismatch should have the proper diagnostics'); - RETURN NEXT pass('results_ne(values, values) fail column count should fail'); - RETURN NEXT pass('results_ne(values, values) fail column count should have the proper description'); - RETURN NEXT pass('results_ne(values, values) fail column count should have the proper diagnostics'); - ELSE - -- 8.4 does true record comparisions, yay! - -- Handle failure due to column mismatch. - FOR tap IN SELECT * FROM check_test( - results_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), - false, - 'results_ne(values, values) mismatch', - '', - ' Columns differ between queries: +-- Handle failure due to column mismatch. +SELECT * FROM check_test( + results_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'results_ne(values, values) mismatch', + '', + ' Columns differ between queries: have: (1,foo) want: (foo,1)' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; +); - -- Handle failure due to subtle column mismatch. - FOR tap IN SELECT * FROM check_test( - results_ne( - 'VALUES (1, ''foo''::varchar), (2, ''bar''::varchar)', - 'VALUES (1, ''foo''), (2, ''bar'')' - ), - false, - 'results_ne(values, values) subtle mismatch', - '', - ' Columns differ between queries: +-- Handle failure due to subtle column mismatch. +SELECT * FROM check_test( + results_ne( + 'VALUES (1, ''foo''::varchar), (2, ''bar''::varchar)', + 'VALUES (1, ''foo''), (2, ''bar'')' + ), + false, + 'results_ne(values, values) subtle mismatch', + '', + ' Columns differ between queries: have: (1,foo) - want: (1,foo)' ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; + want: (1,foo)' +); - -- Handle failure due to column count mismatch. - FOR tap IN SELECT * FROM check_test( - results_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), - false, - 'results_ne(values, values) fail column count', - '', - ' Columns differ between queries: +-- Handle failure due to column count mismatch. +SELECT * FROM check_test( + results_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'results_ne(values, values) fail column count', + '', + ' Columns differ between queries: have: (1) want: (foo,1)' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - - END IF; - RETURN; -END; -$$ LANGUAGE plpgsql; -SELECT * FROM subtlefail(); - +); -- Compare with cursors. CLOSE cwant; diff --git a/test/sql/todotap.sql b/test/sql/todotap.sql index ef268e096fb2..f74db2859553 100644 --- a/test/sql/todotap.sql +++ b/test/sql/todotap.sql @@ -96,8 +96,7 @@ SELECT * FROM check_test( \echo ok 26 - todo fail \echo ok 27 - todo fail SELECT * FROM todo('just because', 2 ); --- We have to use ok() instead of is() to get around lack of cast to text in 8.0. -SELECT ok( +SELECT is( ARRAY( SELECT fail('This is a todo test 1') AS stuff UNION @@ -107,8 +106,8 @@ SELECT ok( UNION SELECT fail('This is a todo test 3') ORDER BY stuff - ) - = ARRAY[ + ), + ARRAY[ 'not ok 25 - This is a todo test 1 # TODO just because # Failed (TODO) test 25: "This is a todo test 1"', 'not ok 26 - This is a todo test 2 # TODO inside @@ -125,8 +124,7 @@ SELECT ok( \echo ok 30 - todo fail \echo ok 31 - todo fail SELECT * FROM todo_start('some todos'); --- We have to use ok() instead of is() to get around lack of cast to text in 8.0. -SELECT ok( +SELECT is( ARRAY( SELECT fail('This is a todo test 1') AS stuff UNION @@ -142,8 +140,8 @@ SELECT ok( UNION SELECT in_todo()::text ORDER BY stuff - ) - = ARRAY[ + ), + ARRAY[ 'false', 'not ok 29 - This is a todo test 1 # TODO some todos # Failed (TODO) test 29: "This is a todo test 1"', diff --git a/test/sql/valueset.sql b/test/sql/valueset.sql index 9e07288305b4..c0a7a9bc11f4 100644 --- a/test/sql/valueset.sql +++ b/test/sql/valueset.sql @@ -684,34 +684,17 @@ SELECT * FROM check_test( want: (foo,1)' ); --- Handle failure due to more subtle column mismatch, valid only on 8.4. -CREATE OR REPLACE FUNCTION subtlefail() RETURNS SETOF TEXT AS $$ -DECLARE - tap record; -BEGIN - IF pg_version_num() < 80400 THEN - -- 8.3 and earlier cast records to text, so subtlety is out. - RETURN NEXT pass('results_eq(values, values) subtle mismatch should fail'); - RETURN NEXT pass('results_eq(values, values) subtle mismatch should have the proper description'); - RETURN NEXT pass('results_eq(values, values) subtle mismatch should have the proper diagnostics'); - ELSE - -- 8.4 does true record comparisions, yay! - FOR tap IN SELECT * FROM check_test( - results_eq( - 'VALUES (1, ''foo''::varchar), (2, ''bar''::varchar)', - 'VALUES (1, ''foo''), (2, ''bar'')' - ), - false, - 'results_eq(values, values) subtle mismatch', - '', - ' Number of columns or their types differ between the queries' ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - END IF; - RETURN; -END; -$$ LANGUAGE plpgsql; -SELECT * FROM subtlefail(); +-- Handle failure due to more subtle column mismatch +SELECT * FROM check_test( + results_eq( + 'VALUES (1, ''foo''::varchar), (2, ''bar''::varchar)', + 'VALUES (1, ''foo''), (2, ''bar'')' + ), + false, + 'results_eq(values, values) subtle mismatch', + '', + ' Number of columns or their types differ between the queries' +); -- Handle failure due to column count mismatch. SELECT * FROM check_test( @@ -1526,71 +1509,41 @@ SELECT * FROM check_test( '' ); --- Handle failure due to more subtle column mismatch, valid only on 8.4. -CREATE OR REPLACE FUNCTION subtlefail() RETURNS SETOF TEXT AS $$ -DECLARE - tap record; -BEGIN - IF pg_version_num() < 80400 THEN - -- 8.3 and earlier cast records to text, so subtlety is out. - RETURN NEXT pass('results_ne(values, values) mismatch should fail'); - RETURN NEXT pass('results_ne(values, values) mismatch should have the proper description'); - RETURN NEXT pass('results_ne(values, values) mismatch should have the proper diagnostics'); - RETURN NEXT pass('results_ne(values, values) subtle mismatch should fail'); - RETURN NEXT pass('results_ne(values, values) subtle mismatch should have the proper description'); - RETURN NEXT pass('results_ne(values, values) subtle mismatch should have the proper diagnostics'); - RETURN NEXT pass('results_ne(values, values) fail column count should fail'); - RETURN NEXT pass('results_ne(values, values) fail column count should have the proper description'); - RETURN NEXT pass('results_ne(values, values) fail column count should have the proper diagnostics'); - ELSE - -- 8.4 does true record comparisions, yay! - -- Handle failure due to column mismatch. - FOR tap IN SELECT * FROM check_test( - results_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), - false, - 'results_ne(values, values) mismatch', - '', - ' Columns differ between queries: +-- Handle failure due to more subtle column mismatch. +SELECT * FROM check_test( + results_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'results_ne(values, values) mismatch', + '', + ' Columns differ between queries: have: (1,foo) want: (foo,1)' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - - -- Handle failure due to subtle column mismatch. - FOR tap IN SELECT * FROM check_test( - results_ne( - 'VALUES (1, ''foo''::varchar), (2, ''bar''::varchar)', - 'VALUES (1, ''foo''), (2, ''bar'')' - ), - false, - 'results_ne(values, values) subtle mismatch', - '', - ' Columns differ between queries: +); + +-- Handle failure due to subtle column mismatch. +SELECT * FROM check_test( + results_ne( + 'VALUES (1, ''foo''::varchar), (2, ''bar''::varchar)', + 'VALUES (1, ''foo''), (2, ''bar'')' + ), + false, + 'results_ne(values, values) subtle mismatch', + '', + ' Columns differ between queries: have: (1,foo) - want: (1,foo)' ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - - -- Handle failure due to column count mismatch. - FOR tap IN SELECT * FROM check_test( - results_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), - false, - 'results_ne(values, values) fail column count', - '', - ' Columns differ between queries: + want: (1,foo)' +); + +-- Handle failure due to column count mismatch. +SELECT * FROM check_test( + results_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + false, + 'results_ne(values, values) fail column count', + '', + ' Columns differ between queries: have: (1) want: (foo,1)' - ) AS a(b) LOOP - RETURN NEXT tap.b; - END LOOP; - - END IF; - RETURN; -END; -$$ LANGUAGE plpgsql; -SELECT * FROM subtlefail(); - +); -- Compare with cursors. CLOSE cwant; From 642ec6967a0547aec0d087559a4d27223065f54f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 24 Oct 2020 17:16:41 -0400 Subject: [PATCH 1094/1195] Use https wherever possible --- Changes | 9 ++++++--- META.json | 6 +++--- compat/gencore | 2 +- doc/pgtap.mmd | 26 +++++++++++++------------- release.md | 2 +- sql/pgtap.sql.in | 8 ++++---- test/sql/resultset.sql | 2 +- test/sql/valueset.sql | 2 +- tools/pg-travis-test.sh | 2 +- 9 files changed, 31 insertions(+), 28 deletions(-) diff --git a/Changes b/Changes index 29317d78193c..58601d8e4cc9 100644 --- a/Changes +++ b/Changes @@ -18,6 +18,9 @@ Revision history for pgTAP identifier. Thanks to Matt DeLuco for the report (#216)! * Fixed the `col_not_null()` drop statements in the uninstall script. Thanks to Kyle L. Jensen for the report (#252). +* Removed straggler references to Postgres 9.0 and earlier, including conditional + tests. +* Updated all relevant URLs to use https instead of http. 1.1.0 2019-11-25T19:05:38Z -------------------------- @@ -220,7 +223,7 @@ Revision history for pgTAP + `has_foreign_table(:schema, :table)` + `hasnt_foreign_table(:schema, :table)` * Fixed the format of SKIP output to match the - [TAP spec](http://podwiki.hexten.net/TAP/TAP.html#Skippingtests). + [TAP spec](https://testanything.org/tap-specification.html#skipping-tests). * Fixed incorrect handling of leading space when comparing diagnostic output in `check_test()`. * Fixed an installation issue on PostgreSQL 9.3.2. @@ -373,7 +376,7 @@ Revision history for pgTAP * Removed `pg_prove` and `pg_tapgen` from the distribution. They must now be installed separately from CPAN. If it's not installed, the intaller will issue a warning. -* Added `META.json` and distributed on [PGXN](http://pgxn.org/). +* Added `META.json` and distributed on [PGXN](https://pgxn.org/). * Rearranged the source directory layout to more closely match the [preferred PGXN layout](http://manager.pgxn.org/howto#new-order). @@ -814,4 +817,4 @@ Revision history for pgTAP 0.01 2008-06-07T05:24:27Z -------------------------- * Initial public release. Announcement at - http://justatheory.com/computers/databases/postgresql/introducing_pgtap.html + https://justatheory.com/2008/06/introducing-pgtap/ diff --git a/META.json b/META.json index c40a7f99b8f4..892656a7fae2 100644 --- a/META.json +++ b/META.json @@ -8,7 +8,7 @@ "pgTAP List " ], "license": { - "PostgreSQL": "http://www.postgresql.org/about/licence" + "PostgreSQL": "https://www.postgresql.org/about/licence" }, "prereqs": { "runtime": { @@ -39,7 +39,7 @@ } }, "resources": { - "homepage": "http://pgtap.org/", + "homepage": "https://pgtap.org/", "bugtracker": { "web": "https://github.com/theory/pgtap/issues" }, @@ -52,7 +52,7 @@ "generated_by": "David E. Wheeler", "meta-spec": { "version": "1.0.0", - "url": "http://pgxn.org/meta/spec.txt" + "url": "https://pgxn.org/meta/spec.txt" }, "tags": [ "testing", diff --git a/compat/gencore b/compat/gencore index 13f453a10604..dc13199cc247 100644 --- a/compat/gencore +++ b/compat/gencore @@ -15,7 +15,7 @@ print qq{ -- project is: -- --- http://pgtap.org/ +-- https://pgtap.org/ -- }; diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index a2dbe594f10f..6555a0840358 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -3,7 +3,7 @@ pgTAP 1.1.0 pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and PL/SQL. It includes a comprehensive collection of -[TAP](http://testanything.org)-emitting assertion functions, as well as the +[TAP](https://testanything.org)-emitting assertion functions, as well as the ability to integrate with other TAP-emitting test frameworks. It can also be used in the xUnit testing style. @@ -125,7 +125,7 @@ Testing pgTAP with pgTAP In addition to the PostgreSQL-standard `installcheck` target, the `test` target uses the `pg_prove` Perl program to do its testing, which will be installed with the -[TAP::Parser::SourceHandler::pgTAP](http://search.cpan.org/dist/TAP-Parser-SourceHandler-pgTAP) +[TAP::Parser::SourceHandler::pgTAP](https://metacpan.org/module/TAP::Parser::SourceHandler::pgTAP) CPAN distribution. You'll need to make sure that you use a database with PL/pgSQL loaded, or else the tests won't work. `pg_prove` supports a number of environment variables that you might need to use, including all the usual @@ -239,7 +239,7 @@ Using `pg_prove` Or save yourself some effort -- and run a batch of tests scripts or all of your xUnit test functions at once -- by using `pg_prove`, available in the -[TAP::Parser::SourceHandler::pgTAP](http://search.cpan.org/dist/TAP-Parser-SourceHandler-pgTAP) +[TAP::Parser::SourceHandler::pgTAP](https://metacpan.org/module/TAP::Parser::SourceHandler::pgTAP) CPAN distribution. If you're not relying on `installcheck`, your test scripts can be a lot less verbose; you don't need to set all the extra variables, because `pg_prove` takes care of that for you: @@ -772,7 +772,7 @@ an unnecessary PITA. Each of the query-executing functions in this section thus support an alternative to make your tests more SQLish: using prepared statements. -[Prepared statements](http://www.postgresql.org/docs/current/static/sql-prepare.html) +[Prepared statements](https://www.postgresql.org/docs/current/static/sql-prepare.html) allow you to just write SQL and simply pass the prepared statement names to test functions. For example, the above example can be rewritten as: @@ -847,7 +847,7 @@ error-prone as you think they should be. : An SQL statement or the name of a prepared statement, passed as a string. `:errcode` -: A [PostgreSQL error code](http://www.postgresql.org/docs/current/static/errcodes-appendix.html +: A [PostgreSQL error code](https://www.postgresql.org/docs/current/static/errcodes-appendix.html "Appendix A. PostgreSQL Error Codes") `:errmsg` @@ -869,9 +869,9 @@ five-character string (if it happens to consist only of numbers and you pass it as an integer, it will still work). If this value is not `NULL`, `throws_ok()` will check the thrown exception to ensure that it is the expected exception. For a complete list of error codes, see [Appendix -A.](http://www.postgresql.org/docs/current/static/errcodes-appendix.html +A.](https://www.postgresql.org/docs/current/static/errcodes-appendix.html "Appendix A. PostgreSQL Error Codes") in the [PostgreSQL -documentation](http://www.postgresql.org/docs/current/static/). +documentation](https://www.postgresql.org/docs/current/static/). The third argument is an error message. This will be most useful for functions you've written that raise exceptions, so that you can test the exception @@ -1640,7 +1640,7 @@ test fails. : An SQL statement or the name of a prepared statement, passed as a string. `:record` -: A row or value, also known as a [composite type](http://www.postgresql.org/docs/current/static/rowtypes.html). +: A row or value, also known as a [composite type](https://www.postgresql.org/docs/current/static/rowtypes.html). `:description` : A short description of the test. @@ -5591,7 +5591,7 @@ But then you check with `has_function()` first, right? Tests the volatility of a function. Supported volatilities are "volatile", "stable", and "immutable". Consult the [`CREATE FUNCTION` -documentation](http://www.postgresql.org/docs/current/static/sql-createfunction.html) +documentation](https://www.postgresql.org/docs/current/static/sql-createfunction.html) for details. The function name is required. If the `:schema` argument is omitted, then the function must be visible in the search path. If the `:args[]` argument is passed, then the function with that argument signature @@ -5674,7 +5674,7 @@ other database objects. : A short description of the test. Tests that the specified procedural language is trusted. See the [CREATE -LANGUAGE](http://www.postgresql.org/docs/current/static/sql-createlanguage.html +LANGUAGE](https://www.postgresql.org/docs/current/static/sql-createlanguage.html "CREATE LANGUAGE") documentation for details on trusted and untrusted procedural languages. If the `:description` argument is not passed, a suitably useful default will be created. @@ -5842,7 +5842,7 @@ varying", and not "VARCHAR". The The supported contexts are "implicit", "assignment", and "explicit". You can also just pass in "i", "a", or "e". Consult the PostgreSQL [`CREATE -CAST`](http://www.postgresql.org/docs/current/static/sql-createcast.html) +CAST`](https://www.postgresql.org/docs/current/static/sql-createcast.html) documentation for the differences between these contexts (hint: they correspond to the default context, `AS IMPLICIT`, and `AS ASSIGNMENT`). If you don't supply a test description, pgTAP will create a reasonable one for you. @@ -5961,7 +5961,7 @@ members, don't you? Of course you do. Checks whether a rule on the specified relation is an `INSTEAD` rule. See the [`CREATE RULE` -Documentation](http://www.postgresql.org/docs/current/static/sql-createrule.html) +Documentation](https://www.postgresql.org/docs/current/static/sql-createrule.html) for details. If the `:schema` argument is omitted, the relation must be visible in the search path. If the `:description` argument is omitted, an appropriate description will be created. An example: @@ -8327,7 +8327,7 @@ comments, suggestions, and bug reports are welcomed there. Author ------ -[David E. Wheeler](http://justatheory.com/) +[David E. Wheeler](https://justatheory.com/) Credits ------- diff --git a/release.md b/release.md index 8327d3d00649..7f36d25d43b0 100644 --- a/release.md +++ b/release.md @@ -90,7 +90,7 @@ Here are the steps to take to make a release of pgTAP: git ci -m 'Timestamp v0.98.0.' git tag -sm 'Tag v0.98.0.' v0.98.0 -* Package the source and submit to [PGXN](http://manager.pgxn.org/). +* Package the source and submit to [PGXN](https://manager.pgxn.org/). gem install pgxn_utils git archive --format zip --prefix=pgtap-1.0.0/ \ diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 4cda13c65810..9c2f9e782731 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -3,7 +3,7 @@ -- -- The home page for the pgTAP project is: -- --- http://pgtap.org/ +-- https://pgtap.org/ CREATE OR REPLACE FUNCTION pg_version() RETURNS text AS 'SELECT current_setting(''server_version'')' @@ -1852,7 +1852,7 @@ RETURNS text AS $$ ), $2); $$ LANGUAGE SQL immutable; --- Borrowed from newsysviews: http://pgfoundry.org/projects/newsysviews/ +-- Borrowed from newsysviews: sFoundry/newsysviews/ CREATE OR REPLACE FUNCTION _pg_sv_column_array( OID, SMALLINT[] ) RETURNS NAME[] AS $$ SELECT ARRAY( @@ -1864,7 +1864,7 @@ RETURNS NAME[] AS $$ ) $$ LANGUAGE SQL stable; --- Borrowed from newsysviews: http://pgfoundry.org/projects/newsysviews/ +-- 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 ( @@ -1879,7 +1879,7 @@ RETURNS BOOLEAN AS $$ END; $$ LANGUAGE SQL immutable strict; --- Borrowed from newsysviews: http://pgfoundry.org/projects/newsysviews/ +-- 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, diff --git a/test/sql/resultset.sql b/test/sql/resultset.sql index 32fef964edd0..d47b78771a01 100644 --- a/test/sql/resultset.sql +++ b/test/sql/resultset.sql @@ -20,7 +20,7 @@ CREATE TABLE someat ( RESET client_min_messages; --- Top 100 boy an 100 girl names in 2005. http://www.ssa.gov/OACT/babynames/ +-- Top 100 boy an 100 girl names in 2005. https://www.ssa.gov/OACT/babynames/ INSERT INTO names (name) VALUES ('Jacob'); INSERT INTO names (name) VALUES ('Emily'); INSERT INTO names (name) VALUES ('Michael'); diff --git a/test/sql/valueset.sql b/test/sql/valueset.sql index c0a7a9bc11f4..4be2cb153aaf 100644 --- a/test/sql/valueset.sql +++ b/test/sql/valueset.sql @@ -14,7 +14,7 @@ CREATE TABLE names ( RESET client_min_messages; --- Top 100 boy an 100 girl names in 2005. http://www.ssa.gov/OACT/babynames/ +-- Top 100 boy an 100 girl names in 2005. https://www.ssa.gov/OACT/babynames/ INSERT INTO names (name) VALUES ('Jacob'); INSERT INTO names (name) VALUES ('Emily'); INSERT INTO names (name) VALUES ('Michael'); diff --git a/tools/pg-travis-test.sh b/tools/pg-travis-test.sh index 832344dbbf83..05b32c7f5d52 100644 --- a/tools/pg-travis-test.sh +++ b/tools/pg-travis-test.sh @@ -139,7 +139,7 @@ fi sudo apt-get update -# bug: http://www.postgresql.org/message-id/20130508192711.GA9243@msgid.df7cb.de +# bug: https://www.postgresql.org/message-id/20130508192711.GA9243@msgid.df7cb.de sudo update-alternatives --remove-all postmaster.1.gz # stop all existing instances (because of https://github.com/travis-ci/travis-cookbooks/pull/221) From f9283f7a100c96843aeccd1ee5eca254991ddc75 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 24 Oct 2020 17:19:56 -0400 Subject: [PATCH 1095/1195] Update copyright dates --- README.md | 2 +- doc/pgtap.mmd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d6ef0dd32403..e78ac8ff4343 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ pgTAP requires PostgreSQL 9.1 or higher. Copyright and License --------------------- -Copyright (c) 2008-2018 David E. Wheeler. Some rights reserved. +Copyright (c) 2008-2020 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 6555a0840358..d46e36289543 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -8338,7 +8338,7 @@ Credits Copyright and License --------------------- -Copyright (c) 2008-2018 David E. Wheeler. Some rights reserved. +Copyright (c) 2008-2020 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is From b02e480b8dcc9a19471528c1c1b8d7e9716ce6fc Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 25 Oct 2020 15:08:10 -0400 Subject: [PATCH 1096/1195] Fix pasto (thx @wolfgangwalther) --- sql/pgtap.sql.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 9c2f9e782731..2b60f98a42cf 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1852,7 +1852,7 @@ RETURNS text AS $$ ), $2); $$ LANGUAGE SQL immutable; --- Borrowed from newsysviews: sFoundry/newsysviews/ +-- 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( From 35e4ecd45af259e907430f97fb329dafd083818a Mon Sep 17 00:00:00 2001 From: Anatoli Babenia Date: Wed, 28 Apr 2021 12:47:30 +0300 Subject: [PATCH 1097/1195] Clarify that `pg_prove` needs to be installed --- doc/pgtap.mmd | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index d46e36289543..872f337e6742 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -122,9 +122,8 @@ schema, use the `SCHEMA` clause to specify the schema, like so: Testing pgTAP with pgTAP ------------------------ -In addition to the PostgreSQL-standard `installcheck` target, the `test` -target uses the `pg_prove` Perl program to do its testing, which will be -installed with the +`test` target uses the `pg_prove` Perl program to do its testing, which needs +to be installed separately from [TAP::Parser::SourceHandler::pgTAP](https://metacpan.org/module/TAP::Parser::SourceHandler::pgTAP) CPAN distribution. You'll need to make sure that you use a database with PL/pgSQL loaded, or else the tests won't work. `pg_prove` supports a number of From f3fe9522aa304ee241850d0326343d8c3f7c599d Mon Sep 17 00:00:00 2001 From: Anatoli Babenia Date: Fri, 30 Apr 2021 05:53:36 +0300 Subject: [PATCH 1098/1195] Bring back `checkinstall` phrase --- doc/pgtap.mmd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 872f337e6742..97f48d530dfb 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -122,7 +122,8 @@ schema, use the `SCHEMA` clause to specify the schema, like so: Testing pgTAP with pgTAP ------------------------ -`test` target uses the `pg_prove` Perl program to do its testing, which needs + In addition to the PostgreSQL-standard `installcheck` target, the `test` +target uses the `pg_prove` Perl program to do its testing, which needs to be installed separately from [TAP::Parser::SourceHandler::pgTAP](https://metacpan.org/module/TAP::Parser::SourceHandler::pgTAP) CPAN distribution. You'll need to make sure that you use a database with From 69108cd4f4e4f862b39330479b398698eb2255b1 Mon Sep 17 00:00:00 2001 From: Anatoli Babenia Date: Fri, 30 Apr 2021 05:54:42 +0300 Subject: [PATCH 1099/1195] Remove leading whitespace --- doc/pgtap.mmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 97f48d530dfb..4d932591ce8a 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -122,7 +122,7 @@ schema, use the `SCHEMA` clause to specify the schema, like so: Testing pgTAP with pgTAP ------------------------ - In addition to the PostgreSQL-standard `installcheck` target, the `test` +In addition to the PostgreSQL-standard `installcheck` target, the `test` target uses the `pg_prove` Perl program to do its testing, which needs to be installed separately from [TAP::Parser::SourceHandler::pgTAP](https://metacpan.org/module/TAP::Parser::SourceHandler::pgTAP) From 9535cbaee6f472c73875bc878c4f61f288b711c1 Mon Sep 17 00:00:00 2001 From: Jim Nasby Date: Thu, 20 May 2021 18:34:20 -0500 Subject: [PATCH 1100/1195] Add missing ' to docs for col_default_is() example --- doc/pgtap.mmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 4d932591ce8a..b55f7606d8a8 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4459,7 +4459,7 @@ which to use: SELECT col_default_is( 'widgets', 'created_by', - CASE WHEN pg_version_num() >= 100000 THEN 'CURRENT_USER ELSE '"current_user"()' END + CASE WHEN pg_version_num() >= 100000 THEN 'CURRENT_USER' ELSE '"current_user"()' END ); See the note in the From d3aad9579fa9f07a9fcf95f90b4b94458b96da9a Mon Sep 17 00:00:00 2001 From: Joel Pepper Date: Tue, 26 Jan 2021 20:13:29 +0100 Subject: [PATCH 1101/1195] Remove { DEFAULT | = } and default_expr in uninstall_pgtap.sql Previously the Makefile produced the DROP Function statements by copying the function definitions verbatim, however DROP Function does not recognize default values and will therefore error when trying to drop a function that the install script created with default values for some or all arguments --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b74520974a48..c36b99363a63 100644 --- a/Makefile +++ b/Makefile @@ -254,7 +254,7 @@ ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][012]|8[.][1234])" && echo yes | endif sql/uninstall_pgtap.sql: sql/pgtap.sql test/setup.sql - grep '^CREATE ' sql/pgtap.sql | $(PERL) -e 'for (reverse ) { chomp; s/CREATE (OR REPLACE )?/DROP /; print "$$_;\n" }' | sed 's/DROP \(FUNCTION\|VIEW\|TYPE\) /DROP \1 IF EXISTS /' > sql/uninstall_pgtap.sql + grep '^CREATE ' sql/pgtap.sql | $(PERL) -e 'for (reverse ) { chomp; s/CREATE (OR REPLACE )?/DROP /; print "$$_;\n" }' | sed 's/DROP \(FUNCTION\|VIEW\|TYPE\) /DROP \1 IF EXISTS /' | sed -E 's/ (DEFAULT|=)[ ]+[a-zA-Z0-9]+//g' > sql/uninstall_pgtap.sql # # Support for static install files From f24ab4fbc058a4b378b2e5f94fb59a224aa248b0 Mon Sep 17 00:00:00 2001 From: Christoph Berg Date: Thu, 9 Sep 2021 16:43:22 +0200 Subject: [PATCH 1102/1195] Debian deprecates `which`, use `command -v` instead debianutils (5.3-1) unstable; urgency=medium * The 'which' utility will be removed in the future. Shell scripts often use it to check whether a command is available. A more standard way to do this is with 'command -v'; for example: if command -v update-icon-caches >/dev/null; then update-icon-caches /usr/share/icons/... fi '2>/dev/null' is unnecessary when using 'command': POSIX says "no output shall be written" if the command isn't found. It's also unnecessary for the debianutils version of 'which', and hides the deprecation warning. -- Clint Adams Fri, 20 Aug 2021 07:22:18 -0400 --- Makefile | 6 +++--- test/test_MVU.sh | 8 ++++---- tools/getos.sh | 6 +++--- tools/util.sh | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index c36b99363a63..fedaf7c652fb 100644 --- a/Makefile +++ b/Makefile @@ -80,7 +80,7 @@ endif # We need to do various things with the PostgreSQL version. VERSION = $(shell $(PG_CONFIG) --version | awk '{print $$2}') $(info ) -$(info GNUmake running against Postgres version $(VERSION), with pg_config located at $(shell dirname `which "$(PG_CONFIG)"`)) +$(info GNUmake running against Postgres version $(VERSION), with pg_config located at $(shell dirname `command -v "$(PG_CONFIG)"`)) $(info ) # @@ -163,10 +163,10 @@ endif # We need Perl. ifneq (,$(findstring missing,$(PERL))) -PERL := $(shell which perl) +PERL := $(shell command -v perl) else ifndef PERL -PERL := $(shell which perl) +PERL := $(shell command -v perl) endif endif diff --git a/test/test_MVU.sh b/test/test_MVU.sh index f2301cca9c54..da374178b543 100755 --- a/test/test_MVU.sh +++ b/test/test_MVU.sh @@ -117,8 +117,8 @@ fi sudo='' if [ "$1" == '-s' ]; then # Useful error if we can't find sudo - which sudo > /dev/null - sudo=$(which sudo) + command -v sudo > /dev/null || echo "sudo not found" + sudo=$(command -v sudo) debug 2 "sudo located at $sudo" shift fi @@ -150,7 +150,7 @@ exit_trap() { set +e # Force sudo on a debian system (see below) - [ -z "$ctl_separator" ] || sudo=$(which sudo) + [ -z "$ctl_separator" ] || sudo=$(command -v sudo) # Attempt to shut down any running clusters, otherwise we'll get log spew # when the temporary directories vanish. @@ -166,7 +166,7 @@ exit_trap() { debug 5 "traps: $(trap -p)" cluster_name=test_pg_upgrade -if which pg_ctlcluster > /dev/null 2>&1; then +if command -v pg_ctlcluster > /dev/null; then # Looks like we're running in a apt / Debian / Ubuntu environment, so use their tooling ctl_separator='--' diff --git a/tools/getos.sh b/tools/getos.sh index 67e25d5b3e64..978216dfe807 100755 --- a/tools/getos.sh +++ b/tools/getos.sh @@ -1,8 +1,8 @@ #!/bin/sh -uname=`which uname` -sed=`which sed` -tr=`which tr` +uname=`command -v uname` +sed=`command -v sed` +tr=`command -v tr` myuname='' newmyuname='' trnl='' diff --git a/tools/util.sh b/tools/util.sh index 67dfd2b5b0d3..b781490c11d3 100644 --- a/tools/util.sh +++ b/tools/util.sh @@ -158,7 +158,7 @@ err_report() { find_at_path() ( export PATH="$1:$PATH" # Unfortunately need to maintain old PATH to be able to find `which` :( -out=$(which $2) +out=$(command -v $2) [ -n "$out" ] || die 2 "unable to find $2" echo $out ) From b409303b34f3cad3cb28e1b3a2442d90b71f3fc6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 9 Oct 2021 12:13:17 -0400 Subject: [PATCH 1103/1195] Document need for -contrib for tests And also note that PostgreSQL needs to be running on the local host, as pgTAP cannot be installed remotely. Resolves #276. --- Changes | 3 +++ README.md | 12 ++++++++++++ doc/pgtap.mmd | 14 +++++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 58601d8e4cc9..620d4028fa26 100644 --- a/Changes +++ b/Changes @@ -21,6 +21,9 @@ Revision history for pgTAP * Removed straggler references to Postgres 9.0 and earlier, including conditional tests. * Updated all relevant URLs to use https instead of http. +* Added instructions to install the extension on a host with PostgreSQL running + and with the contrib modules installed. Thanks to F. Eugene Aumson for the + report (#276). 1.1.0 2019-11-25T19:05:38Z -------------------------- diff --git a/README.md b/README.md index e78ac8ff4343..ff46b9137003 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ documentation in `doc/pgtap.mmd` or [![PGXN version](https://badge.fury.io/pg/pgtap.svg)](https://badge.fury.io/pg/pgtap) [![Build Status](https://travis-ci.org/theory/pgtap.png)](https://travis-ci.org/theory/pgtap) +pgTAP must be installed on a host with PostgreSQL server running; it cannot +be installed remotely. + To build it, just do this: make @@ -59,6 +62,15 @@ You need to run the test suite using a super user, such as the default make installcheck PGUSER=postgres +If you encounter an error such as: + + ERROR: Missing extensions required for testing: citext isn ltree + +Install the PostgreSQL +[Additional Supplied Modules](https://www.postgresql.org/docs/current/contrib.html), +which are required to run the tests. If you used a package management system +such as RPM to install PostgreSQL, install the `-contrib` package. + Once pgTAP is installed, you can add it to a database by connecting as a super user and running: diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index b55f7606d8a8..090f4de2e8ef 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -51,7 +51,10 @@ Synopsis Installation ============ -For the impatient, to install pgTAP into a PostgreSQL database, just do this: +pgTAP must be installed on a host with PostgreSQL server running; it cannot +be installed remotely. + +To install pgTAP into a PostgreSQL database, just do this: make make install @@ -98,6 +101,15 @@ You need to run the test suite using a super user, such as the default make installcheck PGUSER=postgres +If you encounter an error such as: + + ERROR: Missing extensions required for testing: citext isn ltree + +Install the PostgreSQL +[Additional Supplied Modules](https://www.postgresql.org/docs/current/contrib.html), +which are required to run the tests. If you used a package management system +such as RPM to install PostgreSQL, install the `-contrib` package. + Once pgTAP is installed, you can add it to a database by connecting as a super user and running: From 6d072d9771a5fddf197a78091d12bca3fb398240 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 9 Oct 2021 22:45:13 -0400 Subject: [PATCH 1104/1195] Fix test failures on Postgres 14 Due to the following changes: * Support for right unary (postfix) operators was dropped, so has_rightop() does not work (and has no operators to test against) on Postgres 14. So mock the test output for that function on Postgres 14. * The types for functions work on changed from anyelement/anyarray to anycompatible/anycompatiblearray. So use the appropriate types depending on the versin of Postgres. * A primary key was added to pg_catalog.pg_class, breaking tests that did not expect it. So create primary key-less and unique key-less tables in the tests that we can depend on, instead. --- Changes | 6 +- Makefile | 2 +- doc/pgtap.mmd | 18 +-- test/sql/functap.sql | 81 +++++++++----- test/sql/hastap.sql | 255 ++++++++++++++++++++++++++----------------- test/sql/pktap.sql | 20 ++-- test/sql/unique.sql | 12 +- 7 files changed, 241 insertions(+), 153 deletions(-) diff --git a/Changes b/Changes index 620d4028fa26..b202e462de58 100644 --- a/Changes +++ b/Changes @@ -18,12 +18,14 @@ Revision history for pgTAP identifier. Thanks to Matt DeLuco for the report (#216)! * Fixed the `col_not_null()` drop statements in the uninstall script. Thanks to Kyle L. Jensen for the report (#252). -* Removed straggler references to Postgres 9.0 and earlier, including conditional - tests. +* Removed straggler references to Postgres 9.0 and earlier, including + conditional tests. * Updated all relevant URLs to use https instead of http. * Added instructions to install the extension on a host with PostgreSQL running and with the contrib modules installed. Thanks to F. Eugene Aumson for the report (#276). +* Fixed test failures due to changes in PostgreSQL 14. Thanks to Christoph Berg + for the report (#277). 1.1.0 2019-11-25T19:05:38Z -------------------------- diff --git a/Makefile b/Makefile index fedaf7c652fb..88280e29a42b 100644 --- a/Makefile +++ b/Makefile @@ -134,7 +134,7 @@ EXCLUDE_TEST_FILES += test/sql/policy.sql endif # Partition tests tests not supported by 9.x and earlier. -ifeq ($(shell echo $(VERSION) | grep -qE "[89][.]" && echo yes || echo no),yes) +ifeq ($(shell echo $(VERSION) | grep -qE "^[89][.]" && echo yes || echo no),yes) EXCLUDE_TEST_FILES += test/sql/partitions.sql endif diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 090f4de2e8ef..bfc7da40cd89 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -3875,8 +3875,9 @@ fail. If the operator does not exist, the test will fail. Example: If you omit the schema name, then the operator must be visible in the search path. If you omit the test description, pgTAP will generate a reasonable one -for you. The return value is also optional. If you need to test for a left or -right unary operator, use `has_leftop()` or `has_rightop()` instead. +for you. The return value is also optional. If you need to test for a left +(prefix) or right (postfix) unary operator, use `has_leftop()` or +`has_rightop()` instead. ### `has_leftop()` ### @@ -3904,9 +3905,9 @@ right unary operator, use `has_leftop()` or `has_rightop()` instead. `:description` : A short description of the test. -Tests for the presence of a left-unary operator. If the operator exists with -the given schema, name, right argument, and return value, the test will fail. -If the operator does not exist, the test will fail. Example: +Tests for the presence of a left-unary (prefix) operator. If the operator +exists with the given schema, name, right argument, and return value, the +test will fail. If the operator does not exist, the test will fail. Example: SELECT has_leftop( 'pg_catalog', '!!', 'bigint', 'numeric' ); @@ -3940,9 +3941,10 @@ for you. The return type is also optional. `:description` : A short description of the test. -Tests for the presence of a right-unary operator. If the operator exists with -the given left argument, schema, name, and return value, the test will fail. -If the operator does not exist, the test will fail. Example: +Tests for the presence of a right-unary (postfix) operator, supported through +PostgreSQL 13. If the operator exists with the given left argument, schema, +name, and return value, the test will fail. If the operator does not exist, +the test will fail. Example: SELECT has_rightop( 'bigint', 'pg_catalog', '!', 'numeric' ); diff --git a/test/sql/functap.sql b/test/sql/functap.sql index 45f23fc67afa..b62e4ac21ad9 100644 --- a/test/sql/functap.sql +++ b/test/sql/functap.sql @@ -15,12 +15,37 @@ AS 'BEGIN RETURN TRUE; END;' LANGUAGE plpgsql IMMUTABLE; CREATE FUNCTION public.pet () RETURNS SETOF BOOL AS 'BEGIN RETURN NEXT TRUE; RETURN; END;' LANGUAGE plpgsql STABLE; -CREATE AGGREGATE public.tap_accum ( - sfunc = array_append, - basetype = anyelement, - stype = anyarray, - initcond = '{}' -); +DO $$ +BEGIN + IF pg_version_num() >= 140000 THEN + CREATE AGGREGATE public.tap_accum ( + sfunc = array_append, + basetype = anycompatible, + stype = anycompatiblearray, + initcond = '{}' + ); + CREATE FUNCTION atype() + RETURNS text AS 'SELECT ''anycompatiblearray''::text' + LANGUAGE SQL IMMUTABLE; + CREATE FUNCTION etype() + RETURNS text AS 'SELECT ''anycompatible''::text' + LANGUAGE SQL IMMUTABLE; + ELSE + CREATE AGGREGATE public.tap_accum ( + sfunc = array_append, + basetype = anyelement, + stype = anyarray, + initcond = '{}' + ); + CREATE FUNCTION atype() + RETURNS text AS 'SELECT ''anyarray''::text' + LANGUAGE SQL IMMUTABLE; + CREATE FUNCTION etype() + RETURNS text AS 'SELECT ''anyelement''::text' + LANGUAGE SQL IMMUTABLE; + END IF; +END; +$$; /****************************************************************************/ -- Test has_function(). @@ -105,10 +130,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_function( 'array_cat', ARRAY['anyarray','anyarray'] ), + has_function( 'array_cat', ARRAY[atype(), atype()] ), true, 'simple array function', - 'Function array_cat(anyarray, anyarray) should exist', + 'Function array_cat(' || atype() || ', ' || atype() || ') should exist', '' ); @@ -265,10 +290,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_function( 'array_cat', ARRAY['anyarray','anyarray'] ), + hasnt_function( 'array_cat', ARRAY[atype(), atype()] ), false, 'simple array function', - 'Function array_cat(anyarray, anyarray) should not exist', + 'Function array_cat(' || atype() || ', ' || atype() || ') should not exist', '' ); @@ -1018,7 +1043,7 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test is_aggregate() and isnt_aggregate(). SELECT * FROM check_test( - is_aggregate( 'public', 'tap_accum', ARRAY['anyelement'], 'whatever' ), + is_aggregate( 'public', 'tap_accum', ARRAY[etype()], 'whatever' ), true, 'is_aggregate(schema, func, arg, desc)', 'whatever', @@ -1026,7 +1051,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_aggregate( 'public', 'tap_accum', ARRAY['anyelement'], 'whatever' ), + isnt_aggregate( 'public', 'tap_accum', ARRAY[etype()], 'whatever' ), false, 'isnt_aggregate(schema, func, arg, desc)', 'whatever', @@ -1034,18 +1059,18 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_aggregate( 'public', 'tap_accum', ARRAY['anyelement'] ), + is_aggregate( 'public', 'tap_accum', ARRAY[etype()] ), true, 'is_aggregate(schema, func, arg)', - 'Function public.tap_accum(anyelement) should be an aggregate function', + 'Function public.tap_accum(' || etype() || ') should be an aggregate function', '' ); SELECT * FROM check_test( - isnt_aggregate( 'public', 'tap_accum', ARRAY['anyelement'] ), + isnt_aggregate( 'public', 'tap_accum', ARRAY[etype()] ), false, 'isnt_aggregate(schema, func, arg)', - 'Function public.tap_accum(anyelement) should not be an aggregate function', + 'Function public.tap_accum(' || etype() || ') should not be an aggregate function', '' ); @@ -1114,7 +1139,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_aggregate( 'public', 'tap_accum', ARRAY['anyelement'], 'whatever' ), + is_aggregate( 'public', 'tap_accum', ARRAY[etype()], 'whatever' ), true, 'is_aggregate(schema, func, arg, desc)', 'whatever', @@ -1122,7 +1147,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_aggregate( 'public', 'tap_accum', ARRAY['anyelement'], 'whatever' ), + isnt_aggregate( 'public', 'tap_accum', ARRAY[etype()], 'whatever' ), false, 'isnt_aggregate(schema, func, arg, desc)', 'whatever', @@ -1130,18 +1155,18 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_aggregate( 'public', 'tap_accum', ARRAY['anyelement'] ), + is_aggregate( 'public', 'tap_accum', ARRAY[etype()] ), true, 'is_aggregate(schema, func, arg)', - 'Function public.tap_accum(anyelement) should be an aggregate function', + 'Function public.tap_accum(' || etype() || ') should be an aggregate function', '' ); SELECT * FROM check_test( - isnt_aggregate( 'public', 'tap_accum', ARRAY['anyelement'] ), + isnt_aggregate( 'public', 'tap_accum', ARRAY[etype()] ), false, 'isnt_aggregate(schema, func, arg)', - 'Function public.tap_accum(anyelement) should not be an aggregate function', + 'Function public.tap_accum(' || etype() || ') should not be an aggregate function', '' ); @@ -1210,7 +1235,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_aggregate( 'tap_accum', ARRAY['anyelement'], 'whatever' ), + is_aggregate( 'tap_accum', ARRAY[etype()], 'whatever' ), true, 'is_aggregate(func, arg, desc)', 'whatever', @@ -1218,7 +1243,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_aggregate( 'tap_accum', ARRAY['anyelement'], 'whatever' ), + isnt_aggregate( 'tap_accum', ARRAY[etype()], 'whatever' ), false, 'isnt_aggregate(func, arg, desc)', 'whatever', @@ -1226,18 +1251,18 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_aggregate( 'tap_accum', ARRAY['anyelement'] ), + is_aggregate( 'tap_accum', ARRAY[etype()] ), true, 'is_aggregate(func, arg)', - 'Function tap_accum(anyelement) should be an aggregate function', + 'Function tap_accum(' || etype() || ') should be an aggregate function', '' ); SELECT * FROM check_test( - isnt_aggregate( 'tap_accum', ARRAY['anyelement'] ), + isnt_aggregate( 'tap_accum', ARRAY[etype()] ), false, 'isnt_aggregate(func, arg)', - 'Function tap_accum(anyelement) should not be an aggregate function', + 'Function tap_accum(' || etype() || ') should not be an aggregate function', '' ); diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index 344819056417..e63c6db15c71 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1459,7 +1459,7 @@ SELECT * FROM check_test( -- Test has_leftop(). SELECT * FROM check_test( - has_leftop( 'pg_catalog', '!!', 'bigint', 'numeric', 'desc' ), + has_leftop( 'pg_catalog', '+', 'bigint', 'bigint', 'desc' ), true, 'has_leftop( schema, name, right, result, desc )', 'desc', @@ -1467,15 +1467,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_leftop( 'pg_catalog', '!!', 'bigint', 'numeric'::name ), + has_leftop( 'pg_catalog', '+', 'bigint', 'bigint'::name ), true, 'has_leftop( schema, name, right, result )', - 'Left operator pg_catalog.!!(NONE,bigint) RETURNS numeric should exist', + 'Left operator pg_catalog.+(NONE,bigint) RETURNS bigint should exist', '' ); SELECT * FROM check_test( - has_leftop( '!!', 'bigint', 'numeric', 'desc' ), + has_leftop( '+', 'bigint', 'bigint', 'desc' ), true, 'has_leftop( name, right, result, desc )', 'desc', @@ -1483,15 +1483,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_leftop( '!!', 'bigint', 'numeric'::name ), + has_leftop( '+', 'bigint', 'bigint'::name ), true, 'has_leftop( name, right, result )', - 'Left operator !!(NONE,bigint) RETURNS numeric should exist', + 'Left operator +(NONE,bigint) RETURNS bigint should exist', '' ); SELECT * FROM check_test( - has_leftop( '!!', 'bigint', 'desc' ), + has_leftop( '+', 'bigint', 'desc' ), true, 'has_leftop( name, right, desc )', 'desc', @@ -1499,15 +1499,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_leftop( '!!', 'bigint' ), + has_leftop( '+', 'bigint' ), true, 'has_leftop( name, right )', - 'Left operator !!(NONE,bigint) should exist', + 'Left operator +(NONE,bigint) should exist', '' ); SELECT * FROM check_test( - has_leftop( 'pg_catalog', '!!', 'text', 'numeric', 'desc' ), + has_leftop( 'pg_catalog', '+', 'text', 'numeric', 'desc' ), false, 'has_leftop( schema, name, right, result, desc ) fail', 'desc', @@ -1515,15 +1515,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_leftop( 'pg_catalog', '!!', 'text', 'numeric'::name ), + has_leftop( 'pg_catalog', '+', 'text', 'numeric'::name ), false, 'has_leftop( schema, name, right, result ) fail', - 'Left operator pg_catalog.!!(NONE,text) RETURNS numeric should exist', + 'Left operator pg_catalog.+(NONE,text) RETURNS numeric should exist', '' ); SELECT * FROM check_test( - has_leftop( '!!', 'text', 'numeric', 'desc' ), + has_leftop( '+', 'text', 'integer', 'desc' ), false, 'has_leftop( name, right, result, desc ) fail', 'desc', @@ -1531,15 +1531,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_leftop( '!!', 'text', 'numeric'::name ), + has_leftop( '+', 'text', 'integer'::name ), false, 'has_leftop( name, right, result ) fail', - 'Left operator !!(NONE,text) RETURNS numeric should exist', + 'Left operator +(NONE,text) RETURNS integer should exist', '' ); SELECT * FROM check_test( - has_leftop( '!!', 'text', 'desc' ), + has_leftop( '+', 'text', 'desc' ), false, 'has_leftop( name, right, desc ) fail', 'desc', @@ -1547,111 +1547,162 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_leftop( '!!', 'text' ), + has_leftop( '+', 'text' ), false, 'has_leftop( name, right ) fail', - 'Left operator !!(NONE,text) should exist', + 'Left operator +(NONE,text) should exist', '' ); /****************************************************************************/ -- Test has_rightop(). -SELECT * FROM check_test( - has_rightop( 'bigint', 'pg_catalog', '!', 'numeric', 'desc' ), - true, - 'has_rightop( left, schema, name, result, desc )', - 'desc', - '' -); +CREATE FUNCTION test_rightop() RETURNS SETOF TEXT LANGUAGE plpgsql AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() < 140000 THEN + FOR tap IN SELECT * FROM check_test( + has_rightop( 'bigint', 'pg_catalog', '!', 'numeric', 'desc' ), + true, + 'has_rightop( left, schema, name, result, desc )', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - has_rightop( 'bigint', 'pg_catalog', '!', 'numeric'::name ), - true, - 'has_rightop( left, schema, name, result )', - 'Right operator pg_catalog.!(bigint,NONE) RETURNS numeric should exist', - '' -); + FOR tap IN SELECT * FROM check_test( + has_rightop( 'bigint', 'pg_catalog', '!', 'numeric'::name ), + true, + 'has_rightop( left, schema, name, result )', + 'Right operator pg_catalog.!(bigint,NONE) RETURNS numeric should exist', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - has_rightop( 'bigint', '!', 'numeric', 'desc' ), - true, - 'has_rightop( left, name, result, desc )', - 'desc', - '' -); + FOR tap IN SELECT * FROM check_test( + has_rightop( 'bigint', '!', 'numeric', 'desc' ), + true, + 'has_rightop( left, name, result, desc )', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - has_rightop( 'bigint', '!', 'numeric'::name ), - true, - 'has_rightop( left, name, result )', - 'Right operator !(bigint,NONE) RETURNS numeric should exist', - '' -); + FOR tap IN SELECT * FROM check_test( + has_rightop( 'bigint', '!', 'numeric'::name ), + true, + 'has_rightop( left, name, result )', + 'Right operator !(bigint,NONE) RETURNS numeric should exist', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - has_rightop( 'bigint', '!', 'desc' ), - true, - 'has_rightop( left, name, desc )', - 'desc', - '' -); + FOR tap IN SELECT * FROM check_test( + has_rightop( 'bigint', '!', 'desc' ), + true, + 'has_rightop( left, name, desc )', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - has_rightop( 'bigint', '!' ), - true, - 'has_rightop( left, name )', - 'Right operator !(bigint,NONE) should exist', - '' -); + FOR tap IN SELECT * FROM check_test( + has_rightop( 'bigint', '!' ), + true, + 'has_rightop( left, name )', + 'Right operator !(bigint,NONE) should exist', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - has_rightop( 'text', 'pg_catalog', '!', 'numeric', 'desc' ), - false, - 'has_rightop( left, schema, name, result, desc ) fail', - 'desc', - '' -); + FOR tap IN SELECT * FROM check_test( + has_rightop( 'text', 'pg_catalog', '!', 'numeric', 'desc' ), + false, + 'has_rightop( left, schema, name, result, desc ) fail', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - has_rightop( 'text', 'pg_catalog', '!', 'numeric'::name ), - false, - 'has_rightop( left, schema, name, result ) fail', - 'Right operator pg_catalog.!(text,NONE) RETURNS numeric should exist', - '' -); + FOR tap IN SELECT * FROM check_test( + has_rightop( 'text', 'pg_catalog', '!', 'numeric'::name ), + false, + 'has_rightop( left, schema, name, result ) fail', + 'Right operator pg_catalog.!(text,NONE) RETURNS numeric should exist', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - has_rightop( 'text', '!', 'numeric', 'desc' ), - false, - 'has_rightop( left, name, result, desc ) fail', - 'desc', - '' -); + FOR tap IN SELECT * FROM check_test( + has_rightop( 'text', '!', 'numeric', 'desc' ), + false, + 'has_rightop( left, name, result, desc ) fail', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - has_rightop( 'text', '!', 'numeric'::name ), - false, - 'has_rightop( left, name, result ) fail', - 'Right operator !(text,NONE) RETURNS numeric should exist', - '' -); + FOR tap IN SELECT * FROM check_test( + has_rightop( 'text', '!', 'numeric'::name ), + false, + 'has_rightop( left, name, result ) fail', + 'Right operator !(text,NONE) RETURNS numeric should exist', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - has_rightop( 'text', '!', 'desc' ), - false, - 'has_rightop( left, name, desc ) fail', - 'desc', - '' -); + FOR tap IN SELECT * FROM check_test( + has_rightop( 'text', '!', 'desc' ), + false, + 'has_rightop( left, name, desc ) fail', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + + FOR tap IN SELECT * FROM check_test( + has_rightop( 'text', '!' ), + false, + 'has_rightop( left, name ) fail', + 'Right operator !(text,NONE) should exist', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + ELSE + -- PostgreSQL 14 dropped support for postfix operators, so mock the + -- output for the test to pass. + FOR tap IN SELECT * FROM (VALUES + ('has_rightop( left, schema, name, result, desc ) should pass'), + ('has_rightop( left, schema, name, result, desc ) should have the proper description'), + ('has_rightop( left, schema, name, result, desc ) should have the proper diagnostics'), + ('has_rightop( left, schema, name, result ) should pass'), + ('has_rightop( left, schema, name, result ) should have the proper description'), + ('has_rightop( left, schema, name, result ) should have the proper diagnostics'), + ('has_rightop( left, name, result, desc ) should pass'), + ('has_rightop( left, name, result, desc ) should have the proper description'), + ('has_rightop( left, name, result, desc ) should have the proper diagnostics'), + ('has_rightop( left, name, result ) should pass'), + ('has_rightop( left, name, result ) should have the proper description'), + ('has_rightop( left, name, result ) should have the proper diagnostics'), + ('has_rightop( left, name, desc ) should pass'), + ('has_rightop( left, name, desc ) should have the proper description'), + ('has_rightop( left, name, desc ) should have the proper diagnostics'), + ('has_rightop( left, name ) should pass'), + ('has_rightop( left, name ) should have the proper description'), + ('has_rightop( left, name ) should have the proper diagnostics'), + ('has_rightop( left, schema, name, result, desc ) fail should fail'), + ('has_rightop( left, schema, name, result, desc ) fail should have the proper description'), + ('has_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics'), + ('has_rightop( left, schema, name, result ) fail should fail'), + ('has_rightop( left, schema, name, result ) fail should have the proper description'), + ('has_rightop( left, schema, name, result ) fail should have the proper diagnostics'), + ('has_rightop( left, name, result, desc ) fail should fail'), + ('has_rightop( left, name, result, desc ) fail should have the proper description'), + ('has_rightop( left, name, result, desc ) fail should have the proper diagnostics'), + ('has_rightop( left, name, result ) fail should fail'), + ('has_rightop( left, name, result ) fail should have the proper description'), + ('has_rightop( left, name, result ) fail should have the proper diagnostics'), + ('has_rightop( left, name, desc ) fail should fail'), + ('has_rightop( left, name, desc ) fail should have the proper description'), + ('has_rightop( left, name, desc ) fail should have the proper diagnostics'), + ('has_rightop( left, name ) fail should fail'), + ('has_rightop( left, name ) fail should have the proper description'), + ('has_rightop( left, name ) fail should have the proper diagnostics') + ) AS A(b) LOOP RETURN NEXT pass(tap.b); END LOOP; + END IF; +END; +$$; +SELECT * FROM test_rightop(); -SELECT * FROM check_test( - has_rightop( 'text', '!' ), - false, - 'has_rightop( left, name ) fail', - 'Right operator !(text,NONE) should exist', - '' -); /****************************************************************************/ -- Test has_language() and hasnt_language(). diff --git a/test/sql/pktap.sql b/test/sql/pktap.sql index f7577c361d5c..b814277406fd 100644 --- a/test/sql/pktap.sql +++ b/test/sql/pktap.sql @@ -12,6 +12,10 @@ CREATE TABLE public.sometab( numb NUMERIC(10, 2), myint NUMERIC(8) ); +-- This table has no pk +CREATE TABLE public.pkless( + id INT NOT NULL UNIQUE +); CREATE SCHEMA hide; CREATE TABLE hide.hidesometab( id INT NOT NULL PRIMARY KEY, @@ -66,18 +70,18 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_pk( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should have a pk' ), + has_pk( 'public', 'pkless', 'public.pkless should have a pk' ), false, 'has_pk( schema, table, description ) fail', - 'pg_catalog.pg_class should have a pk', + 'public.pkless should have a pk', '' ); SELECT * FROM check_test( - has_pk( 'pg_class', 'pg_class should have a pk' ), + has_pk( 'pkless', 'pkless should have a pk' ), false, 'has_pk( table, description ) fail', - 'pg_class should have a pk', + 'pkless should have a pk', '' ); @@ -109,18 +113,18 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_pk( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should not have a pk' ), + hasnt_pk( 'public', 'pkless', 'public.pkless should not have a pk' ), true, 'hasnt_pk( schema, table, description ) pass', - 'pg_catalog.pg_class should not have a pk', + 'public.pkless should not have a pk', '' ); SELECT * FROM check_test( - hasnt_pk( 'pg_class', 'pg_class should not have a pk' ), + hasnt_pk( 'pkless', 'pkless should not have a pk' ), true, 'hasnt_pk( table, description ) pass', - 'pg_class should not have a pk', + 'pkless should not have a pk', '' ); diff --git a/test/sql/unique.sql b/test/sql/unique.sql index cd6ec325ba82..1019beb2adf2 100644 --- a/test/sql/unique.sql +++ b/test/sql/unique.sql @@ -12,6 +12,10 @@ CREATE TABLE public.sometab( myint NUMERIC(8), UNIQUE (numb, myint) ); +-- This table has no unique index +CREATE TABLE public.uniqueless( + id INT PRIMARY KEY +); RESET client_min_messages; /****************************************************************************/ @@ -42,18 +46,18 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_unique( 'pg_catalog', 'pg_class', 'pg_catalog.pg_class should have a unique constraint' ), + has_unique( 'public', 'uniqueless', 'public.uniqueless should have a unique constraint' ), false, 'has_unique( schema, table, description ) fail', - 'pg_catalog.pg_class should have a unique constraint', + 'public.uniqueless should have a unique constraint', '' ); SELECT * FROM check_test( - has_unique( 'pg_class', 'pg_class should have a unique constraint' ), + has_unique( 'uniqueless', 'uniqueless should have a unique constraint' ), false, 'has_unique( table, description ) fail', - 'pg_class should have a unique constraint', + 'uniqueless should have a unique constraint', '' ); From a760d7c767325f593cf4a2d6f4fea1efd36d4ad6 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Fri, 29 May 2020 15:08:00 +0200 Subject: [PATCH 1105/1195] Add isnt_member_of --- Changes | 4 +++- doc/pgtap.mmd | 39 ++++++++++++++++++++++++++++++ sql/pgtap--1.1.0--1.2.0.sql | 47 +++++++++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 47 +++++++++++++++++++++++++++++++++++++ test/expected/usergroup.out | 38 ++++++++++++++++++++---------- test/sql/usergroup.sql | 36 +++++++++++++++++++++++++++- 6 files changed, 196 insertions(+), 15 deletions(-) diff --git a/Changes b/Changes index b202e462de58..211f115c6dda 100644 --- a/Changes +++ b/Changes @@ -26,6 +26,8 @@ Revision history for pgTAP report (#276). * Fixed test failures due to changes in PostgreSQL 14. Thanks to Christoph Berg for the report (#277). +* Added `isnt_member_of()` (#38, #204). Thanks to Wolfgang Walther for the + pull request (#249). 1.1.0 2019-11-25T19:05:38Z -------------------------- @@ -44,7 +46,7 @@ Revision history for pgTAP version 0.99.0 to 1.0.0 (#214). * Fix issue with using pg_upgrade to PostgreSQL 11+ with pgTap installed (#201, #215). -* Start using `DEFAULT` in functions. Previously +* Start using `DEFAULT` in functions. * Add automated testing of extension upgrade scripts (#128), as well as testing of pg_upgrade with pgTap installed (#218, #222). diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index bfc7da40cd89..12bec9992e54 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5952,6 +5952,45 @@ If the group role does not exist, the diagnostics will tell you that, instead. But you use `has_role()` to make sure the role exists before you check its members, don't you? Of course you do. +### `isnt_member_of()` ### + + SELECT isnt_member_of( :role, :members, :description ); + SELECT isnt_member_of( :role, :members ); + SELECT isnt_member_of( :role, :member, :description ); + SELECT isnt_member_of( :role, :member ); + +**Parameters** + +`:role` +: Name of a PostgreSQL group role. + +`:members` +: Array of names of roles that should *not* be members of the group role. + +`:member` +: Name of a role that should *not* be a member of the group role. + +`:description` +: A short description of the test. + + SELECT isnt_member_of( 'meanies', 'anna' 'Anna should not be a meanie' ); + SELECT isnt_member_of( 'sweeties', ARRAY['dr_evil', 'dr_no' ] ); + +The inverse of `is_member_of()`, checks whether a group role does not contain +a member role or none of an array of member roles. If the description is +omitted, it will default to "Should not have members of role `:role`." On +failure, `isnt_member_of()` will output diagnostics listing the missing member +roles, like so: + + # Failed test 371: "Should not have members of role sweeties" + # Members, who should not be in sweeties role: + # dr_evil + # dr_no + +If the group role does not exist, the diagnostics will tell you that, instead. +But you use `has_role()` to make sure the role exists before you check its +members, don't you? Of course you do. + ### `rule_is_instead()` ### SELECT rule_is_instead( :schema, :table, :rule, :description ); diff --git a/sql/pgtap--1.1.0--1.2.0.sql b/sql/pgtap--1.1.0--1.2.0.sql index 5bebdc100221..61273de79b99 100644 --- a/sql/pgtap--1.1.0--1.2.0.sql +++ b/sql/pgtap--1.1.0--1.2.0.sql @@ -36,3 +36,50 @@ BEGIN RETURN thing; END; $$ LANGUAGE plpgsql; + +-- 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 2b60f98a42cf..c51d931e91cd 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -4000,6 +4000,53 @@ 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 $$ DECLARE diff --git a/test/expected/usergroup.out b/test/expected/usergroup.out index 03bec0bf28dd..112070cd81ef 100644 --- a/test/expected/usergroup.out +++ b/test/expected/usergroup.out @@ -1,5 +1,5 @@ \unset ECHO -1..78 +1..90 ok 1 - has_user(current user) should pass ok 2 - has_user(current user) should have the proper description ok 3 - has_user(current user) should have the proper diagnostics @@ -66,15 +66,27 @@ ok 63 - hasnt_group(nonexistent group) should have the proper diagnostics ok 64 - hasnt_group(nonexistent group, desc) should pass ok 65 - hasnt_group(nonexistent group, desc) should have the proper description ok 66 - hasnt_group(nonexistent group, desc) should have the proper diagnostics -ok 67 - is_member_of(meanies, [current_user], desc) should pass -ok 68 - is_member_of(meanies, [current_user], desc) should have the proper description -ok 69 - is_member_of(meanies, [current_user], desc) should have the proper diagnostics -ok 70 - is_member_of(meanies, [current_user]) should pass -ok 71 - is_member_of(meanies, [current_user]) should have the proper description -ok 72 - is_member_of(meanies, [current_user]) should have the proper diagnostics -ok 73 - is_member_of(meanies, current_user, desc) should pass -ok 74 - is_member_of(meanies, current_user, desc) should have the proper description -ok 75 - is_member_of(meanies, current_user, desc) should have the proper diagnostics -ok 76 - is_member_of(meanies, current_user) should pass -ok 77 - is_member_of(meanies, current_user) should have the proper description -ok 78 - is_member_of(meanies, current_user) should have the proper diagnostics +ok 67 - isnt_member_of(meanies, [current_user], desc) should pass +ok 68 - isnt_member_of(meanies, [current_user], desc) should have the proper description +ok 69 - isnt_member_of(meanies, [current_user], desc) should have the proper diagnostics +ok 70 - isnt_member_of(meanies, [current_user]) should pass +ok 71 - isnt_member_of(meanies, [current_user]) should have the proper description +ok 72 - isnt_member_of(meanies, [current_user]) should have the proper diagnostics +ok 73 - isnt_member_of(meanies, current_user, desc) should pass +ok 74 - isnt_member_of(meanies, current_user, desc) should have the proper description +ok 75 - isnt_member_of(meanies, current_user, desc) should have the proper diagnostics +ok 76 - isnt_member_of(meanies, current_user) should pass +ok 77 - isnt_member_of(meanies, current_user) should have the proper description +ok 78 - isnt_member_of(meanies, current_user) should have the proper diagnostics +ok 79 - is_member_of(meanies, [current_user], desc) should pass +ok 80 - is_member_of(meanies, [current_user], desc) should have the proper description +ok 81 - is_member_of(meanies, [current_user], desc) should have the proper diagnostics +ok 82 - is_member_of(meanies, [current_user]) should pass +ok 83 - is_member_of(meanies, [current_user]) should have the proper description +ok 84 - is_member_of(meanies, [current_user]) should have the proper diagnostics +ok 85 - is_member_of(meanies, current_user, desc) should pass +ok 86 - is_member_of(meanies, current_user, desc) should have the proper description +ok 87 - is_member_of(meanies, current_user, desc) should have the proper diagnostics +ok 88 - is_member_of(meanies, current_user) should pass +ok 89 - is_member_of(meanies, current_user) should have the proper description +ok 90 - is_member_of(meanies, current_user) should have the proper diagnostics diff --git a/test/sql/usergroup.sql b/test/sql/usergroup.sql index c140c1c4af3c..add4c8476945 100644 --- a/test/sql/usergroup.sql +++ b/test/sql/usergroup.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(78); +SELECT plan(90); --SELECT * FROM no_plan(); /****************************************************************************/ @@ -181,6 +181,40 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test isnt_member_of(). +SELECT * FROM check_test( + isnt_member_of('meanies', ARRAY[current_user], 'whatever' ), + true, + 'isnt_member_of(meanies, [current_user], desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_member_of('meanies', ARRAY[current_user] ), + true, + 'isnt_member_of(meanies, [current_user])', + 'Should not have members of role meanies', + '' +); + +SELECT * FROM check_test( + isnt_member_of('meanies', current_user, 'whatever' ), + true, + 'isnt_member_of(meanies, current_user, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_member_of('meanies', current_user ), + true, + 'isnt_member_of(meanies, current_user)', + 'Should not have members of role meanies', + '' +); + /****************************************************************************/ -- Test is_member_of(). CREATE OR REPLACE FUNCTION addmember() RETURNS SETOF TEXT AS $$ From 3c93cd2bff2cb99af17175fe8391704d8609e40c Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sun, 10 Oct 2021 17:51:33 +0200 Subject: [PATCH 1106/1195] Add docker files for local test environment (#250) Add a `DockerFile` and compose file, along with instructions for running it to simplify testing. --- Changes | 2 ++ doc/pgtap.mmd | 13 +++++++++++++ test/Dockerfile | 9 +++++++++ test/docker-compose.yml | 15 +++++++++++++++ test/run | 7 +++++++ 5 files changed, 46 insertions(+) create mode 100644 test/Dockerfile create mode 100644 test/docker-compose.yml create mode 100755 test/run diff --git a/Changes b/Changes index 211f115c6dda..b4ce75adc2b1 100644 --- a/Changes +++ b/Changes @@ -28,6 +28,8 @@ Revision history for pgTAP for the report (#277). * Added `isnt_member_of()` (#38, #204). Thanks to Wolfgang Walther for the pull request (#249). +* Added docker files for local test environment. Thanks to Wolfgang Walther for + the pull request (#250). 1.1.0 2019-11-25T19:05:38Z -------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 12bec9992e54..1cd8da28bfaa 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -152,6 +152,19 @@ You can use it to run the test suite as a database super user like so: make test PGUSER=postgres +To run the tests in a local docker environment for development, run: + + cd test + docker-compose build test + # start the postgres server in a docker container in the background + docker-compose up -d test + # run the regression tests + docker-compose exec test make install installcheck + # run the tests with pg_proove + # "run" builds and installs pgTAP, runs "CREATE EXTENSION" + # and then runs make test + docker-compose exec test run + Adding pgTAP to a Database -------------------------- diff --git a/test/Dockerfile b/test/Dockerfile new file mode 100644 index 000000000000..8a0f1fc1a1e8 --- /dev/null +++ b/test/Dockerfile @@ -0,0 +1,9 @@ +FROM postgres:13.0-alpine +ENV POSTGRES_HOST_AUTH_METHOD trust + +RUN apk --no-cache add make perl-dev patch \ + && cpan TAP::Parser::SourceHandler::pgTAP +ENV PGUSER postgres + +WORKDIR /pgtap +ENV PATH /pgtap/test:$PATH diff --git a/test/docker-compose.yml b/test/docker-compose.yml new file mode 100644 index 000000000000..048e8f393011 --- /dev/null +++ b/test/docker-compose.yml @@ -0,0 +1,15 @@ +version: "3" + +services: + test: + build: . + volumes: + - ../:/pgtap + - postgres:/var/lib/postgresql/data + +volumes: + postgres: + driver_opts: + type: tmpfs + device: tmpfs + diff --git a/test/run b/test/run new file mode 100755 index 000000000000..9b14b07eb184 --- /dev/null +++ b/test/run @@ -0,0 +1,7 @@ +#!/usr/bin/env sh + +set -e -u -o pipefail + +make install +psql -Ec 'DROP EXTENSION IF EXISTS pgtap; CREATE EXTENSION pgtap;' +make test From 33a2a622e49365763fe218d619658303d9df9bda Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 10 Oct 2021 12:24:32 -0400 Subject: [PATCH 1107/1195] Add pgtag to Dokerfile To allow testing against different versions of Postgres. --- doc/pgtap.mmd | 23 +++++++++++++++++------ test/Dockerfile | 5 +++-- test/docker-compose.yml | 4 +++- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 1cd8da28bfaa..b42491cfc968 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -152,18 +152,29 @@ You can use it to run the test suite as a database super user like so: make test PGUSER=postgres -To run the tests in a local docker environment for development, run: +To run the tests in a local docker environment using the latest version +of PostgreSQL, run: cd test - docker-compose build test + docker compose build test # start the postgres server in a docker container in the background - docker-compose up -d test + docker compose up -d test # run the regression tests - docker-compose exec test make install installcheck - # run the tests with pg_proove + docker compose exec test make install installcheck + # run the tests with pg_prove # "run" builds and installs pgTAP, runs "CREATE EXTENSION" # and then runs make test - docker-compose exec test run + docker compose exec test run + # Shut down the postgres container + docker compose down + +To test with a different version of PostgreSQL, set the environment variable +`$pgtag` to one of the [PostgreSQL Docker](https://hub.docker.com/_/postgres) +tags: + + export pgtag=12-alpine + +Then run the above commands. Adding pgTAP to a Database -------------------------- diff --git a/test/Dockerfile b/test/Dockerfile index 8a0f1fc1a1e8..494055288a46 100644 --- a/test/Dockerfile +++ b/test/Dockerfile @@ -1,8 +1,9 @@ -FROM postgres:13.0-alpine +ARG pgtag +FROM postgres:${pgtag:-alpine} ENV POSTGRES_HOST_AUTH_METHOD trust RUN apk --no-cache add make perl-dev patch \ - && cpan TAP::Parser::SourceHandler::pgTAP + && cpan -T TAP::Parser::SourceHandler::pgTAP ENV PGUSER postgres WORKDIR /pgtap diff --git a/test/docker-compose.yml b/test/docker-compose.yml index 048e8f393011..6346cedf479f 100644 --- a/test/docker-compose.yml +++ b/test/docker-compose.yml @@ -2,7 +2,9 @@ version: "3" services: test: - build: . + build: + context: . + args: [pgtag] volumes: - ../:/pgtap - postgres:/var/lib/postgresql/data From 57c74f9e582a5253eab14fc8eac98ee3d34d6630 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sun, 10 Oct 2021 18:29:53 +0200 Subject: [PATCH 1108/1195] Add hasnt_operator, hasnt_leftop, hasnt_rightop (#251) --- Changes | 2 + doc/pgtap.mmd | 92 +++- sql/pgtap--1.1.0--1.2.0.sql | 140 ++++++ sql/pgtap.sql.in | 141 ++++++ test/expected/hastap.out | 878 ++++++++++++++++++++---------------- test/sql/hastap.sql | 299 +++++++++++- 6 files changed, 1165 insertions(+), 387 deletions(-) diff --git a/Changes b/Changes index b4ce75adc2b1..92d6c5f05aea 100644 --- a/Changes +++ b/Changes @@ -30,6 +30,8 @@ Revision history for pgTAP pull request (#249). * Added docker files for local test environment. Thanks to Wolfgang Walther for the pull request (#250). +* Added `hasnt_operator()`, `hasnt_leftop()`, `hasnt_rightop()` (#38). Thanks to + Wolfgang Walther for the pull request (#251). 1.1.0 2019-11-25T19:05:38Z -------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index b42491cfc968..f4fe900deaae 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -3893,7 +3893,7 @@ cast does *not* exist. Tests for the presence of a binary operator. If the operator exists with the given schema, name, left and right arguments, and return value, the test will -fail. If the operator does not exist, the test will fail. Example: +pass. If the operator does not exist, the test will fail. Example: SELECT has_operator( 'integer', 'pg_catalog', '<=', 'integer', 'boolean' ); @@ -3903,6 +3903,38 @@ for you. The return value is also optional. If you need to test for a left (prefix) or right (postfix) unary operator, use `has_leftop()` or `has_rightop()` instead. +### `hasnt_operator()` ### + + SELECT hasnt_operator( :left_type, :schema, :name, :right_type, :return_type, :description ); + SELECT hasnt_operator( :left_type, :schema, :name, :right_type, :return_type ); + SELECT hasnt_operator( :left_type, :name, :right_type, :return_type, :description ); + SELECT hasnt_operator( :left_type, :name, :right_type, :return_type ); + SELECT hasnt_operator( :left_type, :name, :right_type, :description ); + SELECT hasnt_operator( :left_type, :name, :right_type ); + +**Parameters** + +`:left_type` +: Data type of the left operand. + +`:schema` +: Schema in which to find the operator. + +`:name` +: Name of the operator. + +`:right_type` +: Data type of the right operand. + +`:return_type` +: Data type of the return value. + +`:description` +: A short description of the test. + +This function is the inverse of `has_operator()`. The test passes if the +specified operator does *not* exist. + ### `has_leftop()` ### SELECT has_leftop( :schema, :name, :type, :return_type, :description ); @@ -3939,6 +3971,35 @@ If you omit the schema name, then the operator must be visible in the search path. If you omit the test description, pgTAP will generate a reasonable one for you. The return type is also optional. +### `hasnt_leftop()` ### + + SELECT hasnt_leftop( :schema, :name, :type, :return_type, :description ); + SELECT hasnt_leftop( :schema, :name, :type, :return_type ); + SELECT hasnt_leftop( :name, :type, :return_type, :description ); + SELECT hasnt_leftop( :name, :type, :return_type ); + SELECT hasnt_leftop( :name, :type, :description ); + SELECT hasnt_leftop( :name, :type ); + +**Parameters** + +`:schema` +: Schema in which to find the operator. + +`:name` +: Name of the operator. + +`:type` +: Data type of the operand. + +`:return_type` +: Data type of the return value. + +`:description` +: A short description of the test. + +This function is the inverse of `has_leftop()`. The test passes if the +specified operator does *not* exist. + ### `has_rightop()` ### SELECT has_rightop( :schema, :name, :type, :return_type, :description ); @@ -3976,6 +4037,35 @@ If you omit the schema name, then the operator must be visible in the search path. If you omit the test description, pgTAP will generate a reasonable one for you. The return type is also optional. +### `hasnt_rightop()` ### + + SELECT hasnt_rightop( :schema, :name, :type, :return_type, :description ); + SELECT hasnt_rightop( :schema, :name, :type, :return_type ); + SELECT hasnt_rightop( :name, :type, :return_type, :description ); + SELECT hasnt_rightop( :name, :type, :return_type ); + SELECT hasnt_rightop( :name, :type, :description ); + SELECT hasnt_rightop( :name, :type ); + +**Parameters** + +`:schema` +: Schema in which to find the operator. + +`:name` +: Name of the operator. + +`:type` +: Data type of the operand. + +`:return_type` +: Data type of the return value. + +`:description` +: A short description of the test. + +This function is the inverse of `hasnt_rightop()`. The test passes if the +specified operator does *not* exist. + ### `has_opclass()` ### SELECT has_opclass( :schema, :name, :description ); diff --git a/sql/pgtap--1.1.0--1.2.0.sql b/sql/pgtap--1.1.0--1.2.0.sql index 61273de79b99..9920f4b3098e 100644 --- a/sql/pgtap--1.1.0--1.2.0.sql +++ b/sql/pgtap--1.1.0--1.2.0.sql @@ -37,6 +37,146 @@ BEGIN END; $$ LANGUAGE plpgsql; +-- 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; + +-- 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; + +-- 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' + ); +======= -- isnt_member_of( role, members[], description ) CREATE OR REPLACE FUNCTION isnt_member_of( NAME, NAME[], TEXT ) RETURNS TEXT AS $$ diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index c51d931e91cd..438693318aa6 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -4328,6 +4328,54 @@ RETURNS TEXT AS $$ ); $$ 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 $$ @@ -4374,6 +4422,52 @@ RETURNS TEXT AS $$ ); $$ 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 $$ @@ -4421,6 +4515,53 @@ RETURNS TEXT AS $$ ); $$ 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 diff --git a/test/expected/hastap.out b/test/expected/hastap.out index 7eabfa7821e3..1fdd287cd06c 100644 --- a/test/expected/hastap.out +++ b/test/expected/hastap.out @@ -1,5 +1,5 @@ \unset ECHO -1..896 +1..1004 ok 1 - has_tablespace(non-existent tablespace) should fail ok 2 - has_tablespace(non-existent tablespace) should have the proper description ok 3 - has_tablespace(non-existent tablespace) should have the proper diagnostics @@ -512,387 +512,495 @@ ok 509 - has_operator( left, name, right, desc ) fail should have the proper dia ok 510 - has_operator( left, name, right ) fail should fail ok 511 - has_operator( left, name, right ) fail should have the proper description ok 512 - has_operator( left, name, right ) fail should have the proper diagnostics -ok 513 - has_leftop( schema, name, right, result, desc ) should pass -ok 514 - has_leftop( schema, name, right, result, desc ) should have the proper description -ok 515 - has_leftop( schema, name, right, result, desc ) should have the proper diagnostics -ok 516 - has_leftop( schema, name, right, result ) should pass -ok 517 - has_leftop( schema, name, right, result ) should have the proper description -ok 518 - has_leftop( schema, name, right, result ) should have the proper diagnostics -ok 519 - has_leftop( name, right, result, desc ) should pass -ok 520 - has_leftop( name, right, result, desc ) should have the proper description -ok 521 - has_leftop( name, right, result, desc ) should have the proper diagnostics -ok 522 - has_leftop( name, right, result ) should pass -ok 523 - has_leftop( name, right, result ) should have the proper description -ok 524 - has_leftop( name, right, result ) should have the proper diagnostics -ok 525 - has_leftop( name, right, desc ) should pass -ok 526 - has_leftop( name, right, desc ) should have the proper description -ok 527 - has_leftop( name, right, desc ) should have the proper diagnostics -ok 528 - has_leftop( name, right ) should pass -ok 529 - has_leftop( name, right ) should have the proper description -ok 530 - has_leftop( name, right ) should have the proper diagnostics -ok 531 - has_leftop( schema, name, right, result, desc ) fail should fail -ok 532 - has_leftop( schema, name, right, result, desc ) fail should have the proper description -ok 533 - has_leftop( schema, name, right, result, desc ) fail should have the proper diagnostics -ok 534 - has_leftop( schema, name, right, result ) fail should fail -ok 535 - has_leftop( schema, name, right, result ) fail should have the proper description -ok 536 - has_leftop( schema, name, right, result ) fail should have the proper diagnostics -ok 537 - has_leftop( name, right, result, desc ) fail should fail -ok 538 - has_leftop( name, right, result, desc ) fail should have the proper description -ok 539 - has_leftop( name, right, result, desc ) fail should have the proper diagnostics -ok 540 - has_leftop( name, right, result ) fail should fail -ok 541 - has_leftop( name, right, result ) fail should have the proper description -ok 542 - has_leftop( name, right, result ) fail should have the proper diagnostics -ok 543 - has_leftop( name, right, desc ) fail should fail -ok 544 - has_leftop( name, right, desc ) fail should have the proper description -ok 545 - has_leftop( name, right, desc ) fail should have the proper diagnostics -ok 546 - has_leftop( name, right ) fail should fail -ok 547 - has_leftop( name, right ) fail should have the proper description -ok 548 - has_leftop( name, right ) fail should have the proper diagnostics -ok 549 - has_rightop( left, schema, name, result, desc ) should pass -ok 550 - has_rightop( left, schema, name, result, desc ) should have the proper description -ok 551 - has_rightop( left, schema, name, result, desc ) should have the proper diagnostics -ok 552 - has_rightop( left, schema, name, result ) should pass -ok 553 - has_rightop( left, schema, name, result ) should have the proper description -ok 554 - has_rightop( left, schema, name, result ) should have the proper diagnostics -ok 555 - has_rightop( left, name, result, desc ) should pass -ok 556 - has_rightop( left, name, result, desc ) should have the proper description -ok 557 - has_rightop( left, name, result, desc ) should have the proper diagnostics -ok 558 - has_rightop( left, name, result ) should pass -ok 559 - has_rightop( left, name, result ) should have the proper description -ok 560 - has_rightop( left, name, result ) should have the proper diagnostics -ok 561 - has_rightop( left, name, desc ) should pass -ok 562 - has_rightop( left, name, desc ) should have the proper description -ok 563 - has_rightop( left, name, desc ) should have the proper diagnostics -ok 564 - has_rightop( left, name ) should pass -ok 565 - has_rightop( left, name ) should have the proper description -ok 566 - has_rightop( left, name ) should have the proper diagnostics -ok 567 - has_rightop( left, schema, name, result, desc ) fail should fail -ok 568 - has_rightop( left, schema, name, result, desc ) fail should have the proper description -ok 569 - has_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics -ok 570 - has_rightop( left, schema, name, result ) fail should fail -ok 571 - has_rightop( left, schema, name, result ) fail should have the proper description -ok 572 - has_rightop( left, schema, name, result ) fail should have the proper diagnostics -ok 573 - has_rightop( left, name, result, desc ) fail should fail -ok 574 - has_rightop( left, name, result, desc ) fail should have the proper description -ok 575 - has_rightop( left, name, result, desc ) fail should have the proper diagnostics -ok 576 - has_rightop( left, name, result ) fail should fail -ok 577 - has_rightop( left, name, result ) fail should have the proper description -ok 578 - has_rightop( left, name, result ) fail should have the proper diagnostics -ok 579 - has_rightop( left, name, desc ) fail should fail -ok 580 - has_rightop( left, name, desc ) fail should have the proper description -ok 581 - has_rightop( left, name, desc ) fail should have the proper diagnostics -ok 582 - has_rightop( left, name ) fail should fail -ok 583 - has_rightop( left, name ) fail should have the proper description -ok 584 - has_rightop( left, name ) fail should have the proper diagnostics -ok 585 - has_language(language) should pass -ok 586 - has_language(language) should have the proper description -ok 587 - has_language(language) should have the proper diagnostics -ok 588 - has_language(language, desc) should pass -ok 589 - has_language(language, desc) should have the proper description -ok 590 - has_language(language, desc) should have the proper diagnostics -ok 591 - has_language(nonexistent language) should fail -ok 592 - has_language(nonexistent language) should have the proper description -ok 593 - has_language(nonexistent language) should have the proper diagnostics -ok 594 - has_language(nonexistent language, desc) should fail -ok 595 - has_language(nonexistent language, desc) should have the proper description -ok 596 - has_language(nonexistent language, desc) should have the proper diagnostics -ok 597 - hasnt_language(language) should fail -ok 598 - hasnt_language(language) should have the proper description -ok 599 - hasnt_language(language) should have the proper diagnostics -ok 600 - hasnt_language(language, desc) should fail -ok 601 - hasnt_language(language, desc) should have the proper description -ok 602 - hasnt_language(language, desc) should have the proper diagnostics -ok 603 - hasnt_language(nonexistent language) should pass -ok 604 - hasnt_language(nonexistent language) should have the proper description -ok 605 - hasnt_language(nonexistent language) should have the proper diagnostics -ok 606 - hasnt_language(nonexistent language, desc) should pass -ok 607 - hasnt_language(nonexistent language, desc) should have the proper description -ok 608 - hasnt_language(nonexistent language, desc) should have the proper diagnostics -ok 609 - language_is_trusted(language, desc) should pass -ok 610 - language_is_trusted(language, desc) should have the proper description -ok 611 - language_is_trusted(language, desc) should have the proper diagnostics -ok 612 - language_is_trusted(language) should pass -ok 613 - language_is_trusted(language) should have the proper description -ok 614 - language_is_trusted(language) should have the proper diagnostics -ok 615 - language_is_trusted(language, desc) fail should fail -ok 616 - language_is_trusted(language, desc) fail should have the proper description -ok 617 - language_is_trusted(language, desc) fail should have the proper diagnostics -ok 618 - language_is_trusted(language, desc) non-existent should fail -ok 619 - language_is_trusted(language, desc) non-existent should have the proper description -ok 620 - language_is_trusted(language, desc) non-existent should have the proper diagnostics -ok 621 - has_opclass( schema, name, desc ) should pass -ok 622 - has_opclass( schema, name, desc ) should have the proper description -ok 623 - has_opclass( schema, name, desc ) should have the proper diagnostics -ok 624 - has_opclass( schema, name ) should pass -ok 625 - has_opclass( schema, name ) should have the proper description -ok 626 - has_opclass( schema, name ) should have the proper diagnostics -ok 627 - has_opclass( name, desc ) should pass -ok 628 - has_opclass( name, desc ) should have the proper description -ok 629 - has_opclass( name, desc ) should have the proper diagnostics -ok 630 - has_opclass( name ) should pass -ok 631 - has_opclass( name ) should have the proper description -ok 632 - has_opclass( name ) should have the proper diagnostics -ok 633 - has_opclass( schema, name, desc ) fail should fail -ok 634 - has_opclass( schema, name, desc ) fail should have the proper description -ok 635 - has_opclass( schema, name, desc ) fail should have the proper diagnostics -ok 636 - has_opclass( name, desc ) fail should fail -ok 637 - has_opclass( name, desc ) fail should have the proper description -ok 638 - has_opclass( name, desc ) fail should have the proper diagnostics -ok 639 - hasnt_opclass( schema, name, desc ) should fail -ok 640 - hasnt_opclass( schema, name, desc ) should have the proper description -ok 641 - hasnt_opclass( schema, name, desc ) should have the proper diagnostics -ok 642 - hasnt_opclass( schema, name ) should fail -ok 643 - hasnt_opclass( schema, name ) should have the proper description -ok 644 - hasnt_opclass( schema, name ) should have the proper diagnostics -ok 645 - hasnt_opclass( name, desc ) should fail -ok 646 - hasnt_opclass( name, desc ) should have the proper description -ok 647 - hasnt_opclass( name, desc ) should have the proper diagnostics -ok 648 - hasnt_opclass( name ) should fail -ok 649 - hasnt_opclass( name ) should have the proper description -ok 650 - hasnt_opclass( name ) should have the proper diagnostics -ok 651 - hasnt_opclass( schema, name, desc ) fail should pass -ok 652 - hasnt_opclass( schema, name, desc ) fail should have the proper description -ok 653 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnostics -ok 654 - hasnt_opclass( name, desc ) fail should pass -ok 655 - hasnt_opclass( name, desc ) fail should have the proper description -ok 656 - hasnt_opclass( name, desc ) fail should have the proper diagnostics -ok 657 - domain_type_is(schema, domain, schema, type, desc) should pass -ok 658 - domain_type_is(schema, domain, schema, type, desc) should have the proper description -ok 659 - domain_type_is(schema, domain, schema, type, desc) should have the proper diagnostics -ok 660 - domain_type_is(schema, domain, schema, type) should pass -ok 661 - domain_type_is(schema, domain, schema, type) should have the proper description -ok 662 - domain_type_is(schema, domain, schema, type) should have the proper diagnostics -ok 663 - domain_type_is(schema, domain, schema, type, desc) fail should fail -ok 664 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper description -ok 665 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper diagnostics -ok 666 - domain_type_is(schema, nondomain, schema, type, desc) should fail -ok 667 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper description -ok 668 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper diagnostics -ok 669 - domain_type_is(schema, type, schema, type, desc) fail should fail -ok 670 - domain_type_is(schema, type, schema, type, desc) fail should have the proper description -ok 671 - domain_type_is(schema, type, schema, type, desc) fail should have the proper diagnostics -ok 672 - domain_type_is(schema, domain, type, desc) should pass -ok 673 - domain_type_is(schema, domain, type, desc) should have the proper description -ok 674 - domain_type_is(schema, domain, type, desc) should have the proper diagnostics -ok 675 - domain_type_is(schema, domain, type) should pass -ok 676 - domain_type_is(schema, domain, type) should have the proper description -ok 677 - domain_type_is(schema, domain, type) should have the proper diagnostics -ok 678 - domain_type_is(schema, domain, type, desc) fail should fail -ok 679 - domain_type_is(schema, domain, type, desc) fail should have the proper description -ok 680 - domain_type_is(schema, domain, type, desc) fail should have the proper diagnostics -ok 681 - domain_type_is(schema, nondomain, type, desc) should fail -ok 682 - domain_type_is(schema, nondomain, type, desc) should have the proper description -ok 683 - domain_type_is(schema, nondomain, type, desc) should have the proper diagnostics -ok 684 - domain_type_is(schema, type, type, desc) fail should fail -ok 685 - domain_type_is(schema, type, type, desc) fail should have the proper description -ok 686 - domain_type_is(schema, type, type, desc) fail should have the proper diagnostics -ok 687 - domain_type_is(domain, type, desc) should pass -ok 688 - domain_type_is(domain, type, desc) should have the proper description -ok 689 - domain_type_is(domain, type, desc) should have the proper diagnostics -ok 690 - domain_type_is(domain, type) should pass -ok 691 - domain_type_is(domain, type) should have the proper description -ok 692 - domain_type_is(domain, type) should have the proper diagnostics -ok 693 - domain_type_is(domain, type, desc) fail should fail -ok 694 - domain_type_is(domain, type, desc) fail should have the proper description -ok 695 - domain_type_is(domain, type, desc) fail should have the proper diagnostics -ok 696 - domain_type_is(nondomain, type, desc) should fail -ok 697 - domain_type_is(nondomain, type, desc) should have the proper description -ok 698 - domain_type_is(nondomain, type, desc) should have the proper diagnostics -ok 699 - domain_type_is(type, type, desc) fail should fail -ok 700 - domain_type_is(type, type, desc) fail should have the proper description -ok 701 - domain_type_is(type, type, desc) fail should have the proper diagnostics -ok 702 - domain_type_isnt(schema, domain, schema, type, desc) should pass -ok 703 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper description -ok 704 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper diagnostics -ok 705 - domain_type_isnt(schema, domain, schema, type) should pass -ok 706 - domain_type_isnt(schema, domain, schema, type) should have the proper description -ok 707 - domain_type_isnt(schema, domain, schema, type) should have the proper diagnostics -ok 708 - domain_type_isnt(schema, domain, schema, type, desc) fail should fail -ok 709 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper description -ok 710 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper diagnostics -ok 711 - domain_type_isnt(schema, nondomain, schema, type, desc) should fail -ok 712 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper description -ok 713 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper diagnostics -ok 714 - domain_type_isnt(schema, type, schema, type, desc) should fail -ok 715 - domain_type_isnt(schema, type, schema, type, desc) should have the proper description -ok 716 - domain_type_isnt(schema, type, schema, type, desc) should have the proper diagnostics -ok 717 - domain_type_isnt(schema, domain, type, desc) should pass -ok 718 - domain_type_isnt(schema, domain, type, desc) should have the proper description -ok 719 - domain_type_isnt(schema, domain, type, desc) should have the proper diagnostics -ok 720 - domain_type_isnt(schema, domain, type) should pass -ok 721 - domain_type_isnt(schema, domain, type) should have the proper description -ok 722 - domain_type_isnt(schema, domain, type) should have the proper diagnostics -ok 723 - domain_type_isnt(schema, domain, type, desc) fail should fail -ok 724 - domain_type_isnt(schema, domain, type, desc) fail should have the proper description -ok 725 - domain_type_isnt(schema, domain, type, desc) fail should have the proper diagnostics -ok 726 - domain_type_isnt(schema, nondomain, type, desc) should fail -ok 727 - domain_type_isnt(schema, nondomain, type, desc) should have the proper description -ok 728 - domain_type_isnt(schema, nondomain, type, desc) should have the proper diagnostics -ok 729 - domain_type_isnt(schema, type, type, desc) should fail -ok 730 - domain_type_isnt(schema, type, type, desc) should have the proper description -ok 731 - domain_type_isnt(schema, type, type, desc) should have the proper diagnostics -ok 732 - domain_type_isnt(domain, type, desc) should pass -ok 733 - domain_type_isnt(domain, type, desc) should have the proper description -ok 734 - domain_type_isnt(domain, type, desc) should have the proper diagnostics -ok 735 - domain_type_isnt(domain, type) should pass -ok 736 - domain_type_isnt(domain, type) should have the proper description -ok 737 - domain_type_isnt(domain, type) should have the proper diagnostics -ok 738 - domain_type_isnt(domain, type, desc) fail should fail -ok 739 - domain_type_isnt(domain, type, desc) fail should have the proper description -ok 740 - domain_type_isnt(domain, type, desc) fail should have the proper diagnostics -ok 741 - domain_type_isnt(nondomain, type, desc) should fail -ok 742 - domain_type_isnt(nondomain, type, desc) should have the proper description -ok 743 - domain_type_isnt(nondomain, type, desc) should have the proper diagnostics -ok 744 - domain_type_isnt(type, type, desc) should fail -ok 745 - domain_type_isnt(type, type, desc) should have the proper description -ok 746 - domain_type_isnt(type, type, desc) should have the proper diagnostics -ok 747 - has_relation(non-existent relation) should fail -ok 748 - has_relation(non-existent relation) should have the proper description -ok 749 - has_relation(non-existent relation) should have the proper diagnostics -ok 750 - has_relation(non-existent schema, tab) should fail -ok 751 - has_relation(non-existent schema, tab) should have the proper description -ok 752 - has_relation(non-existent schema, tab) should have the proper diagnostics -ok 753 - has_relation(sch, non-existent relation, desc) should fail -ok 754 - has_relation(sch, non-existent relation, desc) should have the proper description -ok 755 - has_relation(sch, non-existent relation, desc) should have the proper diagnostics -ok 756 - has_relation(tab, desc) should pass -ok 757 - has_relation(tab, desc) should have the proper description -ok 758 - has_relation(tab, desc) should have the proper diagnostics -ok 759 - has_relation(sch, tab, desc) should pass -ok 760 - has_relation(sch, tab, desc) should have the proper description -ok 761 - has_relation(sch, tab, desc) should have the proper diagnostics -ok 762 - has_relation(sch, view, desc) should pass -ok 763 - has_relation(sch, view, desc) should have the proper description -ok 764 - has_relation(sch, view, desc) should have the proper diagnostics -ok 765 - has_relation(type, desc) should pass -ok 766 - has_relation(type, desc) should have the proper description -ok 767 - has_relation(type, desc) should have the proper diagnostics -ok 768 - hasnt_relation(non-existent relation) should pass -ok 769 - hasnt_relation(non-existent relation) should have the proper description -ok 770 - hasnt_relation(non-existent relation) should have the proper diagnostics -ok 771 - hasnt_relation(non-existent schema, tab) should pass -ok 772 - hasnt_relation(non-existent schema, tab) should have the proper description -ok 773 - hasnt_relation(non-existent schema, tab) should have the proper diagnostics -ok 774 - hasnt_relation(sch, non-existent tab, desc) should pass -ok 775 - hasnt_relation(sch, non-existent tab, desc) should have the proper description -ok 776 - hasnt_relation(sch, non-existent tab, desc) should have the proper diagnostics -ok 777 - hasnt_relation(tab, desc) should fail -ok 778 - hasnt_relation(tab, desc) should have the proper description -ok 779 - hasnt_relation(tab, desc) should have the proper diagnostics -ok 780 - hasnt_relation(sch, tab, desc) should fail -ok 781 - hasnt_relation(sch, tab, desc) should have the proper description -ok 782 - hasnt_relation(sch, tab, desc) should have the proper diagnostics -ok 783 - has_foreign_table(non-existent table) should fail -ok 784 - has_foreign_table(non-existent table) should have the proper description -ok 785 - has_foreign_table(non-existent table) should have the proper diagnostics -ok 786 - has_foreign_table(non-existent schema, tab) should fail -ok 787 - has_foreign_table(non-existent schema, tab) should have the proper description -ok 788 - has_foreign_table(non-existent schema, tab) should have the proper diagnostics -ok 789 - has_foreign_table(non-existent table, desc) should fail -ok 790 - has_foreign_table(non-existent table, desc) should have the proper description -ok 791 - has_foreign_table(non-existent table, desc) should have the proper diagnostics -ok 792 - has_foreign_table(sch, non-existent table, desc) should fail -ok 793 - has_foreign_table(sch, non-existent table, desc) should have the proper description -ok 794 - has_foreign_table(sch, non-existent table, desc) should have the proper diagnostics -ok 795 - has_foreign_table(tab, desc) should pass -ok 796 - has_foreign_table(tab, desc) should have the proper description -ok 797 - has_foreign_table(tab, desc) should have the proper diagnostics -ok 798 - has_foreign_table(sch, tab, desc) should pass -ok 799 - has_foreign_table(sch, tab, desc) should have the proper description -ok 800 - has_foreign_table(sch, tab, desc) should have the proper diagnostics -ok 801 - has_foreign_table(sch, view, desc) should fail -ok 802 - has_foreign_table(sch, view, desc) should have the proper description -ok 803 - has_foreign_table(sch, view, desc) should have the proper diagnostics -ok 804 - has_foreign_table(type, desc) should fail -ok 805 - has_foreign_table(type, desc) should have the proper description -ok 806 - has_foreign_table(type, desc) should have the proper diagnostics -ok 807 - hasnt_foreign_table(non-existent table) should pass -ok 808 - hasnt_foreign_table(non-existent table) should have the proper description -ok 809 - hasnt_foreign_table(non-existent table) should have the proper diagnostics -ok 810 - hasnt_foreign_table(non-existent schema, tab) should pass -ok 811 - hasnt_foreign_table(non-existent schema, tab) should have the proper description -ok 812 - hasnt_foreign_table(non-existent schema, tab) should have the proper diagnostics -ok 813 - hasnt_foreign_table(non-existent table, desc) should pass -ok 814 - hasnt_foreign_table(non-existent table, desc) should have the proper description -ok 815 - hasnt_foreign_table(non-existent table, desc) should have the proper diagnostics -ok 816 - hasnt_foreign_table(sch, non-existent tab, desc) should pass -ok 817 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper description -ok 818 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper diagnostics -ok 819 - hasnt_foreign_table(tab, desc) should fail -ok 820 - hasnt_foreign_table(tab, desc) should have the proper description -ok 821 - hasnt_foreign_table(tab, desc) should have the proper diagnostics -ok 822 - hasnt_foreign_table(sch, tab, desc) should fail -ok 823 - hasnt_foreign_table(sch, tab, desc) should have the proper description -ok 824 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics -ok 825 - has_materialized_view(non-existent materialized_view) should fail -ok 826 - has_materialized_view(non-existent materialized_view) should have the proper description -ok 827 - has_materialized_view(non-existent materialized_view) should have the proper diagnostics -ok 828 - has_materialized_view(non-existent materialized_view, desc) should fail -ok 829 - has_materialized_view(non-existent materialized_view, desc) should have the proper description -ok 830 - has_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics -ok 831 - has_materialized_view(sch, non-existent materialized_view, desc) should fail -ok 832 - has_materialized_view(sch, non-existent materialized_view, desc) should have the proper description -ok 833 - has_materialized_view(sch, non-existent materialized_view, desc) should have the proper diagnostics -ok 834 - has_materialized_view(materialized_view, desc) should pass -ok 835 - has_materialized_view(materialized_view, desc) should have the proper description -ok 836 - has_materialized_view(materialized_view, desc) should have the proper diagnostics -ok 837 - has_materialized_view(sch, materialized_view, desc) should pass -ok 838 - has_materialized_view(sch, materialized_view, desc) should have the proper description -ok 839 - has_materialized_view(sch, materialized_view, desc) should have the proper diagnostics -ok 840 - hasnt_materialized_view(non-existent materialized_view) should pass -ok 841 - hasnt_materialized_view(non-existent materialized_view) should have the proper description -ok 842 - hasnt_materialized_view(non-existent materialized_view) should have the proper diagnostics -ok 843 - hasnt_materialized_view(non-existent materialized_view, desc) should pass -ok 844 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper description -ok 845 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics -ok 846 - hasnt_materialized_view(sch, non-existent materialized_view, desc) should pass -ok 847 - hasnt_materialized_view(sch, non-existent materialized_view, desc) should have the proper description -ok 848 - hasnt_materialized_view(sch, non-existent materialized_view, desc) should have the proper diagnostics -ok 849 - hasnt_materialized_view(materialized_view, desc) should fail -ok 850 - hasnt_materialized_view(materialized_view, desc) should have the proper description -ok 851 - hasnt_materialized_view(materialized_view, desc) should have the proper diagnostics -ok 852 - hasnt_materialized_view(sch, materialized_view, desc) should fail -ok 853 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper description -ok 854 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper diagnostics -ok 855 - is_partitioned(non-existent part) should fail -ok 856 - is_partitioned(non-existent part) should have the proper description -ok 857 - is_partitioned(non-existent part) should have the proper diagnostics -ok 858 - is_partitioned(non-existent part, desc) should fail -ok 859 - is_partitioned(non-existent part, desc) should have the proper description -ok 860 - is_partitioned(non-existent part, desc) should have the proper diagnostics -ok 861 - is_partitioned(sch, non-existent part, desc) should fail -ok 862 - is_partitioned(sch, non-existent part, desc) should have the proper description -ok 863 - is_partitioned(sch, non-existent part, desc) should have the proper diagnostics -ok 864 - is_partitioned(sch, part, desc) should pass -ok 865 - is_partitioned(sch, part, desc) should have the proper description -ok 866 - is_partitioned(sch, part, desc) should have the proper diagnostics -ok 867 - is_partitioned(sch, part) should pass -ok 868 - is_partitioned(sch, part) should have the proper description -ok 869 - is_partitioned(sch, part) should have the proper diagnostics -ok 870 - is_partitioned(part, desc) should pass -ok 871 - is_partitioned(part, desc) should have the proper description -ok 872 - is_partitioned(part, desc) should have the proper diagnostics -ok 873 - is_partitioned(part) should pass -ok 874 - is_partitioned(part) should have the proper description -ok 875 - is_partitioned(part) should have the proper diagnostics -ok 876 - isnt_partitioned(non-existent part) should pass -ok 877 - isnt_partitioned(non-existent part) should have the proper description -ok 878 - isnt_partitioned(non-existent part) should have the proper diagnostics -ok 879 - isnt_partitioned(non-existent part, desc) should pass -ok 880 - isnt_partitioned(non-existent part, desc) should have the proper description -ok 881 - isnt_partitioned(non-existent part, desc) should have the proper diagnostics -ok 882 - isnt_partitioned(sch, non-existent part, desc) should pass -ok 883 - isnt_partitioned(sch, non-existent part, desc) should have the proper description -ok 884 - isnt_partitioned(sch, non-existent part, desc) should have the proper diagnostics -ok 885 - isnt_partitioned(sch, part, desc) should fail -ok 886 - isnt_partitioned(sch, part, desc) should have the proper description -ok 887 - isnt_partitioned(sch, part, desc) should have the proper diagnostics -ok 888 - isnt_partitioned(sch, part) should fail -ok 889 - isnt_partitioned(sch, part) should have the proper description -ok 890 - isnt_partitioned(sch, part) should have the proper diagnostics -ok 891 - isnt_partitioned(part, desc) should fail -ok 892 - isnt_partitioned(part, desc) should have the proper description -ok 893 - isnt_partitioned(part, desc) should have the proper diagnostics -ok 894 - isnt_partitioned(part) should fail -ok 895 - isnt_partitioned(part) should have the proper description -ok 896 - isnt_partitioned(part) should have the proper diagnostics +ok 513 - hasnt_operator( left, schema, name, right, result, desc ) fail should fail +ok 514 - hasnt_operator( left, schema, name, right, result, desc ) fail should have the proper description +ok 515 - hasnt_operator( left, schema, name, right, result, desc ) fail should have the proper diagnostics +ok 516 - hasnt_operator( left, schema, name, right, result ) fail should fail +ok 517 - hasnt_operator( left, schema, name, right, result ) fail should have the proper description +ok 518 - hasnt_operator( left, schema, name, right, result ) fail should have the proper diagnostics +ok 519 - hasnt_operator( left, name, right, result, desc ) fail should fail +ok 520 - hasnt_operator( left, name, right, result, desc ) fail should have the proper description +ok 521 - hasnt_operator( left, name, right, result, desc ) fail should have the proper diagnostics +ok 522 - hasnt_operator( left, name, right, result ) fail should fail +ok 523 - hasnt_operator( left, name, right, result ) fail should have the proper description +ok 524 - hasnt_operator( left, name, right, result ) fail should have the proper diagnostics +ok 525 - hasnt_operator( left, name, right, desc ) fail should fail +ok 526 - hasnt_operator( left, name, right, desc ) fail should have the proper description +ok 527 - hasnt_operator( left, name, right, desc ) fail should have the proper diagnostics +ok 528 - hasnt_operator( left, name, right ) fail should fail +ok 529 - hasnt_operator( left, name, right ) fail should have the proper description +ok 530 - hasnt_operator( left, name, right ) fail should have the proper diagnostics +ok 531 - hasnt_operator( left, schema, name, right, result, desc ) should pass +ok 532 - hasnt_operator( left, schema, name, right, result, desc ) should have the proper description +ok 533 - hasnt_operator( left, schema, name, right, result, desc ) should have the proper diagnostics +ok 534 - hasnt_operator( left, schema, name, right, result ) should pass +ok 535 - hasnt_operator( left, schema, name, right, result ) should have the proper description +ok 536 - hasnt_operator( left, schema, name, right, result ) should have the proper diagnostics +ok 537 - hasnt_operator( left, name, right, result, desc ) should pass +ok 538 - hasnt_operator( left, name, right, result, desc ) should have the proper description +ok 539 - hasnt_operator( left, name, right, result, desc ) should have the proper diagnostics +ok 540 - hasnt_operator( left, name, right, result ) should pass +ok 541 - hasnt_operator( left, name, right, result ) should have the proper description +ok 542 - hasnt_operator( left, name, right, result ) should have the proper diagnostics +ok 543 - hasnt_operator( left, name, right, desc ) should pass +ok 544 - hasnt_operator( left, name, right, desc ) should have the proper description +ok 545 - hasnt_operator( left, name, right, desc ) should have the proper diagnostics +ok 546 - hasnt_operator( left, name, right ) should pass +ok 547 - hasnt_operator( left, name, right ) should have the proper description +ok 548 - hasnt_operator( left, name, right ) should have the proper diagnostics +ok 549 - has_leftop( schema, name, right, result, desc ) should pass +ok 550 - has_leftop( schema, name, right, result, desc ) should have the proper description +ok 551 - has_leftop( schema, name, right, result, desc ) should have the proper diagnostics +ok 552 - has_leftop( schema, name, right, result ) should pass +ok 553 - has_leftop( schema, name, right, result ) should have the proper description +ok 554 - has_leftop( schema, name, right, result ) should have the proper diagnostics +ok 555 - has_leftop( name, right, result, desc ) should pass +ok 556 - has_leftop( name, right, result, desc ) should have the proper description +ok 557 - has_leftop( name, right, result, desc ) should have the proper diagnostics +ok 558 - has_leftop( name, right, result ) should pass +ok 559 - has_leftop( name, right, result ) should have the proper description +ok 560 - has_leftop( name, right, result ) should have the proper diagnostics +ok 561 - has_leftop( name, right, desc ) should pass +ok 562 - has_leftop( name, right, desc ) should have the proper description +ok 563 - has_leftop( name, right, desc ) should have the proper diagnostics +ok 564 - has_leftop( name, right ) should pass +ok 565 - has_leftop( name, right ) should have the proper description +ok 566 - has_leftop( name, right ) should have the proper diagnostics +ok 567 - has_leftop( schema, name, right, result, desc ) fail should fail +ok 568 - has_leftop( schema, name, right, result, desc ) fail should have the proper description +ok 569 - has_leftop( schema, name, right, result, desc ) fail should have the proper diagnostics +ok 570 - has_leftop( schema, name, right, result ) fail should fail +ok 571 - has_leftop( schema, name, right, result ) fail should have the proper description +ok 572 - has_leftop( schema, name, right, result ) fail should have the proper diagnostics +ok 573 - has_leftop( name, right, result, desc ) fail should fail +ok 574 - has_leftop( name, right, result, desc ) fail should have the proper description +ok 575 - has_leftop( name, right, result, desc ) fail should have the proper diagnostics +ok 576 - has_leftop( name, right, result ) fail should fail +ok 577 - has_leftop( name, right, result ) fail should have the proper description +ok 578 - has_leftop( name, right, result ) fail should have the proper diagnostics +ok 579 - has_leftop( name, right, desc ) fail should fail +ok 580 - has_leftop( name, right, desc ) fail should have the proper description +ok 581 - has_leftop( name, right, desc ) fail should have the proper diagnostics +ok 582 - has_leftop( name, right ) fail should fail +ok 583 - has_leftop( name, right ) fail should have the proper description +ok 584 - has_leftop( name, right ) fail should have the proper diagnostics +ok 585 - hasnt_leftop( schema, name, right, result, desc ) fail should fail +ok 586 - hasnt_leftop( schema, name, right, result, desc ) fail should have the proper description +ok 587 - hasnt_leftop( schema, name, right, result, desc ) fail should have the proper diagnostics +ok 588 - hasnt_leftop( schema, name, right, result ) fail should fail +ok 589 - hasnt_leftop( schema, name, right, result ) fail should have the proper description +ok 590 - hasnt_leftop( schema, name, right, result ) fail should have the proper diagnostics +ok 591 - hasnt_leftop( name, right, result, desc ) fail should fail +ok 592 - hasnt_leftop( name, right, result, desc ) fail should have the proper description +ok 593 - hasnt_leftop( name, right, result, desc ) fail should have the proper diagnostics +ok 594 - hasnt_leftop( name, right, result ) fail should fail +ok 595 - hasnt_leftop( name, right, result ) fail should have the proper description +ok 596 - hasnt_leftop( name, right, result ) fail should have the proper diagnostics +ok 597 - hasnt_leftop( name, right, desc ) fail should fail +ok 598 - hasnt_leftop( name, right, desc ) fail should have the proper description +ok 599 - hasnt_leftop( name, right, desc ) fail should have the proper diagnostics +ok 600 - hasnt_leftop( name, right ) fail should fail +ok 601 - hasnt_leftop( name, right ) fail should have the proper description +ok 602 - hasnt_leftop( name, right ) fail should have the proper diagnostics +ok 603 - hasnt_leftop( schema, name, right, result, desc ) should pass +ok 604 - hasnt_leftop( schema, name, right, result, desc ) should have the proper description +ok 605 - hasnt_leftop( schema, name, right, result, desc ) should have the proper diagnostics +ok 606 - hasnt_leftop( schema, name, right, result ) should pass +ok 607 - hasnt_leftop( schema, name, right, result ) should have the proper description +ok 608 - hasnt_leftop( schema, name, right, result ) should have the proper diagnostics +ok 609 - hasnt_leftop( name, right, result, desc ) should pass +ok 610 - hasnt_leftop( name, right, result, desc ) should have the proper description +ok 611 - hasnt_leftop( name, right, result, desc ) should have the proper diagnostics +ok 612 - hasnt_leftop( name, right, result ) should pass +ok 613 - hasnt_leftop( name, right, result ) should have the proper description +ok 614 - hasnt_leftop( name, right, result ) should have the proper diagnostics +ok 615 - hasnt_leftop( name, right, desc ) should pass +ok 616 - hasnt_leftop( name, right, desc ) should have the proper description +ok 617 - hasnt_leftop( name, right, desc ) should have the proper diagnostics +ok 618 - hasnt_leftop( name, right ) should pass +ok 619 - hasnt_leftop( name, right ) should have the proper description +ok 620 - hasnt_leftop( name, right ) should have the proper diagnostics +ok 621 - has_rightop( left, schema, name, result, desc ) should pass +ok 622 - has_rightop( left, schema, name, result, desc ) should have the proper description +ok 623 - has_rightop( left, schema, name, result, desc ) should have the proper diagnostics +ok 624 - has_rightop( left, schema, name, result ) should pass +ok 625 - has_rightop( left, schema, name, result ) should have the proper description +ok 626 - has_rightop( left, schema, name, result ) should have the proper diagnostics +ok 627 - has_rightop( left, name, result, desc ) should pass +ok 628 - has_rightop( left, name, result, desc ) should have the proper description +ok 629 - has_rightop( left, name, result, desc ) should have the proper diagnostics +ok 630 - has_rightop( left, name, result ) should pass +ok 631 - has_rightop( left, name, result ) should have the proper description +ok 632 - has_rightop( left, name, result ) should have the proper diagnostics +ok 633 - has_rightop( left, name, desc ) should pass +ok 634 - has_rightop( left, name, desc ) should have the proper description +ok 635 - has_rightop( left, name, desc ) should have the proper diagnostics +ok 636 - has_rightop( left, name ) should pass +ok 637 - has_rightop( left, name ) should have the proper description +ok 638 - has_rightop( left, name ) should have the proper diagnostics +ok 639 - has_rightop( left, schema, name, result, desc ) fail should fail +ok 640 - has_rightop( left, schema, name, result, desc ) fail should have the proper description +ok 641 - has_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics +ok 642 - has_rightop( left, schema, name, result ) fail should fail +ok 643 - has_rightop( left, schema, name, result ) fail should have the proper description +ok 644 - has_rightop( left, schema, name, result ) fail should have the proper diagnostics +ok 645 - has_rightop( left, name, result, desc ) fail should fail +ok 646 - has_rightop( left, name, result, desc ) fail should have the proper description +ok 647 - has_rightop( left, name, result, desc ) fail should have the proper diagnostics +ok 648 - has_rightop( left, name, result ) fail should fail +ok 649 - has_rightop( left, name, result ) fail should have the proper description +ok 650 - has_rightop( left, name, result ) fail should have the proper diagnostics +ok 651 - has_rightop( left, name, desc ) fail should fail +ok 652 - has_rightop( left, name, desc ) fail should have the proper description +ok 653 - has_rightop( left, name, desc ) fail should have the proper diagnostics +ok 654 - has_rightop( left, name ) fail should fail +ok 655 - has_rightop( left, name ) fail should have the proper description +ok 656 - has_rightop( left, name ) fail should have the proper diagnostics +ok 657 - hasnt_rightop( left, schema, name, result, desc ) fail should fail +ok 658 - hasnt_rightop( left, schema, name, result, desc ) fail should have the proper description +ok 659 - hasnt_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics +ok 660 - hasnt_rightop( left, schema, name, result ) fail should fail +ok 661 - hasnt_rightop( left, schema, name, result ) fail should have the proper description +ok 662 - hasnt_rightop( left, schema, name, result ) fail should have the proper diagnostics +ok 663 - hasnt_rightop( left, name, result, desc ) fail should fail +ok 664 - hasnt_rightop( left, name, result, desc ) fail should have the proper description +ok 665 - hasnt_rightop( left, name, result, desc ) fail should have the proper diagnostics +ok 666 - hasnt_rightop( left, name, result ) fail should fail +ok 667 - hasnt_rightop( left, name, result ) fail should have the proper description +ok 668 - hasnt_rightop( left, name, result ) fail should have the proper diagnostics +ok 669 - hasnt_rightop( left, name, desc ) fail should fail +ok 670 - hasnt_rightop( left, name, desc ) fail should have the proper description +ok 671 - hasnt_rightop( left, name, desc ) fail should have the proper diagnostics +ok 672 - hasnt_rightop( left, name ) fail should fail +ok 673 - hasnt_rightop( left, name ) fail should have the proper description +ok 674 - hasnt_rightop( left, name ) fail should have the proper diagnostics +ok 675 - hasnt_rightop( left, schema, name, result, desc ) should pass +ok 676 - hasnt_rightop( left, schema, name, result, desc ) should have the proper description +ok 677 - hasnt_rightop( left, schema, name, result, desc ) should have the proper diagnostics +ok 678 - hasnt_rightop( left, schema, name, result ) should pass +ok 679 - hasnt_rightop( left, schema, name, result ) should have the proper description +ok 680 - hasnt_rightop( left, schema, name, result ) should have the proper diagnostics +ok 681 - hasnt_rightop( left, name, result, desc ) should pass +ok 682 - hasnt_rightop( left, name, result, desc ) should have the proper description +ok 683 - hasnt_rightop( left, name, result, desc ) should have the proper diagnostics +ok 684 - hasnt_rightop( left, name, result ) should pass +ok 685 - hasnt_rightop( left, name, result ) should have the proper description +ok 686 - hasnt_rightop( left, name, result ) should have the proper diagnostics +ok 687 - hasnt_rightop( left, name, desc ) should pass +ok 688 - hasnt_rightop( left, name, desc ) should have the proper description +ok 689 - hasnt_rightop( left, name, desc ) should have the proper diagnostics +ok 690 - hasnt_rightop( left, name ) should pass +ok 691 - hasnt_rightop( left, name ) should have the proper description +ok 692 - hasnt_rightop( left, name ) should have the proper diagnostics +ok 693 - has_language(language) should pass +ok 694 - has_language(language) should have the proper description +ok 695 - has_language(language) should have the proper diagnostics +ok 696 - has_language(language, desc) should pass +ok 697 - has_language(language, desc) should have the proper description +ok 698 - has_language(language, desc) should have the proper diagnostics +ok 699 - has_language(nonexistent language) should fail +ok 700 - has_language(nonexistent language) should have the proper description +ok 701 - has_language(nonexistent language) should have the proper diagnostics +ok 702 - has_language(nonexistent language, desc) should fail +ok 703 - has_language(nonexistent language, desc) should have the proper description +ok 704 - has_language(nonexistent language, desc) should have the proper diagnostics +ok 705 - hasnt_language(language) should fail +ok 706 - hasnt_language(language) should have the proper description +ok 707 - hasnt_language(language) should have the proper diagnostics +ok 708 - hasnt_language(language, desc) should fail +ok 709 - hasnt_language(language, desc) should have the proper description +ok 710 - hasnt_language(language, desc) should have the proper diagnostics +ok 711 - hasnt_language(nonexistent language) should pass +ok 712 - hasnt_language(nonexistent language) should have the proper description +ok 713 - hasnt_language(nonexistent language) should have the proper diagnostics +ok 714 - hasnt_language(nonexistent language, desc) should pass +ok 715 - hasnt_language(nonexistent language, desc) should have the proper description +ok 716 - hasnt_language(nonexistent language, desc) should have the proper diagnostics +ok 717 - language_is_trusted(language, desc) should pass +ok 718 - language_is_trusted(language, desc) should have the proper description +ok 719 - language_is_trusted(language, desc) should have the proper diagnostics +ok 720 - language_is_trusted(language) should pass +ok 721 - language_is_trusted(language) should have the proper description +ok 722 - language_is_trusted(language) should have the proper diagnostics +ok 723 - language_is_trusted(language, desc) fail should fail +ok 724 - language_is_trusted(language, desc) fail should have the proper description +ok 725 - language_is_trusted(language, desc) fail should have the proper diagnostics +ok 726 - language_is_trusted(language, desc) non-existent should fail +ok 727 - language_is_trusted(language, desc) non-existent should have the proper description +ok 728 - language_is_trusted(language, desc) non-existent should have the proper diagnostics +ok 729 - has_opclass( schema, name, desc ) should pass +ok 730 - has_opclass( schema, name, desc ) should have the proper description +ok 731 - has_opclass( schema, name, desc ) should have the proper diagnostics +ok 732 - has_opclass( schema, name ) should pass +ok 733 - has_opclass( schema, name ) should have the proper description +ok 734 - has_opclass( schema, name ) should have the proper diagnostics +ok 735 - has_opclass( name, desc ) should pass +ok 736 - has_opclass( name, desc ) should have the proper description +ok 737 - has_opclass( name, desc ) should have the proper diagnostics +ok 738 - has_opclass( name ) should pass +ok 739 - has_opclass( name ) should have the proper description +ok 740 - has_opclass( name ) should have the proper diagnostics +ok 741 - has_opclass( schema, name, desc ) fail should fail +ok 742 - has_opclass( schema, name, desc ) fail should have the proper description +ok 743 - has_opclass( schema, name, desc ) fail should have the proper diagnostics +ok 744 - has_opclass( name, desc ) fail should fail +ok 745 - has_opclass( name, desc ) fail should have the proper description +ok 746 - has_opclass( name, desc ) fail should have the proper diagnostics +ok 747 - hasnt_opclass( schema, name, desc ) should fail +ok 748 - hasnt_opclass( schema, name, desc ) should have the proper description +ok 749 - hasnt_opclass( schema, name, desc ) should have the proper diagnostics +ok 750 - hasnt_opclass( schema, name ) should fail +ok 751 - hasnt_opclass( schema, name ) should have the proper description +ok 752 - hasnt_opclass( schema, name ) should have the proper diagnostics +ok 753 - hasnt_opclass( name, desc ) should fail +ok 754 - hasnt_opclass( name, desc ) should have the proper description +ok 755 - hasnt_opclass( name, desc ) should have the proper diagnostics +ok 756 - hasnt_opclass( name ) should fail +ok 757 - hasnt_opclass( name ) should have the proper description +ok 758 - hasnt_opclass( name ) should have the proper diagnostics +ok 759 - hasnt_opclass( schema, name, desc ) fail should pass +ok 760 - hasnt_opclass( schema, name, desc ) fail should have the proper description +ok 761 - hasnt_opclass( schema, name, desc ) fail should have the proper diagnostics +ok 762 - hasnt_opclass( name, desc ) fail should pass +ok 763 - hasnt_opclass( name, desc ) fail should have the proper description +ok 764 - hasnt_opclass( name, desc ) fail should have the proper diagnostics +ok 765 - domain_type_is(schema, domain, schema, type, desc) should pass +ok 766 - domain_type_is(schema, domain, schema, type, desc) should have the proper description +ok 767 - domain_type_is(schema, domain, schema, type, desc) should have the proper diagnostics +ok 768 - domain_type_is(schema, domain, schema, type) should pass +ok 769 - domain_type_is(schema, domain, schema, type) should have the proper description +ok 770 - domain_type_is(schema, domain, schema, type) should have the proper diagnostics +ok 771 - domain_type_is(schema, domain, schema, type, desc) fail should fail +ok 772 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper description +ok 773 - domain_type_is(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 774 - domain_type_is(schema, nondomain, schema, type, desc) should fail +ok 775 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper description +ok 776 - domain_type_is(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 777 - domain_type_is(schema, type, schema, type, desc) fail should fail +ok 778 - domain_type_is(schema, type, schema, type, desc) fail should have the proper description +ok 779 - domain_type_is(schema, type, schema, type, desc) fail should have the proper diagnostics +ok 780 - domain_type_is(schema, domain, type, desc) should pass +ok 781 - domain_type_is(schema, domain, type, desc) should have the proper description +ok 782 - domain_type_is(schema, domain, type, desc) should have the proper diagnostics +ok 783 - domain_type_is(schema, domain, type) should pass +ok 784 - domain_type_is(schema, domain, type) should have the proper description +ok 785 - domain_type_is(schema, domain, type) should have the proper diagnostics +ok 786 - domain_type_is(schema, domain, type, desc) fail should fail +ok 787 - domain_type_is(schema, domain, type, desc) fail should have the proper description +ok 788 - domain_type_is(schema, domain, type, desc) fail should have the proper diagnostics +ok 789 - domain_type_is(schema, nondomain, type, desc) should fail +ok 790 - domain_type_is(schema, nondomain, type, desc) should have the proper description +ok 791 - domain_type_is(schema, nondomain, type, desc) should have the proper diagnostics +ok 792 - domain_type_is(schema, type, type, desc) fail should fail +ok 793 - domain_type_is(schema, type, type, desc) fail should have the proper description +ok 794 - domain_type_is(schema, type, type, desc) fail should have the proper diagnostics +ok 795 - domain_type_is(domain, type, desc) should pass +ok 796 - domain_type_is(domain, type, desc) should have the proper description +ok 797 - domain_type_is(domain, type, desc) should have the proper diagnostics +ok 798 - domain_type_is(domain, type) should pass +ok 799 - domain_type_is(domain, type) should have the proper description +ok 800 - domain_type_is(domain, type) should have the proper diagnostics +ok 801 - domain_type_is(domain, type, desc) fail should fail +ok 802 - domain_type_is(domain, type, desc) fail should have the proper description +ok 803 - domain_type_is(domain, type, desc) fail should have the proper diagnostics +ok 804 - domain_type_is(nondomain, type, desc) should fail +ok 805 - domain_type_is(nondomain, type, desc) should have the proper description +ok 806 - domain_type_is(nondomain, type, desc) should have the proper diagnostics +ok 807 - domain_type_is(type, type, desc) fail should fail +ok 808 - domain_type_is(type, type, desc) fail should have the proper description +ok 809 - domain_type_is(type, type, desc) fail should have the proper diagnostics +ok 810 - domain_type_isnt(schema, domain, schema, type, desc) should pass +ok 811 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper description +ok 812 - domain_type_isnt(schema, domain, schema, type, desc) should have the proper diagnostics +ok 813 - domain_type_isnt(schema, domain, schema, type) should pass +ok 814 - domain_type_isnt(schema, domain, schema, type) should have the proper description +ok 815 - domain_type_isnt(schema, domain, schema, type) should have the proper diagnostics +ok 816 - domain_type_isnt(schema, domain, schema, type, desc) fail should fail +ok 817 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper description +ok 818 - domain_type_isnt(schema, domain, schema, type, desc) fail should have the proper diagnostics +ok 819 - domain_type_isnt(schema, nondomain, schema, type, desc) should fail +ok 820 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper description +ok 821 - domain_type_isnt(schema, nondomain, schema, type, desc) should have the proper diagnostics +ok 822 - domain_type_isnt(schema, type, schema, type, desc) should fail +ok 823 - domain_type_isnt(schema, type, schema, type, desc) should have the proper description +ok 824 - domain_type_isnt(schema, type, schema, type, desc) should have the proper diagnostics +ok 825 - domain_type_isnt(schema, domain, type, desc) should pass +ok 826 - domain_type_isnt(schema, domain, type, desc) should have the proper description +ok 827 - domain_type_isnt(schema, domain, type, desc) should have the proper diagnostics +ok 828 - domain_type_isnt(schema, domain, type) should pass +ok 829 - domain_type_isnt(schema, domain, type) should have the proper description +ok 830 - domain_type_isnt(schema, domain, type) should have the proper diagnostics +ok 831 - domain_type_isnt(schema, domain, type, desc) fail should fail +ok 832 - domain_type_isnt(schema, domain, type, desc) fail should have the proper description +ok 833 - domain_type_isnt(schema, domain, type, desc) fail should have the proper diagnostics +ok 834 - domain_type_isnt(schema, nondomain, type, desc) should fail +ok 835 - domain_type_isnt(schema, nondomain, type, desc) should have the proper description +ok 836 - domain_type_isnt(schema, nondomain, type, desc) should have the proper diagnostics +ok 837 - domain_type_isnt(schema, type, type, desc) should fail +ok 838 - domain_type_isnt(schema, type, type, desc) should have the proper description +ok 839 - domain_type_isnt(schema, type, type, desc) should have the proper diagnostics +ok 840 - domain_type_isnt(domain, type, desc) should pass +ok 841 - domain_type_isnt(domain, type, desc) should have the proper description +ok 842 - domain_type_isnt(domain, type, desc) should have the proper diagnostics +ok 843 - domain_type_isnt(domain, type) should pass +ok 844 - domain_type_isnt(domain, type) should have the proper description +ok 845 - domain_type_isnt(domain, type) should have the proper diagnostics +ok 846 - domain_type_isnt(domain, type, desc) fail should fail +ok 847 - domain_type_isnt(domain, type, desc) fail should have the proper description +ok 848 - domain_type_isnt(domain, type, desc) fail should have the proper diagnostics +ok 849 - domain_type_isnt(nondomain, type, desc) should fail +ok 850 - domain_type_isnt(nondomain, type, desc) should have the proper description +ok 851 - domain_type_isnt(nondomain, type, desc) should have the proper diagnostics +ok 852 - domain_type_isnt(type, type, desc) should fail +ok 853 - domain_type_isnt(type, type, desc) should have the proper description +ok 854 - domain_type_isnt(type, type, desc) should have the proper diagnostics +ok 855 - has_relation(non-existent relation) should fail +ok 856 - has_relation(non-existent relation) should have the proper description +ok 857 - has_relation(non-existent relation) should have the proper diagnostics +ok 858 - has_relation(non-existent schema, tab) should fail +ok 859 - has_relation(non-existent schema, tab) should have the proper description +ok 860 - has_relation(non-existent schema, tab) should have the proper diagnostics +ok 861 - has_relation(sch, non-existent relation, desc) should fail +ok 862 - has_relation(sch, non-existent relation, desc) should have the proper description +ok 863 - has_relation(sch, non-existent relation, desc) should have the proper diagnostics +ok 864 - has_relation(tab, desc) should pass +ok 865 - has_relation(tab, desc) should have the proper description +ok 866 - has_relation(tab, desc) should have the proper diagnostics +ok 867 - has_relation(sch, tab, desc) should pass +ok 868 - has_relation(sch, tab, desc) should have the proper description +ok 869 - has_relation(sch, tab, desc) should have the proper diagnostics +ok 870 - has_relation(sch, view, desc) should pass +ok 871 - has_relation(sch, view, desc) should have the proper description +ok 872 - has_relation(sch, view, desc) should have the proper diagnostics +ok 873 - has_relation(type, desc) should pass +ok 874 - has_relation(type, desc) should have the proper description +ok 875 - has_relation(type, desc) should have the proper diagnostics +ok 876 - hasnt_relation(non-existent relation) should pass +ok 877 - hasnt_relation(non-existent relation) should have the proper description +ok 878 - hasnt_relation(non-existent relation) should have the proper diagnostics +ok 879 - hasnt_relation(non-existent schema, tab) should pass +ok 880 - hasnt_relation(non-existent schema, tab) should have the proper description +ok 881 - hasnt_relation(non-existent schema, tab) should have the proper diagnostics +ok 882 - hasnt_relation(sch, non-existent tab, desc) should pass +ok 883 - hasnt_relation(sch, non-existent tab, desc) should have the proper description +ok 884 - hasnt_relation(sch, non-existent tab, desc) should have the proper diagnostics +ok 885 - hasnt_relation(tab, desc) should fail +ok 886 - hasnt_relation(tab, desc) should have the proper description +ok 887 - hasnt_relation(tab, desc) should have the proper diagnostics +ok 888 - hasnt_relation(sch, tab, desc) should fail +ok 889 - hasnt_relation(sch, tab, desc) should have the proper description +ok 890 - hasnt_relation(sch, tab, desc) should have the proper diagnostics +ok 891 - has_foreign_table(non-existent table) should fail +ok 892 - has_foreign_table(non-existent table) should have the proper description +ok 893 - has_foreign_table(non-existent table) should have the proper diagnostics +ok 894 - has_foreign_table(non-existent schema, tab) should fail +ok 895 - has_foreign_table(non-existent schema, tab) should have the proper description +ok 896 - has_foreign_table(non-existent schema, tab) should have the proper diagnostics +ok 897 - has_foreign_table(non-existent table, desc) should fail +ok 898 - has_foreign_table(non-existent table, desc) should have the proper description +ok 899 - has_foreign_table(non-existent table, desc) should have the proper diagnostics +ok 900 - has_foreign_table(sch, non-existent table, desc) should fail +ok 901 - has_foreign_table(sch, non-existent table, desc) should have the proper description +ok 902 - has_foreign_table(sch, non-existent table, desc) should have the proper diagnostics +ok 903 - has_foreign_table(tab, desc) should pass +ok 904 - has_foreign_table(tab, desc) should have the proper description +ok 905 - has_foreign_table(tab, desc) should have the proper diagnostics +ok 906 - has_foreign_table(sch, tab, desc) should pass +ok 907 - has_foreign_table(sch, tab, desc) should have the proper description +ok 908 - has_foreign_table(sch, tab, desc) should have the proper diagnostics +ok 909 - has_foreign_table(sch, view, desc) should fail +ok 910 - has_foreign_table(sch, view, desc) should have the proper description +ok 911 - has_foreign_table(sch, view, desc) should have the proper diagnostics +ok 912 - has_foreign_table(type, desc) should fail +ok 913 - has_foreign_table(type, desc) should have the proper description +ok 914 - has_foreign_table(type, desc) should have the proper diagnostics +ok 915 - hasnt_foreign_table(non-existent table) should pass +ok 916 - hasnt_foreign_table(non-existent table) should have the proper description +ok 917 - hasnt_foreign_table(non-existent table) should have the proper diagnostics +ok 918 - hasnt_foreign_table(non-existent schema, tab) should pass +ok 919 - hasnt_foreign_table(non-existent schema, tab) should have the proper description +ok 920 - hasnt_foreign_table(non-existent schema, tab) should have the proper diagnostics +ok 921 - hasnt_foreign_table(non-existent table, desc) should pass +ok 922 - hasnt_foreign_table(non-existent table, desc) should have the proper description +ok 923 - hasnt_foreign_table(non-existent table, desc) should have the proper diagnostics +ok 924 - hasnt_foreign_table(sch, non-existent tab, desc) should pass +ok 925 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper description +ok 926 - hasnt_foreign_table(sch, non-existent tab, desc) should have the proper diagnostics +ok 927 - hasnt_foreign_table(tab, desc) should fail +ok 928 - hasnt_foreign_table(tab, desc) should have the proper description +ok 929 - hasnt_foreign_table(tab, desc) should have the proper diagnostics +ok 930 - hasnt_foreign_table(sch, tab, desc) should fail +ok 931 - hasnt_foreign_table(sch, tab, desc) should have the proper description +ok 932 - hasnt_foreign_table(sch, tab, desc) should have the proper diagnostics +ok 933 - has_materialized_view(non-existent materialized_view) should fail +ok 934 - has_materialized_view(non-existent materialized_view) should have the proper description +ok 935 - has_materialized_view(non-existent materialized_view) should have the proper diagnostics +ok 936 - has_materialized_view(non-existent materialized_view, desc) should fail +ok 937 - has_materialized_view(non-existent materialized_view, desc) should have the proper description +ok 938 - has_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics +ok 939 - has_materialized_view(sch, non-existent materialized_view, desc) should fail +ok 940 - has_materialized_view(sch, non-existent materialized_view, desc) should have the proper description +ok 941 - has_materialized_view(sch, non-existent materialized_view, desc) should have the proper diagnostics +ok 942 - has_materialized_view(materialized_view, desc) should pass +ok 943 - has_materialized_view(materialized_view, desc) should have the proper description +ok 944 - has_materialized_view(materialized_view, desc) should have the proper diagnostics +ok 945 - has_materialized_view(sch, materialized_view, desc) should pass +ok 946 - has_materialized_view(sch, materialized_view, desc) should have the proper description +ok 947 - has_materialized_view(sch, materialized_view, desc) should have the proper diagnostics +ok 948 - hasnt_materialized_view(non-existent materialized_view) should pass +ok 949 - hasnt_materialized_view(non-existent materialized_view) should have the proper description +ok 950 - hasnt_materialized_view(non-existent materialized_view) should have the proper diagnostics +ok 951 - hasnt_materialized_view(non-existent materialized_view, desc) should pass +ok 952 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper description +ok 953 - hasnt_materialized_view(non-existent materialized_view, desc) should have the proper diagnostics +ok 954 - hasnt_materialized_view(sch, non-existent materialized_view, desc) should pass +ok 955 - hasnt_materialized_view(sch, non-existent materialized_view, desc) should have the proper description +ok 956 - hasnt_materialized_view(sch, non-existent materialized_view, desc) should have the proper diagnostics +ok 957 - hasnt_materialized_view(materialized_view, desc) should fail +ok 958 - hasnt_materialized_view(materialized_view, desc) should have the proper description +ok 959 - hasnt_materialized_view(materialized_view, desc) should have the proper diagnostics +ok 960 - hasnt_materialized_view(sch, materialized_view, desc) should fail +ok 961 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper description +ok 962 - hasnt_materialized_view(sch, materialized_view, desc) should have the proper diagnostics +ok 963 - is_partitioned(non-existent part) should fail +ok 964 - is_partitioned(non-existent part) should have the proper description +ok 965 - is_partitioned(non-existent part) should have the proper diagnostics +ok 966 - is_partitioned(non-existent part, desc) should fail +ok 967 - is_partitioned(non-existent part, desc) should have the proper description +ok 968 - is_partitioned(non-existent part, desc) should have the proper diagnostics +ok 969 - is_partitioned(sch, non-existent part, desc) should fail +ok 970 - is_partitioned(sch, non-existent part, desc) should have the proper description +ok 971 - is_partitioned(sch, non-existent part, desc) should have the proper diagnostics +ok 972 - is_partitioned(sch, part, desc) should pass +ok 973 - is_partitioned(sch, part, desc) should have the proper description +ok 974 - is_partitioned(sch, part, desc) should have the proper diagnostics +ok 975 - is_partitioned(sch, part) should pass +ok 976 - is_partitioned(sch, part) should have the proper description +ok 977 - is_partitioned(sch, part) should have the proper diagnostics +ok 978 - is_partitioned(part, desc) should pass +ok 979 - is_partitioned(part, desc) should have the proper description +ok 980 - is_partitioned(part, desc) should have the proper diagnostics +ok 981 - is_partitioned(part) should pass +ok 982 - is_partitioned(part) should have the proper description +ok 983 - is_partitioned(part) should have the proper diagnostics +ok 984 - isnt_partitioned(non-existent part) should pass +ok 985 - isnt_partitioned(non-existent part) should have the proper description +ok 986 - isnt_partitioned(non-existent part) should have the proper diagnostics +ok 987 - isnt_partitioned(non-existent part, desc) should pass +ok 988 - isnt_partitioned(non-existent part, desc) should have the proper description +ok 989 - isnt_partitioned(non-existent part, desc) should have the proper diagnostics +ok 990 - isnt_partitioned(sch, non-existent part, desc) should pass +ok 991 - isnt_partitioned(sch, non-existent part, desc) should have the proper description +ok 992 - isnt_partitioned(sch, non-existent part, desc) should have the proper diagnostics +ok 993 - isnt_partitioned(sch, part, desc) should fail +ok 994 - isnt_partitioned(sch, part, desc) should have the proper description +ok 995 - isnt_partitioned(sch, part, desc) should have the proper diagnostics +ok 996 - isnt_partitioned(sch, part) should fail +ok 997 - isnt_partitioned(sch, part) should have the proper description +ok 998 - isnt_partitioned(sch, part) should have the proper diagnostics +ok 999 - isnt_partitioned(part, desc) should fail +ok 1000 - isnt_partitioned(part, desc) should have the proper description +ok 1001 - isnt_partitioned(part, desc) should have the proper diagnostics +ok 1002 - isnt_partitioned(part) should fail +ok 1003 - isnt_partitioned(part) should have the proper description +ok 1004 - isnt_partitioned(part) should have the proper diagnostics diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index e63c6db15c71..b28706f7dda1 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(896); +SELECT plan(1004); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -1455,6 +1455,105 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test hasnt_operator(). + +SELECT * FROM check_test( + hasnt_operator( 'integer', 'pg_catalog', '<=', 'integer', 'boolean', 'desc' ), + false, + 'hasnt_operator( left, schema, name, right, result, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_operator( 'integer', 'pg_catalog', '<=', 'integer', 'boolean'::name ), + false, + 'hasnt_operator( left, schema, name, right, result ) fail', + 'Operator pg_catalog.<=(integer,integer) RETURNS boolean should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_operator( 'integer', '<=', 'integer', 'boolean', 'desc' ), + false, + 'hasnt_operator( left, name, right, result, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_operator( 'integer', '<=', 'integer', 'boolean'::name ), + false, + 'hasnt_operator( left, name, right, result ) fail', + 'Operator <=(integer,integer) RETURNS boolean should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_operator( 'integer', '<=', 'integer', 'desc' ), + false, + 'hasnt_operator( left, name, right, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_operator( 'integer', '<=', 'integer'::name ), + false, + 'hasnt_operator( left, name, right ) fail', + 'Operator <=(integer,integer) should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_operator( 'integer', 'pg_catalog', '<=', 'text', 'boolean', 'desc' ), + true, + 'hasnt_operator( left, schema, name, right, result, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_operator( 'integer', 'pg_catalog', '<=', 'text', 'boolean'::name ), + true, + 'hasnt_operator( left, schema, name, right, result )', + 'Operator pg_catalog.<=(integer,text) RETURNS boolean should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_operator( 'integer', '<=', 'text', 'boolean', 'desc' ), + true, + 'hasnt_operator( left, name, right, result, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_operator( 'integer', '<=', 'text', 'boolean'::name ), + true, + 'hasnt_operator( left, name, right, result )', + 'Operator <=(integer,text) RETURNS boolean should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_operator( 'integer', '<=', 'text', 'desc' ), + true, + 'hasnt_operator( left, name, right, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_operator( 'integer', '<=', 'text'::name ), + true, + 'hasnt_operator( left, name, right )', + 'Operator <=(integer,text) should not exist', + '' +); + /****************************************************************************/ -- Test has_leftop(). @@ -1554,6 +1653,105 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test hasnt_leftop(). + +SELECT * FROM check_test( + hasnt_leftop( 'pg_catalog', '!!', 'bigint', 'numeric', 'desc' ), + false, + 'hasnt_leftop( schema, name, right, result, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_leftop( 'pg_catalog', '!!', 'bigint', 'numeric'::name ), + false, + 'hasnt_leftop( schema, name, right, result ) fail', + 'Left operator pg_catalog.!!(NONE,bigint) RETURNS numeric should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_leftop( '!!', 'bigint', 'numeric', 'desc' ), + false, + 'hasnt_leftop( name, right, result, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_leftop( '!!', 'bigint', 'numeric'::name ), + false, + 'hasnt_leftop( name, right, result ) fail', + 'Left operator !!(NONE,bigint) RETURNS numeric should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_leftop( '!!', 'bigint', 'desc' ), + false, + 'hasnt_leftop( name, right, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_leftop( '!!', 'bigint' ), + false, + 'hasnt_leftop( name, right ) fail', + 'Left operator !!(NONE,bigint) should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_leftop( 'pg_catalog', '!!', 'text', 'numeric', 'desc' ), + true, + 'hasnt_leftop( schema, name, right, result, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_leftop( 'pg_catalog', '!!', 'text', 'numeric'::name ), + true, + 'hasnt_leftop( schema, name, right, result )', + 'Left operator pg_catalog.!!(NONE,text) RETURNS numeric should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_leftop( '!!', 'text', 'numeric', 'desc' ), + true, + 'hasnt_leftop( name, right, result, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_leftop( '!!', 'text', 'numeric'::name ), + true, + 'hasnt_leftop( name, right, result )', + 'Left operator !!(NONE,text) RETURNS numeric should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_leftop( '!!', 'text', 'desc' ), + true, + 'hasnt_leftop( name, right, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_leftop( '!!', 'text' ), + true, + 'hasnt_leftop( name, right )', + 'Left operator !!(NONE,text) should not exist', + '' +); + /****************************************************************************/ -- Test has_rightop(). @@ -1704,6 +1902,105 @@ $$; SELECT * FROM test_rightop(); +/****************************************************************************/ +-- Test hasnt_rightop(). + +SELECT * FROM check_test( + hasnt_rightop( 'bigint', 'pg_catalog', '!', 'numeric', 'desc' ), + false, + 'hasnt_rightop( left, schema, name, result, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_rightop( 'bigint', 'pg_catalog', '!', 'numeric'::name ), + false, + 'hasnt_rightop( left, schema, name, result ) fail', + 'Right operator pg_catalog.!(bigint,NONE) RETURNS numeric should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_rightop( 'bigint', '!', 'numeric', 'desc' ), + false, + 'hasnt_rightop( left, name, result, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_rightop( 'bigint', '!', 'numeric'::name ), + false, + 'hasnt_rightop( left, name, result ) fail', + 'Right operator !(bigint,NONE) RETURNS numeric should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_rightop( 'bigint', '!', 'desc' ), + false, + 'hasnt_rightop( left, name, desc ) fail', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_rightop( 'bigint', '!' ), + false, + 'hasnt_rightop( left, name ) fail', + 'Right operator !(bigint,NONE) should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_rightop( 'text', 'pg_catalog', '!', 'numeric', 'desc' ), + true, + 'hasnt_rightop( left, schema, name, result, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_rightop( 'text', 'pg_catalog', '!', 'numeric'::name ), + true, + 'hasnt_rightop( left, schema, name, result )', + 'Right operator pg_catalog.!(text,NONE) RETURNS numeric should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_rightop( 'text', '!', 'numeric', 'desc' ), + true, + 'hasnt_rightop( left, name, result, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_rightop( 'text', '!', 'numeric'::name ), + true, + 'hasnt_rightop( left, name, result )', + 'Right operator !(text,NONE) RETURNS numeric should not exist', + '' +); + +SELECT * FROM check_test( + hasnt_rightop( 'text', '!', 'desc' ), + true, + 'hasnt_rightop( left, name, desc )', + 'desc', + '' +); + +SELECT * FROM check_test( + hasnt_rightop( 'text', '!' ), + true, + 'hasnt_rightop( left, name )', + 'Right operator !(text,NONE) should not exist', + '' +); + /****************************************************************************/ -- Test has_language() and hasnt_language(). From fc78be3c78add4233ecdee76e0d5eca21d88ec59 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 10 Oct 2021 12:52:25 -0400 Subject: [PATCH 1109/1195] Skip hasnt_rightop on Postgres 14. --- test/sql/hastap.sql | 261 ++++++++++++++++++++++++++------------------ 1 file changed, 155 insertions(+), 106 deletions(-) diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index b28706f7dda1..5cdc9ca984e3 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1657,7 +1657,7 @@ SELECT * FROM check_test( -- Test hasnt_leftop(). SELECT * FROM check_test( - hasnt_leftop( 'pg_catalog', '!!', 'bigint', 'numeric', 'desc' ), + hasnt_leftop( 'pg_catalog', '+', 'bigint', 'bigint', 'desc' ), false, 'hasnt_leftop( schema, name, right, result, desc ) fail', 'desc', @@ -1665,15 +1665,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_leftop( 'pg_catalog', '!!', 'bigint', 'numeric'::name ), + hasnt_leftop( 'pg_catalog', '+', 'bigint', 'bigint'::name ), false, 'hasnt_leftop( schema, name, right, result ) fail', - 'Left operator pg_catalog.!!(NONE,bigint) RETURNS numeric should not exist', + 'Left operator pg_catalog.+(NONE,bigint) RETURNS bigint should not exist', '' ); SELECT * FROM check_test( - hasnt_leftop( '!!', 'bigint', 'numeric', 'desc' ), + hasnt_leftop( '+', 'bigint', 'bigint', 'desc' ), false, 'hasnt_leftop( name, right, result, desc ) fail', 'desc', @@ -1681,15 +1681,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_leftop( '!!', 'bigint', 'numeric'::name ), + hasnt_leftop( '+', 'bigint', 'bigint'::name ), false, 'hasnt_leftop( name, right, result ) fail', - 'Left operator !!(NONE,bigint) RETURNS numeric should not exist', + 'Left operator +(NONE,bigint) RETURNS bigint should not exist', '' ); SELECT * FROM check_test( - hasnt_leftop( '!!', 'bigint', 'desc' ), + hasnt_leftop( '+', 'bigint', 'desc' ), false, 'hasnt_leftop( name, right, desc ) fail', 'desc', @@ -1697,15 +1697,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_leftop( '!!', 'bigint' ), + hasnt_leftop( '+', 'bigint' ), false, 'hasnt_leftop( name, right ) fail', - 'Left operator !!(NONE,bigint) should not exist', + 'Left operator +(NONE,bigint) should not exist', '' ); SELECT * FROM check_test( - hasnt_leftop( 'pg_catalog', '!!', 'text', 'numeric', 'desc' ), + hasnt_leftop( 'pg_catalog', '+', 'text', 'bigint', 'desc' ), true, 'hasnt_leftop( schema, name, right, result, desc )', 'desc', @@ -1713,15 +1713,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_leftop( 'pg_catalog', '!!', 'text', 'numeric'::name ), + hasnt_leftop( 'pg_catalog', '+', 'text', 'bigint'::name ), true, 'hasnt_leftop( schema, name, right, result )', - 'Left operator pg_catalog.!!(NONE,text) RETURNS numeric should not exist', + 'Left operator pg_catalog.+(NONE,text) RETURNS bigint should not exist', '' ); SELECT * FROM check_test( - hasnt_leftop( '!!', 'text', 'numeric', 'desc' ), + hasnt_leftop( '+', 'text', 'bigint', 'desc' ), true, 'hasnt_leftop( name, right, result, desc )', 'desc', @@ -1729,15 +1729,15 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_leftop( '!!', 'text', 'numeric'::name ), + hasnt_leftop( '+', 'text', 'bigint'::name ), true, 'hasnt_leftop( name, right, result )', - 'Left operator !!(NONE,text) RETURNS numeric should not exist', + 'Left operator +(NONE,text) RETURNS bigint should not exist', '' ); SELECT * FROM check_test( - hasnt_leftop( '!!', 'text', 'desc' ), + hasnt_leftop( '+', 'text', 'desc' ), true, 'hasnt_leftop( name, right, desc )', 'desc', @@ -1745,17 +1745,17 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_leftop( '!!', 'text' ), + hasnt_leftop( '+', 'text' ), true, 'hasnt_leftop( name, right )', - 'Left operator !!(NONE,text) should not exist', + 'Left operator +(NONE,text) should not exist', '' ); /****************************************************************************/ -- Test has_rightop(). -CREATE FUNCTION test_rightop() RETURNS SETOF TEXT LANGUAGE plpgsql AS $$ +CREATE FUNCTION test_has_rightop() RETURNS SETOF TEXT LANGUAGE plpgsql AS $$ DECLARE tap record; BEGIN @@ -1857,7 +1857,7 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; ELSE -- PostgreSQL 14 dropped support for postfix operators, so mock the - -- output for the test to pass. + -- output for the tests to pass. FOR tap IN SELECT * FROM (VALUES ('has_rightop( left, schema, name, result, desc ) should pass'), ('has_rightop( left, schema, name, result, desc ) should have the proper description'), @@ -1899,107 +1899,156 @@ BEGIN END IF; END; $$; -SELECT * FROM test_rightop(); - +SELECT * FROM test_has_rightop(); /****************************************************************************/ -- Test hasnt_rightop(). -SELECT * FROM check_test( - hasnt_rightop( 'bigint', 'pg_catalog', '!', 'numeric', 'desc' ), - false, - 'hasnt_rightop( left, schema, name, result, desc ) fail', - 'desc', - '' -); +CREATE FUNCTION test_hasnt_rightop() RETURNS SETOF TEXT LANGUAGE plpgsql AS $$ +DECLARE + tap record; +BEGIN + IF pg_version_num() < 140000 THEN + FOR tap IN SELECT * FROM check_test( + hasnt_rightop( 'bigint', 'pg_catalog', '!', 'numeric', 'desc' ), + false, + 'hasnt_rightop( left, schema, name, result, desc ) fail', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - hasnt_rightop( 'bigint', 'pg_catalog', '!', 'numeric'::name ), - false, - 'hasnt_rightop( left, schema, name, result ) fail', - 'Right operator pg_catalog.!(bigint,NONE) RETURNS numeric should not exist', - '' -); + FOR tap IN SELECT * FROM check_test( + hasnt_rightop( 'bigint', 'pg_catalog', '!', 'numeric'::name ), + false, + 'hasnt_rightop( left, schema, name, result ) fail', + 'Right operator pg_catalog.!(bigint,NONE) RETURNS numeric should not exist', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - hasnt_rightop( 'bigint', '!', 'numeric', 'desc' ), - false, - 'hasnt_rightop( left, name, result, desc ) fail', - 'desc', - '' -); + FOR tap IN SELECT * FROM check_test( + hasnt_rightop( 'bigint', '!', 'numeric', 'desc' ), + false, + 'hasnt_rightop( left, name, result, desc ) fail', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - hasnt_rightop( 'bigint', '!', 'numeric'::name ), - false, - 'hasnt_rightop( left, name, result ) fail', - 'Right operator !(bigint,NONE) RETURNS numeric should not exist', - '' -); + FOR tap IN SELECT * FROM check_test( + hasnt_rightop( 'bigint', '!', 'numeric'::name ), + false, + 'hasnt_rightop( left, name, result ) fail', + 'Right operator !(bigint,NONE) RETURNS numeric should not exist', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - hasnt_rightop( 'bigint', '!', 'desc' ), - false, - 'hasnt_rightop( left, name, desc ) fail', - 'desc', - '' -); + FOR tap IN SELECT * FROM check_test( + hasnt_rightop( 'bigint', '!', 'desc' ), + false, + 'hasnt_rightop( left, name, desc ) fail', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - hasnt_rightop( 'bigint', '!' ), - false, - 'hasnt_rightop( left, name ) fail', - 'Right operator !(bigint,NONE) should not exist', - '' -); + FOR tap IN SELECT * FROM check_test( + hasnt_rightop( 'bigint', '!' ), + false, + 'hasnt_rightop( left, name ) fail', + 'Right operator !(bigint,NONE) should not exist', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - hasnt_rightop( 'text', 'pg_catalog', '!', 'numeric', 'desc' ), - true, - 'hasnt_rightop( left, schema, name, result, desc )', - 'desc', - '' -); + FOR tap IN SELECT * FROM check_test( + hasnt_rightop( 'text', 'pg_catalog', '!', 'numeric', 'desc' ), + true, + 'hasnt_rightop( left, schema, name, result, desc )', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - hasnt_rightop( 'text', 'pg_catalog', '!', 'numeric'::name ), - true, - 'hasnt_rightop( left, schema, name, result )', - 'Right operator pg_catalog.!(text,NONE) RETURNS numeric should not exist', - '' -); + FOR tap IN SELECT * FROM check_test( + hasnt_rightop( 'text', 'pg_catalog', '!', 'numeric'::name ), + true, + 'hasnt_rightop( left, schema, name, result )', + 'Right operator pg_catalog.!(text,NONE) RETURNS numeric should not exist', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - hasnt_rightop( 'text', '!', 'numeric', 'desc' ), - true, - 'hasnt_rightop( left, name, result, desc )', - 'desc', - '' -); + FOR tap IN SELECT * FROM check_test( + hasnt_rightop( 'text', '!', 'numeric', 'desc' ), + true, + 'hasnt_rightop( left, name, result, desc )', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - hasnt_rightop( 'text', '!', 'numeric'::name ), - true, - 'hasnt_rightop( left, name, result )', - 'Right operator !(text,NONE) RETURNS numeric should not exist', - '' -); + FOR tap IN SELECT * FROM check_test( + hasnt_rightop( 'text', '!', 'numeric'::name ), + true, + 'hasnt_rightop( left, name, result )', + 'Right operator !(text,NONE) RETURNS numeric should not exist', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - hasnt_rightop( 'text', '!', 'desc' ), - true, - 'hasnt_rightop( left, name, desc )', - 'desc', - '' -); + FOR tap IN SELECT * FROM check_test( + hasnt_rightop( 'text', '!', 'desc' ), + true, + 'hasnt_rightop( left, name, desc )', + 'desc', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; -SELECT * FROM check_test( - hasnt_rightop( 'text', '!' ), - true, - 'hasnt_rightop( left, name )', - 'Right operator !(text,NONE) should not exist', - '' -); + FOR tap IN SELECT * FROM check_test( + hasnt_rightop( 'text', '!' ), + true, + 'hasnt_rightop( left, name )', + 'Right operator !(text,NONE) should not exist', + '' + ) AS b LOOP RETURN NEXT tap.b; END LOOP; + ELSE + -- PostgreSQL 14 dropped support for postfix operators, so mock the + -- output for the tests to pass. + FOR tap IN SELECT * FROM (VALUES + ('hasnt_rightop( left, schema, name, result, desc ) fail should fail'), + ('hasnt_rightop( left, schema, name, result, desc ) fail should have the proper description'), + ('hasnt_rightop( left, schema, name, result, desc ) fail should have the proper diagnostics'), + ('hasnt_rightop( left, schema, name, result ) fail should fail'), + ('hasnt_rightop( left, schema, name, result ) fail should have the proper description'), + ('hasnt_rightop( left, schema, name, result ) fail should have the proper diagnostics'), + ('hasnt_rightop( left, name, result, desc ) fail should fail'), + ('hasnt_rightop( left, name, result, desc ) fail should have the proper description'), + ('hasnt_rightop( left, name, result, desc ) fail should have the proper diagnostics'), + ('hasnt_rightop( left, name, result ) fail should fail'), + ('hasnt_rightop( left, name, result ) fail should have the proper description'), + ('hasnt_rightop( left, name, result ) fail should have the proper diagnostics'), + ('hasnt_rightop( left, name, desc ) fail should fail'), + ('hasnt_rightop( left, name, desc ) fail should have the proper description'), + ('hasnt_rightop( left, name, desc ) fail should have the proper diagnostics'), + ('hasnt_rightop( left, name ) fail should fail'), + ('hasnt_rightop( left, name ) fail should have the proper description'), + ('hasnt_rightop( left, name ) fail should have the proper diagnostics'), + ('hasnt_rightop( left, schema, name, result, desc ) should pass'), + ('hasnt_rightop( left, schema, name, result, desc ) should have the proper description'), + ('hasnt_rightop( left, schema, name, result, desc ) should have the proper diagnostics'), + ('hasnt_rightop( left, schema, name, result ) should pass'), + ('hasnt_rightop( left, schema, name, result ) should have the proper description'), + ('hasnt_rightop( left, schema, name, result ) should have the proper diagnostics'), + ('hasnt_rightop( left, name, result, desc ) should pass'), + ('hasnt_rightop( left, name, result, desc ) should have the proper description'), + ('hasnt_rightop( left, name, result, desc ) should have the proper diagnostics'), + ('hasnt_rightop( left, name, result ) should pass'), + ('hasnt_rightop( left, name, result ) should have the proper description'), + ('hasnt_rightop( left, name, result ) should have the proper diagnostics'), + ('hasnt_rightop( left, name, desc ) should pass'), + ('hasnt_rightop( left, name, desc ) should have the proper description'), + ('hasnt_rightop( left, name, desc ) should have the proper diagnostics'), + ('hasnt_rightop( left, name ) should pass'), + ('hasnt_rightop( left, name ) should have the proper description'), + ('hasnt_rightop( left, name ) should have the proper diagnostics') + ) AS A(b) LOOP RETURN NEXT pass(tap.b); END LOOP; + END IF; +END; +$$; +SELECT * FROM test_hasnt_rightop(); /****************************************************************************/ -- Test has_language() and hasnt_language(). From 3064044e4342a81dbefb6383a503528cff4b6768 Mon Sep 17 00:00:00 2001 From: Matt DeLuco Date: Sun, 10 Oct 2021 12:53:45 -0400 Subject: [PATCH 1110/1195] Follow up to #216 (fixed in 007ee2f). Fixes additional test failures (#259) where the current username must be quoted as an identifier. --- test/sql/ownership.sql | 58 +++++++++++++++++++++--------------------- test/sql/privs.sql | 54 +++++++++++++++++++-------------------- test/sql/roletap.sql | 4 +-- test/sql/usergroup.sql | 2 +- 4 files changed, 59 insertions(+), 59 deletions(-) diff --git a/test/sql/ownership.sql b/test/sql/ownership.sql index 531024ad1e1c..31a571d44bd5 100644 --- a/test/sql/ownership.sql +++ b/test/sql/ownership.sql @@ -71,7 +71,7 @@ SELECT * FROM check_test( db_owner_is(current_database(), current_user), true, 'db_owner_is(db, user)', - 'Database ' || quote_ident(current_database()) || ' should be owned by ' || current_user, + 'Database ' || quote_ident(current_database()) || ' should be owned by ' || quote_ident(current_user), '' ); @@ -141,7 +141,7 @@ SELECT * FROM check_test( relation_owner_is('public', 'sometab', current_user), true, 'relation_owner_is(sch, tab, user)', - 'Relation public.sometab should be owned by ' || current_user, + 'Relation public.sometab should be owned by ' || quote_ident(current_user), '' ); @@ -173,7 +173,7 @@ SELECT * FROM check_test( relation_owner_is('sometab', current_user), true, 'relation_owner_is(tab, user)', - 'Relation sometab should be owned by ' || current_user, + 'Relation sometab should be owned by ' || quote_ident(current_user), '' ); @@ -199,7 +199,7 @@ SELECT * FROM check_test( relation_owner_is('public', 'apart', current_user), true, 'relation_owner_is(sch, part, user)', - 'Relation public.apart should be owned by ' || current_user, + 'Relation public.apart should be owned by ' || quote_ident(current_user), '' ); @@ -231,7 +231,7 @@ SELECT * FROM check_test( relation_owner_is('apart', current_user), true, 'relation_owner_is(part, user)', - 'Relation apart should be owned by ' || current_user, + 'Relation apart should be owned by ' || quote_ident(current_user), '' ); @@ -257,7 +257,7 @@ SELECT * FROM check_test( relation_owner_is('public', 'someseq', current_user), true, 'relation_owner_is(sch, seq, user)', - 'Relation public.someseq should be owned by ' || current_user, + 'Relation public.someseq should be owned by ' || quote_ident(current_user), '' ); @@ -289,7 +289,7 @@ SELECT * FROM check_test( relation_owner_is('someseq', current_user), true, 'relation_owner_is(seq, user)', - 'Relation someseq should be owned by ' || current_user, + 'Relation someseq should be owned by ' || quote_ident(current_user), '' ); @@ -315,7 +315,7 @@ SELECT * FROM check_test( table_owner_is('public', 'sometab', current_user), true, 'table_owner_is(sch, tab, user)', - 'Table public.sometab should be owned by ' || current_user, + 'Table public.sometab should be owned by ' || quote_ident(current_user), '' ); @@ -347,7 +347,7 @@ SELECT * FROM check_test( table_owner_is('sometab', current_user), true, 'table_owner_is(tab, user)', - 'Table sometab should be owned by ' || current_user, + 'Table sometab should be owned by ' || quote_ident(current_user), '' ); @@ -407,7 +407,7 @@ SELECT * FROM check_test( view_owner_is('public', 'someview', current_user), true, 'view_owner_is(sch, view, user)', - 'View public.someview should be owned by ' || current_user, + 'View public.someview should be owned by ' || quote_ident(current_user), '' ); @@ -439,7 +439,7 @@ SELECT * FROM check_test( view_owner_is('someview', current_user), true, 'view_owner_is(view, user)', - 'View someview should be owned by ' || current_user, + 'View someview should be owned by ' || quote_ident(current_user), '' ); @@ -482,7 +482,7 @@ SELECT * FROM check_test( sequence_owner_is('public', 'someseq', current_user), true, 'sequence_owner_is(sch, sequence, user)', - 'Sequence public.someseq should be owned by ' || current_user, + 'Sequence public.someseq should be owned by ' || quote_ident(current_user), '' ); @@ -514,7 +514,7 @@ SELECT * FROM check_test( sequence_owner_is('someseq', current_user), true, 'sequence_owner_is(sequence, user)', - 'Sequence someseq should be owned by ' || current_user, + 'Sequence someseq should be owned by ' || quote_ident(current_user), '' ); @@ -557,7 +557,7 @@ SELECT * FROM check_test( composite_owner_is('public', 'sometype', current_user), true, 'composite_owner_is(sch, composite, user)', - 'Composite type public.sometype should be owned by ' || current_user, + 'Composite type public.sometype should be owned by ' || quote_ident(current_user), '' ); @@ -589,7 +589,7 @@ SELECT * FROM check_test( composite_owner_is('sometype', current_user), true, 'composite_owner_is(composite, user)', - 'Composite type sometype should be owned by ' || current_user, + 'Composite type sometype should be owned by ' || quote_ident(current_user), '' ); @@ -643,7 +643,7 @@ BEGIN foreign_table_owner_is('public', 'my_fdw', current_user), true, 'foreign_table_owner_is(sch, tab, user)', - 'Foreign table public.my_fdw should be owned by ' || current_user, + 'Foreign table public.my_fdw should be owned by ' || quote_ident(current_user), '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -675,7 +675,7 @@ BEGIN foreign_table_owner_is('my_fdw', current_user), true, 'foreign_table_owner_is(tab, user)', - 'Foreign table my_fdw should be owned by ' || current_user, + 'Foreign table my_fdw should be owned by ' || quote_ident(current_user), '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -798,7 +798,7 @@ SELECT * FROM check_test( function_owner_is('public', 'somefunction', ARRAY['integer'], current_user), true, 'function_owner_is(sch, function, args[integer], user)', - 'Function public.somefunction(integer) should be owned by ' || current_user, + 'Function public.somefunction(integer) should be owned by ' || quote_ident(current_user), '' ); @@ -814,7 +814,7 @@ SELECT * FROM check_test( function_owner_is('public', 'test_fdw', '{}'::NAME[], current_user), true, 'function_owner_is(sch, function, args[], user)', - 'Function public.test_fdw() should be owned by ' || current_user, + 'Function public.test_fdw() should be owned by ' || quote_ident(current_user), '' ); @@ -830,7 +830,7 @@ SELECT * FROM check_test( function_owner_is('somefunction', ARRAY['integer'], current_user), true, 'function_owner_is(function, args[integer], user)', - 'Function somefunction(integer) should be owned by ' || current_user, + 'Function somefunction(integer) should be owned by ' || quote_ident(current_user), '' ); @@ -846,7 +846,7 @@ SELECT * FROM check_test( function_owner_is('test_fdw', '{}'::NAME[], current_user), true, 'function_owner_is(function, args[], user)', - 'Function test_fdw() should be owned by ' || current_user, + 'Function test_fdw() should be owned by ' || quote_ident(current_user), '' ); @@ -942,7 +942,7 @@ SELECT * FROM check_test( index_owner_is('someschema', 'anothertab', 'idx_name', current_user), true, 'index_owner_is(schema, table, index, user)', - 'Index idx_name ON someschema.anothertab should be owned by ' || current_user, + 'Index idx_name ON someschema.anothertab should be owned by ' || quote_ident(current_user), '' ); @@ -999,7 +999,7 @@ SELECT * FROM check_test( index_owner_is('sometab', 'idx_hey', current_user), true, 'index_owner_is(table, index, user)', - 'Index idx_hey ON sometab should be owned by ' || current_user, + 'Index idx_hey ON sometab should be owned by ' || quote_ident(current_user), '' ); @@ -1213,7 +1213,7 @@ SELECT * FROM check_test( type_owner_is('someschema', 'us_postal_code', current_user), true, 'type_owner_is(schema, type, user)', - 'Type someschema.us_postal_code should be owned by ' || current_user, + 'Type someschema.us_postal_code should be owned by ' || quote_ident(current_user), '' ); @@ -1262,7 +1262,7 @@ SELECT * FROM check_test( type_owner_is('sometype', current_user), true, 'type_owner_is(type, user)', - 'Type sometype should be owned by ' || current_user, + 'Type sometype should be owned by ' || quote_ident(current_user), '' ); @@ -1308,7 +1308,7 @@ BEGIN materialized_view_owner_is('public', 'somemview', current_user), true, 'materialized_view_owner_is(sch, materialized_view, user)', - 'Materialized view public.somemview should be owned by ' || current_user, + 'Materialized view public.somemview should be owned by ' || quote_ident(current_user), '' ) AS b LOOP RETURN NEXT tap.b; @@ -1348,7 +1348,7 @@ BEGIN materialized_view_owner_is('somemview', current_user), true, 'materialized_view_owner_is(view, user)', - 'Materialized view somemview should be owned by ' || current_user, + 'Materialized view somemview should be owned by ' || quote_ident(current_user), '' ) AS b LOOP RETURN NEXT tap.b; @@ -1400,7 +1400,7 @@ BEGIN view_owner_is('public', 'someview', current_user), true, 'materialized_view_owner_is(sch, materialized_view, user)', - 'View public.someview should be owned by ' || current_user, + 'View public.someview should be owned by ' || quote_ident(current_user), '' ) AS b LOOP RETURN NEXT tap.b; @@ -1440,7 +1440,7 @@ BEGIN view_owner_is('someview', current_user), true, 'materialized_view_owner_is(view, user)', - 'View someview should be owned by ' || current_user, + 'View someview should be owned by ' || quote_ident(current_user), '' ) AS b LOOP RETURN NEXT tap.b; diff --git a/test/sql/privs.sql b/test/sql/privs.sql index 0b0d841d88be..41daac599c52 100644 --- a/test/sql/privs.sql +++ b/test/sql/privs.sql @@ -51,7 +51,7 @@ SELECT * FROM check_test( table_privs_are( 'ha', 'sometab', current_user, _table_privs() ), true, 'table_privs_are(sch, tab, role, privs)', - 'Role ' || current_user || ' should be granted ' + 'Role ' || quote_ident(current_user) || ' should be granted ' || array_to_string(_table_privs(), ', ') || ' on table ha.sometab' , '' ); @@ -60,7 +60,7 @@ SELECT * FROM check_test( table_privs_are( 'LOL', 'ATable', current_user, _table_privs() ), true, 'table_privs_are(LOL, ATable, role, privs)', - 'Role ' || current_user || ' should be granted ' + 'Role ' || quote_ident(current_user) || ' should be granted ' || array_to_string(_table_privs(), ', ') || ' on table "LOL"."ATable"' , '' ); @@ -85,7 +85,7 @@ SELECT * FROM check_test( table_privs_are( 'sometab', current_user, _table_privs() ), true, 'table_privs_are(tab, role, privs)', - 'Role ' || current_user || ' should be granted ' + 'Role ' || quote_ident(current_user) || ' should be granted ' || array_to_string(_table_privs(), ', ') || ' on table sometab' , '' ); @@ -94,7 +94,7 @@ SELECT * FROM check_test( table_privs_are( 'ATable', current_user, _table_privs() ), true, 'table_privs_are(ATable, role, privs)', - 'Role ' || current_user || ' should be granted ' + 'Role ' || quote_ident(current_user) || ' should be granted ' || array_to_string(_table_privs(), ', ') || ' on table "ATable"' , '' ); @@ -214,7 +214,7 @@ SELECT * FROM check_test( database_privs_are( current_database(), current_user, _db_privs() ), true, 'database_privs_are(db, role, privs, desc)', - 'Role ' || current_user || ' should be granted ' + 'Role ' || quote_ident(current_user) || ' should be granted ' || array_to_string(_db_privs(), ', ') || ' on database ' || quote_ident( current_database() ), '' ); @@ -304,7 +304,7 @@ SELECT * FROM check_test( ), true, 'function_privs_are(sch, func, args, role, privs)', - 'Role ' || current_user || ' should be granted EXECUTE on function public.foo(integer, text)' + 'Role ' || quote_ident(current_user) || ' should be granted EXECUTE on function public.foo(integer, text)' '' ); @@ -315,7 +315,7 @@ SELECT * FROM check_test( ), true, 'function_privs_are(LOL, DoIt, args, role, privs)', - 'Role ' || current_user || ' should be granted EXECUTE on function "LOL"."DoIt"(integer, text)' + 'Role ' || quote_ident(current_user) || ' should be granted EXECUTE on function "LOL"."DoIt"(integer, text)' '' ); @@ -348,7 +348,7 @@ SELECT * FROM check_test( ), true, 'function_privs_are(func, args, role, privs)', - 'Role ' || current_user || ' should be granted EXECUTE on function foo(integer, text)' + 'Role ' || quote_ident(current_user) || ' should be granted EXECUTE on function foo(integer, text)' '' ); @@ -359,7 +359,7 @@ SELECT * FROM check_test( ), true, 'function_privs_are(DoIt, args, role, privs)', - 'Role ' || current_user || ' should be granted EXECUTE on function "DoIt"(integer, text)' + 'Role ' || quote_ident(current_user) || ' should be granted EXECUTE on function "DoIt"(integer, text)' '' ); @@ -550,7 +550,7 @@ SELECT * FROM check_test( language_privs_are( 'plpgsql', current_user, '{USAGE}' ), true, 'language_privs_are(lang, role, privs, desc)', - 'Role ' || current_user || ' should be granted USAGE on language plpgsql', + 'Role ' || quote_ident(current_user) || ' should be granted USAGE on language plpgsql', '' ); @@ -615,7 +615,7 @@ SELECT * FROM check_test( schema_privs_are( current_schema(), current_user, ARRAY['CREATE', 'USAGE'] ), true, 'schema_privs_are(schema, role, privs, desc)', - 'Role ' || current_user || ' should be granted ' + 'Role ' || quote_ident(current_user) || ' should be granted ' || array_to_string(ARRAY['CREATE', 'USAGE'], ', ') || ' on schema ' || current_schema(), '' ); @@ -624,7 +624,7 @@ SELECT * FROM check_test( schema_privs_are( 'LOL', current_user, ARRAY['CREATE', 'USAGE'] ), true, 'schema_privs_are(LOL, role, privs, desc)', - 'Role ' || current_user || ' should be granted ' + 'Role ' || quote_ident(current_user) || ' should be granted ' || array_to_string(ARRAY['CREATE', 'USAGE'], ', ') || ' on schema "LOL"', '' ); @@ -695,7 +695,7 @@ SELECT * FROM check_test( tablespace_privs_are( 'pg_default', current_user, '{CREATE}' ), true, 'tablespace_privs_are(tablespace, role, privs, desc)', - 'Role ' || current_user || ' should be granted CREATE on tablespace pg_default', + 'Role ' || quote_ident(current_user) || ' should be granted CREATE on tablespace pg_default', '' ); @@ -773,7 +773,7 @@ BEGIN sequence_privs_are( 'ha', 'someseq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'] ), true, 'sequence_privs_are(sch, seq, role, privs)', - 'Role ' || current_user || ' should be granted ' + 'Role ' || quote_ident(current_user) || ' should be granted ' || array_to_string(ARRAY['USAGE', 'SELECT', 'UPDATE'], ', ') || ' on sequence ha.someseq' , '' @@ -783,7 +783,7 @@ BEGIN sequence_privs_are( 'LOL', 'ASeq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'] ), true, 'sequence_privs_are(sch, seq, role, privs)', - 'Role ' || current_user || ' should be granted ' + 'Role ' || quote_ident(current_user) || ' should be granted ' || array_to_string(ARRAY['USAGE', 'SELECT', 'UPDATE'], ', ') || ' on sequence "LOL"."ASeq"' , '' @@ -813,7 +813,7 @@ BEGIN sequence_privs_are( 'someseq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'] ), true, 'sequence_privs_are(seq, role, privs)', - 'Role ' || current_user || ' should be granted ' + 'Role ' || quote_ident(current_user) || ' should be granted ' || array_to_string(ARRAY['USAGE', 'SELECT', 'UPDATE'], ', ') || ' on sequence someseq' , '' @@ -823,7 +823,7 @@ BEGIN sequence_privs_are( 'ASeq', current_user, ARRAY['USAGE', 'SELECT', 'UPDATE'] ), true, 'sequence_privs_are(seq, role, privs)', - 'Role ' || current_user || ' should be granted ' + 'Role ' || quote_ident(current_user) || ' should be granted ' || array_to_string(ARRAY['USAGE', 'SELECT', 'UPDATE'], ', ') || ' on sequence "ASeq"' , '' @@ -1085,7 +1085,7 @@ BEGIN ] ), true, 'any_column_privs_are(sch, tab, role, privs)', - 'Role ' || current_user || ' should be granted ' + 'Role ' || quote_ident(current_user) || ' should be granted ' || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') || ' on any column in ha.sometab' , '' @@ -1107,7 +1107,7 @@ BEGIN ] ), true, 'any_column_privs_are(tab, role, privs)', - 'Role ' || current_user || ' should be granted ' + 'Role ' || quote_ident(current_user) || ' should be granted ' || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') || ' on any column in sometab' , '' @@ -1345,7 +1345,7 @@ BEGIN ] ), true, 'column_privs_are(sch, tab, col, role, privs)', - 'Role ' || current_user || ' should be granted ' + 'Role ' || quote_ident(current_user) || ' should be granted ' || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') || ' on column ha.sometab.id' , '' @@ -1357,7 +1357,7 @@ BEGIN ] ), true, 'column_privs_are(LOL, ATable, AColumn, role, privs)', - 'Role ' || current_user || ' should be granted ' + 'Role ' || quote_ident(current_user) || ' should be granted ' || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') || ' on column "LOL"."ATable"."AColumn"' , '' @@ -1389,7 +1389,7 @@ BEGIN ] ), true, 'column_privs_are(tab, col, role, privs)', - 'Role ' || current_user || ' should be granted ' + 'Role ' || quote_ident(current_user) || ' should be granted ' || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') || ' on column sometab.id' , '' @@ -1401,7 +1401,7 @@ BEGIN ] ), true, 'column_privs_are(tab, col, role, privs)', - 'Role ' || current_user || ' should be granted ' + 'Role ' || quote_ident(current_user) || ' should be granted ' || array_to_string(ARRAY['INSERT', 'REFERENCES', 'SELECT', 'UPDATE'], ', ') || ' on column "ATable"."AColumn"' , '' @@ -1668,7 +1668,7 @@ BEGIN fdw_privs_are( 'dummy', current_user, '{USAGE}' ), true, 'fdw_privs_are(fdw, role, privs, desc)', - 'Role ' || current_user || ' should be granted USAGE on FDW dummy', + 'Role ' || quote_ident(current_user) || ' should be granted USAGE on FDW dummy', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1676,7 +1676,7 @@ BEGIN fdw_privs_are( 'SomeFDW', current_user, '{USAGE}' ), true, 'fdw_privs_are(SomeFDW, role, privs, desc)', - 'Role ' || current_user || ' should be granted USAGE on FDW "SomeFDW"', + 'Role ' || quote_ident(current_user) || ' should be granted USAGE on FDW "SomeFDW"', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1826,7 +1826,7 @@ BEGIN server_privs_are( 'foo', current_user, '{USAGE}' ), true, 'server_privs_are(server, role, privs, desc)', - 'Role ' || current_user || ' should be granted USAGE on server foo', + 'Role ' || quote_ident(current_user) || ' should be granted USAGE on server foo', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1834,7 +1834,7 @@ BEGIN server_privs_are( 'SomeServer', current_user, '{USAGE}' ), true, 'server_privs_are(SomeServer, role, privs, desc)', - 'Role ' || current_user || ' should be granted USAGE on server "SomeServer"', + 'Role ' || quote_ident(current_user) || ' should be granted USAGE on server "SomeServer"', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; diff --git a/test/sql/roletap.sql b/test/sql/roletap.sql index 30628ba6d033..fd0a0dc16e34 100644 --- a/test/sql/roletap.sql +++ b/test/sql/roletap.sql @@ -103,7 +103,7 @@ SELECT * FROM check_test( 'roles_are(roles, desc) extras', 'whatever', ' Extra roles: - ' || current_role + ' || quote_ident(current_role) ); SELECT * FROM check_test( @@ -112,7 +112,7 @@ SELECT * FROM check_test( 'roles_are(roles, desc) missing and extras', 'whatever', ' Extra roles: - ' || current_role || ' + ' || quote_ident(current_role) || ' Missing roles: __howdy__' ); diff --git a/test/sql/usergroup.sql b/test/sql/usergroup.sql index add4c8476945..ca0015aaa201 100644 --- a/test/sql/usergroup.sql +++ b/test/sql/usergroup.sql @@ -219,7 +219,7 @@ SELECT * FROM check_test( -- Test is_member_of(). CREATE OR REPLACE FUNCTION addmember() RETURNS SETOF TEXT AS $$ BEGIN - EXECUTE 'ALTER GROUP meanies ADD USER ' || current_user; + EXECUTE 'ALTER GROUP meanies ADD USER ' || quote_ident(current_user); RETURN; END; $$ LANGUAGE PLPGSQL; From 65e4fc900dae06c79404e6a50767afdcda6dc1f0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 10 Oct 2021 12:56:56 -0400 Subject: [PATCH 1111/1195] More credit to @mattdeluco --- Changes | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 92d6c5f05aea..040466ca2ebf 100644 --- a/Changes +++ b/Changes @@ -14,8 +14,9 @@ Revision history for pgTAP must be double-quoted when passed to `has_index()` or `is_indexed()`, sadly unlike other column arguments in pgTAP. Thanks to Keith Fiske for the report (#247). -* Fixed a test failure where the current username was not being quoted as an - identifier. Thanks to Matt DeLuco for the report (#216)! +* Fixed test failures where the current username was not being quoted as an + identifier. Thanks to Matt DeLuco for the report (#216) and pull request + #259)! * Fixed the `col_not_null()` drop statements in the uninstall script. Thanks to Kyle L. Jensen for the report (#252). * Removed straggler references to Postgres 9.0 and earlier, including From 986e3d47f0b8bc50f521500f029e1e3cd75ce479 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sun, 10 Oct 2021 19:33:12 +0200 Subject: [PATCH 1112/1195] Improve output of results_eq and results_ne when number of columns or their types are different (#255) Co-authored-by: David E. Wheeler --- Changes | 2 + compat/install-9.1.patch | 18 +++++ sql/pgtap--1.1.0--1.2.0.sql | 132 +++++++++++++++++++++++------------- sql/pgtap.sql.in | 14 ++-- test/sql/resultset.sql | 79 +++++++++++++++++---- test/sql/valueset.sql | 72 ++++++++++++++++---- test/test_MVU.sh | 2 + 7 files changed, 238 insertions(+), 81 deletions(-) diff --git a/Changes b/Changes index 040466ca2ebf..df470480a5fe 100644 --- a/Changes +++ b/Changes @@ -33,6 +33,8 @@ Revision history for pgTAP the pull request (#250). * Added `hasnt_operator()`, `hasnt_leftop()`, `hasnt_rightop()` (#38). Thanks to Wolfgang Walther for the pull request (#251). +* Improve output of results_eq and results_ne when number of columns or their + types are different (#37). 1.1.0 2019-11-25T19:05:38Z -------------------------- diff --git a/compat/install-9.1.patch b/compat/install-9.1.patch index 0047e2a655bb..d5394cd9901a 100644 --- a/compat/install-9.1.patch +++ b/compat/install-9.1.patch @@ -22,3 +22,21 @@ END; -- Always raise an exception to rollback any changes. +--- sql/pgtap.sql ++++ sql/pgtap.sql +@@ -6906,7 +6906,6 @@ + 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' || +@@ -7060,7 +7059,6 @@ + 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' || diff --git a/sql/pgtap--1.1.0--1.2.0.sql b/sql/pgtap--1.1.0--1.2.0.sql index 9920f4b3098e..b4085f089cd3 100644 --- a/sql/pgtap--1.1.0--1.2.0.sql +++ b/sql/pgtap--1.1.0--1.2.0.sql @@ -37,6 +37,91 @@ BEGIN END; $$ LANGUAGE plpgsql; +-- 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; + +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; +======= -- 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 $$ @@ -176,50 +261,3 @@ RETURNS TEXT AS $$ NOT _op_exists($1, $2, NULL ), 'Right operator ' || $2 || '(' || $1 || ',NONE) should not exist' ); -======= --- 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 438693318aa6..01eb8bc32745 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -7070,6 +7070,7 @@ DECLARE 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; @@ -7093,12 +7094,13 @@ BEGIN 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 + END || E'\n ERROR: ' || err_msg ); END; $$ LANGUAGE plpgsql; @@ -7227,6 +7229,7 @@ DECLARE 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; @@ -7245,10 +7248,13 @@ BEGIN RETURN ok( false, $3 ); EXCEPTION WHEN datatype_mismatch THEN + GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT; 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 + 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; diff --git a/test/sql/resultset.sql b/test/sql/resultset.sql index d47b78771a01..7a570ff8947c 100644 --- a/test/sql/resultset.sql +++ b/test/sql/resultset.sql @@ -981,11 +981,20 @@ SELECT * FROM check_test( false, 'results_eq(values, values) mismatch', '', - CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Number of columns or their types differ between the queries:' END || ' + CASE WHEN pg_version_num() >= 90200 THEN + ' Number of columns or their types differ between the queries: have: (1,foo) - want: (foo,1)' + want: (foo,1) + ERROR: cannot compare dissimilar column types integer and text at record column 1' + ELSE + ' Number of columns or their types differ between the queries: + have: (1,foo) + want: (foo,1) + ERROR: details not available in pg <= 9.1' + END ); +-- Handle failure due to more subtle column mismatch SELECT * FROM check_test( results_eq( 'VALUES (1, ''foo''::varchar), (2, ''bar''::varchar)', @@ -994,7 +1003,13 @@ SELECT * FROM check_test( false, 'results_eq(values, values) subtle mismatch', '', - ' Number of columns or their types differ between the queries' + CASE WHEN pg_version_num() >= 90200 THEN + ' Number of columns or their types differ between the queries + ERROR: cannot compare dissimilar column types character varying and text at record column 2' + ELSE + ' Number of columns or their types differ between the queries + ERROR: details not available in pg <= 9.1' + END ); SELECT * FROM check_test( @@ -1005,21 +1020,35 @@ SELECT * FROM check_test( false, 'results_eq(values, values) integer type mismatch', '', - ' Number of columns or their types differ between the queries' + CASE WHEN pg_version_num() >= 90200 THEN + ' Number of columns or their types differ between the queries + ERROR: cannot compare dissimilar column types integer and bigint at record column 1' + ELSE + ' Number of columns or their types differ between the queries + ERROR: details not available in pg <= 9.1' + END ); -- Handle failure due to column count mismatch. SELECT * FROM check_test( results_eq( 'SELECT pk from dubs ORDER BY pk LIMIT 2', - 'SELECT name, pk from dubs ORDER BY pk LIMIT 2' + 'SELECT pk, name from dubs ORDER BY pk LIMIT 2' ), false, 'results_eq(values, values) fail column count', '', - CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Number of columns or their types differ between the queries:' END || ' + CASE WHEN pg_version_num() >= 90200 THEN + ' Number of columns or their types differ between the queries: + have: (1) + want: (1,foo) + ERROR: cannot compare record types with different numbers of columns' + ELSE + ' Number of columns or their types differ between the queries: have: (1) - want: (foo,1)' + want: (1,foo) + ERROR: details not available in pg <= 9.1' + END ); -- Compare with cursors. @@ -2135,9 +2164,17 @@ SELECT * FROM check_test( false, 'results_ne(values, values) mismatch', '', - ' Columns differ between queries: + CASE WHEN pg_version_num() >= 90200 THEN + ' Number of columns or their types differ between the queries: have: (1,foo) - want: (foo,1)' + want: (foo,1) + ERROR: cannot compare dissimilar column types integer and text at record column 1' + ELSE + ' Number of columns or their types differ between the queries: + have: (1,foo) + want: (foo,1) + ERROR: details not available in pg <= 9.1' + END ); -- Handle failure due to subtle column mismatch. @@ -2149,20 +2186,32 @@ SELECT * FROM check_test( false, 'results_ne(values, values) subtle mismatch', '', - ' Columns differ between queries: - have: (1,foo) - want: (1,foo)' + CASE WHEN pg_version_num() >= 90200 THEN + ' Number of columns or their types differ between the queries + ERROR: cannot compare dissimilar column types character varying and text at record column 2' + ELSE + ' Number of columns or their types differ between the queries + ERROR: details not available in pg <= 9.1' + END ); -- Handle failure due to column count mismatch. SELECT * FROM check_test( - results_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + results_ne( 'VALUES (1), (2)', 'VALUES (1, ''foo''), (2, ''bar'')' ), false, 'results_ne(values, values) fail column count', '', - ' Columns differ between queries: + CASE WHEN pg_version_num() >= 90200 THEN + ' Number of columns or their types differ between the queries: + have: (1) + want: (1,foo) + ERROR: cannot compare record types with different numbers of columns' + ELSE + ' Number of columns or their types differ between the queries: have: (1) - want: (foo,1)' + want: (1,foo) + ERROR: details not available in pg <= 9.1' + END ); -- Compare with cursors. diff --git a/test/sql/valueset.sql b/test/sql/valueset.sql index 4be2cb153aaf..9ac440294610 100644 --- a/test/sql/valueset.sql +++ b/test/sql/valueset.sql @@ -679,9 +679,17 @@ SELECT * FROM check_test( false, 'results_eq(values, values) mismatch', '', - CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Number of columns or their types differ between the queries:' END || ' + CASE WHEN pg_version_num() >= 90200 THEN + ' Number of columns or their types differ between the queries: have: (1,foo) - want: (foo,1)' + want: (foo,1) + ERROR: cannot compare dissimilar column types integer and text at record column 1' + ELSE + ' Number of columns or their types differ between the queries: + have: (1,foo) + want: (foo,1) + ERROR: details not available in pg <= 9.1' + END ); -- Handle failure due to more subtle column mismatch @@ -693,18 +701,32 @@ SELECT * FROM check_test( false, 'results_eq(values, values) subtle mismatch', '', - ' Number of columns or their types differ between the queries' + CASE WHEN pg_version_num() >= 90200 THEN + ' Number of columns or their types differ between the queries + ERROR: cannot compare dissimilar column types character varying and text at record column 2' + ELSE + ' Number of columns or their types differ between the queries + ERROR: details not available in pg <= 9.1' + END ); -- Handle failure due to column count mismatch. SELECT * FROM check_test( - results_eq( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + results_eq( 'VALUES (1), (2)', 'VALUES (1, ''foo''), (2, ''bar'')' ), false, 'results_eq(values, values) fail column count', '', - CASE WHEN pg_version_num() < 80400 THEN ' Results differ beginning at row 1:' ELSE ' Number of columns or their types differ between the queries:' END || ' + CASE WHEN pg_version_num() >= 90200 THEN + ' Number of columns or their types differ between the queries: + have: (1) + want: (1,foo) + ERROR: cannot compare record types with different numbers of columns' + ELSE + ' Number of columns or their types differ between the queries: have: (1) - want: (foo,1)' + want: (1,foo) + ERROR: details not available in pg <= 9.1' + END ); -- Compare with cursors. @@ -1509,15 +1531,23 @@ SELECT * FROM check_test( '' ); --- Handle failure due to more subtle column mismatch. +-- Handle failure due to column mismatch. SELECT * FROM check_test( results_ne( 'VALUES (1, ''foo''), (2, ''bar'')', 'VALUES (''foo'', 1), (''bar'', 2)' ), false, 'results_ne(values, values) mismatch', '', - ' Columns differ between queries: + CASE WHEN pg_version_num() >= 90200 THEN + ' Number of columns or their types differ between the queries: + have: (1,foo) + want: (foo,1) + ERROR: cannot compare dissimilar column types integer and text at record column 1' + ELSE + ' Number of columns or their types differ between the queries: have: (1,foo) - want: (foo,1)' + want: (foo,1) + ERROR: details not available in pg <= 9.1' + END ); -- Handle failure due to subtle column mismatch. @@ -1529,20 +1559,32 @@ SELECT * FROM check_test( false, 'results_ne(values, values) subtle mismatch', '', - ' Columns differ between queries: - have: (1,foo) - want: (1,foo)' + CASE WHEN pg_version_num() >= 90200 THEN + ' Number of columns or their types differ between the queries + ERROR: cannot compare dissimilar column types character varying and text at record column 2' + ELSE + ' Number of columns or their types differ between the queries + ERROR: details not available in pg <= 9.1' + END ); -- Handle failure due to column count mismatch. SELECT * FROM check_test( - results_ne( 'VALUES (1), (2)', 'VALUES (''foo'', 1), (''bar'', 2)' ), + results_ne( 'VALUES (1), (2)', 'VALUES (1, ''foo''), (2, ''bar'')' ), false, 'results_ne(values, values) fail column count', '', - ' Columns differ between queries: + CASE WHEN pg_version_num() >= 90200 THEN + ' Number of columns or their types differ between the queries: + have: (1) + want: (1,foo) + ERROR: cannot compare record types with different numbers of columns' + ELSE + ' Number of columns or their types differ between the queries: have: (1) - want: (foo,1)' + want: (1,foo) + ERROR: details not available in pg <= 9.1' + END ); -- Compare with cursors. diff --git a/test/test_MVU.sh b/test/test_MVU.sh index da374178b543..64e0bcf3bb05 100755 --- a/test/test_MVU.sh +++ b/test/test_MVU.sh @@ -306,6 +306,8 @@ add_exclude() { fi } +add_exclude 9.1 9.2 test/sql/resultset.sql +add_exclude 9.1 9.2 test/sql/valueset.sql add_exclude 9.1 9.2 test/sql/throwtap.sql add_exclude 9.4 9.5 test/sql/policy.sql test/sql/throwtap.sql add_exclude 9.6 10 test/sql/partitions.sql From 07ba0c5a57f02d69512aab6ed3440b40fd04b5ef Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 16 Oct 2021 18:36:39 -0400 Subject: [PATCH 1113/1195] Copyedit Changes --- Changes | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index df470480a5fe..031a2321ecb0 100644 --- a/Changes +++ b/Changes @@ -3,7 +3,7 @@ Revision history for pgTAP 1.2.0 -------------------------- -* Make description field optional in has_view() and hasnt_view() when +* Made the description field optional in `has_view()` and `hasnt_view()` when specifying schema (#230). Thanks to Godwottery for the patch! * Added support for the date and time keywords `CURRENT_DATE`, `CURRENT_TIME`, `CURRENT_TIMESTAMP`, `LOCALTIME`, and `LOCALTIMESTAMP` to `col_default_is()`. @@ -33,8 +33,9 @@ Revision history for pgTAP the pull request (#250). * Added `hasnt_operator()`, `hasnt_leftop()`, `hasnt_rightop()` (#38). Thanks to Wolfgang Walther for the pull request (#251). -* Improve output of results_eq and results_ne when number of columns or their - types are different (#37). +* Improved the output of `results_eq()` and `results_ne()` when the number of + columns or their types are different (#37). Thanks to Wolfgang Walther for + the pull request (#255). 1.1.0 2019-11-25T19:05:38Z -------------------------- From 472808e36f9ed36bd70d5a93dbb2b1a300701101 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 5 Nov 2021 16:51:11 -0400 Subject: [PATCH 1114/1195] Disable .psqlrc in parallel_conn.sh I have the timer enabled in my ~/.psqlrc, which messed with the collection of output in `parallel_conn.sh`, so tests never ran in parallel. So use -X to disable `.psqlrc`, and add some other options to eliminate other stuff that might appear in the output. Restores parallel testing on 9.6 and later on my box, which means the partition test is properly skipped on 9.6. Mostly fixes issue #279, though we should also be sure that serialized tests always pass, too. While at it, change the table names in the partitions test to eliminate conflicts with the inheritance tests. --- test/sql/partitions.sql | 30 +++++++++++++++--------------- tools/parallel_conn.sh | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/test/sql/partitions.sql b/test/sql/partitions.sql index 54c704bed79d..09e5645062a1 100644 --- a/test/sql/partitions.sql +++ b/test/sql/partitions.sql @@ -8,8 +8,8 @@ SELECT plan(102); SET client_min_messages = warning; -- Create inherited tables (not partitions). -CREATE TABLE public.parent(id INT PRIMARY KEY); -CREATE TABLE public.child(id INT PRIMARY KEY) INHERITS (public.parent); +CREATE TABLE public.base(id INT PRIMARY KEY); +CREATE TABLE public.sub(id INT PRIMARY KEY) INHERITS (public.base); -- Create a partitioned table with two partitions. CREATE TABLE public.parted(id INT NOT NULL) PARTITION BY RANGE (id); @@ -67,7 +67,7 @@ SELECT * FROM check_test( -- is_partition_of() should fail for inherited but not partitioned tables. SELECT * FROM check_test( - is_partition_of( 'public', 'child', 'public', 'parent', 'whatevs' ), + is_partition_of( 'public', 'sub', 'public', 'base', 'whatevs' ), false, 'is_partition_of( csch, non-part ctab, psch, non-part ptab, desc )', 'whatevs', @@ -75,16 +75,16 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_partition_of( 'child', 'parent', 'whatevs' ), + is_partition_of( 'sub', 'base', 'whatevs' ), false, 'is_partition_of( non-part ctab, non-part ptab, desc )', 'whatevs', '' ); --- is_partition_of() should fail for parted table and non-part child. +-- is_partition_of() should fail for parted table and non-part sub. SELECT * FROM check_test( - is_partition_of( 'public', 'child', 'public', 'parted', 'whatevs' ), + is_partition_of( 'public', 'sub', 'public', 'parted', 'whatevs' ), false, 'is_partition_of( csch, non-part ctab, psch, ptab, desc )', 'whatevs', @@ -92,16 +92,16 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_partition_of( 'child', 'parted', 'whatevs' ), + is_partition_of( 'sub', 'parted', 'whatevs' ), false, 'is_partition_of( non-part ctab, ptab, desc )', 'whatevs', '' ); --- is_partition_of() should fail for partition child but wrong parent. +-- is_partition_of() should fail for partition sub but wrong base. SELECT * FROM check_test( - is_partition_of( 'public', 'part1', 'public', 'parent', 'whatevs' ), + is_partition_of( 'public', 'part1', 'public', 'base', 'whatevs' ), false, 'is_partition_of( csch, ctab, psch, non-part ptab, desc )', 'whatevs', @@ -109,7 +109,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_partition_of( 'part1', 'parent', 'whatevs' ), + is_partition_of( 'part1', 'base', 'whatevs' ), false, 'is_partition_of( ctab, non-part ptab, desc )', 'whatevs', @@ -152,7 +152,7 @@ SELECT * FROM check_test( '' ); --- Should find public partition for hidden parent. +-- Should find public partition for hidden base. SELECT * FROM check_test( is_partition_of( 'public', 'not_hidden_part3', 'hide', 'hidden_parted', 'whatevs' ), true, @@ -307,21 +307,21 @@ SELECT * FROM check_test( -- Should not work for unpartitioned but inherited table SELECT * FROM check_test( - partitions_are( 'public', 'parent', '{child}', 'hi' ), + partitions_are( 'public', 'base', '{sub}', 'hi' ), false, 'partitions_are( sch, non-parted tab, inherited tab, desc )', 'hi', ' Missing partitions: - child' + sub' ); SELECT * FROM check_test( - partitions_are( 'parent', '{child}'::name[], 'hi' ), + partitions_are( 'base', '{sub}'::name[], 'hi' ), false, 'partitions_are( non-parted tab, inherited tab, desc )', 'hi', ' Missing partitions: - child' + sub' ); -- Should not work for non-existent table. diff --git a/tools/parallel_conn.sh b/tools/parallel_conn.sh index 5cad67e8df10..1923ec5408d8 100755 --- a/tools/parallel_conn.sh +++ b/tools/parallel_conn.sh @@ -22,7 +22,7 @@ fi COMMAND="SELECT greatest(1, current_setting('max_connections')::int - current_setting('superuser_reserved_connections')::int - (SELECT count(*) FROM pg_stat_activity) - 2)" -if PARALLEL_CONN=`psql -d ${PGDATABASE:-postgres} -qtc "$COMMAND" 2> /dev/null`; then +if PARALLEL_CONN=`psql -d ${PGDATABASE:-postgres} -P pager=off -P tuples_only=true -qAXtc "$COMMAND" 2> /dev/null`; then if [ $PARALLEL_CONN -ge 1 ] 2>/dev/null; then # We know it's a number at this point [ $PARALLEL_CONN -eq 1 ] && error "NOTICE: unable to run tests in parallel; not enough connections" From 47092f904d5a48a9a4cd348a41d55ea5f0ed7def Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 5 Nov 2021 17:00:11 -0400 Subject: [PATCH 1115/1195] Increment copyright date --- README.md | 2 +- doc/pgtap.mmd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ff46b9137003..827c8aa17c25 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ pgTAP requires PostgreSQL 9.1 or higher. Copyright and License --------------------- -Copyright (c) 2008-2020 David E. Wheeler. Some rights reserved. +Copyright (c) 2008-2021 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index f4fe900deaae..b096dd07efda 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -8505,7 +8505,7 @@ Credits Copyright and License --------------------- -Copyright (c) 2008-2020 David E. Wheeler. Some rights reserved. +Copyright (c) 2008-2021 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is From 17e2ea7bd8220827b9f96c76278a0bf38728b90e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 5 Nov 2021 17:13:15 -0400 Subject: [PATCH 1116/1195] Filter ignore tests from serial schedule Resolves #279. --- Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 88280e29a42b..9d04d7717283 100644 --- a/Makefile +++ b/Makefile @@ -345,6 +345,7 @@ REGRESS_OPTS = --inputdir=test --max-connections=$(PARALLEL_CONN) --schedule $(S SETUP_SCH = test/schedule/main.sch # schedule to use for test setup; this can be forcibly changed by some targets! IGNORE_TESTS = $(notdir $(EXCLUDE_TEST_FILES:.sql=)) PARALLEL_TESTS = $(filter-out $(IGNORE_TESTS),$(filter-out $(SERIAL_TESTS),$(ALL_TESTS))) +SERIAL_SCHEDULE_TESTS = $(filter-out $(IGNORE_TESTS),$(ALL_TESTS)) PG_PROVE_PARALLEL_TESTS = $(filter-out $(PG_PROVE_EXCLUDE_TESTS),$(PARALLEL_TESTS)) PG_PROVE_SERIAL_TESTS = $(filter-out $(PG_PROVE_EXCLUDE_TESTS),$(SERIAL_TESTS)) PG_PROVE_PARALLEL_FILES = $(call get_test_file,$(PG_PROVE_PARALLEL_TESTS)) @@ -384,7 +385,10 @@ $(TB_DIR)/exclude_tests: $(TB_DIR)/ $(TB_DIR)/all_tests @[ "`cat $@ 2>/dev/null`" = "$(EXCLUDE_TEST)" ] || (echo "Rebuilding $@"; echo "$(EXCLUDE_TEST)" > $@) $(TB_DIR)/serial.sch: $(GENERATED_SCHEDULE_DEPS) - @(for f in $(IGNORE_TESTS); do echo "ignore: $$f"; done; for f in $(ALL_TESTS); do echo "test: $$f"; done) > $@ + @( \ + for f in $(IGNORE_TESTS); do echo "ignore: $$f"; done; \ + for f in $(SERIAL_SCHEDULE_TESTS); do echo "test: $$f"; done \ + ) > $@ $(TB_DIR)/parallel.sch: $(GENERATED_SCHEDULE_DEPS) @( \ @@ -396,6 +400,7 @@ $(TB_DIR)/parallel.sch: $(GENERATED_SCHEDULE_DEPS) $(TB_DIR)/run.sch: $(TB_DIR)/which_schedule $(GENERATED_SCHEDULES) cp `cat $<` $@ + # Don't generate noise if we're not running tests... .PHONY: extension_check extension_check: From 5394f343d4da512c20cb80e14ad6ed2983d22479 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 5 Nov 2021 17:17:21 -0400 Subject: [PATCH 1117/1195] Note test fixes in changes --- Changes | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Changes b/Changes index 031a2321ecb0..6ea0e3e1c071 100644 --- a/Changes +++ b/Changes @@ -36,6 +36,11 @@ Revision history for pgTAP * Improved the output of `results_eq()` and `results_ne()` when the number of columns or their types are different (#37). Thanks to Wolfgang Walther for the pull request (#255). +* Fixed testing issue where tests for unsupported features were not properly + skipped on Postgres 9.6 and earlier. Only appeared when tests were run + serially (#279). +* Fixed an issue where tests might not run in parallel even when the server + supported it (#279). 1.1.0 2019-11-25T19:05:38Z -------------------------- From 4a4815b08b594ccf1710836f6b4fb581415ce932 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 6 Nov 2021 13:33:59 -0400 Subject: [PATCH 1118/1195] Add function type testing Previously we had `is_aggregate()` and `isnt_aggregate()`. Extend that model with these new functions: * `is_normal_function()` * `isnt_normal_function()` * `is_window()` * `isnt_window()` * `is_procedure()` * `isnt_procedure()` Procedures are not supported on Postgres 10 and earlier, so we don't test them on those versions. Also update `is_aggregate()` and `isnt_aggregate()` to consistently output diagnostics for nonexistent functions, and test those diagnostics (and do the same for the new functions, of course). Resolves #280. Also, update `gencore` with the new functions, plus some changes that have been missed in the past (does anyone even use `pgtap-core.sql` or `pgtap-schema.sql`?). --- Changes | 13 +- Makefile | 6 +- compat/gencore | 16 +- doc/pgtap.mmd | 260 ++++++- sql/pgtap--1.1.0--1.2.0.sql | 552 ++++++++++++++ sql/pgtap.sql.in | 460 +++++++++++- test/expected/functap.out | 934 ++++++++++++++++------- test/expected/proctap.out | 194 +++++ test/sql/functap.sql | 1396 +++++++++++++++++++++++++++++++---- test/sql/proctap.sql | 560 ++++++++++++++ 10 files changed, 3894 insertions(+), 497 deletions(-) create mode 100644 test/expected/proctap.out create mode 100644 test/sql/proctap.sql diff --git a/Changes b/Changes index 6ea0e3e1c071..39bb3800eac6 100644 --- a/Changes +++ b/Changes @@ -150,7 +150,18 @@ Revision history for pgTAP + `partitions_are()` * Added the materialized view-testing assertion functions to the v0.95.0 upgrade script; they were inadvertently omitted in the v0.95.0 release. -* Fixed faling tests on Postgres 8.1. +* Fixed failing tests on Postgres 8.1. +* Added function type testing functions to complement `is_aggregate()` and + `isnt_aggregate()`: + + `is_normal_function()` + + `isnt_normal_function()` + + `is_window()` + + `isnt_window()` + + `is_procedure()` + + `isnt_procedure()` +* Made the diagnostic output of `is_aggregate()` and `isnt_aggregate()` + consistent. Previously, when the function did not exist, some instances would + generate diagnostic output and some would not. 0.97.0 2016-11-28T22:18:29Z --------------------------- diff --git a/Makefile b/Makefile index 9d04d7717283..ead436bc7587 100644 --- a/Makefile +++ b/Makefile @@ -138,6 +138,11 @@ ifeq ($(shell echo $(VERSION) | grep -qE "^[89][.]" && echo yes || echo no),yes) EXCLUDE_TEST_FILES += test/sql/partitions.sql endif +# Stored procecures not supported prior to Postgres 11. +ifeq ($(shell echo $(VERSION) | grep -qE "^([89]|10)[.]" && echo yes || echo no),yes) +EXCLUDE_TEST_FILES += test/sql/proctap.sql +endif + # # Check for missing extensions # @@ -400,7 +405,6 @@ $(TB_DIR)/parallel.sch: $(GENERATED_SCHEDULE_DEPS) $(TB_DIR)/run.sch: $(TB_DIR)/which_schedule $(GENERATED_SCHEDULES) cp `cat $<` $@ - # Don't generate noise if we're not running tests... .PHONY: extension_check extension_check: diff --git a/compat/gencore b/compat/gencore index dc13199cc247..424ed7788e8b 100644 --- a/compat/gencore +++ b/compat/gencore @@ -5,9 +5,8 @@ use warnings; my $invert = shift; my %keep = map { chomp; $_ => 1 } ; - - my ($name, $type) = $invert ? ('Schema', 'schema-testing') : ('Core', 'assertion'); + print qq{ -- This file defines pgTAP $name, a portable collection of $type -- functions for TAP-based unit testing on PostgreSQL 9.1 or higher. It is @@ -83,6 +82,7 @@ throws_ok lives_ok performs_ok performs_within +_time_trial_type _time_trials _ident_array_to_string _prokind @@ -128,8 +128,16 @@ _returns function_returns _definer is_definer -_agg +isnt_definer +_type_func is_aggregate +isnt_aggregate +is_normal_function +isnt_normal_function +is_window +isnt_window +is_procedure +isnt_procedure _strict is_strict isnt_strict @@ -174,3 +182,5 @@ _get_dtype domain_type_is domain_type_isnt row_eq +_error_diag + diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index b096dd07efda..e8d3d45b743a 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -5609,6 +5609,87 @@ If the function does not exist, a handy diagnostic message will let you know: But then you check with `has_function()` first, right? +### `is_normal_function()` ### + + SELECT is_normal_function( :schema, :function, :args, :description ); + SELECT is_normal_function( :schema, :function, :args ); + SELECT is_normal_function( :schema, :function, :description ); + SELECT is_normal_function( :schema, :function ); + SELECT is_normal_function( :function, :args, :description ); + SELECT is_normal_function( :function, :args ); + SELECT is_normal_function( :function, :description ); + SELECT is_normal_function( :function ); + +**Parameters** + +`:schema` +: Schema in which to find the function. + +`:function` +: Function name. + +`:args` +: Array of data types for the function arguments. + +`:description` +: A short description of the test. + +Tests that a function is a normal function --- that is, not an aggregate, +window, or procedural function. If the `:schema` argument is omitted, then the +function must be visible in the search path. If the `:args[]` argument is +passed, then the function with that argument signature will be the one tested; +otherwise, a function with any signature will be checked (pass an empty array to +specify a function with an empty signature). If the `:description` is omitted, a +reasonable substitute will be created. Fails if the function is not a normal +function or if the function does not exist. Examples: + + SELECT is_normal_function( 'myschema', 'foo', ARRAY['integer', 'text'] ); + SELECT is_normal_function( 'do_something' ); + SELECT is_normal_function( 'do_something', ARRAY['integer'] ); + SELECT is_normal_function( 'do_something', ARRAY['numeric'] ); + +If no such function exists, a handy diagnostic message will let you know: + + # Failed test 290: "Function nasty() should be a normal function" + # Function nasty() does not exist + +But then you check with `has_function()` first, right? + +### `isnt_normal_function()` ### + + SELECT isnt_normal_function( :schema, :function, :args, :description ); + SELECT isnt_normal_function( :schema, :function, :args ); + SELECT isnt_normal_function( :schema, :function, :description ); + SELECT isnt_normal_function( :schema, :function ); + SELECT isnt_normal_function( :function, :args, :description ); + SELECT isnt_normal_function( :function, :args ); + SELECT isnt_normal_function( :function, :description ); + SELECT isnt_normal_function( :function ); + +**Parameters** + +`:schema` +: Schema in which to find the function. + +`:function` +: Function name. + +`:args` +: Array of data types for the function arguments. + +`:description` +: A short description of the test. + +This function is the inverse of `is_normal_function()`. The test passes if the +specified function exists and is not a normal function. + +If no such function exists, a handy diagnostic message will let you know: + + # Failed test 290: "Function nasty() should not be a normal function" + # Function nasty() does not exist + +But then you check with `has_function()` first, right? + ### `is_aggregate()` ### SELECT is_aggregate( :schema, :function, :args, :description ); @@ -5635,11 +5716,12 @@ But then you check with `has_function()` first, right? : A short description of the test. Tests that a function is an aggregate function. If the `:schema` argument is -omitted, then the function must be visible in the search path. If the -`:args[]` argument is passed, then the function with that argument signature -will be the one tested; otherwise, a function with any signature will be -checked (pass an empty array to specify a function with an empty signature). -If the `:description` is omitted, a reasonable substitute will be created. +omitted, then the function must be visible in the search path. If the `:args[]` +argument is passed, then the function with that argument signature will be the +one tested; otherwise, a function with any signature will be checked (pass an +empty array to specify a function with an empty signature). If the +`:description` is omitted, a reasonable substitute will be created. Fails if the +function is not an aggregate function, or if the function does not exist. Examples: SELECT is_aggregate( 'myschema', 'foo', ARRAY['integer', 'text'] ); @@ -5647,7 +5729,7 @@ Examples: SELECT is_aggregate( 'do_something', ARRAY['integer'] ); SELECT is_aggregate( 'do_something', ARRAY['numeric'] ); -If the function does not exist, a handy diagnostic message will let you know: +If no such function exists, a handy diagnostic message will let you know: # Failed test 290: "Function nasty() should be an aggregate function" # Function nasty() does not exist @@ -5679,16 +5761,176 @@ But then you check with `has_function()` first, right? `:description` : A short description of the test. -This function is the inverse of `is_aggregate()`. The test passes if the specified -function is not an aggregate function. +This function is the inverse of `is_aggregate()`. The test passes if the +specified function exists and is not an aggregate function. -If the function does not exist, a handy diagnostic message will let you know: +If no such function exists, a handy diagnostic message will let you know: # Failed test 290: "Function nasty() should not be an aggregate function" # Function nasty() does not exist But then you check with `has_function()` first, right? +### `is_window()` ### + + SELECT is_window( :schema, :function, :args, :description ); + SELECT is_window( :schema, :function, :args ); + SELECT is_window( :schema, :function, :description ); + SELECT is_window( :schema, :function ); + SELECT is_window( :function, :args, :description ); + SELECT is_window( :function, :args ); + SELECT is_window( :function, :description ); + SELECT is_window( :function ); + +**Parameters** + +`:schema` +: Schema in which to find the function. + +`:function` +: Function name. + +`:args` +: Array of data types for the function arguments. + +`:description` +: A short description of the test. + +Tests that a function is a window function. If the `:schema` argument is +omitted, then the function must be visible in the search path. If the `:args[]` +argument is passed, then the function with that argument signature will be the +one tested; otherwise, a function with any signature will be checked (pass an +empty array to specify a function with an empty signature). If the +`:description` is omitted, a reasonable substitute will be created. Fails if the +function is not a window function or if the function does not exist. Examples: + + SELECT is_window( 'myschema', 'foo', ARRAY['integer', 'text'] ); + SELECT is_window( 'do_something' ); + SELECT is_window( 'do_something', ARRAY['integer'] ); + SELECT is_window( 'do_something', ARRAY['numeric'] ); + +If no such function exists, a handy diagnostic message will let you know: + + # Failed test 290: "Function nasty() should be a window function" + # Function nasty() does not exist + +But then you check with `has_function()` first, right? + +### `isnt_window()` ### + + SELECT isnt_window( :schema, :function, :args, :description ); + SELECT isnt_window( :schema, :function, :args ); + SELECT isnt_window( :schema, :function, :description ); + SELECT isnt_window( :schema, :function ); + SELECT isnt_window( :function, :args, :description ); + SELECT isnt_window( :function, :args ); + SELECT isnt_window( :function, :description ); + SELECT isnt_window( :function ); + +**Parameters** + +`:schema` +: Schema in which to find the function. + +`:function` +: Function name. + +`:args` +: Array of data types for the function arguments. + +`:description` +: A short description of the test. + +This function is the inverse of `is_window()`. The test passes if the +specified function exists and is not a window function. + +If no such function exists, a handy diagnostic message will let you know: + + # Failed test 290: "Function nasty() should not be a window function" + # Function nasty() does not exist + +But then you check with `has_function()` first, right? + +### `is_procedure()` ### + + SELECT is_procedure( :schema, :function, :args, :description ); + SELECT is_procedure( :schema, :function, :args ); + SELECT is_procedure( :schema, :function, :description ); + SELECT is_procedure( :schema, :function ); + SELECT is_procedure( :function, :args, :description ); + SELECT is_procedure( :function, :args ); + SELECT is_procedure( :function, :description ); + SELECT is_procedure( :function ); + +**Parameters** + +`:schema` +: Schema in which to find the function. + +`:function` +: Function name. + +`:args` +: Array of data types for the function arguments. + +`:description` +: A short description of the test. + +Tests that a function is a procedural function. If the `:schema` argument is +omitted, then the function must be visible in the search path. If the `:args[]` +argument is passed, then the function with that argument signature will be the +one tested; otherwise, a function with any signature will be checked (pass an +empty array to specify a function with an empty signature). If the +`:description` is omitted, a reasonable substitute will be created. Fails if the +function is not a procedure or if the function does not exist. Examples: + + SELECT is_procedure( 'myschema', 'foo', ARRAY['integer', 'text'] ); + SELECT is_procedure( 'do_something' ); + SELECT is_procedure( 'do_something', ARRAY['integer'] ); + SELECT is_procedure( 'do_something', ARRAY['numeric'] ); + +If no such function exists, a handy diagnostic message will let you know: + + # Failed test 290: "Function nasty() should be a procedure" + # Function nasty() does not exist + +But then you check with `has_function()` first, right? + +### `isnt_procedure()` ### + + SELECT isnt_procedure( :schema, :function, :args, :description ); + SELECT isnt_procedure( :schema, :function, :args ); + SELECT isnt_procedure( :schema, :function, :description ); + SELECT isnt_procedure( :schema, :function ); + SELECT isnt_procedure( :function, :args, :description ); + SELECT isnt_procedure( :function, :args ); + SELECT isnt_procedure( :function, :description ); + SELECT isnt_procedure( :function ); + +**Parameters** + +`:schema` +: Schema in which to find the function. + +`:function` +: Function name. + +`:args` +: Array of data types for the function arguments. + +`:description` +: A short description of the test. + +This function is the inverse of `is_procedure()`. The test passes if the +specified function exists and is not a procedure. + +If no such function exists, a handy diagnostic message will let you know: + + # Failed test 290: "Function nasty() should not be a procedure" + # Function nasty() does not exist + +But then you check with `has_function()` first, right? + ### `volatility_is()` ### SELECT volatility_is( :schema, :function, :args, :volatility, :description ); diff --git a/sql/pgtap--1.1.0--1.2.0.sql b/sql/pgtap--1.1.0--1.2.0.sql index b4085f089cd3..ad8274c7db59 100644 --- a/sql/pgtap--1.1.0--1.2.0.sql +++ b/sql/pgtap--1.1.0--1.2.0.sql @@ -261,3 +261,555 @@ RETURNS TEXT AS $$ NOT _op_exists($1, $2, NULL ), 'Right operator ' || $2 || '(' || $1 || ',NONE) should not exist' ); +$$ LANGUAGE SQL; + +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 FUNCTION _type_func ( "char", NAME, NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT kind = $1 + FROM tap_funky + WHERE schema = $2 + AND name = $3 + AND args = array_to_string($4, ',') +$$ LANGUAGE SQL; + +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; + +CREATE OR REPLACE FUNCTION _type_func ( "char", NAME, NAME[] ) +RETURNS BOOLEAN AS $$ + SELECT kind = $1 + FROM tap_funky + WHERE name = $2 + AND args = array_to_string($3, ',') + AND is_visible; +$$ LANGUAGE SQL; + +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; + +DROP FUNCTION _agg ( NAME ); +DROP FUNCTION _agg ( NAME, NAME[] ); +DROP FUNCTION _agg ( NAME, NAME ); +DROP FUNCTION _agg ( NAME, NAME, NAME[] ); +DROP FUNCTION _agg ( NAME ); +DROP FUNCTION _agg ( NAME, NAME[] ); +DROP FUNCTION _agg ( NAME, NAME ); +DROP FUNCTION _agg ( NAME, NAME, NAME[] ); +DROP FUNCTION _agg ( NAME ); +DROP FUNCTION _agg ( NAME, NAME[] ); +DROP FUNCTION _agg ( NAME, NAME ); +DROP FUNCTION _agg ( NAME, NAME, NAME[] ); + +-- 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 01eb8bc32745..dd096ec84000 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1073,9 +1073,10 @@ $$ 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' - ); + SELECT has_view ( + $1, $2, + 'View ' || quote_ident($1) || '.' || quote_ident($2) || ' should exist' + ); $$ LANGUAGE SQL; -- has_view( view, description ) @@ -2517,13 +2518,12 @@ BEGIN IF pg_version_num() >= 110000 THEN RETURN prokind FROM pg_catalog.pg_proc WHERE oid = p_oid; ELSE - RETURN CASE proisagg WHEN true THEN 'a' WHEN false THEN 'f' END + 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, @@ -5893,45 +5893,45 @@ RETURNS TEXT AS $$ SELECT ok( NOT _definer($1), 'Function ' || quote_ident($1) || '() should not be security definer' ); $$ LANGUAGE sql; -CREATE OR REPLACE FUNCTION _agg ( NAME, NAME, NAME[] ) +CREATE OR REPLACE FUNCTION _type_func ( "char", NAME, NAME, NAME[] ) RETURNS BOOLEAN AS $$ - SELECT kind = 'a' + SELECT kind = $1 FROM tap_funky - WHERE schema = $1 - AND name = $2 - AND args = array_to_string($3, ',') + WHERE schema = $2 + AND name = $3 + AND args = array_to_string($4, ',') $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _agg ( NAME, NAME ) +CREATE OR REPLACE FUNCTION _type_func ( "char", NAME, NAME ) RETURNS BOOLEAN AS $$ - SELECT kind = 'a' FROM tap_funky WHERE schema = $1 AND name = $2 + SELECT kind = $1 FROM tap_funky WHERE schema = $2 AND name = $3 $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _agg ( NAME, NAME[] ) +CREATE OR REPLACE FUNCTION _type_func ( "char", NAME, NAME[] ) RETURNS BOOLEAN AS $$ - SELECT kind = 'a' + SELECT kind = $1 FROM tap_funky - WHERE name = $1 - AND args = array_to_string($2, ',') + WHERE name = $2 + AND args = array_to_string($3, ',') AND is_visible; $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION _agg ( NAME ) +CREATE OR REPLACE FUNCTION _type_func ( "char", NAME ) RETURNS BOOLEAN AS $$ - SELECT kind = 'a' FROM tap_funky WHERE name = $1 AND is_visible; + 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, _agg($1, $2, $3), $4 ); + 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 ok( - _agg($1, $2, $3), + 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' ); @@ -5940,14 +5940,14 @@ $$ LANGUAGE sql; -- is_aggregate( schema, function, description ) CREATE OR REPLACE FUNCTION is_aggregate ( NAME, NAME, TEXT ) RETURNS TEXT AS $$ - SELECT _func_compare($1, $2, _agg($1, $2), $3 ); + 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 ok( - _agg($1, $2), + SELECT _func_compare( + $1, $2, _type_func('a', $1, $2), 'Function ' || quote_ident($1) || '.' || quote_ident($2) || '() should be an aggregate function' ); $$ LANGUAGE sql; @@ -5955,14 +5955,14 @@ $$ 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, _agg($1, $2), $3 ); + 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 ok( - _agg($1, $2), + SELECT _func_compare( + NULL, $1, $2, _type_func('a', $1, $2), 'Function ' || quote_ident($1) || '(' || array_to_string($2, ', ') || ') should be an aggregate function' ); @@ -5971,26 +5971,29 @@ $$ LANGUAGE sql; -- is_aggregate( function, description ) CREATE OR REPLACE FUNCTION is_aggregate( NAME, TEXT ) RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, _agg($1), $2 ); + 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 ok( _agg($1), 'Function ' || quote_ident($1) || '() should be an aggregate function' ); + 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 _agg($1, $2, $3), $4 ); + 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 ok( - NOT _agg($1, $2, $3), + 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' ); @@ -5999,14 +6002,14 @@ $$ 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 _agg($1, $2), $3 ); + 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 ok( - NOT _agg($1, $2), + 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; @@ -6014,14 +6017,14 @@ $$ 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 _agg($1, $2), $3 ); + 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 ok( - NOT _agg($1, $2), + 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' ); @@ -6030,13 +6033,16 @@ $$ LANGUAGE sql; -- isnt_aggregate( function, description ) CREATE OR REPLACE FUNCTION isnt_aggregate( NAME, TEXT ) RETURNS TEXT AS $$ - SELECT _func_compare(NULL, $1, NOT _agg($1), $2 ); + 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 ok( NOT _agg($1), 'Function ' || quote_ident($1) || '() should not be an aggregate function' ); + 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[] ) @@ -10966,3 +10972,377 @@ RETURNS TEXT AS $$ '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; diff --git a/test/expected/functap.out b/test/expected/functap.out index 200c46ec4008..83ed9fad1a8f 100644 --- a/test/expected/functap.out +++ b/test/expected/functap.out @@ -1,5 +1,5 @@ \unset ECHO -1..640 +1..1012 ok 1 - simple function should pass ok 2 - simple function should have the proper description ok 3 - simple function should have the proper diagnostics @@ -360,283 +360,655 @@ ok 357 - is_definer(func) should have the proper diagnostics ok 358 - isnt_definer(func) should fail ok 359 - isnt_definer(func) should have the proper description ok 360 - isnt_definer(func) should have the proper diagnostics -ok 361 - is_aggregate(schema, func, arg, desc) should pass -ok 362 - is_aggregate(schema, func, arg, desc) should have the proper description -ok 363 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics -ok 364 - isnt_aggregate(schema, func, arg, desc) should fail -ok 365 - isnt_aggregate(schema, func, arg, desc) should have the proper description -ok 366 - isnt_aggregate(schema, func, arg, desc) should have the proper diagnostics -ok 367 - is_aggregate(schema, func, arg) should pass -ok 368 - is_aggregate(schema, func, arg) should have the proper description -ok 369 - is_aggregate(schema, func, arg) should have the proper diagnostics -ok 370 - isnt_aggregate(schema, func, arg) should fail -ok 371 - isnt_aggregate(schema, func, arg) should have the proper description -ok 372 - isnt_aggregate(schema, func, arg) should have the proper diagnostics -ok 373 - is_aggregate(schema, func, args, desc) should fail -ok 374 - is_aggregate(schema, func, args, desc) should have the proper description -ok 375 - is_aggregate(schema, func, args, desc) should have the proper diagnostics -ok 376 - isnt_aggregate(schema, func, args, desc) should pass -ok 377 - isnt_aggregate(schema, func, args, desc) should have the proper description -ok 378 - isnt_aggregate(schema, func, args, desc) should have the proper diagnostics -ok 379 - is_aggregate(schema, func, args) should fail -ok 380 - is_aggregate(schema, func, args) should have the proper description -ok 381 - is_aggregate(schema, func, args) should have the proper diagnostics -ok 382 - isnt_aggregate(schema, func, args) should pass -ok 383 - isnt_aggregate(schema, func, args) should have the proper description -ok 384 - isnt_aggregate(schema, func, args) should have the proper diagnostics -ok 385 - is_aggregate(schema, func, desc) should pass -ok 386 - is_aggregate(schema, func, desc) should have the proper description -ok 387 - is_aggregate(schema, func, desc) should have the proper diagnostics -ok 388 - isnt_aggregate(schema, func, desc) should fail -ok 389 - isnt_aggregate(schema, func, desc) should have the proper description -ok 390 - isnt_aggregate(schema, func, desc) should have the proper diagnostics -ok 391 - is_aggregate(schema, func) should pass -ok 392 - is_aggregate(schema, func) should have the proper description -ok 393 - is_aggregate(schema, func) should have the proper diagnostics -ok 394 - isnt_aggregate(schema, func) should fail -ok 395 - isnt_aggregate(schema, func) should have the proper description -ok 396 - isnt_aggregate(schema, func) should have the proper diagnostics -ok 397 - is_aggregate(schema, func, arg, desc) should pass -ok 398 - is_aggregate(schema, func, arg, desc) should have the proper description -ok 399 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics -ok 400 - isnt_aggregate(schema, func, arg, desc) should fail -ok 401 - isnt_aggregate(schema, func, arg, desc) should have the proper description -ok 402 - isnt_aggregate(schema, func, arg, desc) should have the proper diagnostics -ok 403 - is_aggregate(schema, func, arg) should pass -ok 404 - is_aggregate(schema, func, arg) should have the proper description -ok 405 - is_aggregate(schema, func, arg) should have the proper diagnostics -ok 406 - isnt_aggregate(schema, func, arg) should fail -ok 407 - isnt_aggregate(schema, func, arg) should have the proper description -ok 408 - isnt_aggregate(schema, func, arg) should have the proper diagnostics -ok 409 - is_aggregate(schema, func, args, desc) should fail -ok 410 - is_aggregate(schema, func, args, desc) should have the proper description -ok 411 - is_aggregate(schema, func, args, desc) should have the proper diagnostics -ok 412 - isnt_aggregate(schema, func, args, desc) should pass -ok 413 - isnt_aggregate(schema, func, args, desc) should have the proper description -ok 414 - isnt_aggregate(schema, func, args, desc) should have the proper diagnostics -ok 415 - is_aggregate(schema, func, args) should fail -ok 416 - is_aggregate(schema, func, args) should have the proper description -ok 417 - is_aggregate(schema, func, args) should have the proper diagnostics -ok 418 - isnt_aggregate(schema, func, args) should pass -ok 419 - isnt_aggregate(schema, func, args) should have the proper description -ok 420 - isnt_aggregate(schema, func, args) should have the proper diagnostics -ok 421 - is_aggregate(schema, func, desc) should pass -ok 422 - is_aggregate(schema, func, desc) should have the proper description -ok 423 - is_aggregate(schema, func, desc) should have the proper diagnostics -ok 424 - isnt_aggregate(schema, func, desc) should fail -ok 425 - isnt_aggregate(schema, func, desc) should have the proper description -ok 426 - isnt_aggregate(schema, func, desc) should have the proper diagnostics -ok 427 - is_aggregate(schema, func) should pass -ok 428 - is_aggregate(schema, func) should have the proper description -ok 429 - is_aggregate(schema, func) should have the proper diagnostics -ok 430 - isnt_aggregate(schema, func) should fail -ok 431 - isnt_aggregate(schema, func) should have the proper description -ok 432 - isnt_aggregate(schema, func) should have the proper diagnostics -ok 433 - is_aggregate(func, arg, desc) should pass -ok 434 - is_aggregate(func, arg, desc) should have the proper description -ok 435 - is_aggregate(func, arg, desc) should have the proper diagnostics -ok 436 - isnt_aggregate(func, arg, desc) should fail -ok 437 - isnt_aggregate(func, arg, desc) should have the proper description -ok 438 - isnt_aggregate(func, arg, desc) should have the proper diagnostics -ok 439 - is_aggregate(func, arg) should pass -ok 440 - is_aggregate(func, arg) should have the proper description -ok 441 - is_aggregate(func, arg) should have the proper diagnostics -ok 442 - isnt_aggregate(func, arg) should fail -ok 443 - isnt_aggregate(func, arg) should have the proper description -ok 444 - isnt_aggregate(func, arg) should have the proper diagnostics -ok 445 - is_aggregate(func, args, desc) should fail -ok 446 - is_aggregate(func, args, desc) should have the proper description -ok 447 - is_aggregate(func, args, desc) should have the proper diagnostics -ok 448 - isnt_aggregate(func, args, desc) should pass -ok 449 - isnt_aggregate(func, args, desc) should have the proper description -ok 450 - isnt_aggregate(func, args, desc) should have the proper diagnostics -ok 451 - is_aggregate(func, args) should fail -ok 452 - is_aggregate(func, args) should have the proper description -ok 453 - is_aggregate(func, args) should have the proper diagnostics -ok 454 - isnt_aggregate(func, args) should pass -ok 455 - isnt_aggregate(func, args) should have the proper description -ok 456 - isnt_aggregate(func, args) should have the proper diagnostics -ok 457 - is_aggregate(func, desc) should pass -ok 458 - is_aggregate(func, desc) should have the proper description -ok 459 - is_aggregate(func, desc) should have the proper diagnostics -ok 460 - isnt_aggregate(func, desc) should fail -ok 461 - isnt_aggregate(func, desc) should have the proper description -ok 462 - isnt_aggregate(func, desc) should have the proper diagnostics -ok 463 - is_aggregate(func) should pass -ok 464 - is_aggregate(func) should have the proper description -ok 465 - is_aggregate(func) should have the proper diagnostics -ok 466 - isnt_aggregate(func) should fail -ok 467 - isnt_aggregate(func) should have the proper description -ok 468 - isnt_aggregate(func) should have the proper diagnostics -ok 469 - is_strict(schema, func, 0 args, desc) should pass -ok 470 - is_strict(schema, func, 0 args, desc) should have the proper description -ok 471 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 472 - isnt_strict(schema, func, 0 args, desc) should fail -ok 473 - isnt_strict(schema, func, 0 args, desc) should have the proper description -ok 474 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 475 - is_strict(schema, func, 0 args) should pass -ok 476 - is_strict(schema, func, 0 args) should have the proper description -ok 477 - is_strict(schema, func, 0 args) should have the proper diagnostics -ok 478 - isnt_strict(schema, func, 0 args) should fail -ok 479 - isnt_strict(schema, func, 0 args) should have the proper description -ok 480 - isnt_strict(schema, func, 0 args) should have the proper diagnostics -ok 481 - is_strict(schema, func, args, desc) should fail -ok 482 - is_strict(schema, func, args, desc) should have the proper description -ok 483 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 484 - is_strict(schema, func, args, desc) should pass -ok 485 - is_strict(schema, func, args, desc) should have the proper description -ok 486 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 487 - is_strict(schema, func, args) should fail -ok 488 - is_strict(schema, func, args) should have the proper description -ok 489 - is_strict(schema, func, args) should have the proper diagnostics -ok 490 - is_strict(schema, func, args) should pass -ok 491 - is_strict(schema, func, args) should have the proper description -ok 492 - is_strict(schema, func, args) should have the proper diagnostics -ok 493 - is_strict(schema, func, desc) should pass -ok 494 - is_strict(schema, func, desc) should have the proper description -ok 495 - is_strict(schema, func, desc) should have the proper diagnostics -ok 496 - isnt_strict(schema, func, desc) should fail -ok 497 - isnt_strict(schema, func, desc) should have the proper description -ok 498 - isnt_strict(schema, func, desc) should have the proper diagnostics -ok 499 - is_strict(schema, func) should pass -ok 500 - is_strict(schema, func) should have the proper description -ok 501 - is_strict(schema, func) should have the proper diagnostics -ok 502 - isnt_strict(schema, func) should fail -ok 503 - isnt_strict(schema, func) should have the proper description -ok 504 - isnt_strict(schema, func) should have the proper diagnostics -ok 505 - is_strict(schema, func, 0 args, desc) should pass -ok 506 - is_strict(schema, func, 0 args, desc) should have the proper description -ok 507 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 508 - isnt_strict(schema, func, 0 args, desc) should fail -ok 509 - isnt_strict(schema, func, 0 args, desc) should have the proper description -ok 510 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 511 - is_strict(schema, func, 0 args) should pass -ok 512 - is_strict(schema, func, 0 args) should have the proper description -ok 513 - is_strict(schema, func, 0 args) should have the proper diagnostics -ok 514 - isnt_strict(schema, func, 0 args) should fail -ok 515 - isnt_strict(schema, func, 0 args) should have the proper description -ok 516 - isnt_strict(schema, func, 0 args) should have the proper diagnostics -ok 517 - is_strict(schema, func, args, desc) should fail -ok 518 - is_strict(schema, func, args, desc) should have the proper description -ok 519 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 520 - isnt_strict(schema, func, args, desc) should pass -ok 521 - isnt_strict(schema, func, args, desc) should have the proper description -ok 522 - isnt_strict(schema, func, args, desc) should have the proper diagnostics -ok 523 - is_strict(schema, func, args) should fail -ok 524 - is_strict(schema, func, args) should have the proper description -ok 525 - is_strict(schema, func, args) should have the proper diagnostics -ok 526 - isnt_strict(schema, func, args) should pass -ok 527 - isnt_strict(schema, func, args) should have the proper description -ok 528 - isnt_strict(schema, func, args) should have the proper diagnostics -ok 529 - is_strict(schema, func, desc) should pass -ok 530 - is_strict(schema, func, desc) should have the proper description -ok 531 - is_strict(schema, func, desc) should have the proper diagnostics -ok 532 - isnt_strict(schema, func, desc) should fail -ok 533 - isnt_strict(schema, func, desc) should have the proper description -ok 534 - isnt_strict(schema, func, desc) should have the proper diagnostics -ok 535 - is_strict(schema, func) should pass -ok 536 - is_strict(schema, func) should have the proper description -ok 537 - is_strict(schema, func) should have the proper diagnostics -ok 538 - isnt_strict(schema, func) should fail -ok 539 - isnt_strict(schema, func) should have the proper description -ok 540 - isnt_strict(schema, func) should have the proper diagnostics -ok 541 - is_strict(func, 0 args, desc) should pass -ok 542 - is_strict(func, 0 args, desc) should have the proper description -ok 543 - is_strict(func, 0 args, desc) should have the proper diagnostics -ok 544 - isnt_strict(func, 0 args, desc) should fail -ok 545 - isnt_strict(func, 0 args, desc) should have the proper description -ok 546 - isnt_strict(func, 0 args, desc) should have the proper diagnostics -ok 547 - is_strict(func, 0 args) should pass -ok 548 - is_strict(func, 0 args) should have the proper description -ok 549 - is_strict(func, 0 args) should have the proper diagnostics -ok 550 - isnt_strict(func, 0 args) should fail -ok 551 - isnt_strict(func, 0 args) should have the proper description -ok 552 - isnt_strict(func, 0 args) should have the proper diagnostics -ok 553 - is_strict(func, args, desc) should fail -ok 554 - is_strict(func, args, desc) should have the proper description -ok 555 - is_strict(func, args, desc) should have the proper diagnostics -ok 556 - isnt_strict(func, args, desc) should pass -ok 557 - isnt_strict(func, args, desc) should have the proper description -ok 558 - isnt_strict(func, args, desc) should have the proper diagnostics -ok 559 - is_strict(func, args) should fail -ok 560 - is_strict(func, args) should have the proper description -ok 561 - is_strict(func, args) should have the proper diagnostics -ok 562 - isnt_strict(func, args) should pass -ok 563 - isnt_strict(func, args) should have the proper description -ok 564 - isnt_strict(func, args) should have the proper diagnostics -ok 565 - is_strict(func, desc) should pass -ok 566 - is_strict(func, desc) should have the proper description -ok 567 - is_strict(func, desc) should have the proper diagnostics -ok 568 - isnt_strict(func, desc) should fail -ok 569 - isnt_strict(func, desc) should have the proper description -ok 570 - isnt_strict(func, desc) should have the proper diagnostics -ok 571 - is_strict(func) should pass -ok 572 - is_strict(func) should have the proper description -ok 573 - is_strict(func) should have the proper diagnostics -ok 574 - isnt_strict(func) should fail -ok 575 - isnt_strict(func) should have the proper description -ok 576 - isnt_strict(func) should have the proper diagnostics -ok 577 - function_volatility(schema, func, 0 args, volatile, desc) should pass -ok 578 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper description -ok 579 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper diagnostics -ok 580 - function_volatility(schema, func, 0 args, VOLATILE, desc) should pass -ok 581 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper description -ok 582 - function_volatility(schema, func, 0 args, VOLATILE, desc) should have the proper diagnostics -ok 583 - function_volatility(schema, func, 0 args, v, desc) should pass -ok 584 - function_volatility(schema, func, 0 args, v, desc) should have the proper description -ok 585 - function_volatility(schema, func, 0 args, v, desc) should have the proper diagnostics -ok 586 - function_volatility(schema, func, args, immutable, desc) should pass -ok 587 - function_volatility(schema, func, args, immutable, desc) should have the proper description -ok 588 - function_volatility(schema, func, args, immutable, desc) should have the proper diagnostics -ok 589 - function_volatility(schema, func, 0 args, stable, desc) should pass -ok 590 - function_volatility(schema, func, 0 args, stable, desc) should have the proper description -ok 591 - function_volatility(schema, func, 0 args, stable, desc) should have the proper diagnostics -ok 592 - function_volatility(schema, func, 0 args, volatile) should pass -ok 593 - function_volatility(schema, func, 0 args, volatile) should have the proper description -ok 594 - function_volatility(schema, func, 0 args, volatile) should have the proper diagnostics -ok 595 - function_volatility(schema, func, args, immutable) should pass -ok 596 - function_volatility(schema, func, args, immutable) should have the proper description -ok 597 - function_volatility(schema, func, volatile, desc) should pass -ok 598 - function_volatility(schema, func, volatile, desc) should have the proper description -ok 599 - function_volatility(schema, func, volatile, desc) should have the proper diagnostics -ok 600 - function_volatility(schema, func, volatile) should pass -ok 601 - function_volatility(schema, func, volatile) should have the proper description -ok 602 - function_volatility(schema, func, volatile) should have the proper diagnostics -ok 603 - function_volatility(schema, func, immutable, desc) should pass -ok 604 - function_volatility(schema, func, immutable, desc) should have the proper description -ok 605 - function_volatility(schema, func, immutable, desc) should have the proper diagnostics -ok 606 - function_volatility(schema, func, stable, desc) should pass -ok 607 - function_volatility(schema, func, stable, desc) should have the proper description -ok 608 - function_volatility(schema, func, stable, desc) should have the proper diagnostics -ok 609 - function_volatility(func, 0 args, volatile, desc) should pass -ok 610 - function_volatility(func, 0 args, volatile, desc) should have the proper description -ok 611 - function_volatility(func, 0 args, volatile, desc) should have the proper diagnostics -ok 612 - function_volatility(func, 0 args, VOLATILE, desc) should pass -ok 613 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper description -ok 614 - function_volatility(func, 0 args, VOLATILE, desc) should have the proper diagnostics -ok 615 - function_volatility(func, 0 args, v, desc) should pass -ok 616 - function_volatility(func, 0 args, v, desc) should have the proper description -ok 617 - function_volatility(func, 0 args, v, desc) should have the proper diagnostics -ok 618 - function_volatility(func, args, immutable, desc) should pass -ok 619 - function_volatility(func, args, immutable, desc) should have the proper description -ok 620 - function_volatility(func, args, immutable, desc) should have the proper diagnostics -ok 621 - function_volatility(func, 0 args, stable, desc) should pass -ok 622 - function_volatility(func, 0 args, stable, desc) should have the proper description -ok 623 - function_volatility(func, 0 args, stable, desc) should have the proper diagnostics -ok 624 - function_volatility(func, 0 args, volatile) should pass -ok 625 - function_volatility(func, 0 args, volatile) should have the proper description -ok 626 - function_volatility(func, 0 args, volatile) should have the proper diagnostics -ok 627 - function_volatility(func, args, immutable) should pass -ok 628 - function_volatility(func, args, immutable) should have the proper description -ok 629 - function_volatility(func, volatile, desc) should pass -ok 630 - function_volatility(func, volatile, desc) should have the proper description -ok 631 - function_volatility(func, volatile, desc) should have the proper diagnostics -ok 632 - function_volatility(func, volatile) should pass -ok 633 - function_volatility(func, volatile) should have the proper description -ok 634 - function_volatility(func, volatile) should have the proper diagnostics -ok 635 - function_volatility(func, immutable, desc) should pass -ok 636 - function_volatility(func, immutable, desc) should have the proper description -ok 637 - function_volatility(func, immutable, desc) should have the proper diagnostics -ok 638 - function_volatility(func, stable, desc) should pass -ok 639 - function_volatility(func, stable, desc) should have the proper description -ok 640 - function_volatility(func, stable, desc) should have the proper diagnostics +ok 361 - is_normal_function(schema, func, noargs, desc) should pass +ok 362 - is_normal_function(schema, func, noargs, desc) should have the proper description +ok 363 - is_normal_function(schema, func, noargs, desc) should have the proper diagnostics +ok 364 - is_normal_function(schema, agg, arg, desc) should fail +ok 365 - is_normal_function(schema, agg, arg, desc) should have the proper description +ok 366 - is_normal_function(schema, agg, arg, desc) should have the proper diagnostics +ok 367 - isnt_normal_function(schema, func, noargs, desc) should fail +ok 368 - isnt_normal_function(schema, func, noargs, desc) should have the proper description +ok 369 - isnt_normal_function(schema, func, noargs, desc) should have the proper diagnostics +ok 370 - isnt_normal_function(schema, agg, noargs, desc) should pass +ok 371 - isnt_normal_function(schema, agg, noargs, desc) should have the proper description +ok 372 - isnt_normal_function(schema, agg, noargs, desc) should have the proper diagnostics +ok 373 - is_normal_function(schema, func, args, desc) should pass +ok 374 - is_normal_function(schema, func, args, desc) should have the proper description +ok 375 - is_normal_function(schema, func, args, desc) should have the proper diagnostics +ok 376 - is_normal_function(schema, agg, args, desc) should fail +ok 377 - is_normal_function(schema, agg, args, desc) should have the proper description +ok 378 - is_normal_function(schema, agg, args, desc) should have the proper diagnostics +ok 379 - is_normal_function(schema, func, args, desc) should fail +ok 380 - is_normal_function(schema, func, args, desc) should have the proper description +ok 381 - is_normal_function(schema, func, args, desc) should have the proper diagnostics +ok 382 - isnt_normal_function(schema, agg, args, desc) should pass +ok 383 - isnt_normal_function(schema, agg, args, desc) should have the proper description +ok 384 - isnt_normal_function(schema, agg, args, desc) should have the proper diagnostics +ok 385 - is_normal_function(schema, nofunc, noargs, desc) should fail +ok 386 - is_normal_function(schema, nofunc, noargs, desc) should have the proper description +ok 387 - is_normal_function(schema, nofunc, noargs, desc) should have the proper diagnostics +ok 388 - isnt_normal_function(schema, noagg, args, desc) should fail +ok 389 - isnt_normal_function(schema, noagg, args, desc) should have the proper description +ok 390 - isnt_normal_function(schema, noagg, args, desc) should have the proper diagnostics +ok 391 - is_normal_function(schema, func, noargs) should pass +ok 392 - is_normal_function(schema, func, noargs) should have the proper description +ok 393 - is_normal_function(schema, func, noargs) should have the proper diagnostics +ok 394 - is_normal_function(schema, agg, noargs) should fail +ok 395 - is_normal_function(schema, agg, noargs) should have the proper description +ok 396 - is_normal_function(schema, agg, noargs) should have the proper diagnostics +ok 397 - isnt_normal_function(schema, func, noargs) should fail +ok 398 - isnt_normal_function(schema, func, noargs) should have the proper description +ok 399 - isnt_normal_function(schema, func, noargs) should have the proper diagnostics +ok 400 - isnt_normal_function(schema, agg, noargs) should pass +ok 401 - isnt_normal_function(schema, agg, noargs) should have the proper description +ok 402 - isnt_normal_function(schema, agg, noargs) should have the proper diagnostics +ok 403 - is_normal_function(schema, func2, args) should pass +ok 404 - is_normal_function(schema, func2, args) should have the proper description +ok 405 - is_normal_function(schema, func2, args) should have the proper diagnostics +ok 406 - isnt_normal_function(schema, func2, args) should fail +ok 407 - isnt_normal_function(schema, func2, args) should have the proper description +ok 408 - isnt_normal_function(schema, func2, args) should have the proper diagnostics +ok 409 - is_normal_function(schema, func, noargs) should fail +ok 410 - is_normal_function(schema, func, noargs) should have the proper description +ok 411 - is_normal_function(schema, func, noargs) should have the proper diagnostics +ok 412 - is_normal_function(schema, nofunc, noargs) should fail +ok 413 - is_normal_function(schema, nofunc, noargs) should have the proper description +ok 414 - is_normal_function(schema, nofunc, noargs) should have the proper diagnostics +ok 415 - is_normal_function(schema, func, desc) should pass +ok 416 - is_normal_function(schema, func, desc) should have the proper description +ok 417 - is_normal_function(schema, func, desc) should have the proper diagnostics +ok 418 - is_normal_function(schema, agg, desc) should fail +ok 419 - is_normal_function(schema, agg, desc) should have the proper description +ok 420 - is_normal_function(schema, agg, desc) should have the proper diagnostics +ok 421 - isnt_normal_function(schema, func, desc) should fail +ok 422 - isnt_normal_function(schema, func, desc) should have the proper description +ok 423 - isnt_normal_function(schema, func, desc) should have the proper diagnostics +ok 424 - isnt_normal_function(schema, agg, desc) should pass +ok 425 - isnt_normal_function(schema, agg, desc) should have the proper description +ok 426 - isnt_normal_function(schema, agg, desc) should have the proper diagnostics +ok 427 - is_normal_function(schema, func2, desc) should pass +ok 428 - is_normal_function(schema, func2, desc) should have the proper description +ok 429 - is_normal_function(schema, func2, desc) should have the proper diagnostics +ok 430 - isnt_normal_function(schema, func2, desc) should fail +ok 431 - isnt_normal_function(schema, func2, desc) should have the proper description +ok 432 - isnt_normal_function(schema, func2, desc) should have the proper diagnostics +ok 433 - is_normal_function(schema, nofunc, desc) should fail +ok 434 - is_normal_function(schema, nofunc, desc) should have the proper description +ok 435 - is_normal_function(schema, nofunc, desc) should have the proper diagnostics +ok 436 - is_normal_function(schema, noagg, desc) should fail +ok 437 - is_normal_function(schema, noagg, desc) should have the proper description +ok 438 - is_normal_function(schema, noagg, desc) should have the proper diagnostics +ok 439 - is_normal_function(schema, func) should pass +ok 440 - is_normal_function(schema, func) should have the proper description +ok 441 - is_normal_function(schema, func) should have the proper diagnostics +ok 442 - is_normal_function(schema, agg) should fail +ok 443 - is_normal_function(schema, agg) should have the proper description +ok 444 - is_normal_function(schema, agg) should have the proper diagnostics +ok 445 - isnt_normal_function(schema, func) should fail +ok 446 - isnt_normal_function(schema, func) should have the proper description +ok 447 - isnt_normal_function(schema, func) should have the proper diagnostics +ok 448 - isnt_normal_function(schema, agg) should pass +ok 449 - isnt_normal_function(schema, agg) should have the proper description +ok 450 - isnt_normal_function(schema, agg) should have the proper diagnostics +ok 451 - is_normal_function(schema, func2, args) should pass +ok 452 - is_normal_function(schema, func2, args) should have the proper description +ok 453 - is_normal_function(schema, func2, args) should have the proper diagnostics +ok 454 - isnt_normal_function(schema, func2) should fail +ok 455 - isnt_normal_function(schema, func2) should have the proper description +ok 456 - isnt_normal_function(schema, func2) should have the proper diagnostics +ok 457 - is_normal_function(schema, nofunc) should fail +ok 458 - is_normal_function(schema, nofunc) should have the proper description +ok 459 - is_normal_function(schema, nofunc) should have the proper diagnostics +ok 460 - is_normal_function(schema, nogg) should fail +ok 461 - is_normal_function(schema, nogg) should have the proper description +ok 462 - is_normal_function(schema, nogg) should have the proper diagnostics +ok 463 - is_normal_function(func, noargs, desc) should pass +ok 464 - is_normal_function(func, noargs, desc) should have the proper description +ok 465 - is_normal_function(func, noargs, desc) should have the proper diagnostics +ok 466 - is_normal_function(func, agg, desc) should fail +ok 467 - is_normal_function(func, agg, desc) should have the proper description +ok 468 - is_normal_function(func, agg, desc) should have the proper diagnostics +ok 469 - isnt_normal_function(func, noargs, desc) should fail +ok 470 - isnt_normal_function(func, noargs, desc) should have the proper description +ok 471 - isnt_normal_function(func, noargs, desc) should have the proper diagnostics +ok 472 - isnt_normal_function(func, agg, desc) should pass +ok 473 - isnt_normal_function(func, agg, desc) should have the proper description +ok 474 - isnt_normal_function(func, agg, desc) should have the proper diagnostics +ok 475 - is_normal_function(func, args, desc) should pass +ok 476 - is_normal_function(func, args, desc) should have the proper description +ok 477 - is_normal_function(func, args, desc) should have the proper diagnostics +ok 478 - isnt_normal_function(func, args, desc) should fail +ok 479 - isnt_normal_function(func, args, desc) should have the proper description +ok 480 - isnt_normal_function(func, args, desc) should have the proper diagnostics +ok 481 - is_normal_function(nofunc, noargs, desc) should fail +ok 482 - is_normal_function(nofunc, noargs, desc) should have the proper description +ok 483 - is_normal_function(nofunc, noargs, desc) should have the proper diagnostics +ok 484 - is_normal_function(func, noagg, desc) should fail +ok 485 - is_normal_function(func, noagg, desc) should have the proper description +ok 486 - is_normal_function(func, noagg, desc) should have the proper diagnostics +ok 487 - is_normal_function(func, noargs) should pass +ok 488 - is_normal_function(func, noargs) should have the proper description +ok 489 - is_normal_function(func, noargs) should have the proper diagnostics +ok 490 - isnt_normal_function(func, noargs) should fail +ok 491 - isnt_normal_function(func, noargs) should have the proper description +ok 492 - isnt_normal_function(func, noargs) should have the proper diagnostics +ok 493 - is_normal_function(func, noargs) should pass +ok 494 - is_normal_function(func, noargs) should have the proper description +ok 495 - is_normal_function(func, noargs) should have the proper diagnostics +ok 496 - isnt_normal_function(func, noargs) should fail +ok 497 - isnt_normal_function(func, noargs) should have the proper description +ok 498 - isnt_normal_function(func, noargs) should have the proper diagnostics +ok 499 - is_normal_function(nofunc, noargs) should fail +ok 500 - is_normal_function(nofunc, noargs) should have the proper description +ok 501 - is_normal_function(nofunc, noargs) should have the proper diagnostics +ok 502 - isnt_normal_function(fnounc, noargs) should fail +ok 503 - isnt_normal_function(fnounc, noargs) should have the proper description +ok 504 - isnt_normal_function(fnounc, noargs) should have the proper diagnostics +ok 505 - is_normal_function(func, desc) should pass +ok 506 - is_normal_function(func, desc) should have the proper description +ok 507 - is_normal_function(func, desc) should have the proper diagnostics +ok 508 - is_normal_function(agg, desc) should fail +ok 509 - is_normal_function(agg, desc) should have the proper description +ok 510 - is_normal_function(agg, desc) should have the proper diagnostics +ok 511 - isnt_normal_function(func, desc) should fail +ok 512 - isnt_normal_function(func, desc) should have the proper description +ok 513 - isnt_normal_function(func, desc) should have the proper diagnostics +ok 514 - isnt_normal_function(agg, desc) should pass +ok 515 - isnt_normal_function(agg, desc) should have the proper description +ok 516 - isnt_normal_function(agg, desc) should have the proper diagnostics +ok 517 - is_normal_function(func2, desc) should pass +ok 518 - is_normal_function(func2, desc) should have the proper description +ok 519 - is_normal_function(func2, desc) should have the proper diagnostics +ok 520 - isnt_normal_function(func2, desc) should fail +ok 521 - isnt_normal_function(func2, desc) should have the proper description +ok 522 - isnt_normal_function(func2, desc) should have the proper diagnostics +ok 523 - is_normal_function(nofunc, desc) should fail +ok 524 - is_normal_function(nofunc, desc) should have the proper description +ok 525 - is_normal_function(nofunc, desc) should have the proper diagnostics +ok 526 - is_normal_function(noagg, desc) should fail +ok 527 - is_normal_function(noagg, desc) should have the proper description +ok 528 - is_normal_function(noagg, desc) should have the proper diagnostics +ok 529 - is_normal_function(func) should pass +ok 530 - is_normal_function(func) should have the proper description +ok 531 - is_normal_function(func) should have the proper diagnostics +ok 532 - is_normal_function(agg) should fail +ok 533 - is_normal_function(agg) should have the proper description +ok 534 - is_normal_function(agg) should have the proper diagnostics +ok 535 - isnt_normal_function(func) should fail +ok 536 - isnt_normal_function(func) should have the proper description +ok 537 - isnt_normal_function(func) should have the proper diagnostics +ok 538 - isnt_normal_function(agg) should pass +ok 539 - isnt_normal_function(agg) should have the proper description +ok 540 - isnt_normal_function(agg) should have the proper diagnostics +ok 541 - is_normal_function(func2) should pass +ok 542 - is_normal_function(func2) should have the proper description +ok 543 - is_normal_function(func2) should have the proper diagnostics +ok 544 - isnt_normal_function(func2,) should fail +ok 545 - isnt_normal_function(func2,) should have the proper description +ok 546 - isnt_normal_function(func2,) should have the proper diagnostics +ok 547 - is_normal_function(nofunc) should fail +ok 548 - is_normal_function(nofunc) should have the proper description +ok 549 - is_normal_function(nofunc) should have the proper diagnostics +ok 550 - is_normal_function(noagg) should fail +ok 551 - is_normal_function(noagg) should have the proper description +ok 552 - is_normal_function(noagg) should have the proper diagnostics +ok 553 - is_aggregate(schema, func, arg, desc) should pass +ok 554 - is_aggregate(schema, func, arg, desc) should have the proper description +ok 555 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 556 - isnt_aggregate(schema, agg, arg, desc) should fail +ok 557 - isnt_aggregate(schema, agg, arg, desc) should have the proper description +ok 558 - isnt_aggregate(schema, agg, arg, desc) should have the proper diagnostics +ok 559 - is_aggregate(schema, func, args, desc) should fail +ok 560 - is_aggregate(schema, func, args, desc) should have the proper description +ok 561 - is_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 562 - isnt_aggregate(schema, func, args, desc) should pass +ok 563 - isnt_aggregate(schema, func, args, desc) should have the proper description +ok 564 - isnt_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 565 - is_aggregate(schema, nofunc, arg, desc) should fail +ok 566 - is_aggregate(schema, nofunc, arg, desc) should have the proper description +ok 567 - is_aggregate(schema, nofunc, arg, desc) should have the proper diagnostics +ok 568 - isnt_aggregate(schema, noagg, arg, desc) should fail +ok 569 - isnt_aggregate(schema, noagg, arg, desc) should have the proper description +ok 570 - isnt_aggregate(schema, noagg, arg, desc) should have the proper diagnostics +ok 571 - is_aggregate(schema, agg, arg) should pass +ok 572 - is_aggregate(schema, agg, arg) should have the proper description +ok 573 - is_aggregate(schema, agg, arg) should have the proper diagnostics +ok 574 - isnt_aggregate(schema, agg, arg) should fail +ok 575 - isnt_aggregate(schema, agg, arg) should have the proper description +ok 576 - isnt_aggregate(schema, agg, arg) should have the proper diagnostics +ok 577 - is_aggregate(schema, func, args) should fail +ok 578 - is_aggregate(schema, func, args) should have the proper description +ok 579 - is_aggregate(schema, func, args) should have the proper diagnostics +ok 580 - isnt_aggregate(schema, func, args) should pass +ok 581 - isnt_aggregate(schema, func, args) should have the proper description +ok 582 - isnt_aggregate(schema, func, args) should have the proper diagnostics +ok 583 - is_aggregate(schema, noagg, arg) should fail +ok 584 - is_aggregate(schema, noagg, arg) should have the proper description +ok 585 - is_aggregate(schema, noagg, arg) should have the proper diagnostics +ok 586 - isnt_aggregate(schema, noagg, arg) should fail +ok 587 - isnt_aggregate(schema, noagg, arg) should have the proper description +ok 588 - isnt_aggregate(schema, noagg, arg) should have the proper diagnostics +ok 589 - is_aggregate(schema, agg, desc) should pass +ok 590 - is_aggregate(schema, agg, desc) should have the proper description +ok 591 - is_aggregate(schema, agg, desc) should have the proper diagnostics +ok 592 - isnt_aggregate(schema, agg, desc) should fail +ok 593 - isnt_aggregate(schema, agg, desc) should have the proper description +ok 594 - isnt_aggregate(schema, agg, desc) should have the proper diagnostics +ok 595 - is_aggregate(schema, noagg, desc) should fail +ok 596 - is_aggregate(schema, noagg, desc) should have the proper description +ok 597 - is_aggregate(schema, noagg, desc) should have the proper diagnostics +ok 598 - isnt_aggregate(schema, noagg, desc) should fail +ok 599 - isnt_aggregate(schema, noagg, desc) should have the proper description +ok 600 - isnt_aggregate(schema, noagg, desc) should have the proper diagnostics +ok 601 - is_aggregate(schema, agg) should pass +ok 602 - is_aggregate(schema, agg) should have the proper description +ok 603 - is_aggregate(schema, agg) should have the proper diagnostics +ok 604 - isnt_aggregate(schema, agg) should fail +ok 605 - isnt_aggregate(schema, agg) should have the proper description +ok 606 - isnt_aggregate(schema, agg) should have the proper diagnostics +ok 607 - is_aggregate(schema, noagg) should fail +ok 608 - is_aggregate(schema, noagg) should have the proper description +ok 609 - is_aggregate(schema, noagg) should have the proper diagnostics +ok 610 - isnt_aggregate(schema, noagg) should fail +ok 611 - isnt_aggregate(schema, noagg) should have the proper description +ok 612 - isnt_aggregate(schema, noagg) should have the proper diagnostics +ok 613 - is_aggregate(agg, arg, desc) should pass +ok 614 - is_aggregate(agg, arg, desc) should have the proper description +ok 615 - is_aggregate(agg, arg, desc) should have the proper diagnostics +ok 616 - isnt_aggregate(agg, arg, desc) should fail +ok 617 - isnt_aggregate(agg, arg, desc) should have the proper description +ok 618 - isnt_aggregate(agg, arg, desc) should have the proper diagnostics +ok 619 - is_aggregate(func, args, desc) should fail +ok 620 - is_aggregate(func, args, desc) should have the proper description +ok 621 - is_aggregate(func, args, desc) should have the proper diagnostics +ok 622 - isnt_aggregate(func, args, desc) should pass +ok 623 - isnt_aggregate(func, args, desc) should have the proper description +ok 624 - isnt_aggregate(func, args, desc) should have the proper diagnostics +ok 625 - is_aggregate(noagg, arg, desc) should fail +ok 626 - is_aggregate(noagg, arg, desc) should have the proper description +ok 627 - is_aggregate(noagg, arg, desc) should have the proper diagnostics +ok 628 - isnt_aggregate(noagg, arg, desc) should fail +ok 629 - isnt_aggregate(noagg, arg, desc) should have the proper description +ok 630 - isnt_aggregate(noagg, arg, desc) should have the proper diagnostics +ok 631 - is_aggregate(agg, arg) should pass +ok 632 - is_aggregate(agg, arg) should have the proper description +ok 633 - is_aggregate(agg, arg) should have the proper diagnostics +ok 634 - isnt_aggregate(agg, arg) should fail +ok 635 - isnt_aggregate(agg, arg) should have the proper description +ok 636 - isnt_aggregate(agg, arg) should have the proper diagnostics +ok 637 - is_aggregate(func, args) should fail +ok 638 - is_aggregate(func, args) should have the proper description +ok 639 - is_aggregate(func, args) should have the proper diagnostics +ok 640 - isnt_aggregate(func, args) should pass +ok 641 - isnt_aggregate(func, args) should have the proper description +ok 642 - isnt_aggregate(func, args) should have the proper diagnostics +ok 643 - is_aggregate(noagg, arg) should fail +ok 644 - is_aggregate(noagg, arg) should have the proper description +ok 645 - is_aggregate(noagg, arg) should have the proper diagnostics +ok 646 - isnt_aggregate(noagg, arg) should fail +ok 647 - isnt_aggregate(noagg, arg) should have the proper description +ok 648 - isnt_aggregate(noagg, arg) should have the proper diagnostics +ok 649 - is_aggregate(func, desc) should pass +ok 650 - is_aggregate(func, desc) should have the proper description +ok 651 - is_aggregate(func, desc) should have the proper diagnostics +ok 652 - isnt_aggregate(agg, desc) should fail +ok 653 - isnt_aggregate(agg, desc) should have the proper description +ok 654 - isnt_aggregate(agg, desc) should have the proper diagnostics +ok 655 - is_aggregate(nofunc, desc) should fail +ok 656 - is_aggregate(nofunc, desc) should have the proper description +ok 657 - is_aggregate(nofunc, desc) should have the proper diagnostics +ok 658 - isnt_aggregate(noagg, desc) should fail +ok 659 - isnt_aggregate(noagg, desc) should have the proper description +ok 660 - isnt_aggregate(noagg, desc) should have the proper diagnostics +ok 661 - is_aggregate(agg) should pass +ok 662 - is_aggregate(agg) should have the proper description +ok 663 - is_aggregate(agg) should have the proper diagnostics +ok 664 - isnt_aggregate(agg) should fail +ok 665 - isnt_aggregate(agg) should have the proper description +ok 666 - isnt_aggregate(agg) should have the proper diagnostics +ok 667 - is_aggregate(noagg) should fail +ok 668 - is_aggregate(noagg) should have the proper description +ok 669 - is_aggregate(noagg) should have the proper diagnostics +ok 670 - isnt_aggregate(noagg) should fail +ok 671 - isnt_aggregate(noagg) should have the proper description +ok 672 - isnt_aggregate(noagg) should have the proper diagnostics +ok 673 - is_window(schema, win, arg, desc) should pass +ok 674 - is_window(schema, win, arg, desc) should have the proper description +ok 675 - is_window(schema, win, arg, desc) should have the proper diagnostics +ok 676 - isnt_window(schema, win, arg, desc) should fail +ok 677 - isnt_window(schema, win, arg, desc) should have the proper description +ok 678 - isnt_window(schema, win, arg, desc) should have the proper diagnostics +ok 679 - is_window(schema, func, arg, desc) should fail +ok 680 - is_window(schema, func, arg, desc) should have the proper description +ok 681 - is_window(schema, func, arg, desc) should have the proper diagnostics +ok 682 - isnt_window(schema, func, arg, desc) should pass +ok 683 - isnt_window(schema, func, arg, desc) should have the proper description +ok 684 - isnt_window(schema, func, arg, desc) should have the proper diagnostics +ok 685 - is_window(schema, win, noargs, desc) should pass +ok 686 - is_window(schema, win, noargs, desc) should have the proper description +ok 687 - is_window(schema, win, noargs, desc) should have the proper diagnostics +ok 688 - isnt_window(schema, win, noargs, desc) should fail +ok 689 - isnt_window(schema, win, noargs, desc) should have the proper description +ok 690 - isnt_window(schema, win, noargs, desc) should have the proper diagnostics +ok 691 - is_window(schema, func, noarg, desc) should fail +ok 692 - is_window(schema, func, noarg, desc) should have the proper description +ok 693 - is_window(schema, func, noarg, desc) should have the proper diagnostics +ok 694 - is_window(schema, win, noargs, desc) should fail +ok 695 - is_window(schema, win, noargs, desc) should have the proper description +ok 696 - is_window(schema, win, noargs, desc) should have the proper diagnostics +ok 697 - is_window(schema, nowin, arg, desc) should fail +ok 698 - is_window(schema, nowin, arg, desc) should have the proper description +ok 699 - is_window(schema, nowin, arg, desc) should have the proper diagnostics +ok 700 - isnt_window(schema, nowin, arg, desc) should fail +ok 701 - isnt_window(schema, nowin, arg, desc) should have the proper description +ok 702 - isnt_window(schema, nowin, arg, desc) should have the proper diagnostics +ok 703 - is_window(schema, win, arg) should pass +ok 704 - is_window(schema, win, arg) should have the proper description +ok 705 - is_window(schema, win, arg) should have the proper diagnostics +ok 706 - isnt_window(schema, win, arg) should fail +ok 707 - isnt_window(schema, win, arg) should have the proper description +ok 708 - isnt_window(schema, win, arg) should have the proper diagnostics +ok 709 - is_window(schema, func, arg) should fail +ok 710 - is_window(schema, func, arg) should have the proper description +ok 711 - is_window(schema, func, arg) should have the proper diagnostics +ok 712 - isnt_window(schema, func, arg) should pass +ok 713 - isnt_window(schema, func, arg) should have the proper description +ok 714 - isnt_window(schema, func, arg) should have the proper diagnostics +ok 715 - is_window(schema, win, noargs) should pass +ok 716 - is_window(schema, win, noargs) should have the proper description +ok 717 - is_window(schema, win, noargs) should have the proper diagnostics +ok 718 - isnt_window(schema, win, noargs) should fail +ok 719 - isnt_window(schema, win, noargs) should have the proper description +ok 720 - isnt_window(schema, win, noargs) should have the proper diagnostics +ok 721 - is_window(schema, func, noarg) should fail +ok 722 - is_window(schema, func, noarg) should have the proper description +ok 723 - is_window(schema, func, noarg) should have the proper diagnostics +ok 724 - isnt_window(schema, win, noargs) should fail +ok 725 - isnt_window(schema, win, noargs) should have the proper description +ok 726 - isnt_window(schema, win, noargs) should have the proper diagnostics +ok 727 - is_window(schema, nowin, arg) should fail +ok 728 - is_window(schema, nowin, arg) should have the proper description +ok 729 - is_window(schema, nowin, arg) should have the proper diagnostics +ok 730 - isnt_window(schema, nowin, arg) should fail +ok 731 - isnt_window(schema, nowin, arg) should have the proper description +ok 732 - isnt_window(schema, nowin, arg) should have the proper diagnostics +ok 733 - is_window(schema, win, desc) should pass +ok 734 - is_window(schema, win, desc) should have the proper description +ok 735 - is_window(schema, win, desc) should have the proper diagnostics +ok 736 - isnt_window(schema, win, desc) should fail +ok 737 - isnt_window(schema, win, desc) should have the proper description +ok 738 - isnt_window(schema, win, desc) should have the proper diagnostics +ok 739 - is_window(schema, func, desc) should fail +ok 740 - is_window(schema, func, desc) should have the proper description +ok 741 - is_window(schema, func, desc) should have the proper diagnostics +ok 742 - isnt_window(schema, func, desc) should pass +ok 743 - isnt_window(schema, func, desc) should have the proper description +ok 744 - isnt_window(schema, func, desc) should have the proper diagnostics +ok 745 - is_window(schema, func, desc) should fail +ok 746 - is_window(schema, func, desc) should have the proper description +ok 747 - is_window(schema, func, desc) should have the proper diagnostics +ok 748 - isnt_window(schema, win, desc) should fail +ok 749 - isnt_window(schema, win, desc) should have the proper description +ok 750 - isnt_window(schema, win, desc) should have the proper diagnostics +ok 751 - is_window(schema, nowin, desc) should fail +ok 752 - is_window(schema, nowin, desc) should have the proper description +ok 753 - is_window(schema, nowin, desc) should have the proper diagnostics +ok 754 - isnt_window(schema, nowin, desc) should fail +ok 755 - isnt_window(schema, nowin, desc) should have the proper description +ok 756 - isnt_window(schema, nowin, desc) should have the proper diagnostics +ok 757 - is_window(schema, win) should pass +ok 758 - is_window(schema, win) should have the proper description +ok 759 - is_window(schema, win) should have the proper diagnostics +ok 760 - isnt_window(schema, win) should fail +ok 761 - isnt_window(schema, win) should have the proper description +ok 762 - isnt_window(schema, win) should have the proper diagnostics +ok 763 - is_window(schema, func) should fail +ok 764 - is_window(schema, func) should have the proper description +ok 765 - is_window(schema, func) should have the proper diagnostics +ok 766 - isnt_window(schema, func) should pass +ok 767 - isnt_window(schema, func) should have the proper description +ok 768 - isnt_window(schema, func) should have the proper diagnostics +ok 769 - is_window(schema, nowin) should fail +ok 770 - is_window(schema, nowin) should have the proper description +ok 771 - is_window(schema, nowin) should have the proper diagnostics +ok 772 - isnt_window(schema, nowin) should fail +ok 773 - isnt_window(schema, nowin) should have the proper description +ok 774 - isnt_window(schema, nowin) should have the proper diagnostics +ok 775 - is_window(win, arg, desc) should pass +ok 776 - is_window(win, arg, desc) should have the proper description +ok 777 - is_window(win, arg, desc) should have the proper diagnostics +ok 778 - isnt_window(win, arg, desc) should fail +ok 779 - isnt_window(win, arg, desc) should have the proper description +ok 780 - isnt_window(win, arg, desc) should have the proper diagnostics +ok 781 - is_window(func, arg, desc) should fail +ok 782 - is_window(func, arg, desc) should have the proper description +ok 783 - is_window(func, arg, desc) should have the proper diagnostics +ok 784 - isnt_window(func, arg, desc) should pass +ok 785 - isnt_window(func, arg, desc) should have the proper description +ok 786 - isnt_window(func, arg, desc) should have the proper diagnostics +ok 787 - is_window(win, noargs, desc) should pass +ok 788 - is_window(win, noargs, desc) should have the proper description +ok 789 - is_window(win, noargs, desc) should have the proper diagnostics +ok 790 - isnt_window(win, noargs, desc) should fail +ok 791 - isnt_window(win, noargs, desc) should have the proper description +ok 792 - isnt_window(win, noargs, desc) should have the proper diagnostics +ok 793 - is_window(func, noarg, desc) should fail +ok 794 - is_window(func, noarg, desc) should have the proper description +ok 795 - is_window(func, noarg, desc) should have the proper diagnostics +ok 796 - isnt_window(win, noargs, desc) should fail +ok 797 - isnt_window(win, noargs, desc) should have the proper description +ok 798 - isnt_window(win, noargs, desc) should have the proper diagnostics +ok 799 - is_window(nowin, arg, desc) should fail +ok 800 - is_window(nowin, arg, desc) should have the proper description +ok 801 - is_window(nowin, arg, desc) should have the proper diagnostics +ok 802 - isnt_window(nowin, arg, desc) should fail +ok 803 - isnt_window(nowin, arg, desc) should have the proper description +ok 804 - isnt_window(nowin, arg, desc) should have the proper diagnostics +ok 805 - is_window(win, arg, desc) should pass +ok 806 - is_window(win, arg, desc) should have the proper description +ok 807 - is_window(win, arg, desc) should have the proper diagnostics +ok 808 - isnt_window(win, arg, desc) should fail +ok 809 - isnt_window(win, arg, desc) should have the proper description +ok 810 - isnt_window(win, arg, desc) should have the proper diagnostics +ok 811 - is_window(func, arg, desc) should fail +ok 812 - is_window(func, arg, desc) should have the proper description +ok 813 - is_window(func, arg, desc) should have the proper diagnostics +ok 814 - isnt_window(func, arg, desc) should pass +ok 815 - isnt_window(func, arg, desc) should have the proper description +ok 816 - isnt_window(func, arg, desc) should have the proper diagnostics +ok 817 - is_window(win, noargs, desc) should pass +ok 818 - is_window(win, noargs, desc) should have the proper description +ok 819 - is_window(win, noargs, desc) should have the proper diagnostics +ok 820 - isnt_window(win, noargs, desc) should fail +ok 821 - isnt_window(win, noargs, desc) should have the proper description +ok 822 - isnt_window(win, noargs, desc) should have the proper diagnostics +ok 823 - is_window(func, noarg, desc) should fail +ok 824 - is_window(func, noarg, desc) should have the proper description +ok 825 - is_window(func, noarg, desc) should have the proper diagnostics +ok 826 - isnt_window(win, noargs, desc) should fail +ok 827 - isnt_window(win, noargs, desc) should have the proper description +ok 828 - isnt_window(win, noargs, desc) should have the proper diagnostics +ok 829 - is_window(nowin, arg, desc) should fail +ok 830 - is_window(nowin, arg, desc) should have the proper description +ok 831 - is_window(nowin, arg, desc) should have the proper diagnostics +ok 832 - isnt_window(nowin, arg, desc) should fail +ok 833 - isnt_window(nowin, arg, desc) should have the proper description +ok 834 - isnt_window(nowin, arg, desc) should have the proper diagnostics +ok 835 - is_window(win, desc) should pass +ok 836 - is_window(win, desc) should have the proper description +ok 837 - is_window(win, desc) should have the proper diagnostics +ok 838 - isnt_window(win, desc) should fail +ok 839 - isnt_window(win, desc) should have the proper description +ok 840 - isnt_window(win, desc) should have the proper diagnostics +ok 841 - is_window(func, desc) should fail +ok 842 - is_window(func, desc) should have the proper description +ok 843 - is_window(func, desc) should have the proper diagnostics +ok 844 - isnt_window(func, desc) should pass +ok 845 - isnt_window(func, desc) should have the proper description +ok 846 - isnt_window(func, desc) should have the proper diagnostics +ok 847 - is_window(func, desc) should fail +ok 848 - is_window(func, desc) should have the proper description +ok 849 - is_window(func, desc) should have the proper diagnostics +ok 850 - isnt_window(win, desc) should fail +ok 851 - isnt_window(win, desc) should have the proper description +ok 852 - isnt_window(win, desc) should have the proper diagnostics +ok 853 - is_window(nowin, desc) should fail +ok 854 - is_window(nowin, desc) should have the proper description +ok 855 - is_window(nowin, desc) should have the proper diagnostics +ok 856 - isnt_window(nowin, desc) should fail +ok 857 - isnt_window(nowin, desc) should have the proper description +ok 858 - isnt_window(nowin, desc) should have the proper diagnostics +ok 859 - is_window(win) should pass +ok 860 - is_window(win) should have the proper description +ok 861 - is_window(win) should have the proper diagnostics +ok 862 - isnt_window(win) should fail +ok 863 - isnt_window(win) should have the proper description +ok 864 - isnt_window(win) should have the proper diagnostics +ok 865 - is_window(func) should fail +ok 866 - is_window(func) should have the proper description +ok 867 - is_window(func) should have the proper diagnostics +ok 868 - isnt_window(func) should pass +ok 869 - isnt_window(func) should have the proper description +ok 870 - isnt_window(func) should have the proper diagnostics +ok 871 - is_window(nowin) should fail +ok 872 - is_window(nowin) should have the proper description +ok 873 - is_window(nowin) should have the proper diagnostics +ok 874 - isnt_window(nowin) should fail +ok 875 - isnt_window(nowin) should have the proper description +ok 876 - isnt_window(nowin) should have the proper diagnostics +ok 877 - is_strict(schema, func, 0 args, desc) should pass +ok 878 - is_strict(schema, func, 0 args, desc) should have the proper description +ok 879 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 880 - isnt_strict(schema, func, 0 args, desc) should fail +ok 881 - isnt_strict(schema, func, 0 args, desc) should have the proper description +ok 882 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 883 - is_strict(schema, func, 0 args) should pass +ok 884 - is_strict(schema, func, 0 args) should have the proper description +ok 885 - is_strict(schema, func, 0 args) should have the proper diagnostics +ok 886 - isnt_strict(schema, func, 0 args) should fail +ok 887 - isnt_strict(schema, func, 0 args) should have the proper description +ok 888 - isnt_strict(schema, func, 0 args) should have the proper diagnostics +ok 889 - is_strict(schema, func, args, desc) should fail +ok 890 - is_strict(schema, func, args, desc) should have the proper description +ok 891 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 892 - isnt_strict(schema, func, args, desc) should pass +ok 893 - isnt_strict(schema, func, args, desc) should have the proper description +ok 894 - isnt_strict(schema, func, args, desc) should have the proper diagnostics +ok 895 - is_strict(schema, func, args) should fail +ok 896 - is_strict(schema, func, args) should have the proper description +ok 897 - is_strict(schema, func, args) should have the proper diagnostics +ok 898 - isnt_strict(schema, func, args) should pass +ok 899 - isnt_strict(schema, func, args) should have the proper description +ok 900 - isnt_strict(schema, func, args) should have the proper diagnostics +ok 901 - is_strict(schema, func, desc) should pass +ok 902 - is_strict(schema, func, desc) should have the proper description +ok 903 - is_strict(schema, func, desc) should have the proper diagnostics +ok 904 - isnt_strict(schema, func, desc) should fail +ok 905 - isnt_strict(schema, func, desc) should have the proper description +ok 906 - isnt_strict(schema, func, desc) should have the proper diagnostics +ok 907 - is_strict(schema, func) should pass +ok 908 - is_strict(schema, func) should have the proper description +ok 909 - is_strict(schema, func) should have the proper diagnostics +ok 910 - isnt_strict(schema, func) should fail +ok 911 - isnt_strict(schema, func) should have the proper description +ok 912 - isnt_strict(schema, func) should have the proper diagnostics +ok 913 - isnt_strict(schema, func, args, desc) should pass +ok 914 - isnt_strict(schema, func, args, desc) should have the proper description +ok 915 - isnt_strict(schema, func, args, desc) should have the proper diagnostics +ok 916 - isnt_strict(schema, func, args) should pass +ok 917 - isnt_strict(schema, func, args) should have the proper description +ok 918 - isnt_strict(schema, func, args) should have the proper diagnostics +ok 919 - is_strict(func, 0 args, desc) should pass +ok 920 - is_strict(func, 0 args, desc) should have the proper description +ok 921 - is_strict(func, 0 args, desc) should have the proper diagnostics +ok 922 - isnt_strict(func, 0 args, desc) should fail +ok 923 - isnt_strict(func, 0 args, desc) should have the proper description +ok 924 - isnt_strict(func, 0 args, desc) should have the proper diagnostics +ok 925 - is_strict(func, 0 args) should pass +ok 926 - is_strict(func, 0 args) should have the proper description +ok 927 - is_strict(func, 0 args) should have the proper diagnostics +ok 928 - isnt_strict(func, 0 args) should fail +ok 929 - isnt_strict(func, 0 args) should have the proper description +ok 930 - isnt_strict(func, 0 args) should have the proper diagnostics +ok 931 - is_strict(func, args, desc) should fail +ok 932 - is_strict(func, args, desc) should have the proper description +ok 933 - is_strict(func, args, desc) should have the proper diagnostics +ok 934 - isnt_strict(func, args, desc) should pass +ok 935 - isnt_strict(func, args, desc) should have the proper description +ok 936 - isnt_strict(func, args, desc) should have the proper diagnostics +ok 937 - is_strict(func, args) should fail +ok 938 - is_strict(func, args) should have the proper description +ok 939 - is_strict(func, args) should have the proper diagnostics +ok 940 - isnt_strict(func, args) should pass +ok 941 - isnt_strict(func, args) should have the proper description +ok 942 - isnt_strict(func, args) should have the proper diagnostics +ok 943 - is_strict(func, desc) should pass +ok 944 - is_strict(func, desc) should have the proper description +ok 945 - is_strict(func, desc) should have the proper diagnostics +ok 946 - isnt_strict(func, desc) should fail +ok 947 - isnt_strict(func, desc) should have the proper description +ok 948 - isnt_strict(func, desc) should have the proper diagnostics +ok 949 - is_strict(func) should pass +ok 950 - is_strict(func) should have the proper description +ok 951 - is_strict(func) should have the proper diagnostics +ok 952 - isnt_strict(func) should fail +ok 953 - isnt_strict(func) should have the proper description +ok 954 - isnt_strict(func) should have the proper diagnostics +ok 955 - function_volatility(schema, func, 0 args, volatile, desc) should pass +ok 956 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper description +ok 957 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper diagnostics +ok 958 - function_volatility(schema, func, 0 args, v, desc) should pass +ok 959 - function_volatility(schema, func, 0 args, v, desc) should have the proper description +ok 960 - function_volatility(schema, func, 0 args, v, desc) should have the proper diagnostics +ok 961 - function_volatility(schema, func, args, immutable, desc) should pass +ok 962 - function_volatility(schema, func, args, immutable, desc) should have the proper description +ok 963 - function_volatility(schema, func, args, immutable, desc) should have the proper diagnostics +ok 964 - function_volatility(schema, func, 0 args, stable, desc) should pass +ok 965 - function_volatility(schema, func, 0 args, stable, desc) should have the proper description +ok 966 - function_volatility(schema, func, 0 args, stable, desc) should have the proper diagnostics +ok 967 - function_volatility(schema, func, 0 args, volatile) should pass +ok 968 - function_volatility(schema, func, 0 args, volatile) should have the proper description +ok 969 - function_volatility(schema, func, 0 args, volatile) should have the proper diagnostics +ok 970 - function_volatility(schema, func, args, immutable) should pass +ok 971 - function_volatility(schema, func, args, immutable) should have the proper description +ok 972 - function_volatility(schema, func, volatile, desc) should pass +ok 973 - function_volatility(schema, func, volatile, desc) should have the proper description +ok 974 - function_volatility(schema, func, volatile, desc) should have the proper diagnostics +ok 975 - function_volatility(schema, func, volatile) should pass +ok 976 - function_volatility(schema, func, volatile) should have the proper description +ok 977 - function_volatility(schema, func, volatile) should have the proper diagnostics +ok 978 - function_volatility(schema, func, immutable, desc) should pass +ok 979 - function_volatility(schema, func, immutable, desc) should have the proper description +ok 980 - function_volatility(schema, func, immutable, desc) should have the proper diagnostics +ok 981 - function_volatility(schema, func, stable, desc) should pass +ok 982 - function_volatility(schema, func, stable, desc) should have the proper description +ok 983 - function_volatility(schema, func, stable, desc) should have the proper diagnostics +ok 984 - function_volatility(func, 0 args, volatile, desc) should pass +ok 985 - function_volatility(func, 0 args, volatile, desc) should have the proper description +ok 986 - function_volatility(func, 0 args, volatile, desc) should have the proper diagnostics +ok 987 - function_volatility(func, 0 args, v, desc) should pass +ok 988 - function_volatility(func, 0 args, v, desc) should have the proper description +ok 989 - function_volatility(func, 0 args, v, desc) should have the proper diagnostics +ok 990 - function_volatility(func, args, immutable, desc) should pass +ok 991 - function_volatility(func, args, immutable, desc) should have the proper description +ok 992 - function_volatility(func, args, immutable, desc) should have the proper diagnostics +ok 993 - function_volatility(func, 0 args, stable, desc) should pass +ok 994 - function_volatility(func, 0 args, stable, desc) should have the proper description +ok 995 - function_volatility(func, 0 args, stable, desc) should have the proper diagnostics +ok 996 - function_volatility(func, 0 args, volatile) should pass +ok 997 - function_volatility(func, 0 args, volatile) should have the proper description +ok 998 - function_volatility(func, 0 args, volatile) should have the proper diagnostics +ok 999 - function_volatility(func, args, immutable) should pass +ok 1000 - function_volatility(func, args, immutable) should have the proper description +ok 1001 - function_volatility(func, volatile, desc) should pass +ok 1002 - function_volatility(func, volatile, desc) should have the proper description +ok 1003 - function_volatility(func, volatile, desc) should have the proper diagnostics +ok 1004 - function_volatility(func, volatile) should pass +ok 1005 - function_volatility(func, volatile) should have the proper description +ok 1006 - function_volatility(func, volatile) should have the proper diagnostics +ok 1007 - function_volatility(func, immutable, desc) should pass +ok 1008 - function_volatility(func, immutable, desc) should have the proper description +ok 1009 - function_volatility(func, immutable, desc) should have the proper diagnostics +ok 1010 - function_volatility(func, stable, desc) should pass +ok 1011 - function_volatility(func, stable, desc) should have the proper description +ok 1012 - function_volatility(func, stable, desc) should have the proper diagnostics diff --git a/test/expected/proctap.out b/test/expected/proctap.out new file mode 100644 index 000000000000..bf30b540535d --- /dev/null +++ b/test/expected/proctap.out @@ -0,0 +1,194 @@ +\unset ECHO +1..192 +ok 1 - is_procedure(schema, proc, noargs, desc) should pass +ok 2 - is_procedure(schema, proc, noargs, desc) should have the proper description +ok 3 - is_procedure(schema, proc, noargs, desc) should have the proper diagnostics +ok 4 - isnt_procedure(schema, proc, noargs, desc) should fail +ok 5 - isnt_procedure(schema, proc, noargs, desc) should have the proper description +ok 6 - isnt_procedure(schema, proc, noargs, desc) should have the proper diagnostics +ok 7 - is_procedure(schema, func, noargs, desc) should fail +ok 8 - is_procedure(schema, func, noargs, desc) should have the proper description +ok 9 - is_procedure(schema, func, noargs, desc) should have the proper diagnostics +ok 10 - isnt_procedure(schema, func, noargs, desc) should pass +ok 11 - isnt_procedure(schema, func, noargs, desc) should have the proper description +ok 12 - isnt_procedure(schema, func, noargs, desc) should have the proper diagnostics +ok 13 - is_procedure(schema, proc, args, desc) should pass +ok 14 - is_procedure(schema, proc, args, desc) should have the proper description +ok 15 - is_procedure(schema, proc, args, desc) should have the proper diagnostics +ok 16 - isnt_procedure(schema, proc, args, desc) should fail +ok 17 - isnt_procedure(schema, proc, args, desc) should have the proper description +ok 18 - isnt_procedure(schema, proc, args, desc) should have the proper diagnostics +ok 19 - is_procedure(schema, func, args, desc) should fail +ok 20 - is_procedure(schema, func, args, desc) should have the proper description +ok 21 - is_procedure(schema, func, args, desc) should have the proper diagnostics +ok 22 - isnt_procedure(schema, func, args, desc) should pass +ok 23 - isnt_procedure(schema, func, args, desc) should have the proper description +ok 24 - isnt_procedure(schema, func, args, desc) should have the proper diagnostics +ok 25 - is_procedure(schema, noproc, noargs, desc) should fail +ok 26 - is_procedure(schema, noproc, noargs, desc) should have the proper description +ok 27 - is_procedure(schema, noproc, noargs, desc) should have the proper diagnostics +ok 28 - isnt_procedure(schema, noproc, noargs, desc) should fail +ok 29 - isnt_procedure(schema, noproc, noargs, desc) should have the proper description +ok 30 - isnt_procedure(schema, noproc, noargs, desc) should have the proper diagnostics +ok 31 - is_procedure(schema, proc, noargs) should pass +ok 32 - is_procedure(schema, proc, noargs) should have the proper description +ok 33 - is_procedure(schema, proc, noargs) should have the proper diagnostics +ok 34 - isnt_procedure(schema, proc, noargs) should fail +ok 35 - isnt_procedure(schema, proc, noargs) should have the proper description +ok 36 - isnt_procedure(schema, proc, noargs) should have the proper diagnostics +ok 37 - is_procedure(schema, func, noargs) should fail +ok 38 - is_procedure(schema, func, noargs) should have the proper description +ok 39 - is_procedure(schema, func, noargs) should have the proper diagnostics +ok 40 - isnt_procedure(schema, func, noargs) should pass +ok 41 - isnt_procedure(schema, func, noargs) should have the proper description +ok 42 - isnt_procedure(schema, func, noargs) should have the proper diagnostics +ok 43 - is_procedure(schema, proc, args) should pass +ok 44 - is_procedure(schema, proc, args) should have the proper description +ok 45 - is_procedure(schema, proc, args) should have the proper diagnostics +ok 46 - isnt_procedure(schema, proc, args) should fail +ok 47 - isnt_procedure(schema, proc, args) should have the proper description +ok 48 - isnt_procedure(schema, proc, args) should have the proper diagnostics +ok 49 - is_procedure(schema, func, args) should fail +ok 50 - is_procedure(schema, func, args) should have the proper description +ok 51 - is_procedure(schema, func, args) should have the proper diagnostics +ok 52 - isnt_procedure(schema, func, args) should pass +ok 53 - isnt_procedure(schema, func, args) should have the proper description +ok 54 - isnt_procedure(schema, func, args) should have the proper diagnostics +ok 55 - is_procedure(schema, noproc, noargs) should fail +ok 56 - is_procedure(schema, noproc, noargs) should have the proper description +ok 57 - is_procedure(schema, noproc, noargs) should have the proper diagnostics +ok 58 - isnt_procedure(schema, noproc, noargs) should fail +ok 59 - isnt_procedure(schema, noproc, noargs) should have the proper description +ok 60 - isnt_procedure(schema, noproc, noargs) should have the proper diagnostics +ok 61 - is_procedure(schema, proc, desc) should pass +ok 62 - is_procedure(schema, proc, desc) should have the proper description +ok 63 - is_procedure(schema, proc, desc) should have the proper diagnostics +ok 64 - isnt_procedure(schema, proc, desc) should fail +ok 65 - isnt_procedure(schema, proc, desc) should have the proper description +ok 66 - isnt_procedure(schema, proc, desc) should have the proper diagnostics +ok 67 - is_procedure(schema, func, desc) should fail +ok 68 - is_procedure(schema, func, desc) should have the proper description +ok 69 - is_procedure(schema, func, desc) should have the proper diagnostics +ok 70 - isnt_procedure(schema, func, desc) should pass +ok 71 - isnt_procedure(schema, func, desc) should have the proper description +ok 72 - isnt_procedure(schema, func, desc) should have the proper diagnostics +ok 73 - is_procedure(schema, noproc, desc) should fail +ok 74 - is_procedure(schema, noproc, desc) should have the proper description +ok 75 - is_procedure(schema, noproc, desc) should have the proper diagnostics +ok 76 - isnt_procedure(schema, noproc, desc) should fail +ok 77 - isnt_procedure(schema, noproc, desc) should have the proper description +ok 78 - isnt_procedure(schema, noproc, desc) should have the proper diagnostics +ok 79 - is_procedure(schema, proc) should pass +ok 80 - is_procedure(schema, proc) should have the proper description +ok 81 - is_procedure(schema, proc) should have the proper diagnostics +ok 82 - isnt_procedure(schema, proc) should fail +ok 83 - isnt_procedure(schema, proc) should have the proper description +ok 84 - isnt_procedure(schema, proc) should have the proper diagnostics +ok 85 - is_procedure(schema, func) should fail +ok 86 - is_procedure(schema, func) should have the proper description +ok 87 - is_procedure(schema, func) should have the proper diagnostics +ok 88 - isnt_procedure(schema, func) should pass +ok 89 - isnt_procedure(schema, func) should have the proper description +ok 90 - isnt_procedure(schema, func) should have the proper diagnostics +ok 91 - is_procedure(schema, noproc) should fail +ok 92 - is_procedure(schema, noproc) should have the proper description +ok 93 - is_procedure(schema, noproc) should have the proper diagnostics +ok 94 - isnt_procedure(schema, noproc) should fail +ok 95 - isnt_procedure(schema, noproc) should have the proper description +ok 96 - isnt_procedure(schema, noproc) should have the proper diagnostics +ok 97 - is_procedure(proc, noargs, desc) should pass +ok 98 - is_procedure(proc, noargs, desc) should have the proper description +ok 99 - is_procedure(proc, noargs, desc) should have the proper diagnostics +ok 100 - isnt_procedure(proc, noargs, desc) should fail +ok 101 - isnt_procedure(proc, noargs, desc) should have the proper description +ok 102 - isnt_procedure(proc, noargs, desc) should have the proper diagnostics +ok 103 - is_procedure(func, noargs, desc) should fail +ok 104 - is_procedure(func, noargs, desc) should have the proper description +ok 105 - is_procedure(func, noargs, desc) should have the proper diagnostics +ok 106 - isnt_procedure(schema, func, noargs, desc) should pass +ok 107 - isnt_procedure(schema, func, noargs, desc) should have the proper description +ok 108 - isnt_procedure(schema, func, noargs, desc) should have the proper diagnostics +ok 109 - is_procedure(proc, args, desc) should pass +ok 110 - is_procedure(proc, args, desc) should have the proper description +ok 111 - is_procedure(proc, args, desc) should have the proper diagnostics +ok 112 - isnt_procedure(proc, args, desc) should fail +ok 113 - isnt_procedure(proc, args, desc) should have the proper description +ok 114 - isnt_procedure(proc, args, desc) should have the proper diagnostics +ok 115 - is_procedure(unc, args, desc) should fail +ok 116 - is_procedure(unc, args, desc) should have the proper description +ok 117 - is_procedure(unc, args, desc) should have the proper diagnostics +ok 118 - isnt_procedure(func, args, desc) should pass +ok 119 - isnt_procedure(func, args, desc) should have the proper description +ok 120 - isnt_procedure(func, args, desc) should have the proper diagnostics +ok 121 - is_procedure(noproc, noargs, desc) should fail +ok 122 - is_procedure(noproc, noargs, desc) should have the proper description +ok 123 - is_procedure(noproc, noargs, desc) should have the proper diagnostics +ok 124 - isnt_procedure(noproc, noargs, desc) should fail +ok 125 - isnt_procedure(noproc, noargs, desc) should have the proper description +ok 126 - isnt_procedure(noproc, noargs, desc) should have the proper diagnostics +ok 127 - is_procedure(proc, noargs) should pass +ok 128 - is_procedure(proc, noargs) should have the proper description +ok 129 - is_procedure(proc, noargs) should have the proper diagnostics +ok 130 - isnt_procedure(proc, noargs) should fail +ok 131 - isnt_procedure(proc, noargs) should have the proper description +ok 132 - isnt_procedure(proc, noargs) should have the proper diagnostics +ok 133 - is_procedure(func, noargs) should fail +ok 134 - is_procedure(func, noargs) should have the proper description +ok 135 - is_procedure(func, noargs) should have the proper diagnostics +ok 136 - isnt_procedure(schema, func, noargs) should pass +ok 137 - isnt_procedure(schema, func, noargs) should have the proper description +ok 138 - isnt_procedure(schema, func, noargs) should have the proper diagnostics +ok 139 - is_procedure(proc, args) should pass +ok 140 - is_procedure(proc, args) should have the proper description +ok 141 - is_procedure(proc, args) should have the proper diagnostics +ok 142 - isnt_procedure(proc, args) should fail +ok 143 - isnt_procedure(proc, args) should have the proper description +ok 144 - isnt_procedure(proc, args) should have the proper diagnostics +ok 145 - is_procedure(unc, args) should fail +ok 146 - is_procedure(unc, args) should have the proper description +ok 147 - is_procedure(unc, args) should have the proper diagnostics +ok 148 - isnt_procedure(func, args) should pass +ok 149 - isnt_procedure(func, args) should have the proper description +ok 150 - isnt_procedure(func, args) should have the proper diagnostics +ok 151 - is_procedure(noproc, noargs) should fail +ok 152 - is_procedure(noproc, noargs) should have the proper description +ok 153 - is_procedure(noproc, noargs) should have the proper diagnostics +ok 154 - isnt_procedure(noproc, noargs) should fail +ok 155 - isnt_procedure(noproc, noargs) should have the proper description +ok 156 - isnt_procedure(noproc, noargs) should have the proper diagnostics +ok 157 - is_procedure(proc, desc) should pass +ok 158 - is_procedure(proc, desc) should have the proper description +ok 159 - is_procedure(proc, desc) should have the proper diagnostics +ok 160 - isnt_procedure(proc, desc) should fail +ok 161 - isnt_procedure(proc, desc) should have the proper description +ok 162 - isnt_procedure(proc, desc) should have the proper diagnostics +ok 163 - is_procedure(func, desc) should fail +ok 164 - is_procedure(func, desc) should have the proper description +ok 165 - is_procedure(func, desc) should have the proper diagnostics +ok 166 - isnt_procedure(func, desc) should pass +ok 167 - isnt_procedure(func, desc) should have the proper description +ok 168 - isnt_procedure(func, desc) should have the proper diagnostics +ok 169 - is_procedure(noproc, desc) should fail +ok 170 - is_procedure(noproc, desc) should have the proper description +ok 171 - is_procedure(noproc, desc) should have the proper diagnostics +ok 172 - isnt_procedure(noproc, desc) should fail +ok 173 - isnt_procedure(noproc, desc) should have the proper description +ok 174 - isnt_procedure(noproc, desc) should have the proper diagnostics +ok 175 - is_procedure(proc) should pass +ok 176 - is_procedure(proc) should have the proper description +ok 177 - is_procedure(proc) should have the proper diagnostics +ok 178 - isnt_procedure(proc) should fail +ok 179 - isnt_procedure(proc) should have the proper description +ok 180 - isnt_procedure(proc) should have the proper diagnostics +ok 181 - is_procedure(func) should fail +ok 182 - is_procedure(func) should have the proper description +ok 183 - is_procedure(func) should have the proper diagnostics +ok 184 - isnt_procedure(func) should pass +ok 185 - isnt_procedure(func) should have the proper description +ok 186 - isnt_procedure(func) should have the proper diagnostics +ok 187 - is_procedure(noproc) should fail +ok 188 - is_procedure(noproc) should have the proper description +ok 189 - is_procedure(noproc) should have the proper diagnostics +ok 190 - isnt_procedure(noproc) should fail +ok 191 - isnt_procedure(noproc) should have the proper description +ok 192 - isnt_procedure(noproc) should have the proper diagnostics diff --git a/test/sql/functap.sql b/test/sql/functap.sql index b62e4ac21ad9..f02475ffe0ac 100644 --- a/test/sql/functap.sql +++ b/test/sql/functap.sql @@ -1,8 +1,8 @@ \unset ECHO \i test/setup.sql -SELECT plan(640); ---SELECT * FROM no_plan(); +SELECT plan(1012); +-- SELECT * FROM no_plan(); CREATE SCHEMA someschema; CREATE FUNCTION someschema.huh () RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL; @@ -1040,394 +1040,1466 @@ SELECT * FROM check_test( '' ); +/****************************************************************************/ +-- Test is_normal_function() and isnt_normal_function(). + +-- is_normal_function ( NAME, NAME, NAME[], TEXT ) +-- isnt_normal_function ( NAME, NAME, NAME[], TEXT ) +SELECT * FROM check_test( + is_normal_function( 'someschema', 'huh', '{}', 'whatever' ), + true, + 'is_normal_function(schema, func, noargs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_normal_function( 'public', 'tap_accum', ARRAY[etype()], 'whatever' ), + false, + 'is_normal_function(schema, agg, arg, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'someschema', 'huh', '{}', 'whatever' ), + false, + 'isnt_normal_function(schema, func, noargs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'public', 'tap_accum', ARRAY[etype()], 'whatever' ), + true, + 'isnt_normal_function(schema, agg, noargs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_normal_function( 'someschema', 'bah', ARRAY['integer', 'text'], 'whatever' ), + true, + 'is_normal_function(schema, func, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_normal_function( 'public', 'tap_accum', ARRAY[etype()], 'whatever' ), + false, + 'is_normal_function(schema, agg, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'someschema', 'bah', ARRAY['integer', 'text'], 'whatever' ), + false, + 'is_normal_function(schema, func, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'public', 'tap_accum', ARRAY[etype()], 'whatever' ), + true, + 'isnt_normal_function(schema, agg, args, desc)', + 'whatever', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_normal_function( 'someschema', 'nonesuch', '{}', 'whatever' ), + false, + 'is_normal_function(schema, nofunc, noargs, desc)', + 'whatever', + ' Function someschema.nonesuch() does not exist' +); + +SELECT * FROM check_test( + isnt_normal_function( 'someschema', 'nonesuch', ARRAY[etype()], 'whatever' ), + false, + 'isnt_normal_function(schema, noagg, args, desc)', + 'whatever', + ' Function someschema.nonesuch(' || etype() || ') does not exist' +); + +-- is_normal_function( NAME, NAME, NAME[] ) +-- isnt_normal_function( NAME, NAME, NAME[] ) +SELECT * FROM check_test( + is_normal_function( 'someschema', 'huh', '{}'::name[] ), + true, + 'is_normal_function(schema, func, noargs)', + 'Function someschema.huh() should be a normal function', + '' +); + +SELECT * FROM check_test( + is_normal_function( 'public', 'tap_accum', ARRAY[etype()] ), + false, + 'is_normal_function(schema, agg, noargs)', + 'Function public.tap_accum(' || etype() || ') should be a normal function', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'someschema', 'huh', '{}'::name[] ), + false, + 'isnt_normal_function(schema, func, noargs)', + 'Function someschema.huh() should not be a normal function', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'public', 'tap_accum', ARRAY[etype()] ), + true, + 'isnt_normal_function(schema, agg, noargs)', + 'Function public.tap_accum(' || etype() || ') should not be a normal function', + '' +); + +SELECT * FROM check_test( + is_normal_function( 'someschema', 'bah', ARRAY['integer', 'text'] ), + true, + 'is_normal_function(schema, func2, args)', + 'Function someschema.bah(integer, text) should be a normal function', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'someschema', 'bah', ARRAY['integer', 'text'] ), + false, + 'isnt_normal_function(schema, func2, args)', + 'Function someschema.bah(integer, text) should not be a normal function', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_normal_function( 'someschema', 'nonesuch', '{}'::name[] ), + false, + 'is_normal_function(schema, func, noargs)', + 'Function someschema.nonesuch() should be a normal function', + ' Function someschema.nonesuch() does not exist' +); + +SELECT * FROM check_test( + is_normal_function( 'public', 'nonesuch', ARRAY[etype()] ), + false, + 'is_normal_function(schema, nofunc, noargs)', + 'Function public.nonesuch(' || etype() || ') should be a normal function', + ' Function public.nonesuch(' || etype() || ') does not exist' + '' +); + +-- is_normal_function ( NAME, NAME, TEXT ) +-- isnt_normal_function ( NAME, NAME, TEXT ) +SELECT * FROM check_test( + is_normal_function( 'someschema', 'huh', 'whatever' ), + true, + 'is_normal_function(schema, func, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_normal_function( 'public', 'tap_accum', 'whatever' ), + false, + 'is_normal_function(schema, agg, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'someschema', 'huh', 'whatever' ), + false, + 'isnt_normal_function(schema, func, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'public', 'tap_accum', 'whatever' ), + true, + 'isnt_normal_function(schema, agg, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_normal_function( 'someschema', 'bah', 'whatever' ), + true, + 'is_normal_function(schema, func2, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'someschema', 'bah', 'whatever' ), + false, + 'isnt_normal_function(schema, func2, desc)', + 'whatever', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_normal_function( 'someschema', 'nonesuch', 'whatever' ), + false, + 'is_normal_function(schema, nofunc, desc)', + 'whatever', + ' Function someschema.nonesuch() does not exist' +); + +SELECT * FROM check_test( + is_normal_function( 'public', 'nonesuch', 'whatever' ), + false, + 'is_normal_function(schema, noagg, desc)', + 'whatever', + ' Function public.nonesuch() does not exist' +); + +-- is_normal_function( NAME, NAME ) +-- isnt_normal_function( NAME, NAME ) +SELECT * FROM check_test( + is_normal_function( 'someschema', 'huh'::name ), + true, + 'is_normal_function(schema, func)', + 'Function someschema.huh() should be a normal function', + '' +); + +SELECT * FROM check_test( + is_normal_function( 'public', 'tap_accum'::name ), + false, + 'is_normal_function(schema, agg)', + 'Function public.tap_accum() should be a normal function', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'someschema', 'huh'::name ), + false, + 'isnt_normal_function(schema, func)', + 'Function someschema.huh() should not be a normal function', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'public', 'tap_accum'::name ), + true, + 'isnt_normal_function(schema, agg)', + 'Function public.tap_accum() should not be a normal function', + '' +); + +SELECT * FROM check_test( + is_normal_function( 'someschema', 'bah'::name ), + true, + 'is_normal_function(schema, func2, args)', + 'Function someschema.bah() should be a normal function', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'someschema', 'bah'::name ), + false, + 'isnt_normal_function(schema, func2)', + 'Function someschema.bah() should not be a normal function', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_normal_function( 'someschema', 'nonesuch'::name ), + false, + 'is_normal_function(schema, nofunc)', + 'Function someschema.nonesuch() should be a normal function', + ' Function someschema.nonesuch() does not exist' +); + +SELECT * FROM check_test( + is_normal_function( 'public', 'nonesuch'::name ), + false, + 'is_normal_function(schema, nogg)', + 'Function public.nonesuch() should be a normal function', + ' Function public.nonesuch() does not exist' +); + +-- is_normal_function ( NAME, NAME[], TEXT ) +-- isnt_normal_function ( NAME, NAME[], TEXT ) +SELECT * FROM check_test( + is_normal_function( 'yay', '{}'::name[], 'whatever' ), + true, + 'is_normal_function(func, noargs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_normal_function( 'tap_accum', ARRAY[etype()], 'whatever' ), + false, + 'is_normal_function(func, agg, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'yay', '{}'::name[], 'whatever' ), + false, + 'isnt_normal_function(func, noargs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'tap_accum', ARRAY[etype()], 'whatever' ), + true, + 'isnt_normal_function(func, agg, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_normal_function( 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'is_normal_function(func, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'oww', ARRAY['integer', 'text'], 'whatever' ), + false, + 'isnt_normal_function(func, args, desc)', + 'whatever', + '' +); + +-- test diagnostics +SELECT * FROM check_test( + is_normal_function( 'nonesuch', '{}'::name[], 'whatever' ), + false, + 'is_normal_function(nofunc, noargs, desc)', + 'whatever', + ' Function nonesuch() does not exist' +); + +SELECT * FROM check_test( + is_normal_function( 'nonesuch', ARRAY[etype()], 'whatever' ), + false, + 'is_normal_function(func, noagg, desc)', + 'whatever', + ' Function nonesuch(' || etype() || ') does not exist' +); + +-- is_normal_function( NAME, NAME[] ) +-- isnt_normal_function( NAME, NAME[] ) +SELECT * FROM check_test( + is_normal_function( 'yay', '{}'::name[] ), + true, + 'is_normal_function(func, noargs)', + 'Function yay() should be a normal function', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'yay', '{}'::name[] ), + false, + 'isnt_normal_function(func, noargs)', + 'Function yay() should not be a normal function', + '' +); + +SELECT * FROM check_test( + is_normal_function( 'oww', ARRAY['integer', 'text'] ), + true, + 'is_normal_function(func, noargs)', + 'Function oww(integer, text) should be a normal function', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'oww', ARRAY['integer', 'text'] ), + false, + 'isnt_normal_function(func, noargs)', + 'Function oww(integer, text) should not be a normal function', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_normal_function( 'nope', '{}'::name[] ), + false, + 'is_normal_function(nofunc, noargs)', + 'Function nope() should be a normal function', + ' Function nope() does not exist' +); + +SELECT * FROM check_test( + isnt_normal_function( 'nope', '{}'::name[] ), + false, + 'isnt_normal_function(fnounc, noargs)', + 'Function nope() should not be a normal function', + ' Function nope() does not exist' +); + +-- is_normal_function( NAME, TEXT ) +-- isnt_normal_function( NAME, TEXT ) +SELECT * FROM check_test( + is_normal_function( 'yay', 'whatever' ), + true, + 'is_normal_function(func, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_normal_function( 'tap_accum', 'whatever' ), + false, + 'is_normal_function(agg, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'yay', 'howdy' ), + false, + 'isnt_normal_function(func, desc)', + 'howdy', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'tap_accum', 'whatever' ), + true, + 'isnt_normal_function(agg, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_normal_function( 'oww', 'coyote' ), + true, + 'is_normal_function(func2, desc)', + 'coyote', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'oww', 'hi there' ), + false, + 'isnt_normal_function(func2, desc)', + 'hi there', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_normal_function( 'none', 'whatever' ), + false, + 'is_normal_function(nofunc, desc)', + 'whatever', + ' Function "none"() does not exist' +); + +SELECT * FROM check_test( + is_normal_function( 'none', 'whatever' ), + false, + 'is_normal_function(noagg, desc)', + 'whatever', + ' Function "none"() does not exist' +); + +-- is_normal_function( NAME ) +-- isnt_normal_function( NAME ) +SELECT * FROM check_test( + is_normal_function( 'yay' ), + true, + 'is_normal_function(func)', + 'Function yay() should be a normal function', + '' +); + +SELECT * FROM check_test( + is_normal_function( 'tap_accum' ), + false, + 'is_normal_function(agg)', + 'Function tap_accum() should be a normal function', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'yay' ), + false, + 'isnt_normal_function(func)', + 'Function yay() should not be a normal function', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'tap_accum' ), + true, + 'isnt_normal_function(agg)', + 'Function tap_accum() should not be a normal function', + '' +); + +SELECT * FROM check_test( + is_normal_function( 'oww' ), + true, + 'is_normal_function(func2)', + 'Function oww() should be a normal function', + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'oww' ), + false, + 'isnt_normal_function(func2,)', + 'Function oww() should not be a normal function', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_normal_function( 'zippo' ), + false, + 'is_normal_function(nofunc)', + 'Function zippo() should be a normal function', + ' Function zippo() does not exist' +); + +SELECT * FROM check_test( + is_normal_function( 'zippo' ), + false, + 'is_normal_function(noagg)', + 'Function zippo() should be a normal function', + ' Function zippo() does not exist' +); + /****************************************************************************/ -- Test is_aggregate() and isnt_aggregate(). + +-- is_aggregate ( NAME, NAME, NAME[], TEXT ) +-- isnt_aggregate ( NAME, NAME, NAME[], TEXT ) +SELECT * FROM check_test( + is_aggregate( 'public', 'tap_accum', ARRAY[etype()], 'whatever' ), + true, + 'is_aggregate(schema, func, arg, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum', ARRAY[etype()], 'whatever' ), + false, + 'isnt_aggregate(schema, agg, arg, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + false, + 'is_aggregate(schema, func, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_aggregate(schema, func, args, desc)', + 'whatever', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_aggregate( 'public', 'nope', ARRAY[etype()], 'whatever' ), + false, + 'is_aggregate(schema, nofunc, arg, desc)', + 'whatever', + ' Function public.nope(' || etype() || ') does not exist' +); + +SELECT * FROM check_test( + isnt_aggregate( 'public', 'nope', ARRAY[etype()], 'whatever' ), + false, + 'isnt_aggregate(schema, noagg, arg, desc)', + 'whatever', + ' Function public.nope(' || etype() || ') does not exist' +); + +-- is_aggregate( NAME, NAME, NAME[] ) +-- isnt_aggregate( NAME, NAME, NAME[] ) +SELECT * FROM check_test( + is_aggregate( 'public', 'tap_accum', ARRAY[etype()] ), + true, + 'is_aggregate(schema, agg, arg)', + 'Function public.tap_accum(' || etype() || ') should be an aggregate function', + '' +); + +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum', ARRAY[etype()] ), + false, + 'isnt_aggregate(schema, agg, arg)', + 'Function public.tap_accum(' || etype() || ') should not be an aggregate function', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), + false, + 'is_aggregate(schema, func, args)', + 'Function public.oww(integer, text) should be an aggregate function', + '' +); + +SELECT * FROM check_test( + isnt_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), + true, + 'isnt_aggregate(schema, func, args)', + 'Function public.oww(integer, text) should not be an aggregate function', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_aggregate( 'public', 'uhuh', ARRAY[etype()] ), + false, + 'is_aggregate(schema, noagg, arg)', + 'Function public.uhuh(' || etype() || ') should be an aggregate function', + ' Function public.uhuh(' || etype() || ') does not exist' +); + +SELECT * FROM check_test( + isnt_aggregate( 'public', 'uhuh', ARRAY[etype()] ), + false, + 'isnt_aggregate(schema, noagg, arg)', + 'Function public.uhuh(' || etype() || ') should not be an aggregate function', + ' Function public.uhuh(' || etype() || ') does not exist' +); + +-- is_aggregate ( NAME, NAME, TEXT ) +-- isnt_aggregate ( NAME, NAME, TEXT ) +SELECT * FROM check_test( + is_aggregate( 'public', 'tap_accum', 'whatever' ), + true, + 'is_aggregate(schema, agg, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum', 'whatever' ), + false, + 'isnt_aggregate(schema, agg, desc)', + 'whatever', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_aggregate( 'public', 'nada', 'whatever' ), + false, + 'is_aggregate(schema, noagg, desc)', + 'whatever', + ' Function public.nada() does not exist' +); + +SELECT * FROM check_test( + isnt_aggregate( 'public', 'nada', 'whatever' ), + false, + 'isnt_aggregate(schema, noagg, desc)', + 'whatever', + ' Function public.nada() does not exist' +); + +-- is_aggregate( NAME, NAME ) +-- isnt_aggregate( NAME, NAME ) +SELECT * FROM check_test( + is_aggregate( 'public', 'tap_accum'::name ), + true, + 'is_aggregate(schema, agg)', + 'Function public.tap_accum() should be an aggregate function', + '' +); + +SELECT * FROM check_test( + isnt_aggregate( 'public', 'tap_accum'::name ), + false, + 'isnt_aggregate(schema, agg)', + 'Function public.tap_accum() should not be an aggregate function', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_aggregate( 'public', 'nope'::name ), + false, + 'is_aggregate(schema, noagg)', + 'Function public.nope() should be an aggregate function', + ' Function public.nope() does not exist' +); + +SELECT * FROM check_test( + isnt_aggregate( 'public', 'nope'::name ), + false, + 'isnt_aggregate(schema, noagg)', + 'Function public.nope() should not be an aggregate function', + ' Function public.nope() does not exist' +); + +-- is_aggregate ( NAME, NAME[], TEXT ) +-- isnt_aggregate ( NAME, NAME[], TEXT ) +SELECT * FROM check_test( + is_aggregate( 'tap_accum', ARRAY[etype()], 'whatever' ), + true, + 'is_aggregate(agg, arg, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_aggregate( 'tap_accum', ARRAY[etype()], 'whatever' ), + false, + 'isnt_aggregate(agg, arg, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'oww', ARRAY['integer', 'text'], 'whatever' ), + false, + 'is_aggregate(func, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_aggregate( 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_aggregate(func, args, desc)', + 'whatever', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_aggregate( 'nonesuch', ARRAY[etype()], 'whatever' ), + false, + 'is_aggregate(noagg, arg, desc)', + 'whatever', + ' Function nonesuch(' || etype() || ') does not exist' +); + +SELECT * FROM check_test( + isnt_aggregate( 'nonesuch', ARRAY[etype()], 'whatever' ), + false, + 'isnt_aggregate(noagg, arg, desc)', + 'whatever', + ' Function nonesuch(' || etype() || ') does not exist' +); + +-- is_aggregate( NAME, NAME[] ) +-- isnt_aggregate( NAME, NAME[] ) +SELECT * FROM check_test( + is_aggregate( 'tap_accum', ARRAY[etype()] ), + true, + 'is_aggregate(agg, arg)', + 'Function tap_accum(' || etype() || ') should be an aggregate function', + '' +); + +SELECT * FROM check_test( + isnt_aggregate( 'tap_accum', ARRAY[etype()] ), + false, + 'isnt_aggregate(agg, arg)', + 'Function tap_accum(' || etype() || ') should not be an aggregate function', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'oww', ARRAY['integer', 'text'] ), + false, + 'is_aggregate(func, args)', + 'Function oww(integer, text) should be an aggregate function', + '' +); + +SELECT * FROM check_test( + isnt_aggregate( 'oww', ARRAY['integer', 'text'] ), + true, + 'isnt_aggregate(func, args)', + 'Function oww(integer, text) should not be an aggregate function', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_aggregate( '_zip', ARRAY[etype()] ), + false, + 'is_aggregate(noagg, arg)', + 'Function _zip(' || etype() || ') should be an aggregate function', + ' Function _zip(' || etype() || ') does not exist' +); + +SELECT * FROM check_test( + isnt_aggregate( '_zip', ARRAY[etype()] ), + false, + 'isnt_aggregate(noagg, arg)', + 'Function _zip(' || etype() || ') should not be an aggregate function', + ' Function _zip(' || etype() || ') does not exist' +); + +-- is_aggregate( NAME, TEXT ) +-- isnt_aggregate( NAME, TEXT ) +SELECT * FROM check_test( + is_aggregate( 'tap_accum', 'whatever' ), + true, + 'is_aggregate(func, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_aggregate( 'tap_accum', 'whatever' ), + false, + 'isnt_aggregate(agg, desc)', + 'whatever', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_aggregate( 'nope', 'whatever' ), + false, + 'is_aggregate(nofunc, desc)', + 'whatever', + ' Function nope() does not exist' +); + +SELECT * FROM check_test( + isnt_aggregate( 'nope', 'whatever' ), + false, + 'isnt_aggregate(noagg, desc)', + 'whatever', + ' Function nope() does not exist' +); + +-- is_aggregate( NAME ) +-- isnt_aggregate( NAME ) +SELECT * FROM check_test( + is_aggregate( 'tap_accum'::name ), + true, + 'is_aggregate(agg)', + 'Function tap_accum() should be an aggregate function', + '' +); + +SELECT * FROM check_test( + isnt_aggregate( 'tap_accum'::name ), + false, + 'isnt_aggregate(agg)', + 'Function tap_accum() should not be an aggregate function', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_aggregate( 'nope'::name ), + false, + 'is_aggregate(noagg)', + 'Function nope() should be an aggregate function', + ' Function nope() does not exist' +); + +SELECT * FROM check_test( + isnt_aggregate( 'nope'::name ), + false, + 'isnt_aggregate(noagg)', + 'Function nope() should not be an aggregate function', + ' Function nope() does not exist' +); + +/****************************************************************************/ +-- Test is_window() and isnt_window(). + +-- is_window ( NAME, NAME, NAME[], TEXT ) +-- isnt_window ( NAME, NAME, NAME[], TEXT ) +SELECT * FROM check_test( + is_window( 'pg_catalog', 'ntile', ARRAY['integer'], 'whatever' ), + true, + 'is_window(schema, win, arg, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_window( 'pg_catalog', 'ntile', ARRAY['integer'], 'whatever' ), + false, + 'isnt_window(schema, win, arg, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_window( 'someschema', 'bah', ARRAY['integer', 'text'], 'whatever' ), + false, + 'is_window(schema, func, arg, desc)', + 'whatever', + '' +); + SELECT * FROM check_test( - is_aggregate( 'public', 'tap_accum', ARRAY[etype()], 'whatever' ), + isnt_window( 'someschema', 'bah', ARRAY['integer', 'text'], 'whatever' ), true, - 'is_aggregate(schema, func, arg, desc)', + 'isnt_window(schema, func, arg, desc)', 'whatever', '' ); SELECT * FROM check_test( - isnt_aggregate( 'public', 'tap_accum', ARRAY[etype()], 'whatever' ), - false, - 'isnt_aggregate(schema, func, arg, desc)', + is_window( 'pg_catalog', 'dense_rank', '{}'::name[], 'whatever' ), + true, + 'is_window(schema, win, noargs, desc)', 'whatever', '' ); SELECT * FROM check_test( - is_aggregate( 'public', 'tap_accum', ARRAY[etype()] ), - true, - 'is_aggregate(schema, func, arg)', - 'Function public.tap_accum(' || etype() || ') should be an aggregate function', + isnt_window( 'pg_catalog', 'dense_rank', '{}'::name[], 'whatever' ), + false, + 'isnt_window(schema, win, noargs, desc)', + 'whatever', '' ); SELECT * FROM check_test( - isnt_aggregate( 'public', 'tap_accum', ARRAY[etype()] ), + is_window( 'someschema', 'huh', '{}'::name[], 'whatever' ), false, - 'isnt_aggregate(schema, func, arg)', - 'Function public.tap_accum(' || etype() || ') should not be an aggregate function', + 'is_window(schema, func, noarg, desc)', + 'whatever', '' ); SELECT * FROM check_test( - is_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + isnt_window( 'pg_catalog', 'dense_rank', '{}'::name[], 'whatever' ), false, - 'is_aggregate(schema, func, args, desc)', + 'is_window(schema, win, noargs, desc)', 'whatever', '' ); +-- Test diagnostics SELECT * FROM check_test( - isnt_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), - true, - 'isnt_aggregate(schema, func, args, desc)', + is_window( 'someschema', 'nope', ARRAY['integer'], 'whatever' ), + false, + 'is_window(schema, nowin, arg, desc)', + 'whatever', + ' Function someschema.nope(integer) does not exist' +); + +SELECT * FROM check_test( + isnt_window( 'someschema', 'nope', ARRAY['integer'], 'whatever' ), + false, + 'isnt_window(schema, nowin, arg, desc)', 'whatever', + ' Function someschema.nope(integer) does not exist' +); + +-- is_window ( NAME, NAME, NAME[] ) +-- isnt_window ( NAME, NAME, NAME[] ) +SELECT * FROM check_test( + is_window( 'pg_catalog', 'ntile', ARRAY['integer'] ), + true, + 'is_window(schema, win, arg)', + 'Function pg_catalog.ntile(integer) should be a window function', '' ); SELECT * FROM check_test( - is_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), + isnt_window( 'pg_catalog', 'ntile', ARRAY['integer'] ), false, - 'is_aggregate(schema, func, args)', - 'Function public.oww(integer, text) should be an aggregate function', + 'isnt_window(schema, win, arg)', + 'Function pg_catalog.ntile(integer) should not be a window function', '' ); SELECT * FROM check_test( - isnt_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), + is_window( 'someschema', 'bah', ARRAY['integer', 'text'] ), + false, + 'is_window(schema, func, arg)', + 'Function someschema.bah(integer, text) should be a window function', + '' +); + +SELECT * FROM check_test( + isnt_window( 'someschema', 'bah', ARRAY['integer', 'text'] ), true, - 'isnt_aggregate(schema, func, args)', - 'Function public.oww(integer, text) should not be an aggregate function', + 'isnt_window(schema, func, arg)', + 'Function someschema.bah(integer, text) should not be a window function', '' ); SELECT * FROM check_test( - is_aggregate( 'public', 'tap_accum', 'whatever' ), + is_window( 'pg_catalog', 'dense_rank', '{}'::name[] ), true, - 'is_aggregate(schema, func, desc)', - 'whatever', + 'is_window(schema, win, noargs)', + 'Function pg_catalog.dense_rank() should be a window function', '' ); SELECT * FROM check_test( - isnt_aggregate( 'public', 'tap_accum', 'whatever' ), + isnt_window( 'pg_catalog', 'dense_rank', '{}'::name[] ), false, - 'isnt_aggregate(schema, func, desc)', - 'whatever', + 'isnt_window(schema, win, noargs)', + 'Function pg_catalog.dense_rank() should not be a window function', '' ); SELECT * FROM check_test( - is_aggregate( 'public', 'tap_accum'::name ), - true, - 'is_aggregate(schema, func)', - 'Function public.tap_accum() should be an aggregate function', + is_window( 'someschema', 'huh', '{}'::name[] ), + false, + 'is_window(schema, func, noarg)', + 'Function someschema.huh() should be a window function', '' ); SELECT * FROM check_test( - isnt_aggregate( 'public', 'tap_accum'::name ), + isnt_window( 'pg_catalog', 'dense_rank', '{}'::name[] ), false, - 'isnt_aggregate(schema, func)', - 'Function public.tap_accum() should not be an aggregate function', + 'isnt_window(schema, win, noargs)', + 'Function pg_catalog.dense_rank() should not be a window function', '' ); +-- Test diagnostics SELECT * FROM check_test( - is_aggregate( 'public', 'tap_accum', ARRAY[etype()], 'whatever' ), + is_window( 'someschema', 'nada', ARRAY['integer'] ), + false, + 'is_window(schema, nowin, arg)', + 'Function someschema.nada(integer) should be a window function', + ' Function someschema.nada(integer) does not exist' +); + +SELECT * FROM check_test( + isnt_window( 'someschema', 'nada', ARRAY['integer'] ), + false, + 'isnt_window(schema, nowin, arg)', + 'Function someschema.nada(integer) should not be a window function', + ' Function someschema.nada(integer) does not exist' +); + +-- is_window ( NAME, NAME, TEXT ) +-- isnt_window ( NAME, NAME, TEXT ) +SELECT * FROM check_test( + is_window( 'pg_catalog', 'ntile'::name, 'whatever' ), true, - 'is_aggregate(schema, func, arg, desc)', + 'is_window(schema, win, desc)', 'whatever', '' ); SELECT * FROM check_test( - isnt_aggregate( 'public', 'tap_accum', ARRAY[etype()], 'whatever' ), + isnt_window( 'pg_catalog', 'ntile'::name, 'whatever' ), false, - 'isnt_aggregate(schema, func, arg, desc)', + 'isnt_window(schema, win, desc)', 'whatever', '' ); SELECT * FROM check_test( - is_aggregate( 'public', 'tap_accum', ARRAY[etype()] ), + is_window( 'someschema', 'bah'::name, 'whatever' ), + false, + 'is_window(schema, func, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_window( 'someschema', 'bah'::name, 'whatever' ), true, - 'is_aggregate(schema, func, arg)', - 'Function public.tap_accum(' || etype() || ') should be an aggregate function', + 'isnt_window(schema, func, desc)', + 'whatever', '' ); SELECT * FROM check_test( - isnt_aggregate( 'public', 'tap_accum', ARRAY[etype()] ), + is_window( 'someschema', 'huh'::name, 'whatever' ), false, - 'isnt_aggregate(schema, func, arg)', - 'Function public.tap_accum(' || etype() || ') should not be an aggregate function', + 'is_window(schema, func, desc)', + 'whatever', '' ); SELECT * FROM check_test( - is_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + isnt_window( 'pg_catalog', 'dense_rank'::name, 'whatever' ), false, - 'is_aggregate(schema, func, args, desc)', + 'isnt_window(schema, win, desc)', 'whatever', '' ); +-- Test diagnostics SELECT * FROM check_test( - isnt_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), - true, - 'isnt_aggregate(schema, func, args, desc)', + is_window( 'someschema', 'nil'::name, 'whatever' ), + false, + 'is_window(schema, nowin, desc)', + 'whatever', + ' Function someschema.nil() does not exist' +); + +SELECT * FROM check_test( + isnt_window( 'someschema', 'nil'::name, 'whatever' ), + false, + 'isnt_window(schema, nowin, desc)', 'whatever', + ' Function someschema.nil() does not exist' +); + +-- is_window( NAME, NAME ) +-- isnt_window( NAME, NAME ) +SELECT * FROM check_test( + is_window( 'pg_catalog', 'ntile'::name ), + true, + 'is_window(schema, win)', + 'Function pg_catalog.ntile() should be a window function', '' ); SELECT * FROM check_test( - is_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), + isnt_window( 'pg_catalog', 'ntile'::name ), false, - 'is_aggregate(schema, func, args)', - 'Function public.oww(integer, text) should be an aggregate function', + 'isnt_window(schema, win)', + 'Function pg_catalog.ntile() should not be a window function', '' ); SELECT * FROM check_test( - isnt_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), + is_window( 'someschema', 'bah'::name ), + false, + 'is_window(schema, func)', + 'Function someschema.bah() should be a window function', + '' +); + +SELECT * FROM check_test( + isnt_window( 'someschema', 'bah'::name ), true, - 'isnt_aggregate(schema, func, args)', - 'Function public.oww(integer, text) should not be an aggregate function', + 'isnt_window(schema, func)', + 'Function someschema.bah() should not be a window function', '' ); +-- Test diagnostics SELECT * FROM check_test( - is_aggregate( 'public', 'tap_accum', 'whatever' ), + is_window( 'someschema', 'zilch'::name ), + false, + 'is_window(schema, nowin)', + 'Function someschema.zilch() should be a window function', + ' Function someschema.zilch() does not exist' +); + +SELECT * FROM check_test( + isnt_window( 'someschema', 'zilch'::name ), + false, + 'isnt_window(schema, nowin)', + 'Function someschema.zilch() should not be a window function', + ' Function someschema.zilch() does not exist' +); + +-- is_window ( NAME, NAME[], TEXT ) +-- isnt_window ( NAME, NAME[], TEXT ) +SELECT * FROM check_test( + is_window( 'ntile', ARRAY['integer'], 'whatever' ), true, - 'is_aggregate(schema, func, desc)', + 'is_window(win, arg, desc)', 'whatever', '' ); SELECT * FROM check_test( - isnt_aggregate( 'public', 'tap_accum', 'whatever' ), + isnt_window( 'ntile', ARRAY['integer'], 'whatever' ), false, - 'isnt_aggregate(schema, func, desc)', + 'isnt_window(win, arg, desc)', 'whatever', '' ); SELECT * FROM check_test( - is_aggregate( 'public', 'tap_accum'::name ), - true, - 'is_aggregate(schema, func)', - 'Function public.tap_accum() should be an aggregate function', + is_window( 'oww', ARRAY['integer', 'text'], 'whatever' ), + false, + 'is_window(func, arg, desc)', + 'whatever', '' ); SELECT * FROM check_test( - isnt_aggregate( 'public', 'tap_accum'::name ), - false, - 'isnt_aggregate(schema, func)', - 'Function public.tap_accum() should not be an aggregate function', + isnt_window( 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_window(func, arg, desc)', + 'whatever', '' ); SELECT * FROM check_test( - is_aggregate( 'tap_accum', ARRAY[etype()], 'whatever' ), + is_window( 'dense_rank', '{}'::name[], 'whatever' ), true, - 'is_aggregate(func, arg, desc)', + 'is_window(win, noargs, desc)', 'whatever', '' ); SELECT * FROM check_test( - isnt_aggregate( 'tap_accum', ARRAY[etype()], 'whatever' ), + isnt_window( 'dense_rank', '{}'::name[], 'whatever' ), false, - 'isnt_aggregate(func, arg, desc)', + 'isnt_window(win, noargs, desc)', 'whatever', '' ); SELECT * FROM check_test( - is_aggregate( 'tap_accum', ARRAY[etype()] ), - true, - 'is_aggregate(func, arg)', - 'Function tap_accum(' || etype() || ') should be an aggregate function', + is_window( 'yay', '{}'::name[], 'whatever' ), + false, + 'is_window(func, noarg, desc)', + 'whatever', '' ); SELECT * FROM check_test( - isnt_aggregate( 'tap_accum', ARRAY[etype()] ), + isnt_window( 'dense_rank', '{}'::name[], 'whatever' ), false, - 'isnt_aggregate(func, arg)', - 'Function tap_accum(' || etype() || ') should not be an aggregate function', + 'isnt_window(win, noargs, desc)', + 'whatever', '' ); +-- Test diagnostics SELECT * FROM check_test( - is_aggregate( 'oww', ARRAY['integer', 'text'], 'whatever' ), + is_window( 'nada', ARRAY['integer'], 'whatever' ), false, - 'is_aggregate(func, args, desc)', + 'is_window(nowin, arg, desc)', 'whatever', - '' + ' Function nada(integer) does not exist' ); SELECT * FROM check_test( - isnt_aggregate( 'oww', ARRAY['integer', 'text'], 'whatever' ), - true, - 'isnt_aggregate(func, args, desc)', + isnt_window( 'nada', ARRAY['integer'], 'whatever' ), + false, + 'isnt_window(nowin, arg, desc)', 'whatever', + ' Function nada(integer) does not exist' +); + +-- is_window( NAME, NAME[] ) +-- isnt_window( NAME, NAME[] ) +SELECT * FROM check_test( + is_window( 'ntile', ARRAY['integer'] ), + true, + 'is_window(win, arg, desc)', + 'Function ntile(integer) should be a window function', '' ); SELECT * FROM check_test( - is_aggregate( 'oww', ARRAY['integer', 'text'] ), + isnt_window( 'ntile', ARRAY['integer'] ), false, - 'is_aggregate(func, args)', - 'Function oww(integer, text) should be an aggregate function', + 'isnt_window(win, arg, desc)', + 'Function ntile(integer) should not be a window function', '' ); SELECT * FROM check_test( - isnt_aggregate( 'oww', ARRAY['integer', 'text'] ), + is_window( 'oww', ARRAY['integer', 'text'] ), + false, + 'is_window(func, arg, desc)', + 'Function oww(integer, text) should be a window function', + '' +); + +SELECT * FROM check_test( + isnt_window( 'oww', ARRAY['integer', 'text'] ), true, - 'isnt_aggregate(func, args)', - 'Function oww(integer, text) should not be an aggregate function', + 'isnt_window(func, arg, desc)', + 'Function oww(integer, text) should not be a window function', '' ); SELECT * FROM check_test( - is_aggregate( 'tap_accum', 'whatever' ), + is_window( 'dense_rank', '{}'::name[] ), true, - 'is_aggregate(func, desc)', - 'whatever', + 'is_window(win, noargs, desc)', + 'Function dense_rank() should be a window function', '' ); SELECT * FROM check_test( - isnt_aggregate( 'tap_accum', 'whatever' ), + isnt_window( 'dense_rank', '{}'::name[] ), false, - 'isnt_aggregate(func, desc)', - 'whatever', + 'isnt_window(win, noargs, desc)', + 'Function dense_rank() should not be a window function', '' ); SELECT * FROM check_test( - is_aggregate( 'tap_accum'::name ), - true, - 'is_aggregate(func)', - 'Function tap_accum() should be an aggregate function', + is_window( 'yay', '{}'::name[] ), + false, + 'is_window(func, noarg, desc)', + 'Function yay() should be a window function', '' ); SELECT * FROM check_test( - isnt_aggregate( 'tap_accum'::name ), + isnt_window( 'dense_rank', '{}'::name[] ), false, - 'isnt_aggregate(func)', - 'Function tap_accum() should not be an aggregate function', + 'isnt_window(win, noargs, desc)', + 'Function dense_rank() should not be a window function', '' ); -/****************************************************************************/ --- Test is_strict() and isnt_strict(). +-- Test diagnostics SELECT * FROM check_test( - is_strict( 'public', 'yay', '{}'::name[], 'whatever' ), + is_window( 'nope', ARRAY['integer'] ), + false, + 'is_window(nowin, arg, desc)', + 'Function nope(integer) should be a window function', + ' Function nope(integer) does not exist' +); + +SELECT * FROM check_test( + isnt_window( 'nope', ARRAY['integer'] ), + false, + 'isnt_window(nowin, arg, desc)', + 'Function nope(integer) should not be a window function', + ' Function nope(integer) does not exist' +); + +-- is_window( NAME, TEXT ) +-- isnt_window( NAME, TEXT ) +SELECT * FROM check_test( + is_window( 'ntile'::name, 'whatever' ), true, - 'is_strict(schema, func, 0 args, desc)', + 'is_window(win, desc)', 'whatever', '' ); SELECT * FROM check_test( - isnt_strict( 'public', 'yay', '{}'::name[], 'whatever' ), + isnt_window( 'ntile'::name, 'whatever' ), false, - 'isnt_strict(schema, func, 0 args, desc)', + 'isnt_window(win, desc)', 'whatever', '' ); SELECT * FROM check_test( - is_strict( 'public', 'yay', '{}'::name[] ), + is_window( 'oww'::name, 'whatever' ), + false, + 'is_window(func, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_window('oww'::name, 'whatever' ), true, - 'is_strict(schema, func, 0 args)', - 'Function public.yay() should be strict', + 'isnt_window(func, desc)', + 'whatever', '' ); SELECT * FROM check_test( - isnt_strict( 'public', 'yay', '{}'::name[] ), + is_window( 'yay'::name, 'whatever' ), false, - 'isnt_strict(schema, func, 0 args)', - 'Function public.yay() should not be strict', + 'is_window(func, desc)', + 'whatever', '' ); SELECT * FROM check_test( - is_strict( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + isnt_window( 'dense_rank'::name, 'whatever' ), false, - 'is_strict(schema, func, args, desc)', + 'isnt_window(win, desc)', 'whatever', '' ); +-- Test diagnostics SELECT * FROM check_test( - isnt_strict( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), - true, - 'is_strict(schema, func, args, desc)', + is_window( 'nonesuch'::name, 'whatever' ), + false, + 'is_window(nowin, desc)', 'whatever', - '' + ' Function nonesuch() does not exist' ); SELECT * FROM check_test( - is_strict( 'public', 'oww', ARRAY['integer', 'text'] ), + isnt_window( 'nonesuch'::name, 'whatever' ), false, - 'is_strict(schema, func, args)', - 'Function public.oww(integer, text) should be strict', - '' + 'isnt_window(nowin, desc)', + 'whatever', + ' Function nonesuch() does not exist' ); +-- is_window( NAME ) +-- isnt_window( NAME ) SELECT * FROM check_test( - isnt_strict( 'public', 'oww', ARRAY['integer', 'text'] ), + is_window( 'ntile' ), true, - 'is_strict(schema, func, args)', - 'Function public.oww(integer, text) should not be strict', + 'is_window(win)', + 'Function ntile() should be a window function', '' ); SELECT * FROM check_test( - is_strict( 'public', 'yay', 'whatever' ), - true, - 'is_strict(schema, func, desc)', - 'whatever', + isnt_window( 'ntile' ), + false, + 'isnt_window(win)', + 'Function ntile() should not be a window function', '' ); SELECT * FROM check_test( - isnt_strict( 'public', 'yay', 'whatever' ), + is_window( 'oww' ), false, - 'isnt_strict(schema, func, desc)', - 'whatever', + 'is_window(func)', + 'Function oww() should be a window function', '' ); SELECT * FROM check_test( - is_strict( 'public', 'yay'::name ), + isnt_window('oww' ), true, - 'is_strict(schema, func)', - 'Function public.yay() should be strict', + 'isnt_window(func)', + 'Function oww() should not be a window function', '' ); +-- Test diagnostics SELECT * FROM check_test( - isnt_strict( 'public', 'yay'::name ), + is_window( 'nooo' ), false, - 'isnt_strict(schema, func)', - 'Function public.yay() should not be strict', - '' + 'is_window(nowin)', + 'Function nooo() should be a window function', + ' Function nooo() does not exist' +); + +SELECT * FROM check_test( + isnt_window( 'nooo' ), + false, + 'isnt_window(nowin)', + 'Function nooo() should not be a window function', + ' Function nooo() does not exist' ); +/****************************************************************************/ +-- Test is_strict() and isnt_strict(). SELECT * FROM check_test( is_strict( 'public', 'yay', '{}'::name[], 'whatever' ), true, @@ -1524,6 +2596,22 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + isnt_strict( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_strict(schema, func, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_strict( 'public', 'oww', ARRAY['integer', 'text'] ), + true, + 'isnt_strict(schema, func, args)', + 'Function public.oww(integer, text) should not be strict', + '' +); + SELECT * FROM check_test( is_strict( 'yay', '{}'::name[], 'whatever' ), true, @@ -1630,14 +2718,6 @@ SELECT * FROM check_test( '' ); -SELECT * FROM check_test( - volatility_is( 'public', 'yay', '{}'::name[], 'VOLATILE', 'whatever' ), - true, - 'function_volatility(schema, func, 0 args, VOLATILE, desc)', - 'whatever', - '' -); - SELECT * FROM check_test( volatility_is( 'public', 'yay', '{}'::name[], 'v', 'whatever' ), true, @@ -1718,14 +2798,6 @@ SELECT * FROM check_test( '' ); -SELECT * FROM check_test( - volatility_is( 'yay', '{}'::name[], 'VOLATILE', 'whatever' ), - true, - 'function_volatility(func, 0 args, VOLATILE, desc)', - 'whatever', - '' -); - SELECT * FROM check_test( volatility_is( 'yay', '{}'::name[], 'v', 'whatever' ), true, diff --git a/test/sql/proctap.sql b/test/sql/proctap.sql new file mode 100644 index 000000000000..a7e4d3715030 --- /dev/null +++ b/test/sql/proctap.sql @@ -0,0 +1,560 @@ +\unset ECHO +\i test/setup.sql + +SELECT plan(192); +-- SELECT * FROM no_plan(); + +CREATE SCHEMA procschema; +CREATE FUNCTION procschema.afunc() RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL; +CREATE FUNCTION procschema.argfunc(int, text) RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL; + +CREATE PROCEDURE procschema.aproc() AS 'SELECT TRUE' LANGUAGE SQL; +CREATE PROCEDURE procschema.argproc(int, text) AS 'SELECT TRUE' LANGUAGE SQL; + +CREATE FUNCTION public.pubfunc() RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL; +CREATE FUNCTION public.argpubfunc(int, text) RETURNS BOOL AS 'SELECT TRUE' LANGUAGE SQL; + +CREATE PROCEDURE public.pubproc() AS 'SELECT TRUE' LANGUAGE SQL; +CREATE PROCEDURE public.argpubproc(int, text) AS 'SELECT TRUE' LANGUAGE SQL; + +-- is_procedure ( NAME, NAME, NAME[], TEXT ) +-- isnt_procedure ( NAME, NAME, NAME[], TEXT ) +SELECT * FROM check_test( + is_procedure( 'procschema', 'aproc', '{}'::name[], 'whatever' ), + true, + 'is_procedure(schema, proc, noargs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'procschema', 'aproc', '{}'::name[], 'whatever' ), + false, + 'isnt_procedure(schema, proc, noargs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_procedure( 'procschema', 'afunc', '{}'::name[], 'whatever' ), + false, + 'is_procedure(schema, func, noargs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'procschema', 'afunc', '{}'::name[], 'whatever' ), + true, + 'isnt_procedure(schema, func, noargs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_procedure( 'procschema', 'argproc', ARRAY['integer', 'text'], 'whatever' ), + true, + 'is_procedure(schema, proc, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'procschema', 'argproc', ARRAY['integer', 'text'], 'whatever' ), + false, + 'isnt_procedure(schema, proc, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_procedure( 'procschema', 'argfunc', ARRAY['integer', 'text'], 'whatever' ), + false, + 'is_procedure(schema, func, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'procschema', 'argfunc', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_procedure(schema, func, args, desc)', + 'whatever', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_procedure( 'procschema', 'nope', '{}'::name[], 'whatever' ), + false, + 'is_procedure(schema, noproc, noargs, desc)', + 'whatever', + ' Function procschema.nope() does not exist' +); + +SELECT * FROM check_test( + isnt_procedure( 'procschema', 'nope', '{}'::name[], 'whatever' ), + false, + 'isnt_procedure(schema, noproc, noargs, desc)', + 'whatever', + ' Function procschema.nope() does not exist' +); + +-- is_procedure( NAME, NAME, NAME[] ) +-- isnt_procedure( NAME, NAME, NAME[] ) +SELECT * FROM check_test( + is_procedure( 'procschema', 'aproc', '{}'::name[] ), + true, + 'is_procedure(schema, proc, noargs)', + 'Function procschema.aproc() should be a procedure', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'procschema', 'aproc', '{}'::name[] ), + false, + 'isnt_procedure(schema, proc, noargs)', + 'Function procschema.aproc() should not be a procedure', + '' +); + +SELECT * FROM check_test( + is_procedure( 'procschema', 'afunc', '{}'::name[] ), + false, + 'is_procedure(schema, func, noargs)', + 'Function procschema.afunc() should be a procedure', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'procschema', 'afunc', '{}'::name[] ), + true, + 'isnt_procedure(schema, func, noargs)', + 'Function procschema.afunc() should not be a procedure', + '' +); + +SELECT * FROM check_test( + is_procedure( 'procschema', 'argproc', ARRAY['integer', 'text'] ), + true, + 'is_procedure(schema, proc, args)', + 'Function procschema.argproc(integer, text) should be a procedure', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'procschema', 'argproc', ARRAY['integer', 'text'] ), + false, + 'isnt_procedure(schema, proc, args)', + 'Function procschema.argproc(integer, text) should not be a procedure', + '' +); + +SELECT * FROM check_test( + is_procedure( 'procschema', 'argfunc', ARRAY['integer', 'text'] ), + false, + 'is_procedure(schema, func, args)', + 'Function procschema.argfunc(integer, text) should be a procedure', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'procschema', 'argfunc', ARRAY['integer', 'text'] ), + true, + 'isnt_procedure(schema, func, args)', + 'Function procschema.argfunc(integer, text) should not be a procedure', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_procedure( 'procschema', 'nada', '{}'::name[] ), + false, + 'is_procedure(schema, noproc, noargs)', + 'Function procschema.nada() should be a procedure', + ' Function procschema.nada() does not exist' +); + +SELECT * FROM check_test( + isnt_procedure( 'procschema', 'nada', '{}'::name[] ), + false, + 'isnt_procedure(schema, noproc, noargs)', + 'Function procschema.nada() should not be a procedure', + ' Function procschema.nada() does not exist' +); + +-- is_procedure ( NAME, NAME, TEXT ) +-- isnt_procedure ( NAME, NAME, TEXT ) +SELECT * FROM check_test( + is_procedure( 'procschema', 'argproc', 'whatever' ), + true, + 'is_procedure(schema, proc, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'procschema', 'argproc', 'whatever' ), + false, + 'isnt_procedure(schema, proc, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_procedure( 'procschema', 'argfunc', 'whatever' ), + false, + 'is_procedure(schema, func, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'procschema', 'argfunc', 'whatever' ), + true, + 'isnt_procedure(schema, func, desc)', + 'whatever', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_procedure( 'procschema', 'nork', 'whatever' ), + false, + 'is_procedure(schema, noproc, desc)', + 'whatever', + ' Function procschema.nork() does not exist' +); + +SELECT * FROM check_test( + isnt_procedure( 'procschema', 'nork', 'whatever' ), + false, + 'isnt_procedure(schema, noproc, desc)', + 'whatever', + ' Function procschema.nork() does not exist' +); + +-- is_procedure( NAME, NAME ) +-- isnt_procedure( NAME, NAME ) +SELECT * FROM check_test( + is_procedure( 'procschema', 'argproc'::name ), + true, + 'is_procedure(schema, proc)', + 'Function procschema.argproc() should be a procedure', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'procschema', 'argproc'::name ), + false, + 'isnt_procedure(schema, proc)', + 'Function procschema.argproc() should not be a procedure', + '' +); + +SELECT * FROM check_test( + is_procedure( 'procschema', 'argfunc'::name ), + false, + 'is_procedure(schema, func)', + 'Function procschema.argfunc() should be a procedure', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'procschema', 'argfunc'::name ), + true, + 'isnt_procedure(schema, func)', + 'Function procschema.argfunc() should not be a procedure', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_procedure( 'procschema', 'zippo'::name ), + false, + 'is_procedure(schema, noproc)', + 'Function procschema.zippo() should be a procedure', + ' Function procschema.zippo() does not exist' +); + +SELECT * FROM check_test( + isnt_procedure( 'procschema', 'zippo'::name ), + false, + 'isnt_procedure(schema, noproc)', + 'Function procschema.zippo() should not be a procedure', + ' Function procschema.zippo() does not exist' +); + + +-- is_procedure ( NAME, NAME[], TEXT ) +-- isnt_procedure ( NAME, NAME[], TEXT ) +SELECT * FROM check_test( + is_procedure( 'pubproc', '{}'::name[], 'whatever' ), + true, + 'is_procedure(proc, noargs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'pubproc', '{}'::name[], 'whatever' ), + false, + 'isnt_procedure(proc, noargs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_procedure( 'pubfunc', '{}'::name[], 'whatever' ), + false, + 'is_procedure(func, noargs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'pubfunc', '{}'::name[], 'whatever' ), + true, + 'isnt_procedure(schema, func, noargs, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_procedure( 'argpubproc', ARRAY['integer', 'text'], 'whatever' ), + true, + 'is_procedure(proc, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'argpubproc', ARRAY['integer', 'text'], 'whatever' ), + false, + 'isnt_procedure(proc, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_procedure( 'argpubfunc', ARRAY['integer', 'text'], 'whatever' ), + false, + 'is_procedure(unc, args, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'argpubfunc', ARRAY['integer', 'text'], 'whatever' ), + true, + 'isnt_procedure(func, args, desc)', + 'whatever', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_procedure( 'nonesuch', '{}'::name[], 'whatever' ), + false, + 'is_procedure(noproc, noargs, desc)', + 'whatever', + ' Function nonesuch() does not exist' +); + +SELECT * FROM check_test( + isnt_procedure( 'nonesuch', '{}'::name[], 'whatever' ), + false, + 'isnt_procedure(noproc, noargs, desc)', + 'whatever', + ' Function nonesuch() does not exist' +); + +-- is_procedure( NAME, NAME[] ) +-- isnt_procedure( NAME, NAME[] ) +SELECT * FROM check_test( + is_procedure( 'pubproc', '{}'::name[] ), + true, + 'is_procedure(proc, noargs)', + 'Function pubproc() should be a procedure', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'pubproc', '{}'::name[] ), + false, + 'isnt_procedure(proc, noargs)', + 'Function pubproc() should not be a procedure', + '' +); + +SELECT * FROM check_test( + is_procedure( 'pubfunc', '{}'::name[] ), + false, + 'is_procedure(func, noargs)', + 'Function pubfunc() should be a procedure', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'pubfunc', '{}'::name[] ), + true, + 'isnt_procedure(schema, func, noargs)', + 'Function pubfunc() should not be a procedure', + '' +); + +SELECT * FROM check_test( + is_procedure( 'argpubproc', ARRAY['integer', 'text'] ), + true, + 'is_procedure(proc, args)', + 'Function argpubproc(integer, text) should be a procedure', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'argpubproc', ARRAY['integer', 'text'] ), + false, + 'isnt_procedure(proc, args)', + 'Function argpubproc(integer, text) should not be a procedure', + '' +); + +SELECT * FROM check_test( + is_procedure( 'argpubfunc', ARRAY['integer', 'text'] ), + false, + 'is_procedure(unc, args)', + 'Function argpubfunc(integer, text) should be a procedure', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'argpubfunc', ARRAY['integer', 'text'] ), + true, + 'isnt_procedure(func, args)', + 'Function argpubfunc(integer, text) should not be a procedure', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_procedure( 'nix', '{}'::name[] ), + false, + 'is_procedure(noproc, noargs)', + 'Function nix() should be a procedure', + ' Function nix() does not exist' +); + +SELECT * FROM check_test( + isnt_procedure( 'nix', '{}'::name[] ), + false, + 'isnt_procedure(noproc, noargs)', + 'Function nix() should not be a procedure', + ' Function nix() does not exist' +); + +-- is_procedure( NAME, TEXT ) +-- isnt_procedure( NAME, TEXT ) +SELECT * FROM check_test( + is_procedure( 'argpubproc', 'whatever' ), + true, + 'is_procedure(proc, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'argpubproc', 'whatever' ), + false, + 'isnt_procedure(proc, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + is_procedure( 'argpubfunc', 'whatever' ), + false, + 'is_procedure(func, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'argpubfunc', 'whatever' ), + true, + 'isnt_procedure(func, desc)', + 'whatever', + '' +); + +-- Test diagnostics +SELECT * FROM check_test( + is_procedure( 'zilch', 'whatever' ), + false, + 'is_procedure(noproc, desc)', + 'whatever', + ' Function zilch() does not exist' +); + +SELECT * FROM check_test( + isnt_procedure( 'zilch', 'whatever' ), + false, + 'isnt_procedure(noproc, desc)', + 'whatever', + ' Function zilch() does not exist' +); + +-- is_procedure( NAME ) +-- isnt_procedure( NAME ) +SELECT * FROM check_test( + is_procedure( 'argpubproc' ), + true, + 'is_procedure(proc)', + 'Function argpubproc() should be a procedure', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'argpubproc' ), + false, + 'isnt_procedure(proc)', + 'Function argpubproc() should not be a procedure', + '' +); + +SELECT * FROM check_test( + is_procedure( 'argpubfunc' ), + false, + 'is_procedure(func)', + 'Function argpubfunc() should be a procedure', + '' +); + +SELECT * FROM check_test( + isnt_procedure( 'argpubfunc' ), + true, + 'isnt_procedure(func)', + 'Function argpubfunc() should not be a procedure', + '' +); + +-- Test diagnosics +SELECT * FROM check_test( + is_procedure( 'nope' ), + false, + 'is_procedure(noproc)', + 'Function nope() should be a procedure', + ' Function nope() does not exist' +); + +SELECT * FROM check_test( + isnt_procedure( 'nope' ), + false, + 'isnt_procedure(noproc)', + 'Function nope() should not be a procedure', + ' Function nope() does not exist' +); + +/****************************************************************************/ +-- Finish the tests and clean up. +SELECT * FROM finish(); +ROLLBACK; From d76abc4746639dea82f47bbefa4beeac92cc3020 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 8 Nov 2021 21:18:37 -0500 Subject: [PATCH 1119/1195] Add comments explaining new util functions --- sql/pgtap--1.1.0--1.2.0.sql | 15 +++++++++++++++ sql/pgtap.sql.in | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/sql/pgtap--1.1.0--1.2.0.sql b/sql/pgtap--1.1.0--1.2.0.sql index ad8274c7db59..66538c75e50c 100644 --- a/sql/pgtap--1.1.0--1.2.0.sql +++ b/sql/pgtap--1.1.0--1.2.0.sql @@ -263,6 +263,13 @@ RETURNS TEXT AS $$ ); $$ 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 @@ -275,6 +282,11 @@ BEGIN END; $$ LANGUAGE plpgsql STABLE; +-- 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 @@ -284,11 +296,13 @@ RETURNS BOOLEAN AS $$ AND args = array_to_string($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 @@ -298,6 +312,7 @@ RETURNS BOOLEAN AS $$ 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index dd096ec84000..f66510cfe1ac 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -5893,6 +5893,11 @@ 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 @@ -5902,11 +5907,13 @@ RETURNS BOOLEAN AS $$ AND args = array_to_string($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 @@ -5916,6 +5923,7 @@ RETURNS BOOLEAN AS $$ 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; From 2ccda7f0c29d6740d733779b28f76942f0d84002 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 13 Nov 2021 18:47:55 -0500 Subject: [PATCH 1120/1195] Replace Travis with GitHub Workflows Travis has not worked in a while, so switch to GitHub workflows. All previous tests aside from Postgres upgrades have been replicated (and upgrades should be back soon). In the process, discover missing update SQL, so add it to `sql/pgtap--1.1.0--1.2.0.sql`. Yay testing! Also add a workflow for releasing pgTAP with a semantic version tag. --- .gitattributes | 2 +- .github/workflows/release.yml | 37 ++++ .github/workflows/test.yml | 63 +++++++ .gitignore | 1 + .travis.yml | 37 ---- Changes | 15 +- Makefile | 10 +- README.md | 2 +- sql/pgtap--1.1.0--1.2.0.sql | 49 ++++- test/expected/functap.out | 325 +++++++++++++++++----------------- test/sql/functap.sql | 12 +- test/test_MVU.sh | 4 +- tools/pg-travis-test.sh | 192 -------------------- 13 files changed, 331 insertions(+), 418 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/test.yml delete mode 100644 .travis.yml delete mode 100644 tools/pg-travis-test.sh diff --git a/.gitattributes b/.gitattributes index 834f849cc33f..796ed6fefe93 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ .gitignore export-ignore .gitattributes export-ignore tocgen export-ignore -.travis.yml export-ignore .release.mmd export-ignore +.github export-ignore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000000..b46447b7c55d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,37 @@ +name: 🚀 Release +on: + push: + tags: [v*] +jobs: + release: + name: 🚀 Release on GitHub and PGXN + runs-on: ubuntu-latest + container: pgxn/pgxn-tools + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PGXN_USERNAME: ${{ secrets.PGXN_USERNAME }} + PGXN_PASSWORD: ${{ secrets.PGXN_PASSWORD }} + steps: + - name: Check out the repo + uses: actions/checkout@v2 + - name: Bundle the Release + id: bundle + run: pgxn-bundle + - name: Release on PGXN + run: pgxn-release + - name: Generate Release Changes + run: make latest-changes.md + - name: Create GitHub Release + id: release + uses: actions/create-release@v1 + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + body_path: latest-changes.md + - name: Upload Release Asset + uses: actions/upload-release-asset@v1 + with: + upload_url: ${{ steps.release.outputs.upload_url }} + asset_path: ./${{ steps.bundle.outputs.bundle }} + asset_name: ${{ steps.bundle.outputs.bundle }} + asset_content_type: application/zip diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000000..ae11b80a7cfb --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,63 @@ +name: 🐘 Test +on: + push: + pull_request: + schedule: + - cron: '0 14 3 * *' # Monthly at 2pm on the third +jobs: + build: + strategy: + matrix: + include: + - { version: 14, upgrade_to: "", update_from: 0.99.0 } + - { version: 13, upgrade_to: 14, update_from: 0.99.0 } + - { version: 12, upgrade_to: 13, update_from: 0.99.0 } + - { version: 11, upgrade_to: 12, update_from: 0.99.0 } # Versions prior to 0.99.0 don't support Postgres 11 + - { version: 10, upgrade_to: 11, update_from: 0.95.0 } + - { version: 9.6, upgrade_to: 10, update_from: 0.95.0 } + - { version: 9.5, upgrade_to: 9.6, update_from: 0.95.0 } + - { version: 9.4, upgrade_to: 9.5, update_from: 0.95.0 } + - { version: 9.3, upgrade_to: 9.4, update_from: 0.95.0 } + - { version: 9.2, upgrade_to: 9.3, update_from: "" } # updatecheck is not supported prior to 9.3 + - { version: 9.1, upgrade_to: 9.2, update_from: "" } + # Also test pg_upgrade across many versions + # - { version: 9.1, upgrade_to: 14, update_from: 0.99.0 } + # - { version: 9.4, upgrade_to: 14, update_from: 0.99.0 } + name: 🐘 PostgreSQL ${{ matrix.version }} + runs-on: ubuntu-latest + container: pgxn/pgxn-tools + env: + PGUSER: postgres + UPDATE_FROM: "${{ matrix.update_from }}" + steps: + - run: pg-start ${{ matrix.version }} + - uses: actions/checkout@v2 + + # Basic regression test. + - run: pg-build-test + + # Test update. + - run: 'if [ -d "$UPDATE_FROM" ]; then make uninstall clean updatecheck; fi' + + # Test upgrade. + # - run: ./test/test_MVU.sh -s 55667 55778 "{{ matrix.version }}" "{{ matrix.upgrade_to }}" "/usr/lib/postgresql/{{ matrix.version }}/bin/" "/usr/lib/postgresql/{{ matrix.upgrade_to }}/bin/" + + # Test all, install, test, test-serial, and test-parallel, both from clean + # repo and repeated with existing build, with and without PARALLEL_CONN=1. + - run: make uninstall clean all + - run: make all + - run: make uninstall clean install + - run: make install + - run: "psql -Ec 'CREATE EXTENSION pgtap'" + - run: make uninstall clean test + - run: make test + - run: make uninstall clean test PARALLEL_CONN=1 + - run: make test PARALLEL_CONN=1 + - run: make uninstall clean test-serial + - run: make test-serial + - run: make uninstall clean test-serial PARALLEL_CONN=1 + - run: make test-serial PARALLEL_CONN=1 + - run: make uninstall clean test-parallel + - run: make test-parallel + - run: make uninstall clean test-parallel PARALLEL_CONN=1 + - run: make test-parallel PARALLEL_CONN=1 diff --git a/.gitignore b/.gitignore index 565d2934311d..fc0b7c6d41ee 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ bbin *.sql.orig test/build +/latest-changes.md # Misc mac crap .DS_Store diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a148c4453abc..000000000000 --- a/.travis.yml +++ /dev/null @@ -1,37 +0,0 @@ -language: c -before_install: - - wget https://gist.githubusercontent.com/petere/5893799/raw/apt.postgresql.org.sh - - sudo sh ./apt.postgresql.org.sh - - sudo rm -vf /etc/apt/sources.list.d/pgdg-source.list - -env: - # WARNING! UPGRADE_TO tests pg_upgrade; UDPATE_FROM tests ALTER EXTENSION! Note UPGRADE vs UPDATE! - - UPGRADE_TO=9.2 PGVERSION=9.1 ALLOW_MISSING_EXTENSIONS=1 - - UPGRADE_TO=9.3 PGVERSION=9.2 ALLOW_MISSING_EXTENSIONS=1 - - UPGRADE_TO=9.4 PGVERSION=9.3 ALLOW_MISSING_EXTENSIONS=1 - - UPGRADE_TO=9.5 PGVERSION=9.4 - - UPGRADE_TO=9.6 PGVERSION=9.5 - - UPGRADE_TO=10 PGVERSION=9.6 - - UPGRADE_TO=11 PGVERSION=10 - - UPGRADE_TO=12 PGVERSION=11 UPDATE_FROM=0.99.0 # Versions prior to 0.99.0 don't support Postgres 11 - - UPGRADE_TO=13 PGVERSION=12 UPDATE_FROM=0.99.0 - - PGVERSION=13 UPDATE_FROM=0.99.0 - - # Duplication below is via s/-.*PGVERSION/- PARALLEL_CONN=1 PGVERSION/ - - PARALLEL_CONN=1 PGVERSION=9.1 ALLOW_MISSING_EXTENSIONS=1 - - PARALLEL_CONN=1 PGVERSION=9.2 ALLOW_MISSING_EXTENSIONS=1 - - PARALLEL_CONN=1 PGVERSION=9.3 ALLOW_MISSING_EXTENSIONS=1 - - PARALLEL_CONN=1 PGVERSION=9.4 - - PARALLEL_CONN=1 PGVERSION=9.5 - - PARALLEL_CONN=1 PGVERSION=9.6 - - PARALLEL_CONN=1 PGVERSION=10 - - PARALLEL_CONN=1 PGVERSION=11 UPDATE_FROM=0.99.0 # Versions prior to 0.99.0 don't support Postgres 11 - - PARALLEL_CONN=1 PGVERSION=12 UPDATE_FROM=0.99.0 - - PARALLEL_CONN=1 PGVERSION=13 UPDATE_FROM=0.99.0 - - # Also test pg_upgrade across many versions - - UPGRADE_TO=13 PGVERSION=9.1 ALLOW_MISSING_EXTENSIONS=1 - - UPGRADE_TO=13 PGVERSION=9.4 -script: bash tools/pg-travis-test.sh - -# vi: noexpandtab ts=2 sw=2 diff --git a/Changes b/Changes index 39bb3800eac6..f0438b06f2cb 100644 --- a/Changes +++ b/Changes @@ -47,21 +47,22 @@ Revision history for pgTAP * Remove support for PostgreSQL older than 9.1. Note that some automated tests don't run on versions older than 9.4, so it's recommended you run PostgreSQL 9.4 at minimum. -* Add a `throw_excepton` argument to `finish()`. `finish(true)` will raise an +* Add a `throw_exception` argument to `finish()`. `finish(true)` will raise an exception if any tests failed. Thanks to Stephen Paul Weber for the idea (#80), and Rodolphe Quiédeville for most of the work (#104)! -* Make description optional for col_not_null() (#221). +* Make description optional for `col_not_null()` (#221). * Fix syntax of test_user function example in the docs (#208). Thanks to PeteDevoy for the patch! -* Fix issue with pgtap-core referencing pg_proc.proisagg on PostgreSQL 11+ (#197) +* Fix issue with `pgtap-core` referencing `pg_proc.proisagg` on PostgreSQL 11+ + (#197). * Add PostgreSQL 12.0 to automated tests (#223). -* Fix pgtap_version(), which incorrectly returned 0.99 when upgrading from +* Fix `pgtap_version()`, which incorrectly returned 0.99 when upgrading from version 0.99.0 to 1.0.0 (#214). -* Fix issue with using pg_upgrade to PostgreSQL 11+ with pgTap installed (#201, - #215). +* Fix issue with using `pg_upgrade` to PostgreSQL 11+ with pgTAP installed + (#201, #215). * Start using `DEFAULT` in functions. * Add automated testing of extension upgrade scripts (#128), as well as testing - of pg_upgrade with pgTap installed (#218, #222). + of `pg_upgrade` with pgTAP installed (#218, #222). 1.0.0 2019-02-21T22:39:42Z -------------------------- diff --git a/Makefile b/Makefile index ead436bc7587..fb5cf84513db 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,8 @@ MAINEXT = pgtap EXTENSION = $(MAINEXT) EXTVERSION = $(shell grep default_version $(MAINEXT).control | \ sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") +DISTVERSION = $(shell grep -m 1 '[[:space:]]\{3\}"version":' META.json | \ + sed -e 's/[[:space:]]*"version":[[:space:]]*"\([^"]*\)",\{0,1\}/\1/') NUMVERSION = $(shell echo $(EXTVERSION) | sed -e 's/\([[:digit:]]*[.][[:digit:]]*\).*/\1/') VERSION_FILES = sql/$(MAINEXT)--$(EXTVERSION).sql sql/$(MAINEXT)-core--$(EXTVERSION).sql sql/$(MAINEXT)-schema--$(EXTVERSION).sql BASE_FILES = $(subst --$(EXTVERSION),,$(VERSION_FILES)) sql/uninstall_$(MAINEXT).sql @@ -446,9 +448,8 @@ $(B_DIR)/: pgtap-version-%: $(EXTENSION_DIR)/pgtap--%.sql @true # Necessary to have a fake action here - -# Travis will complain if we reinstall too quickly, so don't run make install -# unless actually necessary. +# CI/CD workflow might complain if we reinstall too quickly, so don't run make +# install unless actually necessary. $(EXTENSION_DIR)/pgtap--$(EXTVERSION).sql: sql/pgtap--$(EXTVERSION).sql $(MAKE) install @@ -484,6 +485,9 @@ updatecheck_setup: updatecheck_deps .PHONY: updatecheck_run updatecheck_run: updatecheck_setup installcheck +latest-changes.md: Changes + perl -e 'while (<>) {last if /^(v?\Q${DISTVERSION}\E)/; } print "Changes for v${DISTVERSION}:\n"; while (<>) { last if /^\s*$$/; s/^\s+//; print }' Changes > $@ + # # STOLEN FROM pgxntool # diff --git a/README.md b/README.md index 827c8aa17c25..e8b1eb1da70e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ documentation in `doc/pgtap.mmd` or [online](https://pgtap.org/documentation.html "Complete pgTAP Documentation"). [![PGXN version](https://badge.fury.io/pg/pgtap.svg)](https://badge.fury.io/pg/pgtap) -[![Build Status](https://travis-ci.org/theory/pgtap.png)](https://travis-ci.org/theory/pgtap) +[![🐘 Postgres](https://github.com/theory/pgtap/actions/workflows/test.yml/badge.svg)](https://github.com/theory/pgtap/actions/workflows/test.yml) pgTAP must be installed on a host with PostgreSQL server running; it cannot be installed remotely. diff --git a/sql/pgtap--1.1.0--1.2.0.sql b/sql/pgtap--1.1.0--1.2.0.sql index 66538c75e50c..6f3c52a29b13 100644 --- a/sql/pgtap--1.1.0--1.2.0.sql +++ b/sql/pgtap--1.1.0--1.2.0.sql @@ -2,6 +2,53 @@ CREATE OR REPLACE FUNCTION pgtap_version() RETURNS NUMERIC AS 'SELECT 1.2;' LANGUAGE SQL IMMUTABLE; +-- 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; + -- has_view( schema, view ) CREATE OR REPLACE FUNCTION has_view ( NAME, NAME ) RETURNS TEXT AS $$ @@ -121,7 +168,7 @@ EXCEPTION ); END; $$ LANGUAGE plpgsql; -======= + -- 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 $$ diff --git a/test/expected/functap.out b/test/expected/functap.out index 83ed9fad1a8f..8d9f7469b0e4 100644 --- a/test/expected/functap.out +++ b/test/expected/functap.out @@ -1,5 +1,5 @@ \unset ECHO -1..1012 +1..1009 ok 1 - simple function should pass ok 2 - simple function should have the proper description ok 3 - simple function should have the proper diagnostics @@ -849,166 +849,163 @@ ok 846 - isnt_window(func, desc) should have the proper diagnostics ok 847 - is_window(func, desc) should fail ok 848 - is_window(func, desc) should have the proper description ok 849 - is_window(func, desc) should have the proper diagnostics -ok 850 - isnt_window(win, desc) should fail -ok 851 - isnt_window(win, desc) should have the proper description -ok 852 - isnt_window(win, desc) should have the proper diagnostics -ok 853 - is_window(nowin, desc) should fail -ok 854 - is_window(nowin, desc) should have the proper description -ok 855 - is_window(nowin, desc) should have the proper diagnostics -ok 856 - isnt_window(nowin, desc) should fail -ok 857 - isnt_window(nowin, desc) should have the proper description -ok 858 - isnt_window(nowin, desc) should have the proper diagnostics -ok 859 - is_window(win) should pass -ok 860 - is_window(win) should have the proper description -ok 861 - is_window(win) should have the proper diagnostics -ok 862 - isnt_window(win) should fail -ok 863 - isnt_window(win) should have the proper description -ok 864 - isnt_window(win) should have the proper diagnostics -ok 865 - is_window(func) should fail -ok 866 - is_window(func) should have the proper description -ok 867 - is_window(func) should have the proper diagnostics -ok 868 - isnt_window(func) should pass -ok 869 - isnt_window(func) should have the proper description -ok 870 - isnt_window(func) should have the proper diagnostics -ok 871 - is_window(nowin) should fail -ok 872 - is_window(nowin) should have the proper description -ok 873 - is_window(nowin) should have the proper diagnostics -ok 874 - isnt_window(nowin) should fail -ok 875 - isnt_window(nowin) should have the proper description -ok 876 - isnt_window(nowin) should have the proper diagnostics -ok 877 - is_strict(schema, func, 0 args, desc) should pass -ok 878 - is_strict(schema, func, 0 args, desc) should have the proper description -ok 879 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 880 - isnt_strict(schema, func, 0 args, desc) should fail -ok 881 - isnt_strict(schema, func, 0 args, desc) should have the proper description -ok 882 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 883 - is_strict(schema, func, 0 args) should pass -ok 884 - is_strict(schema, func, 0 args) should have the proper description -ok 885 - is_strict(schema, func, 0 args) should have the proper diagnostics -ok 886 - isnt_strict(schema, func, 0 args) should fail -ok 887 - isnt_strict(schema, func, 0 args) should have the proper description -ok 888 - isnt_strict(schema, func, 0 args) should have the proper diagnostics -ok 889 - is_strict(schema, func, args, desc) should fail -ok 890 - is_strict(schema, func, args, desc) should have the proper description -ok 891 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 892 - isnt_strict(schema, func, args, desc) should pass -ok 893 - isnt_strict(schema, func, args, desc) should have the proper description -ok 894 - isnt_strict(schema, func, args, desc) should have the proper diagnostics -ok 895 - is_strict(schema, func, args) should fail -ok 896 - is_strict(schema, func, args) should have the proper description -ok 897 - is_strict(schema, func, args) should have the proper diagnostics -ok 898 - isnt_strict(schema, func, args) should pass -ok 899 - isnt_strict(schema, func, args) should have the proper description -ok 900 - isnt_strict(schema, func, args) should have the proper diagnostics -ok 901 - is_strict(schema, func, desc) should pass -ok 902 - is_strict(schema, func, desc) should have the proper description -ok 903 - is_strict(schema, func, desc) should have the proper diagnostics -ok 904 - isnt_strict(schema, func, desc) should fail -ok 905 - isnt_strict(schema, func, desc) should have the proper description -ok 906 - isnt_strict(schema, func, desc) should have the proper diagnostics -ok 907 - is_strict(schema, func) should pass -ok 908 - is_strict(schema, func) should have the proper description -ok 909 - is_strict(schema, func) should have the proper diagnostics -ok 910 - isnt_strict(schema, func) should fail -ok 911 - isnt_strict(schema, func) should have the proper description -ok 912 - isnt_strict(schema, func) should have the proper diagnostics -ok 913 - isnt_strict(schema, func, args, desc) should pass -ok 914 - isnt_strict(schema, func, args, desc) should have the proper description -ok 915 - isnt_strict(schema, func, args, desc) should have the proper diagnostics -ok 916 - isnt_strict(schema, func, args) should pass -ok 917 - isnt_strict(schema, func, args) should have the proper description -ok 918 - isnt_strict(schema, func, args) should have the proper diagnostics -ok 919 - is_strict(func, 0 args, desc) should pass -ok 920 - is_strict(func, 0 args, desc) should have the proper description -ok 921 - is_strict(func, 0 args, desc) should have the proper diagnostics -ok 922 - isnt_strict(func, 0 args, desc) should fail -ok 923 - isnt_strict(func, 0 args, desc) should have the proper description -ok 924 - isnt_strict(func, 0 args, desc) should have the proper diagnostics -ok 925 - is_strict(func, 0 args) should pass -ok 926 - is_strict(func, 0 args) should have the proper description -ok 927 - is_strict(func, 0 args) should have the proper diagnostics -ok 928 - isnt_strict(func, 0 args) should fail -ok 929 - isnt_strict(func, 0 args) should have the proper description -ok 930 - isnt_strict(func, 0 args) should have the proper diagnostics -ok 931 - is_strict(func, args, desc) should fail -ok 932 - is_strict(func, args, desc) should have the proper description -ok 933 - is_strict(func, args, desc) should have the proper diagnostics -ok 934 - isnt_strict(func, args, desc) should pass -ok 935 - isnt_strict(func, args, desc) should have the proper description -ok 936 - isnt_strict(func, args, desc) should have the proper diagnostics -ok 937 - is_strict(func, args) should fail -ok 938 - is_strict(func, args) should have the proper description -ok 939 - is_strict(func, args) should have the proper diagnostics -ok 940 - isnt_strict(func, args) should pass -ok 941 - isnt_strict(func, args) should have the proper description -ok 942 - isnt_strict(func, args) should have the proper diagnostics -ok 943 - is_strict(func, desc) should pass -ok 944 - is_strict(func, desc) should have the proper description -ok 945 - is_strict(func, desc) should have the proper diagnostics -ok 946 - isnt_strict(func, desc) should fail -ok 947 - isnt_strict(func, desc) should have the proper description -ok 948 - isnt_strict(func, desc) should have the proper diagnostics -ok 949 - is_strict(func) should pass -ok 950 - is_strict(func) should have the proper description -ok 951 - is_strict(func) should have the proper diagnostics -ok 952 - isnt_strict(func) should fail -ok 953 - isnt_strict(func) should have the proper description -ok 954 - isnt_strict(func) should have the proper diagnostics -ok 955 - function_volatility(schema, func, 0 args, volatile, desc) should pass -ok 956 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper description -ok 957 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper diagnostics -ok 958 - function_volatility(schema, func, 0 args, v, desc) should pass -ok 959 - function_volatility(schema, func, 0 args, v, desc) should have the proper description -ok 960 - function_volatility(schema, func, 0 args, v, desc) should have the proper diagnostics -ok 961 - function_volatility(schema, func, args, immutable, desc) should pass -ok 962 - function_volatility(schema, func, args, immutable, desc) should have the proper description -ok 963 - function_volatility(schema, func, args, immutable, desc) should have the proper diagnostics -ok 964 - function_volatility(schema, func, 0 args, stable, desc) should pass -ok 965 - function_volatility(schema, func, 0 args, stable, desc) should have the proper description -ok 966 - function_volatility(schema, func, 0 args, stable, desc) should have the proper diagnostics -ok 967 - function_volatility(schema, func, 0 args, volatile) should pass -ok 968 - function_volatility(schema, func, 0 args, volatile) should have the proper description -ok 969 - function_volatility(schema, func, 0 args, volatile) should have the proper diagnostics -ok 970 - function_volatility(schema, func, args, immutable) should pass -ok 971 - function_volatility(schema, func, args, immutable) should have the proper description -ok 972 - function_volatility(schema, func, volatile, desc) should pass -ok 973 - function_volatility(schema, func, volatile, desc) should have the proper description -ok 974 - function_volatility(schema, func, volatile, desc) should have the proper diagnostics -ok 975 - function_volatility(schema, func, volatile) should pass -ok 976 - function_volatility(schema, func, volatile) should have the proper description -ok 977 - function_volatility(schema, func, volatile) should have the proper diagnostics -ok 978 - function_volatility(schema, func, immutable, desc) should pass -ok 979 - function_volatility(schema, func, immutable, desc) should have the proper description -ok 980 - function_volatility(schema, func, immutable, desc) should have the proper diagnostics -ok 981 - function_volatility(schema, func, stable, desc) should pass -ok 982 - function_volatility(schema, func, stable, desc) should have the proper description -ok 983 - function_volatility(schema, func, stable, desc) should have the proper diagnostics -ok 984 - function_volatility(func, 0 args, volatile, desc) should pass -ok 985 - function_volatility(func, 0 args, volatile, desc) should have the proper description -ok 986 - function_volatility(func, 0 args, volatile, desc) should have the proper diagnostics -ok 987 - function_volatility(func, 0 args, v, desc) should pass -ok 988 - function_volatility(func, 0 args, v, desc) should have the proper description -ok 989 - function_volatility(func, 0 args, v, desc) should have the proper diagnostics -ok 990 - function_volatility(func, args, immutable, desc) should pass -ok 991 - function_volatility(func, args, immutable, desc) should have the proper description -ok 992 - function_volatility(func, args, immutable, desc) should have the proper diagnostics -ok 993 - function_volatility(func, 0 args, stable, desc) should pass -ok 994 - function_volatility(func, 0 args, stable, desc) should have the proper description -ok 995 - function_volatility(func, 0 args, stable, desc) should have the proper diagnostics -ok 996 - function_volatility(func, 0 args, volatile) should pass -ok 997 - function_volatility(func, 0 args, volatile) should have the proper description -ok 998 - function_volatility(func, 0 args, volatile) should have the proper diagnostics -ok 999 - function_volatility(func, args, immutable) should pass -ok 1000 - function_volatility(func, args, immutable) should have the proper description -ok 1001 - function_volatility(func, volatile, desc) should pass -ok 1002 - function_volatility(func, volatile, desc) should have the proper description -ok 1003 - function_volatility(func, volatile, desc) should have the proper diagnostics -ok 1004 - function_volatility(func, volatile) should pass -ok 1005 - function_volatility(func, volatile) should have the proper description -ok 1006 - function_volatility(func, volatile) should have the proper diagnostics -ok 1007 - function_volatility(func, immutable, desc) should pass -ok 1008 - function_volatility(func, immutable, desc) should have the proper description -ok 1009 - function_volatility(func, immutable, desc) should have the proper diagnostics -ok 1010 - function_volatility(func, stable, desc) should pass -ok 1011 - function_volatility(func, stable, desc) should have the proper description -ok 1012 - function_volatility(func, stable, desc) should have the proper diagnostics +ok 850 - is_window(nowin, desc) should fail +ok 851 - is_window(nowin, desc) should have the proper description +ok 852 - is_window(nowin, desc) should have the proper diagnostics +ok 853 - isnt_window(nowin, desc) should fail +ok 854 - isnt_window(nowin, desc) should have the proper description +ok 855 - isnt_window(nowin, desc) should have the proper diagnostics +ok 856 - is_window(win) should pass +ok 857 - is_window(win) should have the proper description +ok 858 - is_window(win) should have the proper diagnostics +ok 859 - isnt_window(win) should fail +ok 860 - isnt_window(win) should have the proper description +ok 861 - isnt_window(win) should have the proper diagnostics +ok 862 - is_window(func) should fail +ok 863 - is_window(func) should have the proper description +ok 864 - is_window(func) should have the proper diagnostics +ok 865 - isnt_window(func) should pass +ok 866 - isnt_window(func) should have the proper description +ok 867 - isnt_window(func) should have the proper diagnostics +ok 868 - is_window(nowin) should fail +ok 869 - is_window(nowin) should have the proper description +ok 870 - is_window(nowin) should have the proper diagnostics +ok 871 - isnt_window(nowin) should fail +ok 872 - isnt_window(nowin) should have the proper description +ok 873 - isnt_window(nowin) should have the proper diagnostics +ok 874 - is_strict(schema, func, 0 args, desc) should pass +ok 875 - is_strict(schema, func, 0 args, desc) should have the proper description +ok 876 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 877 - isnt_strict(schema, func, 0 args, desc) should fail +ok 878 - isnt_strict(schema, func, 0 args, desc) should have the proper description +ok 879 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 880 - is_strict(schema, func, 0 args) should pass +ok 881 - is_strict(schema, func, 0 args) should have the proper description +ok 882 - is_strict(schema, func, 0 args) should have the proper diagnostics +ok 883 - isnt_strict(schema, func, 0 args) should fail +ok 884 - isnt_strict(schema, func, 0 args) should have the proper description +ok 885 - isnt_strict(schema, func, 0 args) should have the proper diagnostics +ok 886 - is_strict(schema, func, args, desc) should fail +ok 887 - is_strict(schema, func, args, desc) should have the proper description +ok 888 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 889 - isnt_strict(schema, func, args, desc) should pass +ok 890 - isnt_strict(schema, func, args, desc) should have the proper description +ok 891 - isnt_strict(schema, func, args, desc) should have the proper diagnostics +ok 892 - is_strict(schema, func, args) should fail +ok 893 - is_strict(schema, func, args) should have the proper description +ok 894 - is_strict(schema, func, args) should have the proper diagnostics +ok 895 - isnt_strict(schema, func, args) should pass +ok 896 - isnt_strict(schema, func, args) should have the proper description +ok 897 - isnt_strict(schema, func, args) should have the proper diagnostics +ok 898 - is_strict(schema, func, desc) should pass +ok 899 - is_strict(schema, func, desc) should have the proper description +ok 900 - is_strict(schema, func, desc) should have the proper diagnostics +ok 901 - isnt_strict(schema, func, desc) should fail +ok 902 - isnt_strict(schema, func, desc) should have the proper description +ok 903 - isnt_strict(schema, func, desc) should have the proper diagnostics +ok 904 - is_strict(schema, func) should pass +ok 905 - is_strict(schema, func) should have the proper description +ok 906 - is_strict(schema, func) should have the proper diagnostics +ok 907 - isnt_strict(schema, func) should fail +ok 908 - isnt_strict(schema, func) should have the proper description +ok 909 - isnt_strict(schema, func) should have the proper diagnostics +ok 910 - isnt_strict(schema, func, args, desc) should pass +ok 911 - isnt_strict(schema, func, args, desc) should have the proper description +ok 912 - isnt_strict(schema, func, args, desc) should have the proper diagnostics +ok 913 - isnt_strict(schema, func, args) should pass +ok 914 - isnt_strict(schema, func, args) should have the proper description +ok 915 - isnt_strict(schema, func, args) should have the proper diagnostics +ok 916 - is_strict(func, 0 args, desc) should pass +ok 917 - is_strict(func, 0 args, desc) should have the proper description +ok 918 - is_strict(func, 0 args, desc) should have the proper diagnostics +ok 919 - isnt_strict(func, 0 args, desc) should fail +ok 920 - isnt_strict(func, 0 args, desc) should have the proper description +ok 921 - isnt_strict(func, 0 args, desc) should have the proper diagnostics +ok 922 - is_strict(func, 0 args) should pass +ok 923 - is_strict(func, 0 args) should have the proper description +ok 924 - is_strict(func, 0 args) should have the proper diagnostics +ok 925 - isnt_strict(func, 0 args) should fail +ok 926 - isnt_strict(func, 0 args) should have the proper description +ok 927 - isnt_strict(func, 0 args) should have the proper diagnostics +ok 928 - is_strict(func, args, desc) should fail +ok 929 - is_strict(func, args, desc) should have the proper description +ok 930 - is_strict(func, args, desc) should have the proper diagnostics +ok 931 - isnt_strict(func, args, desc) should pass +ok 932 - isnt_strict(func, args, desc) should have the proper description +ok 933 - isnt_strict(func, args, desc) should have the proper diagnostics +ok 934 - is_strict(func, args) should fail +ok 935 - is_strict(func, args) should have the proper description +ok 936 - is_strict(func, args) should have the proper diagnostics +ok 937 - isnt_strict(func, args) should pass +ok 938 - isnt_strict(func, args) should have the proper description +ok 939 - isnt_strict(func, args) should have the proper diagnostics +ok 940 - is_strict(func, desc) should pass +ok 941 - is_strict(func, desc) should have the proper description +ok 942 - is_strict(func, desc) should have the proper diagnostics +ok 943 - isnt_strict(func, desc) should fail +ok 944 - isnt_strict(func, desc) should have the proper description +ok 945 - isnt_strict(func, desc) should have the proper diagnostics +ok 946 - is_strict(func) should pass +ok 947 - is_strict(func) should have the proper description +ok 948 - is_strict(func) should have the proper diagnostics +ok 949 - isnt_strict(func) should fail +ok 950 - isnt_strict(func) should have the proper description +ok 951 - isnt_strict(func) should have the proper diagnostics +ok 952 - function_volatility(schema, func, 0 args, volatile, desc) should pass +ok 953 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper description +ok 954 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper diagnostics +ok 955 - function_volatility(schema, func, 0 args, v, desc) should pass +ok 956 - function_volatility(schema, func, 0 args, v, desc) should have the proper description +ok 957 - function_volatility(schema, func, 0 args, v, desc) should have the proper diagnostics +ok 958 - function_volatility(schema, func, args, immutable, desc) should pass +ok 959 - function_volatility(schema, func, args, immutable, desc) should have the proper description +ok 960 - function_volatility(schema, func, args, immutable, desc) should have the proper diagnostics +ok 961 - function_volatility(schema, func, 0 args, stable, desc) should pass +ok 962 - function_volatility(schema, func, 0 args, stable, desc) should have the proper description +ok 963 - function_volatility(schema, func, 0 args, stable, desc) should have the proper diagnostics +ok 964 - function_volatility(schema, func, 0 args, volatile) should pass +ok 965 - function_volatility(schema, func, 0 args, volatile) should have the proper description +ok 966 - function_volatility(schema, func, 0 args, volatile) should have the proper diagnostics +ok 967 - function_volatility(schema, func, args, immutable) should pass +ok 968 - function_volatility(schema, func, args, immutable) should have the proper description +ok 969 - function_volatility(schema, func, volatile, desc) should pass +ok 970 - function_volatility(schema, func, volatile, desc) should have the proper description +ok 971 - function_volatility(schema, func, volatile, desc) should have the proper diagnostics +ok 972 - function_volatility(schema, func, volatile) should pass +ok 973 - function_volatility(schema, func, volatile) should have the proper description +ok 974 - function_volatility(schema, func, volatile) should have the proper diagnostics +ok 975 - function_volatility(schema, func, immutable, desc) should pass +ok 976 - function_volatility(schema, func, immutable, desc) should have the proper description +ok 977 - function_volatility(schema, func, immutable, desc) should have the proper diagnostics +ok 978 - function_volatility(schema, func, stable, desc) should pass +ok 979 - function_volatility(schema, func, stable, desc) should have the proper description +ok 980 - function_volatility(schema, func, stable, desc) should have the proper diagnostics +ok 981 - function_volatility(func, 0 args, volatile, desc) should pass +ok 982 - function_volatility(func, 0 args, volatile, desc) should have the proper description +ok 983 - function_volatility(func, 0 args, volatile, desc) should have the proper diagnostics +ok 984 - function_volatility(func, 0 args, v, desc) should pass +ok 985 - function_volatility(func, 0 args, v, desc) should have the proper description +ok 986 - function_volatility(func, 0 args, v, desc) should have the proper diagnostics +ok 987 - function_volatility(func, args, immutable, desc) should pass +ok 988 - function_volatility(func, args, immutable, desc) should have the proper description +ok 989 - function_volatility(func, args, immutable, desc) should have the proper diagnostics +ok 990 - function_volatility(func, 0 args, stable, desc) should pass +ok 991 - function_volatility(func, 0 args, stable, desc) should have the proper description +ok 992 - function_volatility(func, 0 args, stable, desc) should have the proper diagnostics +ok 993 - function_volatility(func, 0 args, volatile) should pass +ok 994 - function_volatility(func, 0 args, volatile) should have the proper description +ok 995 - function_volatility(func, 0 args, volatile) should have the proper diagnostics +ok 996 - function_volatility(func, args, immutable) should pass +ok 997 - function_volatility(func, args, immutable) should have the proper description +ok 998 - function_volatility(func, volatile, desc) should pass +ok 999 - function_volatility(func, volatile, desc) should have the proper description +ok 1000 - function_volatility(func, volatile, desc) should have the proper diagnostics +ok 1001 - function_volatility(func, volatile) should pass +ok 1002 - function_volatility(func, volatile) should have the proper description +ok 1003 - function_volatility(func, volatile) should have the proper diagnostics +ok 1004 - function_volatility(func, immutable, desc) should pass +ok 1005 - function_volatility(func, immutable, desc) should have the proper description +ok 1006 - function_volatility(func, immutable, desc) should have the proper diagnostics +ok 1007 - function_volatility(func, stable, desc) should pass +ok 1008 - function_volatility(func, stable, desc) should have the proper description +ok 1009 - function_volatility(func, stable, desc) should have the proper diagnostics diff --git a/test/sql/functap.sql b/test/sql/functap.sql index f02475ffe0ac..fb3297248e3b 100644 --- a/test/sql/functap.sql +++ b/test/sql/functap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(1012); +SELECT plan(1009); -- SELECT * FROM no_plan(); CREATE SCHEMA someschema; @@ -2391,7 +2391,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_window( 'ntile'::name, 'whatever' ), + isnt_window( 'lag'::name, 'whatever' ), false, 'isnt_window(win, desc)', 'whatever', @@ -2422,14 +2422,6 @@ SELECT * FROM check_test( '' ); -SELECT * FROM check_test( - isnt_window( 'dense_rank'::name, 'whatever' ), - false, - 'isnt_window(win, desc)', - 'whatever', - '' -); - -- Test diagnostics SELECT * FROM check_test( is_window( 'nonesuch'::name, 'whatever' ), diff --git a/test/test_MVU.sh b/test/test_MVU.sh index 64e0bcf3bb05..81125debeecd 100755 --- a/test/test_MVU.sh +++ b/test/test_MVU.sh @@ -181,7 +181,7 @@ if command -v pg_ctlcluster > /dev/null; then new_initdb="sudo pg_createcluster $NEW_VERSION $cluster_name -u $USER -p $NEW_PORT -d $new_dir -- -A trust" new_pg_ctl="sudo pg_ctlcluster $NEW_VERSION test_pg_upgrade" - # See also ../pg-travis-test.sh + # See also ../.github/workflows/test.yml new_pg_upgrade=/usr/lib/postgresql/$NEW_VERSION/bin/pg_upgrade else ctl_separator='' @@ -312,7 +312,7 @@ add_exclude 9.1 9.2 test/sql/throwtap.sql add_exclude 9.4 9.5 test/sql/policy.sql test/sql/throwtap.sql add_exclude 9.6 10 test/sql/partitions.sql -# Use this if there's a single test failing in Travis that you can't figure out... +# Use this if there's a single test failing .github/workflows/test.yml that you can't figure out... #(cd $(dirname $0)/..; pg_prove -v --pset tuples_only=1 test/sql/throwtap.sql) export EXCLUDE_TEST_FILES diff --git a/tools/pg-travis-test.sh b/tools/pg-travis-test.sh deleted file mode 100644 index 05b32c7f5d52..000000000000 --- a/tools/pg-travis-test.sh +++ /dev/null @@ -1,192 +0,0 @@ -#!/bin/bash - -# Based on https://gist.github.com/petere/6023944 - -set -E -e -u -o pipefail - -# -# NOTE: you can control what tests run by setting the TARGETS environment -# variable for a particular branch in the Travis console -# - -# You can set this to higher levels for more debug output -#export DEBUG=9 -#set -x - -BASEDIR=`dirname $0` -if ! . $BASEDIR/util.sh; then - echo "FATAL: error sourcing $BASEDIR/util.sh" 1>&2 - exit 99 -fi -trap err_report ERR - -# For sanity sake, ensure that we run from the top level directory -cd "$BASEDIR"/.. || die 3 "Unable to cd to $BASEDIR/.." - -export UPGRADE_TO=${UPGRADE_TO:-} -FAIL_FAST=${FAIL_FAST:-} -failed='' -tests_run=0 - -get_packages() { - echo "libtap-parser-sourcehandler-pgtap-perl postgresql-$1 postgresql-server-dev-$1" -} -get_path() { - # See also test/test_MVU.sh - echo "/usr/lib/postgresql/$1/bin/" -} - -# Do NOT use () here; we depend on being able to set failed -test_cmd() { -local status rc -if [ "$1" == '-s' ]; then - status="$2" - shift 2 -else - status="$1" -fi - -# NOTE! While this script is under tools/, we expect to be running from the main directory - -# NOTE: simply aliasing a local variable to "$@" does not work as desired, -# probably because by default the variable isn't an array. If it ever becomes -# an issue we can figure out how to do it. - -echo -echo ############################################################################# -echo "PG-TRAVIS: running $@" -echo ############################################################################# -tests_run=$((tests_run + 1)) -# Use || so as not to trip up -e, and a sub-shell to be safe. -rc=0 -( "$@" ) || rc=$? -if [ $rc -ne 0 ]; then - echo - echo '!!!!!!!!!!!!!!!! FAILURE !!!!!!!!!!!!!!!!' - echo "$@ returned $rc" - echo '!!!!!!!!!!!!!!!! FAILURE !!!!!!!!!!!!!!!!' - echo - failed="$failed '$status'" - [ -z "$FAIL_FAST" ] || die 1 "command failed and \$FAIL_FAST is not empty" -fi -} - -# Ensure test_cmd sets failed properly -old_FAST_FAIL=$FAIL_FAST -FAIL_FAST='' -test_cmd false > /dev/null # DO NOT redirect stderr, otherwise it's horrible to debug problems here! -if [ -z "$failed" ]; then - echo "code error: test_cmd() did not set \$failed" - exit 91 -fi -failed='' -FAIL_FAST=$old_FAST_FAIL - -test_make() { - # Many tests depend on install, so just use sudo for all of them - test_cmd -s "$*" sudo make "$@" -} - -######################################################## -# TEST TARGETS -sanity() { - test_make clean regress -} - -update() { - # pg_regress --launcher not supported prior to 9.1 - # There are some other failures in 9.1 and 9.2 (see https://travis-ci.org/decibel/pgtap/builds/358206497). - echo $PGVERSION | grep -qE "8[.]|9[.][012]" || test_make clean updatecheck -} - -tests_run_by_target_all=11 # 1 + 5 * 2 -all() { - local tests_run_start=$tests_run - # the test* targets use pg_prove, which assumes it's making a default psql - # connection to a database that has pgTap installed, so we need to set that - # up. - test_cmd -s "all(): psql create extension" psql -Ec 'CREATE EXTENSION pgtap' - - # TODO: install software necessary to allow testing 'html' target - # UPDATE tests_run_by_target_all IF YOU ADD ANY TESTS HERE! - for t in all install test test-serial test-parallel ; do - # Test from a clean slate... - test_make uninstall clean $t - # And then test again - test_make $t - done - local commands_run=$(($tests_run - $tests_run_start)) - [ $commands_run -eq $tests_run_by_target_all ] || die 92 "all() expected to run $tests_run_by_target_all but actually ran $commands_run tests" -} - -upgrade() { -if [ -n "$UPGRADE_TO" ]; then - # We need to tell test_MVU.sh to run some steps via sudo since we're - # actually installing from pgxn into a system directory. We also use a - # different port number to avoid conflicting with existing clusters. - test_cmd test/test_MVU.sh -s 55667 55778 $PGVERSION $UPGRADE_TO "$(get_path $PGVERSION)" "$(get_path $UPGRADE_TO)" -fi -} - - -######################################################## -# Install packages -packages="python-setuptools postgresql-common $(get_packages $PGVERSION)" - -if [ -n "$UPGRADE_TO" ]; then - packages="$packages $(get_packages $UPGRADE_TO)" -fi - -sudo apt-get update - -# bug: https://www.postgresql.org/message-id/20130508192711.GA9243@msgid.df7cb.de -sudo update-alternatives --remove-all postmaster.1.gz - -# stop all existing instances (because of https://github.com/travis-ci/travis-cookbooks/pull/221) -sudo service postgresql stop -# and make sure they don't come back -echo 'exit 0' | sudo tee /etc/init.d/postgresql -sudo chmod a+x /etc/init.d/postgresql - -sudo apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install $packages - -# Need to explicitly set which pg_config we want to use -export PG_CONFIG="$(get_path $PGVERSION)pg_config" -[ "$PG_CONFIG" != 'pg_config' ] - -# Make life easier for test_MVU.sh -sudo usermod -a -G postgres $USER - - -# Setup cluster -export PGPORT=55435 -export PGUSER=postgres -sudo pg_createcluster --start $PGVERSION test -p $PGPORT -- -A trust - -sudo easy_install pgxnclient - -set +x -total_tests=$((3 + $tests_run_by_target_all)) -for t in ${TARGETS:-sanity update upgrade all}; do - $t -done - -# You can use this to check tests that are failing pg_prove -#pg_prove -f --pset tuples_only=1 test/sql/unique.sql test/sql/check.sql || true - -if [ $tests_run -eq $total_tests ]; then - echo Ran $tests_run tests -elif [ $tests_run -gt 0 ]; then - echo "WARNING! ONLY RAN $tests_run OUT OF $total_tests TESTS!" - # We don't consider this an error... -else - echo No tests were run! - exit 2 -fi - -if [ -n "$failed" ]; then - echo - # $failed will have a leading space if it's not empty - echo "These test targets failed:$failed" - exit 1 -fi From c72a59e811eb7a4018451a2ada018fca785a7228 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 27 Nov 2021 12:36:20 -0500 Subject: [PATCH 1121/1195] Restore upgrade testing in GitHub Actions Required some changes to pgxn/docker-pgxn-tools to allow for an unprivileged user to use `sudo`, but with that in places the changes to `test/test_MVU.sh` are minor: * Use the versions for path args if none passed. * Some indentation tweaks * Use `$sudo` consistently * Remove run_make in favor of explict calls to `make` with `$sudo` used as appropriate. * Fall back on `whoami` if `$USER` is not set * Run tests without `sudo` Changes to `.github/workflows/test.yml` are mostly aesthetic, but the new "Upgrade" step makes sure that pgTAP is installed and installs the packages for the version to upgrade to before handing execution off to `test/test_MVU.sh`. With these changes, we should be ready to release v1.2.0. --- .github/workflows/test.yml | 23 +++--- test/test_MVU.sh | 140 +++++++++++++++++-------------------- 2 files changed, 80 insertions(+), 83 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ae11b80a7cfb..2bd5bf06a74d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: 🐘 Test +name: ✅ Test on: push: pull_request: @@ -21,9 +21,9 @@ jobs: - { version: 9.2, upgrade_to: 9.3, update_from: "" } # updatecheck is not supported prior to 9.3 - { version: 9.1, upgrade_to: 9.2, update_from: "" } # Also test pg_upgrade across many versions - # - { version: 9.1, upgrade_to: 14, update_from: 0.99.0 } - # - { version: 9.4, upgrade_to: 14, update_from: 0.99.0 } - name: 🐘 PostgreSQL ${{ matrix.version }} + - { version: 9.1, upgrade_to: 14, update_from: "", suffix: –14 } + - { version: 9.4, upgrade_to: 14, update_from: "", suffix: –14 } + name: 🐘 PostgreSQL ${{ matrix.version }}${{ matrix.suffix }} runs-on: ubuntu-latest container: pgxn/pgxn-tools env: @@ -39,16 +39,13 @@ jobs: # Test update. - run: 'if [ -d "$UPDATE_FROM" ]; then make uninstall clean updatecheck; fi' - # Test upgrade. - # - run: ./test/test_MVU.sh -s 55667 55778 "{{ matrix.version }}" "{{ matrix.upgrade_to }}" "/usr/lib/postgresql/{{ matrix.version }}/bin/" "/usr/lib/postgresql/{{ matrix.upgrade_to }}/bin/" - # Test all, install, test, test-serial, and test-parallel, both from clean # repo and repeated with existing build, with and without PARALLEL_CONN=1. - run: make uninstall clean all - run: make all - run: make uninstall clean install - run: make install - - run: "psql -Ec 'CREATE EXTENSION pgtap'" + - run: psql -Ec 'CREATE EXTENSION pgtap' - run: make uninstall clean test - run: make test - run: make uninstall clean test PARALLEL_CONN=1 @@ -61,3 +58,13 @@ jobs: - run: make test-parallel - run: make uninstall clean test-parallel PARALLEL_CONN=1 - run: make test-parallel PARALLEL_CONN=1 + + # Test upgrade last, since the new version's client will be preferred. + - if: ${{ matrix.upgrade_to != '' }} + name: Upgrade to ${{ matrix.upgrade_to }} + # Based on https://gist.github.com/petere/6023944 + # See also https://askubuntu.com/a/104912 for --force options + run: | + make install + apt-get install -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" postgresql-${{ matrix.upgrade_to }} postgresql-server-dev-${{ matrix.upgrade_to }} + sudo -u postgres test/test_MVU.sh -s 55432 55433 "${{ matrix.version }}" "${{ matrix.upgrade_to }}" diff --git a/test/test_MVU.sh b/test/test_MVU.sh index 81125debeecd..12464681397d 100755 --- a/test/test_MVU.sh +++ b/test/test_MVU.sh @@ -10,8 +10,8 @@ set -E -e -u -o pipefail BASEDIR=`dirname $0` if ! . $BASEDIR/../tools/util.sh; then - echo "FATAL: error sourcing $BASEDIR/../tools/util.sh" 1>&2 - exit 99 + echo "FATAL: error sourcing $BASEDIR/../tools/util.sh" 1>&2 + exit 99 fi trap err_report ERR @@ -19,12 +19,11 @@ debug 19 "Arguments: $@" rc=0 - byte_len() ( -[ $# -eq 1 ] || die 99 "Expected 1 argument, not $# ($@)" -LANG=C LC_ALL=C -debug 99 "byte_len($@) = ${#1}" -echo ${#1} + [ $# -eq 1 ] || die 99 "Expected 1 argument, not $# ($@)" + LANG=C LC_ALL=C + debug 99 "byte_len($@) = ${#1}" + echo ${#1} ) check_bin() { @@ -36,22 +35,22 @@ check_bin() { # mktemp on OS X results is a super-long path name that can cause problems, ie: # connection to database failed: Unix-domain socket path "/private/var/folders/rp/mv0457r17cg0xqyw5j7701892tlc0h/T/test_pgtap_upgrade.upgrade.7W4BLF/.s.PGSQL.50432" is too long (maximum 103 bytes) # -# This function looks for that condition and replaces the output with something more sane +# This function looks for that condition and replaces the output with something more legible short_tmpdir() ( -[ $# -eq 1 ] || die 99 "Expected 1 argument, not $# ($@)" -[ "$TMPDIR" != "" ] || die 99 '$TMPDIR not set' -out=$(mktemp -p '' -d $1.XXXXXX) -if echo "$out" | egrep -q '^(/private)?/var/folders'; then - newout=$(echo "$out" | sed -e "s#.*/$TMPDIR#$TMPDIR#") - debug 19 "replacing '$out' with '$newout'" -fi + [ $# -eq 1 ] || die 99 "Expected 1 argument, not $# ($@)" + [ "$TMPDIR" != "" ] || die 99 '$TMPDIR not set' + out=$(mktemp -p '' -d $1.XXXXXX) + if echo "$out" | egrep -q '^(/private)?/var/folders'; then + newout=$(echo "$out" | sed -e "s#.*/$TMPDIR#$TMPDIR#") + debug 19 "replacing '$out' with '$newout'" + fi -debug 9 "$0($@) = $out" -# Postgres blows up if this is too long. Technically the limit is 103 bytes, -# but need to account for the socket name, plus the fact that OS X might -# prepend '/private' to what we return. :( -[ $(byte_len "$out") -lt 75 ] || die 9 "short_tmpdir($@) returning a value >= 75 bytes ('$out')" -echo "$out" + debug 9 "$0($@) = $out" + # Postgres blows up if this is too long. Technically the limit is 103 bytes, + # but need to account for the socket name, plus the fact that OS X might + # prepend '/private' to what we return. :( + [ $(byte_len "$out") -lt 75 ] || die 9 "short_tmpdir($@) returning a value >= 75 bytes ('$out')" + echo "$out" ) banner() { @@ -62,47 +61,42 @@ banner() { echo } -run_make() ( -cd $(dirname $0)/.. -$sudo make $@ -) - modify_config() ( -# See below for definition of ctl_separator -if [ -z "$ctl_separator" ]; then - confDir=$PGDATA - conf=$confDir/postgresql.conf - debug 6 "$0: conf = $conf" + # See below for definition of ctl_separator + if [ -z "$ctl_separator" ]; then + confDir=$PGDATA + conf=$confDir/postgresql.conf + debug 6 "$0: conf = $conf" + + debug 0 "Modifying NATIVE $conf" + + echo "port = $PGPORT" >> $conf + else + confDir="/etc/postgresql/$1/$cluster_name" + conf="$confDir/postgresql.conf" + debug 6 "$0: confDir = $confDir conf=$conf" + debug_ls 9 -la $confDir + + debug 0 "Modifying DEBIAN $confDir and $PGDATA" + + debug 2 ln -s $conf $PGDATA/ + ln -s $conf $PGDATA/ + # Some versions also have a conf.d ... + if [ -e "$confDir/conf.d" ]; then + debug 2 ln -s $confDir/conf.d $PGDATA/ + ln -s $confDir/conf.d $PGDATA/ + fi + debug_ls 8 -la $PGDATA - debug 0 "Modifying NATIVE $conf" + # Shouldn't need to muck with PGPORT... - echo "port = $PGPORT" >> $conf -else - confDir="/etc/postgresql/$1/$cluster_name" - conf="$confDir/postgresql.conf" - debug 6 "$0: confDir = $confDir conf=$conf" - debug_ls 9 -la $confDir - - debug 0 "Modifying DEBIAN $confDir and $PGDATA" - - debug 2 ln -s $conf $PGDATA/ - ln -s $conf $PGDATA/ - # Some versions also have a conf.d ... - if [ -e "$confDir/conf.d" ]; then - debug 2 ln -s $confDir/conf.d $PGDATA/ - ln -s $confDir/conf.d $PGDATA/ + # GUC changed somewhere between 9.1 and 9.5, so read config to figure out correct value + guc=$(grep unix_socket_director $conf | sed -e 's/^# *//' | cut -d ' ' -f 1) + debug 4 "$0: guc = $guc" + echo "$guc = '/tmp'" >> $conf fi - debug_ls 8 -la $PGDATA - - # Shouldn't need to muck with PGPORT... - # GUC changed somewhere between 9.1 and 9.5, so read config to figure out correct value - guc=$(grep unix_socket_director $conf | sed -e 's/^# *//' | cut -d ' ' -f 1) - debug 4 "$0: guc = $guc" - echo "$guc = '/tmp'" >> $conf -fi - -echo "synchronous_commit = off" >> $conf + echo "synchronous_commit = off" >> $conf ) ############################# @@ -127,8 +121,8 @@ OLD_PORT=$1 NEW_PORT=$2 OLD_VERSION=$3 NEW_VERSION=$4 -OLD_PATH=$5 -NEW_PATH=$6 +OLD_PATH="${5:-/usr/lib/postgresql/$OLD_VERSION/bin}" +NEW_PATH="${5:-/usr/lib/postgresql/$NEW_VERSION/bin}" export PGDATABASE=test_pgtap_upgrade @@ -174,15 +168,15 @@ if command -v pg_ctlcluster > /dev/null; then export PGHOST=/tmp # And force current user - export PGUSER=$USER + export PGUSER=${USER:-$(whoami)} - old_initdb="sudo pg_createcluster $OLD_VERSION $cluster_name -u $USER -p $OLD_PORT -d $old_dir -- -A trust" - old_pg_ctl="sudo pg_ctlcluster $OLD_VERSION test_pg_upgrade" - new_initdb="sudo pg_createcluster $NEW_VERSION $cluster_name -u $USER -p $NEW_PORT -d $new_dir -- -A trust" - new_pg_ctl="sudo pg_ctlcluster $NEW_VERSION test_pg_upgrade" + old_initdb="$sudo pg_createcluster $OLD_VERSION $cluster_name -u $PGUSER -p $OLD_PORT -d $old_dir -- -A trust" + old_pg_ctl="$sudo pg_ctlcluster $OLD_VERSION test_pg_upgrade" + new_initdb="$sudo pg_createcluster $NEW_VERSION $cluster_name -u $PGUSER -p $NEW_PORT -d $new_dir -- -A trust" + new_pg_ctl="$sudo pg_ctlcluster $NEW_VERSION test_pg_upgrade" # See also ../.github/workflows/test.yml - new_pg_upgrade=/usr/lib/postgresql/$NEW_VERSION/bin/pg_upgrade + new_pg_upgrade="/usr/lib/postgresql/$NEW_VERSION/bin/pg_upgrade" else ctl_separator='' old_initdb="$(find_at_path "$OLD_PATH" initdb) -D $old_dir -N" @@ -218,7 +212,7 @@ echo "Installing pgtap" # If user requested sudo then we need to use it for the install step. TODO: # it'd be nice to move this into the Makefile, if the PGXS make stuff allows # it... -run_make clean install +$sudo make clean install banner "Loading extension" psql -c 'CREATE EXTENSION pgtap' # Also uses PGPORT @@ -226,8 +220,6 @@ psql -c 'CREATE EXTENSION pgtap' # Also uses PGPORT echo "Stopping OLD postgres via $old_pg_ctl" $old_pg_ctl stop $ctl_separator -w # older versions don't support --wait - - ################################################################################################## banner "Running pg_upgrade" export PGDATA=$new_dir @@ -258,8 +250,6 @@ modify_config $NEW_VERSION fi ) - - ################################################################################################## banner "Testing UPGRADED cluster" @@ -286,7 +276,7 @@ export PG_CONFIG=$(find_at_path "$NEW_PATH" pg_config) [ -x "$PG_CONFIG" ] || ( debug_ls 1 "$NEW_PATH"; die 4 "unable to find executable pg_config at $NEW_PATH" ) # When crossing certain upgrade boundaries we need to exclude some tests -# because they test functions not available in the previous version. +# because the test functions are not available in the previous version. int_ver() { local ver ver=$(echo $1 | tr -d .) @@ -316,7 +306,8 @@ add_exclude 9.6 10 test/sql/partitions.sql #(cd $(dirname $0)/..; pg_prove -v --pset tuples_only=1 test/sql/throwtap.sql) export EXCLUDE_TEST_FILES -run_make clean test +$sudo make clean +make test if [ -n "$EXCLUDE_TEST_FILES" ]; then banner "Rerunning test after a reinstall due to version differences" @@ -324,10 +315,9 @@ if [ -n "$EXCLUDE_TEST_FILES" ]; then export EXCLUDED_TEST_FILES='' # Need to build with the new version, then install - run_make install + $sudo make install psql -E -c 'DROP EXTENSION pgtap; CREATE EXTENSION pgtap;' - run_make test + make test fi - From 6ca7aec6d7e9ddca05fcecba3ac9132a446e2511 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 5 Dec 2021 13:09:16 -0500 Subject: [PATCH 1122/1195] Timestamp v1.2.0 And update release instructions. --- Changes | 2 +- META.json | 8 ++++---- Makefile | 2 +- README.md | 2 +- contrib/pgtap.spec | 7 +++++-- doc/pgtap.mmd | 9 +++++++-- release.md | 34 ++++++++++++++++++++-------------- 7 files changed, 39 insertions(+), 25 deletions(-) diff --git a/Changes b/Changes index f0438b06f2cb..c8ecd5ab0a0a 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -1.2.0 +1.2.0 2021-12-05T18:08:13Z -------------------------- * Made the description field optional in `has_view()` and `hasnt_view()` when specifying schema (#230). Thanks to Godwottery for the patch! diff --git a/META.json b/META.json index 892656a7fae2..18fffdaf544b 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "1.1.0", + "version": "1.2.0", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "1.1.0" + "version": "1.2.0" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "1.1.0" + "version": "1.2.0" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "1.1.0" + "version": "1.2.0" } }, "resources": { diff --git a/Makefile b/Makefile index fb5cf84513db..766581c8cb1b 100644 --- a/Makefile +++ b/Makefile @@ -271,7 +271,7 @@ sql/uninstall_pgtap.sql: sql/pgtap.sql test/setup.sql # TODO: the sed command needs the equivalent of bash's PIPEFAIL; should just replace this with some perl magic sql/pgtap-static.sql: sql/pgtap.sql.in cp $< $@.tmp - for p in `ls compat/install-*.patch | sort -rn`; do \ + @for p in `ls compat/install-*.patch | sort -rn`; do \ echo; echo '***' "Patching pgtap-static.sql with $$p"; \ sed -e 's#sql/pgtap.sql#sql/pgtap-static.sql.tmp#g' "$$p" | patch -p0; \ done diff --git a/README.md b/README.md index e8b1eb1da70e..55a23d7a19c4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 1.1.0 +pgTAP 1.2.0 ============ [pgTAP](https://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 91882f674599..b088bcba56ac 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,7 +1,7 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 1.1.0 -Release: 2%{?dist} +Version: 1.2.0 +Release: 1%{?dist} Group: Applications/Databases License: PostgreSQL URL: https://pgtap.org/ @@ -41,6 +41,9 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog +* Sun Dec 5 2021 David E. Wheeler 1.2.0-1 +- Update to 1.2.0 + * Sat Oct 24 2020 Jim Nasby 1.1.0-2 - Remove last vestiges of pre-PostgreSQL 9.1 - Use https for URLs diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index e8d3d45b743a..979c8592fbdf 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 1.1.0 +pgTAP 1.2.0 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and @@ -8683,11 +8683,16 @@ PostgreSQL. This helps you to understand any side-effects. To see the specifics for each version of PostgreSQL, consult the files in the `compat/` directory in the pgTAP distribution. -10 and Up +11 and Up --------- No changes. Everything should just work. +10 and Down +----------- +* The stored procedure-testing funtions are not available, because stored + procedures were not introduced until 11. + 9.6 and Down ------------ * The partition-testing functions are not available, because partitions were diff --git a/release.md b/release.md index 7f36d25d43b0..4a7010472b5b 100644 --- a/release.md +++ b/release.md @@ -9,10 +9,11 @@ Here are the steps to take to make a release of pgTAP: * Review and merge any appropriate [pull requests](https://github.com/theory/pgtap/pulls). -* Test on all supported PostgreSQL versions, starting with the latest version - (12) and moving backward in order (9.6, 9.5, 9.4, etc.). - [pgenv](https://github.com/theory/pgenv/) is a handy tool for installing and - switching between versions. For each version, ensure that: +* Make sure that [all tests](https://github.com/theory/pgtap/actions) pass on + all supported versions of Postgres. If you want to run the tests manually, + you can use the [pgxn-utils Docker image](https://github.com/pgxn/docker-pgxn-tools) + or [pgenv](https://github.com/theory/pgenv/) to install and + switch between versions. For each version, ensure that: + Patches apply cleanly (try to eliminate Hunk warnings for patches to `pgtap.sql` itself, usually by fixing line numbers) @@ -69,7 +70,7 @@ Here are the steps to take to make a release of pgTAP: `DOC SANS pgTAP x.xx` section, and then remove the first `

  • ` element that says "pgTAP x.xx". - + Sanity-check that everything looks right; use `git diff` to make sure + + Review to ensure that everything looks right; use `git diff` to make sure nothing important was lost. It should mainly be additions. + Commit the changes, but don't push them yet. @@ -85,21 +86,26 @@ Here are the steps to take to make a release of pgTAP: Paste that into the line with the new version, maybe increment by a minute to account for the time you'll need to actually do the release. -* Commit the timestamp and tag it: +* Commit the timestamp and push it: - git ci -m 'Timestamp v0.98.0.' - git tag -sm 'Tag v0.98.0.' v0.98.0 + git ci -m 'Timestamp v0.98.0.' + git push + +* Once again make sure [all tests](https://github.com/theory/pgtap/actions) + pass. Fix any that fail. -* Package the source and submit to [PGXN](https://manager.pgxn.org/). +* Once all tests pass, tag the release with its semantic version (including + the leading `v`) and push the tag. - gem install pgxn_utils - git archive --format zip --prefix=pgtap-1.0.0/ \ - --output pgtap-1.0.0.zip master + git tag -sm 'Tag v0.98.0.' v0.98.0 + git push --tags -* Push all the changes to GitHub. +* Monitor the [release workflow](https://github.com/theory/pgtap/actions/workflows/release.yml) + to make sure the new version is released on both PGXN and GitHub. + +* Push the `gh-pages` branch: git push - git push --tags git push origin up/gh-pages:gh-pages * Increment the minor version to kick off development for the next release. From 6bae661a3bdc1998a829f44c10fe05a49391f7f3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 5 Dec 2021 13:17:13 -0500 Subject: [PATCH 1123/1195] Increment to v1.2.1 --- Changes | 3 +++ META.json | 8 ++++---- README.md | 2 +- contrib/pgtap.spec | 2 +- doc/pgtap.mmd | 2 +- pgtap.control | 2 +- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Changes b/Changes index c8ecd5ab0a0a..152f3f8184a2 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Revision history for pgTAP ========================== +1.2.1 +-------------------------- + 1.2.0 2021-12-05T18:08:13Z -------------------------- * Made the description field optional in `has_view()` and `hasnt_view()` when diff --git a/META.json b/META.json index 18fffdaf544b..4b4347342a8c 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "1.2.0", + "version": "1.2.1", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "1.2.0" + "version": "1.2.1" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "1.2.0" + "version": "1.2.1" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "1.2.0" + "version": "1.2.1" } }, "resources": { diff --git a/README.md b/README.md index 55a23d7a19c4..ddc782cb5a96 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 1.2.0 +pgTAP 1.2.1 ============ [pgTAP](https://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index b088bcba56ac..6b83c2bcb460 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 1.2.0 +Version: 1.2.1 Release: 1%{?dist} Group: Applications/Databases License: PostgreSQL diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 979c8592fbdf..b5fe21792802 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 1.2.0 +pgTAP 1.2.1 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/pgtap.control b/pgtap.control index 19213f0c9d69..9223dce62f6e 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '1.2.0' +default_version = '1.2.1' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true From 51c6187bcb620dbf5dcad3940ff4a37cba955d03 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 17 Jan 2022 11:02:27 -0500 Subject: [PATCH 1124/1195] Do not test on tag --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2bd5bf06a74d..8d4a3a681df9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,7 @@ name: ✅ Test on: push: + branches: ['*'] pull_request: schedule: - cron: '0 14 3 * *' # Monthly at 2pm on the third From 364facc6975b00d8977539c4ce384241b5809c6c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 17 Apr 2022 17:06:20 -0400 Subject: [PATCH 1125/1195] Just use Perl to create uninstall script Rather than grep plus Perl plus two calls to `sed`. Seems like sometimes `sed` doesn't do all that we need it too, and we were already using Perl, so just move the replacement expressions into Perl loop. Fixes #270. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 766581c8cb1b..96a4d11f44a1 100644 --- a/Makefile +++ b/Makefile @@ -261,7 +261,7 @@ ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][012]|8[.][1234])" && echo yes | endif sql/uninstall_pgtap.sql: sql/pgtap.sql test/setup.sql - grep '^CREATE ' sql/pgtap.sql | $(PERL) -e 'for (reverse ) { chomp; s/CREATE (OR REPLACE )?/DROP /; print "$$_;\n" }' | sed 's/DROP \(FUNCTION\|VIEW\|TYPE\) /DROP \1 IF EXISTS /' | sed -E 's/ (DEFAULT|=)[ ]+[a-zA-Z0-9]+//g' > sql/uninstall_pgtap.sql + $(PERL) -e 'for (grep { /^CREATE /} reverse <>) { chomp; s/CREATE (OR REPLACE )?/DROP /; s/DROP (FUNCTION|VIEW|TYPE) /DROP $$1 IF EXISTS /; s/ (DEFAULT|=)[ ]+[a-zA-Z0-9]+//g; print "$$_;\n" }' $< > $@ # # Support for static install files From 335e3187422c5359df5b297b219d4dd832750af9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 17 Apr 2022 14:59:05 -0400 Subject: [PATCH 1126/1195] Fix xUnit runner to handle all errors. Since 8fb3233 (released in 0.95.0), xUnit tests have run each test function as a subtest and tried to catch unexpected errors and report them as failures rather than just exit complete. However, this worked only for PL/pgSQL `RAISE EXCEPTION` errors, which is a small subset. So change the behavior to catch virtually all exceptions by using `EXCEPTION WHEN OTHERS`. Confirmed by adding a divide by zero error to a test function in the `runtests` test. This required re-generation of the expected test output for all supported versions. So create a comment in `test/sql/runtests.sql` documenting which output files go with which versions, and regenerate four of those files and delete two, which are no longer needed since pgTAP no longer supports verions of PostgreSQL prior to 9.1. Also, fix a pluralization when reporting xUnit test failures so it no longer emits "failed 1 tests" but the correct "failed 1 test". --- Changes | 6 ++ sql/pgtap--1.2.0--1.2.1.sql | 145 ++++++++++++++++++++++++++++ sql/pgtap.sql.in | 4 +- test/expected/runjusttests.out | 2 +- test/expected/runjusttests_1.out | 2 +- test/expected/runjusttests_2.out | 2 +- test/expected/runjusttests_3.out | 2 +- test/expected/runjusttests_4.out | 2 +- test/expected/runjusttests_5.out | 2 +- test/expected/runjusttests_6.out | 2 +- test/expected/runtests.out | 69 +++++++++----- test/expected/runtests_1.out | 71 +++++++++----- test/expected/runtests_2.out | 81 +++++++++++----- test/expected/runtests_3.out | 80 +++++++++++----- test/expected/runtests_4.out | 64 ++++++++----- test/expected/runtests_5.out | 66 ------------- test/expected/runtests_6.out | 158 ------------------------------- test/setup.sql | 1 + test/sql/runtests.sql | 16 ++++ 19 files changed, 426 insertions(+), 349 deletions(-) create mode 100644 sql/pgtap--1.2.0--1.2.1.sql delete mode 100644 test/expected/runtests_5.out delete mode 100644 test/expected/runtests_6.out diff --git a/Changes b/Changes index 152f3f8184a2..1884633383f3 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,12 @@ Revision history for pgTAP 1.2.1 -------------------------- +* Fixed an issue with xUnit tests where they would exit immediately on + unexpected errors (aside from PL/pgSQL RAISE errors) rather than exit just the + current test function. Thanks to @kzathey for the report and to @fjf2002 for + the analysis. +* Fixed a pluralization error reporting xUnit test failures to report "failed 1 + test" instead of "failed 1 tests". 1.2.0 2021-12-05T18:08:13Z -------------------------- diff --git a/sql/pgtap--1.2.0--1.2.1.sql b/sql/pgtap--1.2.0--1.2.1.sql new file mode 100644 index 000000000000..9556a49d6a72 --- /dev/null +++ b/sql/pgtap--1.2.0--1.2.1.sql @@ -0,0 +1,145 @@ +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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index f66510cfe1ac..15a216aa90c2 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -6622,13 +6622,13 @@ BEGIN tok := FALSE; RETURN NEXT ' ' || diag( 'Looks like you failed ' || ffaild || ' test' || - CASE tfaild WHEN 1 THEN '' ELSE 's' END + CASE ffaild WHEN 1 THEN '' ELSE 's' END || ' of ' || fnumb ); END IF; END IF; - EXCEPTION WHEN raise_exception THEN + EXCEPTION WHEN OTHERS THEN -- Something went wrong. Record that fact. errstate := SQLSTATE; errmsg := SQLERRM; diff --git a/test/expected/runjusttests.out b/test/expected/runjusttests.out index 1885d9c11cf5..fc46be512e6c 100644 --- a/test/expected/runjusttests.out +++ b/test/expected/runjusttests.out @@ -42,7 +42,7 @@ ok 5 - whatever.testthis not ok 2 - this test intentionally fails # Failed test 2: "this test intentionally fails" 1..2 - # Looks like you failed 1 tests of 2 + # Looks like you failed 1 test of 2 not ok 6 - whatever.testy # Failed test 6: "whatever.testy" # Subtest: whatever.testz() diff --git a/test/expected/runjusttests_1.out b/test/expected/runjusttests_1.out index 89f40bb4cee7..6cdfaa5f3181 100644 --- a/test/expected/runjusttests_1.out +++ b/test/expected/runjusttests_1.out @@ -42,7 +42,7 @@ ok 5 - whatever.testthis not ok 2 - this test intentionally fails # Failed test 2: "this test intentionally fails" 1..2 - # Looks like you failed 1 tests of 2 + # Looks like you failed 1 test of 2 not ok 6 - whatever.testy # Failed test 6: "whatever.testy" # Subtest: whatever.testz() diff --git a/test/expected/runjusttests_2.out b/test/expected/runjusttests_2.out index 0b45fdce3fb9..2a5acf4d9ad9 100644 --- a/test/expected/runjusttests_2.out +++ b/test/expected/runjusttests_2.out @@ -37,7 +37,7 @@ ok 5 - whatever.testthis not ok 2 - this test intentionally fails # Failed test 2: "this test intentionally fails" 1..2 - # Looks like you failed 1 tests of 2 + # Looks like you failed 1 test of 2 not ok 6 - whatever.testy # Failed test 6: "whatever.testy" # Subtest: whatever.testz() diff --git a/test/expected/runjusttests_3.out b/test/expected/runjusttests_3.out index 3814a9ef9405..7081f578dbb3 100644 --- a/test/expected/runjusttests_3.out +++ b/test/expected/runjusttests_3.out @@ -37,7 +37,7 @@ ok 5 - whatever.testthis not ok 2 - this test intentionally fails # Failed test 2: "this test intentionally fails" 1..2 - # Looks like you failed 1 tests of 2 + # Looks like you failed 1 test of 2 not ok 6 - whatever.testy # Failed test 6: "whatever.testy" # Subtest: whatever.testz() diff --git a/test/expected/runjusttests_4.out b/test/expected/runjusttests_4.out index 767b0ab63ecf..afb90028cc88 100644 --- a/test/expected/runjusttests_4.out +++ b/test/expected/runjusttests_4.out @@ -30,7 +30,7 @@ ok 5 - whatever.testthis not ok 2 - this test intentionally fails # Failed test 2: "this test intentionally fails" 1..2 - # Looks like you failed 1 tests of 2 + # Looks like you failed 1 test of 2 not ok 6 - whatever.testy # Failed test 6: "whatever.testy" # Subtest: whatever.testz() diff --git a/test/expected/runjusttests_5.out b/test/expected/runjusttests_5.out index 54862df7e430..e65b83b0ec54 100644 --- a/test/expected/runjusttests_5.out +++ b/test/expected/runjusttests_5.out @@ -43,7 +43,7 @@ ok 5 - whatever.testthis not ok 2 - this test intentionally fails # Failed test 2: "this test intentionally fails" 1..2 - # Looks like you failed 1 tests of 2 + # Looks like you failed 1 test of 2 not ok 6 - whatever.testy # Failed test 6: "whatever.testy" # Subtest: whatever.testz() diff --git a/test/expected/runjusttests_6.out b/test/expected/runjusttests_6.out index 5dac15ac91b3..cfc3ed3b3444 100644 --- a/test/expected/runjusttests_6.out +++ b/test/expected/runjusttests_6.out @@ -30,7 +30,7 @@ ok 5 - whatever.testthis not ok 2 - this test intentionally fails # Failed test 2: "this test intentionally fails" 1..2 - # Looks like you failed 1 tests of 2 + # Looks like you failed 1 test of 2 not ok 6 - whatever.testy # Failed test 6: "whatever.testy" # Subtest: whatever.testz() diff --git a/test/expected/runtests.out b/test/expected/runtests.out index 38e2e7dc51e7..256e11ad5050 100644 --- a/test/expected/runtests.out +++ b/test/expected/runtests.out @@ -11,6 +11,18 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" + # Subtest: whatever.testdividebyzero() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: 22012: division by zero + # CONTEXT: + # SQL function "testdividebyzero" during startup + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 + # SQL function "runtests" statement 1 +not ok 4 - whatever.testdividebyzero +# Failed test 4: "whatever.testdividebyzero" # Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table @@ -21,7 +33,7 @@ ok 3 - whatever."test ident" ok 7 - teardown ok 8 - teardown more 1..8 -ok 4 - whatever.testplpgsql +ok 5 - whatever.testplpgsql # Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table @@ -35,13 +47,14 @@ ok 4 - whatever.testplpgsql # CONSTRAINT: CONSTRAINT # TYPE: TYPE # CONTEXT: + # PL/pgSQL function __die() line 3 at RAISE # SQL statement "SELECT __die();" # PL/pgSQL function whatever.testplpgsqldie() line 23 at EXECUTE # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement # SQL function "runtests" statement 1 # SQL function "runtests" statement 1 -not ok 5 - whatever.testplpgsqldie -# Failed test 5: "whatever.testplpgsqldie" +not ok 6 - whatever.testplpgsqldie +# Failed test 6: "whatever.testplpgsqldie" # Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table @@ -51,7 +64,7 @@ not ok 5 - whatever.testplpgsqldie ok 6 - teardown ok 7 - teardown more 1..7 -ok 6 - whatever.testthis +ok 7 - whatever.testthis # Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table @@ -62,8 +75,8 @@ ok 6 - whatever.testthis ok 6 - teardown more 1..6 # Looks like you failed 1 test of 6 -not ok 7 - whatever.testy -# Failed test 7: "whatever.testy" +not ok 8 - whatever.testy +# Failed test 8: "whatever.testy" # Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table @@ -72,11 +85,11 @@ not ok 7 - whatever.testy ok 5 - teardown ok 6 - teardown more 1..6 -ok 8 - whatever.testz -ok 9 - shutting down -ok 10 - shutting down more -1..10 -# Looks like you failed 2 tests of 10 +ok 9 - whatever.testz +ok 10 - shutting down +ok 11 - shutting down more +1..11 +# Looks like you failed 3 tests of 11 ok 1 - starting up ok 2 - starting up some more # Subtest: whatever."test ident"() @@ -89,6 +102,17 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" + # Subtest: whatever.testdividebyzero() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: 22012: division by zero + # CONTEXT: + # SQL function "testdividebyzero" during startup + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 +not ok 4 - whatever.testdividebyzero +# Failed test 4: "whatever.testdividebyzero" # Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table @@ -99,7 +123,7 @@ ok 3 - whatever."test ident" ok 7 - teardown ok 8 - teardown more 1..8 -ok 4 - whatever.testplpgsql +ok 5 - whatever.testplpgsql # Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table @@ -113,12 +137,13 @@ ok 4 - whatever.testplpgsql # CONSTRAINT: CONSTRAINT # TYPE: TYPE # CONTEXT: + # PL/pgSQL function __die() line 3 at RAISE # SQL statement "SELECT __die();" # PL/pgSQL function whatever.testplpgsqldie() line 23 at EXECUTE # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement # SQL function "runtests" statement 1 -not ok 5 - whatever.testplpgsqldie -# Failed test 5: "whatever.testplpgsqldie" +not ok 6 - whatever.testplpgsqldie +# Failed test 6: "whatever.testplpgsqldie" # Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table @@ -128,7 +153,7 @@ not ok 5 - whatever.testplpgsqldie ok 6 - teardown ok 7 - teardown more 1..7 -ok 6 - whatever.testthis +ok 7 - whatever.testthis # Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table @@ -139,8 +164,8 @@ ok 6 - whatever.testthis ok 6 - teardown more 1..6 # Looks like you failed 1 test of 6 -not ok 7 - whatever.testy -# Failed test 7: "whatever.testy" +not ok 8 - whatever.testy +# Failed test 8: "whatever.testy" # Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table @@ -149,8 +174,8 @@ not ok 7 - whatever.testy ok 5 - teardown ok 6 - teardown more 1..6 -ok 8 - whatever.testz -ok 9 - shutting down -ok 10 - shutting down more -1..10 -# Looks like you failed 2 tests of 10 +ok 9 - whatever.testz +ok 10 - shutting down +ok 11 - shutting down more +1..11 +# Looks like you failed 3 tests of 11 diff --git a/test/expected/runtests_1.out b/test/expected/runtests_1.out index a2e106611157..58461f1d742a 100644 --- a/test/expected/runtests_1.out +++ b/test/expected/runtests_1.out @@ -11,6 +11,18 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" + # Subtest: whatever.testdividebyzero() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: 22012: division by zero + # CONTEXT: + # SQL function "testdividebyzero" during startup + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 + # SQL function "runtests" statement 1 +not ok 4 - whatever.testdividebyzero +# Failed test 4: "whatever.testdividebyzero" # Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table @@ -21,7 +33,7 @@ ok 3 - whatever."test ident" ok 7 - teardown ok 8 - teardown more 1..8 -ok 4 - whatever.testplpgsql +ok 5 - whatever.testplpgsql # Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table @@ -36,12 +48,12 @@ ok 4 - whatever.testplpgsql # TYPE: TYPE # CONTEXT: # SQL statement "SELECT __die();" - # PL/pgSQL function whatever.testplpgsqldie() line 23 at EXECUTE statement + # PL/pgSQL function whatever.testplpgsqldie() line 23 at EXECUTE # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement # SQL function "runtests" statement 1 # SQL function "runtests" statement 1 -not ok 5 - whatever.testplpgsqldie -# Failed test 5: "whatever.testplpgsqldie" +not ok 6 - whatever.testplpgsqldie +# Failed test 6: "whatever.testplpgsqldie" # Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table @@ -51,7 +63,7 @@ not ok 5 - whatever.testplpgsqldie ok 6 - teardown ok 7 - teardown more 1..7 -ok 6 - whatever.testthis +ok 7 - whatever.testthis # Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table @@ -62,8 +74,8 @@ ok 6 - whatever.testthis ok 6 - teardown more 1..6 # Looks like you failed 1 test of 6 -not ok 7 - whatever.testy -# Failed test 7: "whatever.testy" +not ok 8 - whatever.testy +# Failed test 8: "whatever.testy" # Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table @@ -72,11 +84,11 @@ not ok 7 - whatever.testy ok 5 - teardown ok 6 - teardown more 1..6 -ok 8 - whatever.testz -ok 9 - shutting down -ok 10 - shutting down more -1..10 -# Looks like you failed 2 tests of 10 +ok 9 - whatever.testz +ok 10 - shutting down +ok 11 - shutting down more +1..11 +# Looks like you failed 3 tests of 11 ok 1 - starting up ok 2 - starting up some more # Subtest: whatever."test ident"() @@ -89,6 +101,17 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" + # Subtest: whatever.testdividebyzero() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: 22012: division by zero + # CONTEXT: + # SQL function "testdividebyzero" during startup + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 +not ok 4 - whatever.testdividebyzero +# Failed test 4: "whatever.testdividebyzero" # Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table @@ -99,7 +122,7 @@ ok 3 - whatever."test ident" ok 7 - teardown ok 8 - teardown more 1..8 -ok 4 - whatever.testplpgsql +ok 5 - whatever.testplpgsql # Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table @@ -114,11 +137,11 @@ ok 4 - whatever.testplpgsql # TYPE: TYPE # CONTEXT: # SQL statement "SELECT __die();" - # PL/pgSQL function whatever.testplpgsqldie() line 23 at EXECUTE statement + # PL/pgSQL function whatever.testplpgsqldie() line 23 at EXECUTE # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement # SQL function "runtests" statement 1 -not ok 5 - whatever.testplpgsqldie -# Failed test 5: "whatever.testplpgsqldie" +not ok 6 - whatever.testplpgsqldie +# Failed test 6: "whatever.testplpgsqldie" # Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table @@ -128,7 +151,7 @@ not ok 5 - whatever.testplpgsqldie ok 6 - teardown ok 7 - teardown more 1..7 -ok 6 - whatever.testthis +ok 7 - whatever.testthis # Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table @@ -139,8 +162,8 @@ ok 6 - whatever.testthis ok 6 - teardown more 1..6 # Looks like you failed 1 test of 6 -not ok 7 - whatever.testy -# Failed test 7: "whatever.testy" +not ok 8 - whatever.testy +# Failed test 8: "whatever.testy" # Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table @@ -149,8 +172,8 @@ not ok 7 - whatever.testy ok 5 - teardown ok 6 - teardown more 1..6 -ok 8 - whatever.testz -ok 9 - shutting down -ok 10 - shutting down more -1..10 -# Looks like you failed 2 tests of 10 +ok 9 - whatever.testz +ok 10 - shutting down +ok 11 - shutting down more +1..11 +# Looks like you failed 3 tests of 11 diff --git a/test/expected/runtests_2.out b/test/expected/runtests_2.out index af097face9e8..eb148b927924 100644 --- a/test/expected/runtests_2.out +++ b/test/expected/runtests_2.out @@ -11,6 +11,18 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" + # Subtest: whatever.testdividebyzero() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: 22012: division by zero + # CONTEXT: + # SQL function "testdividebyzero" during startup + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 + # SQL function "runtests" statement 1 +not ok 4 - whatever.testdividebyzero +# Failed test 4: "whatever.testdividebyzero" # Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table @@ -21,7 +33,7 @@ ok 3 - whatever."test ident" ok 7 - teardown ok 8 - teardown more 1..8 -ok 4 - whatever.testplpgsql +ok 5 - whatever.testplpgsql # Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table @@ -29,14 +41,19 @@ ok 4 - whatever.testplpgsql # Test died: P0001: This test should die, but not halt execution. # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. # DETAIL: DETAIL + # SCHEMA: SCHEMA + # TABLE: TABLE + # COLUMN: COLUMN + # CONSTRAINT: CONSTRAINT + # TYPE: TYPE # CONTEXT: # SQL statement "SELECT __die();" - # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement + # PL/pgSQL function whatever.testplpgsqldie() line 23 at EXECUTE statement # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement # SQL function "runtests" statement 1 # SQL function "runtests" statement 1 -not ok 5 - whatever.testplpgsqldie -# Failed test 5: "whatever.testplpgsqldie" +not ok 6 - whatever.testplpgsqldie +# Failed test 6: "whatever.testplpgsqldie" # Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table @@ -46,7 +63,7 @@ not ok 5 - whatever.testplpgsqldie ok 6 - teardown ok 7 - teardown more 1..7 -ok 6 - whatever.testthis +ok 7 - whatever.testthis # Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table @@ -57,8 +74,8 @@ ok 6 - whatever.testthis ok 6 - teardown more 1..6 # Looks like you failed 1 test of 6 -not ok 7 - whatever.testy -# Failed test 7: "whatever.testy" +not ok 8 - whatever.testy +# Failed test 8: "whatever.testy" # Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table @@ -67,11 +84,11 @@ not ok 7 - whatever.testy ok 5 - teardown ok 6 - teardown more 1..6 -ok 8 - whatever.testz -ok 9 - shutting down -ok 10 - shutting down more -1..10 -# Looks like you failed 2 tests of 10 +ok 9 - whatever.testz +ok 10 - shutting down +ok 11 - shutting down more +1..11 +# Looks like you failed 3 tests of 11 ok 1 - starting up ok 2 - starting up some more # Subtest: whatever."test ident"() @@ -84,6 +101,17 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" + # Subtest: whatever.testdividebyzero() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: 22012: division by zero + # CONTEXT: + # SQL function "testdividebyzero" during startup + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 +not ok 4 - whatever.testdividebyzero +# Failed test 4: "whatever.testdividebyzero" # Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table @@ -94,7 +122,7 @@ ok 3 - whatever."test ident" ok 7 - teardown ok 8 - teardown more 1..8 -ok 4 - whatever.testplpgsql +ok 5 - whatever.testplpgsql # Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table @@ -102,13 +130,18 @@ ok 4 - whatever.testplpgsql # Test died: P0001: This test should die, but not halt execution. # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. # DETAIL: DETAIL + # SCHEMA: SCHEMA + # TABLE: TABLE + # COLUMN: COLUMN + # CONSTRAINT: CONSTRAINT + # TYPE: TYPE # CONTEXT: # SQL statement "SELECT __die();" - # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement + # PL/pgSQL function whatever.testplpgsqldie() line 23 at EXECUTE statement # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement # SQL function "runtests" statement 1 -not ok 5 - whatever.testplpgsqldie -# Failed test 5: "whatever.testplpgsqldie" +not ok 6 - whatever.testplpgsqldie +# Failed test 6: "whatever.testplpgsqldie" # Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table @@ -118,7 +151,7 @@ not ok 5 - whatever.testplpgsqldie ok 6 - teardown ok 7 - teardown more 1..7 -ok 6 - whatever.testthis +ok 7 - whatever.testthis # Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table @@ -129,8 +162,8 @@ ok 6 - whatever.testthis ok 6 - teardown more 1..6 # Looks like you failed 1 test of 6 -not ok 7 - whatever.testy -# Failed test 7: "whatever.testy" +not ok 8 - whatever.testy +# Failed test 8: "whatever.testy" # Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table @@ -139,8 +172,8 @@ not ok 7 - whatever.testy ok 5 - teardown ok 6 - teardown more 1..6 -ok 8 - whatever.testz -ok 9 - shutting down -ok 10 - shutting down more -1..10 -# Looks like you failed 2 tests of 10 +ok 9 - whatever.testz +ok 10 - shutting down +ok 11 - shutting down more +1..11 +# Looks like you failed 3 tests of 11 diff --git a/test/expected/runtests_3.out b/test/expected/runtests_3.out index 79c65048e69a..e1df08ca7c8c 100644 --- a/test/expected/runtests_3.out +++ b/test/expected/runtests_3.out @@ -11,6 +11,18 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" + # Subtest: whatever.testdividebyzero() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: 22012: division by zero + # CONTEXT: + # SQL function "testdividebyzero" during startup + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 + # SQL function "runtests" statement 1 +not ok 4 - whatever.testdividebyzero +# Failed test 4: "whatever.testdividebyzero" # Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table @@ -21,15 +33,22 @@ ok 3 - whatever."test ident" ok 7 - teardown ok 8 - teardown more 1..8 -ok 4 - whatever.testplpgsql +ok 5 - whatever.testplpgsql # Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more # Test died: P0001: This test should die, but not halt execution. # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. -not ok 5 - whatever.testplpgsqldie -# Failed test 5: "whatever.testplpgsqldie" + # DETAIL: DETAIL + # CONTEXT: + # SQL statement "SELECT __die();" + # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 + # SQL function "runtests" statement 1 +not ok 6 - whatever.testplpgsqldie +# Failed test 6: "whatever.testplpgsqldie" # Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table @@ -39,7 +58,7 @@ not ok 5 - whatever.testplpgsqldie ok 6 - teardown ok 7 - teardown more 1..7 -ok 6 - whatever.testthis +ok 7 - whatever.testthis # Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table @@ -50,8 +69,8 @@ ok 6 - whatever.testthis ok 6 - teardown more 1..6 # Looks like you failed 1 test of 6 -not ok 7 - whatever.testy -# Failed test 7: "whatever.testy" +not ok 8 - whatever.testy +# Failed test 8: "whatever.testy" # Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table @@ -60,11 +79,11 @@ not ok 7 - whatever.testy ok 5 - teardown ok 6 - teardown more 1..6 -ok 8 - whatever.testz -ok 9 - shutting down -ok 10 - shutting down more -1..10 -# Looks like you failed 2 tests of 10 +ok 9 - whatever.testz +ok 10 - shutting down +ok 11 - shutting down more +1..11 +# Looks like you failed 3 tests of 11 ok 1 - starting up ok 2 - starting up some more # Subtest: whatever."test ident"() @@ -77,6 +96,17 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" + # Subtest: whatever.testdividebyzero() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: 22012: division by zero + # CONTEXT: + # SQL function "testdividebyzero" during startup + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 +not ok 4 - whatever.testdividebyzero +# Failed test 4: "whatever.testdividebyzero" # Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table @@ -87,15 +117,21 @@ ok 3 - whatever."test ident" ok 7 - teardown ok 8 - teardown more 1..8 -ok 4 - whatever.testplpgsql +ok 5 - whatever.testplpgsql # Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more # Test died: P0001: This test should die, but not halt execution. # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. -not ok 5 - whatever.testplpgsqldie -# Failed test 5: "whatever.testplpgsqldie" + # DETAIL: DETAIL + # CONTEXT: + # SQL statement "SELECT __die();" + # PL/pgSQL function whatever.testplpgsqldie() line 34 at EXECUTE statement + # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement + # SQL function "runtests" statement 1 +not ok 6 - whatever.testplpgsqldie +# Failed test 6: "whatever.testplpgsqldie" # Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table @@ -105,7 +141,7 @@ not ok 5 - whatever.testplpgsqldie ok 6 - teardown ok 7 - teardown more 1..7 -ok 6 - whatever.testthis +ok 7 - whatever.testthis # Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table @@ -116,8 +152,8 @@ ok 6 - whatever.testthis ok 6 - teardown more 1..6 # Looks like you failed 1 test of 6 -not ok 7 - whatever.testy -# Failed test 7: "whatever.testy" +not ok 8 - whatever.testy +# Failed test 8: "whatever.testy" # Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table @@ -126,8 +162,8 @@ not ok 7 - whatever.testy ok 5 - teardown ok 6 - teardown more 1..6 -ok 8 - whatever.testz -ok 9 - shutting down -ok 10 - shutting down more -1..10 -# Looks like you failed 2 tests of 10 +ok 9 - whatever.testz +ok 10 - shutting down +ok 11 - shutting down more +1..11 +# Looks like you failed 3 tests of 11 diff --git a/test/expected/runtests_4.out b/test/expected/runtests_4.out index c46f2c792ec0..d891e7690f89 100644 --- a/test/expected/runtests_4.out +++ b/test/expected/runtests_4.out @@ -11,6 +11,13 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" + # Subtest: whatever.testdividebyzero() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: 22012: division by zero +not ok 4 - whatever.testdividebyzero +# Failed test 4: "whatever.testdividebyzero" # Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table @@ -21,14 +28,15 @@ ok 3 - whatever."test ident" ok 7 - teardown ok 8 - teardown more 1..8 -ok 4 - whatever.testplpgsql +ok 5 - whatever.testplpgsql # Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more - # Test died: P0001: This test should die, but not halt execution -not ok 5 - whatever.testplpgsqldie -# Failed test 5: "whatever.testplpgsqldie" + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. +not ok 6 - whatever.testplpgsqldie +# Failed test 6: "whatever.testplpgsqldie" # Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table @@ -38,7 +46,7 @@ not ok 5 - whatever.testplpgsqldie ok 6 - teardown ok 7 - teardown more 1..7 -ok 6 - whatever.testthis +ok 7 - whatever.testthis # Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table @@ -49,8 +57,8 @@ ok 6 - whatever.testthis ok 6 - teardown more 1..6 # Looks like you failed 1 test of 6 -not ok 7 - whatever.testy -# Failed test 7: "whatever.testy" +not ok 8 - whatever.testy +# Failed test 8: "whatever.testy" # Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table @@ -59,11 +67,11 @@ not ok 7 - whatever.testy ok 5 - teardown ok 6 - teardown more 1..6 -ok 8 - whatever.testz -ok 9 - shutting down -ok 10 - shutting down more -1..10 -# Looks like you failed 2 tests of 10 +ok 9 - whatever.testz +ok 10 - shutting down +ok 11 - shutting down more +1..11 +# Looks like you failed 3 tests of 11 ok 1 - starting up ok 2 - starting up some more # Subtest: whatever."test ident"() @@ -76,6 +84,13 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" + # Subtest: whatever.testdividebyzero() + ok 1 - setup + ok 2 - Should be nothing in the test table + ok 3 - setup more + # Test died: 22012: division by zero +not ok 4 - whatever.testdividebyzero +# Failed test 4: "whatever.testdividebyzero" # Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table @@ -86,14 +101,15 @@ ok 3 - whatever."test ident" ok 7 - teardown ok 8 - teardown more 1..8 -ok 4 - whatever.testplpgsql +ok 5 - whatever.testplpgsql # Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more - # Test died: P0001: This test should die, but not halt execution -not ok 5 - whatever.testplpgsqldie -# Failed test 5: "whatever.testplpgsqldie" + # Test died: P0001: This test should die, but not halt execution. + # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. +not ok 6 - whatever.testplpgsqldie +# Failed test 6: "whatever.testplpgsqldie" # Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table @@ -103,7 +119,7 @@ not ok 5 - whatever.testplpgsqldie ok 6 - teardown ok 7 - teardown more 1..7 -ok 6 - whatever.testthis +ok 7 - whatever.testthis # Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table @@ -114,8 +130,8 @@ ok 6 - whatever.testthis ok 6 - teardown more 1..6 # Looks like you failed 1 test of 6 -not ok 7 - whatever.testy -# Failed test 7: "whatever.testy" +not ok 8 - whatever.testy +# Failed test 8: "whatever.testy" # Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table @@ -124,8 +140,8 @@ not ok 7 - whatever.testy ok 5 - teardown ok 6 - teardown more 1..6 -ok 8 - whatever.testz -ok 9 - shutting down -ok 10 - shutting down more -1..10 -# Looks like you failed 2 tests of 10 +ok 9 - whatever.testz +ok 10 - shutting down +ok 11 - shutting down more +1..11 +# Looks like you failed 3 tests of 11 diff --git a/test/expected/runtests_5.out b/test/expected/runtests_5.out deleted file mode 100644 index 6cfda37662bc..000000000000 --- a/test/expected/runtests_5.out +++ /dev/null @@ -1,66 +0,0 @@ -\unset ECHO -ok 1 - starting up -ok 2 - starting up some more - # Subtest: whatever."test ident"() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - ok 4 - ident - ok 5 - ident 2 - ok 6 - teardown - ok 7 - teardown more - 1..7 -ok 3 - whatever."test ident" - # Subtest: whatever.testplpgsql() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - ok 4 - plpgsql simple - ok 5 - plpgsql simple 2 - ok 6 - Should be a 1 in the test table - ok 7 - teardown - ok 8 - teardown more - 1..8 -ok 4 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - # Test died: P0001: This test should die, but not halt execution -not ok 5 - whatever.testplpgsqldie -# Failed test 5: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - ok 4 - simple pass - ok 5 - another simple pass - ok 6 - teardown - ok 7 - teardown more - 1..7 -ok 6 - whatever.testthis - # Subtest: whatever.testy() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - not ok 4 - this test intentionally fails - # Failed test 4: "this test intentionally fails" - ok 5 - teardown - ok 6 - teardown more - 1..6 - # Looks like you failed 1 test of 6 -not ok 7 - whatever.testy -# Failed test 7: "whatever.testy" - # Subtest: whatever.testz() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - ok 4 - Late test should find nothing in the test table - ok 5 - teardown - ok 6 - teardown more - 1..6 -ok 8 - whatever.testz -ok 9 - shutting down -ok 10 - shutting down more -1..10 -# Looks like you failed 2 tests of 10 diff --git a/test/expected/runtests_6.out b/test/expected/runtests_6.out deleted file mode 100644 index 20fadf630f6d..000000000000 --- a/test/expected/runtests_6.out +++ /dev/null @@ -1,158 +0,0 @@ -\unset ECHO -ok 1 - starting up -ok 2 - starting up some more - # Subtest: whatever."test ident"() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - ok 4 - ident - ok 5 - ident 2 - ok 6 - teardown - ok 7 - teardown more - 1..7 -ok 3 - whatever."test ident" - # Subtest: whatever.testplpgsql() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - ok 4 - plpgsql simple - ok 5 - plpgsql simple 2 - ok 6 - Should be a 1 in the test table - ok 7 - teardown - ok 8 - teardown more - 1..8 -ok 4 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - # Test died: P0001: This test should die, but not halt execution. - # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. - # DETAIL: DETAIL - # SCHEMA: SCHEMA - # TABLE: TABLE - # COLUMN: COLUMN - # CONSTRAINT: CONSTRAINT - # TYPE: TYPE - # CONTEXT: - # PL/pgSQL function __die() line 3 at RAISE - # SQL statement "SELECT __die();" - # PL/pgSQL function whatever.testplpgsqldie() line 23 at EXECUTE - # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement - # SQL function "runtests" statement 1 - # SQL function "runtests" statement 1 -not ok 5 - whatever.testplpgsqldie -# Failed test 5: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - ok 4 - simple pass - ok 5 - another simple pass - ok 6 - teardown - ok 7 - teardown more - 1..7 -ok 6 - whatever.testthis - # Subtest: whatever.testy() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - not ok 4 - this test intentionally fails - # Failed test 4: "this test intentionally fails" - ok 5 - teardown - ok 6 - teardown more - 1..6 - # Looks like you failed 1 test of 6 -not ok 7 - whatever.testy -# Failed test 7: "whatever.testy" - # Subtest: whatever.testz() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - ok 4 - Late test should find nothing in the test table - ok 5 - teardown - ok 6 - teardown more - 1..6 -ok 8 - whatever.testz -ok 9 - shutting down -ok 10 - shutting down more -1..10 -# Looks like you failed 2 tests of 10 -ok 1 - starting up -ok 2 - starting up some more - # Subtest: whatever."test ident"() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - ok 4 - ident - ok 5 - ident 2 - ok 6 - teardown - ok 7 - teardown more - 1..7 -ok 3 - whatever."test ident" - # Subtest: whatever.testplpgsql() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - ok 4 - plpgsql simple - ok 5 - plpgsql simple 2 - ok 6 - Should be a 1 in the test table - ok 7 - teardown - ok 8 - teardown more - 1..8 -ok 4 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - # Test died: P0001: This test should die, but not halt execution. - # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. - # DETAIL: DETAIL - # SCHEMA: SCHEMA - # TABLE: TABLE - # COLUMN: COLUMN - # CONSTRAINT: CONSTRAINT - # TYPE: TYPE - # CONTEXT: - # PL/pgSQL function __die() line 3 at RAISE - # SQL statement "SELECT __die();" - # PL/pgSQL function whatever.testplpgsqldie() line 23 at EXECUTE - # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement - # SQL function "runtests" statement 1 -not ok 5 - whatever.testplpgsqldie -# Failed test 5: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - ok 4 - simple pass - ok 5 - another simple pass - ok 6 - teardown - ok 7 - teardown more - 1..7 -ok 6 - whatever.testthis - # Subtest: whatever.testy() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - not ok 4 - this test intentionally fails - # Failed test 4: "this test intentionally fails" - ok 5 - teardown - ok 6 - teardown more - 1..6 - # Looks like you failed 1 test of 6 -not ok 7 - whatever.testy -# Failed test 7: "whatever.testy" - # Subtest: whatever.testz() - ok 1 - setup - ok 2 - Should be nothing in the test table - ok 3 - setup more - ok 4 - Late test should find nothing in the test table - ok 5 - teardown - ok 6 - teardown more - 1..6 -ok 8 - whatever.testz -ok 9 - shutting down -ok 10 - shutting down more -1..10 -# Looks like you failed 2 tests of 10 diff --git a/test/setup.sql b/test/setup.sql index 492e91d83b5a..9ecbdfdc1918 100644 --- a/test/setup.sql +++ b/test/setup.sql @@ -1,3 +1,4 @@ \i test/psql.sql BEGIN; +-- \i sql/pgtap.sql diff --git a/test/sql/runtests.sql b/test/sql/runtests.sql index 8832fefd80b4..aa13b2d6ee1b 100644 --- a/test/sql/runtests.sql +++ b/test/sql/runtests.sql @@ -5,6 +5,18 @@ SET client_min_messages = warning; CREATE SCHEMA whatever; CREATE TABLE whatever.foo ( id serial primary key ); +/* + +Expected output: + +runtests.out: 9.6 and up +runtests_1.out: 9.5 +runtests_2.out: 9.3 - 9.4 +runtests_3.out: 9.2 +runtests_4.out: 9.1 + +*/ + -- Make sure we get test function names. SET client_min_messages = notice; @@ -99,6 +111,10 @@ Note that in some cases we get what appears to be a duplicate context message, b END; $$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION whatever.testdividebyzero() RETURNS SETOF TEXT AS $$ + select cast(1/0 as text) +$$ LANGUAGE sql; + CREATE OR REPLACE FUNCTION whatever.testy() RETURNS SETOF TEXT AS $$ SELECT fail('this test intentionally fails'); $$ LANGUAGE SQL; From d63d6a8b70467929ec2ef5b0e816785a0d25ea3e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 17 Apr 2022 16:25:52 -0400 Subject: [PATCH 1127/1195] Unindent subtest diag header In order to make it clearer where a subest starts. This contrasts with how Test::More does it, but is easier to read for errors and aligns with how some other TAP frameworks like node tap do it. The format change will have no impact on how TAP-parsing tools like `pg_prove` interpret test output, but could break diff-based tooling such as `pg_regress`. That change is demonstrated in the expected files modified in this commit. --- Changes | 5 +++++ doc/pgtap.mmd | 4 ++-- sql/pgtap--1.2.0--1.2.1.sql | 2 +- sql/pgtap.sql.in | 2 +- test/expected/runjusttests.out | 14 +++++++------- test/expected/runjusttests_1.out | 14 +++++++------- test/expected/runjusttests_2.out | 14 +++++++------- test/expected/runjusttests_3.out | 14 +++++++------- test/expected/runjusttests_4.out | 14 +++++++------- test/expected/runjusttests_5.out | 14 +++++++------- test/expected/runjusttests_6.out | 14 +++++++------- test/expected/runnotests.out | 2 +- test/expected/runtests.out | 28 ++++++++++++++-------------- test/expected/runtests_1.out | 28 ++++++++++++++-------------- test/expected/runtests_2.out | 28 ++++++++++++++-------------- test/expected/runtests_3.out | 28 ++++++++++++++-------------- test/expected/runtests_4.out | 28 ++++++++++++++-------------- 17 files changed, 129 insertions(+), 124 deletions(-) diff --git a/Changes b/Changes index 1884633383f3..6025614dcaac 100644 --- a/Changes +++ b/Changes @@ -9,6 +9,11 @@ Revision history for pgTAP the analysis. * Fixed a pluralization error reporting xUnit test failures to report "failed 1 test" instead of "failed 1 tests". +* Removed the indentation for diagnostic comments at the start of subtests to + make it easier to see where they start and end. This changes the output + format, which will not affect tests run by `pg_prove`, but will break tests + that depend on diffing files, as `pg_regress` does. Thanks to Matt DeLuco for + highlighting the visual confusion of the indented diagnostic (#264). 1.2.0 2021-12-05T18:08:13Z -------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index b5fe21792802..38a971241e58 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -8411,11 +8411,11 @@ The output, assuming a single startup test, two subtests, and a single shutdown test, will look something like this: ok 1 - Startup test - # Subtest: public.test_this() + # Subtest: public.test_this() ok 1 - simple pass ok 2 - another simple pass ok 2 - public.test_this() - # Subtest: public.test_that() + # Subtest: public.test_that() ok 1 - that simple ok 2 - that simple 2 ok 3 - public.test_that() diff --git a/sql/pgtap--1.2.0--1.2.1.sql b/sql/pgtap--1.2.0--1.2.1.sql index 9556a49d6a72..ce8c31a83575 100644 --- a/sql/pgtap--1.2.0--1.2.1.sql +++ b/sql/pgtap--1.2.0--1.2.1.sql @@ -29,7 +29,7 @@ BEGIN FOR i IN 1..COALESCE(array_upper(tests, 1), 0) LOOP -- What subtest are we running? - RETURN NEXT ' ' || diag_test_name('Subtest: ' || tests[i]); + RETURN NEXT diag_test_name('Subtest: ' || tests[i]); -- Reset the results. tok := TRUE; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 15a216aa90c2..13e878cdd157 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -6567,7 +6567,7 @@ BEGIN FOR i IN 1..COALESCE(array_upper(tests, 1), 0) LOOP -- What subtest are we running? - RETURN NEXT ' ' || diag_test_name('Subtest: ' || tests[i]); + RETURN NEXT diag_test_name('Subtest: ' || tests[i]); -- Reset the results. tok := TRUE; diff --git a/test/expected/runjusttests.out b/test/expected/runjusttests.out index fc46be512e6c..89f6cc0cc9c1 100644 --- a/test/expected/runjusttests.out +++ b/test/expected/runjusttests.out @@ -1,21 +1,21 @@ \unset ECHO - # Subtest: whatever."test ident"() +# Subtest: whatever."test ident"() ok 1 - ident ok 2 - ident 2 1..2 ok 1 - whatever."test ident" - # Subtest: whatever.testnada() +# Subtest: whatever.testnada() 1..0 # No tests run! not ok 2 - whatever.testnada # Failed test 2: "whatever.testnada" - # Subtest: whatever.testplpgsql() +# Subtest: whatever.testplpgsql() ok 1 - plpgsql simple ok 2 - plpgsql simple 2 ok 3 - Should be a 1 in the test table 1..3 ok 3 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() +# Subtest: whatever.testplpgsqldie() # Test died: P0001: This test should die, but not halt execution. # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. # DETAIL: DETAIL @@ -32,12 +32,12 @@ ok 3 - whatever.testplpgsql # SQL function "runtests" statement 1 not ok 4 - whatever.testplpgsqldie # Failed test 4: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() ok 1 - simple pass ok 2 - another simple pass 1..2 ok 5 - whatever.testthis - # Subtest: whatever.testy() +# Subtest: whatever.testy() ok 1 - pass not ok 2 - this test intentionally fails # Failed test 2: "this test intentionally fails" @@ -45,7 +45,7 @@ ok 5 - whatever.testthis # Looks like you failed 1 test of 2 not ok 6 - whatever.testy # Failed test 6: "whatever.testy" - # Subtest: whatever.testz() +# Subtest: whatever.testz() ok 1 - Late test should find nothing in the test table 1..1 ok 7 - whatever.testz diff --git a/test/expected/runjusttests_1.out b/test/expected/runjusttests_1.out index 6cdfaa5f3181..498e00858aff 100644 --- a/test/expected/runjusttests_1.out +++ b/test/expected/runjusttests_1.out @@ -1,21 +1,21 @@ \unset ECHO - # Subtest: whatever."test ident"() +# Subtest: whatever."test ident"() ok 1 - ident ok 2 - ident 2 1..2 ok 1 - whatever."test ident" - # Subtest: whatever.testnada() +# Subtest: whatever.testnada() 1..0 # No tests run! not ok 2 - whatever.testnada # Failed test 2: "whatever.testnada" - # Subtest: whatever.testplpgsql() +# Subtest: whatever.testplpgsql() ok 1 - plpgsql simple ok 2 - plpgsql simple 2 ok 3 - Should be a 1 in the test table 1..3 ok 3 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() +# Subtest: whatever.testplpgsqldie() # Test died: P0001: This test should die, but not halt execution. # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. # DETAIL: DETAIL @@ -32,12 +32,12 @@ ok 3 - whatever.testplpgsql # SQL function "runtests" statement 1 not ok 4 - whatever.testplpgsqldie # Failed test 4: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() ok 1 - simple pass ok 2 - another simple pass 1..2 ok 5 - whatever.testthis - # Subtest: whatever.testy() +# Subtest: whatever.testy() ok 1 - pass not ok 2 - this test intentionally fails # Failed test 2: "this test intentionally fails" @@ -45,7 +45,7 @@ ok 5 - whatever.testthis # Looks like you failed 1 test of 2 not ok 6 - whatever.testy # Failed test 6: "whatever.testy" - # Subtest: whatever.testz() +# Subtest: whatever.testz() ok 1 - Late test should find nothing in the test table 1..1 ok 7 - whatever.testz diff --git a/test/expected/runjusttests_2.out b/test/expected/runjusttests_2.out index 2a5acf4d9ad9..a95869348891 100644 --- a/test/expected/runjusttests_2.out +++ b/test/expected/runjusttests_2.out @@ -1,21 +1,21 @@ \unset ECHO - # Subtest: whatever."test ident"() +# Subtest: whatever."test ident"() ok 1 - ident ok 2 - ident 2 1..2 ok 1 - whatever."test ident" - # Subtest: whatever.testnada() +# Subtest: whatever.testnada() 1..0 # No tests run! not ok 2 - whatever.testnada # Failed test 2: "whatever.testnada" - # Subtest: whatever.testplpgsql() +# Subtest: whatever.testplpgsql() ok 1 - plpgsql simple ok 2 - plpgsql simple 2 ok 3 - Should be a 1 in the test table 1..3 ok 3 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() +# Subtest: whatever.testplpgsqldie() # Test died: P0001: This test should die, but not halt execution. # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. # DETAIL: DETAIL @@ -27,12 +27,12 @@ ok 3 - whatever.testplpgsql # SQL function "runtests" statement 1 not ok 4 - whatever.testplpgsqldie # Failed test 4: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() ok 1 - simple pass ok 2 - another simple pass 1..2 ok 5 - whatever.testthis - # Subtest: whatever.testy() +# Subtest: whatever.testy() ok 1 - pass not ok 2 - this test intentionally fails # Failed test 2: "this test intentionally fails" @@ -40,7 +40,7 @@ ok 5 - whatever.testthis # Looks like you failed 1 test of 2 not ok 6 - whatever.testy # Failed test 6: "whatever.testy" - # Subtest: whatever.testz() +# Subtest: whatever.testz() ok 1 - Late test should find nothing in the test table 1..1 ok 7 - whatever.testz diff --git a/test/expected/runjusttests_3.out b/test/expected/runjusttests_3.out index 7081f578dbb3..3776aa08f1d4 100644 --- a/test/expected/runjusttests_3.out +++ b/test/expected/runjusttests_3.out @@ -1,21 +1,21 @@ \unset ECHO - # Subtest: whatever."test ident"() +# Subtest: whatever."test ident"() ok 1 - ident ok 2 - ident 2 1..2 ok 1 - whatever."test ident" - # Subtest: whatever.testnada() +# Subtest: whatever.testnada() 1..0 # No tests run! not ok 2 - whatever.testnada # Failed test 2: "whatever.testnada" - # Subtest: whatever.testplpgsql() +# Subtest: whatever.testplpgsql() ok 1 - plpgsql simple ok 2 - plpgsql simple 2 ok 3 - Should be a 1 in the test table 1..3 ok 3 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() +# Subtest: whatever.testplpgsqldie() # Test died: P0001: This test should die, but not halt execution. # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. # DETAIL: DETAIL @@ -27,12 +27,12 @@ ok 3 - whatever.testplpgsql # SQL function "runtests" statement 1 not ok 4 - whatever.testplpgsqldie # Failed test 4: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() ok 1 - simple pass ok 2 - another simple pass 1..2 ok 5 - whatever.testthis - # Subtest: whatever.testy() +# Subtest: whatever.testy() ok 1 - pass not ok 2 - this test intentionally fails # Failed test 2: "this test intentionally fails" @@ -40,7 +40,7 @@ ok 5 - whatever.testthis # Looks like you failed 1 test of 2 not ok 6 - whatever.testy # Failed test 6: "whatever.testy" - # Subtest: whatever.testz() +# Subtest: whatever.testz() ok 1 - Late test should find nothing in the test table 1..1 ok 7 - whatever.testz diff --git a/test/expected/runjusttests_4.out b/test/expected/runjusttests_4.out index afb90028cc88..e7022c70e4c5 100644 --- a/test/expected/runjusttests_4.out +++ b/test/expected/runjusttests_4.out @@ -1,31 +1,31 @@ \unset ECHO - # Subtest: whatever."test ident"() +# Subtest: whatever."test ident"() ok 1 - ident ok 2 - ident 2 1..2 ok 1 - whatever."test ident" - # Subtest: whatever.testnada() +# Subtest: whatever.testnada() 1..0 # No tests run! not ok 2 - whatever.testnada # Failed test 2: "whatever.testnada" - # Subtest: whatever.testplpgsql() +# Subtest: whatever.testplpgsql() ok 1 - plpgsql simple ok 2 - plpgsql simple 2 ok 3 - Should be a 1 in the test table 1..3 ok 3 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() +# Subtest: whatever.testplpgsqldie() # Test died: P0001: This test should die, but not halt execution. # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. not ok 4 - whatever.testplpgsqldie # Failed test 4: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() ok 1 - simple pass ok 2 - another simple pass 1..2 ok 5 - whatever.testthis - # Subtest: whatever.testy() +# Subtest: whatever.testy() ok 1 - pass not ok 2 - this test intentionally fails # Failed test 2: "this test intentionally fails" @@ -33,7 +33,7 @@ ok 5 - whatever.testthis # Looks like you failed 1 test of 2 not ok 6 - whatever.testy # Failed test 6: "whatever.testy" - # Subtest: whatever.testz() +# Subtest: whatever.testz() ok 1 - Late test should find nothing in the test table 1..1 ok 7 - whatever.testz diff --git a/test/expected/runjusttests_5.out b/test/expected/runjusttests_5.out index e65b83b0ec54..8eece0a16d75 100644 --- a/test/expected/runjusttests_5.out +++ b/test/expected/runjusttests_5.out @@ -1,21 +1,21 @@ \unset ECHO - # Subtest: whatever."test ident"() +# Subtest: whatever."test ident"() ok 1 - ident ok 2 - ident 2 1..2 ok 1 - whatever."test ident" - # Subtest: whatever.testnada() +# Subtest: whatever.testnada() 1..0 # No tests run! not ok 2 - whatever.testnada # Failed test 2: "whatever.testnada" - # Subtest: whatever.testplpgsql() +# Subtest: whatever.testplpgsql() ok 1 - plpgsql simple ok 2 - plpgsql simple 2 ok 3 - Should be a 1 in the test table 1..3 ok 3 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() +# Subtest: whatever.testplpgsqldie() # Test died: P0001: This test should die, but not halt execution. # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. # DETAIL: DETAIL @@ -33,12 +33,12 @@ ok 3 - whatever.testplpgsql # SQL function "runtests" statement 1 not ok 4 - whatever.testplpgsqldie # Failed test 4: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() ok 1 - simple pass ok 2 - another simple pass 1..2 ok 5 - whatever.testthis - # Subtest: whatever.testy() +# Subtest: whatever.testy() ok 1 - pass not ok 2 - this test intentionally fails # Failed test 2: "this test intentionally fails" @@ -46,7 +46,7 @@ ok 5 - whatever.testthis # Looks like you failed 1 test of 2 not ok 6 - whatever.testy # Failed test 6: "whatever.testy" - # Subtest: whatever.testz() +# Subtest: whatever.testz() ok 1 - Late test should find nothing in the test table 1..1 ok 7 - whatever.testz diff --git a/test/expected/runjusttests_6.out b/test/expected/runjusttests_6.out index cfc3ed3b3444..2644211bc326 100644 --- a/test/expected/runjusttests_6.out +++ b/test/expected/runjusttests_6.out @@ -1,31 +1,31 @@ \unset ECHO - # Subtest: whatever."test ident"() +# Subtest: whatever."test ident"() ok 1 - ident ok 2 - ident 2 1..2 ok 1 - whatever."test ident" - # Subtest: whatever.testnada() +# Subtest: whatever.testnada() 1..0 # No tests run! not ok 2 - whatever.testnada # Failed test 2: "whatever.testnada" - # Subtest: whatever.testplpgsql() +# Subtest: whatever.testplpgsql() ok 1 - plpgsql simple ok 2 - plpgsql simple 2 ok 3 - Should be a 1 in the test table 1..3 ok 3 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() +# Subtest: whatever.testplpgsqldie() # Test died: This test should die, but not halt execution. # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. not ok 4 - whatever.testplpgsqldie # Failed test 4: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() ok 1 - simple pass ok 2 - another simple pass 1..2 ok 5 - whatever.testthis - # Subtest: whatever.testy() +# Subtest: whatever.testy() ok 1 - pass not ok 2 - this test intentionally fails # Failed test 2: "this test intentionally fails" @@ -33,7 +33,7 @@ ok 5 - whatever.testthis # Looks like you failed 1 test of 2 not ok 6 - whatever.testy # Failed test 6: "whatever.testy" - # Subtest: whatever.testz() +# Subtest: whatever.testz() ok 1 - Late test should find nothing in the test table 1..1 ok 7 - whatever.testz diff --git a/test/expected/runnotests.out b/test/expected/runnotests.out index bfa4fd01caaf..96665ba0eed8 100644 --- a/test/expected/runnotests.out +++ b/test/expected/runnotests.out @@ -1,5 +1,5 @@ \unset ECHO - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() 1..0 # No tests run! not ok 1 - whatever.testthis diff --git a/test/expected/runtests.out b/test/expected/runtests.out index 256e11ad5050..1baf8555a2bb 100644 --- a/test/expected/runtests.out +++ b/test/expected/runtests.out @@ -1,7 +1,7 @@ \unset ECHO ok 1 - starting up ok 2 - starting up some more - # Subtest: whatever."test ident"() +# Subtest: whatever."test ident"() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -11,7 +11,7 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" - # Subtest: whatever.testdividebyzero() +# Subtest: whatever.testdividebyzero() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -23,7 +23,7 @@ ok 3 - whatever."test ident" # SQL function "runtests" statement 1 not ok 4 - whatever.testdividebyzero # Failed test 4: "whatever.testdividebyzero" - # Subtest: whatever.testplpgsql() +# Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -34,7 +34,7 @@ not ok 4 - whatever.testdividebyzero ok 8 - teardown more 1..8 ok 5 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() +# Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -55,7 +55,7 @@ ok 5 - whatever.testplpgsql # SQL function "runtests" statement 1 not ok 6 - whatever.testplpgsqldie # Failed test 6: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -65,7 +65,7 @@ not ok 6 - whatever.testplpgsqldie ok 7 - teardown more 1..7 ok 7 - whatever.testthis - # Subtest: whatever.testy() +# Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -77,7 +77,7 @@ ok 7 - whatever.testthis # Looks like you failed 1 test of 6 not ok 8 - whatever.testy # Failed test 8: "whatever.testy" - # Subtest: whatever.testz() +# Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -92,7 +92,7 @@ ok 11 - shutting down more # Looks like you failed 3 tests of 11 ok 1 - starting up ok 2 - starting up some more - # Subtest: whatever."test ident"() +# Subtest: whatever."test ident"() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -102,7 +102,7 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" - # Subtest: whatever.testdividebyzero() +# Subtest: whatever.testdividebyzero() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -113,7 +113,7 @@ ok 3 - whatever."test ident" # SQL function "runtests" statement 1 not ok 4 - whatever.testdividebyzero # Failed test 4: "whatever.testdividebyzero" - # Subtest: whatever.testplpgsql() +# Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -124,7 +124,7 @@ not ok 4 - whatever.testdividebyzero ok 8 - teardown more 1..8 ok 5 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() +# Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -144,7 +144,7 @@ ok 5 - whatever.testplpgsql # SQL function "runtests" statement 1 not ok 6 - whatever.testplpgsqldie # Failed test 6: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -154,7 +154,7 @@ not ok 6 - whatever.testplpgsqldie ok 7 - teardown more 1..7 ok 7 - whatever.testthis - # Subtest: whatever.testy() +# Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -166,7 +166,7 @@ ok 7 - whatever.testthis # Looks like you failed 1 test of 6 not ok 8 - whatever.testy # Failed test 8: "whatever.testy" - # Subtest: whatever.testz() +# Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more diff --git a/test/expected/runtests_1.out b/test/expected/runtests_1.out index 58461f1d742a..43061258a68a 100644 --- a/test/expected/runtests_1.out +++ b/test/expected/runtests_1.out @@ -1,7 +1,7 @@ \unset ECHO ok 1 - starting up ok 2 - starting up some more - # Subtest: whatever."test ident"() +# Subtest: whatever."test ident"() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -11,7 +11,7 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" - # Subtest: whatever.testdividebyzero() +# Subtest: whatever.testdividebyzero() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -23,7 +23,7 @@ ok 3 - whatever."test ident" # SQL function "runtests" statement 1 not ok 4 - whatever.testdividebyzero # Failed test 4: "whatever.testdividebyzero" - # Subtest: whatever.testplpgsql() +# Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -34,7 +34,7 @@ not ok 4 - whatever.testdividebyzero ok 8 - teardown more 1..8 ok 5 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() +# Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -54,7 +54,7 @@ ok 5 - whatever.testplpgsql # SQL function "runtests" statement 1 not ok 6 - whatever.testplpgsqldie # Failed test 6: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -64,7 +64,7 @@ not ok 6 - whatever.testplpgsqldie ok 7 - teardown more 1..7 ok 7 - whatever.testthis - # Subtest: whatever.testy() +# Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -76,7 +76,7 @@ ok 7 - whatever.testthis # Looks like you failed 1 test of 6 not ok 8 - whatever.testy # Failed test 8: "whatever.testy" - # Subtest: whatever.testz() +# Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -91,7 +91,7 @@ ok 11 - shutting down more # Looks like you failed 3 tests of 11 ok 1 - starting up ok 2 - starting up some more - # Subtest: whatever."test ident"() +# Subtest: whatever."test ident"() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -101,7 +101,7 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" - # Subtest: whatever.testdividebyzero() +# Subtest: whatever.testdividebyzero() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -112,7 +112,7 @@ ok 3 - whatever."test ident" # SQL function "runtests" statement 1 not ok 4 - whatever.testdividebyzero # Failed test 4: "whatever.testdividebyzero" - # Subtest: whatever.testplpgsql() +# Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -123,7 +123,7 @@ not ok 4 - whatever.testdividebyzero ok 8 - teardown more 1..8 ok 5 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() +# Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -142,7 +142,7 @@ ok 5 - whatever.testplpgsql # SQL function "runtests" statement 1 not ok 6 - whatever.testplpgsqldie # Failed test 6: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -152,7 +152,7 @@ not ok 6 - whatever.testplpgsqldie ok 7 - teardown more 1..7 ok 7 - whatever.testthis - # Subtest: whatever.testy() +# Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -164,7 +164,7 @@ ok 7 - whatever.testthis # Looks like you failed 1 test of 6 not ok 8 - whatever.testy # Failed test 8: "whatever.testy" - # Subtest: whatever.testz() +# Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more diff --git a/test/expected/runtests_2.out b/test/expected/runtests_2.out index eb148b927924..4a03bd60b00b 100644 --- a/test/expected/runtests_2.out +++ b/test/expected/runtests_2.out @@ -1,7 +1,7 @@ \unset ECHO ok 1 - starting up ok 2 - starting up some more - # Subtest: whatever."test ident"() +# Subtest: whatever."test ident"() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -11,7 +11,7 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" - # Subtest: whatever.testdividebyzero() +# Subtest: whatever.testdividebyzero() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -23,7 +23,7 @@ ok 3 - whatever."test ident" # SQL function "runtests" statement 1 not ok 4 - whatever.testdividebyzero # Failed test 4: "whatever.testdividebyzero" - # Subtest: whatever.testplpgsql() +# Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -34,7 +34,7 @@ not ok 4 - whatever.testdividebyzero ok 8 - teardown more 1..8 ok 5 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() +# Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -54,7 +54,7 @@ ok 5 - whatever.testplpgsql # SQL function "runtests" statement 1 not ok 6 - whatever.testplpgsqldie # Failed test 6: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -64,7 +64,7 @@ not ok 6 - whatever.testplpgsqldie ok 7 - teardown more 1..7 ok 7 - whatever.testthis - # Subtest: whatever.testy() +# Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -76,7 +76,7 @@ ok 7 - whatever.testthis # Looks like you failed 1 test of 6 not ok 8 - whatever.testy # Failed test 8: "whatever.testy" - # Subtest: whatever.testz() +# Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -91,7 +91,7 @@ ok 11 - shutting down more # Looks like you failed 3 tests of 11 ok 1 - starting up ok 2 - starting up some more - # Subtest: whatever."test ident"() +# Subtest: whatever."test ident"() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -101,7 +101,7 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" - # Subtest: whatever.testdividebyzero() +# Subtest: whatever.testdividebyzero() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -112,7 +112,7 @@ ok 3 - whatever."test ident" # SQL function "runtests" statement 1 not ok 4 - whatever.testdividebyzero # Failed test 4: "whatever.testdividebyzero" - # Subtest: whatever.testplpgsql() +# Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -123,7 +123,7 @@ not ok 4 - whatever.testdividebyzero ok 8 - teardown more 1..8 ok 5 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() +# Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -142,7 +142,7 @@ ok 5 - whatever.testplpgsql # SQL function "runtests" statement 1 not ok 6 - whatever.testplpgsqldie # Failed test 6: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -152,7 +152,7 @@ not ok 6 - whatever.testplpgsqldie ok 7 - teardown more 1..7 ok 7 - whatever.testthis - # Subtest: whatever.testy() +# Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -164,7 +164,7 @@ ok 7 - whatever.testthis # Looks like you failed 1 test of 6 not ok 8 - whatever.testy # Failed test 8: "whatever.testy" - # Subtest: whatever.testz() +# Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more diff --git a/test/expected/runtests_3.out b/test/expected/runtests_3.out index e1df08ca7c8c..96e620d2fb93 100644 --- a/test/expected/runtests_3.out +++ b/test/expected/runtests_3.out @@ -1,7 +1,7 @@ \unset ECHO ok 1 - starting up ok 2 - starting up some more - # Subtest: whatever."test ident"() +# Subtest: whatever."test ident"() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -11,7 +11,7 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" - # Subtest: whatever.testdividebyzero() +# Subtest: whatever.testdividebyzero() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -23,7 +23,7 @@ ok 3 - whatever."test ident" # SQL function "runtests" statement 1 not ok 4 - whatever.testdividebyzero # Failed test 4: "whatever.testdividebyzero" - # Subtest: whatever.testplpgsql() +# Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -34,7 +34,7 @@ not ok 4 - whatever.testdividebyzero ok 8 - teardown more 1..8 ok 5 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() +# Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -49,7 +49,7 @@ ok 5 - whatever.testplpgsql # SQL function "runtests" statement 1 not ok 6 - whatever.testplpgsqldie # Failed test 6: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -59,7 +59,7 @@ not ok 6 - whatever.testplpgsqldie ok 7 - teardown more 1..7 ok 7 - whatever.testthis - # Subtest: whatever.testy() +# Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -71,7 +71,7 @@ ok 7 - whatever.testthis # Looks like you failed 1 test of 6 not ok 8 - whatever.testy # Failed test 8: "whatever.testy" - # Subtest: whatever.testz() +# Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -86,7 +86,7 @@ ok 11 - shutting down more # Looks like you failed 3 tests of 11 ok 1 - starting up ok 2 - starting up some more - # Subtest: whatever."test ident"() +# Subtest: whatever."test ident"() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -96,7 +96,7 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" - # Subtest: whatever.testdividebyzero() +# Subtest: whatever.testdividebyzero() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -107,7 +107,7 @@ ok 3 - whatever."test ident" # SQL function "runtests" statement 1 not ok 4 - whatever.testdividebyzero # Failed test 4: "whatever.testdividebyzero" - # Subtest: whatever.testplpgsql() +# Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -118,7 +118,7 @@ not ok 4 - whatever.testdividebyzero ok 8 - teardown more 1..8 ok 5 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() +# Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -132,7 +132,7 @@ ok 5 - whatever.testplpgsql # SQL function "runtests" statement 1 not ok 6 - whatever.testplpgsqldie # Failed test 6: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -142,7 +142,7 @@ not ok 6 - whatever.testplpgsqldie ok 7 - teardown more 1..7 ok 7 - whatever.testthis - # Subtest: whatever.testy() +# Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -154,7 +154,7 @@ ok 7 - whatever.testthis # Looks like you failed 1 test of 6 not ok 8 - whatever.testy # Failed test 8: "whatever.testy" - # Subtest: whatever.testz() +# Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more diff --git a/test/expected/runtests_4.out b/test/expected/runtests_4.out index d891e7690f89..1ecbaee2cd60 100644 --- a/test/expected/runtests_4.out +++ b/test/expected/runtests_4.out @@ -1,7 +1,7 @@ \unset ECHO ok 1 - starting up ok 2 - starting up some more - # Subtest: whatever."test ident"() +# Subtest: whatever."test ident"() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -11,14 +11,14 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" - # Subtest: whatever.testdividebyzero() +# Subtest: whatever.testdividebyzero() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more # Test died: 22012: division by zero not ok 4 - whatever.testdividebyzero # Failed test 4: "whatever.testdividebyzero" - # Subtest: whatever.testplpgsql() +# Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -29,7 +29,7 @@ not ok 4 - whatever.testdividebyzero ok 8 - teardown more 1..8 ok 5 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() +# Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -37,7 +37,7 @@ ok 5 - whatever.testplpgsql # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. not ok 6 - whatever.testplpgsqldie # Failed test 6: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -47,7 +47,7 @@ not ok 6 - whatever.testplpgsqldie ok 7 - teardown more 1..7 ok 7 - whatever.testthis - # Subtest: whatever.testy() +# Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -59,7 +59,7 @@ ok 7 - whatever.testthis # Looks like you failed 1 test of 6 not ok 8 - whatever.testy # Failed test 8: "whatever.testy" - # Subtest: whatever.testz() +# Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -74,7 +74,7 @@ ok 11 - shutting down more # Looks like you failed 3 tests of 11 ok 1 - starting up ok 2 - starting up some more - # Subtest: whatever."test ident"() +# Subtest: whatever."test ident"() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -84,14 +84,14 @@ ok 2 - starting up some more ok 7 - teardown more 1..7 ok 3 - whatever."test ident" - # Subtest: whatever.testdividebyzero() +# Subtest: whatever.testdividebyzero() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more # Test died: 22012: division by zero not ok 4 - whatever.testdividebyzero # Failed test 4: "whatever.testdividebyzero" - # Subtest: whatever.testplpgsql() +# Subtest: whatever.testplpgsql() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -102,7 +102,7 @@ not ok 4 - whatever.testdividebyzero ok 8 - teardown more 1..8 ok 5 - whatever.testplpgsql - # Subtest: whatever.testplpgsqldie() +# Subtest: whatever.testplpgsqldie() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -110,7 +110,7 @@ ok 5 - whatever.testplpgsql # Note that in some cases we get what appears to be a duplicate context message, but that is due to Postgres itself. not ok 6 - whatever.testplpgsqldie # Failed test 6: "whatever.testplpgsqldie" - # Subtest: whatever.testthis() +# Subtest: whatever.testthis() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -120,7 +120,7 @@ not ok 6 - whatever.testplpgsqldie ok 7 - teardown more 1..7 ok 7 - whatever.testthis - # Subtest: whatever.testy() +# Subtest: whatever.testy() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more @@ -132,7 +132,7 @@ ok 7 - whatever.testthis # Looks like you failed 1 test of 6 not ok 8 - whatever.testy # Failed test 8: "whatever.testy" - # Subtest: whatever.testz() +# Subtest: whatever.testz() ok 1 - setup ok 2 - Should be nothing in the test table ok 3 - setup more From 8f4a88173f279802bebe9167b03447327128f22c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 21 May 2022 17:52:24 -0400 Subject: [PATCH 1128/1195] Add 2 more col_is_pk() variants without description Thanks to Matteo Ferrando for the PR; resolves #199. --- Changes | 3 ++ doc/pgtap.mmd | 2 + sql/pgtap--1.2.0--1.2.1.sql | 12 +++++ sql/pgtap.sql.in | 16 +++++- test/expected/pktap.out | 98 ++++++++++++++++++++----------------- test/sql/pktap.sql | 18 ++++++- 6 files changed, 100 insertions(+), 49 deletions(-) diff --git a/Changes b/Changes index 6025614dcaac..553753b0dcd4 100644 --- a/Changes +++ b/Changes @@ -14,6 +14,9 @@ Revision history for pgTAP format, which will not affect tests run by `pg_prove`, but will break tests that depend on diffing files, as `pg_regress` does. Thanks to Matt DeLuco for highlighting the visual confusion of the indented diagnostic (#264). +* Added variants of `col_is_pk()` with schema and without description (variants + without schema or description already existed). Thanks to Matteo Ferrando for + the PR (#199)! 1.2.0 2021-12-05T18:08:13Z -------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 38a971241e58..d0e50cc38d60 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4707,6 +4707,8 @@ foreign key does *not* exist. SELECT col_is_pk( :schema, :table, :columns, :description ); SELECT col_is_pk( :schema, :table, :column, :description ); + SELECT col_is_pk( :schema, :table, :columns ); + SELECT col_is_pk( :schema, :table, :column ); SELECT col_is_pk( :table, :columns, :description ); SELECT col_is_pk( :table, :column, :description ); SELECT col_is_pk( :table, :columns ); diff --git a/sql/pgtap--1.2.0--1.2.1.sql b/sql/pgtap--1.2.0--1.2.1.sql index ce8c31a83575..26bd6a6dcdc0 100644 --- a/sql/pgtap--1.2.0--1.2.1.sql +++ b/sql/pgtap--1.2.0--1.2.1.sql @@ -143,3 +143,15 @@ BEGIN RETURN; END; $$ LANGUAGE plpgsql; + +-- 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( 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 13e878cdd157..9370c511fea9 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1983,13 +1983,19 @@ RETURNS NAME[] AS $$ SELECT * FROM _keys($1, $2) LIMIT 1; $$ LANGUAGE sql; --- col_is_pk( schema, table, column, description ) +-- 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( table, column, description ) +-- 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 ); @@ -2007,6 +2013,12 @@ 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 $$ diff --git a/test/expected/pktap.out b/test/expected/pktap.out index 2441c71f6f53..f9d3cc28494d 100644 --- a/test/expected/pktap.out +++ b/test/expected/pktap.out @@ -1,5 +1,5 @@ \unset ECHO -1..84 +1..90 ok 1 - has_pk( schema, table, description ) should pass ok 2 - has_pk( schema, table, description ) should have the proper description ok 3 - has_pk( schema, table, description ) should have the proper diagnostics @@ -39,48 +39,54 @@ ok 36 - hasnt_pk( table, description ) pass should have the proper diagnostics ok 37 - col_is_pk( schema, table, column, description ) should pass ok 38 - col_is_pk( schema, table, column, description ) should have the proper description ok 39 - col_is_pk( schema, table, column, description ) should have the proper diagnostics -ok 40 - col_is_pk( table, column, description ) should pass -ok 41 - col_is_pk( table, column, description ) should have the proper description -ok 42 - col_is_pk( table, column, description ) should have the proper diagnostics -ok 43 - col_is_pk( table, column ) should pass -ok 44 - col_is_pk( table, column ) should have the proper description -ok 45 - col_is_pk( table, column ) should have the proper diagnostics -ok 46 - col_is_pk( schema, table, column, description ) fail should fail -ok 47 - col_is_pk( schema, table, column, description ) fail should have the proper description -ok 48 - col_is_pk( schema, table, column, description ) fail should have the proper diagnostics -ok 49 - col_is_pk( table, column, description ) fail should fail -ok 50 - col_is_pk( table, column, description ) fail should have the proper description -ok 51 - col_is_pk( table, column, description ) fail should have the proper diagnostics -ok 52 - col_is_pk( schema, table, column[], description ) should pass -ok 53 - col_is_pk( schema, table, column[], description ) should have the proper description -ok 54 - col_is_pk( schema, table, column[], description ) should have the proper diagnostics -ok 55 - col_is_pk( table, column[], description ) should pass -ok 56 - col_is_pk( table, column[], description ) should have the proper description -ok 57 - col_is_pk( table, column[], description ) should have the proper diagnostics -ok 58 - col_is_pk( table, column[] ) should pass -ok 59 - col_is_pk( table, column[] ) should have the proper description -ok 60 - col_is_pk( table, column[] ) should have the proper diagnostics -ok 61 - col_isnt_pk( schema, table, column, description ) should fail -ok 62 - col_isnt_pk( schema, table, column, description ) should have the proper description -ok 63 - col_isnt_pk( schema, table, column, description ) should have the proper diagnostics -ok 64 - col_isnt_pk( table, column, description ) should fail -ok 65 - col_isnt_pk( table, column, description ) should have the proper description -ok 66 - col_isnt_pk( table, column, description ) should have the proper diagnostics -ok 67 - col_isnt_pk( table, column ) should fail -ok 68 - col_isnt_pk( table, column ) should have the proper description -ok 69 - col_isnt_pk( table, column ) should have the proper diagnostics -ok 70 - col_isnt_pk( schema, table, column, description ) pass should pass -ok 71 - col_isnt_pk( schema, table, column, description ) pass should have the proper description -ok 72 - col_isnt_pk( schema, table, column, description ) pass should have the proper diagnostics -ok 73 - col_isnt_pk( table, column, description ) pass should pass -ok 74 - col_isnt_pk( table, column, description ) pass should have the proper description -ok 75 - col_isnt_pk( table, column, description ) pass should have the proper diagnostics -ok 76 - col_isnt_pk( schema, table, column[], description ) should pass -ok 77 - col_isnt_pk( schema, table, column[], description ) should have the proper description -ok 78 - col_isnt_pk( schema, table, column[], description ) should have the proper diagnostics -ok 79 - col_isnt_pk( table, column[], description ) should pass -ok 80 - col_isnt_pk( table, column[], description ) should have the proper description -ok 81 - col_isnt_pk( table, column[], description ) should have the proper diagnostics -ok 82 - col_isnt_pk( table, column[] ) should pass -ok 83 - col_isnt_pk( table, column[] ) should have the proper description -ok 84 - col_isnt_pk( table, column[] ) should have the proper diagnostics +ok 40 - col_is_pk( schema, table, column ) should pass +ok 41 - col_is_pk( schema, table, column ) should have the proper description +ok 42 - col_is_pk( schema, table, column ) should have the proper diagnostics +ok 43 - col_is_pk( table, column, description ) should pass +ok 44 - col_is_pk( table, column, description ) should have the proper description +ok 45 - col_is_pk( table, column, description ) should have the proper diagnostics +ok 46 - col_is_pk( table, column ) should pass +ok 47 - col_is_pk( table, column ) should have the proper description +ok 48 - col_is_pk( table, column ) should have the proper diagnostics +ok 49 - col_is_pk( schema, table, column, description ) fail should fail +ok 50 - col_is_pk( schema, table, column, description ) fail should have the proper description +ok 51 - col_is_pk( schema, table, column, description ) fail should have the proper diagnostics +ok 52 - col_is_pk( table, column, description ) fail should fail +ok 53 - col_is_pk( table, column, description ) fail should have the proper description +ok 54 - col_is_pk( table, column, description ) fail should have the proper diagnostics +ok 55 - col_is_pk( schema, table, column[], description ) should pass +ok 56 - col_is_pk( schema, table, column[], description ) should have the proper description +ok 57 - col_is_pk( schema, table, column[], description ) should have the proper diagnostics +ok 58 - col_is_pk( schema, table, column[] ) should pass +ok 59 - col_is_pk( schema, table, column[] ) should have the proper description +ok 60 - col_is_pk( schema, table, column[] ) should have the proper diagnostics +ok 61 - col_is_pk( table, column[], description ) should pass +ok 62 - col_is_pk( table, column[], description ) should have the proper description +ok 63 - col_is_pk( table, column[], description ) should have the proper diagnostics +ok 64 - col_is_pk( table, column[] ) should pass +ok 65 - col_is_pk( table, column[] ) should have the proper description +ok 66 - col_is_pk( table, column[] ) should have the proper diagnostics +ok 67 - col_isnt_pk( schema, table, column, description ) should fail +ok 68 - col_isnt_pk( schema, table, column, description ) should have the proper description +ok 69 - col_isnt_pk( schema, table, column, description ) should have the proper diagnostics +ok 70 - col_isnt_pk( table, column, description ) should fail +ok 71 - col_isnt_pk( table, column, description ) should have the proper description +ok 72 - col_isnt_pk( table, column, description ) should have the proper diagnostics +ok 73 - col_isnt_pk( table, column ) should fail +ok 74 - col_isnt_pk( table, column ) should have the proper description +ok 75 - col_isnt_pk( table, column ) should have the proper diagnostics +ok 76 - col_isnt_pk( schema, table, column, description ) pass should pass +ok 77 - col_isnt_pk( schema, table, column, description ) pass should have the proper description +ok 78 - col_isnt_pk( schema, table, column, description ) pass should have the proper diagnostics +ok 79 - col_isnt_pk( table, column, description ) pass should pass +ok 80 - col_isnt_pk( table, column, description ) pass should have the proper description +ok 81 - col_isnt_pk( table, column, description ) pass should have the proper diagnostics +ok 82 - col_isnt_pk( schema, table, column[], description ) should pass +ok 83 - col_isnt_pk( schema, table, column[], description ) should have the proper description +ok 84 - col_isnt_pk( schema, table, column[], description ) should have the proper diagnostics +ok 85 - col_isnt_pk( table, column[], description ) should pass +ok 86 - col_isnt_pk( table, column[], description ) should have the proper description +ok 87 - col_isnt_pk( table, column[], description ) should have the proper diagnostics +ok 88 - col_isnt_pk( table, column[] ) should pass +ok 89 - col_isnt_pk( table, column[] ) should have the proper description +ok 90 - col_isnt_pk( table, column[] ) should have the proper diagnostics diff --git a/test/sql/pktap.sql b/test/sql/pktap.sql index b814277406fd..23ee6d4bf05d 100644 --- a/test/sql/pktap.sql +++ b/test/sql/pktap.sql @@ -1,7 +1,7 @@ \unset ECHO \i test/setup.sql -SELECT plan(84); +SELECT plan(90); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -139,6 +139,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + col_is_pk( 'public', 'sometab', 'id'::name ), + true, + 'col_is_pk( schema, table, column )', + 'Column public.sometab(id) should be a primary key', + '' +); + SELECT * FROM check_test( col_is_pk( 'sometab', 'id', 'sometab.id should be a pk' ), true, @@ -192,6 +200,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + col_is_pk( 'public', 'argh', ARRAY['id', 'name']::name[] ), + true, + 'col_is_pk( schema, table, column[] )', + 'Columns public.argh(id, name) should be a primary key', + '' +); + SELECT * FROM check_test( col_is_pk( 'argh', ARRAY['id', 'name'], 'id + name should be a pk' ), true, From 38f2d906f84ca8b92df65fe82350ce479e09b1f9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 21 May 2022 18:57:48 -0400 Subject: [PATCH 1129/1195] Document handline of temp schemas In the `fk_ok()` docs, where it's most likely to come up. The trick is to use `pg_my_temp_schema()::regnamespace::name` or `(SELECT nspname FROM pg_namespace WHERE oid = pg_my_temp_schema())` to specify the temporary schema name. Closes #109. --- Changes | 3 ++ doc/pgtap.mmd | 11 +++++ test/expected/fktap.out | 104 ++++++++++++++++++++-------------------- test/sql/fktap.sql | 33 ++++++++++++- 4 files changed, 98 insertions(+), 53 deletions(-) diff --git a/Changes b/Changes index 553753b0dcd4..281fe9ea79bb 100644 --- a/Changes +++ b/Changes @@ -17,6 +17,9 @@ Revision history for pgTAP * Added variants of `col_is_pk()` with schema and without description (variants without schema or description already existed). Thanks to Matteo Ferrando for the PR (#199)! +* Added a note on the use of temporary tables to the `fk_ok()` documentation, as + well as a test to ensure the suggested expressions work. Thanks to Jim Nasby + for highlighting the special treatment of the temporary schema (#109). 1.2.0 2021-12-05T18:08:13Z -------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index d0e50cc38d60..8917d9e0f719 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4912,6 +4912,17 @@ should reference `:pk_column.pk_table(:pk_column)`. Some examples: ARRAY['given_name', 'surname'], ); +To test constraints in a temporary table (for example, after running a function +that's expected to create one), either omit the schema names or use +`pg_my_temp_schema()::regnamespace::name` (on PostgreSQL 9.5 and higher) or +`(SELECT nspname FROM pg_namespace WHERE oid = pg_my_temp_schema())` (on +PostgreSQL 9.4 and lower) to specify the temporary schema name. For example: + + SELECT fk_ok( + pg_my_temp_schema()::regnamespace::name, 'tmpa', 'id', + pg_my_temp_schema()::regnamespace::name, 'tmpb', 'id' + ); + If the test fails, it will output useful diagnostics. For example this test: SELECT fk_ok( 'contacts', 'person_id', 'persons', 'id' ); diff --git a/test/expected/fktap.out b/test/expected/fktap.out index 675455f9c440..61652c306060 100644 --- a/test/expected/fktap.out +++ b/test/expected/fktap.out @@ -1,5 +1,5 @@ \unset ECHO -1..132 +1..134 ok 1 - has_fk( schema, table, description ) should pass ok 2 - has_fk( schema, table, description ) should have the proper description ok 3 - has_fk( table, description ) should pass @@ -82,53 +82,55 @@ ok 79 - col_isnt_fk( table, column[] ) should fail ok 80 - col_isnt_fk( table, column[] ) should have the proper description ok 81 - full fk_ok array should pass ok 82 - full fk_ok array should have the proper description -ok 83 - multiple fk fk_ok desc should pass -ok 84 - multiple fk fk_ok desc should have the proper description -ok 85 - fk_ok array desc should pass -ok 86 - fk_ok array desc should have the proper description -ok 87 - fk_ok array noschema desc should pass -ok 88 - fk_ok array noschema desc should have the proper description -ok 89 - multiple fk fk_ok noschema desc should pass -ok 90 - multiple fk fk_ok noschema desc should have the proper description -ok 91 - fk_ok array noschema should pass -ok 92 - fk_ok array noschema should have the proper description -ok 93 - basic fk_ok should pass -ok 94 - basic fk_ok should have the proper description -ok 95 - basic fk_ok desc should pass -ok 96 - basic fk_ok desc should have the proper description -ok 97 - basic fk_ok noschema should pass -ok 98 - basic fk_ok noschema should have the proper description -ok 99 - basic fk_ok noschema desc should pass -ok 100 - basic fk_ok noschema desc should have the proper description -ok 101 - basic fk_ok noschema desc should have the proper diagnostics -ok 102 - Test should pass -ok 103 - fk_ok fail should fail -ok 104 - fk_ok fail should have the proper description -ok 105 - fk_ok fail should have the proper diagnostics -ok 106 - fk_ok fail desc should fail -ok 107 - fk_ok fail desc should have the proper description -ok 108 - fk_ok fail desc should have the proper diagnostics -ok 109 - fk_ok fail no schema should fail -ok 110 - fk_ok fail no schema should have the proper description -ok 111 - fk_ok fail no schema should have the proper diagnostics -ok 112 - fk_ok fail no schema desc should fail -ok 113 - fk_ok fail no schema desc should have the proper description -ok 114 - fk_ok fail no schema desc should have the proper diagnostics -ok 115 - fk_ok bad PK test should fail -ok 116 - fk_ok bad PK test should have the proper description -ok 117 - fk_ok bad PK test should have the proper diagnostics -ok 118 - double fk schema test should pass -ok 119 - double fk schema test should have the proper description -ok 120 - double fk schema test should have the proper diagnostics -ok 121 - double fk test should pass -ok 122 - double fk test should have the proper description -ok 123 - double fk test should have the proper diagnostics -ok 124 - double fk and col schema test should pass -ok 125 - double fk and col schema test should have the proper description -ok 126 - double fk and col schema test should have the proper diagnostics -ok 127 - missing fk test should fail -ok 128 - missing fk test should have the proper description -ok 129 - missing fk test should have the proper diagnostics -ok 130 - bad FK column test should fail -ok 131 - bad FK column test should have the proper description -ok 132 - bad FK column test should have the proper diagnostics +ok 83 - pg_my_temp_schema() should pass +ok 84 - pg_my_temp_schema() should have the proper description +ok 85 - multiple fk fk_ok desc should pass +ok 86 - multiple fk fk_ok desc should have the proper description +ok 87 - fk_ok array desc should pass +ok 88 - fk_ok array desc should have the proper description +ok 89 - fk_ok array noschema desc should pass +ok 90 - fk_ok array noschema desc should have the proper description +ok 91 - multiple fk fk_ok noschema desc should pass +ok 92 - multiple fk fk_ok noschema desc should have the proper description +ok 93 - fk_ok array noschema should pass +ok 94 - fk_ok array noschema should have the proper description +ok 95 - basic fk_ok should pass +ok 96 - basic fk_ok should have the proper description +ok 97 - basic fk_ok desc should pass +ok 98 - basic fk_ok desc should have the proper description +ok 99 - basic fk_ok noschema should pass +ok 100 - basic fk_ok noschema should have the proper description +ok 101 - basic fk_ok noschema desc should pass +ok 102 - basic fk_ok noschema desc should have the proper description +ok 103 - basic fk_ok noschema desc should have the proper diagnostics +ok 104 - Test should pass +ok 105 - fk_ok fail should fail +ok 106 - fk_ok fail should have the proper description +ok 107 - fk_ok fail should have the proper diagnostics +ok 108 - fk_ok fail desc should fail +ok 109 - fk_ok fail desc should have the proper description +ok 110 - fk_ok fail desc should have the proper diagnostics +ok 111 - fk_ok fail no schema should fail +ok 112 - fk_ok fail no schema should have the proper description +ok 113 - fk_ok fail no schema should have the proper diagnostics +ok 114 - fk_ok fail no schema desc should fail +ok 115 - fk_ok fail no schema desc should have the proper description +ok 116 - fk_ok fail no schema desc should have the proper diagnostics +ok 117 - fk_ok bad PK test should fail +ok 118 - fk_ok bad PK test should have the proper description +ok 119 - fk_ok bad PK test should have the proper diagnostics +ok 120 - double fk schema test should pass +ok 121 - double fk schema test should have the proper description +ok 122 - double fk schema test should have the proper diagnostics +ok 123 - double fk test should pass +ok 124 - double fk test should have the proper description +ok 125 - double fk test should have the proper diagnostics +ok 126 - double fk and col schema test should pass +ok 127 - double fk and col schema test should have the proper description +ok 128 - double fk and col schema test should have the proper diagnostics +ok 129 - missing fk test should fail +ok 130 - missing fk test should have the proper description +ok 131 - missing fk test should have the proper diagnostics +ok 132 - bad FK column test should fail +ok 133 - bad FK column test should have the proper description +ok 134 - bad FK column test should have the proper diagnostics diff --git a/test/sql/fktap.sql b/test/sql/fktap.sql index 0ba65edfb723..8a776eac651a 100644 --- a/test/sql/fktap.sql +++ b/test/sql/fktap.sql @@ -1,11 +1,10 @@ \unset ECHO \i test/setup.sql -SELECT plan(132); +SELECT plan(134); --SELECT * from no_plan(); -- These will be rolled back. :-) -SET client_min_messages = warning; CREATE TABLE public.pk ( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '' @@ -45,6 +44,28 @@ CREATE TABLE public.fk4 ( id INT REFERENCES pk3(id) ); +SET client_min_messages = warning; +CREATE TEMP TABLE temp_pk( + id INT NOT NULL PRIMARY KEY, + name TEXT DEFAULT '' +); + +CREATE TEMP TABLE temp_fk ( + id INT NOT NULL PRIMARY KEY, + pk_id INT NOT NULL REFERENCES temp_pk(id) +); + +-- Create a funcion to return the temp scheme name. +DO $F$ +BEGIN + IF pg_version_num() >= 95000 THEN + EXECUTE 'CREATE FUNCTION tmpns() RETURNS NAME AS $$ SELECT pg_my_temp_schema()::regnamespace $$ LANGUAGE SQL;'; + ELSE + EXECUTE 'CREATE FUNCTION tmpns() RETURNS NAME AS $$ SELECT nspname FROM pg_namespace WHERE oid = pg_my_temp_schema() $$ LANGUAGE SQL;'; + END IF; +END; +$F$; + RESET client_min_messages; /****************************************************************************/ @@ -330,6 +351,14 @@ SELECT * FROM check_test( 'WHATEVER' ); +-- Make sure it works with the temp schema. +SELECT * FROM check_test( + fk_ok( tmpns(), 'temp_fk', ARRAY['pk_id'], tmpns(), 'temp_pk', ARRAY['id'], 'WHATEVER' ), + true, + 'pg_my_temp_schema()', + 'WHATEVER' +); + SELECT * FROM check_test( fk_ok( 'public', 'fk2', ARRAY['pk2_num', 'pk2_dot'], 'public', 'pk2', ARRAY['num', 'dot'] ), true, From 12e0d2f7d2eafb9ae46225c6bfed9d98f0427923 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 21 May 2022 19:15:37 -0400 Subject: [PATCH 1130/1195] Fix test failures --- test/sql/fktap.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/sql/fktap.sql b/test/sql/fktap.sql index 8a776eac651a..9dcee261aed5 100644 --- a/test/sql/fktap.sql +++ b/test/sql/fktap.sql @@ -5,6 +5,7 @@ SELECT plan(134); --SELECT * from no_plan(); -- These will be rolled back. :-) +SET client_min_messages = warning; CREATE TABLE public.pk ( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '' @@ -44,7 +45,6 @@ CREATE TABLE public.fk4 ( id INT REFERENCES pk3(id) ); -SET client_min_messages = warning; CREATE TEMP TABLE temp_pk( id INT NOT NULL PRIMARY KEY, name TEXT DEFAULT '' @@ -59,7 +59,7 @@ CREATE TEMP TABLE temp_fk ( DO $F$ BEGIN IF pg_version_num() >= 95000 THEN - EXECUTE 'CREATE FUNCTION tmpns() RETURNS NAME AS $$ SELECT pg_my_temp_schema()::regnamespace $$ LANGUAGE SQL;'; + EXECUTE 'CREATE FUNCTION tmpns() RETURNS NAME AS $$ SELECT pg_my_temp_schema()::regnamespace::name $$ LANGUAGE SQL;'; ELSE EXECUTE 'CREATE FUNCTION tmpns() RETURNS NAME AS $$ SELECT nspname FROM pg_namespace WHERE oid = pg_my_temp_schema() $$ LANGUAGE SQL;'; END IF; From ff4a30b59d602ab7a3ea48ce91402fa9bc83abdf Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 21 May 2022 19:19:54 -0400 Subject: [PATCH 1131/1195] Thanks @fluca1978! --- Changes | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 281fe9ea79bb..f7bc5fcb2eef 100644 --- a/Changes +++ b/Changes @@ -15,8 +15,8 @@ Revision history for pgTAP that depend on diffing files, as `pg_regress` does. Thanks to Matt DeLuco for highlighting the visual confusion of the indented diagnostic (#264). * Added variants of `col_is_pk()` with schema and without description (variants - without schema or description already existed). Thanks to Matteo Ferrando for - the PR (#199)! + without schema or description already existed). Thanks to Luca Ferrari and + Matteo Ferrando for the PRs (#178 & #199)! * Added a note on the use of temporary tables to the `fk_ok()` documentation, as well as a test to ensure the suggested expressions work. Thanks to Jim Nasby for highlighting the special treatment of the temporary schema (#109). From e1436a19f1bcc14992bf25ac300c54d48f298291 Mon Sep 17 00:00:00 2001 From: Robins Tharakan Date: Sat, 21 Jan 2023 11:47:38 +0000 Subject: [PATCH 1132/1195] Remove redundant DROP FUNCTIONs --- sql/pgtap--1.1.0--1.2.0.sql | 8 -------- 1 file changed, 8 deletions(-) diff --git a/sql/pgtap--1.1.0--1.2.0.sql b/sql/pgtap--1.1.0--1.2.0.sql index 6f3c52a29b13..ab4ab8dd13c3 100644 --- a/sql/pgtap--1.1.0--1.2.0.sql +++ b/sql/pgtap--1.1.0--1.2.0.sql @@ -489,14 +489,6 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE sql; -DROP FUNCTION _agg ( NAME ); -DROP FUNCTION _agg ( NAME, NAME[] ); -DROP FUNCTION _agg ( NAME, NAME ); -DROP FUNCTION _agg ( NAME, NAME, NAME[] ); -DROP FUNCTION _agg ( NAME ); -DROP FUNCTION _agg ( NAME, NAME[] ); -DROP FUNCTION _agg ( NAME, NAME ); -DROP FUNCTION _agg ( NAME, NAME, NAME[] ); DROP FUNCTION _agg ( NAME ); DROP FUNCTION _agg ( NAME, NAME[] ); DROP FUNCTION _agg ( NAME, NAME ); From c86203e9b2d2f81aa4945fab403739be005491e4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 21 Jan 2023 18:16:17 -0500 Subject: [PATCH 1133/1195] Note upgrade fix from @robins --- Changes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changes b/Changes index f7bc5fcb2eef..d01c77bd8ff9 100644 --- a/Changes +++ b/Changes @@ -20,6 +20,9 @@ Revision history for pgTAP * Added a note on the use of temporary tables to the `fk_ok()` documentation, as well as a test to ensure the suggested expressions work. Thanks to Jim Nasby for highlighting the special treatment of the temporary schema (#109). +* Removed redundant `DROP` statements from the 1.2.0 upgrade script that + prevented upgrades from working properly. Thanks to @robins for the PR + (#300)! 1.2.0 2021-12-05T18:08:13Z -------------------------- From 56c591fc3bf6b2dba49e15739625916d49e3fc27 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 20 Feb 2023 19:07:36 -0500 Subject: [PATCH 1134/1195] Use checkout@v3 --- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b46447b7c55d..17fe9ba8fc28 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: PGXN_PASSWORD: ${{ secrets.PGXN_PASSWORD }} steps: - name: Check out the repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Bundle the Release id: bundle run: pgxn-bundle diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8d4a3a681df9..3aa6d1b8f1af 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,7 +32,7 @@ jobs: UPDATE_FROM: "${{ matrix.update_from }}" steps: - run: pg-start ${{ matrix.version }} - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # Basic regression test. - run: pg-build-test From d0bcb773453ab9d7e5a1ae57de0970a4077f8914 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 20 May 2023 15:30:41 -0400 Subject: [PATCH 1135/1195] Remove SELECT rule from test It was not necessary to test the rule functions, and was present just to be sure that they worked with select rules. But the removal of select rules in postgres/postgres@b23cd18 breaks the test, so just use an ON INSERT rule to ensure that `rule_is_instead()` works. Resolves #309. --- Changes | 3 +++ test/expected/ruletap.out | 12 ++++++------ test/sql/ruletap.sql | 25 +++++++++++++------------ 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/Changes b/Changes index d01c77bd8ff9..6405bd249817 100644 --- a/Changes +++ b/Changes @@ -23,6 +23,9 @@ Revision history for pgTAP * Removed redundant `DROP` statements from the 1.2.0 upgrade script that prevented upgrades from working properly. Thanks to @robins for the PR (#300)! +* Fixed a test failure caused by the removal of support for select rules in + postgres/postgres@b23cd18 (expected in Postgres 16). Thanks to @Deltaus for + the report (#309)! 1.2.0 2021-12-05T18:08:13Z -------------------------- diff --git a/test/expected/ruletap.out b/test/expected/ruletap.out index d5c3fcbe0544..ef42cd0e1154 100644 --- a/test/expected/ruletap.out +++ b/test/expected/ruletap.out @@ -72,9 +72,9 @@ ok 69 - rule_is_on(schema, table, rule, insert, desc) should have the proper dia ok 70 - rule_is_on(schema, table, rule, update, desc) should pass ok 71 - rule_is_on(schema, table, rule, update, desc) should have the proper description ok 72 - rule_is_on(schema, table, rule, update, desc) should have the proper diagnostics -ok 73 - rule_is_on(schema, table, rule, SELECT, desc) should pass -ok 74 - rule_is_on(schema, table, rule, SELECT, desc) should have the proper description -ok 75 - rule_is_on(schema, table, rule, SELECT, desc) should have the proper diagnostics +ok 73 - rule_is_on(schema, table, rule, insert, desc) should pass +ok 74 - rule_is_on(schema, table, rule, insert, desc) should have the proper description +ok 75 - rule_is_on(schema, table, rule, insert, desc) should have the proper diagnostics ok 76 - rule_is_on(schema, table, rule, delete, desc) should pass ok 77 - rule_is_on(schema, table, rule, delete, desc) should have the proper description ok 78 - rule_is_on(schema, table, rule, delete, desc) should have the proper diagnostics @@ -105,9 +105,9 @@ ok 102 - rule_is_on(table, rule, insert, desc) should have the proper diagnostic ok 103 - rule_is_on(table, rule, update, desc) should pass ok 104 - rule_is_on(table, rule, update, desc) should have the proper description ok 105 - rule_is_on(table, rule, update, desc) should have the proper diagnostics -ok 106 - rule_is_on(table, rule, SELECT, desc) should pass -ok 107 - rule_is_on(table, rule, SELECT, desc) should have the proper description -ok 108 - rule_is_on(table, rule, SELECT, desc) should have the proper diagnostics +ok 106 - rule_is_on(table, rule, insert, desc) should pass +ok 107 - rule_is_on(table, rule, insert, desc) should have the proper description +ok 108 - rule_is_on(table, rule, insert, desc) should have the proper diagnostics ok 109 - rule_is_on(table, rule, delete, desc) should pass ok 110 - rule_is_on(table, rule, delete, desc) should have the proper description ok 111 - rule_is_on(table, rule, delete, desc) should have the proper diagnostics diff --git a/test/sql/ruletap.sql b/test/sql/ruletap.sql index df93b125a430..5a4ed5f3960d 100644 --- a/test/sql/ruletap.sql +++ b/test/sql/ruletap.sql @@ -15,8 +15,9 @@ CREATE TABLE public.sometab( CREATE RULE ins_me AS ON INSERT TO public.sometab DO NOTHING; CREATE RULE upd_me AS ON UPDATE TO public.sometab DO ALSO SELECT now(); -CREATE TABLE public.toview ( id INT ); -CREATE RULE "_RETURN" AS ON SELECT TO public.toview DO INSTEAD SELECT 42 AS id; +CREATE TABLE public.sprockets ( id INT ); +CREATE RULE ins_me AS ON INSERT TO public.sprockets DO INSTEAD NOTHING; +CREATE RULE del_me AS ON delete TO public.sprockets DO INSTEAD NOTHING; CREATE TABLE public.widgets (id int); CREATE RULE del_me AS ON DELETE TO public.widgets DO NOTHING; @@ -127,7 +128,7 @@ SELECT * FROM check_test( -- Test rule_is_instead(). SELECT * FROM check_test( - rule_is_instead( 'public', 'toview', '_RETURN', 'whatever' ), + rule_is_instead( 'public', 'sprockets', 'ins_me', 'whatever' ), true, 'rule_is_instead(schema, table, rule, desc)', 'whatever', @@ -135,10 +136,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - rule_is_instead( 'public', 'toview', '_RETURN'::name ), + rule_is_instead( 'public', 'sprockets', 'ins_me'::name ), true, 'rule_is_instead(schema, table, rule)', - 'Rule "_RETURN" on relation public.toview should be an INSTEAD rule', + 'Rule ins_me on relation public.sprockets should be an INSTEAD rule', '' ); @@ -159,7 +160,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - rule_is_instead( 'toview', '_RETURN', 'whatever' ), + rule_is_instead( 'sprockets', 'ins_me', 'whatever' ), true, 'rule_is_instead(table, rule, desc)', 'whatever', @@ -167,10 +168,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - rule_is_instead( 'toview', '_RETURN'::name ), + rule_is_instead( 'sprockets', 'ins_me'::name ), true, 'rule_is_instead(table, rule)', - 'Rule "_RETURN" on relation toview should be an INSTEAD rule', + 'Rule ins_me on relation sprockets should be an INSTEAD rule', '' ); @@ -226,9 +227,9 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - rule_is_on( 'public', 'toview', '_RETURN', 'SELECT', 'whatever' ), + rule_is_on( 'public', 'sprockets', 'ins_me', 'insert', 'whatever' ), true, - 'rule_is_on(schema, table, rule, SELECT, desc)', + 'rule_is_on(schema, table, rule, insert, desc)', 'whatever', '' ); @@ -315,9 +316,9 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - rule_is_on( 'toview', '_RETURN', 'SELECT', 'whatever' ), + rule_is_on( 'sprockets', 'ins_me', 'insert', 'whatever' ), true, - 'rule_is_on(table, rule, SELECT, desc)', + 'rule_is_on(table, rule, insert, desc)', 'whatever', '' ); From cc0e4371d6bd8c29bdf57ee5f367a83ba84cd686 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 20 May 2023 15:33:57 -0400 Subject: [PATCH 1136/1195] Test Postres 15 And remove test for upgrades from 9.1, which apparently is no longer supported. --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3aa6d1b8f1af..4e3e144784ab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,8 @@ jobs: strategy: matrix: include: - - { version: 14, upgrade_to: "", update_from: 0.99.0 } + - { version: 15, upgrade_to: "", update_from: 0.99.0 } + - { version: 14, upgrade_to: 15, update_from: 0.99.0 } - { version: 13, upgrade_to: 14, update_from: 0.99.0 } - { version: 12, upgrade_to: 13, update_from: 0.99.0 } - { version: 11, upgrade_to: 12, update_from: 0.99.0 } # Versions prior to 0.99.0 don't support Postgres 11 @@ -20,10 +21,9 @@ jobs: - { version: 9.4, upgrade_to: 9.5, update_from: 0.95.0 } - { version: 9.3, upgrade_to: 9.4, update_from: 0.95.0 } - { version: 9.2, upgrade_to: 9.3, update_from: "" } # updatecheck is not supported prior to 9.3 - - { version: 9.1, upgrade_to: 9.2, update_from: "" } # Also test pg_upgrade across many versions - - { version: 9.1, upgrade_to: 14, update_from: "", suffix: –14 } - - { version: 9.4, upgrade_to: 14, update_from: "", suffix: –14 } + - { version: 9.2, upgrade_to: 15, update_from: "", suffix: –15 } + - { version: 9.4, upgrade_to: 15, update_from: "", suffix: –15 } name: 🐘 PostgreSQL ${{ matrix.version }}${{ matrix.suffix }} runs-on: ubuntu-latest container: pgxn/pgxn-tools From 96a7a416311ea5f2fa140f59cfdf7c7afbded17c Mon Sep 17 00:00:00 2001 From: "Jehan-Guillaume (ioguix) de Rorthais" Date: Thu, 8 Jun 2023 14:06:57 +0200 Subject: [PATCH 1137/1195] Escape "_" when using LIKE operator --- Changes | 3 +++ sql/pgtap--1.2.0--1.2.1.sql | 27 +++++++++++++++++++++++++++ sql/pgtap.sql.in | 4 ++-- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 6405bd249817..1aad7b29dd74 100644 --- a/Changes +++ b/Changes @@ -26,6 +26,9 @@ Revision history for pgTAP * Fixed a test failure caused by the removal of support for select rules in postgres/postgres@b23cd18 (expected in Postgres 16). Thanks to @Deltaus for the report (#309)! +* Fixed `LIKE` expression in `schemas_are()` to escape the underscore wildcard, + making it `pg\_%` instead of `pg_%`. Thanks to Jehan-Guillaume (ioguix) de + Rorthais for the PR (#311)! 1.2.0 2021-12-05T18:08:13Z -------------------------- diff --git a/sql/pgtap--1.2.0--1.2.1.sql b/sql/pgtap--1.2.0--1.2.1.sql index 26bd6a6dcdc0..4a8b9f801a07 100644 --- a/sql/pgtap--1.2.0--1.2.1.sql +++ b/sql/pgtap--1.2.0--1.2.1.sql @@ -155,3 +155,30 @@ 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; + +-- 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 9370c511fea9..eb732b25ab8f 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -4640,7 +4640,7 @@ RETURNS TEXT AS $$ ARRAY( SELECT nspname FROM pg_catalog.pg_namespace - WHERE nspname NOT LIKE 'pg_%' + WHERE nspname NOT LIKE 'pg\_%' AND nspname <> 'information_schema' EXCEPT SELECT $1[i] @@ -4652,7 +4652,7 @@ RETURNS TEXT AS $$ EXCEPT SELECT nspname FROM pg_catalog.pg_namespace - WHERE nspname NOT LIKE 'pg_%' + WHERE nspname NOT LIKE 'pg\_%' AND nspname <> 'information_schema' ), $2 From 6944c1c48cf1f109aa02b5e7d365443232383ba2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 14 May 2022 16:40:30 -0400 Subject: [PATCH 1138/1195] Allow short type names for function args. Enabled by the `_funkargs()` function added in 2888bb8. There are still a few places where types must use their ful names, notably the return value argument to `function_returns()` and the type arguments to `col_type_is()` and `cast_context_is()`. Resolves #292. --- Changes | 7 +- doc/pgtap.mmd | 29 ++-- sql/pgtap--1.2.0--1.2.1.sql | 127 ++++++++++++++++++ sql/pgtap.sql.in | 28 ++-- test/sql/functap.sql | 258 ++++++++++++++++++------------------ tools/util.sh | 7 - 6 files changed, 287 insertions(+), 169 deletions(-) diff --git a/Changes b/Changes index 1aad7b29dd74..2841700f7454 100644 --- a/Changes +++ b/Changes @@ -29,6 +29,11 @@ Revision history for pgTAP * Fixed `LIKE` expression in `schemas_are()` to escape the underscore wildcard, making it `pg\_%` instead of `pg_%`. Thanks to Jehan-Guillaume (ioguix) de Rorthais for the PR (#311)! +* Updated function-testing functions to allow short names for argument types, so + that they can be specified as, e.g., `int` and `bool` rather than `integer` + and `boolean`. The return value argument to `function_returns` and and data + type tests such as `col_type_is` and `cast_context_is` must still use full + type names. Thanks to @wphilips53 for the suggestion (#292). 1.2.0 2021-12-05T18:08:13Z -------------------------- @@ -444,7 +449,7 @@ Revision history for pgTAP 0.24 2010-05-24T23:33:22Z -------------------------- * Got `sql/artap.sql` tests passing again when building with `$TAPSCHEMA` set. -* Changed to saner source URL in `contrib/pgtap.spec`. +* Changed to nicer source URL in `contrib/pgtap.spec`. * Fixed a bug in `has_member()` where it failed to confirm that users were actually members of a group. * Fixed a bug where `display_type()` would include the schema name in its diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 8917d9e0f719..e190774ac276 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -3733,15 +3733,9 @@ regard to its arguments. Some examples: ); SELECT has_function( 'do_something' ); - SELECT has_function( 'do_something', ARRAY['integer'] ); + SELECT has_function( 'do_something', ARRAY['int'] ); SELECT has_function( 'do_something', ARRAY['numeric'] ); -The `:args` argument should be formatted as it would be displayed in the view -of a function using the `\df` command in `psql`. For example, even if you have -a numeric column with a precision of 8, you should specify `ARRAY['numeric']`. -If you created a `varchar(64)` column, you should pass the `:args` argument as -`ARRAY['character varying']`. - If you wish to use the two-argument form of `has_function()`, specifying only the schema and the function name, you must cast the `:function` argument to `:name` in order to disambiguate it from from the @@ -5303,9 +5297,9 @@ Feeling Funky Perhaps more important than testing the database schema is testing your custom functions. Especially if you write functions that provide the interface for -clients to interact with the database, making sure that they work will save -you time in the long run. So check out these assertions to maintain your -sanity. +clients to interact with the database, making sure that they work will save you +time in the long run. So use these assertions to save yourself heartache in the +future. ### `can()` ### @@ -5428,15 +5422,14 @@ But then you check with `has_function()` first, right? `:description` : A short description of the test. -Tests that a particular function returns a particular data type. The `:args[]` -and `:type` arguments should be formatted as they would be displayed in the -view of a function using the `\df` command in `psql`. For example, use -"character varying" rather than "varchar", and "boolean" rather than "bool". -For set returning functions, the `:type` argument should start with "setof " -(yes, lowercase). Examples: +Tests that a particular function returns a particular data type. The `:type` +argument should be formatted as it would be displayed in the view of a function +using the `\df` command in `psql`. For example, use "character varying" rather +than "varchar", and "boolean" rather than "bool". For set returning functions, +the `:type` argument should start with "setof " (yes, lowercase). Examples: - SELECT function_returns( 'myschema', 'foo', ARRAY['integer', 'text'], 'integer' ); - SELECT function_returns( 'do_something', 'setof bool' ); + SELECT function_returns( 'myschema', 'foo', ARRAY['int', 'text'], 'integer' ); + SELECT function_returns( 'do_something', 'setof boolean' ); SELECT function_returns( 'do_something', ARRAY['integer'], 'boolean' ); SELECT function_returns( 'do_something', ARRAY['numeric'], 'numeric' ); diff --git a/sql/pgtap--1.2.0--1.2.1.sql b/sql/pgtap--1.2.0--1.2.1.sql index 4a8b9f801a07..9cea2c187995 100644 --- a/sql/pgtap--1.2.0--1.2.1.sql +++ b/sql/pgtap--1.2.0--1.2.1.sql @@ -181,4 +181,131 @@ RETURNS TEXT AS $$ ), $2 ); +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.name = $1 + AND f.args = _funkargs($2) + AND f.is_visible; +$$ 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 name = $1 + AND args = _funkargs($2) + AND is_visible; +$$ 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 name = $1 + AND args = _funkargs($2) + AND is_visible; +$$ LANGUAGE SQL; + +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, 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; + +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 name = $1 + AND args = _funkargs($2) + AND is_visible; +$$ LANGUAGE SQL; + +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.name = $1 + AND f.args = _funkargs($2) + AND f.is_visible; +$$ 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index eb732b25ab8f..c0bf52eccc9d 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -5573,7 +5573,7 @@ RETURNS NAME AS $$ JOIN pg_catalog.pg_language l ON f.langoid = l.oid WHERE f.schema = $1 and f.name = $2 - AND f.args = array_to_string($3, ',') + AND f.args = _funkargs($3) $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _lang ( NAME, NAME ) @@ -5591,7 +5591,7 @@ RETURNS NAME AS $$ FROM tap_funky f JOIN pg_catalog.pg_language l ON f.langoid = l.oid WHERE f.name = $1 - AND f.args = array_to_string($2, ',') + AND f.args = _funkargs($2) AND f.is_visible; $$ LANGUAGE SQL; @@ -5674,7 +5674,7 @@ RETURNS TEXT AS $$ FROM tap_funky WHERE schema = $1 AND name = $2 - AND args = array_to_string($3, ',') + AND args = _funkargs($3) $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _returns ( NAME, NAME ) @@ -5687,7 +5687,7 @@ RETURNS TEXT AS $$ SELECT returns FROM tap_funky WHERE name = $1 - AND args = array_to_string($2, ',') + AND args = _funkargs($2) AND is_visible; $$ LANGUAGE SQL; @@ -5765,7 +5765,7 @@ RETURNS BOOLEAN AS $$ FROM tap_funky WHERE schema = $1 AND name = $2 - AND args = array_to_string($3, ',') + AND args = _funkargs($3) $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _definer ( NAME, NAME ) @@ -5778,7 +5778,7 @@ RETURNS BOOLEAN AS $$ SELECT is_definer FROM tap_funky WHERE name = $1 - AND args = array_to_string($2, ',') + AND args = _funkargs($2) AND is_visible; $$ LANGUAGE SQL; @@ -5916,7 +5916,7 @@ RETURNS BOOLEAN AS $$ FROM tap_funky WHERE schema = $2 AND name = $3 - AND args = array_to_string($4, ',') + AND args = _funkargs($4) $$ LANGUAGE SQL; -- _type_func(type, schema, function) @@ -5931,7 +5931,7 @@ RETURNS BOOLEAN AS $$ SELECT kind = $1 FROM tap_funky WHERE name = $2 - AND args = array_to_string($3, ',') + AND args = _funkargs($3) AND is_visible; $$ LANGUAGE SQL; @@ -6071,7 +6071,7 @@ RETURNS BOOLEAN AS $$ FROM tap_funky WHERE schema = $1 AND name = $2 - AND args = array_to_string($3, ',') + AND args = _funkargs($3) $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _strict ( NAME, NAME ) @@ -6084,7 +6084,7 @@ RETURNS BOOLEAN AS $$ SELECT is_strict FROM tap_funky WHERE name = $1 - AND args = array_to_string($2, ',') + AND args = _funkargs($2) AND is_visible; $$ LANGUAGE SQL; @@ -6231,7 +6231,7 @@ RETURNS TEXT AS $$ FROM tap_funky f WHERE f.schema = $1 and f.name = $2 - AND f.args = array_to_string($3, ',') + AND f.args = _funkargs($3) $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _vol ( NAME, NAME ) @@ -6245,7 +6245,7 @@ RETURNS TEXT AS $$ SELECT _expand_vol(volatility) FROM tap_funky f WHERE f.name = $1 - AND f.args = array_to_string($2, ',') + AND f.args = _funkargs($2) AND f.is_visible; $$ LANGUAGE SQL; @@ -8697,7 +8697,7 @@ RETURNS NAME AS $$ FROM tap_funky WHERE schema = $1 AND name = $2 - AND args = array_to_string($3, ',') + AND args = _funkargs($3) $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _get_func_owner ( NAME, NAME[] ) @@ -8705,7 +8705,7 @@ RETURNS NAME AS $$ SELECT owner FROM tap_funky WHERE name = $1 - AND args = array_to_string($2, ',') + AND args = _funkargs($2) AND is_visible $$ LANGUAGE SQL; diff --git a/test/sql/functap.sql b/test/sql/functap.sql index fb3297248e3b..3d77581335f9 100644 --- a/test/sql/functap.sql +++ b/test/sql/functap.sql @@ -114,18 +114,18 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_function( 'lower', '{text}'::name[] ), + has_function( 'abs', '{int}'::name[] ), true, 'simple function with 1 arg', - 'Function lower(text) should exist', + 'Function abs(int) should exist', '' ); SELECT * FROM check_test( - has_function( 'decode', '{text,text}'::name[] ), + has_function( 'age', '{timestamptz,timestamptz}'::name[] ), true, 'simple function with 2 args', - 'Function decode(text, text) should exist', + 'Function age(timestamptz, timestamptz) should exist', '' ); @@ -393,7 +393,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_lang_is( 'someschema', 'bah', '{"integer", "text"}'::name[], 'plpgsql', 'whatever' ), + function_lang_is( 'someschema', 'bah', '{"int", "text"}'::name[], 'plpgsql', 'whatever' ), true, 'function_lang_is(schema, func, args, plpgsql, desc)', 'whatever', @@ -401,10 +401,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_lang_is( 'someschema', 'bah', '{"integer", "text"}'::name[], 'plpgsql' ), + function_lang_is( 'someschema', 'bah', '{"int", "text"}'::name[], 'plpgsql' ), true, 'function_lang_is(schema, func, args, plpgsql)', - 'Function someschema.bah(integer, text) should be written in plpgsql', + 'Function someschema.bah(int, text) should be written in plpgsql', '' ); @@ -426,11 +426,11 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_lang_is( 'someschema', 'why', '{"integer", "text"}'::name[], 'plpgsql' ), + function_lang_is( 'someschema', 'why', '{"int", "text"}'::name[], 'plpgsql' ), false, 'function_lang_is(schema, func, args, plpgsql)', - 'Function someschema.why(integer, text) should be written in plpgsql', - ' Function someschema.why(integer, text) does not exist' + 'Function someschema.why(int, text) should be written in plpgsql', + ' Function someschema.why(int, text) does not exist' ); SELECT * FROM check_test( @@ -483,7 +483,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_lang_is( 'oww', '{"integer", "text"}'::name[], 'plpgsql', 'whatever' ), + function_lang_is( 'oww', '{"int", "text"}'::name[], 'plpgsql', 'whatever' ), true, 'function_lang_is(func, args, plpgsql, desc)', 'whatever', @@ -491,10 +491,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_lang_is( 'oww', '{"integer", "text"}'::name[], 'plpgsql' ), + function_lang_is( 'oww', '{"int", "text"}'::name[], 'plpgsql' ), true, 'function_lang_is(func, args, plpgsql)', - 'Function oww(integer, text) should be written in plpgsql', + 'Function oww(int, text) should be written in plpgsql', '' ); @@ -516,11 +516,11 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_lang_is( 'why', '{"integer", "text"}'::name[], 'plpgsql' ), + function_lang_is( 'why', '{"int", "text"}'::name[], 'plpgsql' ), false, 'function_lang_is(func, args, plpgsql)', - 'Function why(integer, text) should be written in plpgsql', - ' Function why(integer, text) does not exist' + 'Function why(int, text) should be written in plpgsql', + ' Function why(int, text) does not exist' ); SELECT * FROM check_test( @@ -575,7 +575,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'someschema', 'bah', ARRAY['integer', 'text'], 'boolean', 'whatever' ), + function_returns( 'someschema', 'bah', ARRAY['int', 'text'], 'boolean', 'whatever' ), true, 'function_returns(schema, func, args, bool, false)', 'whatever', @@ -583,10 +583,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'someschema', 'bah', ARRAY['integer', 'text'], 'boolean' ), + function_returns( 'someschema', 'bah', ARRAY['int', 'text'], 'boolean' ), true, 'function_returns(schema, func, args, bool)', - 'Function someschema.bah(integer, text) should return boolean', + 'Function someschema.bah(int, text) should return boolean', '' ); @@ -671,7 +671,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'oww', ARRAY['integer', 'text'], 'boolean', 'whatever' ), + function_returns( 'oww', ARRAY['int', 'text'], 'boolean', 'whatever' ), true, 'function_returns(func, args, bool, false)', 'whatever', @@ -679,10 +679,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'oww', ARRAY['integer', 'text'], 'boolean' ), + function_returns( 'oww', ARRAY['int', 'text'], 'boolean' ), true, 'function_returns(func, args, bool)', - 'Function oww(integer, text) should return boolean', + 'Function oww(int, text) should return boolean', '' ); @@ -785,7 +785,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + is_definer( 'public', 'oww', ARRAY['int', 'text'], 'whatever' ), false, 'is_definer(schema, func, args, desc)', 'whatever', @@ -793,7 +793,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + isnt_definer( 'public', 'oww', ARRAY['int', 'text'], 'whatever' ), true, 'isnt_definer(schema, func, args, desc)', 'whatever', @@ -801,18 +801,18 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_definer( 'public', 'oww', ARRAY['integer', 'text'] ), + is_definer( 'public', 'oww', ARRAY['int', 'text'] ), false, 'is_definer(schema, func, args)', - 'Function public.oww(integer, text) should be security definer', + 'Function public.oww(int, text) should be security definer', '' ); SELECT * FROM check_test( - isnt_definer( 'public', 'oww', ARRAY['integer', 'text'] ), + isnt_definer( 'public', 'oww', ARRAY['int', 'text'] ), true, 'isnt_definer(schema, func, args)', - 'Function public.oww(integer, text) should not be security definer', + 'Function public.oww(int, text) should not be security definer', '' ); @@ -881,7 +881,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + is_definer( 'public', 'oww', ARRAY['int', 'text'], 'whatever' ), false, 'is_definer(schema, func, args, desc)', 'whatever', @@ -889,7 +889,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_definer( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + isnt_definer( 'public', 'oww', ARRAY['int', 'text'], 'whatever' ), true, 'isnt_definer(schema, func, args, desc)', 'whatever', @@ -897,18 +897,18 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_definer( 'public', 'oww', ARRAY['integer', 'text'] ), + is_definer( 'public', 'oww', ARRAY['int', 'text'] ), false, 'is_definer(schema, func, args)', - 'Function public.oww(integer, text) should be security definer', + 'Function public.oww(int, text) should be security definer', '' ); SELECT * FROM check_test( - isnt_definer( 'public', 'oww', ARRAY['integer', 'text'] ), + isnt_definer( 'public', 'oww', ARRAY['int', 'text'] ), true, 'isnt_definer(schema, func, args)', - 'Function public.oww(integer, text) should not be security definer', + 'Function public.oww(int, text) should not be security definer', '' ); @@ -977,7 +977,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_definer( 'oww', ARRAY['integer', 'text'], 'whatever' ), + is_definer( 'oww', ARRAY['int', 'text'], 'whatever' ), false, 'is_definer(func, args, desc)', 'whatever', @@ -985,7 +985,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_definer( 'oww', ARRAY['integer', 'text'], 'whatever' ), + isnt_definer( 'oww', ARRAY['int', 'text'], 'whatever' ), true, 'isnt_definer(func, args, desc)', 'whatever', @@ -993,18 +993,18 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_definer( 'oww', ARRAY['integer', 'text'] ), + is_definer( 'oww', ARRAY['int', 'text'] ), false, 'is_definer(func, args)', - 'Function oww(integer, text) should be security definer', + 'Function oww(int, text) should be security definer', '' ); SELECT * FROM check_test( - isnt_definer( 'oww', ARRAY['integer', 'text'] ), + isnt_definer( 'oww', ARRAY['int', 'text'] ), true, 'isnt_definer(func, args)', - 'Function oww(integer, text) should not be security definer', + 'Function oww(int, text) should not be security definer', '' ); @@ -1078,7 +1078,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_normal_function( 'someschema', 'bah', ARRAY['integer', 'text'], 'whatever' ), + is_normal_function( 'someschema', 'bah', ARRAY['int', 'text'], 'whatever' ), true, 'is_normal_function(schema, func, args, desc)', 'whatever', @@ -1094,7 +1094,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_normal_function( 'someschema', 'bah', ARRAY['integer', 'text'], 'whatever' ), + isnt_normal_function( 'someschema', 'bah', ARRAY['int', 'text'], 'whatever' ), false, 'is_normal_function(schema, func, args, desc)', 'whatever', @@ -1161,18 +1161,18 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_normal_function( 'someschema', 'bah', ARRAY['integer', 'text'] ), + is_normal_function( 'someschema', 'bah', ARRAY['int', 'text'] ), true, 'is_normal_function(schema, func2, args)', - 'Function someschema.bah(integer, text) should be a normal function', + 'Function someschema.bah(int, text) should be a normal function', '' ); SELECT * FROM check_test( - isnt_normal_function( 'someschema', 'bah', ARRAY['integer', 'text'] ), + isnt_normal_function( 'someschema', 'bah', ARRAY['int', 'text'] ), false, 'isnt_normal_function(schema, func2, args)', - 'Function someschema.bah(integer, text) should not be a normal function', + 'Function someschema.bah(int, text) should not be a normal function', '' ); @@ -1363,7 +1363,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_normal_function( 'oww', ARRAY['integer', 'text'], 'whatever' ), + is_normal_function( 'oww', ARRAY['int', 'text'], 'whatever' ), true, 'is_normal_function(func, args, desc)', 'whatever', @@ -1371,7 +1371,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_normal_function( 'oww', ARRAY['integer', 'text'], 'whatever' ), + isnt_normal_function( 'oww', ARRAY['int', 'text'], 'whatever' ), false, 'isnt_normal_function(func, args, desc)', 'whatever', @@ -1414,18 +1414,18 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_normal_function( 'oww', ARRAY['integer', 'text'] ), + is_normal_function( 'oww', ARRAY['int', 'text'] ), true, 'is_normal_function(func, noargs)', - 'Function oww(integer, text) should be a normal function', + 'Function oww(int, text) should be a normal function', '' ); SELECT * FROM check_test( - isnt_normal_function( 'oww', ARRAY['integer', 'text'] ), + isnt_normal_function( 'oww', ARRAY['int', 'text'] ), false, 'isnt_normal_function(func, noargs)', - 'Function oww(integer, text) should not be a normal function', + 'Function oww(int, text) should not be a normal function', '' ); @@ -1602,7 +1602,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + is_aggregate( 'public', 'oww', ARRAY['int', 'text'], 'whatever' ), false, 'is_aggregate(schema, func, args, desc)', 'whatever', @@ -1610,7 +1610,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_aggregate( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + isnt_aggregate( 'public', 'oww', ARRAY['int', 'text'], 'whatever' ), true, 'isnt_aggregate(schema, func, args, desc)', 'whatever', @@ -1653,18 +1653,18 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), + is_aggregate( 'public', 'oww', ARRAY['int', 'text'] ), false, 'is_aggregate(schema, func, args)', - 'Function public.oww(integer, text) should be an aggregate function', + 'Function public.oww(int, text) should be an aggregate function', '' ); SELECT * FROM check_test( - isnt_aggregate( 'public', 'oww', ARRAY['integer', 'text'] ), + isnt_aggregate( 'public', 'oww', ARRAY['int', 'text'] ), true, 'isnt_aggregate(schema, func, args)', - 'Function public.oww(integer, text) should not be an aggregate function', + 'Function public.oww(int, text) should not be an aggregate function', '' ); @@ -1774,7 +1774,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_aggregate( 'oww', ARRAY['integer', 'text'], 'whatever' ), + is_aggregate( 'oww', ARRAY['int', 'text'], 'whatever' ), false, 'is_aggregate(func, args, desc)', 'whatever', @@ -1782,7 +1782,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_aggregate( 'oww', ARRAY['integer', 'text'], 'whatever' ), + isnt_aggregate( 'oww', ARRAY['int', 'text'], 'whatever' ), true, 'isnt_aggregate(func, args, desc)', 'whatever', @@ -1825,18 +1825,18 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_aggregate( 'oww', ARRAY['integer', 'text'] ), + is_aggregate( 'oww', ARRAY['int', 'text'] ), false, 'is_aggregate(func, args)', - 'Function oww(integer, text) should be an aggregate function', + 'Function oww(int, text) should be an aggregate function', '' ); SELECT * FROM check_test( - isnt_aggregate( 'oww', ARRAY['integer', 'text'] ), + isnt_aggregate( 'oww', ARRAY['int', 'text'] ), true, 'isnt_aggregate(func, args)', - 'Function oww(integer, text) should not be an aggregate function', + 'Function oww(int, text) should not be an aggregate function', '' ); @@ -1933,7 +1933,7 @@ SELECT * FROM check_test( -- is_window ( NAME, NAME, NAME[], TEXT ) -- isnt_window ( NAME, NAME, NAME[], TEXT ) SELECT * FROM check_test( - is_window( 'pg_catalog', 'ntile', ARRAY['integer'], 'whatever' ), + is_window( 'pg_catalog', 'ntile', ARRAY['int'], 'whatever' ), true, 'is_window(schema, win, arg, desc)', 'whatever', @@ -1941,7 +1941,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_window( 'pg_catalog', 'ntile', ARRAY['integer'], 'whatever' ), + isnt_window( 'pg_catalog', 'ntile', ARRAY['int'], 'whatever' ), false, 'isnt_window(schema, win, arg, desc)', 'whatever', @@ -1949,7 +1949,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_window( 'someschema', 'bah', ARRAY['integer', 'text'], 'whatever' ), + is_window( 'someschema', 'bah', ARRAY['int', 'text'], 'whatever' ), false, 'is_window(schema, func, arg, desc)', 'whatever', @@ -1957,7 +1957,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_window( 'someschema', 'bah', ARRAY['integer', 'text'], 'whatever' ), + isnt_window( 'someschema', 'bah', ARRAY['int', 'text'], 'whatever' ), true, 'isnt_window(schema, func, arg, desc)', 'whatever', @@ -1998,52 +1998,52 @@ SELECT * FROM check_test( -- Test diagnostics SELECT * FROM check_test( - is_window( 'someschema', 'nope', ARRAY['integer'], 'whatever' ), + is_window( 'someschema', 'nope', ARRAY['int'], 'whatever' ), false, 'is_window(schema, nowin, arg, desc)', 'whatever', - ' Function someschema.nope(integer) does not exist' + ' Function someschema.nope(int) does not exist' ); SELECT * FROM check_test( - isnt_window( 'someschema', 'nope', ARRAY['integer'], 'whatever' ), + isnt_window( 'someschema', 'nope', ARRAY['int'], 'whatever' ), false, 'isnt_window(schema, nowin, arg, desc)', 'whatever', - ' Function someschema.nope(integer) does not exist' + ' Function someschema.nope(int) does not exist' ); -- is_window ( NAME, NAME, NAME[] ) -- isnt_window ( NAME, NAME, NAME[] ) SELECT * FROM check_test( - is_window( 'pg_catalog', 'ntile', ARRAY['integer'] ), + is_window( 'pg_catalog', 'ntile', ARRAY['int'] ), true, 'is_window(schema, win, arg)', - 'Function pg_catalog.ntile(integer) should be a window function', + 'Function pg_catalog.ntile(int) should be a window function', '' ); SELECT * FROM check_test( - isnt_window( 'pg_catalog', 'ntile', ARRAY['integer'] ), + isnt_window( 'pg_catalog', 'ntile', ARRAY['int'] ), false, 'isnt_window(schema, win, arg)', - 'Function pg_catalog.ntile(integer) should not be a window function', + 'Function pg_catalog.ntile(int) should not be a window function', '' ); SELECT * FROM check_test( - is_window( 'someschema', 'bah', ARRAY['integer', 'text'] ), + is_window( 'someschema', 'bah', ARRAY['int', 'text'] ), false, 'is_window(schema, func, arg)', - 'Function someschema.bah(integer, text) should be a window function', + 'Function someschema.bah(int, text) should be a window function', '' ); SELECT * FROM check_test( - isnt_window( 'someschema', 'bah', ARRAY['integer', 'text'] ), + isnt_window( 'someschema', 'bah', ARRAY['int', 'text'] ), true, 'isnt_window(schema, func, arg)', - 'Function someschema.bah(integer, text) should not be a window function', + 'Function someschema.bah(int, text) should not be a window function', '' ); @@ -2081,19 +2081,19 @@ SELECT * FROM check_test( -- Test diagnostics SELECT * FROM check_test( - is_window( 'someschema', 'nada', ARRAY['integer'] ), + is_window( 'someschema', 'nada', ARRAY['int'] ), false, 'is_window(schema, nowin, arg)', - 'Function someschema.nada(integer) should be a window function', - ' Function someschema.nada(integer) does not exist' + 'Function someschema.nada(int) should be a window function', + ' Function someschema.nada(int) does not exist' ); SELECT * FROM check_test( - isnt_window( 'someschema', 'nada', ARRAY['integer'] ), + isnt_window( 'someschema', 'nada', ARRAY['int'] ), false, 'isnt_window(schema, nowin, arg)', - 'Function someschema.nada(integer) should not be a window function', - ' Function someschema.nada(integer) does not exist' + 'Function someschema.nada(int) should not be a window function', + ' Function someschema.nada(int) does not exist' ); -- is_window ( NAME, NAME, TEXT ) @@ -2217,7 +2217,7 @@ SELECT * FROM check_test( -- is_window ( NAME, NAME[], TEXT ) -- isnt_window ( NAME, NAME[], TEXT ) SELECT * FROM check_test( - is_window( 'ntile', ARRAY['integer'], 'whatever' ), + is_window( 'ntile', ARRAY['int'], 'whatever' ), true, 'is_window(win, arg, desc)', 'whatever', @@ -2225,7 +2225,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_window( 'ntile', ARRAY['integer'], 'whatever' ), + isnt_window( 'ntile', ARRAY['int'], 'whatever' ), false, 'isnt_window(win, arg, desc)', 'whatever', @@ -2233,7 +2233,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_window( 'oww', ARRAY['integer', 'text'], 'whatever' ), + is_window( 'oww', ARRAY['int', 'text'], 'whatever' ), false, 'is_window(func, arg, desc)', 'whatever', @@ -2241,7 +2241,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_window( 'oww', ARRAY['integer', 'text'], 'whatever' ), + isnt_window( 'oww', ARRAY['int', 'text'], 'whatever' ), true, 'isnt_window(func, arg, desc)', 'whatever', @@ -2282,52 +2282,52 @@ SELECT * FROM check_test( -- Test diagnostics SELECT * FROM check_test( - is_window( 'nada', ARRAY['integer'], 'whatever' ), + is_window( 'nada', ARRAY['int'], 'whatever' ), false, 'is_window(nowin, arg, desc)', 'whatever', - ' Function nada(integer) does not exist' + ' Function nada(int) does not exist' ); SELECT * FROM check_test( - isnt_window( 'nada', ARRAY['integer'], 'whatever' ), + isnt_window( 'nada', ARRAY['int'], 'whatever' ), false, 'isnt_window(nowin, arg, desc)', 'whatever', - ' Function nada(integer) does not exist' + ' Function nada(int) does not exist' ); -- is_window( NAME, NAME[] ) -- isnt_window( NAME, NAME[] ) SELECT * FROM check_test( - is_window( 'ntile', ARRAY['integer'] ), + is_window( 'ntile', ARRAY['int'] ), true, 'is_window(win, arg, desc)', - 'Function ntile(integer) should be a window function', + 'Function ntile(int) should be a window function', '' ); SELECT * FROM check_test( - isnt_window( 'ntile', ARRAY['integer'] ), + isnt_window( 'ntile', ARRAY['int'] ), false, 'isnt_window(win, arg, desc)', - 'Function ntile(integer) should not be a window function', + 'Function ntile(int) should not be a window function', '' ); SELECT * FROM check_test( - is_window( 'oww', ARRAY['integer', 'text'] ), + is_window( 'oww', ARRAY['int', 'text'] ), false, 'is_window(func, arg, desc)', - 'Function oww(integer, text) should be a window function', + 'Function oww(int, text) should be a window function', '' ); SELECT * FROM check_test( - isnt_window( 'oww', ARRAY['integer', 'text'] ), + isnt_window( 'oww', ARRAY['int', 'text'] ), true, 'isnt_window(func, arg, desc)', - 'Function oww(integer, text) should not be a window function', + 'Function oww(int, text) should not be a window function', '' ); @@ -2365,19 +2365,19 @@ SELECT * FROM check_test( -- Test diagnostics SELECT * FROM check_test( - is_window( 'nope', ARRAY['integer'] ), + is_window( 'nope', ARRAY['int'] ), false, 'is_window(nowin, arg, desc)', - 'Function nope(integer) should be a window function', - ' Function nope(integer) does not exist' + 'Function nope(int) should be a window function', + ' Function nope(int) does not exist' ); SELECT * FROM check_test( - isnt_window( 'nope', ARRAY['integer'] ), + isnt_window( 'nope', ARRAY['int'] ), false, 'isnt_window(nowin, arg, desc)', - 'Function nope(integer) should not be a window function', - ' Function nope(integer) does not exist' + 'Function nope(int) should not be a window function', + ' Function nope(int) does not exist' ); -- is_window( NAME, TEXT ) @@ -2525,7 +2525,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_strict( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + is_strict( 'public', 'oww', ARRAY['int', 'text'], 'whatever' ), false, 'is_strict(schema, func, args, desc)', 'whatever', @@ -2533,7 +2533,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_strict( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + isnt_strict( 'public', 'oww', ARRAY['int', 'text'], 'whatever' ), true, 'isnt_strict(schema, func, args, desc)', 'whatever', @@ -2541,18 +2541,18 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_strict( 'public', 'oww', ARRAY['integer', 'text'] ), + is_strict( 'public', 'oww', ARRAY['int', 'text'] ), false, 'is_strict(schema, func, args)', - 'Function public.oww(integer, text) should be strict', + 'Function public.oww(int, text) should be strict', '' ); SELECT * FROM check_test( - isnt_strict( 'public', 'oww', ARRAY['integer', 'text'] ), + isnt_strict( 'public', 'oww', ARRAY['int', 'text'] ), true, 'isnt_strict(schema, func, args)', - 'Function public.oww(integer, text) should not be strict', + 'Function public.oww(int, text) should not be strict', '' ); @@ -2589,7 +2589,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_strict( 'public', 'oww', ARRAY['integer', 'text'], 'whatever' ), + isnt_strict( 'public', 'oww', ARRAY['int', 'text'], 'whatever' ), true, 'isnt_strict(schema, func, args, desc)', 'whatever', @@ -2597,10 +2597,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_strict( 'public', 'oww', ARRAY['integer', 'text'] ), + isnt_strict( 'public', 'oww', ARRAY['int', 'text'] ), true, 'isnt_strict(schema, func, args)', - 'Function public.oww(integer, text) should not be strict', + 'Function public.oww(int, text) should not be strict', '' ); @@ -2637,7 +2637,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_strict( 'oww', ARRAY['integer', 'text'], 'whatever' ), + is_strict( 'oww', ARRAY['int', 'text'], 'whatever' ), false, 'is_strict(func, args, desc)', 'whatever', @@ -2645,7 +2645,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - isnt_strict( 'oww', ARRAY['integer', 'text'], 'whatever' ), + isnt_strict( 'oww', ARRAY['int', 'text'], 'whatever' ), true, 'isnt_strict(func, args, desc)', 'whatever', @@ -2653,18 +2653,18 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - is_strict( 'oww', ARRAY['integer', 'text'] ), + is_strict( 'oww', ARRAY['int', 'text'] ), false, 'is_strict(func, args)', - 'Function oww(integer, text) should be strict', + 'Function oww(int, text) should be strict', '' ); SELECT * FROM check_test( - isnt_strict( 'oww', ARRAY['integer', 'text'] ), + isnt_strict( 'oww', ARRAY['int', 'text'] ), true, 'isnt_strict(func, args)', - 'Function oww(integer, text) should not be strict', + 'Function oww(int, text) should not be strict', '' ); @@ -2719,7 +2719,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - volatility_is( 'public', 'oww', ARRAY['integer', 'text'], 'immutable', 'whatever' ), + volatility_is( 'public', 'oww', ARRAY['int', 'text'], 'immutable', 'whatever' ), true, 'function_volatility(schema, func, args, immutable, desc)', 'whatever', @@ -2743,10 +2743,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - volatility_is( 'public', 'oww', ARRAY['integer', 'text'], 'immutable' ), + volatility_is( 'public', 'oww', ARRAY['int', 'text'], 'immutable' ), true, 'function_volatility(schema, func, args, immutable)', - 'Function public.oww(integer, text) should be IMMUTABLE' + 'Function public.oww(int, text) should be IMMUTABLE' '' ); @@ -2799,7 +2799,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - volatility_is( 'oww', ARRAY['integer', 'text'], 'immutable', 'whatever' ), + volatility_is( 'oww', ARRAY['int', 'text'], 'immutable', 'whatever' ), true, 'function_volatility(func, args, immutable, desc)', 'whatever', @@ -2823,10 +2823,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - volatility_is( 'oww', ARRAY['integer', 'text'], 'immutable' ), + volatility_is( 'oww', ARRAY['int', 'text'], 'immutable' ), true, 'function_volatility(func, args, immutable)', - 'Function oww(integer, text) should be IMMUTABLE' + 'Function oww(int, text) should be IMMUTABLE' '' ); diff --git a/tools/util.sh b/tools/util.sh index b781490c11d3..39e83381916e 100644 --- a/tools/util.sh +++ b/tools/util.sh @@ -110,13 +110,6 @@ die() { exit $return } -file_sanity() { - for file in "$@"; do - [ -e "$file" ] || die 1 "error: file '$file' does not exist" - [ -r "$file" ] || die 1 "error: file '$file' is not readable" - done -} - db_exists() { local exists exists=`psql -qtc "SELECT EXISTS( SELECT 1 FROM pg_database WHERE datname = '$dbname' )" postgres $@ | tr -d ' '` From 1e1d745fc8a4312e8a43b9514a00141c9453d1c3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 15 May 2022 18:47:28 -0400 Subject: [PATCH 1139/1195] Allow short names for most type args To complement the support for short aliases for function argument specifications added in 8fa72c4. Affects the following test functions: * `function_returns()` * `col_type_is()` * `has_cast()` * `hasnt_cast()` * `cast_context_is()` * `has_operator()` * `hasnt_operator()` * `has_leftop()` * `hasnt_leftop()` * `has_rightop()` * `hasnt_rightop()` * `domain_type_is()` * `domain_type_isnt()` * `types_are()` * `domains_are()` * `enums_are()` --- Changes | 5 + doc/pgtap.mmd | 11 +-- sql/pgtap--1.2.0--1.2.1.sql | 182 ++++++++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 69 ++++++++------ test/sql/aretap.sql | 1 - test/sql/coltap.sql | 12 +-- test/sql/functap.sql | 24 ++--- test/sql/hastap.sql | 110 +++++++++++----------- 8 files changed, 304 insertions(+), 110 deletions(-) diff --git a/Changes b/Changes index 2841700f7454..a1503535103b 100644 --- a/Changes +++ b/Changes @@ -34,6 +34,11 @@ Revision history for pgTAP and `boolean`. The return value argument to `function_returns` and and data type tests such as `col_type_is` and `cast_context_is` must still use full type names. Thanks to @wphilips53 for the suggestion (#292). +* Updated type-testing functions to allow short names for types, and updated + function-testing functions to allow short names for argument types. This means + that common aliases for standard types can be specified as, e.g., `int` and + `bool` rather than `integer` and `boolean`. Thanks to @wphilips53 for the + suggestion (#292). 1.2.0 2021-12-05T18:08:13Z -------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index e190774ac276..21c6b43de3f4 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4499,8 +4499,8 @@ will fail if the table or column in question does not exist. The type argument should be formatted as it would be displayed in the view of a table using the `\d` command in `psql`. For example, if you have a numeric column with a precision of 8, you should specify "numeric(8,0)". If you -created a `varchar(64)` column, you should pass the type as "character -varying(64)". Example: +created a `varchar(64)` column, you should pass the type as "varchar(64)" or +"character varying(64)". Example: SELECT col_type_is( 'myschema', 'sometable', 'somecolumn', 'numeric(10,2)' ); @@ -5423,10 +5423,9 @@ But then you check with `has_function()` first, right? : A short description of the test. Tests that a particular function returns a particular data type. The `:type` -argument should be formatted as it would be displayed in the view of a function -using the `\df` command in `psql`. For example, use "character varying" rather -than "varchar", and "boolean" rather than "bool". For set returning functions, -the `:type` argument should start with "setof " (yes, lowercase). Examples: +argument may be formatted with full or aliased type names, e.g., `integer`, +`int4`, or `int`. For set returning functions, the `:type` argument should start +with "setof " (yes, lowercase). Examples: SELECT function_returns( 'myschema', 'foo', ARRAY['int', 'text'], 'integer' ); SELECT function_returns( 'do_something', 'setof boolean' ); diff --git a/sql/pgtap--1.2.0--1.2.1.sql b/sql/pgtap--1.2.0--1.2.1.sql index 9cea2c187995..c8532f09c70a 100644 --- a/sql/pgtap--1.2.0--1.2.1.sql +++ b/sql/pgtap--1.2.0--1.2.1.sql @@ -309,3 +309,185 @@ RETURNS NAME AS $$ AND args = _funkargs($2) AND is_visible $$ 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 _quote_ident_like(TEXT, TEXT) +RETURNS TEXT AS $$ +DECLARE + typname TEXT := _typename($1); + pcision TEXT := COALESCE(substring($1 FROM '[(][^")]+[)]$'), ''); +BEGIN + -- Just return it if rhs isn't quoted. + IF $2 !~ '"' THEN RETURN typname || pcision; END IF; + + -- Otherwise return it with the type part quoted. + RETURN quote_ident(typname) || pcision; +END; +$$ LANGUAGE plpgsql; + +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, 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( 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, 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; + +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( 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; + +-- _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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index c0bf52eccc9d..36bfe9506e0e 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1454,26 +1454,24 @@ RETURNS TEXT AS $$ 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 _quote_ident_like(TEXT, TEXT) RETURNS TEXT AS $$ DECLARE - have TEXT; - pcision TEXT; + typname TEXT := _typename($1); + pcision TEXT := COALESCE(substring($1 FROM '[(][^")]+[)]$'), ''); BEGIN -- Just return it if rhs isn't quoted. - IF $2 !~ '"' THEN RETURN $1; END IF; - - -- If it's quoted ident without precision, return it quoted. - IF $2 ~ '"$' THEN RETURN quote_ident($1); END IF; + IF $2 !~ '"' THEN RETURN typname || pcision; END IF; - pcision := substring($1 FROM '[(][^")]+[)]$'); - - -- Just quote it if thre is no precision. - if pcision IS NULL THEN RETURN quote_ident($1); END IF; - - -- Quote the non-precision part and concatenate with precision. - RETURN quote_ident(substring($1 FOR char_length($1) - char_length(pcision))) - || pcision; + -- Otherwise return it with the type part quoted. + RETURN quote_ident(typname) || pcision; END; $$ LANGUAGE plpgsql; @@ -4247,6 +4245,7 @@ RETURNS TEXT AS $$ ); $$ 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 ( @@ -4256,13 +4255,14 @@ RETURNS BOOLEAN AS $$ WHERE n.nspname = $2 AND o.oprname = $3 AND CASE o.oprkind WHEN 'l' THEN $1 IS NULL - ELSE _cmp_types(o.oprleft, $1) END + ELSE _cmp_types(o.oprleft, _typename($1)) END AND CASE o.oprkind WHEN 'r' THEN $4 IS NULL - ELSE _cmp_types(o.oprright, $4) END + 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 ( @@ -4271,13 +4271,14 @@ RETURNS BOOLEAN AS $$ 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, $1) END + ELSE _cmp_types(o.oprleft, _typename($1)) END AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL - ELSE _cmp_types(o.oprright, $3) END + 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 ( @@ -4286,9 +4287,9 @@ RETURNS BOOLEAN AS $$ 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, $1) END + ELSE _cmp_types(o.oprleft, _typename($1)) END AND CASE o.oprkind WHEN 'r' THEN $3 IS NULL - ELSE _cmp_types(o.oprright, $3) END + ELSE _cmp_types(o.oprright, _typename($3)) END ); $$ LANGUAGE SQL; @@ -5696,10 +5697,20 @@ 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), $4, $5 ); + SELECT _func_compare($1, $2, $3, _returns($1, $2, $3), _retval($4), $5 ); $$ LANGUAGE SQL; -- function_returns( schema, function, args[], type ) @@ -5715,7 +5726,7 @@ $$ 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), $3, $4 ); + SELECT _func_compare($1, $2, _returns($1, $2), _retval($3), $4 ); $$ LANGUAGE SQL; -- function_returns( schema, function, type ) @@ -5731,7 +5742,7 @@ $$ 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), $3, $4 ); + SELECT _func_compare(NULL, $1, $2, _returns($1, $2), _retval($3), $4 ); $$ LANGUAGE SQL; -- function_returns( function, args[], type ) @@ -5747,7 +5758,7 @@ $$ 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), $2, $3 ); + SELECT _func_compare(NULL, $1, _returns($1), _retval($2), $3 ); $$ LANGUAGE SQL; -- function_returns( function, type ) @@ -7606,11 +7617,11 @@ RETURNS TEXT AS $$ AND n.nspname = $1 AND t.typtype = ANY( COALESCE($4, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT - SELECT $2[i] + SELECT _typename($2[i]) FROM generate_series(1, array_upper($2, 1)) s(i) ), ARRAY( - SELECT $2[i] + SELECT _typename($2[i]) FROM generate_series(1, array_upper($2, 1)) s(i) EXCEPT SELECT t.typname @@ -7658,11 +7669,11 @@ RETURNS TEXT AS $$ AND pg_catalog.pg_type_is_visible(t.oid) AND t.typtype = ANY( COALESCE($3, ARRAY['b', 'c', 'd', 'p', 'e']) ) EXCEPT - SELECT $1[i] + SELECT _typename($1[i]) FROM generate_series(1, array_upper($1, 1)) s(i) ), ARRAY( - SELECT $1[i] + SELECT _typename($1[i]) FROM generate_series(1, array_upper($1, 1)) s(i) EXCEPT SELECT t.typname @@ -7681,7 +7692,6 @@ RETURNS TEXT AS $$ ); $$ LANGUAGE SQL; - -- types_are( types[], description ) CREATE OR REPLACE FUNCTION types_are ( NAME[], TEXT ) RETURNS TEXT AS $$ @@ -8076,7 +8086,6 @@ BEGIN END; $$ LANGUAGE plpgsql; - -- casts_are( casts[], description ) CREATE OR REPLACE FUNCTION casts_are ( TEXT[], TEXT ) RETURNS TEXT AS $$ diff --git a/test/sql/aretap.sql b/test/sql/aretap.sql index 8e2fe948aabc..7306432be95f 100644 --- a/test/sql/aretap.sql +++ b/test/sql/aretap.sql @@ -82,7 +82,6 @@ CREATE TYPE someschema."myType" AS ( foo INT ); - RESET client_min_messages; /****************************************************************************/ diff --git a/test/sql/coltap.sql b/test/sql/coltap.sql index a950a3434e40..5674f751db3a 100644 --- a/test/sql/coltap.sql +++ b/test/sql/coltap.sql @@ -167,18 +167,18 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test col_type_is(). SELECT * FROM check_test( - col_type_is( 'public', 'sometab', 'name', 'pg_catalog', 'text', 'name is text' ), + col_type_is( 'public', 'sometab', 'ctstz', 'pg_catalog', 'timestamptz', 'ctstz is tstz' ), true, 'col_type_is( sch, tab, col, sch, type, desc )', - 'name is text', + 'ctstz is tstz', '' ); SELECT * FROM check_test( - col_type_is( 'public', 'sometab', 'name', 'pg_catalog'::name, 'text' ), + col_type_is( 'public', 'sometab', 'ctstz', 'pg_catalog'::name, 'timestamptz' ), true, 'col_type_is( sch, tab, col, sch, type, desc )', - 'Column public.sometab.name should be type pg_catalog.text', + 'Column public.sometab.ctstz should be type pg_catalog.timestamptz', '' ); @@ -226,7 +226,7 @@ SELECT * FROM check_test( -- Try failures. SELECT * FROM check_test( - col_type_is( 'public', 'sometab', 'name', 'pg_catalog', 'integer', 'whatever' ), + col_type_is( 'public', 'sometab', 'name', 'pg_catalog', 'int', 'whatever' ), false, 'col_type_is( sch, tab, col, sch, type, desc ) fail', 'whatever', @@ -299,7 +299,7 @@ SELECT * FROM check_test( 'col_type_is( tab, col, type ) fail', 'Column sometab.name should be type int4', ' have: text - want: int4' + want: integer' ); -- Make sure missing column is in diagnostics. diff --git a/test/sql/functap.sql b/test/sql/functap.sql index 3d77581335f9..0bcba9eefe74 100644 --- a/test/sql/functap.sql +++ b/test/sql/functap.sql @@ -559,7 +559,7 @@ SELECT * FROM check_test( /****************************************************************************/ -- Test function_returns(). SELECT * FROM check_test( - function_returns( 'someschema', 'huh', '{}'::name[], 'boolean', 'whatever' ), + function_returns( 'someschema', 'huh', '{}'::name[], 'bool', 'whatever' ), true, 'function_returns(schema, func, 0 args, bool, desc)', 'whatever', @@ -575,7 +575,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'someschema', 'bah', ARRAY['int', 'text'], 'boolean', 'whatever' ), + function_returns( 'someschema', 'bah', ARRAY['int', 'text'], 'bool', 'whatever' ), true, 'function_returns(schema, func, args, bool, false)', 'whatever', @@ -591,7 +591,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'public', 'pet', '{}'::name[], 'setof boolean', 'whatever' ), + function_returns( 'public', 'pet', '{}'::name[], 'setof bool', 'whatever' ), true, 'function_returns(schema, func, 0 args, setof bool, desc)', 'whatever', @@ -607,7 +607,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'someschema', 'huh', 'boolean', 'whatever' ), + function_returns( 'someschema', 'huh', 'bool', 'whatever' ), true, 'function_returns(schema, func, bool, desc)', 'whatever', @@ -623,7 +623,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'someschema', 'bah', 'boolean', 'whatever' ), + function_returns( 'someschema', 'bah', 'bool', 'whatever' ), true, 'function_returns(schema, other func, bool, false)', 'whatever', @@ -639,7 +639,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'public', 'pet', 'setof boolean', 'whatever' ), + function_returns( 'public', 'pet', 'setof bool', 'whatever' ), true, 'function_returns(schema, func, setof bool, desc)', 'whatever', @@ -655,7 +655,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'yay', '{}'::name[], 'boolean', 'whatever' ), + function_returns( 'yay', '{}'::name[], 'bool', 'whatever' ), true, 'function_returns(func, 0 args, bool, desc)', 'whatever', @@ -671,7 +671,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'oww', ARRAY['int', 'text'], 'boolean', 'whatever' ), + function_returns( 'oww', ARRAY['int', 'text'], 'bool', 'whatever' ), true, 'function_returns(func, args, bool, false)', 'whatever', @@ -687,7 +687,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'pet', '{}'::name[], 'setof boolean', 'whatever' ), + function_returns( 'pet', '{}'::name[], 'setof bool', 'whatever' ), true, 'function_returns(func, 0 args, setof bool, desc)', 'whatever', @@ -703,7 +703,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'yay', 'boolean', 'whatever' ), + function_returns( 'yay', 'bool', 'whatever' ), true, 'function_returns(func, bool, desc)', 'whatever', @@ -719,7 +719,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'oww', 'boolean', 'whatever' ), + function_returns( 'oww', 'bool', 'whatever' ), true, 'function_returns(other func, bool, false)', 'whatever', @@ -735,7 +735,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - function_returns( 'pet', 'setof boolean', 'whatever' ), + function_returns( 'pet', 'setof bool', 'whatever' ), true, 'function_returns(func, setof bool, desc)', 'whatever', diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index 5cdc9ca984e3..6ce878284a69 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1360,7 +1360,7 @@ SELECT * FROM check_test( -- Test has_operator(). SELECT * FROM check_test( - has_operator( 'integer', 'pg_catalog', '<=', 'integer', 'boolean', 'desc' ), + has_operator( 'integer', 'pg_catalog', '<=', 'int', 'bool', 'desc' ), true, 'has_operator( left, schema, name, right, result, desc )', 'desc', @@ -1376,7 +1376,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_operator( 'integer', '<=', 'integer', 'boolean', 'desc' ), + has_operator( 'integer', '<=', 'int', 'bool', 'desc' ), true, 'has_operator( left, name, right, result, desc )', 'desc', @@ -1392,7 +1392,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_operator( 'integer', '<=', 'integer', 'desc' ), + has_operator( 'integer', '<=', 'int', 'desc' ), true, 'has_operator( left, name, right, desc )', 'desc', @@ -1408,7 +1408,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_operator( 'integer', 'pg_catalog', '<=', 'text', 'boolean', 'desc' ), + has_operator( 'integer', 'pg_catalog', '<=', 'text', 'bool', 'desc' ), false, 'has_operator( left, schema, name, right, result, desc ) fail', 'desc', @@ -1424,7 +1424,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_operator( 'integer', '<=', 'text', 'boolean', 'desc' ), + has_operator( 'integer', '<=', 'text', 'bool', 'desc' ), false, 'has_operator( left, name, right, result, desc ) fail', 'desc', @@ -1467,10 +1467,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_operator( 'integer', 'pg_catalog', '<=', 'integer', 'boolean'::name ), + hasnt_operator( 'integer', 'pg_catalog', '<=', 'int', 'bool'::name ), false, 'hasnt_operator( left, schema, name, right, result ) fail', - 'Operator pg_catalog.<=(integer,integer) RETURNS boolean should not exist', + 'Operator pg_catalog.<=(integer,int) RETURNS bool should not exist', '' ); @@ -1483,10 +1483,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_operator( 'integer', '<=', 'integer', 'boolean'::name ), + hasnt_operator( 'integer', '<=', 'int', 'bool'::name ), false, 'hasnt_operator( left, name, right, result ) fail', - 'Operator <=(integer,integer) RETURNS boolean should not exist', + 'Operator <=(integer,int) RETURNS bool should not exist', '' ); @@ -1499,10 +1499,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_operator( 'integer', '<=', 'integer'::name ), + hasnt_operator( 'integer', '<=', 'int'::name ), false, 'hasnt_operator( left, name, right ) fail', - 'Operator <=(integer,integer) should not exist', + 'Operator <=(integer,int) should not exist', '' ); @@ -1515,10 +1515,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_operator( 'integer', 'pg_catalog', '<=', 'text', 'boolean'::name ), + hasnt_operator( 'integer', 'pg_catalog', '<=', 'text', 'bool'::name ), true, 'hasnt_operator( left, schema, name, right, result )', - 'Operator pg_catalog.<=(integer,text) RETURNS boolean should not exist', + 'Operator pg_catalog.<=(integer,text) RETURNS bool should not exist', '' ); @@ -1531,10 +1531,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_operator( 'integer', '<=', 'text', 'boolean'::name ), + hasnt_operator( 'integer', '<=', 'text', 'bool'::name ), true, 'hasnt_operator( left, name, right, result )', - 'Operator <=(integer,text) RETURNS boolean should not exist', + 'Operator <=(integer,text) RETURNS bool should not exist', '' ); @@ -1558,7 +1558,7 @@ SELECT * FROM check_test( -- Test has_leftop(). SELECT * FROM check_test( - has_leftop( 'pg_catalog', '+', 'bigint', 'bigint', 'desc' ), + has_leftop( 'pg_catalog', '+', 'bigint', 'int8', 'desc' ), true, 'has_leftop( schema, name, right, result, desc )', 'desc', @@ -1574,7 +1574,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_leftop( '+', 'bigint', 'bigint', 'desc' ), + has_leftop( '+', 'bigint', 'int8', 'desc' ), true, 'has_leftop( name, right, result, desc )', 'desc', @@ -1582,10 +1582,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_leftop( '+', 'bigint', 'bigint'::name ), + has_leftop( '+', 'bigint', 'int8'::name ), true, 'has_leftop( name, right, result )', - 'Left operator +(NONE,bigint) RETURNS bigint should exist', + 'Left operator +(NONE,bigint) RETURNS int8 should exist', '' ); @@ -1598,10 +1598,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_leftop( '+', 'bigint' ), + has_leftop( '+', 'int8' ), true, 'has_leftop( name, right )', - 'Left operator +(NONE,bigint) should exist', + 'Left operator +(NONE,int8) should exist', '' ); @@ -1622,7 +1622,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_leftop( '+', 'text', 'integer', 'desc' ), + has_leftop( '+', 'text', 'inte', 'desc' ), false, 'has_leftop( name, right, result, desc ) fail', 'desc', @@ -1630,10 +1630,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_leftop( '+', 'text', 'integer'::name ), + has_leftop( '+', 'text', 'int'::name ), false, 'has_leftop( name, right, result ) fail', - 'Left operator +(NONE,text) RETURNS integer should exist', + 'Left operator +(NONE,text) RETURNS int should exist', '' ); @@ -1657,7 +1657,7 @@ SELECT * FROM check_test( -- Test hasnt_leftop(). SELECT * FROM check_test( - hasnt_leftop( 'pg_catalog', '+', 'bigint', 'bigint', 'desc' ), + hasnt_leftop( 'pg_catalog', '+', 'bigint', 'int8', 'desc' ), false, 'hasnt_leftop( schema, name, right, result, desc ) fail', 'desc', @@ -1665,10 +1665,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_leftop( 'pg_catalog', '+', 'bigint', 'bigint'::name ), + hasnt_leftop( 'pg_catalog', '+', 'bigint', 'int8'::name ), false, 'hasnt_leftop( schema, name, right, result ) fail', - 'Left operator pg_catalog.+(NONE,bigint) RETURNS bigint should not exist', + 'Left operator pg_catalog.+(NONE,bigint) RETURNS int8 should not exist', '' ); @@ -1681,10 +1681,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_leftop( '+', 'bigint', 'bigint'::name ), + hasnt_leftop( '+', 'int8', 'int8'::name ), false, 'hasnt_leftop( name, right, result ) fail', - 'Left operator +(NONE,bigint) RETURNS bigint should not exist', + 'Left operator +(NONE,int8) RETURNS int8 should not exist', '' ); @@ -1697,10 +1697,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_leftop( '+', 'bigint' ), + hasnt_leftop( '+', 'int8' ), false, 'hasnt_leftop( name, right ) fail', - 'Left operator +(NONE,bigint) should not exist', + 'Left operator +(NONE,int8) should not exist', '' ); @@ -1729,10 +1729,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_leftop( '+', 'text', 'bigint'::name ), + hasnt_leftop( '+', 'text', 'int8'::name ), true, 'hasnt_leftop( name, right, result )', - 'Left operator +(NONE,text) RETURNS bigint should not exist', + 'Left operator +(NONE,text) RETURNS int8 should not exist', '' ); @@ -1793,7 +1793,7 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - has_rightop( 'bigint', '!', 'desc' ), + has_rightop( 'int8', '!', 'desc' ), true, 'has_rightop( left, name, desc )', 'desc', @@ -1801,10 +1801,10 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - has_rightop( 'bigint', '!' ), + has_rightop( 'int8', '!' ), true, 'has_rightop( left, name )', - 'Right operator !(bigint,NONE) should exist', + 'Right operator !(int8,NONE) should exist', '' ) AS b LOOP RETURN NEXT tap.b; END LOOP; @@ -1910,7 +1910,7 @@ DECLARE BEGIN IF pg_version_num() < 140000 THEN FOR tap IN SELECT * FROM check_test( - hasnt_rightop( 'bigint', 'pg_catalog', '!', 'numeric', 'desc' ), + hasnt_rightop( 'int8', 'pg_catalog', '!', 'numeric', 'desc' ), false, 'hasnt_rightop( left, schema, name, result, desc ) fail', 'desc', @@ -1926,7 +1926,7 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - hasnt_rightop( 'bigint', '!', 'numeric', 'desc' ), + hasnt_rightop( 'int8', '!', 'numeric', 'desc' ), false, 'hasnt_rightop( left, name, result, desc ) fail', 'desc', @@ -1942,7 +1942,7 @@ BEGIN ) AS b LOOP RETURN NEXT tap.b; END LOOP; FOR tap IN SELECT * FROM check_test( - hasnt_rightop( 'bigint', '!', 'desc' ), + hasnt_rightop( 'int8', '!', 'desc' ), false, 'hasnt_rightop( left, name, desc ) fail', 'desc', @@ -2269,7 +2269,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_is( 'public', 'us_postal_code', 'pg_catalog', 'integer', 'whatever'), + domain_type_is( 'public', 'us_postal_code', 'pg_catalog', 'int', 'whatever'), false, 'domain_type_is(schema, domain, schema, type, desc) fail', 'whatever', @@ -2278,7 +2278,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_is( 'public', 'zip_code', 'pg_catalog', 'integer', 'whatever'), + domain_type_is( 'public', 'zip_code', 'pg_catalog', 'int', 'whatever'), false, 'domain_type_is(schema, nondomain, schema, type, desc)', 'whatever', @@ -2286,7 +2286,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_is( 'public', 'integer', 'pg_catalog', 'integer', 'whatever'), + domain_type_is( 'public', 'integer', 'pg_catalog', 'int', 'whatever'), false, 'domain_type_is(schema, type, schema, type, desc) fail', 'whatever', @@ -2310,7 +2310,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_is( 'public', 'us_postal_code', 'integer', 'whatever'), + domain_type_is( 'public', 'us_postal_code', 'int', 'whatever'), false, 'domain_type_is(schema, domain, type, desc) fail', 'whatever', @@ -2319,7 +2319,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_is( 'public', 'zip_code', 'integer', 'whatever'), + domain_type_is( 'public', 'zip_code', 'int', 'whatever'), false, 'domain_type_is(schema, nondomain, type, desc)', 'whatever', @@ -2327,7 +2327,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_is( 'public', 'integer', 'integer', 'whatever'), + domain_type_is( 'public', 'integer', 'int', 'whatever'), false, 'domain_type_is(schema, type, type, desc) fail', 'whatever', @@ -2351,7 +2351,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_is( 'us_postal_code', 'integer', 'whatever'), + domain_type_is( 'us_postal_code', 'int', 'whatever'), false, 'domain_type_is(domain, type, desc) fail', 'whatever', @@ -2360,7 +2360,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_is( 'zip_code', 'integer', 'whatever'), + domain_type_is( 'zip_code', 'int', 'whatever'), false, 'domain_type_is(nondomain, type, desc)', 'whatever', @@ -2368,7 +2368,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_is( 'integer', 'integer', 'whatever'), + domain_type_is( 'integer', 'int', 'whatever'), false, 'domain_type_is(type, type, desc) fail', 'whatever', @@ -2376,7 +2376,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_isnt( 'public', 'us_postal_code', 'public', 'integer', 'whatever'), + domain_type_isnt( 'public', 'us_postal_code', 'public', 'int', 'whatever'), true, 'domain_type_isnt(schema, domain, schema, type, desc)', 'whatever', @@ -2384,10 +2384,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_isnt( 'public', 'us_postal_code', 'pg_catalog'::name, 'integer'), + domain_type_isnt( 'public', 'us_postal_code', 'pg_catalog'::name, 'int4'), true, 'domain_type_isnt(schema, domain, schema, type)', - 'Domain public.us_postal_code should not extend type pg_catalog.integer', + 'Domain public.us_postal_code should not extend type pg_catalog.int4', '' ); @@ -2425,10 +2425,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_isnt( 'public'::name, 'us_postal_code', 'integer'), + domain_type_isnt( 'public'::name, 'us_postal_code', 'int'), true, 'domain_type_isnt(schema, domain, type)', - 'Domain public.us_postal_code should not extend type integer', + 'Domain public.us_postal_code should not extend type int', '' ); @@ -2466,10 +2466,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_isnt( 'us_postal_code', 'integer'), + domain_type_isnt( 'us_postal_code', 'int'), true, 'domain_type_isnt(domain, type)', - 'Domain us_postal_code should not extend type integer', + 'Domain us_postal_code should not extend type int', '' ); From b12bb4481116427958953331de66f9a790620e22 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 13 Aug 2023 16:51:47 -0400 Subject: [PATCH 1140/1195] Tweak installation docs for Docker Resolves #275. --- doc/pgtap.mmd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 21c6b43de3f4..91eb89d9d2d9 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -52,7 +52,8 @@ Installation ============ pgTAP must be installed on a host with PostgreSQL server running; it cannot -be installed remotely. +be installed remotely. If you're using PostgreSQL in Docker, you need to install +pgTAP inside the Docker container. To install pgTAP into a PostgreSQL database, just do this: From 45647ce1f6959a3a19568ed3dcb6ef9862d95371 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 13 Aug 2023 16:59:46 -0400 Subject: [PATCH 1141/1195] Tweak installation docs for downloads And mention binary distributions. Resolves #301. --- Changes | 3 +++ doc/pgtap.mmd | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index a1503535103b..1152ec22627a 100644 --- a/Changes +++ b/Changes @@ -39,6 +39,9 @@ Revision history for pgTAP that common aliases for standard types can be specified as, e.g., `int` and `bool` rather than `integer` and `boolean`. Thanks to @wphilips53 for the suggestion (#292). +* Tweaked the installation docs for Docker, binary Linux distributions, and + downloading the source from PGXN. Thanks to @jed-walker-icd (#275) and + @machineghost (#301) for the suggestions! 1.2.0 2021-12-05T18:08:13Z -------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 91eb89d9d2d9..13c5e590ff3e 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -55,12 +55,27 @@ pgTAP must be installed on a host with PostgreSQL server running; it cannot be installed remotely. If you're using PostgreSQL in Docker, you need to install pgTAP inside the Docker container. -To install pgTAP into a PostgreSQL database, just do this: +If you are using Linux, you may (depending on your distribution) be able to use +you distribution's package management system to install pgTAP. For instance, on +Debian, Ubuntu, or Linux Mint pgTAP can be installed with the command: + + sudo apt-get install pgtap + +On other systems pgTAP has to be downloaded and built. First, download pgTAP +[from PGXN](https://pgxn.org/dist/pgtap/) (click the green download button in +the upper-right). Extract the downloaded zip file, and (at the command line) +navigate to the extracted folder. + +To build pgTAP and install it into a PostgreSQL database, run the following +commands: make make install make installcheck +Potential Issues +---------------- + If you encounter an error such as: "Makefile", line 8: Need an operator From 46ed5f98934b9c97464f5876c6bb80a21c5e1df5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 2 Aug 2023 20:05:49 -0400 Subject: [PATCH 1142/1195] Test on Postgres 16 --- .github/workflows/test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4e3e144784ab..dd30ad54277c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,8 @@ jobs: strategy: matrix: include: - - { version: 15, upgrade_to: "", update_from: 0.99.0 } + - { version: 16, upgrade_to: "", update_from: 0.99.0 } + - { version: 15, upgrade_to: 16, update_from: 0.99.0 } - { version: 14, upgrade_to: 15, update_from: 0.99.0 } - { version: 13, upgrade_to: 14, update_from: 0.99.0 } - { version: 12, upgrade_to: 13, update_from: 0.99.0 } @@ -22,8 +23,8 @@ jobs: - { version: 9.3, upgrade_to: 9.4, update_from: 0.95.0 } - { version: 9.2, upgrade_to: 9.3, update_from: "" } # updatecheck is not supported prior to 9.3 # Also test pg_upgrade across many versions - - { version: 9.2, upgrade_to: 15, update_from: "", suffix: –15 } - - { version: 9.4, upgrade_to: 15, update_from: "", suffix: –15 } + - { version: 9.2, upgrade_to: 16, update_from: "", suffix: –16 } + - { version: 9.4, upgrade_to: 16, update_from: "", suffix: –16 } name: 🐘 PostgreSQL ${{ matrix.version }}${{ matrix.suffix }} runs-on: ubuntu-latest container: pgxn/pgxn-tools From b9e2da854dd978bfe09ccc3722f01bcb569a172f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Aug 2023 17:07:05 -0400 Subject: [PATCH 1143/1195] Do without server-dev? --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dd30ad54277c..010c5a4abcfb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -68,5 +68,5 @@ jobs: # See also https://askubuntu.com/a/104912 for --force options run: | make install - apt-get install -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" postgresql-${{ matrix.upgrade_to }} postgresql-server-dev-${{ matrix.upgrade_to }} + apt-get install -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" postgresql-${{ matrix.upgrade_to }} sudo -u postgres test/test_MVU.sh -s 55432 55433 "${{ matrix.version }}" "${{ matrix.upgrade_to }}" From 4bc2a993226a0a20fa573de3d4c730b94fc31832 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Aug 2023 17:33:08 -0400 Subject: [PATCH 1144/1195] Try using apt.postgresql.org.sh --- .github/workflows/test.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 010c5a4abcfb..84d10b9ce576 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,21 +10,21 @@ jobs: strategy: matrix: include: - - { version: 16, upgrade_to: "", update_from: 0.99.0 } - - { version: 15, upgrade_to: 16, update_from: 0.99.0 } - - { version: 14, upgrade_to: 15, update_from: 0.99.0 } - - { version: 13, upgrade_to: 14, update_from: 0.99.0 } - - { version: 12, upgrade_to: 13, update_from: 0.99.0 } - - { version: 11, upgrade_to: 12, update_from: 0.99.0 } # Versions prior to 0.99.0 don't support Postgres 11 - - { version: 10, upgrade_to: 11, update_from: 0.95.0 } - - { version: 9.6, upgrade_to: 10, update_from: 0.95.0 } - - { version: 9.5, upgrade_to: 9.6, update_from: 0.95.0 } - - { version: 9.4, upgrade_to: 9.5, update_from: 0.95.0 } - - { version: 9.3, upgrade_to: 9.4, update_from: 0.95.0 } - - { version: 9.2, upgrade_to: 9.3, update_from: "" } # updatecheck is not supported prior to 9.3 + # - { version: 16, upgrade_to: "", update_from: 0.99.0 } + # - { version: 15, upgrade_to: 16, update_from: 0.99.0 } + # - { version: 14, upgrade_to: 15, update_from: 0.99.0 } + # - { version: 13, upgrade_to: 14, update_from: 0.99.0 } + # - { version: 12, upgrade_to: 13, update_from: 0.99.0 } + # - { version: 11, upgrade_to: 12, update_from: 0.99.0 } # Versions prior to 0.99.0 don't support Postgres 11 + # - { version: 10, upgrade_to: 11, update_from: 0.95.0 } + # - { version: 9.6, upgrade_to: 10, update_from: 0.95.0 } + # - { version: 9.5, upgrade_to: 9.6, update_from: 0.95.0 } + # - { version: 9.4, upgrade_to: 9.5, update_from: 0.95.0 } + # - { version: 9.3, upgrade_to: 9.4, update_from: 0.95.0 } + # - { version: 9.2, upgrade_to: 9.3, update_from: "" } # updatecheck is not supported prior to 9.3 # Also test pg_upgrade across many versions - { version: 9.2, upgrade_to: 16, update_from: "", suffix: –16 } - - { version: 9.4, upgrade_to: 16, update_from: "", suffix: –16 } + # - { version: 9.4, upgrade_to: 16, update_from: "", suffix: –16 } name: 🐘 PostgreSQL ${{ matrix.version }}${{ matrix.suffix }} runs-on: ubuntu-latest container: pgxn/pgxn-tools @@ -68,5 +68,5 @@ jobs: # See also https://askubuntu.com/a/104912 for --force options run: | make install - apt-get install -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" postgresql-${{ matrix.upgrade_to }} + sudo apt.postgresql.org.sh -i -v "${{ matrix.upgrade_to }}" sudo -u postgres test/test_MVU.sh -s 55432 55433 "${{ matrix.version }}" "${{ matrix.upgrade_to }}" From 1d5265a674979f3cb47c0428596e25f2409db68a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Aug 2023 17:38:24 -0400 Subject: [PATCH 1145/1195] Test on Postgres 9.1 and 16 And switch to `apt.postgresql.org.sh` to install the second version of Postgres to test the upgrade, thus allowing pre-releases to be properly tested. --- .github/workflows/test.yml | 27 ++++++++++++++------------- Changes | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 84d10b9ce576..5f69c998f1c3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,21 +10,22 @@ jobs: strategy: matrix: include: - # - { version: 16, upgrade_to: "", update_from: 0.99.0 } - # - { version: 15, upgrade_to: 16, update_from: 0.99.0 } - # - { version: 14, upgrade_to: 15, update_from: 0.99.0 } - # - { version: 13, upgrade_to: 14, update_from: 0.99.0 } - # - { version: 12, upgrade_to: 13, update_from: 0.99.0 } - # - { version: 11, upgrade_to: 12, update_from: 0.99.0 } # Versions prior to 0.99.0 don't support Postgres 11 - # - { version: 10, upgrade_to: 11, update_from: 0.95.0 } - # - { version: 9.6, upgrade_to: 10, update_from: 0.95.0 } - # - { version: 9.5, upgrade_to: 9.6, update_from: 0.95.0 } - # - { version: 9.4, upgrade_to: 9.5, update_from: 0.95.0 } - # - { version: 9.3, upgrade_to: 9.4, update_from: 0.95.0 } - # - { version: 9.2, upgrade_to: 9.3, update_from: "" } # updatecheck is not supported prior to 9.3 + - { version: 16, upgrade_to: "", update_from: 0.99.0 } + - { version: 15, upgrade_to: 16, update_from: 0.99.0 } + - { version: 14, upgrade_to: 15, update_from: 0.99.0 } + - { version: 13, upgrade_to: 14, update_from: 0.99.0 } + - { version: 12, upgrade_to: 13, update_from: 0.99.0 } + - { version: 11, upgrade_to: 12, update_from: 0.99.0 } # Versions prior to 0.99.0 don't support Postgres 11 + - { version: 10, upgrade_to: 11, update_from: 0.95.0 } + - { version: 9.6, upgrade_to: 10, update_from: 0.95.0 } + - { version: 9.5, upgrade_to: 9.6, update_from: 0.95.0 } + - { version: 9.4, upgrade_to: 9.5, update_from: 0.95.0 } + - { version: 9.3, upgrade_to: 9.4, update_from: 0.95.0 } + - { version: 9.2, upgrade_to: 9.3, update_from: "" } # updatecheck is not supported prior to 9.3 + - { version: 9.1, upgrade_to: 9.2, update_from: "" } # updatecheck is not supported prior to 9.3 # Also test pg_upgrade across many versions - { version: 9.2, upgrade_to: 16, update_from: "", suffix: –16 } - # - { version: 9.4, upgrade_to: 16, update_from: "", suffix: –16 } + - { version: 9.4, upgrade_to: 16, update_from: "", suffix: –16 } name: 🐘 PostgreSQL ${{ matrix.version }}${{ matrix.suffix }} runs-on: ubuntu-latest container: pgxn/pgxn-tools diff --git a/Changes b/Changes index 1152ec22627a..fe68beee4c7b 100644 --- a/Changes +++ b/Changes @@ -58,7 +58,7 @@ Revision history for pgTAP the report (#247). * Fixed test failures where the current username was not being quoted as an identifier. Thanks to Matt DeLuco for the report (#216) and pull request - #259)! + (#259)! * Fixed the `col_not_null()` drop statements in the uninstall script. Thanks to Kyle L. Jensen for the report (#252). * Removed straggler references to Postgres 9.0 and earlier, including From acdd76876b19559538797a1f1dcf96e2e4200158 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Aug 2023 18:04:44 -0400 Subject: [PATCH 1146/1195] Increment to and timestamp v1.3.0 --- Changes | 2 +- META.json | 8 ++++---- README.md | 2 +- contrib/pgtap.spec | 5 ++++- doc/pgtap.mmd | 2 +- pgtap.control | 2 +- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Changes b/Changes index fe68beee4c7b..eae2db4ec698 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -1.2.1 +1.3.0 2023-08-14T22:14:20Z -------------------------- * Fixed an issue with xUnit tests where they would exit immediately on unexpected errors (aside from PL/pgSQL RAISE errors) rather than exit just the diff --git a/META.json b/META.json index 4b4347342a8c..412886bc3318 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "1.2.1", + "version": "1.3.0", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "1.2.1" + "version": "1.3.0" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "1.2.1" + "version": "1.3.0" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "1.2.1" + "version": "1.3.0" } }, "resources": { diff --git a/README.md b/README.md index ddc782cb5a96..966311ee3e36 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 1.2.1 +pgTAP 1.3.0 ============ [pgTAP](https://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 6b83c2bcb460..c19ff9ba6e51 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 1.2.1 +Version: 1.3.0 Release: 1%{?dist} Group: Applications/Databases License: PostgreSQL @@ -41,6 +41,9 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog +* Mon Aug 14 2023 David E. Wheeler 1.3.0-1 +- Update to 1.3.0 + * Sun Dec 5 2021 David E. Wheeler 1.2.0-1 - Update to 1.2.0 diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 13c5e590ff3e..77e93299ca0d 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 1.2.1 +pgTAP 1.3.0 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/pgtap.control b/pgtap.control index 9223dce62f6e..5df95a1ba510 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '1.2.1' +default_version = '1.3.0' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true From b2c933238131c895ef0e8240a67dcfea75e5013a Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 14 Aug 2023 18:14:42 -0400 Subject: [PATCH 1147/1195] Increment to v1.3.1 --- Changes | 3 +++ META.json | 8 ++++---- README.md | 2 +- contrib/pgtap.spec | 6 +++--- doc/pgtap.mmd | 2 +- pgtap.control | 2 +- release.md | 1 - 7 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Changes b/Changes index eae2db4ec698..a7cd06c76e57 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Revision history for pgTAP ========================== +1.3.1 +-------------------------- + 1.3.0 2023-08-14T22:14:20Z -------------------------- * Fixed an issue with xUnit tests where they would exit immediately on diff --git a/META.json b/META.json index 412886bc3318..bc47195a9eb9 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "1.3.0", + "version": "1.3.1", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "1.3.0" + "version": "1.3.1" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "1.3.0" + "version": "1.3.1" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "1.3.0" + "version": "1.3.1" } }, "resources": { diff --git a/README.md b/README.md index 966311ee3e36..c80ffe461c33 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 1.3.0 +pgTAP 1.3.1 ============ [pgTAP](https://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index c19ff9ba6e51..c19fb1afcf19 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 1.3.0 +Version: 1.3.1 Release: 1%{?dist} Group: Applications/Databases License: PostgreSQL @@ -41,8 +41,8 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog -* Mon Aug 14 2023 David E. Wheeler 1.3.0-1 -- Update to 1.3.0 +* Mon Aug 14 2023 David E. Wheeler 1.3.1-1 +- Update to 1.3.1 * Sun Dec 5 2021 David E. Wheeler 1.2.0-1 - Update to 1.2.0 diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 77e93299ca0d..25f3f24cd614 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 1.3.0 +pgTAP 1.3.1 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/pgtap.control b/pgtap.control index 5df95a1ba510..e0b8b56c70b7 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '1.3.0' +default_version = '1.3.1' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true diff --git a/release.md b/release.md index 4a7010472b5b..a56597c87329 100644 --- a/release.md +++ b/release.md @@ -106,7 +106,6 @@ Here are the steps to take to make a release of pgTAP: * Push the `gh-pages` branch: git push - git push origin up/gh-pages:gh-pages * Increment the minor version to kick off development for the next release. The version should be added to the `Changes` file, and incremented in the From 8a73cbe0fd2b1dcb00204d7f26f0c694c512c360 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 25 Aug 2023 13:45:05 -0400 Subject: [PATCH 1148/1195] Update copyright year --- README.md | 2 +- doc/pgtap.mmd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c80ffe461c33..bd481fb57168 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ pgTAP requires PostgreSQL 9.1 or higher. Copyright and License --------------------- -Copyright (c) 2008-2021 David E. Wheeler. Some rights reserved. +Copyright (c) 2008-2023 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 25f3f24cd614..ec41332fc431 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -8773,7 +8773,7 @@ Credits Copyright and License --------------------- -Copyright (c) 2008-2021 David E. Wheeler. Some rights reserved. +Copyright (c) 2008-2023 David E. Wheeler. Some rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is From 4ec32e76ec4ec6e7f374b45d9db0cb2fccb431c3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 17 Sep 2023 21:53:30 -0400 Subject: [PATCH 1149/1195] Improve data type parsing and formatting Add a C module to delegate the parsing of data types by `col_type_is()` to the Postgres core `parseTypeString()` function, which is the canonical type parser. This ensure that no matter the aliasing or typemod specified for a data type, we end up with exactly the same spelling as the core uses, ensuring more accurate comparisons. This was necessitated by the change in 1e1d745 that added support for type aliases but broke complicated typmods such as ` second(0)` in `interval second(0)`. Resolves #315. Document the two new functions, `parse_type()` and `format_type_string()` and remove the documentation for `pg_typeof()`, which hasn't shipped with pgTAP in several years, since it has been in the Postgres core since 8.3. Also, fix the name of the v1.3.9 upgrade file. --- .gitignore | 2 + Changes | 12 + Makefile | 1 + doc/pgtap.mmd | 82 ++-- ...2.0--1.2.1.sql => pgtap--1.2.0--1.3.0.sql} | 0 sql/pgtap--1.3.0--1.3.1.sql | 98 +++++ sql/pgtap.sql.in | 33 +- src/pgtap.c | 90 ++++ test/expected/coltap.out | 401 ++++++++++-------- test/sql/coltap.sql | 126 +++++- 10 files changed, 619 insertions(+), 226 deletions(-) rename sql/{pgtap--1.2.0--1.2.1.sql => pgtap--1.2.0--1.3.0.sql} (100%) create mode 100644 sql/pgtap--1.3.0--1.3.1.sql create mode 100644 src/pgtap.c diff --git a/.gitignore b/.gitignore index fc0b7c6d41ee..249e89fb29cb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ pgtap-schema.sql uninstall_pgtap.sql results pgtap.so +pgtap.dylib +pgtap.dll regression.* *.html *.html1 diff --git a/Changes b/Changes index a7cd06c76e57..d86606504bbd 100644 --- a/Changes +++ b/Changes @@ -4,6 +4,18 @@ Revision history for pgTAP 1.3.1 -------------------------- +* Revamped the handling of data type declarations in `col_type_is()` to always + accurately normalize data type representations, whether using aliases, such + as `varchar` for `character varying`, or more complicated types that failed + to work in v1.3.0, such as `interval second(0)`. This is done by delegating + the parsing of the type declaration to a core PostgresSQL function. Many + thanks to Erik Wienhold for the report and for ultimately uncovering and + implementing an SQL interface to the `parseTypeString()` core function + (#315). +* Removed the documentation for `pg_typeof()`, which was removed from pgTAP + when support for PostgreSQL 8.3 was dropped, since the same function has + been available in the PostgreSQL core since then. + 1.3.0 2023-08-14T22:14:20Z -------------------------- * Fixed an issue with xUnit tests where they would exit immediately on diff --git a/Makefile b/Makefile index 96a4d11f44a1..7ce76ccafd9e 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ EXTRA_CLEAN = $(VERSION_FILES) sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap- EXTRA_CLEAN += $(wildcard sql/*.orig) # These are files left behind by patch DOCS = doc/pgtap.mmd PG_CONFIG ?= pg_config +MODULES = src/pgtap # # Test configuration. This must be done BEFORE including PGXS diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index ec41332fc431..ae06515b3b16 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -375,9 +375,9 @@ discrepancy between the planned number of tests and the number actually run: If you need to throw an exception if some test failed, you can pass an option to `finish()`. - - SELECT * FROM finish(true); - + + SELECT * FROM finish(true); + What a sweet unit! ------------------ @@ -4504,21 +4504,23 @@ fourth the type's schema, the fifth the type, and the sixth is the test description. If the table schema is omitted, the table must be visible in the search path. -If the type schema is omitted, it must be visible in the search path; -otherwise, the diagnostics will report the schema it's actually in. The schema -can optionally be included in the `:type` argument, e.g., `'contrib.citext`. +If the type schema is omitted, it must be visible in the search path. The +schema can optionally be included in the `:type` argument, e.g., +"contrib.citext". If the test description is omitted, it will be set to "Column `:schema.:table.:column` should be type `:schema.:type`". Note that this test will fail if the table or column in question does not exist. -The type argument should be formatted as it would be displayed in the view of -a table using the `\d` command in `psql`. For example, if you have a numeric -column with a precision of 8, you should specify "numeric(8,0)". If you -created a `varchar(64)` column, you should pass the type as "varchar(64)" or -"character varying(64)". Example: +The type argument may be formatted using the full name of the type or any +supported alias. For example, if you created a `varchar(64)` column, you can +pass the type as either "varchar(64)" or "character varying(64)". Example: + + SELECT col_type_is( 'myschema', 'sometable', 'somecolumn', 'timespantz(3)' ); - SELECT col_type_is( 'myschema', 'sometable', 'somecolumn', 'numeric(10,2)' ); +Types with case-sensitive names or special characters must be double-quoted: + + SELECT col_type_is( 'myschema', 'sometable', 'somecolumn', '"myType"' ); If the test fails, it will output useful diagnostics. For example this test: @@ -4530,8 +4532,8 @@ Will produce something like this: # have: name # want: text -It will even tell you if the test fails because a column doesn't exist or -actually has no default. But use `has_column()` to make sure the column exists +It will even tell you if the test fails because a column doesn't exist or if +the type doesn't exist. But use `has_column()` to make sure the column exists first, eh? ### `col_default_is()` ### @@ -8190,7 +8192,7 @@ the stringified version number displayed in the first part of the core `version()` function and stored in the "server_version" setting: try=% select current_setting( 'server_version'), pg_version(); - current_setting | pg_version + current_setting | pg_version -----------------+------------ 12.2 | 12.2 (1 row) @@ -8273,23 +8275,50 @@ included in the display. For example: Used internally by pgTAP to compare operators, but may be more generally useful. -### `pg_typeof()` ### +### `parse_type()` ### + + SELECT * FROM parse_type( :text ); + +**Parameters** + +`:text` +: An SQL type declaration, optionally schema-qualified. + +Parses a string representing an SQL type declaration as used in a `CREATE TABLE` +statement, optionally schema-qualified. Returns a record with two fields, +`typid` and `typmod`, representing the OID and modifier for the type. These are +the underlying values that define column data types, and which can be passed to +the PostgreSQL core +[`format_type()`](https://www.postgresql.org/docs/current/functions-info.html#FUNCTIONS-INFO-CATALOG) +function to display the normalized string representation of the data type. +Raises an error if the specify type does not exist or cannot be found in the +search path. - SELECT pg_typeof(:any); + try=% SELECT format_type(p.typid, p.typmod) + try-% FROM parse_type('timestamp(4)') p; + format_type + -------------------------------- + timestamp(4) without time zone + +### `format_type_string()` ### + + SELECT format_type_string( :text ); **Parameters** -`:any` -: Any SQL value. +`:text` +: An SQL type declaration, optionally schema-qualified. -Returns a `regtype` identifying the type of value passed to the function. This -function is used internally by `cmp_ok()` to properly construct types when -executing the comparison, but might be generally useful. +This function normalizes data type declarations for accurate comparison +to table columns by `col_type_is()`. It's effectively the identical to +the calling `format_type()` with the values returned by `parse_type()`, +but returns a `NULL` on an invalid or missing type, rather than raising +an error. - try=% select pg_typeof(12), pg_typeof(100.2); - pg_typeof | pg_typeof - -----------+----------- - integer | numeric + try=# SELECT format_type_string('timestamp(3)'); + format_type_string + -------------------------------- + timestamp(3) without time zone ### `findfuncs()` ### @@ -8309,7 +8338,6 @@ executing the comparison, but might be generally useful. `:pattern` : Regular expression pattern to exclude functions with matching names. - This function searches the named schema or, if no schema is passed, the search patch, for all functions that match the regular expression pattern. The optional exclude regular expression pattern can be used to prevent matchin diff --git a/sql/pgtap--1.2.0--1.2.1.sql b/sql/pgtap--1.2.0--1.3.0.sql similarity index 100% rename from sql/pgtap--1.2.0--1.2.1.sql rename to sql/pgtap--1.2.0--1.3.0.sql diff --git a/sql/pgtap--1.3.0--1.3.1.sql b/sql/pgtap--1.3.0--1.3.1.sql new file mode 100644 index 000000000000..699856458668 --- /dev/null +++ b/sql/pgtap--1.3.0--1.3.1.sql @@ -0,0 +1,98 @@ +CREATE FUNCTION parse_type(type text, OUT typid oid, OUT typmod int4) +RETURNS RECORD +AS '$libdir/pgtap' +LANGUAGE C STABLE STRICT; + +CREATE OR REPLACE FUNCTION format_type_string ( TEXT ) +RETURNS TEXT AS $$ +BEGIN RETURN format_type(p.typid, p.typmod) from parse_type($1) p; +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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 36bfe9506e0e..c4c94ccdf14d 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -22,6 +22,11 @@ CREATE OR REPLACE FUNCTION pgtap_version() RETURNS NUMERIC AS 'SELECT __VERSION__;' LANGUAGE SQL IMMUTABLE; +CREATE FUNCTION parse_type(type text, OUT typid oid, OUT typmod int4) +RETURNS RECORD +AS '$libdir/pgtap' +LANGUAGE C STABLE STRICT; + CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ DECLARE @@ -1461,6 +1466,13 @@ EXCEPTION WHEN undefined_object THEN RETURN $1; END; $$ LANGUAGE PLPGSQL STABLE; +CREATE OR REPLACE FUNCTION format_type_string ( TEXT ) +RETURNS TEXT AS $$ +BEGIN RETURN format_type(p.typid, p.typmod) from parse_type($1) p; +EXCEPTION WHEN OTHERS THEN RETURN NULL; +END; +$$ LANGUAGE PLPGSQL STABLE; + CREATE OR REPLACE FUNCTION _quote_ident_like(TEXT, TEXT) RETURNS TEXT AS $$ DECLARE @@ -1489,7 +1501,18 @@ BEGIN ); END IF; - want_type := quote_ident($4) || '.' || _quote_ident_like($5, have_type); + 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 ); @@ -1531,7 +1554,13 @@ BEGIN ); END IF; - want_type := _quote_ident_like($4, have_type); + 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 ); diff --git a/src/pgtap.c b/src/pgtap.c new file mode 100644 index 000000000000..aa92d5ba484a --- /dev/null +++ b/src/pgtap.c @@ -0,0 +1,90 @@ +/* + * PostgreSQL utility functions for pgTAP. + */ + +#include "postgres.h" +#include "fmgr.h" +#include "funcapi.h" /* for returning composite type */ +#include "utils/builtins.h" /* text_to_cstring() */ +#include "parser/parse_type.h" /* parseTypeString() */ + + +#if PG_VERSION_NUM < 90300 +#include "access/htup.h" /* heap_form_tuple() */ +#elif PG_VERSION_NUM < 130000 +#include "access/htup_details.h" /* heap_form_tuple() */ +#endif + +#ifdef PG_MODULE_MAGIC +PG_MODULE_MAGIC; +#endif + + +/* + * Given a string that is supposed to be a SQL-compatible type declaration, + * such as "int4" or "integer" or "character varying(32)", parse + * the string and convert it to a type OID and type modifier. + * + * Raises an error on an invalid type. + */ + +/* + * parse_type() is the inverse of pg_catalog.format_type(): it takes a string + * representing an SQL-compatible type declaration, such as "int4" or "integer" + * or "character varying(32)", parses it, and returns the OID and type modifier. + * Returns NULL for an invalid type. + * + * Internally it relies on the Postgres core parseTypeString() function defined + * in src/backend/parser/parse_type.c. + */ +Datum parse_type(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1(parse_type); + +Datum +parse_type(PG_FUNCTION_ARGS) +{ +#define PARSE_TYPE_STRING_COLS 2 /* Returns two columns. */ + const char *type; /* the type string we want to resolve */ + Oid typid; /* the resolved type oid */ + int32 typmod; /* the resolved type modifier */ + TupleDesc tupdesc; + HeapTuple rettuple; + Datum values[PARSE_TYPE_STRING_COLS] = {0}; + bool nulls[PARSE_TYPE_STRING_COLS] = {0}; + + type = text_to_cstring(PG_GETARG_TEXT_PP(0)); + + /* + * Build a tuple descriptor for our result type; return an error if not + * called in a context that expects a record. + */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) { + ereport( + ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context that cannot accept type record")) + ); + } + + BlessTupleDesc(tupdesc); + + /* + * Parse type-name argument to obtain type OID and encoded typmod. We don't + * need to check for parseTypeString failure, but just let the error be + * raised. The 0 arg works both as the `Node *escontext` arg in Postgres 16 + * and the `bool missing_ok` arg in 9.4-15. + */ +#if PG_VERSION_NUM < 90400 + (void) parseTypeString(type, &typid, &typmod); +#else + (void) parseTypeString(type, &typid, &typmod, 0); +#endif + + /* Create and return tuple. */ + values[0] = typid; + values[1] = typmod; + rettuple = heap_form_tuple(tupdesc, values, nulls); + return HeapTupleGetDatum(rettuple); +#undef PARSE_TYPE_STRING_COLS +} diff --git a/test/expected/coltap.out b/test/expected/coltap.out index 73beed97435b..9a076e7732e5 100644 --- a/test/expected/coltap.out +++ b/test/expected/coltap.out @@ -1,5 +1,5 @@ \unset ECHO -1..243 +1..276 ok 1 - col_not_null( sch, tab, col, desc ) should pass ok 2 - col_not_null( sch, tab, col, desc ) should have the proper description ok 3 - col_not_null( sch, tab, col, desc ) should have the proper diagnostics @@ -57,189 +57,222 @@ ok 54 - col_type_is( sch, tab, myNum, sch, type, desc ) should have the proper d ok 55 - col_type_is( sch, tab, camel, sch, type, desc ) should pass ok 56 - col_type_is( sch, tab, camel, sch, type, desc ) should have the proper description ok 57 - col_type_is( sch, tab, camel, sch, type, desc ) should have the proper diagnostics -ok 58 - col_type_is( sch, tab, camel, sch, type, desc ) should pass -ok 59 - col_type_is( sch, tab, camel, sch, type, desc ) should have the proper description -ok 60 - col_type_is( sch, tab, camel, sch, type, desc ) should have the proper diagnostics +ok 58 - col_type_is( sch, tab, camel, sch, type ) should pass +ok 59 - col_type_is( sch, tab, camel, sch, type ) should have the proper description +ok 60 - col_type_is( sch, tab, camel, sch, type ) should have the proper diagnostics ok 61 - col_type_is( sch, tab, camel, type, desc ) should pass ok 62 - col_type_is( sch, tab, camel, type, desc ) should have the proper description ok 63 - col_type_is( sch, tab, camel, type, desc ) should have the proper diagnostics -ok 64 - col_type_is( sch, tab, col, sch, type, desc ) fail should fail -ok 65 - col_type_is( sch, tab, col, sch, type, desc ) fail should have the proper description -ok 66 - col_type_is( sch, tab, col, sch, type, desc ) fail should have the proper diagnostics -ok 67 - col_type_is( sch, tab, col, sch, non-type, desc ) should fail -ok 68 - col_type_is( sch, tab, col, sch, non-type, desc ) should have the proper description -ok 69 - col_type_is( sch, tab, col, sch, non-type, desc ) should have the proper diagnostics -ok 70 - col_type_is( sch, tab, col, non-sch, type, desc ) should fail -ok 71 - col_type_is( sch, tab, col, non-sch, type, desc ) should have the proper description -ok 72 - col_type_is( sch, tab, col, non-sch, type, desc ) should have the proper diagnostics -ok 73 - col_type_is( sch, tab, non-col, sch, type, desc ) should fail -ok 74 - col_type_is( sch, tab, non-col, sch, type, desc ) should have the proper description -ok 75 - col_type_is( sch, tab, non-col, sch, type, desc ) should have the proper diagnostics -ok 76 - col_type_is( sch, tab, col, type, desc ) should pass -ok 77 - col_type_is( sch, tab, col, type, desc ) should have the proper description -ok 78 - col_type_is( sch, tab, col, type, desc ) should have the proper diagnostics -ok 79 - col_type_is( sch, tab, col, type ) should pass -ok 80 - col_type_is( sch, tab, col, type ) should have the proper description -ok 81 - col_type_is( sch, tab, col, type ) should have the proper diagnostics -ok 82 - col_type_is( tab, col, type, desc ) should pass -ok 83 - col_type_is( tab, col, type, desc ) should have the proper description -ok 84 - col_type_is( tab, col, type, desc ) should have the proper diagnostics -ok 85 - col_type_is( tab, col, type ) should pass -ok 86 - col_type_is( tab, col, type ) should have the proper description -ok 87 - col_type_is( tab, col, type ) should have the proper diagnostics -ok 88 - col_type_is( tab, col, type ) fail should fail -ok 89 - col_type_is( tab, col, type ) fail should have the proper description -ok 90 - col_type_is( tab, col, type ) fail should have the proper diagnostics -ok 91 - col_type_is( tab, noncol, type ) fail should fail -ok 92 - col_type_is( tab, noncol, type ) fail should have the proper description -ok 93 - col_type_is( tab, noncol, type ) fail should have the proper diagnostics -ok 94 - col_type_is( sch, tab, noncol, type, desc ) fail should fail -ok 95 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper description -ok 96 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper diagnostics -ok 97 - col_type_is with precision should pass -ok 98 - col_type_is with precision should have the proper description -ok 99 - col_type_is with precision should have the proper diagnostics -ok 100 - col_type_is precision fail should fail -ok 101 - col_type_is precision fail should have the proper description -ok 102 - col_type_is precision fail should have the proper diagnostics -ok 103 - col_has_default( sch, tab, col, desc ) should pass -ok 104 - col_has_default( sch, tab, col, desc ) should have the proper description -ok 105 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics -ok 106 - col_has_default( tab, col, desc ) should pass -ok 107 - col_has_default( tab, col, desc ) should have the proper description -ok 108 - col_has_default( tab, col, desc ) should have the proper diagnostics -ok 109 - col_has_default( tab, col ) should pass -ok 110 - col_has_default( tab, col ) should have the proper description -ok 111 - col_has_default( tab, col ) should have the proper diagnostics -ok 112 - col_has_default( sch, tab, col, desc ) should fail -ok 113 - col_has_default( sch, tab, col, desc ) should have the proper description -ok 114 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics -ok 115 - col_has_default( tab, col, desc ) should fail -ok 116 - col_has_default( tab, col, desc ) should have the proper description -ok 117 - col_has_default( tab, col, desc ) should have the proper diagnostics -ok 118 - col_has_default( tab, col ) should fail -ok 119 - col_has_default( tab, col ) should have the proper description -ok 120 - col_has_default( tab, col ) should have the proper diagnostics -ok 121 - col_has_default( sch, tab, col, desc ) should fail -ok 122 - col_has_default( sch, tab, col, desc ) should have the proper description -ok 123 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics -ok 124 - col_has_default( tab, col, desc ) should fail -ok 125 - col_has_default( tab, col, desc ) should have the proper description -ok 126 - col_has_default( tab, col, desc ) should have the proper diagnostics -ok 127 - col_has_default( tab, col ) should fail -ok 128 - col_has_default( tab, col ) should have the proper description -ok 129 - col_has_default( tab, col ) should have the proper diagnostics -ok 130 - col_hasnt_default( sch, tab, col, desc ) should fail -ok 131 - col_hasnt_default( sch, tab, col, desc ) should have the proper description -ok 132 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics -ok 133 - col_hasnt_default( tab, col, desc ) should fail -ok 134 - col_hasnt_default( tab, col, desc ) should have the proper description -ok 135 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics -ok 136 - col_hasnt_default( tab, col ) should fail -ok 137 - col_hasnt_default( tab, col ) should have the proper description -ok 138 - col_hasnt_default( tab, col ) should have the proper diagnostics -ok 139 - col_hasnt_default( sch, tab, col, desc ) should pass -ok 140 - col_hasnt_default( sch, tab, col, desc ) should have the proper description -ok 141 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics -ok 142 - col_hasnt_default( tab, col, desc ) should pass -ok 143 - col_hasnt_default( tab, col, desc ) should have the proper description -ok 144 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics -ok 145 - col_hasnt_default( tab, col ) should pass -ok 146 - col_hasnt_default( tab, col ) should have the proper description -ok 147 - col_hasnt_default( tab, col ) should have the proper diagnostics -ok 148 - col_hasnt_default( sch, tab, col, desc ) should fail -ok 149 - col_hasnt_default( sch, tab, col, desc ) should have the proper description -ok 150 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics -ok 151 - col_hasnt_default( tab, col, desc ) should fail -ok 152 - col_hasnt_default( tab, col, desc ) should have the proper description -ok 153 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics -ok 154 - col_hasnt_default( tab, col ) should fail -ok 155 - col_hasnt_default( tab, col ) should have the proper description -ok 156 - col_hasnt_default( tab, col ) should have the proper diagnostics -ok 157 - col_default_is( sch, tab, col, def, desc ) should pass -ok 158 - col_default_is( sch, tab, col, def, desc ) should have the proper description -ok 159 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics -ok 160 - col_default_is() fail should fail -ok 161 - col_default_is() fail should have the proper description -ok 162 - col_default_is() fail should have the proper diagnostics -ok 163 - col_default_is( tab, col, def, desc ) should pass -ok 164 - col_default_is( tab, col, def, desc ) should have the proper description -ok 165 - col_default_is( tab, col, def, desc ) should have the proper diagnostics -ok 166 - col_default_is( tab, col, def ) should pass -ok 167 - col_default_is( tab, col, def ) should have the proper description -ok 168 - col_default_is( tab, col, def ) should have the proper diagnostics -ok 169 - col_default_is( tab, col, int ) should pass -ok 170 - col_default_is( tab, col, int ) should have the proper description -ok 171 - col_default_is( tab, col, int ) should have the proper diagnostics -ok 172 - col_default_is( tab, col, NULL, desc ) should pass -ok 173 - col_default_is( tab, col, NULL, desc ) should have the proper description -ok 174 - col_default_is( tab, col, NULL, desc ) should have the proper diagnostics -ok 175 - col_default_is( tab, col, NULL ) should pass -ok 176 - col_default_is( tab, col, NULL ) should have the proper description -ok 177 - col_default_is( tab, col, NULL ) should have the proper diagnostics -ok 178 - col_default_is( tab, col, bogus, desc ) should fail -ok 179 - col_default_is( tab, col, bogus, desc ) should have the proper description -ok 180 - col_default_is( tab, col, bogus, desc ) should have the proper diagnostics -ok 181 - col_default_is( tab, col, bogus ) should fail -ok 182 - col_default_is( tab, col, bogus ) should have the proper description -ok 183 - col_default_is( tab, col, bogus ) should have the proper diagnostics -ok 184 - col_default_is( tab, col, expression ) should pass -ok 185 - col_default_is( tab, col, expression ) should have the proper description -ok 186 - col_default_is( tab, col, expression ) should have the proper diagnostics -ok 187 - col_default_is( tab, col, expression::text ) should pass -ok 188 - col_default_is( tab, col, expression::text ) should have the proper description -ok 189 - col_default_is( tab, col, expression::text ) should have the proper diagnostics -ok 190 - col_default_is( tab, col, expression, desc ) should pass -ok 191 - col_default_is( tab, col, expression, desc ) should have the proper description -ok 192 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics -ok 193 - col_default_is( tab, col, expression, desc ) should pass -ok 194 - col_default_is( tab, col, expression, desc ) should have the proper description -ok 195 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics -ok 196 - col_default_is( schema, tab, col, expression, desc ) should pass -ok 197 - col_default_is( schema, tab, col, expression, desc ) should have the proper description -ok 198 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics -ok 199 - col_default_is( schema, tab, col, expression, desc ) should pass -ok 200 - col_default_is( schema, tab, col, expression, desc ) should have the proper description -ok 201 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics -ok 202 - col_default_is( sch, tab, col, def, desc ) should fail -ok 203 - col_default_is( sch, tab, col, def, desc ) should have the proper description -ok 204 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics -ok 205 - col_default_is( tab, col, def, desc ) should fail -ok 206 - col_default_is( tab, col, def, desc ) should have the proper description -ok 207 - col_default_is( tab, col, def, desc ) should have the proper diagnostics -ok 208 - col_default_is( tab, col, def ) should fail -ok 209 - col_default_is( tab, col, def ) should have the proper description -ok 210 - col_default_is( tab, col, def ) should have the proper diagnostics -ok 211 - col_default_is( tab, col, CURRENT_CATALOG ) should pass -ok 212 - col_default_is( tab, col, CURRENT_CATALOG ) should have the proper description -ok 213 - col_default_is( tab, col, CURRENT_CATALOG ) should have the proper diagnostics -ok 214 - col_default_is( tab, col, CURRENT_ROLE ) should pass -ok 215 - col_default_is( tab, col, CURRENT_ROLE ) should have the proper description -ok 216 - col_default_is( tab, col, CURRENT_ROLE ) should have the proper diagnostics -ok 217 - col_default_is( tab, col, CURRENT_SCHEMA ) should pass -ok 218 - col_default_is( tab, col, CURRENT_SCHEMA ) should have the proper description -ok 219 - col_default_is( tab, col, CURRENT_SCHEMA ) should have the proper diagnostics -ok 220 - col_default_is( tab, col, CURRENT_USER ) should pass -ok 221 - col_default_is( tab, col, CURRENT_USER ) should have the proper description -ok 222 - col_default_is( tab, col, CURRENT_USER ) should have the proper diagnostics -ok 223 - col_default_is( tab, col, SESSION_USER ) should pass -ok 224 - col_default_is( tab, col, SESSION_USER ) should have the proper description -ok 225 - col_default_is( tab, col, SESSION_USER ) should have the proper diagnostics -ok 226 - col_default_is( tab, col, USER ) should pass -ok 227 - col_default_is( tab, col, USER ) should have the proper description -ok 228 - col_default_is( tab, col, USER ) should have the proper diagnostics -ok 229 - col_default_is( tab, col, CURRENT_DATE ) should pass -ok 230 - col_default_is( tab, col, CURRENT_DATE ) should have the proper description -ok 231 - col_default_is( tab, col, CURRENT_DATE ) should have the proper diagnostics -ok 232 - col_default_is( tab, col, CURRENT_TIME ) should pass -ok 233 - col_default_is( tab, col, CURRENT_TIME ) should have the proper description -ok 234 - col_default_is( tab, col, CURRENT_TIME ) should have the proper diagnostics -ok 235 - col_default_is( tab, col, CURRENT_TIMESTAMP ) should pass -ok 236 - col_default_is( tab, col, CURRENT_TIMESTAMP ) should have the proper description -ok 237 - col_default_is( tab, col, CURRENT_TIMESTAMP ) should have the proper diagnostics -ok 238 - col_default_is( tab, col, LOCALTIME ) should pass -ok 239 - col_default_is( tab, col, LOCALTIME ) should have the proper description -ok 240 - col_default_is( tab, col, LOCALTIME ) should have the proper diagnostics -ok 241 - col_default_is( tab, col, LOCALTIMESTAMP ) should pass -ok 242 - col_default_is( tab, col, LOCALTIMESTAMP ) should have the proper description -ok 243 - col_default_is( tab, col, LOCALTIMESTAMP ) should have the proper diagnostics +ok 64 - col_type_is( sch, tab, interval, sch, type, desc ) should pass +ok 65 - col_type_is( sch, tab, interval, sch, type, desc ) should have the proper description +ok 66 - col_type_is( sch, tab, interval, sch, type, desc ) should have the proper diagnostics +ok 67 - col_type_is( sch, tab, interval, sch, type, desc ) should pass +ok 68 - col_type_is( sch, tab, interval, sch, type, desc ) should have the proper description +ok 69 - col_type_is( sch, tab, interval, sch, type, desc ) should have the proper diagnostics +ok 70 - col_type_is( sch, tab, inval, type, desc ) should pass +ok 71 - col_type_is( sch, tab, inval, type, desc ) should have the proper description +ok 72 - col_type_is( sch, tab, inval, type, desc ) should have the proper diagnostics +ok 73 - col_type_is( sch, tab, intsec, sch, type, desc ) should pass +ok 74 - col_type_is( sch, tab, intsec, sch, type, desc ) should have the proper description +ok 75 - col_type_is( sch, tab, intsec, sch, type, desc ) should have the proper diagnostics +ok 76 - col_type_is( sch, tab, interval, sch, type, desc ) should pass +ok 77 - col_type_is( sch, tab, interval, sch, type, desc ) should have the proper description +ok 78 - col_type_is( sch, tab, interval, sch, type, desc ) should have the proper diagnostics +ok 79 - col_type_is( sch, tab, inval, type, desc ) should pass +ok 80 - col_type_is( sch, tab, inval, type, desc ) should have the proper description +ok 81 - col_type_is( sch, tab, inval, type, desc ) should have the proper diagnostics +ok 82 - col_type_is( sch, tab, stuff, sch, type, desc ) should pass +ok 83 - col_type_is( sch, tab, stuff, sch, type, desc ) should have the proper description +ok 84 - col_type_is( sch, tab, stuff, sch, type, desc ) should have the proper diagnostics +ok 85 - col_type_is( sch, tab, stuff, sch, type, desc ) should pass +ok 86 - col_type_is( sch, tab, stuff, sch, type, desc ) should have the proper description +ok 87 - col_type_is( sch, tab, stuff, sch, type, desc ) should have the proper diagnostics +ok 88 - col_type_is( sch, tab, col, sch, type, desc ) fail should fail +ok 89 - col_type_is( sch, tab, col, sch, type, desc ) fail should have the proper description +ok 90 - col_type_is( sch, tab, col, sch, type, desc ) fail should have the proper diagnostics +ok 91 - col_type_is( sch, tab, col, sch, non-type, desc ) should fail +ok 92 - col_type_is( sch, tab, col, sch, non-type, desc ) should have the proper description +ok 93 - col_type_is( sch, tab, col, sch, non-type, desc ) should have the proper diagnostics +ok 94 - col_type_is( sch, tab, col, non-sch, type, desc ) should fail +ok 95 - col_type_is( sch, tab, col, non-sch, type, desc ) should have the proper description +ok 96 - col_type_is( sch, tab, col, non-sch, type, desc ) should have the proper diagnostics +ok 97 - col_type_is( sch, tab, col, non-sch, type, desc ) should fail +ok 98 - col_type_is( sch, tab, col, non-sch, type, desc ) should have the proper description +ok 99 - col_type_is( sch, tab, col, non-sch, type, desc ) should have the proper diagnostics +ok 100 - col_type_is( sch, tab, col, non-type, desc ) should fail +ok 101 - col_type_is( sch, tab, col, non-type, desc ) should have the proper description +ok 102 - col_type_is( sch, tab, col, non-type, desc ) should have the proper diagnostics +ok 103 - col_type_is( tab, col, non-type, desc ) should fail +ok 104 - col_type_is( tab, col, non-type, desc ) should have the proper description +ok 105 - col_type_is( tab, col, non-type, desc ) should have the proper diagnostics +ok 106 - col_type_is( sch, tab, non-col, sch, type, desc ) should fail +ok 107 - col_type_is( sch, tab, non-col, sch, type, desc ) should have the proper description +ok 108 - col_type_is( sch, tab, non-col, sch, type, desc ) should have the proper diagnostics +ok 109 - col_type_is( sch, tab, col, type, desc ) should pass +ok 110 - col_type_is( sch, tab, col, type, desc ) should have the proper description +ok 111 - col_type_is( sch, tab, col, type, desc ) should have the proper diagnostics +ok 112 - col_type_is( sch, tab, col, type ) should pass +ok 113 - col_type_is( sch, tab, col, type ) should have the proper description +ok 114 - col_type_is( sch, tab, col, type ) should have the proper diagnostics +ok 115 - col_type_is( tab, col, type, desc ) should pass +ok 116 - col_type_is( tab, col, type, desc ) should have the proper description +ok 117 - col_type_is( tab, col, type, desc ) should have the proper diagnostics +ok 118 - col_type_is( tab, col, type ) should pass +ok 119 - col_type_is( tab, col, type ) should have the proper description +ok 120 - col_type_is( tab, col, type ) should have the proper diagnostics +ok 121 - col_type_is( tab, col, type ) fail should fail +ok 122 - col_type_is( tab, col, type ) fail should have the proper description +ok 123 - col_type_is( tab, col, type ) fail should have the proper diagnostics +ok 124 - col_type_is( tab, noncol, type ) fail should fail +ok 125 - col_type_is( tab, noncol, type ) fail should have the proper description +ok 126 - col_type_is( tab, noncol, type ) fail should have the proper diagnostics +ok 127 - col_type_is( sch, tab, noncol, type, desc ) fail should fail +ok 128 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper description +ok 129 - col_type_is( sch, tab, noncol, type, desc ) fail should have the proper diagnostics +ok 130 - col_type_is with precision should pass +ok 131 - col_type_is with precision should have the proper description +ok 132 - col_type_is with precision should have the proper diagnostics +ok 133 - col_type_is precision fail should fail +ok 134 - col_type_is precision fail should have the proper description +ok 135 - col_type_is precision fail should have the proper diagnostics +ok 136 - col_has_default( sch, tab, col, desc ) should pass +ok 137 - col_has_default( sch, tab, col, desc ) should have the proper description +ok 138 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics +ok 139 - col_has_default( tab, col, desc ) should pass +ok 140 - col_has_default( tab, col, desc ) should have the proper description +ok 141 - col_has_default( tab, col, desc ) should have the proper diagnostics +ok 142 - col_has_default( tab, col ) should pass +ok 143 - col_has_default( tab, col ) should have the proper description +ok 144 - col_has_default( tab, col ) should have the proper diagnostics +ok 145 - col_has_default( sch, tab, col, desc ) should fail +ok 146 - col_has_default( sch, tab, col, desc ) should have the proper description +ok 147 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics +ok 148 - col_has_default( tab, col, desc ) should fail +ok 149 - col_has_default( tab, col, desc ) should have the proper description +ok 150 - col_has_default( tab, col, desc ) should have the proper diagnostics +ok 151 - col_has_default( tab, col ) should fail +ok 152 - col_has_default( tab, col ) should have the proper description +ok 153 - col_has_default( tab, col ) should have the proper diagnostics +ok 154 - col_has_default( sch, tab, col, desc ) should fail +ok 155 - col_has_default( sch, tab, col, desc ) should have the proper description +ok 156 - col_has_default( sch, tab, col, desc ) should have the proper diagnostics +ok 157 - col_has_default( tab, col, desc ) should fail +ok 158 - col_has_default( tab, col, desc ) should have the proper description +ok 159 - col_has_default( tab, col, desc ) should have the proper diagnostics +ok 160 - col_has_default( tab, col ) should fail +ok 161 - col_has_default( tab, col ) should have the proper description +ok 162 - col_has_default( tab, col ) should have the proper diagnostics +ok 163 - col_hasnt_default( sch, tab, col, desc ) should fail +ok 164 - col_hasnt_default( sch, tab, col, desc ) should have the proper description +ok 165 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics +ok 166 - col_hasnt_default( tab, col, desc ) should fail +ok 167 - col_hasnt_default( tab, col, desc ) should have the proper description +ok 168 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics +ok 169 - col_hasnt_default( tab, col ) should fail +ok 170 - col_hasnt_default( tab, col ) should have the proper description +ok 171 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 172 - col_hasnt_default( sch, tab, col, desc ) should pass +ok 173 - col_hasnt_default( sch, tab, col, desc ) should have the proper description +ok 174 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics +ok 175 - col_hasnt_default( tab, col, desc ) should pass +ok 176 - col_hasnt_default( tab, col, desc ) should have the proper description +ok 177 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics +ok 178 - col_hasnt_default( tab, col ) should pass +ok 179 - col_hasnt_default( tab, col ) should have the proper description +ok 180 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 181 - col_hasnt_default( sch, tab, col, desc ) should fail +ok 182 - col_hasnt_default( sch, tab, col, desc ) should have the proper description +ok 183 - col_hasnt_default( sch, tab, col, desc ) should have the proper diagnostics +ok 184 - col_hasnt_default( tab, col, desc ) should fail +ok 185 - col_hasnt_default( tab, col, desc ) should have the proper description +ok 186 - col_hasnt_default( tab, col, desc ) should have the proper diagnostics +ok 187 - col_hasnt_default( tab, col ) should fail +ok 188 - col_hasnt_default( tab, col ) should have the proper description +ok 189 - col_hasnt_default( tab, col ) should have the proper diagnostics +ok 190 - col_default_is( sch, tab, col, def, desc ) should pass +ok 191 - col_default_is( sch, tab, col, def, desc ) should have the proper description +ok 192 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics +ok 193 - col_default_is() fail should fail +ok 194 - col_default_is() fail should have the proper description +ok 195 - col_default_is() fail should have the proper diagnostics +ok 196 - col_default_is( tab, col, def, desc ) should pass +ok 197 - col_default_is( tab, col, def, desc ) should have the proper description +ok 198 - col_default_is( tab, col, def, desc ) should have the proper diagnostics +ok 199 - col_default_is( tab, col, def ) should pass +ok 200 - col_default_is( tab, col, def ) should have the proper description +ok 201 - col_default_is( tab, col, def ) should have the proper diagnostics +ok 202 - col_default_is( tab, col, int ) should pass +ok 203 - col_default_is( tab, col, int ) should have the proper description +ok 204 - col_default_is( tab, col, int ) should have the proper diagnostics +ok 205 - col_default_is( tab, col, NULL, desc ) should pass +ok 206 - col_default_is( tab, col, NULL, desc ) should have the proper description +ok 207 - col_default_is( tab, col, NULL, desc ) should have the proper diagnostics +ok 208 - col_default_is( tab, col, NULL ) should pass +ok 209 - col_default_is( tab, col, NULL ) should have the proper description +ok 210 - col_default_is( tab, col, NULL ) should have the proper diagnostics +ok 211 - col_default_is( tab, col, bogus, desc ) should fail +ok 212 - col_default_is( tab, col, bogus, desc ) should have the proper description +ok 213 - col_default_is( tab, col, bogus, desc ) should have the proper diagnostics +ok 214 - col_default_is( tab, col, bogus ) should fail +ok 215 - col_default_is( tab, col, bogus ) should have the proper description +ok 216 - col_default_is( tab, col, bogus ) should have the proper diagnostics +ok 217 - col_default_is( tab, col, expression ) should pass +ok 218 - col_default_is( tab, col, expression ) should have the proper description +ok 219 - col_default_is( tab, col, expression ) should have the proper diagnostics +ok 220 - col_default_is( tab, col, expression::text ) should pass +ok 221 - col_default_is( tab, col, expression::text ) should have the proper description +ok 222 - col_default_is( tab, col, expression::text ) should have the proper diagnostics +ok 223 - col_default_is( tab, col, expression, desc ) should pass +ok 224 - col_default_is( tab, col, expression, desc ) should have the proper description +ok 225 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics +ok 226 - col_default_is( tab, col, expression, desc ) should pass +ok 227 - col_default_is( tab, col, expression, desc ) should have the proper description +ok 228 - col_default_is( tab, col, expression, desc ) should have the proper diagnostics +ok 229 - col_default_is( schema, tab, col, expression, desc ) should pass +ok 230 - col_default_is( schema, tab, col, expression, desc ) should have the proper description +ok 231 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics +ok 232 - col_default_is( schema, tab, col, expression, desc ) should pass +ok 233 - col_default_is( schema, tab, col, expression, desc ) should have the proper description +ok 234 - col_default_is( schema, tab, col, expression, desc ) should have the proper diagnostics +ok 235 - col_default_is( sch, tab, col, def, desc ) should fail +ok 236 - col_default_is( sch, tab, col, def, desc ) should have the proper description +ok 237 - col_default_is( sch, tab, col, def, desc ) should have the proper diagnostics +ok 238 - col_default_is( tab, col, def, desc ) should fail +ok 239 - col_default_is( tab, col, def, desc ) should have the proper description +ok 240 - col_default_is( tab, col, def, desc ) should have the proper diagnostics +ok 241 - col_default_is( tab, col, def ) should fail +ok 242 - col_default_is( tab, col, def ) should have the proper description +ok 243 - col_default_is( tab, col, def ) should have the proper diagnostics +ok 244 - col_default_is( tab, col, CURRENT_CATALOG ) should pass +ok 245 - col_default_is( tab, col, CURRENT_CATALOG ) should have the proper description +ok 246 - col_default_is( tab, col, CURRENT_CATALOG ) should have the proper diagnostics +ok 247 - col_default_is( tab, col, CURRENT_ROLE ) should pass +ok 248 - col_default_is( tab, col, CURRENT_ROLE ) should have the proper description +ok 249 - col_default_is( tab, col, CURRENT_ROLE ) should have the proper diagnostics +ok 250 - col_default_is( tab, col, CURRENT_SCHEMA ) should pass +ok 251 - col_default_is( tab, col, CURRENT_SCHEMA ) should have the proper description +ok 252 - col_default_is( tab, col, CURRENT_SCHEMA ) should have the proper diagnostics +ok 253 - col_default_is( tab, col, CURRENT_USER ) should pass +ok 254 - col_default_is( tab, col, CURRENT_USER ) should have the proper description +ok 255 - col_default_is( tab, col, CURRENT_USER ) should have the proper diagnostics +ok 256 - col_default_is( tab, col, SESSION_USER ) should pass +ok 257 - col_default_is( tab, col, SESSION_USER ) should have the proper description +ok 258 - col_default_is( tab, col, SESSION_USER ) should have the proper diagnostics +ok 259 - col_default_is( tab, col, USER ) should pass +ok 260 - col_default_is( tab, col, USER ) should have the proper description +ok 261 - col_default_is( tab, col, USER ) should have the proper diagnostics +ok 262 - col_default_is( tab, col, CURRENT_DATE ) should pass +ok 263 - col_default_is( tab, col, CURRENT_DATE ) should have the proper description +ok 264 - col_default_is( tab, col, CURRENT_DATE ) should have the proper diagnostics +ok 265 - col_default_is( tab, col, CURRENT_TIME ) should pass +ok 266 - col_default_is( tab, col, CURRENT_TIME ) should have the proper description +ok 267 - col_default_is( tab, col, CURRENT_TIME ) should have the proper diagnostics +ok 268 - col_default_is( tab, col, CURRENT_TIMESTAMP ) should pass +ok 269 - col_default_is( tab, col, CURRENT_TIMESTAMP ) should have the proper description +ok 270 - col_default_is( tab, col, CURRENT_TIMESTAMP ) should have the proper diagnostics +ok 271 - col_default_is( tab, col, LOCALTIME ) should pass +ok 272 - col_default_is( tab, col, LOCALTIME ) should have the proper description +ok 273 - col_default_is( tab, col, LOCALTIME ) should have the proper diagnostics +ok 274 - col_default_is( tab, col, LOCALTIMESTAMP ) should pass +ok 275 - col_default_is( tab, col, LOCALTIMESTAMP ) should have the proper description +ok 276 - col_default_is( tab, col, LOCALTIMESTAMP ) should have the proper diagnostics diff --git a/test/sql/coltap.sql b/test/sql/coltap.sql index 5674f751db3a..2a0a9af2586b 100644 --- a/test/sql/coltap.sql +++ b/test/sql/coltap.sql @@ -1,14 +1,21 @@ \unset ECHO \i test/setup.sql +-- \i sql/pgtap.sql -SELECT plan(243); ---SELECT * from no_plan(); +SELECT plan(276); +-- SELECT * from no_plan(); CREATE TYPE public."myType" AS ( id INT, foo INT ); +CREATE SCHEMA hidden; +CREATE TYPE hidden.stuff AS ( + id INT, + foo INT +); + -- This will be rolled back. :-) SET client_min_messages = warning; CREATE TABLE public.sometab( @@ -29,7 +36,11 @@ CREATE TABLE public.sometab( ltime TIME DEFAULT LOCALTIME, ltstz TIMESTAMPTZ DEFAULT LOCALTIMESTAMP, plain INTEGER, - camel "myType" + camel "myType", + inval INTERVAL(0), + isecd INTERVAL SECOND(0), + iyear INTERVAL YEAR, + stuff hidden.stuff ); CREATE OR REPLACE FUNCTION fakeout( eok boolean, name text ) @@ -201,7 +212,7 @@ SELECT * FROM check_test( -- Try case-sensitive type name. SELECT * FROM check_test( - col_type_is( 'public', 'sometab', 'camel', 'public', 'myType', 'camel is myType' ), + col_type_is( 'public', 'sometab', 'camel', 'public', '"myType"', 'camel is myType' ), true, 'col_type_is( sch, tab, camel, sch, type, desc )', 'camel is myType', @@ -209,21 +220,88 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - col_type_is( 'public', 'sometab', 'camel', 'public'::name, 'myType' ), + col_type_is( 'public', 'sometab', 'camel', 'public'::name, '"myType"' ), true, - 'col_type_is( sch, tab, camel, sch, type, desc )', - 'Column public.sometab.camel should be type public.myType', + 'col_type_is( sch, tab, camel, sch, type )', + 'Column public.sometab.camel should be type public."myType"', '' ); SELECT * FROM check_test( - col_type_is( 'public', 'sometab', 'camel', 'myType', 'whatever' ), + col_type_is( 'public', 'sometab', 'camel', '"myType"', 'whatever' ), true, 'col_type_is( sch, tab, camel, type, desc )', 'whatever', '' ); +-- Try interval. +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'inval', 'pg_catalog', 'interval(0)', 'inval is interval(0)' ), + true, + 'col_type_is( sch, tab, interval, sch, type, desc )', + 'inval is interval(0)', + '' +); + +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'inval', 'pg_catalog'::name, 'interval(0)' ), + true, + 'col_type_is( sch, tab, interval, sch, type, desc )', + 'Column public.sometab.inval should be type pg_catalog.interval(0)', + '' +); + +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'inval', 'interval(0)', 'whatever' ), + true, + 'col_type_is( sch, tab, inval, type, desc )', + 'whatever', + '' +); + +-- Try interval second. +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'isecd', 'pg_catalog', 'interval second(0)', 'isecd is interval second(0)' ), + true, + 'col_type_is( sch, tab, intsec, sch, type, desc )', + 'isecd is interval second(0)', + '' +); + +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'inval', 'pg_catalog'::name, 'interval(0)' ), + true, + 'col_type_is( sch, tab, interval, sch, type, desc )', + 'Column public.sometab.inval should be type pg_catalog.interval(0)', + '' +); + +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'inval', 'interval(0)', 'whatever' ), + true, + 'col_type_is( sch, tab, inval, type, desc )', + 'whatever', + '' +); + +-- Try type not in search path. +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'stuff', 'hidden', 'stuff', 'stuff is stuff' ), + true, + 'col_type_is( sch, tab, stuff, sch, type, desc )', + 'stuff is stuff', + '' +); + +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'stuff', 'hidden'::name, 'stuff' ), + true, + 'col_type_is( sch, tab, stuff, sch, type, desc )', + 'Column public.sometab.stuff should be type hidden.stuff', + '' +); + -- Try failures. SELECT * FROM check_test( col_type_is( 'public', 'sometab', 'name', 'pg_catalog', 'int', 'whatever' ), @@ -239,8 +317,7 @@ SELECT * FROM check_test( false, 'col_type_is( sch, tab, col, sch, non-type, desc )', 'whatever', - ' have: pg_catalog.text - want: pg_catalog.blech' + ' Type pg_catalog.blech does not exist' ); SELECT * FROM check_test( @@ -248,8 +325,31 @@ SELECT * FROM check_test( false, 'col_type_is( sch, tab, col, non-sch, type, desc )', 'whatever', - ' have: pg_catalog.text - want: fooey.text' + ' Type fooey.text does not exist' +); + +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'name', 'fooey', 'text', 'whatever' ), + false, + 'col_type_is( sch, tab, col, non-sch, type, desc )', + 'whatever', + ' Type fooey.text does not exist' +); + +SELECT * FROM check_test( + col_type_is( 'public', 'sometab', 'name', 'nonesuch', 'whatever' ), + false, + 'col_type_is( sch, tab, col, non-type, desc )', + 'whatever', + ' Type nonesuch does not exist' +); + +SELECT * FROM check_test( + col_type_is( 'sometab', 'name', 'nonesuch', 'whatever' ), + false, + 'col_type_is( tab, col, non-type, desc )', + 'whatever', + ' Type nonesuch does not exist' ); SELECT * FROM check_test( @@ -336,7 +436,7 @@ SELECT * FROM check_test( 'col_type_is precision fail', 'should be numeric(7)', ' have: numeric(8,0) - want: numeric(7)' + want: numeric(7,0)' ); /****************************************************************************/ From 67e65caf14d5a9667c4348a5b82ffda3e9af8dd3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 23 Sep 2023 17:17:20 -0400 Subject: [PATCH 1150/1195] Replace _quote_ident_like() with _typename() This function returns the canonical data type name, without typemod, allowing any alias to be used. Testers are no longer limited to spelling out `character varying` or `timestamp with time zone`; they can now use `varchar` or `timestamptz` or any other alias. Done by replacing all uses of the hacky old `_quote_ident_like()` function, now removed, with `_typename()`. This impacts the following test functions: * `has_cast()` * `hasnt_cast()` * `cast_context_is()` * `domain_type_is()` * `domain_type_isnt()` * `has_operator()` * `hasnt_operator()` Update the documentation to reflect this change. --- Changes | 8 +++ doc/pgtap.mmd | 32 ++++++---- sql/pgtap--1.3.0--1.3.1.sql | 115 ++++++++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 40 +++++-------- test/sql/hastap.sql | 62 +++++++++---------- 5 files changed, 190 insertions(+), 67 deletions(-) diff --git a/Changes b/Changes index d86606504bbd..7942593460dc 100644 --- a/Changes +++ b/Changes @@ -15,6 +15,14 @@ Revision history for pgTAP * Removed the documentation for `pg_typeof()`, which was removed from pgTAP when support for PostgreSQL 8.3 was dropped, since the same function has been available in the PostgreSQL core since then. +* Improved the support for type name aliases to the following functions: + * `has_cast()` + * `hasnt_cast()` + * `cast_context_is()` + * `domain_type_is()` + * `domain_type_isnt()` + * `has_operator()` + * `hasnt_operator()` 1.3.0 2023-08-14T22:14:20Z -------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index ae06515b3b16..89a7e4d53671 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -3834,13 +3834,15 @@ resolve the function name as a description. For example: pgTAP will generate a useful description if you don't provide one. -Note that pgTAP does not compare typemods. So if you wanted to test for a cast -between, say, a `uuid` type and `bit(128)`, this will not work: +Types can be defined by their canonical names or their aliases, +e.g., `character varying` or `varchar`, so both these tests will pass: - SELECT has_cast( 'integer', 'bit(128)' ); + SELECT has_cast( 'text', 'character varying' ); + SELECT has_cast( 'text', 'varchar' ); -But this will: +Note that pgTAP ignores typemods, so either of these tests will pass: + SELECT has_cast( 'integer', 'bit(128)' ); SELECT has_cast( 'integer', 'bit' ); ### `hasnt_cast()` ### @@ -3907,6 +3909,10 @@ pass. If the operator does not exist, the test will fail. Example: SELECT has_operator( 'integer', 'pg_catalog', '<=', 'integer', 'boolean' ); +Types can be defined by their canonical names or their aliases, e.g., +`timestamp with time zone` or `timestamptz`, or `character varying` or +`varchar`. + If you omit the schema name, then the operator must be visible in the search path. If you omit the test description, pgTAP will generate a reasonable one for you. The return value is also optional. If you need to test for a left @@ -6139,7 +6145,7 @@ omit the test description, it will be set to "Enum `:enum` should have labels `:description` : A short description of the test. -Tests the data type underlying a domain. The first two arguments are the +Tests the data type underlying a domain. The first two arguments are the schema and name of the domain. The second two are the schema and name of the type that the domain should extend. The fifth argument is a description. If there is no description, a reasonable default description will be created. @@ -6148,6 +6154,10 @@ The schema arguments are also optional. However, if there is no `:schema` argument, there cannot be a `:type_schema` argument, either, though the schema can be included in the `type` argument, e.g., `contrib.citext`. +Types can be defined by their canonical names or their aliases, e.g., +`timestamp with time zone` or `timestamptz`, or `character varying` or +`varchar`. + For the 3- and 4-argument forms with schemas, cast the schemas to `NAME` to avoid ambiguities. Example: @@ -6229,12 +6239,14 @@ Example: SELECT cast_context_is( 'integer', 'bigint', 'implicit' ); -The data types should be passed as they are displayed by -`pg_catalog.format_type()`. For example, you would need to pass "character -varying", and not "VARCHAR". +The data types may be defined by their canonical names or their aliases, +e.g., `character varying` or `varchar`, so both these tests will pass: + + SELECT cast_context_is( 'text', 'character varying', 'implicit' ); + SELECT cast_context_is( 'text', 'varchar', 'implicit' ); -The The supported contexts are "implicit", "assignment", and "explicit". You -can also just pass in "i", "a", or "e". Consult the PostgreSQL [`CREATE +The supported contexts are "implicit", "assignment", and "explicit". You can +also just pass in "i", "a", or "e". Consult the PostgreSQL [`CREATE CAST`](https://www.postgresql.org/docs/current/static/sql-createcast.html) documentation for the differences between these contexts (hint: they correspond to the default context, `AS IMPLICIT`, and `AS ASSIGNMENT`). If you diff --git a/sql/pgtap--1.3.0--1.3.1.sql b/sql/pgtap--1.3.0--1.3.1.sql index 699856458668..6a5f54a8b323 100644 --- a/sql/pgtap--1.3.0--1.3.1.sql +++ b/sql/pgtap--1.3.0--1.3.1.sql @@ -96,3 +96,118 @@ BEGIN ); END; $$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION _cmp_types(oid, name) +RETURNS BOOLEAN AS $$ + SELECT pg_catalog.format_type($1, NULL) = _typename($2); +$$ 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, 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( 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_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, 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( 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; + +DROP FUNCTION _quote_ident_like(TEXT, TEXT); diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index c4c94ccdf14d..01d135fe97e6 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1473,20 +1473,6 @@ EXCEPTION WHEN OTHERS THEN RETURN NULL; END; $$ LANGUAGE PLPGSQL STABLE; -CREATE OR REPLACE FUNCTION _quote_ident_like(TEXT, TEXT) -RETURNS TEXT AS $$ -DECLARE - typname TEXT := _typename($1); - pcision TEXT := COALESCE(substring($1 FROM '[(][^")]+[)]$'), ''); -BEGIN - -- Just return it if rhs isn't quoted. - IF $2 !~ '"' THEN RETURN typname || pcision; END IF; - - -- Otherwise return it with the type part quoted. - RETURN quote_ident(typname) || pcision; -END; -$$ LANGUAGE plpgsql; - -- 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 $$ @@ -4088,12 +4074,8 @@ $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION _cmp_types(oid, name) RETURNS BOOLEAN AS $$ -DECLARE - dtype TEXT := pg_catalog.format_type($1, NULL); -BEGIN - RETURN dtype = _quote_ident_like($2, dtype); -END; -$$ LANGUAGE plpgsql; + SELECT pg_catalog.format_type($1, NULL) = _typename($2); +$$ LANGUAGE sql; CREATE OR REPLACE FUNCTION _cast_exists ( NAME, NAME, NAME, NAME ) RETURNS BOOLEAN AS $$ @@ -7843,7 +7825,10 @@ BEGIN ); END IF; - RETURN is( actual_type, quote_ident($3) || '.' || _quote_ident_like($4, actual_type), $5 ); + 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; @@ -7870,7 +7855,7 @@ BEGIN ); END IF; - RETURN is( actual_type, _quote_ident_like($3, actual_type), $4 ); + RETURN is( actual_type, _typename($3), $4 ); END; $$ LANGUAGE plpgsql; @@ -7896,7 +7881,7 @@ BEGIN ); END IF; - RETURN is( actual_type, _quote_ident_like($2, actual_type), $3 ); + RETURN is( actual_type, _typename($2), $3 ); END; $$ LANGUAGE plpgsql; @@ -7922,7 +7907,10 @@ BEGIN ); END IF; - RETURN isnt( actual_type, quote_ident($3) || '.' || _quote_ident_like($4, actual_type), $5 ); + 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; @@ -7949,7 +7937,7 @@ BEGIN ); END IF; - RETURN isnt( actual_type, _quote_ident_like($3, actual_type), $4 ); + RETURN isnt( actual_type, _typename($3), $4 ); END; $$ LANGUAGE plpgsql; @@ -7975,7 +7963,7 @@ BEGIN ); END IF; - RETURN isnt( actual_type, _quote_ident_like($2, actual_type), $3 ); + RETURN isnt( actual_type, _typename($2), $3 ); END; $$ LANGUAGE plpgsql; diff --git a/test/sql/hastap.sql b/test/sql/hastap.sql index 6ce878284a69..d9523a5ea587 100644 --- a/test/sql/hastap.sql +++ b/test/sql/hastap.sql @@ -1,5 +1,6 @@ \unset ECHO \i test/setup.sql +-- \i sql/pgtap.sql SELECT plan(1004); --SELECT * FROM no_plan(); @@ -48,7 +49,6 @@ CREATE DOMAIN public."myDomain" AS TEXT CHECK(TRUE); CREATE SEQUENCE public.someseq; - CREATE SCHEMA someschema; RESET client_min_messages; @@ -1109,10 +1109,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_cast( 'integer', 'bigint', 'pg_catalog', 'int8'::name), + has_cast( 'int4', 'BIGINT', 'pg_catalog', 'int8'::name), true, 'has_cast( src, targ, schema, func )', - 'Cast ("integer" AS "bigint") WITH FUNCTION pg_catalog.int8() should exist', + 'Cast (int4 AS "BIGINT") WITH FUNCTION pg_catalog.int8() should exist', '' ); @@ -1125,10 +1125,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_cast( 'integer', 'bigint', 'int8'::name), + has_cast( 'INT4', 'BIGINT', 'int8'::name), true, 'has_cast( src, targ, func)', - 'Cast ("integer" AS "bigint") WITH FUNCTION int8() should exist', + 'Cast ("INT4" AS "BIGINT") WITH FUNCTION int8() should exist', '' ); @@ -1141,10 +1141,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_cast( 'integer', 'bigint' ), + has_cast( 'int4', 'BIGINT' ), true, 'has_cast( src, targ )', - 'Cast ("integer" AS "bigint") should exist', + 'Cast (int4 AS "BIGINT") should exist', '' ); @@ -1157,7 +1157,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_cast( 'integer', 'bigint', 'foo', 'desc' ), + has_cast( 'INT4', 'BIGINT', 'foo', 'desc' ), false, 'has_cast( src, targ, func, desc ) fail', 'desc', @@ -1184,10 +1184,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_cast( 'integer', 'bigint', 'pg_catalog', 'int8'::name), + hasnt_cast( 'int4', 'int8', 'pg_catalog', 'int8'::name), false, 'hasnt_cast( src, targ, schema, func )', - 'Cast ("integer" AS "bigint") WITH FUNCTION pg_catalog.int8() should not exist', + 'Cast (int4 AS int8) WITH FUNCTION pg_catalog.int8() should not exist', '' ); @@ -1200,10 +1200,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_cast( 'integer', 'bigint', 'int8'::name), + hasnt_cast( 'INT4', 'INT8', 'int8'::name), false, 'hasnt_cast( src, targ, func)', - 'Cast ("integer" AS "bigint") WITH FUNCTION int8() should not exist', + 'Cast ("INT4" AS "INT8") WITH FUNCTION int8() should not exist', '' ); @@ -1216,10 +1216,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_cast( 'integer', 'bigint' ), + hasnt_cast( 'int4', 'int8' ), false, 'hasnt_cast( src, targ )', - 'Cast ("integer" AS "bigint") should not exist', + 'Cast (int4 AS int8) should not exist', '' ); @@ -1232,7 +1232,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - hasnt_cast( 'integer', 'bigint', 'foo', 'desc' ), + hasnt_cast( 'INT4', 'INT8', 'foo', 'desc' ), true, 'hasnt_cast( src, targ, func, desc ) fail', 'desc', @@ -1259,10 +1259,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - cast_context_is( 'integer', 'bigint', 'implicit' ), + cast_context_is( 'int4', 'int8', 'implicit' ), true, 'cast_context_is( src, targ, context )', - 'Cast ("integer" AS "bigint") context should be implicit', + 'Cast (int4 AS int8) context should be implicit', '' ); @@ -1275,7 +1275,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - cast_context_is( 'integer', 'bigint', 'IMPL', 'desc' ), + cast_context_is( 'INT4', 'INT8', 'IMPL', 'desc' ), true, 'cast_context_is( src, targ, IMPL, desc )', 'desc', @@ -1291,7 +1291,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - cast_context_is( 'bigint', 'smallint', 'a', 'desc' ), + cast_context_is( 'int4', 'int2', 'a', 'desc' ), true, 'cast_context_is( src, targ, a, desc )', 'desc', @@ -1307,7 +1307,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - cast_context_is( 'bit', 'integer', 'explicit', 'desc' ), + cast_context_is( 'bit(128)', 'integer', 'explicit', 'desc' ), true, 'cast_context_is( src, targ, explicit, desc )', 'desc', @@ -1315,7 +1315,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - cast_context_is( 'bit', 'integer', 'e', 'desc' ), + cast_context_is( 'bit', 'int4', 'e', 'desc' ), true, 'cast_context_is( src, targ, e, desc )', 'desc', @@ -1331,7 +1331,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - cast_context_is( 'integer', 'bigint', 'ex', 'desc' ), + cast_context_is( 'integer', 'int8', 'ex', 'desc' ), false, 'cast_context_is( src, targ, context, desc ) fail', 'desc', @@ -1340,10 +1340,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - cast_context_is( 'integer', 'bigint', 'ex' ), + cast_context_is( 'INT4', 'INT8', 'ex' ), false, 'cast_context_is( src, targ, context ) fail', - 'Cast ("integer" AS "bigint") context should be explicit', + 'Cast ("INT4" AS "INT8") context should be explicit', ' have: implicit want: explicit' ); @@ -1368,10 +1368,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_operator( 'integer', 'pg_catalog', '<=', 'integer', 'boolean'::name ), + has_operator( 'int4', 'pg_catalog', '<=', 'integer', 'boolean'::name ), true, 'has_operator( left, schema, name, right, result )', - 'Operator pg_catalog.<=(integer,integer) RETURNS boolean should exist', + 'Operator pg_catalog.<=(int4,integer) RETURNS boolean should exist', '' ); @@ -1384,10 +1384,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_operator( 'integer', '<=', 'integer', 'boolean'::name ), + has_operator( 'integer', '<=', 'int4', 'boolean'::name ), true, 'has_operator( left, name, right, result )', - 'Operator <=(integer,integer) RETURNS boolean should exist', + 'Operator <=(integer,int4) RETURNS boolean should exist', '' ); @@ -1400,10 +1400,10 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - has_operator( 'integer', '<=', 'integer'::name ), + has_operator( 'integer', '<=', 'int4'::name ), true, 'has_operator( left, name, right )', - 'Operator <=(integer,integer) should exist', + 'Operator <=(integer,int4) should exist', '' ); @@ -2269,7 +2269,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - domain_type_is( 'public', 'us_postal_code', 'pg_catalog', 'int', 'whatever'), + domain_type_is( 'public', 'us_postal_code', 'pg_catalog', 'int4', 'whatever'), false, 'domain_type_is(schema, domain, schema, type, desc) fail', 'whatever', From d663c046ddbb3e705792cb4e3381821217ce428f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 23 Sep 2023 18:56:51 -0400 Subject: [PATCH 1151/1195] Fix patch offesets --- Makefile | 3 --- compat/10/pgtap--0.98.0--0.99.0.patch | 2 +- compat/9.4/pgtap--0.99.0--1.0.0.patch | 2 +- compat/9.6/pgtap--0.97.0--0.98.0.patch | 2 +- compat/install-9.1.patch | 10 ++++------ compat/install-9.2.patch | 4 ++-- compat/install-9.4.patch | 6 +++--- compat/install-9.6.patch | 2 +- 8 files changed, 13 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index 7ce76ccafd9e..be71d4c6b2b9 100644 --- a/Makefile +++ b/Makefile @@ -250,9 +250,6 @@ sql/pgtap--0.96.0--0.97.0.sql: sql/pgtap--0.96.0--0.97.0.sql.in ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][01234]|8[.][1234])" && echo yes || echo no),yes) patch -p0 < compat/9.4/pgtap--0.96.0--0.97.0.patch endif -ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.]0|8[.][1234])" && echo yes || echo no),yes) - patch -p0 < compat/9.0/pgtap--0.96.0--0.97.0.patch -endif EXTRA_CLEAN += sql/pgtap--0.95.0--0.96.0.sql sql/pgtap--0.95.0--0.96.0.sql: sql/pgtap--0.95.0--0.96.0.sql.in diff --git a/compat/10/pgtap--0.98.0--0.99.0.patch b/compat/10/pgtap--0.98.0--0.99.0.patch index 6fbc46c1e616..4115f34cd6cc 100644 --- a/compat/10/pgtap--0.98.0--0.99.0.patch +++ b/compat/10/pgtap--0.98.0--0.99.0.patch @@ -8,7 +8,7 @@ CREATE OR REPLACE VIEW tap_funky AS SELECT p.oid AS oid, n.nspname AS schema, -@@ -124,7 +124,7 @@ CREATE OR REPLACE VIEW tap_funky +@@ -144,7 +144,7 @@ CREATE OR REPLACE VIEW tap_funky || p.prorettype::regtype AS returns, p.prolang AS langoid, p.proisstrict AS is_strict, diff --git a/compat/9.4/pgtap--0.99.0--1.0.0.patch b/compat/9.4/pgtap--0.99.0--1.0.0.patch index 662524a0c352..faf8f97d17c0 100644 --- a/compat/9.4/pgtap--0.99.0--1.0.0.patch +++ b/compat/9.4/pgtap--0.99.0--1.0.0.patch @@ -1,6 +1,6 @@ --- sql/pgtap--0.99.0--1.0.0.sql +++ sql/pgtap--0.99.0--1.0.0.sql -@@ -7,233 +7,6 @@ RETURNS text AS $$ +@@ -11,233 +11,6 @@ RETURNS text AS $$ ), $2); $$ LANGUAGE SQL immutable; diff --git a/compat/9.6/pgtap--0.97.0--0.98.0.patch b/compat/9.6/pgtap--0.97.0--0.98.0.patch index 8494c8a0062a..abcd7177c107 100644 --- a/compat/9.6/pgtap--0.97.0--0.98.0.patch +++ b/compat/9.6/pgtap--0.97.0--0.98.0.patch @@ -1,6 +1,6 @@ --- sql/pgtap--0.97.0--0.98.0.sql +++ sql/pgtap--0.97.0--0.98.0.sql -@@ -292,133 +292,3 @@ +@@ -309,133 +309,3 @@ 'Table ' || quote_ident($1) || ' should not be partitioned' ); $$ LANGUAGE sql; diff --git a/compat/install-9.1.patch b/compat/install-9.1.patch index d5394cd9901a..7bc16243c70b 100644 --- a/compat/install-9.1.patch +++ b/compat/install-9.1.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -776,10 +776,6 @@ +@@ -786,10 +786,6 @@ RETURN ok( TRUE, descr ); EXCEPTION WHEN OTHERS THEN -- There should have been no exception. @@ -11,7 +11,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6374,10 +6370,6 @@ +@@ -6661,10 +6657,6 @@ -- Something went wrong. Record that fact. errstate := SQLSTATE; errmsg := SQLERRM; @@ -22,9 +22,7 @@ END; -- Always raise an exception to rollback any changes. ---- sql/pgtap.sql -+++ sql/pgtap.sql -@@ -6906,7 +6906,6 @@ +@@ -7132,7 +7124,6 @@ RETURN ok( true, $3 ); EXCEPTION WHEN datatype_mismatch THEN @@ -32,7 +30,7 @@ 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' || -@@ -7060,7 +7059,6 @@ +@@ -7286,7 +7277,6 @@ RETURN ok( false, $3 ); EXCEPTION WHEN datatype_mismatch THEN diff --git a/compat/install-9.2.patch b/compat/install-9.2.patch index c71bf65a607a..5b8c2f30aaa2 100644 --- a/compat/install-9.2.patch +++ b/compat/install-9.2.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -779,12 +779,7 @@ +@@ -789,12 +789,7 @@ GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, hint = PG_EXCEPTION_HINT, @@ -14,7 +14,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6382,12 +6397,7 @@ +@@ -6669,12 +6664,7 @@ GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, hint = PG_EXCEPTION_HINT, diff --git a/compat/install-9.4.patch b/compat/install-9.4.patch index 0ab9929b6b76..4fda6f7075f9 100644 --- a/compat/install-9.4.patch +++ b/compat/install-9.4.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -670,7 +670,7 @@ +@@ -680,7 +680,7 @@ ' caught: no exception' || E'\n wanted: ' || COALESCE( errcode, 'an exception' ) ); @@ -9,7 +9,7 @@ IF (errcode IS NULL OR SQLSTATE = errcode) AND ( errmsg IS NULL OR SQLERRM = errmsg) THEN -@@ -774,7 +774,7 @@ +@@ -784,7 +784,7 @@ BEGIN EXECUTE code; RETURN ok( TRUE, descr ); @@ -18,7 +18,7 @@ -- There should have been no exception. GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, -@@ -9916,233 +9916,6 @@ +@@ -10213,233 +10213,6 @@ ), $2); $$ LANGUAGE SQL immutable; diff --git a/compat/install-9.6.patch b/compat/install-9.6.patch index 3ce5b460bc99..831f2fc7ea51 100644 --- a/compat/install-9.6.patch +++ b/compat/install-9.6.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -9898,136 +9898,6 @@ +@@ -10195,136 +10195,6 @@ ); $$ LANGUAGE sql; From b000b4b3dd476facdc681f8b90321243b72096e4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 23 Sep 2023 19:24:01 -0400 Subject: [PATCH 1152/1195] Add the `has_pk(:schema, :table)` variant Resolves #287. Also removed outdated manual custom schema installation instructions from the README. --- Changes | 3 + Makefile | 1 + README.md | 6 -- compat/install-9.1.patch | 6 +- compat/install-9.2.patch | 2 +- compat/install-9.4.patch | 2 +- compat/install-9.6.patch | 2 +- doc/pgtap.mmd | 6 ++ sql/pgtap.sql.in | 6 ++ test/expected/pktap.out | 182 ++++++++++++++++++++------------------- test/sql/pktap.sql | 19 +++- 11 files changed, 134 insertions(+), 101 deletions(-) diff --git a/Changes b/Changes index 7942593460dc..b67aeb5d4721 100644 --- a/Changes +++ b/Changes @@ -23,6 +23,9 @@ Revision history for pgTAP * `domain_type_isnt()` * `has_operator()` * `hasnt_operator()` +* Added the `has_pk( :schema, :table )` variant, which requires that `:table` be + cast to `name` to avoid confusion with the `has_pk( :schema, :description )` + variant. Thanks to Adrian Klaver for the report (#287)! 1.3.0 2023-08-14T22:14:20Z -------------------------- diff --git a/Makefile b/Makefile index be71d4c6b2b9..ab00b403ac78 100644 --- a/Makefile +++ b/Makefile @@ -205,6 +205,7 @@ uninstall-all: rm -f $(EXTENSION_DIR)/pgtap* # TODO: switch this whole thing to a perl or shell script that understands the file naming convention and how to compare that to $VERSION. +# VERSION = 9.1.0 # Uncomment to test all patches. sql/pgtap.sql: sql/pgtap.sql.in cp $< $@ ifeq ($(shell echo $(VERSION) | grep -qE "^(9[.][0123456]|8[.][1234])" && echo yes || echo no),yes) diff --git a/README.md b/README.md index bd481fb57168..9b161314f325 100644 --- a/README.md +++ b/README.md @@ -81,12 +81,6 @@ installed, you can upgrade it to a properly packaged extension with: CREATE EXTENSION pgtap FROM unpackaged; -If you want to install pgTAP and all of its supporting objects into a -specific schema, use the `PGOPTIONS` environment variable to specify the -schema, like so: - - PGOPTIONS=--search_path=tap psql -d mydb -f pgTAP.sql - If you want to install pgTAP and all of its supporting objects into a specific schema, use the `SCHEMA` clause to specify the schema, like so: diff --git a/compat/install-9.1.patch b/compat/install-9.1.patch index 7bc16243c70b..633afc9da3b6 100644 --- a/compat/install-9.1.patch +++ b/compat/install-9.1.patch @@ -11,7 +11,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6661,10 +6657,6 @@ +@@ -6667,10 +6663,6 @@ -- Something went wrong. Record that fact. errstate := SQLSTATE; errmsg := SQLERRM; @@ -22,7 +22,7 @@ END; -- Always raise an exception to rollback any changes. -@@ -7132,7 +7124,6 @@ +@@ -7138,7 +7130,6 @@ RETURN ok( true, $3 ); EXCEPTION WHEN datatype_mismatch THEN @@ -30,7 +30,7 @@ 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' || -@@ -7286,7 +7277,6 @@ +@@ -7292,7 +7283,6 @@ RETURN ok( false, $3 ); EXCEPTION WHEN datatype_mismatch THEN diff --git a/compat/install-9.2.patch b/compat/install-9.2.patch index 5b8c2f30aaa2..715285a00e08 100644 --- a/compat/install-9.2.patch +++ b/compat/install-9.2.patch @@ -14,7 +14,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6669,12 +6664,7 @@ +@@ -6675,12 +6670,7 @@ GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, hint = PG_EXCEPTION_HINT, diff --git a/compat/install-9.4.patch b/compat/install-9.4.patch index 4fda6f7075f9..483d20a3677f 100644 --- a/compat/install-9.4.patch +++ b/compat/install-9.4.patch @@ -18,7 +18,7 @@ -- There should have been no exception. GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, -@@ -10213,233 +10213,6 @@ +@@ -10219,233 +10219,6 @@ ), $2); $$ LANGUAGE SQL immutable; diff --git a/compat/install-9.6.patch b/compat/install-9.6.patch index 831f2fc7ea51..fd63ed8879ad 100644 --- a/compat/install-9.6.patch +++ b/compat/install-9.6.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -10195,136 +10195,6 @@ +@@ -10201,136 +10201,6 @@ ); $$ LANGUAGE sql; diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 89a7e4d53671..0d4cf7b1eb92 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4636,6 +4636,7 @@ the diagnostics will tell you that, too. But you use `has_column()` and ### `has_pk()` ### SELECT has_pk( :schema, :table, :description ); + SELECT has_pk( :schema, :table ); SELECT has_pk( :table, :description ); SELECT has_pk( :table ); @@ -4657,6 +4658,11 @@ test description is omitted, it will be set to "Table `:table` should have a primary key". Note that this test will fail if the table in question does not exist. +If you find that the function call confuses the table name for a +description, cast the table to the `NAME` type: + + SELECT has_pk( 'myschema', 'mytable'::name ); + ### `hasnt_pk()` ### SELECT hasnt_pk( :schema, :table, :description ); diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 01d135fe97e6..0e873948efc4 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1827,6 +1827,12 @@ 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 $$ diff --git a/test/expected/pktap.out b/test/expected/pktap.out index f9d3cc28494d..013ee883ef08 100644 --- a/test/expected/pktap.out +++ b/test/expected/pktap.out @@ -1,92 +1,98 @@ \unset ECHO -1..90 +1..96 ok 1 - has_pk( schema, table, description ) should pass ok 2 - has_pk( schema, table, description ) should have the proper description ok 3 - has_pk( schema, table, description ) should have the proper diagnostics -ok 4 - has_pk( hideschema, hidetable, description ) should pass -ok 5 - has_pk( hideschema, hidetable, description ) should have the proper description -ok 6 - has_pk( hideschema, hidetable, description ) should have the proper diagnostics -ok 7 - has_pk( table, description ) should pass -ok 8 - has_pk( table, description ) should have the proper description -ok 9 - has_pk( table, description ) should have the proper diagnostics -ok 10 - has_pk( hidetable, description ) fail should fail -ok 11 - has_pk( hidetable, description ) fail should have the proper description -ok 12 - has_pk( hidetable, description ) fail should have the proper diagnostics -ok 13 - has_pk( table ) should pass -ok 14 - has_pk( table ) should have the proper description -ok 15 - has_pk( table ) should have the proper diagnostics -ok 16 - has_pk( schema, table, description ) fail should fail -ok 17 - has_pk( schema, table, description ) fail should have the proper description -ok 18 - has_pk( schema, table, description ) fail should have the proper diagnostics -ok 19 - has_pk( table, description ) fail should fail -ok 20 - has_pk( table, description ) fail should have the proper description -ok 21 - has_pk( table, description ) fail should have the proper diagnostics -ok 22 - hasnt_pk( schema, table, description ) should fail -ok 23 - hasnt_pk( schema, table, description ) should have the proper description -ok 24 - hasnt_pk( schema, table, description ) should have the proper diagnostics -ok 25 - hasnt_pk( table, description ) should fail -ok 26 - hasnt_pk( table, description ) should have the proper description -ok 27 - hasnt_pk( table, description ) should have the proper diagnostics -ok 28 - hasnt_pk( table ) should fail -ok 29 - hasnt_pk( table ) should have the proper description -ok 30 - hasnt_pk( table ) should have the proper diagnostics -ok 31 - hasnt_pk( schema, table, description ) pass should pass -ok 32 - hasnt_pk( schema, table, description ) pass should have the proper description -ok 33 - hasnt_pk( schema, table, description ) pass should have the proper diagnostics -ok 34 - hasnt_pk( table, description ) pass should pass -ok 35 - hasnt_pk( table, description ) pass should have the proper description -ok 36 - hasnt_pk( table, description ) pass should have the proper diagnostics -ok 37 - col_is_pk( schema, table, column, description ) should pass -ok 38 - col_is_pk( schema, table, column, description ) should have the proper description -ok 39 - col_is_pk( schema, table, column, description ) should have the proper diagnostics -ok 40 - col_is_pk( schema, table, column ) should pass -ok 41 - col_is_pk( schema, table, column ) should have the proper description -ok 42 - col_is_pk( schema, table, column ) should have the proper diagnostics -ok 43 - col_is_pk( table, column, description ) should pass -ok 44 - col_is_pk( table, column, description ) should have the proper description -ok 45 - col_is_pk( table, column, description ) should have the proper diagnostics -ok 46 - col_is_pk( table, column ) should pass -ok 47 - col_is_pk( table, column ) should have the proper description -ok 48 - col_is_pk( table, column ) should have the proper diagnostics -ok 49 - col_is_pk( schema, table, column, description ) fail should fail -ok 50 - col_is_pk( schema, table, column, description ) fail should have the proper description -ok 51 - col_is_pk( schema, table, column, description ) fail should have the proper diagnostics -ok 52 - col_is_pk( table, column, description ) fail should fail -ok 53 - col_is_pk( table, column, description ) fail should have the proper description -ok 54 - col_is_pk( table, column, description ) fail should have the proper diagnostics -ok 55 - col_is_pk( schema, table, column[], description ) should pass -ok 56 - col_is_pk( schema, table, column[], description ) should have the proper description -ok 57 - col_is_pk( schema, table, column[], description ) should have the proper diagnostics -ok 58 - col_is_pk( schema, table, column[] ) should pass -ok 59 - col_is_pk( schema, table, column[] ) should have the proper description -ok 60 - col_is_pk( schema, table, column[] ) should have the proper diagnostics -ok 61 - col_is_pk( table, column[], description ) should pass -ok 62 - col_is_pk( table, column[], description ) should have the proper description -ok 63 - col_is_pk( table, column[], description ) should have the proper diagnostics -ok 64 - col_is_pk( table, column[] ) should pass -ok 65 - col_is_pk( table, column[] ) should have the proper description -ok 66 - col_is_pk( table, column[] ) should have the proper diagnostics -ok 67 - col_isnt_pk( schema, table, column, description ) should fail -ok 68 - col_isnt_pk( schema, table, column, description ) should have the proper description -ok 69 - col_isnt_pk( schema, table, column, description ) should have the proper diagnostics -ok 70 - col_isnt_pk( table, column, description ) should fail -ok 71 - col_isnt_pk( table, column, description ) should have the proper description -ok 72 - col_isnt_pk( table, column, description ) should have the proper diagnostics -ok 73 - col_isnt_pk( table, column ) should fail -ok 74 - col_isnt_pk( table, column ) should have the proper description -ok 75 - col_isnt_pk( table, column ) should have the proper diagnostics -ok 76 - col_isnt_pk( schema, table, column, description ) pass should pass -ok 77 - col_isnt_pk( schema, table, column, description ) pass should have the proper description -ok 78 - col_isnt_pk( schema, table, column, description ) pass should have the proper diagnostics -ok 79 - col_isnt_pk( table, column, description ) pass should pass -ok 80 - col_isnt_pk( table, column, description ) pass should have the proper description -ok 81 - col_isnt_pk( table, column, description ) pass should have the proper diagnostics -ok 82 - col_isnt_pk( schema, table, column[], description ) should pass -ok 83 - col_isnt_pk( schema, table, column[], description ) should have the proper description -ok 84 - col_isnt_pk( schema, table, column[], description ) should have the proper diagnostics -ok 85 - col_isnt_pk( table, column[], description ) should pass -ok 86 - col_isnt_pk( table, column[], description ) should have the proper description -ok 87 - col_isnt_pk( table, column[], description ) should have the proper diagnostics -ok 88 - col_isnt_pk( table, column[] ) should pass -ok 89 - col_isnt_pk( table, column[] ) should have the proper description -ok 90 - col_isnt_pk( table, column[] ) should have the proper diagnostics +ok 4 - has_pk( schema, table ) should pass +ok 5 - has_pk( schema, table ) should have the proper description +ok 6 - has_pk( schema, table ) should have the proper diagnostics +ok 7 - has_pk( hideschema, hidetable, description ) should pass +ok 8 - has_pk( hideschema, hidetable, description ) should have the proper description +ok 9 - has_pk( hideschema, hidetable, description ) should have the proper diagnostics +ok 10 - has_pk( hideschema, hidetable ) should pass +ok 11 - has_pk( hideschema, hidetable ) should have the proper description +ok 12 - has_pk( hideschema, hidetable ) should have the proper diagnostics +ok 13 - has_pk( table, description ) should pass +ok 14 - has_pk( table, description ) should have the proper description +ok 15 - has_pk( table, description ) should have the proper diagnostics +ok 16 - has_pk( hidetable, description ) fail should fail +ok 17 - has_pk( hidetable, description ) fail should have the proper description +ok 18 - has_pk( hidetable, description ) fail should have the proper diagnostics +ok 19 - has_pk( table ) should pass +ok 20 - has_pk( table ) should have the proper description +ok 21 - has_pk( table ) should have the proper diagnostics +ok 22 - has_pk( schema, table, description ) fail should fail +ok 23 - has_pk( schema, table, description ) fail should have the proper description +ok 24 - has_pk( schema, table, description ) fail should have the proper diagnostics +ok 25 - has_pk( table, description ) fail should fail +ok 26 - has_pk( table, description ) fail should have the proper description +ok 27 - has_pk( table, description ) fail should have the proper diagnostics +ok 28 - hasnt_pk( schema, table, description ) should fail +ok 29 - hasnt_pk( schema, table, description ) should have the proper description +ok 30 - hasnt_pk( schema, table, description ) should have the proper diagnostics +ok 31 - hasnt_pk( table, description ) should fail +ok 32 - hasnt_pk( table, description ) should have the proper description +ok 33 - hasnt_pk( table, description ) should have the proper diagnostics +ok 34 - hasnt_pk( table ) should fail +ok 35 - hasnt_pk( table ) should have the proper description +ok 36 - hasnt_pk( table ) should have the proper diagnostics +ok 37 - hasnt_pk( schema, table, description ) pass should pass +ok 38 - hasnt_pk( schema, table, description ) pass should have the proper description +ok 39 - hasnt_pk( schema, table, description ) pass should have the proper diagnostics +ok 40 - hasnt_pk( table, description ) pass should pass +ok 41 - hasnt_pk( table, description ) pass should have the proper description +ok 42 - hasnt_pk( table, description ) pass should have the proper diagnostics +ok 43 - col_is_pk( schema, table, column, description ) should pass +ok 44 - col_is_pk( schema, table, column, description ) should have the proper description +ok 45 - col_is_pk( schema, table, column, description ) should have the proper diagnostics +ok 46 - col_is_pk( schema, table, column ) should pass +ok 47 - col_is_pk( schema, table, column ) should have the proper description +ok 48 - col_is_pk( schema, table, column ) should have the proper diagnostics +ok 49 - col_is_pk( table, column, description ) should pass +ok 50 - col_is_pk( table, column, description ) should have the proper description +ok 51 - col_is_pk( table, column, description ) should have the proper diagnostics +ok 52 - col_is_pk( table, column ) should pass +ok 53 - col_is_pk( table, column ) should have the proper description +ok 54 - col_is_pk( table, column ) should have the proper diagnostics +ok 55 - col_is_pk( schema, table, column, description ) fail should fail +ok 56 - col_is_pk( schema, table, column, description ) fail should have the proper description +ok 57 - col_is_pk( schema, table, column, description ) fail should have the proper diagnostics +ok 58 - col_is_pk( table, column, description ) fail should fail +ok 59 - col_is_pk( table, column, description ) fail should have the proper description +ok 60 - col_is_pk( table, column, description ) fail should have the proper diagnostics +ok 61 - col_is_pk( schema, table, column[], description ) should pass +ok 62 - col_is_pk( schema, table, column[], description ) should have the proper description +ok 63 - col_is_pk( schema, table, column[], description ) should have the proper diagnostics +ok 64 - col_is_pk( schema, table, column[] ) should pass +ok 65 - col_is_pk( schema, table, column[] ) should have the proper description +ok 66 - col_is_pk( schema, table, column[] ) should have the proper diagnostics +ok 67 - col_is_pk( table, column[], description ) should pass +ok 68 - col_is_pk( table, column[], description ) should have the proper description +ok 69 - col_is_pk( table, column[], description ) should have the proper diagnostics +ok 70 - col_is_pk( table, column[] ) should pass +ok 71 - col_is_pk( table, column[] ) should have the proper description +ok 72 - col_is_pk( table, column[] ) should have the proper diagnostics +ok 73 - col_isnt_pk( schema, table, column, description ) should fail +ok 74 - col_isnt_pk( schema, table, column, description ) should have the proper description +ok 75 - col_isnt_pk( schema, table, column, description ) should have the proper diagnostics +ok 76 - col_isnt_pk( table, column, description ) should fail +ok 77 - col_isnt_pk( table, column, description ) should have the proper description +ok 78 - col_isnt_pk( table, column, description ) should have the proper diagnostics +ok 79 - col_isnt_pk( table, column ) should fail +ok 80 - col_isnt_pk( table, column ) should have the proper description +ok 81 - col_isnt_pk( table, column ) should have the proper diagnostics +ok 82 - col_isnt_pk( schema, table, column, description ) pass should pass +ok 83 - col_isnt_pk( schema, table, column, description ) pass should have the proper description +ok 84 - col_isnt_pk( schema, table, column, description ) pass should have the proper diagnostics +ok 85 - col_isnt_pk( table, column, description ) pass should pass +ok 86 - col_isnt_pk( table, column, description ) pass should have the proper description +ok 87 - col_isnt_pk( table, column, description ) pass should have the proper diagnostics +ok 88 - col_isnt_pk( schema, table, column[], description ) should pass +ok 89 - col_isnt_pk( schema, table, column[], description ) should have the proper description +ok 90 - col_isnt_pk( schema, table, column[], description ) should have the proper diagnostics +ok 91 - col_isnt_pk( table, column[], description ) should pass +ok 92 - col_isnt_pk( table, column[], description ) should have the proper description +ok 93 - col_isnt_pk( table, column[], description ) should have the proper diagnostics +ok 94 - col_isnt_pk( table, column[] ) should pass +ok 95 - col_isnt_pk( table, column[] ) should have the proper description +ok 96 - col_isnt_pk( table, column[] ) should have the proper diagnostics diff --git a/test/sql/pktap.sql b/test/sql/pktap.sql index 23ee6d4bf05d..e361f7dd3c7a 100644 --- a/test/sql/pktap.sql +++ b/test/sql/pktap.sql @@ -1,7 +1,8 @@ \unset ECHO \i test/setup.sql +-- \i sql/pgtap.sql -SELECT plan(90); +SELECT plan(96); --SELECT * FROM no_plan(); -- This will be rolled back. :-) @@ -37,6 +38,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + has_pk( 'public', 'sometab'::name ), + true, + 'has_pk( schema, table )', + 'Table public.sometab should have a primary key', + '' +); + SELECT * FROM check_test( has_pk( 'hide', 'hidesometab', 'hide.sometab should have a pk' ), true, @@ -45,6 +54,14 @@ SELECT * FROM check_test( '' ); +SELECT * FROM check_test( + has_pk( 'hide', 'hidesometab'::name ), + true, + 'has_pk( hideschema, hidetable )', + 'Table hide.hidesometab should have a primary key', + '' +); + SELECT * FROM check_test( has_pk( 'sometab', 'sometab should have a pk' ), true, From e94465b2243821c1677b992db8d5e55608ba5d73 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 24 Sep 2023 11:12:56 -0400 Subject: [PATCH 1153/1195] Revamp results target Teach it to always finish (closes #220), document it should be run on the latest stable version of Postgres, and note that alternate output files will not be updated. Swap `runjusttests.out` alternates so the output from Postgres 16 is in the default file. Also update `.gitignore` to ignore more compiler-generated files (it was not ignoring `pgtap.o`). --- .gitignore | 11 ++++++++--- Makefile | 13 ++++++------- test/expected/runjusttests.out | 1 + test/expected/runjusttests_5.out | 1 - 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 249e89fb29cb..9a8dd1e0d1d0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,15 +6,20 @@ pgtap-core.sql pgtap-schema.sql uninstall_pgtap.sql results -pgtap.so -pgtap.dylib -pgtap.dll regression.* *.html *.html1 *.sql.orig bbin +# Compiler-generated files (borrowed from +# https://github.com/github/gitignore/blob/main/C.gitignore) +*.o +*.dll +*.so +*.so.* +*.dylib + /sql/pgtap--?.??.?.sql /sql/pgtap--?.?.?.sql /sql/pgtap-core--* diff --git a/Makefile b/Makefile index ab00b403ac78..41e09611db31 100644 --- a/Makefile +++ b/Makefile @@ -490,15 +490,14 @@ latest-changes.md: Changes # # STOLEN FROM pgxntool # - -# TARGET results: runs `make test` and copies all result files to test/expected/. DO NOT RUN THIS UNLESS YOU'RE CERTAIN ALL YOUR TESTS ARE PASSING! +# TARGET results: runs `make test` and copies all result files to +# test/expected/. Use for basic test changes with the latest version of +# Postgres, but be aware that alternate `_n.out` files will not be updated. +# DO NOT RUN THIS UNLESS YOU'RE CERTAIN ALL YOUR TESTS ARE PASSING! .PHONY: results -results: installcheck result-rsync - -.PHONY: -result-rsync: +results: + $(MAKE) installcheck || true rsync -rlpgovP results/ test/expected - # To use this, do make print-VARIABLE_NAME print-% : ; $(info $* is $(flavor $*) variable set to "$($*)") @true diff --git a/test/expected/runjusttests.out b/test/expected/runjusttests.out index 89f6cc0cc9c1..8eece0a16d75 100644 --- a/test/expected/runjusttests.out +++ b/test/expected/runjusttests.out @@ -25,6 +25,7 @@ ok 3 - whatever.testplpgsql # CONSTRAINT: CONSTRAINT # TYPE: TYPE # CONTEXT: + # PL/pgSQL function __die() line 3 at RAISE # SQL statement "SELECT __die();" # PL/pgSQL function whatever.testplpgsqldie() line 43 at EXECUTE # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement diff --git a/test/expected/runjusttests_5.out b/test/expected/runjusttests_5.out index 8eece0a16d75..89f6cc0cc9c1 100644 --- a/test/expected/runjusttests_5.out +++ b/test/expected/runjusttests_5.out @@ -25,7 +25,6 @@ ok 3 - whatever.testplpgsql # CONSTRAINT: CONSTRAINT # TYPE: TYPE # CONTEXT: - # PL/pgSQL function __die() line 3 at RAISE # SQL statement "SELECT __die();" # PL/pgSQL function whatever.testplpgsqldie() line 43 at EXECUTE # PL/pgSQL function _runner(text[],text[],text[],text[],text[]) line 62 at FOR over EXECUTE statement From bcecdab224162c6c6cbf9b6ce1953b8ec52db994 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 24 Sep 2023 11:28:02 -0400 Subject: [PATCH 1154/1195] Timestamp v1.3.1 --- Changes | 2 +- contrib/pgtap.spec | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index b67aeb5d4721..4a1d7b3a31db 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -1.3.1 +1.3.1 2023-09-24T15:29:42Z -------------------------- * Revamped the handling of data type declarations in `col_type_is()` to always diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index c19fb1afcf19..e3b3e5112937 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -41,9 +41,12 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog -* Mon Aug 14 2023 David E. Wheeler 1.3.1-1 +* Sun Sep 24 2023 David E. Wheeler 1.3.1-1 - Update to 1.3.1 +* Mon Aug 14 2023 David E. Wheeler 1.3.0-1 +- Update to 1.3.0 + * Sun Dec 5 2021 David E. Wheeler 1.2.0-1 - Update to 1.2.0 From e14d800ee9e2f033efb787384eac8ce9b2fd215e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 24 Sep 2023 11:38:15 -0400 Subject: [PATCH 1155/1195] Increment to v1.3.2 --- Changes | 3 +++ META.json | 8 ++++---- README.md | 2 +- contrib/pgtap.spec | 2 +- doc/pgtap.mmd | 2 +- pgtap.control | 2 +- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Changes b/Changes index 4a1d7b3a31db..958cd49c4c3f 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Revision history for pgTAP ========================== +1.3.2 +-------------------------- + 1.3.1 2023-09-24T15:29:42Z -------------------------- diff --git a/META.json b/META.json index bc47195a9eb9..82633609920f 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "1.3.1", + "version": "1.3.2", "maintainer": [ "David E. Wheeler ", "pgTAP List " @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "1.3.1" + "version": "1.3.2" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "1.3.1" + "version": "1.3.2" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "1.3.1" + "version": "1.3.2" } }, "resources": { diff --git a/README.md b/README.md index 9b161314f325..0da4546b0a91 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 1.3.1 +pgTAP 1.3.2 ============ [pgTAP](https://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index e3b3e5112937..8d04d9be0fb0 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 1.3.1 +Version: 1.3.2 Release: 1%{?dist} Group: Applications/Databases License: PostgreSQL diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 0d4cf7b1eb92..8bbad0fd264a 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 1.3.1 +pgTAP 1.3.2 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/pgtap.control b/pgtap.control index e0b8b56c70b7..808219fad925 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '1.3.1' +default_version = '1.3.2' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true From 6fdae2bda7e4122f95d5d2265b67f613f31d1de6 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 24 Sep 2023 11:55:03 -0400 Subject: [PATCH 1156/1195] Fix latest-changes.md make target --- Changes | 1 - Makefile | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Changes b/Changes index 958cd49c4c3f..c60b8b132689 100644 --- a/Changes +++ b/Changes @@ -6,7 +6,6 @@ Revision history for pgTAP 1.3.1 2023-09-24T15:29:42Z -------------------------- - * Revamped the handling of data type declarations in `col_type_is()` to always accurately normalize data type representations, whether using aliases, such as `varchar` for `character varying`, or more complicated types that failed diff --git a/Makefile b/Makefile index 41e09611db31..b8538d0d36ac 100644 --- a/Makefile +++ b/Makefile @@ -485,7 +485,7 @@ updatecheck_setup: updatecheck_deps updatecheck_run: updatecheck_setup installcheck latest-changes.md: Changes - perl -e 'while (<>) {last if /^(v?\Q${DISTVERSION}\E)/; } print "Changes for v${DISTVERSION}:\n"; while (<>) { last if /^\s*$$/; s/^\s+//; print }' Changes > $@ + perl -e 'while (<>) {last if /^(v?\Q${DISTVERSION}\E)/; } print "Changes for v${DISTVERSION}\n"; while (<>) { last if /^\s*$$/; s/\s+$$//; if (/^\s*[*]/) { print "\n" } else { s/^\s+/ / } print } print "\n"' $< > $@ # # STOLEN FROM pgxntool From 83b45855ccd494977202fb1fc8b13018c208bac2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 24 Sep 2023 12:14:19 -0400 Subject: [PATCH 1157/1195] Rename master branch to main And remove all references to master (even unrelated ones). Everyone with a local clone will need to update it; [instructions](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-branches-in-your-repository/renaming-a-branch#updating-a-local-clone-after-a-branch-name-changes): ``` sh git branch -m master main git fetch origin git branch -u origin/main main git remote set-head origin -a ``` --- Makefile | 2 +- contrib/pgtap.spec | 2 +- release.md | 2 +- tools/util.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index b8538d0d36ac..a8b4eaedc4ee 100644 --- a/Makefile +++ b/Makefile @@ -470,7 +470,7 @@ updatecheck_deps: pgtap-version-$(UPDATE_FROM) test/sql/update.sql # pg_regress --launcher not supported prior to 9.1 # There are some other failures in 9.1 and 9.2 (see https://travis-ci.org/decibel/pgtap/builds/358206497). # TODO: find something that can generically compare majors (ie: GE91 from -# https://github.com/decibel/pgxntool/blob/master/base.mk). +# https://github.com/decibel/pgxntool/blob/0.1.10/base.mk). updatecheck_setup: updatecheck_deps @if echo $(VERSION) | grep -qE "8[.]|9[.][012]"; then echo "updatecheck is not supported prior to 9.3"; exit 1; fi $(eval SETUP_SCH = test/schedule/update.sch) diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 8d04d9be0fb0..aa5cd66748d9 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -5,7 +5,7 @@ Release: 1%{?dist} Group: Applications/Databases License: PostgreSQL URL: https://pgtap.org/ -Source0: https://master.pgxn.org/dist/pgtap/%{version}/pgtap-%{version}.zip +Source0: https://api.pgxn.org/dist/pgtap/%{version}/pgtap-%{version}.zip BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root Provides: %{name} Provides: %{name}-core diff --git a/release.md b/release.md index a56597c87329..5b902c45d3de 100644 --- a/release.md +++ b/release.md @@ -75,7 +75,7 @@ Here are the steps to take to make a release of pgTAP: + Commit the changes, but don't push them yet. -* Go back to the `master` branch and proofread the additions to the `Changes` +* Go back to the `main` branch and proofread the additions to the `Changes` file since the last release. Make sure all relevant changes are recorded there, and that any typos or formatting errors are corrected. diff --git a/tools/util.sh b/tools/util.sh index 39e83381916e..8107c42805e8 100644 --- a/tools/util.sh +++ b/tools/util.sh @@ -1,4 +1,4 @@ -# Adapted from https://github.com/decibel/db_tools/blob/master/lib/util.sh +# Adapted from https://github.com/decibel/db_tools/blob/0.1.10/lib/util.sh ME=`basename $0` From c47f86226229b8c1b2281db8543c884586fb50b1 Mon Sep 17 00:00:00 2001 From: cstork Date: Mon, 2 Oct 2023 20:50:20 +0200 Subject: [PATCH 1158/1195] Update pgtap.mmd: minor corrections --- doc/pgtap.mmd | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 8bbad0fd264a..75cecde3e519 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -8034,7 +8034,7 @@ Which would produce: # As a result, some tests may fail. YMMV. You can pass data of any type to `diag()` and it will all be converted to text -for the diagnostics. You can als pass any number of arguments (as long as they +for the diagnostics. You can also pass any number of arguments (as long as they are all the same data type) and they will be concatenated together. Conditional Tests @@ -8605,9 +8605,6 @@ handy-dandy test function! **Parameters** -`:schema` -: Name of a schema containing pgTAP test functions. - `:test_output` : The output from your test. Usually it's just returned by a call to the test function itself. Required. From 1ace1532632eb82f5b2d38704aebad2eb79e3fa2 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 3 Feb 2024 17:11:14 -0500 Subject: [PATCH 1159/1195] Replace C parse_type() function with PL/pgSQL The C code cause a number of challenges, including the requirement for a superuser to install the extension, and would require a fair bit of refactoring to abide by [secure design principals]. It also makes installation difficult on Windows, and would likely be rejected by organizations like AWS that tend to balk at C code. So delete the `parse_type()` function and all the C code added in 4ec32e7 and rewrite the `format_type_string()` function based on the prototype that @ewie developed in the comments of #315. By looking up the type output format function in the catalog, we're able to correctly output the data type with its full SQL name as before. The exception is interval types, which require the PostgreSQL grammar to parse the interval fields (`second`, `interval minute`, etc.) into a bitmask for the `typmod`. So require that they be specified exactly as PostgreSQL outputs them. Make adjustments to the patches to eliminate hunk offset messages due to the changes to the SQL code, and upgrade `actions/checkout` to v4 to eliminate warnings in the workflows. [secure design principals]: https://www.postgresql.org/docs/current/extend-extensions.html#EXTEND-EXTENSIONS-SECURITY --- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 2 +- Changes | 8 ++++ Makefile | 1 - compat/install-9.1.patch | 8 ++-- compat/install-9.2.patch | 4 +- compat/install-9.4.patch | 6 +-- compat/install-9.6.patch | 2 +- doc/pgtap.mmd | 39 +++++---------- sql/pgtap--1.3.1--1.3.2.sql | 34 +++++++++++++ sql/pgtap.sql.in | 33 ++++++++++--- src/pgtap.c | 90 ----------------------------------- 12 files changed, 92 insertions(+), 137 deletions(-) create mode 100644 sql/pgtap--1.3.1--1.3.2.sql delete mode 100644 src/pgtap.c diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 17fe9ba8fc28..d11daf044dc5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: PGXN_PASSWORD: ${{ secrets.PGXN_PASSWORD }} steps: - name: Check out the repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Bundle the Release id: bundle run: pgxn-bundle diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5f69c998f1c3..3d71ff5602f5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: UPDATE_FROM: "${{ matrix.update_from }}" steps: - run: pg-start ${{ matrix.version }} - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Basic regression test. - run: pg-build-test diff --git a/Changes b/Changes index c60b8b132689..cd4579728490 100644 --- a/Changes +++ b/Changes @@ -4,6 +4,14 @@ Revision history for pgTAP 1.3.2 -------------------------- +* Replaced the C `parse_type()` function added in v1.3.1 with a PL/pgSQL + function, eliminating the need for the the installing use to be a superuser. + Aliases still work for all data types passed to `col_type_is()` except for + intervals, which must be specified exactly as rendered by Postgres itself, + without aliasing. Thanks to @spencerbryson for the report (#328) and to + Erik Wienhold for the PL/pgSQL technique necessary to properly format + type strings (#315). + 1.3.1 2023-09-24T15:29:42Z -------------------------- * Revamped the handling of data type declarations in `col_type_is()` to always diff --git a/Makefile b/Makefile index a8b4eaedc4ee..f255fe6a9b7a 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,6 @@ EXTRA_CLEAN = $(VERSION_FILES) sql/pgtap.sql sql/uninstall_pgtap.sql sql/pgtap- EXTRA_CLEAN += $(wildcard sql/*.orig) # These are files left behind by patch DOCS = doc/pgtap.mmd PG_CONFIG ?= pg_config -MODULES = src/pgtap # # Test configuration. This must be done BEFORE including PGXS diff --git a/compat/install-9.1.patch b/compat/install-9.1.patch index 633afc9da3b6..5196c6ea6a41 100644 --- a/compat/install-9.1.patch +++ b/compat/install-9.1.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -786,10 +786,6 @@ +@@ -781,10 +781,6 @@ RETURN ok( TRUE, descr ); EXCEPTION WHEN OTHERS THEN -- There should have been no exception. @@ -11,7 +11,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6667,10 +6663,6 @@ +@@ -6688,10 +6684,6 @@ -- Something went wrong. Record that fact. errstate := SQLSTATE; errmsg := SQLERRM; @@ -22,7 +22,7 @@ END; -- Always raise an exception to rollback any changes. -@@ -7138,7 +7130,6 @@ +@@ -7159,7 +7151,6 @@ RETURN ok( true, $3 ); EXCEPTION WHEN datatype_mismatch THEN @@ -30,7 +30,7 @@ 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' || -@@ -7292,7 +7283,6 @@ +@@ -7313,7 +7304,6 @@ RETURN ok( false, $3 ); EXCEPTION WHEN datatype_mismatch THEN diff --git a/compat/install-9.2.patch b/compat/install-9.2.patch index 715285a00e08..38e897479a40 100644 --- a/compat/install-9.2.patch +++ b/compat/install-9.2.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -789,12 +789,7 @@ +@@ -784,12 +784,7 @@ GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, hint = PG_EXCEPTION_HINT, @@ -14,7 +14,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6675,12 +6670,7 @@ +@@ -6696,12 +6691,7 @@ GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, hint = PG_EXCEPTION_HINT, diff --git a/compat/install-9.4.patch b/compat/install-9.4.patch index 483d20a3677f..758bb6ed98b1 100644 --- a/compat/install-9.4.patch +++ b/compat/install-9.4.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -680,7 +680,7 @@ +@@ -675,7 +675,7 @@ ' caught: no exception' || E'\n wanted: ' || COALESCE( errcode, 'an exception' ) ); @@ -9,7 +9,7 @@ IF (errcode IS NULL OR SQLSTATE = errcode) AND ( errmsg IS NULL OR SQLERRM = errmsg) THEN -@@ -784,7 +784,7 @@ +@@ -779,7 +779,7 @@ BEGIN EXECUTE code; RETURN ok( TRUE, descr ); @@ -18,7 +18,7 @@ -- There should have been no exception. GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, -@@ -10219,233 +10219,6 @@ +@@ -10240,233 +10240,6 @@ ), $2); $$ LANGUAGE SQL immutable; diff --git a/compat/install-9.6.patch b/compat/install-9.6.patch index fd63ed8879ad..969c7c356c52 100644 --- a/compat/install-9.6.patch +++ b/compat/install-9.6.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -10201,136 +10201,6 @@ +@@ -10222,136 +10222,6 @@ ); $$ LANGUAGE sql; diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 75cecde3e519..bce5cb9ed08a 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4524,6 +4524,11 @@ pass the type as either "varchar(64)" or "character varying(64)". Example: SELECT col_type_is( 'myschema', 'sometable', 'somecolumn', 'timespantz(3)' ); +The exception to this rule is interval types, which must be specified as +rendered by PostgreSQL itself: + + SELECT col_type_is( 'myschema', 'sometable', 'somecolumn', 'interval second(3)' ); + Types with case-sensitive names or special characters must be double-quoted: SELECT col_type_is( 'myschema', 'sometable', 'somecolumn', '"myType"' ); @@ -8293,31 +8298,6 @@ included in the display. For example: Used internally by pgTAP to compare operators, but may be more generally useful. -### `parse_type()` ### - - SELECT * FROM parse_type( :text ); - -**Parameters** - -`:text` -: An SQL type declaration, optionally schema-qualified. - -Parses a string representing an SQL type declaration as used in a `CREATE TABLE` -statement, optionally schema-qualified. Returns a record with two fields, -`typid` and `typmod`, representing the OID and modifier for the type. These are -the underlying values that define column data types, and which can be passed to -the PostgreSQL core -[`format_type()`](https://www.postgresql.org/docs/current/functions-info.html#FUNCTIONS-INFO-CATALOG) -function to display the normalized string representation of the data type. -Raises an error if the specify type does not exist or cannot be found in the -search path. - - try=% SELECT format_type(p.typid, p.typmod) - try-% FROM parse_type('timestamp(4)') p; - format_type - -------------------------------- - timestamp(4) without time zone - ### `format_type_string()` ### SELECT format_type_string( :text ); @@ -8329,9 +8309,12 @@ search path. This function normalizes data type declarations for accurate comparison to table columns by `col_type_is()`. It's effectively the identical to -the calling `format_type()` with the values returned by `parse_type()`, -but returns a `NULL` on an invalid or missing type, rather than raising -an error. +the calling `format_type()` with the type OID and type modifier that define +the column, but returns a `NULL` on an invalid or missing type, rather than +raising an error. Types can be defined by their canonical names or their +aliases, e.g., `character varying` or `varchar`. The exception is `interval` +types, which must be specified exactly as Postgres renders them internally, +e.g., `'interval(0)`, `interval second(0)`, or `interval day to second(4)`. try=# SELECT format_type_string('timestamp(3)'); format_type_string diff --git a/sql/pgtap--1.3.1--1.3.2.sql b/sql/pgtap--1.3.1--1.3.2.sql new file mode 100644 index 000000000000..d0dbd80cb9a0 --- /dev/null +++ b/sql/pgtap--1.3.1--1.3.2.sql @@ -0,0 +1,34 @@ +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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index 0e873948efc4..e245cbfe2cb4 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -22,11 +22,6 @@ CREATE OR REPLACE FUNCTION pgtap_version() RETURNS NUMERIC AS 'SELECT __VERSION__;' LANGUAGE SQL IMMUTABLE; -CREATE FUNCTION parse_type(type text, OUT typid oid, OUT typmod int4) -RETURNS RECORD -AS '$libdir/pgtap' -LANGUAGE C STABLE STRICT; - CREATE OR REPLACE FUNCTION plan( integer ) RETURNS TEXT AS $$ DECLARE @@ -1468,7 +1463,33 @@ $$ LANGUAGE PLPGSQL STABLE; CREATE OR REPLACE FUNCTION format_type_string ( TEXT ) RETURNS TEXT AS $$ -BEGIN RETURN format_type(p.typid, p.typmod) from parse_type($1) p; +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; diff --git a/src/pgtap.c b/src/pgtap.c deleted file mode 100644 index aa92d5ba484a..000000000000 --- a/src/pgtap.c +++ /dev/null @@ -1,90 +0,0 @@ -/* - * PostgreSQL utility functions for pgTAP. - */ - -#include "postgres.h" -#include "fmgr.h" -#include "funcapi.h" /* for returning composite type */ -#include "utils/builtins.h" /* text_to_cstring() */ -#include "parser/parse_type.h" /* parseTypeString() */ - - -#if PG_VERSION_NUM < 90300 -#include "access/htup.h" /* heap_form_tuple() */ -#elif PG_VERSION_NUM < 130000 -#include "access/htup_details.h" /* heap_form_tuple() */ -#endif - -#ifdef PG_MODULE_MAGIC -PG_MODULE_MAGIC; -#endif - - -/* - * Given a string that is supposed to be a SQL-compatible type declaration, - * such as "int4" or "integer" or "character varying(32)", parse - * the string and convert it to a type OID and type modifier. - * - * Raises an error on an invalid type. - */ - -/* - * parse_type() is the inverse of pg_catalog.format_type(): it takes a string - * representing an SQL-compatible type declaration, such as "int4" or "integer" - * or "character varying(32)", parses it, and returns the OID and type modifier. - * Returns NULL for an invalid type. - * - * Internally it relies on the Postgres core parseTypeString() function defined - * in src/backend/parser/parse_type.c. - */ -Datum parse_type(PG_FUNCTION_ARGS); - -PG_FUNCTION_INFO_V1(parse_type); - -Datum -parse_type(PG_FUNCTION_ARGS) -{ -#define PARSE_TYPE_STRING_COLS 2 /* Returns two columns. */ - const char *type; /* the type string we want to resolve */ - Oid typid; /* the resolved type oid */ - int32 typmod; /* the resolved type modifier */ - TupleDesc tupdesc; - HeapTuple rettuple; - Datum values[PARSE_TYPE_STRING_COLS] = {0}; - bool nulls[PARSE_TYPE_STRING_COLS] = {0}; - - type = text_to_cstring(PG_GETARG_TEXT_PP(0)); - - /* - * Build a tuple descriptor for our result type; return an error if not - * called in a context that expects a record. - */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) { - ereport( - ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("function returning record called in context that cannot accept type record")) - ); - } - - BlessTupleDesc(tupdesc); - - /* - * Parse type-name argument to obtain type OID and encoded typmod. We don't - * need to check for parseTypeString failure, but just let the error be - * raised. The 0 arg works both as the `Node *escontext` arg in Postgres 16 - * and the `bool missing_ok` arg in 9.4-15. - */ -#if PG_VERSION_NUM < 90400 - (void) parseTypeString(type, &typid, &typmod); -#else - (void) parseTypeString(type, &typid, &typmod, 0); -#endif - - /* Create and return tuple. */ - values[0] = typid; - values[1] = typmod; - rettuple = heap_form_tuple(tupdesc, values, nulls); - return HeapTupleGetDatum(rettuple); -#undef PARSE_TYPE_STRING_COLS -} From 2bfb0108c5151530426448311cce509ff985735c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 3 Feb 2024 19:11:27 -0500 Subject: [PATCH 1160/1195] Test and document proc with function assertions Add tests for all of the function assertion test functions to ensure that they also work as expected with procedures. Document that support where appropriate. Closes #329. --- Changes | 3 + doc/pgtap.mmd | 44 +- test/expected/functap.out | 2002 +++++++++++++++++++------------------ test/sql/aretap.sql | 24 +- test/sql/functap.sql | 282 +++++- 5 files changed, 1376 insertions(+), 979 deletions(-) diff --git a/Changes b/Changes index cd4579728490..5c1af86aa45b 100644 --- a/Changes +++ b/Changes @@ -11,6 +11,9 @@ Revision history for pgTAP without aliasing. Thanks to @spencerbryson for the report (#328) and to Erik Wienhold for the PL/pgSQL technique necessary to properly format type strings (#315). +* Added tests to ensure that that the function-testing assertions also support + procedures and noted the fact where appropriate in the documentation. + Thanks to @hettie-d for the call-out (#329)! 1.3.1 2023-09-24T15:29:42Z -------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index bce5cb9ed08a..592992ba91b8 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -2255,16 +2255,16 @@ missing triggers, like so: : Name of a schema in which to find functions. `:functions` -: An array of function names. +: An array of function and/or procedure names. `:description` : A short description of the test. -This function tests that all of the functions in the named schema, or that are -visible in the search path, are only the functions that *should* be there. If -the `:schema` argument is omitted, functions will be sought in the search -path, excluding `pg_catalog` and `information_schema` If the description is -omitted, a generally useful default description will be generated. Example: +This function tests that all of the functions or procedures in the named schema, +or that are visible in the search path, are only the functions that *should* be +there. If the `:schema` argument is omitted, functions will be sought in the +search path, excluding `pg_catalog` and `information_schema` If the description +is omitted, a generally useful default description will be generated. Example: SELECT functions_are( 'myschema', @@ -3727,7 +3727,7 @@ rule does *not* exist. : Name of a schema in which to find the function. `:function` -: Name of a function. +: Name of a function or procedure. `:args` : Array of data types of the function arguments. @@ -3735,11 +3735,11 @@ rule does *not* exist. `:description` : A short description of the test. -Checks to be sure that the given function exists in the named schema and with -the specified argument data types. If `:schema` is omitted, `has_function()` -will search for the function in the schemas defined in the search path. If -`:args` is omitted, `has_function()` will see if the function exists without -regard to its arguments. Some examples: +Checks to be sure that the given function or procedure exists in the named +schema and with the specified argument data types. If `:schema` is omitted, +`has_function()` will search for the function in the schemas defined in the +search path. If `:args` is omitted, `has_function()` will see if the function +exists without regard to its arguments. Some examples: SELECT has_function( 'pg_catalog', @@ -3784,7 +3784,7 @@ future version of pgTAP. : Name of a schema in which not to find the function. `:function` -: Name of a function. +: Name of a function or procedure. `:args` : Array of data types of the function arguments. @@ -3793,7 +3793,8 @@ future version of pgTAP. : A short description of the test. This function is the inverse of `has_function()`. The test passes if the -specified function (optionally with the specified signature) does *not* exist. +specified function or procedure (optionally with the specified signature) does +*not* exist. ### `has_cast()` ### @@ -5349,7 +5350,7 @@ future. : Schema in which to find the functions. `:functions` -: Array of function names. +: Array of function and/or procedure names. `:description` : A short description of the test. @@ -5474,6 +5475,10 @@ signature will be checked (pass an empty array to specify a function with an empty signature). If the `:description` is omitted, a reasonable substitute will be created. +Procedures can also be tested; they always return `void`: + + SELECT function_returns( 'my_proc', 'void' ); + In the event of a failure, you'll useful diagnostics will tell you what went wrong, for example: @@ -5505,7 +5510,7 @@ But then you check with `has_function()` first, right? : Schema in which to find the function. `:function` -: Function name. +: Function or proceudure name. `:args` : Array of data types for the function arguments. @@ -5513,7 +5518,7 @@ But then you check with `has_function()` first, right? `:description` : A short description of the test. -Tests that a function is a security definer (i.e., a "setuid" function). If +Tests that a function or procedure is a security definer (i.e., a "setuid" function). If the `:schema` argument is omitted, then the function must be visible in the search path. If the `:args` argument is passed, then the function with that argument signature will be the one tested; otherwise, a function with any @@ -5550,7 +5555,7 @@ But then you check with `has_function()` first, right? : Schema in which to find the function. `:function` -: Function name. +: Function or proceure name. `:args` : Array of data types for the function arguments. @@ -5558,9 +5563,8 @@ But then you check with `has_function()` first, right? `:description` : A short description of the test. - This function is the inverse of `is_definer()`. The test passes if the specified -function is not a security definer. +function or procedure is not a security definer. If the function does not exist, a handy diagnostic message will let you know: diff --git a/test/expected/functap.out b/test/expected/functap.out index 8d9f7469b0e4..53ce62c10c25 100644 --- a/test/expected/functap.out +++ b/test/expected/functap.out @@ -1,5 +1,5 @@ \unset ECHO -1..1009 +1..1105 ok 1 - simple function should pass ok 2 - simple function should have the proper description ok 3 - simple function should have the proper diagnostics @@ -54,958 +54,1054 @@ ok 51 - custom qualified function with intword qualified argument should have th ok 52 - failure output should fail ok 53 - failure output should have the proper description ok 54 - failure output should have the proper diagnostics -ok 55 - simple function should fail -ok 56 - simple function should have the proper description -ok 57 - simple function should have the proper diagnostics -ok 58 - simple schema.function should fail -ok 59 - simple schema.function should have the proper description -ok 60 - simple schema.function should have the proper diagnostics -ok 61 - simple function desc should fail -ok 62 - simple function desc should have the proper description -ok 63 - simple function desc should have the proper diagnostics -ok 64 - simple with 0 args should fail -ok 65 - simple with 0 args should have the proper description -ok 66 - simple with 0 args should have the proper diagnostics -ok 67 - simple with 0 args desc should fail -ok 68 - simple with 0 args desc should have the proper description -ok 69 - simple with 0 args desc should have the proper diagnostics -ok 70 - simple schema.func with 0 args should fail -ok 71 - simple schema.func with 0 args should have the proper description -ok 72 - simple schema.func with 0 args should have the proper diagnostics -ok 73 - simple schema.func with desc should fail -ok 74 - simple schema.func with desc should have the proper description -ok 75 - simple schema.func with desc should have the proper diagnostics -ok 76 - simple schema.func with 0 args, desc should fail -ok 77 - simple schema.func with 0 args, desc should have the proper description -ok 78 - simple schema.func with 0 args, desc should have the proper diagnostics -ok 79 - simple function with 1 arg should fail -ok 80 - simple function with 1 arg should have the proper description -ok 81 - simple function with 1 arg should have the proper diagnostics -ok 82 - simple function with 2 args should fail -ok 83 - simple function with 2 args should have the proper description -ok 84 - simple function with 2 args should have the proper diagnostics -ok 85 - simple array function should fail -ok 86 - simple array function should have the proper description -ok 87 - simple array function should have the proper diagnostics -ok 88 - custom array function should fail -ok 89 - custom array function should have the proper description -ok 90 - custom array function should have the proper diagnostics -ok 91 - custom numeric function should fail -ok 92 - custom numeric function should have the proper description -ok 93 - custom numeric function should have the proper diagnostics -ok 94 - can(schema) with desc should pass -ok 95 - can(schema) with desc should have the proper description -ok 96 - can(schema) with desc should have the proper diagnostics -ok 97 - can(schema) should pass -ok 98 - can(schema) should have the proper description -ok 99 - can(schema) should have the proper diagnostics -ok 100 - fail can(schema) with desc should fail -ok 101 - fail can(schema) with desc should have the proper description -ok 102 - fail can(schema) with desc should have the proper diagnostics -ok 103 - fail can(someschema) with desc should fail -ok 104 - fail can(someschema) with desc should have the proper description -ok 105 - fail can(someschema) with desc should have the proper diagnostics -ok 106 - can() with desc should pass -ok 107 - can() with desc should have the proper description -ok 108 - can() with desc should have the proper diagnostics +ok 55 - public procedure should pass +ok 56 - public procedure should have the proper description +ok 57 - public procedure should have the proper diagnostics +ok 58 - public procedure should pass +ok 59 - public procedure should have the proper description +ok 60 - public procedure should have the proper diagnostics +ok 61 - simple function should fail +ok 62 - simple function should have the proper description +ok 63 - simple function should have the proper diagnostics +ok 64 - simple schema.function should fail +ok 65 - simple schema.function should have the proper description +ok 66 - simple schema.function should have the proper diagnostics +ok 67 - simple function desc should fail +ok 68 - simple function desc should have the proper description +ok 69 - simple function desc should have the proper diagnostics +ok 70 - simple with 0 args should fail +ok 71 - simple with 0 args should have the proper description +ok 72 - simple with 0 args should have the proper diagnostics +ok 73 - simple with 0 args desc should fail +ok 74 - simple with 0 args desc should have the proper description +ok 75 - simple with 0 args desc should have the proper diagnostics +ok 76 - simple schema.func with 0 args should fail +ok 77 - simple schema.func with 0 args should have the proper description +ok 78 - simple schema.func with 0 args should have the proper diagnostics +ok 79 - simple schema.func with desc should fail +ok 80 - simple schema.func with desc should have the proper description +ok 81 - simple schema.func with desc should have the proper diagnostics +ok 82 - simple schema.func with 0 args, desc should fail +ok 83 - simple schema.func with 0 args, desc should have the proper description +ok 84 - simple schema.func with 0 args, desc should have the proper diagnostics +ok 85 - simple function with 1 arg should fail +ok 86 - simple function with 1 arg should have the proper description +ok 87 - simple function with 1 arg should have the proper diagnostics +ok 88 - simple function with 2 args should fail +ok 89 - simple function with 2 args should have the proper description +ok 90 - simple function with 2 args should have the proper diagnostics +ok 91 - simple array function should fail +ok 92 - simple array function should have the proper description +ok 93 - simple array function should have the proper diagnostics +ok 94 - custom array function should fail +ok 95 - custom array function should have the proper description +ok 96 - custom array function should have the proper diagnostics +ok 97 - custom numeric function should fail +ok 98 - custom numeric function should have the proper description +ok 99 - custom numeric function should have the proper diagnostics +ok 100 - public procedure should fail +ok 101 - public procedure should have the proper description +ok 102 - public procedure should have the proper diagnostics +ok 103 - public procedure should fail +ok 104 - public procedure should have the proper description +ok 105 - public procedure should have the proper diagnostics +ok 106 - can(schema) with desc should pass +ok 107 - can(schema) with desc should have the proper description +ok 108 - can(schema) with desc should have the proper diagnostics ok 109 - can(schema) should pass ok 110 - can(schema) should have the proper description ok 111 - can(schema) should have the proper diagnostics -ok 112 - fail can() with desc should fail -ok 113 - fail can() with desc should have the proper description -ok 114 - fail can() with desc should have the proper diagnostics -ok 115 - function_lang_is(schema, func, 0 args, sql, desc) should pass -ok 116 - function_lang_is(schema, func, 0 args, sql, desc) should have the proper description -ok 117 - function_lang_is(schema, func, 0 args, sql, desc) should have the proper diagnostics -ok 118 - function_lang_is(schema, func, 0 args, sql) should pass -ok 119 - function_lang_is(schema, func, 0 args, sql) should have the proper description -ok 120 - function_lang_is(schema, func, 0 args, sql) should have the proper diagnostics -ok 121 - function_lang_is(schema, func, args, plpgsql, desc) should pass -ok 122 - function_lang_is(schema, func, args, plpgsql, desc) should have the proper description -ok 123 - function_lang_is(schema, func, args, plpgsql, desc) should have the proper diagnostics -ok 124 - function_lang_is(schema, func, args, plpgsql) should pass -ok 125 - function_lang_is(schema, func, args, plpgsql) should have the proper description -ok 126 - function_lang_is(schema, func, args, plpgsql) should have the proper diagnostics -ok 127 - function_lang_is(schema, func, 0 args, perl, desc) should fail -ok 128 - function_lang_is(schema, func, 0 args, perl, desc) should have the proper description -ok 129 - function_lang_is(schema, func, 0 args, perl, desc) should have the proper diagnostics -ok 130 - function_lang_is(schema, non-func, 0 args, sql, desc) should fail -ok 131 - function_lang_is(schema, non-func, 0 args, sql, desc) should have the proper description -ok 132 - function_lang_is(schema, non-func, 0 args, sql, desc) should have the proper diagnostics -ok 133 - function_lang_is(schema, func, args, plpgsql) should fail -ok 134 - function_lang_is(schema, func, args, plpgsql) should have the proper description -ok 135 - function_lang_is(schema, func, args, plpgsql) should have the proper diagnostics -ok 136 - function_lang_is(schema, func, sql, desc) should pass -ok 137 - function_lang_is(schema, func, sql, desc) should have the proper description -ok 138 - function_lang_is(schema, func, sql, desc) should have the proper diagnostics -ok 139 - function_lang_is(schema, func, sql) should pass -ok 140 - function_lang_is(schema, func, sql) should have the proper description -ok 141 - function_lang_is(schema, func, sql) should have the proper diagnostics -ok 142 - function_lang_is(schema, func, perl, desc) should fail -ok 143 - function_lang_is(schema, func, perl, desc) should have the proper description -ok 144 - function_lang_is(schema, func, perl, desc) should have the proper diagnostics -ok 145 - function_lang_is(schema, non-func, sql, desc) should fail -ok 146 - function_lang_is(schema, non-func, sql, desc) should have the proper description -ok 147 - function_lang_is(schema, non-func, sql, desc) should have the proper diagnostics -ok 148 - function_lang_is(func, 0 args, sql, desc) should pass -ok 149 - function_lang_is(func, 0 args, sql, desc) should have the proper description -ok 150 - function_lang_is(func, 0 args, sql, desc) should have the proper diagnostics -ok 151 - function_lang_is(func, 0 args, sql) should pass -ok 152 - function_lang_is(func, 0 args, sql) should have the proper description -ok 153 - function_lang_is(func, 0 args, sql) should have the proper diagnostics -ok 154 - function_lang_is(func, args, plpgsql, desc) should pass -ok 155 - function_lang_is(func, args, plpgsql, desc) should have the proper description -ok 156 - function_lang_is(func, args, plpgsql, desc) should have the proper diagnostics -ok 157 - function_lang_is(func, args, plpgsql) should pass -ok 158 - function_lang_is(func, args, plpgsql) should have the proper description -ok 159 - function_lang_is(func, args, plpgsql) should have the proper diagnostics -ok 160 - function_lang_is(func, 0 args, perl, desc) should fail -ok 161 - function_lang_is(func, 0 args, perl, desc) should have the proper description -ok 162 - function_lang_is(func, 0 args, perl, desc) should have the proper diagnostics -ok 163 - function_lang_is(non-func, 0 args, sql, desc) should fail -ok 164 - function_lang_is(non-func, 0 args, sql, desc) should have the proper description -ok 165 - function_lang_is(non-func, 0 args, sql, desc) should have the proper diagnostics -ok 166 - function_lang_is(func, args, plpgsql) should fail -ok 167 - function_lang_is(func, args, plpgsql) should have the proper description -ok 168 - function_lang_is(func, args, plpgsql) should have the proper diagnostics -ok 169 - function_lang_is(func, sql, desc) should pass -ok 170 - function_lang_is(func, sql, desc) should have the proper description -ok 171 - function_lang_is(func, sql, desc) should have the proper diagnostics -ok 172 - function_lang_is(func, sql) should pass -ok 173 - function_lang_is(func, sql) should have the proper description -ok 174 - function_lang_is(func, sql) should have the proper diagnostics -ok 175 - function_lang_is(func, perl, desc) should fail -ok 176 - function_lang_is(func, perl, desc) should have the proper description -ok 177 - function_lang_is(func, perl, desc) should have the proper diagnostics -ok 178 - function_lang_is(non-func, sql, desc) should fail -ok 179 - function_lang_is(non-func, sql, desc) should have the proper description -ok 180 - function_lang_is(non-func, sql, desc) should have the proper diagnostics -ok 181 - function_returns(schema, func, 0 args, bool, desc) should pass -ok 182 - function_returns(schema, func, 0 args, bool, desc) should have the proper description -ok 183 - function_returns(schema, func, 0 args, bool, desc) should have the proper diagnostics -ok 184 - function_returns(schema, func, 0 args, bool) should pass -ok 185 - function_returns(schema, func, 0 args, bool) should have the proper description -ok 186 - function_returns(schema, func, 0 args, bool) should have the proper diagnostics -ok 187 - function_returns(schema, func, args, bool, false) should pass -ok 188 - function_returns(schema, func, args, bool, false) should have the proper description -ok 189 - function_returns(schema, func, args, bool, false) should have the proper diagnostics -ok 190 - function_returns(schema, func, args, bool) should pass -ok 191 - function_returns(schema, func, args, bool) should have the proper description -ok 192 - function_returns(schema, func, args, bool) should have the proper diagnostics -ok 193 - function_returns(schema, func, 0 args, setof bool, desc) should pass -ok 194 - function_returns(schema, func, 0 args, setof bool, desc) should have the proper description -ok 195 - function_returns(schema, func, 0 args, setof bool, desc) should have the proper diagnostics -ok 196 - function_returns(schema, func, 0 args, setof bool) should pass -ok 197 - function_returns(schema, func, 0 args, setof bool) should have the proper description -ok 198 - function_returns(schema, func, 0 args, setof bool) should have the proper diagnostics -ok 199 - function_returns(schema, func, bool, desc) should pass -ok 200 - function_returns(schema, func, bool, desc) should have the proper description -ok 201 - function_returns(schema, func, bool, desc) should have the proper diagnostics -ok 202 - function_returns(schema, func, bool) should pass -ok 203 - function_returns(schema, func, bool) should have the proper description -ok 204 - function_returns(schema, func, bool) should have the proper diagnostics -ok 205 - function_returns(schema, other func, bool, false) should pass -ok 206 - function_returns(schema, other func, bool, false) should have the proper description -ok 207 - function_returns(schema, other func, bool, false) should have the proper diagnostics -ok 208 - function_returns(schema, other func, bool) should pass -ok 209 - function_returns(schema, other func, bool) should have the proper description -ok 210 - function_returns(schema, other func, bool) should have the proper diagnostics -ok 211 - function_returns(schema, func, setof bool, desc) should pass -ok 212 - function_returns(schema, func, setof bool, desc) should have the proper description -ok 213 - function_returns(schema, func, setof bool, desc) should have the proper diagnostics -ok 214 - function_returns(schema, func, setof bool) should pass -ok 215 - function_returns(schema, func, setof bool) should have the proper description -ok 216 - function_returns(schema, func, setof bool) should have the proper diagnostics -ok 217 - function_returns(func, 0 args, bool, desc) should pass -ok 218 - function_returns(func, 0 args, bool, desc) should have the proper description -ok 219 - function_returns(func, 0 args, bool, desc) should have the proper diagnostics -ok 220 - function_returns(func, 0 args, bool) should pass -ok 221 - function_returns(func, 0 args, bool) should have the proper description -ok 222 - function_returns(func, 0 args, bool) should have the proper diagnostics -ok 223 - function_returns(func, args, bool, false) should pass -ok 224 - function_returns(func, args, bool, false) should have the proper description -ok 225 - function_returns(func, args, bool, false) should have the proper diagnostics -ok 226 - function_returns(func, args, bool) should pass -ok 227 - function_returns(func, args, bool) should have the proper description -ok 228 - function_returns(func, args, bool) should have the proper diagnostics -ok 229 - function_returns(func, 0 args, setof bool, desc) should pass -ok 230 - function_returns(func, 0 args, setof bool, desc) should have the proper description -ok 231 - function_returns(func, 0 args, setof bool, desc) should have the proper diagnostics -ok 232 - function_returns(func, 0 args, setof bool) should pass -ok 233 - function_returns(func, 0 args, setof bool) should have the proper description -ok 234 - function_returns(func, 0 args, setof bool) should have the proper diagnostics -ok 235 - function_returns(func, bool, desc) should pass -ok 236 - function_returns(func, bool, desc) should have the proper description -ok 237 - function_returns(func, bool, desc) should have the proper diagnostics -ok 238 - function_returns(func, bool) should pass -ok 239 - function_returns(func, bool) should have the proper description -ok 240 - function_returns(func, bool) should have the proper diagnostics -ok 241 - function_returns(other func, bool, false) should pass -ok 242 - function_returns(other func, bool, false) should have the proper description -ok 243 - function_returns(other func, bool, false) should have the proper diagnostics -ok 244 - function_returns(other func, bool) should pass -ok 245 - function_returns(other func, bool) should have the proper description -ok 246 - function_returns(other func, bool) should have the proper diagnostics -ok 247 - function_returns(func, setof bool, desc) should pass -ok 248 - function_returns(func, setof bool, desc) should have the proper description -ok 249 - function_returns(func, setof bool, desc) should have the proper diagnostics -ok 250 - function_returns(func, setof bool) should pass -ok 251 - function_returns(func, setof bool) should have the proper description -ok 252 - function_returns(func, setof bool) should have the proper diagnostics -ok 253 - is_definer(schema, func, 0 args, desc) should pass -ok 254 - is_definer(schema, func, 0 args, desc) should have the proper description -ok 255 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics -ok 256 - isnt_definer(schema, func, 0 args, desc) should fail -ok 257 - isnt_definer(schema, func, 0 args, desc) should have the proper description -ok 258 - isnt_definer(schema, func, 0 args, desc) should have the proper diagnostics -ok 259 - is_definer(schema, func, 0 args) should pass -ok 260 - is_definer(schema, func, 0 args) should have the proper description -ok 261 - is_definer(schema, func, 0 args) should have the proper diagnostics -ok 262 - isnt_definer(schema, func, 0 args) should fail -ok 263 - isnt_definer(schema, func, 0 args) should have the proper description -ok 264 - isnt_definer(schema, func, 0 args) should have the proper diagnostics -ok 265 - is_definer(schema, func, args, desc) should fail -ok 266 - is_definer(schema, func, args, desc) should have the proper description -ok 267 - is_definer(schema, func, args, desc) should have the proper diagnostics -ok 268 - isnt_definer(schema, func, args, desc) should pass -ok 269 - isnt_definer(schema, func, args, desc) should have the proper description -ok 270 - isnt_definer(schema, func, args, desc) should have the proper diagnostics -ok 271 - is_definer(schema, func, args) should fail -ok 272 - is_definer(schema, func, args) should have the proper description -ok 273 - is_definer(schema, func, args) should have the proper diagnostics -ok 274 - isnt_definer(schema, func, args) should pass -ok 275 - isnt_definer(schema, func, args) should have the proper description -ok 276 - isnt_definer(schema, func, args) should have the proper diagnostics -ok 277 - is_definer(schema, func, desc) should pass -ok 278 - is_definer(schema, func, desc) should have the proper description -ok 279 - is_definer(schema, func, desc) should have the proper diagnostics -ok 280 - isnt_definer(schema, func, desc) should fail -ok 281 - isnt_definer(schema, func, desc) should have the proper description -ok 282 - isnt_definer(schema, func, desc) should have the proper diagnostics -ok 283 - is_definer(schema, func) should pass -ok 284 - is_definer(schema, func) should have the proper description -ok 285 - is_definer(schema, func) should have the proper diagnostics -ok 286 - isnt_definer(schema, func) should fail -ok 287 - isnt_definer(schema, func) should have the proper description -ok 288 - isnt_definer(schema, func) should have the proper diagnostics -ok 289 - is_definer(schema, func, 0 args, desc) should pass -ok 290 - is_definer(schema, func, 0 args, desc) should have the proper description -ok 291 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics -ok 292 - isnt_definer(schema, func, 0 args, desc) should fail -ok 293 - isnt_definer(schema, func, 0 args, desc) should have the proper description -ok 294 - isnt_definer(schema, func, 0 args, desc) should have the proper diagnostics -ok 295 - is_definer(schema, func, 0 args) should pass -ok 296 - is_definer(schema, func, 0 args) should have the proper description -ok 297 - is_definer(schema, func, 0 args) should have the proper diagnostics -ok 298 - isnt_definer(schema, func, 0 args) should fail -ok 299 - isnt_definer(schema, func, 0 args) should have the proper description -ok 300 - isnt_definer(schema, func, 0 args) should have the proper diagnostics -ok 301 - is_definer(schema, func, args, desc) should fail -ok 302 - is_definer(schema, func, args, desc) should have the proper description -ok 303 - is_definer(schema, func, args, desc) should have the proper diagnostics -ok 304 - isnt_definer(schema, func, args, desc) should pass -ok 305 - isnt_definer(schema, func, args, desc) should have the proper description -ok 306 - isnt_definer(schema, func, args, desc) should have the proper diagnostics -ok 307 - is_definer(schema, func, args) should fail -ok 308 - is_definer(schema, func, args) should have the proper description -ok 309 - is_definer(schema, func, args) should have the proper diagnostics -ok 310 - isnt_definer(schema, func, args) should pass -ok 311 - isnt_definer(schema, func, args) should have the proper description -ok 312 - isnt_definer(schema, func, args) should have the proper diagnostics -ok 313 - is_definer(schema, func, desc) should pass -ok 314 - is_definer(schema, func, desc) should have the proper description -ok 315 - is_definer(schema, func, desc) should have the proper diagnostics -ok 316 - isnt_definer(schema, func, desc) should fail -ok 317 - isnt_definer(schema, func, desc) should have the proper description -ok 318 - isnt_definer(schema, func, desc) should have the proper diagnostics -ok 319 - is_definer(schema, func) should pass -ok 320 - is_definer(schema, func) should have the proper description -ok 321 - is_definer(schema, func) should have the proper diagnostics -ok 322 - isnt_definer(schema, func) should fail -ok 323 - isnt_definer(schema, func) should have the proper description -ok 324 - isnt_definer(schema, func) should have the proper diagnostics -ok 325 - is_definer(func, 0 args, desc) should pass -ok 326 - is_definer(func, 0 args, desc) should have the proper description -ok 327 - is_definer(func, 0 args, desc) should have the proper diagnostics -ok 328 - isnt_definer(func, 0 args, desc) should fail -ok 329 - isnt_definer(func, 0 args, desc) should have the proper description -ok 330 - isnt_definer(func, 0 args, desc) should have the proper diagnostics -ok 331 - is_definer(func, 0 args) should pass -ok 332 - is_definer(func, 0 args) should have the proper description -ok 333 - is_definer(func, 0 args) should have the proper diagnostics -ok 334 - isnt_definer(func, 0 args) should fail -ok 335 - isnt_definer(func, 0 args) should have the proper description -ok 336 - isnt_definer(func, 0 args) should have the proper diagnostics -ok 337 - is_definer(func, args, desc) should fail -ok 338 - is_definer(func, args, desc) should have the proper description -ok 339 - is_definer(func, args, desc) should have the proper diagnostics -ok 340 - isnt_definer(func, args, desc) should pass -ok 341 - isnt_definer(func, args, desc) should have the proper description -ok 342 - isnt_definer(func, args, desc) should have the proper diagnostics -ok 343 - is_definer(func, args) should fail -ok 344 - is_definer(func, args) should have the proper description -ok 345 - is_definer(func, args) should have the proper diagnostics -ok 346 - isnt_definer(func, args) should pass -ok 347 - isnt_definer(func, args) should have the proper description -ok 348 - isnt_definer(func, args) should have the proper diagnostics -ok 349 - is_definer(func, desc) should pass -ok 350 - is_definer(func, desc) should have the proper description -ok 351 - is_definer(func, desc) should have the proper diagnostics -ok 352 - isnt_definer(func, desc) should fail -ok 353 - isnt_definer(func, desc) should have the proper description -ok 354 - isnt_definer(func, desc) should have the proper diagnostics -ok 355 - is_definer(func) should pass -ok 356 - is_definer(func) should have the proper description -ok 357 - is_definer(func) should have the proper diagnostics -ok 358 - isnt_definer(func) should fail -ok 359 - isnt_definer(func) should have the proper description -ok 360 - isnt_definer(func) should have the proper diagnostics -ok 361 - is_normal_function(schema, func, noargs, desc) should pass -ok 362 - is_normal_function(schema, func, noargs, desc) should have the proper description -ok 363 - is_normal_function(schema, func, noargs, desc) should have the proper diagnostics -ok 364 - is_normal_function(schema, agg, arg, desc) should fail -ok 365 - is_normal_function(schema, agg, arg, desc) should have the proper description -ok 366 - is_normal_function(schema, agg, arg, desc) should have the proper diagnostics -ok 367 - isnt_normal_function(schema, func, noargs, desc) should fail -ok 368 - isnt_normal_function(schema, func, noargs, desc) should have the proper description -ok 369 - isnt_normal_function(schema, func, noargs, desc) should have the proper diagnostics -ok 370 - isnt_normal_function(schema, agg, noargs, desc) should pass -ok 371 - isnt_normal_function(schema, agg, noargs, desc) should have the proper description -ok 372 - isnt_normal_function(schema, agg, noargs, desc) should have the proper diagnostics -ok 373 - is_normal_function(schema, func, args, desc) should pass -ok 374 - is_normal_function(schema, func, args, desc) should have the proper description -ok 375 - is_normal_function(schema, func, args, desc) should have the proper diagnostics -ok 376 - is_normal_function(schema, agg, args, desc) should fail -ok 377 - is_normal_function(schema, agg, args, desc) should have the proper description -ok 378 - is_normal_function(schema, agg, args, desc) should have the proper diagnostics -ok 379 - is_normal_function(schema, func, args, desc) should fail -ok 380 - is_normal_function(schema, func, args, desc) should have the proper description -ok 381 - is_normal_function(schema, func, args, desc) should have the proper diagnostics -ok 382 - isnt_normal_function(schema, agg, args, desc) should pass -ok 383 - isnt_normal_function(schema, agg, args, desc) should have the proper description -ok 384 - isnt_normal_function(schema, agg, args, desc) should have the proper diagnostics -ok 385 - is_normal_function(schema, nofunc, noargs, desc) should fail -ok 386 - is_normal_function(schema, nofunc, noargs, desc) should have the proper description -ok 387 - is_normal_function(schema, nofunc, noargs, desc) should have the proper diagnostics -ok 388 - isnt_normal_function(schema, noagg, args, desc) should fail -ok 389 - isnt_normal_function(schema, noagg, args, desc) should have the proper description -ok 390 - isnt_normal_function(schema, noagg, args, desc) should have the proper diagnostics -ok 391 - is_normal_function(schema, func, noargs) should pass -ok 392 - is_normal_function(schema, func, noargs) should have the proper description -ok 393 - is_normal_function(schema, func, noargs) should have the proper diagnostics -ok 394 - is_normal_function(schema, agg, noargs) should fail -ok 395 - is_normal_function(schema, agg, noargs) should have the proper description -ok 396 - is_normal_function(schema, agg, noargs) should have the proper diagnostics -ok 397 - isnt_normal_function(schema, func, noargs) should fail -ok 398 - isnt_normal_function(schema, func, noargs) should have the proper description -ok 399 - isnt_normal_function(schema, func, noargs) should have the proper diagnostics -ok 400 - isnt_normal_function(schema, agg, noargs) should pass -ok 401 - isnt_normal_function(schema, agg, noargs) should have the proper description -ok 402 - isnt_normal_function(schema, agg, noargs) should have the proper diagnostics -ok 403 - is_normal_function(schema, func2, args) should pass -ok 404 - is_normal_function(schema, func2, args) should have the proper description -ok 405 - is_normal_function(schema, func2, args) should have the proper diagnostics -ok 406 - isnt_normal_function(schema, func2, args) should fail -ok 407 - isnt_normal_function(schema, func2, args) should have the proper description -ok 408 - isnt_normal_function(schema, func2, args) should have the proper diagnostics -ok 409 - is_normal_function(schema, func, noargs) should fail -ok 410 - is_normal_function(schema, func, noargs) should have the proper description -ok 411 - is_normal_function(schema, func, noargs) should have the proper diagnostics -ok 412 - is_normal_function(schema, nofunc, noargs) should fail -ok 413 - is_normal_function(schema, nofunc, noargs) should have the proper description -ok 414 - is_normal_function(schema, nofunc, noargs) should have the proper diagnostics -ok 415 - is_normal_function(schema, func, desc) should pass -ok 416 - is_normal_function(schema, func, desc) should have the proper description -ok 417 - is_normal_function(schema, func, desc) should have the proper diagnostics -ok 418 - is_normal_function(schema, agg, desc) should fail -ok 419 - is_normal_function(schema, agg, desc) should have the proper description -ok 420 - is_normal_function(schema, agg, desc) should have the proper diagnostics -ok 421 - isnt_normal_function(schema, func, desc) should fail -ok 422 - isnt_normal_function(schema, func, desc) should have the proper description -ok 423 - isnt_normal_function(schema, func, desc) should have the proper diagnostics -ok 424 - isnt_normal_function(schema, agg, desc) should pass -ok 425 - isnt_normal_function(schema, agg, desc) should have the proper description -ok 426 - isnt_normal_function(schema, agg, desc) should have the proper diagnostics -ok 427 - is_normal_function(schema, func2, desc) should pass -ok 428 - is_normal_function(schema, func2, desc) should have the proper description -ok 429 - is_normal_function(schema, func2, desc) should have the proper diagnostics -ok 430 - isnt_normal_function(schema, func2, desc) should fail -ok 431 - isnt_normal_function(schema, func2, desc) should have the proper description -ok 432 - isnt_normal_function(schema, func2, desc) should have the proper diagnostics -ok 433 - is_normal_function(schema, nofunc, desc) should fail -ok 434 - is_normal_function(schema, nofunc, desc) should have the proper description -ok 435 - is_normal_function(schema, nofunc, desc) should have the proper diagnostics -ok 436 - is_normal_function(schema, noagg, desc) should fail -ok 437 - is_normal_function(schema, noagg, desc) should have the proper description -ok 438 - is_normal_function(schema, noagg, desc) should have the proper diagnostics -ok 439 - is_normal_function(schema, func) should pass -ok 440 - is_normal_function(schema, func) should have the proper description -ok 441 - is_normal_function(schema, func) should have the proper diagnostics -ok 442 - is_normal_function(schema, agg) should fail -ok 443 - is_normal_function(schema, agg) should have the proper description -ok 444 - is_normal_function(schema, agg) should have the proper diagnostics -ok 445 - isnt_normal_function(schema, func) should fail -ok 446 - isnt_normal_function(schema, func) should have the proper description -ok 447 - isnt_normal_function(schema, func) should have the proper diagnostics -ok 448 - isnt_normal_function(schema, agg) should pass -ok 449 - isnt_normal_function(schema, agg) should have the proper description -ok 450 - isnt_normal_function(schema, agg) should have the proper diagnostics -ok 451 - is_normal_function(schema, func2, args) should pass -ok 452 - is_normal_function(schema, func2, args) should have the proper description -ok 453 - is_normal_function(schema, func2, args) should have the proper diagnostics -ok 454 - isnt_normal_function(schema, func2) should fail -ok 455 - isnt_normal_function(schema, func2) should have the proper description -ok 456 - isnt_normal_function(schema, func2) should have the proper diagnostics -ok 457 - is_normal_function(schema, nofunc) should fail -ok 458 - is_normal_function(schema, nofunc) should have the proper description -ok 459 - is_normal_function(schema, nofunc) should have the proper diagnostics -ok 460 - is_normal_function(schema, nogg) should fail -ok 461 - is_normal_function(schema, nogg) should have the proper description -ok 462 - is_normal_function(schema, nogg) should have the proper diagnostics -ok 463 - is_normal_function(func, noargs, desc) should pass -ok 464 - is_normal_function(func, noargs, desc) should have the proper description -ok 465 - is_normal_function(func, noargs, desc) should have the proper diagnostics -ok 466 - is_normal_function(func, agg, desc) should fail -ok 467 - is_normal_function(func, agg, desc) should have the proper description -ok 468 - is_normal_function(func, agg, desc) should have the proper diagnostics -ok 469 - isnt_normal_function(func, noargs, desc) should fail -ok 470 - isnt_normal_function(func, noargs, desc) should have the proper description -ok 471 - isnt_normal_function(func, noargs, desc) should have the proper diagnostics -ok 472 - isnt_normal_function(func, agg, desc) should pass -ok 473 - isnt_normal_function(func, agg, desc) should have the proper description -ok 474 - isnt_normal_function(func, agg, desc) should have the proper diagnostics -ok 475 - is_normal_function(func, args, desc) should pass -ok 476 - is_normal_function(func, args, desc) should have the proper description -ok 477 - is_normal_function(func, args, desc) should have the proper diagnostics -ok 478 - isnt_normal_function(func, args, desc) should fail -ok 479 - isnt_normal_function(func, args, desc) should have the proper description -ok 480 - isnt_normal_function(func, args, desc) should have the proper diagnostics -ok 481 - is_normal_function(nofunc, noargs, desc) should fail -ok 482 - is_normal_function(nofunc, noargs, desc) should have the proper description -ok 483 - is_normal_function(nofunc, noargs, desc) should have the proper diagnostics -ok 484 - is_normal_function(func, noagg, desc) should fail -ok 485 - is_normal_function(func, noagg, desc) should have the proper description -ok 486 - is_normal_function(func, noagg, desc) should have the proper diagnostics -ok 487 - is_normal_function(func, noargs) should pass -ok 488 - is_normal_function(func, noargs) should have the proper description -ok 489 - is_normal_function(func, noargs) should have the proper diagnostics -ok 490 - isnt_normal_function(func, noargs) should fail -ok 491 - isnt_normal_function(func, noargs) should have the proper description -ok 492 - isnt_normal_function(func, noargs) should have the proper diagnostics -ok 493 - is_normal_function(func, noargs) should pass -ok 494 - is_normal_function(func, noargs) should have the proper description -ok 495 - is_normal_function(func, noargs) should have the proper diagnostics -ok 496 - isnt_normal_function(func, noargs) should fail -ok 497 - isnt_normal_function(func, noargs) should have the proper description -ok 498 - isnt_normal_function(func, noargs) should have the proper diagnostics -ok 499 - is_normal_function(nofunc, noargs) should fail -ok 500 - is_normal_function(nofunc, noargs) should have the proper description -ok 501 - is_normal_function(nofunc, noargs) should have the proper diagnostics -ok 502 - isnt_normal_function(fnounc, noargs) should fail -ok 503 - isnt_normal_function(fnounc, noargs) should have the proper description -ok 504 - isnt_normal_function(fnounc, noargs) should have the proper diagnostics -ok 505 - is_normal_function(func, desc) should pass -ok 506 - is_normal_function(func, desc) should have the proper description -ok 507 - is_normal_function(func, desc) should have the proper diagnostics -ok 508 - is_normal_function(agg, desc) should fail -ok 509 - is_normal_function(agg, desc) should have the proper description -ok 510 - is_normal_function(agg, desc) should have the proper diagnostics -ok 511 - isnt_normal_function(func, desc) should fail -ok 512 - isnt_normal_function(func, desc) should have the proper description -ok 513 - isnt_normal_function(func, desc) should have the proper diagnostics -ok 514 - isnt_normal_function(agg, desc) should pass -ok 515 - isnt_normal_function(agg, desc) should have the proper description -ok 516 - isnt_normal_function(agg, desc) should have the proper diagnostics -ok 517 - is_normal_function(func2, desc) should pass -ok 518 - is_normal_function(func2, desc) should have the proper description -ok 519 - is_normal_function(func2, desc) should have the proper diagnostics -ok 520 - isnt_normal_function(func2, desc) should fail -ok 521 - isnt_normal_function(func2, desc) should have the proper description -ok 522 - isnt_normal_function(func2, desc) should have the proper diagnostics -ok 523 - is_normal_function(nofunc, desc) should fail -ok 524 - is_normal_function(nofunc, desc) should have the proper description -ok 525 - is_normal_function(nofunc, desc) should have the proper diagnostics -ok 526 - is_normal_function(noagg, desc) should fail -ok 527 - is_normal_function(noagg, desc) should have the proper description -ok 528 - is_normal_function(noagg, desc) should have the proper diagnostics -ok 529 - is_normal_function(func) should pass -ok 530 - is_normal_function(func) should have the proper description -ok 531 - is_normal_function(func) should have the proper diagnostics -ok 532 - is_normal_function(agg) should fail -ok 533 - is_normal_function(agg) should have the proper description -ok 534 - is_normal_function(agg) should have the proper diagnostics -ok 535 - isnt_normal_function(func) should fail -ok 536 - isnt_normal_function(func) should have the proper description -ok 537 - isnt_normal_function(func) should have the proper diagnostics -ok 538 - isnt_normal_function(agg) should pass -ok 539 - isnt_normal_function(agg) should have the proper description -ok 540 - isnt_normal_function(agg) should have the proper diagnostics -ok 541 - is_normal_function(func2) should pass -ok 542 - is_normal_function(func2) should have the proper description -ok 543 - is_normal_function(func2) should have the proper diagnostics -ok 544 - isnt_normal_function(func2,) should fail -ok 545 - isnt_normal_function(func2,) should have the proper description -ok 546 - isnt_normal_function(func2,) should have the proper diagnostics -ok 547 - is_normal_function(nofunc) should fail -ok 548 - is_normal_function(nofunc) should have the proper description -ok 549 - is_normal_function(nofunc) should have the proper diagnostics -ok 550 - is_normal_function(noagg) should fail -ok 551 - is_normal_function(noagg) should have the proper description -ok 552 - is_normal_function(noagg) should have the proper diagnostics -ok 553 - is_aggregate(schema, func, arg, desc) should pass -ok 554 - is_aggregate(schema, func, arg, desc) should have the proper description -ok 555 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics -ok 556 - isnt_aggregate(schema, agg, arg, desc) should fail -ok 557 - isnt_aggregate(schema, agg, arg, desc) should have the proper description -ok 558 - isnt_aggregate(schema, agg, arg, desc) should have the proper diagnostics -ok 559 - is_aggregate(schema, func, args, desc) should fail -ok 560 - is_aggregate(schema, func, args, desc) should have the proper description -ok 561 - is_aggregate(schema, func, args, desc) should have the proper diagnostics -ok 562 - isnt_aggregate(schema, func, args, desc) should pass -ok 563 - isnt_aggregate(schema, func, args, desc) should have the proper description -ok 564 - isnt_aggregate(schema, func, args, desc) should have the proper diagnostics -ok 565 - is_aggregate(schema, nofunc, arg, desc) should fail -ok 566 - is_aggregate(schema, nofunc, arg, desc) should have the proper description -ok 567 - is_aggregate(schema, nofunc, arg, desc) should have the proper diagnostics -ok 568 - isnt_aggregate(schema, noagg, arg, desc) should fail -ok 569 - isnt_aggregate(schema, noagg, arg, desc) should have the proper description -ok 570 - isnt_aggregate(schema, noagg, arg, desc) should have the proper diagnostics -ok 571 - is_aggregate(schema, agg, arg) should pass -ok 572 - is_aggregate(schema, agg, arg) should have the proper description -ok 573 - is_aggregate(schema, agg, arg) should have the proper diagnostics -ok 574 - isnt_aggregate(schema, agg, arg) should fail -ok 575 - isnt_aggregate(schema, agg, arg) should have the proper description -ok 576 - isnt_aggregate(schema, agg, arg) should have the proper diagnostics -ok 577 - is_aggregate(schema, func, args) should fail -ok 578 - is_aggregate(schema, func, args) should have the proper description -ok 579 - is_aggregate(schema, func, args) should have the proper diagnostics -ok 580 - isnt_aggregate(schema, func, args) should pass -ok 581 - isnt_aggregate(schema, func, args) should have the proper description -ok 582 - isnt_aggregate(schema, func, args) should have the proper diagnostics -ok 583 - is_aggregate(schema, noagg, arg) should fail -ok 584 - is_aggregate(schema, noagg, arg) should have the proper description -ok 585 - is_aggregate(schema, noagg, arg) should have the proper diagnostics -ok 586 - isnt_aggregate(schema, noagg, arg) should fail -ok 587 - isnt_aggregate(schema, noagg, arg) should have the proper description -ok 588 - isnt_aggregate(schema, noagg, arg) should have the proper diagnostics -ok 589 - is_aggregate(schema, agg, desc) should pass -ok 590 - is_aggregate(schema, agg, desc) should have the proper description -ok 591 - is_aggregate(schema, agg, desc) should have the proper diagnostics -ok 592 - isnt_aggregate(schema, agg, desc) should fail -ok 593 - isnt_aggregate(schema, agg, desc) should have the proper description -ok 594 - isnt_aggregate(schema, agg, desc) should have the proper diagnostics -ok 595 - is_aggregate(schema, noagg, desc) should fail -ok 596 - is_aggregate(schema, noagg, desc) should have the proper description -ok 597 - is_aggregate(schema, noagg, desc) should have the proper diagnostics -ok 598 - isnt_aggregate(schema, noagg, desc) should fail -ok 599 - isnt_aggregate(schema, noagg, desc) should have the proper description -ok 600 - isnt_aggregate(schema, noagg, desc) should have the proper diagnostics -ok 601 - is_aggregate(schema, agg) should pass -ok 602 - is_aggregate(schema, agg) should have the proper description -ok 603 - is_aggregate(schema, agg) should have the proper diagnostics -ok 604 - isnt_aggregate(schema, agg) should fail -ok 605 - isnt_aggregate(schema, agg) should have the proper description -ok 606 - isnt_aggregate(schema, agg) should have the proper diagnostics -ok 607 - is_aggregate(schema, noagg) should fail -ok 608 - is_aggregate(schema, noagg) should have the proper description -ok 609 - is_aggregate(schema, noagg) should have the proper diagnostics -ok 610 - isnt_aggregate(schema, noagg) should fail -ok 611 - isnt_aggregate(schema, noagg) should have the proper description -ok 612 - isnt_aggregate(schema, noagg) should have the proper diagnostics -ok 613 - is_aggregate(agg, arg, desc) should pass -ok 614 - is_aggregate(agg, arg, desc) should have the proper description -ok 615 - is_aggregate(agg, arg, desc) should have the proper diagnostics -ok 616 - isnt_aggregate(agg, arg, desc) should fail -ok 617 - isnt_aggregate(agg, arg, desc) should have the proper description -ok 618 - isnt_aggregate(agg, arg, desc) should have the proper diagnostics -ok 619 - is_aggregate(func, args, desc) should fail -ok 620 - is_aggregate(func, args, desc) should have the proper description -ok 621 - is_aggregate(func, args, desc) should have the proper diagnostics -ok 622 - isnt_aggregate(func, args, desc) should pass -ok 623 - isnt_aggregate(func, args, desc) should have the proper description -ok 624 - isnt_aggregate(func, args, desc) should have the proper diagnostics -ok 625 - is_aggregate(noagg, arg, desc) should fail -ok 626 - is_aggregate(noagg, arg, desc) should have the proper description -ok 627 - is_aggregate(noagg, arg, desc) should have the proper diagnostics -ok 628 - isnt_aggregate(noagg, arg, desc) should fail -ok 629 - isnt_aggregate(noagg, arg, desc) should have the proper description -ok 630 - isnt_aggregate(noagg, arg, desc) should have the proper diagnostics -ok 631 - is_aggregate(agg, arg) should pass -ok 632 - is_aggregate(agg, arg) should have the proper description -ok 633 - is_aggregate(agg, arg) should have the proper diagnostics -ok 634 - isnt_aggregate(agg, arg) should fail -ok 635 - isnt_aggregate(agg, arg) should have the proper description -ok 636 - isnt_aggregate(agg, arg) should have the proper diagnostics -ok 637 - is_aggregate(func, args) should fail -ok 638 - is_aggregate(func, args) should have the proper description -ok 639 - is_aggregate(func, args) should have the proper diagnostics -ok 640 - isnt_aggregate(func, args) should pass -ok 641 - isnt_aggregate(func, args) should have the proper description -ok 642 - isnt_aggregate(func, args) should have the proper diagnostics -ok 643 - is_aggregate(noagg, arg) should fail -ok 644 - is_aggregate(noagg, arg) should have the proper description -ok 645 - is_aggregate(noagg, arg) should have the proper diagnostics -ok 646 - isnt_aggregate(noagg, arg) should fail -ok 647 - isnt_aggregate(noagg, arg) should have the proper description -ok 648 - isnt_aggregate(noagg, arg) should have the proper diagnostics -ok 649 - is_aggregate(func, desc) should pass -ok 650 - is_aggregate(func, desc) should have the proper description -ok 651 - is_aggregate(func, desc) should have the proper diagnostics -ok 652 - isnt_aggregate(agg, desc) should fail -ok 653 - isnt_aggregate(agg, desc) should have the proper description -ok 654 - isnt_aggregate(agg, desc) should have the proper diagnostics -ok 655 - is_aggregate(nofunc, desc) should fail -ok 656 - is_aggregate(nofunc, desc) should have the proper description -ok 657 - is_aggregate(nofunc, desc) should have the proper diagnostics -ok 658 - isnt_aggregate(noagg, desc) should fail -ok 659 - isnt_aggregate(noagg, desc) should have the proper description -ok 660 - isnt_aggregate(noagg, desc) should have the proper diagnostics -ok 661 - is_aggregate(agg) should pass -ok 662 - is_aggregate(agg) should have the proper description -ok 663 - is_aggregate(agg) should have the proper diagnostics -ok 664 - isnt_aggregate(agg) should fail -ok 665 - isnt_aggregate(agg) should have the proper description -ok 666 - isnt_aggregate(agg) should have the proper diagnostics -ok 667 - is_aggregate(noagg) should fail -ok 668 - is_aggregate(noagg) should have the proper description -ok 669 - is_aggregate(noagg) should have the proper diagnostics -ok 670 - isnt_aggregate(noagg) should fail -ok 671 - isnt_aggregate(noagg) should have the proper description -ok 672 - isnt_aggregate(noagg) should have the proper diagnostics -ok 673 - is_window(schema, win, arg, desc) should pass -ok 674 - is_window(schema, win, arg, desc) should have the proper description -ok 675 - is_window(schema, win, arg, desc) should have the proper diagnostics -ok 676 - isnt_window(schema, win, arg, desc) should fail -ok 677 - isnt_window(schema, win, arg, desc) should have the proper description -ok 678 - isnt_window(schema, win, arg, desc) should have the proper diagnostics -ok 679 - is_window(schema, func, arg, desc) should fail -ok 680 - is_window(schema, func, arg, desc) should have the proper description -ok 681 - is_window(schema, func, arg, desc) should have the proper diagnostics -ok 682 - isnt_window(schema, func, arg, desc) should pass -ok 683 - isnt_window(schema, func, arg, desc) should have the proper description -ok 684 - isnt_window(schema, func, arg, desc) should have the proper diagnostics -ok 685 - is_window(schema, win, noargs, desc) should pass -ok 686 - is_window(schema, win, noargs, desc) should have the proper description -ok 687 - is_window(schema, win, noargs, desc) should have the proper diagnostics -ok 688 - isnt_window(schema, win, noargs, desc) should fail -ok 689 - isnt_window(schema, win, noargs, desc) should have the proper description -ok 690 - isnt_window(schema, win, noargs, desc) should have the proper diagnostics -ok 691 - is_window(schema, func, noarg, desc) should fail -ok 692 - is_window(schema, func, noarg, desc) should have the proper description -ok 693 - is_window(schema, func, noarg, desc) should have the proper diagnostics -ok 694 - is_window(schema, win, noargs, desc) should fail -ok 695 - is_window(schema, win, noargs, desc) should have the proper description -ok 696 - is_window(schema, win, noargs, desc) should have the proper diagnostics -ok 697 - is_window(schema, nowin, arg, desc) should fail -ok 698 - is_window(schema, nowin, arg, desc) should have the proper description -ok 699 - is_window(schema, nowin, arg, desc) should have the proper diagnostics -ok 700 - isnt_window(schema, nowin, arg, desc) should fail -ok 701 - isnt_window(schema, nowin, arg, desc) should have the proper description -ok 702 - isnt_window(schema, nowin, arg, desc) should have the proper diagnostics -ok 703 - is_window(schema, win, arg) should pass -ok 704 - is_window(schema, win, arg) should have the proper description -ok 705 - is_window(schema, win, arg) should have the proper diagnostics -ok 706 - isnt_window(schema, win, arg) should fail -ok 707 - isnt_window(schema, win, arg) should have the proper description -ok 708 - isnt_window(schema, win, arg) should have the proper diagnostics -ok 709 - is_window(schema, func, arg) should fail -ok 710 - is_window(schema, func, arg) should have the proper description -ok 711 - is_window(schema, func, arg) should have the proper diagnostics -ok 712 - isnt_window(schema, func, arg) should pass -ok 713 - isnt_window(schema, func, arg) should have the proper description -ok 714 - isnt_window(schema, func, arg) should have the proper diagnostics -ok 715 - is_window(schema, win, noargs) should pass -ok 716 - is_window(schema, win, noargs) should have the proper description -ok 717 - is_window(schema, win, noargs) should have the proper diagnostics -ok 718 - isnt_window(schema, win, noargs) should fail -ok 719 - isnt_window(schema, win, noargs) should have the proper description -ok 720 - isnt_window(schema, win, noargs) should have the proper diagnostics -ok 721 - is_window(schema, func, noarg) should fail -ok 722 - is_window(schema, func, noarg) should have the proper description -ok 723 - is_window(schema, func, noarg) should have the proper diagnostics -ok 724 - isnt_window(schema, win, noargs) should fail -ok 725 - isnt_window(schema, win, noargs) should have the proper description -ok 726 - isnt_window(schema, win, noargs) should have the proper diagnostics -ok 727 - is_window(schema, nowin, arg) should fail -ok 728 - is_window(schema, nowin, arg) should have the proper description -ok 729 - is_window(schema, nowin, arg) should have the proper diagnostics -ok 730 - isnt_window(schema, nowin, arg) should fail -ok 731 - isnt_window(schema, nowin, arg) should have the proper description -ok 732 - isnt_window(schema, nowin, arg) should have the proper diagnostics -ok 733 - is_window(schema, win, desc) should pass -ok 734 - is_window(schema, win, desc) should have the proper description -ok 735 - is_window(schema, win, desc) should have the proper diagnostics -ok 736 - isnt_window(schema, win, desc) should fail -ok 737 - isnt_window(schema, win, desc) should have the proper description -ok 738 - isnt_window(schema, win, desc) should have the proper diagnostics -ok 739 - is_window(schema, func, desc) should fail -ok 740 - is_window(schema, func, desc) should have the proper description -ok 741 - is_window(schema, func, desc) should have the proper diagnostics -ok 742 - isnt_window(schema, func, desc) should pass -ok 743 - isnt_window(schema, func, desc) should have the proper description -ok 744 - isnt_window(schema, func, desc) should have the proper diagnostics -ok 745 - is_window(schema, func, desc) should fail -ok 746 - is_window(schema, func, desc) should have the proper description -ok 747 - is_window(schema, func, desc) should have the proper diagnostics -ok 748 - isnt_window(schema, win, desc) should fail -ok 749 - isnt_window(schema, win, desc) should have the proper description -ok 750 - isnt_window(schema, win, desc) should have the proper diagnostics -ok 751 - is_window(schema, nowin, desc) should fail -ok 752 - is_window(schema, nowin, desc) should have the proper description -ok 753 - is_window(schema, nowin, desc) should have the proper diagnostics -ok 754 - isnt_window(schema, nowin, desc) should fail -ok 755 - isnt_window(schema, nowin, desc) should have the proper description -ok 756 - isnt_window(schema, nowin, desc) should have the proper diagnostics -ok 757 - is_window(schema, win) should pass -ok 758 - is_window(schema, win) should have the proper description -ok 759 - is_window(schema, win) should have the proper diagnostics -ok 760 - isnt_window(schema, win) should fail -ok 761 - isnt_window(schema, win) should have the proper description -ok 762 - isnt_window(schema, win) should have the proper diagnostics -ok 763 - is_window(schema, func) should fail -ok 764 - is_window(schema, func) should have the proper description -ok 765 - is_window(schema, func) should have the proper diagnostics -ok 766 - isnt_window(schema, func) should pass -ok 767 - isnt_window(schema, func) should have the proper description -ok 768 - isnt_window(schema, func) should have the proper diagnostics -ok 769 - is_window(schema, nowin) should fail -ok 770 - is_window(schema, nowin) should have the proper description -ok 771 - is_window(schema, nowin) should have the proper diagnostics -ok 772 - isnt_window(schema, nowin) should fail -ok 773 - isnt_window(schema, nowin) should have the proper description -ok 774 - isnt_window(schema, nowin) should have the proper diagnostics -ok 775 - is_window(win, arg, desc) should pass -ok 776 - is_window(win, arg, desc) should have the proper description -ok 777 - is_window(win, arg, desc) should have the proper diagnostics -ok 778 - isnt_window(win, arg, desc) should fail -ok 779 - isnt_window(win, arg, desc) should have the proper description -ok 780 - isnt_window(win, arg, desc) should have the proper diagnostics -ok 781 - is_window(func, arg, desc) should fail -ok 782 - is_window(func, arg, desc) should have the proper description -ok 783 - is_window(func, arg, desc) should have the proper diagnostics -ok 784 - isnt_window(func, arg, desc) should pass -ok 785 - isnt_window(func, arg, desc) should have the proper description -ok 786 - isnt_window(func, arg, desc) should have the proper diagnostics -ok 787 - is_window(win, noargs, desc) should pass -ok 788 - is_window(win, noargs, desc) should have the proper description -ok 789 - is_window(win, noargs, desc) should have the proper diagnostics -ok 790 - isnt_window(win, noargs, desc) should fail -ok 791 - isnt_window(win, noargs, desc) should have the proper description -ok 792 - isnt_window(win, noargs, desc) should have the proper diagnostics -ok 793 - is_window(func, noarg, desc) should fail -ok 794 - is_window(func, noarg, desc) should have the proper description -ok 795 - is_window(func, noarg, desc) should have the proper diagnostics -ok 796 - isnt_window(win, noargs, desc) should fail -ok 797 - isnt_window(win, noargs, desc) should have the proper description -ok 798 - isnt_window(win, noargs, desc) should have the proper diagnostics -ok 799 - is_window(nowin, arg, desc) should fail -ok 800 - is_window(nowin, arg, desc) should have the proper description -ok 801 - is_window(nowin, arg, desc) should have the proper diagnostics -ok 802 - isnt_window(nowin, arg, desc) should fail -ok 803 - isnt_window(nowin, arg, desc) should have the proper description -ok 804 - isnt_window(nowin, arg, desc) should have the proper diagnostics -ok 805 - is_window(win, arg, desc) should pass -ok 806 - is_window(win, arg, desc) should have the proper description -ok 807 - is_window(win, arg, desc) should have the proper diagnostics -ok 808 - isnt_window(win, arg, desc) should fail -ok 809 - isnt_window(win, arg, desc) should have the proper description -ok 810 - isnt_window(win, arg, desc) should have the proper diagnostics -ok 811 - is_window(func, arg, desc) should fail -ok 812 - is_window(func, arg, desc) should have the proper description -ok 813 - is_window(func, arg, desc) should have the proper diagnostics -ok 814 - isnt_window(func, arg, desc) should pass -ok 815 - isnt_window(func, arg, desc) should have the proper description -ok 816 - isnt_window(func, arg, desc) should have the proper diagnostics -ok 817 - is_window(win, noargs, desc) should pass -ok 818 - is_window(win, noargs, desc) should have the proper description -ok 819 - is_window(win, noargs, desc) should have the proper diagnostics -ok 820 - isnt_window(win, noargs, desc) should fail -ok 821 - isnt_window(win, noargs, desc) should have the proper description -ok 822 - isnt_window(win, noargs, desc) should have the proper diagnostics -ok 823 - is_window(func, noarg, desc) should fail -ok 824 - is_window(func, noarg, desc) should have the proper description -ok 825 - is_window(func, noarg, desc) should have the proper diagnostics -ok 826 - isnt_window(win, noargs, desc) should fail -ok 827 - isnt_window(win, noargs, desc) should have the proper description -ok 828 - isnt_window(win, noargs, desc) should have the proper diagnostics -ok 829 - is_window(nowin, arg, desc) should fail -ok 830 - is_window(nowin, arg, desc) should have the proper description -ok 831 - is_window(nowin, arg, desc) should have the proper diagnostics -ok 832 - isnt_window(nowin, arg, desc) should fail -ok 833 - isnt_window(nowin, arg, desc) should have the proper description -ok 834 - isnt_window(nowin, arg, desc) should have the proper diagnostics -ok 835 - is_window(win, desc) should pass -ok 836 - is_window(win, desc) should have the proper description -ok 837 - is_window(win, desc) should have the proper diagnostics -ok 838 - isnt_window(win, desc) should fail -ok 839 - isnt_window(win, desc) should have the proper description -ok 840 - isnt_window(win, desc) should have the proper diagnostics -ok 841 - is_window(func, desc) should fail -ok 842 - is_window(func, desc) should have the proper description -ok 843 - is_window(func, desc) should have the proper diagnostics -ok 844 - isnt_window(func, desc) should pass -ok 845 - isnt_window(func, desc) should have the proper description -ok 846 - isnt_window(func, desc) should have the proper diagnostics -ok 847 - is_window(func, desc) should fail -ok 848 - is_window(func, desc) should have the proper description -ok 849 - is_window(func, desc) should have the proper diagnostics -ok 850 - is_window(nowin, desc) should fail -ok 851 - is_window(nowin, desc) should have the proper description -ok 852 - is_window(nowin, desc) should have the proper diagnostics -ok 853 - isnt_window(nowin, desc) should fail -ok 854 - isnt_window(nowin, desc) should have the proper description -ok 855 - isnt_window(nowin, desc) should have the proper diagnostics -ok 856 - is_window(win) should pass -ok 857 - is_window(win) should have the proper description -ok 858 - is_window(win) should have the proper diagnostics -ok 859 - isnt_window(win) should fail -ok 860 - isnt_window(win) should have the proper description -ok 861 - isnt_window(win) should have the proper diagnostics -ok 862 - is_window(func) should fail -ok 863 - is_window(func) should have the proper description -ok 864 - is_window(func) should have the proper diagnostics -ok 865 - isnt_window(func) should pass -ok 866 - isnt_window(func) should have the proper description -ok 867 - isnt_window(func) should have the proper diagnostics -ok 868 - is_window(nowin) should fail -ok 869 - is_window(nowin) should have the proper description -ok 870 - is_window(nowin) should have the proper diagnostics -ok 871 - isnt_window(nowin) should fail -ok 872 - isnt_window(nowin) should have the proper description -ok 873 - isnt_window(nowin) should have the proper diagnostics -ok 874 - is_strict(schema, func, 0 args, desc) should pass -ok 875 - is_strict(schema, func, 0 args, desc) should have the proper description -ok 876 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 877 - isnt_strict(schema, func, 0 args, desc) should fail -ok 878 - isnt_strict(schema, func, 0 args, desc) should have the proper description -ok 879 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics -ok 880 - is_strict(schema, func, 0 args) should pass -ok 881 - is_strict(schema, func, 0 args) should have the proper description -ok 882 - is_strict(schema, func, 0 args) should have the proper diagnostics -ok 883 - isnt_strict(schema, func, 0 args) should fail -ok 884 - isnt_strict(schema, func, 0 args) should have the proper description -ok 885 - isnt_strict(schema, func, 0 args) should have the proper diagnostics -ok 886 - is_strict(schema, func, args, desc) should fail -ok 887 - is_strict(schema, func, args, desc) should have the proper description -ok 888 - is_strict(schema, func, args, desc) should have the proper diagnostics -ok 889 - isnt_strict(schema, func, args, desc) should pass -ok 890 - isnt_strict(schema, func, args, desc) should have the proper description -ok 891 - isnt_strict(schema, func, args, desc) should have the proper diagnostics -ok 892 - is_strict(schema, func, args) should fail -ok 893 - is_strict(schema, func, args) should have the proper description -ok 894 - is_strict(schema, func, args) should have the proper diagnostics -ok 895 - isnt_strict(schema, func, args) should pass -ok 896 - isnt_strict(schema, func, args) should have the proper description -ok 897 - isnt_strict(schema, func, args) should have the proper diagnostics -ok 898 - is_strict(schema, func, desc) should pass -ok 899 - is_strict(schema, func, desc) should have the proper description -ok 900 - is_strict(schema, func, desc) should have the proper diagnostics -ok 901 - isnt_strict(schema, func, desc) should fail -ok 902 - isnt_strict(schema, func, desc) should have the proper description -ok 903 - isnt_strict(schema, func, desc) should have the proper diagnostics -ok 904 - is_strict(schema, func) should pass -ok 905 - is_strict(schema, func) should have the proper description -ok 906 - is_strict(schema, func) should have the proper diagnostics -ok 907 - isnt_strict(schema, func) should fail -ok 908 - isnt_strict(schema, func) should have the proper description -ok 909 - isnt_strict(schema, func) should have the proper diagnostics -ok 910 - isnt_strict(schema, func, args, desc) should pass -ok 911 - isnt_strict(schema, func, args, desc) should have the proper description -ok 912 - isnt_strict(schema, func, args, desc) should have the proper diagnostics -ok 913 - isnt_strict(schema, func, args) should pass -ok 914 - isnt_strict(schema, func, args) should have the proper description -ok 915 - isnt_strict(schema, func, args) should have the proper diagnostics -ok 916 - is_strict(func, 0 args, desc) should pass -ok 917 - is_strict(func, 0 args, desc) should have the proper description -ok 918 - is_strict(func, 0 args, desc) should have the proper diagnostics -ok 919 - isnt_strict(func, 0 args, desc) should fail -ok 920 - isnt_strict(func, 0 args, desc) should have the proper description -ok 921 - isnt_strict(func, 0 args, desc) should have the proper diagnostics -ok 922 - is_strict(func, 0 args) should pass -ok 923 - is_strict(func, 0 args) should have the proper description -ok 924 - is_strict(func, 0 args) should have the proper diagnostics -ok 925 - isnt_strict(func, 0 args) should fail -ok 926 - isnt_strict(func, 0 args) should have the proper description -ok 927 - isnt_strict(func, 0 args) should have the proper diagnostics -ok 928 - is_strict(func, args, desc) should fail -ok 929 - is_strict(func, args, desc) should have the proper description -ok 930 - is_strict(func, args, desc) should have the proper diagnostics -ok 931 - isnt_strict(func, args, desc) should pass -ok 932 - isnt_strict(func, args, desc) should have the proper description -ok 933 - isnt_strict(func, args, desc) should have the proper diagnostics -ok 934 - is_strict(func, args) should fail -ok 935 - is_strict(func, args) should have the proper description -ok 936 - is_strict(func, args) should have the proper diagnostics -ok 937 - isnt_strict(func, args) should pass -ok 938 - isnt_strict(func, args) should have the proper description -ok 939 - isnt_strict(func, args) should have the proper diagnostics -ok 940 - is_strict(func, desc) should pass -ok 941 - is_strict(func, desc) should have the proper description -ok 942 - is_strict(func, desc) should have the proper diagnostics -ok 943 - isnt_strict(func, desc) should fail -ok 944 - isnt_strict(func, desc) should have the proper description -ok 945 - isnt_strict(func, desc) should have the proper diagnostics -ok 946 - is_strict(func) should pass -ok 947 - is_strict(func) should have the proper description -ok 948 - is_strict(func) should have the proper diagnostics -ok 949 - isnt_strict(func) should fail -ok 950 - isnt_strict(func) should have the proper description -ok 951 - isnt_strict(func) should have the proper diagnostics -ok 952 - function_volatility(schema, func, 0 args, volatile, desc) should pass -ok 953 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper description -ok 954 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper diagnostics -ok 955 - function_volatility(schema, func, 0 args, v, desc) should pass -ok 956 - function_volatility(schema, func, 0 args, v, desc) should have the proper description -ok 957 - function_volatility(schema, func, 0 args, v, desc) should have the proper diagnostics -ok 958 - function_volatility(schema, func, args, immutable, desc) should pass -ok 959 - function_volatility(schema, func, args, immutable, desc) should have the proper description -ok 960 - function_volatility(schema, func, args, immutable, desc) should have the proper diagnostics -ok 961 - function_volatility(schema, func, 0 args, stable, desc) should pass -ok 962 - function_volatility(schema, func, 0 args, stable, desc) should have the proper description -ok 963 - function_volatility(schema, func, 0 args, stable, desc) should have the proper diagnostics -ok 964 - function_volatility(schema, func, 0 args, volatile) should pass -ok 965 - function_volatility(schema, func, 0 args, volatile) should have the proper description -ok 966 - function_volatility(schema, func, 0 args, volatile) should have the proper diagnostics -ok 967 - function_volatility(schema, func, args, immutable) should pass -ok 968 - function_volatility(schema, func, args, immutable) should have the proper description -ok 969 - function_volatility(schema, func, volatile, desc) should pass -ok 970 - function_volatility(schema, func, volatile, desc) should have the proper description -ok 971 - function_volatility(schema, func, volatile, desc) should have the proper diagnostics -ok 972 - function_volatility(schema, func, volatile) should pass -ok 973 - function_volatility(schema, func, volatile) should have the proper description -ok 974 - function_volatility(schema, func, volatile) should have the proper diagnostics -ok 975 - function_volatility(schema, func, immutable, desc) should pass -ok 976 - function_volatility(schema, func, immutable, desc) should have the proper description -ok 977 - function_volatility(schema, func, immutable, desc) should have the proper diagnostics -ok 978 - function_volatility(schema, func, stable, desc) should pass -ok 979 - function_volatility(schema, func, stable, desc) should have the proper description -ok 980 - function_volatility(schema, func, stable, desc) should have the proper diagnostics -ok 981 - function_volatility(func, 0 args, volatile, desc) should pass -ok 982 - function_volatility(func, 0 args, volatile, desc) should have the proper description -ok 983 - function_volatility(func, 0 args, volatile, desc) should have the proper diagnostics -ok 984 - function_volatility(func, 0 args, v, desc) should pass -ok 985 - function_volatility(func, 0 args, v, desc) should have the proper description -ok 986 - function_volatility(func, 0 args, v, desc) should have the proper diagnostics -ok 987 - function_volatility(func, args, immutable, desc) should pass -ok 988 - function_volatility(func, args, immutable, desc) should have the proper description -ok 989 - function_volatility(func, args, immutable, desc) should have the proper diagnostics -ok 990 - function_volatility(func, 0 args, stable, desc) should pass -ok 991 - function_volatility(func, 0 args, stable, desc) should have the proper description -ok 992 - function_volatility(func, 0 args, stable, desc) should have the proper diagnostics -ok 993 - function_volatility(func, 0 args, volatile) should pass -ok 994 - function_volatility(func, 0 args, volatile) should have the proper description -ok 995 - function_volatility(func, 0 args, volatile) should have the proper diagnostics -ok 996 - function_volatility(func, args, immutable) should pass -ok 997 - function_volatility(func, args, immutable) should have the proper description -ok 998 - function_volatility(func, volatile, desc) should pass -ok 999 - function_volatility(func, volatile, desc) should have the proper description -ok 1000 - function_volatility(func, volatile, desc) should have the proper diagnostics -ok 1001 - function_volatility(func, volatile) should pass -ok 1002 - function_volatility(func, volatile) should have the proper description -ok 1003 - function_volatility(func, volatile) should have the proper diagnostics -ok 1004 - function_volatility(func, immutable, desc) should pass -ok 1005 - function_volatility(func, immutable, desc) should have the proper description -ok 1006 - function_volatility(func, immutable, desc) should have the proper diagnostics -ok 1007 - function_volatility(func, stable, desc) should pass -ok 1008 - function_volatility(func, stable, desc) should have the proper description -ok 1009 - function_volatility(func, stable, desc) should have the proper diagnostics +ok 112 - fail can(schema) with desc should fail +ok 113 - fail can(schema) with desc should have the proper description +ok 114 - fail can(schema) with desc should have the proper diagnostics +ok 115 - fail can(someschema) with desc should fail +ok 116 - fail can(someschema) with desc should have the proper description +ok 117 - fail can(someschema) with desc should have the proper diagnostics +ok 118 - can() with desc should pass +ok 119 - can() with desc should have the proper description +ok 120 - can() with desc should have the proper diagnostics +ok 121 - can(schema) should pass +ok 122 - can(schema) should have the proper description +ok 123 - can(schema) should have the proper diagnostics +ok 124 - fail can() with desc should fail +ok 125 - fail can() with desc should have the proper description +ok 126 - fail can() with desc should have the proper diagnostics +ok 127 - can(sch, proc) with desc should pass +ok 128 - can(sch, proc) with desc should have the proper description +ok 129 - can(sch, proc) with desc should have the proper diagnostics +ok 130 - can(proc) with desc should pass +ok 131 - can(proc) with desc should have the proper description +ok 132 - can(proc) with desc should have the proper diagnostics +ok 133 - function_lang_is(schema, func, 0 args, sql, desc) should pass +ok 134 - function_lang_is(schema, func, 0 args, sql, desc) should have the proper description +ok 135 - function_lang_is(schema, func, 0 args, sql, desc) should have the proper diagnostics +ok 136 - function_lang_is(schema, func, 0 args, sql) should pass +ok 137 - function_lang_is(schema, func, 0 args, sql) should have the proper description +ok 138 - function_lang_is(schema, func, 0 args, sql) should have the proper diagnostics +ok 139 - function_lang_is(schema, func, args, plpgsql, desc) should pass +ok 140 - function_lang_is(schema, func, args, plpgsql, desc) should have the proper description +ok 141 - function_lang_is(schema, func, args, plpgsql, desc) should have the proper diagnostics +ok 142 - function_lang_is(schema, func, args, plpgsql) should pass +ok 143 - function_lang_is(schema, func, args, plpgsql) should have the proper description +ok 144 - function_lang_is(schema, func, args, plpgsql) should have the proper diagnostics +ok 145 - function_lang_is(schema, func, 0 args, perl, desc) should fail +ok 146 - function_lang_is(schema, func, 0 args, perl, desc) should have the proper description +ok 147 - function_lang_is(schema, func, 0 args, perl, desc) should have the proper diagnostics +ok 148 - function_lang_is(schema, non-func, 0 args, sql, desc) should fail +ok 149 - function_lang_is(schema, non-func, 0 args, sql, desc) should have the proper description +ok 150 - function_lang_is(schema, non-func, 0 args, sql, desc) should have the proper diagnostics +ok 151 - function_lang_is(schema, func, args, plpgsql) should fail +ok 152 - function_lang_is(schema, func, args, plpgsql) should have the proper description +ok 153 - function_lang_is(schema, func, args, plpgsql) should have the proper diagnostics +ok 154 - function_lang_is(schema, func, sql, desc) should pass +ok 155 - function_lang_is(schema, func, sql, desc) should have the proper description +ok 156 - function_lang_is(schema, func, sql, desc) should have the proper diagnostics +ok 157 - function_lang_is(schema, func, sql) should pass +ok 158 - function_lang_is(schema, func, sql) should have the proper description +ok 159 - function_lang_is(schema, func, sql) should have the proper diagnostics +ok 160 - function_lang_is(schema, func, perl, desc) should fail +ok 161 - function_lang_is(schema, func, perl, desc) should have the proper description +ok 162 - function_lang_is(schema, func, perl, desc) should have the proper diagnostics +ok 163 - function_lang_is(schema, non-func, sql, desc) should fail +ok 164 - function_lang_is(schema, non-func, sql, desc) should have the proper description +ok 165 - function_lang_is(schema, non-func, sql, desc) should have the proper diagnostics +ok 166 - function_lang_is(func, 0 args, sql, desc) should pass +ok 167 - function_lang_is(func, 0 args, sql, desc) should have the proper description +ok 168 - function_lang_is(func, 0 args, sql, desc) should have the proper diagnostics +ok 169 - function_lang_is(func, 0 args, sql) should pass +ok 170 - function_lang_is(func, 0 args, sql) should have the proper description +ok 171 - function_lang_is(func, 0 args, sql) should have the proper diagnostics +ok 172 - function_lang_is(func, args, plpgsql, desc) should pass +ok 173 - function_lang_is(func, args, plpgsql, desc) should have the proper description +ok 174 - function_lang_is(func, args, plpgsql, desc) should have the proper diagnostics +ok 175 - function_lang_is(func, args, plpgsql) should pass +ok 176 - function_lang_is(func, args, plpgsql) should have the proper description +ok 177 - function_lang_is(func, args, plpgsql) should have the proper diagnostics +ok 178 - function_lang_is(func, 0 args, perl, desc) should fail +ok 179 - function_lang_is(func, 0 args, perl, desc) should have the proper description +ok 180 - function_lang_is(func, 0 args, perl, desc) should have the proper diagnostics +ok 181 - function_lang_is(non-func, 0 args, sql, desc) should fail +ok 182 - function_lang_is(non-func, 0 args, sql, desc) should have the proper description +ok 183 - function_lang_is(non-func, 0 args, sql, desc) should have the proper diagnostics +ok 184 - function_lang_is(func, args, plpgsql) should fail +ok 185 - function_lang_is(func, args, plpgsql) should have the proper description +ok 186 - function_lang_is(func, args, plpgsql) should have the proper diagnostics +ok 187 - function_lang_is(func, sql, desc) should pass +ok 188 - function_lang_is(func, sql, desc) should have the proper description +ok 189 - function_lang_is(func, sql, desc) should have the proper diagnostics +ok 190 - function_lang_is(func, sql) should pass +ok 191 - function_lang_is(func, sql) should have the proper description +ok 192 - function_lang_is(func, sql) should have the proper diagnostics +ok 193 - function_lang_is(func, perl, desc) should fail +ok 194 - function_lang_is(func, perl, desc) should have the proper description +ok 195 - function_lang_is(func, perl, desc) should have the proper diagnostics +ok 196 - function_lang_is(non-func, sql, desc) should fail +ok 197 - function_lang_is(non-func, sql, desc) should have the proper description +ok 198 - function_lang_is(non-func, sql, desc) should have the proper diagnostics +ok 199 - function_lang_is(schema, proc, desc) should pass +ok 200 - function_lang_is(schema, proc, desc) should have the proper description +ok 201 - function_lang_is(schema, proc, desc) should have the proper diagnostics +ok 202 - function_lang_is(schema, proc, desc) should pass +ok 203 - function_lang_is(schema, proc, desc) should have the proper description +ok 204 - function_lang_is(schema, proc, desc) should have the proper diagnostics +ok 205 - function_returns(schema, func, 0 args, bool, desc) should pass +ok 206 - function_returns(schema, func, 0 args, bool, desc) should have the proper description +ok 207 - function_returns(schema, func, 0 args, bool, desc) should have the proper diagnostics +ok 208 - function_returns(schema, func, 0 args, bool) should pass +ok 209 - function_returns(schema, func, 0 args, bool) should have the proper description +ok 210 - function_returns(schema, func, 0 args, bool) should have the proper diagnostics +ok 211 - function_returns(schema, func, args, bool, false) should pass +ok 212 - function_returns(schema, func, args, bool, false) should have the proper description +ok 213 - function_returns(schema, func, args, bool, false) should have the proper diagnostics +ok 214 - function_returns(schema, func, args, bool) should pass +ok 215 - function_returns(schema, func, args, bool) should have the proper description +ok 216 - function_returns(schema, func, args, bool) should have the proper diagnostics +ok 217 - function_returns(schema, func, 0 args, setof bool, desc) should pass +ok 218 - function_returns(schema, func, 0 args, setof bool, desc) should have the proper description +ok 219 - function_returns(schema, func, 0 args, setof bool, desc) should have the proper diagnostics +ok 220 - function_returns(schema, func, 0 args, setof bool) should pass +ok 221 - function_returns(schema, func, 0 args, setof bool) should have the proper description +ok 222 - function_returns(schema, func, 0 args, setof bool) should have the proper diagnostics +ok 223 - function_returns(schema, func, bool, desc) should pass +ok 224 - function_returns(schema, func, bool, desc) should have the proper description +ok 225 - function_returns(schema, func, bool, desc) should have the proper diagnostics +ok 226 - function_returns(schema, func, bool) should pass +ok 227 - function_returns(schema, func, bool) should have the proper description +ok 228 - function_returns(schema, func, bool) should have the proper diagnostics +ok 229 - function_returns(schema, other func, bool, false) should pass +ok 230 - function_returns(schema, other func, bool, false) should have the proper description +ok 231 - function_returns(schema, other func, bool, false) should have the proper diagnostics +ok 232 - function_returns(schema, other func, bool) should pass +ok 233 - function_returns(schema, other func, bool) should have the proper description +ok 234 - function_returns(schema, other func, bool) should have the proper diagnostics +ok 235 - function_returns(schema, func, setof bool, desc) should pass +ok 236 - function_returns(schema, func, setof bool, desc) should have the proper description +ok 237 - function_returns(schema, func, setof bool, desc) should have the proper diagnostics +ok 238 - function_returns(schema, func, setof bool) should pass +ok 239 - function_returns(schema, func, setof bool) should have the proper description +ok 240 - function_returns(schema, func, setof bool) should have the proper diagnostics +ok 241 - function_returns(func, 0 args, bool, desc) should pass +ok 242 - function_returns(func, 0 args, bool, desc) should have the proper description +ok 243 - function_returns(func, 0 args, bool, desc) should have the proper diagnostics +ok 244 - function_returns(func, 0 args, bool) should pass +ok 245 - function_returns(func, 0 args, bool) should have the proper description +ok 246 - function_returns(func, 0 args, bool) should have the proper diagnostics +ok 247 - function_returns(func, args, bool, false) should pass +ok 248 - function_returns(func, args, bool, false) should have the proper description +ok 249 - function_returns(func, args, bool, false) should have the proper diagnostics +ok 250 - function_returns(func, args, bool) should pass +ok 251 - function_returns(func, args, bool) should have the proper description +ok 252 - function_returns(func, args, bool) should have the proper diagnostics +ok 253 - function_returns(func, 0 args, setof bool, desc) should pass +ok 254 - function_returns(func, 0 args, setof bool, desc) should have the proper description +ok 255 - function_returns(func, 0 args, setof bool, desc) should have the proper diagnostics +ok 256 - function_returns(func, 0 args, setof bool) should pass +ok 257 - function_returns(func, 0 args, setof bool) should have the proper description +ok 258 - function_returns(func, 0 args, setof bool) should have the proper diagnostics +ok 259 - function_returns(func, bool, desc) should pass +ok 260 - function_returns(func, bool, desc) should have the proper description +ok 261 - function_returns(func, bool, desc) should have the proper diagnostics +ok 262 - function_returns(func, bool) should pass +ok 263 - function_returns(func, bool) should have the proper description +ok 264 - function_returns(func, bool) should have the proper diagnostics +ok 265 - function_returns(other func, bool, false) should pass +ok 266 - function_returns(other func, bool, false) should have the proper description +ok 267 - function_returns(other func, bool, false) should have the proper diagnostics +ok 268 - function_returns(other func, bool) should pass +ok 269 - function_returns(other func, bool) should have the proper description +ok 270 - function_returns(other func, bool) should have the proper diagnostics +ok 271 - function_returns(func, setof bool, desc) should pass +ok 272 - function_returns(func, setof bool, desc) should have the proper description +ok 273 - function_returns(func, setof bool, desc) should have the proper diagnostics +ok 274 - function_returns(func, setof bool) should pass +ok 275 - function_returns(func, setof bool) should have the proper description +ok 276 - function_returns(func, setof bool) should have the proper diagnostics +ok 277 - function_returns(sch, proc, void) should pass +ok 278 - function_returns(sch, proc, void) should have the proper description +ok 279 - function_returns(sch, proc, void) should have the proper diagnostics +ok 280 - function_returns(sch, proc, void) should pass +ok 281 - function_returns(sch, proc, void) should have the proper description +ok 282 - function_returns(sch, proc, void) should have the proper diagnostics +ok 283 - is_definer(schema, func, 0 args, desc) should pass +ok 284 - is_definer(schema, func, 0 args, desc) should have the proper description +ok 285 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 286 - isnt_definer(schema, func, 0 args, desc) should fail +ok 287 - isnt_definer(schema, func, 0 args, desc) should have the proper description +ok 288 - isnt_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 289 - is_definer(schema, func, 0 args) should pass +ok 290 - is_definer(schema, func, 0 args) should have the proper description +ok 291 - is_definer(schema, func, 0 args) should have the proper diagnostics +ok 292 - isnt_definer(schema, func, 0 args) should fail +ok 293 - isnt_definer(schema, func, 0 args) should have the proper description +ok 294 - isnt_definer(schema, func, 0 args) should have the proper diagnostics +ok 295 - is_definer(schema, func, args, desc) should fail +ok 296 - is_definer(schema, func, args, desc) should have the proper description +ok 297 - is_definer(schema, func, args, desc) should have the proper diagnostics +ok 298 - isnt_definer(schema, func, args, desc) should pass +ok 299 - isnt_definer(schema, func, args, desc) should have the proper description +ok 300 - isnt_definer(schema, func, args, desc) should have the proper diagnostics +ok 301 - is_definer(schema, func, args) should fail +ok 302 - is_definer(schema, func, args) should have the proper description +ok 303 - is_definer(schema, func, args) should have the proper diagnostics +ok 304 - isnt_definer(schema, func, args) should pass +ok 305 - isnt_definer(schema, func, args) should have the proper description +ok 306 - isnt_definer(schema, func, args) should have the proper diagnostics +ok 307 - is_definer(schema, func, desc) should pass +ok 308 - is_definer(schema, func, desc) should have the proper description +ok 309 - is_definer(schema, func, desc) should have the proper diagnostics +ok 310 - isnt_definer(schema, func, desc) should fail +ok 311 - isnt_definer(schema, func, desc) should have the proper description +ok 312 - isnt_definer(schema, func, desc) should have the proper diagnostics +ok 313 - is_definer(schema, func) should pass +ok 314 - is_definer(schema, func) should have the proper description +ok 315 - is_definer(schema, func) should have the proper diagnostics +ok 316 - isnt_definer(schema, func) should fail +ok 317 - isnt_definer(schema, func) should have the proper description +ok 318 - isnt_definer(schema, func) should have the proper diagnostics +ok 319 - is_definer(schema, func, 0 args, desc) should pass +ok 320 - is_definer(schema, func, 0 args, desc) should have the proper description +ok 321 - is_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 322 - isnt_definer(schema, func, 0 args, desc) should fail +ok 323 - isnt_definer(schema, func, 0 args, desc) should have the proper description +ok 324 - isnt_definer(schema, func, 0 args, desc) should have the proper diagnostics +ok 325 - is_definer(schema, func, 0 args) should pass +ok 326 - is_definer(schema, func, 0 args) should have the proper description +ok 327 - is_definer(schema, func, 0 args) should have the proper diagnostics +ok 328 - isnt_definer(schema, func, 0 args) should fail +ok 329 - isnt_definer(schema, func, 0 args) should have the proper description +ok 330 - isnt_definer(schema, func, 0 args) should have the proper diagnostics +ok 331 - is_definer(schema, func, args, desc) should fail +ok 332 - is_definer(schema, func, args, desc) should have the proper description +ok 333 - is_definer(schema, func, args, desc) should have the proper diagnostics +ok 334 - isnt_definer(schema, func, args, desc) should pass +ok 335 - isnt_definer(schema, func, args, desc) should have the proper description +ok 336 - isnt_definer(schema, func, args, desc) should have the proper diagnostics +ok 337 - is_definer(schema, func, args) should fail +ok 338 - is_definer(schema, func, args) should have the proper description +ok 339 - is_definer(schema, func, args) should have the proper diagnostics +ok 340 - isnt_definer(schema, func, args) should pass +ok 341 - isnt_definer(schema, func, args) should have the proper description +ok 342 - isnt_definer(schema, func, args) should have the proper diagnostics +ok 343 - is_definer(schema, func, desc) should pass +ok 344 - is_definer(schema, func, desc) should have the proper description +ok 345 - is_definer(schema, func, desc) should have the proper diagnostics +ok 346 - isnt_definer(schema, func, desc) should fail +ok 347 - isnt_definer(schema, func, desc) should have the proper description +ok 348 - isnt_definer(schema, func, desc) should have the proper diagnostics +ok 349 - is_definer(schema, func) should pass +ok 350 - is_definer(schema, func) should have the proper description +ok 351 - is_definer(schema, func) should have the proper diagnostics +ok 352 - isnt_definer(schema, func) should fail +ok 353 - isnt_definer(schema, func) should have the proper description +ok 354 - isnt_definer(schema, func) should have the proper diagnostics +ok 355 - is_definer(func, 0 args, desc) should pass +ok 356 - is_definer(func, 0 args, desc) should have the proper description +ok 357 - is_definer(func, 0 args, desc) should have the proper diagnostics +ok 358 - isnt_definer(func, 0 args, desc) should fail +ok 359 - isnt_definer(func, 0 args, desc) should have the proper description +ok 360 - isnt_definer(func, 0 args, desc) should have the proper diagnostics +ok 361 - is_definer(func, 0 args) should pass +ok 362 - is_definer(func, 0 args) should have the proper description +ok 363 - is_definer(func, 0 args) should have the proper diagnostics +ok 364 - isnt_definer(func, 0 args) should fail +ok 365 - isnt_definer(func, 0 args) should have the proper description +ok 366 - isnt_definer(func, 0 args) should have the proper diagnostics +ok 367 - is_definer(func, args, desc) should fail +ok 368 - is_definer(func, args, desc) should have the proper description +ok 369 - is_definer(func, args, desc) should have the proper diagnostics +ok 370 - isnt_definer(func, args, desc) should pass +ok 371 - isnt_definer(func, args, desc) should have the proper description +ok 372 - isnt_definer(func, args, desc) should have the proper diagnostics +ok 373 - is_definer(func, args) should fail +ok 374 - is_definer(func, args) should have the proper description +ok 375 - is_definer(func, args) should have the proper diagnostics +ok 376 - isnt_definer(func, args) should pass +ok 377 - isnt_definer(func, args) should have the proper description +ok 378 - isnt_definer(func, args) should have the proper diagnostics +ok 379 - is_definer(func, desc) should pass +ok 380 - is_definer(func, desc) should have the proper description +ok 381 - is_definer(func, desc) should have the proper diagnostics +ok 382 - isnt_definer(func, desc) should fail +ok 383 - isnt_definer(func, desc) should have the proper description +ok 384 - isnt_definer(func, desc) should have the proper diagnostics +ok 385 - is_definer(func) should pass +ok 386 - is_definer(func) should have the proper description +ok 387 - is_definer(func) should have the proper diagnostics +ok 388 - isnt_definer(func) should fail +ok 389 - isnt_definer(func) should have the proper description +ok 390 - isnt_definer(func) should have the proper diagnostics +ok 391 - is_definer(sch, proc) should fail +ok 392 - is_definer(sch, proc) should have the proper description +ok 393 - is_definer(sch, proc) should have the proper diagnostics +ok 394 - isnt_definer(sch, proc) should pass +ok 395 - isnt_definer(sch, proc) should have the proper description +ok 396 - isnt_definer(sch, proc) should have the proper diagnostics +ok 397 - is_definer(proc) should fail +ok 398 - is_definer(proc) should have the proper description +ok 399 - is_definer(proc) should have the proper diagnostics +ok 400 - isnt_definer(proc) should pass +ok 401 - isnt_definer(proc) should have the proper description +ok 402 - isnt_definer(proc) should have the proper diagnostics +ok 403 - is_normal_function(schema, func, noargs, desc) should pass +ok 404 - is_normal_function(schema, func, noargs, desc) should have the proper description +ok 405 - is_normal_function(schema, func, noargs, desc) should have the proper diagnostics +ok 406 - is_normal_function(schema, agg, arg, desc) should fail +ok 407 - is_normal_function(schema, agg, arg, desc) should have the proper description +ok 408 - is_normal_function(schema, agg, arg, desc) should have the proper diagnostics +ok 409 - isnt_normal_function(schema, func, noargs, desc) should fail +ok 410 - isnt_normal_function(schema, func, noargs, desc) should have the proper description +ok 411 - isnt_normal_function(schema, func, noargs, desc) should have the proper diagnostics +ok 412 - isnt_normal_function(schema, agg, noargs, desc) should pass +ok 413 - isnt_normal_function(schema, agg, noargs, desc) should have the proper description +ok 414 - isnt_normal_function(schema, agg, noargs, desc) should have the proper diagnostics +ok 415 - is_normal_function(schema, func, args, desc) should pass +ok 416 - is_normal_function(schema, func, args, desc) should have the proper description +ok 417 - is_normal_function(schema, func, args, desc) should have the proper diagnostics +ok 418 - is_normal_function(schema, agg, args, desc) should fail +ok 419 - is_normal_function(schema, agg, args, desc) should have the proper description +ok 420 - is_normal_function(schema, agg, args, desc) should have the proper diagnostics +ok 421 - is_normal_function(schema, func, args, desc) should fail +ok 422 - is_normal_function(schema, func, args, desc) should have the proper description +ok 423 - is_normal_function(schema, func, args, desc) should have the proper diagnostics +ok 424 - isnt_normal_function(schema, agg, args, desc) should pass +ok 425 - isnt_normal_function(schema, agg, args, desc) should have the proper description +ok 426 - isnt_normal_function(schema, agg, args, desc) should have the proper diagnostics +ok 427 - is_normal_function(schema, nofunc, noargs, desc) should fail +ok 428 - is_normal_function(schema, nofunc, noargs, desc) should have the proper description +ok 429 - is_normal_function(schema, nofunc, noargs, desc) should have the proper diagnostics +ok 430 - isnt_normal_function(schema, noagg, args, desc) should fail +ok 431 - isnt_normal_function(schema, noagg, args, desc) should have the proper description +ok 432 - isnt_normal_function(schema, noagg, args, desc) should have the proper diagnostics +ok 433 - is_normal_function(schema, func, noargs) should pass +ok 434 - is_normal_function(schema, func, noargs) should have the proper description +ok 435 - is_normal_function(schema, func, noargs) should have the proper diagnostics +ok 436 - is_normal_function(schema, agg, noargs) should fail +ok 437 - is_normal_function(schema, agg, noargs) should have the proper description +ok 438 - is_normal_function(schema, agg, noargs) should have the proper diagnostics +ok 439 - isnt_normal_function(schema, func, noargs) should fail +ok 440 - isnt_normal_function(schema, func, noargs) should have the proper description +ok 441 - isnt_normal_function(schema, func, noargs) should have the proper diagnostics +ok 442 - isnt_normal_function(schema, agg, noargs) should pass +ok 443 - isnt_normal_function(schema, agg, noargs) should have the proper description +ok 444 - isnt_normal_function(schema, agg, noargs) should have the proper diagnostics +ok 445 - is_normal_function(schema, func2, args) should pass +ok 446 - is_normal_function(schema, func2, args) should have the proper description +ok 447 - is_normal_function(schema, func2, args) should have the proper diagnostics +ok 448 - isnt_normal_function(schema, func2, args) should fail +ok 449 - isnt_normal_function(schema, func2, args) should have the proper description +ok 450 - isnt_normal_function(schema, func2, args) should have the proper diagnostics +ok 451 - is_normal_function(schema, func, noargs) should fail +ok 452 - is_normal_function(schema, func, noargs) should have the proper description +ok 453 - is_normal_function(schema, func, noargs) should have the proper diagnostics +ok 454 - is_normal_function(schema, nofunc, noargs) should fail +ok 455 - is_normal_function(schema, nofunc, noargs) should have the proper description +ok 456 - is_normal_function(schema, nofunc, noargs) should have the proper diagnostics +ok 457 - is_normal_function(schema, func, desc) should pass +ok 458 - is_normal_function(schema, func, desc) should have the proper description +ok 459 - is_normal_function(schema, func, desc) should have the proper diagnostics +ok 460 - is_normal_function(schema, agg, desc) should fail +ok 461 - is_normal_function(schema, agg, desc) should have the proper description +ok 462 - is_normal_function(schema, agg, desc) should have the proper diagnostics +ok 463 - isnt_normal_function(schema, func, desc) should fail +ok 464 - isnt_normal_function(schema, func, desc) should have the proper description +ok 465 - isnt_normal_function(schema, func, desc) should have the proper diagnostics +ok 466 - isnt_normal_function(schema, agg, desc) should pass +ok 467 - isnt_normal_function(schema, agg, desc) should have the proper description +ok 468 - isnt_normal_function(schema, agg, desc) should have the proper diagnostics +ok 469 - is_normal_function(schema, func2, desc) should pass +ok 470 - is_normal_function(schema, func2, desc) should have the proper description +ok 471 - is_normal_function(schema, func2, desc) should have the proper diagnostics +ok 472 - isnt_normal_function(schema, func2, desc) should fail +ok 473 - isnt_normal_function(schema, func2, desc) should have the proper description +ok 474 - isnt_normal_function(schema, func2, desc) should have the proper diagnostics +ok 475 - is_normal_function(schema, nofunc, desc) should fail +ok 476 - is_normal_function(schema, nofunc, desc) should have the proper description +ok 477 - is_normal_function(schema, nofunc, desc) should have the proper diagnostics +ok 478 - is_normal_function(schema, noagg, desc) should fail +ok 479 - is_normal_function(schema, noagg, desc) should have the proper description +ok 480 - is_normal_function(schema, noagg, desc) should have the proper diagnostics +ok 481 - is_normal_function(schema, func) should pass +ok 482 - is_normal_function(schema, func) should have the proper description +ok 483 - is_normal_function(schema, func) should have the proper diagnostics +ok 484 - is_normal_function(schema, agg) should fail +ok 485 - is_normal_function(schema, agg) should have the proper description +ok 486 - is_normal_function(schema, agg) should have the proper diagnostics +ok 487 - isnt_normal_function(schema, func) should fail +ok 488 - isnt_normal_function(schema, func) should have the proper description +ok 489 - isnt_normal_function(schema, func) should have the proper diagnostics +ok 490 - isnt_normal_function(schema, agg) should pass +ok 491 - isnt_normal_function(schema, agg) should have the proper description +ok 492 - isnt_normal_function(schema, agg) should have the proper diagnostics +ok 493 - is_normal_function(schema, func2, args) should pass +ok 494 - is_normal_function(schema, func2, args) should have the proper description +ok 495 - is_normal_function(schema, func2, args) should have the proper diagnostics +ok 496 - isnt_normal_function(schema, func2) should fail +ok 497 - isnt_normal_function(schema, func2) should have the proper description +ok 498 - isnt_normal_function(schema, func2) should have the proper diagnostics +ok 499 - is_normal_function(schema, nofunc) should fail +ok 500 - is_normal_function(schema, nofunc) should have the proper description +ok 501 - is_normal_function(schema, nofunc) should have the proper diagnostics +ok 502 - is_normal_function(schema, nogg) should fail +ok 503 - is_normal_function(schema, nogg) should have the proper description +ok 504 - is_normal_function(schema, nogg) should have the proper diagnostics +ok 505 - is_normal_function(func, noargs, desc) should pass +ok 506 - is_normal_function(func, noargs, desc) should have the proper description +ok 507 - is_normal_function(func, noargs, desc) should have the proper diagnostics +ok 508 - is_normal_function(func, agg, desc) should fail +ok 509 - is_normal_function(func, agg, desc) should have the proper description +ok 510 - is_normal_function(func, agg, desc) should have the proper diagnostics +ok 511 - isnt_normal_function(func, noargs, desc) should fail +ok 512 - isnt_normal_function(func, noargs, desc) should have the proper description +ok 513 - isnt_normal_function(func, noargs, desc) should have the proper diagnostics +ok 514 - isnt_normal_function(func, agg, desc) should pass +ok 515 - isnt_normal_function(func, agg, desc) should have the proper description +ok 516 - isnt_normal_function(func, agg, desc) should have the proper diagnostics +ok 517 - is_normal_function(func, args, desc) should pass +ok 518 - is_normal_function(func, args, desc) should have the proper description +ok 519 - is_normal_function(func, args, desc) should have the proper diagnostics +ok 520 - isnt_normal_function(func, args, desc) should fail +ok 521 - isnt_normal_function(func, args, desc) should have the proper description +ok 522 - isnt_normal_function(func, args, desc) should have the proper diagnostics +ok 523 - is_normal_function(nofunc, noargs, desc) should fail +ok 524 - is_normal_function(nofunc, noargs, desc) should have the proper description +ok 525 - is_normal_function(nofunc, noargs, desc) should have the proper diagnostics +ok 526 - is_normal_function(func, noagg, desc) should fail +ok 527 - is_normal_function(func, noagg, desc) should have the proper description +ok 528 - is_normal_function(func, noagg, desc) should have the proper diagnostics +ok 529 - is_normal_function(func, noargs) should pass +ok 530 - is_normal_function(func, noargs) should have the proper description +ok 531 - is_normal_function(func, noargs) should have the proper diagnostics +ok 532 - isnt_normal_function(func, noargs) should fail +ok 533 - isnt_normal_function(func, noargs) should have the proper description +ok 534 - isnt_normal_function(func, noargs) should have the proper diagnostics +ok 535 - is_normal_function(func, noargs) should pass +ok 536 - is_normal_function(func, noargs) should have the proper description +ok 537 - is_normal_function(func, noargs) should have the proper diagnostics +ok 538 - isnt_normal_function(func, noargs) should fail +ok 539 - isnt_normal_function(func, noargs) should have the proper description +ok 540 - isnt_normal_function(func, noargs) should have the proper diagnostics +ok 541 - is_normal_function(nofunc, noargs) should fail +ok 542 - is_normal_function(nofunc, noargs) should have the proper description +ok 543 - is_normal_function(nofunc, noargs) should have the proper diagnostics +ok 544 - isnt_normal_function(fnounc, noargs) should fail +ok 545 - isnt_normal_function(fnounc, noargs) should have the proper description +ok 546 - isnt_normal_function(fnounc, noargs) should have the proper diagnostics +ok 547 - is_normal_function(func, desc) should pass +ok 548 - is_normal_function(func, desc) should have the proper description +ok 549 - is_normal_function(func, desc) should have the proper diagnostics +ok 550 - is_normal_function(agg, desc) should fail +ok 551 - is_normal_function(agg, desc) should have the proper description +ok 552 - is_normal_function(agg, desc) should have the proper diagnostics +ok 553 - isnt_normal_function(func, desc) should fail +ok 554 - isnt_normal_function(func, desc) should have the proper description +ok 555 - isnt_normal_function(func, desc) should have the proper diagnostics +ok 556 - isnt_normal_function(agg, desc) should pass +ok 557 - isnt_normal_function(agg, desc) should have the proper description +ok 558 - isnt_normal_function(agg, desc) should have the proper diagnostics +ok 559 - is_normal_function(func2, desc) should pass +ok 560 - is_normal_function(func2, desc) should have the proper description +ok 561 - is_normal_function(func2, desc) should have the proper diagnostics +ok 562 - isnt_normal_function(func2, desc) should fail +ok 563 - isnt_normal_function(func2, desc) should have the proper description +ok 564 - isnt_normal_function(func2, desc) should have the proper diagnostics +ok 565 - is_normal_function(nofunc, desc) should fail +ok 566 - is_normal_function(nofunc, desc) should have the proper description +ok 567 - is_normal_function(nofunc, desc) should have the proper diagnostics +ok 568 - is_normal_function(noagg, desc) should fail +ok 569 - is_normal_function(noagg, desc) should have the proper description +ok 570 - is_normal_function(noagg, desc) should have the proper diagnostics +ok 571 - is_normal_function(func) should pass +ok 572 - is_normal_function(func) should have the proper description +ok 573 - is_normal_function(func) should have the proper diagnostics +ok 574 - is_normal_function(agg) should fail +ok 575 - is_normal_function(agg) should have the proper description +ok 576 - is_normal_function(agg) should have the proper diagnostics +ok 577 - isnt_normal_function(func) should fail +ok 578 - isnt_normal_function(func) should have the proper description +ok 579 - isnt_normal_function(func) should have the proper diagnostics +ok 580 - isnt_normal_function(agg) should pass +ok 581 - isnt_normal_function(agg) should have the proper description +ok 582 - isnt_normal_function(agg) should have the proper diagnostics +ok 583 - is_normal_function(func2) should pass +ok 584 - is_normal_function(func2) should have the proper description +ok 585 - is_normal_function(func2) should have the proper diagnostics +ok 586 - isnt_normal_function(func2,) should fail +ok 587 - isnt_normal_function(func2,) should have the proper description +ok 588 - isnt_normal_function(func2,) should have the proper diagnostics +ok 589 - is_normal_function(nofunc) should fail +ok 590 - is_normal_function(nofunc) should have the proper description +ok 591 - is_normal_function(nofunc) should have the proper diagnostics +ok 592 - is_normal_function(noagg) should fail +ok 593 - is_normal_function(noagg) should have the proper description +ok 594 - is_normal_function(noagg) should have the proper diagnostics +ok 595 - is_normal_function(schema, proc) should fail +ok 596 - is_normal_function(schema, proc) should have the proper description +ok 597 - is_normal_function(schema, proc) should have the proper diagnostics +ok 598 - isnt_normal_function(schema, proc) should pass +ok 599 - isnt_normal_function(schema, proc) should have the proper description +ok 600 - isnt_normal_function(schema, proc) should have the proper diagnostics +ok 601 - is_normal_function(proc) should fail +ok 602 - is_normal_function(proc) should have the proper description +ok 603 - is_normal_function(proc) should have the proper diagnostics +ok 604 - isnt_normal_function(proc) should pass +ok 605 - isnt_normal_function(proc) should have the proper description +ok 606 - isnt_normal_function(proc) should have the proper diagnostics +ok 607 - is_aggregate(schema, func, arg, desc) should pass +ok 608 - is_aggregate(schema, func, arg, desc) should have the proper description +ok 609 - is_aggregate(schema, func, arg, desc) should have the proper diagnostics +ok 610 - isnt_aggregate(schema, agg, arg, desc) should fail +ok 611 - isnt_aggregate(schema, agg, arg, desc) should have the proper description +ok 612 - isnt_aggregate(schema, agg, arg, desc) should have the proper diagnostics +ok 613 - is_aggregate(schema, func, args, desc) should fail +ok 614 - is_aggregate(schema, func, args, desc) should have the proper description +ok 615 - is_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 616 - isnt_aggregate(schema, func, args, desc) should pass +ok 617 - isnt_aggregate(schema, func, args, desc) should have the proper description +ok 618 - isnt_aggregate(schema, func, args, desc) should have the proper diagnostics +ok 619 - is_aggregate(schema, nofunc, arg, desc) should fail +ok 620 - is_aggregate(schema, nofunc, arg, desc) should have the proper description +ok 621 - is_aggregate(schema, nofunc, arg, desc) should have the proper diagnostics +ok 622 - isnt_aggregate(schema, noagg, arg, desc) should fail +ok 623 - isnt_aggregate(schema, noagg, arg, desc) should have the proper description +ok 624 - isnt_aggregate(schema, noagg, arg, desc) should have the proper diagnostics +ok 625 - is_aggregate(schema, agg, arg) should pass +ok 626 - is_aggregate(schema, agg, arg) should have the proper description +ok 627 - is_aggregate(schema, agg, arg) should have the proper diagnostics +ok 628 - isnt_aggregate(schema, agg, arg) should fail +ok 629 - isnt_aggregate(schema, agg, arg) should have the proper description +ok 630 - isnt_aggregate(schema, agg, arg) should have the proper diagnostics +ok 631 - is_aggregate(schema, func, args) should fail +ok 632 - is_aggregate(schema, func, args) should have the proper description +ok 633 - is_aggregate(schema, func, args) should have the proper diagnostics +ok 634 - isnt_aggregate(schema, func, args) should pass +ok 635 - isnt_aggregate(schema, func, args) should have the proper description +ok 636 - isnt_aggregate(schema, func, args) should have the proper diagnostics +ok 637 - is_aggregate(schema, noagg, arg) should fail +ok 638 - is_aggregate(schema, noagg, arg) should have the proper description +ok 639 - is_aggregate(schema, noagg, arg) should have the proper diagnostics +ok 640 - isnt_aggregate(schema, noagg, arg) should fail +ok 641 - isnt_aggregate(schema, noagg, arg) should have the proper description +ok 642 - isnt_aggregate(schema, noagg, arg) should have the proper diagnostics +ok 643 - is_aggregate(schema, agg, desc) should pass +ok 644 - is_aggregate(schema, agg, desc) should have the proper description +ok 645 - is_aggregate(schema, agg, desc) should have the proper diagnostics +ok 646 - isnt_aggregate(schema, agg, desc) should fail +ok 647 - isnt_aggregate(schema, agg, desc) should have the proper description +ok 648 - isnt_aggregate(schema, agg, desc) should have the proper diagnostics +ok 649 - is_aggregate(schema, noagg, desc) should fail +ok 650 - is_aggregate(schema, noagg, desc) should have the proper description +ok 651 - is_aggregate(schema, noagg, desc) should have the proper diagnostics +ok 652 - isnt_aggregate(schema, noagg, desc) should fail +ok 653 - isnt_aggregate(schema, noagg, desc) should have the proper description +ok 654 - isnt_aggregate(schema, noagg, desc) should have the proper diagnostics +ok 655 - is_aggregate(schema, agg) should pass +ok 656 - is_aggregate(schema, agg) should have the proper description +ok 657 - is_aggregate(schema, agg) should have the proper diagnostics +ok 658 - isnt_aggregate(schema, agg) should fail +ok 659 - isnt_aggregate(schema, agg) should have the proper description +ok 660 - isnt_aggregate(schema, agg) should have the proper diagnostics +ok 661 - is_aggregate(schema, noagg) should fail +ok 662 - is_aggregate(schema, noagg) should have the proper description +ok 663 - is_aggregate(schema, noagg) should have the proper diagnostics +ok 664 - isnt_aggregate(schema, noagg) should fail +ok 665 - isnt_aggregate(schema, noagg) should have the proper description +ok 666 - isnt_aggregate(schema, noagg) should have the proper diagnostics +ok 667 - is_aggregate(agg, arg, desc) should pass +ok 668 - is_aggregate(agg, arg, desc) should have the proper description +ok 669 - is_aggregate(agg, arg, desc) should have the proper diagnostics +ok 670 - isnt_aggregate(agg, arg, desc) should fail +ok 671 - isnt_aggregate(agg, arg, desc) should have the proper description +ok 672 - isnt_aggregate(agg, arg, desc) should have the proper diagnostics +ok 673 - is_aggregate(func, args, desc) should fail +ok 674 - is_aggregate(func, args, desc) should have the proper description +ok 675 - is_aggregate(func, args, desc) should have the proper diagnostics +ok 676 - isnt_aggregate(func, args, desc) should pass +ok 677 - isnt_aggregate(func, args, desc) should have the proper description +ok 678 - isnt_aggregate(func, args, desc) should have the proper diagnostics +ok 679 - is_aggregate(noagg, arg, desc) should fail +ok 680 - is_aggregate(noagg, arg, desc) should have the proper description +ok 681 - is_aggregate(noagg, arg, desc) should have the proper diagnostics +ok 682 - isnt_aggregate(noagg, arg, desc) should fail +ok 683 - isnt_aggregate(noagg, arg, desc) should have the proper description +ok 684 - isnt_aggregate(noagg, arg, desc) should have the proper diagnostics +ok 685 - is_aggregate(agg, arg) should pass +ok 686 - is_aggregate(agg, arg) should have the proper description +ok 687 - is_aggregate(agg, arg) should have the proper diagnostics +ok 688 - isnt_aggregate(agg, arg) should fail +ok 689 - isnt_aggregate(agg, arg) should have the proper description +ok 690 - isnt_aggregate(agg, arg) should have the proper diagnostics +ok 691 - is_aggregate(func, args) should fail +ok 692 - is_aggregate(func, args) should have the proper description +ok 693 - is_aggregate(func, args) should have the proper diagnostics +ok 694 - isnt_aggregate(func, args) should pass +ok 695 - isnt_aggregate(func, args) should have the proper description +ok 696 - isnt_aggregate(func, args) should have the proper diagnostics +ok 697 - is_aggregate(noagg, arg) should fail +ok 698 - is_aggregate(noagg, arg) should have the proper description +ok 699 - is_aggregate(noagg, arg) should have the proper diagnostics +ok 700 - isnt_aggregate(noagg, arg) should fail +ok 701 - isnt_aggregate(noagg, arg) should have the proper description +ok 702 - isnt_aggregate(noagg, arg) should have the proper diagnostics +ok 703 - is_aggregate(func, desc) should pass +ok 704 - is_aggregate(func, desc) should have the proper description +ok 705 - is_aggregate(func, desc) should have the proper diagnostics +ok 706 - isnt_aggregate(agg, desc) should fail +ok 707 - isnt_aggregate(agg, desc) should have the proper description +ok 708 - isnt_aggregate(agg, desc) should have the proper diagnostics +ok 709 - is_aggregate(nofunc, desc) should fail +ok 710 - is_aggregate(nofunc, desc) should have the proper description +ok 711 - is_aggregate(nofunc, desc) should have the proper diagnostics +ok 712 - isnt_aggregate(noagg, desc) should fail +ok 713 - isnt_aggregate(noagg, desc) should have the proper description +ok 714 - isnt_aggregate(noagg, desc) should have the proper diagnostics +ok 715 - is_aggregate(agg) should pass +ok 716 - is_aggregate(agg) should have the proper description +ok 717 - is_aggregate(agg) should have the proper diagnostics +ok 718 - isnt_aggregate(agg) should fail +ok 719 - isnt_aggregate(agg) should have the proper description +ok 720 - isnt_aggregate(agg) should have the proper diagnostics +ok 721 - is_aggregate(noagg) should fail +ok 722 - is_aggregate(noagg) should have the proper description +ok 723 - is_aggregate(noagg) should have the proper diagnostics +ok 724 - isnt_aggregate(noagg) should fail +ok 725 - isnt_aggregate(noagg) should have the proper description +ok 726 - isnt_aggregate(noagg) should have the proper diagnostics +ok 727 - is_aggregate(schema, proc) should fail +ok 728 - is_aggregate(schema, proc) should have the proper description +ok 729 - is_aggregate(schema, proc) should have the proper diagnostics +ok 730 - is_aggregate(schema, proc) should pass +ok 731 - is_aggregate(schema, proc) should have the proper description +ok 732 - is_aggregate(schema, proc) should have the proper diagnostics +ok 733 - is_aggregate(proc) should fail +ok 734 - is_aggregate(proc) should have the proper description +ok 735 - is_aggregate(proc) should have the proper diagnostics +ok 736 - is_aggregate(proc) should pass +ok 737 - is_aggregate(proc) should have the proper description +ok 738 - is_aggregate(proc) should have the proper diagnostics +ok 739 - is_window(schema, win, arg, desc) should pass +ok 740 - is_window(schema, win, arg, desc) should have the proper description +ok 741 - is_window(schema, win, arg, desc) should have the proper diagnostics +ok 742 - isnt_window(schema, win, arg, desc) should fail +ok 743 - isnt_window(schema, win, arg, desc) should have the proper description +ok 744 - isnt_window(schema, win, arg, desc) should have the proper diagnostics +ok 745 - is_window(schema, func, arg, desc) should fail +ok 746 - is_window(schema, func, arg, desc) should have the proper description +ok 747 - is_window(schema, func, arg, desc) should have the proper diagnostics +ok 748 - isnt_window(schema, func, arg, desc) should pass +ok 749 - isnt_window(schema, func, arg, desc) should have the proper description +ok 750 - isnt_window(schema, func, arg, desc) should have the proper diagnostics +ok 751 - is_window(schema, win, noargs, desc) should pass +ok 752 - is_window(schema, win, noargs, desc) should have the proper description +ok 753 - is_window(schema, win, noargs, desc) should have the proper diagnostics +ok 754 - isnt_window(schema, win, noargs, desc) should fail +ok 755 - isnt_window(schema, win, noargs, desc) should have the proper description +ok 756 - isnt_window(schema, win, noargs, desc) should have the proper diagnostics +ok 757 - is_window(schema, func, noarg, desc) should fail +ok 758 - is_window(schema, func, noarg, desc) should have the proper description +ok 759 - is_window(schema, func, noarg, desc) should have the proper diagnostics +ok 760 - is_window(schema, win, noargs, desc) should fail +ok 761 - is_window(schema, win, noargs, desc) should have the proper description +ok 762 - is_window(schema, win, noargs, desc) should have the proper diagnostics +ok 763 - is_window(schema, nowin, arg, desc) should fail +ok 764 - is_window(schema, nowin, arg, desc) should have the proper description +ok 765 - is_window(schema, nowin, arg, desc) should have the proper diagnostics +ok 766 - isnt_window(schema, nowin, arg, desc) should fail +ok 767 - isnt_window(schema, nowin, arg, desc) should have the proper description +ok 768 - isnt_window(schema, nowin, arg, desc) should have the proper diagnostics +ok 769 - is_window(schema, win, arg) should pass +ok 770 - is_window(schema, win, arg) should have the proper description +ok 771 - is_window(schema, win, arg) should have the proper diagnostics +ok 772 - isnt_window(schema, win, arg) should fail +ok 773 - isnt_window(schema, win, arg) should have the proper description +ok 774 - isnt_window(schema, win, arg) should have the proper diagnostics +ok 775 - is_window(schema, func, arg) should fail +ok 776 - is_window(schema, func, arg) should have the proper description +ok 777 - is_window(schema, func, arg) should have the proper diagnostics +ok 778 - isnt_window(schema, func, arg) should pass +ok 779 - isnt_window(schema, func, arg) should have the proper description +ok 780 - isnt_window(schema, func, arg) should have the proper diagnostics +ok 781 - is_window(schema, win, noargs) should pass +ok 782 - is_window(schema, win, noargs) should have the proper description +ok 783 - is_window(schema, win, noargs) should have the proper diagnostics +ok 784 - isnt_window(schema, win, noargs) should fail +ok 785 - isnt_window(schema, win, noargs) should have the proper description +ok 786 - isnt_window(schema, win, noargs) should have the proper diagnostics +ok 787 - is_window(schema, func, noarg) should fail +ok 788 - is_window(schema, func, noarg) should have the proper description +ok 789 - is_window(schema, func, noarg) should have the proper diagnostics +ok 790 - isnt_window(schema, win, noargs) should fail +ok 791 - isnt_window(schema, win, noargs) should have the proper description +ok 792 - isnt_window(schema, win, noargs) should have the proper diagnostics +ok 793 - is_window(schema, nowin, arg) should fail +ok 794 - is_window(schema, nowin, arg) should have the proper description +ok 795 - is_window(schema, nowin, arg) should have the proper diagnostics +ok 796 - isnt_window(schema, nowin, arg) should fail +ok 797 - isnt_window(schema, nowin, arg) should have the proper description +ok 798 - isnt_window(schema, nowin, arg) should have the proper diagnostics +ok 799 - is_window(schema, win, desc) should pass +ok 800 - is_window(schema, win, desc) should have the proper description +ok 801 - is_window(schema, win, desc) should have the proper diagnostics +ok 802 - isnt_window(schema, win, desc) should fail +ok 803 - isnt_window(schema, win, desc) should have the proper description +ok 804 - isnt_window(schema, win, desc) should have the proper diagnostics +ok 805 - is_window(schema, func, desc) should fail +ok 806 - is_window(schema, func, desc) should have the proper description +ok 807 - is_window(schema, func, desc) should have the proper diagnostics +ok 808 - isnt_window(schema, func, desc) should pass +ok 809 - isnt_window(schema, func, desc) should have the proper description +ok 810 - isnt_window(schema, func, desc) should have the proper diagnostics +ok 811 - is_window(schema, func, desc) should fail +ok 812 - is_window(schema, func, desc) should have the proper description +ok 813 - is_window(schema, func, desc) should have the proper diagnostics +ok 814 - isnt_window(schema, win, desc) should fail +ok 815 - isnt_window(schema, win, desc) should have the proper description +ok 816 - isnt_window(schema, win, desc) should have the proper diagnostics +ok 817 - is_window(schema, nowin, desc) should fail +ok 818 - is_window(schema, nowin, desc) should have the proper description +ok 819 - is_window(schema, nowin, desc) should have the proper diagnostics +ok 820 - isnt_window(schema, nowin, desc) should fail +ok 821 - isnt_window(schema, nowin, desc) should have the proper description +ok 822 - isnt_window(schema, nowin, desc) should have the proper diagnostics +ok 823 - is_window(schema, win) should pass +ok 824 - is_window(schema, win) should have the proper description +ok 825 - is_window(schema, win) should have the proper diagnostics +ok 826 - isnt_window(schema, win) should fail +ok 827 - isnt_window(schema, win) should have the proper description +ok 828 - isnt_window(schema, win) should have the proper diagnostics +ok 829 - is_window(schema, func) should fail +ok 830 - is_window(schema, func) should have the proper description +ok 831 - is_window(schema, func) should have the proper diagnostics +ok 832 - isnt_window(schema, func) should pass +ok 833 - isnt_window(schema, func) should have the proper description +ok 834 - isnt_window(schema, func) should have the proper diagnostics +ok 835 - is_window(schema, nowin) should fail +ok 836 - is_window(schema, nowin) should have the proper description +ok 837 - is_window(schema, nowin) should have the proper diagnostics +ok 838 - isnt_window(schema, nowin) should fail +ok 839 - isnt_window(schema, nowin) should have the proper description +ok 840 - isnt_window(schema, nowin) should have the proper diagnostics +ok 841 - is_window(win, arg, desc) should pass +ok 842 - is_window(win, arg, desc) should have the proper description +ok 843 - is_window(win, arg, desc) should have the proper diagnostics +ok 844 - isnt_window(win, arg, desc) should fail +ok 845 - isnt_window(win, arg, desc) should have the proper description +ok 846 - isnt_window(win, arg, desc) should have the proper diagnostics +ok 847 - is_window(func, arg, desc) should fail +ok 848 - is_window(func, arg, desc) should have the proper description +ok 849 - is_window(func, arg, desc) should have the proper diagnostics +ok 850 - isnt_window(func, arg, desc) should pass +ok 851 - isnt_window(func, arg, desc) should have the proper description +ok 852 - isnt_window(func, arg, desc) should have the proper diagnostics +ok 853 - is_window(win, noargs, desc) should pass +ok 854 - is_window(win, noargs, desc) should have the proper description +ok 855 - is_window(win, noargs, desc) should have the proper diagnostics +ok 856 - isnt_window(win, noargs, desc) should fail +ok 857 - isnt_window(win, noargs, desc) should have the proper description +ok 858 - isnt_window(win, noargs, desc) should have the proper diagnostics +ok 859 - is_window(func, noarg, desc) should fail +ok 860 - is_window(func, noarg, desc) should have the proper description +ok 861 - is_window(func, noarg, desc) should have the proper diagnostics +ok 862 - isnt_window(win, noargs, desc) should fail +ok 863 - isnt_window(win, noargs, desc) should have the proper description +ok 864 - isnt_window(win, noargs, desc) should have the proper diagnostics +ok 865 - is_window(nowin, arg, desc) should fail +ok 866 - is_window(nowin, arg, desc) should have the proper description +ok 867 - is_window(nowin, arg, desc) should have the proper diagnostics +ok 868 - isnt_window(nowin, arg, desc) should fail +ok 869 - isnt_window(nowin, arg, desc) should have the proper description +ok 870 - isnt_window(nowin, arg, desc) should have the proper diagnostics +ok 871 - is_window(win, arg, desc) should pass +ok 872 - is_window(win, arg, desc) should have the proper description +ok 873 - is_window(win, arg, desc) should have the proper diagnostics +ok 874 - isnt_window(win, arg, desc) should fail +ok 875 - isnt_window(win, arg, desc) should have the proper description +ok 876 - isnt_window(win, arg, desc) should have the proper diagnostics +ok 877 - is_window(func, arg, desc) should fail +ok 878 - is_window(func, arg, desc) should have the proper description +ok 879 - is_window(func, arg, desc) should have the proper diagnostics +ok 880 - isnt_window(func, arg, desc) should pass +ok 881 - isnt_window(func, arg, desc) should have the proper description +ok 882 - isnt_window(func, arg, desc) should have the proper diagnostics +ok 883 - is_window(win, noargs, desc) should pass +ok 884 - is_window(win, noargs, desc) should have the proper description +ok 885 - is_window(win, noargs, desc) should have the proper diagnostics +ok 886 - isnt_window(win, noargs, desc) should fail +ok 887 - isnt_window(win, noargs, desc) should have the proper description +ok 888 - isnt_window(win, noargs, desc) should have the proper diagnostics +ok 889 - is_window(func, noarg, desc) should fail +ok 890 - is_window(func, noarg, desc) should have the proper description +ok 891 - is_window(func, noarg, desc) should have the proper diagnostics +ok 892 - isnt_window(win, noargs, desc) should fail +ok 893 - isnt_window(win, noargs, desc) should have the proper description +ok 894 - isnt_window(win, noargs, desc) should have the proper diagnostics +ok 895 - is_window(nowin, arg, desc) should fail +ok 896 - is_window(nowin, arg, desc) should have the proper description +ok 897 - is_window(nowin, arg, desc) should have the proper diagnostics +ok 898 - isnt_window(nowin, arg, desc) should fail +ok 899 - isnt_window(nowin, arg, desc) should have the proper description +ok 900 - isnt_window(nowin, arg, desc) should have the proper diagnostics +ok 901 - is_window(win, desc) should pass +ok 902 - is_window(win, desc) should have the proper description +ok 903 - is_window(win, desc) should have the proper diagnostics +ok 904 - isnt_window(win, desc) should fail +ok 905 - isnt_window(win, desc) should have the proper description +ok 906 - isnt_window(win, desc) should have the proper diagnostics +ok 907 - is_window(func, desc) should fail +ok 908 - is_window(func, desc) should have the proper description +ok 909 - is_window(func, desc) should have the proper diagnostics +ok 910 - isnt_window(func, desc) should pass +ok 911 - isnt_window(func, desc) should have the proper description +ok 912 - isnt_window(func, desc) should have the proper diagnostics +ok 913 - is_window(func, desc) should fail +ok 914 - is_window(func, desc) should have the proper description +ok 915 - is_window(func, desc) should have the proper diagnostics +ok 916 - is_window(nowin, desc) should fail +ok 917 - is_window(nowin, desc) should have the proper description +ok 918 - is_window(nowin, desc) should have the proper diagnostics +ok 919 - isnt_window(nowin, desc) should fail +ok 920 - isnt_window(nowin, desc) should have the proper description +ok 921 - isnt_window(nowin, desc) should have the proper diagnostics +ok 922 - is_window(win) should pass +ok 923 - is_window(win) should have the proper description +ok 924 - is_window(win) should have the proper diagnostics +ok 925 - isnt_window(win) should fail +ok 926 - isnt_window(win) should have the proper description +ok 927 - isnt_window(win) should have the proper diagnostics +ok 928 - is_window(func) should fail +ok 929 - is_window(func) should have the proper description +ok 930 - is_window(func) should have the proper diagnostics +ok 931 - isnt_window(func) should pass +ok 932 - isnt_window(func) should have the proper description +ok 933 - isnt_window(func) should have the proper diagnostics +ok 934 - is_window(nowin) should fail +ok 935 - is_window(nowin) should have the proper description +ok 936 - is_window(nowin) should have the proper diagnostics +ok 937 - isnt_window(nowin) should fail +ok 938 - isnt_window(nowin) should have the proper description +ok 939 - isnt_window(nowin) should have the proper diagnostics +ok 940 - is_window(schema, proc) should fail +ok 941 - is_window(schema, proc) should have the proper description +ok 942 - is_window(schema, proc) should have the proper diagnostics +ok 943 - is_window(schema, proc) should pass +ok 944 - is_window(schema, proc) should have the proper description +ok 945 - is_window(schema, proc) should have the proper diagnostics +ok 946 - is_window(proc) should fail +ok 947 - is_window(proc) should have the proper description +ok 948 - is_window(proc) should have the proper diagnostics +ok 949 - is_window(proc) should pass +ok 950 - is_window(proc) should have the proper description +ok 951 - is_window(proc) should have the proper diagnostics +ok 952 - is_strict(schema, func, 0 args, desc) should pass +ok 953 - is_strict(schema, func, 0 args, desc) should have the proper description +ok 954 - is_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 955 - isnt_strict(schema, func, 0 args, desc) should fail +ok 956 - isnt_strict(schema, func, 0 args, desc) should have the proper description +ok 957 - isnt_strict(schema, func, 0 args, desc) should have the proper diagnostics +ok 958 - is_strict(schema, func, 0 args) should pass +ok 959 - is_strict(schema, func, 0 args) should have the proper description +ok 960 - is_strict(schema, func, 0 args) should have the proper diagnostics +ok 961 - isnt_strict(schema, func, 0 args) should fail +ok 962 - isnt_strict(schema, func, 0 args) should have the proper description +ok 963 - isnt_strict(schema, func, 0 args) should have the proper diagnostics +ok 964 - is_strict(schema, func, args, desc) should fail +ok 965 - is_strict(schema, func, args, desc) should have the proper description +ok 966 - is_strict(schema, func, args, desc) should have the proper diagnostics +ok 967 - isnt_strict(schema, func, args, desc) should pass +ok 968 - isnt_strict(schema, func, args, desc) should have the proper description +ok 969 - isnt_strict(schema, func, args, desc) should have the proper diagnostics +ok 970 - is_strict(schema, func, args) should fail +ok 971 - is_strict(schema, func, args) should have the proper description +ok 972 - is_strict(schema, func, args) should have the proper diagnostics +ok 973 - isnt_strict(schema, func, args) should pass +ok 974 - isnt_strict(schema, func, args) should have the proper description +ok 975 - isnt_strict(schema, func, args) should have the proper diagnostics +ok 976 - is_strict(schema, func, desc) should pass +ok 977 - is_strict(schema, func, desc) should have the proper description +ok 978 - is_strict(schema, func, desc) should have the proper diagnostics +ok 979 - isnt_strict(schema, func, desc) should fail +ok 980 - isnt_strict(schema, func, desc) should have the proper description +ok 981 - isnt_strict(schema, func, desc) should have the proper diagnostics +ok 982 - is_strict(schema, func) should pass +ok 983 - is_strict(schema, func) should have the proper description +ok 984 - is_strict(schema, func) should have the proper diagnostics +ok 985 - isnt_strict(schema, func) should fail +ok 986 - isnt_strict(schema, func) should have the proper description +ok 987 - isnt_strict(schema, func) should have the proper diagnostics +ok 988 - isnt_strict(schema, func, args, desc) should pass +ok 989 - isnt_strict(schema, func, args, desc) should have the proper description +ok 990 - isnt_strict(schema, func, args, desc) should have the proper diagnostics +ok 991 - isnt_strict(schema, func, args) should pass +ok 992 - isnt_strict(schema, func, args) should have the proper description +ok 993 - isnt_strict(schema, func, args) should have the proper diagnostics +ok 994 - is_strict(func, 0 args, desc) should pass +ok 995 - is_strict(func, 0 args, desc) should have the proper description +ok 996 - is_strict(func, 0 args, desc) should have the proper diagnostics +ok 997 - isnt_strict(func, 0 args, desc) should fail +ok 998 - isnt_strict(func, 0 args, desc) should have the proper description +ok 999 - isnt_strict(func, 0 args, desc) should have the proper diagnostics +ok 1000 - is_strict(func, 0 args) should pass +ok 1001 - is_strict(func, 0 args) should have the proper description +ok 1002 - is_strict(func, 0 args) should have the proper diagnostics +ok 1003 - isnt_strict(func, 0 args) should fail +ok 1004 - isnt_strict(func, 0 args) should have the proper description +ok 1005 - isnt_strict(func, 0 args) should have the proper diagnostics +ok 1006 - is_strict(func, args, desc) should fail +ok 1007 - is_strict(func, args, desc) should have the proper description +ok 1008 - is_strict(func, args, desc) should have the proper diagnostics +ok 1009 - isnt_strict(func, args, desc) should pass +ok 1010 - isnt_strict(func, args, desc) should have the proper description +ok 1011 - isnt_strict(func, args, desc) should have the proper diagnostics +ok 1012 - is_strict(func, args) should fail +ok 1013 - is_strict(func, args) should have the proper description +ok 1014 - is_strict(func, args) should have the proper diagnostics +ok 1015 - isnt_strict(func, args) should pass +ok 1016 - isnt_strict(func, args) should have the proper description +ok 1017 - isnt_strict(func, args) should have the proper diagnostics +ok 1018 - is_strict(func, desc) should pass +ok 1019 - is_strict(func, desc) should have the proper description +ok 1020 - is_strict(func, desc) should have the proper diagnostics +ok 1021 - isnt_strict(func, desc) should fail +ok 1022 - isnt_strict(func, desc) should have the proper description +ok 1023 - isnt_strict(func, desc) should have the proper diagnostics +ok 1024 - is_strict(func) should pass +ok 1025 - is_strict(func) should have the proper description +ok 1026 - is_strict(func) should have the proper diagnostics +ok 1027 - isnt_strict(func) should fail +ok 1028 - isnt_strict(func) should have the proper description +ok 1029 - isnt_strict(func) should have the proper diagnostics +ok 1030 - is_strict(sch, proc) should fail +ok 1031 - is_strict(sch, proc) should have the proper description +ok 1032 - is_strict(sch, proc) should have the proper diagnostics +ok 1033 - isnt_strict(sch, proc) should pass +ok 1034 - isnt_strict(sch, proc) should have the proper description +ok 1035 - isnt_strict(sch, proc) should have the proper diagnostics +ok 1036 - is_strict(proc) should fail +ok 1037 - is_strict(proc) should have the proper description +ok 1038 - is_strict(proc) should have the proper diagnostics +ok 1039 - isnt_strict(proc) should pass +ok 1040 - isnt_strict(proc) should have the proper description +ok 1041 - isnt_strict(proc) should have the proper diagnostics +ok 1042 - function_volatility(schema, func, 0 args, volatile, desc) should pass +ok 1043 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper description +ok 1044 - function_volatility(schema, func, 0 args, volatile, desc) should have the proper diagnostics +ok 1045 - function_volatility(schema, func, 0 args, v, desc) should pass +ok 1046 - function_volatility(schema, func, 0 args, v, desc) should have the proper description +ok 1047 - function_volatility(schema, func, 0 args, v, desc) should have the proper diagnostics +ok 1048 - function_volatility(schema, func, args, immutable, desc) should pass +ok 1049 - function_volatility(schema, func, args, immutable, desc) should have the proper description +ok 1050 - function_volatility(schema, func, args, immutable, desc) should have the proper diagnostics +ok 1051 - function_volatility(schema, func, 0 args, stable, desc) should pass +ok 1052 - function_volatility(schema, func, 0 args, stable, desc) should have the proper description +ok 1053 - function_volatility(schema, func, 0 args, stable, desc) should have the proper diagnostics +ok 1054 - function_volatility(schema, func, 0 args, volatile) should pass +ok 1055 - function_volatility(schema, func, 0 args, volatile) should have the proper description +ok 1056 - function_volatility(schema, func, 0 args, volatile) should have the proper diagnostics +ok 1057 - function_volatility(schema, func, args, immutable) should pass +ok 1058 - function_volatility(schema, func, args, immutable) should have the proper description +ok 1059 - function_volatility(schema, func, volatile, desc) should pass +ok 1060 - function_volatility(schema, func, volatile, desc) should have the proper description +ok 1061 - function_volatility(schema, func, volatile, desc) should have the proper diagnostics +ok 1062 - function_volatility(schema, func, volatile) should pass +ok 1063 - function_volatility(schema, func, volatile) should have the proper description +ok 1064 - function_volatility(schema, func, volatile) should have the proper diagnostics +ok 1065 - function_volatility(schema, func, immutable, desc) should pass +ok 1066 - function_volatility(schema, func, immutable, desc) should have the proper description +ok 1067 - function_volatility(schema, func, immutable, desc) should have the proper diagnostics +ok 1068 - function_volatility(schema, func, stable, desc) should pass +ok 1069 - function_volatility(schema, func, stable, desc) should have the proper description +ok 1070 - function_volatility(schema, func, stable, desc) should have the proper diagnostics +ok 1071 - function_volatility(func, 0 args, volatile, desc) should pass +ok 1072 - function_volatility(func, 0 args, volatile, desc) should have the proper description +ok 1073 - function_volatility(func, 0 args, volatile, desc) should have the proper diagnostics +ok 1074 - function_volatility(func, 0 args, v, desc) should pass +ok 1075 - function_volatility(func, 0 args, v, desc) should have the proper description +ok 1076 - function_volatility(func, 0 args, v, desc) should have the proper diagnostics +ok 1077 - function_volatility(func, args, immutable, desc) should pass +ok 1078 - function_volatility(func, args, immutable, desc) should have the proper description +ok 1079 - function_volatility(func, args, immutable, desc) should have the proper diagnostics +ok 1080 - function_volatility(func, 0 args, stable, desc) should pass +ok 1081 - function_volatility(func, 0 args, stable, desc) should have the proper description +ok 1082 - function_volatility(func, 0 args, stable, desc) should have the proper diagnostics +ok 1083 - function_volatility(func, 0 args, volatile) should pass +ok 1084 - function_volatility(func, 0 args, volatile) should have the proper description +ok 1085 - function_volatility(func, 0 args, volatile) should have the proper diagnostics +ok 1086 - function_volatility(func, args, immutable) should pass +ok 1087 - function_volatility(func, args, immutable) should have the proper description +ok 1088 - function_volatility(func, volatile, desc) should pass +ok 1089 - function_volatility(func, volatile, desc) should have the proper description +ok 1090 - function_volatility(func, volatile, desc) should have the proper diagnostics +ok 1091 - function_volatility(func, volatile) should pass +ok 1092 - function_volatility(func, volatile) should have the proper description +ok 1093 - function_volatility(func, volatile) should have the proper diagnostics +ok 1094 - function_volatility(func, immutable, desc) should pass +ok 1095 - function_volatility(func, immutable, desc) should have the proper description +ok 1096 - function_volatility(func, immutable, desc) should have the proper diagnostics +ok 1097 - function_volatility(func, stable, desc) should pass +ok 1098 - function_volatility(func, stable, desc) should have the proper description +ok 1099 - function_volatility(func, stable, desc) should have the proper diagnostics +ok 1100 - function_volatility(sch, proc, volatile) should pass +ok 1101 - function_volatility(sch, proc, volatile) should have the proper description +ok 1102 - function_volatility(sch, proc, volatile) should have the proper diagnostics +ok 1103 - function_volatility(proc, volatile) should pass +ok 1104 - function_volatility(proc, volatile) should have the proper description +ok 1105 - function_volatility(proc, volatile) should have the proper diagnostics diff --git a/test/sql/aretap.sql b/test/sql/aretap.sql index 7306432be95f..c2b41ccc5d49 100644 --- a/test/sql/aretap.sql +++ b/test/sql/aretap.sql @@ -1,5 +1,6 @@ \unset ECHO \i test/setup.sql +-- \i sql/pgtap.sql SELECT plan(459); --SELECT * FROM no_plan(); @@ -82,6 +83,19 @@ CREATE TYPE someschema."myType" AS ( foo INT ); +-- Create a procedure. +DO $$ +BEGIN + IF pg_version_num() >= 110000 THEN + EXECUTE 'CREATE PROCEDURE someschema.someproc(int) LANGUAGE SQL AS '''''; + ELSE + CREATE FUNCTION someschema.someproc(int) + RETURNS void AS '' + LANGUAGE SQL; + END IF; +END; +$$; + RESET client_min_messages; /****************************************************************************/ @@ -497,7 +511,7 @@ SELECT * FROM check_test( -- Test functions_are(). SELECT * FROM check_test( - functions_are( 'someschema', ARRAY['yip', 'yap'], 'whatever' ), + functions_are( 'someschema', ARRAY['yip', 'yap', 'someproc'], 'whatever' ), true, 'functions_are(schema, functions, desc)', 'whatever', @@ -505,7 +519,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - functions_are( 'someschema', ARRAY['yip', 'yap'] ), + functions_are( 'someschema', ARRAY['yip', 'yap', 'someproc'] ), true, 'functions_are(schema, functions)', 'Schema someschema should have the correct functions' @@ -513,7 +527,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - functions_are( 'someschema', ARRAY['yip', 'yap', 'yop'], 'whatever' ), + functions_are( 'someschema', ARRAY['yip', 'yap', 'someproc', 'yop'], 'whatever' ), false, 'functions_are(schema, functions, desc) + missing', 'whatever', @@ -522,7 +536,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - functions_are( 'someschema', ARRAY['yip'], 'whatever' ), + functions_are( 'someschema', ARRAY['yip', 'someproc'], 'whatever' ), false, 'functions_are(schema, functions, desc) + extra', 'whatever', @@ -531,7 +545,7 @@ SELECT * FROM check_test( ); SELECT * FROM check_test( - functions_are( 'someschema', ARRAY['yap', 'yop'], 'whatever' ), + functions_are( 'someschema', ARRAY['yap', 'yop', 'someproc'], 'whatever' ), false, 'functions_are(schema, functions, desc) + extra & missing', 'whatever', diff --git a/test/sql/functap.sql b/test/sql/functap.sql index 0bcba9eefe74..cb77e7b9b5f9 100644 --- a/test/sql/functap.sql +++ b/test/sql/functap.sql @@ -1,7 +1,8 @@ \unset ECHO \i test/setup.sql +-- \i sql/pgtap.sql -SELECT plan(1009); +SELECT plan(1105); -- SELECT * FROM no_plan(); CREATE SCHEMA someschema; @@ -44,6 +45,14 @@ BEGIN RETURNS text AS 'SELECT ''anyelement''::text' LANGUAGE SQL IMMUTABLE; END IF; + + IF pg_version_num() >= 110000 THEN + EXECUTE 'CREATE PROCEDURE public.someproc(int) LANGUAGE SQL AS '''''; + ELSE + CREATE FUNCTION public.someproc(int) + RETURNS void AS '' + LANGUAGE SQL; + END IF; END; $$; @@ -207,6 +216,23 @@ SELECT * FROM check_test( '' -- No diagnostics. ); +-- Test with a procedure. +SELECT * FROM check_test( + has_function( 'public', 'someproc', 'hi' ), + true, + 'public procedure', + 'hi', + '' +); + +SELECT * FROM check_test( + has_function( 'someproc' ), + true, + 'public procedure', + 'Function someproc() should exist', + '' +); + /****************************************************************************/ -- Test hasnt_function(). SELECT * FROM check_test( @@ -313,6 +339,23 @@ SELECT * FROM check_test( '' ); +-- Test with a procedure. +SELECT * FROM check_test( + hasnt_function( 'public', 'someproc', 'hi' ), + false, + 'public procedure', + 'hi', + '' +); + +SELECT * FROM check_test( + hasnt_function( 'someproc' ), + false, + 'public procedure', + 'Function someproc() should not exist', + '' +); + /****************************************************************************/ -- Try can() function names. SELECT * FROM check_test( @@ -374,6 +417,23 @@ SELECT * FROM check_test( bar() missing' ); +-- Try can() with a procedure. +SELECT * FROM check_test( + can( 'public', ARRAY['someproc'], 'whatever' ), + true, + 'can(sch, proc) with desc', + 'whatever', + '' +); + +SELECT * FROM check_test( + can( ARRAY['someproc'], 'whatever' ), + true, + 'can(proc) with desc', + 'whatever', + '' +); + /****************************************************************************/ -- Test function_lang_is(). SELECT * FROM check_test( @@ -556,6 +616,23 @@ SELECT * FROM check_test( ' Function why() does not exist' ); +-- Test with procedure. +SELECT * FROM check_test( + function_lang_is( 'public', 'someproc', 'sql', 'whatever' ), + true, + 'function_lang_is(schema, proc, desc)', + 'whatever', + '' +); + +SELECT * FROM check_test( + function_lang_is( 'someproc', 'sql', 'whatever' ), + true, + 'function_lang_is(schema, proc, desc)', + 'whatever', + '' +); + /****************************************************************************/ -- Test function_returns(). SELECT * FROM check_test( @@ -750,6 +827,23 @@ SELECT * FROM check_test( '' ); +-- Try with a procedure. +SELECT * FROM check_test( + function_returns( 'public', 'someproc'::name, 'void' ), + true, + 'function_returns(sch, proc, void)', + 'Function public.someproc() should return void', + '' +); + +SELECT * FROM check_test( + function_returns( 'someproc'::name, 'void' ), + true, + 'function_returns(sch, proc, void)', + 'Function someproc() should return void', + '' +); + /****************************************************************************/ -- Test is_definer() isnt_definer(). SELECT * FROM check_test( @@ -1040,6 +1134,39 @@ SELECT * FROM check_test( '' ); +-- Try with a procedure. +SELECT * FROM check_test( + is_definer( 'public'::name, 'someproc'::name ), + false, + 'is_definer(sch, proc)', + 'Function public.someproc() should be security definer', + '' +); + +SELECT * FROM check_test( + isnt_definer( 'public'::name, 'someproc'::name ), + true, + 'isnt_definer(sch, proc)', + 'Function public.someproc() should not be security definer', + '' +); + +SELECT * FROM check_test( + is_definer( 'someproc'::name ), + false, + 'is_definer(proc)', + 'Function someproc() should be security definer', + '' +); + +SELECT * FROM check_test( + isnt_definer( 'someproc'::name ), + true, + 'isnt_definer(proc)', + 'Function someproc() should not be security definer', + '' +); + /****************************************************************************/ -- Test is_normal_function() and isnt_normal_function(). @@ -1580,6 +1707,43 @@ SELECT * FROM check_test( ' Function zippo() does not exist' ); +CREATE FUNCTION proc_name() RETURNS TEXT LANGUAGE SQL AS $$ + -- Use an aggregate function in place of the proc on v10 and earlier. + SELECT CASE WHEN pg_version_num() >= 110000 THEN 'someproc' ELSE 'tap_accum' END; +$$; + +SELECT * FROM check_test( + is_normal_function( 'public', proc_name()::name ), + false, + 'is_normal_function(schema, proc)', + format('Function public.%s() should be a normal function', proc_name()), + '' +); + +SELECT * FROM check_test( + isnt_normal_function( 'public', proc_name()::name ), + true, + 'isnt_normal_function(schema, proc)', + format('Function public.%s() should not be a normal function', proc_name()), + '' +); + +SELECT * FROM check_test( + is_normal_function( proc_name()::name ), + false, + 'is_normal_function(proc)', + format('Function %s() should be a normal function', proc_name()), + '' +); + +SELECT * FROM check_test( + isnt_normal_function( proc_name()::name ), + true, + 'isnt_normal_function(proc)', + format('Function %s() should not be a normal function', proc_name()), + '' +); + /****************************************************************************/ -- Test is_aggregate() and isnt_aggregate(). @@ -1927,6 +2091,39 @@ SELECT * FROM check_test( ' Function nope() does not exist' ); +-- Try with a procedure. +SELECT * FROM check_test( + is_aggregate( 'public', 'someproc'::name ), + false, + 'is_aggregate(schema, proc)', + 'Function public.someproc() should be an aggregate function', + '' +); + +SELECT * FROM check_test( + isnt_aggregate( 'public', 'someproc'::name ), + true, + 'is_aggregate(schema, proc)', + 'Function public.someproc() should not be an aggregate function', + '' +); + +SELECT * FROM check_test( + is_aggregate( 'someproc' ), + false, + 'is_aggregate(proc)', + 'Function someproc() should be an aggregate function', + '' +); + +SELECT * FROM check_test( + isnt_aggregate( 'someproc' ), + true, + 'is_aggregate(proc)', + 'Function someproc() should not be an aggregate function', + '' +); + /****************************************************************************/ -- Test is_window() and isnt_window(). @@ -2490,6 +2687,39 @@ SELECT * FROM check_test( ' Function nooo() does not exist' ); +-- Try with a procedure. +SELECT * FROM check_test( + is_window( 'public', 'someproc'::name ), + false, + 'is_window(schema, proc)', + 'Function public.someproc() should be a window function', + '' +); + +SELECT * FROM check_test( + isnt_window( 'public', 'someproc'::name ), + true, + 'is_window(schema, proc)', + 'Function public.someproc() should not be a window function', + '' +); + +SELECT * FROM check_test( + is_window( 'someproc' ), + false, + 'is_window(proc)', + 'Function someproc() should be a window function', + '' +); + +SELECT * FROM check_test( + isnt_window( 'someproc' ), + true, + 'is_window(proc)', + 'Function someproc() should not be a window function', + '' +); + /****************************************************************************/ -- Test is_strict() and isnt_strict(). SELECT * FROM check_test( @@ -2700,6 +2930,39 @@ SELECT * FROM check_test( '' ); +-- Try a procedure (never strict). +SELECT * FROM check_test( + is_strict( 'public', 'someproc'::name ), + false, + 'is_strict(sch, proc)', + 'Function public.someproc() should be strict', + '' +); + +SELECT * FROM check_test( + isnt_strict( 'public', 'someproc'::name ), + true, + 'isnt_strict(sch, proc)', + 'Function public.someproc() should not be strict', + '' +); + +SELECT * FROM check_test( + is_strict( 'someproc'::name ), + false, + 'is_strict(proc)', + 'Function someproc() should be strict', + '' +); + +SELECT * FROM check_test( + isnt_strict( 'someproc'::name ), + true, + 'isnt_strict(proc)', + 'Function someproc() should not be strict', + '' +); + /****************************************************************************/ -- Test volatility_is(). SELECT * FROM check_test( @@ -2862,6 +3125,23 @@ SELECT * FROM check_test( '' ); +-- Test a procedure (always volatile) +SELECT * FROM check_test( + volatility_is( 'public', 'someproc'::name, 'volatile' ), + true, + 'function_volatility(sch, proc, volatile)', + 'Function public.someproc() should be VOLATILE', + '' +); + +SELECT * FROM check_test( + volatility_is( 'someproc'::name, 'volatile' ), + true, + 'function_volatility(proc, volatile)', + 'Function someproc() should be VOLATILE', + '' +); + /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); From 0f62166098716e2de25b532018d50b364ff90cba Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 4 Feb 2024 13:57:28 -0500 Subject: [PATCH 1161/1195] Timestamp v1.3.2 --- Changes | 2 +- contrib/pgtap.spec | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 5c1af86aa45b..a0557fef1d50 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -1.3.2 +1.3.2 2024-02-04T18:59:37Z -------------------------- * Replaced the C `parse_type()` function added in v1.3.1 with a PL/pgSQL diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index aa5cd66748d9..d3fe05e59736 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -41,6 +41,9 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog +* Sun Feb 4 2024 David E. Wheeler 1.3.2-1 +- Update to 1.3.2 + * Sun Sep 24 2023 David E. Wheeler 1.3.1-1 - Update to 1.3.1 From f65094c19f0f025b34609c18c92a89edd37d0e76 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 4 Feb 2024 13:59:20 -0500 Subject: [PATCH 1162/1195] Increment to v1.3.3 --- Changes | 2 ++ META.json | 6 +++--- README.md | 2 +- contrib/pgtap.spec | 2 +- doc/pgtap.mmd | 2 +- pgtap.control | 2 +- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Changes b/Changes index a0557fef1d50..5709c01c9463 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,8 @@ Revision history for pgTAP ========================== +1.3.3 + 1.3.2 2024-02-04T18:59:37Z -------------------------- diff --git a/META.json b/META.json index 82633609920f..ca53bcee9dec 100644 --- a/META.json +++ b/META.json @@ -25,17 +25,17 @@ "pgtap": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap.sql", - "version": "1.3.2" + "version": "1.3.3" }, "pgtap-core": { "abstract": "Unit testing for PostgreSQL", "file": "sql/pgtap-core.sql", - "version": "1.3.2" + "version": "1.3.3" }, "pgtap-schema": { "abstract": "Schema unit testing for PostgreSQL", "file": "sql/pgtap-schema.sql", - "version": "1.3.2" + "version": "1.3.3" } }, "resources": { diff --git a/README.md b/README.md index 0da4546b0a91..2a7cdcf95388 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pgTAP 1.3.2 +pgTAP 1.3.3 ============ [pgTAP](https://pgtap.org) is a unit testing framework for PostgreSQL written diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index d3fe05e59736..617715a97c1e 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -1,6 +1,6 @@ Summary: Unit testing suite for PostgreSQL Name: pgtap -Version: 1.3.2 +Version: 1.3.3 Release: 1%{?dist} Group: Applications/Databases License: PostgreSQL diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 592992ba91b8..576c84a59fb3 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -1,4 +1,4 @@ -pgTAP 1.3.2 +pgTAP 1.3.3 ============ pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and diff --git a/pgtap.control b/pgtap.control index 808219fad925..b977904328d3 100644 --- a/pgtap.control +++ b/pgtap.control @@ -1,6 +1,6 @@ # pgTAP extension comment = 'Unit testing for PostgreSQL' -default_version = '1.3.2' +default_version = '1.3.3' module_pathname = '$libdir/pgtap' requires = 'plpgsql' relocatable = true From 7462b564ef7cac5c7cdf751d4e03c1a134631bdf Mon Sep 17 00:00:00 2001 From: Erik Wienhold Date: Sat, 10 Feb 2024 20:27:33 +0100 Subject: [PATCH 1163/1195] Run qualified typmodin function in format_type_string() Formatting typmodin_func with %I causes a qualified function name to be formatted as a identifier when the namespace is not on the search path. For example, foo.bar() will be formatted as "foo.bar"() when foo is not on the search_path. Calling "foo.bar"() triggers the exception handler in most cases either because the function doesn't exist or because it's not a typmodin function. format_type_string() will then return NULL which in turn results in col_type_is() to fail with an error that the wanted type does not exist. Fix this by formatting the function name with %s. This works because %s emits qualified names and quoted identifiers if necessary for regproc (or any other OID). --- sql/pgtap--1.3.2--1.3.3.sql | 32 ++++++++++++++++++++++++++++++++ sql/pgtap.sql.in | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 sql/pgtap--1.3.2--1.3.3.sql diff --git a/sql/pgtap--1.3.2--1.3.3.sql b/sql/pgtap--1.3.2--1.3.3.sql new file mode 100644 index 000000000000..013670ae6966 --- /dev/null +++ b/sql/pgtap--1.3.2--1.3.3.sql @@ -0,0 +1,32 @@ +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 %s(%L)', typmodin_func, typmodin_arg) INTO typmod; + RETURN format_type(want_type::regtype, typmod); +EXCEPTION WHEN OTHERS THEN RETURN NULL; +END; +$$ LANGUAGE PLPGSQL STABLE; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index e245cbfe2cb4..aa35820961ab 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1488,7 +1488,7 @@ BEGIN END IF; -- Get typemod via type-specific typmodin function. - EXECUTE format('SELECT %I(%L)', typmodin_func, typmodin_arg) INTO typmod; + EXECUTE format('SELECT %s(%L)', typmodin_func, typmodin_arg) INTO typmod; RETURN format_type(want_type::regtype, typmod); EXCEPTION WHEN OTHERS THEN RETURN NULL; END; From 02bc769c92c48d01e4c2f76db6523287017b45a9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 17 Feb 2024 12:55:25 -0500 Subject: [PATCH 1164/1195] Note `col_type_is`/`format_type_string` fix in Changes --- Changes | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Changes b/Changes index 5709c01c9463..dc10942a85cf 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,10 @@ Revision history for pgTAP ========================== 1.3.3 +-------------------------- + +* Fix bug introduced in v1.3.2 where `col_type_is` throws an error when the + type isn't in the search path. Thanks to Erik Wienhold for the PR (#332)! 1.3.2 2024-02-04T18:59:37Z -------------------------- From f5d166b8b4f78a517b859fc40fa2f205a292e04d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 26 Feb 2024 13:01:14 -0500 Subject: [PATCH 1165/1195] Fix a couple of links in the documentation PGUnit seems to have dissappeared, and the GitHub link doesn't need or want the `/tree`. Resolves #333. --- Changes | 3 +++ doc/pgtap.mmd | 11 +++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index dc10942a85cf..faff3e119915 100644 --- a/Changes +++ b/Changes @@ -6,6 +6,9 @@ Revision history for pgTAP * Fix bug introduced in v1.3.2 where `col_type_is` throws an error when the type isn't in the search path. Thanks to Erik Wienhold for the PR (#332)! +* Removed the reference to PGUnit from the docs, as the project seems to have + disappeared. Also the link to the GitHub repo. Thanks to Vick Khera for the + report (#333)! 1.3.2 2024-02-04T18:59:37Z -------------------------- diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index 576c84a59fb3..e25148b9b727 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -382,8 +382,7 @@ What a sweet unit! ------------------ If you're used to xUnit testing frameworks, you can collect all of your tests -into database functions and run them all at once with `runtests()`. This is -similar to how [PGUnit](http://en.dklab.ru/lib/dklab_pgunit/) works. The +into database functions and run them all at once with `runtests()`. The `runtests()` function does all the work of finding and running your test functions in individual transactions. It even supports setup and teardown functions. To use it, write your unit test functions so that they return a set @@ -8779,15 +8778,15 @@ Public Repository ----------------- The source code for pgTAP is available on -[GitHub](https://github.com/theory/pgtap/tree/). Please feel free to fork and +[GitHub](https://github.com/theory/pgtap/). Please feel free to fork and contribute! Mail List --------- -Join the pgTAP community by subscribing to the [pgtap-users mail -list](https://groups.google.com/forum/#!forum/pgtap-users). All questions, -comments, suggestions, and bug reports are welcomed there. +Join the pgTAP community by subscribing to the +[pgtap-users mail list](https://groups.google.com/forum/#!forum/pgtap-users). +All questions, comments, suggestions, and bug reports are welcomed there. Author ------ From b0184e4fe90f2a1a45a74804bca2d5859f6e8867 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sun, 31 Mar 2024 22:12:38 -0400 Subject: [PATCH 1166/1195] Use `to_regtypemod` on Postgres 17 When running on Postgres 17 or higher, take advantage of the new `to_regtypemod` function to much more correctly and efficiently normalize type names, including their type modifiers. Notably, interval types will now also be normalized, so note this fact in the docs. Update the CI testing to test on Postgres 17 and adjust patch offsets for the line count change in `pgtap.sql.in`. --- .github/workflows/test.yml | 7 +++--- Changes | 3 ++- compat/install-9.1.patch | 6 ++--- compat/install-9.2.patch | 2 +- compat/install-9.4.patch | 2 +- compat/install-9.6.patch | 2 +- doc/pgtap.mmd | 14 ++++++----- sql/pgtap--1.3.2--1.3.3.sql | 46 ++++++++++++++++++++++--------------- sql/pgtap.sql.in | 46 ++++++++++++++++++++++--------------- 9 files changed, 76 insertions(+), 52 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3d71ff5602f5..73a5a665f303 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,8 @@ jobs: strategy: matrix: include: - - { version: 16, upgrade_to: "", update_from: 0.99.0 } + - { version: 17, upgrade_to: "", update_from: 0.99.0 } + - { version: 16, upgrade_to: 17, update_from: 0.99.0 } - { version: 15, upgrade_to: 16, update_from: 0.99.0 } - { version: 14, upgrade_to: 15, update_from: 0.99.0 } - { version: 13, upgrade_to: 14, update_from: 0.99.0 } @@ -24,8 +25,8 @@ jobs: - { version: 9.2, upgrade_to: 9.3, update_from: "" } # updatecheck is not supported prior to 9.3 - { version: 9.1, upgrade_to: 9.2, update_from: "" } # updatecheck is not supported prior to 9.3 # Also test pg_upgrade across many versions - - { version: 9.2, upgrade_to: 16, update_from: "", suffix: –16 } - - { version: 9.4, upgrade_to: 16, update_from: "", suffix: –16 } + - { version: 9.2, upgrade_to: 17, update_from: "", suffix: –17 } + - { version: 9.4, upgrade_to: 17, update_from: "", suffix: –17 } name: 🐘 PostgreSQL ${{ matrix.version }}${{ matrix.suffix }} runs-on: ubuntu-latest container: pgxn/pgxn-tools diff --git a/Changes b/Changes index faff3e119915..626bdd8b6d74 100644 --- a/Changes +++ b/Changes @@ -3,7 +3,8 @@ Revision history for pgTAP 1.3.3 -------------------------- - +* Improved the correctness and performance of `col_type_is` on Postgres 17 + thanks to the introduction of the `to_regtypemod()` function. * Fix bug introduced in v1.3.2 where `col_type_is` throws an error when the type isn't in the search path. Thanks to Erik Wienhold for the PR (#332)! * Removed the reference to PGUnit from the docs, as the project seems to have diff --git a/compat/install-9.1.patch b/compat/install-9.1.patch index 5196c6ea6a41..7091f65a33c1 100644 --- a/compat/install-9.1.patch +++ b/compat/install-9.1.patch @@ -11,7 +11,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6688,10 +6684,6 @@ +@@ -6698,10 +6694,6 @@ -- Something went wrong. Record that fact. errstate := SQLSTATE; errmsg := SQLERRM; @@ -22,7 +22,7 @@ END; -- Always raise an exception to rollback any changes. -@@ -7159,7 +7151,6 @@ +@@ -7169,7 +7161,6 @@ RETURN ok( true, $3 ); EXCEPTION WHEN datatype_mismatch THEN @@ -30,7 +30,7 @@ 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' || -@@ -7313,7 +7304,6 @@ +@@ -7323,7 +7314,6 @@ RETURN ok( false, $3 ); EXCEPTION WHEN datatype_mismatch THEN diff --git a/compat/install-9.2.patch b/compat/install-9.2.patch index 38e897479a40..e0bc71f6f5dd 100644 --- a/compat/install-9.2.patch +++ b/compat/install-9.2.patch @@ -14,7 +14,7 @@ RETURN ok( FALSE, descr ) || E'\n' || diag( ' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname) ); -@@ -6696,12 +6691,7 @@ +@@ -6706,12 +6701,7 @@ GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, hint = PG_EXCEPTION_HINT, diff --git a/compat/install-9.4.patch b/compat/install-9.4.patch index 758bb6ed98b1..a2b8bec03089 100644 --- a/compat/install-9.4.patch +++ b/compat/install-9.4.patch @@ -18,7 +18,7 @@ -- There should have been no exception. GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL, -@@ -10240,233 +10240,6 @@ +@@ -10250,233 +10250,6 @@ ), $2); $$ LANGUAGE SQL immutable; diff --git a/compat/install-9.6.patch b/compat/install-9.6.patch index 969c7c356c52..81379cf5eec6 100644 --- a/compat/install-9.6.patch +++ b/compat/install-9.6.patch @@ -1,6 +1,6 @@ --- sql/pgtap.sql +++ sql/pgtap.sql -@@ -10222,136 +10222,6 @@ +@@ -10232,136 +10232,6 @@ ); $$ LANGUAGE sql; diff --git a/doc/pgtap.mmd b/doc/pgtap.mmd index e25148b9b727..cb858927cf5d 100644 --- a/doc/pgtap.mmd +++ b/doc/pgtap.mmd @@ -4520,12 +4520,13 @@ will fail if the table or column in question does not exist. The type argument may be formatted using the full name of the type or any supported alias. For example, if you created a `varchar(64)` column, you can -pass the type as either "varchar(64)" or "character varying(64)". Example: +pass the type as either "varchar(64)" or "character varying(64)". Same deal +for timestamps, as in this example: - SELECT col_type_is( 'myschema', 'sometable', 'somecolumn', 'timespantz(3)' ); + SELECT col_type_is( 'myschema', 'sometable', 'somecolumn', 'timestamptz(3)' ); -The exception to this rule is interval types, which must be specified as -rendered by PostgreSQL itself: +The exception to this rule is interval types prior to Postgres 17, which must +be specified as rendered by PostgreSQL itself: SELECT col_type_is( 'myschema', 'sometable', 'somecolumn', 'interval second(3)' ); @@ -8316,8 +8317,9 @@ the calling `format_type()` with the type OID and type modifier that define the column, but returns a `NULL` on an invalid or missing type, rather than raising an error. Types can be defined by their canonical names or their aliases, e.g., `character varying` or `varchar`. The exception is `interval` -types, which must be specified exactly as Postgres renders them internally, -e.g., `'interval(0)`, `interval second(0)`, or `interval day to second(4)`. +types prior to Postgres 17, which must be specified exactly as Postgres +renders them internally, e.g., `'interval(0)`, `interval second(0)`, or +`interval day to second(4)`. try=# SELECT format_type_string('timestamp(3)'); format_type_string diff --git a/sql/pgtap--1.3.2--1.3.3.sql b/sql/pgtap--1.3.2--1.3.3.sql index 013670ae6966..7b6b7a584ec5 100644 --- a/sql/pgtap--1.3.2--1.3.3.sql +++ b/sql/pgtap--1.3.2--1.3.3.sql @@ -2,31 +2,41 @@ 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 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 - -- RAISE NOTICE 'cannot resolve: %', want_type; -- TODO + -- We cannot normlize interval types without to_regtypemod(), So + -- just return it as is. RETURN want_type; END IF; - -- Extract type modifier from type declaration and format as cstring[] literal. - typmodin_arg := translate(substring(want_type FROM '[(][^")]+[)]'), '()', '{}'); + -- 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; + -- 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; + 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); -EXCEPTION WHEN OTHERS THEN RETURN NULL; + -- 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; diff --git a/sql/pgtap.sql.in b/sql/pgtap.sql.in index aa35820961ab..1a99e4b7bd6f 100644 --- a/sql/pgtap.sql.in +++ b/sql/pgtap.sql.in @@ -1465,32 +1465,42 @@ 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 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 - -- RAISE NOTICE 'cannot resolve: %', want_type; -- TODO + -- We cannot normlize interval types without to_regtypemod(), So + -- just return it as is. RETURN want_type; END IF; - -- Extract type modifier from type declaration and format as cstring[] literal. - typmodin_arg := translate(substring(want_type FROM '[(][^")]+[)]'), '()', '{}'); + -- 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; + -- 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; + 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); -EXCEPTION WHEN OTHERS THEN RETURN NULL; + -- 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; From b941782fada240afdb7057065eb3261a21e8512c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 8 Apr 2024 09:43:45 -0400 Subject: [PATCH 1167/1195] Timestamp v1.3.3 --- Changes | 2 +- META.json | 2 +- contrib/pgtap.spec | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 626bdd8b6d74..7ea88e6ca170 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,7 @@ Revision history for pgTAP ========================== -1.3.3 +1.3.3 2024-04-08T13:44:11Z -------------------------- * Improved the correctness and performance of `col_type_is` on Postgres 17 thanks to the introduction of the `to_regtypemod()` function. diff --git a/META.json b/META.json index ca53bcee9dec..606976f4ea5d 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgTAP", "abstract": "Unit testing for PostgreSQL", "description": "pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions.", - "version": "1.3.2", + "version": "1.3.3", "maintainer": [ "David E. Wheeler ", "pgTAP List " diff --git a/contrib/pgtap.spec b/contrib/pgtap.spec index 617715a97c1e..8d7192f8e49b 100644 --- a/contrib/pgtap.spec +++ b/contrib/pgtap.spec @@ -41,6 +41,9 @@ make install USE_PGXS=1 DESTDIR=%{buildroot} %{_docdir}/pgsql/contrib/README.pgtap %changelog +* Mon Apr 8 2024 David E. Wheeler 1.3.3-1 +- Update to 1.3.3 + * Sun Feb 4 2024 David E. Wheeler 1.3.2-1 - Update to 1.3.2 From 1315a10524d6c1cd3eeeb484157dc63fb926ccef Mon Sep 17 00:00:00 2001 From: siddharth2411 <43139012+siddharth2411@users.noreply.github.com> Date: Wed, 17 Jul 2024 14:40:42 +0530 Subject: [PATCH 1168/1195] [docs] PG compatible logical replication architecture (#23220) Architecture of CDC via logical replication --- .../cdc-logical-replication.md | 80 ++++++++++++++++++ .../docdb-replication/change-data-capture.md | 6 +- .../cdc_service_vwal_interaction.png | Bin 0 -> 185673 bytes .../logical_replication_architecture.png | Bin 0 -> 121939 bytes .../vwal_walsender_interaction.png | Bin 0 -> 181183 bytes 5 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 docs/content/preview/architecture/docdb-replication/cdc-logical-replication.md create mode 100644 docs/static/images/architecture/cdc_service_vwal_interaction.png create mode 100644 docs/static/images/architecture/logical_replication_architecture.png create mode 100644 docs/static/images/architecture/vwal_walsender_interaction.png diff --git a/docs/content/preview/architecture/docdb-replication/cdc-logical-replication.md b/docs/content/preview/architecture/docdb-replication/cdc-logical-replication.md new file mode 100644 index 000000000000..a2d5c5edb4cc --- /dev/null +++ b/docs/content/preview/architecture/docdb-replication/cdc-logical-replication.md @@ -0,0 +1,80 @@ +--- +title: Logical replication in YugabyteDB +headerTitle: CDC - logical replication +linkTitle: CDC - logical replication +description: Learn how YugabyteDB supports asynchronous replication of data changes (inserts, updates, and deletes) to external databases or applications. +headContent: Asynchronous replication of data changes (inserts, updates, and deletes) to external databases or applications +badges: ea +menu: + preview: + parent: architecture-docdb-replication + identifier: architecture-docdb-replication-cdc-logical-replication + weight: 500 +type: docs +--- + +Change data capture (CDC) in YugabyteDB provides technology to ensure that any changes in data due to operations such as inserts, updates, and deletions are identified, captured, and made available for consumption by applications and other tools. + +CDC in YugabyteDB is based on the Postgres Logical Replication model. The fundamental concept here is that of the Replication Slot. A Replication Slot represents a stream of changes that can be replayed to the client in the order they were made on the origin server in a manner that preserves transactional consistency. This is the basis for the support for Transactional CDC in YugabyteDB. Where the strict requirements of Transactional CDC are not present, multiple replication slots can be used to stream changes from unrelated tables in parallel. + +## Architecture + +![Logical-Replication-Architecture](/images/architecture/logical_replication_architecture.png) + +The following are the main components of the Yugabyte CDC solution - + +1. Walsender - A special purpose PG backend responsible for streaming changes to the client and handling acknowledgments. + +2. Virtual WAL (VWAL) - Assembles changes from all the shards of user tables (under the publication) to maintain transactional consistency. + +3. CDCService - Retrieves changes from the WAL of a specified shard starting from a given checkpoint. + +### Data Flow + +Logical replication starts by copying a snapshot of the data on the publisher database. Once that is done, changes on the publisher are streamed to the server as they occur in near real time. + +To setup Logical Replication, an application will first have to create a replication slot. When a replication slot is created a “boundary” is established between the snapshot data and the streaming changes. This “boundary” or “consistent_point” is a consistent state of the source database. It corresponds to a commit time (HybridTime value). Data from transactions with commit time <= commit time corresponding to the consistent_point are consumed as part of the initial snapshot. Changes from transactions with commit time > commit time of the consistent_point are consumed in the streaming phase in transaction commit time order. + +#### Initial Snapshot + +The initial snapshot data for each table is consumed by executing a corresponding snapshot query (SELECT statement) on that table. This snapshot query should be executed as of the database state corresponding to the consistent_point. This database state is represented by a value of HybridTime.  + +First, a `SET LOCAL yb_read_time TO ‘ ht’` command should be executed on the connection (session). The SELECT statement corresponding to the snapshot query should then be executed as part of the same transaction. + +The HybridTime value to use in the `SET LOCAL yb_read_time `command is the value of the `snapshot_name` field that is returned by the `CREATE_REPLICATION_SLOT` command. Alternatively, it can be obtained by querying the `pg_replication_slots` view. + +During Snapshot consumption, the snapshot data from all tables will be from the same consistent state (consistent_point). At the end of Snapshot consumption, the state of the target system is at/based on the consistent_point. History of the tables as of the consistent_point is retained on the source until the snapshot is consumed. + +#### Streaming Data Flow + +YugabyteDB automatically splits user tables into multiple shards (also called tablets) using either a hash- or range-based strategy. The primary key for each row in the table uniquely identifies the location of the tablet in the row. + +Each tablet has its own WAL. WAL is NOT in-memory, but it is disk persisted. Each WAL preserves the information on the changes involved in the transactions (or changes) for that tablet as well as additional metadata related to the transactions. + +**Step 1 - Data flow from the tablets’ WAL to the VWAL** + +![CDCService-VWAL](/images/architecture/cdc_service_vwal_interaction.png) + +Each tablet sends changes in transaction commit time order. Further, within a transaction, the changes are in the order in which the operations were performed in the transaction. + +**Step 2 - Sorting in the VWAL and sending transactions to the Walsender** + +![VWAL-Walsender](/images/architecture/vwal_walsender_interaction.png) + +VWAL collects changes across multiple tablets, assembles the transactions, assigns LSN to each change and transaction boundary (BEGIN, COMMIT) record and sends the changes to the Walsender in transaction commit time order. + +**Step 3 - Walsender to client** + +Walsender sends changes to the output plugin, which filters them according to the slot's publication and converts them into the client's desired format. These changes are then streamed to the client using the appropriate streaming replication protocols determined by the output plugin. Yugabyte follows the same streaming replication protocols as defined in PostgreSQL. + +{{< note title="Note" >}} + +Please refer to [Replication Protocol](../../../explore/logical-replication/#Streaming-Protocol) section for more details. + +{{< /note >}} + +{{< tip title="Explore" >}} + +See [Getting Started with Logical Replication](../../../explore/logical-replication/getting-started) in Explore to setup Logical Replication in YugabyteDB. + +{{< /tip >}} diff --git a/docs/content/preview/architecture/docdb-replication/change-data-capture.md b/docs/content/preview/architecture/docdb-replication/change-data-capture.md index 10f262030ab3..aec5a8ce5b09 100644 --- a/docs/content/preview/architecture/docdb-replication/change-data-capture.md +++ b/docs/content/preview/architecture/docdb-replication/change-data-capture.md @@ -1,7 +1,7 @@ --- title: Change data capture (CDC) in YugabyteDB -headerTitle: Change data capture (CDC) -linkTitle: Change data capture (CDC) +headerTitle: CDC - gRPC replication +linkTitle: CDC - gRPC replication description: Learn how YugabyteDB supports asynchronous replication of data changes (inserts, updates, and deletes) to external databases or applications. badges: ea aliases: @@ -10,7 +10,7 @@ menu: preview: parent: architecture-docdb-replication identifier: architecture-docdb-replication-cdc - weight: 500 + weight: 600 type: docs --- diff --git a/docs/static/images/architecture/cdc_service_vwal_interaction.png b/docs/static/images/architecture/cdc_service_vwal_interaction.png new file mode 100644 index 0000000000000000000000000000000000000000..c0a8005b4537f02e981cd1ad71932326b864874f GIT binary patch literal 185673 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�`T> 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
    JE#3dFJY{uoJ|tEWz;dO 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�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 dCor{(%sV1-AH#gcY}K3Q_ns3-}`(# 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 K=XuX`J^u$<2u@xA literal 0 HcmV?d00001 From 88e92a07ccc08668aa9d50ce7ff59665e3f18846 Mon Sep 17 00:00:00 2001 From: Nikhil Chandrappa Date: Tue, 16 Jul 2024 16:49:48 +0000 Subject: [PATCH 1169/1195] [#23216] yugabyted: Adding a new field ObjectName to model SqlObjectMetadata. Summary: Addding a new field ObjectName to the model SqlObjectMetadata. Jira: DB-12159 Test Plan: Manual Tests Reviewers: djiang Reviewed By: djiang Subscribers: yugabyted-dev Differential Revision: https://phorge.dev.yugabyte.com/D36633 --- yugabyted-ui/apiserver/cmd/server/.docs/api/openapi.yaml | 7 +++++++ yugabyted-ui/apiserver/cmd/server/handlers/api_voyager.go | 1 + .../cmd/server/models/model_sql_object_metadata.go | 2 ++ yugabyted-ui/apiserver/conf/openapi.yml | 2 ++ yugabyted-ui/apiserver/conf/openapi/schemas/_index.yaml | 2 ++ 5 files changed, 14 insertions(+) diff --git a/yugabyted-ui/apiserver/cmd/server/.docs/api/openapi.yaml b/yugabyted-ui/apiserver/cmd/server/.docs/api/openapi.yaml index 9cbe982f307b..a5ccc14946b4 100644 --- a/yugabyted-ui/apiserver/cmd/server/.docs/api/openapi.yaml +++ b/yugabyted-ui/apiserver/cmd/server/.docs/api/openapi.yaml @@ -1725,10 +1725,13 @@ components: description: size, count, iops details of tables and indexes example: size: 1 + object_name: object_name sql_type: sql_type iops: 5 row_count: 6 properties: + object_name: + type: string sql_type: type: string row_count: @@ -1747,10 +1750,12 @@ components: example: sql_objects_metadata: - size: 1 + object_name: object_name sql_type: sql_type iops: 5 row_count: 6 - size: 1 + object_name: object_name sql_type: sql_type iops: 5 row_count: 6 @@ -3418,10 +3423,12 @@ components: data: sql_objects_metadata: - size: 1 + object_name: object_name sql_type: sql_type iops: 5 row_count: 6 - size: 1 + object_name: object_name sql_type: sql_type iops: 5 row_count: 6 diff --git a/yugabyted-ui/apiserver/cmd/server/handlers/api_voyager.go b/yugabyted-ui/apiserver/cmd/server/handlers/api_voyager.go index 4ac0791a4fc0..3f15f551ab72 100644 --- a/yugabyted-ui/apiserver/cmd/server/handlers/api_voyager.go +++ b/yugabyted-ui/apiserver/cmd/server/handlers/api_voyager.go @@ -799,6 +799,7 @@ func getMigrationAssessmentReportFuture(log logger.Logger, migrationUuid string, sqlMetadataList := []models.SqlObjectMetadata{} var sqlMetadata1 models.SqlObjectMetadata + sqlMetadata1.ObjectName = "gemoteric_shape" sqlMetadata1.SqlType = "Table" sqlMetadata1.RowCount = 1000000 sqlMetadata1.Iops = 1000 diff --git a/yugabyted-ui/apiserver/cmd/server/models/model_sql_object_metadata.go b/yugabyted-ui/apiserver/cmd/server/models/model_sql_object_metadata.go index 3529a3bed5c3..dbed661f63c9 100644 --- a/yugabyted-ui/apiserver/cmd/server/models/model_sql_object_metadata.go +++ b/yugabyted-ui/apiserver/cmd/server/models/model_sql_object_metadata.go @@ -3,6 +3,8 @@ package models // SqlObjectMetadata - size, count, iops details of tables and indexes type SqlObjectMetadata struct { + ObjectName string `json:"object_name"` + SqlType string `json:"sql_type"` RowCount int64 `json:"row_count"` diff --git a/yugabyted-ui/apiserver/conf/openapi.yml b/yugabyted-ui/apiserver/conf/openapi.yml index 47a0b7282866..504affb39161 100644 --- a/yugabyted-ui/apiserver/conf/openapi.yml +++ b/yugabyted-ui/apiserver/conf/openapi.yml @@ -1007,6 +1007,8 @@ components: description: size, count, iops details of tables and indexes type: object properties: + object_name: + type: string sql_type: type: string row_count: diff --git a/yugabyted-ui/apiserver/conf/openapi/schemas/_index.yaml b/yugabyted-ui/apiserver/conf/openapi/schemas/_index.yaml index a3655ae898b7..900fc3d17b8a 100644 --- a/yugabyted-ui/apiserver/conf/openapi/schemas/_index.yaml +++ b/yugabyted-ui/apiserver/conf/openapi/schemas/_index.yaml @@ -1184,6 +1184,8 @@ SqlObjectMetadata: description: size, count, iops details of tables and indexes type: object properties: + object_name: + type: string sql_type: type: string row_count: From 7786cf7b85746dcc069b697b5cdf1f0ad3151676 Mon Sep 17 00:00:00 2001 From: kkannan Date: Wed, 17 Jul 2024 10:52:22 +0530 Subject: [PATCH 1170/1195] [PLAT-12072][Platform] Implement Task Details Drawer Summary: Created this new diff , because I'm not able to land the original [[ https://phorge.dev.yugabyte.com/D35615 | one ]] Implemented task details view. Instead of using react query , we use the data from the redux store. Old task components use redux store. We want to make sure we display the same progress across the ui. [[ https://www.figma.com/design/rv2p1rVjhxfUTTjcRgsirF/Long-Running-Progress-Report?node-id=201-4409&m=dev | FIGMA ]] For now , this diff integrates the task details only to the backup. For other Components, we will be doing this in the following tickets. This diff also integrate the before and after task details Test Plan: Tested manually {F265990} {F265991} {F265992} {F265993} {F265994} {F265995} {F265996} Reviewers: lsangappa Reviewed By: lsangappa Subscribers: yugaware Differential Revision: https://phorge.dev.yugabyte.com/D36645 --- managed/ui/src/actions/tasks.js | 15 + .../backupv2/Universe/UniverseLevelBackup.tsx | 9 +- .../backupv2/components/BackupList.scss | 4 + .../backupv2/components/BackupList.tsx | 27 +- .../restore/BackupAndRestoreBanner.tsx | 33 -- .../tasks/TaskList/TaskListTable.js | 37 +- .../components/tasks/TaskList/TasksList.js | 2 +- .../tasks/TaskList/TasksListContainer.js | 3 +- .../UniverseDetail/UniverseDetail.js | 10 +- .../UniverseDetail/UniverseDetail.scss | 3 + .../compounds/UniverseTaskList.tsx | 17 +- .../UniverseStatus/UniverseStatus.js | 18 +- .../UniverseStatus/UniverseStatus.scss | 4 +- .../universes/UniverseView/UniverseView.scss | 6 +- .../YBUniverseItem/YBUniverseItem.js | 6 +- managed/ui/src/redesign/assets/Close-Bold.svg | 5 + .../ui/src/redesign/assets/switch-icon.svg | 5 + .../YBProgress/YBLinearProgress.tsx | 61 +++ .../src/redesign/features/tasks/TaskUtils.ts | 49 ++ .../tasks/components/TaskDetailBanner.tsx | 120 +++++ .../tasks/components/TaskDetailDrawer.tsx | 157 +++++++ .../tasks/components/TaskDetailSimpleComp.tsx | 67 +++ .../tasks/components/TaskDiffModal.tsx | 103 ++++ .../components/bannerComp/BannerStyles.ts | 33 ++ .../bannerComp/TaskFailedBanner.tsx | 84 ++++ .../bannerComp/TaskInProgressBanner.tsx | 83 ++++ .../bannerComp/TaskSuccessBanner.tsx | 59 +++ .../tasks/components/bannerComp/dtos.ts | 16 + .../tasks/components/diffComp/DiffActions.tsx | 36 ++ .../tasks/components/diffComp/DiffBadge.tsx | 81 ++++ .../tasks/components/diffComp/DiffBanners.tsx | 104 ++++ .../tasks/components/diffComp/DiffCard.tsx | 196 ++++++++ .../components/diffComp/DiffCardWrapper.tsx | 24 + .../tasks/components/diffComp/DiffUtils.ts | 75 +++ .../features/tasks/components/diffComp/api.ts | 23 + .../components/diffComp/diffs/BaseDiff.tsx | 11 + .../components/diffComp/diffs/GFlagsDiff.tsx | 128 +++++ .../diffComp/diffs/SoftwareUpgradeDiff.tsx | 54 +++ .../diffComp/diffs/UniverseDiff.tsx | 444 ++++++++++++++++++ .../tasks/components/diffComp/dtos.ts | 34 ++ .../components/drawerComp/SubTaskDetails.tsx | 255 ++++++++++ .../drawerComp/TaskDetailActions.tsx | 173 +++++++ .../drawerComp/TaskDetailHeader.tsx | 48 ++ .../components/drawerComp/TaskDetailInfo.tsx | 135 ++++++ .../drawerComp/TaskDetailProgress.tsx | 68 +++ .../tasks/components/drawerComp/api.ts | 45 ++ .../tasks/components/drawerComp/dtos.ts | 14 + .../ui/src/redesign/features/tasks/dtos.ts | 73 +++ .../ui/src/redesign/features/tasks/index.ts | 10 + .../universe/universe-form/utils/dto.ts | 8 +- managed/ui/src/redesign/helpers/api.ts | 1 - managed/ui/src/reducers/feature.js | 3 +- managed/ui/src/reducers/reducer_tasks.js | 11 +- managed/ui/src/translations/en.json | 73 ++- 54 files changed, 3095 insertions(+), 68 deletions(-) delete mode 100644 managed/ui/src/components/backupv2/restore/BackupAndRestoreBanner.tsx create mode 100644 managed/ui/src/redesign/assets/Close-Bold.svg create mode 100644 managed/ui/src/redesign/assets/switch-icon.svg create mode 100644 managed/ui/src/redesign/components/YBProgress/YBLinearProgress.tsx create mode 100644 managed/ui/src/redesign/features/tasks/TaskUtils.ts create mode 100644 managed/ui/src/redesign/features/tasks/components/TaskDetailBanner.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/TaskDetailDrawer.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/TaskDetailSimpleComp.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/TaskDiffModal.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/bannerComp/BannerStyles.ts create mode 100644 managed/ui/src/redesign/features/tasks/components/bannerComp/TaskFailedBanner.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/bannerComp/TaskInProgressBanner.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/bannerComp/TaskSuccessBanner.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/bannerComp/dtos.ts create mode 100644 managed/ui/src/redesign/features/tasks/components/diffComp/DiffActions.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/diffComp/DiffBadge.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/diffComp/DiffBanners.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/diffComp/DiffCard.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/diffComp/DiffCardWrapper.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/diffComp/DiffUtils.ts create mode 100644 managed/ui/src/redesign/features/tasks/components/diffComp/api.ts create mode 100644 managed/ui/src/redesign/features/tasks/components/diffComp/diffs/BaseDiff.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/diffComp/diffs/GFlagsDiff.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/diffComp/diffs/SoftwareUpgradeDiff.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/diffComp/diffs/UniverseDiff.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/diffComp/dtos.ts create mode 100644 managed/ui/src/redesign/features/tasks/components/drawerComp/SubTaskDetails.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/drawerComp/TaskDetailActions.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/drawerComp/TaskDetailHeader.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/drawerComp/TaskDetailInfo.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/drawerComp/TaskDetailProgress.tsx create mode 100644 managed/ui/src/redesign/features/tasks/components/drawerComp/api.ts create mode 100644 managed/ui/src/redesign/features/tasks/components/drawerComp/dtos.ts create mode 100644 managed/ui/src/redesign/features/tasks/dtos.ts create mode 100644 managed/ui/src/redesign/features/tasks/index.ts diff --git a/managed/ui/src/actions/tasks.js b/managed/ui/src/actions/tasks.js index f976b473c5c1..117d786e37af 100644 --- a/managed/ui/src/actions/tasks.js +++ b/managed/ui/src/actions/tasks.js @@ -19,6 +19,8 @@ export const RETRY_TASK_RESPONSE = 'RETRY_TASK_RESPONSE'; export const ABORT_TASK = 'ABORT_TASK'; export const ABORT_TASK_RESPONSE = 'ABORT_TASK_RESPONSE'; +export const PATCH_TASKS_FOR_CUSTOMER = 'PATCH_TASKS_FOR_CUSTOMER'; + export function fetchTaskProgress(taskUUID) { const request = axios.get(`${getCustomerEndpoint()}/tasks/${taskUUID}`); return { @@ -48,6 +50,19 @@ export function fetchCustomerTasks() { }; } +/** + * used to patch a particular universe's tasks to the list of tasks + */ +export function patchTasksForCustomer(universeUUID, tasks) { + return { + type: PATCH_TASKS_FOR_CUSTOMER, + payload: { + universeUUID, + tasks + } + }; +} + export function fetchCustomerTasksSuccess(result) { return { type: FETCH_CUSTOMER_TASKS_SUCCESS, diff --git a/managed/ui/src/components/backupv2/Universe/UniverseLevelBackup.tsx b/managed/ui/src/components/backupv2/Universe/UniverseLevelBackup.tsx index 611fb9bee633..4901d555a638 100644 --- a/managed/ui/src/components/backupv2/Universe/UniverseLevelBackup.tsx +++ b/managed/ui/src/components/backupv2/Universe/UniverseLevelBackup.tsx @@ -9,12 +9,13 @@ import { FC, useState } from 'react'; import { DropdownButton, MenuItem, Tab } from 'react-bootstrap'; +import { useQuery } from 'react-query'; import { withRouter } from 'react-router'; +import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import { BackupList, Restore } from '..'; import { YBTabsPanel } from '../../panels'; import { ScheduledBackup } from '../scheduled/ScheduledBackup'; -import { BackupAndRestoreBanner } from '../restore/BackupAndRestoreBanner'; import { PointInTimeRecovery } from '../pitr/PointInTimeRecovery'; import { isYbcInstalledInUniverse, getPrimaryCluster } from '../../../utils/UniverseUtils'; import { BackupThrottleParameters } from '../components/BackupThrottleParameters'; @@ -24,13 +25,10 @@ import { RbacValidator } from '../../../redesign/features/rbac/common/RbacApiPer import { Universe } from '../../../redesign/helpers/dtos'; import { compareYBSoftwareVersions } from '../../../utils/universeUtilsTyped'; -import './UniverseLevelBackup.scss'; import { ApiPermissionMap } from '../../../redesign/features/rbac/ApiAndUserPermMapping'; -import { useQuery } from 'react-query'; import { api } from '../../../redesign/helpers/api'; import { YBErrorIndicator, YBLoading } from '../../common/indicators'; -import { useTranslation } from 'react-i18next'; - +import './UniverseLevelBackup.scss'; interface UniverseBackupProps { params: { uuid: string; @@ -89,7 +87,6 @@ const UniverseBackup: FC = ({ params: { uuid } }) => { return ( <> - {featureFlags.test.enableNewAdvancedRestoreModal ? ( showAdvancedRestore && ( = ({ return { ...b, backupUUID: b.commonBackupInfo.backupUUID }; }); + const isBackupNotSucceeded = (state: Backup_States) => [ + Backup_States.IN_PROGRESS, + Backup_States.STOPPING, + Backup_States.FAILED, + Backup_States.FAILED_TO_DELETE, + Backup_States.SKIPPED, + Backup_States.STOPPED + ].includes(state); + if (!isFilterApplied() && backups?.length === 0) { return allowTakingBackup ? ( <> @@ -657,16 +668,24 @@ export const BackupList: FC = ({ row.fullChainSizeInBytes || row.commonBackupInfo.totalBackupSizeInBytes ); }} - width="20%" + width="10%" > Size { - return ; + dataFormat={(lastBackupState, row: IBackup) => { + + return

    ; }} - width="15%" + width="25%" > Last Status diff --git a/managed/ui/src/components/backupv2/restore/BackupAndRestoreBanner.tsx b/managed/ui/src/components/backupv2/restore/BackupAndRestoreBanner.tsx deleted file mode 100644 index 299a8a72f17e..000000000000 --- a/managed/ui/src/components/backupv2/restore/BackupAndRestoreBanner.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Created on Tue Aug 16 2022 - * - * Copyright 2021 YugaByte, Inc. and Contributors - * Licensed under the Polyform Free Trial License 1.0.0 (the "License") - * You may not use this file except in compliance with the License. You may obtain a copy of the License at - * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt - */ -import { useSelector } from 'react-redux'; -import { BACKUP_IN_PROGRESS_MSG, RESTORE_IN_PROGRESS_MSG } from '../common/BackupUtils'; - -export const BackupAndRestoreBanner = () => { - const currentUniverse = useSelector((state: any) => state.universe.currentUniverse); - - if (!currentUniverse) { - return null; - } - - const { - universeDetails: { updateInProgress, updatingTask } - } = currentUniverse.data; - - if (updateInProgress) { - if (updatingTask === 'CreateBackup') { - return BACKUP_IN_PROGRESS_MSG; - } - if (updatingTask === 'RestoreBackup') { - return RESTORE_IN_PROGRESS_MSG; - } - } - - return null; -}; diff --git a/managed/ui/src/components/tasks/TaskList/TaskListTable.js b/managed/ui/src/components/tasks/TaskList/TaskListTable.js index aaa9da0dce05..1369ba30ad14 100644 --- a/managed/ui/src/components/tasks/TaskList/TaskListTable.js +++ b/managed/ui/src/components/tasks/TaskList/TaskListTable.js @@ -4,16 +4,16 @@ import { Component } from 'react'; import PropTypes from 'prop-types'; import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table'; import { Link } from 'react-router'; -import { toast } from 'react-toastify'; import { YBPanelItem } from '../../panels'; import { timeFormatter, successStringFormatter } from '../../../utils/TableFormatters'; -import { YBConfirmModal } from '../../modals'; import { hasNecessaryPerm, RbacValidator } from '../../../redesign/features/rbac/common/RbacApiPermValidator'; import { ApiPermissionMap } from '../../../redesign/features/rbac/ApiAndUserPermMapping'; +import { TaskDetailDrawer } from '../../../redesign/features/tasks'; import { SoftwareUpgradeTaskType } from '../../universes/helpers/universeHelpers'; +import { YBConfirmModal } from '../../modals'; import './TasksList.scss'; export default class TaskListTable extends Component { @@ -25,8 +25,13 @@ export default class TaskListTable extends Component { overrideContent: PropTypes.object }; + state = { + selectedTaskUUID: undefined + } + render() { - const { taskList, title, visibleModal, hideTaskAbortModal, showTaskAbortModal } = this.props; + const { taskList, title, visibleModal, hideTaskAbortModal, showTaskAbortModal, featureFlags } = this.props; + const isNewTaskDetailsUIEnabled = featureFlags?.test?.newTaskDetailsUI || featureFlags?.released?.newTaskDetailsUI; function nameFormatter(cell, row) { return {row.title.replace(/.*:\s*/, '')}; @@ -105,9 +110,18 @@ export default class TaskListTable extends Component { return ; } }; + + const tableBodyContainer = { marginBottom: '1%', paddingBottom: '1%' }; return ( - + + { + this.setState({ + selectedTaskUUID: undefined + }); + }} /> {title}
  • } body={ @@ -118,6 +132,9 @@ export default class TaskListTable extends Component { search multiColumnSearch searchPlaceholder="Search by Name or Type" + options={{ + onRowClick: (task) => isNewTaskDetailsUIEnabled && this.setState({ selectedTaskUUID: task.id }) + }} >
    + - {[...tabElements,
    ]} + { + [ + ...tabElements, +
    + ] + } diff --git a/managed/ui/src/components/universes/UniverseDetail/UniverseDetail.scss b/managed/ui/src/components/universes/UniverseDetail/UniverseDetail.scss index 4354fe4064ec..220622529f28 100644 --- a/managed/ui/src/components/universes/UniverseDetail/UniverseDetail.scss +++ b/managed/ui/src/components/universes/UniverseDetail/UniverseDetail.scss @@ -177,6 +177,9 @@ $padding: 30px; right: 0; position: relative; padding: 0 20px; + @media (max-width: 1490px) { + height: 120px !important; + } @media (max-width: 1120px) { height: fit-content; } diff --git a/managed/ui/src/components/universes/UniverseDetail/compounds/UniverseTaskList.tsx b/managed/ui/src/components/universes/UniverseDetail/compounds/UniverseTaskList.tsx index d9ad1f083b45..60ed31976cb7 100644 --- a/managed/ui/src/components/universes/UniverseDetail/compounds/UniverseTaskList.tsx +++ b/managed/ui/src/components/universes/UniverseDetail/compounds/UniverseTaskList.tsx @@ -1,10 +1,12 @@ import { useTranslation } from 'react-i18next'; import { useQuery, useQueryClient } from 'react-query'; +import { useDispatch, useSelector } from 'react-redux'; import { api, taskQueryKey, universeQueryKey } from '../../../../redesign/helpers/api'; import { YBErrorIndicator, YBLoading } from '../../../common/indicators'; import { TaskListTable, TaskProgressContainer } from '../../../tasks'; import { TASK_SHORT_TIMEOUT } from '../../../tasks/constants'; +import { patchTasksForCustomer } from '../../../../actions/tasks'; interface UniverseTaskListProps { universeUuid: string; @@ -28,10 +30,22 @@ export const UniverseTaskList = ({ }: UniverseTaskListProps) => { const { t } = useTranslation('translation', { keyPrefix: TRANSLATION_KEY_PREFIX }); const queryClient = useQueryClient(); + + const dispatch = useDispatch(); + const featureFlags = useSelector((state: any) => state.featureFlags); + const universeTasksQuery = useQuery( taskQueryKey.universe(universeUuid), () => api.fetchUniverseTasks(universeUuid), - { refetchInterval: TASK_SHORT_TIMEOUT } + { + refetchInterval: TASK_SHORT_TIMEOUT, + select: data => data.data, + onSuccess(data) { + // patch the current universe tasks to the store's list of tasks. + // used to synchronize the react query data with redux data + dispatch(patchTasksForCustomer(universeUuid, data)); + }, + } ); if (universeTasksQuery.isError) { @@ -68,6 +82,7 @@ export const UniverseTaskList = ({ hideTaskAbortModal={hideTaskAbortModal} showTaskAbortModal={showTaskAbortModal} visibleModal={visibleModal} + featureFlags={featureFlags} />
    ); diff --git a/managed/ui/src/components/universes/UniverseStatus/UniverseStatus.js b/managed/ui/src/components/universes/UniverseStatus/UniverseStatus.js index 11c8a99d4e45..25e97dcc51d8 100644 --- a/managed/ui/src/components/universes/UniverseStatus/UniverseStatus.js +++ b/managed/ui/src/components/universes/UniverseStatus/UniverseStatus.js @@ -18,6 +18,7 @@ import { SoftwareUpgradeTaskType } from '../helpers/universeHelpers'; import { UniverseAlertBadge } from '../YBUniverseItem/UniverseAlertBadge'; +import { TaskDetailSimpleComp } from '../../../redesign/features/tasks/components/TaskDetailSimpleComp'; import { RbacValidator } from '../../../redesign/features/rbac/common/RbacApiPermValidator'; import { ApiPermissionMap } from '../../../redesign/features/rbac/ApiAndUserPermMapping'; //icons @@ -30,7 +31,7 @@ export default class UniverseStatus extends Component { const { currentUniverse: { universeUUID, universeDetails }, tasks: { customerTaskList }, - refreshUniverseData + refreshUniverseData, } = this.props; if ( @@ -63,6 +64,7 @@ export default class UniverseStatus extends Component { : browserHistory.push(`/universes/${universeUUID}/tasks`); }; + render() { const { currentUniverse, @@ -70,7 +72,8 @@ export default class UniverseStatus extends Component { tasks: { customerTaskList }, showAlertsBadge, shouldDisplayTaskButton, - runtimeConfigs + runtimeConfigs, + showTaskDetails = false } = this.props; const isRollBackFeatEnabled = @@ -83,6 +86,7 @@ export default class UniverseStatus extends Component { currentUniverse.universeUUID, customerTaskList ); + let taskToDisplayInDrawer = universePendingTask; const universeUpgradeState = _.get(currentUniverse, 'universeDetails.softwareUpgradeState'); let statusDisplay = (
    @@ -110,6 +114,7 @@ export default class UniverseStatus extends Component { ); } if (failedTask?.type === SoftwareUpgradeTaskType.SOFTWARE_UPGRADE) { + taskToDisplayInDrawer = failedTask; statusDisplay = (
    -- @@ -185,6 +190,7 @@ export default class UniverseStatus extends Component { universeStatus.state === UniverseState.WARNING ) { const failedTask = getcurrentUniverseFailedTask(currentUniverse, customerTaskList); + taskToDisplayInDrawer = failedTask; statusDisplay = (
    @@ -249,8 +255,14 @@ export default class UniverseStatus extends Component { } return ( -
    +
    { e.preventDefault(); }}> {statusDisplay} + { showTaskDetails && + [UniverseState.PENDING, UniverseState.BAD].includes(universeStatus.state) && ( + + + ) + } {showAlertsBadge && }
    ); diff --git a/managed/ui/src/components/universes/UniverseStatus/UniverseStatus.scss b/managed/ui/src/components/universes/UniverseStatus/UniverseStatus.scss index e093a5cf9e48..e55aaa8cca8d 100644 --- a/managed/ui/src/components/universes/UniverseStatus/UniverseStatus.scss +++ b/managed/ui/src/components/universes/UniverseStatus/UniverseStatus.scss @@ -106,7 +106,9 @@ } .status-pending-display-container { - display: inline-block; + display: flex; + align-items: baseline; + width: 280px; color: colors.$YB_ORANGE; float: right; diff --git a/managed/ui/src/components/universes/UniverseView/UniverseView.scss b/managed/ui/src/components/universes/UniverseView/UniverseView.scss index 2cf1180b10c7..3555fea76548 100644 --- a/managed/ui/src/components/universes/UniverseView/UniverseView.scss +++ b/managed/ui/src/components/universes/UniverseView/UniverseView.scss @@ -160,7 +160,11 @@ a:hover .universe-name-cell { flex: 0 0 300px; } } - +.new-task-details { + & > *:last-child { + flex: 0 0 410px; + } +} .detail-item-cell { border-right: 1px solid grey; height: 70px; diff --git a/managed/ui/src/components/universes/YBUniverseItem/YBUniverseItem.js b/managed/ui/src/components/universes/YBUniverseItem/YBUniverseItem.js index 1d01f19760c8..0b8376e2260d 100644 --- a/managed/ui/src/components/universes/YBUniverseItem/YBUniverseItem.js +++ b/managed/ui/src/components/universes/YBUniverseItem/YBUniverseItem.js @@ -14,6 +14,7 @@ import { UniverseStatusContainer } from '..'; import { CellLocationPanel } from './CellLocationPanel'; import { CellResourcesPanel } from './CellResourcePanel'; import { timeFormatter } from '../../../utils/TableFormatters'; +import { useIsTaskNewUIEnabled } from '../../../redesign/features/tasks/TaskUtils'; export const YBUniverseItem = (props) => { const { @@ -23,11 +24,11 @@ export const YBUniverseItem = (props) => { } = props; const isPricingKnown = universe.resources?.pricingKnown; const primaryCluster = getPrimaryCluster(universe?.universeDetails?.clusters); - + const isNewTaskDetailsUIEnabled = useIsTaskNewUIEnabled(); return (
    -
    +
    {universe.name}
    @@ -52,6 +53,7 @@ export const YBUniverseItem = (props) => { refreshUniverseData={props.fetchUniverseMetadata} shouldDisplayTaskButton={false} showAlertsBadge={true} + showTaskDetails={true} />
    diff --git a/managed/ui/src/redesign/assets/Close-Bold.svg b/managed/ui/src/redesign/assets/Close-Bold.svg new file mode 100644 index 000000000000..85b7144811d7 --- /dev/null +++ b/managed/ui/src/redesign/assets/Close-Bold.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/managed/ui/src/redesign/assets/switch-icon.svg b/managed/ui/src/redesign/assets/switch-icon.svg new file mode 100644 index 000000000000..55fe62bc265d --- /dev/null +++ b/managed/ui/src/redesign/assets/switch-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/managed/ui/src/redesign/components/YBProgress/YBLinearProgress.tsx b/managed/ui/src/redesign/components/YBProgress/YBLinearProgress.tsx new file mode 100644 index 000000000000..f4155cd2c967 --- /dev/null +++ b/managed/ui/src/redesign/components/YBProgress/YBLinearProgress.tsx @@ -0,0 +1,61 @@ +/* + * Created on Fri Dec 22 2023 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { FC } from 'react'; +import { LinearProgress, withStyles } from '@material-ui/core'; + +export enum YBProgressBarState { + Error = 'error', + Success = 'success', + InProgress = 'inProgress', + Unknown = 'unknown', + Warning = 'Warning' +} + +interface YBProgressProps { + state: YBProgressBarState; + value: number; + height?: number; + width?: number; +} + +export const YBProgress: FC = ({ state, value, height, width }) => { + const Progress = withStyles((theme) => ({ + root: { + height: height ?? 10, + borderRadius: (height ?? 10) / 2, + width: width ?? '100%' + }, + colorPrimary: { + backgroundColor: () => { + return theme.palette.ybacolors.ybBorderGray; + } + }, + bar: { + borderRadius: height ?? 10, + backgroundColor: () => { + switch (state) { + case YBProgressBarState.Error: + return theme.palette.ybacolors.error; + case YBProgressBarState.Success: + return '#13A768'; + case YBProgressBarState.InProgress: + return '#1890FF'; + case YBProgressBarState.Warning: + return theme.palette.ybacolors.warning; + case YBProgressBarState.Unknown: + default: + return theme.palette.ybacolors.ybDarkGray; + } + } + } + }))(LinearProgress); + + return ; +}; diff --git a/managed/ui/src/redesign/features/tasks/TaskUtils.ts b/managed/ui/src/redesign/features/tasks/TaskUtils.ts new file mode 100644 index 000000000000..4d93bba5bd8d --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/TaskUtils.ts @@ -0,0 +1,49 @@ +/* + * Created on Wed May 15 2024 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { useSelector } from 'react-redux'; +import { Task, TaskStates, TaskType } from './dtos'; + +/** + * Checks if a task is currently running. + * @param task - The task object to check. + * @returns A boolean indicating whether the task is running or not. + */ +export const isTaskRunning = (task: Task): boolean => { + return [TaskStates.RUNNING, TaskStates.INITIALIZING, TaskStates.RUNNING].includes(task.status); +}; + +/** + * Checks if a task has failed. + * @param task - The task object to check. + * @returns A boolean indicating whether the task has failed or not. + */ +export const isTaskFailed = (task: Task): boolean => + [TaskStates.FAILURE, TaskStates.ABORTED].includes(task.status); + +/** + * Checks if a task supports before and after data. + * @param task - The task object to check. + * @returns A boolean indicating whether the task supports before/after data or not. + */ +export const doesTaskSupportsDiffData = (task: Task): boolean => { + if (task.type === TaskType.EDIT) { + return task.target === 'Universe'; + } + return [TaskType.GFlags_UPGRADE, TaskType.SOFTWARE_UPGRADE].includes(task.type); +}; + +/** + * Custom hook to check if the new task details UI is enabled. + * @returns A boolean indicating whether the new task details UI is enabled or not. + */ +export function useIsTaskNewUIEnabled(): boolean { + const featureFlags = useSelector((state: any) => state.featureFlags); + return featureFlags?.test?.newTaskDetailsUI || featureFlags?.release?.newTaskDetailsUI; +} diff --git a/managed/ui/src/redesign/features/tasks/components/TaskDetailBanner.tsx b/managed/ui/src/redesign/features/tasks/components/TaskDetailBanner.tsx new file mode 100644 index 000000000000..a9a35b7ac8ae --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/TaskDetailBanner.tsx @@ -0,0 +1,120 @@ +/* + * Created on Thu Dec 21 2023 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { FC, useCallback, useEffect } from 'react'; +import { useSessionStorage, useUnmount } from 'react-use'; +import { useSelector } from 'react-redux'; +import { find, noop } from 'lodash'; +import { TaskDetailDrawer } from './TaskDetailDrawer'; +import { TaskInProgressBanner } from './bannerComp/TaskInProgressBanner'; +import { TaskSuccessBanner } from './bannerComp/TaskSuccessBanner'; +import { TaskFailedBanner } from './bannerComp/TaskFailedBanner'; +import { Task, TaskStates } from '../dtos'; +import { useIsTaskNewUIEnabled } from '../TaskUtils'; + +type TaskDetailBannerProps = { + taskUUID: string; + universeUUID?: string; +}; + +export const TaskDetailBanner: FC = ({ taskUUID, universeUUID }) => { + //We use session storage to prevent the states getting reset to defaults incase of re-rendering. + const [taskID, setTaskID] = useSessionStorage(`taskID`, taskUUID); + + const [showTaskDetailsDrawer, toggleTaskDetailsDrawer] = useSessionStorage( + `show-task-detail`, + false + ); + + // instead of using react query , we use the data from the redux store. + // Old task components use redux store. We want to make sure we display the same progress across the ui. + const taskList = useSelector((data: any) => data.tasks); + + const task: Task | undefined = find(taskList.customerTaskList, { id: taskID }); + + useEffect(() => { + if (taskUUID) { + setTaskID(taskUUID); + } + // if we move away from universe, close the banner + if (!universeUUID) { + setTaskID(null); + } + }, [taskUUID, universeUUID]); + + useUnmount(() => { + setTaskID(null); + }); + + const DrawerComp = ( + { + toggleTaskDetailsDrawer(false); + }} + taskUUID={taskID!} + /> + ); + + // display banner based on type + const bannerComp = useCallback((task: Task) => { + switch (task.status) { + case TaskStates.RUNNING: + return ( + { + toggleTaskDetailsDrawer(true); + }} + onClose={noop} + /> + ); + case TaskStates.SUCCESS: + return ( + { + toggleTaskDetailsDrawer(true); + }} + onClose={() => setTaskID(null)} + /> + ); + case TaskStates.FAILURE: + return ( + { + toggleTaskDetailsDrawer(true); + }} + onClose={() => setTaskID(null)} + /> + ); + default: + return null; + } + }, []); + + const isNewTaskDetailsUIEnabled = useIsTaskNewUIEnabled(); + if (!isNewTaskDetailsUIEnabled) return null; + + if (universeUUID && task?.targetUUID !== universeUUID) return null; + + if (!task && showTaskDetailsDrawer) { + return DrawerComp; + } + + if (!task) return null; + + return ( + <> + {bannerComp(task)} + {DrawerComp} + + ); +}; diff --git a/managed/ui/src/redesign/features/tasks/components/TaskDetailDrawer.tsx b/managed/ui/src/redesign/features/tasks/components/TaskDetailDrawer.tsx new file mode 100644 index 000000000000..47585eb9c9d1 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/TaskDetailDrawer.tsx @@ -0,0 +1,157 @@ +/* + * Created on Wed Dec 20 2023 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { FC, useEffect, useState } from 'react'; +import { useSessionStorage } from 'react-use'; +import { useDispatch, useSelector } from 'react-redux'; +import { find } from 'lodash'; +import { useTranslation } from 'react-i18next'; +import { Snackbar, makeStyles } from '@material-ui/core'; +import { Alert } from '@material-ui/lab'; + +import { YBSidePanel } from '../../../components'; +import { TaskDetailActions } from './drawerComp/TaskDetailActions'; +import { TaskDetailsHeader } from './drawerComp/TaskDetailHeader'; +import { TaskDetailInfo } from './drawerComp/TaskDetailInfo'; +import { TaskDetailProgress } from './drawerComp/TaskDetailProgress'; +import { SubTaskDetails } from './drawerComp/SubTaskDetails'; +import { YBLoadingCircleIcon } from '../../../../components/common/indicators'; + +import { + fetchCustomerTasks, + fetchCustomerTasksFailure, + fetchCustomerTasksSuccess +} from '../../../../actions/tasks'; + +import { Task } from '../dtos'; + +interface TaskDetailDrawerProps { + taskUUID: string; + visible: boolean; + onClose: () => void; +} + +const useStyles = makeStyles((theme) => ({ + root: { + width: '600px', + background: theme.palette.ybacolors.backgroundGrayLightest + }, + dialogContent: { + padding: '0 !important' + }, + content: { + padding: '0px 20px', + '& > *': { + marginTop: '16px' + } + } +})); + +enum taskRetryStates { + NOT_RETRIED, + RETRIED_LOADING, + RETRIED_FINISHED +} + +export const TaskDetailDrawer: FC = ({ visible, taskUUID, onClose }) => { + const classes = useStyles(); + const dispatch = useDispatch(); + + const [currentTask, setCurrentTask] = useState(null); + + const [taskRetryStatus, setTaskRetryStatus] = useSessionStorage( + `task-retried-${taskUUID}`, + taskRetryStates.NOT_RETRIED + ); + + const { t } = useTranslation('translation', { + keyPrefix: 'taskDetails' + }); + + const taskList = useSelector((data: any) => data.tasks); + + const refetchTask = () => { + dispatch(fetchCustomerTasks() as any).then((response: any) => { + if (!response.error) { + setTaskRetryStatus(taskRetryStates.RETRIED_FINISHED); + dispatch(fetchCustomerTasksSuccess(response.payload)); + } else { + dispatch(fetchCustomerTasksFailure(response.payload)); + } + }); + }; + + useEffect(() => { + const task = find(taskList.customerTaskList, { id: taskUUID }); + if (task) { + setCurrentTask(task); + } else if (taskRetryStatus === taskRetryStates.NOT_RETRIED) { + // if we don't find the task in the store (created just now), refresh the task list in redux store + setTaskRetryStatus(taskRetryStates.RETRIED_LOADING); + refetchTask(); + } + return () => { + setCurrentTask(null); + }; + }, [taskUUID, taskList, taskRetryStatus]); + + const onHide = () => { + onClose(); + }; + + if (visible && !currentTask && taskRetryStatus === taskRetryStates.RETRIED_LOADING) { + return ; + } + // we did try refetching the tasks , but still can't find the task.(i.e task might be deleted). + // we show an error toast. + // but, how are we supposed to find the deleted task?. we can find those attached to the old backups. + // task uuid is present, but the original task is deleted. + if (!currentTask && taskRetryStatus === taskRetryStates.RETRIED_FINISHED && visible) { + return ( + + + {t('taskNotfound')} + + + ); + } + + if (!currentTask) return null; + + return ( + + +
    + + + + +
    +
    + ); +}; diff --git a/managed/ui/src/redesign/features/tasks/components/TaskDetailSimpleComp.tsx b/managed/ui/src/redesign/features/tasks/components/TaskDetailSimpleComp.tsx new file mode 100644 index 000000000000..16a0e6af979c --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/TaskDetailSimpleComp.tsx @@ -0,0 +1,67 @@ +/* + * Created on Tue Jan 09 2024 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { FC } from 'react'; +import { useToggle } from 'react-use'; +import { useTranslation } from 'react-i18next'; +import { Typography, makeStyles } from '@material-ui/core'; +import { TaskDetailDrawer } from './TaskDetailDrawer'; +import { useIsTaskNewUIEnabled } from '../TaskUtils'; + +interface TaskDetailSimpleCompProps { + taskUUID: string; + universeUUID?: string; +} + +const useStyles = makeStyles((theme) => ({ + viewDetails: { + color: theme.palette.ybacolors.textInProgress, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + textDecoration: 'underline', + marginLeft: '8px', + marginRight: '8px', + whiteSpace: 'nowrap' + } +})); + +export const TaskDetailSimpleComp: FC = ({ taskUUID, universeUUID }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'taskDetails.simple' + }); + const [showTaskDetailsDrawer, toggleTaskDetailsDrawer] = useToggle(false); + const classes = useStyles(); + + const isTaskNewUIEnabled = useIsTaskNewUIEnabled(); + if (!isTaskNewUIEnabled) return null; + + return ( + <> +
    { + e.preventDefault(); + e.stopPropagation(); + toggleTaskDetailsDrawer(true); + }} + className={classes.viewDetails} + > + {t('viewDetails')} +
    + { + toggleTaskDetailsDrawer(false); + }} + taskUUID={taskUUID} + /> + + ); +}; diff --git a/managed/ui/src/redesign/features/tasks/components/TaskDiffModal.tsx b/managed/ui/src/redesign/features/tasks/components/TaskDiffModal.tsx new file mode 100644 index 000000000000..5ebe21c5c570 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/TaskDiffModal.tsx @@ -0,0 +1,103 @@ +/* + * Created on Wed May 15 2024 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useQuery } from 'react-query'; +import { YBModal } from '../../../components'; +import { BaseDiff } from './diffComp/diffs/BaseDiff'; +import GFlagsDiff from './diffComp/diffs/GFlagsDiff'; +import SoftwareUpgradeDiff from './diffComp/diffs/SoftwareUpgradeDiff'; +import UniverseDiff from './diffComp/diffs/UniverseDiff'; +import { getTaskDiffDetails } from './diffComp/api'; +import { TargetType, Task, TaskType } from '../dtos'; +import { DiffComponentProps } from './diffComp/dtos'; +import { toast } from 'react-toastify'; + +interface TaskDiffModalProps { + visible: boolean; + onClose: () => void; + currentTask: Task | null; +} + +const TaskDiffModal: React.FC = ({ visible, onClose, currentTask }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'taskDetails.diffModal' + }); + + // Differ to be used for the current task. + const [differ, setDiffer] = useState | null>(null); + + const { data: taskDiffDetails } = useQuery( + ['taskDiffDetails', currentTask?.id], + () => getTaskDiffDetails(currentTask!.id), + { + enabled: !!currentTask && visible, + select: (data) => data.data, + // old task won't have diff details + onError: () => { + toast.error(t('diffDetailsNotFound')); + } + } + ); + + useEffect(() => { + if (!currentTask || !visible || !taskDiffDetails) { + return; + } + + // Set the differ based on the task type. + + if (currentTask.target === TargetType.UNIVERSE && currentTask.type === TaskType.EDIT) { + setDiffer(new UniverseDiff({ ...taskDiffDetails, task: currentTask })); + } + if ( + currentTask.target === TargetType.UNIVERSE && + currentTask.type === TaskType.GFlags_UPGRADE + ) { + setDiffer(new GFlagsDiff({ ...taskDiffDetails, task: currentTask })); + } + if ( + currentTask.target === TargetType.UNIVERSE && + currentTask.type === TaskType.SOFTWARE_UPGRADE + ) { + setDiffer(new SoftwareUpgradeDiff({ ...taskDiffDetails, task: currentTask })); + } + }, [currentTask, visible, taskDiffDetails]); + + // Get the diff component to be rendered. + // memoize to avoid re-rendering on every state change. + const diffComponents = useMemo(() => { + if (!differ) { + return null; + } + return differ.getDiffComponent(); + }, [differ]); + + if (!currentTask || !visible || !taskDiffDetails) { + return null; + } + + return ( + + {diffComponents} + + ); +}; + +export default TaskDiffModal; diff --git a/managed/ui/src/redesign/features/tasks/components/bannerComp/BannerStyles.ts b/managed/ui/src/redesign/features/tasks/components/bannerComp/BannerStyles.ts new file mode 100644 index 000000000000..eb49e19d64b3 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/bannerComp/BannerStyles.ts @@ -0,0 +1,33 @@ +/* + * Created on Fri Dec 22 2023 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { makeStyles } from '@material-ui/core'; + +export const useBannerCommonStyles = makeStyles((theme) => ({ + viewDetsailsButton: { + color: theme.palette.ybacolors.ybDarkGray, + fontSize: '12px', + fontWeight: 500, + height: '30px', + '& .MuiButton-label': { + fontSize: '12px' + } + }, + flex: { + display: 'flex', + alignItems: 'center', + gap: '8px' + }, + divider: { + width: '1px', + height: '24px', + background: theme.palette.ybacolors.ybBorderGray, + border: `1px solid ${theme.palette.ybacolors.ybBorderGray}` + } +})); diff --git a/managed/ui/src/redesign/features/tasks/components/bannerComp/TaskFailedBanner.tsx b/managed/ui/src/redesign/features/tasks/components/bannerComp/TaskFailedBanner.tsx new file mode 100644 index 000000000000..211b60829121 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/bannerComp/TaskFailedBanner.tsx @@ -0,0 +1,84 @@ +/* + * Created on Fri Dec 22 2023 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { FC } from 'react'; +import clsx from 'clsx'; +import { useTranslation } from 'react-i18next'; +import { Divider, Typography, makeStyles } from '@material-ui/core'; +import { AlertVariant, YBAlert, YBButton } from '../../../../components'; +import { YBProgress, YBProgressBarState } from '../../../../components/YBProgress/YBLinearProgress'; +import { TaskBannerCompProps } from './dtos'; +import { useBannerCommonStyles } from './BannerStyles'; +import ErrorIcon from '../../../../assets/error.svg'; + +const useStyles = makeStyles((theme) => ({ + root: { + margin: '8px 20px' + }, + bannerStyles: { + border: '1px solid rgba(231, 62, 54, 0.25)', + background: 'rgba(231, 62, 54, 0.15)', + padding: '8px 16px', + alignItems: 'center' + }, + errorIcon: { + width: 22, + height: 22 + } +})); + +export const TaskFailedBanner: FC = ({ + currentTask, + viewDetails, + onClose +}) => { + const classes = useStyles(); + const commonStyles = useBannerCommonStyles(); + const { t } = useTranslation('translation', { + keyPrefix: 'taskDetails.banner' + }); + return ( +
    + +
    + {currentTask.title} +
    +
    + + {t('taskProgress', { percent: Math.trunc(currentTask.percentComplete) })} + + +
    + + viewDetails()} + > + {t('viewDetails')} + +
    + } + open + variant={AlertVariant.Error} + className={classes.bannerStyles} + onClose={onClose} + icon={} + /> +
    + ); +}; diff --git a/managed/ui/src/redesign/features/tasks/components/bannerComp/TaskInProgressBanner.tsx b/managed/ui/src/redesign/features/tasks/components/bannerComp/TaskInProgressBanner.tsx new file mode 100644 index 000000000000..091e072eaf1a --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/bannerComp/TaskInProgressBanner.tsx @@ -0,0 +1,83 @@ +/* + * Created on Fri Dec 22 2023 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { FC } from 'react'; +import clsx from 'clsx'; +import { useTranslation } from 'react-i18next'; +import { Divider, Typography, makeStyles } from '@material-ui/core'; +import { AlertVariant, YBAlert, YBButton } from '../../../../components'; +import { YBProgress, YBProgressBarState } from '../../../../components/YBProgress/YBLinearProgress'; +import { useBannerCommonStyles } from './BannerStyles'; +import { TaskBannerCompProps } from './dtos'; + +const useStyles = makeStyles(() => ({ + root: { + margin: '8px 20px' + }, + bannerStyles: { + border: `1px solid #A5BDF2`, + background: `#EBF1FF`, + alignItems: 'center', + padding: '8px 16px', + '& svg': { + marginTop: 0 + } + }, + + content: { + gap: '12px' + } +})); + +export const TaskInProgressBanner: FC = ({ currentTask, viewDetails }) => { + const classes = useStyles(); + const commonStyles = useBannerCommonStyles(); + + const { t } = useTranslation('translation', { + keyPrefix: 'taskDetails.banner' + }); + + return ( +
    + +
    + {currentTask.title}... + {t('opNotAvailable')} +
    +
    + + {t('taskProgress', { percent: Math.trunc(currentTask.percentComplete) })} + + +
    + + viewDetails()} + > + {t('viewDetails')} + +
    + } + open + variant={AlertVariant.InProgress} + className={classes.bannerStyles} + /> +
    + ); +}; diff --git a/managed/ui/src/redesign/features/tasks/components/bannerComp/TaskSuccessBanner.tsx b/managed/ui/src/redesign/features/tasks/components/bannerComp/TaskSuccessBanner.tsx new file mode 100644 index 000000000000..380a40e86bd8 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/bannerComp/TaskSuccessBanner.tsx @@ -0,0 +1,59 @@ +/* + * Created on Fri Dec 22 2023 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { FC } from 'react'; +import clsx from 'clsx'; +import { useTranslation } from 'react-i18next'; +import { Divider, Typography, makeStyles } from '@material-ui/core'; +import { AlertVariant, YBAlert, YBButton } from '../../../../components'; +import { TaskBannerCompProps } from './dtos'; +import { useBannerCommonStyles } from './BannerStyles'; + +const useStyles = makeStyles((theme) => ({ + root: { + margin: '8px 20px' + } +})); + +export const TaskSuccessBanner: FC = ({ + currentTask, + viewDetails, + onClose +}) => { + const classes = useStyles(); + const commonStyles = useBannerCommonStyles(); + const { t } = useTranslation('translation', { + keyPrefix: 'taskDetails.banner' + }); + return ( +
    + +
    + {currentTask.title} +
    + + viewDetails()} + > + {t('viewDetails')} + +
    + } + open + variant={AlertVariant.Success} + onClose={onClose} + /> +
    + ); +}; diff --git a/managed/ui/src/redesign/features/tasks/components/bannerComp/dtos.ts b/managed/ui/src/redesign/features/tasks/components/bannerComp/dtos.ts new file mode 100644 index 000000000000..5a098c806d2c --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/bannerComp/dtos.ts @@ -0,0 +1,16 @@ +/* + * Created on Fri Dec 22 2023 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { Task } from '../../dtos'; + +export interface TaskBannerCompProps { + currentTask: Task; + viewDetails: () => void; + onClose: () => void; +} diff --git a/managed/ui/src/redesign/features/tasks/components/diffComp/DiffActions.tsx b/managed/ui/src/redesign/features/tasks/components/diffComp/DiffActions.tsx new file mode 100644 index 000000000000..fc51ed336988 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/diffComp/DiffActions.tsx @@ -0,0 +1,36 @@ +/* + * Created on Fri Jun 07 2024 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { FC } from 'react'; +import { Grid, Typography } from '@material-ui/core'; +import { YBButton } from '../../../../components'; +import { useTranslation } from 'react-i18next'; + +interface DiffActionsProps { + onExpandAll: () => void; + changesCount: number; +} + +export const DiffActions: FC = ({ onExpandAll, changesCount }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'taskDetails.diffModal' + }); + return ( + + + {changesCount} changes + + + + {t('expandAll')} + + + + ); +}; diff --git a/managed/ui/src/redesign/features/tasks/components/diffComp/DiffBadge.tsx b/managed/ui/src/redesign/features/tasks/components/diffComp/DiffBadge.tsx new file mode 100644 index 000000000000..9f40c384c5d5 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/diffComp/DiffBadge.tsx @@ -0,0 +1,81 @@ +/* + * Created on Thu May 16 2024 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { FC } from 'react'; +import clsx from 'clsx'; +import { DiffOperation } from './dtos'; +import { Typography, makeStyles } from '@material-ui/core'; +import { ReactComponent as CheckIcon } from '../../../../assets/check.svg'; +import { ReactComponent as DeleteIcon } from '../../../../assets/Close-Bold.svg'; +import { ReactComponent as ChangeIcon } from '../../../../assets/switch-icon.svg'; + +interface DiffBadgeProps { + type: DiffOperation; + minimal?: boolean; + hideIcon?: boolean; + customText?: string | JSX.Element; +} + +const useStyles = makeStyles((theme) => ({ + root: { + padding: `2px 6px`, + height: theme.spacing(3), + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '6px', + gap: '4px', + lineHeight: theme.spacing(2), + '& > svg': { + width: theme.spacing(2), + height: theme.spacing(2) + } + }, + minimal: { + width: theme.spacing(3), + padding: 0 + }, + Added: { + background: theme.palette.success[100], + color: theme.palette.success[700] + }, + Changed: { + background: theme.palette.warning[100], + color: theme.palette.warning[900] + }, + Removed: { + background: theme.palette.grey[200], + color: theme.palette.ybacolors.textDarkGray + }, + bold: { + fontWeight: 500 + } +})); + +const DiffBadge: FC = ({ minimal = false, type, hideIcon = false, customText }) => { + const classes = useStyles(); + let Icon = CheckIcon; + if (type === DiffOperation.REMOVED) { + Icon = DeleteIcon; + } else if (type === DiffOperation.CHANGED) { + Icon = ChangeIcon; + } + return ( +
    + {!hideIcon && } + {!minimal && ( + + {customText ?? type} + + )} +
    + ); +}; + +export default DiffBadge; diff --git a/managed/ui/src/redesign/features/tasks/components/diffComp/DiffBanners.tsx b/managed/ui/src/redesign/features/tasks/components/diffComp/DiffBanners.tsx new file mode 100644 index 000000000000..5481b11963dc --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/diffComp/DiffBanners.tsx @@ -0,0 +1,104 @@ +/* + * Created on Mon Jun 10 2024 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { FC } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { Grid, Typography, makeStyles } from '@material-ui/core'; + +import { Task } from '../../dtos'; +import DiffBadge from './DiffBadge'; +import { AlertVariant, YBAlert } from '../../../../components'; +import { isTaskFailed, isTaskRunning } from '../../TaskUtils'; +import { DiffOperation } from './dtos'; + +interface TaskDiffBannerProps { + task: Task; + diffCount?: number; +} + +export const TaskDiffBanner: FC = ({ task, diffCount }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'taskDetails.diffModal.alerts' + }); + + if (isTaskRunning(task)) { + return ( + + ); + } + if (isTaskFailed(task)) { + return ( + }} />} + open={true} + /> + ); + } + return null; +}; + +interface DiffTitleBannerProps { + title: string; + showLegends?: boolean; +} + +const useStyles = makeStyles((theme) => ({ + legends: { + display: 'flex', + gap: '12px', + '& > div': { + display: 'flex', + alignItems: 'center' + } + }, + divider: { + width: '1px', + height: '16px', + borderLeft: `1px solid ${theme.palette.ybacolors.backgroundGrayDark}`, + marginRight: '12px' + }, + title: { + textTransform: 'uppercase', + color: theme.palette.ybacolors.textDarkGray, + fontWeight: 500 + } +})); + +export const DiffTitleBanner: FC = ({ title, showLegends = true }) => { + const classes = useStyles(); + return ( + + + + {title} + + + {showLegends && ( + +
    + +
    +
    + + +
    +
    + + +
    +
    + )} +
    + ); +}; diff --git a/managed/ui/src/redesign/features/tasks/components/diffComp/DiffCard.tsx b/managed/ui/src/redesign/features/tasks/components/diffComp/DiffCard.tsx new file mode 100644 index 000000000000..108ae40cc6df --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/diffComp/DiffCard.tsx @@ -0,0 +1,196 @@ +/* + * Created on Thu May 16 2024 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { forwardRef, isValidElement, useImperativeHandle } from 'react'; +import clsx from 'clsx'; +import { useToggle } from 'react-use'; +import { DiffOperation } from './dtos'; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Grid, + Typography, + makeStyles +} from '@material-ui/core'; +import DiffBadge from './DiffBadge'; +import { ArrowDropDown } from '@material-ui/icons'; + +type diffElem = { + title?: string; + element?: JSX.Element; +}; + +type DiffCardProps = { + attribute: diffElem; +} & ( + | { + operation: DiffOperation.CHANGED; + beforeValue: diffElem; + afterValue: diffElem; + } + | { + // The following two properties are optional for the ADDED and REMOVED operations + operation: DiffOperation.ADDED | DiffOperation.REMOVED; + beforeValue?: diffElem; + afterValue?: diffElem; + } +); + +const useStyles = makeStyles((theme) => ({ + accordionSummary: { + padding: '12px', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + height: '48px' + }, + header: { + display: 'flex', + gap: '16px', + alignItems: 'center' + }, + headerOp: { + display: 'flex', + gap: '8px', + alignItems: 'center' + }, + expandMore: { + fontSize: theme.spacing(4) + }, + strikeOut: { + textDecoration: 'line-through' + }, + strikeOutRed: { + '&>span': { + backgroundColor: '#FEEDED', + mixBlendMode: 'darken' + } + }, + strikeOutGreen: { + '&>span': { + backgroundColor: '#CDEFE1', + mixBlendMode: 'darken' + } + }, + ellipsis: { + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + wordBreak: 'break-all', + maxWidth: '150px', + overflow: 'hidden' + }, + accordionDetails: { + borderTop: `1px solid ${theme.palette.ybacolors.ybBorderGray}`, + display: 'block', + padding: '12px' + }, + detailsHeader: { + display: 'flex', + gap: '8px', + '&>div': { + minWidth: '230px', + maxWidth: '230px', + padding: '0px 8px 8px 0px', + wordBreak: 'break-all' + } + }, + seperator: { + borderRight: `1px solid ${theme.palette.ybacolors.ybBorderGray}` + }, + noPaddingLeft: { + paddingLeft: `0 !important` + } +})); + +export type DiffCardRef = { + onExpand: (flag: boolean) => void; +}; + +const DiffCard = forwardRef>( + ({ attribute, operation, beforeValue, afterValue }, forwardRef) => { + // Implement the component logic here + const classes = useStyles(); + const [expandAccordion, setExpandAccordion] = useToggle(false); + + useImperativeHandle(forwardRef, () => ({ onExpand: setExpandAccordion }), []); + + const getElementOrPlaceHolder = (value: DiffCardProps['beforeValue']) => { + if (isValidElement(value?.element)) { + return value?.element; + } + return value?.title ? {value.title} : '---'; + }; + + return ( + setExpandAccordion(!expandAccordion)}> + } + className={classes.accordionSummary} + > +
    +
    + {!expandAccordion && } + +
    + {!expandAccordion && ( + <> + + {operation !== DiffOperation.ADDED && beforeValue?.title} + + + {afterValue?.title} + + + )} +
    +
    + + + + + Before + + + After + + + + + {attribute.element ?? attribute.title} + + + {getElementOrPlaceHolder(beforeValue)} + + + {getElementOrPlaceHolder(afterValue)} + + + +
    + ); + } +); + +DiffCard.displayName = 'DiffCard'; + +export default DiffCard; diff --git a/managed/ui/src/redesign/features/tasks/components/diffComp/DiffCardWrapper.tsx b/managed/ui/src/redesign/features/tasks/components/diffComp/DiffCardWrapper.tsx new file mode 100644 index 000000000000..aecc7d4f45c3 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/diffComp/DiffCardWrapper.tsx @@ -0,0 +1,24 @@ +/* + * Created on Tue Jun 11 2024 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { FC } from 'react'; +import { makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles(() => ({ + root: { + '&>div': { + marginBottom: '12px' + } + } +})); + +export const DiffCardWrapper: FC = ({ children }) => { + const classes = useStyles(); + return
    {children}
    ; +}; diff --git a/managed/ui/src/redesign/features/tasks/components/diffComp/DiffUtils.ts b/managed/ui/src/redesign/features/tasks/components/diffComp/DiffUtils.ts new file mode 100644 index 000000000000..734060cf5f5d --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/diffComp/DiffUtils.ts @@ -0,0 +1,75 @@ +/* + * Created on Wed Jun 05 2024 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { difference, isEqual, keys, pick, union } from 'lodash'; +import { DiffOperation, DiffProps } from './dtos'; +import { PlacementAZ } from '../../../universe/universe-form/utils/dto'; + +/** + * Compares two objects and returns the differences between them. + * @param obj1 - The first object to compare. + * @param obj2 - The second object to compare. + * @returns An object containing the differences between the two objects. + */ +export const getDiffsInObject = (obj1: Record, obj2: Record) => { + const diffs = { + [DiffOperation.ADDED]: [] as Partial[], + [DiffOperation.REMOVED]: [] as Partial[], + [DiffOperation.CHANGED]: [] as Partial[] + }; + const setA = keys(obj1); + const setB = keys(obj2); + + const keysRemoved = difference(setA, setB); + const keysAdded = difference(setB, setA); + const keysUnionUnique = union(setA, setB); + + // iterate over the union of keys + keysUnionUnique.forEach((key) => { + // check if the key is in the removed + if (keysRemoved.includes(key)) { + diffs[DiffOperation.REMOVED].push({ + operation: DiffOperation.REMOVED, + beforeData: obj1[key], + afterData: undefined, + attribute: key + }); + // check if the key is in the added + } else if (keysAdded.includes(key)) { + diffs[DiffOperation.ADDED].push({ + operation: DiffOperation.ADDED, + beforeData: undefined, + afterData: obj2[key], + attribute: key + }); + // check if the key is in both objects + // we are checking if the values are not equal, + // if they are equal we don't need to add it to the diffs as they are not changed + } else if (!isEqual(obj1[key], obj2[key])) { + diffs[DiffOperation.CHANGED].push({ + operation: DiffOperation.CHANGED, + beforeData: obj1[key], + afterData: obj2[key], + attribute: key + }); + } + }); + return diffs; +}; + +/** + * Checks if two PlacementAZ objects are equal based on specific fields. + * @param a - The first PlacementAZ object. + * @param b - The second PlacementAZ object. + * @returns True if the objects are equal, false otherwise. + */ +export const isAZEqual = (a: PlacementAZ, b: PlacementAZ) => { + const fields = ['name', 'numNodesInAZ', 'isAffinitized', 'replicationFactor']; + return isEqual(pick(a, fields), pick(b, fields)); +}; diff --git a/managed/ui/src/redesign/features/tasks/components/diffComp/api.ts b/managed/ui/src/redesign/features/tasks/components/diffComp/api.ts new file mode 100644 index 000000000000..ea8ef4b5e270 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/diffComp/api.ts @@ -0,0 +1,23 @@ +/* + * Created on Tue Jun 04 2024 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import axios from 'axios'; +import { ROOT_URL } from '../../../../../config'; +import { DiffApiResp } from './dtos'; + +/** + * Retrieves the diff details for a specific task. + * @param taskUUID - The UUID of the task. + * @returns DiffApiResp. + */ +export const getTaskDiffDetails = (taskUUID: string) => { + const cUUID = localStorage.getItem('customerId'); + const requestUrl = `${ROOT_URL}/customers/${cUUID}/tasks/${taskUUID}/diff_details`; + return axios.get(requestUrl); +}; diff --git a/managed/ui/src/redesign/features/tasks/components/diffComp/diffs/BaseDiff.tsx b/managed/ui/src/redesign/features/tasks/components/diffComp/diffs/BaseDiff.tsx new file mode 100644 index 000000000000..22ae48661228 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/diffComp/diffs/BaseDiff.tsx @@ -0,0 +1,11 @@ +import { Component } from 'react'; +import { DiffComponentProps } from '../dtos'; + +// Base class for all the diff components. +export abstract class BaseDiff

    extends Component { + // Get the title for the modal. + abstract getModalTitle(): string; + + // Get the diff component to render. + abstract getDiffComponent(): React.ReactElement; +} diff --git a/managed/ui/src/redesign/features/tasks/components/diffComp/diffs/GFlagsDiff.tsx b/managed/ui/src/redesign/features/tasks/components/diffComp/diffs/GFlagsDiff.tsx new file mode 100644 index 000000000000..052178799302 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/diffComp/diffs/GFlagsDiff.tsx @@ -0,0 +1,128 @@ +/* + * Created on Fri May 17 2024 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { keys } from 'lodash'; +import { DiffActions } from '../DiffActions'; +import { DiffTitleBanner, TaskDiffBanner } from '../DiffBanners'; +import { DiffCardWrapper } from '../DiffCardWrapper'; +import DiffCard, { DiffCardRef } from '../DiffCard'; +import { Task } from '../../../dtos'; +import { BaseDiff } from './BaseDiff'; +import { getDiffsInObject } from '../DiffUtils'; +import { DiffComponentProps, DiffOperation, DiffProps } from '../dtos'; + +/** + * Represents a component for displaying the differences the task made during the GFlag operation. + * Extends the BaseDiff component. + */ +export class GFlagsDiff extends BaseDiff { + diffProps: DiffProps; + cardRefs: React.RefObject[]; + task: Task; + + constructor(props: DiffComponentProps) { + super(props); + this.diffProps = props; + this.cardRefs = []; + this.task = props.task; + } + + getModalTitle() { + return 'GFlags'; + } + + getDiffComponent(): React.ReactElement { + const { beforeData, afterData } = this.diffProps; + + const cards: Record[]> = { + masterGFlags: [], + tserverGFlags: [] + }; + + // Get the differences in the master and tserver GFlags. + const masterGFlagsDiffs = getDiffsInObject( + beforeData.clusters[0].userIntent.specificGFlags!.perProcessFlags.value!.MASTER!, + afterData.clusters[0].userIntent.specificGFlags!.perProcessFlags.value!.MASTER! + ); + + const tserverGFlagsDiffs = getDiffsInObject( + beforeData.clusters[0].userIntent.specificGFlags!.perProcessFlags.value!.TSERVER!, + afterData.clusters[0].userIntent.specificGFlags!.perProcessFlags.value!.TSERVER! + ); + + // Create the diff cards for the master and tserver GFlags. + keys(masterGFlagsDiffs).forEach((key) => { + masterGFlagsDiffs[key].forEach((diff: DiffProps) => { + cards.masterGFlags.push( + this.cardRefs?.push({ current: ref })} + attribute={{ + title: diff.attribute ?? '' + }} + beforeValue={{ + title: (diff.beforeData as unknown) as string + }} + afterValue={{ + title: (diff.afterData as unknown) as string + }} + operation={diff.operation as DiffOperation} + /> + ); + }); + }); + + keys(tserverGFlagsDiffs).forEach((key) => { + tserverGFlagsDiffs[key].forEach((diff: DiffProps) => { + cards.tserverGFlags.push( + this.cardRefs.push({ current: ref })} + attribute={{ + title: diff.attribute ?? '' + }} + beforeValue={{ + title: (diff.beforeData as unknown) as string + }} + afterValue={{ + title: (diff.afterData as unknown) as string + }} + operation={diff.operation as DiffOperation} + /> + ); + }); + }); + return ( + + { + // Expand all the cards. + this.cardRefs.forEach((ref) => { + ref?.current?.onExpand(true); + }); + }} + // Get the count of changes. + changesCount={cards.masterGFlags.length + cards.tserverGFlags.length} + /> + + {cards.tserverGFlags.length > 0 && } + {/* Render the diff cards for the tserver GFlags. */} + {cards.tserverGFlags} + {cards.masterGFlags.length > 0 && ( + + )} + {/* Render the diff cards for the master GFlags. */} + {cards.masterGFlags} + + ); + } +} + +export default GFlagsDiff; diff --git a/managed/ui/src/redesign/features/tasks/components/diffComp/diffs/SoftwareUpgradeDiff.tsx b/managed/ui/src/redesign/features/tasks/components/diffComp/diffs/SoftwareUpgradeDiff.tsx new file mode 100644 index 000000000000..5ba0bd2c7fe2 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/diffComp/diffs/SoftwareUpgradeDiff.tsx @@ -0,0 +1,54 @@ +/* + * Created on Fri May 17 2024 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { Task } from '../../../dtos'; +import { TaskDiffBanner } from '../DiffBanners'; +import DiffCard from '../DiffCard'; +import { DiffCardWrapper } from '../DiffCardWrapper'; +import { DiffComponentProps, DiffOperation } from '../dtos'; +import { BaseDiff } from './BaseDiff'; + +/** + * Represents a component for displaying the differences the task made during the software upgrade operation. + * Extends the BaseDiff component. + */ +export class SoftwareUpgradeDiff extends BaseDiff { + task: Task; + constructor(props: DiffComponentProps) { + super(props); + this.task = props.task; + } + + getModalTitle() { + return 'Software Upgrade'; + } + + getDiffComponent(): React.ReactElement { + return ( + + + + ; + + ); + } +} + +export default SoftwareUpgradeDiff; diff --git a/managed/ui/src/redesign/features/tasks/components/diffComp/diffs/UniverseDiff.tsx b/managed/ui/src/redesign/features/tasks/components/diffComp/diffs/UniverseDiff.tsx new file mode 100644 index 000000000000..bc6a6fd42ff7 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/diffComp/diffs/UniverseDiff.tsx @@ -0,0 +1,444 @@ +/* + * Created on Fri May 17 2024 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { MutableRefObject } from 'react'; +import clsx from 'clsx'; +import { differenceWith, intersectionWith, isEqual } from 'lodash'; +import { useTranslation } from 'react-i18next'; +import { makeStyles } from '@material-ui/core'; +import { ClusterType } from '../../../../../helpers/dtos'; +import { + Cluster, + PlacementAZ, + PlacementRegion +} from '../../../../universe/universe-form/utils/dto'; +import { getPrimaryCluster } from '../../../../universe/universe-form/utils/helpers'; +import { Task } from '../../../dtos'; +import { DiffActions } from '../DiffActions'; +import { DiffTitleBanner, TaskDiffBanner } from '../DiffBanners'; +import DiffCard, { DiffCardRef } from '../DiffCard'; +import { DiffCardWrapper } from '../DiffCardWrapper'; +import { isAZEqual } from '../DiffUtils'; +import { DiffComponentProps, DiffOperation, DiffProps } from '../dtos'; +import { BaseDiff } from './BaseDiff'; + +/** + * Represents a component for displaying the differences the task made during the edit operation. + * Extends the BaseDiff component. + */ +export class UniverseDiff extends BaseDiff { + diffProps: DiffProps; + task: Task; + // Cards for the primary and async clusters. + cards: Record[]>; + + // Refs for the diff cards. used to expand all cards. + cardRefs: MutableRefObject[]; + + constructor(props: DiffComponentProps) { + super(props); + this.diffProps = props; + this.task = props.task; + this.cards = { + [ClusterType.PRIMARY]: [], + [ClusterType.ASYNC]: [] + }; + this.cardRefs = []; + } + + getModalTitle() { + return 'Universe'; + } + + // Get the differences in the instance type of the cluster. + getInstanceTypeDiff(beforeValue: Cluster, afterValue: Cluster, clusterType: ClusterType) { + // If the instance type is changed, add a diff card. + if (beforeValue.userIntent.instanceType !== afterValue.userIntent.instanceType) { + this.cards[clusterType].push( + ref && this.cardRefs?.push({ current: ref })} + attribute={{ title: 'Instance Type' }} + operation={DiffOperation.CHANGED} + beforeValue={{ + title: beforeValue.userIntent.instanceType! + }} + afterValue={{ + title: afterValue.userIntent.instanceType! + }} + /> + ); + } + } + + // Get the differences in the cloud list of the cluster. + // Flow: getCloudListDiffComponent -> getRegionsDiffComponent -> getPlacementAZDiffComponent + // cloudlist will call getRegionsDiffComponent to get the differences in the regions. + // getRegionsDiffComponent will call getPlacementAZDiffComponent to get the differences in the placement AZs. + // getPlacementAZDiffComponent will return the diff component for the placement AZs. + + getCloudListDiffComponent(beforeValue: Cluster, afterValue: Cluster, clusterType: ClusterType) { + const beforeCloudList = beforeValue.placementInfo?.cloudList ?? []; + const afterCloudList = afterValue.placementInfo?.cloudList ?? []; + + // Get the cloud list that are removed and added. + const cloudListRemoved = differenceWith( + beforeCloudList, + afterCloudList, + (a, b) => a.code === b.code + ); + const cloudListAdded = differenceWith( + afterCloudList, + beforeCloudList, + (a, b) => a.code === b.code + ); + + // Create diff cards for the removed and added cloud lists. + cloudListRemoved.forEach((cloudList) => { + cloudList.regionList.forEach((region) => { + this.cards[clusterType].push(this.getRegionsDiffComponent(region, undefined)); + }); + }); + + cloudListAdded.forEach((cloudList) => { + cloudList.regionList.forEach((region) => { + this.cards[clusterType].push(this.getRegionsDiffComponent(undefined, region)); + }); + }); + + // Get the cloud list that are changed. + const cloudListChanged = intersectionWith( + beforeCloudList, + afterCloudList, + (a, b) => a.code === b.code && !isEqual(a, b) + ); + + // Create diff cards for the changed cloud lists. + cloudListChanged.forEach((cloudList) => { + const afterCloudVal = afterCloudList.find((r) => r.code === cloudList.code); + + // Get the regions that are removed and added. + const regionAdded = differenceWith( + cloudList.regionList, + afterCloudVal!.regionList, + (a, b) => a.code === b.code + ); + const regionRemoved = differenceWith( + afterCloudVal!.regionList, + cloudList.regionList, + (a, b) => a.code === b.code + ); + + // Create diff cards for the removed and added regions. + regionAdded.forEach((region) => { + this.cards[clusterType].push(this.getRegionsDiffComponent(undefined, region)); + }); + + regionRemoved.forEach((region) => { + this.cards[clusterType].push(this.getRegionsDiffComponent(region, undefined)); + }); + + // Get the regions that are changed. + const regionChanged = intersectionWith( + cloudList.regionList, + afterCloudVal!.regionList, + (a, b) => a.code === b.code && !isEqual(a, b) + ); + + regionChanged.forEach((region) => { + const afterRegion = afterCloudVal!.regionList.find((r) => r.code === region.code); + this.cards[clusterType].push(this.getRegionsDiffComponent(region, afterRegion)); + }); + }); + } + + // Get the diff component for the regions. + getRegionsDiffComponent( + beforeRegion?: PlacementRegion, + afterRegion?: PlacementRegion + ): React.ReactElement { + // If both regions are null, throw an error. + if (!beforeRegion && !afterRegion) { + throw new Error('Both before and after regions are null'); + } + + const atttributeCompList = [Region]; + const beforeCompList = [

    {beforeRegion ? beforeRegion.name : '---'}
    ]; + const afterCompList = [
    {afterRegion ? afterRegion.name : '---'}
    ]; + + // If the region is added, add a diff card. + if (!beforeRegion && afterRegion) { + afterRegion.azList.forEach((az, index) => { + atttributeCompList.push(); + const [beforePlacementComp, afterPlacementComp] = this.getPlacementAZDiffComponent( + undefined, + az + ); + + beforeCompList.push(beforePlacementComp); + afterCompList.push(afterPlacementComp); + }); + } + + // If the region is removed, add a diff card. + if (beforeRegion && !afterRegion) { + beforeRegion!.azList.forEach((az, index) => { + atttributeCompList.push(); + const [beforePlacementComp, afterPlacementComp] = this.getPlacementAZDiffComponent( + az, + undefined + ); + + beforeCompList.push(beforePlacementComp); + afterCompList.push(afterPlacementComp); + }); + } + + // If the region is changed, add a diff card. + if (beforeRegion && afterRegion) { + const azListAdded = differenceWith( + afterRegion.azList, + beforeRegion.azList, + (a, b) => a.name === b.name && !isAZEqual(a, b) + ); + const azListRemoved = differenceWith( + beforeRegion.azList, + afterRegion.azList, + (a, b) => a.name === b.name && !isAZEqual(a, b) + ); + + // Create diff cards for the added and removed placement AZs. + azListAdded.forEach((az, index) => { + atttributeCompList.push(); + const [beforePlacementComp, afterPlacementComp] = this.getPlacementAZDiffComponent( + undefined, + az + ); + + beforeCompList.push(beforePlacementComp); + afterCompList.push(afterPlacementComp); + }); + + azListRemoved.forEach((az, index) => { + atttributeCompList.push(); + const [beforePlacementComp, afterPlacementComp] = this.getPlacementAZDiffComponent( + az, + undefined + ); + + beforeCompList.push(beforePlacementComp); + afterCompList.push(afterPlacementComp); + }); + + // Get the placement AZs that are changed. + const azListChanged = intersectionWith( + beforeRegion.azList, + afterRegion.azList, + // Check if the AZs are equal based on specific fields. + (a, b) => a.name === b.name && !isAZEqual(a, b) + ); + + // Create diff cards for the changed placement AZs. + azListChanged.forEach((az, index) => { + const afterAz = afterRegion.azList.find((a) => a.name === az.name); + const [beforePlacementComp, afterPlacementComp] = this.getPlacementAZDiffComponent( + az, + afterAz + ); + + atttributeCompList.push(); + beforeCompList.push(beforePlacementComp); + afterCompList.push(afterPlacementComp); + }); + } + + return ( + ref && this.cardRefs?.push({ current: ref })} + attribute={{ + title: 'Region', + element: <>{atttributeCompList} + }} + afterValue={{ + title: afterRegion?.name ?? '---', + element: <>{afterCompList} + }} + beforeValue={{ + title: beforeRegion?.name ?? '---', + element: <>{beforeCompList} + }} + /> + ); + } + + // Get the diff component for the placement AZs. + getPlacementAZDiffComponent( + beforePlacementAZ?: PlacementAZ, + afterPlacementAZ?: PlacementAZ + ): React.ReactElement[] { + if (!beforePlacementAZ && !afterPlacementAZ) { + throw new Error('Both before and after placement AZs are null'); + } + + return [ + , + + ]; + } + + getDiffComponent(): React.ReactElement { + // Get the primary cluster before and after the edit operation. + const beforePrimaryCluster = getPrimaryCluster(this.diffProps.beforeData); + const afterPrimaryCluster = getPrimaryCluster(this.diffProps.afterData); + + // Get the differences in the instance type of the primary cluster. + this.getInstanceTypeDiff(beforePrimaryCluster!, afterPrimaryCluster!, ClusterType.PRIMARY); + // Get the differences in the cloud list of the primary cluster. + this.getCloudListDiffComponent( + beforePrimaryCluster!, + afterPrimaryCluster!, + ClusterType.PRIMARY + ); + + return ( + + { + this.cardRefs.forEach((ref) => { + ref?.current?.onExpand(true); + }); + }} + changesCount={ + this.cards[ClusterType.PRIMARY].length + this.cards[ClusterType.ASYNC].length + } + /> + + + {this.cards[ClusterType.PRIMARY]} + + ); + } +} + +const useStyles = makeStyles((theme) => ({ + root: { + '& ul': { + listStyleType: "'↳'", + paddingLeft: '18px', + marginBottom: 0, + '& > li': { + paddingLeft: '4px' + } + }, + color: theme.palette.grey[900], + lineHeight: '32px', + fontSize: '13px' + }, + region: { + paddingLeft: '6px !important', + marginBottom: 0 + } +})); + +// Placeholder for the region attribute. +const RegionAttributePlaceholder = () => { + const classes = useStyles(); + const { t } = useTranslation('translation', { keyPrefix: 'taskDetails.diffModal.universeDiff' }); + + return ( +
    +
      +
    • {t('availabilityZone')}
    • +
        +
      • {t('nodes')}
      • +
      • {t('preferred')}
      • +
      +
    +
    + ); +}; + +const useStylePlacementAzComponent = makeStyles((theme) => ({ + root: { + color: theme.palette.grey[900], + lineHeight: '32px', + fontSize: '13px', + '& > span': { + width: 'fit-content' + } + }, + strikeOutRed: { + '& > span': { + backgroundColor: '#FEEDED', + mixBlendMode: 'darken' + } + }, + strikeOutGreen: { + '& > span': { + backgroundColor: '#CDEFE1', + mixBlendMode: 'darken' + } + } +})); + +// Component for displaying the placement AZ. +const PlacementAzComponent = ({ + placementAz, + displayEmpty, + operation +}: { + placementAz: PlacementAZ; + operation: DiffOperation; + displayEmpty?: boolean; +}) => { + const classes = useStylePlacementAzComponent(); + + return ( +
    + {displayEmpty ? '---' : placementAz.name} +
    + {displayEmpty ? '---' : placementAz.numNodesInAZ} +
    + {displayEmpty ? '---' : placementAz.isAffinitized + ''} +
    +
    + ); +}; + +export default UniverseDiff; diff --git a/managed/ui/src/redesign/features/tasks/components/diffComp/dtos.ts b/managed/ui/src/redesign/features/tasks/components/diffComp/dtos.ts new file mode 100644 index 000000000000..6f34d05f0850 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/diffComp/dtos.ts @@ -0,0 +1,34 @@ +/* + * Created on Wed May 15 2024 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { UniverseDetails } from '../../../universe/universe-form/utils/dto'; +import { Task } from '../../dtos'; + +export enum DiffOperation { + ADDED = 'Added', + REMOVED = 'Removed', + CHANGED = 'Changed' +} + +export interface DiffProps { + beforeData: UniverseDetails; + afterData: UniverseDetails; + operation?: DiffOperation; + attribute?: string; +} + +export interface DiffComponentProps extends DiffProps { + task: Task; +} + +// Represents the response from the diff API. +export interface DiffApiResp extends DiffProps { + uuid: string; + parentUuid: string; +} diff --git a/managed/ui/src/redesign/features/tasks/components/drawerComp/SubTaskDetails.tsx b/managed/ui/src/redesign/features/tasks/components/drawerComp/SubTaskDetails.tsx new file mode 100644 index 000000000000..e6ca537011b9 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/drawerComp/SubTaskDetails.tsx @@ -0,0 +1,255 @@ +/* + * Created on Wed Dec 20 2023 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { FC } from 'react'; +import clsx from 'clsx'; +import { useToggle } from 'react-use'; +import { useQuery } from 'react-query'; +import { useTranslation } from 'react-i18next'; +import { Collapse, Typography, makeStyles } from '@material-ui/core'; +import { YBButton } from '../../../../components'; +import { YBLoadingCircleIcon } from '../../../../../components/common/indicators'; +import { getFailedTaskDetails } from './api'; +import { TaskDetails, TaskStates } from '../../dtos'; +import { TaskDrawerCompProps } from './dtos'; +import { isTaskFailed } from '../../TaskUtils'; +import LinkIcon from '../../../../assets/link.svg'; + +const useStyles = makeStyles((theme) => ({ + root: { + maxHeight: '500px', + overflowY: 'auto', + '&>div': { + marginBottom: '20px' + } + }, + failedTask: { + borderRadius: '8px', + border: `1px solid ${theme.palette.grey[200]}`, + background: '#FEEDED', + minHeight: '48px', + padding: '8px 12px', + wordBreak: 'break-word', + '&>div': { + marginBottom: '16px' + }, + display: 'flex', + justifyContent: 'space-between', + gap: '8px' + }, + expandMoreButton: { + flex: '0 0 90px' + }, + showLog: { + fontSize: '12px', + fontStyle: 'normal', + fontWeight: 400, + lineHeight: 'normal', + textDecorationLine: 'underline', + '& > img': { + marginLeft: '5px' + }, + textAlign: 'right', + cursor: 'pointer' + } +})); + +export const SubTaskDetails: FC = ({ currentTask }) => { + const classes = useStyles(); + const [expandDetails, toggleExpandDetails] = useToggle(false); + const failedTask = isTaskFailed(currentTask); + + const { t } = useTranslation('translation', { + keyPrefix: 'taskDetails.progress' + }); + + const { data: failedSubTasks, isLoading } = useQuery( + ['failed_task', currentTask.id], + () => getFailedTaskDetails(currentTask.id!), + { + enabled: !!currentTask && failedTask, + select: (data) => data.data + } + ); + + if (!currentTask) return null; + + const getFailedTaskData = () => { + if (isLoading) return ; + return ( + +
    + { + <> +
    + {failedSubTasks?.failedSubTasks.map((task) => ( +
    {task.errorString}
    + ))} +
    + toggleExpandDetails(!expandDetails)} + data-testid="expand-failed-task" + > + {t('expand')} + + + } +
    +
    + ); + }; + + return ( +
    + {failedTask && getFailedTaskData()} + {currentTask.status !== TaskStates.RUNNING && ( +
    { + window.open(`/logs/?queryRegex=${currentTask.correlationId}`, '_blank'); + }} + > + {t('showLog')} + link +
    + )} + + {[...currentTask.details.taskDetails].map((task, index) => ( + + ))} +
    + ); +}; + +export type SubTaskCardProps = { + subTask: TaskDetails; + index: number; +}; + +const subTaskCardStyles = makeStyles((theme) => ({ + card: { + borderRadius: '8px', + border: `1px solid ${theme.palette.grey[200]}`, + background: theme.palette.common.white, + minHeight: '48px', + padding: '8px 12px' + }, + header: { + display: 'flex', + alignItems: 'center', + gap: '8px', + cursor: 'pointer', + userSelect: 'none', + color: theme.palette.grey[900] + }, + caret: { + fontSize: '16px' + }, + indexCircle: { + height: '32px', + width: '32px', + background: theme.palette.grey[100], + borderRadius: '50%', + textAlign: 'center', + lineHeight: '32px', + color: theme.palette.grey[600], + fontSize: '14px', + fontWeight: 400, + '&.Running': { + background: theme.palette.primary[200], + '& i': { + color: theme.palette.primary[600] + } + }, + '&.Success': { + background: '#DCF8EC', + '& i': { + color: theme.palette.success[500] + } + }, + '&.Failure,&.Aborted,&.Abort': { + background: theme.palette.error[100], + '& i': { + color: theme.palette.error[500] + } + } + }, + content: { + padding: '8px 10px', + marginLeft: '46px', + marginBottom: '8px', + borderRadius: '8px', + marginTop: '16px', + background: theme.palette.grey[100], + '&.Success,&.Created': { + background: '#EEDED' + }, + '&.Failure,&.Aborted,&.Abort': { + background: '#FEEDED' + } + }, + rowCollapsed: { + transitionDuration: '0.2s', + transform: 'rotate(0deg)' + }, + rowExpanded: { + transitionDuration: '0.2s', + transform: 'rotate(90deg)' + } +})); + +export const SubTaskCard: FC = ({ subTask, index }) => { + const classes = subTaskCardStyles(); + + const [showDetails, toggleDetails] = useToggle(false); + + const getTaskIcon = () => { + switch (subTask.state) { + case TaskStates.RUNNING: + return ; + case TaskStates.SUCCESS: + return ; + case TaskStates.ABORTED: + case TaskStates.FAILURE: + case TaskStates.ABORT: + return ; + case TaskStates.CREATED: + case TaskStates.INITIALIZING: + case TaskStates.UNKNOWN: + default: + return index; + } + }; + + return ( +
    +
    toggleDetails(!showDetails)}> + +
    + {/* {isTaskRunning ? : index} */} + {getTaskIcon()} +
    + {subTask.title} +
    + +
    + {subTask.description} +
    +
    +
    + ); +}; diff --git a/managed/ui/src/redesign/features/tasks/components/drawerComp/TaskDetailActions.tsx b/managed/ui/src/redesign/features/tasks/components/drawerComp/TaskDetailActions.tsx new file mode 100644 index 000000000000..dfc655662e5f --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/drawerComp/TaskDetailActions.tsx @@ -0,0 +1,173 @@ +/* + * Created on Wed Dec 20 2023 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { FC } from 'react'; +import { useMutation } from 'react-query'; +import { toast } from 'react-toastify'; +import { useDispatch } from 'react-redux'; +import { useToggle } from 'react-use'; +import { useTranslation } from 'react-i18next'; +import { makeStyles } from '@material-ui/core'; +import { YBButton, YBModal } from '../../../../components'; +import { fetchCustomerTasks } from '../../../../../actions/tasks'; +import { abortTask, retryTasks } from './api'; +import { TaskDrawerCompProps } from './dtos'; +import TaskDiffModal from '../TaskDiffModal'; +import { doesTaskSupportsDiffData } from '../../TaskUtils'; + +const useStyles = makeStyles(() => ({ + root: { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-end', + gap: '12px' + } +})); + +export const TaskDetailActions: FC = ({ currentTask }) => { + const [showAbortConfirmationModal, toggleAbortConfirmationModal] = useToggle(false); + const [showRetryConfirmationModal, toggleRetryConfirmationModal] = useToggle(false); + const [showTaskDiffModal, toggleTaskDiffModal] = useToggle(false); + + const { t } = useTranslation('translation', { + keyPrefix: 'taskDetails.actions' + }); + + const classes = useStyles(); + + const dispatch = useDispatch(); + + const doRetryTask = useMutation(() => retryTasks(currentTask?.id), { + onSuccess: () => { + toast.success(t('messages.taskRetrySuccess')); + }, + onError: () => { + toast.error(t('messages.taskRetryFailed')); + }, + onSettled: () => { + toggleRetryConfirmationModal(false); + dispatch(fetchCustomerTasks()); + } + }); + + const doabortTask = useMutation(() => abortTask(currentTask?.id), { + onSuccess: () => { + toast.success(t('messages.taskAbortSuccess')); + }, + onError: () => { + toast.error(t('messages.taskAbortFailed')); + }, + onSettled: () => { + toggleAbortConfirmationModal(false); + dispatch(fetchCustomerTasks()); + } + }); + + return ( +
    + {doesTaskSupportsDiffData(currentTask) && ( + { + toggleTaskDiffModal(true); + }} + > + {t(`diffs.${currentTask?.type}`)} + + )} + {currentTask?.retryable && ( + { + toggleRetryConfirmationModal(true); + }} + data-testid="retry-task" + > + {t('retry')} + + )} + {currentTask?.abortable && ( + { + toggleAbortConfirmationModal(true); + }} + data-testid="abort-task" + > + {t('abort')} + + )} + toggleAbortConfirmationModal(false)} + onSubmit={() => doabortTask.mutate()} + /> + toggleRetryConfirmationModal(false)} + onSubmit={() => doRetryTask.mutate()} + /> + toggleTaskDiffModal(false)} + currentTask={currentTask} + /> +
    + ); +}; + +interface ConfirmationModalProps { + visible: boolean; + onClose: () => void; + onSubmit: () => void; +} + +const AbortConfirmModal: FC = ({ visible, onClose, onSubmit }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'taskDetails.actions' + }); + + if (!visible) return null; + + return ( + + {t('messages.abortConfirmMsg')} + + ); +}; + +const RetryConfirmModal: FC = ({ visible, onClose, onSubmit }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'taskDetails.actions' + }); + + if (!visible) return null; + return ( + + {t('messages.retryConfirmMsg')} + + ); +}; diff --git a/managed/ui/src/redesign/features/tasks/components/drawerComp/TaskDetailHeader.tsx b/managed/ui/src/redesign/features/tasks/components/drawerComp/TaskDetailHeader.tsx new file mode 100644 index 000000000000..28c1f3b9bc15 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/drawerComp/TaskDetailHeader.tsx @@ -0,0 +1,48 @@ +/* + * Created on Wed Dec 20 2023 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { FC } from 'react'; +import clsx from 'clsx'; +import { useTranslation } from 'react-i18next'; +import { Typography, makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles((theme) => ({ + header: { + height: '68px', + padding: '24px 20px', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + borderBottom: `2px solid ${theme.palette.ybacolors.ybBorderGray}` + }, + closeIcon: { + fontSize: '20px', + color: theme.palette.ybacolors.ybIcon, + cursor: 'pointer' + } +})); + +export interface TaskDetailsHeaderProps { + onClose: () => void; +} + +export const TaskDetailsHeader: FC = ({ onClose }) => { + const classes = useStyles(); + + const { t } = useTranslation('translation', { + keyPrefix: 'taskDetails.header' + }); + + return ( +
    + {t('title')} + onClose()} /> +
    + ); +}; diff --git a/managed/ui/src/redesign/features/tasks/components/drawerComp/TaskDetailInfo.tsx b/managed/ui/src/redesign/features/tasks/components/drawerComp/TaskDetailInfo.tsx new file mode 100644 index 000000000000..7a8f42e50af7 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/drawerComp/TaskDetailInfo.tsx @@ -0,0 +1,135 @@ +/* + * Created on Wed Dec 20 2023 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { FC, Fragment } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Typography, makeStyles } from '@material-ui/core'; +import { ybFormatDate } from '../../../../helpers/DateUtils'; +import { Badge_Types, StatusBadge } from '../../../../../components/common/badge/StatusBadge'; +import { TaskDrawerCompProps } from './dtos'; +import { Task, TaskStates } from '../../dtos'; + +const useStyles = makeStyles((theme) => ({ + detailsInfo: { + borderRadius: '4px', + border: `1px solid #E3E3E5`, + background: theme.palette.ybacolors.backgroundGrayLight, + display: 'flex', + justifyContent: 'space-between', + flexWrap: 'wrap' + }, + info: { + padding: '16px', + height: '75px' + }, + label: { + marginBottom: '8px', + color: '#333', + fontSize: '12px' + }, + value: { + color: '#818182', + fontSize: '12px' + }, + hr: { + width: '100%', + height: '1px', + background: '#E3E3E5' + } +})); + +type TaskInfo = { + label: string; + value: string | JSX.Element; +}; + +const getBadgeType = (status: Task['status']): Badge_Types => { + switch (status) { + case TaskStates.CREATED: + case TaskStates.INITIALIZING: + case TaskStates.RUNNING: + return Badge_Types.IN_PROGRESS; + case TaskStates.UNKNOWN: + return Badge_Types.SKIPPED; + case TaskStates.SUCCESS: + return Badge_Types.SUCCESS; + case TaskStates.FAILURE: + return Badge_Types.FAILED; + case TaskStates.ABORTED: + case TaskStates.ABORT: + return Badge_Types.STOPPED; + } +}; + +export const TaskDetailInfo: FC = ({ currentTask }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'taskDetails.info' + }); + + const classes = useStyles(); + + if (!currentTask) return null; + + const taskInfo: TaskInfo[] = [ + { + label: t('type'), + value: currentTask.typeName + }, + { + label: t('target'), + value: currentTask.target + }, + { + label: t('status'), + value: ( + + ) + }, + { + label: t('startTime'), + value: ybFormatDate(currentTask.createTime) + }, + { + label: t('endTime'), + value: ybFormatDate(currentTask.completionTime) + } + ]; + + if (currentTask.type === 'SoftwareUpgrade') { + taskInfo.push({ + label: t('startVersion'), + value: currentTask.details.versionNumbers?.ybSoftwareVersion ?? '-' + }); + taskInfo.push({ + label: t('targetVersion'), + value: currentTask.details.versionNumbers?.ybSoftwareVersion ?? '-' + }); + } + + return ( +
    + {taskInfo.map((task, i) => ( + +
    + + {task.label} + + + {task.value} + +
    + {(i + 1) % 3 === 0 &&
    } + + ))} +
    + ); +}; diff --git a/managed/ui/src/redesign/features/tasks/components/drawerComp/TaskDetailProgress.tsx b/managed/ui/src/redesign/features/tasks/components/drawerComp/TaskDetailProgress.tsx new file mode 100644 index 000000000000..7016ae2309d5 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/drawerComp/TaskDetailProgress.tsx @@ -0,0 +1,68 @@ +/* + * Created on Wed Dec 20 2023 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Typography, makeStyles } from '@material-ui/core'; +import { YBProgress, YBProgressBarState } from '../../../../components/YBProgress/YBLinearProgress'; +import { TaskDrawerCompProps } from './dtos'; +import { TaskStates } from '../../dtos'; + +const useStyles = makeStyles(() => ({ + title: { + fontWeight: 500, + marginLeft: '2px', + marginBottom: '16px' + }, + percentText: { + textAlign: 'right', + color: 'rgba(35, 35, 41, 0.40)', + marginTop: '8px' + } +})); +export const TaskDetailProgress: FC = ({ currentTask }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'taskDetails.progress' + }); + + const classes = useStyles(); + + if (!currentTask) return null; + + const getProgressState = () => { + switch (currentTask.status) { + case TaskStates.ABORT: + case TaskStates.ABORTED: + return YBProgressBarState.Warning; + case TaskStates.FAILURE: + return YBProgressBarState.Error; + case TaskStates.INITIALIZING: + case TaskStates.CREATED: + case TaskStates.RUNNING: + return YBProgressBarState.InProgress; + case TaskStates.SUCCESS: + return YBProgressBarState.Success; + case TaskStates.UNKNOWN: + default: + return YBProgressBarState.Unknown; + } + }; + + return ( +
    + + {t('title')} + + + + {t('complete', { progress: Math.trunc(currentTask.percentComplete) })} + +
    + ); +}; diff --git a/managed/ui/src/redesign/features/tasks/components/drawerComp/api.ts b/managed/ui/src/redesign/features/tasks/components/drawerComp/api.ts new file mode 100644 index 000000000000..b9d6663bfac8 --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/drawerComp/api.ts @@ -0,0 +1,45 @@ +/* + * Created on Tue Jan 09 2024 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import axios from 'axios'; +import { ROOT_URL } from '../../../../../config'; +import { FailedTask } from '../../dtos'; + +/** + * Get the details of the failed task. + * @param taskUUID The UUID of the task. + * @returns The details of the failed task. + */ + +export const getFailedTaskDetails = (taskUUID: string) => { + const cUUID = localStorage.getItem('customerId'); + const requestUrl = `${ROOT_URL}/customers/${cUUID}/tasks/${taskUUID}/failed`; + return axios.get(requestUrl); +}; + +/** + * Retry the failed tasks. + * @param taskUUID The UUID of the task. + * @returns The response of the retry task API. + */ + +export const retryTasks = (taskUUID: string) => { + const cUUID = localStorage.getItem('customerId'); + return axios.post(`${ROOT_URL}/customers/${cUUID}/tasks/${taskUUID}`); +}; + +/** + * Abort the task. + * @param taskUUID The UUID of the task. + * @returns The response of the abort task API. + */ +export const abortTask = (taskUUID: string) => { + const cUUID = localStorage.getItem('customerId'); + return axios.post(`${ROOT_URL}/customers/${cUUID}/tasks/${taskUUID}/abort`); +}; diff --git a/managed/ui/src/redesign/features/tasks/components/drawerComp/dtos.ts b/managed/ui/src/redesign/features/tasks/components/drawerComp/dtos.ts new file mode 100644 index 000000000000..7b3ea98989cf --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/components/drawerComp/dtos.ts @@ -0,0 +1,14 @@ +/* + * Created on Fri Dec 22 2023 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +import { Task } from '../../dtos'; + +export interface TaskDrawerCompProps { + currentTask: Task; +} diff --git a/managed/ui/src/redesign/features/tasks/dtos.ts b/managed/ui/src/redesign/features/tasks/dtos.ts new file mode 100644 index 000000000000..005084078a5c --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/dtos.ts @@ -0,0 +1,73 @@ +/* + * Created on Wed Dec 20 2023 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +// UserTaskDetails.java +export interface TaskDetails { + title: string; + description: string; + state: TaskStates; + extraDetails: any[]; +} + +// CustomerTaskFormData.java + +export enum TaskStates { + CREATED = 'Created', + INITIALIZING = 'Initializing', + RUNNING = 'Running', + UNKNOWN = 'Unknown', + SUCCESS = 'Success', + FAILURE = 'Failure', + ABORTED = 'Aborted', + ABORT = 'Abort' +} + +export const TaskType = { + GFlags_UPGRADE: 'GFlagsUpgrade', + EDIT: 'Update', + SOFTWARE_UPGRADE: 'SoftwareUpgrade' +}; +export const TargetType = { + UNIVERSE: 'Universe', + BACKUP: 'Backup', + GFlags: 'GFlags' +}; +export interface Task { + id: string; + title: string; + percentComplete: number; + createTime: string; + completionTime: string; + target: typeof TargetType & string; + targetUUID: string; + type: typeof TaskType & string; + typeName: string; + status: TaskStates; + details: { + taskDetails: TaskDetails[]; + versionNumbers?: { + ybPrevSoftwareVersion?: string; + ybSoftwareVersion?: string; + }; + }; + abortable: boolean; + retryable: boolean; + correlationId: string; + userEmail: string; +} + +export interface FailedTask { + failedSubTasks: { + errorString: string; + subTaskGroupType: string; + subTaskState: TaskStates; + subTaskType: string; + subTaskUUID: string; + }[]; +} diff --git a/managed/ui/src/redesign/features/tasks/index.ts b/managed/ui/src/redesign/features/tasks/index.ts new file mode 100644 index 000000000000..ef62ff4d260d --- /dev/null +++ b/managed/ui/src/redesign/features/tasks/index.ts @@ -0,0 +1,10 @@ +/* + * Created on Wed Dec 20 2023 + * + * Copyright 2021 YugaByte, Inc. and Contributors + * Licensed under the Polyform Free Trial License 1.0.0 (the "License") + * You may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt + */ + +export * from './components/TaskDetailDrawer'; diff --git a/managed/ui/src/redesign/features/universe/universe-form/utils/dto.ts b/managed/ui/src/redesign/features/universe/universe-form/utils/dto.ts index a2b7990bd88a..9de9e002f9e5 100644 --- a/managed/ui/src/redesign/features/universe/universe-form/utils/dto.ts +++ b/managed/ui/src/redesign/features/universe/universe-form/utils/dto.ts @@ -140,7 +140,12 @@ export interface UserIntent { specificGFlags?: { gflagGroups?: string[]; inheritFromPrimary: boolean; - perProcessFlags: {}; + perProcessFlags: { + value?: { + MASTER?: Record; + TSERVER?: Record; + } + }; perAZ?: {}; }; masterGFlags?: Record; @@ -227,6 +232,7 @@ export interface UniverseDetails { rootAndClientRootCASame: boolean; universeUUID: string; updateInProgress: boolean; + updatingTaskUUID?: string; updateSucceeded: boolean; userAZSelected: boolean; enableYbc: boolean; diff --git a/managed/ui/src/redesign/helpers/api.ts b/managed/ui/src/redesign/helpers/api.ts index 7914ed443374..cd050a39194a 100644 --- a/managed/ui/src/redesign/helpers/api.ts +++ b/managed/ui/src/redesign/helpers/api.ts @@ -685,7 +685,6 @@ class ApiService { const requestUrl = `${ROOT_URL}/customers/${this.getCustomerId()}/tasks_list`; return axios .get(requestUrl, { params: { uUUID: universeUuid } }) - .then((response) => response.data); }; getAlerts = ( diff --git a/managed/ui/src/reducers/feature.js b/managed/ui/src/reducers/feature.js index fe62df2aed1c..f928cbd4d3ea 100644 --- a/managed/ui/src/reducers/feature.js +++ b/managed/ui/src/reducers/feature.js @@ -36,7 +36,8 @@ const initialStateFeatureInTest = { enableNewRestoreModal: true, enableCACertRotation: true, enableNewAdvancedRestoreModal: false, - showReplicationSlots: true + showReplicationSlots: true, + newTaskDetailsUI: false }; const initialStateFeatureReleased = { diff --git a/managed/ui/src/reducers/reducer_tasks.js b/managed/ui/src/reducers/reducer_tasks.js index 26cd94f26283..171c0d54e588 100644 --- a/managed/ui/src/reducers/reducer_tasks.js +++ b/managed/ui/src/reducers/reducer_tasks.js @@ -8,7 +8,8 @@ import { FETCH_CUSTOMER_TASKS_FAILURE, RESET_CUSTOMER_TASKS, FETCH_FAILED_TASK_DETAIL, - FETCH_FAILED_TASK_DETAIL_RESPONSE + FETCH_FAILED_TASK_DETAIL_RESPONSE, + PATCH_TASKS_FOR_CUSTOMER } from '../actions/tasks'; import moment from 'moment'; @@ -61,6 +62,14 @@ export default function (state = INITIAL_STATE, action) { return setPromiseResponse(state, 'failedTasks', action); case RESET_CUSTOMER_TASKS: return { ...state, customerTaskList: [] }; + case PATCH_TASKS_FOR_CUSTOMER: + return { + ...state, + customerTaskList: state.customerTaskList + .filter((task) => task.targetUUID !== action.payload.universeUUID) + .concat(action.payload.tasks) + .sort((a, b) => moment(b.createTime).isBefore(a.createTime) ? -1 : 1) + }; default: return state; } diff --git a/managed/ui/src/translations/en.json b/managed/ui/src/translations/en.json index 5fdbcf26d060..7d23849e7476 100644 --- a/managed/ui/src/translations/en.json +++ b/managed/ui/src/translations/en.json @@ -823,7 +823,9 @@ "label": "Metrics", "metricsUnavailableDuringFailover": "Metrics are unavailable during failover." }, - "tables": { "label": "Tables" } + "tables": { + "label": "Tables" + } }, "status": { "Initializing": "Initializing", @@ -1948,6 +1950,75 @@ "helperText": "YugabyteDB Anywhere will use this URL to retrieve the installation file during deployment and upgrades." } }, + "taskDetails": { + "header": { + "title": "Operation Details" + }, + "taskNotfound": "Task not found!", + "actions": { + "retry": "Retry", + "abort": "Abort", + "diffs": { + "Update": "View Universe Changes", + "SoftwareUpgrade": "View Software Upgrade Changes", + "GFlagsUpgrade": "View GFlags Changes" + }, + "messages": { + "abortConfirmTitle": "Abort Running Task ?", + "abortConfirmMsg": "Do you really want to abort this task?", + "retryConfirmTitle": "Retry Task ?", + "retryConfirmMsg": "Do you really want to retry this task?", + "taskAbortSuccess": "Task aborted successfully", + "taskAbortFailed": "Task abort failed", + "taskRetrySuccess": "Task retry success", + "taskRetryFailed": "Task retry failed" + } + }, + "info": { + "task": "Task", + "type": "Type", + "target": "Target", + "status": "Status", + "startTime": "Start Time", + "endTime": "End time", + "startVersion": "Start Version", + "targetVersion": "Target Version" + }, + "progress": { + "title": "Progress", + "complete": "{{progress}}% Completed", + "expand": "Expand", + "showLog": "YugaWare Log" + }, + "banner": { + "viewDetails": "View Details", + "taskProgress": "{{percent}}%", + "opNotAvailable": "Some operations are not available while the current operation is in progress" + }, + "simple": { + "viewDetails": "View Details" + }, + "diffModal": { + "title": "Universe Changes", + "expandAll": "Expand All", + "diffDetailsNotFound": "Unable to find diff details", + "alerts": { + "inProgress": "This operation intends to modify the following {{count}} value(s). ", + "failed": "Changes haven't been fully implemented due to the failure of the task." + }, + "titles": { + "Update": "Universe Changes", + "SoftwareUpgrade": "Software Upgrade Changes", + "GFlagsUpgrade": " GFlags Upgrade Changes" + }, + "universeDiff": { + "region": "Region", + "availabilityZone": "Availability Zone", + "nodes": "nodes", + "preferred": "preferred" + } + } + }, "ha": { "config": { "globalState": { From 95fb1884e00a763f3db1826557cea400731b6b4a Mon Sep 17 00:00:00 2001 From: asharma-yb Date: Mon, 1 Jul 2024 13:00:36 +0530 Subject: [PATCH 1171/1195] [PLAT-14225] Implement v2 Group Mapping APIs Summary: Add the following v2 Group Mapping CRUD APIs : ``` GET /customers/:cUUID/auth/group_mappings Response body: [ { "group_identifier": "test-group1", "uuid": "b0968020-5626-4ae5-b516-d4d77e5302c8", "type": "OIDC", "role_resource_definitions": [ { "role_uuid": "f3671ea7-6579-4fc0-ba33-f8da45ff19da" }, { "role_uuid": "9ce94a1f-3ed7-4a2f-8a6a-bda354d2dd1b", "resource_group": { "resource_definition_set": [ { "resource_type": "UNIVERSE", "allow_all": true, "resource_uuid_set": [] }, { "resource_type": "OTHER", "allow_all": false, "resource_uuid_set": [ "f33e3c9b-75ab-4c30-80ad-cba85646ea39" ] } ] } } ] } ] PUT v2/customers/:cUUID/auth/group_mappings Request body same as above. DELETE /customers/:custUUID/auth/group_mappings/:groupUUID ``` The diff also has migrations and other rbac changes required to add role bindings for Groups. Please refer to the design doc for complete context - https://docs.google.com/document/d/1qGYu6swY8xWFzqhNWZ6uKHHtKSO9847lm2I0sDB5okU Test Plan: Manually tested all changes. UTs pending. Reviewers: #yba-api-review, sneelakantan, svarshney, skurapati Reviewed By: #yba-api-review, sneelakantan Subscribers: sneelakantan, sanketh, yugaware Differential Revision: https://phorge.dev.yugabyte.com/D36269 --- .../AuthenticationApiControllerImp.java | 31 +++ .../v2/handlers/AuthenticationHandler.java | 181 +++++++++++++++ .../api/v2/mappers/ResourceGroupMapper.java | 27 +++ .../mappers/RoleResourceDefinitionMapper.java | 32 +++ .../java/com/yugabyte/yw/common/AppInit.java | 13 ++ .../yw/common/config/GlobalConfKeys.java | 9 + .../yw/common/rbac/RoleBindingUtil.java | 3 +- .../common/rbac/RoleResourceDefinition.java | 2 +- .../java/com/yugabyte/yw/models/Audit.java | 5 +- .../yugabyte/yw/models/GroupMappingInfo.java | 108 +++++++++ .../com/yugabyte/yw/models/Principal.java | 88 ++++++++ .../java/com/yugabyte/yw/models/Users.java | 23 ++ .../yugabyte/yw/models/migrations/V360.java | 84 +++++++ .../yugabyte/yw/models/rbac/RoleBinding.java | 96 +++++++- .../postgres/V361__Populate_Princpals.java | 25 +++ .../src/main/resources/application.test.conf | 1 + .../common/V360__Create_Group_table.sql | 33 +++ .../common/V362__Alter_role_binding.sql | 10 + .../requestBodies/GroupMappingReq.yaml | 7 + .../responses/GroupMappingResp.yaml | 7 + .../components/schemas/GroupMappingSpec.yaml | 29 +++ .../schemas/ResourceDefinitionSpec.yaml | 25 +++ .../components/schemas/ResourceGroupSpec.yaml | 10 + .../schemas/RoleResourceDefinitionSpec.yaml | 12 + .../main/resources/openapi/openapi_split.yaml | 2 + .../main/resources/openapi/paths/_index.yaml | 117 ++++++++++ .../openapi/paths/authentication.yaml | 117 ++++++++++ managed/src/main/resources/reference.conf | 1 + .../src/main/resources/swagger-strict.json | 59 ++++- managed/src/main/resources/swagger.json | 59 ++++- .../AuthenticationGroupMappingApisTest.java | 211 ++++++++++++++++++ 31 files changed, 1411 insertions(+), 16 deletions(-) create mode 100644 managed/src/main/java/api/v2/controllers/AuthenticationApiControllerImp.java create mode 100644 managed/src/main/java/api/v2/handlers/AuthenticationHandler.java create mode 100644 managed/src/main/java/api/v2/mappers/ResourceGroupMapper.java create mode 100644 managed/src/main/java/api/v2/mappers/RoleResourceDefinitionMapper.java create mode 100644 managed/src/main/java/com/yugabyte/yw/models/GroupMappingInfo.java create mode 100644 managed/src/main/java/com/yugabyte/yw/models/Principal.java create mode 100644 managed/src/main/java/com/yugabyte/yw/models/migrations/V360.java create mode 100644 managed/src/main/java/db/migration/default_/postgres/V361__Populate_Princpals.java create mode 100644 managed/src/main/resources/db/migration/default_/common/V360__Create_Group_table.sql create mode 100644 managed/src/main/resources/db/migration/default_/common/V362__Alter_role_binding.sql create mode 100644 managed/src/main/resources/openapi/components/requestBodies/GroupMappingReq.yaml create mode 100644 managed/src/main/resources/openapi/components/responses/GroupMappingResp.yaml create mode 100644 managed/src/main/resources/openapi/components/schemas/GroupMappingSpec.yaml create mode 100644 managed/src/main/resources/openapi/components/schemas/ResourceDefinitionSpec.yaml create mode 100644 managed/src/main/resources/openapi/components/schemas/ResourceGroupSpec.yaml create mode 100644 managed/src/main/resources/openapi/components/schemas/RoleResourceDefinitionSpec.yaml create mode 100644 managed/src/main/resources/openapi/paths/authentication.yaml create mode 100644 managed/src/test/java/com/yugabyte/yw/api/v2/AuthenticationGroupMappingApisTest.java diff --git a/managed/src/main/java/api/v2/controllers/AuthenticationApiControllerImp.java b/managed/src/main/java/api/v2/controllers/AuthenticationApiControllerImp.java new file mode 100644 index 000000000000..8256fb358648 --- /dev/null +++ b/managed/src/main/java/api/v2/controllers/AuthenticationApiControllerImp.java @@ -0,0 +1,31 @@ +// Copyright (c) Yugabyte, Inc. + +package api.v2.controllers; + +import api.v2.handlers.AuthenticationHandler; +import api.v2.models.GroupMappingSpec; +import com.google.inject.Inject; +import java.util.List; +import java.util.UUID; +import play.mvc.Http; + +public class AuthenticationApiControllerImp extends AuthenticationApiControllerImpInterface { + + @Inject AuthenticationHandler authHandler; + + @Override + public List listMappings(Http.Request request, UUID cUUID) throws Exception { + return authHandler.listMappings(cUUID); + } + + @Override + public void updateGroupMappings( + Http.Request request, UUID cUUID, List groupMappingSpec) throws Exception { + authHandler.updateGroupMappings(request, cUUID, groupMappingSpec); + } + + @Override + public void deleteGroupMappings(Http.Request request, UUID cUUID, UUID gUUID) throws Exception { + authHandler.deleteGroupMappings(request, cUUID, gUUID); + } +} diff --git a/managed/src/main/java/api/v2/handlers/AuthenticationHandler.java b/managed/src/main/java/api/v2/handlers/AuthenticationHandler.java new file mode 100644 index 000000000000..59a42ec234ba --- /dev/null +++ b/managed/src/main/java/api/v2/handlers/AuthenticationHandler.java @@ -0,0 +1,181 @@ +// Copyright (c) Yugabyte, Inc. + +package api.v2.handlers; + +import static play.mvc.Http.Status.BAD_REQUEST; +import static play.mvc.Http.Status.NOT_FOUND; + +import api.v2.mappers.RoleResourceDefinitionMapper; +import api.v2.models.GroupMappingSpec; +import api.v2.models.GroupMappingSpec.TypeEnum; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.yugabyte.yw.common.PlatformServiceException; +import com.yugabyte.yw.common.config.GlobalConfKeys; +import com.yugabyte.yw.common.config.RuntimeConfGetter; +import com.yugabyte.yw.common.rbac.RoleBindingUtil; +import com.yugabyte.yw.common.rbac.RoleResourceDefinition; +import com.yugabyte.yw.controllers.TokenAuthenticator; +import com.yugabyte.yw.models.GroupMappingInfo; +import com.yugabyte.yw.models.GroupMappingInfo.GroupType; +import com.yugabyte.yw.models.rbac.Role; +import com.yugabyte.yw.models.rbac.RoleBinding; +import com.yugabyte.yw.models.rbac.RoleBinding.RoleBindingType; +import io.ebean.annotation.Transactional; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import play.mvc.Http; + +@Slf4j +@Singleton +public class AuthenticationHandler { + + @Inject RuntimeConfGetter confGetter; + @Inject TokenAuthenticator tokenAuthenticator; + @Inject RoleBindingUtil roleBindingUtil; + + public List listMappings(UUID cUUID) throws Exception { + + checkRuntimeConfig(); + + List groupInfoList = + GroupMappingInfo.find.query().where().eq("customer_uuid", cUUID).findList(); + List specList = new ArrayList(); + for (GroupMappingInfo info : groupInfoList) { + GroupMappingSpec spec = + new GroupMappingSpec() + .groupIdentifier(info.getIdentifier()) + .type(TypeEnum.valueOf(info.getType().toString())) + .uuid(info.getGroupUUID()); + + List roleResourceDefinitions = new ArrayList<>(); + if (confGetter.getGlobalConf(GlobalConfKeys.useNewRbacAuthz)) { + // fetch all role rolebindings for the current group + List roleBindingList = RoleBinding.getAll(info.getGroupUUID()); + for (RoleBinding rb : roleBindingList) { + RoleResourceDefinition roleResourceDefinition = + new RoleResourceDefinition(rb.getRole().getRoleUUID(), rb.getResourceGroup()); + roleResourceDefinitions.add(roleResourceDefinition); + } + } else { + // No role bindings present if RBAC is off. + RoleResourceDefinition rrd = new RoleResourceDefinition(); + rrd.setRoleUUID(info.getRoleUUID()); + roleResourceDefinitions.add(rrd); + } + spec.setRoleResourceDefinitions( + RoleResourceDefinitionMapper.INSTANCE.toV2RoleResourceDefinitionList( + roleResourceDefinitions)); + specList.add(spec); + } + return specList; + } + + @Transactional + public void updateGroupMappings( + Http.Request request, UUID cUUID, List groupMappingSpec) { + boolean isSuperAdmin = tokenAuthenticator.superAdminAuthentication(request); + if (!isSuperAdmin) { + throw new PlatformServiceException(BAD_REQUEST, "Only SuperAdmin can create group mappings!"); + } + + checkRuntimeConfig(); + + for (GroupMappingSpec mapping : groupMappingSpec) { + GroupMappingInfo mappingInfo = + GroupMappingInfo.find + .query() + .where() + .eq("customer_uuid", cUUID) + .ieq("identifier", mapping.getGroupIdentifier()) + .findOne(); + + if (mappingInfo == null) { + // new entry for new group + log.info("Adding new group mapping entry for group: " + mapping.getGroupIdentifier()); + mappingInfo = + GroupMappingInfo.create( + cUUID, + mapping.getGroupIdentifier(), + GroupType.valueOf(mapping.getType().toString())); + } else { + // clear role bindings for existing group + clearRoleBindings(mappingInfo); + } + + List roleResourceDefinitions = + RoleResourceDefinitionMapper.INSTANCE.toV1RoleResourceDefinitionList( + mapping.getRoleResourceDefinitions()); + + roleBindingUtil.validateRoles(cUUID, roleResourceDefinitions); + roleBindingUtil.validateResourceGroups(cUUID, roleResourceDefinitions); + + if (confGetter.getGlobalConf(GlobalConfKeys.useNewRbacAuthz)) { + // Add role bindings if rbac is on. + for (RoleResourceDefinition rrd : roleResourceDefinitions) { + Role rbacRole = Role.getOrBadRequest(cUUID, rrd.getRoleUUID()); + RoleBinding.create(mappingInfo, RoleBindingType.Custom, rbacRole, rrd.getResourceGroup()); + } + // This role will be ignored when rbac is on. + mappingInfo.setRoleUUID(Role.get(cUUID, "ConnectOnly").getRoleUUID()); + } else { + validate(roleResourceDefinitions); + mappingInfo.setRoleUUID(roleResourceDefinitions.get(0).getRoleUUID()); + } + mappingInfo.save(); + } + } + + @Transactional + public void deleteGroupMappings(Http.Request request, UUID cUUID, UUID gUUID) { + boolean isSuperAdmin = tokenAuthenticator.superAdminAuthentication(request); + if (!isSuperAdmin) { + throw new PlatformServiceException(BAD_REQUEST, "Only SuperAdmin can delete group mappings!"); + } + + checkRuntimeConfig(); + + GroupMappingInfo entity = + GroupMappingInfo.find + .query() + .where() + .eq("customer_uuid", cUUID) + .eq("uuid", gUUID) + .findOne(); + if (entity == null) { + throw new PlatformServiceException(NOT_FOUND, "No group mapping found with uuid: " + gUUID); + } + + // Delete all role bindings + clearRoleBindings(entity); + log.info("Deleting Group Mapping with name: " + entity.getIdentifier()); + entity.delete(); + } + + private void clearRoleBindings(GroupMappingInfo mappingInfo) { + log.info("Clearing role bindings for group: " + mappingInfo.getIdentifier()); + List list = RoleBinding.getAll(mappingInfo.getGroupUUID()); + list.forEach(rb -> rb.delete()); + } + + private void checkRuntimeConfig() { + if (!confGetter.getGlobalConf(GlobalConfKeys.groupMappingRbac)) { + throw new PlatformServiceException( + BAD_REQUEST, "yb.security.group_mapping_rbac_support runtime config is disabled!"); + } + } + + /** + * Validation to make sure only a single system role is present when RBAC is off. + * + * @param cUUID + * @param rrdList + */ + private void validate(List rrdList) { + if (rrdList.size() != 1) { + throw new PlatformServiceException(BAD_REQUEST, "Need to specify a single system role!"); + } + } +} diff --git a/managed/src/main/java/api/v2/mappers/ResourceGroupMapper.java b/managed/src/main/java/api/v2/mappers/ResourceGroupMapper.java new file mode 100644 index 000000000000..232801fba83c --- /dev/null +++ b/managed/src/main/java/api/v2/mappers/ResourceGroupMapper.java @@ -0,0 +1,27 @@ +// Copyright (c) Yugabyte, Inc. + +package api.v2.mappers; + +import api.v2.models.ResourceDefinitionSpec; +import api.v2.models.ResourceGroupSpec; +import com.yugabyte.yw.models.rbac.ResourceGroup; +import com.yugabyte.yw.models.rbac.ResourceGroup.ResourceDefinition; +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper(config = CentralConfig.class) +public interface ResourceGroupMapper { + ResourceGroupMapper INSTANCE = Mappers.getMapper(ResourceGroupMapper.class); + + ResourceGroupSpec toV2ResourceGroup(ResourceGroup v1ResourceGroup); + + ResourceGroup toV1ResourceGroup(ResourceGroupSpec v2ResourceGroup); + + @Mapping(target = "resourceUuidSet", source = "resourceUUIDSet") + ResourceDefinitionSpec toV2ResourceDefinitionSpec(ResourceDefinition v1ResourceDefinition); + + @InheritInverseConfiguration + ResourceDefinition toV1ResourceDefinitionSpec(ResourceDefinitionSpec v2ResourceDefinition); +} diff --git a/managed/src/main/java/api/v2/mappers/RoleResourceDefinitionMapper.java b/managed/src/main/java/api/v2/mappers/RoleResourceDefinitionMapper.java new file mode 100644 index 000000000000..132c0b41ab9a --- /dev/null +++ b/managed/src/main/java/api/v2/mappers/RoleResourceDefinitionMapper.java @@ -0,0 +1,32 @@ +// Copyright (c) Yugabyte, Inc. + +package api.v2.mappers; + +import api.v2.models.RoleResourceDefinitionSpec; +import com.yugabyte.yw.common.rbac.RoleResourceDefinition; +import java.util.List; +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper( + config = CentralConfig.class, + uses = {ResourceGroupMapper.class}) +public interface RoleResourceDefinitionMapper { + RoleResourceDefinitionMapper INSTANCE = Mappers.getMapper(RoleResourceDefinitionMapper.class); + + @Mapping(target = "roleUuid", source = "roleUUID") + RoleResourceDefinitionSpec toV2RoleResourceDefinition( + RoleResourceDefinition v1RoleResourceDefinition); + + List toV2RoleResourceDefinitionList( + List v1RoleResourceDefinition); + + @InheritInverseConfiguration + RoleResourceDefinition toV1RoleResourceDefinition( + RoleResourceDefinitionSpec v2RoleResourceDefinitionSpec); + + List toV1RoleResourceDefinitionList( + List v2RoleResourceDefinition); +} diff --git a/managed/src/main/java/com/yugabyte/yw/common/AppInit.java b/managed/src/main/java/com/yugabyte/yw/common/AppInit.java index 203ced7cd8da..02440186b6c1 100644 --- a/managed/src/main/java/com/yugabyte/yw/common/AppInit.java +++ b/managed/src/main/java/com/yugabyte/yw/common/AppInit.java @@ -44,8 +44,11 @@ import com.yugabyte.yw.models.ExtraMigration; import com.yugabyte.yw.models.HighAvailabilityConfig; import com.yugabyte.yw.models.MetricConfig; +import com.yugabyte.yw.models.Principal; +import com.yugabyte.yw.models.Users; import com.yugabyte.yw.scheduler.JobScheduler; import com.yugabyte.yw.scheduler.Scheduler; +import db.migration.default_.common.R__Sync_System_Roles; import io.ebean.DB; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.Gauge; @@ -133,6 +136,16 @@ public AppInit( Customer customer = Customer.getAll().get(0); alertDestinationService.createDefaultDestination(customer.getUuid()); alertConfigurationService.createDefaultConfigs(customer); + // Create system roles for the newly created customer. + R__Sync_System_Roles.syncSystemRoles(); + // Principal entry for newly created users. + for (Users user : Users.find.all()) { + Principal principal = Principal.get(user.getUuid()); + if (principal == null) { + log.info("Adding Principal entry for user with email: " + user.getEmail()); + new Principal(user).save(); + } + } } String storagePath = AppConfigHelper.getStoragePath(); diff --git a/managed/src/main/java/com/yugabyte/yw/common/config/GlobalConfKeys.java b/managed/src/main/java/com/yugabyte/yw/common/config/GlobalConfKeys.java index ded1bbf669be..3acebce99e15 100644 --- a/managed/src/main/java/com/yugabyte/yw/common/config/GlobalConfKeys.java +++ b/managed/src/main/java/com/yugabyte/yw/common/config/GlobalConfKeys.java @@ -1414,4 +1414,13 @@ public class GlobalConfKeys extends RuntimeConfigKeysModule { + " throwing UNAVAILABLE", ConfDataType.IntegerType, ImmutableList.of(ConfKeyTags.BETA)); + // Also need rbac to be on for this key to have effect. + public static final ConfKeyInfo groupMappingRbac = + new ConfKeyInfo<>( + "yb.security.group_mapping_rbac_support", + ScopeType.GLOBAL, + "Enable RBAC for Groups", + "Map LDAP/OIDC groups to custom roles defined by RBAC.", + ConfDataType.BooleanType, + ImmutableList.of(ConfKeyTags.INTERNAL)); } diff --git a/managed/src/main/java/com/yugabyte/yw/common/rbac/RoleBindingUtil.java b/managed/src/main/java/com/yugabyte/yw/common/rbac/RoleBindingUtil.java index 0498ff622a93..0e00185c81bd 100644 --- a/managed/src/main/java/com/yugabyte/yw/common/rbac/RoleBindingUtil.java +++ b/managed/src/main/java/com/yugabyte/yw/common/rbac/RoleBindingUtil.java @@ -100,7 +100,8 @@ public void validateRoles( if (Users.Role.SuperAdmin.name().equals(role.getName())) { String errMsg = String.format( - "Cannot assign SuperAdmin role to a user in the roleResourceDefinition: %s.", + "Cannot assign SuperAdmin role to a user or group in the roleResourceDefinition:" + + " %s.", roleResourceDefinition.toString()); log.error(errMsg); throw new PlatformServiceException(BAD_REQUEST, errMsg); diff --git a/managed/src/main/java/com/yugabyte/yw/common/rbac/RoleResourceDefinition.java b/managed/src/main/java/com/yugabyte/yw/common/rbac/RoleResourceDefinition.java index f470e957d3b9..50a451891bfd 100644 --- a/managed/src/main/java/com/yugabyte/yw/common/rbac/RoleResourceDefinition.java +++ b/managed/src/main/java/com/yugabyte/yw/common/rbac/RoleResourceDefinition.java @@ -14,7 +14,7 @@ import lombok.Setter; import lombok.ToString; -@ApiModel(description = "Role and resource group definition.") +@ApiModel(description = "Defines the association of Role to Resource Groups.") @Getter @Setter @AllArgsConstructor diff --git a/managed/src/main/java/com/yugabyte/yw/models/Audit.java b/managed/src/main/java/com/yugabyte/yw/models/Audit.java index fd05abef2411..989965376de3 100644 --- a/managed/src/main/java/com/yugabyte/yw/models/Audit.java +++ b/managed/src/main/java/com/yugabyte/yw/models/Audit.java @@ -181,7 +181,10 @@ public enum TargetType { RoleBinding, @EnumValue("OIDC Group Mapping") - OIDCGroupMapping + OIDCGroupMapping, + + @EnumValue("Group Mapping") + GroupMapping } public enum ActionType { diff --git a/managed/src/main/java/com/yugabyte/yw/models/GroupMappingInfo.java b/managed/src/main/java/com/yugabyte/yw/models/GroupMappingInfo.java new file mode 100644 index 000000000000..0d113b5f064e --- /dev/null +++ b/managed/src/main/java/com/yugabyte/yw/models/GroupMappingInfo.java @@ -0,0 +1,108 @@ +// Copyright (c) Yugabyte, Inc. + +package com.yugabyte.yw.models; + +import static play.mvc.Http.Status.BAD_REQUEST; + +import com.yugabyte.yw.common.PlatformServiceException; +import com.yugabyte.yw.models.rbac.Role; +import io.ebean.Finder; +import io.ebean.Model; +import io.ebean.annotation.EnumValue; +import io.ebean.annotation.Transactional; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.util.UUID; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; + +@Entity +@Table(name = "groups_mapping_info") +@Data +@EqualsAndHashCode(callSuper = false) +@Slf4j +public class GroupMappingInfo extends Model { + @Id + @Column(name = "uuid", nullable = false, unique = true) + private UUID groupUUID = UUID.randomUUID(); + + @Column(name = "customer_uuid", nullable = false) + private UUID customerUUID; + + // This will only be used when RBAC is off. + @Column(name = "role_uuid", nullable = false) + private UUID roleUUID; + + @Column(name = "identifier", nullable = false, unique = true) + private String identifier = null; + + @Column(name = "type", nullable = false) + private GroupType type; + + public enum GroupType { + @EnumValue("LDAP") + LDAP, + + @EnumValue("OIDC") + OIDC; + } + + public static GroupMappingInfo create(UUID customerUUID, String identifier, GroupType type) { + // Assign ConnectOnly if no role is assigned + UUID roleUUID = Role.get(customerUUID, "ConnectOnly").getRoleUUID(); + return create(customerUUID, roleUUID, identifier, type); + } + + @Transactional + public static GroupMappingInfo create( + UUID customerUUID, UUID roleUUID, String identifier, GroupType type) { + GroupMappingInfo entity = new GroupMappingInfo(); + entity.customerUUID = customerUUID; + entity.identifier = identifier.toLowerCase(); + entity.roleUUID = roleUUID; + entity.type = type; + entity.save(); + return entity; + } + + /** Wrapper around save to make sure principal entity is created. */ + @Transactional + @Override + public void save() { + super.save(); + Principal principal = Principal.get(this.groupUUID); + if (principal == null) { + log.info("Adding Principal entry for Group: " + this.identifier); + new Principal(this).save(); + } + } + + /** Wrapper around delete to make sure principal entity is deleted. */ + @Transactional + @Override + public boolean delete() { + log.info("Deleting Principal entry for Group: " + this.identifier); + Principal principal = Principal.getOrBadRequest(this.groupUUID); + principal.delete(); + return super.delete(); + } + + public static GroupMappingInfo get(UUID groupUUID) { + GroupMappingInfo info = find.query().where().eq("uuid", groupUUID).findOne(); + return info; + } + + public static GroupMappingInfo getOrBadRequest(UUID groupUuid) { + GroupMappingInfo info = get(groupUuid); + if (info == null) { + throw new PlatformServiceException(BAD_REQUEST, "Invalid Group UUID:" + groupUuid); + } + return info; + } + + public static final Finder find = + new Finder(GroupMappingInfo.class) {}; +} diff --git a/managed/src/main/java/com/yugabyte/yw/models/Principal.java b/managed/src/main/java/com/yugabyte/yw/models/Principal.java new file mode 100644 index 000000000000..cab9fcbb3671 --- /dev/null +++ b/managed/src/main/java/com/yugabyte/yw/models/Principal.java @@ -0,0 +1,88 @@ +// Copyright (c) Yugabyte, Inc. + +package com.yugabyte.yw.models; + +import static play.mvc.Http.Status.BAD_REQUEST; + +import com.yugabyte.yw.common.PlatformServiceException; +import com.yugabyte.yw.models.GroupMappingInfo.GroupType; +import io.ebean.Finder; +import io.ebean.Model; +import io.ebean.annotation.EnumValue; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.util.UUID; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "principal") +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class Principal extends Model { + // A Principal is an entity which is allowed to have role bindings. + // Currently it can either be a group or an user. + + @Id + @Column(name = "uuid", nullable = false, unique = true) + private UUID uuid; + + // At most one of these can be valid. + @Column(name = "user_uuid") + private UUID userUUID; + + @Column(name = "group_uuid") + private UUID groupUUID; + + @Column(name = "type", nullable = false) + private PrincipalType type; + + public enum PrincipalType { + @EnumValue("USER") + USER, + + @EnumValue("LDAP_GROUP") + LDAP_GROUP, + + @EnumValue("OIDC_GROUP") + OIDC_GROUP + } + + public Principal(Users user) { + uuid = user.getUuid(); + userUUID = uuid; + groupUUID = null; + type = PrincipalType.USER; + } + + public Principal(GroupMappingInfo info) { + uuid = info.getGroupUUID(); + userUUID = null; + groupUUID = uuid; + if (info.getType().equals(GroupType.LDAP)) { + type = PrincipalType.LDAP_GROUP; + } else { + type = PrincipalType.OIDC_GROUP; + } + } + + public static Principal get(UUID principalUUID) { + Principal principal = find.query().where().eq("uuid", principalUUID).findOne(); + return principal; + } + + public static Principal getOrBadRequest(UUID principalUuid) { + Principal principal = get(principalUuid); + if (principal == null) { + throw new PlatformServiceException(BAD_REQUEST, "Invalid Principal UUID:" + principalUuid); + } + return principal; + } + + public static final Finder find = + new Finder(Principal.class) {}; +} diff --git a/managed/src/main/java/com/yugabyte/yw/models/Users.java b/managed/src/main/java/com/yugabyte/yw/models/Users.java index 57f7ac12fbaa..4bf17e05e963 100644 --- a/managed/src/main/java/com/yugabyte/yw/models/Users.java +++ b/managed/src/main/java/com/yugabyte/yw/models/Users.java @@ -18,6 +18,7 @@ import io.ebean.Model; import io.ebean.annotation.Encrypted; import io.ebean.annotation.EnumValue; +import io.ebean.annotation.Transactional; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty.AccessMode; @@ -319,6 +320,28 @@ public static void deleteUser(String email) { return; } + /** Wrapper around save to make sure principal entity is created. */ + @Transactional + @Override + public void save() { + super.save(); + Principal principal = Principal.get(this.uuid); + if (principal == null) { + log.info("Adding Principal entry for user with email: " + this.email); + new Principal(this).save(); + } + } + + /** Wrapper around delete to make sure principal entity is deleted. */ + @Transactional + @Override + public boolean delete() { + log.info("Deleting Principal entry for user with email: " + this.email); + Principal principal = Principal.getOrBadRequest(this.uuid); + principal.delete(); + return super.delete(); + } + /** * Validate if the email and password combination is valid, we use this to authenticate the Users. * diff --git a/managed/src/main/java/com/yugabyte/yw/models/migrations/V360.java b/managed/src/main/java/com/yugabyte/yw/models/migrations/V360.java new file mode 100644 index 000000000000..a641fbfc80d0 --- /dev/null +++ b/managed/src/main/java/com/yugabyte/yw/models/migrations/V360.java @@ -0,0 +1,84 @@ +// Copyright (c) Yugabyte, Inc. + +package com.yugabyte.yw.models.migrations; + +import io.ebean.Finder; +import io.ebean.Model; +import io.ebean.annotation.EnumValue; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.util.List; +import java.util.UUID; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** Snapshot View of ORM entities at the time migration V360 was added. */ +public class V360 { + + @Entity + @Getter + @Setter + public static class Users extends Model { + + public static final Finder find = new Finder(Users.class) {}; + + @Id private UUID uuid; + + public static List getAll() { + return find.query().where().findList(); + } + } + + @Entity + @Table(name = "principal") + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = false) + public static class Principal extends Model { + + @Id + @Column(name = "uuid", nullable = false, unique = true) + private UUID uuid; + + // At most one of these can be valid. + @Column(name = "user_uuid") + private UUID userUUID; + + @Column(name = "group_uuid") + private UUID groupUUID; + + @Column(name = "type", nullable = false) + private PrincipalType type; + + public enum PrincipalType { + @EnumValue("USER") + USER, + + @EnumValue("LDAP_GROUP") + LDAP_GROUP, + + @EnumValue("OIDC_GROUP") + OIDC_GROUP + } + + public Principal(Users user) { + uuid = user.getUuid(); + userUUID = uuid; + groupUUID = null; + type = PrincipalType.USER; + } + + public static Principal get(UUID principalUUID) { + Principal principal = find.query().where().eq("uuid", principalUUID).findOne(); + return principal; + } + + public static final Finder find = + new Finder(Principal.class) {}; + } +} diff --git a/managed/src/main/java/com/yugabyte/yw/models/rbac/RoleBinding.java b/managed/src/main/java/com/yugabyte/yw/models/rbac/RoleBinding.java index 9ec20cff4a8e..17aaab3dd0e0 100644 --- a/managed/src/main/java/com/yugabyte/yw/models/rbac/RoleBinding.java +++ b/managed/src/main/java/com/yugabyte/yw/models/rbac/RoleBinding.java @@ -8,6 +8,9 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.yugabyte.yw.common.PlatformServiceException; +import com.yugabyte.yw.models.GroupMappingInfo; +import com.yugabyte.yw.models.Principal; +import com.yugabyte.yw.models.Principal.PrincipalType; import com.yugabyte.yw.models.Users; import io.ebean.Finder; import io.ebean.Model; @@ -23,6 +26,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.Transient; import java.util.Date; import java.util.List; import java.util.UUID; @@ -50,10 +54,17 @@ public class RoleBinding extends Model { private UUID uuid; @ManyToOne + @JoinColumn(name = "principal_uuid", referencedColumnName = "uuid") + private Principal principal; + @ApiModelProperty(value = "User") - @JoinColumn(name = "user_uuid", referencedColumnName = "uuid") + @Transient private Users user; + @ApiModelProperty(value = "GroupInfo") + @Transient + private GroupMappingInfo groupInfo; + /** * This shows whether the role binding is system generated or user generated. System generated * role bindings are usually for the LDAP group users. Custom role bindings are when the user sets @@ -99,19 +110,56 @@ public enum RoleBindingType { public static RoleBinding create( Users user, RoleBindingType roleBindingType, Role role, ResourceGroup resourceGroup) { + + Principal principal = Principal.find.byId(user.getUuid()); + RoleBinding roleBinding = + new RoleBinding( + UUID.randomUUID(), + principal, + user, + null, + roleBindingType, + role, + new Date(), + new Date(), + resourceGroup); + roleBinding.save(); + return roleBinding; + } + + public static RoleBinding create( + GroupMappingInfo info, + RoleBindingType roleBindingType, + Role role, + ResourceGroup resourceGroup) { + + Principal principal = Principal.find.byId(info.getGroupUUID()); RoleBinding roleBinding = new RoleBinding( - UUID.randomUUID(), user, roleBindingType, role, new Date(), new Date(), resourceGroup); + UUID.randomUUID(), + principal, + null, + info, + roleBindingType, + role, + new Date(), + new Date(), + resourceGroup); roleBinding.save(); return roleBinding; } public static RoleBinding get(UUID roleBindingUUID) { - return find.query().where().eq("uuid", roleBindingUUID).findOne(); + RoleBinding rb = find.query().where().eq("uuid", roleBindingUUID).findOne(); + populatePrincipalInfo(rb); + return rb; } public static List fetchRoleBindingsForUser(UUID userUUID) { - return find.query().where().eq("user_uuid", userUUID).findList(); + Users user = Users.getOrBadRequest(userUUID); + List list = find.query().where().eq("principal_uuid", userUUID).findList(); + list.forEach(rb -> rb.setUser(user)); + return list; } public static RoleBinding getOrBadRequest(UUID roleBindingUUID) { @@ -124,19 +172,32 @@ public static RoleBinding getOrBadRequest(UUID roleBindingUUID) { } public static List getAll() { - return find.query().findList(); + List list = find.query().findList(); + list.forEach(rb -> populatePrincipalInfo(rb)); + return list; } - public static List getAll(UUID userUUID) { - return find.query().where().eq("user_uuid", userUUID).findList(); + public static List getAll(UUID principalUUID) { + Principal principal = Principal.getOrBadRequest(principalUUID); + List list = find.query().where().eq("principal_uuid", principalUUID).findList(); + if (principal.getType().equals(PrincipalType.USER)) { + Users user = Users.getOrBadRequest(principalUUID); + list.forEach(rb -> rb.setUser(user)); + } else { + GroupMappingInfo info = GroupMappingInfo.getOrBadRequest(principalUUID); + list.forEach(rb -> rb.setGroupInfo(info)); + } + return list; } public static List getAllWithRole(UUID roleUUID) { - return find.query().where().eq("role_uuid", roleUUID).findList(); + List rbList = find.query().where().eq("role_uuid", roleUUID).findList(); + rbList.forEach(rb -> populatePrincipalInfo(rb)); + return rbList; } public static boolean checkUserHasRole(UUID userUUID, UUID roleUUID) { - return find.query().where().eq("user_uuid", userUUID).eq("role_uuid", roleUUID).exists(); + return find.query().where().eq("principal_uuid", userUUID).eq("role_uuid", roleUUID).exists(); } public void edit(Role role, ResourceGroup resourceGroup) { @@ -144,4 +205,21 @@ public void edit(Role role, ResourceGroup resourceGroup) { this.resourceGroup = resourceGroup; this.update(); } + + /** + * If the role binding belongs to a user, we populate the user field else the groupInfo field. + * + * @param rb + */ + private static void populatePrincipalInfo(RoleBinding rb) { + if (rb == null) { + return; + } + Principal principal = Principal.getOrBadRequest(rb.getPrincipal().getUuid()); + if (principal.getType().equals(PrincipalType.USER)) { + rb.setUser(Users.getOrBadRequest(principal.getUserUUID())); + } else { + rb.setGroupInfo(GroupMappingInfo.getOrBadRequest(principal.getGroupUUID())); + } + } } diff --git a/managed/src/main/java/db/migration/default_/postgres/V361__Populate_Princpals.java b/managed/src/main/java/db/migration/default_/postgres/V361__Populate_Princpals.java new file mode 100644 index 000000000000..3b352a6aeaed --- /dev/null +++ b/managed/src/main/java/db/migration/default_/postgres/V361__Populate_Princpals.java @@ -0,0 +1,25 @@ +// Copyright (c) Yugabyte, Inc. + +package db.migration.default_.postgres; + +import com.yugabyte.yw.models.migrations.V360.Principal; +import com.yugabyte.yw.models.migrations.V360.Users; +import java.sql.SQLException; +import lombok.extern.slf4j.Slf4j; +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; + +@Slf4j +public class V361__Populate_Princpals extends BaseJavaMigration { + + @Override + public void migrate(Context context) throws SQLException { + for (Users user : Users.find.all()) { + Principal principal = Principal.get(user.getUuid()); + if (principal == null) { + log.info("Adding Principal entry for user with UUID: " + user.getUuid()); + new Principal(user).save(); + } + } + } +} diff --git a/managed/src/main/resources/application.test.conf b/managed/src/main/resources/application.test.conf index e77b383bbd58..314f0a2f8d6b 100644 --- a/managed/src/main/resources/application.test.conf +++ b/managed/src/main/resources/application.test.conf @@ -36,6 +36,7 @@ yb { is_platform_downgrade_allowed=${?YB_IS_PLATFORM_DOWNGRADE_ALLOWED} security.ssh2_enabled = false security.oidc_enable_auto_create_users = true + security.group_mapping_rbac_support = true universe.user_tags.is_enforced = false universe.user_tags.enforced_tags = ["yb_task:test","yb_owner:*","yb_dept:eng","yb_dept:qa"] diff --git a/managed/src/main/resources/db/migration/default_/common/V360__Create_Group_table.sql b/managed/src/main/resources/db/migration/default_/common/V360__Create_Group_table.sql new file mode 100644 index 000000000000..41389a7508fe --- /dev/null +++ b/managed/src/main/resources/db/migration/default_/common/V360__Create_Group_table.sql @@ -0,0 +1,33 @@ +-- Copyright (c) YugaByte, Inc. + +CREATE TABLE IF not EXISTS groups_mapping_info ( + uuid UUID NOT NULL, + identifier VARCHAR(255) NOT NULL, + role_uuid UUID NOT NULL, + customer_uuid UUID NOT NULL, + type VARCHAR(5) NOT NULL, + + CONSTRAINT fk_groups_info_customer_uuid foreign key (customer_uuid) references customer (uuid) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT fk_groups_info_role_uuid foreign key (role_uuid) references role (role_uuid), + CONSTRAINT ck_groups_info_type check(type in ('LDAP', 'OIDC')), + CONSTRAINT pk_group_mapping_info PRIMARY KEY (uuid), + CONSTRAINT uq_groups_info_identifier UNIQUE (identifier) +); + +CREATE TABLE IF NOT EXISTS principal ( + -- uuid will be same as user/group uuid + uuid UUID NOT NULL, + user_uuid UUID, + group_uuid UUID, + type VARCHAR(10) NOT NULL, + + CONSTRAINT pk_principal PRIMARY KEY (uuid), + CONSTRAINT fk_principal_user foreign key (user_uuid) references users (uuid) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT fk_principal_group foreign key (group_uuid) references groups_mapping_info (uuid) ON UPDATE CASCADE ON DELETE CASCADE, + + CONSTRAINT ck_type check(type in ('USER', 'LDAP_GROUP', 'OIDC_GROUP')), + -- CHECK to make sure only one of them is non null + CHECK ( + (user_uuid IS NOT NULL)::INTEGER + + (group_uuid IS NOT NULL)::INTEGER = 1) +); \ No newline at end of file diff --git a/managed/src/main/resources/db/migration/default_/common/V362__Alter_role_binding.sql b/managed/src/main/resources/db/migration/default_/common/V362__Alter_role_binding.sql new file mode 100644 index 000000000000..f53512987899 --- /dev/null +++ b/managed/src/main/resources/db/migration/default_/common/V362__Alter_role_binding.sql @@ -0,0 +1,10 @@ +-- Copyright (c) YugaByte, Inc. + +ALTER TABLE role_binding +DROP CONSTRAINT fk_role_binding_user_uuid; + +ALTER TABLE role_binding +RENAME COLUMN user_uuid TO principal_uuid; + +ALTER TABLE role_binding +ADD CONSTRAINT fk_role_binding_user_uuid FOREIGN KEY (principal_uuid) REFERENCES principal (uuid); \ No newline at end of file diff --git a/managed/src/main/resources/openapi/components/requestBodies/GroupMappingReq.yaml b/managed/src/main/resources/openapi/components/requestBodies/GroupMappingReq.yaml new file mode 100644 index 000000000000..ca5457d1b6b7 --- /dev/null +++ b/managed/src/main/resources/openapi/components/requestBodies/GroupMappingReq.yaml @@ -0,0 +1,7 @@ +required: true +content: + application/json: + schema: + type: array + items: + $ref: "../schemas/GroupMappingSpec.yaml" diff --git a/managed/src/main/resources/openapi/components/responses/GroupMappingResp.yaml b/managed/src/main/resources/openapi/components/responses/GroupMappingResp.yaml new file mode 100644 index 000000000000..8e2df6b83a91 --- /dev/null +++ b/managed/src/main/resources/openapi/components/responses/GroupMappingResp.yaml @@ -0,0 +1,7 @@ +description: OK +content: + application/json: + schema: + type: array + items: + $ref: "../schemas/GroupMappingSpec.yaml" diff --git a/managed/src/main/resources/openapi/components/schemas/GroupMappingSpec.yaml b/managed/src/main/resources/openapi/components/schemas/GroupMappingSpec.yaml new file mode 100644 index 000000000000..b0d882a1a737 --- /dev/null +++ b/managed/src/main/resources/openapi/components/schemas/GroupMappingSpec.yaml @@ -0,0 +1,29 @@ +title: GroupMappingSpec +description: | + GroupMappingSpec + + Group mapping properties. This is used to map LDAP and OIDC group to YBA roles. +type: object +required: + - group_identifier + - type + - role_resource_definitions +properties: + group_identifier: + description: Group name incase of OIDC. Group DN incase of LDAP. + type: string + uuid: + description: System generated UUID for this group mapping. + type: string + format: uuid + readOnly: true + type: + description: The type of group. Can be either LDAP/OIDC. + type: string + enum: + - LDAP + - OIDC + role_resource_definitions: + type: array + items: + $ref: "./RoleResourceDefinitionSpec.yaml" diff --git a/managed/src/main/resources/openapi/components/schemas/ResourceDefinitionSpec.yaml b/managed/src/main/resources/openapi/components/schemas/ResourceDefinitionSpec.yaml new file mode 100644 index 000000000000..e1f44ac64a49 --- /dev/null +++ b/managed/src/main/resources/openapi/components/schemas/ResourceDefinitionSpec.yaml @@ -0,0 +1,25 @@ +title: ResourceDefinitionSpec +description: Resource definition containing the resource type and resource set. +type: object +required: + - resource_type + - allow_all +properties: + resource_type: + description: Resource Type + type: string + enum: + - UNIVERSE + - ROLE + - USER + - OTHER + allow_all: + description: Select all resources (including future resources) + type: boolean + resource_uuid_set: + description: Set of resource UUIDs + type: array + items: + type: string + format: uuid + default: [] diff --git a/managed/src/main/resources/openapi/components/schemas/ResourceGroupSpec.yaml b/managed/src/main/resources/openapi/components/schemas/ResourceGroupSpec.yaml new file mode 100644 index 000000000000..998811ebbdda --- /dev/null +++ b/managed/src/main/resources/openapi/components/schemas/ResourceGroupSpec.yaml @@ -0,0 +1,10 @@ +title: ResourceGroupSpec +description: Resource group definition for the role. Only applicable for custom roles. +type: object +required: + - resource_definition_set +properties: + resource_definition_set: + type: array + items: + $ref: "./ResourceDefinitionSpec.yaml" diff --git a/managed/src/main/resources/openapi/components/schemas/RoleResourceDefinitionSpec.yaml b/managed/src/main/resources/openapi/components/schemas/RoleResourceDefinitionSpec.yaml new file mode 100644 index 000000000000..64fbbe05f1ba --- /dev/null +++ b/managed/src/main/resources/openapi/components/schemas/RoleResourceDefinitionSpec.yaml @@ -0,0 +1,12 @@ +title: RoleResourceDefinitionSpec. +description: Defines the association of Role to Resource Groups. Part of GroupMappingSpec. +type: object +required: + - role_uuid +properties: + role_uuid: + description: UUID of the role to attach resource group to. + type: string + format: uuid + resource_group: + $ref: "./ResourceGroupSpec.yaml" diff --git a/managed/src/main/resources/openapi/openapi_split.yaml b/managed/src/main/resources/openapi/openapi_split.yaml index d6b70d9db26c..1536c40ce308 100644 --- a/managed/src/main/resources/openapi/openapi_split.yaml +++ b/managed/src/main/resources/openapi/openapi_split.yaml @@ -22,6 +22,8 @@ servers: host_port: default: 'localhost:9000' tags: + - name: Authentication + description: Authentication operations on YBA - name: Universe description: CRUD operations for a Universe paths: diff --git a/managed/src/main/resources/openapi/paths/_index.yaml b/managed/src/main/resources/openapi/paths/_index.yaml index 9693e0056b78..0dcf4f7351c3 100644 --- a/managed/src/main/resources/openapi/paths/_index.yaml +++ b/managed/src/main/resources/openapi/paths/_index.yaml @@ -1,3 +1,120 @@ +'/customers/{cUUID}/auth/group-mappings': + parameters: + - name: cUUID + in: path + description: Customer UUID + schema: + type: string + format: uuid + explode: false + style: simple + required: true + get: + operationId: listMappings + summary: List Group Mappings + description: Get list of all OIDC and LDAP Group Mappings. + tags: + - Authentication + responses: + '200': + $ref: "../components/responses/GroupMappingResp.yaml" + '400': + description: Invalid input + '500': + description: Server error + security: + - apiKeyAuth: [] + x-yba-api-audit: + noAudit: true + x-yba-api-authz: + - requiredPermission: + resourceType: other + action: read + resourceLocation: + path: customers + sourceType: endpoint + x-yba-api-since: 2024.2.0.0 + x-yba-api-visibility: preview + put: + operationId: updateGroupMappings + summary: Create Group Mappings + description: Map LDAP and OIDC groups to YBA roles + tags: + - Authentication + requestBody: + $ref: "../components/requestBodies/GroupMappingReq.yaml" + responses: + '200': + description: Operation Successfull + '400': + description: Invalid input + '500': + description: Server error + security: + - apiKeyAuth: [] + x-yba-api-audit: + auditTargetType: GroupMapping + auditTargetId: cUUID.toString() + auditActionType: Set + x-yba-api-authz: + - requiredPermission: + resourceType: other + action: super_admin_actions + resourceLocation: + path: customers + sourceType: endpoint + x-yba-api-since: 2024.2.0.0 + x-yba-api-visibility: preview +'/customers/{cUUID}/auth/group-mappings/{gUUID}': + parameters: + - name: cUUID + in: path + description: Customer UUID + schema: + type: string + format: uuid + explode: false + style: simple + required: true + - name: gUUID + in: path + description: Group UUID + schema: + type: string + format: uuid + explode: false + style: simple + required: true + delete: + operationId: deleteGroupMappings + summary: Delete Group Mappings + description: Delete LDAP and OIDC group mapping + tags: + - Authentication + responses: + '200': + description: OK + '400': + description: Invalid input + '404': + description: Not found + '500': + description: Server error + security: + - apiKeyAuth: [] + x-yba-api-audit: + auditTargetType: GroupMapping + auditTargetId: gUUID.toString() + auditActionType: Delete + x-yba-api-authz: + - requiredPermission: + resourceType: other + action: super_admin_actions + resourceLocation: + path: customers + sourceType: endpoint + x-yba-api-since: 2024.2.0.0 + x-yba-api-visibility: preview '/customers/{cUUID}/universes/{uniUUID}': parameters: - name: cUUID diff --git a/managed/src/main/resources/openapi/paths/authentication.yaml b/managed/src/main/resources/openapi/paths/authentication.yaml new file mode 100644 index 000000000000..3130b89d6b63 --- /dev/null +++ b/managed/src/main/resources/openapi/paths/authentication.yaml @@ -0,0 +1,117 @@ +'/customers/{cUUID}/auth/group-mappings': + parameters: + - name: cUUID + in: path + description: Customer UUID + schema: + type: string + format: uuid + explode: false + style: simple + required: true + get: + operationId: listMappings + summary: List Group Mappings + description: Get list of all OIDC and LDAP Group Mappings. + tags: + - Authentication + responses: + '200': + $ref: "../components/responses/GroupMappingResp.yaml" + '400': + description: Invalid input + '500': + description: Server error + security: + - apiKeyAuth: [] + x-yba-api-audit: + noAudit: true + x-yba-api-authz: + - requiredPermission: + resourceType: other + action: read + resourceLocation: + path: customers + sourceType: endpoint + x-yba-api-since: 2024.2.0.0 + x-yba-api-visibility: preview + put: + operationId: updateGroupMappings + summary: Create Group Mappings + description: Map LDAP and OIDC groups to YBA roles + tags: + - Authentication + requestBody: + $ref: "../components/requestBodies/GroupMappingReq.yaml" + responses: + '200': + description: Operation Successfull + '400': + description: Invalid input + '500': + description: Server error + security: + - apiKeyAuth: [] + x-yba-api-audit: + auditTargetType: GroupMapping + auditTargetId: cUUID.toString() + auditActionType: Set + x-yba-api-authz: + - requiredPermission: + resourceType: other + action: super_admin_actions + resourceLocation: + path: customers + sourceType: endpoint + x-yba-api-since: 2024.2.0.0 + x-yba-api-visibility: preview +'/customers/{cUUID}/auth/group-mappings/{gUUID}': + parameters: + - name: cUUID + in: path + description: Customer UUID + schema: + type: string + format: uuid + explode: false + style: simple + required: true + - name: gUUID + in: path + description: Group UUID + schema: + type: string + format: uuid + explode: false + style: simple + required: true + delete: + operationId: deleteGroupMappings + summary: Delete Group Mappings + description: Delete LDAP and OIDC group mapping + tags: + - Authentication + responses: + '200': + description: OK + '400': + description: Invalid input + '404': + description: Not found + '500': + description: Server error + security: + - apiKeyAuth: [] + x-yba-api-audit: + auditTargetType: GroupMapping + auditTargetId: gUUID.toString() + auditActionType: Delete + x-yba-api-authz: + - requiredPermission: + resourceType: other + action: super_admin_actions + resourceLocation: + path: customers + sourceType: endpoint + x-yba-api-since: 2024.2.0.0 + x-yba-api-visibility: preview diff --git a/managed/src/main/resources/reference.conf b/managed/src/main/resources/reference.conf index 0925ffa6e66b..6fc48bf09ff0 100644 --- a/managed/src/main/resources/reference.conf +++ b/managed/src/main/resources/reference.conf @@ -890,6 +890,7 @@ yb { oidc_enable_auto_create_users = false oidc_default_role = "ReadOnly" ssh2_enabled = false + group_mapping_rbac_support = false ldap { use_ldap = "false" ldap_url = "" diff --git a/managed/src/main/resources/swagger-strict.json b/managed/src/main/resources/swagger-strict.json index 0ae8360d4a09..55c55162047d 100644 --- a/managed/src/main/resources/swagger-strict.json +++ b/managed/src/main/resources/swagger-strict.json @@ -1384,7 +1384,7 @@ }, "target" : { "description" : "Target", - "enum" : [ "Session", "CloudProvider", "Region", "AvailabilityZone", "CustomerConfig", "KMSConfig", "Customer", "Release", "Certificate", "CustomCACertificate", "Alert", "AlertTemplateSettings", "AlertTemplateVariables", "AlertChannel", "AlertChannelTemplates", "AlertDestination", "MaintenanceWindow", "AccessKey", "Universe", "XClusterConfig", "DrConfig", "Table", "Backup", "CustomerTask", "NodeInstance", "PlatformInstance", "Schedule", "User", "LoggingConfig", "RuntimeConfigKey", "HAConfig", "HABackup", "ScheduledScript", "SupportBundle", "TelemetryProvider", "TroubleshootingPlatform", "GFlags", "Hook", "HookScope", "NodeAgent", "CustomerLicense", "PerformanceRecommendation", "PerformanceAdvisorSettings", "PerformanceAdvisorRun", "Role", "RoleBinding", "OIDCGroupMapping" ], + "enum" : [ "Session", "CloudProvider", "Region", "AvailabilityZone", "CustomerConfig", "KMSConfig", "Customer", "Release", "Certificate", "CustomCACertificate", "Alert", "AlertTemplateSettings", "AlertTemplateVariables", "AlertChannel", "AlertChannelTemplates", "AlertDestination", "MaintenanceWindow", "AccessKey", "Universe", "XClusterConfig", "DrConfig", "Table", "Backup", "CustomerTask", "NodeInstance", "PlatformInstance", "Schedule", "User", "LoggingConfig", "RuntimeConfigKey", "HAConfig", "HABackup", "ScheduledScript", "SupportBundle", "TelemetryProvider", "TroubleshootingPlatform", "GFlags", "Hook", "HookScope", "NodeAgent", "CustomerLicense", "PerformanceRecommendation", "PerformanceAdvisorSettings", "PerformanceAdvisorRun", "Role", "RoleBinding", "OIDCGroupMapping", "GroupMapping" ], "example" : "User", "readOnly" : true, "type" : "string" @@ -5814,6 +5814,31 @@ "required" : [ "clusters", "creatingUser", "kubernetesUpgradeSupported", "masterGFlags", "platformUrl", "platformVersion", "sleepAfterMasterRestartMillis", "sleepAfterTServerRestartMillis", "tserverGFlags", "upgradeOption" ], "type" : "object" }, + "GroupMappingInfo" : { + "properties" : { + "customerUUID" : { + "format" : "uuid", + "type" : "string" + }, + "groupUUID" : { + "format" : "uuid", + "type" : "string" + }, + "identifier" : { + "type" : "string" + }, + "roleUUID" : { + "format" : "uuid", + "type" : "string" + }, + "type" : { + "enum" : [ "LDAP", "OIDC" ], + "type" : "string" + } + }, + "required" : [ "customerUUID", "groupUUID", "identifier", "roleUUID", "type" ], + "type" : "object" + }, "HTTP Auth information" : { "discriminator" : "type", "properties" : { @@ -9109,6 +9134,28 @@ }, "type" : "object" }, + "Principal" : { + "properties" : { + "groupUUID" : { + "format" : "uuid", + "type" : "string" + }, + "type" : { + "enum" : [ "USER", "LDAP_GROUP", "OIDC_GROUP" ], + "type" : "string" + }, + "userUUID" : { + "format" : "uuid", + "type" : "string" + }, + "uuid" : { + "format" : "uuid", + "type" : "string" + } + }, + "required" : [ "groupUUID", "type", "userUUID", "uuid" ], + "type" : "object" + }, "Provider" : { "properties" : { "active" : { @@ -10988,6 +11035,13 @@ "format" : "date-time", "type" : "string" }, + "groupInfo" : { + "$ref" : "#/definitions/GroupMappingInfo", + "description" : "GroupInfo" + }, + "principal" : { + "$ref" : "#/definitions/Principal" + }, "resourceGroup" : { "$ref" : "#/definitions/ResourceGroup", "description" : "Details of the resource group" @@ -11018,6 +11072,7 @@ "type" : "string" } }, + "required" : [ "principal" ], "type" : "object" }, "RoleBindingFormData" : { @@ -11058,7 +11113,7 @@ "type" : "object" }, "RoleResourceDefinition" : { - "description" : "Role and resource group definition.", + "description" : "Defines the association of Role to Resource Groups.", "properties" : { "resourceGroup" : { "$ref" : "#/definitions/ResourceGroup", diff --git a/managed/src/main/resources/swagger.json b/managed/src/main/resources/swagger.json index 27fe7261a8a5..2857428e2278 100644 --- a/managed/src/main/resources/swagger.json +++ b/managed/src/main/resources/swagger.json @@ -1396,7 +1396,7 @@ }, "target" : { "description" : "Target", - "enum" : [ "Session", "CloudProvider", "Region", "AvailabilityZone", "CustomerConfig", "KMSConfig", "Customer", "Release", "Certificate", "CustomCACertificate", "Alert", "AlertTemplateSettings", "AlertTemplateVariables", "AlertChannel", "AlertChannelTemplates", "AlertDestination", "MaintenanceWindow", "AccessKey", "Universe", "XClusterConfig", "DrConfig", "Table", "Backup", "CustomerTask", "NodeInstance", "PlatformInstance", "Schedule", "User", "LoggingConfig", "RuntimeConfigKey", "HAConfig", "HABackup", "ScheduledScript", "SupportBundle", "TelemetryProvider", "TroubleshootingPlatform", "GFlags", "Hook", "HookScope", "NodeAgent", "CustomerLicense", "PerformanceRecommendation", "PerformanceAdvisorSettings", "PerformanceAdvisorRun", "Role", "RoleBinding", "OIDCGroupMapping" ], + "enum" : [ "Session", "CloudProvider", "Region", "AvailabilityZone", "CustomerConfig", "KMSConfig", "Customer", "Release", "Certificate", "CustomCACertificate", "Alert", "AlertTemplateSettings", "AlertTemplateVariables", "AlertChannel", "AlertChannelTemplates", "AlertDestination", "MaintenanceWindow", "AccessKey", "Universe", "XClusterConfig", "DrConfig", "Table", "Backup", "CustomerTask", "NodeInstance", "PlatformInstance", "Schedule", "User", "LoggingConfig", "RuntimeConfigKey", "HAConfig", "HABackup", "ScheduledScript", "SupportBundle", "TelemetryProvider", "TroubleshootingPlatform", "GFlags", "Hook", "HookScope", "NodeAgent", "CustomerLicense", "PerformanceRecommendation", "PerformanceAdvisorSettings", "PerformanceAdvisorRun", "Role", "RoleBinding", "OIDCGroupMapping", "GroupMapping" ], "example" : "User", "readOnly" : true, "type" : "string" @@ -5853,6 +5853,31 @@ "required" : [ "clusters", "creatingUser", "kubernetesUpgradeSupported", "masterGFlags", "platformUrl", "platformVersion", "sleepAfterMasterRestartMillis", "sleepAfterTServerRestartMillis", "tserverGFlags", "upgradeOption" ], "type" : "object" }, + "GroupMappingInfo" : { + "properties" : { + "customerUUID" : { + "format" : "uuid", + "type" : "string" + }, + "groupUUID" : { + "format" : "uuid", + "type" : "string" + }, + "identifier" : { + "type" : "string" + }, + "roleUUID" : { + "format" : "uuid", + "type" : "string" + }, + "type" : { + "enum" : [ "LDAP", "OIDC" ], + "type" : "string" + } + }, + "required" : [ "customerUUID", "groupUUID", "identifier", "roleUUID", "type" ], + "type" : "object" + }, "HTTP Auth information" : { "discriminator" : "type", "properties" : { @@ -9160,6 +9185,28 @@ }, "type" : "object" }, + "Principal" : { + "properties" : { + "groupUUID" : { + "format" : "uuid", + "type" : "string" + }, + "type" : { + "enum" : [ "USER", "LDAP_GROUP", "OIDC_GROUP" ], + "type" : "string" + }, + "userUUID" : { + "format" : "uuid", + "type" : "string" + }, + "uuid" : { + "format" : "uuid", + "type" : "string" + } + }, + "required" : [ "groupUUID", "type", "userUUID", "uuid" ], + "type" : "object" + }, "Provider" : { "properties" : { "active" : { @@ -11096,6 +11143,13 @@ "format" : "date-time", "type" : "string" }, + "groupInfo" : { + "$ref" : "#/definitions/GroupMappingInfo", + "description" : "GroupInfo" + }, + "principal" : { + "$ref" : "#/definitions/Principal" + }, "resourceGroup" : { "$ref" : "#/definitions/ResourceGroup", "description" : "Details of the resource group" @@ -11126,6 +11180,7 @@ "type" : "string" } }, + "required" : [ "principal" ], "type" : "object" }, "RoleBindingFormData" : { @@ -11166,7 +11221,7 @@ "type" : "object" }, "RoleResourceDefinition" : { - "description" : "Role and resource group definition.", + "description" : "Defines the association of Role to Resource Groups.", "properties" : { "resourceGroup" : { "$ref" : "#/definitions/ResourceGroup", diff --git a/managed/src/test/java/com/yugabyte/yw/api/v2/AuthenticationGroupMappingApisTest.java b/managed/src/test/java/com/yugabyte/yw/api/v2/AuthenticationGroupMappingApisTest.java new file mode 100644 index 000000000000..c58579b69983 --- /dev/null +++ b/managed/src/test/java/com/yugabyte/yw/api/v2/AuthenticationGroupMappingApisTest.java @@ -0,0 +1,211 @@ +package com.yugabyte.yw.api.v2; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static play.mvc.Http.Status.OK; + +import com.yugabyte.yba.v2.client.ApiClient; +import com.yugabyte.yba.v2.client.ApiException; +import com.yugabyte.yba.v2.client.ApiResponse; +import com.yugabyte.yba.v2.client.Configuration; +import com.yugabyte.yba.v2.client.api.AuthenticationApi; +import com.yugabyte.yba.v2.client.models.GroupMappingSpec; +import com.yugabyte.yba.v2.client.models.GroupMappingSpec.TypeEnum; +import com.yugabyte.yba.v2.client.models.ResourceDefinitionSpec; +import com.yugabyte.yba.v2.client.models.ResourceDefinitionSpec.ResourceTypeEnum; +import com.yugabyte.yba.v2.client.models.ResourceGroupSpec; +import com.yugabyte.yba.v2.client.models.RoleResourceDefinitionSpec; +import com.yugabyte.yw.common.FakeDBApplication; +import com.yugabyte.yw.common.ModelFactory; +import com.yugabyte.yw.common.rbac.Permission; +import com.yugabyte.yw.common.rbac.PermissionInfo.Action; +import com.yugabyte.yw.common.rbac.PermissionInfo.ResourceType; +import com.yugabyte.yw.models.Customer; +import com.yugabyte.yw.models.GroupMappingInfo; +import com.yugabyte.yw.models.GroupMappingInfo.GroupType; +import com.yugabyte.yw.models.RuntimeConfigEntry; +import com.yugabyte.yw.models.Users; +import com.yugabyte.yw.models.rbac.Role; +import com.yugabyte.yw.models.rbac.Role.RoleType; +import com.yugabyte.yw.models.rbac.RoleBinding; +import db.migration.default_.common.R__Sync_System_Roles; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.UUID; +import junitparams.JUnitParamsRunner; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(JUnitParamsRunner.class) +public class AuthenticationGroupMappingApisTest extends FakeDBApplication { + private Customer customer; + private String authToken; + + private Users user; + private ApiClient v2ApiClient; + + // Define test permissions to use later. + public Permission permission1 = new Permission(ResourceType.UNIVERSE, Action.CREATE); + public Permission permission2 = new Permission(ResourceType.UNIVERSE, Action.READ); + + @Before + public void setUp() { + customer = ModelFactory.testCustomer(); + user = ModelFactory.testSuperAdminUserNewRbac(customer); + authToken = user.createAuthToken(); + v2ApiClient = Configuration.getDefaultApiClient(); + String basePath = String.format("http://localhost:%d/api/v2", port); + v2ApiClient = v2ApiClient.setBasePath(basePath).addDefaultHeader("X-AUTH-TOKEN", authToken); + Configuration.setDefaultApiClient(v2ApiClient); + R__Sync_System_Roles.syncSystemRoles(); + } + + @Test + public void testListMappingRbacOff() throws Exception { + UUID cUUID = customer.getUuid(); + GroupMappingInfo info0 = GroupMappingInfo.create(cUUID, "test-group-1", GroupType.OIDC); + GroupMappingInfo info1 = GroupMappingInfo.create(cUUID, "test-group-2", GroupType.LDAP); + info1.setRoleUUID(Role.get(cUUID, "Admin").getRoleUUID()); + info1.save(); + AuthenticationApi api = new AuthenticationApi(); + ApiResponse> result = api.listMappingsWithHttpInfo(cUUID); + assertEquals(OK, result.getStatusCode()); + List res = result.getData(); + assertEquals(2, res.size()); + + assertEquals(1, res.get(0).getRoleResourceDefinitions().size()); + assertEquals(info0.getIdentifier(), res.get(0).getGroupIdentifier()); + assertEquals(info0.getRoleUUID(), res.get(0).getRoleResourceDefinitions().get(0).getRoleUuid()); + assertEquals( + Role.get(cUUID, "ConnectOnly").getRoleUUID(), + res.get(0).getRoleResourceDefinitions().get(0).getRoleUuid()); + + assertEquals(1, res.get(1).getRoleResourceDefinitions().size()); + assertEquals(info1.getIdentifier(), res.get(1).getGroupIdentifier()); + assertEquals(info1.getRoleUUID(), res.get(1).getRoleResourceDefinitions().get(0).getRoleUuid()); + assertEquals( + Role.get(cUUID, "Admin").getRoleUUID(), + res.get(1).getRoleResourceDefinitions().get(0).getRoleUuid()); + } + + @Test + public void testCRUDMappingsRbacOn() throws Exception { + RuntimeConfigEntry.upsertGlobal("yb.rbac.use_new_authz", "true"); + AuthenticationApi api = new AuthenticationApi(); + UUID cUUID = customer.getUuid(); + // create custom role + Role customRole = + Role.create( + cUUID, + "testCustomRole", + "testDescription", + RoleType.Custom, + new HashSet<>(Arrays.asList(permission1, permission2))); + + List mappingSpecList = new ArrayList<>(); + GroupMappingSpec ldapSpec = new GroupMappingSpec(); + GroupMappingSpec oidcSpec = new GroupMappingSpec(); + + ldapSpec.setGroupIdentifier("test-group-ldap"); + ldapSpec.setType(TypeEnum.LDAP); + RoleResourceDefinitionSpec rrdSystemRole = new RoleResourceDefinitionSpec(); + rrdSystemRole.setRoleUuid(Role.get(cUUID, "Admin").getRoleUUID()); + ldapSpec.setRoleResourceDefinitions(Arrays.asList(rrdSystemRole)); + + oidcSpec.setGroupIdentifier("test-group-oidc"); + oidcSpec.setType(TypeEnum.OIDC); + RoleResourceDefinitionSpec rrdCustomRole = new RoleResourceDefinitionSpec(); + rrdCustomRole.setRoleUuid(customRole.getRoleUUID()); + ResourceDefinitionSpec rd1 = + new ResourceDefinitionSpec().allowAll(true).resourceType(ResourceTypeEnum.UNIVERSE); + ResourceDefinitionSpec rd2 = + new ResourceDefinitionSpec() + .allowAll(false) + .resourceType(ResourceTypeEnum.OTHER) + .resourceUuidSet(Arrays.asList(cUUID)); + rrdCustomRole.setResourceGroup( + new ResourceGroupSpec().resourceDefinitionSet(Arrays.asList(rd1, rd2))); + oidcSpec.setRoleResourceDefinitions(Arrays.asList(rrdSystemRole, rrdCustomRole)); + + mappingSpecList.add(ldapSpec); + mappingSpecList.add(oidcSpec); + + ApiResponse resUpdate = api.updateGroupMappingsWithHttpInfo(cUUID, mappingSpecList); + assertEquals(OK, resUpdate.getStatusCode()); + + ApiResponse> resList = api.listMappingsWithHttpInfo(cUUID); + assertEquals(OK, resList.getStatusCode()); + + List specList = resList.getData(); + assertEquals(2, specList.size()); + + GroupMappingSpec ldapGroupInfo = specList.get(0); + assertEquals("test-group-ldap", ldapGroupInfo.getGroupIdentifier()); + assertEquals(TypeEnum.LDAP, ldapGroupInfo.getType()); + assertEquals(1, ldapGroupInfo.getRoleResourceDefinitions().size()); + assertEquals( + Role.get(cUUID, "Admin").getRoleUUID(), + ldapGroupInfo.getRoleResourceDefinitions().get(0).getRoleUuid()); + + GroupMappingSpec oidcGroupInfo = specList.get(1); + assertEquals("test-group-oidc", oidcGroupInfo.getGroupIdentifier()); + assertEquals(TypeEnum.OIDC, oidcGroupInfo.getType()); + assertEquals(2, oidcGroupInfo.getRoleResourceDefinitions().size()); + assertEquals( + Role.get(cUUID, "Admin").getRoleUUID(), + oidcGroupInfo.getRoleResourceDefinitions().get(0).getRoleUuid()); + + UUID oidcGroupUUID = oidcGroupInfo.getUuid(); + + oidcSpec.setGroupIdentifier("Test-Group-OIDC"); + assertTrue(mappingSpecList.remove(ldapSpec)); + resUpdate = api.updateGroupMappingsWithHttpInfo(cUUID, mappingSpecList); + assertEquals(OK, resUpdate.getStatusCode()); + + resList = api.listMappingsWithHttpInfo(cUUID); + assertEquals(OK, resList.getStatusCode()); + + specList = resList.getData(); + assertEquals(2, specList.size()); + // Make sure group names are case insensitive and uuid remains the same after update. + assertEquals(oidcGroupUUID, specList.get(1).getUuid()); + + ApiResponse delResponse = api.deleteGroupMappingsWithHttpInfo(cUUID, oidcGroupUUID); + assertEquals(OK, delResponse.getStatusCode()); + + GroupMappingInfo info = GroupMappingInfo.get(oidcGroupUUID); + assertEquals(null, info); + assertEquals( + 0, RoleBinding.find.query().where().eq("principal_uuid", oidcGroupUUID).findList().size()); + } + + @Test + public void testBadInput() throws Exception { + RuntimeConfigEntry.upsertGlobal("yb.rbac.use_new_authz", "false"); + AuthenticationApi api = new AuthenticationApi(); + UUID cUUID = customer.getUuid(); + List mappingSpecList = new ArrayList<>(); + GroupMappingSpec ldapSpec = new GroupMappingSpec(); + + ldapSpec.setGroupIdentifier("test-group-ldap"); + ldapSpec.setType(TypeEnum.LDAP); + RoleResourceDefinitionSpec rrdSystemRole = new RoleResourceDefinitionSpec(); + rrdSystemRole.setRoleUuid(Role.get(cUUID, "Admin").getRoleUUID()); + List rrdList = new ArrayList<>(); + rrdList.add(rrdSystemRole); + rrdList.add(rrdSystemRole); + ldapSpec.setRoleResourceDefinitions(rrdList); + + mappingSpecList.add(ldapSpec); + assertThrows( + ApiException.class, () -> api.updateGroupMappingsWithHttpInfo(cUUID, mappingSpecList)); + + ldapSpec.getRoleResourceDefinitions().remove(0); + ApiResponse resUpdate = api.updateGroupMappingsWithHttpInfo(cUUID, mappingSpecList); + assertEquals(OK, resUpdate.getStatusCode()); + } +} From 8e33f45c95aa892db7d0350e2d631f170c89e58d Mon Sep 17 00:00:00 2001 From: Rajagopalan Madhavan Date: Mon, 15 Jul 2024 16:03:53 -0400 Subject: [PATCH 1172/1195] [PLAT-12085]: Modify the confirm modal box depending on the type of edit universe op Summary: In case of both FULL MOVE and UPDATE operations, we show modal dialog regarding confirmation of what changes has been done to configuration between old userIntent and new userIntent However, in both scenarios, we never showed the diff in instance tags when it changed, hence adding support for this Test Plan: Please refer to screenshots {F266135} {F266136} Reviewers: jmak Reviewed By: jmak Subscribers: yugaware Differential Revision: https://phorge.dev.yugabyte.com/D36590 --- .../action-modals/FullMoveModal.tsx | 13 +++++++++++++ .../action-modals/InstanceTags.tsx | 17 +++++++++++++++++ .../action-modals/PlacementModal.tsx | 11 +++++++++++ .../universe/universe-form/utils/helpers.ts | 8 ++++++-- 4 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 managed/ui/src/redesign/features/universe/universe-form/action-modals/InstanceTags.tsx diff --git a/managed/ui/src/redesign/features/universe/universe-form/action-modals/FullMoveModal.tsx b/managed/ui/src/redesign/features/universe/universe-form/action-modals/FullMoveModal.tsx index c74e08269915..bd5a136a5c17 100644 --- a/managed/ui/src/redesign/features/universe/universe-form/action-modals/FullMoveModal.tsx +++ b/managed/ui/src/redesign/features/universe/universe-form/action-modals/FullMoveModal.tsx @@ -1,7 +1,9 @@ import { FC } from 'react'; import { useTranslation } from 'react-i18next'; import pluralize from 'pluralize'; +import _ from 'lodash'; import { Box, Theme, Typography, makeStyles } from '@material-ui/core'; +import { InstanceTags } from './InstanceTags'; import { YBModal } from '../../../../components'; import { getAsyncCluster, getPrimaryCluster } from '../utils/helpers'; import { Cluster, MasterPlacementMode, UniverseDetails } from '../utils/dto'; @@ -43,6 +45,8 @@ export const FullMoveModal: FC = ({ const classes = useStyles(); const oldCluster = isPrimary ? getPrimaryCluster(oldConfigData) : getAsyncCluster(oldConfigData); const newCluster = isPrimary ? getPrimaryCluster(newConfigData) : getAsyncCluster(newConfigData); + const oldInstanceTags = oldCluster?.userIntent?.instanceTags; + const newInstanceTags = newCluster?.userIntent?.instanceTags; const renderConfig = (cluster: Cluster, isNew: boolean) => { const { placementInfo, userIntent } = cluster; @@ -92,6 +96,15 @@ export const FullMoveModal: FC = ({ ))} + {!_.isEqual(oldInstanceTags, newInstanceTags) && ( + + {isNew ? ( + <>{} + ) : ( + <>{} + )} + + )} ); }; diff --git a/managed/ui/src/redesign/features/universe/universe-form/action-modals/InstanceTags.tsx b/managed/ui/src/redesign/features/universe/universe-form/action-modals/InstanceTags.tsx new file mode 100644 index 000000000000..3043e3e47d10 --- /dev/null +++ b/managed/ui/src/redesign/features/universe/universe-form/action-modals/InstanceTags.tsx @@ -0,0 +1,17 @@ +import { Box } from '@material-ui/core'; + +interface InstanceTagsProps { + tags: Record; +} + +export const InstanceTags = ({ tags }: InstanceTagsProps) => { + return ( + <> + {Object.entries(tags ?? {}).map(([key, value]) => ( + + {`${key}: ${value}`} + + ))} + + ); +}; \ No newline at end of file diff --git a/managed/ui/src/redesign/features/universe/universe-form/action-modals/PlacementModal.tsx b/managed/ui/src/redesign/features/universe/universe-form/action-modals/PlacementModal.tsx index 53dde3ec29a4..a4e51a96f93b 100644 --- a/managed/ui/src/redesign/features/universe/universe-form/action-modals/PlacementModal.tsx +++ b/managed/ui/src/redesign/features/universe/universe-form/action-modals/PlacementModal.tsx @@ -1,7 +1,9 @@ import { FC } from 'react'; import { useTranslation } from 'react-i18next'; +import _ from 'lodash'; import pluralize from 'pluralize'; import { Box, Theme, Typography, makeStyles } from '@material-ui/core'; +import { InstanceTags } from './InstanceTags'; import { YBModal } from '../../../../components'; import { Cluster, MasterPlacementMode, UniverseDetails } from '../utils/dto'; import { getAsyncCluster, getDiffClusterData, getPrimaryCluster } from '../utils/helpers'; @@ -111,6 +113,15 @@ export const PlacementModal: FC = ({ ))} + {!_.isEqual(diffClusterData.oldInstanceTags, diffClusterData.newInstanceTags) && ( + + {isNew ? ( + <>{} + ) : ( + <>{} + )} + + )} ); }; diff --git a/managed/ui/src/redesign/features/universe/universe-form/utils/helpers.ts b/managed/ui/src/redesign/features/universe/universe-form/utils/helpers.ts index 88f6ffe9642a..ef8da4440fba 100644 --- a/managed/ui/src/redesign/features/universe/universe-form/utils/helpers.ts +++ b/managed/ui/src/redesign/features/universe/universe-form/utils/helpers.ts @@ -449,7 +449,9 @@ export const getDiffClusterData = (currentClusterConfig?: Cluster, newClusterCon currentNodeCount: 0, newNodeCount: 0, oldNumReadReplicas: 0, - newNumReadReplicas: 0 + newNumReadReplicas: 0, + oldInstanceTags: null, + newInstanceTags: null }; } @@ -462,7 +464,9 @@ export const getDiffClusterData = (currentClusterConfig?: Cluster, newClusterCon currentNodeCount: currentClusterConfig?.userIntent?.numNodes, newNodeCount: newClusterConfig?.userIntent?.numNodes, oldNumReadReplicas: currentClusterConfig?.userIntent?.replicationFactor, - newNumReadReplicas: newClusterConfig?.userIntent?.replicationFactor + newNumReadReplicas: newClusterConfig?.userIntent?.replicationFactor, + oldInstanceTags: currentClusterConfig?.userIntent?.instanceTags, + newInstanceTags: newClusterConfig?.userIntent?.instanceTags }; }; From 87fe4b8fceb81a35eee56f2df496b7290c9f5da5 Mon Sep 17 00:00:00 2001 From: Dwight Hodge <79169168+ddhodge@users.noreply.github.com> Date: Wed, 17 Jul 2024 15:46:31 -0400 Subject: [PATCH 1173/1195] [doc][yba] add OIDC URI example (#23222) * URI example * DOC-418 * Update docs/content/preview/yugabyte-platform/administer-yugabyte-platform/oidc-authentication.md * copy to other versions * image update --- .../oidc-authentication.md | 1 + .../authentication/oidc-authentication-aad.md | 6 +++++- .../oidc-authentication.md | 1 + .../authentication/oidc-authentication-aad.md | 6 +++++- .../oidc-authentication.md | 9 ++++++--- .../authentication/oidc-authentication-aad.md | 6 +++++- docs/static/images/yp/oidc-auth-220.png | Bin 241892 -> 283816 bytes 7 files changed, 23 insertions(+), 6 deletions(-) diff --git a/docs/content/preview/yugabyte-platform/administer-yugabyte-platform/oidc-authentication.md b/docs/content/preview/yugabyte-platform/administer-yugabyte-platform/oidc-authentication.md index f30d09ba4f3f..e6e34d2705ff 100644 --- a/docs/content/preview/yugabyte-platform/administer-yugabyte-platform/oidc-authentication.md +++ b/docs/content/preview/yugabyte-platform/administer-yugabyte-platform/oidc-authentication.md @@ -94,6 +94,7 @@ You configure OIDC as follows: - In the **Scope** field, enter your identity provider OIDC scope that is allowed to be requested. This field accepts a space-separated list of values. If left blank, all scopes will be considered. - In the **Email Attribute** field, enter the OIDC scope containing the user email identifier. This field accepts a case-sensitive custom configuration. Typically, this field is left blank. - If you have configured OIDC to use [refresh tokens](https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens), in the **Refresh Token URL** field, enter the URL of the refresh token endpoint. + - If you have configured [OIDC enhancements](../../security/authentication/oidc-authentication-aad/#enable-oidc-enhancements), you can select the **Display JWT token on login** option to allow users to access their JWT from the YugabyteDB Anywhere sign in page. See [Set up OIDC with Azure AD on YugabyteDB Anywhere](../../security/authentication/oidc-authentication-aad/#set-up-oidc-with-azure-ad-on-yugabytedb-anywhere). 1. You can assign the default [role](../anywhere-rbac/#built-in-roles) for OIDC users to be ReadOnly or ConnectOnly. diff --git a/docs/content/preview/yugabyte-platform/security/authentication/oidc-authentication-aad.md b/docs/content/preview/yugabyte-platform/security/authentication/oidc-authentication-aad.md index 01ff490591f6..8f091bb5e35a 100644 --- a/docs/content/preview/yugabyte-platform/security/authentication/oidc-authentication-aad.md +++ b/docs/content/preview/yugabyte-platform/security/authentication/oidc-authentication-aad.md @@ -114,7 +114,11 @@ To register an application, do the following: 1. Select the tenant for the application. -1. Set the redirect URI. This is where the IdP redirects after authentication. +1. Set the redirect URI. This is where the IdP redirects after authentication. The URI is in the following form: + + ```sh + https:///api/v1/callback?client_name=OidcClient + ``` 1. Click **Register**. diff --git a/docs/content/stable/yugabyte-platform/administer-yugabyte-platform/oidc-authentication.md b/docs/content/stable/yugabyte-platform/administer-yugabyte-platform/oidc-authentication.md index 1e5ed15ac66a..0fef6b0d8cee 100644 --- a/docs/content/stable/yugabyte-platform/administer-yugabyte-platform/oidc-authentication.md +++ b/docs/content/stable/yugabyte-platform/administer-yugabyte-platform/oidc-authentication.md @@ -94,6 +94,7 @@ You configure OIDC as follows: - In the **Scope** field, enter your identity provider OIDC scope that is allowed to be requested. This field accepts a space-separated list of values. If left blank, all scopes will be considered. - In the **Email Attribute** field, enter the OIDC scope containing the user email identifier. This field accepts a case-sensitive custom configuration. Typically, this field is left blank. - If you have configured OIDC to use [refresh tokens](https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens), in the **Refresh Token URL** field, enter the URL of the refresh token endpoint. + - If you have configured [OIDC enhancements](../../security/authentication/oidc-authentication-aad/#enable-oidc-enhancements), you can select the **Display JWT token on login** option to allow users to access their JWT from the YugabyteDB Anywhere sign in page. See [Set up OIDC with Azure AD on YugabyteDB Anywhere](../../security/authentication/oidc-authentication-aad/#set-up-oidc-with-azure-ad-on-yugabytedb-anywhere). 1. You can assign the default [role](../anywhere-rbac/#built-in-roles) for OIDC users to be ReadOnly or ConnectOnly. diff --git a/docs/content/stable/yugabyte-platform/security/authentication/oidc-authentication-aad.md b/docs/content/stable/yugabyte-platform/security/authentication/oidc-authentication-aad.md index b8cd1fd5cdb6..f95191fe0f3c 100644 --- a/docs/content/stable/yugabyte-platform/security/authentication/oidc-authentication-aad.md +++ b/docs/content/stable/yugabyte-platform/security/authentication/oidc-authentication-aad.md @@ -114,7 +114,11 @@ To register an application, do the following: 1. Select the tenant for the application. -1. Set the redirect URI. This is where the IdP redirects after authentication. +1. Set the redirect URI. This is where the IdP redirects after authentication. The URI is in the following form: + + ```sh + https:///api/v1/callback?client_name=OidcClient + ``` 1. Click **Register**. diff --git a/docs/content/v2.20/yugabyte-platform/administer-yugabyte-platform/oidc-authentication.md b/docs/content/v2.20/yugabyte-platform/administer-yugabyte-platform/oidc-authentication.md index af9794133d52..b9f886475bda 100644 --- a/docs/content/v2.20/yugabyte-platform/administer-yugabyte-platform/oidc-authentication.md +++ b/docs/content/v2.20/yugabyte-platform/administer-yugabyte-platform/oidc-authentication.md @@ -63,10 +63,13 @@ You configure OIDC as follows: [Google OIDC discovery endpoint](https://developers.google.com/identity/protocols/oauth2/openid-connect#an-id-tokens-payload) is an example of such file. For most identity providers, `/.well-known/openid-configuration` is appended to the issuer to generate the metadata URL for OIDC specifications. - - In the **Scope** field, enter your identity provider OIDC scope that is allowed to be requested. This field accepts a space-separated list of values. If left blank, all scopes will be considered. - - In the **Email Attribute** field, enter the OIDC scope containing the user email identifier. This field accepts a case-sensitive custom configuration. Typically, this field is left blank. - - If you have an airgapped installation, where YBA cannot access the Discovery URL, provide the OIDC configuration for the identity provider directly. + If you have an airgapped installation, where YugabyteDB Anywhere cannot access the Discovery URL, provide the OIDC configuration for the identity provider directly. To do this, click **Configure OIDC Provider Metadata** and paste the OIDC configuration document from your identity provider (in JSON format) into the field. + - In the **Scope** field, enter your identity provider OIDC scope that is allowed to be requested. This field accepts a space-separated list of values. If left blank, all scopes will be considered. + - In the **Email Attribute** field, enter the OIDC scope containing the user email identifier. This field accepts a case-sensitive custom configuration. Typically, this field is left blank. + - If you have configured OIDC to use [refresh tokens](https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens), in the **Refresh Token URL** field, enter the URL of the refresh token endpoint. + - If you have configured [OIDC enhancements](../../security/authentication/oidc-authentication-aad/#enable-oidc-enhancements), you can select the **Display JWT token on login** option to allow users to access their JWT from the YugabyteDB Anywhere sign in page. See [Set up OIDC with Azure AD on YugabyteDB Anywhere](../../security/authentication/oidc-authentication-aad/#set-up-oidc-with-azure-ad-on-yugabytedb-anywhere). + 1. Click **Save**. diff --git a/docs/content/v2.20/yugabyte-platform/security/authentication/oidc-authentication-aad.md b/docs/content/v2.20/yugabyte-platform/security/authentication/oidc-authentication-aad.md index 6110d7667b98..24c04335aab6 100644 --- a/docs/content/v2.20/yugabyte-platform/security/authentication/oidc-authentication-aad.md +++ b/docs/content/v2.20/yugabyte-platform/security/authentication/oidc-authentication-aad.md @@ -114,7 +114,11 @@ To register an application, do the following: 1. Select the tenant for the application. -1. Set the redirect URI. This is where the IdP redirects after authentication. +1. Set the redirect URI. This is where the IdP redirects after authentication. The URI is in the following form: + + ```sh + https:///api/v1/callback?client_name=OidcClient + ``` 1. Click **Register**. diff --git a/docs/static/images/yp/oidc-auth-220.png b/docs/static/images/yp/oidc-auth-220.png index 50055d8e71225add2bf94bbff747b9ab89378b30..543ac175fa8f15549f50ac95134c6ded3d9cc3e3 100644 GIT binary patch literal 283816 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 z

    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πSo8JDj@}^PL;83_+$zcf-;a?Z^rh zM3=v?|B8K|=obw8KV%!lOJxN#M711mJr`0(&F5Ri!#hT!cs?3;C-l94R1HwjoA1@; zLg-s4ty)`wr&iZw{5%?*3uIsa{ZEQ~VO*aC z!(b!jGr^y^5uAPjJ<;qOkm-_~E{5F!6%T&Vt=$9K2ySg)l*=9>^atRyU18l=dr?W& z*AupoD?B~?ahK2B*Hpd=X%_lx-^WyvG&yPK3X(s!ex2D47a%vU0N+}HK<#Re*kux5 z6YNKmc~^E!=Jf9tfVuGFsFr;(9n*lpt!$8DFb{7$M&;}Z$~=mr{>5zoTKj%4c^o_o zBRai@*>zeLfIFhHjKQ59;cW^51hY!|TK6tqwo`=G!{vjx}A$W(m9Z`9X`V*ntvo8P&p1C&qi12z&_hzK{(A$twU_T!SH7sHAvr zoQO<~UO}(+>uju`)$V8MIXiCSpV$EV`nZ77v;u2$G#t@6>5~P2@{mxoAv!10uK;p9 z4`e~~bM#2MveUNZU@sS*X%u}++vly%M}@(pYc^xU^&NIu1rTW(ANX4(hiIDvL|!%y zt_ckSB-d0lQl^*<=h8mQbciWZkH8T4bKA~{Y{1q^7dp+`w)P-gGzP!EJrdt0n6K** zinzC@6vE7EVJT(o?Xl#aKI^=HkWDOHYJp6et?>))P_&L!3SG7UKzLNLw=`OSBUjmu zNZ!jUFT?B|BAl6{iJnrS<|tm?%q9*HTdn)bh6#-Wsw5nr4zo+-ByZz4b%CDyqwi4R z&hyQx!GnIED*RmMZ=!2!s;Ve#D@n)Iv&>F=kx}`b@DcttxnG885@D4CkDv$h=oo3` zelW9P)OdE+zqCRZG8d?(`L-F9ypY7m-h_+#Bv#qI^BL7EX4f7{b!=2N4n%o+qKVFo z89>ka7zBLg7byq0`zg^=$!y% zyB-#Qq7X9SP@dxf%S7_W`(Ve)%Kk`aL(EtOa#o-zQVKM4uaJcQ&Vi!vYwbi`RsFqh zqQkJ_`(De`?WQ(cdbnMHd+@ub^+F)a_OX2#p&)LD|5@DBJXxub-J@y$3>bv=GFug1 z7}e^grjbK}qju3cqm&Kl`CxrDS#K${X|o)9ylFsS|Ni}JdH%qfDyX8jv+35hbA_s` z+j79g)4A+@)L101X%Sciul6{Nam-02OiX$DdI`FgF)AIPsd+C4@>~PlA4yp zrKe&Wt2TkIGGrq_h%+)2flR^eLBHO?4`~zu-nKu%d(?l+#)yAJ5-o}aaLaOgQT}Y$ zUN*YsBTr7A^cVWko&WqK%a}Viwx2dqI%+Ctmqomaxqq$5WNe>uYB%hLSt~kwv-Zb$ z5I)B7L!1R=G0dE+EcPUkL|@4~A4)Y29xZKXtUNlQiSB!t>w=n%VAr;sPRk-KAOYhs zWgT#9y_;#?*^(UINo-{p?wa+2UZlhFMXlG~6PL?jo^OqHQp@#pATk2F4Ze}?{Onxi z3TB(kRCe;A@w#k`X~=2wTl&|( z+?L}wI~Yh5vdez7N!r#R!|!$s*qaJy+NQJyp9Nexo~rOsuid~&W89sW>1gmy40s+z z8eKkZr!5y*T-vy(p!SJ#O#4;HHdI?$X3I@rZ@GQ(QLO{wFWF9XG}|^rn%4o*TzvkT z)+L+6VU39q01bpe?vHMmI>P|$w#9CA=oAKv>PyRTszYkzFitU$s!m`mxYidP8fuOq z7QsGaOY+sQ$YriS-9fzr$u_W3vHbSqaXR?hg2}ENru7BrylWD-(xCT5oNI+HRuVNi=)-}tfQM?HuItd!A4m{BuH?ihe4g_{n zy*f$xlY&F~3m6)YM&ItGU;PvIrY!z+Z5fuVvhPg9T(b{ys1GOgMu~kiot8uRtDZdi zZrdpSmTaG0K15d^5jUcDxomB*ms5p$8Y0F3U6%g> zV+6LHIpEP#S5eCCMYzJ;*XC7Y-9W*o878nfUN@x6=;?Mued1$V0XDA_H9@>K=hJ-% zBoBmk4MIkUP$PvJ8bZo|s?@J9DsZ1lbpH5|lTx*3p{6T*U?IP>eC4Ia)m8}|A`}ys z8lV&58q0Muk)KDiFyt!l7Rk_e%ziyJxPfZz=2m(3Y(Z0Fa{Jvb&%dL5ade6W*ZBFs zw{cz6as4E0$XxX9E?NLN$PXOCtdPR?eQ|C)e zD4zCc%r!VM+hhHREw&(^lQiC$SeF!Ee{cgHY_ackT?}k5z0VOX;5eg~`dOP9Xl$I3;Li56bazOVz970o z82BZtDYXz{LI4M7A(S-;+3Cb|u=bH8qLA7{Q9O@F?13hx(mcD{V8Ltcy{B-MRGElH z7k_>{jYT6FmDVw!N&A-be`tH}c&hvVe_T?zqM?XLM8lR6LXHM1D_NPR2-$n@Qp#*t z*@a_o4%wAMSton%?7cbG@A0DE@9Vlg*Y&x5f9sEKH|KO-;s0(v0zoChw=mzT@*WJuPu7f0)LTZBf&K0iB94F_u4C~GB{8*MWF zkbl+2>SWh1uAuPRN#>((!R~$?GWxfC(C>cAUm>T4^nLcWVD&m^8K>&-#)*H#nqvEp z{Z;hsK&qg#6zXdsnvA(0FEd(=AN9@7vWS)TWIub=7zGJ*&J~rp9ykd96g4vY;AgY7 zv1Ze1wy$q-&({}+-a(+V1JCxUvB=(1<&#C6{$XXJX`sA38u46;U*oNU-YIrd%l$W z9IY{=?UR&V+E$U?wsrn~E_ij{QTpKbQ2w@Z^`@^<)V)(c*!u8>zo52ehO}OENd^2^gA@l{oCf#OY7Lo8|y_@eGY{o+o{88Qd?UQgWs z`ki2Uml8z<5dNFMMy1AiO)hh4guW~O&cEKNXtAWI!6fqbLS!flzF1iLQd08@XIv=d z+=%0tI~Y_7oWD946A^<3siTW}25SzX4#^v*Q!W$n8#J+8rVHNxm6uToyt9HFe-pIJ zTHKSiZ=c32m(~gb$tB0`2Maz6;qn|K_ZXXxZjmx7s7WxP{h6ivlQ0_N|8u%6yCXy zTK(Y{2MD^E^L4I+=Xyobe>~r+F?6)Xme!|HkR-GjU(%L$-0Q0s6}wL@a3~EFHBB07 zq?Hg1E*okjz8m6&is~xBtPO$d!4V(7t$&EbUW?w7g; zBHz`BZ=cEdsbseEeS8!nUgQcdTMP>qTM+xY|7&~H2G>~0(Nk(>po>2n3&4`?dj09 z8$-FSszNrWUPt<-b7WltCOda*J~1%y4$EO7+vxzHtn_vm&9`cQz&@GJeID(+6etBm zu^_AO4I4Mz4325;d#KZsSrcY?X&T-er}5qX(O;x0BL?|e;i^4AgGhIC-7jpayBnfq zJ{AeBnfIcs9(&l_p>Y6Zag%G@KFp|kei^0p+`VEZ=r5H7|Hayyb0MVH^(wbE43Ya2L`=L8#NUEM8= zDI7+nh*3&pjMSsJH{>0T*l~;xnk^7PBIc8z&u{3;ZtdKS9d8~gd*IsEs)e4vBv(a^ zkh}W|&I3a8C;~X>&*ogHgW{V@dKEGEQym=MZ!(!%bYeNKEV0E-KnTLn5W(o^9%DkH$De*7f96qH%`kkB=;?>$h993C31)(v(IYcO@!cSPVLhL zO2^)P%!`#Ym&@#$O*fxk%JWWnG5 zwsZ|-K4QuWM~AdY>~Y?=jCJ(8Bf2$*oFbJFXH)v~;a|R|9RN|oB@W^)?ikHXP_4(s zR3p68-s1Pu;k~3_lwU}yje%=I7v|wpbU=4ub8kkaXymETUP)p0_orn{^*0*pZKQUgTbrf3v{cN! z*?ILK!hFj}=KE_A|D271K_kqeFW)VQ{%r@b!AVno9?}JzKw9@<9FkQXYenyVZaq>C z9lB?|j2+S3LfMbyp1eQVnTNeBNY5@y2$JG7op7#Yo?qauo-%>`U{Tq3x?%I9yp-Cb z>k#MMCV9U$=Tvw^=+8(Un5(~nR$cl;QcwSwhY^;s=#Hv!-s7`m+!*CNj`fj_}5kxZGsEMd?OF+Ktl8H z!L!J`zx)Qv|K1EYAOc5yiHaU}f(+ttrpz*wT)Pi6lt-=zfLe1xem_H4SUv+&Oh)1LyQry;77lM3>%P4^$yPIWa8Ri!-Ov2@9y@<2 zAQby?WBDLz2H%*1Jzs|>B5hc@ravACAxOnMr+i@ONDA4rQ5ja6R zo!T{Adqi%*jn3St|1C<@5z4zOaSs`HZp;NDY~(4^#owTjU(4f;tWGj7JvD#=!x6dq z?#4ps`gsJSw2@EyE2qeWluXSJ3-6TvmHh?(^4~1$J)~T0-ggvgCXf_4SIGpv)AK*1 z*E4 z4zQ-I>ohWZpheh~)82mksP(xmA#N|7oSSd=OY` z6tSkgXMj9204cY2QFED5`8z$?QL{WzIk`7RomdT<$u?F1i@zGX8S2oK4%5UGl2QE$U--h~SUX!jL(EVwDZtOAO#Qn>bF`@i1BzuJ@9@PpJ z0|b{e78vy}+guTpVuqfPyv`6`y8SRJvRlvDL3|Z!-&70TVo6^zB5N0_8 zR*|#7-#~5RvW9f>b$wZ^ScuJPc*=!j1S3D)bcc;3OiQ3mi9hW;iF%^2>;Cmpz97TbpH+piE*pj z!s<8beg~4Y#YM=W8BJC^x^y1d;k(4_#QXd9!J{=3j%n`*Ne~?IaAxB7ukS9yB*nMt zYW~tI=@P&m(CL&($rg3^*T(s+0{9O;S)JN$FFY=AHOa!XOfSpxEXiZZqbgLd6|bKV zdn~6wNqtL{LQGEmXg(WPzGI-6m}vPv+oFTS)0%0TR8%DUuHR#1ioX7h>a2^=nLpYu z_ZB^GmTw%B(ZyV7RP2^rv&0m%SVra*{85Vs#e59Mt z6O)kUXn-7_I`_kkjT98=hr1oO-H7(U_mq%B@}h0|<0zx3n`mhnv|nf}-LiJYK2q(y zZAM4)M;b#Vh<6fF-CmMfGn{{zt56k?Z8mLuUKM0w4Y4= zC>?hkG@r&DUT_GJs~mWuOl+o;=7$XwhavIC{W!Y&dJkX2_s={m zpqD5Ksa2||lqW(>i@|vZKi*H_p{7^$lh?lCX9=rLMLeKrY*Kywj{hDLGPttQKT;lK zGI44YaB!*?v0N)KlRM$$T?n~V9=hDFocI`JGFv9m=mL01b81A8|3?@Qi&i1k?&Mr# zg72BXj4~m^__2@JU-%LVw>@auqW0gJ+nM6ReFNN2q@zSQfe9kDTiVqq0e24JdggwSO}`K#lNKfgOOzmts&m{|r~H8IJ4Rz@QJ+X=&-OBjg} z{M7mlE1jv=-{twI*FBr^J0|*5IMfO*iZOBq%^k-q?`vqGuhZG@T<0|;*)Sl%kU)>p zk2uuOW`5i-z0g^VD6KP!X!F9(mTHEblB`*q6Y`%|`5%8g9EI7v$xmk{PEu*pZLZtf z)j6Tl8N9N2sGr@%(eQ*Somt;ev{7cegMM`_LvX|retkGjXnOKU{Gqw~&jg)nLV6a9 zO`REyvzw1Mp!M)~xKV_r5}v6F^6@Scf!joBbV(`xI~y=Mn}noKFgeoU=I(C)&$j>5 zA3q+zzRRqR&hwj63D~l#Jd#jT*S3X!ZrdlopeLw*5mJ9h_bRWR!dReEJR`WBds)PFWYRmP2IHT z=>Smtau1L5T-Q4TY2p5eC`n~?Ys)F?ymZns>-FQ&+q)xoKz4TW`xAdT6@Og{au6aW zqq@15gSgcKTovsk88s@7T$( z3cs*6>hQ4Fq5JwAl+#8Fw+1HfLx{cF&)=9`iDRs=E{j*UW|ddB%cG=s&>(MOYO1r; zN#7=_Oe(U)Mf_*p{^uX5AQ5w;^EA;VQ{&a8ZL9iH0#293&mHD# zckv;a`U5|gvX_5$3XbIwsb5f(NAJiDQCoO7Yl(;@2u~s#P(p>U-md(zgQ6SvCfIrZnq-ONCcxwSYEd^UXrk&I{x$7gh5q7kGy|H{wyP;D? zfNg3nhrW?8ksdZ(ys{k^wJ|wv)sI6-k3_9(Ef$STMnpJF7~94%`(0&L>$R?|ZJh9P zk-@qwMCrvZh-^>gR89+Fm=6oFT(fSSYDiJji=T1Yo?q-V+zMd2ooDp@Y0iR->|M{| z#j>20NTGNM295A7v%%Cc`KT>j)%S!OX3p0?J*ZgpCATGbq65!Mhrcd%sa)qCB^ybV z+B;*b*SpDeSGHpqD6gGcJ7?Y(#t*S8#j33n2<;2pcUHO_T|SXo*`;I zY25?>BbcZk(l9OE3dYXo{(5ZwG>KN}Ai^&iGB77^Zh3LXE@4o7wPT8NooSBr@06BP1e-_SiXDlYiWIoKIops8@$OCez+vfEM>|p5_O61X?T$KO zUVHMjHSHfdw08Q{V0*8OL^(ggmc|CJJg*-Qj?!?Ec35~7AI}}W-m|zdA`;_btJ+5G z;aLC3RC3$lp$?z#m;*E>NZZ0)%^kGqTuEiU;k>l6K8AM*>#^w4c21!O(1L}Iso>Ia z^jMhSl2^%_5zGl&>+%93b%_AE3%ff;`48AJil3~Z4Cvo>OfIrxI5zpzQo>xU#@^>< zqt}D78#uGKBDZG476{^^6W_5>*3&U`8w4>@pFU9hkgvT7K{xGHKY|9g_Wugtwgw%UZA%q_XM3X*zl0p40zxr zkDvEJ4+*Zl813A`qt0BLolR;i7_L-Uf<3Ru9^bvQIMI$^k`&}EJIMU22p&BTJ!T&% zI1W_}jWz0>3XWW#VZBSusrq)gZ98`IZtv#sfrjNykuz%BmDemAEj1U@cukzPS{@Y0?MiGt>kLR0lAu6H;Cp?emLzQLl%& zAMhnW6WBVP?e+DNH$5(a=Y1HWWJJa?a;p8*h6)=@)3&q)w|*`drVHtSGL&aaWcW39 zP(KCnEe;S|{Fmh$)!iAp>)6}1v7lb`YXB*|j@Vp4cZc-M^>FJ>c$QlAF7~gS)+iho z!Mn(Z=0z`Gl1AG)5vI6B)H-LY*|!6>2D{M-#eRXudsYV)hg(ZCu*TMV@*7ds<^`id zrD^nz!-=N14>06NOf4<(cfHZH-B8JG^;~{FA+qe=+N4>!W;_y`oc_u9$DaIj*NKs_ zl!X#yA(WEK^0P)z2+kZoR{R~e0wcGxjvQ|5{ev46>IW~v?lCNo1l?IUo|^ZkbNE-O z=59g-icNfIrQOnd5x3BzEVOV=qA1=p09&e;Ug>x8ok{UrXRcgZ$51r?P?)Q;3=q#WK#o#ALyP!IbxgY>};e$Ay%;BjeBT^*eCZg?K7eifF|=X~Bmk7=3) zJNjzBujk^vWzB$MC#(8_oRO{VH#6fQy~Jkp@^|Ua?2J-_8M z*`dr0b^_+&=5TONL?pKJUG`-L`^S$utkadr^Q%_4w-g-4(>irJPdnJTbgaAO#e!Y+ zI9+yo*08}FJ=;bZzrEt`8u#pop|EO4T(6yJ$D?)5Y)jCvr{9-eyf%1s=9}MlSE-0g zJR0lC_@CU3)9aD1gE%yl9O>Ch3pXZnrgJJgE(Z{GeZ)D-7f-LWDWv1bPCNaW9;=b> z4KY^juubnKOtlNRe*WNP7Z-aE`F*^H&${D`^Jv15te6=9If%qSAu-MojZc+b5AF8eX zZ6lVwhNJkRyg5vBYal+(zi2zNzHYzcIG(HYeDP*6dW@{rS8{tP&Ni;w##zf|T$;c4 zji1Bj1$lf(C5E{+y%j7U=b@tGjg2cE=Q__41y|?H=*`saKCx^Tx&O#8$G^S- zk-GuN@lfOsU*dA2m2^;=jI#Kl~^laRNPyDwjfSC%U zfWvqr@w_iOG_7>pFfl&k70cWurWFKO!Xwc1ecl-)PShSIIxueq&D?ELCW?Y!n+ zR*e=IO!BW36QuPp&_OgFwYj{IdvoWAY4<9}1Dal^^So?mh>J`6NQ17wtI=X!8>E;A z4gR0LlHppG*cd-*-KxVbrcy_*i+|=)~}KMtVu@~npSO{?mB8GB8fS0-~n3pKJOp?0=H^a5yL z!O{wcUonEe3h3P<^ZG4O7h}}ZTK-Of&7Y18$@H5;rW?Ew>uU@xn*6Hm#;q4H+k}Qf zgF+dhh0d|g-eM+4=g-tKGP0ep zJDxfd{xz<=Aj3=<%*LBo-w}w-Mq02}4wh+Hu3gpd$g$4S7BR1j8w;b}zLd)Ee7Arw zqSsniz1PgJ03AO2bY%LC$XX`<;}P*ETy<0bw}%F9?x_>n>q-#@K3#|_UTG6qT3l?B@h56u7a=TB_J4{wjAHBr zD|tH-_=N}C9gG}%h0t$OQH!%rb7mG~(jw^Cm-ghxPg**GJCAeI2FuakdH$o-_mwpD zb40;Wd6;$F^L>5$XF*t+nsy1U^F$vi_MiHO9ja{F_LC!3PE&ucwCJmlZ`9tvYdKuA zr*-z}u2B9za~>}ecm1dpU-ThOnJQS|)8AaeU{Kwcgf<5w*CiGSTzr>^#og#mN+|DY zG!k)H@cb52x_5&N|Fj`Qqn*L5oXWNxIS5iqG2ubzFB|4De0`eaf01$~AciJhuuW)J z!!J3HLkH}gA78A3k1^!az+L^1&qG9nF^DHavcfPWX)r4cs2VkQ9|OVv%l~TjgKuyM z?%aJ($Js~)F4KWVjk`YYIT}nL2P;&>v8IxicmjP6k95TKwPPwhn{~sKYkbhAH`30P5m$H(ZK|lf+9=}h44T# z8>V>oF^~b#`}fxr1-d1$&OcNU+OpuXYM@Bj{jm>x>|%LVNdIpG1cOfvF75a-jZs9R zU8=a8;N97PVm0!A-Q5t)gRrSOzk&*b+6ma`?|}_r=g01H@R3f(ERXr$cli@|jvLH|70fBpGj@w}E67+@70UWiC>v1b+IIa)%HEUd@e+{he zE_6b)=tf-YP<3!T&YL4ftNg5&oqTeC5#HRuRpBC&x zX7RBbyEiFg0RifJY>~9Uf@r)McBGGcB=TywN> z0OaU>k3Q(9eSrD1Vs1!>v|CvMOq}n*>@Wo+(=krf1Vo{5T7Do}sX1{iMI}3{w3c;4 zDI!!KSTO#6clY|EtI%OP=y{)3%gYFx4t-vevD5IQgjhI5=c+OFF`OXAItHeHiLo4T zAX=09M#E8hb>WoT_lL<+>gQ~r<h^;gI>{@pIJ1T4LSOW0XA4gn3B{DlV1@P@|&=&X`aM5RYC3O`;RhV zMp<_{EUGyr)~5eaOp7X#Ohd~|Ufb!e9AM^;PsMg5vDLQ7z-WCyfOaLw70Ly|GAF5D zGv1BqcSEi~dAcAS5-Q!Zo^R3$^VBVzlT_dI)4h=*#PzU4SbzO@A$gRzGm?&ufr={OoHi(shZMNga|~k z!BEa_3>gU%s(&(!l65V>oluKCd0980FjMu{KKRSg6Ybo`)Ww7J8IHylAEO1Se-dV; z>H(E8TLj!=?C!_wyZfVz7DmxR)HK`;^yGKRsJWzsrt`ItQx>K+2u;cUa@QUC5uou9 zP@-ckYNpci7^yo#Q#tbOVad+$QyN9sJ90xd*-5Z#6mu=$C?Ho9D8rI><(2}nykUN* zx^ZVded&~^D-8ZoO&L=^z#6F(!189#93ONPBSX%}B8b5ywFk^U6yRp8(1mj6%|!3O zsP3wUxbjb|lx{m?v4EouEd21#Q^HE(;ON*0olS$0RfP(06)!%05@bc0U`2!OK9NX7 z?xu0t_m?iCg=2h0l(qi+p8R7}H|76fE);H+t$0zf2IwARyf}_5nauPHMMQLIHRnKE zLwIkiJY>lxX1tRuS|$Ezw*|<-Ep$@6_$V0fA$H3ph3j;8x#5Kf$MhMIJ3tEZ0(?Ah zKP$K-flcK)ujkH<|FSTm`$(M9AGkB=GyN}yXj&KEjXSAIgCX@FboD*)J-zHfnQR@m zV5^YarFQFlG8|c~<5s_)Sj_#Zt)_~TO4U-&WJLCOXZ%VgJ(PiJkGrO;w}(L%?>!X5 zy$Rw}3)QU)SE&*)fF{7mYhSd3;e1xh9lVWJ$i3Np?H_cJf*K6IIW4N?IAg{#vJoRw z*==9Lj2)no2aW(xIikU^2!Vw^VCc5aIsae|!^Vc8!+8XdiVm3X>^G@$J}kmo#Bsmb z|I5&@hL6^a=N!kplipI@vizro6h#)21UC%ijCcVfLyW?fOBvO+fU!~6Ym(#M9T`8UoxsA5KaH$~ z<40#_*w%7hX`JIsF+VO!NVg{B4Yt>1JqHd?+S*S(hB*NK$7RAE?AZVWEn7}}AI{H1HPNJ`t-#Q3TyI%T_bYbl)66SBv|3k_{IeCQ^g1Ngk3-DXV~j$kws--ffo-7(!#R=2Fs`Jb@nB zxs+So1KT<($aaC+h=|-&h~GGxcs5|y7Dn{&_rhUJ6R~zL1oc&l?97)%UWr`m|3enB zgr;Ms$96=((S#zm87g-44SGl?bHe1Mo_i2H#Q~eFE-Aie=REA#jO@07&T@*=geN=F zg_rteAM`HL2;$1`d#Wn>(Qa|@^h*9>R&X%~**jl)A#8p!0cv)~osgYVErf zXmm_o(Gcetq@wUP#p~TOG7_iEZ_Q<-q0#c2%;bwz$|aXBYXUV4ox|pQaH-UcJ>D_P zd3y=H9SC-SXNS3*Csb|ZEEm*QftZW{LWLhzP{$BY8gfYY6Uep3R#*$G+AFOo7Iryn z@9|E$UJgf=9dO<$iM7DS)6p0iwA`lczt|95!YkI7bWqYG`pY?V$>x;lk*h2xHvNVG z=q&|R5>hUS*zFy41K$0Hst{Mv?3bKwo6cswTxM>;gwb4F`YLrD?ONFM|&r_$+o8M(!-ij=KIHGgt zl^Onw=c%`dtY9iX7IVsZ;CR&S-nBa0tW64oS5rZiBroo7)3i@Px87QD|D+YeD@4I) z?x%)4Z_&br$!aA_sitwVu7t*9p|hRTF;^Nj??wqzk9ko2=y z+uRVht3Br^0p>(lECfoP6p76|>1`f3%s}8HAq|)Ch9a_B!e5;Zz)x=~mi(_Rw=KR6 zE_!@#iuU`n7A2x-P*5@#BCPJWG|4)T%ilb#;P;q8!+ZBwtH7~VOpCPG=k-eSoHs-epV@ zeHZGM^`=5XL^7osUy8dy-}m*>>}4^u7H!Qy7LX|{fWBA)X!%C>6j)!~AD3oe~6RNj2d$)BOl{C$9RPDv{&J?lj zuG^an!FydQ6LcuvRtLxU39=QBig8yxwXYuzsaQFHTD|U-D6())+{?S;hz;xhJ}+ax zg+csJ*z3y6CHYWXkR@3%ZJjP@>O7%#z;O+Gnwt6i*0gPDwK$P?;ziE_g>yg`?=@m@ zF-j4&HRs~lphxhY;P=NzoC#wlr*`y zV=f5Zg(;?2l!-Va`j7;sSo~;XoFHOCSQ;kl?MT>YdHA|YLj|o6SSYfq>yr;|EXJdx z88-S3WYhE9Q1VI0MI@%hL!VWoUXmrv>`=uT9}Dfeg~MzvV|sJQWgccM z`PVNpnFYye6S_u3`04)0unYdYzi(^S6GbVToLYHJOCfKePKdkn1oLOHm&VTy@g>k7 zX9pSj9W&^=Brt3lwXXj4p_-|wDWU&}YlsXOL;}2I){(%u(oT5jLb=zhwxj4t{YRsN zKZH*?0Q&{Te6Zrs@O+>M<}S3P*uy3MRIL$VB#Q4eDNMBqAyW6%@&ycr;aQ7aMNbm| zV($R%CnK0r_h25#ywj?8As6p2@2WEQWiYsMTV17XSHzk_Jth&$*ZtBTPp&P#)mON_-7%?;;mtjd(K%X3(QvBY43X!hLNe|OY4K*EH^-G#3K{JB+*AC44n&#n zu9D9@74se3e3Ei8+d4gJzh!sn_U26c6E$-uABC32#^cGX)6~QDTfkexLKnyOREgrF zS7`YYt)&=C+!Lo(bxY|2;E~CWR?G+5scC(0PVBd|s{sr*Q(bcwRtNoMThXr0HengP z;1C+-86kpYw6J&6gs@tPiY)e#i_BFzSK?JxDtZ=GVuE&grdCdNk!_F?7x z6I^}rZK3c@Cx!-5nPX*pkEovuppEGoVm>W0#439SjO{@;!y)d0P-?W3`LYjgF&Fr@ zOtI|ZuHGY;Fqd3DlWD$Y5b0Y%{E22|bi_5oOzmI|o9}gKIFg3UP7!tkuX0y9-QKNh zmXCNizRRj$CD3VLOqT4G)mNShi5fh2y zo18G)7a`XqXIMr9pwc83ymBZ-^ zZwi?>D6c0xV9MZu5YWc!Nn4U?O)z-)jck3$sm_EtIh1dNnh%7g(N$8$Px$KqgV?`6-qhr#_#s2lPWj6Oq$ zOzmzuEQIx{7k1y?*9WF`kmlBWu-=LKsDxIiw#r^F&z`H%6S=I0)(}l>Nhk@_3S%w{ zMlIm9X;Y8z5mBFsrUr%Fey(SwWTja=o?V2FhwYIRr@FcqUhcG=&qzl7vBsv7`M~s) zDX)wtr**xF9+K>#{ z@X?!>Q|tgPnw<}qJFW?2mRd`ebH_Z6%On_kujs5&OT;8kO{yV` zAq}2jK6H%SHb*;6S2$jZO3TmZ79Ox<>e-vgd$8=#QV&?UruR3pW7_XC+@l{~3e#w{ z*>OL9BoePhX+Xp$_X|PHt;`H`#tZh5qBO8=CXy^BICh+rQBp7;ErRR+vUx%T=Rra` zV0oS@QkdxLe1a31?IwNN#rxuIk|w56`K}X>q9Gh&QIG=Cp^6C|zV6REgqFkK# z98_IgJmi^91$%yDV##{=`Kgf=R^U8{3{Nen=hbomCgZ47>aVvU-k&e(R&vkmW&2oad z3wK-kleEvzK+nbbL8TSP{3U-zC6e5aN<0<-+*E)*u+;KV!}7r=o(b|;wBNZ95zsvk zv{(42@76XY2(~;VN-6f6&R(BWkGQUWZ6|$TE(L;&;tP`iRcRimqRIEQy^a*Rj*VeG zyo@=NAFy^Ps=aTPHaTBlUA6#o)tY9imU#rXvN}#|*1IvxpB-)Bl4`eK`)mQ}b1??3 zTj@m_zEBlL!Q3;1*D11Pnp_5zqbz} z$}=s(Wwe`h5(j-m^_;eF$D(*BwrZxd)P%|Ad^YC%U44Svk3}i3tuywXX!U2E*#ucPPR20uO%yb(g$bL-wDNDMJ`Cb~a3 zHuh(e=WT(dsmUj((G~NA=k1>Sfl(`5pE3^(HGcX9oryq5+Tn>1ExVL9Ji(hP%3UiV zIsaPSD?J3|oaRe@qLXQq=0jY_C4nHCI8Mxz??Xin9_J^*opVA#=`Ksj?W8)HE>QKBduI^l?+7`6*vfu`cj5Bv{?zj>~(a z@(QP_{x+JKW*QwXVvBlDQkChVg}U6qK|+1h6L};5t)Gutw1~K5o-Dg0Xrz3;3v*-a zOF^xDuX8miA3t3C-w%5Y5)JQf;Tn%*n2QY-KMWoX=S2|7(|gq|mN z$MSU-uMzu0z*F4@Kj0s-EU>-u>VZG&JLYe6k{gZ>jjGepZKM_Rt#*d0RPB??oZv5z`vtO}u7@uY zt6xx7>4or_9|SPXn!+2AaD|4l1^q5UBoNTG?6!aW$)zQyd`TqkQN@(7{G8kAQD5De z5-+*#d3_q@zGE>o&D_vz%HzG(E7onT@ls?nQJh)>!&HFr6m{jTQRXNc9&z#N)`bO6 zIyY}OH4iDRSB4e26TuhRUMPA!O3?b2NRz^@sSSIDG!?g)v6k}+w_q7YibpsEbIYV% zqb?-*QVV(ZL-NsL^bs?u5IKEc^NLso|)r>q&Wdk%I`Agjjq8~MitJ_ z%3&XQxRReIqMq{U(<3=M*j_x~#ctr9HjqsbW8Ez4PFw3su^+us(5l0U8jXI|04#%< zce%9!Qk@8mtNtG-*u2kuvE-rGZQ4u(kBqHxKN`%&+{7#1XTAvJ0Yc7|0mn1v;wuM= zoYZtE#<|&_Xa%*sfMON@>>VfaFc2M2qs4Jrp1dq|dh<0pzJ7)Pa@)=ZyhdHm6Y*}f zc;9YBM#vnuMmsj=vo6DWtWUaWD&OFW*rw3}+1zoo`D4ErUCF@17hgN#brvY}D zs*aj|^xKv0bHpJjN(daD0u|Jsa5a7c0$>GcKLw?B+#ojYquc$_`9?YQ;<(_JPL4kB zBtGi0DI|#Y@HX+9LJ)=5x4qIg3q&iAzewpZuIaqsT2@KwEREi$ML%2PfAieu*g0YOKNNY}u%UkJriz z{-QGz4Od6P-ys1Aqv8jiMqf#{S@!qMzsVgqF?UKrW8kV^w4Bc_;D$a$?wHd_Y&V5q@QUcv9zRb3tnAr*Y{e0NZ_}Br;L}d+Y zMSy);7^Aa^)TO9s-3!F!jfO8&=FYF6oxid3dnbBnTjhyM`bg!0JD^wSC0}s@+^?P^ zzG=!`N=~KaBe$J&e_qGa*v~_5B6%O>U=iuOSTuJK%?EBmF9(sbQ;GIsE~zk|>inBJ zrtnqp2p*T6S<`!vP0_diR9Bx^=ApUCpMG#}Q{$>;JO9bTnUX_wE=C^TNY`-p8Qcu* zZamV?cC0+}?2j@ku4Xf9>sC{{r@p&R>t2Xcwz-QnpCcv_zXe&3TQnN6ji4_Ci@y+d zL->M#Y)I?6vLvSXX(GlNx$5FQ#v=y2WWI-l9hTA~mviEE4)7jyro?l+u<+W05hiLi zqQvHl`&gV{zJeRIps-`-`g*8r$UTCza^vR(6sx91nFEiTf{`uHPX+Q8Do84lnni6b z#y4J$TB{k*Z+9@iTNPq(De7?MRvGF1&ln3F%VWsyq@o@Nwth`@C83Tf&ZK;%h$E8p zFw(k}>hwKg(7K+x7rk9b^8$&cg!wj5Ro*(v`4pAuA-&0>-0nv~kTY5ccMuOF?;P-M1A0c1*cZ7pNS&yV)APwE~{rfzS9A;NLkuQoNRGx zr4*%xcLm!H$6LX3P=mvsh67cyT1ay#8RT_N956nrdC7hAit(kw`!8`jk4d&;>@b70 znUsb-lbf&U^FtYM*ddT!9u-Y-nUpuR=L92DLmGI$7@4a}adyEXhZFdt9Qe}YO3O)u zD40@?tW}slJ5(n2?a~b$SFp|+HEg>Bnfgw+S34yQ=PUW-zn8T=HM53+lin+^A;5=5mo>v60bUNI~%8ZAc5R#z^Cn;!&;`;EpF`QAhgp+WsEr7AQTG->)* zh`?WLjRSHMk%=2!ra-xeZ}IMd;Eex$Df3U*pwB={SJiIEMfbee%h-6?sC+S==g@d4 zCM^P7edDS=tyxw=BPpB}OWhcaz1TzXTNGOM{bcBn=ayO?c}{}3{Mp+OadVG^;4!ap@luOQ zCvysod-?+}=C3+*N(+POuA7E@)?-NIum$b9*?g+KG?~WDTkeaOq)&OP(ZQOHd}JNj zYDgo++BnARjb8OBl4NLIg%F#($beRnALB!x6dFk>S-P2Or^ngijV=}DV55Wg?&vl4 zT$}Ypa@L{e43Ml(lFn2iFQa;9PEp3;NAy6zxy-=lF?Ebc#ifvyuWs+?idbHB%u_(& z>&10&^WH{KeCUN_BsqZNu@%Sr4Pfl}?0qE#HA_$MA+6^%X#T_cNGgwvdJ+^+*|_I-kLqR6lLG z$=F@r4G=y+0)*=78jw~#2f8z8HM|SKLUrHQ-~5HUfD@-YtxEWBpqhr|XyXJuF!;1r z43OU5>rcDNcH=C?`B&$}SsmuzMTMu1eD)h4m2CGbV6e}Rb+L+tC7pLu$+>{}cnXjN z8r}?!CmSV)W3E!|H3P$9`<=)}{}H_dI7%-HzeKcGW69wwL@R*nWEG3%3<}wQnOK@R zmppz8$EajpRr{n7ocPl~2t`}tQQ(=}T8(m&H^S*mi+G0Q zm0I$yqA>L{T106N7Q`$0I`|+YC}kKbTdd~9Hx7tw0jj<3=3T&^`1Y{yY}sT0&6OG8 z)2$H~^gcw9fdrJYoN)}WtT>7;rpJMv{osN-NPR!qc(n39SZEVY0Xe`6%R$zU!+z#6 zybP|x%Mu9Q*M%O#-|%2TFMg!fxn5Vi+`;ZVZ5fq(Lo{UvKPYgs;O2BB-~sq7MaN<-W>U%Ctg4$6DjN^)CEYK@(Ah=NA8~3Nw<**wJfM zlrh@4`BYP9b~36|hbC<`Ah-;9?ytr6nLv>||Km2vcP)=Nh-ujdR^Pfe^7`iC?--Eq z!8zqHo05@On@4}1ArR4-BeM!hj$p8!Qlbpga?1v_l__ok^LW}`3k$73MKttDU zIDV9wjWR;1CoVd4cJ>7M0r+Rnlz*SB$b1)s9H&(opOjsHPK|`m<+t>5NU!oa@R518 zDoFqYnH-Btutf|Y!xg-;R#UNE1ww;p)bz>BuO8=By$;bF$~`rtGB{G7Il&+#*4_T? z5l&mXSdNGb>$wEMxgaLWWh6iY2+;E3J(4^mY7B1cU#Lr74>%t^lR4wEQka}`x%8gY zj0(f>shagOsM7WsUFI^}oc+ryHZ2aRw_)*Wt zc)jc~+ri#e1R3+?5yXFn@loio7yLI~1GA&i zW@7t(@0A>+^ zGlk*_0&NMXIJ~1cMWbV3tDNJkDGeL?Z#Mm*A`iutOPi%gV>xQKP}u3HP)_cRw# zH&BI>S(3c0YE1VQB3Fto;F`kL;M`D2eDxwy^Py2Z^=9%Z8P~V8_fbMkKphe6*~FB8 z+&vLu=G>*~%R*0|BOb1N6U&Xua$%pjo~3SoJ%S3?I{2;Yj5w9|MQL-;#QcC>Swy1E zI2Oyya^6r1tK=h&Vy@aGSv32&f=C^UCv~{1Tf9*pv3e2KN}N*EG4$e`2MT?}iMXCV zv`^q6euSjWl-`$I5-;;!k>scqS7P7KGbm`@-7G!P2^P+zC39=ONt>rJ2Sk}@&hb3e z&EzH18v0O5Iw3}CJX5?<=ZEgxlmAh!s5R!uT%;GpsuwegkE{&m$ao~~+UA(7EQKn) zkd0Bx@G!5s|5A`oiIRlWO1L6M#rL(EPX_fK+yj6P z+tJUsJ2{Ngv|s8$;o@leyr&?*lf2@$K0NbT{6mvcI(pL6de;*APF$iq`Ez`nZV&NJ z;p4}Vt~5Z=PK4f{VA+k%(dW9!A~;vhE1O{V^n*#yUf zgx~~$X>Y%WxK=<9gY38kj3tvtn5EUe2;pL`ojwXrlx}GUabFBXjX)~|tJa9gk zX_3Sa97`WO&GS^rCswgE#YC`*bg3A!T*(o~u49d{?dNe(g+M{>rP_7@6;0!&R^nuT z;;z@M3I~PHRftnHKHW!lpTy*&G)_kV49}vUG0|cVFA$%XwXc%Ts_$WXeIoZ2MUd7j zeRgd^)dzv?QIg+W035aB4A+xLy8-!gNaZ5?k!w>KUk|g2?@4AbHpzgfOU_&K%@D6E z7@L~-5MQhW3dyo!5X{pfWv@pV0mcqWyqHH46Qk|=RGuDQO*42@Qkc3{e9FM;ofb{X zuKK_h=%v{?nJc7|riy*z6#wDHy_ya!E{tyF3WyL+xL1iYx8W>lJ|q>1n{Pn=?=zAM zJ*%Xw0`?fw?8%soq5dzVS1S^9cpA_}{)5h8Gv3}U2f#Zn6*wQ&as_kJSI;85mQRwx z%p1inVa9=WAv8wmMqQqBFh0=t(U^x=$76N=oOpG0JWXBR71(22G;-YHEI#L+e&M-< zCaE2G?_Ga3#`Ke{@W|SzZ~Gt4C@=Y*3TG^&v3a2`x8t_g+Q>uUbc(ZGAm6g^?HT@W zn&)TLpeY7e^5guG3ue8LVaa~2DJ)_C@{*|}2$)WYyEtv3AS+)@I6c$7D(pAH+Y?N| zXS76U-?y=gW7J>-2b_x~iwhiZ1VZpA^J-!z;((_P5O#soNeBpMn)Xjd#1pQgjZr6_ zvDW(eQ21)y{Z4q}Kd=flhjAfUm$pY9#TeeQn?|w-=Y#`2xjQJJv02N3BrjwCi-^yo zO!qxiggm#vk~xQxxdz-8bQRataF8H0Be<8U$!bimk7Ip_?XgTpKnGeq-sKhk zol`&_L{mc!JOTp@Z_kwh8FU7%EtQ$a{tkfBlUjEXY=TajC z_X(3BPL6Z7B2-U&?gh$bhs@w5lnnTYyYMc{**h+%kgzyywZyq9ynioGhX#q(7IbvM z0CMwOW25>rKsowTHnnbfYjbXrK|}!VLHz zC7c+|+W4n6hmB^LF&BO0>gV;JEZqFa&9l$I=NP~J{7ARdhtfQnrX3h zNn-zuoyp*}W0D?R%G4fS^<_-2DL;HC{J0;^fVW&M6&QG6Em&ABe8YaNu2Iv&vamML}h8Wr?8@qV}Kje^MG zTAX2Nj1rHcaZ{2&sQRg+dvaIFy+`ufkIf8*FbOh5T)gp_=b(0py8Td~ia^HJ)t4*i z{E_~08VWA)gDL2emEswlW!%Ex=27_vfY6bx6HoB5WFn_w&Y=ocnx^O4$v@PeSy<89 zu$dd&aez`9D*jm005o$el}^i@T_q%kxZ_;$e{#n^|8Z-T z9dpA6Wn2DIp3Wq{zfXqaRIc6JfE8#?U3qut_H)G;TyQ{;-g(ZPl&gA5!_fLs0_j#w z7(e&KOvm`6rt?qv<>I091uD5d-+WMf(gsJ6c)%0s4#i)7ZA+~c&zYPSfHgWEs2efh z?>v@|;3FGP^=X#N z*CQ}(PW_d{9LRUXRmacm{(^nq0O|x~@ahrTpm|cj#tG;CIJPsb7azEju9kl@Fm)QF z%g(sHX-{$2>rJ{AZwgI6t9V`3pTUa{&%zoBybVnga}&LQI05&sEa{{o@UAPW)W3=( zTi!xwT=gmahibw{0}n=x7du9y&s))r_Hodd)fl{wS!X49AA96&D->LK=m+D1HA8b* zFbq!XoxLJ=Q!VLsUTYVrTOfwGA3NUS!vt9Vn^cxg%7CD-)@ip6-MO=ZD&l?&E&SL0 z5RHCQhJYOEfYZApjr0pBBOCqtk78rM@mllc|7!28qoUl}z|j$DL@)pq2@?eggHR-F zX$(La@+cw=QUX#UScHmzQiFO3#UUhy21P|gx*1}SZmFS%`@GD6%JH20t>3!qu6x(Q zzup;{{qFsAZ>z+EsRd`PP7|=_E>kM>k;)9cJCHGwIQ~fJgqv&uax^VD+v{dfAB1IA zt0lXt)HP9Xw1z6U^&}_8xTy^8JV&Y?C8__?tVF7vQWDV4oI2o`{Cz?B@GdM=$&F`b zQJ_Np(;~o<=|Vq7w<-wBwSsTWecr;FUmPY%oTk$zGqk?K^%Nh<1p?Vr&~$E^A#5l8tcWw8iv zILW>%YRUJZPzFvC@Pe^H)e-hy5p;Gnxlq;p2z)&9=t(v>p@obu5C6UZIYy5d5sVw< zHZZXMdyh+p3+=6+<%mBaft<+w03#g4Tdf@CB7H@w>%91{pXdQ>$?I-srVvOkSS7u( z@d-;(oWfbZwd>T*$miU3@nowVS#`U1by8D2RWdADg?$R zA$>lZ-$XD+B6%n0`-doX{xL|m(CWSQ3INl$yusvJt)&H!hX^V}UdTQ^^!Ps-w=@O~ z^M0E1E5z~t2jbX`R3*bm`HZT{N`E3)xbH+&oB-ox>cAnXBbg3rK%#HX?;W5}><#!@ z>t$%JMOv#@@~x?nWN^}=q3A6j>9OXi1;+V}Zro&zsF4yE80`GHf9B()PWeKVXbz;q z9D5D@Aw+M>U!fy&uIhPM{0Vz!3k;GsQP)hf)NimV;Hs>8eW9r7(w_?3Wg`ZtM!QN? zaLtY;!BD*1U=$tJ(9F$@NZIGG@2?k`&Guq}=8Y6y>63X}b_2X)x5%#gTaW~giYaGD zn?8m*r*lu#jo|K)f64&^wtJQZp@*$2Ko^9jHW2}jF;L86bxzgh*`*y9h|e*Wf#U?hM@z|80n zT(gL5?>h7|)KW+74AFpETl2|$=GV9(S3sXh@FAn~a+*<_|9J#z`lm{Ih&t;Gu~3$h zk}{fTvRAoM;46sUkb?9Zm?20I$c2mbKsNISR3HsE!1BBxBZ_u?osq=Q+WISr$srwD z4xDcc{`7)mH3HC(ymeQJ=$sKcme2|BM4Dc|RhOq$AR;(w;}&vwkbcd1$d5fzH9Oi5 zH`yqG)uT{zo6Ej&zY^S0?t`^5`2!9>go!}P)us=NyQYMPdhZIjcC)BX5%dgnJc2}R z_&U_72D&O%J~Ejcb7K7hU*EWYV?O+#q4ERQy&)9p`1&t|q<4`9X&^xYBFUDmACNM7 zq+8CfcdiKm{NmzCSRh=p4ts0zF7-CS$9!3n%DvEF^ED^0LC=G^Vgon}VksZY{?HP^ zx+f&B1zG^cpjL2_DcL~4n+P2sh%TxKIO(z2d<4x72#gbu@2h*Bn&>KePD54OEuU3re8;M#csHXX5FG`VY^25?8ziTzmvJeh;IL2 z0eK>`U@5Kl#hTr$<&%xr0bG9MrT3Ap%0`^e5;$mBq6pzg2U3Lvf}f>PgRDRU;j7665kc;Pt;-{RzHT{;lX~SlY(Nfs z{tiQwdkK4YPmdg@S@JnCfsa7Wl9@@4kSTUwhqT=4s|NKc0`^PZ#_CXdCgJNo@7_2_ zEkGjmgb{RU)mOzR1n!edRN51Fus4?DpJ!>>%c_suxOPxRF+@xdDc!G69o$%s zuvzkM5GBEwq)fWfwqA`~3ALvRdJSlB?QdpQ_l%VEls~LAgi4FO-b>dCMSnQ){6QQ)Q7q zj`0H87VZ5z;GgGT`UY`0QaZc(g%ET0$??A33%~5n%sGKTi|VShmzHN_Xifh-;3DX8wZT?mE$>33ji zy@p?0*dn>q7DwL88*ptke2y6HB1?y5Mjm-zCIBMr)Zt78*k8uqWf^h{fIM;*w&L;N zwZIZU{`sH;>=Rs3cPv5|{PmXUEztqHcHNpWetHlmnzdDbW` zps?&1Y=!HhqV_Ky({ijXWdIhffdxdk8-u=3d!?*N-uxe7REG4^o4Aw=u7~{z?M*gc z=mm3Mi-eaV{_17?4-j2|Uql_UW9RLxOqJEUpN zR_=u@CAsliM;n|Qf zPl8-w(G?b@2(}e1x}rrF4S*Fbx}rrFEsYgJ_y0SFuDoDbrj4BDjR`ZOdYK{lqQQB? zUhN;2n(0^1^$rXO+Gb|HutI_{-#!+;HtcmYn)NzZIr$tNbsuetaonxgA2S%Ql`JAP z9UWWo9dDti(A8Kzv9&owT{?p|vN?dOy41H_^tyah{rFw_tmbCWjvg+&z?Rzhxp#Ni$2+Nkwb^&pgNr9=`biqW*>=@YDG)JQ;Chy{0wdEGD z_VxtAgp7h;^B7*J$E?fFI!t>)?!ttl&MwJGLL^3R)Wz=HE___M3HvirmPTX&7qsQE z*dXIR=qY31Jdkh$^^+51&Tu~uZdbsYty`7{PHgVUdQTI-u*sY)nu&UrY~z)e#w@PX zH|ToZ;Is#N+AlY9ej>#$GSa@Kc{5Gf!zii^xJGHSoBeb?w6zz%Ahk>XIlE+2F=c+t zB7;)%3ncXCWLEBwnYYtSoGkxJJeJSN9gW8}X<`uT1tquLZ_0SJD$$u8oQM-aT4@+WpYWNhmF4X(;*`3l>m z*DgNn{q)6RRA?sS2bxZ1&AQtmE?@fOCD&_t&k{>jxP@nH-k0hn#hs!z@6433*qL@s z7FywDBUE6{-4$6qB+lU>dxM^AgQ)VuE6)d2`I5ECjkN2o2T#@3vk(d^@I|Bf^C<`{SY)0P)zE$#3s$ij^cR3RBPZ@7k`@%qC|-CYQGUl#KrZ(bXsU$xa8 zwGL!@SaQ7esHq);gybe%+s3N!cu6V^>-+o+WAM+@yEzDij;TqKhLGl?LFwU$stMNA z6fSd}?e6=pRIqnxhksVoj6Ug8u@RO`MuG%$qGh)k5m!CpZ17p~8`dC9m)8n+nf!i4 zy)RS`=N&INw%4|BjyNcHuW`3!k=Uo?SP^G7@a?vVOE9TLgOr3dvm6~^FturPje6}Q z6Mu`jayzUuJ5L-uTJj_*j$^*ENU=0WrrdB{3KYCHBbO!C8D;*zA3p1d^WpDZUo`Gv^!ZkNj#(0&%;YN4=gAmpE2&2)l zqYty~*`z&sN23#SPnjHa!5q`()Ue+r%WDqwo7QJI=8S|UHY68NelG<v@4LF1Dv%$>~;Ux_<4h(k**FvgnNbE#f>bPJ4uwB|bb4?rvf1%IRjV z=6tpe*3feU-dz20qWdP0*!R}~k0}Bb!Z#8YRKKjhfEPVmD)HQ&4t+c08@A%*lA7*` z%po5J`AF|&3tY#odA4@?@@($jbTrG6>J>Wm?&y3$!2juMW`uJOeJd@EAKdMDMnL4i zf%Qb`xlgOP<|o358@4$x&>@G3$m_nI_G83P!&1-dBm_X;gQ#XMY4+a%5PKMN$xEJ0 zTZmlO`Nw$Ayk2Va={^~qCgw3;9l~3~tgDvRXK!0vMwwgg_|g7`acAo{rufN|YG(|! z;OLv~0k_E3NgWactkm&e%OaC0a;Mr7i!45nqwiD9CoG_TEap3F$uX2U{X=7UYWlb8 zASY68PuCs?E?_qhBiEXUT!amtP*e6_9`K@t)UP;d?19vIRNP-Q`$HjK87dB!mr*?g zT(LivIHs2fyZUQ`Az6^R8-r9*%alG1!QkHzcM)N_X;IC-b&Pq~l!TcefRDhZC`cCM z1GkGej*Dh;1L(%-E6gY>99}DUer@>tWPI+9PKHK*xHWD08b-Ps6LdgQb7xYQPoP7FtCw$L4cs@NBz*-Fq~Pv8tzKF6bYc= zUu7GA7hWXQYZ^$V-)8_huh+qy%Olil03+tCz15CUD6N!U{X@|p6tfuNHP|nFrXlWpIhVN z>&KCp>X}EBu_p9mNVM0YiW>;{H>9nBSZ?DZb~~EN^={}@fl+e^-(ynv*733;O5#xD zfwE%|9ArMNMfAyVW=(e6rVVbfCLIFOK$QgIb!}ondfr}t=-V2yYcfxKhvua+}+3JbM+2f@vxEKVJKB*a#+7_j(hXx)S3cAB31NSvlG`5%j@KC^%VV_$o0BZ>DYCF1$N)!?+u~x%M*=c|e zD*-_Y8puKDwWp6QL+D2V)krA6>AqAqoWOV2wks63uY2~Jjl@`U?cL{dwSxGPPsjku zhheF+btgWAN6((yv{@H7`4|3q`s+f=IgHc}MctW!)FhuKsa)digz=o*M_k`ZOT1#k zt%gpSg=vApt{6ek^TYSkLBabpS z?ek6OJ6FoMgHw!cG!(;fMrL-<@;fI+phhPaF%ga!*+~%(>(88Ui5zXeac0z7f1Xo^ z-PgI_nF|a5E1~WJNV{=R3h2ra@c2SFk%5SaIgST|6iAF+fRC7l@a4JwHLql06q6*+ z)VGAew`kG(UC}CDg~GQaLT5?1{O*sR6Iy*a?vCR!v`6Hs%&=nCNv*Q$j|O9;s*f5P zb^yc#lZq#9>Phs~3!8+UC04M}`MFN(eyGCbhK6C%2Ey_Ko#qrb9iEe`${JRRch;96 zJ!k2-dkhsPch39_DlRlchnjv(3b2A|P;I;8E--%tv=|HY#1tQ(kHj>sO`i z?7mS`soS_STt!;IflwUZzr_?!A{2PuXBwfPp02q$n*+d!n*Bt~ObN`lFJ^^r(x;sClHV34{`xT{KgXee4 z%=5I?MWY3bI=U(cx+kX-x(bEYY-1?+JNI?G^O3DnU!2~}5oLC?^?itXIJ9B6P}YS_ zO=-Kly|GNzm3$xiTRrqg4$g0hvdD_ccBmue6?i2MWF}cB-DSCx&*ONeV4Ua?#^Q9t zdOZGlNF4JSnTDy^$K{C$Q-c#MeWkg39EhE)ot+m|Lfv5b4gsrn3DdSgT1MaeEtnSWx4|oX$ckfwgvf^ zEn-dMIr^sf64q;m`D3;%%>KI5mn2B9S?<)ox*I$(l!@5bYzACG(rFJ#pQD=b4@SEb&BTeRQdcMha)0 z3d&q>j9bxdVTQvSH0O?3Bx~dOKz|Q~tF{5LA6MM6TsTn&ZbYQh^h^um-R60mg71_f zhuGW?DWFD~x0|2z)wp!#*|rASXU91hft|Lt&50=L$CB04q3vPZH=+ILPC$5tsr`tg z?jU`U&rmamo%G0NIfD7y`lu_0F49cwTne|9)TS8nMER|n1zI+IwiBb$?|EQRJ4-x~ z-=El#Z^$`#vo>L93OmtV#}~f_F{d*1AEUIkgiB6%x^Ado)vDDU$=RASQrt?A6zX$y zz^@i!$aL+Pcmnej`^LxG3uAM+2fm4}I zZKaeHkVys6@9Mi%15k}6x;Hg1*_SwQd8lZ#9GNN>zEc=aGJGa!^|nnD=W*y}pqFpM z>gzqmH!ify-g=haI-}p_M><(|v&$(Zj_+7zNZh3VBig}=mgZ=k2CZ~6Vz*#xCoYFy zvV~+gQwKqFQm!kT?`+Y0F^hs(5K~?xMo?-T+Tw89Nxxk7L{ugnK~*wqxbGc<8;5o zob`7B4E-5@5?}6IK*E5b%U)BbyjLMZ#?!v8Lz^c`qzgjqyXo`7q@UBy@6MgQFXB3K zQo?qWxpmS|^F>~kZcD1VTOVf%9oRp9z=20o4pgi{>>t*aR}{s29$+3>XR|zRLVt+n z&3ZRZrQ1h)qPL#eEq17&Cg-4x^8rQO`E1MY3Op*Q8Y#NQgMMbs;nc1!hrRg(Od?mk zczu;7eFu+={itI`Qb#z8;_d4bCgGA}Di&X<&h*^pl9`CrpEDaQ%d#W*xNhl;iViO5 zE6Gu+o4OY*?#GuqS^miNt4*IXH@Dbsj83s@!ThN1hvA)NRV2=qM85v%PgmLEDg~Ot zvU*Kgf6z5IG+*VNp|UJ=8r~qoL6pW0_f#a)i)W39=iiTazNR*QNeG3TVaUZ$R|y$4 z&j;qt>uz)Az`OPl!+6Z+Dt6wnpLUkh*p)T>uFFvAvwhfxlId|7=DyNwkty+{{pkJs zoptf9l04(zcAlJQ?|jXvGme)#JLq5B=D8|uF5cCMJ8M7Bct_Vw_|$`$Ly9BZx-+*6?3_Q> zmpU8N*$g|sGwI2+8@HGpdujJd4rMNMInk`u0^H|TvG|)BqhqZCEuptgkBb(!5mAlb z#x*=m=DUMJYWWkPGxPp|O%OS(1~V&GUCKM^B@iH+ptn9n7bpOE3d*(p!-3gSyAWSwO}rkHYZsppW?03Tl1zTa=CJ}5aGoyy-h zjc$>i>L2{1pFH57b%r#c-P&1+=~t3Wr6c${&pPYRw`3)^xvZ7}AMz%{8jzlg1a|Ql zNKZ!gPmd@BWdIr_u*2%{?;a5=ujOfXep*5Uoz#INfi$fZr4QrqPO;an?=kVG{<1b_ z^qpmI;M{=r2QMa9iJ5OkTW%J~@YI(_4Cv{j%<(Ex+fCED2YuLk_$}u7=GOEUCJ0?t zGU?h?YD_#zOq}p6jCbLeR9@|5RQLLu#XOtv+EMEygAC$ZKa|!NXL3cH_7Moi<;18PAAqQUqaNGgr zQQDNCYdscEC}CI#Qb29pTaaT03Rm>h5CS?c>P@Na*|QzWbxz9t+M2Kcm%IupHI}>- zzoXj4{BsvOCgYMqqB&^l24{XGv?@=S>gcBVdlgrxYLn1)GTK(=6Y;Y;E{C_ykJ051 zm%2)rx)OxF?&IgK+qt*^kqZi39u{Qy5}E6XQ7cwvpS30^30t_FPSFbr7vJEfV4P-c zAu$wyRjXSt8b23>=koVOQdzdiJsK4go}W2Cku#0sUf=1aK^kqx8yi!sl@ zR$krnNS~2wg;u>8F)-vq@le{n%%?6ytAZ1lX9HcXoxA-!dxl&E6m=(>M68?C^of(L zWu){9kY~v)g9Y=-oAB>;WPa*zZNCt0;beP#{!lM)?p<{AsQA>5cZpXgjyhlKoF4Lg zZ6Q2wRFm8@`|V=IU=em^&2<%2+$z`i^V7KYQ5Zi|)y9uTfh5jYvboONg&LOtqnHRsn`^ zF?M1nhXl%);G@o-3`(&x7#`&BRSgjX-)_FbI=?Qp>GI}I!>D|cc#diPlYZw49x)Gj zqr8EHf4RH zah*%NYMc65{!W3+Xnbo_ZCw9`3@6O=%Y+U-*Bqk{Lj44#`l;_8%xd_9FevU&&!{L7?Bkw@{ec6-7{;f+lm%cMt4k}Mls|AXOBGO=o3 zZNYp&!x?LGxn@XGrvK*Tg^*fht~BYbw`xt2+O&g?N_vA-8z@!cR=REFT5cLNWGb&- z(g4H@1T)O&&R5vr+Gs56hJ^GphVoW&H~;(w^!uGpG;W9O6VwhBZtqqV1_5=dE+*9Iz70(3_4G8`55 zcC8k*S6p_OPQI9DSJs&ux7>cY$qeEj#lguF7%3ZGtw_7M}kE!dAHnhRn+h_bSGZU7xqx0)JyE>IIJPaX1Irgas@L#T9>3BN^aTT4- zyK~19-xjB098cC+mNzBMCAYlC&8a<;a@-@SJpXrV`hd)Qd#AivPrCW4w6R-G5rR@B zu6(&O0atBtxURd!177&KvvtJ00x>^BzvlH70d1YWxZ_F-Zq5KH=n#G%TZM^Occm{% zi^);tT?V11UCMYqY_cKRKY*a)PtDF5OTy+>o8`w9NC}%}b#oX0KsS)izUyr%ws2NU z>I0bWJ38@gzS%T{c6+?j)WQ6I!I_Y*iipnUXp+^a#N5^)(cDV8M)>**>LXZ3ZOz6E~byJwLmmYbp-*g$*yYJNcJ0Te9=V6?3(%QlC z$pPZQl8%H4vcX3loI-mDjh0Zdff=s~S@d4qK zu+87%;5Nk>?0kP%Vdl-g691la=S%zT9Y5hdA8WJ7IDq$wCuI{a6vd%}#2V-Bw5T*E zs`DO@bMkr~;_?VfjYI4eoBSx>nSuE}h5SSn?dM@3X-$4!*1DDseU3E~4LhmrPav+3 z{;k^jhs|)QCwq_-#Q8qLNCys+4)4r(u%Vngh$58OA02#@lC>e1Sn)vLEOWnopNDk92WT=h&Dvwk&@8>wzygum*ZF+6;gE$uV^4Fz!Xh)x(sqXI_F zo@iB;*#UtOv~80)5bp=bhx9sP=h`p6dsFw=FDrVkDvwA*U@!CY_OcG2(9mxS8%iM@ zpU6j(T5Vg)oLbVlzuyqZ>+mTh6>Hp4vd`3YNoyx|yUw1Fcyvo0H&;vATtyt6a+%JF zdSCuHak&2}N=xX90vJ0tK?8orhCQA*2j;b39XBNmHg*y|@_C#8>LwfBefam{EC58( zS6zzWUzL_c9Wpq3MSoO^d(-LNV(rnI_^UK=hpPPJg6;bK=NpgY>RC9A?5((uZ+0kW zywt;Y#$xL3JQqam(hH4E`;v5ex-L|f#@NjaOs4BH_gPrq?TjoGcD_~L)yZ5LK&Vhr z_4SS~QW3>L^a*SnlHRlFPLAL%+Uy;u+H9(j!5c#fqvg5WH7(P24>@-ZegEcDdlhq3 zYB)eeC?`C$2VA#c1`Vmv9Bq80z{}e7JPM<=bel}=7orZ5v!$*aYjfJ(b*9Mo z?-Ur&zWIrdfB!}gFdE>_7o@$2p)H2oe0s%_Vq0*4vJ~2*{Evu?I!;ij1$OJtnza%DY~K2h1X&vZ<}@Q`^3UGggZ4#@x-CnhvnhwZ)GeeA<>- z%J53A&YW1+Dq>BT?jH?|&9U;c;6>TfmX(($kQ(t`<6TYrnbb-eEtQ^;NVq4Zqsn0~ z+P(_K=Z+8f=XDGusbIo0raoJa^jbi$`BU?hP4)Zx?Md0KRW{G2OEf-=iMD9f=f~D* zqT(K??T6MsT8M*-6R2+9+6r7?RaLl$;^;2qPCh`gljO2a^7In;U%wEL0A;7i%aDGuL5p(Y^h@S#pT-l?VSZ;4jWM%1C;vr1iNss_{ov zg(BRM@f>zhx~J*-GvEoY^o&k&EwvW}h|MMXjMaZ`WWhX?od--$8){ecF~I#X2Sj;- zP+G;hs7h$!2!Vyf*}CZWL5dZP`o7qdm<3;VC|q@0zR)LlAHPn7^ct}9SiW#4A}whF zAw9?3^2#80#_W7wDS-oEovmuaLJ>e1_&8TZTz&_t-X>({iEji#{85cxvyumqo-0g* zlo!e!NiqHPCNOcWBAjL+cDP`OJcaa$07EnkHTiv01fbqfjbo@1NmV)6jeOvz2neBr zYtX1SCnbecsh?#uct-w>ED}K{8$Z7-qACyHZ^WzGLY(8xjkTp$VQgwW#>Q+eJd*RQZ=Rl9tCxruiV=Ez|c#S`HQWCgAl{8a2U~MDIc7u%=^Q1#LQuyiY! z-p&S;^?AN@qL9rTz^5-d)o?l6<|8?EZ#C`uP)IU$M%rRvBh%Ca`$1Z-n1oBRu-;+-{ zNH8o(_F#T>Pf+gU7?alKc0@X24$6yfS-AWcnBbu7ZW5FY@gp2L zwSF`2vV~NwV4(*bF)aBIlU)7o43cax@3`g&Ln4veZcga_wW0ragSh*5b9w1}n@+<@ z`fq!`cJ4&s4hUms0KBz)*ZsE1^6rNJdG^NO?P|zDa&F3|ql7h)w2=IV9!ev`PxhBs z$ml`M!`h4IfiRyK?B4^zOaSnB$yLP<^qtU?%(X_DUJE6>$RP-XF*hS~d%%IK5pLkkIa9C$I9CPh{d7SxvR((?bJ*|?evRGm z45E8$vxZgjXJ==7&HuKyq1v%yMQV>QvpOH#ee?4Lc8VO? zQsP=nm*4HB#J1(>3m{3oAHW43@$TzR^1?fhJzhfzDs>~$hhX{Bq@?)Ndv*~H#zfY= z9J=UPis`&(L_gYnhO}QM8Jq4_im92U5pkx1)D9?{C!cAkklMLGPsDzOH}S58PZGUX zpDu%#YzoaamgbG-E?oxI+<%vkYw00Qn)lF=%&rMR>wLueaIx&!@YSZ;ke%X`5##pErzTJFB@^Jn-HdcHf| z6_g=9B1t9W`LE5Ht8Rr1$KJB@{`%~7q$CykOqXHtyABT!4eVY4po;9Vd5h?>us3FL z3kT7Y05b4;<$H~i#a&|aizf(#OdSqzo8z{4?0+D-VN;8>d7W)xQcXW8HhmaK+eCl5 zH$J|Tefso15v%uMAJT;#PkcNmVgvAciv=~(Fr6{;2yo9Vw@r!ctydwn5vMC)R*61)gCNUw3>E@NUHvYI#L3kMrUO-6I)dLoN^ixFMtxH%Qp*$ zA2E5G*K-r+NG8tHHedQo9pRnIAR2959BUDad$*?;x!p|UBqAm+DqdJFg7~*V{Xli= z7rJJCYK{BGV|J`YlMvtL;iw6-c7Y3z+cK39KKNWaD|6gZ$__g+Q1Js_UpnD~1F+d1(QEo8DaYCKHD&VuodU0WJMb^|BV!6#;~`^D~*Z)~1Mu2BaxW z*H@ZNxL!2Sa6#XD8b@tU7#C*db6#t^t+M3Nn2fG6 zggmt6ln|bSrl>qf4y^nAijyZCun zxGG89>Ij)3X|eSC$E?Xm17__Ti1klq}XGn_6WBa{2%UE2djZ*T8A%ravwA3pO;4PRGxMnAYQ zP1nM3MtU;Ua_rR*v%b0c>uV(i+eMDBr#5%#>qCyA@Qp=c)i;Ty{$zb#HKZ8hhOIZH zw{uC0E(h=sO{bxXN2gH>XOqnDsLxs)CNVwMIn&!#K+HXHtT+0Ah_iIeg1xX;jhJeO93j`pG*ahW5Wy>J* z+X6^+CYx-5@3NnpnEh>avR{3&6LBeppSP2eg8tDE8O=B(=FK>E8O>+%v=(R zE5h@S3Svd~{i1+=p_~=nw_>`im@bQs*NX655uPiK!;+?0u{>8S&lR_Q(Lh*n+n1cL zUmjN6_7%5%C1|$jey_wl7a_6CkX#X-zsSr*jj|H+T!O^1hZW0n#qwOSJQtZ_MR=|V z&y{rTqD8S1vR;%6zr^uZvh9oHvD_V75}vm7dBvrro)@C8<$wETQ-`y_yrg95A0s(? z9ohWlb)lLfTA-9sa|=uS6WMGk%Y%iCmR%R`w_GCc(0x6W66`9oSzfdB%l_{v z6Q=tt==*B0Og?M`YQjr_h*cA3^y*ey_WWgOZ4KXDGgR?RDwWQZNeHJjAH0RS?8e~o zjQ#&&(*ya*dQ!vHS6(}3Yv}WQe4{YOQmC{%zh2p+wk`%nB?wKe>9vu7+nb+s@cNJQ z&QW%I9STKX3WmA=M~%eBjnBfum}Vwgp2EZmzIh+7#Kgq6hNPa#TOlXEzthul!PiX9 zDrO9rR=ODMn%Utr(IjyB;a5M|6lyOiRFH9LNy+Z{$W>|26X13Vk^TG6VYhE$CZ8e! z87sqPWu&;APJS{I>x;aqszS@jA1_R4(h?J^X$Tp2zpMLhUl?dI&C7ZMC1mhSCD$N> zQ0o$Bm0rGE24jIQ5 zEG*oK5)Xe%+KtlqtcnSyuiAwY7Z=B1Q9&dX{n|Kf(bsWC3&kk^T15Qoq6!4Udk?vq zj*F>OjosZ}-blL})1(E)W^dbgPZAZxzEgJ`>ntNvTUSzCY=Dss3kgvo6^L-!4K_Z% zMQc09s$Pjy8v=TNpW*W-o3u)e&|cVMStN+t*@{ctHz{`H$d-!v3SI7NMYzJ}?OHa*~EAINxtfM%^r!rdlq7#1z6?B(Sakbxzne&4$7rypV%>cPF z%{>JfSR-(Gf>O5o3pFo6GUj&PixYSA3;i$1O=5?qD%DX!ENLBh&(_qMDWXI2hZ_Ti z8acug#Hm8>ZpM18X@;r`(H@qg`ZGkVe%ogsXI3ITIRO+=3w)H#ZY#5x5BewJyT73i zmDta=mPh)@otD$@)LK9$_`isp={aEGkSEx}n5PzL{&~Nw`?J-Sd(k<)=oy!s5%i3m zOU~%Qn7$gHyJq-QPusp4X4R!R1qa}rf113x?6c>G1Dr`VHizb@ zd#{h&uN=#@8FTm{*kVUZ2-NH`dp#;Jo}%Tkk@g8AY4S8zt7Inl~BmN;A$5JWUpHfzU644lFqJ zAJF{*1&=Mh+)`VVLnWSHz4ZBA9)9xXm5{jER1s8d&P)9H8Nu5%##UJC4&qysMu}3q zDEmD=RM7FA45>7^B`Oh79Gqq&qXT#wnzTdkgY{Y&+Z!`%`|<+?L;OV=KhxOTpC$AC zMWpSD+*k7_xZ#MGM|xX+B1^%E-oyjoqU<8oD>{YU%4 z+MslJ%<_;8RX&fKfB8MHeGo*U%n+>F`1K^xKb0$fxrQ!!#puL9iUCM29taXn+ zx(i=WULQ60Rel1C!kyngFtc4maaG#Jw<+Jzc2&chs}9iz(Go_?+N@)&M+~KkZViZl0iq%bWbhGL-*b4iU~Z@Qvc=)jGS4w%WPVRRRJaB zS(tCe^thq4Yo_pnxsl&Bl#ryna#U9(0i6TAbRl6%rJ~Z0k4WO_3q_@af@K6{f_Uul z*I2Y|wmAp#{F5{P@r`U2RdlX&g8`LK=@Yy3N7(+rI6}>&uy%)_cBJ5xe<&xXf`^e2 zL9oe!rezC^{x_WOZ!I==Z&>q|Scb-=(N*_CIv+qSQJmoM2OD)><|4NOQ|21y`&HdW#;X z+y_e^RO1J;SK3?d6*&qrW~R_J1U8g%-<(!)3L%afKo=s^ZYFh7SdCAXUb<`Jo3s70dfKZ-PeQlR^3OF`T2 zHASMgq3QPYq+pAf`W*$0yM0+To85{uoQrTiGq~^o+WsS$pwzRN@^PEC?+@l-g5Wd7 zyDs!=&Y*+>jPF#u%dS*N?l^`s?;0_2pcj%dcixh6a8nS`;!f)pr!ZGM2Ji4`-9Iq( za}$gIm1oIhL;H>WZrPSvbk}GE8sHS)fjL^d+6r5yDUemeh_jUpnocdTHx{8EPL+$mR@_CV zu4NU_i!c9eJAbXe?pYtmIsSowZV;0Ry4x~B&=jB!N-;L6bqOU}NIAWpOFly~vGB*B zi`7x)qK?yX&+{ySRN_ug?qkU^FjVHN9kFJ@`H1<5XystcM^&}1$jsvzabY=TF7L@z zzrV26FG_rI8xhy2%<7Y`c48wmzdoXC(VXc?FR$!Mg6otW@Ut#hR`iUq`M^xr`?N9h zsrYJfnWPLXsG4o6Y~F@Q&to|ZA3B~6-VfsZ;i7i*YW zQorL5?_F*nZPIZsNkGpW9Th>l#LY>tq($nd&)#y0W78^%xAkh&tgV#9HMEx&;=%?- zk~(m3Vd}t`)(Q7XD$Bj6Cn7Gx95?g;`(6A9FS<>7YW#C*dAzfXWK#EI>;`PAk-~rI z+$FrYXE3z4rhLwlJYj`3@fn3PnS~7DaF?gGYF?Rjq?PDtwbf6xM0!{`JPa_WA*l05 zIqOn;37~?Wq|jA1OrUE=BDA<~6~$du;vC?>Sd5#cC`|-Tr-Wzzue8A4y(V2SR1Uh) zb8B1%6=$&3_#+;s*wG$s$@q@a!nkDaG8acRnB5cmz0v_o{TED0TI=A34UwBkA@ZV~ za*3lwLx$P!3Jx`-$Rr+YPn~phLeKn%?p^w?eEtLplbX%GCfXLdtAwhrvn1HI=nDO) zall@%Pd5n@e8|I)$A+tT=SO7)q!&?w(p0YDzFwqhP`t$qA1|2OlHn>)S2L9bR&yZ} zAc#Z9_CE~ZC1PNWl{L&WYu%^mj+_1Zoq%?Xtd4V#H0hS?}#f1szC^A4`2?(3;YbZ;p*<_-t>auZ3E#G+?KlEYSh(PQvZBq*IKj0$k$LlzZVCv3ExsV*DkDP7_uLIY< z5M28{r6HBB>0Rd4AP^n;DV_xnF}21aAjjiH!vly_;fq|goVpiR@$7>JwhAjPqK3+&$VH++8ZhrZj{jQd^m`W#7Vt5fP>n<^!KJh1zSnL*~rv zdYW^Rlwx(0(C>D9sfp9a{NKM%RoC6~Sc>O*CJ&3AaZmS@lD1F!xRSmwl}m$~hD8U? zs3TS8rNK-ex!e>S0@p^^GJO#|S}QeK<4{^7ZxPlt`aL|2u{xKne}&Ayswyi`V5(NMO; zj~b~>K{(uhnB+@<++^o|5K9Uyb+szPp4}ugAob-3nmC(1Gq`J)e{Ff{0jfkg!uc;9 zAB=ta9?tjJ_EipO^^xjzrVT&kesWJ|YdKE$?}p+}k+6z!U-k$5bJ~pP79!F_ju)2uixZ_(&BG0i^SRWnAaB3HnY<;u+#N7V9fpp%*g81(L9Jp18P$WnmTz* zmca7ML&QDV2@%c5;ivHXaEw}xeuM9Ek)=2p``0-GW{ye1s9Mf5_C1Mt3Dku1fss_^ zOobU(&VCCo6pDJA+{t4XX={5i1A_e(R#_5nQ|;cT`O-=LviQFecub6JIU8Z5RhZ`i zn&cmib$aW3Q`l8~eZeS06}wkURWE@x1H9_h8*=ami^4Xz33Nr(Zedzuf)X-B8?g!vXhf-yj-vku>RUxs#PpOQF`y@D zc8|KF8p=HKuZD?JNK|F-UnZWZQ|F^%2XuR199iDQyd>9ud068v+jb!)_VPBNQ+rx+ z?O{4?c9KmrH)GW{RFF0H(^qq>rPlP*8>=#J-AeuCXG`*O`9mVZAu98p7$=_doU?B% z2CAX4wl+a`v-=Ui#F)CC(k1Kx;TP`9fAy!8lNsLHRi~w*$1OEbad)4YS4+BQ&=78< zr(9wd%Ebx(N&){yCGG)C7Y|MbmO|(xC;|IaR(rMOz^MfOKQ+DmL14O*7%obV)eJ%h za-4oH;xLCrUiWOR7?t*Kc-=;BYy0nmKy&Xfbu)X(1(f-Vdu{UI-h`#E344*2oUPaR zygGS_I{y8k>T<+7n=QZD}dS1u**d62A From c212f4bd948e7e614e7d70601e9942c21c0e6af9 Mon Sep 17 00:00:00 2001 From: siddharth2411 <43139012+siddharth2411@users.noreply.github.com> Date: Thu, 18 Jul 2024 03:28:17 +0530 Subject: [PATCH 1174/1195] [docs] Logical replication explore docs sections (#23231) * logical replication skeleton * move docs in subsection * Fix sections * Fix subsection * Fix file name * Fix name * fix links * Fix links * review comments * page titles * minor edits --------- Co-authored-by: Dwight Hodge --- .../cdc-logical-replication.md | 28 +++---- .../docdb-replication/change-data-capture.md | 44 ++++++----- .../explore/change-data-capture/_index.md | 73 +++++++------------ .../change-data-capture/cdc-overview.md | 59 --------------- .../using-logical-replication/_index.md | 17 +++++ .../advanced-configuration.md | 12 +++ .../advanced-topic.md | 12 +++ .../cdc-best-practices.md | 12 +++ .../using-logical-replication/get-started.md | 13 ++++ .../using-logical-replication/monitor.md | 12 +++ .../using-logical-replication/overview.md | 12 +++ .../yugabtyedb-connector.md | 12 +++ .../_index.md | 70 ++++++++++++++++++ .../cdc-get-started.md | 30 ++++---- .../cdc-monitor.md | 4 +- .../cdc-overview.md | 13 ++++ .../debezium-connector-yugabytedb.md | 37 +++++----- .../preview/integrations/cdc/debezium.md | 2 +- .../tutorials/azure/azure-event-hubs.md | 4 +- .../cdc-tutorials/cdc-azure-event-hub.md | 2 +- .../cdc-tutorials/cdc-confluent-cloud.md | 2 +- 21 files changed, 293 insertions(+), 177 deletions(-) delete mode 100644 docs/content/preview/explore/change-data-capture/cdc-overview.md create mode 100644 docs/content/preview/explore/change-data-capture/using-logical-replication/_index.md create mode 100644 docs/content/preview/explore/change-data-capture/using-logical-replication/advanced-configuration.md create mode 100644 docs/content/preview/explore/change-data-capture/using-logical-replication/advanced-topic.md create mode 100644 docs/content/preview/explore/change-data-capture/using-logical-replication/cdc-best-practices.md create mode 100644 docs/content/preview/explore/change-data-capture/using-logical-replication/get-started.md create mode 100644 docs/content/preview/explore/change-data-capture/using-logical-replication/monitor.md create mode 100644 docs/content/preview/explore/change-data-capture/using-logical-replication/overview.md create mode 100644 docs/content/preview/explore/change-data-capture/using-logical-replication/yugabtyedb-connector.md create mode 100644 docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/_index.md rename docs/content/preview/explore/change-data-capture/{ => using-yugabytedb-grpc-replication}/cdc-get-started.md (79%) rename docs/content/preview/explore/change-data-capture/{ => using-yugabytedb-grpc-replication}/cdc-monitor.md (98%) create mode 100644 docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/cdc-overview.md rename docs/content/preview/explore/change-data-capture/{ => using-yugabytedb-grpc-replication}/debezium-connector-yugabytedb.md (97%) diff --git a/docs/content/preview/architecture/docdb-replication/cdc-logical-replication.md b/docs/content/preview/architecture/docdb-replication/cdc-logical-replication.md index a2d5c5edb4cc..a70e352c2d9f 100644 --- a/docs/content/preview/architecture/docdb-replication/cdc-logical-replication.md +++ b/docs/content/preview/architecture/docdb-replication/cdc-logical-replication.md @@ -1,7 +1,7 @@ --- -title: Logical replication in YugabyteDB -headerTitle: CDC - logical replication -linkTitle: CDC - logical replication +title: Change data capture using Logical Replication in YugabyteDB +headerTitle: CDC using Logical Replication +linkTitle: CDC using Logical Replication description: Learn how YugabyteDB supports asynchronous replication of data changes (inserts, updates, and deletes) to external databases or applications. headContent: Asynchronous replication of data changes (inserts, updates, and deletes) to external databases or applications badges: ea @@ -15,7 +15,7 @@ type: docs Change data capture (CDC) in YugabyteDB provides technology to ensure that any changes in data due to operations such as inserts, updates, and deletions are identified, captured, and made available for consumption by applications and other tools. -CDC in YugabyteDB is based on the Postgres Logical Replication model. The fundamental concept here is that of the Replication Slot. A Replication Slot represents a stream of changes that can be replayed to the client in the order they were made on the origin server in a manner that preserves transactional consistency. This is the basis for the support for Transactional CDC in YugabyteDB. Where the strict requirements of Transactional CDC are not present, multiple replication slots can be used to stream changes from unrelated tables in parallel. +CDC in YugabyteDB is based on the PostgreSQL Logical Replication model. The fundamental concept here is that of the Replication Slot. A Replication Slot represents a stream of changes that can be replayed to the client in the order they were made on the origin server in a manner that preserves transactional consistency. This is the basis for the support for Transactional CDC in YugabyteDB. Where the strict requirements of Transactional CDC are not present, multiple replication slots can be used to stream changes from unrelated tables in parallel. ## Architecture @@ -31,19 +31,19 @@ The following are the main components of the Yugabyte CDC solution - ### Data Flow -Logical replication starts by copying a snapshot of the data on the publisher database. Once that is done, changes on the publisher are streamed to the server as they occur in near real time. +Logical replication starts by copying a snapshot of the data on the publisher database. After that is done, changes on the publisher are streamed to the server as they occur in near real time. -To setup Logical Replication, an application will first have to create a replication slot. When a replication slot is created a “boundary” is established between the snapshot data and the streaming changes. This “boundary” or “consistent_point” is a consistent state of the source database. It corresponds to a commit time (HybridTime value). Data from transactions with commit time <= commit time corresponding to the consistent_point are consumed as part of the initial snapshot. Changes from transactions with commit time > commit time of the consistent_point are consumed in the streaming phase in transaction commit time order. +To setup Logical Replication, an application will first have to create a replication slot. When a replication slot is created, a boundary is established between the snapshot data and the streaming changes. This boundary or `consistent_point` is a consistent state of the source database. It corresponds to a commit time (HybridTime value). Data from transactions with commit time <= commit time corresponding to the `consistent_point` are consumed as part of the initial snapshot. Changes from transactions with commit time greater than the commit time of the `consistent_point` are consumed in the streaming phase in transaction commit time order. #### Initial Snapshot -The initial snapshot data for each table is consumed by executing a corresponding snapshot query (SELECT statement) on that table. This snapshot query should be executed as of the database state corresponding to the consistent_point. This database state is represented by a value of HybridTime.  +The initial snapshot data for each table is consumed by executing a corresponding snapshot query (SELECT statement) on that table. This snapshot query should be executed as of the database state corresponding to the `consistent_point`. This database state is represented by a value of HybridTime. -First, a `SET LOCAL yb_read_time TO ‘ ht’` command should be executed on the connection (session). The SELECT statement corresponding to the snapshot query should then be executed as part of the same transaction. +First, a `SET LOCAL yb_read_time TO ' ht'` command should be executed on the connection (session). The SELECT statement corresponding to the snapshot query should then be executed as part of the same transaction. The HybridTime value to use in the `SET LOCAL yb_read_time `command is the value of the `snapshot_name` field that is returned by the `CREATE_REPLICATION_SLOT` command. Alternatively, it can be obtained by querying the `pg_replication_slots` view. -During Snapshot consumption, the snapshot data from all tables will be from the same consistent state (consistent_point). At the end of Snapshot consumption, the state of the target system is at/based on the consistent_point. History of the tables as of the consistent_point is retained on the source until the snapshot is consumed. +During Snapshot consumption, the snapshot data from all tables will be from the same consistent state (`consistent_point`). At the end of Snapshot consumption, the state of the target system is at/based on the `consistent_point`. History of the tables as of the `consistent_point` is retained on the source until the snapshot is consumed. #### Streaming Data Flow @@ -51,17 +51,17 @@ YugabyteDB automatically splits user tables into multiple shards (also called ta Each tablet has its own WAL. WAL is NOT in-memory, but it is disk persisted. Each WAL preserves the information on the changes involved in the transactions (or changes) for that tablet as well as additional metadata related to the transactions. -**Step 1 - Data flow from the tablets’ WAL to the VWAL** +**Step 1 - Data flow from the tablet WAL to the VWAL** ![CDCService-VWAL](/images/architecture/cdc_service_vwal_interaction.png) -Each tablet sends changes in transaction commit time order. Further, within a transaction, the changes are in the order in which the operations were performed in the transaction. +Each tablet sends changes in transaction commit time order. Further, in a transaction, the changes are in the order in which the operations were performed in the transaction. **Step 2 - Sorting in the VWAL and sending transactions to the Walsender** ![VWAL-Walsender](/images/architecture/vwal_walsender_interaction.png) -VWAL collects changes across multiple tablets, assembles the transactions, assigns LSN to each change and transaction boundary (BEGIN, COMMIT) record and sends the changes to the Walsender in transaction commit time order. +VWAL collects changes across multiple tablets, assembles the transactions, assigns LSN to each change and transaction boundary (BEGIN, COMMIT) record, and sends the changes to the Walsender in transaction commit time order. **Step 3 - Walsender to client** @@ -69,12 +69,12 @@ Walsender sends changes to the output plugin, which filters them according to th {{< note title="Note" >}} -Please refer to [Replication Protocol](../../../explore/logical-replication/#Streaming-Protocol) section for more details. +Refer to [Replication Protocol](../../../explore/logical-replication/#Streaming-Protocol) for more details. {{< /note >}} {{< tip title="Explore" >}} -See [Getting Started with Logical Replication](../../../explore/logical-replication/getting-started) in Explore to setup Logical Replication in YugabyteDB. +See [Getting Started with Logical Replication](../../../explore/logical-replication/getting-started) to set up Logical Replication in YugabyteDB. {{< /tip >}} diff --git a/docs/content/preview/architecture/docdb-replication/change-data-capture.md b/docs/content/preview/architecture/docdb-replication/change-data-capture.md index aec5a8ce5b09..bf7d65d29285 100644 --- a/docs/content/preview/architecture/docdb-replication/change-data-capture.md +++ b/docs/content/preview/architecture/docdb-replication/change-data-capture.md @@ -1,7 +1,7 @@ --- -title: Change data capture (CDC) in YugabyteDB -headerTitle: CDC - gRPC replication -linkTitle: CDC - gRPC replication +title: Change data capture (CDC) gRPC Replication in YugabyteDB +headerTitle: CDC using gRPC Replication +linkTitle: CDC using gRPC Replication description: Learn how YugabyteDB supports asynchronous replication of data changes (inserts, updates, and deletes) to external databases or applications. badges: ea aliases: @@ -14,34 +14,42 @@ menu: type: docs --- -Change data capture (CDC) in YugabyteDB provides technology to ensure that any changes in data due to operations such as inserts, updates, and deletions are identified, captured, and automatically applied to another data repository instance, or made available for consumption by applications and other tools. CDC provides the following guarantees. - -- [Ordering is maintained per-tablet](#per-tablet-ordered-delivery) -- [At-least once delivery](#at-least-once-delivery) -- [No gaps](#no-gaps-in-change-stream) - ## Architecture ![Stateless CDC Service](/images/architecture/stateless_cdc_service.png) Every YB-TServer has a `CDC service` that is stateless. The main APIs provided by the CDC service are the following: -* `createCDCSDKStream` API for creating the stream on the database. -* `getChangesCDCSDK` API that can be used by the client to get the latest set of changes. +- `createCDCSDKStream` API for creating the stream on the database. +- `getChangesCDCSDK` API that can be used by the client to get the latest set of changes. ## CDC streams Creating a new CDC stream returns a stream UUID. This is facilitated via the [yb-admin](../../../admin/yb-admin/#change-data-capture-cdc-commands) tool. -## Debezium +YugabyteDB automatically splits user tables into multiple shards (also called tablets) using either a hash- or range-based strategy. The primary key for each row in the table uniquely identifies the location of the tablet in the row. + +Each tablet has its own WAL file. WAL is NOT in-memory, but it is disk persisted. Each WAL preserves the order in which transactions (or changes) happened. Hybrid TS, Operation ID, and additional metadata about the transaction is also preserved. + +![How does CDC work](/images/explore/cdc-overview-work2.png) + +YugabyteDB normally purges WAL segments after some period of time. This means that the connector does not have the complete history of all changes that have been made to the database. Therefore, when the connector first connects to a particular YugabyteDB database, it starts by performing a consistent snapshot of each of the database schemas. + +The Debezium YugabyteDB connector captures row-level changes in the schemas of a YugabyteDB database. The first time it connects to a YugabyteDB cluster, the connector takes a consistent snapshot of all schemas. After that snapshot is complete, the connector continuously captures row-level changes that insert, update, and delete database content, and that were committed to a YugabyteDB database. + +![How does CDC work](/images/explore/cdc-overview-work.png) + +The connector produces a change event for every row-level insert, update, and delete operation that was captured, and sends change event records for each table in a separate Kafka topic. Client applications read the Kafka topics that correspond to the database tables of interest, and can react to every row-level event they receive from those topics. For each table, the default behavior is that the connector streams all generated events to a separate Kafka topic for that table. Applications and services consume data change event records from that topic. + +The core primitive of CDC is the _stream_. Streams can be enabled and disabled on databases. Every change to a watched database table is emitted as a record in a configurable format to a configurable sink. Streams scale to any YugabyteDB cluster independent of its size and are designed to impact production traffic as little as possible. -To consume the events generated by CDC, Debezium is used as the connector. Debezium is an open-source distributed platform that needs to be pointed at the database using the stream ID. For information on how to set up Debezium for YugabyteDB CDC, see [Debezium integration](../../../integrations/cdc/debezium/). +![How does CDC work](/images/explore/cdc-overview-work3.png) -## Pushing changes to external systems +## CDC guarantees -Using the Debezium connector for YugabyteDB, changes are pushed from YugabyteDB to a Kafka topic, which can then be used by any end-user application for the processing and analysis of the records. +CDC in YugabyteDB provides technology to ensure that any changes in data due to operations (such as inserts, updates, and deletions) are identified, captured, and automatically applied to another data repository instance, or made available for consumption by applications and other tools. CDC provides the following guarantees. -## Per-tablet ordered delivery +### Per-tablet ordered delivery All data changes for one row or multiple rows in the same tablet are received in the order in which they occur. Due to the distributed nature of the problem, however, there is no guarantee for the order across tablets. @@ -53,13 +61,13 @@ Consider the following scenario: In this case, it is possible for CDC to push the later update corresponding to `row #2` change to Kafka before pushing the earlier update, corresponding to `row #1`. -## At-least-once delivery +### At-least-once delivery Updates for rows are pushed at least once. With the at-least-once delivery, you never lose a message, however the message might be delivered to a CDC consumer more than once. This can happen in case of a tablet leader change, where the old leader already pushed changes to Kafka, but the latest pushed `op id` was not updated in the CDC metadata. For example, a CDC client has received changes for a row at times `t1` and `t3`. It is possible for the client to receive those updates again. -## No gaps in change stream +### No gaps in change stream When you have received a change for a row for timestamp `t`, you do not receive a previously unseen change for that row from an earlier timestamp. This guarantees that receiving any change implies that all earlier changes have been received for a row. diff --git a/docs/content/preview/explore/change-data-capture/_index.md b/docs/content/preview/explore/change-data-capture/_index.md index 53788190cbb2..c61080eced61 100644 --- a/docs/content/preview/explore/change-data-capture/_index.md +++ b/docs/content/preview/explore/change-data-capture/_index.md @@ -14,69 +14,46 @@ menu: weight: 280 type: indexpage --- -In databases, change data capture (CDC) is a set of software design patterns used to determine and track the data that has changed so that action can be taken using the changed data. CDC is beneficial in a number of scenarios. Let us look at few of them. +In databases, change data capture (CDC) is a set of software design patterns used to determine and track the data that has changed so that action can be taken using the changed data. CDC is beneficial in a number of scenarios: -- **Microservice-oriented architectures** : Some microservices require a stream of changes to the data, and using CDC in YugabyteDB can provide consumable data changes to CDC subscribers. +- **Microservice-oriented architectures**: Some microservices require a stream of changes to the data, and using CDC in YugabyteDB can provide consumable data changes to CDC subscribers. -- **Asynchronous replication to remote systems** : Remote systems may subscribe to a stream of data changes and then transform and consume the changes. Maintaining separate database instances for transactional and reporting purposes can be used to manage workload performance. +- **Asynchronous replication to remote systems**: Remote systems may subscribe to a stream of data changes and then transform and consume the changes. Maintaining separate database instances for transactional and reporting purposes can be used to manage workload performance. -- **Multiple data center strategies** : Maintaining multiple data centers enables enterprises to provide high availability (HA). +- **Multiple data center strategies**: Maintaining multiple data centers enables enterprises to provide high availability (HA). -- **Compliance and auditing** : Auditing and compliance requirements can require you to use CDC to maintain records of data changes. +- **Compliance and auditing**: Auditing and compliance requirements can require you to use CDC to maintain records of data changes. -{{}} +YugabyteDB supports the following methods for reading change events. - {{}} +## PostgreSQL Logical Replication Protocol (Recommended) - {{}} +This method uses the PostgreSQL replication protocol, ensuring compatibility with PostgreSQL CDC systems. Logical replication operates through a publish-subscribe model. It replicates data objects and their changes based on the replication identity. -{{}} +It works as follows: -## How does CDC work +1. Create Publications in the YugabyteDB cluster similar to PostgreSQL. +1. Deploy the YugabyteDB Connector in your preferred Kafka Connect environment. +1. The connector uses replication slots to capture change events and publishes them directly to a Kafka topic. -YugabyteDB CDC captures changes made to data in the database and streams those changes to external processes, applications, or other databases. CDC allows you to track and propagate changes in a YugabyteDB database to downstream consumers based on its Write-Ahead Log (WAL). YugabyteDB CDC uses Debezium to capture row-level changes resulting from INSERT, UPDATE, and DELETE operations in the upstream database, and publishes them as events to Kafka using Kafka Connect-compatible connectors. +This is the recommended approach for most CDC applications due to its compatibility with PostgreSQL. -![What is CDC](/images/explore/cdc-overview-what.png) - -{{}} -To know more about the internals of CDC, see [Overview](./cdc-overview). + -## Debezium connector - -To capture and stream your changes in YugabyteDB to an external system, you need a connector that can read the changes in YugabyteDB and stream it out. For this, you can use the Debezium connector. Debezium is deployed as a set of Kafka Connect-compatible connectors, so you first need to define a YugabyteDB connector configuration and then start the connector by adding it to Kafka Connect. +## YugabyteDB gRPC Replication Protocol -{{}} -To understand how the various features and configuration of the connector, see [Debezium connector](./debezium-connector-yugabytedb). -{{}} +This method involves setting up a change stream in YugabyteDB that uses the native gRPC replication protocol to publish change events. -## Monitoring +It works as follows: -You can monitor the activities and status of the deployed connectors using the http end points provided by YugabyteDB. +1. Establish a change stream in the YugabyteDB cluster using the yb_admin CLI commands. +1. Deploy the YugabyteDB gRPC Connector in your preferred Kafka Connect environment. +1. The connector captures change events using YugabyteDB's native gRPC replication and directly publishes them to a Kafka topic. -{{}} -To know more about how to monitor your CDC setup, see [Monitor](./cdc-monitor). +{{}} +To learn about gRPC Replication, see [Using Yugabyte gRPC Replication](./using-yugabytedb-grpc-replication/). {{}} - -For tutorials on streaming data to Kafka environments, including Amazon MSK, Azure Event Hubs, and Confluent Cloud, see [Kafka environments](/preview/tutorials/cdc-tutorials/). - -## Learn more - -- [Examples of CDC usage and patterns](https://github.com/yugabyte/cdc-examples/tree/main) {{}} -- [Tutorials to deploy in different Kafka environments](../../tutorials/cdc-tutorials/) {{}} -- [Data Streaming Using YugabyteDB CDC, Kafka, and SnowflakeSinkConnector](https://www.yugabyte.com/blog/data-streaming-using-yugabytedb-cdc-kafka-and-snowflakesinkconnector/) {{}} -- [Unlock Azure Storage Options With YugabyteDB CDC](https://www.yugabyte.com/blog/unlocking-azure-storage-options-with-yugabytedb-cdc/) {{}} -- [Change Data Capture From YugabyteDB to Elasticsearch](https://www.yugabyte.com/blog/change-data-capture-cdc-yugabytedb-elasticsearch/) {{}} -- [Snowflake CDC: Publishing Data Using Amazon S3 and YugabyteDB](https://www.yugabyte.com/blog/snowflake-cdc-publish-data-using-amazon-s3-yugabytedb/) {{}} -- [Streaming Changes From YugabyteDB to Downstream Databases](https://www.yugabyte.com/blog/streaming-changes-yugabytedb-cdc-downstream-databases/) {{}} -- [Change Data Capture from YugabyteDB CDC to ClickHouse](https://www.yugabyte.com/blog/change-data-capture-cdc-yugabytedb-clickhouse/) {{}} -- [How to Run Debezium Server with Kafka as a Sink](https://www.yugabyte.com/blog/change-data-capture-cdc-run-debezium-server-kafka-sink/) {{}} -- [Change Data Capture Using a Spring Data Processing Pipeline](https://www.yugabyte.com/blog/change-data-capture-cdc-spring-data-processing-pipeline/) {{}} diff --git a/docs/content/preview/explore/change-data-capture/cdc-overview.md b/docs/content/preview/explore/change-data-capture/cdc-overview.md deleted file mode 100644 index b81c86623a23..000000000000 --- a/docs/content/preview/explore/change-data-capture/cdc-overview.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: Overview of CDC internals -linkTitle: Overview -description: Change Data Capture in YugabyteDB. -headcontent: Change Data Capture in YugabyteDB -menu: - preview: - parent: explore-change-data-capture - identifier: cdc-overview - weight: 10 -type: docs ---- - -YugabyteDB automatically splits user tables into multiple shards (also called tablets) using either a hash- or range-based strategy. The primary key for each row in the table uniquely identifies the location of the tablet in the row. - -Each tablet has its own WAL file. WAL is NOT in-memory, but it is disk persisted. Each WAL preserves the order in which transactions (or changes) happened. Hybrid TS, Operation ID, and additional metadata about the transaction is also preserved. - -![How does CDC work](/images/explore/cdc-overview-work2.png) - -YugabyteDB normally purges WAL segments after some period of time. This means that the connector does not have the complete history of all changes that have been made to the database. Therefore, when the connector first connects to a particular YugabyteDB database, it starts by performing a consistent snapshot of each of the database schemas. - -The Debezium YugabyteDB connector captures row-level changes in the schemas of a YugabyteDB database. The first time it connects to a YugabyteDB cluster, the connector takes a consistent snapshot of all schemas. After that snapshot is complete, the connector continuously captures row-level changes that insert, update, and delete database content, and that were committed to a YugabyteDB database. - -![How does CDC work](/images/explore/cdc-overview-work.png) - -The connector produces a change event for every row-level insert, update, and delete operation that was captured, and sends change event records for each table in a separate Kafka topic. Client applications read the Kafka topics that correspond to the database tables of interest, and can react to every row-level event they receive from those topics. For each table, the default behavior is that the connector streams all generated events to a separate Kafka topic for that table. Applications and services consume data change event records from that topic. - -The core primitive of CDC is the _stream_. Streams can be enabled and disabled on databases. Every change to a watched database table is emitted as a record in a configurable format to a configurable sink. Streams scale to any YugabyteDB cluster independent of its size and are designed to impact production traffic as little as possible. - -![How does CDC work](/images/explore/cdc-overview-work3.png) - -## Known limitations - -* A single stream can only be used to stream data from one namespace only. -* There should be a primary key on the table you want to stream the changes from. -* CDC is not supported on a target table for xCluster replication [11829](https://github.com/yugabyte/yugabyte-db/issues/11829). -* Currently we don't support schema evolution for changes that require table rewrites (ex: ALTER TYPE). -* YCQL tables aren't currently supported. Issue [11320](https://github.com/yugabyte/yugabyte-db/issues/11320). - -In addition, CDC support for the following features will be added in upcoming releases: - -* Support for point-in-time recovery (PITR) is tracked in issue [10938](https://github.com/yugabyte/yugabyte-db/issues/10938). -* Support for transaction savepoints is tracked in issue [10936](https://github.com/yugabyte/yugabyte-db/issues/10936). -* Support for enabling CDC on Read Replicas is tracked in issue [11116](https://github.com/yugabyte/yugabyte-db/issues/11116). -* Support for schema evolution with before image is tracked in issue [15197](https://github.com/yugabyte/yugabyte-db/issues/15197). - -## Learn more - -* Refer to [CDC Examples](https://github.com/yugabyte/cdc-examples/tree/main) for CDC usage and pattern examples. -* Refer to [Tutorials](../../../tutorials/cdc-tutorials/) to deploy in different Kafka environments. -* Refer to blogs about CDC: - * [Data Streaming Using YugabyteDB CDC, Kafka, and SnowflakeSinkConnector](https://www.yugabyte.com/blog/data-streaming-using-yugabytedb-cdc-kafka-and-snowflakesinkconnector/) - * [Unlock Azure Storage Options With YugabyteDB CDC](https://www.yugabyte.com/blog/unlocking-azure-storage-options-with-yugabytedb-cdc/) - * [Change Data Capture From YugabyteDB to Elasticsearch](https://www.yugabyte.com/blog/change-data-capture-cdc-yugabytedb-elasticsearch/) - * [Snowflake CDC: Publishing Data Using Amazon S3 and YugabyteDB](https://www.yugabyte.com/blog/snowflake-cdc-publish-data-using-amazon-s3-yugabytedb/) - * [Streaming Changes From YugabyteDB to Downstream Databases](https://www.yugabyte.com/blog/streaming-changes-yugabytedb-cdc-downstream-databases/) - * [Change Data Capture from YugabyteDB CDC to ClickHouse](https://www.yugabyte.com/blog/change-data-capture-cdc-yugabytedb-clickhouse/) - * [How to Run Debezium Server with Kafka as a Sink](https://www.yugabyte.com/blog/change-data-capture-cdc-run-debezium-server-kafka-sink/) - * [Change Data Capture Using a Spring Data Processing Pipeline](https://www.yugabyte.com/blog/change-data-capture-cdc-spring-data-processing-pipeline/) diff --git a/docs/content/preview/explore/change-data-capture/using-logical-replication/_index.md b/docs/content/preview/explore/change-data-capture/using-logical-replication/_index.md new file mode 100644 index 000000000000..5ff2d5b2dbf8 --- /dev/null +++ b/docs/content/preview/explore/change-data-capture/using-logical-replication/_index.md @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/docs/content/preview/explore/change-data-capture/using-logical-replication/advanced-configuration.md b/docs/content/preview/explore/change-data-capture/using-logical-replication/advanced-configuration.md new file mode 100644 index 000000000000..0f676e580ab7 --- /dev/null +++ b/docs/content/preview/explore/change-data-capture/using-logical-replication/advanced-configuration.md @@ -0,0 +1,12 @@ +--- +title: Advanced Configurations +headerTitle: Advanced Configurations +linkTitle: Advanced Configurations +description: Advanced Configurations for Change Data Capture in YugabyteDB. +menu: + preview: + parent: explore-change-data-capture-logical-replication + identifier: advanced-configurations + weight: 40 +type: docs +--- \ No newline at end of file diff --git a/docs/content/preview/explore/change-data-capture/using-logical-replication/advanced-topic.md b/docs/content/preview/explore/change-data-capture/using-logical-replication/advanced-topic.md new file mode 100644 index 000000000000..73c365ffd09a --- /dev/null +++ b/docs/content/preview/explore/change-data-capture/using-logical-replication/advanced-topic.md @@ -0,0 +1,12 @@ +--- +title: Advanced topics +headerTitle: Advanced topics +linkTitle: Advanced topics +description: Advanced topics for Change Data Capture in YugabyteDB. +menu: + preview: + parent: explore-change-data-capture-logical-replication + identifier: advanced-topics + weight: 50 +type: docs +--- \ No newline at end of file diff --git a/docs/content/preview/explore/change-data-capture/using-logical-replication/cdc-best-practices.md b/docs/content/preview/explore/change-data-capture/using-logical-replication/cdc-best-practices.md new file mode 100644 index 000000000000..a2e5ab485373 --- /dev/null +++ b/docs/content/preview/explore/change-data-capture/using-logical-replication/cdc-best-practices.md @@ -0,0 +1,12 @@ +--- +title: Best Practices +headerTitle: Best Practices +linkTitle: Best Practices +description: Best Practices for Change Data Capture in YugabyteDB. +menu: + preview: + parent: explore-change-data-capture-logical-replication + identifier: best-practices-cdc + weight: 60 +type: docs +--- \ No newline at end of file diff --git a/docs/content/preview/explore/change-data-capture/using-logical-replication/get-started.md b/docs/content/preview/explore/change-data-capture/using-logical-replication/get-started.md new file mode 100644 index 000000000000..c2f1375ace02 --- /dev/null +++ b/docs/content/preview/explore/change-data-capture/using-logical-replication/get-started.md @@ -0,0 +1,13 @@ +--- +title: Get started with CDC in YugabyteDB +headerTitle: Get started +linkTitle: Get started +description: Get started with Change Data Capture in YugabyteDB. +headcontent: Get set up for using CDC in YugabyteDB +menu: + preview: + parent: explore-change-data-capture-logical-replication + identifier: get-started + weight: 20 +type: docs +--- \ No newline at end of file diff --git a/docs/content/preview/explore/change-data-capture/using-logical-replication/monitor.md b/docs/content/preview/explore/change-data-capture/using-logical-replication/monitor.md new file mode 100644 index 000000000000..8aaf773a9913 --- /dev/null +++ b/docs/content/preview/explore/change-data-capture/using-logical-replication/monitor.md @@ -0,0 +1,12 @@ +--- +title: CDC monitoring in YugabyteDB +headerTitle: Monitor +linkTitle: Monitor +description: Monitor Change Data Capture in YugabyteDB. +menu: + preview: + parent: explore-change-data-capture-logical-replication + identifier: monitor + weight: 30 +type: docs +--- \ No newline at end of file diff --git a/docs/content/preview/explore/change-data-capture/using-logical-replication/overview.md b/docs/content/preview/explore/change-data-capture/using-logical-replication/overview.md new file mode 100644 index 000000000000..dbf2a9522fa9 --- /dev/null +++ b/docs/content/preview/explore/change-data-capture/using-logical-replication/overview.md @@ -0,0 +1,12 @@ +--- +title: Overview of CDC - logical replication +linkTitle: Overview +description: Change Data Capture in YugabyteDB. +headcontent: Change Data Capture in YugabyteDB +menu: + preview: + parent: explore-change-data-capture-logical-replication + identifier: overview + weight: 10 +type: docs +--- \ No newline at end of file diff --git a/docs/content/preview/explore/change-data-capture/using-logical-replication/yugabtyedb-connector.md b/docs/content/preview/explore/change-data-capture/using-logical-replication/yugabtyedb-connector.md new file mode 100644 index 000000000000..bb203f0a9b04 --- /dev/null +++ b/docs/content/preview/explore/change-data-capture/using-logical-replication/yugabtyedb-connector.md @@ -0,0 +1,12 @@ +--- +title: YugabyteDB connector +headerTitle: YugabyteDB connector +linkTitle: YugabyteDB connector +description: YugabyteDB connector for Change Data Capture in YugabyteDB. +menu: + preview: + parent: explore-change-data-capture-logical-replication + identifier: yugabytedb-connector + weight: 70 +type: docs +--- \ No newline at end of file diff --git a/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/_index.md b/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/_index.md new file mode 100644 index 000000000000..5f746c0c512a --- /dev/null +++ b/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/_index.md @@ -0,0 +1,70 @@ +--- +title: Using YugabyteDB gRPC replication +headerTitle: Using YugabyteDB gRPC replication +linkTitle: Using YugabyteDB gRPC replication +description: CDC or Change data capture is a process to capture changes made to data in the database. +headcontent: Capture changes made to data in the database +image: /images/section_icons/index/develop.png +cascade: + earlyAccess: /preview/releases/versioning/#feature-maturity +aliases: + - /preview/explore/change-data-capture/cdc-overview/ +menu: + preview: + identifier: explore-change-data-capture-grpc-replication + parent: explore-change-data-capture + weight: 280 +type: indexpage +showRightNav: true +--- + +## Overview + +YugabyteDB CDC captures changes made to data in the database and streams those changes to external processes, applications, or other databases. CDC allows you to track and propagate changes in a YugabyteDB database to downstream consumers based on its Write-Ahead Log (WAL). YugabyteDB CDC uses Debezium to capture row-level changes resulting from INSERT, UPDATE, and DELETE operations in the upstream database, and publishes them as events to Kafka using Kafka Connect-compatible connectors. + +![What is CDC](/images/explore/cdc-overview-what.png) + + + +## YugabyteDB gRPC Connector + +To capture and stream your changes in YugabyteDB to an external system, you need a connector that can read the changes in YugabyteDB and stream it out. For this, you can use the YugabyteDB gRPC (Debezium) connector. The connector is deployed as a set of Kafka Connect-compatible connectors, so you first need to define a YugabyteDB connector configuration and then start the connector by adding it to Kafka Connect. + +{{}} +To understand how the various features and configuration of the connector, see [YugabyteDB gRPC Connector](./debezium-connector-yugabytedb). +{{}} + +## Get started + +Get started with Yugabyte gRPC replication. + +For tutorials on streaming data to Kafka environments, including Amazon MSK, Azure Event Hubs, and Confluent Cloud, see [Kafka environments](/preview/tutorials/cdc-tutorials/). + +{{}} +To learn how get started with the connector, see [Get started](./cdc-get-started). +{{}} + +## Monitoring + +You can monitor the activities and status of the deployed connectors using the http end points provided by YugabyteDB. + +{{}} +To know more about how to monitor your CDC setup, see [Monitor](./cdc-monitor). +{{}} + +## Learn more + +- [Examples of CDC usage and patterns](https://github.com/yugabyte/cdc-examples/tree/main) {{}} +- [Tutorials to deploy in different Kafka environments](../../../tutorials/cdc-tutorials/) {{}} +- [Data Streaming Using YugabyteDB CDC, Kafka, and SnowflakeSinkConnector](https://www.yugabyte.com/blog/data-streaming-using-yugabytedb-cdc-kafka-and-snowflakesinkconnector/) {{}} +- [Unlock Azure Storage Options With YugabyteDB CDC](https://www.yugabyte.com/blog/unlocking-azure-storage-options-with-yugabytedb-cdc/) {{}} +- [Change Data Capture From YugabyteDB to Elasticsearch](https://www.yugabyte.com/blog/change-data-capture-cdc-yugabytedb-elasticsearch/) {{}} +- [Snowflake CDC: Publishing Data Using Amazon S3 and YugabyteDB](https://www.yugabyte.com/blog/snowflake-cdc-publish-data-using-amazon-s3-yugabytedb/) {{}} +- [Streaming Changes From YugabyteDB to Downstream Databases](https://www.yugabyte.com/blog/streaming-changes-yugabytedb-cdc-downstream-databases/) {{}} +- [Change Data Capture from YugabyteDB CDC to ClickHouse](https://www.yugabyte.com/blog/change-data-capture-cdc-yugabytedb-clickhouse/) {{}} +- [How to Run Debezium Server with Kafka as a Sink](https://www.yugabyte.com/blog/change-data-capture-cdc-run-debezium-server-kafka-sink/) {{}} +- [Change Data Capture Using a Spring Data Processing Pipeline](https://www.yugabyte.com/blog/change-data-capture-cdc-spring-data-processing-pipeline/) {{}} diff --git a/docs/content/preview/explore/change-data-capture/cdc-get-started.md b/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/cdc-get-started.md similarity index 79% rename from docs/content/preview/explore/change-data-capture/cdc-get-started.md rename to docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/cdc-get-started.md index ac0c6fa410ea..68cffe315788 100644 --- a/docs/content/preview/explore/change-data-capture/cdc-get-started.md +++ b/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/cdc-get-started.md @@ -4,15 +4,17 @@ headerTitle: Get started linkTitle: Get started description: Get started with Change Data Capture in YugabyteDB. headcontent: Get set up for using CDC in YugabyteDB +aliases: + - /preview/explore/change-data-capture/cdc-get-started/ menu: preview: - parent: explore-change-data-capture + parent: explore-change-data-capture-grpc-replication identifier: cdc-get-started weight: 30 type: docs --- -To stream data change events from YugabyteDB databases, you need to use Debezium YugabyteDB connector. To deploy a Debezium YugabyteDB connector, you install the Debezium YugabyteDB connector archive, configure the connector, and start the connector by adding its configuration to Kafka Connect. You can download the connector from [GitHub releases](https://github.com/yugabyte/debezium-connector-yugabytedb/releases). The connector supports Kafka Connect version 2.x and later, and for YugabyteDB, it supports version 2.14 and later. For more connector configuration details and complete steps, refer to [Debezium connector](../debezium-connector-yugabytedb/). +To stream data change events from YugabyteDB databases, you need to use YugabyteDB gRPC Connector. To deploy the connector, you install the connector archive, configure the connector, and start the connector by adding its configuration to Kafka Connect. You can download the connector from [GitHub releases](https://github.com/yugabyte/debezium-connector-yugabytedb/releases). The connector supports Kafka Connect version 2.x and later, and for YugabyteDB, it supports version 2.14 and later. For more connector configuration details and complete steps, refer to [YugabyteDB gRPC Connector](../debezium-connector-yugabytedb/). ## Ordering guarantees @@ -28,17 +30,17 @@ The following steps are necessary to set up YugabyteDB for use with the Debezium - Create a DB stream ID. - Before you use the YugabyteDB connector to retrieve data change events from YugabyteDB, create a stream ID using the yb-admin CLI command. Refer to the [yb-admin](../../../admin/yb-admin/#change-data-capture-cdc-commands) CDC command reference documentation for more details. + Before you use the YugabyteDB connector to retrieve data change events from YugabyteDB, create a stream ID using the yb-admin CLI command. Refer to the [yb-admin](../../../../admin/yb-admin/#change-data-capture-cdc-commands) CDC command reference documentation for more details. - Make sure the YB-Master and YB-TServer ports are open. - The connector connects to the YB-Master and YB-TServer processes running on the YugabyteDB server. Make sure the ports on which these processes are running are open. The [default ports](../../../reference/configuration/default-ports/) on which the processes run are `7100` and `9100` respectively. + The connector connects to the YB-Master and YB-TServer processes running on the YugabyteDB server. Make sure the ports on which these processes are running are open. The [default ports](../../../../reference/configuration/default-ports/) on which the processes run are `7100` and `9100` respectively. - Monitor available disk space. The change records for CDC are read from the WAL. YugabyteDB CDC maintains checkpoints internally for each DB stream ID and garbage collects the WAL entries if those have been streamed to the CDC clients. - In case CDC is lagging or away for some time, the disk usage may grow and cause YugabyteDB cluster instability. To avoid this scenario, if a stream is inactive for a configured amount of time, the WAL is garbage collected. This is configurable using a [YB-TServer flag](../../../reference/configuration/yb-tserver/#change-data-capture-cdc-flags). + In case CDC is lagging or away for some time, the disk usage may grow and cause YugabyteDB cluster instability. To avoid this scenario, if a stream is inactive for a configured amount of time, the WAL is garbage collected. This is configurable using a [YB-TServer flag](../../../../reference/configuration/yb-tserver/#change-data-capture-cdc-flags). ## Serialization @@ -110,9 +112,9 @@ To use the [protobuf](http://protobuf.dev) format for the serialization/de-seria Before image refers to the state of the row _before_ the change event occurred. The YugabyteDB connector sends the before image of the row when it will be configured using a stream ID enabled with before image. It is populated for UPDATE and DELETE events. For INSERT events, before image doesn't make sense as the change record itself is in the context of new row insertion. -Yugabyte uses multi-version concurrency control (MVCC) mechanism, and compacts data at regular intervals. The compaction or the history retention is controlled by the [history retention interval flag](../../../reference/configuration/yb-tserver/#timestamp-history-retention-interval-sec). However, when before image is enabled for a database, YugabyteDB adjusts the history retention for that database based on the most lagging active CDC stream so that the previous row state is retained, and available. Consequently, in the case of a lagging CDC stream, the amount of space required for the database grows as more data is retained. On the other hand, older rows that are not needed for any of the active CDC streams are identified and garbage collected. +Yugabyte uses multi-version concurrency control (MVCC) mechanism, and compacts data at regular intervals. The compaction or the history retention is controlled by the [history retention interval flag](../../../../reference/configuration/yb-tserver/#timestamp-history-retention-interval-sec). However, when before image is enabled for a database, YugabyteDB adjusts the history retention for that database based on the most lagging active CDC stream so that the previous row state is retained, and available. Consequently, in the case of a lagging CDC stream, the amount of space required for the database grows as more data is retained. On the other hand, older rows that are not needed for any of the active CDC streams are identified and garbage collected. -Schema version that is currently being used by a CDC stream will be used to frame before and current row images. The before image functionality is disabled by default unless it is specifically turned on during the CDC stream creation. The [yb-admin](../../../admin/yb-admin/#enabling-before-image) `create_change_data_stream` command can be used to create a CDC stream with before image enabled. +Schema version that is currently being used by a CDC stream will be used to frame before and current row images. The before image functionality is disabled by default unless it is specifically turned on during the CDC stream creation. The [yb-admin](../../../../admin/yb-admin/#enabling-before-image) `create_change_data_stream` command can be used to create a CDC stream with before image enabled. {{< tip title="Use transformers" >}} @@ -501,7 +503,7 @@ CDC record for UPDATE (using schema version 1): ## Colocated tables -YugabyteDB supports streaming of changes from [colocated tables](../../../architecture/docdb-sharding/colocated-tables). The connector can be configured with regular configuration properties and deployed for streaming. +YugabyteDB supports streaming of changes from [colocated tables](../../../../architecture/docdb-sharding/colocated-tables). The connector can be configured with regular configuration properties and deployed for streaming. {{< note title="Note" >}} @@ -513,15 +515,15 @@ To stream the changes for the new table, delete the existing connector and deplo ## Important configuration settings -You can use several flags to fine-tune YugabyteDB's CDC behavior. These flags are documented in the [Change data capture flags](../../../reference/configuration/yb-tserver/#change-data-capture-cdc-flags) section of the YB-TServer reference and [Change data capture flags](../../../reference/configuration/yb-master/#change-data-capture-cdc-flags) section of the YB-Master reference. The following flags are particularly important for configuring CDC: +You can use several flags to fine-tune YugabyteDB's CDC behavior. These flags are documented in the [Change data capture flags](../../../../reference/configuration/yb-tserver/#change-data-capture-cdc-flags) section of the YB-TServer reference and [Change data capture flags](../../../../reference/configuration/yb-master/#change-data-capture-cdc-flags) section of the YB-Master reference. The following flags are particularly important for configuring CDC: -- [cdc_intent_retention_ms](../../../reference/configuration/yb-tserver/#cdc-intent-retention-ms) - Controls retention of intents, in ms. If a request for change records is not received for this interval, un-streamed intents are garbage collected and the CDC stream is considered expired. This expiry is not reversible, and the only course of action would be to create a new CDC stream. The default value of this flag is 4 hours (4 x 3600 x 1000 ms). +- [cdc_intent_retention_ms](../../../../reference/configuration/yb-tserver/#cdc-intent-retention-ms) - Controls retention of intents, in ms. If a request for change records is not received for this interval, un-streamed intents are garbage collected and the CDC stream is considered expired. This expiry is not reversible, and the only course of action would be to create a new CDC stream. The default value of this flag is 4 hours (4 x 3600 x 1000 ms). -- [cdc_wal_retention_time_secs](../../../reference/configuration/yb-master/#cdc-wal-retention-time-secs) - Controls how long WAL is retained, in seconds. This is irrespective of whether a request for change records is received or not. The default value of this flag is 4 hours (14400 seconds). +- [cdc_wal_retention_time_secs](../../../../reference/configuration/yb-master/#cdc-wal-retention-time-secs) - Controls how long WAL is retained, in seconds. This is irrespective of whether a request for change records is received or not. The default value of this flag is 4 hours (14400 seconds). -- [cdc_snapshot_batch_size](../../../reference/configuration/yb-tserver/#cdc-snapshot-batch-size) - This flag's default value is 250 records included per batch in response to an internal call to get the snapshot. If the table contains a very large amount of data, you may need to increase this value to reduce the amount of time it takes to stream the complete snapshot. You can also choose not to take a snapshot by modifying the [Debezium](../debezium-connector-yugabytedb/) configuration. +- [cdc_snapshot_batch_size](../../../../reference/configuration/yb-tserver/#cdc-snapshot-batch-size) - This flag's default value is 250 records included per batch in response to an internal call to get the snapshot. If the table contains a very large amount of data, you may need to increase this value to reduce the amount of time it takes to stream the complete snapshot. You can also choose not to take a snapshot by modifying the [Debezium](../debezium-connector-yugabytedb/) configuration. -- [cdc_max_stream_intent_records](../../../reference/configuration/yb-tserver/#cdc-max-stream-intent-records) - Controls how many intent records can be streamed in a single `GetChanges` call. Essentially, intents of large transactions are broken down into batches of size equal to this flag, hence this controls how many batches of `GetChanges` calls are needed to stream the entire large transaction. The default value of this flag is 1680, and transactions with intents less than this value are streamed in a single batch. The value of this flag can be increased, if the workload has larger transactions and CDC throughput needs to be increased. Note that high values of this flag can increase the latency of each `GetChanges` call. +- [cdc_max_stream_intent_records](../../../../reference/configuration/yb-tserver/#cdc-max-stream-intent-records) - Controls how many intent records can be streamed in a single `GetChanges` call. Essentially, intents of large transactions are broken down into batches of size equal to this flag, hence this controls how many batches of `GetChanges` calls are needed to stream the entire large transaction. The default value of this flag is 1680, and transactions with intents less than this value are streamed in a single batch. The value of this flag can be increased, if the workload has larger transactions and CDC throughput needs to be increased. Note that high values of this flag can increase the latency of each `GetChanges` call. ## Retaining data for longer durations @@ -535,7 +537,7 @@ Longer values of `cdc_intent_retention_ms`, coupled with longer CDC lags (period ## Content-based routing -By default, the Yugabyte Debezium connector streams all of the change events that it reads from a table to a single static topic. However, you may want to re-route the events into different Kafka topics based on the event's content. You can do this using the Debezium `ContentBasedRouter`. But first, two additional dependencies need to be placed in the Kafka-Connect environment. These are not included in the official *yugabyte-debezium-connector* for security reasons. These dependencies are: +By default, the connector streams all of the change events that it reads from a table to a single static topic. However, you may want to re-route the events into different Kafka topics based on the event's content. You can do this using the Debezium `ContentBasedRouter`. But first, two additional dependencies need to be placed in the Kafka-Connect environment. These are not included in the official *yugabyte-debezium-connector* for security reasons. These dependencies are: - Debezium routing SMT (Single Message Transform) - Groovy JSR223 implementation (or other scripting languages that integrate with [JSR 223](https://jcp.org/en/jsr/detail?id=223)) diff --git a/docs/content/preview/explore/change-data-capture/cdc-monitor.md b/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/cdc-monitor.md similarity index 98% rename from docs/content/preview/explore/change-data-capture/cdc-monitor.md rename to docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/cdc-monitor.md index dbce650c904f..4dad0867f92a 100644 --- a/docs/content/preview/explore/change-data-capture/cdc-monitor.md +++ b/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/cdc-monitor.md @@ -4,9 +4,11 @@ headerTitle: Monitor linkTitle: Monitor description: Monitor Change Data Capture in YugabyteDB. headcontent: Monitor deployed CDC connectors +aliases: + - /preview/explore/change-data-capture/cdc-monitor/ menu: preview: - parent: explore-change-data-capture + parent: explore-change-data-capture-grpc-replication identifier: cdc-monitor weight: 60 type: docs diff --git a/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/cdc-overview.md b/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/cdc-overview.md new file mode 100644 index 000000000000..ee018de84821 --- /dev/null +++ b/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/cdc-overview.md @@ -0,0 +1,13 @@ + diff --git a/docs/content/preview/explore/change-data-capture/debezium-connector-yugabytedb.md b/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/debezium-connector-yugabytedb.md similarity index 97% rename from docs/content/preview/explore/change-data-capture/debezium-connector-yugabytedb.md rename to docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/debezium-connector-yugabytedb.md index 31d0538b2c74..83106b6f3455 100644 --- a/docs/content/preview/explore/change-data-capture/debezium-connector-yugabytedb.md +++ b/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/debezium-connector-yugabytedb.md @@ -1,16 +1,17 @@ --- -title: Debezium connector for YugabyteDB -headerTitle: Debezium connector for YugabyteDB -linkTitle: Debezium connector -description: Debezium is an open source distributed platform used to capture the changes in a database. +title: YugabyteDB gRPC Connector (Debezium) +headerTitle: YugabyteDB gRPC Connector +linkTitle: YugabyteDB gRPC Connector +description: YugabyteDB gRPC Connector is an open source distributed platform used to capture the changes in a database. aliases: - /preview/explore/change-data-capture/debezium-connector-yugabytedb-ysql - /preview/explore/change-data-capture/debezium-connector - /preview/explore/change-data-capture/debezium - /preview/explore/change-data-capture/debezium-connector-postgresql + - /preview/explore/change-data-capture/debezium-connector-yugabytedb menu: preview: - parent: explore-change-data-capture + parent: explore-change-data-capture-grpc-replication identifier: debezium-connector-yugabytedb weight: 20 type: docs @@ -18,7 +19,7 @@ rightNav: hideH4: true --- -The Debezium connector for YugabyteDB captures row-level changes in the schemas of a YugabyteDB database. +The YugabyteDB gRPC (Debezium) Connector captures row-level changes in the schemas of a YugabyteDB database. The first time it connects to a YugabyteDB cluster or universe, the connector takes a consistent snapshot of the tables it is configured for. After that snapshot is complete, the connector continuously captures row-level changes that insert, update, and delete database content that are committed to a YugabyteDB database. The connector generates data change event records and streams them to Kafka topics. For each table, the default behavior is that the connector streams all generated events to a separate Kafka topic for that table. Applications and services consume data change event records from that topic. @@ -697,7 +698,7 @@ When a row is deleted, the _delete_ event value still works with log compaction, {{< tip title="TRUNCATE tables when CDC is enabled" >}} -By default, the YugabyteDB CDC implementation does not allow you to TRUNCATE a table while an active CDC stream is present on the namespace. To allow truncating tables while CDC is active, set the [enable_truncate_cdcsdk_table](../../../reference/configuration/yb-tserver/#enable-truncate-cdcsdk-table) flag to true. +By default, the YugabyteDB CDC implementation does not allow you to TRUNCATE a table while an active CDC stream is present on the namespace. To allow truncating tables while CDC is active, set the [enable_truncate_cdcsdk_table](../../../../reference/configuration/yb-tserver/#enable-truncate-cdcsdk-table) flag to true. {{< /tip >}} @@ -917,8 +918,8 @@ Support for the following YugabyteDB data types will be enabled in future releas Before using the YugabyteDB connector to monitor the changes on a YugabyteDB server, you need to ensure the following: -* You have a stream ID created on the database you want to monitor the changes for. The stream can be created using the [yb-admin create_change_data_stream](../../../admin/yb-admin#create_change_data_stream) command. -* The table which is supposed to be monitored should have a primary key. Only tables which have a primary key can be streamed. See [limitations](../../change-data-capture/cdc-overview/#known-limitations). +* You have a stream ID created on the database you want to monitor the changes for. The stream can be created using the [yb-admin create_change_data_stream](../../../../admin/yb-admin#create_change_data_stream) command. +* The table which is supposed to be monitored should have a primary key. Only tables which have a primary key can be streamed. See [limitations](../cdc-overview/#known-limitations). ### WAL disk space consumption @@ -928,7 +929,7 @@ For example, the connector is lagging behind in streaming the changes. In this c ## Deployment -To deploy a Debezium YugabyteDB connector, you install the Debezium YugabyteDB connector archive, configure the connector, and start the connector by adding its configuration to Kafka Connect. For complete steps, follow the guide to [running the Debezium connector for YugabyteDB](../../../integrations/cdc/debezium/). +To deploy a Debezium YugabyteDB connector, you install the Debezium YugabyteDB connector archive, configure the connector, and start the connector by adding its configuration to Kafka Connect. For complete steps, follow the guide to [running the Debezium connector for YugabyteDB](../../../../integrations/cdc/debezium/). ### Connector configuration example @@ -959,7 +960,7 @@ You can choose to produce events for a subset of the schemas and tables in a dat 1. The address of this YugabyteDB server. 1. The port number of the YugabyteDB YSQL process. 1. List of comma separated values of master nodes of the YugabyteDB server. Usually in the form `host`:`port`. -1. The DB stream ID created using [yb-admin](../../../admin/yb-admin/#change-data-capture-cdc-commands). +1. The DB stream ID created using [yb-admin](../../../../admin/yb-admin/#change-data-capture-cdc-commands). 1. The name of the YugabyteDB user having the privileges to connect to the database. 1. The password for the above specified YugabyteDB user. 1. The name of the YugabyteDB database to connect to. @@ -1017,7 +1018,7 @@ The following properties are _required_ unless a default value is available: | database.password | N/A | Password for the given user. | | database.dbname | N/A | The database from which to stream. | | database.server.name | N/A | Logical name that identifies and provides a namespace for the particular YugabyteDB database server or cluster for which Debezium is capturing changes. This name must be unique, as it's also used to form the Kafka topic. | -| database.streamid | N/A | Stream ID created using [yb-admin](../../../admin/yb-admin/#change-data-capture-cdc-commands) for Change data capture. | +| database.streamid | N/A | Stream ID created using [yb-admin](../../../../admin/yb-admin/#change-data-capture-cdc-commands) for Change data capture. | | table.include.list | N/A | Comma-separated list of table names and schema names, such as `public.test` or `test_schema.test_table_name`. | | table.max.num.tablets | 300 | Maximum number of tablets the connector can poll for. This should be greater than or equal to the number of tablets the table is split into. | | database.sslmode | disable | Whether to use an encrypted connection to the YugabyteDB cluster. Supported options are:

    | @@ -1044,9 +1045,9 @@ The APIs used to fetch the changes are set up to work with TLSv1.2 only. Make su If you have a YugabyteDB cluster with SSL enabled, you need to obtain the root certificate and provide the path of the file in the `database.sslrootcert` configuration property. You can follow these links to get the certificates for your universe: -* [Local deployments](../../../secure/tls-encryption/) -* [YugabyteDB Anywhere](../../../yugabyte-platform/security/enable-encryption-in-transit/#connect-to-a-ysql-endpoint-with-tls) -* [YugabyteDB Aeon](../../../yugabyte-cloud/cloud-secure-clusters/cloud-authentication/#download-your-cluster-certificate) +* [Local deployments](../../../../secure/tls-encryption/) +* [YugabyteDB Anywhere](../../../../yugabyte-platform/security/enable-encryption-in-transit/#connect-to-a-ysql-endpoint-with-tls) +* [YugabyteDB Aeon](../../../../yugabyte-cloud/cloud-secure-clusters/cloud-authentication/#download-your-cluster-certificate) {{< /note >}} @@ -1063,7 +1064,7 @@ Advanced connector configuration properties: | time.precision.mode | adaptive | Time, date, and timestamps can be represented with different kinds of precision:

    `adaptive` captures the time and timestamp values exactly as in the database using millisecond precision values based on the database column's type.

    `adaptive_time_microseconds` captures the date, datetime and timestamp values exactly as in the database using millisecond precision values based on the database column's type. An exception is `TIME` type fields, which are always captured as microseconds.

    `connect` always represents time and timestamp values by using Kafka Connect's built-in representations for Time, Date, and Timestamp, which use millisecond precision regardless of the database columns' precision. See temporal values. | | decimal.handling.mode | double | The `precise` mode is not currently supported.

    `double` maps all the numeric, double, and money types as Java double values (FLOAT64).

    `string` represents the numeric, double, and money types as their string-formatted form.

    | | binary.handling.mode | hex | `hex` is the only supported mode. All binary strings are converted to their respective hex format and emitted as their string representation . | -| interval.handling.mode | numeric | Specifies how the connector should handle values for interval columns:

    `numeric` represents intervals using approximate number of microseconds.

    `string` represents intervals exactly by using the string pattern representation
    `PYMDTHMS`.
    For example: P1Y2M3DT4H5M6.78S. See [YugabyteDB data types](../../../api/ysql/datatypes/). | +| interval.handling.mode | numeric | Specifies how the connector should handle values for interval columns:

    `numeric` represents intervals using approximate number of microseconds.

    `string` represents intervals exactly by using the string pattern representation
    `PYMDTHMS`.
    For example: P1Y2M3DT4H5M6.78S. See [YugabyteDB data types](../../../../api/ysql/datatypes/). | | transaction.topic | `${database.server.name}`
    `.transaction` | Controls the name of the topic to which the connector sends transaction metadata messages. The placeholder `${database.server.name}` can be used for referring to the connector's logical name; defaults to `${database.server.name}.transaction`, for example `dbserver1.transaction`. | | provide.transaction.metadata | `false` | Determines whether the connector generates events with transaction boundaries and enriches change event envelopes with transaction metadata. Specify `true` if you want the connector to do this. See [Transaction metadata](#transaction-metadata) for details. | | skipped.operations | N/A | A comma-separated list of operation types to be skipped during streaming. The types are `c` for insert/create operations, `u` for update operations, and `d` for delete operations. By default, no operations are skipped. | @@ -1161,7 +1162,7 @@ The connector publishes metadata that can be used to distinguish transaction bou ### Prerequisites -* Create the Stream ID should in the `EXPLICIT` checkpointing mode. For more information, see [yb-admin create\_change\_data_stream](../../../admin/yb-admin#create-change-data-stream). +* Create the Stream ID should in the `EXPLICIT` checkpointing mode. For more information, see [yb-admin create\_change\_data_stream](../../../../admin/yb-admin#create-change-data-stream). * You should always run the connector with a single task, that is, `tasks.max` should always be set to 1. ### Known limitations @@ -1196,7 +1197,7 @@ In case one of the tablet servers crashes, the replicas on other YB-TServer node ### YugabyteDB server failures -In case of YugabyteDB server failures, the Debezium YugabyteDB connector will try for a configurable (using a [flag](../../../reference/configuration/yb-tserver/#change-data-capture-cdc-flags)) amount of time for the availability of the YB-TServer and will stop if the cluster cannot start. When the cluster is restarted, the connector can be run again and it will start processing the changes with the committed checkpoint. +In case of YugabyteDB server failures, the Debezium YugabyteDB connector will try for a configurable (using a [flag](../../../../reference/configuration/yb-tserver/#change-data-capture-cdc-flags)) amount of time for the availability of the YB-TServer and will stop if the cluster cannot start. When the cluster is restarted, the connector can be run again and it will start processing the changes with the committed checkpoint. ### Connector unable to find table association with stream ID diff --git a/docs/content/preview/integrations/cdc/debezium.md b/docs/content/preview/integrations/cdc/debezium.md index 6d62c15f8616..a4b955587759 100644 --- a/docs/content/preview/integrations/cdc/debezium.md +++ b/docs/content/preview/integrations/cdc/debezium.md @@ -154,7 +154,7 @@ Do the following: }' ``` -For a list of all the configuration options provided with the Debezium YugabyteDB connector, see [Connector configuration properties](../../../explore/change-data-capture/debezium-connector-yugabytedb/#connector-configuration-properties). +For a list of all the configuration options provided with the Debezium YugabyteDB connector, see [Connector configuration properties](../../../explore/change-data-capture/using-yugabytedb-grpc-replication/debezium-connector-yugabytedb/#connector-configuration-properties). {{< tip title="TRUNCATE tables when CDC is enabled" >}} diff --git a/docs/content/preview/tutorials/azure/azure-event-hubs.md b/docs/content/preview/tutorials/azure/azure-event-hubs.md index c65d08ed71a8..0a59ea932953 100644 --- a/docs/content/preview/tutorials/azure/azure-event-hubs.md +++ b/docs/content/preview/tutorials/azure/azure-event-hubs.md @@ -13,9 +13,9 @@ menu: type: docs --- -The [Azure Event Hubs](https://learn.microsoft.com/en-us/azure/event-hubs/event-hubs-about) data streaming service is [Apache Kafka](https://kafka.apache.org/intro) compatible, enabling existing workloads to easily be moved to Azure. With the [Debezium Connector for YugabyteDB](../../../explore/change-data-capture/debezium-connector-yugabytedb), we can stream changes from a YugabyteDB cluster to a Kafka topic using [Kafka Connect](https://docs.confluent.io/platform/current/connect/index.html#:~:text=Kafka%20Connect%20is%20a%20tool,in%20and%20out%20of%20Kafka.). +The [Azure Event Hubs](https://learn.microsoft.com/en-us/azure/event-hubs/event-hubs-about) data streaming service is [Apache Kafka](https://kafka.apache.org/intro) compatible, enabling existing workloads to easily be moved to Azure. With the [Debezium Connector for YugabyteDB](../../../explore/change-data-capture/using-yugabytedb-grpc-replication/debezium-connector-yugabytedb), we can stream changes from a YugabyteDB cluster to a Kafka topic using [Kafka Connect](https://docs.confluent.io/platform/current/connect/index.html#:~:text=Kafka%20Connect%20is%20a%20tool,in%20and%20out%20of%20Kafka.). -In this tutorial, we'll examine how the [YugabyteDB CDC](../../../explore/change-data-capture/cdc-overview/) can be used with Azure Event Hubs to stream real-time data for downstream processing. +In this tutorial, we'll examine how the [YugabyteDB CDC](../../../explore/change-data-capture/using-yugabytedb-grpc-replication/cdc-overview/) can be used with Azure Event Hubs to stream real-time data for downstream processing. In the following sections, you will: diff --git a/docs/content/preview/tutorials/cdc-tutorials/cdc-azure-event-hub.md b/docs/content/preview/tutorials/cdc-tutorials/cdc-azure-event-hub.md index 6dcd3f9768f8..a58b503724e0 100644 --- a/docs/content/preview/tutorials/cdc-tutorials/cdc-azure-event-hub.md +++ b/docs/content/preview/tutorials/cdc-tutorials/cdc-azure-event-hub.md @@ -25,7 +25,7 @@ The following table describes the components. | Component | Description | | :--- | :--- | | YugabyteDB Anywhere | Yugabyte self-managed DBaaS for deploying YugabyteDB at scale that includes YugabyteDB's self-managed, stateless CDC functionality with support for Confluent Kafka Connect. A pull-based model is used to report changes from the database Write-Ahead-Log (WAL). For more information on CDC, refer to [Change Data Capture](../../../explore/change-data-capture/). | -| [Debezium connector](../../../explore/change-data-capture/debezium-connector-yugabytedb/) | Pulls data from YugabyteDB, publishes it to Kafka, and runs in a Microsoft Azure Virtual Machine. | +| [Debezium connector](../../../explore/change-data-capture/using-yugabytedb-grpc-replication/debezium-connector-yugabytedb/) | Pulls data from YugabyteDB, publishes it to Kafka, and runs in a Microsoft Azure Virtual Machine. | | [Azure Event Hubs](https://learn.microsoft.com/en-us/azure/event-hubs/event-hubs-about) | Azure big data streaming platform and event ingestion service. | [Azure Synapse Pipelines](https://learn.microsoft.com/en-us/azure/data-factory/concepts-pipelines-activities?) | Pipelines and activities in Azure Data Factory and Azure Synapse Analytics required to construct end-to-end data-driven workflows to move and process your data. | | [ADLS Gen2](https://learn.microsoft.com/en-us/azure/storage/blobs/data-lake-storage-introduction) | Storage account | diff --git a/docs/content/preview/tutorials/cdc-tutorials/cdc-confluent-cloud.md b/docs/content/preview/tutorials/cdc-tutorials/cdc-confluent-cloud.md index 848e2002665d..a73c70dff36c 100644 --- a/docs/content/preview/tutorials/cdc-tutorials/cdc-confluent-cloud.md +++ b/docs/content/preview/tutorials/cdc-tutorials/cdc-confluent-cloud.md @@ -155,4 +155,4 @@ CONNECT_VALUE_CONVERTER_SCHEMA_REGISTRY_BASIC_AUTH_USER_INFO: "SCHEMA_REGISTRY_U docker-compose up ``` -1. Deploy the connector. For more information, refer to [Deployment](../../../explore/change-data-capture/debezium-connector-yugabytedb/#deployment). +1. Deploy the connector. For more information, refer to [Deployment](../../../explore/change-data-capture/using-yugabytedb-grpc-replication/debezium-connector-yugabytedb/#deployment). From 0619b50d358bf0ba0500e5d2286276f1383e03ac Mon Sep 17 00:00:00 2001 From: Yogesh Mahajan Date: Wed, 17 Jul 2024 16:04:11 -0700 Subject: [PATCH 1175/1195] CDC Docs changes (#23234) * Restructure YugabyteDB gRPC Connector docs --- .../explore/change-data-capture/_index.md | 2 +- .../_index.md | 15 +++++ .../cdc-get-started.md | 22 +++---- .../debezium-connector-yugabytedb.md | 58 +++++++++++-------- 4 files changed, 61 insertions(+), 36 deletions(-) diff --git a/docs/content/preview/explore/change-data-capture/_index.md b/docs/content/preview/explore/change-data-capture/_index.md index c61080eced61..cdc447229f34 100644 --- a/docs/content/preview/explore/change-data-capture/_index.md +++ b/docs/content/preview/explore/change-data-capture/_index.md @@ -55,5 +55,5 @@ It works as follows: 1. The connector captures change events using YugabyteDB's native gRPC replication and directly publishes them to a Kafka topic. {{}} -To learn about gRPC Replication, see [Using Yugabyte gRPC Replication](./using-yugabytedb-grpc-replication/). +To learn about gRPC Replication, see [Using YugabyteDB gRPC Replication](./using-yugabytedb-grpc-replication/). {{}} diff --git a/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/_index.md b/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/_index.md index 5f746c0c512a..1cf103f6a468 100644 --- a/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/_index.md +++ b/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/_index.md @@ -56,6 +56,21 @@ You can monitor the activities and status of the deployed connectors using the h To know more about how to monitor your CDC setup, see [Monitor](./cdc-monitor). {{}} +## Known limitations + +* A single stream can only be used to stream data from one namespace only. +* There should be a primary key on the table you want to stream the changes from. +* CDC is not supported on a target table for xCluster replication [11829](https://github.com/yugabyte/yugabyte-db/issues/11829). +* Currently we don't support schema evolution for changes that require table rewrites (ex: ALTER TYPE). +* YCQL tables aren't currently supported. Issue [11320](https://github.com/yugabyte/yugabyte-db/issues/11320). + +In addition, CDC support for the following features will be added in upcoming releases: + +* Support for point-in-time recovery (PITR) is tracked in issue [10938](https://github.com/yugabyte/yugabyte-db/issues/10938). +* Support for transaction savepoints is tracked in issue [10936](https://github.com/yugabyte/yugabyte-db/issues/10936). +* Support for enabling CDC on Read Replicas is tracked in issue [11116](https://github.com/yugabyte/yugabyte-db/issues/11116). +* Support for schema evolution with before image is tracked in issue [15197](https://github.com/yugabyte/yugabyte-db/issues/15197). + ## Learn more - [Examples of CDC usage and patterns](https://github.com/yugabyte/cdc-examples/tree/main) {{}} diff --git a/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/cdc-get-started.md b/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/cdc-get-started.md index 68cffe315788..37e92fd28dde 100644 --- a/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/cdc-get-started.md +++ b/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/cdc-get-started.md @@ -14,19 +14,9 @@ menu: type: docs --- -To stream data change events from YugabyteDB databases, you need to use YugabyteDB gRPC Connector. To deploy the connector, you install the connector archive, configure the connector, and start the connector by adding its configuration to Kafka Connect. You can download the connector from [GitHub releases](https://github.com/yugabyte/debezium-connector-yugabytedb/releases). The connector supports Kafka Connect version 2.x and later, and for YugabyteDB, it supports version 2.14 and later. For more connector configuration details and complete steps, refer to [YugabyteDB gRPC Connector](../debezium-connector-yugabytedb/). - -## Ordering guarantees - -|Ordering guarantee| Description| -|----------| ----------------------------| -|Per-tablet ordered delivery guarantee|All changes for a row (or rows in the same tablet) are received in the order in which they happened. However, due to the distributed nature of the problem, there is no guarantee of the order across tablets.| -|At least once delivery|Updates for rows are streamed at least once. This can happen in the case of Kafka Connect Node failure. If the Kafka Connect Node pushes the records to Kafka and crashes before committing the offset, on restart, it will again get the same set of records.| -|No gaps in change stream|Note that after you have received a change for a row for some timestamp `t`, you won't receive a previously unseen change for that row at a lower timestamp. Receiving any change implies that you have received _all older changes_ for that row.| - ## Set up YugabyteDB for CDC -The following steps are necessary to set up YugabyteDB for use with the Debezium YugabyteDB connector: +The following steps are necessary to set up YugabyteDB for use with the YugabyteDB gRPC connector: - Create a DB stream ID. @@ -42,6 +32,16 @@ The following steps are necessary to set up YugabyteDB for use with the Debezium In case CDC is lagging or away for some time, the disk usage may grow and cause YugabyteDB cluster instability. To avoid this scenario, if a stream is inactive for a configured amount of time, the WAL is garbage collected. This is configurable using a [YB-TServer flag](../../../../reference/configuration/yb-tserver/#change-data-capture-cdc-flags). +## Deploying the YugabyteDB gRPC Connector +To stream data change events from YugabyteDB databases, follow these steps to deploy the YugabyteDB gRPC Connector: + +* Download the Connector: You can download the connector from the [GitHub releases](https://github.com/yugabyte/debezium-connector-yugabytedb/releases) +* Install the Connector: Extract and install the connector archive in your Kafka Connect environment. +* Configure the Connector: Modify the connector configuration to suit your specific requirements. +* Start the Connector: Add the connector's configuration to Kafka Connect and start the connector. + +For more details on connector configuration and deployment steps, refer to the [YugabyteDB gRPC Connector documentation]((../debezium-connector-yugabytedb/)). + ## Serialization {{< tabpane text=true >}} diff --git a/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/debezium-connector-yugabytedb.md b/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/debezium-connector-yugabytedb.md index 83106b6f3455..6e8212cc4627 100644 --- a/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/debezium-connector-yugabytedb.md +++ b/docs/content/preview/explore/change-data-capture/using-yugabytedb-grpc-replication/debezium-connector-yugabytedb.md @@ -19,39 +19,22 @@ rightNav: hideH4: true --- -The YugabyteDB gRPC (Debezium) Connector captures row-level changes in the schemas of a YugabyteDB database. - -The first time it connects to a YugabyteDB cluster or universe, the connector takes a consistent snapshot of the tables it is configured for. After that snapshot is complete, the connector continuously captures row-level changes that insert, update, and delete database content that are committed to a YugabyteDB database. The connector generates data change event records and streams them to Kafka topics. For each table, the default behavior is that the connector streams all generated events to a separate Kafka topic for that table. Applications and services consume data change event records from that topic. - -## Overview - -The Debezium connector for YugabyteDB reads the changes produced by YugabyteDB. It uses the CDC service APIs implemented on the server side to get the changes. - -The connector produces a change event for every row-level insert, update, and delete operation that was captured, and sends change event records for each table in separate Kafka topics. Client applications read the Kafka topics corresponding to database tables of interest, and can react to every row-level event they receive from those topics. - -![What is CDC](/images/explore/cdc-overview-what.png) - -YugabyteDB normally purges write-ahead log (WAL) segments after some period of time. This means that the connector does not have the complete history of all changes that have been made to the database. Therefore, when the YugabyteDB connector first connects to a particular YugabyteDB database, it starts by taking a snapshot of each of the database schemas. After the connector completes the snapshot, it continues streaming changes from the exact point at which the snapshot was made. This way, the connector starts with a consistent view of all of the data, and does not omit any changes that were made while the snapshot was being taken. - -The connector is tolerant of failures. As the connector reads changes and produces events, it records the WAL position for each event. If the connector stops for any reason (including communication failures, network problems, or crashes), upon restart the connector continues reading the WAL where it last left off using the WAL position called checkpoints managed on the Kafka side as well as on the YugabyteDB cluster. - -{{< tip title="Use UTF-8 encoding" >}} - -Debezium supports databases with UTF-8 character encoding only. With a single-byte character encoding, it's not possible to correctly process strings that contain extended ASCII code characters. - -{{< /tip >}} - -## Connector compatibility +The YugabyteDB gRPC Connector captures row-level changes in a YugabyteDB database's schemas. +## YugabyteDB gRPC Connector compatibility The connector is compatible with the following versions of YugabyteDB. | YugabyteDB | Connector | | :--- | :--- | -| 2.14 (EA) | 1.9.5.y.3 | +| 2.14 | 1.9.5.y.3 | | 2.16 | 1.9.5.y.24 | | 2.18.2 | 1.9.5.y.33.2 | | 2.20 | 1.9.5.y.220.2 | +Compatibility +* Kafka Connect: The connector supports version 2.x and later. +* YugabyteDB: The connector supports version 2.14 and later. + {{< note title="Note" >}} Starting with YugabyteDB v2.20, the naming convention for releases of the connector uses the scheme *major.y.minor*, as follows: @@ -61,6 +44,33 @@ The connector is backward compatible with previous releases of YugabyteDB unless {{< /note >}} +## Initial Snapshot and Continuous Streaming: + +* Initial Snapshot: Upon its first connection to a YugabyteDB cluster, the connector takes a consistent snapshot of the configured tables. +* Continuous Streaming: After the snapshot, it continuously captures row-level changes (insertions, updates, and deletions) from the database. It then generates data change event records and streams them to Kafka topics. + +![What is CDC](/images/explore/cdc-overview-what.png) + + + +## Kafka Integration: + +For each table, the connector streams all generated events to a separate Kafka topic. Client applications and services can consume these data change event records from their respective topics. + +* CDC (Change Data Capture) Service: The Debezium connector for YugabyteDB leverages the CDC service APIs to read the changes from YugabyteDB. +* Event Production: For every row-level insert, update, and delete operation captured, the connector produces a corresponding change event and sends it to separate Kafka topics dedicated to each table. +* Client Consumption: Applications read the Kafka topics corresponding to the database tables they are interested in and react to the row-level events received. + +## Failure Tolerance +The connector records the WAL position for each event as it reads changes and produces events. If the connector stops (due to communication failures, network problems, or crashes), it resumes reading the WAL from the last recorded position upon restart. This uses checkpoints managed on both the Kafka side and the YugabyteDB cluster. +{{< tip title="Use UTF-8 encoding" >}} + +Debezium supports databases with UTF-8 character encoding only. With a single-byte character encoding, it's not possible to correctly process strings that contain extended ASCII code characters. + +{{< /tip >}} + + + ## How the connector works To optimally configure and run a Debezium YugabyteDB connector, it is helpful to understand how the connector performs snapshots, streams change events, determines Kafka topic names, and uses metadata. From 4823cd42bad0822a951bf90f4905f24b4f3d1329 Mon Sep 17 00:00:00 2001 From: Dwight Hodge <79169168+ddhodge@users.noreply.github.com> Date: Wed, 17 Jul 2024 21:30:52 -0400 Subject: [PATCH 1176/1195] [doc][yba] Encryption in transit update (#23027) * EIT reorg * Encryption in transit section update * links * minor edits * links * misc edits * edits * misc edits * minor edits * typo * DOC-358 * review comments * misc edits * minor edits * Apply suggestions from code review Co-authored-by: Sanketh I * review comments * format * review comments * format * minor edits * review comment * copy to stable * copy to stable * fix pages in stable --------- Co-authored-by: Sanketh I --- .../preview/explore/fault-tolerance/_index.md | 2 +- .../cloud-connect/connect-client-shell.md | 2 +- .../cloud-connect/connect/ysql.md | 2 +- .../cloud-secure-clusters/_index.md | 2 +- .../high-availability.md | 2 +- .../configure-yugabyte-platform/kubernetes.md | 2 +- .../create-deployments/connect-to-universe.md | 37 +- .../create-universe-multi-zone-kubernetes.md | 6 +- .../create-universe-multi-zone.md | 33 +- .../manage-deployments/edit-universe.md | 2 +- .../yugabyte-platform/prepare/networking.md | 2 +- .../yugabyte-platform/security/_index.md | 97 +-- .../security/authorization-platform.md | 6 +- .../security/customize-ports.md | 28 - .../security/enable-encryption-at-rest.md | 11 +- .../security/enable-encryption-in-transit.md | 704 ------------------ .../enable-encryption-in-transit/_index.md | 90 +++ .../add-certificate-ca.md | 113 +++ .../add-certificate-hashicorp.md | 189 +++++ .../add-certificate-kubernetes.md | 69 ++ .../add-certificate-self.md | 99 +++ .../auto-certificate.md | 95 +++ .../rotate-certificates.md | 55 ++ .../trust-store.md | 54 ++ .../security/security-checklist-yp.md | 57 -- .../preview/yugabyte-platform/yba-overview.md | 2 +- .../stable/explore/fault-tolerance/_index.md | 2 +- .../high-availability.md | 2 +- .../configure-yugabyte-platform/kubernetes.md | 2 +- .../create-deployments/connect-to-universe.md | 43 +- .../create-universe-multi-zone-kubernetes.md | 6 +- .../create-universe-multi-zone.md | 33 +- .../manage-deployments/edit-universe.md | 2 +- .../yugabyte-platform/prepare/networking.md | 2 +- .../yugabyte-platform/security/_index.md | 93 +-- .../security/authorization-platform.md | 8 +- .../security/customize-ports.md | 28 - .../security/enable-encryption-at-rest.md | 11 +- .../security/enable-encryption-in-transit.md | 704 ------------------ .../enable-encryption-in-transit/_index.md | 90 +++ .../add-certificate-ca.md | 113 +++ .../add-certificate-hashicorp.md | 189 +++++ .../add-certificate-kubernetes.md | 69 ++ .../add-certificate-self.md | 99 +++ .../auto-certificate.md | 95 +++ .../rotate-certificates.md | 55 ++ .../trust-store.md | 54 ++ .../security/security-checklist-yp.md | 55 -- .../stable/yugabyte-platform/yba-overview.md | 2 +- .../yp/encryption-in-transit/add-cert.png | Bin 53355 -> 185808 bytes .../add-hashicorp-cert.png | Bin 0 -> 106654 bytes .../yp/encryption-in-transit/add-k8s-cert.png | Bin 0 -> 82889 bytes .../encryption-in-transit/add-self-cert.png | Bin 0 -> 106030 bytes .../yp/encryption-in-transit/rotate-cert.png | Bin 0 -> 156284 bytes 54 files changed, 1761 insertions(+), 1757 deletions(-) delete mode 100644 docs/content/preview/yugabyte-platform/security/customize-ports.md delete mode 100644 docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit.md create mode 100644 docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/_index.md create mode 100644 docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-ca.md create mode 100644 docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-hashicorp.md create mode 100644 docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-kubernetes.md create mode 100644 docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-self.md create mode 100644 docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/auto-certificate.md create mode 100644 docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/rotate-certificates.md create mode 100644 docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/trust-store.md delete mode 100644 docs/content/preview/yugabyte-platform/security/security-checklist-yp.md delete mode 100644 docs/content/stable/yugabyte-platform/security/customize-ports.md delete mode 100644 docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit.md create mode 100644 docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/_index.md create mode 100644 docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-ca.md create mode 100644 docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-hashicorp.md create mode 100644 docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-kubernetes.md create mode 100644 docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-self.md create mode 100644 docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/auto-certificate.md create mode 100644 docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/rotate-certificates.md create mode 100644 docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/trust-store.md delete mode 100644 docs/content/stable/yugabyte-platform/security/security-checklist-yp.md create mode 100644 docs/static/images/yp/encryption-in-transit/add-hashicorp-cert.png create mode 100644 docs/static/images/yp/encryption-in-transit/add-k8s-cert.png create mode 100644 docs/static/images/yp/encryption-in-transit/add-self-cert.png create mode 100644 docs/static/images/yp/encryption-in-transit/rotate-cert.png diff --git a/docs/content/preview/explore/fault-tolerance/_index.md b/docs/content/preview/explore/fault-tolerance/_index.md index 6e56a5008f6b..f5503788a4b4 100644 --- a/docs/content/preview/explore/fault-tolerance/_index.md +++ b/docs/content/preview/explore/fault-tolerance/_index.md @@ -18,7 +18,7 @@ type: indexpage showRightNav: true --- -Resiliency, in the context of cloud databases, refers to the ability to withstand and recover from various types of failures, ranging from hardware malfunctions and software bugs to network outages and natural disasters. A resilient database system is designed to maintain data integrity, accessibility, and continuity of operations, even in the face of adverse events. Achieving resilience in cloud databases requires a multi-faceted approach, involving robust architectural design, effective data replication and backup strategies, load balancing, failover mechanisms, and comprehensive monitoring and incident response procedures. +Resiliency, in the context of cloud databases, refers to the ability to withstand and recover from various types of failures. These can range from hardware malfunctions and software bugs to network outages and natural disasters. A resilient database system is designed to maintain data integrity, accessibility, and continuity of operations, even in the face of adverse events. Achieving resilience in cloud databases requires a multi-faceted approach, involving robust architectural design, effective data replication and backup strategies, load balancing, failover mechanisms, and comprehensive monitoring and incident response procedures. YugabyteDB has been designed ground up to be resilient. YugabyteDB can continuously serve requests in the event of planned or unplanned outages, such as system upgrades and outages related to a node, availability zone, or region. YugabyteDB's High availability is achieved through a combination of distributed architecture, data replication, consensus algorithms, automatic rebalancing, and failure detection mechanisms, ensuring that the database remains available, consistent, and resilient to failures of fault domains. diff --git a/docs/content/preview/yugabyte-cloud/cloud-connect/connect-client-shell.md b/docs/content/preview/yugabyte-cloud/cloud-connect/connect-client-shell.md index 96f5360dde66..c3387f3e134e 100644 --- a/docs/content/preview/yugabyte-cloud/cloud-connect/connect-client-shell.md +++ b/docs/content/preview/yugabyte-cloud/cloud-connect/connect-client-shell.md @@ -26,7 +26,7 @@ Before you can connect a desktop client to a YugabyteDB Aeon cluster, you need t Before you can connect using a shell or other client, you need to add your computer to the cluster IP allow list. -By default, clusters deployed in a VPC do not expose any publicly-accessible IP addresses. To add public IP addresses, enable [Public Access](../../../yugabyte-cloud/cloud-secure-clusters/add-connections/#enabling-public-access) on the cluster **Settings > Network Access** tab. Alternatively, use the [Cloud shell](../connect-cloud-shell/) instead. +By default, clusters deployed in a VPC do not expose any publicly-accessible IP addresses. To add public IP addresses, enable [Public Access](../../cloud-secure-clusters/add-connections/#enabling-public-access) on the cluster **Settings > Network Access** tab. Alternatively, use the [Cloud shell](../connect-cloud-shell/) instead. For more information, refer to [IP allow list](../../cloud-secure-clusters/add-connections). diff --git a/docs/content/preview/yugabyte-cloud/cloud-connect/connect/ysql.md b/docs/content/preview/yugabyte-cloud/cloud-connect/connect/ysql.md index beee894fbfc3..f88e7143ae6e 100644 --- a/docs/content/preview/yugabyte-cloud/cloud-connect/connect/ysql.md +++ b/docs/content/preview/yugabyte-cloud/cloud-connect/connect/ysql.md @@ -14,7 +14,7 @@ To connect to a cluster using `ysqlsh`: 1. If your cluster is deployed in a VPC, choose **Private Address** if you are connecting from a peered VPC. Otherwise, choose **Public Address** (only available if you have enabled Public Access for the cluster; not recommended for production). 1. Copy the **YSQL** connection string. - The connection string includes flags specifying the host (`host`), username (`user`), database (`dbname`), and TLS settings (`sslmode` and `sslrootcert`). The command specifies that the connection will use the CA certificate you installed on your computer. For information on using other SSL modes, refer to [SSL modes in YSQL](../../../cloud-secure-clusters/cloud-authentication/#ssl-modes-in-ysql). + The connection string includes flags specifying the host (`host`), username (`user`), database (`dbname`), and TLS settings (`sslmode` and `sslrootcert`). The command specifies that the connection will use the CA certificate you installed on your computer. For information on using other SSL modes, refer to [SSL modes in YSQL](/preview/yugabyte-cloud/cloud-secure-clusters/cloud-authentication/#ssl-modes-in-ysql). Here's an example of the generated `ysqlsh` command: diff --git a/docs/content/preview/yugabyte-cloud/cloud-secure-clusters/_index.md b/docs/content/preview/yugabyte-cloud/cloud-secure-clusters/_index.md index 8c978512f531..9b3cff12603c 100644 --- a/docs/content/preview/yugabyte-cloud/cloud-secure-clusters/_index.md +++ b/docs/content/preview/yugabyte-cloud/cloud-secure-clusters/_index.md @@ -19,7 +19,7 @@ YugabyteDB Aeon clusters include the following security features: | :--- | :--- | | [Network authorization](add-connections/) | Access to YugabyteDB Aeon clusters is limited to IP addresses that you explicitly allow using IP allow lists.
    You can further enhance security and lower network latencies by deploying clusters in a [virtual private cloud (VPC) network](../cloud-basics/cloud-vpcs/). | | [Database authorization](cloud-users/) | YugabyteDB uses [role-based access control](cloud-users/) for database authorization. Using the default database admin user that is created when a cluster is deployed, you can [add additional roles and users](add-users/) to provide custom access to database resources to other team members and database clients. | -| [Encryption in transit](cloud-authentication/) | YugabyteDB Aeon uses encryption-in-transit for client-server and intra-node connectivity. | +| [Encryption in transit](cloud-authentication/) | YugabyteDB Aeon uses encryption in transit for client-server and intra-node connectivity. | | [Encryption at rest](managed-ear/) | Data at rest, including clusters and backups, is AES-256 encrypted using native cloud provider technologies: S3 and EBS volume encryption for AWS, Azure disk encryption, and server-side and persistent disk encryption for GCP. For additional security, you can encrypt your clusters using keys that you manage yourself. | | [Auditing](cloud-activity/) | YugabyteDB Aeon provides detailed auditing of activity on your account, including cluster creation, changes to clusters, changes to IP allow lists, backup activity, billing, access history, and more. | diff --git a/docs/content/preview/yugabyte-platform/administer-yugabyte-platform/high-availability.md b/docs/content/preview/yugabyte-platform/administer-yugabyte-platform/high-availability.md index 2efd9cdbb21a..513cf31a8307 100644 --- a/docs/content/preview/yugabyte-platform/administer-yugabyte-platform/high-availability.md +++ b/docs/content/preview/yugabyte-platform/administer-yugabyte-platform/high-availability.md @@ -144,7 +144,7 @@ For example, if your metrics retention is 14 days on your active instance, and y After HA is operational, it is recommended that you enable certificate validation to improve security of communication between the active and any standby instances. Enable certificate validation as follows: -1. Add certificates for the active and all standbys to the active instance [trust store](../../security/enable-encryption-in-transit/#add-certificates-to-your-trust-store). +1. Add certificates for the active and all standbys to the active instance [trust store](../../security/enable-encryption-in-transit/trust-store/). - If YBA was set up to use a custom server certificate, locate the corresponding Certificate Authority (CA) certificate. - If YBA was set up to use automatically generated self-signed certificates and you installed YBA using YBA Installer, locate the CA certificate at `/opt/yugabyte/data/yba-installer/certs/ca_cert.pem` on both the YBA active and standby instances. (If you configured a custom install root, replace `/opt/yugabyte` with the path you configured.) diff --git a/docs/content/preview/yugabyte-platform/configure-yugabyte-platform/kubernetes.md b/docs/content/preview/yugabyte-platform/configure-yugabyte-platform/kubernetes.md index 4c38e5e644ae..fcdd0bcaea50 100644 --- a/docs/content/preview/yugabyte-platform/configure-yugabyte-platform/kubernetes.md +++ b/docs/content/preview/yugabyte-platform/configure-yugabyte-platform/kubernetes.md @@ -122,7 +122,7 @@ Continue configuring your Kubernetes provider by clicking **Add region** and com 1. Complete the **Overrides** field using one of the provided [options](#overrides). If you do not specify anything, YBA uses defaults specified inside the Helm chart. For additional information, see [Open source Kubernetes](../../../deploy/kubernetes/single-zone/oss/helm-chart/). -1. If you are using [Kubernetes cert-manager](https://cert-manager.io) to manage TLS certificates, specify the issuer type and enter the issuer name. For more information, refer to [Enable encryption in transit](../../security/enable-encryption-in-transit/#kubernetes-cert-manager). +1. If you are using [Kubernetes cert-manager](https://cert-manager.io) to manage TLS certificates, specify the issuer type and enter the issuer name. For more information, refer to [Enable encryption in transit](../../security/enable-encryption-in-transit/add-certificate-kubernetes/). If required, add a new zone by clicking **Add Zone**, as your configuration may have multiple zones. diff --git a/docs/content/preview/yugabyte-platform/create-deployments/connect-to-universe.md b/docs/content/preview/yugabyte-platform/create-deployments/connect-to-universe.md index 004189ced4a9..676fac33bf7c 100644 --- a/docs/content/preview/yugabyte-platform/create-deployments/connect-to-universe.md +++ b/docs/content/preview/yugabyte-platform/create-deployments/connect-to-universe.md @@ -21,13 +21,21 @@ You can connect to the database on a universe in the following ways: ## Download the universe certificate -If the universe uses encryption in transit, to connect you need to first download the universe TLS root certificate. Do the following: +If the universe uses Client-to-Node encryption in transit, to connect you need to first download the universe TLS certificate. Do the following: 1. Navigate to **Configs > Security > Encryption in Transit**. -1. Find the certificate for your universe in the list and click **Actions** and download the certificate. +1. Find your universe in the list. -For more information on connecting to TLS-enabled universes, refer to [Connect to clusters](../../security/enable-encryption-in-transit/#connect-to-clusters). +1. Click **Actions** and choose **Download Root CA Cert**. + + This downloads the `root.crt` file. + +For information on connecting using a client shell using this certificate, see [Connect from your desktop](#connect-from-your-desktop). + +To use TLS to connect an application, refer to the [driver documentation](../../../reference/drivers/). If you are using a PostgreSQL JDBC driver to connect to YugabyteDB, you can also refer to [Configuring the client](https://jdbc.postgresql.org/documentation/head/ssl-client.html) for more details. + +If you are using PostgreSQL/YugabyteDB JDBC driver with SSL, you need to convert the certificates to DER format. To do this, you need to perform only steps 6 and 7 from [Set up SSL certificates for Java applications](../../../reference/drivers/java/postgres-jdbc-reference/#set-up-ssl-certificates-for-java-applications) section after downloading the certificates. ## Connect to a universe node @@ -118,21 +126,9 @@ curl --location --request PUT 'http:///api/v1/customers//runt ### Prerequisites -- If you are using a Yugabyte client shell, ensure you are running the latest versions of the shells (Yugabyte Client 2.6 or later). - - You can download using the following command on Linux or macOS: - - ```sh - $ curl -sSL https://downloads.yugabyte.com/get_clients.sh | bash - ``` - - Windows client shells require Docker. For example: - - ```sh - docker run -it yugabytedb/yugabyte-client ysqlsh -h -p - ``` +- If you are using [ysqlsh](../../../admin/ysqlsh/) or [ycqlsh](../../../admin/ycqlsh/), ensure you are running the latest versions of the shells. -- If your universe has TLS/SSL (encryption in-transit) enabled, you need to [download the certificate](#download-the-universe-certificate) to your computer. +- If your universe has Client-to-Node encryption in transit enabled, you need to [download the certificate](#download-the-universe-certificate) to your computer. - The host address of an endpoint on your universe. @@ -181,7 +177,7 @@ Replace the following: - `` with the IP address of an endpoint on your universe. - `` with your database username. - `yugabyte` with the database name, if you're connecting to a database other than the default (yugabyte). -- `` with the path to the root certificate on your computer. +- `` with the path to the universe root certificate you downloaded to your computer. To load sample data and explore an example using ysqlsh, follow the instructions in [Install the Retail Analytics sample database](../../../sample-data/retail-analytics/#install-the-retail-analytics-sample-database). @@ -203,7 +199,7 @@ Replace the following: - `` with the IP address of an endpoint on your universe. - `` with your database username. -- `` with the path to the root certificate on your computer. +- `` with the path to the universe root certificate you downloaded to your computer.

    8I(k?SW^g(_suTX!eN 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 X^~?#sy>z?4zsnbuF65jyy8C|s(v{CQ literal 241892 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%0D%x|j_1UwRDxxWA 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 z

    @@ -224,7 +220,7 @@ Replace the following: - `` with the IP address of an endpoint on your universe. - `` with your database username. - `yugabyte` with the database name, if you're connecting to a database other than the default (yugabyte). -- `` with the path to the root certificate on your computer. +- `` with the path to the universe root certificate you downloaded to your computer.
    @@ -358,6 +354,7 @@ ycqlsh> SELECT * FROM ybdemo_keyspace.cassandrakeyvalue LIMIT 5; ## Learn more +- [Securing YugabyteDB: Client-to-Server Encryption in Transit](https://www.yugabyte.com/blog/securing-yugabytedb-client-to-server-encryption/#verification-of-server-certificates) - [ysqlsh](../../../admin/ysqlsh/) — Overview of the command line interface (CLI), syntax, and commands. - [YSQL API](../../../api/ysql/) — Reference for supported YSQL statements, data types, functions, and operators. - [ycqlsh](../../../admin/ycqlsh/) — Overview of the command line interface (CLI), syntax, and commands. diff --git a/docs/content/preview/yugabyte-platform/create-deployments/create-universe-multi-zone-kubernetes.md b/docs/content/preview/yugabyte-platform/create-deployments/create-universe-multi-zone-kubernetes.md index 7397c8cc116a..074366e4b4c7 100644 --- a/docs/content/preview/yugabyte-platform/create-deployments/create-universe-multi-zone-kubernetes.md +++ b/docs/content/preview/yugabyte-platform/create-deployments/create-universe-multi-zone-kubernetes.md @@ -71,8 +71,8 @@ Complete the **Security Configurations** section as follows: - **Enable YSQL Auth** - specify whether or not to enable the YSQL password authentication. - **Enable YCQL** - specify whether or not to enable the YCQL API endpoint for running Cassandra-compatible workloads. This setting is enabled by default. - **Enable YCQL Auth** - specify whether or not to enable the YCQL password authentication. -- **Enable Node-to-Node TLS** - specify whether or not to enable encryption-in-transit for communication between the database servers. This setting is enabled by default. -- **Enable Client-to-Node TLS** - specify whether or not to enable encryption-in-transit for communication between clients and the database servers. This setting is enabled by default. +- **Enable Node-to-Node TLS** - specify whether or not to enable encryption in transit for communication between the database servers. This setting is enabled by default. +- **Enable Client-to-Node TLS** - specify whether or not to enable encryption in transit for communication between clients and the database servers. This setting is enabled by default. - **Root Certificate** - select an existing security certificate or create a new one. - **Enable Encryption at Rest** - specify whether or not to enable encryption for data stored on the tablet servers. This setting is disabled by default. @@ -80,7 +80,7 @@ Complete the **Security Configurations** section as follows: Complete the **Advanced** section as follows: -- In the **DB Version** field, specify the YugabyteDB version. The default is either the same as the YugabyteDB Anywhere version or the latest YugabyteDB version available for YugabyteDB Anywhere. +- In the **DB Version** field, specify the YugabyteDB version. The default is either the same as the YugabyteDB Anywhere version or the latest YugabyteDB version available for YugabyteDB Anywhere. If the version you want to add is not listed, you can add it to YugabyteDB Anywhere. Refer to [Manage YugabyteDB releases](../../manage-deployments/ybdb-releases/). - Use the **Enable IPV6** field to specify whether or not you want to use IPV6 networking for connections between database servers. This setting is disabled by default. - Use the **Enable Public Network Access** field to specify whether or not to assign a load balancer or nodeport for connecting to the database endpoints over the internet. This setting is disabled by default. diff --git a/docs/content/preview/yugabyte-platform/create-deployments/create-universe-multi-zone.md b/docs/content/preview/yugabyte-platform/create-deployments/create-universe-multi-zone.md index edc814f8f993..61e6b6bb6c76 100644 --- a/docs/content/preview/yugabyte-platform/create-deployments/create-universe-multi-zone.md +++ b/docs/content/preview/yugabyte-platform/create-deployments/create-universe-multi-zone.md @@ -84,19 +84,42 @@ Specify the instance to use for the universe nodes: ### Security Configurations +#### IP Settings + To enable public access to the universe, select the **Assign Public IP** option. -Enable the YSQL and YCQL endpoints and database authentication. You can also enable and disable authentication after deployment. Navigate to your universe, click **Actions**, and choose **Edit YSQL Configuration** or **Edit YCQL Configuration**. +#### Authentication Settings + +Enable the YSQL and YCQL endpoints and database authentication. Enter the password to use for the default database admin superuser (yugabyte for YSQL, and cassandra for YCQL). For more information, refer to [Database authorization](../../security/authorization-platform/). -Enable encryption in transit to encrypt universe traffic. Refer to [Enable encryption in transit](../../security/enable-encryption-in-transit/). +You can also enable and disable the API endpoints and authentication after deployment. Navigate to your universe, click **Actions**, and choose **Edit YSQL Configuration** or **Edit YCQL Configuration**. + +By default, the API endpoints use ports 5433 (YSQL) and 9042 (YCQL). You can [customize these ports](#advanced-configuration), and, after deployment, you can modify the YCQL API and admin UI endpoint ports. To change YCQL ports, navigate to your universe, click **Actions**, choose **Edit YCQL Configuration**, and select the **Override YCQL Default Ports** option. + +#### Encryption Settings + +Enable encryption in transit to encrypt universe traffic. You can enable the following: + +- **Node-to-Node TLS** to encrypt traffic between universe nodes. +- **Client-to-Node TLS** to encrypt traffic between universe nodes and external clients. + + Note that if you want to enable Client-to-Node encryption, you first must enable Node-to-Node encryption. + +Encryption requires a certificate. YugabyteDB Anywhere can generate a self-signed certificate automatically, or you can use your own certificate. + +To use your own, you must first add it to YugabyteDB Anywhere; refer to [Add certificates](../../security/enable-encryption-in-transit/add-certificate-self/). + +To have YugabyteDB Anywhere generate a certificate for the universe, use the default **Root Certificate** setting of **Create New Certificate**. To use a certificate you added or a previously generated certificate, select it from the **Root Certificate** menu. + +For more information on using and managing certificates, refer to [Encryption in transit](../../security/enable-encryption-in-transit/). -Enable encryption at rest to encrypt the universe data. Refer to [Enable encryption at rest](../../security/enable-encryption-at-rest/). +To encrypt the universe data, select the **Enable encryption at rest** option and select the [KMS configuration](../../security/create-kms-config/aws-kms/) to use for encryption. For more information, refer to [Encryption at rest](../../security/enable-encryption-at-rest/). ### Advanced Configuration -Choose the version of YugabyteDB to install on the nodes. +Choose the version of YugabyteDB to install on the nodes. If the version you want to add is not listed, you can add it to YugabyteDB Anywhere. Refer to [Manage YugabyteDB releases](../../manage-deployments/ybdb-releases/). The access key is the SSH key that is created in the provider. Usually, each provider has its own access key, but if you are reusing keys across providers, they are listed here. @@ -104,7 +127,7 @@ For AWS providers, you can assign an ARN to the nodes in the universe; this allo To use cron instead of systemd for managing nodes, you can disable systemd services. This not recommended. -To customize the ports used for the universe, select the **Override Deployment Ports** option and enter the custom port numbers for the services you want to change. +To customize the [ports used for the universe](../../prepare/networking/), select the **Override Deployment Ports** option and enter the custom port numbers for the services you want to change. Any value from `1024` to `65535` is valid, as long as it doesn't conflict with anything else running on nodes to be provisioned. ### G-Flags diff --git a/docs/content/preview/yugabyte-platform/manage-deployments/edit-universe.md b/docs/content/preview/yugabyte-platform/manage-deployments/edit-universe.md index da7a4534c467..f6a7b77cffb1 100644 --- a/docs/content/preview/yugabyte-platform/manage-deployments/edit-universe.md +++ b/docs/content/preview/yugabyte-platform/manage-deployments/edit-universe.md @@ -40,7 +40,7 @@ YugabyteDB Anywhere performs these modifications through the [YB-Masters](../../ Note that you can't change the replication factor of a universe. -To change the number of nodes of universes created with an on-premises cloud provider and secured with third-party certificates obtained from external certification authorities, follow the instructions in [Expand the universe](../../security/enable-encryption-in-transit#expand-the-universe). +To change the number of nodes of universes created with an on-premises cloud provider and secured with third-party certificates obtained from external certification authorities, you must first add the certificates to the nodes you will add to the universe. Refer to [Add certificates](../../security/enable-encryption-in-transit/add-certificate-ca/). Ensure that the certificates are signed by the same external CA and have the same root certificate. In addition, ensure that you copy the certificates to the same locations that you originally used when creating the universe. ### Smart resize diff --git a/docs/content/preview/yugabyte-platform/prepare/networking.md b/docs/content/preview/yugabyte-platform/prepare/networking.md index 4049dc352211..f41f43bb2442 100644 --- a/docs/content/preview/yugabyte-platform/prepare/networking.md +++ b/docs/content/preview/yugabyte-platform/prepare/networking.md @@ -18,7 +18,7 @@ YugabyteDB Anywhere (YBA) needs to be able to access nodes that will be used to ![YugabyteDB Anywhere network and ports](/images/yb-platform/prepare/yba-networking.png) -The following ports need to be open. (The default port numbers can be customized.) +The following ports need to be open. | From | To | Requirements | | :--- | :--- | :--- | diff --git a/docs/content/preview/yugabyte-platform/security/_index.md b/docs/content/preview/yugabyte-platform/security/_index.md index 67b92d4da7fd..297937b36426 100644 --- a/docs/content/preview/yugabyte-platform/security/_index.md +++ b/docs/content/preview/yugabyte-platform/security/_index.md @@ -5,6 +5,10 @@ linkTitle: Security description: Secure YugabyteDB Anywhere and YugabyteDB universes. image: /images/section_icons/index/secure.png headcontent: Secure YugabyteDB Anywhere and your YugabyteDB universes. +aliases: + - /preview/yugabyte-platform/security/network-security/ + - /preview/yugabyte-platform/security/customize-ports/ + - /preview/yugabyte-platform/security/security-checklist-yp/ menu: preview_yugabyte-platform: parent: yugabytedb-anywhere @@ -13,48 +17,51 @@ weight: 660 type: indexpage --- -{{}} - - {{}} - - {{}} - - {{}} - - {{}} - - {{}} - - {{}} - - {{}} - -{{}} +You can apply security measures to protect your YugabyteDB Anywhere instance and YugabyteDB universes. + +## Network security + +You need to ensure that YugabyteDB Anywhere and the database run in a trusted network environment. You should restrict machine and port access, based on the following guidelines: + +- Servers running YugabyteDB services are directly accessible only by YugabyteDB Anywhere, servers running the application, and database administrators. +- Only YugabyteDB Anywhere and servers running applications can connect to YugabyteDB services on the RPC ports. Access to the YugabyteDB ports should be denied to everybody else. + +{{}} +For information on networking and port requirements, refer to [Networking](../prepare/networking/). +{{}} + +## Database authentication + +Authentication requires that all clients provide valid credentials before they can connect to a YugabyteDB universe. The authentication credentials in YugabyteDB are stored internally in the YB-Master system tables. The authentication mechanisms available to users depends on what is supported and exposed by the YSQL and YCQL APIs. + +You enable authentication for the YSQL and YCQL APIs when you deploy a universe. See [Enable database authentication](authorization-platform/#enable-database-authentication). + +YugabyteDB Anywhere and YugabyteDB also support LDAP and OIDC for managing authentication. See [Database authentication](authentication/). + +For more information on authentication in YugabyteDB, see [Enable authentication](../../secure/enable-authentication/). + +## Role-based access control + +Roles can be assigned to grant users only the essential privileges based on the operations they need to perform in YugabyteDB Anywhere, and in YugabyteDB universes. + +To manage access to your YugabyteDB Anywhere instance, typically you create a [Super Admin role first](../install-yugabyte-platform/create-admin-user/). The Super Admin can create additional admins and other users with fewer privileges. For information on how to manage YugabyteDB Anywhere users and roles, see [Manage YugabyteDB Anywhere users](../administer-yugabyte-platform/anywhere-rbac/). + +For information on how to manage database roles and users, see [Database authorization](authorization-platform/). + +## Encryption in transit + +Encryption in transit (TLS) ensures that network communication between servers is secure. You can configure YugabyteDB to use TLS to encrypt intra-cluster (Node-to-Node) and client to server (Client-to-Node) network communication. You should enable encryption in transit in YugabyteDB universes and clients to ensure the privacy and integrity of data transferred over the network. + +{{}} +For more information, see [Encryption in transit](enable-encryption-in-transit/). +{{}} + +## Encryption at rest + +Encryption at rest ensures that data at rest, stored on disk, is protected. You can configure YugabyteDB universes with a user-generated symmetric key to perform universe-wide encryption. + +Encryption at rest in YugabyteDB Anywhere uses a master key to encrypt and decrypt universe keys. The master key details are stored in YugabyteDB Anywhere in [key management service (KMS) configurations](create-kms-config/aws-kms/). You enable encryption at rest for a universe by assigning the universe a KMS configuration. The master key designated in the configuration is then used for generating the universe keys used for encrypting the universe data. + +{{}} +For more information, see [Enable encryption at rest](enable-encryption-at-rest/). +{{}} diff --git a/docs/content/preview/yugabyte-platform/security/authorization-platform.md b/docs/content/preview/yugabyte-platform/security/authorization-platform.md index 4cfa768ac1b1..84c0c7432b10 100644 --- a/docs/content/preview/yugabyte-platform/security/authorization-platform.md +++ b/docs/content/preview/yugabyte-platform/security/authorization-platform.md @@ -21,17 +21,17 @@ YugabyteDB uses [role-based access control](../../../secure/authorization/) (RBA (For information on managing access to your YugabyteDB Anywhere instance, refer to [Manage account users](../../administer-yugabyte-platform/anywhere-rbac/).) -## Enable database authentication +## Enable database authorization You enable the YSQL and YCQL endpoints and database authentication when deploying a universe. -On the **Create Universe > Primary Cluster** page, under **Security Configurations**, enable the **Authentication Settings** for the APIs you want to use, as shown in the following illustration. +On the **Create Universe > Primary Cluster** page, under **Security Configurations > Authentication Settings**, enable the endpoints and authorization for the APIs you want to use, as shown in the following illustration. ![Enable YSQL and YCQL endpoints](/images/yp/security/enable-endpoints.png) Enter the password to use for the default database admin superuser (`yugabyte` for YSQL, and `cassandra` for YCQL). -You can also enable and disable the endpoints and authentication after deployment. Navigate to your universe, click **Actions**, and choose **Edit YSQL Configuration** or **Edit YCQL Configuration**. +You can also enable and disable the endpoints and authorization after deployment. Navigate to your universe, click **Actions**, and choose **Edit YSQL Configuration** or **Edit YCQL Configuration**. ## Default roles and users diff --git a/docs/content/preview/yugabyte-platform/security/customize-ports.md b/docs/content/preview/yugabyte-platform/security/customize-ports.md deleted file mode 100644 index 14dc4f785a6b..000000000000 --- a/docs/content/preview/yugabyte-platform/security/customize-ports.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Configure ports -headerTitle: Configure ports -linkTitle: Configure ports -description: Configure ports -menu: - preview_yugabyte-platform: - parent: security - identifier: customize-ports - weight: 20 -type: docs ---- - -YugabyteDB Anywhere and the universes it manages use a set of [default ports](../../prepare/networking/) to manage access to services. - -When deploying a universe, YugabyteDB Anywhere allows you to customize these ports. - -## Customize ports - -On the **Create Universe > Primary Cluster** page, under **Advanced Configuration**, enable the **Override Deployment Ports** option, as shown in the following illustration: - -![Override Deployment Ports](/images/yp/security/override-deployment-ports.png) - -Replace the default values with the values identifying the port that each process should use. Any value from `1024` to `65535` is valid, as long as this value does not conflict with anything else running on nodes to be provisioned. - -After deployment, you can modify the YCQL API and admin UI endpoint ports. To change ports, navigate to your universe, click **Actions**, choose **Edit YCQL Configuration**, and select the **Override YCQL Default Ports** option. - -If you change the YCQL API endpoint on an active universe, be sure to update your applications as appropriate. diff --git a/docs/content/preview/yugabyte-platform/security/enable-encryption-at-rest.md b/docs/content/preview/yugabyte-platform/security/enable-encryption-at-rest.md index 5a45fce41668..4258add7b3ef 100644 --- a/docs/content/preview/yugabyte-platform/security/enable-encryption-at-rest.md +++ b/docs/content/preview/yugabyte-platform/security/enable-encryption-at-rest.md @@ -1,8 +1,9 @@ --- -title: Enable encryption at rest -headerTitle: Enable encryption at rest -linkTitle: Enable encryption at rest -description: Enable encryption at rest +title: Encryption at rest in YugabyteDB Anywhere +headerTitle: Encryption at rest +linkTitle: Encryption at rest +description: Use encryption at rest in YugabyteDB Anywhere +headcontent: Encrypt your universes menu: preview_yugabyte-platform: parent: security @@ -18,7 +19,7 @@ YugabyteDB Anywhere uses the following types of keys for envelope encryption: | Key | Description | | :--- | :--- | | Data encryption keys (DEK) | Symmetric keys used to directly encrypt the data. Each file flushed from memory has a unique DEK. This key is generated in the database layer of YugabyteDB. | -| Universe key | Symmetric key used to encrypt and decrypt DEKs. A single universe key is used for all the DEKs in a universe. This key is generated by YugabyteDB Anywhere. +| Universe key | Symmetric key used to encrypt and decrypt DEKs. A single universe key is used for all the DEKs in a universe. This key is generated by YugabyteDB Anywhere. | | Master key | The key at the highest level in the key hierarchy. The master key is used to encrypt universe keys. This key is a customer managed key (CMK) stored and managed in a Key Management Service (KMS). | Master key details are stored in YugabyteDB Anywhere in KMS configurations, and YugabyteDB Anywhere supports CMKs in AWS KMS, GCP KMS, Azure Key Vault, and Hashicorp Vault. You enable encryption at rest for a universe by assigning the universe a KMS configuration. For instructions on creating a KMS configuration, see [Create a KMS configuration](../create-kms-config/aws-kms/). diff --git a/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit.md b/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit.md deleted file mode 100644 index fe140665b8f7..000000000000 --- a/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit.md +++ /dev/null @@ -1,704 +0,0 @@ ---- -title: Enable encryption in transit -headerTitle: Enable encryption in transit -linkTitle: Enable encryption in transit -description: Use YugabyteDB Anywhere to enable encryption in transit (TLS) on a YugabyteDB universe and connect to clients. -menu: - preview_yugabyte-platform: - parent: security - identifier: enable-encryption-in-transit - weight: 40 -rightNav: - hideH4: true -type: docs ---- - -YugabyteDB Anywhere allows you to protect data in transit by using the following: - -- Server-to-server encryption for intra-node communication between YB-Master and YB-TServer nodes. -- Client-to-server encryption for communication between clients and nodes when using CLIs, tools, and APIs for YSQL and YCQL. -- Encryption for communication between YugabyteDB Anywhere and other services, including LDAP, OIDC, Hashicorp Vault, Webhook, and S3 backup storage. - -{{< note title="Note" >}} - -Before you can enable client-to-server encryption, you first must enable server-to-server encryption. - -{{< /note >}} - -YugabyteDB Anywhere lets you create a new self-signed certificate, use an existing self-signed certificate, or upload a third-party certificate from external providers, such as Venafi or DigiCert (which is only available for an on-premises cloud provider). - -You can enable encryption in transit (TLS) during universe creation and change these settings for an existing universe. - -## Self-signed certificates generated by YugabyteDB Anywhere - -YugabyteDB Anywhere can create self-signed certificates for each universe. These certificates may be shared between universes in a single instance of YugabyteDB Anywhere. The certificate name has the following format: - -`yb-environment-universe_name`, where *environment* is the environment type (either `dev`, `stg`, `demo`, or `prod`) that was used during the tenant registration (admin user creation), and *universe-name* is the provided universe name. YugabyteDB Anywhere generates the root certificate, root private key, and node-level certificates (assuming node-to-node encryption is enabled), and then provisions those artifacts to the database nodes any time nodes are created or added to the cluster. The following three files are copied to each node: - -1. The root certificate (`ca.cert`). -1. The node certificate (`node.ip_address.crt`). -1. The node private key (`node.ip_address.key`). - -YugabyteDB Anywhere retains the root certificate and the root private key for all interactions with the cluster. - -### Customize the organization name in self-signed certificates - -YugabyteDB Anywhere automatically creates self-signed certificates when you run some workflows, such as create universe. The organization name in certificates is set to `example.com` by default. - -If you are using YugabyteDB Anywhere version 2.18.2 or later to manage universes with YugabyteDB version 2.18.2 or later, you can set a custom organization name using the global [runtime configuration](../../administer-yugabyte-platform/manage-runtime-config/) flag, `yb.tlsCertificate.organizationName`. - -Note that, for the change to take effect, you need to set the flag _before_ you run a workflow that generates a self-signed certificate. - -Customize the organization name as follows: - -1. In YugabyteDB Anywhere, navigate to **Admin** > **Advanced** and select the **Global Configuration** tab. -1. In the **Search** bar, enter `yb.tlsCertificate.organizationName` to view the flag, as per the following illustration: - - ![Custom Organization name](/images/yp/encryption-in-transit/custom-org-name.png) - -1. Click **Actions** > **Edit Configuration**, enter a new Config Value, and click **Save**. - -#### Validate custom organization name - -You can verify the organization name by running the following `openssl x509` command: - -```sh -openssl x509 -in ca.crt -text -``` - -```output {hl_lines=[6]} -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 1683277970271 (0x187eb2f7b5f) - Signature Algorithm: sha256WithRSAEncryption - Issuer: CN=yb-dev-sb-ybdemo-univ1~2, O=example.com - Validity - Not Before: May 5 09:12:50 2023 GMT - Not After : May 5 09:12:50 2027 GMT -``` - -Notice that default value is `O=example.com`. - -After setting the runtime configuration to a value of your choice, (`org-foo` in this example), you should see output similar to the following: - -```sh -openssl x509 -in ca.crt -text -noout -``` - -```output -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 1689376612248 (0x18956b15f98) - Signature Algorithm: sha256WithRSAEncryption - Issuer: CN = yb-dev-sb-ybdemo-univ1~2, O = org-foo - Validity - Not Before: Jul 14 23:16:52 2023 GMT - Not After : Jul 14 23:16:52 2027 GMT - Subject: CN = yb-dev-sb-ybdemo-univ1~2, O = org-foo - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - Public-Key: (2048 bit) - Modulus: -``` - -### Use YugabyteDB Anywhere-generated certificates to enable TLS - -When you create a universe, you can enable TLS using certificates generated by YugabyteDB Anywhere, as follows: - -1. Create a new universe via **Universes > Create Universe** and then configure it. -1. Based on your requirements, select **Enable Node-to-Node TLS** or **Enable Client-to-Node TLS** or both. -1. Choose an existing certificate from the **Root Certificate** list or create a new certificate by accepting the default option **Create new certificate**. - -To view the certificate, navigate to **Configs > Security > Encryption in Transit > Self Signed**. - -You can also modify TLS settings for an existing universe, as follows: - -1. Navigate to either **Dashboard** or **Universes** and open a specific universe. - -1. Click **Actions > Edit Security > Encryption in-Transit** to open the **TLS Configuration** dialog and then proceed as follows: - - - If encryption in transit is currently disabled for the universe, enable it via the **Encryption in Transit for this Universe** field, as per the following illustration: - - ![TLS Configuration](/images/yp/encryption-in-transit/tls-config1.png) - - Use the expanded **TLS Configuration** dialog shown in the following illustration to change the settings to meet your requirements: - - ![TLS Configuration Expanded](/images/yp/encryption-in-transit/tls-config2.png) - - - If encryption in transit is currently enabled for the universe, you can either disable or modify it, as follows: - - - To disable encryption in transit, disable the **Encryption in Transit for this Universe** field and then click **OK**. - - - To modify encryption in-transit settings, leave the **Encryption in Transit for this Universe** field enabled and make the necessary changes to other fields. - - If you are changing certificates, you need to be aware that this requires restart of the YB-Master and YB-TServer processes and can result in downtime. To avoid downtime, you should accept the default value (enabled) for the **Rolling Upgrade** field to trigger a sequential node-by-node change with a specific delay between node upgrades (as opposed to a simultaneous change of certificates in every node which occurs when the **Rolling Upgrade** field is disabled). If you select the **Create new certificate** option when changing certificates, the corresponding certificates will be rotated, that is, replaced with new certificates. - -## Self-signed self-provided certificates - -Instead of using YugabyteDB Anywhere-provided certificates, you can use your own self-signed certificates that you upload to YugabyteDB Anywhere by following the procedure described in [Use self-signed self-provided certificates to enable TLS](#use-self-signed-self-provided-certificates-to-enable-tls). - -The certificates must meet the following criteria: - -- Be in the `.crt` format and the private key must be in the `.pem` format, with both of these artifacts available for upload. -- Contain IP addresses of the target database nodes or DNS names as the Subject Alternative Names (wildcards are acceptable). - -YugabyteDB Anywhere produces the node (leaf) certificates from the uploaded certificates and copies the certificate chain, leaf certificate, and private key to the nodes in the cluster. - -### Use self-signed self-provided certificates to enable TLS - -When you create a universe, you can enable TLS using your own certificates, as follows: - -1. Navigate to **Configs > Security > Encryption in Transit**. -1. Click **Add Certificate** to open the **Add Certificate** dialog. -1. Select **Self Signed**. -1. Click **Upload Root Certificate**, then browse to the root certificate file (`.crt`) and upload it. -1. Click **Upload Key**, then browse to the root certificate file (`.key`) and upload it. -1. In the **Certificate Name** field, enter a meaningful name for your certificate. -1. In the **Expiration Date** field, specify the expiration date of the root certificate. To find this information, execute the `openssl x509 -in -text -noout` command and note the **Validity Not After** date. -1. Click **Add** to make the certificate available. -1. Go to **Universes > Create Universe** to open the **Create Universe** dialog. -1. Configure the universe. -1. Based on your requirements, select **Enable Node-to-Node TLS** and **Enable Client-to-Node TLS**. -1. Select an existing certificate from the **Root Certificate** list and then select the certificate that you have uploaded. -1. Create the universe. - -You can also modify TLS settings for an existing universe by navigating to **Universes**, opening a specific universe, clicking **Actions > Edit Security > Encryption in-Transit** to open the **TLS Configuration** dialog, and then following the procedure described in [Use YugabyteDB Anywhere-generated certificates to enable TLS](#use-yugabytedb-anywhere-generated-certificates-to-enable-tls) for an existing universe. - -## Custom CA-signed self-provided certificates - -For universes created with an on-premise cloud provider, instead of using self-signed certificates, you can use third-party certificates from external CAs. The third-party CA root certificate must be configured in YugabyteDB Anywhere. You have to copy the custom CA root certificate, node certificate, and node key to the appropriate database nodes using the procedure described in [Use custom CA-signed certificates to enable TLS](#use-custom-ca-signed-certificates-to-enable-tls). - -The certificates must adhere to the following criteria: - -- Be stored in a `.crt` file, with both the certificate and the private key being in the PEM format. - - If your certificates and keys are stored in the PKCS12 format, you can [convert them to the PEM format](#convert-certificates-and-keys-from-pkcs12-to-pem-format). - -- Contain IP addresses of the database nodes or DNS names as the Subject Alternative Names (wildcards are acceptable). - -### Use custom CA-signed certificates to enable TLS - -The following procedure describes how to install certificates on the database nodes. You have to repeat these steps for every database node that is to be used in the creation of a universe. - -**Step 1:** Obtain the keys and the custom CA-signed certificates for each of the on-premise nodes for which you are configuring node-to-node TLS. In addition, obtain the keys and the custom signed certificates for client access for configuring client-to-node TLS. - -**Step 2**: For each on-premise node, copy the custom CA root certificate, node certificate, and node key to that node's file system. - -If you are enabling client-to-node TLS, make sure to copy the client certificate and client key to each of the nodes. - -In addition, ensure the following: - -- The file names and file paths of different certificates and keys are identical across all the database nodes. For example, if you name your CA root certificate as `ca.crt` on one node, then you must name it `ca.crt` on all the nodes. Similarly, if you copy `ca.crt` to `/opt/yugabyte/keys` on one node, then you must copy `ca.crt` to the same path on other nodes. -- The yugabyte system user has read permissions to all the certificates and keys. - -**Step 3**: Create a CA-signed certificate in YugabyteDB Anywhere, as follows: - -1. Navigate to **Configs > Security > Encryption in Transit**. - -1. Click **Add Certificate** to open the **Add Certificate** dialog. - -1. Select **CA Signed**, as per the following illustration: - - ![add-cert](/images/yp/encryption-in-transit/add-cert.png) - -1. Upload the custom CA root certificate as the root certificate. - - If you use an intermediate CA/issuer, but do not have the complete chain of certificates, then you need to create a bundle by executing the `cat intermediate-ca.crt root-ca.crt > bundle.crt` command, and then use this bundle as the root certificate. You might also want to [verify the certificate chain](#verify-certificate-chain). - -1. Enter the file paths for each of the certificates on the nodes. These are the paths from the previous step. - -1. In the **Certificate Name** field, enter a meaningful name for your certificate. - -1. Use the **Expiration Date** field to specify the expiration date of the certificate. To find this information, execute the `openssl x509 -in -text -noout` command and note the **Validity Not After** date. - -1. Click **Add** to make the certificate available. - -1. Go to **Universes > Create Universe** to open the **Create Universe** dialog. - -1. Configure the universe. - -1. Based on your requirements, select **Enable Node-to-Node TLS** and **Enable Client-to-Node TLS**. - -1. Select an existing certificate from the **Root Certificate** list and then select the certificate that you have uploaded. - -1. Create the universe. - -You can rotate certificates for universes configured with the same type of certificates. This involves replacing existing certificates with new database node certificates. - -#### Convert certificates and keys from PKCS12 to PEM format - -If your certificates and keys are stored in the PKCS12 format, you can convert them to the PEM format using OpenSSL. - -You start by extracting the certificate via the following command: - -```sh -openssl pkcs12 -in cert-archive.pfx -out cert.pem -clcerts -nokeys -``` - -To extract the key and write it to the PEM file unencrypted, execute the following command: - -```sh -openssl pkcs12 -in cert-archive.pfx -out key.pem -nocerts -nodes -``` - -If the key is protected by a passphrase in the PKCS12 archive, you are prompted for the passphrase. - -#### Verify certificate chain - -Perform the following steps to verify your certificates: - -1. Execute the following verify command which checks the database node certificate (node.crt) against the root CA certificate (ca.crt): - - ```sh - openssl verify ca.crt node.crt - ``` - -1. Verify that the node certificate (`node.crt`) and the node private key (`node.key`) match. See [How do I verify that a private key matches a certificate?](https://www.ssl247.com/knowledge-base/detail/how-do-i-verify-that-a-private-key-matches-a-certificate-openssl-1527076112539/ka03l0000015hscaay/) - -1. Verify that the node certificate and Root CA certificate expiration is at least 3 months by checking the validity field in the output of the following commands: - - ```sh - openssl x509 -in node.crt -text -noout - ``` - - ```sh - openssl x509 -in ca.crt -text -noout - ``` - -1. Verify that the node certificate Common Name (CN) or Subject Alternate Name (SAN) contains the IP address or DNS name of each on-prem node on which the nodes are deployed. - - {{< note >}} -Each entry you provide for the CN or SAN must match the on-prem node as entered in the provider configuration. For example, if the node address is entered as a DNS address in the on-prem provider configuration, you must use the same DNS entry in the CN or SAN, not the resolved IP address. - {{< /note >}} - - If you face any issue with the above verification, you can customize the level of certificate validation while creating a universe that uses these certificates. Refer to [Customizing the verification of RPC server certificate by the client](https://www.yugabyte.com/blog/yugabytedb-server-to-server-encryption/#customizing-the-verification-of-rpc-server-certificate-by-the-client). - -{{< note >}} -The client certificates and keys are required only if you intend to use [PostgreSQL certificate-based authentication](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html#:~:text=independent%20authentication%20option-,clientcert,-%2C%20which%20can%20be). -{{< /note >}} - -### Rotate custom CA-signed certificates - -You can rotate certificates for universes configured with the same type of certificates. This involves replacing existing certificates with new database node certificates. - -You rotate the existing custom certificates and replace them with new database node certificates issued by the same custom CA that issued the original certificates as follows: - -**Step 1**: Follow Step 1 of [Use custom CA-signed certificates to enable TLS](#use-custom-ca-signed-certificates-to-enable-tls) to obtain a new set of certificates for each of the nodes. - -**Step 2**: Follow Step 2 of [Use custom CA-signed certificates to enable TLS](#use-custom-ca-signed-certificates-to-enable-tls) to copy the certificates to the respective nodes. - -**Step 3**: Follow Step 3 of [Use custom CA-signed certificates to enable TLS](#use-custom-ca-signed-certificates-to-enable-tls) to create a new CA-signed certificate in YugabyteDB Anywhere. - -**Step 4**: Edit the universe to use the new certificates, as follows: - -- Navigate to the universe for which you are rotating the keys. - -- Select **Actions > Edit Security**, as shown in the following illustration: - - ![edit-security](/images/yp/encryption-in-transit/edit-security.png) - -- Select **Encryption in-Transit** to open the **TLS Configuration** dialog. - -- Complete the **TLS Configuration** dialog shown in the following illustration: - - ![Configure TLS](/images/yp/encryption-in-transit/edit-tls-new.png) - - - Select the new certificate which you created in Step 3. - - - Modifying certificates requires restart of YB-Master and YB-TServer processes, which can result in downtime. To avoid downtime, you should accept the default value (enabled) for the **Rolling Upgrade** field to trigger a sequential node-by-node change with a specific delay between node upgrades (as opposed to a simultaneous change of certificates in every node which occurs when the **Rolling Upgrade** field is disabled). - - - Click **OK**. - - Typically, this process takes time, as it needs to wait for the specified delay interval after each node is upgraded. - -### Expand the universe - -You can expand universes configured with custom CA-signed certificates. - -Before adding new nodes to expand an existing universe, you need to prepare those nodes by repeating Step 2 of [Use custom CA-signed certificates to enable TLS](#use-custom-ca-signed-certificates-to-enable-tls) for each of the new nodes you plan to add to the universe. You need to ensure that the certificates are signed by the same external CA and have the same root certificate. In addition, ensure that you copy the certificates to the same locations that you originally used when creating the universe. - -When the universe is ready for expansion, complete the **Edit Universe** dialog to add new nodes. - -## Custom HashiCorp Vault-provided certificates - -YugabyteDB Anywhere allows you to add an encryption in transit configuration using HashiCorp Vault with a public key infrastructure (PKI) secret engine. This configuration can be used to enable TLS for different clusters and YugabyteDB instances. You can apply this configuration to node-to-node encryption, client-to-node encryption, or both. - -For the correct configuration, the following criteria must be met: - -- HashiCorp Vault is unsealed. - -- HashiCorp Vault with the PKI secret engine is configured and enabled. -- HashiCorp Vault URL is accessible by YugabyteDB Anywhere. -- Because HashiCorp Vault is accessed via an authentication token mechanism, a token must be created beforehand while creating a key provider with appropriate permissions. -- HashiCorp Vault needs to be running and always accessible to YugabyteDB Anywhere. -- HashiCorp PKI certificate revocation list (CRL) or CA URLs must be accessible from each node server. -- Appropriate certificates and roles have been created for YugabyteDB Anywhere usage. -- Node servers are able to validate certificates. -- Required permissions have been provided to perform various key management operations. - -### Configure HashiCorp Vault - -Before you can start configuring HashiCorp Vault, install it on a virtual machine, as per instructions provided in [Install Vault](https://www.vaultproject.io/docs/install). The vault can be set up as a multi-node cluster. Ensure that your vault installation meets the following requirements: - -- Has transit secret engine enabled. -- Its seal and unseal mechanism is secure and repeatable. -- Its token creation mechanism is repeatable. - -You need to configure HashiCorp Vault in order to use it with YugabyteDB Anywhere, as follows: - -1. Create a vault configuration file that references your nodes and specifies the address, as follows: - - ```properties - storage "raft" { - path = "./vault/data/" - node_id = "node1" - } - - listener "tcp" { - address = "127.0.0.1:8200" - tls_disable = "true" - } - - api_addr = "http://127.0.0.1:8200" - cluster_addr = "https://127.0.0.1:8201" - ui = true - disable_mlock = true - default_lease_ttl = "768h" - max_lease_ttl = "8760h" - ``` - - Replace `127.0.0.1` with the vault web address. - - For additional configuration options, see [Parameters](https://www.vaultproject.io/docs/configuration#parameters). - -1. Initialize the vault server by following instructions provided in [Operator init](https://www.vaultproject.io/docs/commands/operator/init). - -1. Allow access to the vault by following instructions provided in [Unsealing](https://www.vaultproject.io/docs/concepts/seal#unsealing). - -1. Enable the secret engine by executing the following command: - - ```shell - vault secrets enable pki - ``` - -1. Configure the secret engine, as follows: - - - Create a root CA or configure the top-level CA. - - - Optionally, create an intermediate CA chain and sign them. - - - Create an intermediate CA for YugabyteDB, as per the following example: - - ```sh - export pki=pki - export pki_int="pki_int" - export role_i=RoleName - export ip="s.test.com" - - vault secrets enable -path=$pki_int pki - vault secrets tune -max-lease-ttl=43800h $pki_int - vault write $pki_int/intermediate/generate/internal common_name="test.com Intermediate Authority" ttl=43800h -format=json | jq -r '.data.csr' > pki_int.csr - - \# *** dump the output of the preceding command in pki_int.csr - - vault write $pki/root/sign-intermediate csr=@pki_int.csr format=pem_bundle ttl=43800h -format=json | jq -r .data.certificate > i_signed.pem - - \# *** dump the output in i_signed.pem - - vault write $pki_int/intermediate/set-signed certificate=@i_signed.pem - vault write $pki_int/config/urls issuing_certificates="http://127.0.0.1:8200/v1/pki_int/ca" crl_distribution_points="http://127.0.0.1:8200/v1/pki_int/crl" - ``` - -1. Create the vault policy, as per the following example: - - ```properties - # Enable secrets engine - path "sys/mounts/*" { - capabilities = ["create", "read", "update", "delete", "list"] - } - - # List enabled secrets engine - path "sys/mounts" { - capabilities = ["read", "list"] - } - - # Work with pki secrets engine - path "pki*" { - capabilities = ["create", "read", "update", "delete", "list", "sudo"] - } - ``` - -1. Generate a token with appropriate permissions (as per the referenced policy) by executing the following command: - - ```shell - vault token create -no-default-policy -policy=pki_policy - ``` - - You may also specify the following for your token: - - - `ttl` — Time to live (TTL). If not specified, the default TTL of 32 days is used, which means that the generated token will expire after 32 days. - - `period` — If specified, the token can be infinitely renewed. - - YugabyteDB Anywhere automatically tries to renew the token every 12 hours after it has passed 70% of its expiry window; as a result, you should set the TTL or period to be greater than 12 hours. - - For more information, refer to [Tokens](https://developer.hashicorp.com/vault/tutorials/tokens/tokens) in the Hashicorp documentation. - -1. Create a role that maps a name in the vault to a procedure for generating a certificate, as follows: - - ```sh - vault write /roles/ allow_any_name=true allow_subdomains=true max_ttl="8640h" - ``` - - Credentials are generated against this role. - -1. Issue certificates for nodes or a YugabyteDB client: - - - For a node, execute the following: - - ```sh - vault write /issue/ common_name="" ip_sans="" ttl="860h" - ``` - - - For YugabyteDB client, execute the following: - - ```sh - vault write /issue/ common_name="" - ``` - -### Use HashiCorp Vault-provided certificates to enable TLS - -When you create a universe, you can enable TLS using certificates provided by HashiCorp Vault, as follows: - -1. Navigate to **Configs > Security > Encryption in Transit**. -1. Click **Add Certificate** to open the **Add Certificate** dialog. -1. Select **Hashicorp**. -1. In the **Config Name** field, enter a meaningful name for your configuration. -1. In the **Vault Address** field, specify a valid URL that includes the port number. The format is `http://0.0.0.0:0000`, which corresponds to `VAULT_HOSTNAME:0000` -1. In the **Secret Token** field, specify the secret token for the vault. -1. In the **Role** field, specify the role used for creating certificates. -1. Optionally, provide the secret engine path on which the PKI is mounted. If you do not supply this information, `pki/` will be used. -1. Click **Add** to make the certificate available. -1. Go to **Universes > Create Universe** to open the **Create Universe** dialog. -1. Configure the universe. -1. Based on your requirements, select **Enable Node-to-Node TLS** and **Enable Client-to-Node TLS**. -1. Select an existing certificate from the **Root Certificate** list and then select the certificate that you have uploaded. -1. Create the universe. - -You can also edit TLS settings for an existing universe by navigating to **Universes**, opening a specific universe, clicking **Actions > Edit Security > Encryption in-Transit** to open the **TLS Configuration** dialog, and then modifying the required settings. - -## Kubernetes cert-manager - -For a universe created on Kubernetes, YugabyteDB Anywhere allows you to configure an existing running instance of the [cert-manager](https://cert-manager.io/) as a TLS certificate provider for a cluster, assuming that the following criteria are met: - -- The cert-manager is running in the Kubernetes cluster. -- A root or intermediate CA (either self-signed or external) is already configured on the cert-manager. The same root certificate file must be prepared for upload to YugabyteDB Anywhere. -- An Issuer or ClusterIssuer Kind is configured on the cert-manager and is ready to issue certificates using the previously-mentioned root or intermediate certificate. - -During the universe creation, you can enable TLS certificates issued by the cert-manager, as follows: - -1. Upload the root certificate to YugabyteDB Anywhere: - - - Prepare the root certificate in a file (for example, `root.crt`). - - Navigate to **Configs > Security > Encryption in Transit** and click **Add Certificate**. - - On the **Add Certificate** dialog shown in the following illustration, select **K8S cert-manager**: - - ![Add Certificate](/images/yp/security/kubernetes-cert-manager.png) - - - In the **Certificate Name** field, enter a meaningful name for your certificate configuration. - - Click **Upload Root Certificate** and select the root certificate file that you prepared. - - Click **Add** to make the certificate available. - -1. Configure the Kubernetes-based cloud provider by following instructions provided in [Configure region and zones](../../configure-yugabyte-platform/kubernetes/#configure-region-and-zones). In the **Add new region** dialog shown in the following illustration, you would be able to specify the Issuer name or the ClusterIssuer name for each zone. Because an Issuer Kind is a Kubernetes namespace-scoped resource, the zone definition should also set the **Namespace** field value if an Issuer Kind is selected: - - ![Add new region](/images/yp/security/kubernetes-cert-manager-add-region.png) - -1. Create the universe: - - - Navigate to **Universes** and click **Create Universe**. - - In the **Provider** field, select the cloud provider that you have configured in step 2. - - Complete the fields based on your requirements, and select **Enable Node-to-Node TLS** or **Enable Client-to-Node TLS**. - - Select the root certificate that you have uploaded in step 1. - - Click **Create**. - -### Troubleshoot - -If you encounter problems, you should verify the name of Issuer or ClusterIssuer in the Kubernetes cluster, as well as ensure that the Kubernetes cluster is in Ready state. You can use the following commands: - -```sh -kubectl get ClusterIssuer -``` - -```sh -kubectl -n Issuer -``` - -## Connect to clusters - -Using TLS, you can connect to the YSQL and YCQL endpoints. - -### Connect to a YSQL endpoint with TLS - -If you created your universe with the Client-to-Node TLS option enabled, then you must download client certificates to your client computer to establish connection to your database, as follows: - -- Navigate to the **Certificates** page and then to your universe's certificate. - -- Click **Actions** and select **Download YSQL Cert**, as shown in the following illustration. This triggers the download of the `yugabytedb.crt` and `yugabytedb.key` files. - - ![download-ysql-cert](/images/yp/encryption-in-transit/download-ysql-cert.png) - -- Optionally, when connecting to universes that are configured with custom CA-signed certificates, obtain the root CA and client YSQL certificate from your administrator. These certificates are not available on YugabyteDB Anywhere for downloading. - -- For testing with a `ysqlsh` client, paste the `yugabytedb.crt` and `yugabytedb.key` files into the `/.yugabytedb` directory and change the permissions to `0600`, as follows: - - ```sh - mkdir ~/.yugabytedb; cd ~/.yugabytedb - cp /yugabytedb.crt . - cp /yugabytedb.key . - chmod 600 yugabytedb.* - ``` - -- Run `ysqlsh` using the `sslmode=require` option, as follows: - - ```sh - cd - bin/ysqlsh -h 172.152.43.78 -p 5433 sslmode=require - ``` - - ```output - ysqlsh (11.2-YB-2.3.3.0-b0) - SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off) - Type "help" for help. - - yugabyte=# - ``` - -To use TLS from a different client, consult the client-specific documentation. For example, if you are using a PostgreSQL JDBC driver to connect to YugabyteDB, see [Configuring the client](https://jdbc.postgresql.org/documentation/head/ssl-client.html) for more details. - -If you are using PostgreSQL/YugabyteDB JDBC driver with SSL, you need to convert the certificates to DER format. To do this, you need to perform only steps 6 and 7 from [Set up SSL certificates for Java applications](../../../reference/drivers/java/postgres-jdbc-reference/#set-up-ssl-certificates-for-java-applications) section after downloading the certificates. - -### Connect to a YCQL endpoint with TLS - -If you created your universe with the Client-to-Node TLS option enabled, then you must download client certificates to your client computer to establish connection to your database, as follows: - -- Navigate to the **Certificates** page and then to your universe's certificate. - -- Click **Actions** and select **Download Root Cert**, as shown in the following illustration. This triggers the download of the `root.crt` file. - - ![download-root-cert](/images/yp/encryption-in-transit/download-root-cert.png) - -- Optionally, when connecting to universes that are configured with custom CA-signed certificates, obtain the root CA and client YSQL certificate from your administrator. These certificates are not available on YugabyteDB Anywhere for downloading. - -- Set `SSL_CERTFILE` environment variable to point to the location of the downloaded root certificate. - -- Run `ycqlsh` using the `-ssl` option, as follows: - - ```sh - cp /root.crt ~/.yugabytedb/root.crt - export SSL_CERTFILE=~/.yugabytedb/root.crt - bin/ycqlsh 172.152.43.78 --ssl - ``` - - ```output - Connected to local cluster at 172.152.43.78:9042. - [ycqlsh 5.0.1 | Cassandra 3.9-SNAPSHOT | CQL spec 3.4.2 | Native protocol v4] - Use HELP for help. - ycqlsh> - ``` - -To use TLS from a different client, consult the client-specific documentation. For example, if you are using a Cassandra driver to connect to YugabyteDB, see [SSL](https://docs.datastax.com/en/developer/python-driver/3.19/security/#ssl). - -## Validate certificates - -When configuring and using certificates, SSL issues may occasionally arise. You can validate your certificates and keys as follows: - -1. Verify that the CA CRT and CA private key match by executing the following commands: - - ```shell - openssl rsa -noout -modulus -in ca.key | openssl md5 - openssl x509 -noout -modulus -in ca.crt | openssl md5 - - \# outputs should match - ``` - -2. Verify that the CA CRT is actually a certificate authority by executing the following command: - - ```shell - openssl x509 -text -noout -in ca.crt - - \# Look for fields - - X509v3 Basic Constraints: - - CA:TRUE - ``` - -3. Verify that certificates and keys are in PEM format (as opposed to the DER or other format). If these artifacts are not in the PEM format and you require assistance with converting them or identifying the format, consult [Converting certificates](https://support.globalsign.com/ssl/ssl-certificates-installation/converting-certificates-openssl). - -4. Ensure that the private key does not have a passphrase associated with it. For information on how to identify this condition, see [Decrypt an encrypted SSL RSA private key](https://techjourney.net/how-to-decrypt-an-enrypted-ssl-rsa-private-key-pem-key/). - -## Enforcing TLS versions - -As TLS 1.0 and 1.1 are no longer accepted by PCI compliance, and considering significant vulnerabilities around these versions of the protocol, it is recommended that you migrate to TLS 1.2 or later versions. - -You can set the TLS version for node-to-node and client-node communication. To enforce TLS 1.2, add the following flag for YB-TServer: - -```shell -ssl_protocols = tls12 -``` - -To enforce the minimum TLS version of 1.2, you need to specify all available subsequent versions for YB-TServer, as follows: - -```shell -ssl_protocols = tls12,tls13 -``` - -In addition, as the `ssl_protocols` setting does not propagate to PostgreSQL, it is recommended that you specify the minimum TLS version (`ssl_min_protocol_version`) for PostgreSQL by setting the following YB-TServer flag: - -```shell ---ysql_pg_conf_csv="ssl_min_protocol_version='TLSv1.2'" -``` - -## Use self-signed and custom CA certificates - -YugabyteDB Anywhere uses TLS to protect data in transit when connecting to other services, including: - -- LDAP -- OIDC -- Webhook -- [S3 backup storage](../../back-up-restore-universes/configure-backup-storage/) -- Hashicorp Vault -- [YugabyteDB Anywhere high availability](../../administer-yugabyte-platform/high-availability/) - -If you are using self-signed or custom CA certificates, YugabyteDB cannot verify your TLS connections unless you add the certificates to the YugabyteDB Anywhere Trust Store. - -### Add certificates to your trust store - -To add a certificate to the YugabyteDB Anywhere Trust Store, do the following: - -1. Navigate to **Admin > CA Certificates**. - -1. Click **Upload Trusted CA Certificate**. - -1. Enter a name for the certificate. - -1. Click **Upload**, select your certificate (in .crt format) and click **Save CA Certificate**. - -### Rotate a certificate in your trust store - -To rotate a certificate in your YugabyteDB Anywhere Trust Store, do the following: - -1. Navigate to **Admin > CA Certificates**. - -1. Click the **...** button for the certificate and choose **Update Certificate**. - -1. Click **Upload**, select your certificate (in .crt format) and click **Save CA Certificate**. - -### Delete a certificate in your trust store - -To delete a certificate in your YugabyteDB Anywhere Trust Store, do the following: - -1. Navigate to **Admin > CA Certificates**. - -1. Click the **...** button for the certificate and choose **Delete**, then click **Delete CA Certificate**. diff --git a/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/_index.md b/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/_index.md new file mode 100644 index 000000000000..0909c9f96b7c --- /dev/null +++ b/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/_index.md @@ -0,0 +1,90 @@ +--- +title: Encryption in transit in YugabyteDB Anywhere +headerTitle: Encryption in transit +linkTitle: Encryption in transit +description: Use encryption in transit (TLS) to secure data traffic. +headcontent: Secure intra-node and application traffic +menu: + preview_yugabyte-platform: + parent: security + identifier: enable-encryption-in-transit + weight: 40 +type: indexpage +showRightNav: true +--- + +YugabyteDB Anywhere allows you to protect data in transit by using the following: + +- Node-to-Node TLS to encrypt intra-node communication between YB-Master and YB-TServer nodes. +- Client-to-Node TLS to encrypt communication between a universe and clients. This includes applications, shells (ysqlsh, ycqlsh, psql, and so on), and other tools, using the YSQL and YCQL APIs. + +## Manage certificates + +Use YugabyteDB Anywhere to manage certificates used for encryption in transit. + +{{}} + + {{}} + + {{}} + + {{}} + + {{}} + +{{}} + +## Enable encryption in transit + +You enable Node-to-Node and Client-to-Node encryption in transit when you [create a universe](../../create-deployments/create-universe-multi-zone/). + +You can also enable and disable encryption in transit for an existing universe as follows: + +1. Navigate to your universe. +1. Click **Actions > Edit Security > Encryption in-Transit** to open the **Manage encryption in transit** dialog. +1. Enable or disable the **Enable encryption in transit for this Universe** option. +1. Click **Apply**. + +### Enforce TLS versions + +As TLS 1.0 and 1.1 are no longer accepted by PCI compliance, and considering significant vulnerabilities around these versions of the protocol, it is recommended that you migrate to TLS 1.2 or later versions. + +You can set the TLS version for node-to-node and client-node communication. To enforce TLS 1.2, add the following flag for YB-TServer: + +```shell +ssl_protocols = tls12 +``` + +To enforce the minimum TLS version of 1.2, you need to specify all available subsequent versions for YB-TServer, as follows: + +```shell +ssl_protocols = tls12,tls13 +``` + +In addition, as the `ssl_protocols` setting does not propagate to PostgreSQL, it is recommended that you specify the minimum TLS version (`ssl_min_protocol_version`) for PostgreSQL by setting the following YB-TServer flag: + +```shell +--ysql_pg_conf_csv="ssl_min_protocol_version='TLSv1.2'" +``` + +## Learn more + +- [Securing YugabyteDB: Server-to-Server Encryption in Transit](https://www.yugabyte.com/blog/yugabytedb-server-to-server-encryption/) +- [Securing YugabyteDB: SQL Client-to-Server Encryption in Transit](https://www.yugabyte.com/blog/securing-yugabytedb-client-to-server-encryption/) +- [Securing YugabyteDB: CQL Client-to-Server Encryption in Transit](https://www.yugabyte.com/blog/securing-yugabytedb-part-3-cql-client-server-encryption-transit/) diff --git a/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-ca.md b/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-ca.md new file mode 100644 index 000000000000..3121eed05e01 --- /dev/null +++ b/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-ca.md @@ -0,0 +1,113 @@ +--- +title: Add CA-signed certificates to YugabyteDB Anywhere +headerTitle: Add certificates +linkTitle: Add certificates +description: Add CA-signed certificates to YugabyteDB Anywhere. +headcontent: Use your own certificates for encryption in transit +menu: + preview_yugabyte-platform: + parent: enable-encryption-in-transit + identifier: add-certificate-2-ca + weight: 20 +type: docs +--- + +{{}} +{{}} +{{}} +{{}} +{{}} +{{}} + +For universes created with an on-premises provider, instead of using self-signed certificates, you can use third-party certificates from external certificate authorities (CA). The third-party CA root certificate must be configured in YugabyteDB Anywhere. You also have to copy the custom CA root certificate, node certificate, and node key to the appropriate on-premises provider nodes. + +## Prerequisites + +The server and CA certificates must adhere to the following criteria: + +- Be stored in a `.crt` file, with both the certificate and the private key being in the PEM format. + + If your certificates and keys are stored in the PKCS12 format, you can [convert them to the PEM format](#convert-certificates-and-keys-from-pkcs12-to-pem-format). + +The server certificates must adhere to the following criteria: + +- Contain IP addresses of the database nodes in the Common Name or in the Subject Alternative Name. For on-premises universes where nodes are identified using DNS addresses, the server certificates should include the DNS names of the database nodes in the Common Name or Subject Alternate Name (wildcards are acceptable). + +## Add CA-signed certificates + +The following procedure describes how to install certificates on the database nodes. You have to repeat these steps for every database node that is to be used in the creation of a universe. + +### Obtain certificates and keys + +Obtain the keys and the custom CA-signed certificates for each of the on-premise nodes for which you are configuring node-to-node TLS. In addition, obtain the keys and the custom signed certificates for client access for configuring client-to-node TLS. + +### Copy the certificates to each node + +For each on-premises provider node, copy the custom CA certificate, node certificate, and node key to that node's file system. + +If you are enabling client-to-node TLS, make sure to copy the client-facing server certificate and client-facing server key to each of the nodes. + +In addition, ensure the following: + +- The file names and file paths of different certificates and keys are identical across all the database nodes. For example, if you name your CA root certificate as `ca.crt` on one node, then you must name it `ca.crt` on all the nodes. Similarly, if you copy `ca.crt` to `/opt/yugabyte/keys` on one node, then you must copy `ca.crt` to the same path on other nodes. +- The `yugabyte` system user has read permissions to all the certificates and keys. + +### Add the CA certificate to YugabyteDB Anywhere + +Add a CA-signed certificate to YugabyteDB Anywhere as follows: + +1. Navigate to **Configs > Security > Encryption in Transit**. + +1. Click **Add Certificate** to open the **Add Certificate** dialog. + +1. Select **CA Signed**, as per the following illustration: + + ![Add CA certificate](/images/yp/encryption-in-transit/add-cert.png) + +1. In the **Certificate Name** field, enter a meaningful name for your certificate. + +1. Upload the custom CA certificate (including any intermediate certificates in the chain) as the Root CA certificate. + + If you use an intermediate CA/issuer, but do not have the complete chain of certificates, then you need to create a bundle by executing the `cat intermediate-ca.crt root-ca.crt > bundle.crt` command, and then use this bundle as the root certificate. You might also want to [verify the certificate chain](#verify-certificate-chain). + +1. Enter the file paths for each of the certificates on the nodes. These are the paths from the previous step. + +1. Use the **Expiration Date** field to specify the expiration date of the certificate. To find this information, execute the `openssl x509 -in -text -noout` command and note the **Validity Not After** date. + +1. Click **Add** to make the certificate available. + +You can rotate certificates for universes configured with the same type of certificates. This involves replacing existing certificates with new database node certificates. + +### Verify certificate chain + +Perform the following steps to verify your certificates: + +1. Execute the following verify command which checks the database node certificate (node.crt) against the root CA certificate (ca.crt): + + ```sh + openssl verify ca.crt node.crt + ``` + +1. Verify that the node certificate (`node.crt`) and the node private key (`node.key`) match. See [How do I verify that a private key matches a certificate?](https://www.ssl247.com/knowledge-base/detail/how-do-i-verify-that-a-private-key-matches-a-certificate-openssl-1527076112539/ka03l0000015hscaay/) + +1. Verify that the node certificate and Root CA certificate expiration is at least 3 months by checking the validity field in the output of the following commands: + + ```sh + openssl x509 -in node.crt -text -noout + ``` + + ```sh + openssl x509 -in ca.crt -text -noout + ``` + +1. Verify that the node certificate Common Name (CN) or Subject Alternate Name (SAN) contains the IP address or DNS name of each on-premises node on which the nodes are deployed. + + {{< note >}} +Each entry you provide for the CN or SAN must match the on-premises node as entered in the provider configuration. For example, if the node address is entered as a DNS address in the on-premises provider configuration, you must use the same DNS entry in the CN or SAN, not the resolved IP address. + {{< /note >}} + + If you face any issue with the above verification, you can customize the level of certificate validation while creating a universe that uses these certificates. Refer to [Customizing the verification of RPC server certificate by the client](https://www.yugabyte.com/blog/yugabytedb-server-to-server-encryption/#customizing-the-verification-of-rpc-server-certificate-by-the-client). + +{{< note >}} +The client certificates and keys are required only if you intend to use [PostgreSQL certificate-based authentication](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html#:~:text=independent%20authentication%20option-,clientcert,-%2C%20which%20can%20be). +{{< /note >}} diff --git a/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-hashicorp.md b/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-hashicorp.md new file mode 100644 index 000000000000..0de3ff36ffb2 --- /dev/null +++ b/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-hashicorp.md @@ -0,0 +1,189 @@ +--- +title: Add Hashicorp Vault certificates to YugabyteDB Anywhere +headerTitle: Add certificates +linkTitle: Add certificates +description: Add Hashicorp Vault certificates to YugabyteDB Anywhere. +headcontent: Use your own certificates for encryption in transit +menu: + preview_yugabyte-platform: + parent: enable-encryption-in-transit + identifier: add-certificate-3-hashicorp + weight: 20 +type: docs +--- + +{{}} +{{}} +{{}} +{{}} +{{}} +{{}} + +YugabyteDB Anywhere allows you to add an encryption in transit configuration using HashiCorp Vault with a public key infrastructure (PKI) secret engine. This configuration can be used to enable TLS for different clusters and YugabyteDB instances. You can apply this configuration to node-to-node encryption, client-to-node encryption, or both. + +## Prerequisites + +For the correct configuration, the following criteria must be met: + +- HashiCorp Vault is unsealed. +- HashiCorp Vault with the PKI secret engine is configured and enabled. +- HashiCorp Vault URL is accessible by YugabyteDB Anywhere. +- Because HashiCorp Vault is accessed via an authentication token mechanism, a token must be created beforehand while creating a key provider with appropriate permissions. +- HashiCorp Vault needs to be running and always accessible to YugabyteDB Anywhere. +- HashiCorp PKI certificate revocation list (CRL) or CA URLs must be accessible from each node server. +- Appropriate certificates and roles have been created for YugabyteDB Anywhere usage. +- Node servers are able to validate certificates. +- Required permissions have been provided to perform various key management operations. + +## Configure HashiCorp Vault + +Before you can start configuring HashiCorp Vault, install it on a virtual machine, as per instructions provided in [Install Vault](https://www.vaultproject.io/docs/install). The vault can be set up as a multi-node cluster. Ensure that your vault installation meets the following requirements: + +- Has transit secret engine enabled. +- Its seal and unseal mechanism is secure and repeatable. +- Its token creation mechanism is repeatable. + +You need to configure HashiCorp Vault in order to use it with YugabyteDB Anywhere, as follows: + +1. Create a vault configuration file that references your nodes and specifies the address, as follows: + + ```properties + storage "raft" { + path = "./vault/data/" + node_id = "node1" + } + + listener "tcp" { + address = "127.0.0.1:8200" + tls_disable = "true" + } + + api_addr = "http://127.0.0.1:8200" + cluster_addr = "https://127.0.0.1:8201" + ui = true + disable_mlock = true + default_lease_ttl = "768h" + max_lease_ttl = "8760h" + ``` + + Replace `127.0.0.1` with the vault web address. + + For additional configuration options, see [Parameters](https://www.vaultproject.io/docs/configuration#parameters). + +1. Initialize the vault server by following instructions provided in [Operator init](https://www.vaultproject.io/docs/commands/operator/init). + +1. Allow access to the vault by following instructions provided in [Unsealing](https://www.vaultproject.io/docs/concepts/seal#unsealing). + +1. Enable the secret engine by executing the following command: + + ```shell + vault secrets enable pki + ``` + +1. Configure the secret engine, as follows: + + - Create a root CA or configure the top-level CA. + + - Optionally, create an intermediate CA chain and sign them. + + - Create an intermediate CA for YugabyteDB, as per the following example: + + ```sh + export pki=pki + export pki_int="pki_int" + export role_i=RoleName + export ip="s.test.com" + + vault secrets enable -path=$pki_int pki + vault secrets tune -max-lease-ttl=43800h $pki_int + vault write $pki_int/intermediate/generate/internal common_name="test.com Intermediate Authority" ttl=43800h -format=json | jq -r '.data.csr' > pki_int.csr + + \# *** dump the output of the preceding command in pki_int.csr + + vault write $pki/root/sign-intermediate csr=@pki_int.csr format=pem_bundle ttl=43800h -format=json | jq -r .data.certificate > i_signed.pem + + \# *** dump the output in i_signed.pem + + vault write $pki_int/intermediate/set-signed certificate=@i_signed.pem + vault write $pki_int/config/urls issuing_certificates="http://127.0.0.1:8200/v1/pki_int/ca" crl_distribution_points="http://127.0.0.1:8200/v1/pki_int/crl" + ``` + +1. Create the vault policy, as per the following example: + + ```properties + # Enable secrets engine + path "sys/mounts/*" { + capabilities = ["create", "read", "update", "delete", "list"] + } + + # List enabled secrets engine + path "sys/mounts" { + capabilities = ["read", "list"] + } + + # Work with pki secrets engine + path "pki*" { + capabilities = ["create", "read", "update", "delete", "list", "sudo"] + } + ``` + +1. Generate a token with appropriate permissions (as per the referenced policy) by executing the following command: + + ```shell + vault token create -no-default-policy -policy=pki_policy + ``` + + You may also specify the following for your token: + + - `ttl` — Time to live (TTL). If not specified, the default TTL of 32 days is used, which means that the generated token will expire after 32 days. + - `period` — If specified, the token can be infinitely renewed. + + YugabyteDB Anywhere automatically tries to renew the token every 12 hours after it has passed 70% of its expiry window; as a result, you should set the TTL or period to be greater than 12 hours. + + For more information, refer to [Tokens](https://developer.hashicorp.com/vault/tutorials/tokens/tokens) in the Hashicorp documentation. + +1. Create a role that maps a name in the vault to a procedure for generating a certificate, as follows: + + ```sh + vault write /roles/ allow_any_name=true allow_subdomains=true max_ttl="8640h" + ``` + + Credentials are generated against this role. + +1. Issue certificates for nodes or a YugabyteDB client: + + - For a node, execute the following: + + ```sh + vault write /issue/ common_name="" ip_sans="" ttl="860h" + ``` + + - For YugabyteDB client, execute the following: + + ```sh + vault write /issue/ common_name="" + ``` + +## Add HashiCorp Vault-provided certificates + +When you create a universe, you can enable TLS using certificates provided by HashiCorp Vault, as follows: + +1. Navigate to **Configs > Security > Encryption in Transit**. + +1. Click **Add Certificate** to open the **Add Certificate** dialog. + +1. Select **Hashicorp**. + + ![Add Hashicorp certificate](/images/yp/encryption-in-transit/add-hashicorp-cert.png) + +1. In the **Config Name** field, enter a meaningful name for your configuration. + +1. In the **Vault Address** field, specify a valid URL that includes the port number. The format is `http://0.0.0.0:0000`, which corresponds to `VAULT_HOSTNAME:0000` + +1. In the **Secret Token** field, specify the secret token for the vault. + +1. In the **Role** field, specify the role used for creating certificates. + +1. Optionally, provide the secret engine path on which the PKI is mounted. If you do not supply this information, `pki/` will be used. + +1. Click **Add** to make the certificate available. diff --git a/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-kubernetes.md b/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-kubernetes.md new file mode 100644 index 000000000000..dfc98c52c593 --- /dev/null +++ b/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-kubernetes.md @@ -0,0 +1,69 @@ +--- +title: Add cert-manager certificates to YugabyteDB Anywhere +headerTitle: Add certificates +linkTitle: Add certificates +description: Add cert-manager certificates to YugabyteDB Anywhere. +headcontent: Use your own certificates for encryption in transit +menu: + preview_yugabyte-platform: + parent: enable-encryption-in-transit + identifier: add-certificate-4-kubernetes + weight: 20 +type: docs +--- + +{{}} +{{}} +{{}} +{{}} +{{}} +{{}} + +For a universe created on Kubernetes, YugabyteDB Anywhere allows you to configure an existing running instance of the [cert-manager](https://cert-manager.io/) as a TLS certificate provider for a cluster. + +## Prerequisites + +The following criteria must be met: + +- The cert-manager is running in the Kubernetes cluster. +- A root or intermediate CA (either self-signed or external) is already configured on the cert-manager. The same CA certificate file, including any intermediate CAs, must be prepared for upload to YugabyteDB Anywhere. For intermediate certificates, the chained CA certificate can be constructed using a command similar to `cat intermediate-ca.crt root-ca.crt > bundle.crt`. +- An Issuer or ClusterIssuer Kind is configured on the cert-manager and is ready to issue certificates using the previously-mentioned root or intermediate certificate. +- Prepare the root certificate in a file (for example, `root.crt`). + +## Add certificates using cert-manager + +Add TLS certificates issued by the cert-manager as follows: + +1. Navigate to **Configs > Security > Encryption in Transit**. + +1. Click **Add Certificate** to open the **Add Certificate** dialog. + +1. Select **K8S cert-manager**. + + ![Add Kubernetes Certificate](/images/yp/encryption-in-transit/add-k8s-cert.png) + +1. In the **Certificate Name** field, enter a meaningful name for your certificate. + +1. Click **Upload Root Certificate** and select the CA certificate file that you prepared. + +1. Click **Add** to make the certificate available. + +## Configure the provider + +After the certificate is added to YugabyteDB Anywhere, configure the Kubernetes provider configuration by following instructions provided in [Configure region and zones](../../../configure-yugabyte-platform/kubernetes/#configure-region-and-zones). + +In the **Add new region** dialog shown in the following illustration, you would be able to specify the Issuer name or the ClusterIssuer name for each zone. Because an Issuer Kind is a Kubernetes namespace-scoped resource, the zone definition should also set the **Namespace** field value if an Issuer Kind is selected. + +![Add new region](/images/yp/security/kubernetes-cert-manager-add-region.png) + +## Troubleshoot + +If you encounter problems, you should verify the name of Issuer or ClusterIssuer in the Kubernetes cluster, as well as ensure that the Kubernetes cluster is in Ready state. You can use the following commands: + +```sh +kubectl get ClusterIssuer +``` + +```sh +kubectl -n Issuer +``` diff --git a/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-self.md b/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-self.md new file mode 100644 index 000000000000..7af7acd877b4 --- /dev/null +++ b/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-self.md @@ -0,0 +1,99 @@ +--- +title: Add self-signed certificates to YugabyteDB Anywhere +headerTitle: Add certificates +linkTitle: Add certificates +description: Add self-signed certificates to YugabyteDB Anywhere. +headcontent: Use your own certificates for encryption in transit +menu: + preview_yugabyte-platform: + parent: enable-encryption-in-transit + identifier: add-certificate-1-self + weight: 20 +type: docs +--- + +{{}} +{{}} +{{}} +{{}} +{{}} +{{}} + +Instead of using YugabyteDB Anywhere-provided certificates, you can use your own self-signed certificates that you upload to YugabyteDB Anywhere. + +## Prerequisites + +The certificates must meet the following criteria: + +- Be in the `.crt` format and the private key must be in the `.pem` format, with both of these artifacts available for upload. + +YugabyteDB Anywhere produces the node (leaf) certificates from the uploaded certificates and copies the certificate chain, leaf certificate, and private key to the nodes in the cluster. + +### Convert certificates and keys from PKCS12 to PEM format + +If your certificates and keys are stored in the PKCS12 format, you can convert them to the PEM format using OpenSSL. + +Start by extracting the certificate via the following command: + +```sh +openssl pkcs12 -in cert-archive.pfx -out cert.pem -clcerts -nokeys +``` + +To extract the key and write it to the PEM file unencrypted, execute the following command: + +```sh +openssl pkcs12 -in cert-archive.pfx -out key.pem -nocerts -nodes +``` + +If the key is protected by a passphrase in the PKCS12 archive, you are prompted for the passphrase. + +## Add self-signed certificates + +To add self-signed certificates to YugabyteDB Anywhere: + +1. Navigate to **Configs > Security > Encryption in Transit**. + +1. Click **Add Certificate** to open the **Add Certificate** dialog. + +1. Select **Self Signed**. + + ![Add Self Signed certificate](/images/yp/encryption-in-transit/add-self-cert.png) + +1. In the **Certificate Name** field, enter a meaningful name for your certificate. + +1. Click **Upload Root Certificate**, then browse to the root certificate file (`.crt`) and upload it. + +1. Click **Upload Key**, then browse to the root certificate file (`.key`) and upload it. + +1. In the **Expiration Date** field, specify the expiration date of the root certificate. To find this information, execute the `openssl x509 -in -text -noout` command and note the **Validity Not After** date. + +1. Click **Add** to make the certificate available. + +## Validate certificates + +When configuring and using certificates, SSL issues may occasionally arise. You can validate your certificates and keys as follows: + +- Verify that the CA CRT and CA private key match by executing the following commands: + + ```shell + openssl rsa -noout -modulus -in ca.key | openssl md5 + openssl x509 -noout -modulus -in ca.crt | openssl md5 + + \# outputs should match + ``` + +- Verify that the CA CRT is actually a certificate authority by executing the following command: + + ```shell + openssl x509 -text -noout -in ca.crt + + \# Look for fields + + X509v3 Basic Constraints: + + CA:TRUE + ``` + +- Verify that certificates and keys are in PEM format (as opposed to the DER or other format). If these artifacts are not in the PEM format and you require assistance with converting them or identifying the format, consult [Converting certificates](https://support.globalsign.com/ssl/ssl-certificates-installation/converting-certificates-openssl). + +- Ensure that the private key does not have a passphrase associated with it. For information on how to identify this condition, see [Decrypt an encrypted SSL RSA private key](https://techjourney.net/how-to-decrypt-an-enrypted-ssl-rsa-private-key-pem-key/). diff --git a/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/auto-certificate.md b/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/auto-certificate.md new file mode 100644 index 000000000000..b5c1c7bdc66e --- /dev/null +++ b/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/auto-certificate.md @@ -0,0 +1,95 @@ +--- +title: Automatically generated certificates on YugabyteDB Anywhere +headerTitle: Auto-generated certificates +linkTitle: Auto-generated certificates +description: YugabyteDB Anywhere-generated self-signed certificates. +headcontent: Let YugabyteDB Anywhere manage certificates for your universe +menu: + preview_yugabyte-platform: + parent: enable-encryption-in-transit + identifier: auto-certificate + weight: 10 +type: docs +--- + +YugabyteDB Anywhere can automatically create and manage self-signed certificates for universes when you create them. These certificates may be shared between universes in a single instance of YugabyteDB Anywhere. + +Automatically generated certificates are named using the following convention: + +```sh +yb-environment-universe_name +``` + +where *environment* is the environment type (either `dev`, `stg`, `demo`, or `prod`) that was used during the tenant registration (admin user creation), and *universe_name* is the provided universe name. + +YugabyteDB Anywhere generates the root CA certificate, root private key, and node-level certificates (assuming node-to-node or client-to-node encryption is enabled), and then provisions those artifacts to the database nodes any time nodes are created or added to the cluster. The following three files are copied to each node: + +1. The root certificate (`ca.cert`). +1. The node certificate (`node.ip_address.crt`). +1. The node private key (`node.ip_address.key`). + +YugabyteDB Anywhere retains the root certificate and the root private key for all interactions with the cluster. + +To view the certificate details, navigate to **Configs > Security > Encryption in Transit** and click **Show details**. + +## Customize the organization name in self-signed certificates + +YugabyteDB Anywhere automatically creates self-signed certificates when you run some workflows, such as create universe. The organization name in certificates is set to `example.com` by default. + +If you are using YugabyteDB Anywhere version 2.18.2 or later to manage universes with YugabyteDB version 2.18.2 or later, you can set a custom organization name using the global [runtime configuration](../../../administer-yugabyte-platform/manage-runtime-config/) flag, `yb.tlsCertificate.organizationName`. + +Note that, for the change to take effect, you need to set the flag _before_ you run a workflow that generates a self-signed certificate. + +Customize the organization name as follows: + +1. In YugabyteDB Anywhere, navigate to **Admin** > **Advanced** and select the **Global Configuration** tab. +1. In the **Search** bar, enter `yb.tlsCertificate.organizationName` to view the flag, as per the following illustration: + + ![Custom Organization name](/images/yp/encryption-in-transit/custom-org-name.png) + +1. Click **Actions** > **Edit Configuration**, enter a new Config Value, and click **Save**. + +## Validate custom organization name + +You can verify the organization name by running the following `openssl x509` command: + +```sh +openssl x509 -in ca.crt -text +``` + +```output {hl_lines=[6]} +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1683277970271 (0x187eb2f7b5f) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=yb-dev-sb-ybdemo-univ1~2, O=example.com + Validity + Not Before: May 5 09:12:50 2023 GMT + Not After : May 5 09:12:50 2027 GMT +``` + +Notice that default value is `O=example.com`. + +After setting the runtime configuration to a value of your choice, (`org-foo` in this example), you should see output similar to the following: + +```sh +openssl x509 -in ca.crt -text -noout +``` + +```output +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1689376612248 (0x18956b15f98) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN = yb-dev-sb-ybdemo-univ1~2, O = org-foo + Validity + Not Before: Jul 14 23:16:52 2023 GMT + Not After : Jul 14 23:16:52 2027 GMT + Subject: CN = yb-dev-sb-ybdemo-univ1~2, O = org-foo + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: +``` diff --git a/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/rotate-certificates.md b/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/rotate-certificates.md new file mode 100644 index 000000000000..9f130d51782a --- /dev/null +++ b/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/rotate-certificates.md @@ -0,0 +1,55 @@ +--- +title: Rotate certificates on YugabyteDB Anywhere +headerTitle: Rotate certificates +linkTitle: Rotate certificates +description: Rotate certificates on YugabyteDB Anywhere. +headcontent: Rotate certificates used by a universe +menu: + preview_yugabyte-platform: + parent: enable-encryption-in-transit + identifier: rotate-certificates + weight: 30 +type: docs +--- + +You can rotate certificates for universes configured with the same type of certificates. This involves replacing existing certificates with new database node certificates. + +Before rotating certificates, ensure that you have added the certificates to YugabyteDB Anywhere. Refer to [Add certificates](../add-certificate-self/). + +**Client-to-node certificates** + +Regardless of whether the client-to-node certificates are expired or not expired, you can always trigger a rolling upgrade to rotate the certificates. + +- If the universe was created before v2.16.6, then the rotation requires a restart, which can be done in a rolling manner with no downtime. +- If the universe was created after v2.16.6, then the rotation can be done without a restart and no downtime. + +**Node-to-node certificates** + +If the certificate has expired, the rotation requires a simultaneous restart of all nodes, resulting in some downtime. + +If the certificate has not expired, the rotation can be done using a rolling upgrade. + +- If the universe was created before v2.16.6, then the rotation requires a restart, which can be done in a rolling manner with no downtime. +- If the universe is created after v2.16.6, then the rotation can be done without a restart and no downtime. + +You can always opt to not perform rolling updates to update all nodes at the same time, but this will result in downtime. + +## Rotate certificates + +To modify encryption in transit settings and rotate certificates for a universe, do the following: + +1. Navigate to your universe. + +1. Click **Actions > Edit Security > Encryption in-Transit** to open the **Manage encryption in transit** dialog. + + ![Rotate certificates](/images/yp/encryption-in-transit/rotate-cert.png) + +1. To rotate the CA certificate, on the **Certificate Authority** tab, select the new CA certificate(s). + + If you wish to have YBA generate a new self-signed CA certificate [automatically](../auto-certificate/), delete the root certificate field. + +1. To rotate the server certificates, on the **Server Certificate** tab, select the **Rotate Node-to-Node Server Certificate** and **Rotate Client-to-Node Server Certificate** options as appropriate. + +1. Select the **Use rolling upgrade to apply this change** option to perform the upgrade in a rolling update (recommended) and enter the number of seconds to wait between node upgrades. + +1. Click **Apply**. diff --git a/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/trust-store.md b/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/trust-store.md new file mode 100644 index 000000000000..7b1c3df608a0 --- /dev/null +++ b/docs/content/preview/yugabyte-platform/security/enable-encryption-in-transit/trust-store.md @@ -0,0 +1,54 @@ +--- +title: Add CA-signed certificates to YugabyteDB Anywhere +headerTitle: Add certificates to your trust store +linkTitle: Trust store +description: Add certificates to the YugabyteDB Anywhere trust store. +headcontent: Add certificates for third-party services +menu: + preview_yugabyte-platform: + parent: enable-encryption-in-transit + identifier: trust-store + weight: 40 +type: docs +--- + +YugabyteDB Anywhere uses certificates to validate connections between YugabyteDB Anywhere and other external services, including: + +- [LDAP](../../../administer-yugabyte-platform/ldap-authentication/) +- [OIDC](../../../administer-yugabyte-platform/oidc-authentication/) +- [Webhook](../../../alerts-monitoring/set-up-alerts-health-check/) +- [S3 backup storage](../../../back-up-restore-universes/configure-backup-storage/) +- [Hashicorp Vault](../../create-kms-config/hashicorp-kms/) +- Other [YugabyteDB Anywhere high availability](../../../administer-yugabyte-platform/high-availability/) replicas. + +When using self-signed or custom CA certificates, to enable YugabyteDB Anywhere to validate your TLS connections, you _must_ add the certificates to the YugabyteDB Anywhere Trust Store + +## Add certificates to your trust store + +To add a certificate to the YugabyteDB Anywhere Trust Store, do the following: + +1. Navigate to **Admin > CA Certificates**. + +1. Click **Upload Trusted CA Certificate**. + +1. Enter a name for the certificate. + +1. Click **Upload**, select your certificate (in .crt format) and click **Save CA Certificate**. + +## Rotate a certificate in your trust store + +To rotate a certificate in your YugabyteDB Anywhere Trust Store, do the following: + +1. Navigate to **Admin > CA Certificates**. + +1. Click the **...** button for the certificate and choose **Update Certificate**. + +1. Click **Upload**, select your certificate (in .crt format) and click **Save CA Certificate**. + +## Delete a certificate in your trust store + +To delete a certificate in your YugabyteDB Anywhere Trust Store, do the following: + +1. Navigate to **Admin > CA Certificates**. + +1. Click the **...** button for the certificate and choose **Delete**, then click **Delete CA Certificate**. diff --git a/docs/content/preview/yugabyte-platform/security/security-checklist-yp.md b/docs/content/preview/yugabyte-platform/security/security-checklist-yp.md deleted file mode 100644 index d880a0561307..000000000000 --- a/docs/content/preview/yugabyte-platform/security/security-checklist-yp.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: Security checklist for YugabyteDB Anywhere -headerTitle: Security checklist -linkTitle: Security checklist -description: Security measures that can be implemented to protect your YugabyteDB Anywhere and YugabyteDB universes. -aliases: - - /preview/yugabyte-platform/security/network-security/ -menu: - preview_yugabyte-platform: - parent: security - identifier: security-checklist-yp - weight: 10 -type: docs ---- - -You can apply security measures to protect your YugabyteDB Anywhere instance and YugabyteDB universes. - -## Network security - -You need to ensure that YugabyteDB Anywhere and the database run in a trusted network environment. You should restrict machine and port access, based on the following guidelines: - -- Servers running YugabyteDB services are directly accessible only by YugabyteDB Anywhere, servers running the application, and database administrators. -- Only YugabyteDB Anywhere and servers running applications can connect to YugabyteDB services on the RPC ports. Access to the YugabyteDB ports should be denied to everybody else. - -For information on configuring ports, refer to [Configure ports](../customize-ports/). - -## Database authentication - -Authentication requires that all clients provide valid credentials before they can connect to a YugabyteDB universe. The authentication credentials in YugabyteDB are stored internally in the YB-Master system tables. The authentication mechanisms available to users depends on what is supported and exposed by the YSQL and YCQL APIs. - -You enable authentication for the YSQL and YCQL APIs when you deploy a universe. See [Enable database authentication](../authorization-platform/#enable-database-authentication). - -YugabyteDB Anywhere and YugabyteDB also support LDAP and OIDC for managing authentication. See [Database authentication](../authentication/). - -For more information on authentication in YugabyteDB, see [Enable authentication](../../../secure/enable-authentication/). - -## Role-based access control - -Roles can be assigned to grant users only the essential privileges based on the operations they need to perform in YugabyteDB Anywhere, and in YugabyteDB universes. - -To manage access to your YugabyteDB Anywhere instance, typically you create a [Super Admin role first](../../install-yugabyte-platform/create-admin-user/). The Super Admin can create additional admins and other users with fewer privileges. For information on how to manage YugabyteDB Anywhere users and roles, see [Manage YugabyteDB Anywhere users](../../administer-yugabyte-platform/anywhere-rbac/). - -For information on how to manage database roles and users, see [Database authorization](../authorization-platform). - -## Encryption in transit - -Encryption in transit (TLS) ensures that network communication between servers is secure. You can configure YugabyteDB to use TLS to encrypt intra-cluster and client to server network communication. You should enable encryption in transit in YugabyteDB universes and clients to ensure the privacy and integrity of data transferred over the network. - -For more information, see [Enable encryption in transit](../enable-encryption-in-transit). - -## Encryption at rest - -Encryption at rest ensures that data at rest, stored on disk, is protected. You can configure YugabyteDB universes with a user-generated symmetric key to perform universe-wide encryption. - -Encryption at rest in YugabyteDB Anywhere uses a master key to encrypt and decrypt universe keys. The master key details are stored in YugabyteDB Anywhere in [key management service (KMS) configurations](../create-kms-config/aws-kms/). You enable encryption at rest for a universe by assigning the universe a KMS configuration. The master key designated in the configuration is then used for generating the universe keys used for encrypting the universe data. - -For more information, see [Enable encryption at rest](../enable-encryption-at-rest). diff --git a/docs/content/preview/yugabyte-platform/yba-overview.md b/docs/content/preview/yugabyte-platform/yba-overview.md index dd28ef75d07a..9d60f6722330 100644 --- a/docs/content/preview/yugabyte-platform/yba-overview.md +++ b/docs/content/preview/yugabyte-platform/yba-overview.md @@ -14,7 +14,7 @@ type: docs YugabyteDB Anywhere (YBA) is a self-managed database-as-a-service that allows you to deploy and operate YugabyteDB database clusters (also known as universes) at scale. -In YBA, a database cluster is called a [universe](../../architecture/key-concepts/#universe), and the terms are used interchangeably. More precisely, a universe in YBA always consists of one (and only one) primary cluster, and can optionally also include a single [read replica](../../architecture/docdb-replication/read-replicas/) cluster attached to the primary cluster. +In YBA, a database cluster is called a [universe](../../architecture/key-concepts/#universe), and the terms are used interchangeably. More precisely, a universe in YBA always consists of one (and only one) [primary cluster](../../architecture/key-concepts/#primary-cluster), and can optionally also include a single [read replica](../../architecture/key-concepts/#read-replica-cluster/) cluster attached to the primary cluster. ## Features diff --git a/docs/content/stable/explore/fault-tolerance/_index.md b/docs/content/stable/explore/fault-tolerance/_index.md index 986fa265a985..ba165019c705 100644 --- a/docs/content/stable/explore/fault-tolerance/_index.md +++ b/docs/content/stable/explore/fault-tolerance/_index.md @@ -14,7 +14,7 @@ type: indexpage showRightNav: true --- -Resiliency, in the context of cloud databases, refers to the ability to withstand and recover from various types of failures, ranging from hardware malfunctions and software bugs to network outages and natural disasters. A resilient database system is designed to maintain data integrity, accessibility, and continuity of operations, even in the face of adverse events. Achieving resilience in cloud databases requires a multi-faceted approach, involving robust architectural design, effective data replication and backup strategies, load balancing, failover mechanisms, and comprehensive monitoring and incident response procedures. +Resiliency, in the context of cloud databases, refers to the ability to withstand and recover from various types of failures. These can range from hardware malfunctions and software bugs to network outages and natural disasters. A resilient database system is designed to maintain data integrity, accessibility, and continuity of operations, even in the face of adverse events. Achieving resilience in cloud databases requires a multi-faceted approach, involving robust architectural design, effective data replication and backup strategies, load balancing, failover mechanisms, and comprehensive monitoring and incident response procedures. YugabyteDB has been designed ground up to be resilient. YugabyteDB can continuously serve requests in the event of planned or unplanned outages, such as system upgrades and outages related to a node, availability zone, or region. YugabyteDB's High availability is achieved through a combination of distributed architecture, data replication, consensus algorithms, automatic rebalancing, and failure detection mechanisms, ensuring that the database remains available, consistent, and resilient to failures of fault domains. diff --git a/docs/content/stable/yugabyte-platform/administer-yugabyte-platform/high-availability.md b/docs/content/stable/yugabyte-platform/administer-yugabyte-platform/high-availability.md index 8ab6bb467915..7e89080a5f2c 100644 --- a/docs/content/stable/yugabyte-platform/administer-yugabyte-platform/high-availability.md +++ b/docs/content/stable/yugabyte-platform/administer-yugabyte-platform/high-availability.md @@ -141,7 +141,7 @@ For example, if your metrics retention is 14 days on your active instance, and y After HA is operational, it is recommended that you enable certificate validation to improve security of communication between the active and any standby instances. Enable certificate validation as follows: -1. Add certificates for the active and all standbys to the active instance [trust store](../../security/enable-encryption-in-transit/#add-certificates-to-your-trust-store). +1. Add certificates for the active and all standbys to the active instance [trust store](../../security/enable-encryption-in-transit/trust-store/). - If YBA was set up to use a custom server certificate, locate the corresponding Certificate Authority (CA) certificate. - If YBA was set up to use automatically generated self-signed certificates and you installed YBA using YBA Installer, locate the CA certificate at `/opt/yugabyte/data/yba-installer/certs/ca_cert.pem` on both the YBA active and standby instances. (If you configured a custom install root, replace `/opt/yugabyte` with the path you configured.) diff --git a/docs/content/stable/yugabyte-platform/configure-yugabyte-platform/kubernetes.md b/docs/content/stable/yugabyte-platform/configure-yugabyte-platform/kubernetes.md index c0d803b1aa22..85104b90c75d 100644 --- a/docs/content/stable/yugabyte-platform/configure-yugabyte-platform/kubernetes.md +++ b/docs/content/stable/yugabyte-platform/configure-yugabyte-platform/kubernetes.md @@ -120,7 +120,7 @@ Continue configuring your Kubernetes provider by clicking **Add region** and com 1. Complete the **Overrides** field using one of the provided [options](#overrides). If you do not specify anything, YBA uses defaults specified inside the Helm chart. For additional information, see [Open source Kubernetes](../../../deploy/kubernetes/single-zone/oss/helm-chart/). -1. If you are using [Kubernetes cert-manager](https://cert-manager.io) to manage TLS certificates, specify the issuer type and enter the issuer name. For more information, refer to [Enable encryption in transit](../../security/enable-encryption-in-transit/#kubernetes-cert-manager). +1. If you are using [Kubernetes cert-manager](https://cert-manager.io) to manage TLS certificates, specify the issuer type and enter the issuer name. For more information, refer to [Enable encryption in transit](../../security/enable-encryption-in-transit/add-certificate-kubernetes/). If required, add a new zone by clicking **Add Zone**, as your configuration may have multiple zones. diff --git a/docs/content/stable/yugabyte-platform/create-deployments/connect-to-universe.md b/docs/content/stable/yugabyte-platform/create-deployments/connect-to-universe.md index 786964e574ef..f7db84b41830 100644 --- a/docs/content/stable/yugabyte-platform/create-deployments/connect-to-universe.md +++ b/docs/content/stable/yugabyte-platform/create-deployments/connect-to-universe.md @@ -21,13 +21,21 @@ You can connect to the database on a universe in the following ways: ## Download the universe certificate -If the universe uses encryption in transit, to connect you need to first download the universe TLS root certificate. Do the following: +If the universe uses Client-to-Node encryption in transit, to connect you need to first download the universe TLS certificate. Do the following: 1. Navigate to **Configs > Security > Encryption in Transit**. -1. Find the certificate for your universe in the list and click **Actions** and download the certificate. +1. Find your universe in the list. -For more information on connecting to TLS-enabled universes, refer to [Connect to clusters](../../security/enable-encryption-in-transit/#connect-to-clusters). +1. Click **Actions** and choose **Download Root CA Cert**. + + This downloads the `root.crt` file. + +For information on connecting using a client shell using this certificate, see [Connect from your desktop](#connect-from-your-desktop). + +To use TLS to connect an application, refer to the [driver documentation](../../../reference/drivers/). If you are using a PostgreSQL JDBC driver to connect to YugabyteDB, you can also refer to [Configuring the client](https://jdbc.postgresql.org/documentation/head/ssl-client.html) for more details. + +If you are using PostgreSQL/YugabyteDB JDBC driver with SSL, you need to convert the certificates to DER format. To do this, you need to perform only steps 6 and 7 from [Set up SSL certificates for Java applications](../../../reference/drivers/java/postgres-jdbc-reference/#set-up-ssl-certificates-for-java-applications) section after downloading the certificates. ## Connect to a universe node @@ -97,13 +105,13 @@ To run a shell from a universe node, do the following: ### Enable Tectia SSH -By default, YBA uses OpenSSH for SSH to remote nodes. YBA also supports the use of Tectia SSH that is based on the latest SSH G3 protocol. +By default, YugabyteDB Anywhere uses OpenSSH for SSH to remote nodes. YugabyteDB Anywhere also supports the use of Tectia SSH that is based on the latest SSH G3 protocol. -[Tectia SSH](https://www.ssh.com/products/tectia-ssh/) is used for secure file transfer, secure remote access and tunnelling. YBA is shipped with a trial version of Tectia SSH client that requires a license to notify YBA to permanently use Tectia instead of OpenSSH. +[Tectia SSH](https://www.ssh.com/products/tectia-ssh/) is used for secure file transfer, secure remote access and tunnelling. YugabyteDB Anywhere is shipped with a trial version of Tectia SSH client that requires a license to notify YugabyteDB Anywhere to permanently use Tectia instead of OpenSSH. To upload the Tectia license, manually copy it at `${storage_path}/yugaware/data/licenses/`, where _storage_path_ is the path provided during the Replicated installation. -After the license is uploaded, YBA exposes the runtime flag `yb.security.ssh2_enabled` that you need to enable, as per the following example: +After the license is uploaded, YugabyteDB Anywhere exposes the runtime flag `yb.security.ssh2_enabled` that you need to enable, as per the following example: ```shell curl --location --request PUT 'http:///api/v1/customers//runtime_config/00000000-0000-0000-0000-000000000000/key/yb.security.ssh2_enabled' @@ -118,21 +126,9 @@ curl --location --request PUT 'http:///api/v1/customers//runt ### Prerequisites -- If you are using a Yugabyte client shell, ensure you are running the latest versions of the shells (Yugabyte Client 2.6 or later). - - You can download using the following command on Linux or macOS: - - ```sh - $ curl -sSL https://downloads.yugabyte.com/get_clients.sh | bash - ``` - - Windows client shells require Docker. For example: - - ```sh - docker run -it yugabytedb/yugabyte-client ysqlsh -h -p - ``` +- If you are using [ysqlsh](../../../admin/ysqlsh/) or [ycqlsh](../../../admin/ycqlsh/), ensure you are running the latest versions of the shells. -- If your universe has TLS/SSL (encryption in-transit) enabled, you need to [download the certificate](#download-the-universe-certificate) to your computer. +- If your universe has Client-to-Node encryption in transit enabled, you need to [download the certificate](#download-the-universe-certificate) to your computer. - The host address of an endpoint on your universe. @@ -181,7 +177,7 @@ Replace the following: - `` with the IP address of an endpoint on your universe. - `` with your database username. - `yugabyte` with the database name, if you're connecting to a database other than the default (yugabyte). -- `` with the path to the root certificate on your computer. +- `` with the path to the universe root certificate you downloaded to your computer. To load sample data and explore an example using ysqlsh, follow the instructions in [Install the Retail Analytics sample database](../../../sample-data/retail-analytics/#install-the-retail-analytics-sample-database). @@ -203,7 +199,7 @@ Replace the following: - `` with the IP address of an endpoint on your universe. - `` with your database username. -- `` with the path to the root certificate on your computer. +- `` with the path to the universe root certificate you downloaded to your computer.
    @@ -224,7 +220,7 @@ Replace the following: - `` with the IP address of an endpoint on your universe. - `` with your database username. - `yugabyte` with the database name, if you're connecting to a database other than the default (yugabyte). -- `` with the path to the root certificate on your computer. +- `` with the path to the universe root certificate you downloaded to your computer.
    @@ -358,6 +354,7 @@ ycqlsh> SELECT * FROM ybdemo_keyspace.cassandrakeyvalue LIMIT 5; ## Learn more +- [Securing YugabyteDB: Client-to-Server Encryption in Transit](https://www.yugabyte.com/blog/securing-yugabytedb-client-to-server-encryption/#verification-of-server-certificates) - [ysqlsh](../../../admin/ysqlsh/) — Overview of the command line interface (CLI), syntax, and commands. - [YSQL API](../../../api/ysql/) — Reference for supported YSQL statements, data types, functions, and operators. - [ycqlsh](../../../admin/ycqlsh/) — Overview of the command line interface (CLI), syntax, and commands. diff --git a/docs/content/stable/yugabyte-platform/create-deployments/create-universe-multi-zone-kubernetes.md b/docs/content/stable/yugabyte-platform/create-deployments/create-universe-multi-zone-kubernetes.md index e78bd637938b..41c69725009b 100644 --- a/docs/content/stable/yugabyte-platform/create-deployments/create-universe-multi-zone-kubernetes.md +++ b/docs/content/stable/yugabyte-platform/create-deployments/create-universe-multi-zone-kubernetes.md @@ -71,8 +71,8 @@ Complete the **Security Configurations** section as follows: - **Enable YSQL Auth** - specify whether or not to enable the YSQL password authentication. - **Enable YCQL** - specify whether or not to enable the YCQL API endpoint for running Cassandra-compatible workloads. This setting is enabled by default. - **Enable YCQL Auth** - specify whether or not to enable the YCQL password authentication. -- **Enable Node-to-Node TLS** - specify whether or not to enable encryption-in-transit for communication between the database servers. This setting is enabled by default. -- **Enable Client-to-Node TLS** - specify whether or not to enable encryption-in-transit for communication between clients and the database servers. This setting is enabled by default. +- **Enable Node-to-Node TLS** - specify whether or not to enable encryption in transit for communication between the database servers. This setting is enabled by default. +- **Enable Client-to-Node TLS** - specify whether or not to enable encryption in transit for communication between clients and the database servers. This setting is enabled by default. - **Root Certificate** - select an existing security certificate or create a new one. - **Enable Encryption at Rest** - specify whether or not to enable encryption for data stored on the tablet servers. This setting is disabled by default. @@ -80,7 +80,7 @@ Complete the **Security Configurations** section as follows: Complete the **Advanced** section as follows: -- In the **DB Version** field, specify the YugabyteDB version. The default is either the same as the YugabyteDB Anywhere version or the latest YugabyteDB version available for YugabyteDB Anywhere. +- In the **DB Version** field, specify the YugabyteDB version. The default is either the same as the YugabyteDB Anywhere version or the latest YugabyteDB version available for YugabyteDB Anywhere. If the version you want to add is not listed, you can add it to YugabyteDB Anywhere. Refer to [Manage YugabyteDB releases](../../manage-deployments/ybdb-releases/). - Use the **Enable IPV6** field to specify whether or not you want to use IPV6 networking for connections between database servers. This setting is disabled by default. - Use the **Enable Public Network Access** field to specify whether or not to assign a load balancer or nodeport for connecting to the database endpoints over the internet. This setting is disabled by default. diff --git a/docs/content/stable/yugabyte-platform/create-deployments/create-universe-multi-zone.md b/docs/content/stable/yugabyte-platform/create-deployments/create-universe-multi-zone.md index 926bf2ce40cd..0c5e307fb2ca 100644 --- a/docs/content/stable/yugabyte-platform/create-deployments/create-universe-multi-zone.md +++ b/docs/content/stable/yugabyte-platform/create-deployments/create-universe-multi-zone.md @@ -84,19 +84,42 @@ Specify the instance to use for the universe nodes: ### Security Configurations +#### IP Settings + To enable public access to the universe, select the **Assign Public IP** option. -Enable the YSQL and YCQL endpoints and database authentication. You can also enable and disable authentication after deployment. Navigate to your universe, click **Actions**, and choose **Edit YSQL Configuration** or **Edit YCQL Configuration**. +#### Authentication Settings + +Enable the YSQL and YCQL endpoints and database authentication. Enter the password to use for the default database admin superuser (yugabyte for YSQL, and cassandra for YCQL). For more information, refer to [Database authorization](../../security/authorization-platform/). -Enable encryption in transit to encrypt universe traffic. Refer to [Enable encryption in transit](../../security/enable-encryption-in-transit/). +You can also enable and disable the API endpoints and authentication after deployment. Navigate to your universe, click **Actions**, and choose **Edit YSQL Configuration** or **Edit YCQL Configuration**. + +By default, the API endpoints use ports 5433 (YSQL) and 9042 (YCQL). You can [customize these ports](#advanced-configuration), and, after deployment, you can modify the YCQL API and admin UI endpoint ports. To change YCQL ports, navigate to your universe, click **Actions**, choose **Edit YCQL Configuration**, and select the **Override YCQL Default Ports** option. + +#### Encryption Settings + +Enable encryption in transit to encrypt universe traffic. You can enable the following: + +- **Node-to-Node TLS** to encrypt traffic between universe nodes. +- **Client-to-Node TLS** to encrypt traffic between universe nodes and external clients. + + Note that if you want to enable Client-to-Node encryption, you first must enable Node-to-Node encryption. + +Encryption requires a certificate. YugabyteDB Anywhere can generate a self-signed certificate automatically, or you can use your own certificate. + +To use your own, you must first add it to YugabyteDB Anywhere; refer to [Add certificates](../../security/enable-encryption-in-transit/add-certificate-self/). + +To have YugabyteDB Anywhere generate a certificate for the universe, use the default **Root Certificate** setting of **Create New Certificate**. To use a certificate you added or a previously generated certificate, select it from the **Root Certificate** menu. + +For more information on using and managing certificates, refer to [Encryption in transit](../../security/enable-encryption-in-transit/). -Enable encryption at rest to encrypt the universe data. Refer to [Enable encryption at rest](../../security/enable-encryption-at-rest/). +To encrypt the universe data, select the **Enable encryption at rest** option and select the [KMS configuration](../../security/create-kms-config/aws-kms/) to use for encryption. For more information, refer to [Encryption at rest](../../security/enable-encryption-at-rest/). ### Advanced Configuration -Choose the version of YugabyteDB to install on the nodes. +Choose the version of YugabyteDB to install on the nodes. If the version you want to add is not listed, you can add it to YugabyteDB Anywhere. Refer to [Manage YugabyteDB releases](../../manage-deployments/ybdb-releases/). The access key is the SSH key that is created in the provider. Usually, each provider has its own access key, but if you are reusing keys across providers, they are listed here. @@ -104,7 +127,7 @@ For AWS providers, you can assign an ARN to the nodes in the universe; this allo To use cron instead of systemd for managing nodes, you can disable systemd services. This not recommended. -To customize the ports used for the universe, select the **Override Deployment Ports** option and enter the custom port numbers for the services you want to change. +To customize the [ports used for the universe](../../prepare/networking/), select the **Override Deployment Ports** option and enter the custom port numbers for the services you want to change. Any value from `1024` to `65535` is valid, as long as it doesn't conflict with anything else running on nodes to be provisioned. ### G-Flags diff --git a/docs/content/stable/yugabyte-platform/manage-deployments/edit-universe.md b/docs/content/stable/yugabyte-platform/manage-deployments/edit-universe.md index ba49842f7bd8..80666a5cd90f 100644 --- a/docs/content/stable/yugabyte-platform/manage-deployments/edit-universe.md +++ b/docs/content/stable/yugabyte-platform/manage-deployments/edit-universe.md @@ -38,7 +38,7 @@ YugabyteDB Anywhere performs these modifications through the [YB-Masters](../../ Note that you can't change the replication factor of a universe. -To change the number of nodes of universes created with an on-premises cloud provider and secured with third-party certificates obtained from external certification authorities, follow the instructions in [Expand the universe](../../security/enable-encryption-in-transit#expand-the-universe). +To change the number of nodes of universes created with an on-premises cloud provider and secured with third-party certificates obtained from external certification authorities, you must first add the certificates to the nodes you will add to the universe. Refer to [Add certificates](../../security/enable-encryption-in-transit/add-certificate-ca/). Ensure that the certificates are signed by the same external CA and have the same root certificate. In addition, ensure that you copy the certificates to the same locations that you originally used when creating the universe. ### Smart resize diff --git a/docs/content/stable/yugabyte-platform/prepare/networking.md b/docs/content/stable/yugabyte-platform/prepare/networking.md index 60481addf5f1..84510fa85577 100644 --- a/docs/content/stable/yugabyte-platform/prepare/networking.md +++ b/docs/content/stable/yugabyte-platform/prepare/networking.md @@ -18,7 +18,7 @@ YugabyteDB Anywhere (YBA) needs to be able to access nodes that will be used to ![YugabyteDB Anywhere network and ports](/images/yb-platform/prepare/yba-networking.png) -The following ports need to be open. (The default port numbers can be customized.) +The following ports need to be open. | From | To | Requirements | | :--- | :--- | :--- | diff --git a/docs/content/stable/yugabyte-platform/security/_index.md b/docs/content/stable/yugabyte-platform/security/_index.md index 238de3dbdb5e..adecab2fae6b 100644 --- a/docs/content/stable/yugabyte-platform/security/_index.md +++ b/docs/content/stable/yugabyte-platform/security/_index.md @@ -13,48 +13,51 @@ weight: 660 type: indexpage --- -{{}} - - {{}} - - {{}} - - {{}} - - {{}} - - {{}} - - {{}} - - {{}} - -{{}} +You can apply security measures to protect your YugabyteDB Anywhere instance and YugabyteDB universes. + +## Network security + +You need to ensure that YugabyteDB Anywhere and the database run in a trusted network environment. You should restrict machine and port access, based on the following guidelines: + +- Servers running YugabyteDB services are directly accessible only by YugabyteDB Anywhere, servers running the application, and database administrators. +- Only YugabyteDB Anywhere and servers running applications can connect to YugabyteDB services on the RPC ports. Access to the YugabyteDB ports should be denied to everybody else. + +{{}} +For information on networking and port requirements, refer to [Networking](../prepare/networking/). +{{}} + +## Database authentication + +Authentication requires that all clients provide valid credentials before they can connect to a YugabyteDB universe. The authentication credentials in YugabyteDB are stored internally in the YB-Master system tables. The authentication mechanisms available to users depends on what is supported and exposed by the YSQL and YCQL APIs. + +You enable authentication for the YSQL and YCQL APIs when you deploy a universe. See [Enable database authentication](authorization-platform/#enable-database-authentication). + +YugabyteDB Anywhere and YugabyteDB also support LDAP and OIDC for managing authentication. See [Database authentication](authentication/). + +For more information on authentication in YugabyteDB, see [Enable authentication](../../secure/enable-authentication/). + +## Role-based access control + +Roles can be assigned to grant users only the essential privileges based on the operations they need to perform in YugabyteDB Anywhere, and in YugabyteDB universes. + +To manage access to your YugabyteDB Anywhere instance, typically you create a [Super Admin role first](../install-yugabyte-platform/create-admin-user/). The Super Admin can create additional admins and other users with fewer privileges. For information on how to manage YugabyteDB Anywhere users and roles, see [Manage YugabyteDB Anywhere users](../administer-yugabyte-platform/anywhere-rbac/). + +For information on how to manage database roles and users, see [Database authorization](authorization-platform/). + +## Encryption in transit + +Encryption in transit (TLS) ensures that network communication between servers is secure. You can configure YugabyteDB to use TLS to encrypt intra-cluster (Node-to-Node) and client to server (Client-to-Node) network communication. You should enable encryption in transit in YugabyteDB universes and clients to ensure the privacy and integrity of data transferred over the network. + +{{}} +For more information, see [Encryption in transit](enable-encryption-in-transit/). +{{}} + +## Encryption at rest + +Encryption at rest ensures that data at rest, stored on disk, is protected. You can configure YugabyteDB universes with a user-generated symmetric key to perform universe-wide encryption. + +Encryption at rest in YugabyteDB Anywhere uses a master key to encrypt and decrypt universe keys. The master key details are stored in YugabyteDB Anywhere in [key management service (KMS) configurations](create-kms-config/aws-kms/). You enable encryption at rest for a universe by assigning the universe a KMS configuration. The master key designated in the configuration is then used for generating the universe keys used for encrypting the universe data. + +{{}} +For more information, see [Enable encryption at rest](enable-encryption-at-rest/). +{{}} diff --git a/docs/content/stable/yugabyte-platform/security/authorization-platform.md b/docs/content/stable/yugabyte-platform/security/authorization-platform.md index 628d466fc7e8..e5e9f14a3c45 100644 --- a/docs/content/stable/yugabyte-platform/security/authorization-platform.md +++ b/docs/content/stable/yugabyte-platform/security/authorization-platform.md @@ -21,17 +21,17 @@ YugabyteDB uses [role-based access control](../../../secure/authorization/) (RBA (For information on managing access to your YugabyteDB Anywhere instance, refer to [Manage account users](../../administer-yugabyte-platform/anywhere-rbac/).) -## Enable database authentication +## Enable database authorization You enable the YSQL and YCQL endpoints and database authentication when deploying a universe. -On the **Create Universe > Primary Cluster** page, under **Security Configurations**, enable the **Authentication Settings** for the APIs you want to use, as shown in the following illustration. +On the **Create Universe > Primary Cluster** page, under **Security Configurations > Authentication Settings**, enable the endpoints and authorization for the APIs you want to use, as shown in the following illustration. ![Enable YSQL and YCQL endpoints](/images/yp/security/enable-endpoints.png) Enter the password to use for the default database admin superuser (`yugabyte` for YSQL, and `cassandra` for YCQL). -You can also enable and disable the endpoints and authentication after deployment. Navigate to your universe, click **Actions**, and choose **Edit YSQL Configuration** or **Edit YCQL Configuration**. +You can also enable and disable the endpoints and authorization after deployment. Navigate to your universe, click **Actions**, and choose **Edit YSQL Configuration** or **Edit YCQL Configuration**. ## Default roles and users @@ -47,7 +47,7 @@ yugabyte=> \du ```output List of roles - Role name | Attributes | Member of + Role name | Attributes | Member of --------------+------------------------------------------------------------+----------- postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {} yb_db_admin | No inheritance, Cannot login | {} diff --git a/docs/content/stable/yugabyte-platform/security/customize-ports.md b/docs/content/stable/yugabyte-platform/security/customize-ports.md deleted file mode 100644 index 96e31ce9b2c5..000000000000 --- a/docs/content/stable/yugabyte-platform/security/customize-ports.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Configure ports -headerTitle: Configure ports -linkTitle: Configure ports -description: Configure ports -menu: - stable_yugabyte-platform: - parent: security - identifier: customize-ports - weight: 20 -type: docs ---- - -YugabyteDB Anywhere and the universes it manages use a set of [default ports](../../prepare/networking/) to manage access to services. - -When deploying a universe, YugabyteDB Anywhere allows you to customize these ports. - -## Customize ports - -On the **Create Universe > Primary Cluster** page, under **Advanced Configuration**, enable the **Override Deployment Ports** option, as shown in the following illustration: - -![Override Deployment Ports](/images/yp/security/override-deployment-ports.png) - -Replace the default values with the values identifying the port that each process should use. Any value from `1024` to `65535` is valid, as long as this value does not conflict with anything else running on nodes to be provisioned. - -After deployment, you can modify the YCQL API and admin UI endpoint ports. To change ports, navigate to your universe, click **Actions**, choose **Edit YCQL Configuration**, and select the **Override YCQL Default Ports** option. - -If you change the YCQL API endpoint on an active universe, be sure to update your applications as appropriate. diff --git a/docs/content/stable/yugabyte-platform/security/enable-encryption-at-rest.md b/docs/content/stable/yugabyte-platform/security/enable-encryption-at-rest.md index 939ee6e46291..eb6210b5d269 100644 --- a/docs/content/stable/yugabyte-platform/security/enable-encryption-at-rest.md +++ b/docs/content/stable/yugabyte-platform/security/enable-encryption-at-rest.md @@ -1,8 +1,9 @@ --- -title: Enable encryption at rest -headerTitle: Enable encryption at rest -linkTitle: Enable encryption at rest -description: Enable encryption at rest +title: Encryption at rest in YugabyteDB Anywhere +headerTitle: Encryption at rest +linkTitle: Encryption at rest +description: Use encryption at rest in YugabyteDB Anywhere +headcontent: Encrypt your universes menu: stable_yugabyte-platform: parent: security @@ -18,7 +19,7 @@ YugabyteDB Anywhere uses the following types of keys for envelope encryption: | Key | Description | | :--- | :--- | | Data encryption keys (DEK) | Symmetric keys used to directly encrypt the data. Each file flushed from memory has a unique DEK. This key is generated in the database layer of YugabyteDB. | -| Universe key | Symmetric key used to encrypt and decrypt DEKs. A single universe key is used for all the DEKs in a universe. This key is generated by YugabyteDB Anywhere. +| Universe key | Symmetric key used to encrypt and decrypt DEKs. A single universe key is used for all the DEKs in a universe. This key is generated by YugabyteDB Anywhere. | | Master key | The key at the highest level in the key hierarchy. The master key is used to encrypt universe keys. This key is a customer managed key (CMK) stored and managed in a Key Management Service (KMS). | Master key details are stored in YugabyteDB Anywhere in KMS configurations, and YugabyteDB Anywhere supports CMKs in AWS KMS, GCP KMS, Azure Key Vault, and Hashicorp Vault. You enable encryption at rest for a universe by assigning the universe a KMS configuration. For instructions on creating a KMS configuration, see [Create a KMS configuration](../create-kms-config/aws-kms/). diff --git a/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit.md b/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit.md deleted file mode 100644 index 81ac79f914aa..000000000000 --- a/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit.md +++ /dev/null @@ -1,704 +0,0 @@ ---- -title: Enable encryption in transit -headerTitle: Enable encryption in transit -linkTitle: Enable encryption in transit -description: Use YugabyteDB Anywhere to enable encryption in transit (TLS) on a YugabyteDB universe and connect to clients. -menu: - stable_yugabyte-platform: - parent: security - identifier: enable-encryption-in-transit - weight: 40 -rightNav: - hideH4: true -type: docs ---- - -YugabyteDB Anywhere allows you to protect data in transit by using the following: - -- Server-to-server encryption for intra-node communication between YB-Master and YB-TServer nodes. -- Client-to-server encryption for communication between clients and nodes when using CLIs, tools, and APIs for YSQL and YCQL. -- Encryption for communication between YugabyteDB Anywhere and other services, including LDAP, OIDC, Hashicorp Vault, Webhook, and S3 backup storage. - -{{< note title="Note" >}} - -Before you can enable client-to-server encryption, you first must enable server-to-server encryption. - -{{< /note >}} - -YugabyteDB Anywhere lets you create a new self-signed certificate, use an existing self-signed certificate, or upload a third-party certificate from external providers, such as Venafi or DigiCert (which is only available for an on-premises cloud provider). - -You can enable encryption in transit (TLS) during universe creation and change these settings for an existing universe. - -## Self-signed certificates generated by YugabyteDB Anywhere - -YugabyteDB Anywhere can create self-signed certificates for each universe. These certificates may be shared between universes in a single instance of YugabyteDB Anywhere. The certificate name has the following format: - -`yb-environment-universe_name`, where *environment* is the environment type (either `dev`, `stg`, `demo`, or `prod`) that was used during the tenant registration (admin user creation), and *universe-name* is the provided universe name. YugabyteDB Anywhere generates the root certificate, root private key, and node-level certificates (assuming node-to-node encryption is enabled), and then provisions those artifacts to the database nodes any time nodes are created or added to the cluster. The following three files are copied to each node: - -1. The root certificate (`ca.cert`). -1. The node certificate (`node.ip_address.crt`). -1. The node private key (`node.ip_address.key`). - -YugabyteDB Anywhere retains the root certificate and the root private key for all interactions with the cluster. - -### Customize the organization name in self-signed certificates - -YugabyteDB Anywhere automatically creates self-signed certificates when you run some workflows, such as create universe. The organization name in certificates is set to `example.com` by default. - -If you are using YBA version 2.18.2 or later to manage universes with YugabyteDB version 2.18.2 or later, you can set a custom organization name using the global [runtime configuration](../../administer-yugabyte-platform/manage-runtime-config/) flag, `yb.tlsCertificate.organizationName`. - -Note that, for the change to take effect, you need to set the flag _before_ you run a workflow that generates a self-signed certificate. - -Customize the organization name as follows: - -1. In YugabyteDB Anywhere, navigate to **Admin** > **Advanced** and select the **Global Configuration** tab. -1. In the **Search** bar, enter `yb.tlsCertificate.organizationName` to view the flag, as per the following illustration: - - ![Custom Organization name](/images/yp/encryption-in-transit/custom-org-name.png) - -1. Click **Actions** > **Edit Configuration**, enter a new Config Value, and click **Save**. - -#### Validate custom organization name - -You can verify the organization name by running the following `openssl x509` command: - -```sh -openssl x509 -in ca.crt -text -``` - -```output {hl_lines=[6]} -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 1683277970271 (0x187eb2f7b5f) - Signature Algorithm: sha256WithRSAEncryption - Issuer: CN=yb-dev-sb-ybdemo-univ1~2, O=example.com - Validity - Not Before: May 5 09:12:50 2023 GMT - Not After : May 5 09:12:50 2027 GMT -``` - -Notice that default value is `O=example.com`. - -After setting the runtime configuration to a value of your choice, (`org-foo` in this example), you should see output similar to the following: - -```sh -openssl x509 -in ca.crt -text -noout -``` - -```output -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 1689376612248 (0x18956b15f98) - Signature Algorithm: sha256WithRSAEncryption - Issuer: CN = yb-dev-sb-ybdemo-univ1~2, O = org-foo - Validity - Not Before: Jul 14 23:16:52 2023 GMT - Not After : Jul 14 23:16:52 2027 GMT - Subject: CN = yb-dev-sb-ybdemo-univ1~2, O = org-foo - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - Public-Key: (2048 bit) - Modulus: -``` - -### Use YugabyteDB Anywhere-generated certificates to enable TLS - -When you create a universe, you can enable TLS using certificates generated by YugabyteDB Anywhere, as follows: - -1. Create a new universe via **Universes > Create Universe** and then configure it. -1. Based on your requirements, select **Enable Node-to-Node TLS** or **Enable Client-to-Node TLS** or both. -1. Choose an existing certificate from the **Root Certificate** list or create a new certificate by accepting the default option **Create new certificate**. - -To view the certificate, navigate to **Configs > Security > Encryption in Transit > Self Signed**. - -You can also modify TLS settings for an existing universe, as follows: - -1. Navigate to either **Dashboard** or **Universes** and open a specific universe. - -1. Click **Actions > Edit Security > Encryption in-Transit** to open the **TLS Configuration** dialog and then proceed as follows: - - - If encryption in transit is currently disabled for the universe, enable it via the **Encryption in Transit for this Universe** field, as per the following illustration: - - ![TLS Configuration](/images/yp/encryption-in-transit/tls-config1.png) - - Use the expanded **TLS Configuration** dialog shown in the following illustration to change the settings to meet your requirements: - - ![TLS Configuration Expanded](/images/yp/encryption-in-transit/tls-config2.png) - - - If encryption in transit is currently enabled for the universe, you can either disable or modify it, as follows: - - - To disable encryption in transit, disable the **Encryption in Transit for this Universe** field and then click **OK**. - - - To modify encryption in-transit settings, leave the **Encryption in Transit for this Universe** field enabled and make the necessary changes to other fields. - - If you are changing certificates, you need to be aware that this requires restart of the YB-Master and YB-TServer processes and can result in downtime. To avoid downtime, you should accept the default value (enabled) for the **Rolling Upgrade** field to trigger a sequential node-by-node change with a specific delay between node upgrades (as opposed to a simultaneous change of certificates in every node which occurs when the **Rolling Upgrade** field is disabled). If you select the **Create new certificate** option when changing certificates, the corresponding certificates will be rotated, that is, replaced with new certificates. - -## Self-signed self-provided certificates - -Instead of using YugabyteDB Anywhere-provided certificates, you can use your own self-signed certificates that you upload to YugabyteDB Anywhere by following the procedure described in [Use self-signed self-provided certificates to enable TLS](#use-self-signed-self-provided-certificates-to-enable-tls). - -The certificates must meet the following criteria: - -- Be in the `.crt` format and the private key must be in the `.pem` format, with both of these artifacts available for upload. -- Contain IP addresses of the target database nodes or DNS names as the Subject Alternative Names (wildcards are acceptable). - -YugabyteDB Anywhere produces the node (leaf) certificates from the uploaded certificates and copies the certificate chain, leaf certificate, and private key to the nodes in the cluster. - -### Use self-signed self-provided certificates to enable TLS - -When you create a universe, you can enable TLS using your own certificates, as follows: - -1. Navigate to **Configs > Security > Encryption in Transit**. -1. Click **Add Certificate** to open the **Add Certificate** dialog. -1. Select **Self Signed**. -1. Click **Upload Root Certificate**, then browse to the root certificate file (`.crt`) and upload it. -1. Click **Upload Key**, then browse to the root certificate file (`.key`) and upload it. -1. In the **Certificate Name** field, enter a meaningful name for your certificate. -1. In the **Expiration Date** field, specify the expiration date of the root certificate. To find this information, execute the `openssl x509 -in -text -noout` command and note the **Validity Not After** date. -1. Click **Add** to make the certificate available. -1. Go to **Universes > Create Universe** to open the **Create Universe** dialog. -1. Configure the universe. -1. Based on your requirements, select **Enable Node-to-Node TLS** and **Enable Client-to-Node TLS**. -1. Select an existing certificate from the **Root Certificate** list and then select the certificate that you have uploaded. -1. Create the universe. - -You can also modify TLS settings for an existing universe by navigating to **Universes**, opening a specific universe, clicking **Actions > Edit Security > Encryption in-Transit** to open the **TLS Configuration** dialog, and then following the procedure described in [Use YugabyteDB Anywhere-generated certificates to enable TLS](#use-yugabytedb-anywhere-generated-certificates-to-enable-tls) for an existing universe. - -## Custom CA-signed self-provided certificates - -For universes created with an on-premise cloud provider, instead of using self-signed certificates, you can use third-party certificates from external CAs. The third-party CA root certificate must be configured in YugabyteDB Anywhere. You have to copy the custom CA root certificate, node certificate, and node key to the appropriate database nodes using the procedure described in [Use custom CA-signed certificates to enable TLS](#use-custom-ca-signed-certificates-to-enable-tls). - -The certificates must adhere to the following criteria: - -- Be stored in a `.crt` file, with both the certificate and the private key being in the PEM format. - - If your certificates and keys are stored in the PKCS12 format, you can [convert them to the PEM format](#convert-certificates-and-keys-from-pkcs12-to-pem-format). - -- Contain IP addresses of the database nodes or DNS names as the Subject Alternative Names (wildcards are acceptable). - -### Use custom CA-signed certificates to enable TLS - -The following procedure describes how to install certificates on the database nodes. You have to repeat these steps for every database node that is to be used in the creation of a universe. - -**Step 1:** Obtain the keys and the custom CA-signed certificates for each of the on-premise nodes for which you are configuring node-to-node TLS. In addition, obtain the keys and the custom signed certificates for client access for configuring client-to-node TLS. - -**Step 2**: For each on-premise node, copy the custom CA root certificate, node certificate, and node key to that node's file system. - -If you are enabling client-to-node TLS, make sure to copy the client certificate and client key to each of the nodes. - -In addition, ensure the following: - -- The file names and file paths of different certificates and keys are identical across all the database nodes. For example, if you name your CA root certificate as `ca.crt` on one node, then you must name it `ca.crt` on all the nodes. Similarly, if you copy `ca.crt` to `/opt/yugabyte/keys` on one node, then you must copy `ca.crt` to the same path on other nodes. -- The yugabyte system user has read permissions to all the certificates and keys. - -**Step 3**: Create a CA-signed certificate in YugabyteDB Anywhere, as follows: - -1. Navigate to **Configs > Security > Encryption in Transit**. - -1. Click **Add Certificate** to open the **Add Certificate** dialog. - -1. Select **CA Signed**, as per the following illustration: - - ![add-cert](/images/yp/encryption-in-transit/add-cert.png) - -1. Upload the custom CA root certificate as the root certificate. - - If you use an intermediate CA/issuer, but do not have the complete chain of certificates, then you need to create a bundle by executing the `cat intermediate-ca.crt root-ca.crt > bundle.crt` command, and then use this bundle as the root certificate. You might also want to [verify the certificate chain](#verify-certificate-chain). - -1. Enter the file paths for each of the certificates on the nodes. These are the paths from the previous step. - -1. In the **Certificate Name** field, enter a meaningful name for your certificate. - -1. Use the **Expiration Date** field to specify the expiration date of the certificate. To find this information, execute the `openssl x509 -in -text -noout` command and note the **Validity Not After** date. - -1. Click **Add** to make the certificate available. - -1. Go to **Universes > Create Universe** to open the **Create Universe** dialog. - -1. Configure the universe. - -1. Based on your requirements, select **Enable Node-to-Node TLS** and **Enable Client-to-Node TLS**. - -1. Select an existing certificate from the **Root Certificate** list and then select the certificate that you have uploaded. - -1. Create the universe. - -You can rotate certificates for universes configured with the same type of certificates. This involves replacing existing certificates with new database node certificates. - -#### Convert certificates and keys from PKCS12 to PEM format - -If your certificates and keys are stored in the PKCS12 format, you can convert them to the PEM format using OpenSSL. - -You start by extracting the certificate via the following command: - -```sh -openssl pkcs12 -in cert-archive.pfx -out cert.pem -clcerts -nokeys -``` - -To extract the key and write it to the PEM file unencrypted, execute the following command: - -```sh -openssl pkcs12 -in cert-archive.pfx -out key.pem -nocerts -nodes -``` - -If the key is protected by a passphrase in the PKCS12 archive, you are prompted for the passphrase. - -#### Verify certificate chain - -Perform the following steps to verify your certificates: - -1. Execute the following verify command which checks the database node certificate (node.crt) against the root CA certificate (ca.crt): - - ```sh - openssl verify ca.crt node.crt - ``` - -1. Verify that the node certificate (`node.crt`) and the node private key (`node.key`) match. See [How do I verify that a private key matches a certificate?](https://www.ssl247.com/knowledge-base/detail/how-do-i-verify-that-a-private-key-matches-a-certificate-openssl-1527076112539/ka03l0000015hscaay/) - -1. Verify that the node certificate and Root CA certificate expiration is at least 3 months by checking the validity field in the output of the following commands: - - ```sh - openssl x509 -in node.crt -text -noout - ``` - - ```sh - openssl x509 -in ca.crt -text -noout - ``` - -1. Verify that the node certificate Common Name (CN) or Subject Alternate Name (SAN) contains the IP address or DNS name of each on-prem node on which the nodes are deployed. - - {{< note >}} -Each entry you provide for the CN or SAN must match the on-prem node as entered in the provider configuration. For example, if the node address is entered as a DNS address in the on-prem provider configuration, you must use the same DNS entry in the CN or SAN, not the resolved IP address. - {{< /note >}} - - If you face any issue with the above verification, you can customize the level of certificate validation while creating a universe that uses these certificates. Refer to [Customizing the verification of RPC server certificate by the client](https://www.yugabyte.com/blog/yugabytedb-server-to-server-encryption/#customizing-the-verification-of-rpc-server-certificate-by-the-client). - -{{< note >}} -The client certificates and keys are required only if you intend to use [PostgreSQL certificate-based authentication](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html#:~:text=independent%20authentication%20option-,clientcert,-%2C%20which%20can%20be). -{{< /note >}} - -### Rotate custom CA-signed certificates - -You can rotate certificates for universes configured with the same type of certificates. This involves replacing existing certificates with new database node certificates. - -You rotate the existing custom certificates and replace them with new database node certificates issued by the same custom CA that issued the original certificates as follows: - -**Step 1**: Follow Step 1 of [Use custom CA-signed certificates to enable TLS](#use-custom-ca-signed-certificates-to-enable-tls) to obtain a new set of certificates for each of the nodes. - -**Step 2**: Follow Step 2 of [Use custom CA-signed certificates to enable TLS](#use-custom-ca-signed-certificates-to-enable-tls) to copy the certificates to the respective nodes. - -**Step 3**: Follow Step 3 of [Use custom CA-signed certificates to enable TLS](#use-custom-ca-signed-certificates-to-enable-tls) to create a new CA-signed certificate in YugabyteDB Anywhere. - -**Step 4**: Edit the universe to use the new certificates, as follows: - -- Navigate to the universe for which you are rotating the keys. - -- Select **Actions > Edit Security**, as shown in the following illustration: - - ![edit-security](/images/yp/encryption-in-transit/edit-security.png) - -- Select **Encryption in-Transit** to open the **TLS Configuration** dialog. - -- Complete the **TLS Configuration** dialog shown in the following illustration: - - ![Configure TLS](/images/yp/encryption-in-transit/edit-tls-new.png) - - - Select the new certificate which you created in Step 3. - - - Modifying certificates requires restart of YB-Master and YB-TServer processes, which can result in downtime. To avoid downtime, you should accept the default value (enabled) for the **Rolling Upgrade** field to trigger a sequential node-by-node change with a specific delay between node upgrades (as opposed to a simultaneous change of certificates in every node which occurs when the **Rolling Upgrade** field is disabled). - - - Click **OK**. - - Typically, this process takes time, as it needs to wait for the specified delay interval after each node is upgraded. - -### Expand the universe - -You can expand universes configured with custom CA-signed certificates. - -Before adding new nodes to expand an existing universe, you need to prepare those nodes by repeating Step 2 of [Use custom CA-signed certificates to enable TLS](#use-custom-ca-signed-certificates-to-enable-tls) for each of the new nodes you plan to add to the universe. You need to ensure that the certificates are signed by the same external CA and have the same root certificate. In addition, ensure that you copy the certificates to the same locations that you originally used when creating the universe. - -When the universe is ready for expansion, complete the **Edit Universe** dialog to add new nodes. - -## Custom HashiCorp Vault-provided certificates - -YugabyteDB Anywhere allows you to add an encryption in transit configuration using HashiCorp Vault with a public key infrastructure (PKI) secret engine. This configuration can be used to enable TLS for different clusters and YugabyteDB instances. You can apply this configuration to node-to-node encryption, client-to-node encryption, or both. - -For the correct configuration, the following criteria must be met: - -- HashiCorp Vault is unsealed. - -- HashiCorp Vault with the PKI secret engine is configured and enabled. -- HashiCorp Vault URL is accessible by YugabyteDB Anywhere. -- Because HashiCorp Vault is accessed via an authentication token mechanism, a token must be created beforehand while creating a key provider with appropriate permissions. -- HashiCorp Vault needs to be running and always accessible to YugabyteDB Anywhere. -- HashiCorp PKI certificate revocation list (CRL) or CA URLs must be accessible from each node server. -- Appropriate certificates and roles have been created for YugabyteDB Anywhere usage. -- Node servers are able to validate certificates. -- Required permissions have been provided to perform various key management operations. - -### Configure HashiCorp Vault - -Before you can start configuring HashiCorp Vault, install it on a virtual machine, as per instructions provided in [Install Vault](https://www.vaultproject.io/docs/install). The vault can be set up as a multi-node cluster. Ensure that your vault installation meets the following requirements: - -- Has transit secret engine enabled. -- Its seal and unseal mechanism is secure and repeatable. -- Its token creation mechanism is repeatable. - -You need to configure HashiCorp Vault in order to use it with YugabyteDB Anywhere, as follows: - -1. Create a vault configuration file that references your nodes and specifies the address, as follows: - - ```properties - storage "raft" { - path = "./vault/data/" - node_id = "node1" - } - - listener "tcp" { - address = "127.0.0.1:8200" - tls_disable = "true" - } - - api_addr = "http://127.0.0.1:8200" - cluster_addr = "https://127.0.0.1:8201" - ui = true - disable_mlock = true - default_lease_ttl = "768h" - max_lease_ttl = "8760h" - ``` - - Replace `127.0.0.1` with the vault web address. - - For additional configuration options, see [Parameters](https://www.vaultproject.io/docs/configuration#parameters). - -1. Initialize the vault server by following instructions provided in [Operator init](https://www.vaultproject.io/docs/commands/operator/init). - -1. Allow access to the vault by following instructions provided in [Unsealing](https://www.vaultproject.io/docs/concepts/seal#unsealing). - -1. Enable the secret engine by executing the following command: - - ```shell - vault secrets enable pki - ``` - -1. Configure the secret engine, as follows: - - - Create a root CA or configure the top-level CA. - - - Optionally, create an intermediate CA chain and sign them. - - - Create an intermediate CA for YugabyteDB, as per the following example: - - ```sh - export pki=pki - export pki_int="pki_int" - export role_i=RoleName - export ip="s.test.com" - - vault secrets enable -path=$pki_int pki - vault secrets tune -max-lease-ttl=43800h $pki_int - vault write $pki_int/intermediate/generate/internal common_name="test.com Intermediate Authority" ttl=43800h -format=json | jq -r '.data.csr' > pki_int.csr - - \# *** dump the output of the preceding command in pki_int.csr - - vault write $pki/root/sign-intermediate csr=@pki_int.csr format=pem_bundle ttl=43800h -format=json | jq -r .data.certificate > i_signed.pem - - \# *** dump the output in i_signed.pem - - vault write $pki_int/intermediate/set-signed certificate=@i_signed.pem - vault write $pki_int/config/urls issuing_certificates="http://127.0.0.1:8200/v1/pki_int/ca" crl_distribution_points="http://127.0.0.1:8200/v1/pki_int/crl" - ``` - -1. Create the vault policy, as per the following example: - - ```properties - # Enable secrets engine - path "sys/mounts/*" { - capabilities = ["create", "read", "update", "delete", "list"] - } - - # List enabled secrets engine - path "sys/mounts" { - capabilities = ["read", "list"] - } - - # Work with pki secrets engine - path "pki*" { - capabilities = ["create", "read", "update", "delete", "list", "sudo"] - } - ``` - -1. Generate a token with appropriate permissions (as per the referenced policy) by executing the following command: - - ```shell - vault token create -no-default-policy -policy=pki_policy - ``` - - You may also specify the following for your token: - - - `ttl` — Time to live (TTL). If not specified, the default TTL of 32 days is used, which means that the generated token will expire after 32 days. - - `period` — If specified, the token can be infinitely renewed. - - YBA automatically tries to renew the token every 12 hours after it has passed 70% of its expiry window; as a result, you should set the TTL or period to be greater than 12 hours. - - For more information, refer to [Tokens](https://developer.hashicorp.com/vault/tutorials/tokens/tokens) in the Hashicorp documentation. - -1. Create a role that maps a name in the vault to a procedure for generating a certificate, as follows: - - ```sh - vault write /roles/ allow_any_name=true allow_subdomains=true max_ttl="8640h" - ``` - - Credentials are generated against this role. - -1. Issue certificates for nodes or a YugabyteDB client: - - - For a node, execute the following: - - ```sh - vault write /issue/ common_name="" ip_sans="" ttl="860h" - ``` - - - For YugabyteDB client, execute the following: - - ```sh - vault write /issue/ common_name="" - ``` - -### Use HashiCorp Vault-provided certificates to enable TLS - -When you create a universe, you can enable TLS using certificates provided by HashiCorp Vault, as follows: - -1. Navigate to **Configs > Security > Encryption in Transit**. -1. Click **Add Certificate** to open the **Add Certificate** dialog. -1. Select **Hashicorp**. -1. In the **Config Name** field, enter a meaningful name for your configuration. -1. In the **Vault Address** field, specify a valid URL that includes the port number. The format is `http://0.0.0.0:0000`, which corresponds to `VAULT_HOSTNAME:0000` -1. In the **Secret Token** field, specify the secret token for the vault. -1. In the **Role** field, specify the role used for creating certificates. -1. Optionally, provide the secret engine path on which the PKI is mounted. If you do not supply this information, `pki/` will be used. -1. Click **Add** to make the certificate available. -1. Go to **Universes > Create Universe** to open the **Create Universe** dialog. -1. Configure the universe. -1. Based on your requirements, select **Enable Node-to-Node TLS** and **Enable Client-to-Node TLS**. -1. Select an existing certificate from the **Root Certificate** list and then select the certificate that you have uploaded. -1. Create the universe. - -You can also edit TLS settings for an existing universe by navigating to **Universes**, opening a specific universe, clicking **Actions > Edit Security > Encryption in-Transit** to open the **TLS Configuration** dialog, and then modifying the required settings. - -## Kubernetes cert-manager - -For a universe created on Kubernetes, YugabyteDB Anywhere allows you to configure an existing running instance of the [cert-manager](https://cert-manager.io/) as a TLS certificate provider for a cluster, assuming that the following criteria are met: - -- The cert-manager is running in the Kubernetes cluster. -- A root or intermediate CA (either self-signed or external) is already configured on the cert-manager. The same root certificate file must be prepared for upload to YugabyteDB Anywhere. -- An Issuer or ClusterIssuer Kind is configured on the cert-manager and is ready to issue certificates using the previously-mentioned root or intermediate certificate. - -During the universe creation, you can enable TLS certificates issued by the cert-manager, as follows: - -1. Upload the root certificate to YugabyteDB Anywhere: - - - Prepare the root certificate in a file (for example, `root.crt`). - - Navigate to **Configs > Security > Encryption in Transit** and click **Add Certificate**. - - On the **Add Certificate** dialog shown in the following illustration, select **K8S cert-manager**: - - ![Add Certificate](/images/yp/security/kubernetes-cert-manager.png) - - - In the **Certificate Name** field, enter a meaningful name for your certificate configuration. - - Click **Upload Root Certificate** and select the root certificate file that you prepared. - - Click **Add** to make the certificate available. - -1. Configure the Kubernetes-based cloud provider by following instructions provided in [Configure region and zones](../../configure-yugabyte-platform/kubernetes/#configure-region-and-zones). In the **Add new region** dialog shown in the following illustration, you would be able to specify the Issuer name or the ClusterIssuer name for each zone. Because an Issuer Kind is a Kubernetes namespace-scoped resource, the zone definition should also set the **Namespace** field value if an Issuer Kind is selected: - - ![Add new region](/images/yp/security/kubernetes-cert-manager-add-region.png) - -1. Create the universe: - - - Navigate to **Universes** and click **Create Universe**. - - In the **Provider** field, select the cloud provider that you have configured in step 2. - - Complete the fields based on your requirements, and select **Enable Node-to-Node TLS** or **Enable Client-to-Node TLS**. - - Select the root certificate that you have uploaded in step 1. - - Click **Create**. - -### Troubleshoot - -If you encounter problems, you should verify the name of Issuer or ClusterIssuer in the Kubernetes cluster, as well as ensure that the Kubernetes cluster is in Ready state. You can use the following commands: - -```sh -kubectl get ClusterIssuer -``` - -```sh -kubectl -n Issuer -``` - -## Connect to clusters - -Using TLS, you can connect to the YSQL and YCQL endpoints. - -### Connect to a YSQL endpoint with TLS - -If you created your universe with the Client-to-Node TLS option enabled, then you must download client certificates to your client computer to establish connection to your database, as follows: - -- Navigate to the **Certificates** page and then to your universe's certificate. - -- Click **Actions** and select **Download YSQL Cert**, as shown in the following illustration. This triggers the download of the `yugabytedb.crt` and `yugabytedb.key` files. - - ![download-ysql-cert](/images/yp/encryption-in-transit/download-ysql-cert.png) - -- Optionally, when connecting to universes that are configured with custom CA-signed certificates, obtain the root CA and client YSQL certificate from your administrator. These certificates are not available on YugabyteDB Anywhere for downloading. - -- For testing with a `ysqlsh` client, paste the `yugabytedb.crt` and `yugabytedb.key` files into the `/.yugabytedb` directory and change the permissions to `0600`, as follows: - - ```sh - mkdir ~/.yugabytedb; cd ~/.yugabytedb - cp /yugabytedb.crt . - cp /yugabytedb.key . - chmod 600 yugabytedb.* - ``` - -- Run `ysqlsh` using the `sslmode=require` option, as follows: - - ```sh - cd - bin/ysqlsh -h 172.152.43.78 -p 5433 sslmode=require - ``` - - ```output - ysqlsh (11.2-YB-2.3.3.0-b0) - SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off) - Type "help" for help. - - yugabyte=# - ``` - -To use TLS from a different client, consult the client-specific documentation. For example, if you are using a PostgreSQL JDBC driver to connect to YugabyteDB, see [Configuring the client](https://jdbc.postgresql.org/documentation/head/ssl-client.html) for more details. - -If you are using PostgreSQL/YugabyteDB JDBC driver with SSL, you need to convert the certificates to DER format. To do this, you need to perform only steps 6 and 7 from [Set up SSL certificates for Java applications](../../../reference/drivers/java/postgres-jdbc-reference/#set-up-ssl-certificates-for-java-applications) section after downloading the certificates. - -### Connect to a YCQL endpoint with TLS - -If you created your universe with the Client-to-Node TLS option enabled, then you must download client certificates to your client computer to establish connection to your database, as follows: - -- Navigate to the **Certificates** page and then to your universe's certificate. - -- Click **Actions** and select **Download Root Cert**, as shown in the following illustration. This triggers the download of the `root.crt` file. - - ![download-root-cert](/images/yp/encryption-in-transit/download-root-cert.png) - -- Optionally, when connecting to universes that are configured with custom CA-signed certificates, obtain the root CA and client YSQL certificate from your administrator. These certificates are not available on YugabyteDB Anywhere for downloading. - -- Set `SSL_CERTFILE` environment variable to point to the location of the downloaded root certificate. - -- Run `ycqlsh` using the `-ssl` option, as follows: - - ```sh - cp /root.crt ~/.yugabytedb/root.crt - export SSL_CERTFILE=~/.yugabytedb/root.crt - bin/ycqlsh 172.152.43.78 --ssl - ``` - - ```output - Connected to local cluster at 172.152.43.78:9042. - [ycqlsh 5.0.1 | Cassandra 3.9-SNAPSHOT | CQL spec 3.4.2 | Native protocol v4] - Use HELP for help. - ycqlsh> - ``` - -To use TLS from a different client, consult the client-specific documentation. For example, if you are using a Cassandra driver to connect to YugabyteDB, see [SSL](https://docs.datastax.com/en/developer/python-driver/3.19/security/#ssl). - -## Validate certificates - -When configuring and using certificates, SSL issues may occasionally arise. You can validate your certificates and keys as follows: - -1. Verify that the CA CRT and CA private key match by executing the following commands: - - ```shell - openssl rsa -noout -modulus -in ca.key | openssl md5 - openssl x509 -noout -modulus -in ca.crt | openssl md5 - - \# outputs should match - ``` - -2. Verify that the CA CRT is actually a certificate authority by executing the following command: - - ```shell - openssl x509 -text -noout -in ca.crt - - \# Look for fields - - X509v3 Basic Constraints: - - CA:TRUE - ``` - -3. Verify that certificates and keys are in PEM format (as opposed to the DER or other format). If these artifacts are not in the PEM format and you require assistance with converting them or identifying the format, consult [Converting certificates](https://support.globalsign.com/ssl/ssl-certificates-installation/converting-certificates-openssl). - -4. Ensure that the private key does not have a passphrase associated with it. For information on how to identify this condition, see [Decrypt an encrypted SSL RSA private key](https://techjourney.net/how-to-decrypt-an-enrypted-ssl-rsa-private-key-pem-key/). - -## Enforcing TLS versions - -As TLS 1.0 and 1.1 are no longer accepted by PCI compliance, and considering significant vulnerabilities around these versions of the protocol, it is recommended that you migrate to TLS 1.2 or later versions. - -You can set the TLS version for node-to-node and client-node communication. To enforce TLS 1.2, add the following flag for YB-TServer: - -```shell -ssl_protocols = tls12 -``` - -To enforce the minimum TLS version of 1.2, you need to specify all available subsequent versions for YB-TServer, as follows: - -```shell -ssl_protocols = tls12,tls13 -``` - -In addition, as the `ssl_protocols` setting does not propagate to PostgreSQL, it is recommended that you specify the minimum TLS version (`ssl_min_protocol_version`) for PostgreSQL by setting the following YB-TServer flag: - -```shell ---ysql_pg_conf_csv="ssl_min_protocol_version='TLSv1.2'" -``` - -## Use self-signed and custom CA certificates - -YugabyteDB Anywhere uses TLS to protect data in transit when connecting to other services, including: - -- LDAP -- OIDC -- Webhook -- [S3 backup storage](../../back-up-restore-universes/configure-backup-storage/) -- Hashicorp Vault -- [YBA high availability](../../administer-yugabyte-platform/high-availability/) - -If you are using self-signed or custom CA certificates, YugabyteDB cannot verify your TLS connections unless you add the certificates to the YugabyteDB Anywhere Trust Store. - -### Add certificates to your trust store - -To add a certificate to the YugabyteDB Anywhere Trust Store, do the following: - -1. Navigate to **Admin > CA Certificates**. - -1. Click **Upload Trusted CA Certificate**. - -1. Enter a name for the certificate. - -1. Click **Upload**, select your certificate (in .crt format) and click **Save CA Certificate**. - -### Rotate a certificate in your trust store - -To rotate a certificate in your YugabyteDB Anywhere Trust Store, do the following: - -1. Navigate to **Admin > CA Certificates**. - -1. Click the **...** button for the certificate and choose **Update Certificate**. - -1. Click **Upload**, select your certificate (in .crt format) and click **Save CA Certificate**. - -### Delete a certificate in your trust store - -To delete a certificate in your YugabyteDB Anywhere Trust Store, do the following: - -1. Navigate to **Admin > CA Certificates**. - -1. Click the **...** button for the certificate and choose **Delete**, then click **Delete CA Certificate**. diff --git a/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/_index.md b/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/_index.md new file mode 100644 index 000000000000..410e552ad5de --- /dev/null +++ b/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/_index.md @@ -0,0 +1,90 @@ +--- +title: Encryption in transit in YugabyteDB Anywhere +headerTitle: Encryption in transit +linkTitle: Encryption in transit +description: Use encryption in transit (TLS) to secure data traffic. +headcontent: Secure intra-node and application traffic +menu: + stable_yugabyte-platform: + parent: security + identifier: enable-encryption-in-transit + weight: 40 +type: indexpage +showRightNav: true +--- + +YugabyteDB Anywhere allows you to protect data in transit by using the following: + +- Node-to-Node TLS to encrypt intra-node communication between YB-Master and YB-TServer nodes. +- Client-to-Node TLS to encrypt communication between a universe and clients. This includes applications, shells (ysqlsh, ycqlsh, psql, and so on), and other tools, using the YSQL and YCQL APIs. + +## Manage certificates + +Use YugabyteDB Anywhere to manage certificates used for encryption in transit. + +{{}} + + {{}} + + {{}} + + {{}} + + {{}} + +{{}} + +## Enable encryption in transit + +You enable Node-to-Node and Client-to-Node encryption in transit when you [create a universe](../../create-deployments/create-universe-multi-zone/). + +You can also enable and disable encryption in transit for an existing universe as follows: + +1. Navigate to your universe. +1. Click **Actions > Edit Security > Encryption in-Transit** to open the **Manage encryption in transit** dialog. +1. Enable or disable the **Enable encryption in transit for this Universe** option. +1. Click **Apply**. + +### Enforce TLS versions + +As TLS 1.0 and 1.1 are no longer accepted by PCI compliance, and considering significant vulnerabilities around these versions of the protocol, it is recommended that you migrate to TLS 1.2 or later versions. + +You can set the TLS version for node-to-node and client-node communication. To enforce TLS 1.2, add the following flag for YB-TServer: + +```shell +ssl_protocols = tls12 +``` + +To enforce the minimum TLS version of 1.2, you need to specify all available subsequent versions for YB-TServer, as follows: + +```shell +ssl_protocols = tls12,tls13 +``` + +In addition, as the `ssl_protocols` setting does not propagate to PostgreSQL, it is recommended that you specify the minimum TLS version (`ssl_min_protocol_version`) for PostgreSQL by setting the following YB-TServer flag: + +```shell +--ysql_pg_conf_csv="ssl_min_protocol_version='TLSv1.2'" +``` + +## Learn more + +- [Securing YugabyteDB: Server-to-Server Encryption in Transit](https://www.yugabyte.com/blog/yugabytedb-server-to-server-encryption/) +- [Securing YugabyteDB: SQL Client-to-Server Encryption in Transit](https://www.yugabyte.com/blog/securing-yugabytedb-client-to-server-encryption/) +- [Securing YugabyteDB: CQL Client-to-Server Encryption in Transit](https://www.yugabyte.com/blog/securing-yugabytedb-part-3-cql-client-server-encryption-transit/) diff --git a/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-ca.md b/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-ca.md new file mode 100644 index 000000000000..0a2dd3fe5b0f --- /dev/null +++ b/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-ca.md @@ -0,0 +1,113 @@ +--- +title: Add CA-signed certificates to YugabyteDB Anywhere +headerTitle: Add certificates +linkTitle: Add certificates +description: Add CA-signed certificates to YugabyteDB Anywhere. +headcontent: Use your own certificates for encryption in transit +menu: + stable_yugabyte-platform: + parent: enable-encryption-in-transit + identifier: add-certificate-2-ca + weight: 20 +type: docs +--- + +{{}} +{{}} +{{}} +{{}} +{{}} +{{}} + +For universes created with an on-premises provider, instead of using self-signed certificates, you can use third-party certificates from external certificate authorities (CA). The third-party CA root certificate must be configured in YugabyteDB Anywhere. You also have to copy the custom CA root certificate, node certificate, and node key to the appropriate on-premises provider nodes. + +## Prerequisites + +The server and CA certificates must adhere to the following criteria: + +- Be stored in a `.crt` file, with both the certificate and the private key being in the PEM format. + + If your certificates and keys are stored in the PKCS12 format, you can [convert them to the PEM format](#convert-certificates-and-keys-from-pkcs12-to-pem-format). + +The server certificates must adhere to the following criteria: + +- Contain IP addresses of the database nodes in the Common Name or in the Subject Alternative Name. For on-premises universes where nodes are identified using DNS addresses, the server certificates should include the DNS names of the database nodes in the Common Name or Subject Alternate Name (wildcards are acceptable). + +## Add CA-signed certificates + +The following procedure describes how to install certificates on the database nodes. You have to repeat these steps for every database node that is to be used in the creation of a universe. + +### Obtain certificates and keys + +Obtain the keys and the custom CA-signed certificates for each of the on-premise nodes for which you are configuring node-to-node TLS. In addition, obtain the keys and the custom signed certificates for client access for configuring client-to-node TLS. + +### Copy the certificates to each node + +For each on-premises provider node, copy the custom CA certificate, node certificate, and node key to that node's file system. + +If you are enabling client-to-node TLS, make sure to copy the client-facing server certificate and client-facing server key to each of the nodes. + +In addition, ensure the following: + +- The file names and file paths of different certificates and keys are identical across all the database nodes. For example, if you name your CA root certificate as `ca.crt` on one node, then you must name it `ca.crt` on all the nodes. Similarly, if you copy `ca.crt` to `/opt/yugabyte/keys` on one node, then you must copy `ca.crt` to the same path on other nodes. +- The `yugabyte` system user has read permissions to all the certificates and keys. + +### Add the CA certificate to YugabyteDB Anywhere + +Add a CA-signed certificate to YugabyteDB Anywhere as follows: + +1. Navigate to **Configs > Security > Encryption in Transit**. + +1. Click **Add Certificate** to open the **Add Certificate** dialog. + +1. Select **CA Signed**, as per the following illustration: + + ![Add CA certificate](/images/yp/encryption-in-transit/add-cert.png) + +1. In the **Certificate Name** field, enter a meaningful name for your certificate. + +1. Upload the custom CA certificate (including any intermediate certificates in the chain) as the Root CA certificate. + + If you use an intermediate CA/issuer, but do not have the complete chain of certificates, then you need to create a bundle by executing the `cat intermediate-ca.crt root-ca.crt > bundle.crt` command, and then use this bundle as the root certificate. You might also want to [verify the certificate chain](#verify-certificate-chain). + +1. Enter the file paths for each of the certificates on the nodes. These are the paths from the previous step. + +1. Use the **Expiration Date** field to specify the expiration date of the certificate. To find this information, execute the `openssl x509 -in -text -noout` command and note the **Validity Not After** date. + +1. Click **Add** to make the certificate available. + +You can rotate certificates for universes configured with the same type of certificates. This involves replacing existing certificates with new database node certificates. + +### Verify certificate chain + +Perform the following steps to verify your certificates: + +1. Execute the following verify command which checks the database node certificate (node.crt) against the root CA certificate (ca.crt): + + ```sh + openssl verify ca.crt node.crt + ``` + +1. Verify that the node certificate (`node.crt`) and the node private key (`node.key`) match. See [How do I verify that a private key matches a certificate?](https://www.ssl247.com/knowledge-base/detail/how-do-i-verify-that-a-private-key-matches-a-certificate-openssl-1527076112539/ka03l0000015hscaay/) + +1. Verify that the node certificate and Root CA certificate expiration is at least 3 months by checking the validity field in the output of the following commands: + + ```sh + openssl x509 -in node.crt -text -noout + ``` + + ```sh + openssl x509 -in ca.crt -text -noout + ``` + +1. Verify that the node certificate Common Name (CN) or Subject Alternate Name (SAN) contains the IP address or DNS name of each on-premises node on which the nodes are deployed. + + {{< note >}} +Each entry you provide for the CN or SAN must match the on-premises node as entered in the provider configuration. For example, if the node address is entered as a DNS address in the on-premises provider configuration, you must use the same DNS entry in the CN or SAN, not the resolved IP address. + {{< /note >}} + + If you face any issue with the above verification, you can customize the level of certificate validation while creating a universe that uses these certificates. Refer to [Customizing the verification of RPC server certificate by the client](https://www.yugabyte.com/blog/yugabytedb-server-to-server-encryption/#customizing-the-verification-of-rpc-server-certificate-by-the-client). + +{{< note >}} +The client certificates and keys are required only if you intend to use [PostgreSQL certificate-based authentication](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html#:~:text=independent%20authentication%20option-,clientcert,-%2C%20which%20can%20be). +{{< /note >}} diff --git a/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-hashicorp.md b/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-hashicorp.md new file mode 100644 index 000000000000..3ed069276439 --- /dev/null +++ b/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-hashicorp.md @@ -0,0 +1,189 @@ +--- +title: Add Hashicorp Vault certificates to YugabyteDB Anywhere +headerTitle: Add certificates +linkTitle: Add certificates +description: Add Hashicorp Vault certificates to YugabyteDB Anywhere. +headcontent: Use your own certificates for encryption in transit +menu: + stable_yugabyte-platform: + parent: enable-encryption-in-transit + identifier: add-certificate-3-hashicorp + weight: 20 +type: docs +--- + +{{}} +{{}} +{{}} +{{}} +{{}} +{{}} + +YugabyteDB Anywhere allows you to add an encryption in transit configuration using HashiCorp Vault with a public key infrastructure (PKI) secret engine. This configuration can be used to enable TLS for different clusters and YugabyteDB instances. You can apply this configuration to node-to-node encryption, client-to-node encryption, or both. + +## Prerequisites + +For the correct configuration, the following criteria must be met: + +- HashiCorp Vault is unsealed. +- HashiCorp Vault with the PKI secret engine is configured and enabled. +- HashiCorp Vault URL is accessible by YugabyteDB Anywhere. +- Because HashiCorp Vault is accessed via an authentication token mechanism, a token must be created beforehand while creating a key provider with appropriate permissions. +- HashiCorp Vault needs to be running and always accessible to YugabyteDB Anywhere. +- HashiCorp PKI certificate revocation list (CRL) or CA URLs must be accessible from each node server. +- Appropriate certificates and roles have been created for YugabyteDB Anywhere usage. +- Node servers are able to validate certificates. +- Required permissions have been provided to perform various key management operations. + +## Configure HashiCorp Vault + +Before you can start configuring HashiCorp Vault, install it on a virtual machine, as per instructions provided in [Install Vault](https://www.vaultproject.io/docs/install). The vault can be set up as a multi-node cluster. Ensure that your vault installation meets the following requirements: + +- Has transit secret engine enabled. +- Its seal and unseal mechanism is secure and repeatable. +- Its token creation mechanism is repeatable. + +You need to configure HashiCorp Vault in order to use it with YugabyteDB Anywhere, as follows: + +1. Create a vault configuration file that references your nodes and specifies the address, as follows: + + ```properties + storage "raft" { + path = "./vault/data/" + node_id = "node1" + } + + listener "tcp" { + address = "127.0.0.1:8200" + tls_disable = "true" + } + + api_addr = "http://127.0.0.1:8200" + cluster_addr = "https://127.0.0.1:8201" + ui = true + disable_mlock = true + default_lease_ttl = "768h" + max_lease_ttl = "8760h" + ``` + + Replace `127.0.0.1` with the vault web address. + + For additional configuration options, see [Parameters](https://www.vaultproject.io/docs/configuration#parameters). + +1. Initialize the vault server by following instructions provided in [Operator init](https://www.vaultproject.io/docs/commands/operator/init). + +1. Allow access to the vault by following instructions provided in [Unsealing](https://www.vaultproject.io/docs/concepts/seal#unsealing). + +1. Enable the secret engine by executing the following command: + + ```shell + vault secrets enable pki + ``` + +1. Configure the secret engine, as follows: + + - Create a root CA or configure the top-level CA. + + - Optionally, create an intermediate CA chain and sign them. + + - Create an intermediate CA for YugabyteDB, as per the following example: + + ```sh + export pki=pki + export pki_int="pki_int" + export role_i=RoleName + export ip="s.test.com" + + vault secrets enable -path=$pki_int pki + vault secrets tune -max-lease-ttl=43800h $pki_int + vault write $pki_int/intermediate/generate/internal common_name="test.com Intermediate Authority" ttl=43800h -format=json | jq -r '.data.csr' > pki_int.csr + + \# *** dump the output of the preceding command in pki_int.csr + + vault write $pki/root/sign-intermediate csr=@pki_int.csr format=pem_bundle ttl=43800h -format=json | jq -r .data.certificate > i_signed.pem + + \# *** dump the output in i_signed.pem + + vault write $pki_int/intermediate/set-signed certificate=@i_signed.pem + vault write $pki_int/config/urls issuing_certificates="http://127.0.0.1:8200/v1/pki_int/ca" crl_distribution_points="http://127.0.0.1:8200/v1/pki_int/crl" + ``` + +1. Create the vault policy, as per the following example: + + ```properties + # Enable secrets engine + path "sys/mounts/*" { + capabilities = ["create", "read", "update", "delete", "list"] + } + + # List enabled secrets engine + path "sys/mounts" { + capabilities = ["read", "list"] + } + + # Work with pki secrets engine + path "pki*" { + capabilities = ["create", "read", "update", "delete", "list", "sudo"] + } + ``` + +1. Generate a token with appropriate permissions (as per the referenced policy) by executing the following command: + + ```shell + vault token create -no-default-policy -policy=pki_policy + ``` + + You may also specify the following for your token: + + - `ttl` — Time to live (TTL). If not specified, the default TTL of 32 days is used, which means that the generated token will expire after 32 days. + - `period` — If specified, the token can be infinitely renewed. + + YugabyteDB Anywhere automatically tries to renew the token every 12 hours after it has passed 70% of its expiry window; as a result, you should set the TTL or period to be greater than 12 hours. + + For more information, refer to [Tokens](https://developer.hashicorp.com/vault/tutorials/tokens/tokens) in the Hashicorp documentation. + +1. Create a role that maps a name in the vault to a procedure for generating a certificate, as follows: + + ```sh + vault write /roles/ allow_any_name=true allow_subdomains=true max_ttl="8640h" + ``` + + Credentials are generated against this role. + +1. Issue certificates for nodes or a YugabyteDB client: + + - For a node, execute the following: + + ```sh + vault write /issue/ common_name="" ip_sans="" ttl="860h" + ``` + + - For YugabyteDB client, execute the following: + + ```sh + vault write /issue/ common_name="" + ``` + +## Add HashiCorp Vault-provided certificates + +When you create a universe, you can enable TLS using certificates provided by HashiCorp Vault, as follows: + +1. Navigate to **Configs > Security > Encryption in Transit**. + +1. Click **Add Certificate** to open the **Add Certificate** dialog. + +1. Select **Hashicorp**. + + ![Add Hashicorp certificate](/images/yp/encryption-in-transit/add-hashicorp-cert.png) + +1. In the **Config Name** field, enter a meaningful name for your configuration. + +1. In the **Vault Address** field, specify a valid URL that includes the port number. The format is `http://0.0.0.0:0000`, which corresponds to `VAULT_HOSTNAME:0000` + +1. In the **Secret Token** field, specify the secret token for the vault. + +1. In the **Role** field, specify the role used for creating certificates. + +1. Optionally, provide the secret engine path on which the PKI is mounted. If you do not supply this information, `pki/` will be used. + +1. Click **Add** to make the certificate available. diff --git a/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-kubernetes.md b/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-kubernetes.md new file mode 100644 index 000000000000..b899acfdf666 --- /dev/null +++ b/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-kubernetes.md @@ -0,0 +1,69 @@ +--- +title: Add cert-manager certificates to YugabyteDB Anywhere +headerTitle: Add certificates +linkTitle: Add certificates +description: Add cert-manager certificates to YugabyteDB Anywhere. +headcontent: Use your own certificates for encryption in transit +menu: + stable_yugabyte-platform: + parent: enable-encryption-in-transit + identifier: add-certificate-4-kubernetes + weight: 20 +type: docs +--- + +{{}} +{{}} +{{}} +{{}} +{{}} +{{}} + +For a universe created on Kubernetes, YugabyteDB Anywhere allows you to configure an existing running instance of the [cert-manager](https://cert-manager.io/) as a TLS certificate provider for a cluster. + +## Prerequisites + +The following criteria must be met: + +- The cert-manager is running in the Kubernetes cluster. +- A root or intermediate CA (either self-signed or external) is already configured on the cert-manager. The same CA certificate file, including any intermediate CAs, must be prepared for upload to YugabyteDB Anywhere. For intermediate certificates, the chained CA certificate can be constructed using a command similar to `cat intermediate-ca.crt root-ca.crt > bundle.crt`. +- An Issuer or ClusterIssuer Kind is configured on the cert-manager and is ready to issue certificates using the previously-mentioned root or intermediate certificate. +- Prepare the root certificate in a file (for example, `root.crt`). + +## Add certificates using cert-manager + +Add TLS certificates issued by the cert-manager as follows: + +1. Navigate to **Configs > Security > Encryption in Transit**. + +1. Click **Add Certificate** to open the **Add Certificate** dialog. + +1. Select **K8S cert-manager**. + + ![Add Kubernetes Certificate](/images/yp/encryption-in-transit/add-k8s-cert.png) + +1. In the **Certificate Name** field, enter a meaningful name for your certificate. + +1. Click **Upload Root Certificate** and select the CA certificate file that you prepared. + +1. Click **Add** to make the certificate available. + +## Configure the provider + +After the certificate is added to YugabyteDB Anywhere, configure the Kubernetes provider configuration by following instructions provided in [Configure region and zones](../../../configure-yugabyte-platform/kubernetes/#configure-region-and-zones). + +In the **Add new region** dialog shown in the following illustration, you would be able to specify the Issuer name or the ClusterIssuer name for each zone. Because an Issuer Kind is a Kubernetes namespace-scoped resource, the zone definition should also set the **Namespace** field value if an Issuer Kind is selected. + +![Add new region](/images/yp/security/kubernetes-cert-manager-add-region.png) + +## Troubleshoot + +If you encounter problems, you should verify the name of Issuer or ClusterIssuer in the Kubernetes cluster, as well as ensure that the Kubernetes cluster is in Ready state. You can use the following commands: + +```sh +kubectl get ClusterIssuer +``` + +```sh +kubectl -n Issuer +``` diff --git a/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-self.md b/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-self.md new file mode 100644 index 000000000000..f2600a6cf505 --- /dev/null +++ b/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/add-certificate-self.md @@ -0,0 +1,99 @@ +--- +title: Add self-signed certificates to YugabyteDB Anywhere +headerTitle: Add certificates +linkTitle: Add certificates +description: Add self-signed certificates to YugabyteDB Anywhere. +headcontent: Use your own certificates for encryption in transit +menu: + stable_yugabyte-platform: + parent: enable-encryption-in-transit + identifier: add-certificate-1-self + weight: 20 +type: docs +--- + +{{}} +{{}} +{{}} +{{}} +{{}} +{{}} + +Instead of using YugabyteDB Anywhere-provided certificates, you can use your own self-signed certificates that you upload to YugabyteDB Anywhere. + +## Prerequisites + +The certificates must meet the following criteria: + +- Be in the `.crt` format and the private key must be in the `.pem` format, with both of these artifacts available for upload. + +YugabyteDB Anywhere produces the node (leaf) certificates from the uploaded certificates and copies the certificate chain, leaf certificate, and private key to the nodes in the cluster. + +### Convert certificates and keys from PKCS12 to PEM format + +If your certificates and keys are stored in the PKCS12 format, you can convert them to the PEM format using OpenSSL. + +Start by extracting the certificate via the following command: + +```sh +openssl pkcs12 -in cert-archive.pfx -out cert.pem -clcerts -nokeys +``` + +To extract the key and write it to the PEM file unencrypted, execute the following command: + +```sh +openssl pkcs12 -in cert-archive.pfx -out key.pem -nocerts -nodes +``` + +If the key is protected by a passphrase in the PKCS12 archive, you are prompted for the passphrase. + +## Add self-signed certificates + +To add self-signed certificates to YugabyteDB Anywhere: + +1. Navigate to **Configs > Security > Encryption in Transit**. + +1. Click **Add Certificate** to open the **Add Certificate** dialog. + +1. Select **Self Signed**. + + ![Add Self Signed certificate](/images/yp/encryption-in-transit/add-self-cert.png) + +1. In the **Certificate Name** field, enter a meaningful name for your certificate. + +1. Click **Upload Root Certificate**, then browse to the root certificate file (`.crt`) and upload it. + +1. Click **Upload Key**, then browse to the root certificate file (`.key`) and upload it. + +1. In the **Expiration Date** field, specify the expiration date of the root certificate. To find this information, execute the `openssl x509 -in -text -noout` command and note the **Validity Not After** date. + +1. Click **Add** to make the certificate available. + +## Validate certificates + +When configuring and using certificates, SSL issues may occasionally arise. You can validate your certificates and keys as follows: + +- Verify that the CA CRT and CA private key match by executing the following commands: + + ```shell + openssl rsa -noout -modulus -in ca.key | openssl md5 + openssl x509 -noout -modulus -in ca.crt | openssl md5 + + \# outputs should match + ``` + +- Verify that the CA CRT is actually a certificate authority by executing the following command: + + ```shell + openssl x509 -text -noout -in ca.crt + + \# Look for fields + + X509v3 Basic Constraints: + + CA:TRUE + ``` + +- Verify that certificates and keys are in PEM format (as opposed to the DER or other format). If these artifacts are not in the PEM format and you require assistance with converting them or identifying the format, consult [Converting certificates](https://support.globalsign.com/ssl/ssl-certificates-installation/converting-certificates-openssl). + +- Ensure that the private key does not have a passphrase associated with it. For information on how to identify this condition, see [Decrypt an encrypted SSL RSA private key](https://techjourney.net/how-to-decrypt-an-enrypted-ssl-rsa-private-key-pem-key/). diff --git a/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/auto-certificate.md b/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/auto-certificate.md new file mode 100644 index 000000000000..be142ccab0a1 --- /dev/null +++ b/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/auto-certificate.md @@ -0,0 +1,95 @@ +--- +title: Automatically generated certificates on YugabyteDB Anywhere +headerTitle: Auto-generated certificates +linkTitle: Auto-generated certificates +description: YugabyteDB Anywhere-generated self-signed certificates. +headcontent: Let YugabyteDB Anywhere manage certificates for your universe +menu: + stable_yugabyte-platform: + parent: enable-encryption-in-transit + identifier: auto-certificate + weight: 10 +type: docs +--- + +YugabyteDB Anywhere can automatically create and manage self-signed certificates for universes when you create them. These certificates may be shared between universes in a single instance of YugabyteDB Anywhere. + +Automatically generated certificates are named using the following convention: + +```sh +yb-environment-universe_name +``` + +where *environment* is the environment type (either `dev`, `stg`, `demo`, or `prod`) that was used during the tenant registration (admin user creation), and *universe_name* is the provided universe name. + +YugabyteDB Anywhere generates the root CA certificate, root private key, and node-level certificates (assuming node-to-node or client-to-node encryption is enabled), and then provisions those artifacts to the database nodes any time nodes are created or added to the cluster. The following three files are copied to each node: + +1. The root certificate (`ca.cert`). +1. The node certificate (`node.ip_address.crt`). +1. The node private key (`node.ip_address.key`). + +YugabyteDB Anywhere retains the root certificate and the root private key for all interactions with the cluster. + +To view the certificate details, navigate to **Configs > Security > Encryption in Transit** and click **Show details**. + +## Customize the organization name in self-signed certificates + +YugabyteDB Anywhere automatically creates self-signed certificates when you run some workflows, such as create universe. The organization name in certificates is set to `example.com` by default. + +If you are using YugabyteDB Anywhere version 2.18.2 or later to manage universes with YugabyteDB version 2.18.2 or later, you can set a custom organization name using the global [runtime configuration](../../../administer-yugabyte-platform/manage-runtime-config/) flag, `yb.tlsCertificate.organizationName`. + +Note that, for the change to take effect, you need to set the flag _before_ you run a workflow that generates a self-signed certificate. + +Customize the organization name as follows: + +1. In YugabyteDB Anywhere, navigate to **Admin** > **Advanced** and select the **Global Configuration** tab. +1. In the **Search** bar, enter `yb.tlsCertificate.organizationName` to view the flag, as per the following illustration: + + ![Custom Organization name](/images/yp/encryption-in-transit/custom-org-name.png) + +1. Click **Actions** > **Edit Configuration**, enter a new Config Value, and click **Save**. + +## Validate custom organization name + +You can verify the organization name by running the following `openssl x509` command: + +```sh +openssl x509 -in ca.crt -text +``` + +```output {hl_lines=[6]} +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1683277970271 (0x187eb2f7b5f) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=yb-dev-sb-ybdemo-univ1~2, O=example.com + Validity + Not Before: May 5 09:12:50 2023 GMT + Not After : May 5 09:12:50 2027 GMT +``` + +Notice that default value is `O=example.com`. + +After setting the runtime configuration to a value of your choice, (`org-foo` in this example), you should see output similar to the following: + +```sh +openssl x509 -in ca.crt -text -noout +``` + +```output +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1689376612248 (0x18956b15f98) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN = yb-dev-sb-ybdemo-univ1~2, O = org-foo + Validity + Not Before: Jul 14 23:16:52 2023 GMT + Not After : Jul 14 23:16:52 2027 GMT + Subject: CN = yb-dev-sb-ybdemo-univ1~2, O = org-foo + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: +``` diff --git a/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/rotate-certificates.md b/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/rotate-certificates.md new file mode 100644 index 000000000000..e1500da8997a --- /dev/null +++ b/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/rotate-certificates.md @@ -0,0 +1,55 @@ +--- +title: Rotate certificates on YugabyteDB Anywhere +headerTitle: Rotate certificates +linkTitle: Rotate certificates +description: Rotate certificates on YugabyteDB Anywhere. +headcontent: Rotate certificates used by a universe +menu: + stable_yugabyte-platform: + parent: enable-encryption-in-transit + identifier: rotate-certificates + weight: 30 +type: docs +--- + +You can rotate certificates for universes configured with the same type of certificates. This involves replacing existing certificates with new database node certificates. + +Before rotating certificates, ensure that you have added the certificates to YugabyteDB Anywhere. Refer to [Add certificates](../add-certificate-self/). + +**Client-to-node certificates** + +Regardless of whether the client-to-node certificates are expired or not expired, you can always trigger a rolling upgrade to rotate the certificates. + +- If the universe was created before v2.16.6, then the rotation requires a restart, which can be done in a rolling manner with no downtime. +- If the universe was created after v2.16.6, then the rotation can be done without a restart and no downtime. + +**Node-to-node certificates** + +If the certificate has expired, the rotation requires a simultaneous restart of all nodes, resulting in some downtime. + +If the certificate has not expired, the rotation can be done using a rolling upgrade. + +- If the universe was created before v2.16.6, then the rotation requires a restart, which can be done in a rolling manner with no downtime. +- If the universe is created after v2.16.6, then the rotation can be done without a restart and no downtime. + +You can always opt to not perform rolling updates to update all nodes at the same time, but this will result in downtime. + +## Rotate certificates + +To modify encryption in transit settings and rotate certificates for a universe, do the following: + +1. Navigate to your universe. + +1. Click **Actions > Edit Security > Encryption in-Transit** to open the **Manage encryption in transit** dialog. + + ![Rotate certificates](/images/yp/encryption-in-transit/rotate-cert.png) + +1. To rotate the CA certificate, on the **Certificate Authority** tab, select the new CA certificate(s). + + If you wish to have YBA generate a new self-signed CA certificate [automatically](../auto-certificate/), delete the root certificate field. + +1. To rotate the server certificates, on the **Server Certificate** tab, select the **Rotate Node-to-Node Server Certificate** and **Rotate Client-to-Node Server Certificate** options as appropriate. + +1. Select the **Use rolling upgrade to apply this change** option to perform the upgrade in a rolling update (recommended) and enter the number of seconds to wait between node upgrades. + +1. Click **Apply**. diff --git a/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/trust-store.md b/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/trust-store.md new file mode 100644 index 000000000000..286799bcd215 --- /dev/null +++ b/docs/content/stable/yugabyte-platform/security/enable-encryption-in-transit/trust-store.md @@ -0,0 +1,54 @@ +--- +title: Add CA-signed certificates to YugabyteDB Anywhere +headerTitle: Add certificates to your trust store +linkTitle: Trust store +description: Add certificates to the YugabyteDB Anywhere trust store. +headcontent: Add certificates for third-party services +menu: + stable_yugabyte-platform: + parent: enable-encryption-in-transit + identifier: trust-store + weight: 40 +type: docs +--- + +YugabyteDB Anywhere uses certificates to validate connections between YugabyteDB Anywhere and other external services, including: + +- [LDAP](../../../administer-yugabyte-platform/ldap-authentication/) +- [OIDC](../../../administer-yugabyte-platform/oidc-authentication/) +- [Webhook](../../../alerts-monitoring/set-up-alerts-health-check/) +- [S3 backup storage](../../../back-up-restore-universes/configure-backup-storage/) +- [Hashicorp Vault](../../create-kms-config/hashicorp-kms/) +- Other [YugabyteDB Anywhere high availability](../../../administer-yugabyte-platform/high-availability/) replicas. + +When using self-signed or custom CA certificates, to enable YugabyteDB Anywhere to validate your TLS connections, you _must_ add the certificates to the YugabyteDB Anywhere Trust Store + +## Add certificates to your trust store + +To add a certificate to the YugabyteDB Anywhere Trust Store, do the following: + +1. Navigate to **Admin > CA Certificates**. + +1. Click **Upload Trusted CA Certificate**. + +1. Enter a name for the certificate. + +1. Click **Upload**, select your certificate (in .crt format) and click **Save CA Certificate**. + +## Rotate a certificate in your trust store + +To rotate a certificate in your YugabyteDB Anywhere Trust Store, do the following: + +1. Navigate to **Admin > CA Certificates**. + +1. Click the **...** button for the certificate and choose **Update Certificate**. + +1. Click **Upload**, select your certificate (in .crt format) and click **Save CA Certificate**. + +## Delete a certificate in your trust store + +To delete a certificate in your YugabyteDB Anywhere Trust Store, do the following: + +1. Navigate to **Admin > CA Certificates**. + +1. Click the **...** button for the certificate and choose **Delete**, then click **Delete CA Certificate**. diff --git a/docs/content/stable/yugabyte-platform/security/security-checklist-yp.md b/docs/content/stable/yugabyte-platform/security/security-checklist-yp.md deleted file mode 100644 index 28fe48937101..000000000000 --- a/docs/content/stable/yugabyte-platform/security/security-checklist-yp.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Security checklist for YugabyteDB Anywhere -headerTitle: Security checklist -linkTitle: Security checklist -description: Security measures that can be implemented to protect your YugabyteDB Anywhere and YugabyteDB universes. -menu: - stable_yugabyte-platform: - parent: security - identifier: security-checklist-yp - weight: 10 -type: docs ---- - -You can apply security measures to protect your YugabyteDB Anywhere instance and YugabyteDB universes. - -## Network Security - -You need to ensure that YugabyteDB Anywhere and the database run in a trusted network environment. You should restrict machine and port access, based on the following guidelines: - -- Servers running YugabyteDB services are directly accessible only by YugabyteDB Anywhere, servers running the application, and database administrators. -- Only YugabyteDB Anywhere and servers running applications can connect to YugabyteDB services on the RPC ports. Access to the YugabyteDB ports should be denied to everybody else. - -For information on configuring ports, refer to [Configure ports](../customize-ports/). - -## Database authentication - -Authentication requires that all clients provide valid credentials before they can connect to a YugabyteDB universe. The authentication credentials in YugabyteDB are stored internally in the YB-Master system tables. The authentication mechanisms available to users depends on what is supported and exposed by the YSQL and YCQL APIs. - -You enable authentication for the YSQL and YCQL APIs when you deploy a universe. See [Enable database authentication](../authorization-platform/#enable-database-authentication). - -YugabyteDB Anywhere and YugabyteDB also support LDAP and OIDC for managing authentication. See [Database authentication](../authentication/). - -For more information on authentication in YugabyteDB, see [Enable authentication](../../../secure/enable-authentication/). - -## Role-based access control - -Roles can be assigned to grant users only the essential privileges based on the operations they need to perform in YugabyteDB Anywhere, and in YugabyteDB universes. - -To manage access to your YugabyteDB Anywhere instance, typically you create a [Super Admin role first](../../install-yugabyte-platform/create-admin-user/). The Super Admin can create additional admins and other users with fewer privileges. For information on how to manage YugabyteDB Anywhere users and roles, see [Manage YugabyteDB Anywhere users](../../administer-yugabyte-platform/anywhere-rbac/). - -For information on how to manage database roles and users, see [Database authorization](../authorization-platform). - -## Encryption in transit - -Encryption in transit (TLS) ensures that network communication between servers is secure. You can configure YugabyteDB to use TLS to encrypt intra-cluster and client to server network communication. You should enable encryption in transit in YugabyteDB universes and clients to ensure the privacy and integrity of data transferred over the network. - -For more information, see [Enable encryption in transit](../enable-encryption-in-transit). - -## Encryption at rest - -Encryption at rest ensures that data at rest, stored on disk, is protected. You can configure YugabyteDB universes with a user-generated symmetric key to perform universe-wide encryption. - -Encryption at rest in YugabyteDB Anywhere uses a master key to encrypt and decrypt universe keys. The master key details are stored in YugabyteDB Anywhere in [key management service (KMS) configurations](../create-kms-config/aws-kms/). You enable encryption at rest for a universe by assigning the universe a KMS configuration. The master key designated in the configuration is then used for generating the universe keys used for encrypting the universe data. - -For more information, see [Enable encryption at rest](../enable-encryption-at-rest). diff --git a/docs/content/stable/yugabyte-platform/yba-overview.md b/docs/content/stable/yugabyte-platform/yba-overview.md index 858604670f65..fea817704bb9 100644 --- a/docs/content/stable/yugabyte-platform/yba-overview.md +++ b/docs/content/stable/yugabyte-platform/yba-overview.md @@ -14,7 +14,7 @@ type: docs YugabyteDB Anywhere (YBA) is a self-managed database-as-a-service that allows you to deploy and operate YugabyteDB database clusters (also known as universes) at scale. -In YBA, a database cluster is called a [universe](../../architecture/key-concepts/#universe), and the terms are used interchangeably. More precisely, a universe in YBA always consists of one (and only one) primary cluster, and can optionally also include a single [read replica](../../architecture/docdb-replication/read-replicas/) cluster attached to the primary cluster. +In YBA, a database cluster is called a [universe](../../architecture/key-concepts/#universe), and the terms are used interchangeably. More precisely, a universe in YBA always consists of one (and only one) [primary cluster](../../architecture/key-concepts/#primary-cluster), and can optionally also include a single [read replica](../../architecture/key-concepts/#read-replica-cluster/) cluster attached to the primary cluster. ## Features diff --git a/docs/static/images/yp/encryption-in-transit/add-cert.png b/docs/static/images/yp/encryption-in-transit/add-cert.png index 25dbaf2e456f91f7c666f62ee980b59307388ea7..68690f671a60051db48c77e52ea2dc5d5954020c 100644 GIT binary patch literal 185808 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%0DY=IV*y0$aw8>XPzpm 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 literal 53355 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 DB84a! diff --git a/docs/static/images/yp/encryption-in-transit/add-hashicorp-cert.png b/docs/static/images/yp/encryption-in-transit/add-hashicorp-cert.png new file mode 100644 index 0000000000000000000000000000000000000000..2b5294620c534aa117f2238b5985b44b01b8860e GIT binary patch literal 106654 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 Ld^=a_?!*5BBzJBI literal 0 HcmV?d00001 diff --git a/docs/static/images/yp/encryption-in-transit/add-self-cert.png b/docs/static/images/yp/encryption-in-transit/add-self-cert.png new file mode 100644 index 0000000000000000000000000000000000000000..d27bccf47f7427fca6ddf56505a0f086c2074521 GIT binary patch literal 106030 zcmeFZg;!inwg(CX5?n)w5j?oNLvWf996DHVhu{uLa0u@17ThIBfZ*;9!CiuD^NR1A zJ9j2`{(<-2uvV`=^r`CFXV>=qs}7KpmB2uKjtU0{han{?ssIOv7z18O$Pd9M)96U} zaB!%FCL$toQX(S6ayC|mCguikaFPMhv5%A#p?IGTTp5$SW3YL=7ZA4)!*JiAr4bXF zdy@-Mkv#koh!Nh7uYpEGnulCZlkw|49F#U=>IoA5?hAb!xrz2K7}0m|e!uoTVRwRe zNvu{)obkMl`~2tYeGfim^x+4M>KMSWh)zcw!V!s^Yw$e~g!}Lbj^ZSLV9g)^s-r^! z7fpTb=)8!OX6@SP!Z+D`2h$ewVeDFlhx18K>Ka@&NOQv-C*Uh1e*ve@xXt-Kg8tQp zN|Xt1rBKvCsO=FsR9W9)9W*y8Zf6izUHtH@LjWpYd@z zo%l2>8ChiL5pz`k@Y6AgSA&BxHw^EN7HwYl*xJ-}+oWjpdSko!K$q?I%kOM?VKw?$ zklb}9vFIdH6m*FqntG%2Q5LT?I_V@lp7u8|(Hq2E$p@J^ak1?osk#D_oN59b?y_Bv zDN$~@kNBpOx~QM%Uy8|;k+W(Ds784AC+HUIyJ(rbkD5y*{nd56kt5n6V;#ceGVs_a zTMY4CXX%W>pRiB* z2|k&(jO_mQDP?W%C2w7gGJlBHsDtoO;%7yCOKDA&BjSgfCfQehH75bC$ipS?t|ubk z4zYuSgOlUWkUSs$%7TX@pROD%#Dfz%nbV$cjg+=|?T+V&f+o3n2Zv#d!2bcU?=ySp zqk$iUP7egJ5#I=t`#(T~$IAMIUU@|E0ZHuvsxb2udbO909Lm{ekzZ6?n9p0DeZ(r2G+~K95PVCD|4H z*vKrp2t|F-Nm7}((DB=TsOilye~zO<$EtW*fm#8t7{cvs@}p9B|APxtTbz{|s>&lX zANw|&(y}T2U0idL>c_5duBfihT(M^T+(X-zHn~`n&={}@{2=Z0ofbd#I>uKRe(`*4 zti|DbRT5Cs>iSFMw}u->6LvG6C!*)8#ejo0PjV{k6O=PV&o_L+qFt0xxR`hYI6Nq~ z-trkN!`_?X=oCi*z5a>**8%-M)qe^sQ%U*giysk-MJPy_Ng8DFj2MjQji@6bA&DTV z`qZJm@!|7f@zFz7#dmou5U$)EqtD?i0F_>l6p(BZ@(}WXq|3=rFGM*>a~P0i``QE? zMIFh9e>2YPl3%4QRwhe2MkDm5SFQqwiSX+6EbBuH(^vjme(SD57}pTFGFBVKQ1d zU*$X_Hq%)}wEcr8hG&VV*Zyp4ZtU6c_$bB@{?7hbV-|0g#N_b=WPCQqQPxT>_1i%{ zkNN|g87HiMbqOUp+2)%?JNZN7rvQPqL<*8jCkw zx6r}Z#%a%Zjq2mviqm4iHruR8vSQGvsu6)vfNk<|T2L9}OD{vLu)bsUu_RK&Re7qE_@zIZ%>|{oZF#3W;tAT zQFi4&<~Uk+B|6+Uf|e>kCDcmX%-zWaa@_<<=cj0=3EXfN;TB039R!t|shVw?b78)) zZdetp4PF+$_W=WI7TW3l0aDqrNB^p9c0_GlH zQuXjjKOe^}ddx3a|N2VgtCft@S2_p7-JYoz*6*!bYjd33-0W^XUxYh*99IwSBp$Fe zfAp9{s6c`cXsG8^F}W{Ii%AF72Fa3tz|+OWq;y;IwJE403rPB@8PLYZVfE;MVllBf zSdi)vI|}(Kk}pC!;$Z}TM9pig9H;E49CVcFR-2`ru^(eSV^}8XCXYrtfQs(0B0ocU zmMiuxgH7HnM{9kg53?+~R*p`6x7z7?Sx2gLRp?1n6>$M|qA!OyXZHFCQ5HXU3O7DC zQLHEj?8-8on$LA$a7`E73(gb||scR=p%!mskqj+aa7d z(6}q|*S2e%s;;fP=zX~0yCXP#t8_Lx)sYpvmzc!4H~n>%Zf35k-K;kqH#%Z2Vxg#_ zhQnOXoOb5S@;YO0QkoD#>lo%B>DbltrYFJS`N3SnF~nK=vJo{k@Jf^C`^3RjX=3wt z+tBlrdg=L|<(@%TAJ+p_BqesGM3vb6w!Plzb2rZuSaoW=;N;689yzyPJHo@l)9HsI zMeGdh?0)u`R^NQgva7r#)`{xe$IguvlkGK?`5T)qo^rkBKUh#D(8y0@r z8Qb(_BWff2_Ue}X!Px_&hqsTcn&Zwh4ze%SHszL5X*~B`3}D-<+6$iZFGC3TUFn_q z&kWnt`)eL8ayOYhLwVtRC9rb4>6a0jK!oM4>tTBR^0>~^uAAT{w!Gwtc49M9! zGso+7=~}!ZxGywGQHVbhz2M}Q;nEhwul<}OZ%@31A{XKC5Mg-l&gSrz(S-<2J7!7G zkl;enO%wdq#qAp?0#6>6dUzLxg`0hXk(R!Ak)C#Xqmb;i=#r{Pj5k z9GssC9O6I5$b$F#zcBE+pY!+og9u-^N8mqP@N!B+_@B{;F=-F}d7T9A!3ishNZk{m zsApqfU}zEp5sE{*wRs9#I2ZJsXpcb|zMq#P{E;qibbv$4^RnztBJb{-)Ew z$>e`mvb6ojV}S=`yuZW9%)rF>&+i6P`R+gEkuz~JFo%koSOA`ZH3XQMxtRF=n(%*a z{qK_hGF9zgr*g9W>(qa__2*P&TLT*rD+{owoxuNY*gq!!>&<^mt{A5>`QC<)0OieKO zb;m+kP~azoKKiq{!RzpCB++hFPlz)Da0*;WHv`?_;|M}9tMu2$_ z|2yyha^Syw`G2##q;VnGHl^w})n$cl)ozp-4%>6kGRS{cJ2o0*S=12tL3eYFA=62S zMT7m_#t$ckrZXmqRp!Il?zFh4RJwA3ieU>9fecTd$Ny1AA$i1gnPl_V==?wW5(WA@ zw7;EqJq&{2M!){b)zb$%aZfbzd)3LAQ0rZ)G&cB25=wG-GWssjanYTVF63uw`<0#TtVK=yq?T*H8|-ppeb%ZuI4Gv#AScn1#jL1GWk7)A|H@@MZXB;g z;H6C$EIn^^gk4l4Fi@Id5Yv;l#cpm%s0LbPFz%fltL-t(cfDI-IFQ7>)LvydKV#l} zduEtyKY?-{?Yg$)N5o;2WGyaR?ez$YFr{|JQ9;tqB-*_^_4EWAuNwdqTlDr5PhbUVRNFi|sLd!L?E3G6(i1`? zxUl>EplR4VaHiNmhnjvd;|Dg%zf%o|c!V2w-FCj-+tPAagDBrk!62qV11RU?-Jk8X zo3a$Ff&Mm7_RfApbgAVLk$KE}Q|gKz2xw!x#e_k0<^F^=PhCqNI^?XWKZq5_A^BJ529KA0dE3H;7iw z_|96Bta1^@tmhlgWv%PPZ?n)iXVdF53k$qa+-p2vfH8dc0D>?%H`L-nApb$)eG^jo z8q>c!V|e7p#7ss#qO3ZSOz8W$L})+z_+L^oWaCHA*z=Cj4L2I?qtH|;TkC8km=!at zYT7HeEN>h~Dl%E8Ag?|CbTb-7yBMqG(#=zLcI>bt@ya=id$+W$?+qILssnUm!{&>C zepi^9(HQSuWh6Jy;YA|hF_7cqo3+_6mYqj8CtkmzWuG>`E8Zi0pc@{rOCz%uvPZuX>={g) z;yiJ)Re6xwCritp(7(Ts-`#bvkz^<+INO_j0e$f{Rbaj&{Lp&desb;SB>HE{6eryQ z0TkgTJ;#0;E|ZZgrjXv*y&*c=@?C`sK!f^V&E$KtB7*_m13m(N>wMeoIiD{*WN0+d z^{sc!m$Jz`@f0y?t?v^q4H%{*cU+8*XEmDH(Y9{K?4BT^FjL9KyEF?|<*fjwk-oz|ix` zL+a(MeL-hS{X1W6`6Hd(-anoALD~lBx=C`9%QQPEkz? zKE7S?tmSulO6ex(5>2#~EPMmTZC(iC)-?RJ8Elo{XMiZn;dV z=>@x7SBYedf-Os&OpUyd{1u*>UP~;oG7Ozso!zP&m8}rDN^#xiKJK9{SyfY-|Ja7H zJ|uz_+{ULP{k2$a0V_TfrAcki#-bOOzC6+z+p{$#(;vJrY>pz+CV+$_xwo5(US639 zz%KNz)t!Nrx|!$RInuG;&d*H%9%EzI=)u3U5DxLH6fHxQ@o4pGy4_wqk!8%LgWY(U zs*+m7;V~e0O=C>UbJOxm**)YxM3vRG7P)JEjW z^J>%acxc0nQk*+Zb|&9_ZzU#?gjpRyw`EfJrgv*sLUatlj|?Y)64uo7}p; z6;m7eYFGY78gF^Oz{AmXzs^WFoNeDNR>MY$Ye$pYhXO;sVL6a?SvKBLd+v1Y?GNEU zVx)d=A&U0wv-wb_AjcrbXhn<$e7qRg<)G*L0?a#7wq5ZjGDq#1`M-d;&AKp-9dTEV ztEgXg+e{dUWk4GvzPPcwgJ4L?Zx6<$ldo{OPAfSlC)|FHRvjtk`BXtC&>qamK>NLP> zrWGfTLn|LdaVf~N9;YWWGQ>dnv^PoVwj5#tY3p$3+sl&uM5jO7G zKVs(u{?F3>kYIY6t(^PaM612oy%4^3^BX0T2w^)4UGeMZT%7y*oV{lezu2rR%Ss~;URc)ZG~SvxbS31g*I1q+ zKElY3z>hr0g7q?2_?ha}BX!`EbJoR=m? zZGe@AZjXj9y(zLIKTumsECD?)j!pfUy6KF6URW`}a)hzwmsgsl<5Aud@DQVIRE~*z zPbDn1I9#wLtai0|;q2ky5pQ`_;ww-TGt3Yz*!zOR7b(5U$n+*wqgA zMtCik%gN@;RP`&(ZqN62YPu&^HY~b-*aIO}N;z5UeD`y*2as&C7seCTL{Wyt1ZpWYg(h6$2`yKPSj}aH z7~bcvHXa4|`rI(#W5$@Q<{5)Q_*gb1_Ty~P&j2}|7~c}Z-D4zC z$B^2kFrJ)pNYYnd0&TZpW69U>e;UVaJSxB1D13P-RaG1vDaQRK>~y?t1yNQnF|V6m zQNQBubA_Y>lC@{}NNgv2M0XbwA#1_Besr-8GbXt%BAI^)js2SB``0LtNe$cJ%h6O= zR8(Qst@U{;w_Ip1m1PGfiujTH+M9?{^7K5lJ5>{JtmI~9ABz6{vk7%8ZghuyckAUq zOw*30JXRN|L3f=CwQ$A=Z~+!YM;Vw+q5NlRWVD&al5OXnch{-hSDSOs5LbU{icqh7 zadT`B_;5aF8!F#O{%1`?hz~vhqMcF}8ZUSpdx7kHR=v1FRdG>#y7=IjJbyF291U{3 z-d|B6;(onX!;LhFoy&hVMP`)BVcB$PXbU32jor|zaqCAbISG?8O3!_x9xsoG`efH~ zt#xtc%UF6wlx9uWvfbpp6edl6nSCwX6kR=S_`*}Qwm5%(#Wzrjd>Gvo6^O`R`GU6F zZ>ADATJY#=lWMIIqS8=2F)!bt?h-|Xu!{_yr8m7X^z){>M?s8DOjgae)#0B`wsKQv z(hY~}S1G?@GTL{J9FCXrV7xo^@}@{8qfT;Ok3YrC7q9|ro}N|wA>nX{+sJ*X{Jn10 zE?BINM?WLN>Be2Npmxc-4YtvmYA&basj5T$u4HBNer=am>+?h?3ejr6w5n$^<*2^b^YAkC$t)iOn}_ zjKlN!)$CsJhAd{rhX)c7rH_-V1s1tSk%@Xr%Dl6QJT6Ek^4;nRd)W$QoW*Wmtm)Fpfbar+NF-Ix7Bl>pH(g5VEW=?UP+G zD8TEmDU(zcK}NL*87{qpT_-lVU$5_m;Ni=1uSHEk3pL*Ki#vcEtp4ryuc-Hqdn1ea z1uo9AFLSc(0JlSRbsYJrLw7*A+e$65-*iG3BRZsCH z>OkVY(z0qi{p#ymTsa~we$GxKbWby$TYt{f5VpO`!qAN*)<3Ive+Bkw)Yw$Hd8GxxD+OGE8_^r>KZjkvCij>uQckRd_ZGxo~@DbK$To zqU#sS1XIv$-B>pt)RH6?7lP_cP55aDk^W~pkabLJmhVSKrxHbovIHnvW0pmFXS2CQ z`Wp7!MKaP|tr-yhX{3+v^0M+)2O9p7V_PRGYsS1)yV@=&k*Tqq-^SN!%L~L9jy2Cd zHhLbE*raK#Z(Vu4|J{t8Qp0Xo%mG<-VBiDUKOEOeH4u%7Dd-fN=J%gO7+!%7-wG}Q z*QJKT$djp+G#+vksaMghwm1D-)FJi7{Ca%011sDFqK#}y(DxL0Z)&9uUz}1 zzPs9y)wX^#HJY~ z*P+H(%v$o0VqJ6xdGI=T2o6@mKID>jZ4}C{5&lespDJR=cUxQ1S?8O#M)>NtV=O)g zC7y6)`Sh?8(tX~I6h7zj;h~z^k0Vl))6;7_4!g$WHYQJ4p&_kw^!93@Ue$?dr`b1-ejWD_~t&6K~;sxxWD5+jT-){ zHC(!tlb8Tus3V`##MGfCI(~r3^+WQLyfH`!2-kB+R8WJd0Xysx8s$Pt*6>U~IZP!f zlIrQHU)B-t9rSo(kn@E_Ww^r{B8Fm=L6j9q77zKOMQai%HR;}SZ#-G1a=9a~?Wh~x zqNc0urTtJr*wo3^D5XP(FkR9cnZkefYW;CS@8$DFLRa2?f9t@Zjmh8Dmw2Wrg&51T zg;25Sk{BD6lOEnnO1DJ;eIg=nEgHj+DIQEy|J526c2d57ogUsCYgW=&y~(#iD(*wk zp0Ctoy%D+(DQ5`ipsj5D-SHw3IG`ymoQ`(StuCyEk-d_*ZICq;-jCBL(80Fq^RbMbp3Vko*rd%u3CSVjN%ybSXXt*yf` z_Rkx$?)0K7t{cr6^R88TqC+k9G~9N%Hoh{#LryB@8OTIjjp@^ucx>KiY$6G4y__MU zypPk|)(%58LGGP?KZ`dWIeHx1q6~Gr_}V&AV;&~nG&IDiz-rUj@JgK6g!Lr<}!ay$HO%IKr;@F2ktCZCT zzrNU5Apor$26bBU$@FWrxNHd*e0ga{!(l8r0X&Q4`%2^R$SjT42$o>mkIA;>AIKoB z5j|%hteu`7&8WZ-@7=3DbrsLj&C}D(iSIzFa_bgaQdvrP0nF zMt3@2^lYx;pi}t)aw?0W5>^b&KO-*V0mP<8ow)elb0QQZn};=cp!&Uxfi)5cq5b;Y z=zX<%LzYcefW{LYsV_b-jj+6|B}H6&X$UFDTa@4RwLith%D8=EOk6sm>*b(9SoF!g{?l-5JfxV~hpecRa<=XLi-{i(i69mczv866b znmTl8{MZ@u$|~qpoUIJgO_zaunE}*0J21Mq>+2~3U71K*727D3d7w3@yI$e)iLE7p z#2YU+Rg=6FI=?3%4m})fm#TZ}m6=`T0T*QhKbY#nss&lzse4PE(68Vpe zlsF9XD|T>!T|3oPN!U8SO@J~!WcDC6kxQpQ%{o6JBO-8y+&dd_eb@JoPUK(OAN=M= zAjF6W2i-ZIz!jz-N)aneVx2#e5z*sQy1I`E`6J%?Yi&7k1gT|(=kne7v{D+T=<-qq zZ`5oYeJTEQDfdQ|iuJR$fP1GKKiy&7Nx_WnS%=8d$*#tbqETSAa*rjcwvS zP#g6K)eAh!SIT>;qQSHCq2lmhk`qG2PABI?1Crxn8DxmH($u4Ww`2cktICDJwJHdU zivHPv{d=%K*{9OS5A+0J2b8I%3bXC3_`ZRtW|I#*-NR{=<34(Z0Dl`w4W=sxJj8n9pvkDs?J|WYSm= z^@YhH8L6Hr&u(sY-;I1zF8F0&SN0=GRomSHU`z~-6$Q$@O{dG1p@;=NVZ^G) zV*>E*Y2e^cNYSWEF-@0gzU{^$`{r`qAV#Z%E^-#Bkh|<(h%_NG+&rc4-rcN|6R zu3Gtcgj=cG={P6HZAK8o^9xH;gUL$}Mk=NLNaD8rvb4s!8(>hS^lgxWKb2DEVkXCG zw%URix26y#_E8*J=m+lM&T)CK14CW2jQhGDBAA#cBr;oM8XJ4eF7Wvw3Pdqi7OUwS z43`U2YwA5MJ8Vr30h!GT0V6*$^7UJkLM+e9FG>LAlE5ih{0iY2aauQ%;b02>g{+ww zkNx|MEL{0iNUNAWvQiStOcq17}22JEq33P}9|02sAWxqo-qdq4!CprBv@w8A5N3l+RM^m1=1u3bK& zNM0I!Cs;FRv~4Rj<&XY35XV^3ef?6SZ1znDSoPKYgB|JjroWa>6K=hiM6)ux{RSnx z_BI3Iv@_(aqF4Z7h;N(%sDjA_F-2WuHhBa93aL=725DZ5ozK`t#7CTG2+AZv?!~Bv zvIy|VQQ<-}4h3Juhzp*R0I($)XQ1wzw0mS_dj#n##PWB<7Ji_}bbTI!%OMCJf8BAg zP8WHM=X2$rN|83_8e=l>sat)L@SkPyfKxx&OaF*X6(BT&N!Y3&6&Ha1`5ShcfGQFz zyuCp$TJgf>r#G{DVF)Z$`R4);wN&lix3D=NRB#j0rm2yj;6fxXq7F@|$ z%jhy>ePuE*)^t@|UIzIw1dytx@Y74^3?*xCh=k7IZb6}SUED*EU5;9=*# zo$ln^WCOO*BtN4j4l@Bvk8k3CjEO*j^A~+ID6Bx}@B!DqeOM<4+y4wD%c7=74YB+i z4j9XBlS%a&F^~d~N=LmcOni+v;;@MwQ|@U+t#eE!_R0JN@a{S6(5jFzY z7CrFwF?JUlV7koWE7V7yBar{1FWNi43p)=La9x?n78h8+uZSbn>IQTO&~aoi_IaKB zV?2a*TtI5f#04r04t=8#8Cmr z2{1j!3d_e!Na}C;q+4Zxw)z0B)5LM301XgFFqti4dGfi?)EkTqo}&sx`K)`NRYC%t zu)734R}%7gXYVemOHbW^4NhF--v}g;+qtK&%40sN*$%KGo0SX+k>xF%4)th_U#oB$ z`QP+W=)7h@{PyP4l?Hxa5+ca3$?nDWS&)zl`x~%9;+Buwy@6A~jtQg?<3rX@{ab9$ z4(KV&J-`O{AEel}V*^YXpznRNWsf!wV8uv$I08caYY;1L&5A4$%i;b_Uj!50|NqqL zNAMM>%J&sQEBfvm>QB@@e11;FEM_hPwj@|8)m9{};qN`~aV6@)Kv*Lda%XrXb_6nj zZ<F*3V*7Yz|Dp(SF<4XRLjofTn*BqN%u84oU?0i2j>>$L0e6I996#K30z#C)YE8 zrb(=}n02AZS9d=dmgooAHM6GcectbxVnI*V0p^E|kjE~MfO9eA@^~W=1@Cp-Jt*V` zw2lnNl~t=e(HI*(nzOWnwp3!jx>cjWJ?60jRMR^;%mR6vZ;l|x!1~5>Sj@x$bY<~t zMjRdLJBvuUYtZ>jkcw<#7ld)O(KX-L7_LRDG6yl#8^gSrB{l|<%>=G@6kz~2Xcds3#Co$ z9D}aA4)CA`vuagUmtRsUK+CyGK1MAg67*f&8#lMDzU{#d+kT7hm=%_}3?1y7k1f)3 z$RuAiUV@ThALpV>rTba5suuT|M*F%1AF>c}EHKctoCMVg4}g07VgQGquMJnb!VR~_ z@=y2cURi=BZV>mcPYw!9sfrT8%F8{T#D&lpXhQEHMht+@HB+dYxC_ zkBSS9-TT=m<3OqYwNve20fA$@K@9U>uvHx&e4CI)fUH;tFwC-RyW4xXmdW@xW!a>h zdghP6glHLn8x36lg2s^7-7BgV*ld9tLgaP`f4p1q`&9VJ=lObj&P1yPb^))r#*(Hh zL0cdfGi11|U@z@5Ht{0)tS2Rif;oX_jkdu#*g1m~r)BFG z?qC+{?RDxhrA7G;ZofoGVL?fw%PF&2ah+jgoyz$OHRm;aNPSKY9UW*WJqF-bpLFS_s!berARy5cUO3NH>UXL zq3&_d5(n)xrQFVGop!T8i(k+)!*+aCmIse8r4c;_`R|wj93hW?q2AuicE-f$=_&Uu zljWRXzX%q;T8ZcT$%6&Ut~y(w(VI^>v6=n^Uw1-H55pE9**e}o$-X4KXjM2^XxxD!5xO0=h$l-% z8*~ynvUy#1v>^oBmh(pN$1EmXknmv|4OuYui2UI_bMgwo#H%*Fg%TL zVr=5skaVPOBS1HQn!r_a{%~mn-=HWO@};PQJnRgKp7ylvsm@k@E)1WqZND_`boV*a zhY!S}x39nMIJyCtH#ovxm*#}(v3OnLIY0H%rYrqaAZ;^q>n-Q}2Q80FgV;HSm6S+z ziz@pMjn_3U&a32wWf-SBp4VX`FRbf2_O%D%kEzl!w z3Ke$%m*AMc+9|QZ_L*Bkqy8;+mFUEogttAO8os0I9^L?OePKfTT!G^8f%mh?Lcpz303g7jdSbIoS z-M;*`wB?~E=8V7IjV49WeLvr@FRs`8d6m>Qc7ZLuJal;qAovYZJg%r&t(Qe#NK7a{ zdin8#6D9_Q(@qiR9(J?f-R0o@$pZ9H_BDL(>?=o3TE>i1$iyLk|G2Kysy>t+BIRyQ z!}d6TDXTku4soO@%^+KV05=8(7!?PagRg&1)EsoT99FZpS`=Lja@QquU2Ud(<|>Z} z&eX{O2%l(>#FWqPPakt-3KSBNzn5xI7&<%#iZ^%zJNGOl5@HuU16Z@lHnCG{m8Cd6 zmxn|KoTWQ&mr}1b$i$P(7}si7WSMq~zgsHS(iM}m-G>&>n#coHND%tptMMH7u@*Uz zRzE*m^^~`6SYuOfYbR&xt~~2_VMP~o8p8MrbmUhzq!9exG<)2BA?R(cUw&M5GHO-3 z7iFn0hjM=Q@xp&oA;u0@7$ zYrjFPrY_P;Lf*9~cySeBsg^sM0v)>}?HtCOOmR(fpR_Ey%QJ1KrOWh#D&VbHCkA6@ zvUJ)8%GZ<K|7w-mdfgUpVWlpGq2>2FrLhEuAL}!lKQMKua300-! z%wiMhR|+-&;z(J3f9j(sl|<|*i@s!|_3}&!BiWHb*2BB&gXS7*6{rC|?@#&xLHVT8 z698_G<6LlR^T>BTn>LCrx>d+@drXeoo$i#Lmd>WCVeJ$A`uA?A=Z&5&Zh-7)?z_MG z+V>5*R;T-O4UMneO=doLT`==i2hrmvpL_{Ee(t(c>>AT4OSF0=DhS^HzAcJ-X|ZMH8;VEBzi7k7Eww>g`M z;L@>5?`#yjCN|*bV;XO5B3vh+ZAS?|uYZq|sC05sqrY-9JeK=H=a*r3|pR2KI9x|z&A-SxQ+qh=*yo3~rc`e@nC8!<< z9fzvfbW>PtcmlszJ`vxe-r|V9yYYS6P;>V%5rC$q^7^lqg9ab?E%(L&J3@9w(Q3GU zdks$E6t=HsNkz&4G?zu=^$O8s0RC`;{#gRR7gznDTyQn)iRwNx&;_7n=JLLXm5D{) zhrjfs=E{#qw-?)Nid3c5uE1G>zI(*)wan--WRQj5b1j#`q#R8%vXDUV;+Ok1Z&c(* z?lmokX#=l~9>cP&41pMZ{G!++5PJRSP`!=K&UhW}?|TP37j$633rd>a%UNx2Y;B|qe5K1c(Jf9p6L$YZt}PpQ2aMcwttsn?`9w8cCcA$A zyHpP&SeO)#a9w93@2hrjAmm6sdQpxsuTsnm@Sz4F9w&3pNHU zzPHFUCjZHy(Bm1dn4A1uOyn!S#aSMkj%QwaD^!e7#;weLB!aK#D|sQ9FP*;4FMy7F zQAx|;2YoR-=w|$I^s9>!FRoH5a+AcS)XHs^G|w|FC^9QLma8q=Udb59Qx ztiF4PFSYskVJQhV@_61KOlTx1=F*`a(5XqCT!fu*R-7-mUVqn}r0~`RGI|#0#=ci? z=JCkiI=%~9=i6^lHLlXqds@aVKPIOQpg-%`vD;cgw_e+BkiO@9PNi=bL1plVcE6Q~ zy++fF_N2eSI%Uny(T*KBi{yvZYwQhg8{?y4(hB60X46dtw{!QV}cy#tUITd zKI8U%T-eJSJ3At}tDWV8In7%YYB$3^UzEADpgrezQ96t`FAnB(IiV`5Mj~d(?REVy33Y7+j=D0pxf!DKY=OyY zuU2*mSAO(vKn}mS;mGv+n4qBcrF}$Rw)=2eNJSHRXMa!5vlE8KlZY@2wj!-o*ON`0 z>t$MaHI?_cKAFOdhu`;S=ns)pDRW}m29-}<2G=E5i^N6~3`>{N)|6(&dV_p~qH`li z+wv5{<$Pb=N-}iW_~{-n4~D5m9?y$t=YM?89e1@p@}W4zIYGe3aEUQR4n@=!%)>O1 zH23=u(}hDeaEvv5>bT&{TiVdTRvkh+7%97rZx#T}^DxZ|ACX2sYXfHq^bh%9-{-SA zCQup}Ld+yI>Q3Qv)obRx&eo2mSbB7hEx+5_b|=o2Pwdzxm6C^l+#ISGNVS{-8BQXV z%XaNGRc7pOowjXXJAkQqh$Ya5+Hh5yzhuX-DY8kOJ7!T}d9pn|OGvZBV!^LK0wC~y zyP_o4ND-2DQ#yW=*Ft-fp@MhLBDMhOm1r{W{5T92;tTR+*j<;Xd3tX-R?bu4rO#>{e*LfP6h4u2)OBSY~ zk7BBew>CRG?b7ZNi!*c0SfGOKT?c~EU6~vPi@8z-$HEbaBVo2Islt{`xaS!%o0*Oj zEPB;UQU}EIsD?(|wOYCPL-eDn2_VAsBP6$Q=jbGsH!*= zW3&A|7BM}3gf~SrWJ1^U!>O2>_RBHh*w&J?X%RLV#;Y`EpebImH)|XrFcY_?*iyDUGvFt*lmD=|k1Oidi;R4gwqVmc}8F z^u@@O#w(V6k3^RzCR4#y@?_}_KmC^&;%`xljP@>muZTRF#r-rO;2P(?Q0u=BWQ$_0 z@#ctjiYI%qE=3sCMLgLP=EMD_$Q3>qFNV$Hx>U!=&tL+gnkx%=PYdrz*rUq0BHywN zY$r!a!;v;&b&LDw^J`{nfmJu1ZvN{k4VSI#J$v+PWv-8FXFJ~}#xZ5$m`Dnnc`kvs zkXO>qEzYVsZ3bEc&Ol)wRt!AHmHk?FJn(<KRZB8XR>R9~k(?<$iTiF+t_wc8W9) zxdx>RtLix$$X#hN_ywQvO0aY1M@9KWCp`WV@!!H%Vk;GUfp=+gse$C-4xFt9j!`?a|RSg^jn^ld{F7t${7q zZ+d@>{9a*t_pmn${5HVfzXed$ZJG!a*EdO(=sDYT5Z$^)Se%l1sz~!&cVt_O+@5T# zkor5_y)RdexUm|C2VU2N#&xR^5G46^bzy}|HgQmjWEY2W8?E3PPqaob?r(DB4M*9B z`ihw}$_>Th^{2cR7KzqY;eeG^fI0~ScQl*e3=q9PJ1o!m4Zz!N2l)xq2sKQPl{*zPq7&b!Fn-eO_{v-!X9FOP zBfq1+U|ysNd%anb!H7Mw9T=gQ>TZ}oa(54-i2Cxg5KNv-7%^4Hss@bg=R5rIU%(xbpI9(;Eh7boeHo3db z%6_?Px4*f->h~d-c<_6Qdig|8J!tn^Jc~{cbn5I4=0TaB$c)g zpIH5`Y#@6Ik8DJG|X5I3eS|)K*Cb<}>*Lp5mJlZlgkJ1-2cM7CF z1z^6oYW^V@nS#c!`5<7Rh|jI8gOJf+I?QGD`bshW5jq!pp+tQOz${NzQ_{SPWk`H9 zar4MW?0Cnocxmdx(eR!O8;-lV&8N7RtjJx6^u-u@sXA#9N0Dhk9b24=Cf8j5KFY&&?Z0tY z@@cj=oVyJxC70I&#g0&RPt^wC0lAnUjSH{2SH7|q3X;Sw(4X2bfrBwc1i2%v5j?rE znhwwqpHHain7M8^761$C(7_4CZ1(}ux*YEcKlYJp68`*n6C9JoAE;ym=Zv`Zogd>L zp5QN!DeiO(9)N=qEwx9BJS(^nY~t#{g@y4MCiYDg13a`(31BfQMMJAvZtQj&A-wyg z*X)Q2j61_j4!deQFQl(=VlwPZt<+U)x8tbtM97%4Ukb0Rja$q4H=WeUI-X>gL>>2( zv?!};K5P0=SJ|UTXvBT2D#62WL5QcV7V(k1fXIhD{B4`rSe-qv1%cN*d2HJn-&P9K zVpZ;X6LyHV4Mb&!GuuqYw37cH_TDnA>h4(|S0n@lln@1JklKLKAtj)6*QQZArDanB z0-_RbVAI{*Eg&e3bazUF)RyMIw$F3Ub@c0b`G560yx_Xv)3avHnl&@`J+O7Ak!CTQ zcl&`U3b8xc>cXX5;)#^THm~+NJiq99F|bcTzp#KT2x^~=4)bP=PsAcs&|qBMDAC_G z#(3zkl>M;wEA8<$_>WYQ2lbzi#4aRG4%SQtpC()pd@nVOea@36hIipCjCn~;A}p8JDB~(ZD?^mk;m%~H!j+6F00>YpEvMKtg`cu_v*54MKm(?A{Ej_g}AhOgq*sf!3Psi|n~B{goJ z*)<$bT31-Q0!WRrBA*6XYNdqDbP&Tx1%pgcr8Z^FaDX{t(!3h%9?(dLLdT{{1so`I zD`=|{?(2tULEbIBm!&>=#J=Tr_} z9V@Tzd$#%mOyvyDHG%K0dkYIA6Ak6$wclwVkuZ_8W|fo!n46Mds+X^@X}tUP+lN74 zwSy{gp53=kKeV`0F@#O)&a&+<{bb{_kKZTS#sUxsq&gia;E}Kiy0MMH#SG!^mqHML zC>!PJ6Pnxd?A|!AEr>z%(r`m-(pKW)Slq*@0A{cG!{96Z^*oz*Hwvc& z5bTML6Zs^0g9?JQY-cW)d{F9yh^eTSm}BnJHDL5cY>1j z4u{r;7HYO;ItzLlE&Lk_EEF`3D!qQX;NIevlE^ zG*w%5v5%L`ep62uxXD)7cu>&0q}l_8GAlgVN-vX%N_&Ov(-M%rZ+SOe7nAYGGuynO zfxcpP<;Olbx202luhjA~Bp1Mq8u1-Q8bA^IGsQ{sNtBIddB|YP1#)NTIfLMxnPVXN4R32VatvC`%C_c*c#x=U84+k};h0VoQ~FuZ|XN z=M@MX+KvbWsYokC)8p}HEnM6ZkJQX8BeO0RGrvtNR8%=#a~rX+Iy53&FVQg}Np0|~ zC?XGTaK$r5^*pHI2NPxEi?AiD?1=pw`2o!uU|zVh8;jnE)vg-lVHI9g|s2rY~3=iAn{1Ym0eDH#ILg}NMo;vCaZ?6nxxMhIwhwQi@W#Z zOQ=)-4Ys!6(=5*>O^HiM-dH8^q2*+#*ca$Q*4z!**!nJ9)e5Y7@4~5x@V z^;*rAPlnG1N19ycPI-)bd6GkCKnot=D|FOVjjAOXc?2tQ2D zbCm8SbM<01Nr13Wd1?*qw;pp0JQ{YzCST7YAsiT?opsRgE!j~W7Ae}k%xlVjk8{B} z0f5-`$idpfC(ZD^sA0#38X%R82lk%3P~K@k!{COAIY|FvO7H&4)hv5 z@np%KjUhT9i)!DQ-Fdz(uHqFrlLYD(oNC%d+(FuWZ;B9G7G&UL`Qqj9MUP3toI~{= zo{#tJ852^#E%E{^nbdT&aBkerZ-2KA@FCYJ9&7q1T!pe)jPR2H?1=mq50DoaV-yd# zS(0EzGHZ}aJ*r6q+=vd6@w0Mm*M$&vh{g5zfaXX7=-Y`tO5i%yRGHv=)ZJ^~)|K_a_i`^Pq9{qjKzf?1uc4Su zmI*2sNid}(AWs5_C=|!j_ZpKof^QIHur*Zo$PDZ(wX_secmh}8njmb^Q_-!!S=5_Ptv26TKw2?_r{h(YMMQ)3(S5gzLkedGBu{*G>}XON5=;vtsfG z&yp{8{YerjwMP?NXE0KngDKf9^cIFdj^wV_+x}wf*(@!UTCfeRq80B5@0s7(0r_LW zGmTT-^S4$n&bYMM=^z%X6Cln^ERQHF7}uG8S@%3~$HJe!9>4t(esZNpn>@@tE}*o*DO zGnR3`nZ|;u8G*Y3`y}q3q#@ZGQx6t~Cg0m?av${6zZSgBO3JV?Y2bNS!1{cpXV*X* z-Z@KTAdyg0LNPLnMjPTR_ASQ$zjJ0_&9c0GEwd=%eI~wz zSijEQa-!>M;_M}eiA!qEOU4Y5Rufo>H$ooI?L`qsNWA!Foe6!rbWI{vbwD;L{}Al> zj}4!;@j93)t9Zehwr9OX^LN6)*EXz{fock!wv%mb9M+vr`#U4+QoD3VQhXc)nE;yO_jb@$>3*fH@5;bk=y}EE3*!h$EM?7V% z8++#=RR=nb&3aq~jjSRjJp;MXr*l~a-(jFEEY@U6cbuxv+Mk{u?#PL~)|B0&pc1wQ z3Li#==ui<@K44^T_LmWHL&E{#x*To5q{{&aOptS2|}^RQe9r1k#@9sGLEz zpJ|tOz;w;I0nBAcZt&2T&7teA{n6${Qgpk%W= zTmz8ATyS^N9D(|Uk}q9TYSaDm7A+n@t~4$s;`8xu^=0DH97I6j&39A?UB9`5;b)cj z(b?_lx19W0NuBC-e^3+4X;xxm*UY!+*K9OHZ;GNR; z*bt^a=5}g0AX=Gn48)?uj_R|lpA)fDXXh3~_)d|pE@t?ixh?NoASLE@lvY$%XD^A$ z0MXL;p~ody><*O`1#)^A%!QjEYg6*dS_5Gc@*c^_C;9j`%Di_-spsy$jFQ!|YN$fe$~UW-=eCr3*JE zkCNhaURSfa>a-l$7~By;h#;E9=CYY6$3Dx{B|G(9H?>ne0~*rV1tnbG4^J=3ZXyf5%ez5LK{H@uc7LeVn9}aCZDE;Kyq#7`f~^krE~(q8o}QMf zPEaJpSqWgH!@ftVl0x$I*ggnoy+@R@FImWXPcDf~{hJMXh7-6lwo%9NgMs645AvMZ z6et7n(kQ9`X@sB)`|0rnd5|JO3X#WE=HTLVixKX4HqwJL3ci=GO*C>v?|fs)o2<}J z3V%btJ5rHA!Iz+^r~LNh%8>AEsdUeH+Lt+^XHDJ|$2N^ST+p)6HCH&1M_y#Qz-{@s zN(v@Qy}FZ`79N`oYAoT6Eq_gvh+}_SmK`Jy}i@>4c;u@ZXVUmp7*rfE!ukGBR#8#=g^VF^*f=LzaMnF@F^B~~#GxVox316#= z!?gwMI=CM6_wUP7c(B$?YNJ)yOpf&MJ!llei6lbMIS5@GEg~Na?tF55sM?(3B6MOm zRc-%tH0@S`1*^0cyf+7n;??KOkDqB%I(C#qK>f!Eeq`{iGnTAkCqT4>QD!V;RYt*O zu2fI`biI8;-$LLOwp&(`7tFlmu*mzI<&|cYRZFX+amz{);| zs!Vm*fx(NH0)leGMV?C}NQNY-2saoHr_Z&0qc6yH( zKgwC?%~-slANHqrF-tS?_Kz{J=44@X0B zAA}xPeG^nTP=Ow7UaY3=B0U$=!(oAx39`2l9`BcJr)$ZM)vctYfV#S6-bM^P&7D?% zTXuKUeGFtCuqdlQwL>R8g!|yrmw*Ifyp)3iPIak@G)pt?ly^70lEe=uk8g!bCYT<4 zs5LyI`tik0*$^YoMT@hsLq;P7tbjexEfcT;PAo5Z1S=+wmGZQ2S>?7$kZE?r@E7rQgkR71&2LiC=b?M3URpWQvGlc{1Z|Ve4J{2qq&}+0Q z=??iEfzm(-Z;8_iTNX%VDq=&mnHhJ$ZhHVy@|8eC4j%pBJI;wOgj(7}S*ZpK(3%zL zO%k+JVxO@V0MqM&z=0a9C04&KUXmKRP%(%yo)NZR`1<2H*ZZ`FryVk4hmv1-xjkAi zW)JjF&q8Ddoa)#L5Cje<2RUT}%I->=2X(ecp5>I;LV*vYS?pq)lXeUk&YLgNjBEzM}@c7Hr0h~=lp3GLu?Bg*gaMhxe zD=k-gi^n8G3t^%x8EWIhCL;C9gTlG23VbK(IImp_+57qgD}A}NtVb15Ia!^B@~^wD zFHan{9Bjf(LeK6pOC119qqkMeW1|@Tu<2yXq4(Ok0+(@5MwDbNL@nWNidnV(#i5{z zx7ymQ+e*RbtNWatJptK#bI3MUGL=v+_g$zNZplKy9w<5^|6nRQ!krzGBme6BaCFmx zz7UVnOaGN##&ucdw6fHpeYG#>la+dqyQY=e3kOL1(RJkV25&VVn4lTXHVNx{Gwwf& zg)Ma|t%Y0&xMQykmkAi`bVsQl8>2-AV~lnCC$d83^j>RL(Ub>$JHvUJe8RILna(i} zx~$djC|;LU7O1E%h)M->4047RyJNXsBh_wr313=XhGhc^ zH969NZJV$#|2T4suq;V0KZ*5Hn0xE7@YU`t-(iHF>u|%>p+1GLWvDkTRQSOWX^75ZV>r?HgRX;`*t=l*^XYb`<8x_EZc0Dt#qdxSi3u{*b3B|I6u{Dp~gL zQMw!g2W>z4= z!ext~fiQgoR^u7N(dzo$^#8d%%(wXIrbR`~r9aJHlj4-;;!mS7Q@e^dJ4Ceh`4MaXq6yRoTrC7=f(6E|g~NKC`&)EkbBW2kAHb=hPqVyQ3fZ1Z`L$ zzWtd0a?Z$I7P7y6q?6IJ4a%?HxgE{hAHE@TCOVL`1jP$IYnwIk#xcnxvYyW2;&!9A zU$z0pTbD2l_!Fk@>$y6;LOK6l(r<}kLzY1eOyiN(csEnJO#zXSP2)s zen`8-k7++3IldX1S37kerhz4IaH7Dis0%Z4jE$Dh_yp;7 zF4N7c^rGwu_F}??t-c9wdZZsf*0dZ zcDO3}V4X;toyUQ63j&+66SwD1-vl-8&+D-O##qY)Q>g5Hynj$!AEMBZyfStEbJonet4E+?nVu!{a71TT z7(eyPTiyG^P<};3OG??CbK8Ngme(=y=KkZY{CrSuwmv&+-)3_-K^Uoc5rv${d$yX{ z6j`HG@3K3kq9MDdNlz)ceX@Tn<+!$hXgMGY5iHCS3KgMEPZ|aB9lIjlo+mmx*kJK? zJkoi&wzOgc0GQVqFjmJoUGGf)6Pi&7OLj~bDq>0UdI1JJpIjrSAzD}%R6ul!8Dv=X zd7lj6nW;`yheB&bPCVDe6DD9Ht2L?8n#T9m1D|jEQ8vbIfRME%;-jO9pJ*8g;%qYc z9c`pGz63Vm`qJuEb?Y$tP>L{)G_@?1gudsQ_gQ%^%XibOK?ry>6R%J|`H)60CmE|0 zQci@Na_oKipvECd(IC1-$2XbO!}%#(UnXs3M2G;N`vRu4>ct%iz)nXBxrGWb9Q?)l zaYliK>tabQ$+cV-163m;AH8N{6RRS4a)zvG=L0sj!iX~V{WDbdnTQO86CNlgJ{Qd0 znyk9eSjoy1Z77LI9m+SCN#qA$8fZCa z7%<13@mAanXwCjWF32|1H)t#@bXar23U5CE^-h!5N=X$7!q>C7koQt&bG*(?0KRIlp^>q37tI74Srg1?8gHy+qcyd0_eQ$@0T?8-0`{p@% zPgwYE-Y6CDQqBu`fRPr!_5iiUA&vE-(t&qw9h7mQ=B2&uABzpznG_J*X zyYH94wnCrH^>B^4YRzoUt7C%~iWD8~v!G*6@H(+TzK@YeLlyrQ>*{iULW7=9!!}sw zf5c>8+j>aDqW%w-Q0-8s1$!TbHS`nr?AfQCzlF{rDhitRRWRkHp8pj2?6g+uro~)8 zX@0Z}CcMSZ4%eBtw^vAP#z{@pUZB$Dq;+!dbTC0G#^m)_d}&xm!I^N!S4}b8q5xdW z_M|hCniydxYfwFmPr7_wA(Kr55HQTmg(<2XN&fpu>Ca^i;Ueg`HzLB-88EFblf|;4 zocY9XjXHIee{G03Tv_6reL$$sR^+e-s(FpA0rUE&bEHmF_Ing75<^13HlVTZRM1{J zl^b|gOP81V(C-xj)~p;nj0Tfbn2mhq=8&_xhp9kx#qZo3p7 zyWs=5{R$PG?a(a5G0T6CkS8QHM_3%!TjNpN)D7Psdl_5sUYc!e7euW}f zr=4N6DVHd$(TvJ@VAt!=}`Ym248 zFsgwp41%bSHvx-xWZBInbu#cemW#s5GS`U!2r$Q7P2!E{_kWR>7b7a4O{D5&k6c^>JudDe1j3Q|SKNi3!Dxffc z4hWx5mVV$|n<)kvY@bwAR66+G+gt6A|G@xu&uZh0-NXhM#d-M^IJMFu3P`~1HBKag z0rY~XPYTTkf&$6zO{ZAjVhsKVZD(O=PzVj4@a1$4N7r?PbHYB zl~8!iROQ>Z(PI8l?1*C>*{gBX0ItdTX-$^{-7+2pl5J&=TgajU$V4FL68L0Tn1C0}6P_-zBPFAqK{WXPYqNjc7l({@qhaiR;FR|A^wd9V>=)019~d zBh#_tR<=D#=%kI-?~;6h&~eP^d_)mI<|fD6N<7yip8kXUK@CNCTybf)Bw=x9(Bnsm8N ze4I*&^dC(c;3FhM8wPO6LQIpMs{o%BjvC*16RM&KFurPJKZ8`BB%6clCDUkV&^i7E zY42Ad(0Ihb_?o^NtTDbm#D@}kumMktA`m(g1{YkEstd@x)WemL3|)ryA2>6q*>mw^ zQXur<GVya`poxA*z?asBZ@9H6{N1qE~iWD-)g^dl>y?s zIaZiH0J@N@@Oag^WMr_c(MbS0G8vkOpmhqFH`w+vGLawF(CPj!fOlpLpX`Url@-?g zsS3K5fO&&BM#Y#ij;^AZ%lpD(|8O+iIx|6#oj8tP5Rh}M6TWO#IYqulI63G`{KKQ* zkD81CoLvWp7^zKec=&ZN>qUGl2EDjAP0x&Pd35z6?*BpfKE&ZOUS_5Pu9sb|MgS^^!OU|* z0Vl&TIeuGoH`M&fR7g;mhA=4Y`TkDX(}#W?EH`q4wa$5cmO1yMBDA$)dF>Jb_eLS; zZBU5f^5{~&g8~}=(INsx>ymxT2V`Jc0DlR`U4l_S2Dr>8-_Z>BU}2Kd8khxO=HP%Y zxBqe-xLc{LsIBT9-C}3H`wH|n*+m(kDw~0M?Q73-kYk^$%;3k?fc*zu=r1Z6(ZlO! zM~gREAOI{zO2bCX3OP=Nv)wbwf$=7Civ7#O_CINhd;$nSiAIE-wtWtx5fd}>+8YAI zlDzI6zyiXWsrSFot$iSf7Cy(aDjBMEo_<+v{%G*6EQWsupRzPVq2rVgmJt(-WbCip zcK7J!-ehPLXhX4P>?I-Ux}7Y5K~!df{e1?%<1NI3E!yed1R^?e9RNb#-(e*PY>cE~ zi-bVI!3WG>HKexy8=i@9y+g=$EUn(c(qhi zm)g4J*fv%AYfzXcfNxkJ}yeKYrFY zdo$i|hSRnX{K>0}lCyF6YeK}?zt3ZNJGl~K0aem+cmk)Xb7n=ot)NSv(f^`nPq95N z&xgkTxp;#kb+TX9Dy*2x9cj&JHDc{oOw`iJ^S4>ie&;I45-RaAkk(|#Y!tQGJS$>+ zrsZ%)nhoB#H8MT$v*blv*CXYsd zSoeTb@BmX??C<2OJTRaaB)DBUO<>gV(eSTI8~e~F7hASRKpHY#+c@;t!MUfdM9Z?d zk(UkkKULM|fy8GL#JZ`!ECo(h7qTGY%$ZU;zXeYCk*L4mR2J0S?r$q3-1)h0{2%<$ zzw8Os3-kwy7d-+yAX`!)rqwz2ErsuIa}f9~q-z?XvQeJ8S50C*kOMe?wavxB5 z^Ng$e8?LjXY*izh$#yYcRc|ZyR~ig&1HLnP!Rbe=-a5Rd+fy~D5yLcM!XhX8>l4G# z!G;#_J{f9MBYdLOG`pX_p>H{mWN`HvX@e7fm{RZ-)KtnPOimZ4hVX0uZ)&nch){zW z7&=T|s8d6ZxwQVO%irDt_ms1CT$lN86BGE(xi@5OrF75f#pgTSUUb~}&v5{(O0lAk zz?N|?;8RToi>A|LE3O1B21y&+w96g=_{h6||Hwt)4sv+;F;e{P!R^eztIeOe*NO2r z-!A-h@yZ8OF`erPDY}1^?SDM>vUeuobBFTKIpw8ES@^WEtK+OI6}8VT zTk$Jj?ln_ay?#Z*JoD}`jkg2_#_nwlNlIcOV&z*HYN9oEXpbDk@!G|?XUXMw~@VFr~dD=|9KSnvk>PN zq-KI+=*FLqLcjG=_qAciE@mq1-xvEI7r)L!LzgYAym@G;ZU|@sRP^*%BO@bKw6!NJ z-EjW<-v4!-s0(=Q(v6tW`T}U#Qv|Ed13>W~Sz5|*PsHofhx`1c$xosVH%7bgy?$JS z790BL0xn?(fUK^1m>}d{dA1kT7y&vU@z-yIh8g7^^EV8__$IS>nSB|8gM&+7?LK$R zBqk>2upTSc=+*Apa1jW;`{+q6pch`}A^Jmvj0#tMGtkTHFd-;v2;-4_Xo{KR(R^SR zZ^JI`Jt3IWDlF=brJ)l*!DZwffF4EBVcl$f$AHuPYfmO+<6(ha%&pvttwA!Pj_~AB zfaGWR5{MuA_*7LOti&7Ni6yvHG-Jt#(}OkI#?$GY?r51c z|C#XRuBa|R1jD4Q&%pWE?e;#D9iR^bHs~^pLK+gS`h^!OybU-=;0%t#DRdf!7;}L)aU-W@Oy!rO0JCLG%RFzM@R!MWd z04Ui6U1-=$DM?R;hKDLC`feOwt_&{-@B{ z)padnHf~s7b-ls7pJu$+$PbV{e9`sIN)!*o^DIjKX+7WU|GiX*6;O$scjXT@mGy%5 zZTq^Rt^gX#J^9qksgd-l#q`0ebrFjJI#7-f0e;FEDJ!V*+u#UFPSX#oR!_0q@D=QR z>21P6J#5dAauNVO^$zWTo*Vd{1xJ&Tp`HNJ5Mxs@Ix$t__)T;_oI%m{&WA#sDg}V} z=XL`*+Hb{f#%kv&2`r;4XzO-L(O$D{<-j-&-b;0mO*tHNeI@k|Ei>zdz6A%0vYRmlyjN2KmvZw8OC@z4uaj?bNo`D2oRtq)3af%js^ zTxvbeQ5K9;=}+W$8E{TI8OrLee(C}pOT)xFS)LL(_M_i2N{`ArX23<1lLR46JGquQ zvg(c>1f_$3--SiwY#x8hy0tCTV~KW8 z98Znn0Cy4zIAJVDyo^jXIlPOx0G8?)ShggJ9qF#K@4Ss4#qs#R-r)^Z2BI=ke7q}B zwr9lIq;G#=DE*~Q?rP??>@{}p-txH%?sA-3HmzdOp@s@+Z@Zqy``sh&N+^C`(5N!O zG?rtGdWRzAe{j?%ux26&=q(iM^0NH&HVk`QsBET_@VE-1K6CT%ywh*a6neEubo1s} z6CBaMRbdv6JnEO7MNYdKIIh7dp0Uf9Mt!n?ZUv=>>kCK8WOO@vUomnA%_LyjB)A3E zig$6n&#ng6cQb*R`C_LH(0fsVo^3%1k*G#7Z|+WsmZC zb#Gq1vXBJU1xfebReC>EBNX$o7g=E7XTN(mU2zt&tLKu%k{`7I3|0B0_pmFv2 zvh3ujHPRZffsgyToFrCXAB6KQin!_}l(QR(;0qt!v1Oht7cab8k1)`*Hnwxy4{NXil;C%_Ksgy)M877+5pdjNG47mIO@M zYNMW?Khh}A+uvM;8P?}Z`S~Ih1QbHHjM9Vxxpez?9nQipOVfd>sCWTPRF>u`(7B-0 zC@GV5A>vPOdIO+q32usKSKr_wHer`@)1;5hr~OoikOGw-XKWO*iuKu>uJ%*9Z$4TB zFhA?b%5NV|(_fsM^JNM}9UQp~T#>&eWde?jpRjYJ>aLfjFe~8 zL;7afw5i5ana5~1%aYFXS49NBB?{@V)vnSATIpYcB|Iu-RCaMlmjM^O!qeK&><&!i zfIyHOUycEng!}FDC%-#-tJOeP+i)Elu(mo<>e-xdysp|=nVi*YZXWS8d72l+fi`Y5 z^<&<=Jl@4bJe3MC+8t?OKYCor9zf#d@J=lzJzrOs0xHQ97%n-ol)G;rn zovhwf0o^k>OnhJHSKNWD&8-Lshbxe3t<5br8A^;EyXUU)sa+&Xjt0sb0o8X0gidi7 zycdb)KwXN@Pos`|#iLH$F;Rwq)!3lX<#f(U++BHmPO8g%haLA?K}krx36`+P?pMOW z`bgiD#OcNWqom_*vu^8R-bmIjw12FvlfW^Yutof2^*R|$>I6mq7V9Zd=U?xgGN@)8 zIy|T!lF9;nVMDsjcQlZ8-lsaSk`VHtMsIJ<9el)@0dmmaQ_*)`jLjW#%n&783yQB&hx z*A5+o$rWYWno9kOvDj+{A1YmAB6~V<@cCF-<*YTsjoL=NQ-~-)j zH>o*<*XP!e1BD;_KM`N-kH9P$SCf2QRR_E7IiJzG7lIX_SJ55!n4zF(MqL5RYSv8Q z>0PY<;sQwW?0!Az;>qSq@2R(OSJ`FnC#GmpJVsheZQBZ=m{#zge?_`$hCAByzNME8Jy1UC4fT)?V$X&d%s!)C!-A>AdVvc z<7cg>jlt_Ka7}y9c@8}HTe$C?N?keQ9=kb|UH!yuJ@{HNs}fUo;)c`?TGk-oON(lF z{0*`?7dqlgKV7enw?Y{`OZxPr%}?NDHEqzfw@b2^Xmc(7 znv1A?=hzJKw_xptMMUJYPu(Y1mnh9v#3@h0 zJQm4j%}YY{>Rf_mc4gZ#v1c;tm3--im@ZAcVNCPN&&Y>2b;-!cmQj3_RoSD&u(fz( z_nyuM?2>9uyD%rGXKa1rYdYk}Hy77)}xMcLH{1V4^H6t~Lpd$l3jT8R~e`9x9V zyd359tT~Y4t%*)mR)ye6Y&9O9pa`q_>r9Us$Qh5T8qW)$>A;=91SH3%4XjP@5q0$RmGWkd?19ZwF2Lr%T@2jb~_N)wfH&mD~CO=)~&GG zN6wd+wf3swkc@+m+BcKuwefL#4%lEwl@{Ppq>ahIIzDGPC-4Tu zj9hu|y-E`K>{Hhy(^5X-McXjoz9=G$IS}w9xQXy}WgFDzoWLYS zo6uJ!^1yz-40X+7wnXkaaeXZbxMi63 zzqWjq1%8z=iEX7OT}z->le~e7MOCkQae7lfs3bLonT@|dQ9J$GN_^e~-I=X;#;WVn z;#s@?T!~ImE0NvGS45M}uvuN#U`}!@b9U}4&H5Z1HHGJI$mwg2`HneZ0F?*W|V+|0do>i$$*QD#FH-DHMC9+sFr)AX)?Z+!KpnpSUeur97rL z0s4*`vk4v;sF;QV<{qbhm;0b?o!-#;3msq}W030uE|o=TVBamy#Z?wQ<1a7HAvjmZ zHaa6ap)p}4#mYrB*g*-Y7-4Gy8f>@;_Fm(m=P4;+_3`rT-)$ZN$2bBxlVMdxI?QR}vS`$j!~waLu{;5joYLEbT|_ zy;GpUi^MT6q7up!$g_EvU3S{nwPL+PSuhIdUeW<()Wvy*^6-p=UEg}W5df1buKVmk zn`&zX2({ODo2;w3y$Sey97(vCSK|ASSaBLVtv{P>zfwwP>*#(YJ2tzZ@F}-a=;sle@(4 z{w>O-c=zPBVGd(#O!!}pm9m4O@KicomHg~QGA>wU>nFD?w zvM}+hWcSzng-$$cL4a`6)%2V~$WTON2z5S6a`VbPu*deWO&g5i;?M*fF-_~c`oC#O@xF9u734wa+U zes0#*!W`qp{4hC?DmU$YGww>U-N`8`iW<%vjU{Qw%$}~z=>Gh|KR??y;jrL z5qq7pNIxrht;ey9<9XH$j&hsE`ba_CT8>2akc(pF?^^_rZ@^h~-4=@aGVJd9J}(@w zD8`h$e-?6I;Thh0qE~cUIIpXPb@b}>cx?j>?NWhqDW-N#Bf0uu<5;~Xe zz%F8aef?9Oe`RzC`8CXgsLsxJ03j1U0ve=eO_7U42gs1Jt-YQWa@UBNkr7bhCH(!v zP?q7DDEs@tIMcPCJ6n^kucDgOr7tf$%gXlOZ224Jd?r=fLC?yuX!P>@Bvx0)S*YSu zK{+h>yUluO@ENH+QRA3pJ#$ugFTk`ttZ7^GF+Mk)R_8U?GdznOOkVT8D~|W#XD~wk z7;|u=QkLWwtivNCohOH6^pVVm+jzW?IxMO7nd=CgeCj9&)YS5^)2iW)e*%Z!TjDV$ zCA~jqZ9GX|*ZcvVJ#0B_24Z!v`mtw}pOP3nJrIT!V&R7xa)*1mSogq*wVS`^y*=)Z{OH>SJ%pRdpsB5#XSWGnY0bO>Ut%r?X z%c0UD`|cm7`T{ez!2O^}= z|2K*L&$j%NT)l$!|GdeyTpL(UW#0ztw~U2Fj?>yeOH8fuAFIw)bU4i%KH{M6=W0J~ zxPv8B2;Vm&-@)agM?v$Hb?@Vs-fv#GQka-FmP%z&$)b7@U9cm`TeRZD|p z@+UkBzu#U01c4xvmFlvJRt1$Dm3snCZ$WmMPhEuB?{`PxwI~5PsCF2uXtB82B z--+Z0J5wDltW%vDhYBt(uJ&ZHYZ=c%xB2;K0{`?&ldI8Pw(~!NEs8t2pvrel?G|U` zZZE=+sUeb+-gR?~X=Bvxp&uY#;ThVf_?j>uZ*l)mZ$;?`+Jug@pfiXvbZrbh`n)mJ zpElQFlRvt`DDZjF_r@P_I`$(}#r%wSB$Z)?@L)DH~}Nmbc0_2ZGt#efBoW0J2v zv!OvBcyX0Yj1Lwb$_(tLkgb+4fHV4Jt$lB?!qW{wO6+XpXl-~Q#8$XZmVFS4$&i-PhU zlh3a&^xM_8tvz%8aPdHW5caC;2B;#4c@$Z03wpbRy~Al$e>bRrE^8lgpIy~p)=*`* zwe_j$;NBj`4$s@=?*t>Z3M0I_6`eAhL`LQ+4TgLz1;kLyLK0*m zWAVsLOicK5WC`$|6i2+#INRX|hd^%tok46XUxi@RKRiOw98JX^*7J-0AIRZ@>~}43JN%>!)GXz384>u_ufF*K~L0=mRu&Yyj-obvoM)6*ps zgxzWrA_D?2-OhFy2KSNDV8+A-rl+EI*}tSrz5gk1gypFCC6p-ifwUPV(Q1(Qpl-fCiatxaFZy;FHdghQy& zq^oXk$Gcaicaovr{SZzYD$v~1`Td&?D4M%W2QS#*dD0m{`(P3}UTzT%$~M?hZ*5ou zjAtikDQ2yiMTvDlNwpK_VGi@!nsDA*&nrTmA$AR`JteS0-}Xnc80cAqHTli>aD901 zWWz2K9BuN$>Y@(3Z)|2VoIAYNO2*oSOD-S*=r}(+)ey62bZ(A20zq_u1i{khYe2Hl z1`q%47*1XxyQB|yAL%}jpP23G9LVT0p7~UTILm8G~?yi-x}2 zvO2WN))V#5r7%B}A9G?ZBd6prNgnI5FrX`V`(C`5{~vd69aZHP#fuuCA`%j!D54-D zt)QfYA|S1VfTU6?DJd-&DALGAI;2~=HX$P2-BQw^G;B7!`GMyg51#k#d*j|a#vSkc z!SE>i+uvGiu9?62o3Sz3I6l8M{Ir&($t!ittUD-9cKCWMrW7IoixN${!4k|3G zTWNh^8Ybaq9G33N0g;1#{dWF_lZh#B4@NMf1yj-@1D)p~Gky83fT;B1|Ndz5ZZj?a z_NZUuZFTjX^=##7#}wJ+hx$WtwTDS50Nz}NdyAWai7hj8p-Wp9-8DCsx`eZAGr zqA`)tX1ncU3#L|qbxk8|lD|N9Uc>B*k683-K!Vz!fCD_EymgEnEw(z}sdSNgYrxJi z*6iTM>gqr(k>tDy^2HKWcKKU^G25Emk7t9j7p4r-db^jjfAHISz(3v(SzYiSuv-si z8LWLV-^`?x?8~L!C_~*SfQM2ouCrqc?_EG&UptF7oHPsMGYRXLhJ!M$CYeWXrlhYf z+#Rr6DxRDj_&H!bZZ}_2CO$~NjBSy5cfw|V4F5jrG@ljQJldXGweEd$M%m-Oyif2| zT}#8cgJEp%DA>f|ny?JiqRpGk@np(Y7xK-=t3h5~ROpmoFIGVtcWdo+(V-443uybC)%?^kJI5dV1s~_f%2p>livvpL9b9eZo3XQx zZNk$umjv{4?Y+Wg{HN!c0)C?VF#WUBSi;`N-|L)e3+Hp}y#yZC47J3Wz)NBTe1_l` zN@4<5o!mq9>YiyNCaD&Sm2Ah;#^|So<$PFmpf9(}Uv<)qS#^pC4YT>KGS6S|6_G?9 z+e8J0d(q?Jj#DZL2}UY&-0&}j^ZXkL^x-}|-m42*`SfRjS^|B{j;&xbA-V&qhV$c4~Z%&YNTInr)D;~W`a|O#T9hdJBBLa za-;42`QWO2dRwC%Eng88wPyMF(y4KPegC zc!zu`>(YVvSLgS?cO-6iva;>PN1dp|Lk$GYJDfCHkG!__K%tzIpx;IQ9nnXPQuOr; z-v}K~wqG`_a63sYie_U?Dq_1_me5Zvs%azLk*Z437fSJjlgM4eLuD?8LK8)Bnk%PE z^GErkSgnK#*JdZXvKADI`hFaafI9+Sp%&G*g|{KVM;(B5;YyYfedGvR6v%pwE9p%1 zk@CCi&R+!zWE~RJnW8>@HU5Z9nHdbIdQZVzv37Y1d*#-k>v2dbepJ|}yA*J9`{5_8vZy83AuX@NEFTk?|43C= zFyE1vH?S?~9r(7PuCkq%gx)eti+#BAxHg}ZIa{Q*Im5O(7@`Pcd-a0%&jQ{a;|eHx z?~m?G8Ej6JXW8rRE?kUI`ts;70lMV|n>w|C{fAR7!mch4+@RD6+1743-VT>7nxY?@ z%n4H}W9s4js5!faoD<~2|4zS}-W6hN?R-G@)sss5>+7~F&UgXC*K;+TbzSKUg?6$HNRB*<1*)So&tTbQ4lA0T%W*w3*PhTEUC{+j#4_A841d#i*>jhuPR zzgI(DUw3KZuGu$dzrIw%vj$@6@5MG(&fGsd>9{sjS#+bIu;ytc6$>Dn4H}KR4*h$- z;E^;qOHU&8!$Z$YOG}$ARMSf^`nz3HFG@o4_OqSIDokv}YZ=E*bGm=YD(&%{+OrNa z)N_q1&_>D_czylyDRv#x2Z{n>*>uV^$1QRQwb@?qeS0p>ZoigpIE%vW4QZYW32Ch_ zkRw1~lYR3~*&pHzmL0sV2SKt_#gHzaRa@2f`d}z=OE5Cpa;wQF?kVa$7kYpXUa@L{ z#pJ`SNudEL76@|h2XUjLBO{w9H}3N66;a|2+C}_zIjjb zp7;(#T0c7Xm&xV5%ap`}dLrc?^+X|OT>KM(nL*r7Zysn&9f`Y@-uARSWbVON_rUMZ zB5#Lr^`q3op^`TW@v98ta=aMSFzDvx#w|$JJ+t_V(a^B@Ozy8FNKo0Ql zBRb~{L1D9pL^|2*NZ)-Iy78cwNB1<>>{KKQpxhAU5TYuXKC0$-=ls7Hzmo)x4lw# z=lK;>)M@`+X#h=j)iQ`(H{(C0LHc>&XOY}XyW*G66!M?K1(u>83S!8yKoKxieD4Z( z$~u3GAC&L@_Z?gxIqP5dsnB@VRz9v6yTPl(wfD|E7m~x3OK6D>yYJfYPAfs!K7TMT zGV;pm;juS+F9caW6c*R;p>)_+(H#RX&q=V?`0YsDGmyGhh1`YjwW_;gBOmTQMu+Ut zlPA7YQ?mmb{c?(mfGDIpRhx0(*OB#+Xt~Estbpl9Zte?fzQ1MB`L^@M#vzpgi$Li6 z2{Z%5fKFTdT{#Vn6Zuq3zpyu@1UvVE@IN;G6z)}p`x!b3ZJnK5O!D{dGeS-GqS}+# z&%d&^#?a6wl8gVRQW1(5ioH{P;-Xpx$@N{~`<4UCx4^*f@4Zo3#E)1MGENh-s^I;C z3YjXwE)^IZ`BR+*;e8?SpAmg2dC%n+7N!qcbKHz6{=A0HSwgD9bcN@KeEq-uLg}pH z4j74i-n)P4Nn(@db3dF5MtX=@EBvF*>|U+)<8YxC&9y4MKrcNzh?^vq zPDb`%x7)*c6W`*hLWaIjo(|o!U0_iuKVmpIq0PF?x#f}DbQ#hv@={rXI z+jgK}JD%Br=G$}BBA2{p?^KhJ{IeTC%ISjpPKpj1yS%q+xRnpO&>cQamnd;;fGVb( zcUM!I(Ko3?`tl$AOvhCA3tO~K{w>wCdFzi;6*_*@!YW=z94{al=NF=DR2KHTCv5i~ z|8}h|u{*21l0f;JR!jWYRog<2P==4{|HIHY(~wkI#_K%({h=DjL!l22F%`c*l*HK{ z9vbl5Lyf_1N1^}Yp~O|z@j7pRf2a(iM^s67r0Cx#=*#>~B3{Si_g}sTCa8XPKmO<= z97)`kJ2^o(|HuumW@+}F>aZ*Sh=+3@9vlZed+(9pk(ypp$f)~G=*(zARn1BLp&KvU zjz~xlKf!7*HZ?sRD!@b#@YSPNb;;*1kOr@qR+)>4UL`5L%tXjRvNa6^7P0?SQDG^i z`>ta}7F}9kIjxJ`Ai%x53jD)0fYv!0m8!6dC3C(>h8${&=pQEK!X2&UscRXJTfe(- zX99c@vQg#uzkd$7CD;DACE#1MxA*E!w&>zM)uR@C$?X3gF&k{ZIas zp_{k>sJRf#W#R+SLHfpYC>UJ4LwWVlbHC+}a=R+)f(UYn>Hav?{W$JI!on|r;>(5b z@q|#{*w`3V&*9U*oa~+U-6;P5`qaKM5;mM&arb3F{E*&XmWP1AqoMunUv~cakus9B z1HXQc1#$Q=IK4JjfKPr|+o^{mx$ZljxBG^-!0`~lq@UQ)qRuw~KX}yS#vdjzq6(zb zZ4OVZb%kc3c;$83WHqy;zQTQLe&`D_Z);RL( zxaD^&Jk##mgRd8wMEbW^bsT;V_t)>KB7WftUe4kSfS~aL(&_>7Mc4Rr*BHG)Krt&1 zSa#L#D^?J{0hd2uK|3xZ`8xjdRx!j^zFmaqQqn&!t_XZyVU%s33^j7Z=RO_4Kf%mC z&iQos<(DvRF&THQ1X^30E>v0J;`4^{zg_@AvK@R&v-`4S5&NuPy)(Z0$GzFNA2h%h z{QbM4qqAcMe=YrY-}?XU!68x1_qxE&^BxYXq32>^)cLZAIDdtE&NIlFP|fFW!ixI-0MV8!CIuwcf(z%V(H+V#r} zMv)*Ysa|?VCH1_1Ejm7R>sQ=hs;&i!ENVOBcvF`-)s=@Imgf3<8?0)yEiJPlxV@f) zIeR&xJxXb;Kk>pPUp5bvJO1th-6{WEy@7V&)LP7vQ} zO7e=rLb#r;?kglsJ{=J77_F47%m6BFk(xEBakbaP#bUNbXh=mwSAXXf-hz-GHPQG( zQwFg$(74Fo>@CzJGqMqtV$~Nq~g>4IZPL-ahS@Y&}2~HyQYeYN-%&q&q^#W zTS5Z#p1D$f-a#kp(JzoJIFGhno~dH(`lZyOh<6SLEY+S6gqztrNsHlj1Uf)M;{L!8 zi?YmkaFCUxu&{7fqKrBytAwJNGds8D2BJwk#Q*CFX`%|l6|@#R7h##4-sz5Aihj=~>%9oDIUG7tSkBHGeFeL9c4mTVz+ZY_8)6v$O_+Wv+d5 zygY~{Ep>nun)wZKr1T-(vFCyRC6pI%B>C-D-ovQatF7%Ck}!51`RpOW8MYUP2%h8= z0U{b)mbh;A(DTYqZhZb_J8$BiU{=;a$%b&Bw{ZBxc0rjjU1{G7YYOETcXt4z&PB+Z zGP1Dv{~W0lhyQxaraO!>?AP6pa-YV4`d1Hwfx^e-uRSuXW_eXFU~i|tkNt+Xd!9ZA zAi(lP1mB_EG1=C*zQoK4sf2y}78AsifcrJ=3Bqr8r;&QX^zCJDu z;uqcd!!LSXegKka?ACLtgq@e26oW56pcapjTTj|2wyVrR^TO3Q?e>RLY^o+yd9sD* zy3{-#=txyj_me9F2f}{E+d`rcpauARO-5|vQ)44x;g9B)Q8#zBd>-`VE2Z7f7R@p2 z9yYsXelHiizZxLE4(ius)jvGGt2K|1r3*1C-Y?CbFG?h3whS|hn+eoPkXP+bpjjAUZycnb5k#Y<8RL?E0*K?WOZDve_nff86MQ(t|sQ z=l@ZT28lSnCnLdQY3B4A!*OQIzRPiTT0iDO%*-$E`AQlJ#9SbGq5thkEl{!lYqOF6 zjsg%g&5pzDZQ-ntcy%(aD|L}p`3Z&U7AK8zO*sFr3;Fk6UeW-4{3D0{e^#{h>TR1t z+S?MuF8-L|b(DZKGgsZ@*^@6L44k9i*swqAD!E z$l;H0YR7fk_1_Q_{QoHV>#UNgaaI4C<&@9=4^>Jqml3Bv*P|b$IN`b&Sr4rQtd&i7 z2BaC;D#s#ziTQ4D2vSd$i75KIxbUBScqsOkynGj}>UyildwYJbddG*={Qs<6goGl% zU9g(#W7yd4fYvV^00e&5j5wLO^L&vDNV`|F6SRGkV_iBvioJRt5J2v;FV1b>fmfF< z8+5#j?U9c=tMaCOeu)?C5bskfqS9~u-OD;A(CDS^92x7-c>CQZyrJ-#+3d@mn_(aK-6FOb+g79--+2 zl`3#kf9CS0#-YigpMwjDRGL|O7mfVuTVynx7m=Q!N%3zZy8)+{O89OLnqs0L(d z{fAQ`9-pgs{F*FDp~_K+NYGrInlC2Dcul|UTkJ{={d64EoVN%P=1KiaXFErcH%ShF zZh0-qZ@c8}d=RtPITqz`&2$*fNyrK(iz0ce3}!h}k}^(GGcS?wAf?fB;fSlh@o%q2 zgeii0b9#EZo?Bw0EPOYSdEU6h8H*HtvLNP~8Z^1v5LQQ+L{0Bds^1=hM&#jgQ zoxx95#%k5hU!}0Tb?)f_J67qg#m*5QT37h_CHpcxQfbShi$LoHuxUQFttLFpSvQRy zy@!p5@P^E_G+Bi$h%}UUxoVhuh%=0?Ozw2qf_ey~CHfDXp>`fV5ucFYBR%l#70})R zcDiKv>$jPmY=s#O$fGENyX%z935OQQFh6ZGV&nzc;CgIQvV=n&|LRj)yfWX`df_oh zl$4@a=4}JYX1$^3?uz%5Vbx$##1rl#j!#WJfo+k4zzC#zL^LKI7v9VtagWV+ldXRh z!N~ld4{YT}Mh+B`_GtkOoGHu^aMGIbkw^yDrYI~{8AW(YjO7?LNyRoS2u_|-|~Nv#njO^1~0+ne}iFb}%w z#$hUDGNTG;eHOytcBH7XrIl>GX1>4Vsi3G>eWg;$AL6?lCs)Jn2k=dVq!|Zsh^t)S z0J&*)%ch+v>Lybixtf~#)3>22aW4PBs}szR4?B#(wB0TmkBNNjYk<*${Lphx# zrrFE*J>WvQ2A(<0P*e?Wtq>uAPj-imtH2T_uWdq+8_^*?y|SF9IqdM5wQhfR$$I@f za*Iao1`83@NVVD~oe#5~ zr{GA^8ya4^epIVpfs z)f&4j=nX86hM6-cRbziZg9Xl73QFZeuC&I3L+r*rXVV?8fGJjnpS?)Phm8&*SPIDJ zF{K~+Sa9TmRD?IMQpM{8(@+n!0No8sTzK)gVu~o$c zIA*sm7_pds52U`T={;XCExQ0%#*U8q&`W|=GsUrIIrOfNy*eY@qd;G1WN_MpitnCr z3D%nZk$2%iE8~Hp?m~kO_B78I86+?H0i0{LKBn|6vcKVWnCA9|lG$gxN)c3bd(Fm% zEyCQ55a!{CJ*Y&_WH5~uW9GP$fMv-M_$$NpDmF`!955^XF!&uZ6{p9TG}nEXW_!g> zuxSkAqU~-D0Cisleu86*A;!Mp3(vNFN7~2Ai^@~+=X&8Xh#~c_th0MB6PZN%dj^FJ z5C3V?>TO+rt?HbcR%ea4+kCVp-I!QSPF#NEovv3;**w7Fw9y(G8wGWO0Kx5b*@l&- z<^g>;cat$+$>QNx=QL-C=Ztc(cY$fmsa^m;8&x6{4sx+lJZc-O^ZK@|=#`$+$XM|z zH*zNFKof`QV2waoi-`~2U2EO?;eN7C8|G%f0%+qr6MoY=fj_-?B|Gii>sP@0;st&L z7nAjLN{QceTFH$=L}!@+*_rnq#Yr8V4J?oiywleM1)YjJ@nOPr1CKsuuRu7*(+0ut z<>`q}U^y78R=_G8M+_zZ9z#iT>7`Zzl!7>wSTL5>O?hwg=wq@(e#gwG6gs4b{|Cn=C2I@t$?-+f{OM7GBvTN5~i}pFfhxkf_xAK z43u5T8*|oazFrP9AoN;U+)hZO&K z)o2s SBRz2Nr)wub20vP?s>Ba2JbL-1@~a^^jyhWg*1!p%$h;W{@U!$EupQoNVoqY%rS6z3AuH;HZ&VAZeBJ6xhJ(eVdokTp4ly6m4DEa^uUOqg^ot zcQVJwvDwT7g7LTGZ5lmoNr!2tY@%YV-Gd~^!xh8FkXoUr#0-B>XVS zv_8@)We9V zK^+)PgTiO!Zahsi!xG429&7Dd87QZu6kgmn>mzp7Pf5c@QLizK2@seW3ERLc7cel+ zRbrD5?M@$T&|iM8mX!1tSqUyD;McRymMx7?4?YSTbXeurQ%iu^IC4vtseT%V*5At7 z*InKCoZTo4dPOYgOrnZl;sn>rZS?HkZq~Bw}zCDvFXd^1-u%cD$BTn6cPkV{c)ie$prl9ru`c$)TC!f|zc$8qlk=xNJ&q z2rkUMs0BF85E8Rq_Vpl2NgKDMXtw6j)t3pS?aKh#dR z1jKfqt8N`=Mwu=Z zWN|ywXp^JtN^pY!#-8m<&@92kmL1PjJnFo*fsxJ6ig3<=ZrnsXd?lg6ZM z-Z1Nde6w~%B?*Y-W-r<}L8HGN=^gNE?rSZ^)_>nTOklMtdUoCLoJ)-TRPIO@WLjps z+@ui)dFDvo8Ro|(?~+nCQuANPt2SDJ5G^0lEAB7!w^dZ-@=z@1co74%oCnh8oURO2 zbQW8^wYJU`j)5{cFfLM{B?BFGU5;Wur)iJjl;nxwN`k<;bxP{UvJntG7uaaVmtrHD zwJ}@mUk$2#wKU4%kiX)3lhRl^*sAEv(=S68Z?&AIr{tcDUWTzwN%iL2M6)So(82OS zq{nU)H1f;*-oUfl4Se(L{1!{8YF6&y4TLCxAI_QVgk3X4*VqasZnjEFX-e82{-n8W z+n6%BOsKs&xSiWnR>Ec6_vp~e4#_5qJCkeb?Uw9`K1P(0JWCu zLTv+gdxU2_`R07#I#ivO+A!pSvB&Pg9S=Ug{Q2F+Krk^W34QjO-PR*=`bP#vPFCoZ zV&3gdest{Bi68-oAiORp{Lybv23|8jF;Jf9>edo}!Go5PB>)TCNEtlWUn?!k-~SBi zXb4O)t+U0zteimb%Ii)I%z%M}-BNd_qjGvQ=GkI>w+350@4V$YN9(k>mwDY4c}xHN zrKiw*@?m^~mQlGShkL}GD5w_Ca^H_u+3n}uvW)&uI#BeXSNmTkPKMgb_F(v0Q4p{^ zCdvi(qOjY~5c(^cl7WaiJ zC$yh106E-mwVZQ553nAVe6U81op^Oo3oo^Bu|cqA$hCwmFR%4GABS>=fARKKiR6z3 za%_e@+?ik++wsgs`|mWWeb)hIy(GDDNmzk2$J&2&eKp43O^=W#Q3noi-$ zT{D|5F)Et-2x1A8$TnqN)wUV5R z1QcNCcM>jDQM+3E&5>~5PEDu&LXQ4t^BLtOS$Xn-uO-7VePMj)e$(>hu==pl`Z-~0 zKH!&I(c&3<-&)T~!5Y%}xz{N!bcYqA(~*1MJ!z3w%?et^j|!OGyQn>WXss? zpG4=Kg}BQdUfdKpe&d$V)iY*qa_*&Ax8F}%j$hTZdz4~rU^LrktCW;|_R?}<$9-?f z<7HKJW^bSOiG4o)()8tp(rrxW8Wm?|rXOAKNMUB0&2=sP?|hZkVT00otD%AOZ9K}H zw?`xNc>F$Timc2Jvc<&2WSD4v$}$Y3%I6&!!~+ZV5DD_zf=YqlB*ejbx+A7z%9NY0 zc+|IcY_U;aB!%5-rk36G+uOT~Nd{k)G+7XLxw3oHja;(=9b7n9AO_<$g;Ptmc=MM# zf@@N&x3MJwK>FSuxY|27a9&C6r=QuF-f*CPm|w}}*xl-yCouB0(Fe@K2O6H~WOJk( z3r%Ecz(l@cJw9kV%{GQz-DUy~qyS)JBY4~t;jF4B>h|v4*ybXeRfphm+t32A{|3{H zTaMKzE;Ng^j()~(3=s9msPPYcJ5(-K4UnHP;4${mKznj#j(KnmpkQ`-ZNlPcHMK<2 zJ(hlU4moAzOL5^_!qv64=6cfM0*XD^2KsIzwzC6h!|?fH4C;)XA={wk(W5!wxec6K z{9f+|*}0l8Z>ewJzaNyDywzvcA}$;q5g`Q1Pz^ZWsh9e{LX)-vCY%MxoR6ERVy87t z5^PG$JAyCY2&SGaBxoHdpwe$_@V8xNuVGmQ`y`TGwrI9k&sF2WwH2kNa-B3kKXbH2 zk}W_clsC~Tn5>bJoYfpNTjB#`!m=`aMp@T?e)*WHI_d^!#f8>zjj_!T4TojLg(S`X z?H|;8!2Z3vK*?iHhFto~NvQekd}Ii`G+39@|?La)cW093GbeCNuDto+vF|`TAgdI3`lf*0 zvI$->)1f45x@StTYiXvtH({-kS{7Bsrt4VN0#)A_>v_k91#pFmMn^+t+%Z@#>$&UX zXVizu>O+H>q=Pq{#Ftf-8Ve_)YD3t%yac!6L!N#;Y5fT^XLm=#MmKqLqcb;2Ye>Sn z*!q^MF|U2AFilwtNE?e7wZr?ubu5PHwxeUQ{bIcY_yn8EFfkO;912!?Q! z_qAl`90R2xy$PbZTlHZ8*q;8yCpj+tp? zfB408_1vZln2Zt>_2xH2{Xm%3sBk~)(GcrARkPB!rAc!a4vYtvhJ55Ll^;Xu4N~*H zxw4!_HGbC&I;xUY%tyQRx97;P%V|z`G0Yf}B>fLeH#MFOsUQ>tD*RK|*t^6pD7*Ef z>x(K`#xif&H_wu1gYrigLQke@YDuXn?`d|pnIE1R-oVJ6%C7XAs2y)L@%0Iw$iMe2) zi~P8p_)e$_Qs0c75$&g#)vdbeWG0R)WZKQ;YdTzUQa8>YyO<9XYx_j7E_9@qb@cU< ze(1k0G)z8j&KInCIjV%r=|>q3bYhYsb34p?g9oi>fEL-HtywI*Jt~V0eDPqG~=lS_zN1GyUKy1r)FOMbDUJ*yq>-s`^h=bK;?q`vlvG)w5 z(E!~b@8~pfuGy^nFsop(>7zs&WI1f(@I81m!C?RE@>IKA{Cm#ez1L~=VP9m70%V!bjAY07*xsUQmdDuYBobIwmkImJS{rRc=g#~ zY#2wbLQuqoQ!X)8IU&~!Vvkk^RoYd}nfYS_ubjTD@yR8-ig)TqDgYq#+Dkhj{c?Hg z3A&U#F#+iKg0#As4;=YI2U(N4TflLwtquTfOQgl+HUp%EYOduLFB^P$lOgc(tC!Ot zp3-o4%ZDraHIS*YH*sJO_aUK|c5Th{GuV?st5G(C8? zMY7qUI?}dM>q6U@8d^P8s}xUI)M)WFiC4MhG`4?!PM#{*%RF^B@XCl^yiCK0jS}s9_$c)`qsPSdR6SQ^f~wa;>ZE~D=;dancI0m6dL(sK zxuq8joP`S7HoE{lZ=65s`))Zo?-2HZ-RGBY8&VXNG}h(yL-PlhOJam$svP}7K67t# z+z@BSk@PW|oINHORxZ7yFFIfTtl}t&W;&klQK!-rT`MqyNTrCVA3qWfiDuJal09O2 zuG=LBl1BqJG(mH0Uyf~5KY*BqucgR_gCnU&la`O&@kbd(!L^Y>A+Hh4KmvVd=rHp* zfyR;)&=a${lJi$gzn6c_Co8+Sk~B=gJjr>MGkGQ_!0l!_bp9s_c#Cpdx~p~c9g3Nb zO*zeFJi|PKZEUNxvj>CZClz#6{H)^|k=b0nu59aKYEqo#ho|KPTzn$!$C*VmDn?eb zvaJWPUVV<3w2=Y_KwAwcg_>_OK2gyoZp3_M#*`jhO{9OPuIw4j=n@0n;i6j`ot2?U z?E?<0($!U$wny{L_%@Ai4i<3RuRR*aS~l{R_|>v9;nQ`dsHT1QGD9WCYnzwMPT5?% zB>q}Uyt6Y)Hu^;vb))x0qx*pCPd9SrNnI%_rl}GsHiKTFbRE@qcc^jXB%vIhhsHy> zI3zQL3OK~d#{vJN@@{Tqh$5X%d1EG1ZCQDNPf@HX*uL2^E^Fi~nsTk;SxYp-6e{hs zeP^eapCESW%2db4k~Nf)UTnUulxF|J=Ax51pNVHS%VIRMx`0s+S^MET7_)alK%0Hc zRti1n&#ouzSmw*hp_IDzia}2uy8#XO3b{J`GksIhFQ^vdyGpi}@oXTQp;$#+xJ}=0 z#lsWF8@zZ(5;AvO?M}_wiDzlsSnOp*=Q8^89Epk+8%jcLV5=tyf*s#4t+}6Sjg}7X zux&d9{4!CgUl^y=HrtgneU-=c9CSOcb*iz+$2ndY54BF+{OLs$B%QZEl2BH|asAZ? z*;Unat;o#@FqEq->5G^M!dYqRhmciQ zRQiI}Fm|#X28Me8wb92jH4~Ectnq*KzSn zMXStd7TARk8uivq4>kb?sQvnLF_YpVlw_YA(t}wt#Fy@*r<5|7w5~LbKQAShOZL`c zsHQB6j=oy`M9T8G&^f6CIXIF^nppgCNn7ufVv!j)CPx}9W|e7G*8u`p_NT)->re)f zJ&KS2g7ZAM`#H8>Fjt&wzo^Db$b>JvdElIWVduJ=|esy$!tl+mL5-nqyroJ3OM8_j5ahzF?Axw&=xH%LW8VAE!1^@}mT(CDIX4`?Vn%g4jtf z#ydQ2l3#gOJL)p!+7D_?g1RgX*ybFva zRZI&c-~nOYjIUAL!gQsO24PoL;u-I`}NM#ZA!P) zZ703J&(~-mtwdtpg7S&St+7d}rCT#I@|uc2KjXvgckJq*M?nqUz4Yj`^nC2fV0;qe)`-3McBSB!2CEikV_H`x{-cR? zOIasW-tNyGO@_jEjX3=ncRRM;#4%>Wkhc@f}R|K1I_k zNU_l&uALNK9OuL-tr8{3@$*c{fYK;NdhlL?ll6xrf@QxGgLd-{h4VVIpvHJkNEQk9zgIRQDnLO(<`8@U1E${ zIxW03#}Ug*e~#U*N$K%KZz<)q8urW@G$?J&Q;{Xije#7M>%M>m5o*?)&~FTzHD>GZ z$NS_$ho;4nWkMb(aIuY(AN+q4`pew~mce!5f|`wHsqb5;$hi!aSH`*m*ZZA_1UKJo zV&`2`lbe;KCYv6@bgl*x-t?yPuZsEW*pyqS2sCZRuFVCQIw+#%sF}>RVX9cxX34g7 zp?L^+@sIKQ*KIX4!scgfV9uT&BwBixJB&Kh9~@N4H}fauo;NVuS;eb`*^m1mn`s|^y0*-koH%mA=YUCTO_gmTImHDPBjn4Ym-ghK(RWLZA8l{EMbedP}_IR3#ds zMBl}IY}HDXEZ(5a^&>Hh3FAaS&W3jkJru&4oNUYJhoI>-oYQeZlRNk?c5awA@C@cJeBql#%f%+%zc=W%3i+UdNa<0l zWr`sf^QXLAZSJq5rzd*C|1?*bZ?VJFLM@wRab59BPo7z=#cYqFBX+vCO;Lolww{X= zY~v?Vk_ub5_LbQ|UawrQqJpYqyPp*l396XXfO{!XCMN8)?o^n90+;dQT&7nRm&r+B zYC-6fReo#i%9__3&BEOwck7fMrw9E~P64&(vFB$9#-Y-UZGJ!$9_%5|7) zcjDxa38oGhNT+K9)I9x3>2+4G38|u19L)2RzDlW#CK;z`ZY^*v{H$#4>vpuX#AiCW zzPZ@V_}->@HAkN6)dtt-P$36^tLN56L{qwivT~oXhIc=6r zvRv+G%oL~%U|m#_`knJkriQKt437Mm%Bm9g zITy|;?yF3CYy91hd0a7RX3fh_da~XwzUrC3mm1+Q>FBwnc%z{4682SJT3T4dvO$K_ zVZ4oJ%OfMIkc7y(0c{_{sV>Edl4)rs&XTf?cRHvDG0&F$wV+4uUanRQAI8gW{K|KqN9J!%XgfEM+%1Rg3G;Y|>dfKJiw z);=7`l`PzA@C8$VD@%0LUIAdxCr}R$dnZVCKM*GyKG_Bd;r$&uL@UJtEN8lof zEwzq;oHASX4m@5_a1TPz%I)VMjY1G_2%RUxCoXl}TY2BeX{1_-mSJRp<_Vn?bvd~p zoolfLpl2>Z%z8QdVkJUweZdR%X(5aJn#j4j1E)?+J`gC9pgvtD7c==pU&JM*=c#@g zPzzp+pbm8amagTh)rR(kH>M#wpe^DbM<*5=rrHfc7iC-tXVDc>2;T78MF6-%#BpwYn5cE|xb3`K_G=G>j@F8K`SWmhg zbmi$n&(3xSz4C(fCo5ye0lhfmZkCLc6k6zdcm8qIaG*#8QsglXm>jthN9>QYm&A7H zJ+wT8?luc_XEDe^w+B5ifMvvLAWYwKV&}N5QReA9JTK(OBZx+B=eHd)i8fsg8m z)%Xkn$od+PdNQn~Kz8Ey%mk0Ki;IbFV_3hhf4g@vt6b_bj}|N`LJxOgF*n}4;jCpt zweR=(e1cTHJF;N;0(0oVf%T$vYzlDk|t2 z6f-QR+V19R=K5!4Wl^Rb?S7eZe)Lqt)a8H1L~H4fS+7ddV0oOIsK3fN!CAtk(i`Z)7?p)y~9R&@_T zy$~HO^mx({Xtdrzd@{J6lu>N5Yaz^-$v4;#B9`GAPuq~de8(o&K5_{VgvMkZ6PB`lT%1n zF;+Eh9(;T3#&WdCyAx^&3dhfk!Fsh1K*c4*VQlnR>L-Vbt)53vz6se1>NbpOzDUF1 zxmP&QZpOlAJ!fI;(drr+7&zHS6A-8}xL#{+%exvK*VOAQi~ttxahFnzd2hFDySc6K z2wB0IeGxqXiiPUt&+jNFQ zCqU)yb0$091j8@lFfuJl8B;>yn%%p*i7@H+@#09933T|AFsuAcZL!lFfc!Z31&+TA zVJi9U>ip9}f&A&_&O@L>2ziK1xIk+{D*&vMlTQELp}Z@>@G!#zxV=Y1grBtLSauPn zNO-zxB}kk!Fs7xWi@BD|v9gCV2hQ75Nc4~_&x#!r$WMaubuX>z;jfWTc3__W{D_2J zo&^&i?{T6q7>8B-2j2>^iY-_La&`MQi|S?iGgNg{RmJvF4?P!B#xXP3=W+G*&$LX^544yoyw*1L+&rPnaX@i-esGE3Hy`Ii1;R$lW(GQ z%12i|r2_Ft!Jl+D=OH>diqPHsW(Psoch4UY`*Xw(k%s_((ErPa?6Hk|9>(eHVuagp zKKpSmuc7Nbx=P`61v>X@xjw$WEkV7f?Dp{Wz#)r)GN8hv1`S9}7Oa1qxx0_I4biN> zNy52gWRF>P_xLZnw)5_P|9A%pb z@jGT_hg`?J6PLJ;pc3C_Zg72-A4(kj!2jFE>^xixu2YE54=2EkgIZuM^UeN?9OR#8 z-bCmY`r`i=%KHEK!b~nra)j~{P+l!9DEONU_Qi;>RPa;=jWv&vd7R3`@7ZsXBXg4 z&cui|hW@Y}kzHKmWA5gW>Cvyw}opZ+*eLA`3O`VKA_K_<5D;VQnl^5v^nno7UkzcZumgqEh(@* zDnE`vSk>j!%C~>)EeUCSsLK9%-81mX$jIKzX(xbz1nzZ)YDeyN zIiQe!@*t(y!*PA=CM5Mekn;Z|WZRZ!->~dWb>^xcL z4?+7~U1Z$zo}LNiMBDEk z5;-+znOucv2g_6NQW;qK**3EC@|34K(@fN<68_-2s%A`wOfX`b&)AImd~TO0OSz3$ z0Hep?K0;ur5|yUWBKroh%-NPP5Sst9AIP6Byeowk2;HyljXID)YfB7u4#i16d<}}z|pJE5Eks7+%UiYazccP0SB26KP%cR&GU+vyK z;Zy9RWC_4sAL^eSe-B6U#5GafL?B(3Szl@mOl*vid)yNJ_ShNj=NLhzgVnry7RSH> zh=N7k*4XRxQZ>~)Dv&mkcO7~TcItYT%5wUIVch)Yg%zE9lqD(AG~o)@2f zRPCEKx;BFO;hkLmh{h$Ryfnk+?qYNvujMU%mek}}RZbKC&+4yQl2tgyU1>Nn1#)Y; z``+x;xDh=#;A(8#P=>(Q51DWb^v_5u)ZFT5fo1Cj7M7Hr73vNG=&eZCUG^I$JbV3I zra(BYYL2l7?@b3qc`K_NzHJy7Xzdu^cWTfhMpsD{q{R_ZUkoH*@1JClk4@C% zu9(X@b&6-9&1|(>Cg`dN=mn`y7+$l{BiU-N`S>AAKaLWy{%TG8QOrvrv@E7DlV0k> zC2+n?fNdOkg}G&Sj<4^7Q)|YS*oKJ&LllfLwlwPe{-#-p;is}( zt$q?tPM%`7T{>cI_4Mf{sj%zqp`5hK>{HcMcjJP@wv>{DFt}CB8MMYqkB$1tn#JXsY~K^WFn7W?*S9qW&RuUhhKt)v$F*wC zFa@07pu>i(WsCG5Z;J3(7;Wv*fLz5QV0})(lv_=F zz6yxTCJQ9x2o7@4o#kxH;Rb|;Jg*hV0`6~FU>jCUq14{clGqu6RZLbrt>v`nXL}JE z)}8}dbo11PS&0fglytJMn25f-{4Qu=!p=9qm%O(0wO*}%VXJ;rq5aV0^2@Q?XZ5-| zYxO>I_vf|hLuV3G6%m%i-gpY_&jdgca>+%N;1?TxZ1+a=Jw2qDRw`qMU_EI5pZ zLl7crI9uL|FSk3ehVjUSM3%k~5OZk6t{vdM^7S(C45u_SKWZMnJDsn-{j#l;sn}y# zeQUOI>_joc+6iiF*6kIt>|UDbZy(^#*|9p-FH=_31?*bn&$B)qR2Ps5sYMN5_D{6b zj_0MY_iT>jAD){XQ%@0iZbvYk?^wj^@3Or7-r{+v^O~Wo*mkz`!8LEs!Ack96m_YQ zme$h^t81AaTYnBvkHyhT2@YPe&6yIo>T-7WP@kpU{rgXO#iV9g&r*)53nUyFo-1~E z-o~>w7&obxV7VH9`f>ie4$CmVKa%MRiHvN-svJCC({x+7ck-huD?CG?{|t|qSV(Pq zW7nUH^WUi7HDJe1Yz8#)@Q$gMBnw%zzX?TNwnQN$(qJ_>G7|Zkzi0TwB)|V~qUC~m zn5C6oBR0LE*-y|vRc?50#L}wrC3SoLhO$4?mgTCEHLKQGih(Fllfsof8=64}T@ikT zebYZNj`X@G? zUbB2^I}=(3)7Q((a9D9n$uTm^18-OaVj4OlH!U)b>1O&T3oJDsS3 zxIu)4!kX1PJ$P9?MX+?NF}-i#q4Q>&fk)0*;W0O#&a-`npez>cW6rY~ExGN}NxNbt z^se;7xVH9}_hiwqt)>n`vA>8`RR^$kT2B_M+x{$QWGK}Zce(#ko%Ne%^T{>Mj%Ob9 znAfV2qA%)9={r0-ZjV)n2rr(j_%nTn7zK(cb$8hHYQ!XcCSgbFeu%G#E-6^6{gbTS zvspHbBr;My&G)cW+9e66zv?d^WqzSVD{6gVg!)r3mukx?=dMwbfVGMk+sjT~YZn4z z1WnNgXZN8l3VJ1wWUo~uQ-0mNK*gavw@>=x$MrVP4)3~en9e*8`u5j-gyahWC%3c> zy@}8Q7ZdYS4=Sva$W8C%_^^KalA?Cob^NH-oMx;Zl2jw6Z19mRK5DOg={@}?+3JaC7VL*ToAq}Z$k;dVBDolqc~6FFi}T+L z?>|sk*wYhr&7BQAE3-ecZx<7xkIA24PS(o3dW>0p#I)b*!QQxRWBIhK%WJ}k+{&t% z_Q}ke86WO_a$01~HHu!lP|!AAaaW0)dC{sx&UaAC}$7mH;5RwD`9d3CSe zLgwg>a}ppNcR3}5QAzMs4HrMb%iq5{1NlbZx3tE)Jy-D5&NIPR8MhkU{^h%;kcOrI zR@uG#R**ERfkve0d##pV{^h$n5CuM6P3QXU5$RrKz*pyc7EWCLm+$`XtL^ro|KH4- z!D4rG^m;Tvhb_R}AMP)I^jr@juo7#5L9dHb8=(GE_3!{0#*pJ(uJzhzY=C%IsY@vo zo6o?J8x$F*68t#H#8C}2`@-HQwG z1uBDDhOp~wo#zr?|4C{8tgyEslpMFx*SoXC$#_^hLhAjYJLZvl^eP!rPT@;do?(J=a8~9-O)b@v5dVdqqvls%@I`|wP3VRW( z>(T^I%x5Q)_6)z^%U|}{^0KjMeXCzD2QSP-HP?r1O{qw|hsz`i3{Frx2y5UxCwR3i zlO9DtBpaXA1Ind2tbcQDwpqXA+S248D_9YGpe1_^z7DU|2%Xc~0@oXalu0G)ZaVUr z_1L*$2U_?`ghEocS_$zZdh;_+I80pBmsLzwXVGHOKn4_Cu$pGEhL*{)RV=SfKc-{V4mY z{*LctEl;{%;p1+ z7zU=R5366daue{Ax4~C?(>{%k8MF1G6xg(iK@>?Ky6D3qIf2DO3x73J(Z_!xk($xH zvmyQYYqqJjf4y@SbDQWm)s z#`zQ9>cJ-pFN7;t)gJ3&*ZvY=>70aB&Au1kfZfDQViPS_!70n%;*WOG3OH#d7h1P| zrzF5it9W?i)|mR12;cDBA~;TTYwA<{VyWK`<*7!m2mWe>q9L^uVqvxLQ_jTBny>Vz zI{jXyX^TWfcoQY(;W^f^6HZx$ecWS3>)E{9_5@C$ZNn&zB;|+(Z0$S$@xDz$aD_uv z>QufT$tew345ka^@65ualwtvEs4ZWe+C$*j^cQ}AHqD~z9Z4$V8aDw*8w=Iw_K;FQ zU4f&fIrUk`L43FYyhY%tl!fPoK|HgjULx(3@?2opp|SaCDDsTg8?g+bpS40F)dDMI z!%_|68@rl^NA?)yqYu}9aj&`c zOHL8|~3} zFybF~(js5E%xAgcF5-&fqxwi9*HCU-7GJ2xl;$epwp!km7CrEMJmz1UHt?EO)t0C9 zZgH&$0qTlE+EMDiI8Bq>ql2ro4LvPS&6qZV_ots0C3)0?3B*dk<+&D8wO{dT1uQi+ z-G<=&z6$e_OaAK@s7|{+Tf2}TkmY>1(z64(yKu}Q2CEwb)J3&8pO}4-fOWI1_6n?? zY@-)z7uF!QB+qyt=#?TXnAwax?Fe^&l6)=8^X^diw*|Qn%kqKn1p=AP#y?RCX<6){ z+M|i7-~*3R-i z>e|L-Q^`kx*u9Z<_gT3eI!=I1GHzQcq7{Xq0f$deuB>GVF6zOD`vk7d0e3M2M17-Y zGj1417Npud6-%|N=Wokho^ZU=jm1D!0Ju@*I~pNwDp3>qJqJlUdH7Q=Iu&a_vnm%wmwUqGT8uB5#U+0 zuWIFm6F6P{=t(PSu@x7&eK}b$S6`2s=wnSz-S#ZZ|Hnw*eYGRBM4J@PfyXkB_HnTi z--wL+YERDf>J7{T69$7ZqH-CB(=t@dWEIDD$3RLHW1m7LB6V z7WEvh$D-nUuJiKd7PY0ijAS^Y5+$>DXYJxR_c)1pl?Vy4$tu$t3BV%f&ckjxLIo_M#5^kuYUBn?DBz| zc}KNShnYVP>tbbj<-1F_T`i^JrikCc+1Wd$uZNO3zbhr(gTG*VkL>KI(k68fu|Fe1 z(){d2lE$@uyx9JgKYw#yrL*zYm;Ikj-TCzJmO|m;%)J-d=f!(0ps~!S{X$~z>GN+E zq~m^bZ50pPWBbf%+5X10aQX7P-d4ZlgeZEylgb%UClAF^6-Y%_zY+A=9D5c$5PRrN z-A&$6ffIu0U8>|lbT`7&(xhXg!s0_om6U*NbV@z{XqF?R|6ykO+v9-M!)2narg~Lg z@-MwT!-to!I}`4T!Fzs;<#o1krvzkOqol(6Y?N}28AFfG^A*3Y?5QY+jg_YfZ>9=B zF@zNIpPG7H6}z*WP-vnHpFS4IWm0hBvbhkjz|d1tQtm;;nu6G*PxfZaEjxDOjuWFz z5qqB+`eak@5UKuM(ICCSy2q)q3DQftphVe{qR}&y;CQedtsVb3Jkf~z8&}@o%W)QP z=;9LUGpKjX)jrZ0LI}Ji!GK5d&P<&Q;hm-Ktr)y{1XubqTb?SGm6oQ1gbg|=>G8j% zR$ukv5s=XMg2#1v+e)nO&t`}>n$IdW0;O(tf%9%PPDs5YfKt!dv?Bs%-J>5KFt*vd z55+lN-?w`?Hz9?sX<&x`kL!yM@oLe@<}VW91N&n9Fc{qJhue^r#`BWKMM*#VefO5e7G=fA8H-fZ`_DVz7;*nGlR|VK;n_F(|JbRN!t3Q2 z#_dux!oA)Gyk2$x$L@Rp_5CerLKg!af&@`%=KhR==MZ}c;G(y+vOiwk=0H=WtU0J( z;1=sJ=AZRUCmo)bcOBt1hl-IUI#G#D7;>nm0s-T{+sugjxNE&bo*t&VD?G% zXG+hoJ6k=D8v2tLy1UY`4lrXyZbj{VhlJ<_tV2b{Vzu;U;B8V@%4_nxhv%zzLPVH*goJUQU+wGRN8$l7lU2KWJS z4iFH+^2>^fYAg6#fTo;T8x^PP_4ALDkV<;})CKV+(KO#P2lW!f)Bd^X8vZ?Ezq-R@ zKfN3eU|(E}e>-yMbn$W?OFRzvX~gQm_g=k1_S^KGJaXCmiz~C%){CPQ6lzZLkVa@8 zywRNc!wr?@$4w?zFHGiG*Hs@gP~fPhdeu<)1_y+OmlTpEf?Nu^J}CE$2u3Zd3Jv@m z^df%?id3HiFp|3(_^i7O;zMaD0^Z6nQM)R80@7u-pv~*0xQr6xQ{4U%Vuy4telAF3 z>^iy#BZO`bumS+$7W`SPBsGHW{O|l({ClJj9j;vnSkA_p(@EeIk)6vnY^|wT`{AIM zBLrK$KK}y75eo-lWi+b>`qks9Cs#N)I3i5az7vBJ$|u9_PGk7(L51EU8Xhlp44}|u ze1NBzgQNyx|5RmVjWqtDiGBhq9;`doimn%d^9~5Sm!P38lcgUpGKS!w+s6Q~kI01( zVqE-n=^Ya!l!2U(xpcpwya4?HIqj|eUuJN7;2Ny!q5coQS#dR+hM)daNUQeo9ryz<=ifJ|JQl1V^6lxIV8QB3m}E88*Z2 z^YA5Tx0vnQF{|ak5_7aS<=@8DRr@J7-p=+L09hv@Q>EW_s;es^ym9=y@P;{VSpdVr zu4S=%v=Q()EY)3Wj_wO=gIUqLb3Cfq^5|4HK*04 z_kO7z?_-{w4Xe_mI#X-s?c5Pt;B;=sG5~Z4j!?B%znzaa?t>jV>ay2-1viiph(Bp| z=l7k99Ko_12j36iiXRUZwLxE?AssZVIkGC>R=Gjk^-hU+yv5FzB|HrB-#OZsfw7ZWHS5gD7m?mm=SN4a$-4J5eXrOEwIj0x0w%wC zrs{6s8dT?);7=_KcmZBmp4WEj!R5p>Xw=3HcAG0~rVH%9P4A5(;9MNKf#Ae}GsLeg z@BW}@Nl$ZG3-n%!OtXbXNQPWOSX%&+fC8}kJ4Fyh&mBpOI6^(vaqCT8-A!>63S}jS z^w-0U0L?!mpZDe4zggvb6rZ81W5h+_aMN3z$+mcpOU4~_Hx+A*Z{Lnc(k#xA$i*Zm zprBVT+w6%#^p!h0I}iTwCV&z7b;aa4Jps4K`I0k-+&!8qQhx{kbn@`$486o5ELLf? zSyxxLN=9rkuESNoE5l5;zvLZbssm8IL;>PRp+tl0yX@9m+(P=@(MppR(u&A>Fsr0s zA1ym{o6E?TUZcP&Wv-{z3Upf4*Z`^R)vMw6lGgI3RJ@x1%kspdelKu`?n6}sglG3V z2#**aP{B2GSf<2m{Q{RLD9}7BagS$w+|nYeQtBKz((@(3a-<_UaYWne2h`LZALqM|BZ@d_}4}6tmiNjFpQq>UL zxw5P4Exg-ANGqs>s3?C|rh&J;yZU`PODG0KnPRM9wfp;`*IaDCgr14oahl!EA!aMi zu8wp~St5rUWK1%C$e6_4a!UiA`YSp9ptgF&7s*cmX^A+ja5fXDyhO`HGRNNA-r5Ab z3)OkX?&I0aWwJ*PaO@ZuLa@_w!voKD$GPvXxn0xIW&&5`8O(3;T_NoE*e(`+eaiKtOrpEshG>)io7&lC46A)B=mtVgS-Aa439! z3dcu+%#(B?-ffVuX>G;EMcN~5Nyhnrv;)EW5U7Z-a~y}N6*kqhduPiAiS#P{vQFS2 zrz3G<#-IO;@N;PW&@pj9p4LzUbKe%?7%05hXFyLOkV%VMZHqe#gM-tqfJ&z4FHUh> zpOox^#XiCmT(q~$N9m#A&M<5s!=O>8o}$ctM0^eyTF)F5;l!QbNu-3RG zHC(ul%5lKqgx{?E`#Ww3HWf%LSW<3JDkLNL&%FDQ-HCrV>4(d{#I_&&UBs1v#)r33-qRJf)Z79tc{@q4zOMk zy>j1-?hQ}u$g2WL4Ft}+m?ycFKtOXt}xDq z!vB83-T&ys`sYTt`50$cOGWh#F|Nof3`DIu9^T?z&hzU=ly%YV?TRUF%UUn`h=S+h2|9i>e(j#tdxLy;7 z-@M6x))B5_=0~r|f6xaZjcd1)hju&R2UbsM;Z|QCa~9z9PN&G2+UyeIh7bch|CNJq zHvacoaE|=%=;Qf+ei2+xLjls$uyT&c1)O8Q+(bW4B-^W3I)O7I|BTfiZt^&LgRStk zn*i*Y;RG{eFfujb$dS|k$MOX~AP+GpJ-@qXJK+U#K_o@%;{qw#-C2`=E_CuJuA4yt zx*7h@Gbl&zF`2(ixz*EgQlO@W7Q!a%q>)}dq#sqg*slCmZ1TfrXK#^PozbBI_Wzqy zSBRl=_8x`Nqko1R`4_1vBt5p5{?o1+@Y#m?p#p&P>;IF-l-9!&5fRBSXryT_onHph z4RCy{wiLP!eTH6xGiRjX;A-9;>qur=Ly1;AR`Cf?5sWa&z{UBH?n4^!YM>D>C)X!i znZrF=^+Rf13N$ja8zn(AA>n5>rcj3~p_vyvl6 zv;Wv}p?nlwk=!i=_`e3RM~;0icE~02vw3#Nf8kX?Kzxep-7o5UB2u3;Z7=vMLc?I1 zu#lEWT!AfQ%9cuA3aO@c)3uTNHStV!$gbLhGRIwA^57(T*SA?K1q{!2H&Gn9Aj zWeg^$mO+l}^K>}-P{U=i+WV~&n!KGEk2SXXD{uL-8x%7IDgIVXf9A{#Xmf!gsumdeR+ zGq)b-cV6(XhhXc5Juj`~#9c}wH=)G?BPHmL)c%o&Rlt9UMdyu0>eqs;5XweUJ)n2` zs~r(~gx8a6=8q{{juU&$jXuVb!K5CF4=&LOJQ!-r&|{r(GH8fjnNJXWltJF*Ja~;m z{UbXYV~5Bhi0w>A&S6{+)W*IV)2HVPjUpfur=I=A^8??$YmDKkit2?n#g^H3JP%Q~ zwWCh*OCKUeOj|sz8LkRxhFc&cyTS}-^M;Y52^<&qEo_h=R{rUGfZXW?jjg1EP(@3E z!gI#1vq*FP5H1>q1}Qa-crE>+zOeI_S|JJSXr`E7=`JLvmBpXRilJI0lV0csy& z-kBFDuScYW{z4G2jF9xHs2l_~wr>NxmtTUs1}Pz;x^BLAcXF7Hbay^-eg*m>v8>s1}ecIws-RMDs58Ra3VLSzaghQF2qKG27U?azgFmeBna z{>O+&V5kV^YUMNQ})eEpfrm+loK z9^lOHAIjFLJQQzHbpqH*=tzm&@D~3##-C+an_TC75L&}y@67T!QF87@&M%=k1VXD7 zB%`x00TKf72_juI@VRs*l6d4StT@vJJOVzKeB;lS`Mmx@4g(71bNA>WBQ~SwLe`Kz zQ}AT$K`KW^(7#^YAGkZyg@^CMsZ7N^bdfsgIv%xCY70N9SFS=CfLW5RK4EY25Cy#>lM`D&x9Fqe z4UGy%R@S!GY2$=OZAPIft7x%f+^tqIW+o}K!SCa9|0)n`I0ZZil4m|c&2o7k{XmI1fS+l0n;F83Z(k=_Np5MbZR(662pd&yuSFp+ z@0P{SKqnRpsDGpe)-a4JLic&r00K3MLiCqs5Fxi;gUI^_&Oo3l6Q_G9=WGQ%nJXzX zZNvD@w|OL zkWk^4XD3hx`jrDhglI2E+@lhrLmlTRSI4zzZ5dUYCoL2#FIA^Q)@|r6gAr0h*rDyg z#vVY|I4BE>r0F`5$y^01^IqxwlJ=;%Fumm~&cpEXQGk4!9TfCMdisFajec`Ij1EwI zKJjvbXtq*yI(&TmmV8SCq(P68-#!Y+e>+bY<##?ObO&yuf>FWeqW#jWHVeit$*%Dyh0czdpY7p(!YfoqgQirC|BK6e%)c#rLMBkuXL%Gl~tubnq z4BdD{igr=T+H1@*!5fgp-J^Uxd5YF)aHs@X0KMTA>-?>?amES0;<`~M?4^EdL^0N4 ztz=paBPj3^Y=JXybwyt6#PMaf$$5*5iawrRgZ^5y*2Y)S{*2&YxwiJ}-c<(}{TPcdQ|_m9@bq@nG&G7RsGuv%E7|i4b zq_+BtR=H$DP!FG59nKsAmTH@8AsmexZX5mEo97w-ARbQ47rUh3`3r5%C zR@-?Z3*71DhL`&s7{<_vNE1uqGgk9sAU@ga)R^+@ajk%@9kzt4ghJ zduA7MfZ<)?WL<<>w^f)FFJHk-`Fx0Tz=ru_5?bs-u|DaEA7u3TM8V`hPLTpcsF>_xQoQby@Q@ni$=)pmyQTHndfX(k>`%(N1U^+XgnPB>Xf85HGg zgTFj^OnVzKQ%K*=mh;c<>fgL;;6v;{>1G?^Q?$iBd9@rbsx|9W z$~V#A7)@C~ZyJiN_?m^KrGlVvXO&4V<}0vxpJ!y04Keh$eyOO{fo?Bl+YOBA@;r$n9cg2w4F$NC$om>ZB84wetKU25w@`gG$@>S3bQ4X1fL+imLKQ#39b9 zKp&I*`q=_B#azAiiPiz#>a-?-}l1^N@gK-ar8*&_z21 zI(g5=779x_TpC09$7%w|f@iDWFx&v+){ET~t;?|+EAk*#6lF}`6 z$DbmcFoGN&=v7|BiL6yQ3IYsj}=;kt9ZOF^AO|K=MmlLS6h4fi0j}!6j%0sYa~1W6{_JxIUq9rOQB9k z9d`fyi>s_r6;#xsd@Em^#+H&Z7_wJc|GGH8T)G`uwDJ|j_~f!kF&e|A|Lj)UC8M@; zi+`{j~HHKwz{aUHu!Uc<=(F~9|wNSF8 zp0Y_awsVgj^U*6rEZibeDPP}RDW_v~(vou~?KMZ_b$|_HXs*`f+d~!{Pj}vZo&K=^>*BH&>rTuWNGthx z(r$SKJ52GEjE@D7ZMirs?_m&A*}lJFTdd_%sBRy5;2PdvZV!*p2_1R%BpmPAvu7>W zSLOD_6Upu?lRo|K3EeHBqpvja9tsh%oUtN|dR2?{VaoRzx7{8Nzy^ebu;k`qO>LGr zQkC8Y80Z7pI=}72w;*6#nKL9;4KX$Gj;)l9yXdtLX==X_=Pj^SMa8G)sU96ozsKa_ zWGDZ#Q!ntz&846C>DBhh4D)u_6Zf@5r^Y#K1=!!5J!jmAt?i#!p0qldaMK8o;<9$} z2*==!K!{y*Rgu*Y+EZ}1nMpB7zoECn(j@XKOvoR21NcQpnA*WK^d{_{pbFSn%M zfx8Q{Z+@u&QYSU_+*)S-g%k^4Fv-cuxA*3hln5;5VbsUduTtAX%P;$Qv&iz`qbYk& zqY!PwxFH@kh;vE9gq$>r70N~~_EF}tl`u(6G((f^;!S%K4GoOAAOQm|4Su7@6(Bu5 zrt6+mjl&5~DDpO!dTQBR>=MSr*-@}uCY-FLd?~clqdwuQYA=eWb3atR3~E}~hh)wt z?$o}29(?tgX%J0o<>uYD@d~A(nx&IB1$kpV&S9QMhljtvm-FrHMl;iu$*f4enfJw4 z2RE{Eb8CUuUn8+oE-tt75iuIb+xe}7r(fs8c&I&kz_2e^E%g#Nw^~GPceYj)(23I9 z_TCRFZq)JSKMC3zG40P8+E#>k@8*AT?@!_V!MmJ}E zjQ#-VS1Nh8Y?dc|T0Ey7M}vy_`iNM|H*9-D*LV^1`V?!Oi4D?hDydzR4mku?;C(wQ z?{(BS;G&teStD%rsWg1Wd+>%Mid%kI&xJ+(wg?!W>V7xT||ek;3sp{zyw9K*~B zKuLdyVgxr@n-p1?pf^>YuzX`Bl=Dp3w?BeQl5g4@8I8IMLEQ)nbt^rk6p zjP%~>oRZOHBnImJN$`JM?Q45JcBJsG@Jyr$PxSFxTU%T7*-=!_H?n2X*tU9q9^I2v zpLKumEGOv_v+*K#DUTTFIQG1Acs(jkAUi+YA8vmQqnA9s4K4Z_FnFB_G&(M=&y8M9 zUjZT>OtRi)7&%j)ALy$*AkQIJl`LSUYDiaL@c8}=LP@3`PS_ovT z!TFpO)7x0NuXudHrA}>mA%m;gY}VMwC?{3Hf4X$rIl;eo!?n+{pqGc*VS*%iJ+AiJ z!E;l+w&(m0pdZVISt3xaSxCY?;(|KCBxqk8xC3kCF`2)^CMJOzOeULr-nXf<<;V6wM8%H6yOt^`sQ1D z^nCuDr-BIsT`Rm)l&3mGhSBy8y{1dYhd}w}%xSTgmtvn7Ie(%oRpig#VgP>Rj|l{A z%cp2so0{T2<@2^s?b9F-BN2FzT3mW)ZmM8hvo<~^<`ZitF)tn8V%wXcyulI%J3G5Q zJ-tj2rI@vkPh3k}iI1frru>}mP5I2)Jvu^qI2bw0H}F8(IGYi4{Zdz$P!&{w59soIESC;Mt`M60N0QFzNpMIgTc*Cg+oSOT<&ln{nm_R{K%@7PQDr(P6ze%WWY8q<4VY1dmd8>}-=pX?V9nZMLw~VFK6iu_$acn{Yqc!a(#>|mV87rNoH1ax98fO5H@@9Ku zcuSXyGo=6+dA|-KG^5&8`yMc<>)P)ej<8em)vkFUJ0)%(*d7s%2e$GQZH(|7ksRJe)NNf zJ>GE7u%^qV0_DX!E8(FobPT7Jsh%8LM>5HM0JDAO$VP}hwf?{qC=Z8C4;(~N@b8Yy zNTeNA&}nsKaF1>)EwjHc^X=QQX0v7vvzJ$9KZI5D30SSHoT940x?NWK6&RxFHwP0!T2uHHbN>B9;5FuBKF{{f!j^Gy6@Zw_Oj?~oG8yk+4oC%D{seaE9KEIY*AnqRB0YY!t zN(CCK*HS#S&!6=2Pt=s2Ho148O~Mm9VmLPk|hEn0T7Ldecec>Nf3-3e=DfvX`;|t?|s!_`vTAwrTJ3*q)-rOSj7} z17*SUvCB5s{%~uf7#SP$VJo>%x;-ej?Vg8+OkV{=#i#nse_yHiW8=pm9jgfKh5BB8 zaqXWE!v#L4-Hu$t^uKdnM(KVar13y)Ua2961s?;YAdwQlChp#t#0yJH64s0M#xC|O zBK4nM5=LbS85sDtt6gMbLVbEkeY&fw>%0^HC164(t-1~qS7jTbeLll`IEPhr;aPjk zFKc}p59%#w*n8vk(rX9rudxLI$?vOByb>|(&gafYQ<9S8q0oyCrKnLrR;(ebw3d>Y z#=ywM$d(NWokBjSboD--h=VnK@(MQt4#oCFOK4jpGbcxJQzTOjcy)Cb?v{jqNR>E9 z_&`p(;<4%?p?VN!On01&!oKRhqFY(WmVBa0$HvAz=EPwr21x9}9)QT(RNQ4n7V5R| z+*~muBO?((s@Mwpt#V4dr>FMGD!-?o8^7<@eTIQS4ZkYVZXfHTX@*+}R^AIx9j^(4 zp~Pta!1)FARp&n0kC-Bv16H=nRZv)uj*YDxs}FZzozz7=n+FmNh9iq@sJ zx;oumhu;84XL`eFPf6mV63-paq~Pi%ac(FhBNJbbC~jTRk%qWVrDqw+g;?3xs1o@4 z`Kg$BweX*K3o9UdpVf`trlz|uB4X4urC+i6`S~r#OC0y8L5&7TY_WFiovv@bSuQ)BO$^LpuTH5=u*;hfe^BXc3GKld! zfIi&1^{UTSYhT}O{<)66KGP7b(#2aOEkH`ncXx8&I#`N7@T$qbB%8l>`fW8IITh}} zX42OiwdGzQvY@3JfP3bJ<1_%ek&lyapu@ylkKbj1;crSs0x}>RL;|v(&YXwDBQ&JR z$t>?GZ@2KDvTC~i+1Fg3@j9LU9_mx0g7y<1WcLiP|zilt(%#PhfMF0;`OY=}zPk2-A?$2S zpR^zmOt?Isx@g+aK#*|jPnSe4-l7Oba3L93b-^wB$;$k6wO60ObB!>A#?HGGjcC)j z`0Q>)8I^%9uJ3-^Pfyt*f-{8Cb%-HUm%Wzip?y9gxH{Drz`)5Htwh2 zfMtW>F=O00-2jNw%M!5@JOA^>hRB()`VE|hrP*FZFqN1iRJ)jzaWC;2c?sl@{BK;Q zL*%(;diN#EZ#1FC$8l%;&m%{N**7*mmW-m}*O&j<;)^ROEq@8|;=rATKfQ&kEBz~o z>#q}n`64~1$$APoJ0nc~c?I0RMiJ1ezfKtImF?BKy&e6crlkduoWT0Z~v zeCJaVs0@fNKYJLq4~a4=$6Sx$Ber}>h8Docz?Omvu$`~=3}*R3S< z{zz4At;nf|ut5orvQZpnwp*QQ;@b5dR&a6>IT=7PzVJZ~kXC2rK7PDwT5(%^1li)< zA2Xwm8yO|%D99^5$a!(;2O#;!t=+5+D?{1U`h2GTq#vulZ__xi$)7WrV;W30ujc;t zqXF*kE&SJ>K9CN$*0^)`g=R5Y|GqxW0B*CFLqkIkazcK;fjsWyhHRE3@C0FJDoAcE z){0B4A3*z*2Np?$Ij`{;8XLdq%3OZ)?%lhgkSBpSZ|cYMAxEpZ8wBT2O7c%Gb!iN7 z(0*+Eu*4A-8*3;_yI>QjvS)N^azJ@^Ak>6m_p%hCYX@gwT*N#55@?xrL4{Oy)5534 z**!Wc?QCj`ZJ)|XfR3Q!U&7CGle8JP4ElXHEVBgO3gZ3L`HM%wP=Y}&!~`nDB*IN zZSlhP%P8QyK*z-BQ*!8g-3%)+-td#ffp30(eoEGv4mC5>t@`~C)Z~p-rN<6^Yk7UT z#T!_J&ro}t85jR6NU}KM~d|zUTN530-##;h4nD=!9Qwi3j?`7>gheTR|`&!<{UTH)v(U?^(T3A zyP~GONS%n-&Lh}=`n1Vz+xpVg`2gCvC&+m&Hr_Dv5_j&CsQ}%_zL4TU-)q0^VW`^+t`&sy^Kca_{DYV?tI&=C*(7+L&=mQhF6ElPuC?B zy%YUt)?3u2$xZB#SXTG=Ag#V)ve^6rTcSg^!E|wyI=;HT>OxCxOOj}ptP1K>gQ@^4C@=L1X@mkv_AjUXM0y z^L(3GP-|l;E-qdwPb()V`*hjD)82xc}ON**5-e`KAL+P(i0qe7RUY67ZuC% z6P)y0Xu3c0W50c??#(CY{umMxGB=Ug*fXwwQjksSP@tu&O7@h0iP=CF>)vG2-H1vtHTwn^WzYY|kMiqFftvDDvR8s<#=0W~(<#_xyrttnZb)S(!^ zx}fE3mtJgotfoeyoxsmUaC1-2gIOm1OnzFfdv8HfGjz#bSU9q1ZS05xhWSsWQUm)L zA&&M=?kXSp)BpvC6qnM)>HL7Pv9UI_Gg{n*yoT4$>9c3* zy6rhxpWwJwEwQ@tMry)3GcDO3JE@e@-;h*ON$YefEJfWA7G0{N-jyz!9+SER;4-Y7 zgS2rMJdcBdzw?Wf0D%YF!mEz$jPTl}Ywgs!iODga$Gflhirx})4i=(hF7XTJl)#tlM!TEqrKJJ%6es*d62;?^SM8ZgIGdh zC(QhS@t77}<;(f$<&GdD^M-@R>+~H2HjN8w-lpqJEJEcT1&oK;o%KKoqb0}I{FjMh z`*c%KN8X<{W&Xs4(_;~GakeQCVAY>@9?Y0+}hqjnqt~sGyR;S(e6w+8!@m05MtHzz(^!AkEo0WfWQ~WNM z=IcsbXFv6VE#b0n^b+4tlbuaE4bTcR+?SeaoNH)cU8LL$cHpj-3&UF8Sz$JlEB@mc ztK-SIl52}9&hOpUd$0pbmM-YrtgM8_kuQyNK40&2nQc2wu9Z(xEU2UiL~0$HrKE;` zIX50l?B`xSy7S>d+=rSp4Bg@01Q zhaHV`=3&k0F6dN+Ff0rU+1Z=YVOTknlo7#NrTE4v&+-YviDw;g(y9}XeW^Yclu@jjx;UNc3>Lzj1O3ND;42Rze!;*?0mb_ zYbr5JkAMrZiPCSMp%re z!8&#$s6*q4HKxY4`!`RKYKacVX`gSr*r)JW+wkQYd*Cn$I#gN<;TsM;6w0lXLA{;X zs>QzOe{0#Qs%)(GZq8Q0XwbU@=yGRD1udgRe>Dz~b-hdX(uRn_d9Pe)^Y3^RWA8v? zoffsf<%%@Elm0US|W;wp-X&?kPxK!5i~Y=IXXM{9Ci1Vis%EOmv;MYkTXh$uP7bH^UZ&a3y`3k z4#(oQahd9u2z|$^LGrp@#f$3yLh(zA$XGi%pZFK{VqJv2_*0)fD^fJx6|Ovyf)HIm z@Fgx8_3L-+Nc65_#!vAtq{kl^AeaoJ75f)rV@ibdI6XI%7{|r%>m|Pe_Ul=rcjWHy z03tgth=^)Ft2I9QmrKqu*KY*<_{+t2mWZtDH@!QY8_j7=XuhQSZYEP^wowQDu9`%6u4HuH8NQyU8TICS-7Baq-3GlCs5;gazw3BW7GNAVFDb zPpnnSxL)Q|*t$DC#yCDRQx5Q-XCbY>$2x6?=HTa8m5}bs!95`Z_aL3?9_=`OG0)t) zoTuXSa7GEya)Clj`6(s^_K4T4R%}7m+v-udxy!X;1H92mNjD*$UyS%Y5ejCAh72id zKvjTAxkZP0^w714Ps$dL#RkwJ{`xP{qlj_owkpOiWrJQN65eL4Z1Yo!t^MVP4ySdInb?@R2N9xO7iBKT{L^yS>}9kMC5| z|7!2Mqv71zw+}&rAc-UrT@cZQ=!EDkh~7!G=!p{D7$RM7nIK9Ko#=*9gXlFTI#EZD z7~RZo&w0;zkCXWR{nod>^~)bw%i8Sc+0VZB-uJ%m>$>i5mBS>jHa9o(CRAiW{oo1J z>jq0GR5|_g0n=;{Z2x5+1V7d=_l}R}(+4}~S#r+hq`vWMh*}Gs^}E=;r4Amw4<5}) zqZ!WXpRyh)GCB69WN>`kn(R==OudkmjR%(${rA@mtk)#HB`9dvbQN6RUPAm>&5NuW znRiSohGVMYlaQ1Ejn8>=h6^>D3lbG7s#iaczU=s0f9P0Wd@=Z$6;@}Mk~83Y`^@m1fO zXX!lY8xiSCFWgEHK4{VFZ-^p}1uoLqohaXN6XmgnDCcxulcjRFE=pljDRJ8Rgmdp~ zXrMQNx~-s(AM0e`xS@HszY{<7US*YC)v)tWmqaFbdY&&gN>dq&LiHhHeewg9JwNr6 zWh_K7*__nf`#@B7x;Xw`t4@2l4;L!%%T6njp7KgaLV{ibZj6){xKPgk*6O*MkEsTH z;eyL6fx#1o1)U{XB$tAJjC$|h%J6g7SWmBf+|o`H)VDa70Z`uLu!A!10d}{=qP?pNjJG-2k-l68gYFbVFg6IMl%u=R|XWl|tKNHY! zgiwsi`G(82H{<;}e0*IBc}+G$G%&7id9Z4lD+OZ+hUMQ4QW_N1Bz0A*dM}RM#b>2s zA&g5fQ{_-E_TjN(n0$Pj-PYeG!R577pldw%;`BlW{o-#e*bc|^LnoXuZy&X78EnQL zEP=;BJ2IK(X>F23f8Uz|12yWOO1Cs2DcL)W1UoXRqKM!5wGuqLyZ;9w*=5a`&!c*w z7J)B&UE5~<9-*p_E#;xe!dmOJ->4r`G%Crek!9SW%{$L7kX>N8+fB5?;3RKMxt>6> z+u=zZ%vutOh~2W1xh|mpDPrb_FeS@g(=}VVd5KJ}F*rN&XJrtpDOA8@^SK;`UJc%P z*f$*oQ=76HI%<6vwK)HYp2~D}<=0>rSi~&q^>ib0#w7H3qgnzMMHWqE9G*DOHTE)LZq?cAnq2Ksbg8&Dj)`FPY}usX>obxJ+R% z{%Cp(yGa#Z0Htk{n1J_~tpvI~{rV?d&8Q9}EB7 z{1vRvQ~;c(| zlE=AQU#H#d@&C9{C?0(cTRiMcJ!>XBXQMu4#q#zyPgxpH@{}YZ5xGR}gO5B-&w+v` zd5FZ1k0!1@;(2~HZi{@asI|&Du4}BSjuO#WF{C_m)4&i(9t8bNXNLxzCAH^5|2<7PHkIB zIbMvN?LHd!*xM;CCnzZXMg9SId|4$1N*z8n^GRm}g$Cr$BpssR%l!+)y7LlsZlVv0 z(mhvr&$>EO)U8NZKT1mNrt~6kIAM76;M@C-S)U?{Ewy<>hv{XfUd21D3fHm?oHYxt z;jUC(I||-O0Z5PE)`K)@WiX>TY_bJ0UbSXIZmu5`%<7+}IUQc@IJJyPwKs7pDfG7g%_o?AS}R1e2GL}`?7uVBec9d*XVEW{0CI+@}uW+ z>ZChDJManN5q?`lWZdbCT&Pz>7cPX12naZYHJ{Z4@@>_Gd3FagXgP)8kSJZx9-H`X&BKiX# z#&M3Fi~su3>cHd^ZH-&F4b?5E;Y>uD=yN-YlQxK0g70L$X`;8^`4id40mZz;TO%86 zBS&h86OL*;0(`yIVA?H|w9RgLqEaVyO-JyZIOMyikWqCyf$IG1 zQ#pm86ZR555&YouH(;E~_C;3eX$w!))X7e85TT!m&)U45bwsBSs&+@evu?c3+;CDz zK;0rsCS5b1rr&~HC-?z7bIL3EP|M#ez=Dsk=HeOFCHTNK1C@(J=-*}wtSmX+h%z7^ zU%TjOtMR+t153c&wGUZN`qR%W5no68mBKULE{gm|Jn3I6=L?QE-dT$^k^KEQK34+b z9i8s9|IwfOmw#$i4w`HxAq6`y)CJbJaA4d=_S(noPkLupPy8Mj8 z2MExe#(`0OF_N$frUz)a;z~5Sl+TK=2t*ym^Y(Hw560LajoER^h z#LD`mRW{bj)>hToxg6X~mF@%-C>qmz%PXIIopF>O(yv{j;A1STYVPnl%e=Ne$w0PV z6<{u-bWJ#hS?8hzY@#lzCv(e-uS=$Lvlvio1*jkMB?5Ux?49lOp<2%`vD^=m76avY zsgw-uA{TfvJXK3>NqT>D=L*nD&mC}E1+0~`28?x@B-`&e=T2jlieGmS`FYix1_sah z<;OAW{LIulG{oM;?ewHDy|Gb-orkB`#~(l!USK}>2JEexlC;+&%{SQrjKqSV1 zDciXaGyHWL+`KUn!gYn!sz&BsO63y%gn7pwX zIemum+c*%d_%3}-W>}y1b1jLYQ$W{ z+;r6y9!k;SaU0x)uhtGpRJC{aNvj|xZcm2Ru|LGbfR#L(|e0W8)(!&Hp`bS_FkwfUHF?Gom-LWKkk2`R6? zDQRTI>cyS=au(X%t&XEnFXglF7Z*#|P20P;eQ>OqRlJr&2gB>oy>?C+7_NfZAw?TI z!+E>Hw-um@%D;DMIo{sE8u^hCF)(B`z-md8ijX|7Bmljeu#{KPe2MCklA+-}t%8Cd z?;X-D+XR%e6I9NF3Kl^yH>=&99(9!g9N0!Byd?%E^0)@r|E_THQ|V{K zTU%RS04iAyYxX9GI&R}roX@C{^^EuACDdvgpCJzw^bEdem6k{IQIx69h5H}B34wQ= zZ@G7@XF2ZPzCPB_1Y-c3&_NIu@Gs2(^sW1)UYC~+c6PDTO)BXrjGux@D!7eV?n3Ig zOi`6Q1P?CXz@kF5N|*EAhEsSlHfV`lZ9jGrDMbW&pLL%7qJm!3unV%!peE4LPpPUh z6mrVZcTwz&-z@(A)a*=n18Yold%qH+VR+Mdu8ckk;`o1JcvE+w@O>Mwfqj}F(ASI@ z6Ta`Q9jM22?ndv#1k*ASnRREyE&YL<;Z%zVQt0T14$82azuQQJ7P>|G1y&jQ<`5n9 z)ci_=R-<6)iVvt?o6S_qgI<=yL!;9dl#70R zUsraq^uLyy&O^{I!|3Z3qGRgg(pVT`!9Ja?bIjL}PN{!3qL6FR9-~amkR`IA2KuAM z_nBJL%^fnRqwx7-T~3{*TSP7Wqj;pQ78O2H%Y34OQy1KI#s zuGD^R-Ny$#2L9?XrPdv4kdVokzB?BsffMWAGnPB<;he$Ac?qQw;9iI5e_!Xfuk+=z zMRq}fI-ro?c`xTz%mG$dG{6F7=TQGi;-Yo^(!g)q4F$RY7v9LfbQ~_05BQlt$|@yg zj@fPHWaZ@Kz&38D;raJs4INyFm-=C3& zDp7cfqvW-!^_YBxs-F6L567+`w{(ZEt)~juC@Fvo`nkGW@E;d6y+-;vp(=p#xXy%< z4kjlsPgK05WWy;C*c0A8@7Y-&iUY?3^^8o9Ifj5roJ7HZ7Sy6Gp~9m*C?rZ(bJoSg zywES;J6GfcJ;ab!)e>?*!Jb$etSjqj8k#4$V0@~8+jENm=G7d${dl?XK+4Yi(`PeV zCY(Z#7ft!GOTv^PRE;eODeYn=NiE+DaW{uYeLrS~@y682mOOYXC!Y z6A)9C9F{Kw7C{`~!O7cnej6{@alSzfG}e;$pP6^PE(|(*8*cG&+-eLaOi1ilvrN%n zyis>&?$br!c%;QuT7rT)w#qm%$>09{*>}>J1gb!&DCuyo2lB}TNE5^Z)g?A%b#?WK zpG#jIW&w$u37{gg%U0{KM-^&GaLe8Rm=RcLlSym_uf|=rhJ{sr=m!T+bfc5o;G}g7 zzRh9G#BsD&WQ&xFZV@adUijyF0`(%~XEN|vM^MbZU=AzJ0yFNWqmhv;XNB8~8L+#y zICh=rQWo>vQf}dkO2~C57K2yo&VXC96wj*dY?|J}P4Z*W+B`WAGY9P-EoU8SU!6G< z=lF`r5G>;hPt^Y-!vE*!r2X(==eUwQhN^~`od-T@#QUxz1gtbzE55(x<%z8yc_OUh$!yWM}fS{22f%^UB&{nl)C^SEuLeXdV2r%Rxfj&;&-6&q9wZh zZJmhLb0NvItqj>+(PByRZDXz-4?sQ=Tx!iWH~n0m@gIl;!{W_+)N9fC>qaga0;63f z;dmZ1p-4r)v+Gf+Jkb!$PiHr!dcV%MH#aNl#;=L56!`;2;vS%r>Uw#oecOiYR8K~} z=DjU-W6ZTYxuH7LX3H%oV=+I`H|$_MNsI3Xr`|ntjfy<6#|$vPffC>j*vEPZJI0jR zGOS6kJQTH^eF)pAu3ohq6Telrojx0y)W1G8eyKHysfk>q#lNy_0@-(+1{3h|jc7AA zQ;cYxT=`OQ1o---A;(sW7oc7Svkv@JqG&nVUsh$2lHyx@IkzTpv{hpNCez&Lxq5=o zU2?I-KZXH@MDNAcW=Ej`GLkT3*%Y`t-N+C4ccQDTolkeQZ zBi`SoS@Bp1DzMx{YpCj(ZLCMRNzoa>T32Ndmz+O;`q;4{I_XA#p>zy6kg>FZg#d!! ziyMwWUD|+J=%3Aoamp0xP7w!=gn+7z#z`ZlHtq_7x_kC1o|zdL@{%^?$zKO-u}3?U zrnb(`Q=Ar%z4Ey!-~uvnyC4|5 zUNgkj+@l<{^pJ`id)2r71TjS(=c*7MtsIXXC9mANP3ItW*?*#7sVCj4;*`}lE_C(x zGY`@k;GjsH7~A&owW*C;o8w+H_&|V+Ms}P26dw3zO>4Rn1UV$Lby(}M_ zp7+cS$beKIti(k=vq*urRkKmA=PGApb^xf3s)o7#$2>7_^AhFjraMn(V?v9D zd`FF}&~~SXM^^~ZB1?LZfHsEREp+=k(pqLonw(YBL4~W3y*d_1Crfo+bwF_55&Hc0 z{+DQ;*kZ}~k>&jL+G}M$drFaLndwp(|2DRO^oGD418F8sCJ+OXDb_sZ&jnTx_DdS@ z&zwtoGLpRs!7ljvIq|K1`P7QhobZ|ds$}cV*eToNW`L&M2H1~tK{eh(Yx{`)Q_#$3aM@L7#SiojJM4r0ckUFXt{z1G{?p{KkYpFx;=t=FzDjyFOmmCqOI+tOP#Buw=u4|Fn_gJGb zyIH1#aKBH3W{^09+2b*oea(jttLP<8$aOk^RWDo)=^s{9`AXp@xv}Ll-UNxvRCGqZ z#?zjNS+#DI_xxtPX3-{^8BMQh_my|yMV8{i*ud`OB=_D-`KQ;wd|w3s^WA5CaQfJ# zvxY5Vt)U;V$Un9a@4r5rWozvqq&mEACoC=Xpgh1o6chz8LQJoc?RA*@xn#lD;12l} zpJ9~Z!LXePA$L?cKGE(L$_8+G``LC z@qk?lncuoA6N;G4lihirnqKdms^Lh-|?|Ou!0R9 zGP(0DlNXdD7*G+Td6 zxXtg<-vk;tc((!pR}Rl2+mvm5qjWZA*Jl*PEIhIEF@;1S;NX_8=31>YI%`ZBqd2$K z-TfoBlV&c6W3SlaaBgQbyh$`TBkvF&wz)vH+C-gv5PxfIhM~H1BIPnkc@oD=_d$ZG zT#=RbR<(4|An$qc;~T0ZZbJnkZOVIJ3%iS2E*e#Cz@Cl;%D=wc(M*NqVN|0AWC!$@ zVE`m#2C!-tKsZSjlzx&HoYBFH4IT?Ppu35#&nYa7m)!m~%Ab{$b>@nW#D&gSeY5K& zHhW>r2CrDYVu5FX3GJj`Qyft9hWsR}Fk>_}^X;B+>h!Kq=_{`lW?s5et)EnrjOaq} zt$BK?G<+^JXzsRPul9SY_I`bx$zW%Ld+9M8Qb|!U^s_}Hk~n_C)`i)in0?=r(O@b5 z1>Juq2o&~wFAYDfT2@A5U5N0LyaJ}>{&0Zrq&fA6DoUg|BsvcWxS;h)kG2!6>Cz@E z%V)x>4LU}htMe0|jIif`veRje!huEEJHT_$4$qNfdr2OLIb1NF8*y03#jCMxH}xb8 zr?l*5u%8$o=N4b#>l#Uy6#`-k(twW+eMPtilr9&~+vP9C>no!Qre#wewU!rRq=(G+ zDSh3(@)pGOqe@oMJsM+dv)`0_o-0Fq)LftG8XbrMDUG79PO5pXN6B#&<`JR7Apxc3 z4|j(7=dBV2|m|;o@svIF8F57lYUC!imB9*mVe2 z7uvZ!v}UuyOxG>}O0xr!$mQa9{@XBaK|w)cia4M=r8o7b!qR@#Z^C~3G!Fs)N_uDOZF>i(Bw~k-t(wVtoZJYs?TYLU%7EZRF zks;lK5WmM0p0tVwRGg3}kumGuZ%w8DkA|vtCJwVx*ZP%a+Rr7*m0XSvXtP@N1ixH2TsV$zD-oaat4p#)3WKMn4czWT7iR#(kT* z2L+?v*P(a@1R&)MOBbzkKlT8{TT_Hn*N#ZSLHS~9a{+vb+JTlCvHFZ=z9aQHz2o}! zH%pDI`gyh7itsh`%zJl6w+6zx*wgf0y(2M;Ami`T&R!P<2y6Aa(TF{38?B^bEb=1$XM z?piUc8(J~c1p!L2a#-NIV?w{3I<__U5L?L(YT=gmb=k1Tl00$ zb#p6OtSCoGx2m~x$45e2?%ykC;*Hk(x2i>2$h*5H_)4`iw06eBLV5^;<)P?P88B;RO9xZTt~eQuy~dS31ERu z$3=4*AIYN$_BSI--*grNps z)TjZf^z;1V_Yy5e?I70By8E*EiC*8?H^Eeo?#P4#j-{USX?2^XOZp}@AqqC*qDaYi zn0@5v$~C$*_v{}gdU*!-W9goJOIFl9;XD8Umm5W<2(RFzZjk+l zfiuAMXncDatN~k2$whH3G@@jcjwTtQLA{3PHlwKMoqQb2W^!ZB*PnT8I?b2SBhQ#U zB)ZOib-+}g*`a4ouO3tNfi0_bX>B`v;bPI(ivGiqXf2_p$&SZI9$2E+NK6QwlKg9mbO35lFnLp9f@jnxw6(tE@tl3M`xHKutk;cJwV7dXqG7wrNMpP>)2z!|_1|0ReczDusTcWP6RlXbKKe2NhL(=ydP)64&Kks?GbSbwJtMqzS~YX()PgYEdZ)fIK7}mYCJgVr4}kD$C(Hz zD=Iuq{O42qtE(o=D5_He;>+RIkw+rNp0fFQk_QQR)$!e;Mhf)a#a5Rto{m|bQ2x5~ zsXS7uO6is7SkuU5pkhQ*tlN{`_t7+;{_9V~{l0>%LVarbI#VI3Z4FrQ6k)Z-dNoRrtyZ8s$C?H(L>Bzysp)+$n6e!cw*F6&3p(v|)vP!jy`p5~w z8LlvX>teyRds#*vu?@rkr$tcRX^wyZ10yADRGP1Pz+?<@j8K`9w zk=Z@Hgw1LY9Y5rLGCTC&Y7);~$nVndgK#m^hlS5qL2f*zK{#3UpRACQ zDQFu+c$P|dgU7U$WY`ci0(gH&bn0;^0tLurd+*Nomj3Tc{eS$Wvc@Y70ngbAWJ-WG zPb{2DrsF)w6Jh)f{*>n7%ds~IvPbKdOuuQ8^MO+uvBfe)Tn5Q2ejkh_aa~N_P+Uvx zQ>4oAXcMCasbh{q)cf~leY;TNAH^6UStFCfgAksMmnn*Qq{VT3SJX_YO_hhk;npC@sHLfSt};@PfM4@(wVs+9 zlk6+^-f2SbLL_v3h6>akCh0r_dG1-YF$D#T&z(lz6B0zy+HMJ@Wgbe_)*&(R@r2G+ zWYDu0ztvPJgEKq5GE2I>{nZ7IUaua({!g>8r$frtXVQbE8#+b#4-ahF4z)l;TK!+6 zCLnQlcUPX7*JB5=|DP-sPyeS!pfnR17Df&dta1RV4|*(jjJ$PpRQy+u^+nMEydmPN zksGQ&??Z(B(lH3jxf>~`IfhPoE!!RLep=$~--C)5J(DIP2J$U!fSx*ZX&G8gDVgH$ zVw~$Jfqh7{(Vdda&Lc% zIy1yGel{jtiG&cSXc}Z6tCs||015>{cH{l`L)itMtXyeR?>Em0?olJgm*wpp9d!s% z*(ZD1+-%>w*8iD&WVN=j<-jE(ES|xQsw7Ohh4^CkZLMc?-md)x2^Q>jr8>tH2&7X2 z=^e6bYYpML>lxp_Ujx$nI#c8zotKF}Bw|^%f4))(9FdW)&Swxa7`NR;B5UQBFk7mq z?7c?BOAi8m1xDNNFzUT( zgIY{_WZ+u1j&Zf}$jHcGk58PAUJThtkDtbo!X=)h>;88`?O56~fG&@;o_y>ZiG%BH zJaqKqcsJMNi!L(LK0oG=pUVy8HL<@ptyKs#-VG(=0TaS9KWygQMqXgRiWAT%LM<&7 zH>S70>c2x8dsYPrkJXaGszr7`(f&*eEv5Rx!4vta#5rOthgi8z?>GEG5TSe4;J0ra zrsI-BEyczBI0=E-Sr%KqnW@W~LDHK9KsX^Cs9U{DFwTnxFYsmKK5H;N2aI#YX_yOL zk;p6Qa9dEm^SqKVbj;~v`-^0=JRc4+nivNFMY(9B{!jKeQ2HUXyj*YphxQe-k|1?kBoVKN=}mM-BPmE;Dq9DPI}ny3}C5#vbl8$c)utjF{$KFDIi zD3UY3@HSIEw%T;x=-u;L$|+P# z*rjF_qjU`<7M*}hoV%;~u?U^C7c{KfS@6r_=SxR@4xtU{;{31)wdwE&r1-LIDPpXK zE?yno-R1OiTpSz`L5G8jcF1zSPo;Hbng~(n(5-SI<;lzF#rM<{uZZz_NV3fG;I>*% z+W6Y83Mi9&?1@WD+M(|UsyAfpQf&X6-hT&$K=K|S;yl*rxKC(%rW0skr?e+}0#NbK zYI@ne>km(8<4Xq)foywE+cDI@U(fr;|2UzI2`~qLK^^^_sY@>aPRi1RxY030(%)@o zv;S!5kJxl`{jZnv-~8U1f;DH}(9gztpKozNx= zz{wd3=%N5921pN=L4^)!? literal 0 HcmV?d00001 diff --git a/docs/static/images/yp/encryption-in-transit/rotate-cert.png b/docs/static/images/yp/encryption-in-transit/rotate-cert.png new file mode 100644 index 0000000000000000000000000000000000000000..fa75b6ccfefef897908100ccc5b8e15a4b60f167 GIT binary patch literal 156284 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 BA)tg;cjtN* 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
  • +SJ6T?0tN2q6oej9k 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 Date: Fri, 12 Jul 2024 13:18:03 +0300 Subject: [PATCH 1177/1195] [PLAT-14344] Support Premium SSD V2 disks in azure YBA managed provider Summary: Added support for Premium SSD V2 disks. This requires updating API to at least 2022.03. Smart resize is forbidden for that type of disks (so only full move could be used to modify device) As long this type is only available in select regions, we show a warning to user that creation could fail. Test Plan: Create azure universe with premiumV2 disks and customized iops/throughput (verify iops and throughput are passed in request). Edit universe and expand disk - verify that this is performed by full move {F263004} Reviewers: muthu, sanketh, rmadhavan Reviewed By: muthu Subscribers: yugaware Differential Revision: https://phorge.dev.yugabyte.com/D36246 --- .../devops/opscli/ybops/cloud/azure/method.py | 3 +- .../devops/opscli/ybops/cloud/azure/utils.py | 4 +- managed/devops/python3_requirements.txt | 2 +- .../devops/python3_requirements_frozen.txt | 20 ++++---- .../java/api/v2/mappers/UserIntentMapper.java | 1 + .../yw/cloud/PublicCloudConstants.java | 1 + .../yugabyte/yw/forms/ResizeNodeParams.java | 6 ++- .../schemas/ClusterStorageSpec.yaml | 3 +- .../src/main/resources/swagger-strict.json | 14 +++--- managed/src/main/resources/swagger.json | 14 +++--- .../UniverseControllerTestBase.java | 2 +- .../UniverseCreateControllerTestBase.java | 7 +-- .../VolumeInfoField/StorageTypeField.tsx | 2 +- .../VolumeInfoField/VolumeInfoField.tsx | 41 +++++++++++++--- .../VolumeInfoField/VolumeInfoFieldHelper.ts | 49 +++++++++++++------ .../universe/universe-form/utils/dto.ts | 1 + managed/ui/src/redesign/helpers/dtos.ts | 1 + managed/ui/src/translations/en.json | 1 + 18 files changed, 115 insertions(+), 57 deletions(-) diff --git a/managed/devops/opscli/ybops/cloud/azure/method.py b/managed/devops/opscli/ybops/cloud/azure/method.py index a9790030f875..63133753756d 100644 --- a/managed/devops/opscli/ybops/cloud/azure/method.py +++ b/managed/devops/opscli/ybops/cloud/azure/method.py @@ -51,7 +51,8 @@ def __init__(self, base_command): def add_extra_args(self): super(AzureCreateInstancesMethod, self).add_extra_args() self.parser.add_argument("--volume_type", - choices=["premium_lrs", "standardssd_lrs", "ultrassd_lrs"], + choices=["premium_lrs", "standardssd_lrs", "ultrassd_lrs", + "premiumv2_lrs"], default="premium_lrs", help="Volume type for Azure instances.") self.parser.add_argument("--security_group_id", default=None, help="Azure comma delimited security group IDs.") diff --git a/managed/devops/opscli/ybops/cloud/azure/utils.py b/managed/devops/opscli/ybops/cloud/azure/utils.py index 77db8b6351f3..91a5add216f1 100644 --- a/managed/devops/opscli/ybops/cloud/azure/utils.py +++ b/managed/devops/opscli/ybops/cloud/azure/utils.py @@ -50,8 +50,10 @@ SUBNET_ID_FORMAT_STRING = NETWORK_PROVIDER_BASE_PATH + "/virtualNetworks/{}/subnets/{}" NSG_ID_FORMAT_STRING = NETWORK_PROVIDER_BASE_PATH + "/networkSecurityGroups/{}" ULTRASSD_LRS = "ultrassd_lrs" +PREMIUMV2_LRS = "premiumv2_lrs" VNET_ID_FORMAT_STRING = NETWORK_PROVIDER_BASE_PATH + "/virtualNetworks/{}" AZURE_SKU_FORMAT = {"premium_lrs": "Premium_LRS", + "premiumv2_lrs": "PremiumV2_LRS", "standardssd_lrs": "StandardSSD_LRS", ULTRASSD_LRS: "UltraSSD_LRS"} YUGABYTE_VNET_PREFIX = "yugabyte-vnet-{}" @@ -429,7 +431,7 @@ def append_disk(self, vm, vm_name, disk_name, size, lun, zone, vol_type, region, if tags: disk_params["tags"] = tags - if vol_type == ULTRASSD_LRS: + if vol_type == ULTRASSD_LRS or vol_type == PREMIUMV2_LRS: if disk_iops is not None: disk_params['disk_iops_read_write'] = disk_iops if disk_throughput is not None: diff --git a/managed/devops/python3_requirements.txt b/managed/devops/python3_requirements.txt index 6f59672e95c5..14dca34e292f 100644 --- a/managed/devops/python3_requirements.txt +++ b/managed/devops/python3_requirements.txt @@ -22,7 +22,7 @@ ansible==2.9.27 ansible-vault==2.1.0 azure-common==1.1.28 azure-identity==1.6.1 -azure-mgmt-compute==23.1.0 +azure-mgmt-compute==27.1.0 azure-mgmt-privatedns==1.0.0 azure-mgmt-network==19.3.0 azure-mgmt-resource==19.0.0 diff --git a/managed/devops/python3_requirements_frozen.txt b/managed/devops/python3_requirements_frozen.txt index 84d5c65b73b4..36b6ac8acc2f 100644 --- a/managed/devops/python3_requirements_frozen.txt +++ b/managed/devops/python3_requirements_frozen.txt @@ -2,9 +2,9 @@ adal==1.2.7 ansible==2.9.27 ansible-vault==2.1.0 azure-common==1.1.28 -azure-core==1.30.1 +azure-core==1.30.2 azure-identity==1.6.1 -azure-mgmt-compute==23.1.0 +azure-mgmt-compute==27.1.0 azure-mgmt-core==1.4.0 azure-mgmt-network==19.3.0 azure-mgmt-privatedns==1.0.0 @@ -14,18 +14,18 @@ boto==2.49.0 boto3==1.34.23 botocore==1.34.23 cachetools==4.2.4 -certifi==2024.2.2 +certifi==2024.6.2 cffi==1.16.0 chardet==4.0.0 click==8.1.7 -cryptography==42.0.7 +cryptography==42.0.8 deepdiff==5.5.0 distro==1.5.0 fabric==2.2.1 geomet==0.2.1.post1 google-api-core==1.22.0 google-api-python-client==1.10.0 -googleapis-common-protos==1.63.0 +googleapis-common-protos==1.63.2 google-auth==1.19.2 google-auth-httplib2==0.2.0 grpcio==1.57.0 @@ -38,7 +38,7 @@ Jinja2==3.0.3 jmespath==1.0.1 MarkupSafe==2.0.1 mitogen==0.2.9 -msal==1.28.0 +msal==1.29.0 msal-extensions==0.3.1 msrest==0.7.1 msrestazure==0.6.4.post1 @@ -46,7 +46,7 @@ oauth2client==3.0.0 oauthlib==3.2.2 ordered-set==4.0.2 paramiko==3.4.0 -portalocker==2.8.2 +portalocker==2.10.0 protobuf==4.23.4 psycopg2==2.9.9 pyasn1==0.6.0 @@ -62,10 +62,10 @@ PyYAML==6.0.1 requests==2.25.1 requests-oauthlib==1.3.0 rsa==4.9 -s3transfer==0.10.1 +s3transfer==0.10.2 scp==0.14.5 six==1.16.0 -typing_extensions==4.11.0 +typing_extensions==4.12.2 uritemplate==3.0.1 -urllib3==1.26.18 +urllib3==1.26.19 yb-cassandra-driver==3.25.0 diff --git a/managed/src/main/java/api/v2/mappers/UserIntentMapper.java b/managed/src/main/java/api/v2/mappers/UserIntentMapper.java index 973c06dfd6e7..899731a45063 100644 --- a/managed/src/main/java/api/v2/mappers/UserIntentMapper.java +++ b/managed/src/main/java/api/v2/mappers/UserIntentMapper.java @@ -127,6 +127,7 @@ default ClusterNodeSpec userIntentToClusterNodeSpec( @ValueMapping(target = "PERSISTENT", source = "Persistent"), @ValueMapping(target = "STANDARDSSD_LRS", source = "StandardSSD_LRS"), @ValueMapping(target = "PREMIUM_LRS", source = "Premium_LRS"), + @ValueMapping(target = "PREMIUMV2_LRS", source = "PremiumV2_LRS"), @ValueMapping(target = "ULTRASSD_LRS", source = "UltraSSD_LRS"), @ValueMapping(target = "LOCAL", source = "Local"), }) diff --git a/managed/src/main/java/com/yugabyte/yw/cloud/PublicCloudConstants.java b/managed/src/main/java/com/yugabyte/yw/cloud/PublicCloudConstants.java index 2ba355ed0f08..fb2380212320 100644 --- a/managed/src/main/java/com/yugabyte/yw/cloud/PublicCloudConstants.java +++ b/managed/src/main/java/com/yugabyte/yw/cloud/PublicCloudConstants.java @@ -89,6 +89,7 @@ public enum StorageType { Persistent(Common.CloudType.gcp), StandardSSD_LRS(Common.CloudType.azu), Premium_LRS(Common.CloudType.azu), + PremiumV2_LRS(Common.CloudType.azu, new Pair<>(3000, 80_000), new Pair<>(1, 1200)), UltraSSD_LRS(Common.CloudType.azu, new Pair<>(100, 160_000), new Pair<>(1, 3814)), Local(Common.CloudType.local); diff --git a/managed/src/main/java/com/yugabyte/yw/forms/ResizeNodeParams.java b/managed/src/main/java/com/yugabyte/yw/forms/ResizeNodeParams.java index d6030edec6aa..829ef4aee4ef 100644 --- a/managed/src/main/java/com/yugabyte/yw/forms/ResizeNodeParams.java +++ b/managed/src/main/java/com/yugabyte/yw/forms/ResizeNodeParams.java @@ -268,8 +268,10 @@ private static String getResizeIsPossibleError( return error.get(); } if (nodeDiskChanged) { - if (curDeviceInfo.storageType == PublicCloudConstants.StorageType.UltraSSD_LRS) { - return "UltraSSD doesn't support resizing without downtime"; + if (curDeviceInfo.storageType == PublicCloudConstants.StorageType.UltraSSD_LRS + || curDeviceInfo.storageType == PublicCloudConstants.StorageType.PremiumV2_LRS) { + return String.format( + "%s doesn't support resizing without downtime", curDeviceInfo.storageType); } if (currentUserIntent.providerType == Common.CloudType.azu && curDeviceInfo.volumeSize <= AZU_DISK_LIMIT_NO_DOWNTIME diff --git a/managed/src/main/resources/openapi/components/schemas/ClusterStorageSpec.yaml b/managed/src/main/resources/openapi/components/schemas/ClusterStorageSpec.yaml index 03e5fd1fa8ce..cfdb645a9fa5 100644 --- a/managed/src/main/resources/openapi/components/schemas/ClusterStorageSpec.yaml +++ b/managed/src/main/resources/openapi/components/schemas/ClusterStorageSpec.yaml @@ -20,7 +20,7 @@ properties: description: 'Name of the storage class, if this is a kubernetes cluster' type: string storage_type: - description: 'Storage type used for this instance, if this is a aws (IO1, GP2, GP3), gcp (Scratch, Persistent) or azu (StandardSSD_LRS, Premium_LRS, UltraSSD_LRS) cluster.' + description: 'Storage type used for this instance, if this is a aws (IO1, GP2, GP3), gcp (Scratch, Persistent) or azu (StandardSSD_LRS, Premium_LRS, PremiumV2_LRS, UltraSSD_LRS) cluster.' type: string enum: - IO1 @@ -29,6 +29,7 @@ properties: - Scratch - Persistent - StandardSSD_LRS + - PremiumV2_LRS - Premium_LRS - UltraSSD_LRS - Local diff --git a/managed/src/main/resources/swagger-strict.json b/managed/src/main/resources/swagger-strict.json index 55c55162047d..16253e0bd06f 100644 --- a/managed/src/main/resources/swagger-strict.json +++ b/managed/src/main/resources/swagger-strict.json @@ -4766,7 +4766,7 @@ }, "storageType" : { "description" : "Storage type used for this instance", - "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "UltraSSD_LRS", "Local" ], + "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "PremiumV2_LRS", "UltraSSD_LRS", "Local" ], "type" : "string" }, "throughput" : { @@ -28247,7 +28247,7 @@ "description" : "successful operation", "schema" : { "items" : { - "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "UltraSSD_LRS", "Local" ], + "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "PremiumV2_LRS", "UltraSSD_LRS", "Local" ], "type" : "string" }, "type" : "array" @@ -28259,7 +28259,7 @@ "description" : "successful operation", "schema" : { "items" : { - "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "UltraSSD_LRS", "Local" ], + "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "PremiumV2_LRS", "UltraSSD_LRS", "Local" ], "type" : "string" }, "type" : "array" @@ -28283,7 +28283,7 @@ "description" : "successful operation", "schema" : { "items" : { - "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "UltraSSD_LRS", "Local" ], + "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "PremiumV2_LRS", "UltraSSD_LRS", "Local" ], "type" : "string" }, "type" : "array" @@ -28295,7 +28295,7 @@ "description" : "successful operation", "schema" : { "items" : { - "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "UltraSSD_LRS", "Local" ], + "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "PremiumV2_LRS", "UltraSSD_LRS", "Local" ], "type" : "string" }, "type" : "array" @@ -28319,7 +28319,7 @@ "description" : "successful operation", "schema" : { "items" : { - "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "UltraSSD_LRS", "Local" ], + "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "PremiumV2_LRS", "UltraSSD_LRS", "Local" ], "type" : "string" }, "type" : "array" @@ -28331,7 +28331,7 @@ "description" : "successful operation", "schema" : { "items" : { - "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "UltraSSD_LRS", "Local" ], + "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "PremiumV2_LRS", "UltraSSD_LRS", "Local" ], "type" : "string" }, "type" : "array" diff --git a/managed/src/main/resources/swagger.json b/managed/src/main/resources/swagger.json index 2857428e2278..96b94d4b3ab7 100644 --- a/managed/src/main/resources/swagger.json +++ b/managed/src/main/resources/swagger.json @@ -4801,7 +4801,7 @@ }, "storageType" : { "description" : "Storage type used for this instance", - "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "UltraSSD_LRS", "Local" ], + "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "PremiumV2_LRS", "UltraSSD_LRS", "Local" ], "type" : "string" }, "throughput" : { @@ -29709,7 +29709,7 @@ "description" : "successful operation", "schema" : { "items" : { - "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "UltraSSD_LRS", "Local" ], + "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "PremiumV2_LRS", "UltraSSD_LRS", "Local" ], "type" : "string" }, "type" : "array" @@ -29721,7 +29721,7 @@ "description" : "successful operation", "schema" : { "items" : { - "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "UltraSSD_LRS", "Local" ], + "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "PremiumV2_LRS", "UltraSSD_LRS", "Local" ], "type" : "string" }, "type" : "array" @@ -29745,7 +29745,7 @@ "description" : "successful operation", "schema" : { "items" : { - "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "UltraSSD_LRS", "Local" ], + "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "PremiumV2_LRS", "UltraSSD_LRS", "Local" ], "type" : "string" }, "type" : "array" @@ -29757,7 +29757,7 @@ "description" : "successful operation", "schema" : { "items" : { - "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "UltraSSD_LRS", "Local" ], + "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "PremiumV2_LRS", "UltraSSD_LRS", "Local" ], "type" : "string" }, "type" : "array" @@ -29781,7 +29781,7 @@ "description" : "successful operation", "schema" : { "items" : { - "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "UltraSSD_LRS", "Local" ], + "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "PremiumV2_LRS", "UltraSSD_LRS", "Local" ], "type" : "string" }, "type" : "array" @@ -29793,7 +29793,7 @@ "description" : "successful operation", "schema" : { "items" : { - "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "UltraSSD_LRS", "Local" ], + "enum" : [ "IO1", "GP2", "GP3", "Scratch", "Persistent", "StandardSSD_LRS", "Premium_LRS", "PremiumV2_LRS", "UltraSSD_LRS", "Local" ], "type" : "string" }, "type" : "array" diff --git a/managed/src/test/java/com/yugabyte/yw/controllers/UniverseControllerTestBase.java b/managed/src/test/java/com/yugabyte/yw/controllers/UniverseControllerTestBase.java index 15981f767cb0..4d5f009aa2d4 100644 --- a/managed/src/test/java/com/yugabyte/yw/controllers/UniverseControllerTestBase.java +++ b/managed/src/test/java/com/yugabyte/yw/controllers/UniverseControllerTestBase.java @@ -308,7 +308,7 @@ protected ObjectNode createValidDeviceInfo(CloudType cloudType) { case gcp: return createDeviceInfo(StorageType.Persistent, 1, 100, null, null, null); case azu: - return createDeviceInfo(StorageType.Premium_LRS, 1, 100, null, null, null); + return createDeviceInfo(StorageType.PremiumV2_LRS, 1, 100, null, null, null); case kubernetes: return createDeviceInfo(null, 1, 100, null, null, null); default: diff --git a/managed/src/test/java/com/yugabyte/yw/controllers/UniverseCreateControllerTestBase.java b/managed/src/test/java/com/yugabyte/yw/controllers/UniverseCreateControllerTestBase.java index f3c853758152..85513eaca07e 100644 --- a/managed/src/test/java/com/yugabyte/yw/controllers/UniverseCreateControllerTestBase.java +++ b/managed/src/test/java/com/yugabyte/yw/controllers/UniverseCreateControllerTestBase.java @@ -966,16 +966,17 @@ private Object[] parametersToDeviceInfoValidation() { null, null }, + {Common.CloudType.azu, "c3.xlarge", StorageType.Premium_LRS, 1, 100, null, null, null, null}, { Common.CloudType.azu, "c3.xlarge", - PublicCloudConstants.StorageType.Premium_LRS, + PublicCloudConstants.StorageType.PremiumV2_LRS, 1, 100, null, null, null, - null + "Disk IOPS is mandatory for PremiumV2_LRS storage" }, { Common.CloudType.azu, @@ -1205,7 +1206,7 @@ private Object[] parametersToDeviceInfoValidation() { { Common.CloudType.azu, "c3.xlarge", - PublicCloudConstants.StorageType.Premium_LRS, + PublicCloudConstants.StorageType.PremiumV2_LRS, 1, null, null, diff --git a/managed/ui/src/redesign/features/universe/universe-form/form/fields/VolumeInfoField/StorageTypeField.tsx b/managed/ui/src/redesign/features/universe/universe-form/form/fields/VolumeInfoField/StorageTypeField.tsx index dd3f365c2185..78b28c776aaa 100644 --- a/managed/ui/src/redesign/features/universe/universe-form/form/fields/VolumeInfoField/StorageTypeField.tsx +++ b/managed/ui/src/redesign/features/universe/universe-form/form/fields/VolumeInfoField/StorageTypeField.tsx @@ -4,7 +4,7 @@ import { useFormContext, useWatch } from 'react-hook-form'; import { useQuery } from 'react-query'; import { useUpdateEffect } from 'react-use'; import { Box, Grid, MenuItem, makeStyles } from '@material-ui/core'; -import { YBLabel, YBSelect } from '../../../../../../components'; +import { YBLabel, YBSelect} from '../../../../../../components'; import { UniverseFormContext } from '../../../UniverseFormContainer'; import { api, QUERY_KEY } from '../../../utils/api'; import { diff --git a/managed/ui/src/redesign/features/universe/universe-form/form/fields/VolumeInfoField/VolumeInfoField.tsx b/managed/ui/src/redesign/features/universe/universe-form/form/fields/VolumeInfoField/VolumeInfoField.tsx index ecaa5402c584..3a1b3a190ee5 100644 --- a/managed/ui/src/redesign/features/universe/universe-form/form/fields/VolumeInfoField/VolumeInfoField.tsx +++ b/managed/ui/src/redesign/features/universe/universe-form/form/fields/VolumeInfoField/VolumeInfoField.tsx @@ -1,10 +1,17 @@ import { FC, useEffect, useRef } from 'react'; import { useQuery } from 'react-query'; +import clsx from 'clsx'; import { useUpdateEffect } from 'react-use'; import { useTranslation } from 'react-i18next'; import { Controller, useFormContext, useWatch } from 'react-hook-form'; import { Box, Grid, MenuItem, Tooltip, makeStyles } from '@material-ui/core'; -import { YBInput, YBLabel, YBSelect } from '../../../../../../components'; +import { + YBHelper, + YBHelperVariants, + YBInput, + YBLabel, + YBSelect +} from '../../../../../../components'; import { api, QUERY_KEY } from '../../../utils/api'; import { getStorageTypeOptions, @@ -72,6 +79,10 @@ const useStyles = makeStyles((theme) => ({ coolDownTooltip: { marginLeft: theme.spacing(1), alignSelf: 'center' + }, + premiumV2StorageLabelField: { + marginTop: theme.spacing(2), + alignItems: 'flex-start' } })); @@ -172,7 +183,8 @@ export const VolumeInfoField: FC = ({ //reset methods const resetThroughput = () => { const { storageType, throughput, diskIops, volumeSize } = fieldValue; - if ([StorageType.IO1, StorageType.GP3, StorageType.UltraSSD_LRS].includes(storageType)) { + if ([StorageType.IO1, StorageType.GP3, StorageType.UltraSSD_LRS, StorageType.PremiumV2_LRS] + .includes(storageType)) { //resetting throughput const throughputVal = getThroughputByIops(Number(throughput), diskIops, storageType); setValue(UPDATE_FIELD, { @@ -195,19 +207,19 @@ export const VolumeInfoField: FC = ({ }; const onVolumeSizeChanged = (value: any) => { - const { storageType, diskIops } = fieldValue; setValue(UPDATE_FIELD, { ...fieldValue, volumeSize: Number(value) }); }; - /* + /* When storage type is UltraSSD_LRS, disk IOPS is calculated based on volume size. Hence, when volume size is changed, disk IOPS should be recalculated. */ useUpdateEffect(() => { - if (fieldValue.storageType === StorageType.UltraSSD_LRS) { + if (fieldValue.storageType === StorageType.UltraSSD_LRS + || fieldValue.storageType === StorageType.PremiumV2_LRS) { onDiskIopsChanged(fieldValue.diskIops); } },[fieldValue?.volumeSize]); @@ -337,6 +349,7 @@ export const VolumeInfoField: FC = ({ }; const renderStorageType = () => { + const isPremiumV2Storage = fieldValue.storageType === StorageType.PremiumV2_LRS; if ( [CloudType.gcp, CloudType.azu].includes(provider?.code) || (volumeType === VolumeType.EBS && provider?.code === CloudType.aws) @@ -348,7 +361,10 @@ export const VolumeInfoField: FC = ({ {provider?.code === CloudType.aws ? t('universeForm.instanceConfig.ebs') @@ -363,6 +379,13 @@ export const VolumeInfoField: FC = ({ min: 1, 'data-testid': `VolumeInfoField-${dataTag}-StorageTypeSelect` }} + helperText={ + isPremiumV2Storage && ( + + {t('universeForm.instanceConfig.premiumv2Storage')} + + ) + } onChange={(event) => onStorageTypeChanged((event?.target.value as unknown) as StorageType) } @@ -385,7 +408,8 @@ export const VolumeInfoField: FC = ({ const renderDiskIops = () => { if ( - ![StorageType.IO1, StorageType.GP3, StorageType.UltraSSD_LRS].includes(fieldValue.storageType) + ![StorageType.IO1, StorageType.GP3, StorageType.UltraSSD_LRS, StorageType.PremiumV2_LRS] + .includes(fieldValue.storageType) ) return null; @@ -415,7 +439,8 @@ export const VolumeInfoField: FC = ({ }; const renderThroughput = () => { - if (![StorageType.GP3, StorageType.UltraSSD_LRS].includes(fieldValue.storageType)) return null; + if (![StorageType.GP3, StorageType.UltraSSD_LRS, StorageType.PremiumV2_LRS] + .includes(fieldValue.storageType)) return null; return ( diff --git a/managed/ui/src/redesign/features/universe/universe-form/form/fields/VolumeInfoField/VolumeInfoFieldHelper.ts b/managed/ui/src/redesign/features/universe/universe-form/form/fields/VolumeInfoField/VolumeInfoFieldHelper.ts index a044ccc195c6..01719213a0d7 100644 --- a/managed/ui/src/redesign/features/universe/universe-form/form/fields/VolumeInfoField/VolumeInfoFieldHelper.ts +++ b/managed/ui/src/redesign/features/universe/universe-form/form/fields/VolumeInfoField/VolumeInfoFieldHelper.ts @@ -1,25 +1,21 @@ -import { useRef, useState } from 'react'; -import _ from 'lodash'; +import { useState } from 'react'; import { useUpdateEffect } from 'react-use'; -import { useWatch, useFormContext } from 'react-hook-form'; +import { useWatch } from 'react-hook-form'; import { - PLACEMENTS_FIELD, - TOTAL_NODES_FIELD, - INSTANCE_TYPE_FIELD, DEVICE_INFO_FIELD, - PROVIDER_FIELD + INSTANCE_TYPE_FIELD, + PLACEMENTS_FIELD, + PROVIDER_FIELD, + TOTAL_NODES_FIELD } from '../../../utils/constants'; import { CloudType, DeviceInfo, InstanceType, RunTimeConfigEntry, - StorageType, - UniverseFormData, - UpdateActions + StorageType } from '../../../utils/dto'; import { isEphemeralAwsStorageInstance } from '../InstanceTypeField/InstanceTypeFieldHelper'; -import { isNonEmptyArray } from '../../../../../../../utils/ObjectUtils'; export const IO1_DEFAULT_DISK_IOPS = 1000; export const IO1_MAX_DISK_IOPS = 64000; @@ -37,6 +33,13 @@ export const UltraSSD_DISK_IOPS_MAX_PER_GB = 300; export const UltraSSD_IOPS_TO_MAX_DISK_THROUGHPUT = 4; export const UltraSSD_DISK_THROUGHPUT_CAP = 2500; +export const PremiumV2_LRS_DEFAULT_DISK_IOPS = 3000; +export const PremiumV2_LRS_DEFAULT_DISK_THROUGHPUT = 125; +export const PremiumV2_LRS_MIN_DISK_IOPS = 3000; +export const PremiumV2_LRS_DISK_IOPS_MAX_PER_GB = 500; +export const PremiumV2_LRS_IOPS_TO_MAX_DISK_THROUGHPUT = 4; +export const PremiumV2_LRS_DISK_THROUGHPUT_CAP = 2500; + export interface StorageTypeOption { value: StorageType; label: string; @@ -62,13 +65,19 @@ export const GCP_STORAGE_TYPE_OPTIONS: StorageTypeOption[] = [ export const AZURE_STORAGE_TYPE_OPTIONS: StorageTypeOption[] = [ { value: StorageType.StandardSSD_LRS, label: 'Standard' }, { value: StorageType.Premium_LRS, label: 'Premium' }, + { value: StorageType.PremiumV2_LRS, label: 'PremiumV2' }, { value: StorageType.UltraSSD_LRS, label: 'Ultra' } ]; export const getMinDiskIops = (storageType: StorageType, volumeSize: number) => { - return storageType === StorageType.UltraSSD_LRS - ? Math.max(UltraSSD_MIN_DISK_IOPS, volumeSize) - : 0; + switch (storageType) { + case StorageType.UltraSSD_LRS: + return Math.max(UltraSSD_MIN_DISK_IOPS, volumeSize); + case StorageType.PremiumV2_LRS: + return Math.max(PremiumV2_LRS_MIN_DISK_IOPS, volumeSize); + default: + return 0; + } }; export const getMaxDiskIops = (storageType: StorageType, volumeSize: number) => { @@ -77,6 +86,8 @@ export const getMaxDiskIops = (storageType: StorageType, volumeSize: number) => return IO1_MAX_DISK_IOPS; case StorageType.UltraSSD_LRS: return volumeSize * UltraSSD_DISK_IOPS_MAX_PER_GB; + case StorageType.PremiumV2_LRS: + return volumeSize * PremiumV2_LRS_DISK_IOPS_MAX_PER_GB; default: return GP3_MAX_IOPS; } @@ -102,6 +113,8 @@ export const getIopsByStorageType = (storageType: StorageType) => { return GP3_DEFAULT_DISK_IOPS; } else if (storageType === StorageType.UltraSSD_LRS) { return UltraSSD_DEFAULT_DISK_IOPS; + } else if (storageType === StorageType.PremiumV2_LRS) { + return PremiumV2_LRS_DEFAULT_DISK_IOPS; } return null; }; @@ -111,6 +124,8 @@ export const getThroughputByStorageType = (storageType: StorageType) => { return GP3_DEFAULT_DISK_THROUGHPUT; } else if (storageType === StorageType.UltraSSD_LRS) { return UltraSSD_DEFAULT_DISK_THROUGHPUT; + } else if (storageType === StorageType.PremiumV2_LRS) { + return PremiumV2_LRS_DEFAULT_DISK_THROUGHPUT; } return null; }; @@ -136,6 +151,12 @@ export const getThroughputByIops = ( UltraSSD_DISK_THROUGHPUT_CAP ); return Math.max(0, Math.min(maxThroughput, currentThroughput)); + } else if (storageType === StorageType.PremiumV2_LRS) { + const maxThroughput = Math.min( + diskIops / PremiumV2_LRS_IOPS_TO_MAX_DISK_THROUGHPUT, + PremiumV2_LRS_DISK_THROUGHPUT_CAP + ); + return Math.max(0, Math.min(maxThroughput, currentThroughput)); } return currentThroughput; diff --git a/managed/ui/src/redesign/features/universe/universe-form/utils/dto.ts b/managed/ui/src/redesign/features/universe/universe-form/utils/dto.ts index 9de9e002f9e5..e31182d79ca0 100644 --- a/managed/ui/src/redesign/features/universe/universe-form/utils/dto.ts +++ b/managed/ui/src/redesign/features/universe/universe-form/utils/dto.ts @@ -60,6 +60,7 @@ export enum StorageType { Persistent = 'Persistent', StandardSSD_LRS = 'StandardSSD_LRS', Premium_LRS = 'Premium_LRS', + PremiumV2_LRS = 'PremiumV2_LRS', UltraSSD_LRS = 'UltraSSD_LRS' } export interface DeviceInfo { diff --git a/managed/ui/src/redesign/helpers/dtos.ts b/managed/ui/src/redesign/helpers/dtos.ts index eccfe1d740cf..92b0ef5f444a 100644 --- a/managed/ui/src/redesign/helpers/dtos.ts +++ b/managed/ui/src/redesign/helpers/dtos.ts @@ -86,6 +86,7 @@ export enum StorageType { Persistent = 'Persistent', StandardSSD_LRS = 'StandardSSD_LRS', Premium_LRS = 'Premium_LRS', + PremiumV2_LRS = 'PremiumV2_LRS', UltraSSD_LRS = 'UltraSSD_LRS' } diff --git a/managed/ui/src/translations/en.json b/managed/ui/src/translations/en.json index 7d23849e7476..bbd5575c9881 100644 --- a/managed/ui/src/translations/en.json +++ b/managed/ui/src/translations/en.json @@ -415,6 +415,7 @@ "dedicatedNodesHelper": "Place tserver and master processes on separate nodes.", "ebs": "EBS Type", "ephemeralStorage": "! Selected instance type is with ephemeral storage, If you will pause this universe your data will get lost.", + "premiumv2Storage": "! Selected storage type is only available in select regions. Your creation will fail if you choose incorrect region.", "instanceType": "Instance Type", "k8NumCores": "Number of Cores", "k8Memory": "Memory (Gib)", From 4ca56cfe27d1cae64e0e61a1bde22406e003ec04 Mon Sep 17 00:00:00 2001 From: Yury Shchetinin Date: Tue, 16 Jul 2024 17:20:15 +0300 Subject: [PATCH 1178/1195] [PLAT-6774] Enable changing RF for existing universe Summary: Fixed one special case in placing replicas Test Plan: sbt test 1) Create universe with rf-3 and 6 nodes in single az 2) Increase RF to 7 3) Verify ok (previously was failing) Reviewers: svarshney Reviewed By: svarshney Subscribers: yugaware Differential Revision: https://phorge.dev.yugabyte.com/D36626 --- .../yugabyte/yw/common/PlacementInfoUtil.java | 18 +++++---- .../yw/common/PlacementInfoUtilTest.java | 37 +++++++++++++++++++ 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/managed/src/main/java/com/yugabyte/yw/common/PlacementInfoUtil.java b/managed/src/main/java/com/yugabyte/yw/common/PlacementInfoUtil.java index b3f7721351b2..899586f2e9b5 100644 --- a/managed/src/main/java/com/yugabyte/yw/common/PlacementInfoUtil.java +++ b/managed/src/main/java/com/yugabyte/yw/common/PlacementInfoUtil.java @@ -820,7 +820,6 @@ private static boolean isDedicatedModeChanged(Cluster cluster, Set static void setPerAZRF(PlacementInfo placementInfo, int rf, UUID defaultRegionUUID) { LOG.info("Setting per AZ replication factor. Default region {}", defaultRegionUUID); List sortedAZs = getAZsSortedByNumNodes(placementInfo); - int numNodesInUniverse = sortedAZs.stream().map(az -> az.numNodesInAZ).reduce(0, Integer::sum); // Reset per-AZ RF to 0 placementInfo.azStream().forEach(az -> az.replicationFactor = 0); @@ -882,16 +881,21 @@ static void setPerAZRF(PlacementInfo placementInfo, int rf, UUID defaultRegionUU LOG.debug("Special case when RF=3 and number of zones= 2, using 1-1 distribution"); return; } + + sortedAZPlacements.removeIf( + az -> az.getSecond().replicationFactor >= az.getSecond().numNodesInAZ); // Set per-AZ RF according to node distribution across AZs. // We already have one replica in each region. Now placing other. int i = 0; - while ((placedReplicas < rf) && (i < numNodesInUniverse)) { - Pair az = sortedAZPlacements.get(i % sortedAZs.size()); - if (az.getSecond().replicationFactor < az.getSecond().numNodesInAZ) { - az.getSecond().replicationFactor++; - placedReplicas++; + while ((placedReplicas < rf) && sortedAZPlacements.size() > 0) { + Pair az = sortedAZPlacements.get(i % sortedAZPlacements.size()); + az.getSecond().replicationFactor++; + placedReplicas++; + if (az.getSecond().replicationFactor == az.getSecond().numNodesInAZ) { + sortedAZPlacements.remove(az); + } else { + i++; } - i++; } if (placedReplicas < rf) { diff --git a/managed/src/test/java/com/yugabyte/yw/common/PlacementInfoUtilTest.java b/managed/src/test/java/com/yugabyte/yw/common/PlacementInfoUtilTest.java index 9dab707eace4..0f6086007be7 100644 --- a/managed/src/test/java/com/yugabyte/yw/common/PlacementInfoUtilTest.java +++ b/managed/src/test/java/com/yugabyte/yw/common/PlacementInfoUtilTest.java @@ -4612,6 +4612,43 @@ public void testDedicatedIncreaseRF() { assertEquals(2, addedMasters.get()); } + @Test + public void testConfigureIncreaseRF6nodes() { + Customer customer = ModelFactory.testCustomer("Test Customer"); + Provider provider = ModelFactory.newProvider(customer, CloudType.aws); + + Universe existing = createFromConfig(provider, "Existing", "r1-az1-6-3"); + Region region = getOrCreate(provider, "r1"); + AvailabilityZone az1 = AvailabilityZone.getByCode(provider, "az1"); + + AvailabilityZone az2 = AvailabilityZone.createOrThrow(region, "az2", "az2", "subnet-az2"); + + UniverseDefinitionTaskParams params = new UniverseDefinitionTaskParams(); + params.setUniverseUUID(existing.getUniverseUUID()); + params.currentClusterType = ClusterType.PRIMARY; + params.clusters = existing.getUniverseDetails().clusters; + params.getPrimaryCluster().userIntent.numNodes = 7; + params.getPrimaryCluster().userIntent.replicationFactor = 7; + params.nodeDetailsSet = new HashSet<>(existing.getUniverseDetails().nodeDetailsSet); + + PlacementInfoUtil.updateUniverseDefinition( + params, customer.getId(), params.getPrimaryCluster().uuid, EDIT); + + List addedNodes = + params.nodeDetailsSet.stream() + .filter(n -> n.state == NodeState.ToBeAdded) + .collect(Collectors.toList()); + + assertEquals(1, addedNodes.size()); + + assertEquals(7, params.nodeDetailsSet.size()); + assertEquals(az2.getUuid(), addedNodes.get(0).azUuid); + assertEquals( + 1, params.getPrimaryCluster().placementInfo.findByAZUUID(az2.getUuid()).replicationFactor); + assertEquals( + 6, params.getPrimaryCluster().placementInfo.findByAZUUID(az1.getUuid()).replicationFactor); + } + private void markNodeInstancesAsOccupied(Map azUuidToNumNodes) { Map counts = new HashMap<>(azUuidToNumNodes); for (NodeInstance nodeInstance : NodeInstance.getAll()) { From 4af129fe08a3d9fb192f4dad7a48bd3a1233ae8c Mon Sep 17 00:00:00 2001 From: Lingeshwar S Date: Thu, 18 Jul 2024 11:26:05 +0530 Subject: [PATCH 1179/1195] [PLAT-14685] - improvement : Disable Gflags change if gflags belongs to ENHANCED_POSTGRES_COMPATIBILITY group Summary: Do not allow gflags value change if ENHANCED POSTGRES COMPATIBILITY is turned on. - Handled in Create Universe with and without RR flows. - Handled in Edit Flags flow Test Plan: Tested manually {F266264} Reviewers: kkannan Reviewed By: kkannan Subscribers: ui, yugaware Differential Revision: https://phorge.dev.yugabyte.com/D36678 --- .../universes/UniverseForm/AddGFlag.js | 41 ++++-- .../universes/UniverseForm/UniverseForm.scss | 26 +++- .../edit-gflags/EditGflags.tsx | 6 + .../edit-gflags/GflagHelper.ts | 21 +++ .../form/fields/GflagsField/GflagsField.tsx | 134 +++++++++++------- .../form/sections/gflags/Gflags.tsx | 8 +- managed/ui/src/redesign/utils/api.ts | 10 +- managed/ui/src/translations/en.json | 3 +- 8 files changed, 179 insertions(+), 70 deletions(-) diff --git a/managed/ui/src/components/universes/UniverseForm/AddGFlag.js b/managed/ui/src/components/universes/UniverseForm/AddGFlag.js index f4667293ba1a..d95802ec69e8 100644 --- a/managed/ui/src/components/universes/UniverseForm/AddGFlag.js +++ b/managed/ui/src/components/universes/UniverseForm/AddGFlag.js @@ -1,6 +1,5 @@ import { useEffect, useState } from 'react'; -import _ from 'lodash'; -import { useSelector } from 'react-redux'; +import { get } from 'lodash'; import { Field } from 'formik'; import clsx from 'clsx'; import { ListGroupItem, ListGroup, Row, Col, Badge } from 'react-bootstrap'; @@ -15,11 +14,11 @@ import { isDefinedNotNull } from '../../../utils/ObjectUtils'; //Icons import Bulb from '../images/bulb.svg'; import BookOpen from '../images/book_open.svg'; +import WarningIcon from '../../../redesign/assets/warning-triangle.svg'; // Styles import './UniverseForm.scss'; -const AddGFlag = ({ formProps, gFlagProps, updateJWKSDialogStatus }) => { - const featureFlags = useSelector((state) => state.featureFlags); +const AddGFlag = ({ formProps, gFlagProps, updateJWKSDialogStatus, disabledFlags = {} }) => { const { mode, server, dbVersion, existingFlags, isGFlagMultilineConfEnabled } = gFlagProps; const [searchVal, setSearchVal] = useState(''); const [isLoading, setLoader] = useState(true); @@ -36,7 +35,7 @@ const AddGFlag = ({ formProps, gFlagProps, updateJWKSDialogStatus }) => { const handleFlagSelect = (flag) => { let flagvalue = null; - const existingFlagValue = _.get( + const existingFlagValue = get( existingFlags.find((f) => f.Name === flag?.name), server ); @@ -125,17 +124,29 @@ const AddGFlag = ({ formProps, gFlagProps, updateJWKSDialogStatus }) => { const infoText = (
    - -- + Bulb   - Start typing the Flag’s name in the search field above to find the Flag you are looking for + Start typing the Flag's name in the search field above to find the Flag you are looking + for + +
    + ); + + const pgBanner = ( +
    + Warning +   + + You can't change this flag because it was set using the Enhanced Postgres Compatibility + option.
    ); const documentationLink = ( - --{' '} + Book{' '}
    { ? 'default' : 'target'; + const disableInputs = get(disabledFlags, selectedFlag?.name, false); switch (flag?.type) { case 'bool': return ( @@ -173,6 +185,7 @@ const AddGFlag = ({ formProps, gFlagProps, updateJWKSDialogStatus }) => { onChange={() => formProps.setFieldValue('flagvalue', target)} value={`${target}`} checked={`${target}` === `${formProps?.values['flagvalue']}`} + disabled={disableInputs} />{' '} {`${target}`}{' '} @@ -197,7 +210,15 @@ const AddGFlag = ({ formProps, gFlagProps, updateJWKSDialogStatus }) => { /> ); } else { - return ; + return ( + + ); } default: @@ -209,6 +230,7 @@ const AddGFlag = ({ formProps, gFlagProps, updateJWKSDialogStatus }) => { label={valueLabel} component={YBFormInput} step="any" + disabled={disableInputs} /> ); } @@ -283,6 +305,7 @@ const AddGFlag = ({ formProps, gFlagProps, updateJWKSDialogStatus }) => { const defaultKey = selectedFlag?.hasOwnProperty('current') ? 'current' : 'default'; return ( <> + {get(disabledFlags, selectedFlag?.name, false) && pgBanner}
    Flag Details {renderFieldInfo('Name', selectedFlag?.name)} diff --git a/managed/ui/src/components/universes/UniverseForm/UniverseForm.scss b/managed/ui/src/components/universes/UniverseForm/UniverseForm.scss index 62a6a008acee..eb5a028a0427 100644 --- a/managed/ui/src/components/universes/UniverseForm/UniverseForm.scss +++ b/managed/ui/src/components/universes/UniverseForm/UniverseForm.scss @@ -131,6 +131,20 @@ justify-content: center; } +.pg-banner { + width: 100%; + height: fit-content; + display: inline-flex; + align-items: flex-start; + padding: 8px; + border-radius: 8px; + background-color: #ffeec8; + margin-bottom: 16px; + font-weight: 400; + color: #0b1117; + font-size: 13px; +} + .gflag-detail-container { flex-shrink: 1; display: flex; @@ -203,7 +217,7 @@ display: none; } -.table-val-column:hover>.icons>.flag-icons { +.table-val-column:hover > .icons > .flag-icons { display: block; } @@ -213,7 +227,7 @@ margin-left: 2px; } -.table-val-column:hover>.icons>.more-icon { +.table-val-column:hover > .icons > .more-icon { display: none; } @@ -243,7 +257,7 @@ display: none; } -.table-val-column:hover>.flag-icon-button>.add-label { +.table-val-column:hover > .flag-icon-button > .add-label { display: inline; font-size: 11.5px; font-family: 400; @@ -295,7 +309,7 @@ flex-shrink: 0; } -.error-val-column:hover>.icons>.flag-icons { +.error-val-column:hover > .icons > .flag-icons { display: block; } @@ -333,8 +347,8 @@ border-bottom: 1px solid #eeeeee; } -.gflag-edit-table .react-bs-table .table-bordered>thead>tr:first-child>th, -.gflag-read-table .react-bs-table .table-bordered>thead>tr:first-child>th { +.gflag-edit-table .react-bs-table .table-bordered > thead > tr:first-child > th, +.gflag-read-table .react-bs-table .table-bordered > thead > tr:first-child > th { padding-left: 16px !important; } diff --git a/managed/ui/src/redesign/features/universe/universe-actions/edit-gflags/EditGflags.tsx b/managed/ui/src/redesign/features/universe/universe-actions/edit-gflags/EditGflags.tsx index e265ac899b9e..a93b713837d0 100644 --- a/managed/ui/src/redesign/features/universe/universe-actions/edit-gflags/EditGflags.tsx +++ b/managed/ui/src/redesign/features/universe/universe-actions/edit-gflags/EditGflags.tsx @@ -30,6 +30,7 @@ import { useFormMainStyles } from '../../universe-form/universeMainStyle'; import { RBAC_ERR_MSG_NO_PERM } from '../../../rbac/common/validator/ValidatorUtils'; import { hasNecessaryPerm } from '../../../rbac/common/RbacApiPermValidator'; import { ApiPermissionMap } from '../../../rbac/ApiAndUserPermMapping'; +import { isPGEnabledFromIntent } from '../../universe-form/utils/helpers'; interface EditGflagsModalProps { open: boolean; @@ -100,6 +101,9 @@ export const EditGflagsModal: FC = ({ const currentVersion = getCurrentVersion(universeDetails) || ''; const { gFlags, asyncGflags, inheritFlagsFromPrimary } = transformToEditFlagsForm(universeData); const initialGflagSet = useRef({ gFlags, asyncGflags, inheritFlagsFromPrimary }); + const isPGSupported = primaryCluster?.userIntent + ? isPGEnabledFromIntent(primaryCluster.userIntent) + : false; const formMethods = useForm({ defaultValues: { @@ -338,6 +342,7 @@ export const EditGflagsModal: FC = ({ isReadOnly={!canEditGFlags} tableMaxHeight={!asyncCluster ? '420px' : inheritFromPrimary ? '362px' : '296px'} isGFlagMultilineConfEnabled={isGFlagMultilineConfEnabled} + isPGSupported={isPGSupported} /> )} {!isPrimary && ( @@ -350,6 +355,7 @@ export const EditGflagsModal: FC = ({ isReadOnly={!canEditGFlags} tableMaxHeight={!asyncCluster ? '412px' : inheritFromPrimary ? '354px' : '288px'} isGFlagMultilineConfEnabled={isGFlagMultilineConfEnabled} + isPGSupported={isPGSupported} /> )} diff --git a/managed/ui/src/redesign/features/universe/universe-actions/edit-gflags/GflagHelper.ts b/managed/ui/src/redesign/features/universe/universe-actions/edit-gflags/GflagHelper.ts index c6ce07f50abe..b23e9ad25120 100644 --- a/managed/ui/src/redesign/features/universe/universe-actions/edit-gflags/GflagHelper.ts +++ b/managed/ui/src/redesign/features/universe/universe-actions/edit-gflags/GflagHelper.ts @@ -1,3 +1,4 @@ +import { isEmpty } from 'lodash'; import { getPrimaryCluster, getAsyncCluster, @@ -31,6 +32,14 @@ export interface EditGflagPayload { ybSoftwareVersion: string; } +export interface GFlagGroupObject { + group_name: string; + flags: { + MASTER: Record; + TSERVER: Record; + }; +} + export const transformToEditFlagsForm = (universeData: Universe) => { const { universeDetails } = universeData; const editGflagsFormData: Partial = { @@ -59,3 +68,15 @@ export const transformToEditFlagsForm = (universeData: Universe) => { } return editGflagsFormData; }; + +export const getFlagsByGroupName = ( + gFlagGroupsArray: GFlagGroupObject[] | undefined, + groupName: string +) => { + if (!gFlagGroupsArray) return {}; + const currentGroup = gFlagGroupsArray.find((gf) => gf.group_name === groupName); + let gFlagList = {}; + if (currentGroup?.flags?.MASTER) gFlagList = { ...gFlagList, ...currentGroup.flags.MASTER }; + if (currentGroup?.flags?.TSERVER) gFlagList = { ...gFlagList, ...currentGroup.flags.TSERVER }; + return gFlagList; +}; diff --git a/managed/ui/src/redesign/features/universe/universe-form/form/fields/GflagsField/GflagsField.tsx b/managed/ui/src/redesign/features/universe/universe-form/form/fields/GflagsField/GflagsField.tsx index e5d57ce86c0d..3dd2067506ac 100644 --- a/managed/ui/src/redesign/features/universe/universe-form/form/fields/GflagsField/GflagsField.tsx +++ b/managed/ui/src/redesign/features/universe/universe-form/form/fields/GflagsField/GflagsField.tsx @@ -2,6 +2,8 @@ import { useState, ReactElement } from 'react'; import _ from 'lodash'; import * as Yup from 'yup'; import clsx from 'clsx'; +import { useQuery } from 'react-query'; +import { toast } from 'react-toastify'; import { useSelector } from 'react-redux'; import { Box } from '@material-ui/core'; import { useFieldArray } from 'react-hook-form'; @@ -25,6 +27,13 @@ import { useWhenMounted } from '../../../../../../helpers/hooks'; import { validateGFlags } from '../../../../../../../actions/universe'; import { Gflag } from '../../../utils/dto'; import { MULTILINE_GFLAGS_ARRAY } from '../../../../../../../utils/UniverseUtils'; +import { + GFlagGroupObject, + getFlagsByGroupName +} from '../../../../universe-actions/edit-gflags/GflagHelper'; +import { api, QUERY_KEY } from '../../../../../../utils/api'; +import { isVersionPGSupported } from '../../../utils/helpers'; +import { GFLAG_GROUPS } from '../../../../../../helpers/constants'; //Icons import Edit from '../../../../../../assets/edit_pen.svg'; import Close from '../../../../../../assets/close.svg'; @@ -48,6 +57,7 @@ interface GflagsFieldProps { isReadReplica: boolean; tableMaxHeight?: string; isGFlagMultilineConfEnabled: boolean; + isPGSupported: boolean; } interface SelectedOption { @@ -109,7 +119,8 @@ export const GFlagsField = ({ isReadOnly = false, isReadReplica = false, tableMaxHeight, - isGFlagMultilineConfEnabled + isGFlagMultilineConfEnabled, + isPGSupported }: GflagsFieldProps): ReactElement => { const { fields, append, insert, remove } = useFieldArray({ name: fieldPath as any, @@ -126,6 +137,15 @@ export const GFlagsField = ({ const featureFlags = useSelector((state: any) => state.featureFlags); const enableRRGflags = featureFlags.test.enableRRGflags || featureFlags.released.enableRRGflags; + const { data: pgFlags, isLoading } = useQuery( + [QUERY_KEY.getGflagGroups], + () => api.getGflagGroups(dbVersion, GFLAG_GROUPS.ENHANCED_POSTGRES_COMPATIBILITY), + { + enabled: isPGSupported && isVersionPGSupported(dbVersion) + } + ); + + const pgGroupFlags = getFlagsByGroupName(pgFlags, GFLAG_GROUPS.ENHANCED_POSTGRES_COMPATIBILITY); //options Array -- TO DRY THE CODE const OPTIONS = [ { @@ -202,59 +222,64 @@ export const GFlagsField = ({ }; const handleFormSubmit = (values: any, actions: any) => { - switch (values.option) { - case FREE_TEXT: { - try { - const formValues = JSON.parse(values?.flagvalue); - const newFlagArr: Gflag[] = []; - if (Object.keys(formValues).length > 0) { - Object.entries(formValues).forEach(([key, val]) => { - const obj = { Name: key, [values?.server]: val }; - checkExistsAndPush(obj); - newFlagArr.push(obj); - }); - callValidation(newFlagArr); - setToggleModal(false); + if (_.get(pgGroupFlags, values?.flagname || '', false)) { + toast.error(t('universeForm.gFlags.pgGroupWarning')); + actions.setSubmitting(false); + } else { + switch (values.option) { + case FREE_TEXT: { + try { + const formValues = JSON.parse(values?.flagvalue); + const newFlagArr: Gflag[] = []; + if (Object.keys(formValues).length > 0) { + Object.entries(formValues).forEach(([key, val]) => { + const obj = { Name: key, [values?.server]: val }; + checkExistsAndPush(obj); + newFlagArr.push(obj); + }); + callValidation(newFlagArr); + setToggleModal(false); + } + } catch (e) { + setFormError(t('universeForm.gFlags.validationError')); + setTimeout(() => { + setFormError(null); + actions.setSubmitting(false); + }, 5000); } - } catch (e) { - setFormError(t('universeForm.gFlags.validationError')); - setTimeout(() => { - setFormError(null); - actions.setSubmitting(false); - }, 5000); + break; } - break; - } - case ADD_GFLAG: { - const obj: AddGFlagConfObject = { - Name: values?.flagname, - [values?.server]: values?.flagvalue - }; - if (MULTILINE_GFLAGS_ARRAY.includes(values?.server)) { - // In case of any multi-line csv flags, the below variables - // will have concatenated string and preview flag value to be displayed - if (values?.server === TSERVER) { - obj.tserverFlagDetails = { - ConfValue: values?.tserverFlagDetails?.flagvalueobject, - PreviewConfValue: values?.tserverFlagDetails?.previewFlagValue - }; - } else { - obj.masterFlagDetails = { - ConfValue: values?.masterFlagDetails?.flagvalueobject, - PreviewConfValue: values?.masterFlagDetails?.previewFlagValue - }; + case ADD_GFLAG: { + const obj: AddGFlagConfObject = { + Name: values?.flagname, + [values?.server]: values?.flagvalue + }; + if (MULTILINE_GFLAGS_ARRAY.includes(values?.server)) { + // In case of any multi-line csv flags, the below variables + // will have concatenated string and preview flag value to be displayed + if (values?.server === TSERVER) { + obj.tserverFlagDetails = { + ConfValue: values?.tserverFlagDetails?.flagvalueobject, + PreviewConfValue: values?.tserverFlagDetails?.previewFlagValue + }; + } else { + obj.masterFlagDetails = { + ConfValue: values?.masterFlagDetails?.flagvalueobject, + PreviewConfValue: values?.masterFlagDetails?.previewFlagValue + }; + } } + + checkExistsAndPush(obj); + callValidation([obj]); + setToggleModal(false); + break; } - checkExistsAndPush(obj); - callValidation([obj]); - setToggleModal(false); - break; + default: + break; } - - default: - break; } }; @@ -513,6 +538,7 @@ export const GFlagsField = ({ ); }; @@ -599,12 +628,19 @@ export const GFlagsField = ({ {dbVersion}) )} - + {isLoading && Loading Gflag groups ...} + {!isReadOnly && OPTIONS.map((option) => { const { optionName, ...rest } = option; return ( - + {SERVER_LIST.filter((e) => e.visible).map((server) => { const { serverName, label } = server; const serverProps = { diff --git a/managed/ui/src/redesign/features/universe/universe-form/form/sections/gflags/Gflags.tsx b/managed/ui/src/redesign/features/universe/universe-form/form/sections/gflags/Gflags.tsx index 9d086dd1e4d6..2fedffe87c83 100644 --- a/managed/ui/src/redesign/features/universe/universe-form/form/sections/gflags/Gflags.tsx +++ b/managed/ui/src/redesign/features/universe/universe-form/form/sections/gflags/Gflags.tsx @@ -8,7 +8,6 @@ import { GFlagsField } from '../../fields'; import { YBToggleField } from '../../../../../../components'; import { ReadOnlyGflagsModal } from './ReadOnlyGflagsModal'; import { - CloudType, ClusterModes, ClusterType, RunTimeConfigEntry, @@ -16,10 +15,10 @@ import { UniverseFormData } from '../../../utils/dto'; import { - PROVIDER_FIELD, SOFTWARE_VERSION_FIELD, GFLAGS_FIELD, - INHERIT_FLAGS_FROM_PRIMARY + INHERIT_FLAGS_FROM_PRIMARY, + PG_COMPATIBILITY_FIELD } from '../../../utils/constants'; import { RuntimeConfigKey } from '../../../../../../helpers/constants'; import { useSectionStyles } from '../../../universeMainStyle'; @@ -81,9 +80,9 @@ export const GFlags = ({ runtimeConfigs }: UniverseFormConfigurationProps) => { //form Data const { control, getValues } = useFormContext>(); - const provider = useWatch({ name: PROVIDER_FIELD }); const dbVersion = useWatch({ name: SOFTWARE_VERSION_FIELD }); const isInherited = useWatch({ name: INHERIT_FLAGS_FROM_PRIMARY }); + const isPGSupported = useWatch({ name: PG_COMPATIBILITY_FIELD }); if (isEditPrimary && _.isEmpty(getValues(GFLAGS_FIELD))) return null; if (!enableRRGflags && !isPrimary) return null; @@ -136,6 +135,7 @@ export const GFlags = ({ runtimeConfigs }: UniverseFormConfigurationProps) => { isReadReplica={!isPrimary} isReadOnly={isEditMode} isGFlagMultilineConfEnabled={isGFlagMultilineConfEnabled} + isPGSupported={isPGSupported} /> )} diff --git a/managed/ui/src/redesign/utils/api.ts b/managed/ui/src/redesign/utils/api.ts index 2e85cac9a8e6..aed015a9eaed 100644 --- a/managed/ui/src/redesign/utils/api.ts +++ b/managed/ui/src/redesign/utils/api.ts @@ -23,6 +23,7 @@ import { EncryptionInTransitFormValues } from '../features/universe/universe-act import { ReplicationSlotResponse } from '../features/universe/universe-tabs/replication-slots/utils/types'; import { ExportLogPayload, ExportLogResponse } from '../features/export-log/utils/types'; import { AuditLogPayload } from '../features/universe/universe-tabs/db-audit-logs/utils/types'; +import { GFlagGroupObject } from '../features/universe/universe-actions/edit-gflags/GflagHelper'; // define unique names to use them as query keys export enum QUERY_KEY { @@ -39,7 +40,8 @@ export enum QUERY_KEY { getReplicationSlots = 'getReplicationSlots', getSessionInfo = 'getSessionInfo', getAllTelemetryProviders = 'getAllTelemetryProviders', - getTelemetryProviderByID = 'getTelemetryProviderByID' + getTelemetryProviderByID = 'getTelemetryProviderByID', + getGflagGroups = 'getGflagGroups' } class ApiService { @@ -148,6 +150,7 @@ class ApiService { const requestUrl = `${ROOT_URL}/session_info`; return axios.get(requestUrl).then((resp) => resp.data); }; + getAllTelemetryProviders = (): Promise => { const requestUrl = `${ROOT_URL}/customers/${this.getCustomerId()}/telemetry_provider`; return axios.get(requestUrl).then((resp) => resp.data); @@ -172,6 +175,11 @@ class ApiService { const requestUrl = `${ROOT_URL}/customers/${this.getCustomerId()}/telemetry_provider/${tpID}`; return axios.delete(requestUrl).then((resp) => resp.data); }; + + getGflagGroups = (dbVersion: string, groupName: string): Promise => { + const requestUrl = `${ROOT_URL}/metadata/version/${dbVersion}/gflag_groups?group=${groupName}`; + return axios.get(requestUrl).then((resp) => resp.data); + }; } export const api = new ApiService(); diff --git a/managed/ui/src/translations/en.json b/managed/ui/src/translations/en.json index bbd5575c9881..85a5bd9b7bce 100644 --- a/managed/ui/src/translations/en.json +++ b/managed/ui/src/translations/en.json @@ -395,7 +395,8 @@ "flagsModifiedModalBody1": "The Gflags has been modified in another tab/browser/device.", "flagsModifiedModalBody2": "If you don't want to lose modified flags, please reload the page.", "flagsModifiedModalLine1": "Do you want to reload ?", - "flagsModifiedModalLine2": "Note that current changes will be lost." + "flagsModifiedModalLine2": "Note that current changes will be lost.", + "pgGroupWarning": "You can't change this flag because it was set using the Enhanced Postgres Compatibility option" }, "helmOverrides": { "addK8sOverrides": "Add Kubernetes Overrides", From a64fbfc52208e5e1af7a27ddb3c2490a46505fbb Mon Sep 17 00:00:00 2001 From: Bvsk Patnaik Date: Wed, 17 Jul 2024 14:38:09 -0700 Subject: [PATCH 1180/1195] [#23120] YSQL: Do not wait for a safe snapshot in serializable read only deferrable mode. Summary: PostgreSQL uses SSI to implement serializable isolation level. Read only serializable isolation transactions may abort other serializable transactions by creating a cycle in the transaction dependency graph. This can be avoided by waiting for all concurrent transactions to finish by using the READ ONLY DEFERRABLE mode. On the other hand, YugabyteDB does not use SSI for serializable transactions and uses 2-phase locking instead. And YB assigns a different meaning to the DEFERRABLE mode for READ ONLY serializable transactions as compared to Pg. In YB, DEFERRABLE READ ONLY transactions wait out the maximum possible clock skew to avoid read restart errors (see https://docs.yugabyte.com/preview/architecture/transactions/read-restart-error/). So, in YB's use of DEFERRABLE, there is no need to wait for concurrent transactions to complete, apart from waiting out the max_clock_skew_usec. Jira: DB-12053 Test Plan: Jenkins ``` ./yb_build.sh --java-test TestPgRegressWaitQueues ``` There are tests already in modify-transaction-characteristics that use a read only deferrable transaction for a select statement. Modified the test output to remove waiting. Reviewers: pjain, bkolagani, smishra Reviewed By: pjain Subscribers: yql Differential Revision: https://phorge.dev.yugabyte.com/D36671 --- .../src/backend/storage/lmgr/predicate.c | 24 +++++- .../modify-transaction-characteristics.out | 84 ++++++++----------- 2 files changed, 59 insertions(+), 49 deletions(-) diff --git a/src/postgres/src/backend/storage/lmgr/predicate.c b/src/postgres/src/backend/storage/lmgr/predicate.c index e8390311d03f..e1e4b31ea80f 100644 --- a/src/postgres/src/backend/storage/lmgr/predicate.c +++ b/src/postgres/src/backend/storage/lmgr/predicate.c @@ -204,6 +204,8 @@ #include "utils/snapmgr.h" #include "utils/tqual.h" +#include "pg_yb_utils.h" + /* Uncomment the next line to test the graceful degradation code. */ /* #define TEST_OLDSERXID */ @@ -1608,8 +1610,28 @@ GetSerializableTransactionSnapshot(Snapshot snapshot) * A special optimization is available for SERIALIZABLE READ ONLY * DEFERRABLE transactions -- we can wait for a suitable snapshot and * thereby avoid all SSI overhead once it's running. + * + * YB: PG uses SSI to implement serializable isolation level. + * YB uses 2PL (i.e., two phase locking) for the same. + * Both YB and PG expose a different functionality using + * the same DEFERRABLE keyword. + * + * In PG, READ ONLY DEFERRABLE allows a read only transaction + * to avoid serialization errors with concurrent serializable + * transactions, by waiting for the concurrent transactions to + * complete first. + * + * In YB, there is no cycle detection algorithm. Instead READ ONLY + * serializable transactions are essentially equivalent to a + * READ ONLY snapshot isolation transaction (see #23213). + * YB uses DEFERRABLE to allow READ ONLY transactions to wait out + * the maximum possible clock skew (i.e., max_clock_skew_usec) + * so as to avoid read restart errors. + * + * Given this difference, YB need not wait for a safe snapshot + * i.e., concurrent transactions need not be waited for. */ - if (XactReadOnly && XactDeferrable) + if (!YBTransactionsEnabled() && XactReadOnly && XactDeferrable) return GetSafeSnapshot(snapshot); return GetSerializableTransactionSnapshotInt(snapshot, diff --git a/src/postgres/src/test/isolation/expected/modify-transaction-characteristics.out b/src/postgres/src/test/isolation/expected/modify-transaction-characteristics.out index 97ba5e902c3f..6d1c9e3a72c5 100644 --- a/src/postgres/src/test/isolation/expected/modify-transaction-characteristics.out +++ b/src/postgres/src/test/isolation/expected/modify-transaction-characteristics.out @@ -507,12 +507,11 @@ step s2_read_only: SET TRANSACTION READ ONLY; step s2_deferrable: SET TRANSACTION DEFERRABLE; step s1_begin_sr_method1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; step s1_update: UPDATE test SET v=v+1 WHERE k=1; -step s2_select: SELECT * FROM test; -step s1_commit: COMMIT; -step s2_select: <... completed> +step s2_select: SELECT * FROM test; k v -1 3 +1 2 +step s1_commit: COMMIT; step s2_commit: COMMIT; starting permutation: s2_begin_rc_method2 s2_switch_to_sr s2_read_only s2_deferrable s1_begin_sr_method1 s1_update s2_select s1_commit s2_commit @@ -522,12 +521,11 @@ step s2_read_only: SET TRANSACTION READ ONLY; step s2_deferrable: SET TRANSACTION DEFERRABLE; step s1_begin_sr_method1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; step s1_update: UPDATE test SET v=v+1 WHERE k=1; -step s2_select: SELECT * FROM test; -step s1_commit: COMMIT; -step s2_select: <... completed> +step s2_select: SELECT * FROM test; k v -1 3 +1 2 +step s1_commit: COMMIT; step s2_commit: COMMIT; starting permutation: s2_begin_rc_method3_part1 s2_method3_part2 s2_switch_to_sr s2_read_only s2_deferrable s1_begin_sr_method1 s1_update s2_select s1_commit s2_commit @@ -538,12 +536,11 @@ step s2_read_only: SET TRANSACTION READ ONLY; step s2_deferrable: SET TRANSACTION DEFERRABLE; step s1_begin_sr_method1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; step s1_update: UPDATE test SET v=v+1 WHERE k=1; -step s2_select: SELECT * FROM test; -step s1_commit: COMMIT; -step s2_select: <... completed> +step s2_select: SELECT * FROM test; k v -1 3 +1 2 +step s1_commit: COMMIT; step s2_commit: COMMIT; starting permutation: s2_begin_rc_method4 s2_switch_to_sr s2_read_only s2_deferrable s1_begin_sr_method1 s1_update s2_select s1_commit s2_commit @@ -553,12 +550,11 @@ step s2_read_only: SET TRANSACTION READ ONLY; step s2_deferrable: SET TRANSACTION DEFERRABLE; step s1_begin_sr_method1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; step s1_update: UPDATE test SET v=v+1 WHERE k=1; -step s2_select: SELECT * FROM test; -step s1_commit: COMMIT; -step s2_select: <... completed> +step s2_select: SELECT * FROM test; k v -1 3 +1 2 +step s1_commit: COMMIT; step s2_commit: COMMIT; starting permutation: s2_begin_rr_method1 s2_switch_to_sr s2_read_only s2_deferrable s1_begin_sr_method1 s1_update s2_select s1_commit s2_commit @@ -568,12 +564,11 @@ step s2_read_only: SET TRANSACTION READ ONLY; step s2_deferrable: SET TRANSACTION DEFERRABLE; step s1_begin_sr_method1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; step s1_update: UPDATE test SET v=v+1 WHERE k=1; -step s2_select: SELECT * FROM test; -step s1_commit: COMMIT; -step s2_select: <... completed> +step s2_select: SELECT * FROM test; k v -1 3 +1 2 +step s1_commit: COMMIT; step s2_commit: COMMIT; starting permutation: s2_begin_rr_method2 s2_switch_to_sr s2_read_only s2_deferrable s1_begin_sr_method1 s1_update s2_select s1_commit s2_commit @@ -583,12 +578,11 @@ step s2_read_only: SET TRANSACTION READ ONLY; step s2_deferrable: SET TRANSACTION DEFERRABLE; step s1_begin_sr_method1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; step s1_update: UPDATE test SET v=v+1 WHERE k=1; -step s2_select: SELECT * FROM test; -step s1_commit: COMMIT; -step s2_select: <... completed> +step s2_select: SELECT * FROM test; k v -1 3 +1 2 +step s1_commit: COMMIT; step s2_commit: COMMIT; starting permutation: s2_begin_rr_method3_part1 s2_method3_part2 s2_switch_to_sr s2_read_only s2_deferrable s1_begin_sr_method1 s1_update s2_select s1_commit s2_commit @@ -599,12 +593,11 @@ step s2_read_only: SET TRANSACTION READ ONLY; step s2_deferrable: SET TRANSACTION DEFERRABLE; step s1_begin_sr_method1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; step s1_update: UPDATE test SET v=v+1 WHERE k=1; -step s2_select: SELECT * FROM test; -step s1_commit: COMMIT; -step s2_select: <... completed> +step s2_select: SELECT * FROM test; k v -1 3 +1 2 +step s1_commit: COMMIT; step s2_commit: COMMIT; starting permutation: s2_begin_rr_method4 s2_switch_to_sr s2_read_only s2_deferrable s1_begin_sr_method1 s1_update s2_select s1_commit s2_commit @@ -614,12 +607,11 @@ step s2_read_only: SET TRANSACTION READ ONLY; step s2_deferrable: SET TRANSACTION DEFERRABLE; step s1_begin_sr_method1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; step s1_update: UPDATE test SET v=v+1 WHERE k=1; -step s2_select: SELECT * FROM test; -step s1_commit: COMMIT; -step s2_select: <... completed> +step s2_select: SELECT * FROM test; k v -1 3 +1 2 +step s1_commit: COMMIT; step s2_commit: COMMIT; starting permutation: s2_begin_sr_method1 s2_switch_to_sr s2_read_only s2_deferrable s1_begin_sr_method1 s1_update s2_select s1_commit s2_commit @@ -629,12 +621,11 @@ step s2_read_only: SET TRANSACTION READ ONLY; step s2_deferrable: SET TRANSACTION DEFERRABLE; step s1_begin_sr_method1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; step s1_update: UPDATE test SET v=v+1 WHERE k=1; -step s2_select: SELECT * FROM test; -step s1_commit: COMMIT; -step s2_select: <... completed> +step s2_select: SELECT * FROM test; k v -1 3 +1 2 +step s1_commit: COMMIT; step s2_commit: COMMIT; starting permutation: s2_begin_sr_method2 s2_switch_to_sr s2_read_only s2_deferrable s1_begin_sr_method1 s1_update s2_select s1_commit s2_commit @@ -644,12 +635,11 @@ step s2_read_only: SET TRANSACTION READ ONLY; step s2_deferrable: SET TRANSACTION DEFERRABLE; step s1_begin_sr_method1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; step s1_update: UPDATE test SET v=v+1 WHERE k=1; -step s2_select: SELECT * FROM test; -step s1_commit: COMMIT; -step s2_select: <... completed> +step s2_select: SELECT * FROM test; k v -1 3 +1 2 +step s1_commit: COMMIT; step s2_commit: COMMIT; starting permutation: s2_begin_sr_method3_part1 s2_method3_part2 s2_switch_to_sr s2_read_only s2_deferrable s1_begin_sr_method1 s1_update s2_select s1_commit s2_commit @@ -660,12 +650,11 @@ step s2_read_only: SET TRANSACTION READ ONLY; step s2_deferrable: SET TRANSACTION DEFERRABLE; step s1_begin_sr_method1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; step s1_update: UPDATE test SET v=v+1 WHERE k=1; -step s2_select: SELECT * FROM test; -step s1_commit: COMMIT; -step s2_select: <... completed> +step s2_select: SELECT * FROM test; k v -1 3 +1 2 +step s1_commit: COMMIT; step s2_commit: COMMIT; starting permutation: s2_begin_sr_method4 s2_switch_to_sr s2_read_only s2_deferrable s1_begin_sr_method1 s1_update s2_select s1_commit s2_commit @@ -675,12 +664,11 @@ step s2_read_only: SET TRANSACTION READ ONLY; step s2_deferrable: SET TRANSACTION DEFERRABLE; step s1_begin_sr_method1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; step s1_update: UPDATE test SET v=v+1 WHERE k=1; -step s2_select: SELECT * FROM test; -step s1_commit: COMMIT; -step s2_select: <... completed> +step s2_select: SELECT * FROM test; k v -1 3 +1 2 +step s1_commit: COMMIT; step s2_commit: COMMIT; starting permutation: s1_begin_rc_method1 s1_switch_to_sr s1_read_only s1_read_write s2_update s1_commit From 9f82c017b59faa674364494e8630189567e1df52 Mon Sep 17 00:00:00 2001 From: Sandeep L <99765181+lingamsandeep@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:31:57 +0530 Subject: [PATCH 1181/1195] [#23188] DocDB: Persist new colocated_id mapping discovered as part of processing CHANGE_METADATA_OP in xCluster ClusterConfig. Summary: Problem: When a new table is added to a colocated database, the table needs to be created on both xCluster source and target with the same colocation_id. As part of processing the ChangeMetadataOp for the AddTable operation received from the source, the target creates a mapping of source->target schema versions of the newly created table. However, this is not getting persisted in ClusterConfig if the source/target schema versions are default values. As a result while replication may work immediately after setup, upon a restart of the T-server, the mapping of source-target schema versions may be lost and replication may stall until another schema change happens on the source. Fix: Fix is to detect the non-existent colocation_id correctly and persisting the ClusterConfig. Test Plan: ybt xcluster_ysql_colocated-test XClusterYsqlColocatedTest.DatabaseReplication The test was failing without the fix and is passing with the fix. Reviewers: hsunder, xCluster Reviewed By: hsunder Subscribers: ybase Differential Revision: https://phorge.dev.yugabyte.com/D36552 --- .../xcluster/xcluster_ysql_colocated-test.cc | 44 ++++++++++++++++--- src/yb/master/xrepl_catalog_manager.cc | 10 ++++- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/yb/integration-tests/xcluster/xcluster_ysql_colocated-test.cc b/src/yb/integration-tests/xcluster/xcluster_ysql_colocated-test.cc index 37fa87033231..a29acb61ff41 100644 --- a/src/yb/integration-tests/xcluster/xcluster_ysql_colocated-test.cc +++ b/src/yb/integration-tests/xcluster/xcluster_ysql_colocated-test.cc @@ -1,4 +1,4 @@ -// Copyright (c) YugabyteDB, Inc. +// Copyright (c) YugaByte, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at @@ -23,6 +23,7 @@ #include "yb/master/master_ddl.proxy.h" #include "yb/master/master_replication.proxy.h" #include "yb/master/mini_master.h" +#include "yb/tserver/mini_tablet_server.h" #include "yb/util/backoff_waiter.h" DECLARE_bool(xcluster_wait_on_ddl_alter); @@ -118,6 +119,7 @@ class XClusterYsqlColocatedTest : public XClusterYsqlTestBase { auto& tables = onlyColocated ? colocated_consumer_tables : consumer_tables_; for (const auto& consumer_table : tables) { LOG(INFO) << "Checking records for table " << consumer_table->name().ToString(); + RETURN_NOT_OK(WaitForRowCount(consumer_table->name(), num_results, &consumer_cluster_)); RETURN_NOT_OK(ValidateRows(consumer_table->name(), num_results, &consumer_cluster_)); } return true; @@ -196,20 +198,50 @@ class XClusterYsqlColocatedTest : public XClusterYsqlTestBase { [&]() -> Result { LOG(INFO) << "Checking records for table " << new_colocated_consumer_table->name().ToString(); - RETURN_NOT_OK( - ValidateRows(new_colocated_consumer_table->name(), kRecordBatch, &consumer_cluster_)); + RETURN_NOT_OK(WaitForRowCount( + new_colocated_consumer_table->name(), kRecordBatch, &consumer_cluster_)); + RETURN_NOT_OK(ValidateRows( + new_colocated_consumer_table->name(), kRecordBatch, &consumer_cluster_)); return true; }, MonoDelta::FromSeconds(20 * kTimeMultiplier), "IsDataReplicatedCorrectly new colocated table")); - // 6. Drop the new table and ensure that data is getting replicated correctly for + // 6. Shutdown the colocated tablet leader and verify that replication is still happening. + { + auto tablet_ids = ListTabletIdsForTable(consumer_cluster(), colocated_parent_table_id); + auto old_ts = FindTabletLeader(consumer_cluster(), *tablet_ids.begin()); + old_ts->Shutdown(); + const auto deadline = CoarseMonoClock::Now() + 10s * kTimeMultiplier; + RETURN_NOT_OK(WaitUntilTabletHasLeader(consumer_cluster(), *tablet_ids.begin(), deadline)); + RETURN_NOT_OK(old_ts->RestartStoppedServer()); + RETURN_NOT_OK(old_ts->WaitStarted()); + + RETURN_NOT_OK(InsertRowsInProducer( + kRecordBatch, 2 * kRecordBatch, new_colocated_producer_table, + use_transaction)); + + RETURN_NOT_OK(WaitFor( + [&]() -> Result { + LOG(INFO) << "Checking records for table " + << new_colocated_consumer_table->name().ToString(); + RETURN_NOT_OK(WaitForRowCount( + new_colocated_consumer_table->name(), 2 * kRecordBatch, &consumer_cluster_)); + RETURN_NOT_OK(ValidateRows( + new_colocated_consumer_table->name(), 2 * kRecordBatch, &consumer_cluster_)); + return true; + }, + MonoDelta::FromSeconds(20 * kTimeMultiplier), + "IsDataReplicatedCorrectly new colocated table")); + } + + // 7. Drop the new table and ensure that data is getting replicated correctly for // the other tables RETURN_NOT_OK( DropYsqlTable(&producer_cluster_, namespace_name, "", Format("test_table_$0", idx))); LOG(INFO) << Format("Dropped test_table_$0 on Producer side", idx); - // 7. Add additional data to the original tables. + // 8. Add additional data to the original tables. for (const auto& producer_table : producer_tables_) { LOG(INFO) << "Writing records for table " << producer_table->name().ToString(); RETURN_NOT_OK( @@ -217,7 +249,7 @@ class XClusterYsqlColocatedTest : public XClusterYsqlTestBase { } count += kRecordBatch; - // 8. Verify all tables are properly replicated. + // 9. Verify all tables are properly replicated. RETURN_NOT_OK(WaitFor( [&]() -> Result { return data_replicated_correctly(count, false); }, MonoDelta::FromSeconds(20 * kTimeMultiplier), diff --git a/src/yb/master/xrepl_catalog_manager.cc b/src/yb/master/xrepl_catalog_manager.cc index 322caab04986..fdf8e420a8b6 100644 --- a/src/yb/master/xrepl_catalog_manager.cc +++ b/src/yb/master/xrepl_catalog_manager.cc @@ -5670,16 +5670,22 @@ Status CatalogManager::UpdateConsumerOnProducerMetadata( schema_cached->Clear(); cdc::SchemaVersionsPB* schema_versions_pb = nullptr; + bool schema_versions_updated = false; // TODO (#16557): Support remove_table_id() for colocated tables / tablegroups. if (IsColocationParentTableId(consumer_table_id) && req->colocation_id() != kColocationIdNotSet) { auto map = stream_entry->mutable_colocated_schema_versions(); - schema_versions_pb = &((*map)[req->colocation_id()]); + schema_versions_pb = FindOrNull(*map, req->colocation_id()); + if (nullptr == schema_versions_pb) { + // If the colocation_id itself does not exist, it needs to be recorded in clusterconfig. + // This is to handle the case where source-target schema version mapping is 0:0. + schema_versions_updated = true; + schema_versions_pb = &((*map)[req->colocation_id()]); + } } else { schema_versions_pb = stream_entry->mutable_schema_versions(); } - bool schema_versions_updated = false; SchemaVersion current_producer_schema_version = schema_versions_pb->current_producer_schema_version(); SchemaVersion current_consumer_schema_version = From 152252289bee53440f18e6b7580d287adbf8c0cd Mon Sep 17 00:00:00 2001 From: Mikhail Bautin Date: Thu, 18 Jul 2024 00:02:26 -0700 Subject: [PATCH 1182/1195] Update third-party dependencies to remove CentOS 7 Summary: CentOS 7 has reached its EOL and has been removed from the third-party archive build matrix. Use Amazon Linux 2 instead of CentOS 7 as the "default" OS for third-party dependencies during a YugabyteDB build. With the removal of CentOS 7, Amazon Linux 2 currently has the oldest supported glibc version (2.26) of any Linux distribution that we use in our build matrix, so third-party pre-built archives built with Amazon Linux 2 can be used for a non-sanitizer YugabyteDB build with any supported Linux distribution. Also the new third-party version enables Clang 18 (not yet supported in the YugabyteDB build itself). Test Plan: Jenkins Reviewers: steve.varnau Reviewed By: steve.varnau Subscribers: ybase Differential Revision: https://phorge.dev.yugabyte.com/D36673 --- build-support/thirdparty_archives.yml | 71 ++++++++++++++++----------- python/yugabyte/thirdparty_tool.py | 15 +++--- 2 files changed, 50 insertions(+), 36 deletions(-) diff --git a/build-support/thirdparty_archives.yml b/build-support/thirdparty_archives.yml index bca1e6594407..2d202b7cc1ef 100644 --- a/build-support/thirdparty_archives.yml +++ b/build-support/thirdparty_archives.yml @@ -1,90 +1,101 @@ -sha: 31776c4936a67fe6f1fc218fb64a6a9909c77311 +sha: b6b07342fdfd4a65ee2608d75dd31e4b0ecc0737 archives: - os_type: almalinux8 architecture: x86_64 compiler_type: clang17 - tag: v20240620163555-31776c4936-almalinux8-x86_64-clang17 + tag: v20240713003527-b6b07342fd-almalinux8-x86_64-clang17 + + - os_type: almalinux8 + architecture: x86_64 + compiler_type: clang18 + tag: v20240713003521-b6b07342fd-almalinux8-x86_64-clang18 - os_type: almalinux8 architecture: x86_64 compiler_type: gcc11 - tag: v20240620163541-31776c4936-almalinux8-x86_64-gcc11 + tag: v20240713003520-b6b07342fd-almalinux8-x86_64-gcc11 - os_type: almalinux9 architecture: x86_64 compiler_type: clang17 - tag: v20240620163555-31776c4936-almalinux9-x86_64-clang17 + tag: v20240713003540-b6b07342fd-almalinux9-x86_64-clang17 - os_type: almalinux9 architecture: x86_64 compiler_type: gcc12 - tag: v20240620163623-31776c4936-almalinux9-x86_64-gcc12 + tag: v20240713003537-b6b07342fd-almalinux9-x86_64-gcc12 - - os_type: centos7 + - os_type: amzn2 architecture: aarch64 - compiler_type: clang16 - tag: v20240620163739-31776c4936-centos7-aarch64-clang16 + compiler_type: clang17 + tag: v20240713003725-b6b07342fd-amzn2-aarch64-clang17 - - os_type: centos7 + - os_type: amzn2 architecture: aarch64 - compiler_type: clang16 + compiler_type: clang17 lto_type: full - tag: v20240620163735-31776c4936-centos7-aarch64-clang16-full-lto + tag: v20240713003827-b6b07342fd-amzn2-aarch64-clang17-full-lto - - os_type: centos7 + - os_type: amzn2 architecture: aarch64 - compiler_type: clang17 - tag: v20240620163738-31776c4936-centos7-aarch64-clang17 + compiler_type: clang18 + tag: v20240713003831-b6b07342fd-amzn2-aarch64-clang18 - - os_type: centos7 + - os_type: amzn2 architecture: aarch64 - compiler_type: clang17 + compiler_type: clang18 lto_type: full - tag: v20240620163737-31776c4936-centos7-aarch64-clang17-full-lto + tag: v20240713003853-b6b07342fd-amzn2-aarch64-clang18-full-lto - - os_type: centos7 + - os_type: amzn2 architecture: x86_64 compiler_type: clang17 - tag: v20240620163551-31776c4936-centos7-x86_64-clang17 + tag: v20240713003538-b6b07342fd-amzn2-x86_64-clang17 - - os_type: centos7 + - os_type: amzn2 architecture: x86_64 compiler_type: clang17 lto_type: full - tag: v20240620163542-31776c4936-centos7-x86_64-clang17-full-lto + tag: v20240713003542-b6b07342fd-amzn2-x86_64-clang17-full-lto - - os_type: centos7 + - os_type: amzn2 architecture: x86_64 - compiler_type: gcc11 - tag: v20240620163544-31776c4936-centos7-x86_64-gcc11 + compiler_type: clang18 + tag: v20240713003544-b6b07342fd-amzn2-x86_64-clang18 + + - os_type: amzn2 + architecture: x86_64 + compiler_type: clang18 + lto_type: full + tag: v20240713003540-b6b07342fd-amzn2-x86_64-clang18-full-lto - os_type: macos architecture: arm64 compiler_type: clang - tag: v20240620173126-31776c4936-macos-arm64 + tag: v20240713011052-b6b07342fd-macos-arm64 - os_type: macos architecture: x86_64 compiler_type: clang - tag: v20240620163640-31776c4936-macos-x86_64 + tag: v20240713003540-b6b07342fd-macos-x86_64 - os_type: ubuntu20.04 architecture: x86_64 compiler_type: clang16 - tag: v20240620163547-31776c4936-ubuntu2004-x86_64-clang16 + tag: v20240713003520-b6b07342fd-ubuntu2004-x86_64-clang16 - os_type: ubuntu22.04 architecture: x86_64 compiler_type: clang17 - tag: v20240620163538-31776c4936-ubuntu2204-x86_64-clang17 + tag: v20240713003517-b6b07342fd-ubuntu2204-x86_64-clang17 - os_type: ubuntu22.04 architecture: x86_64 compiler_type: gcc11 - tag: v20240620163535-31776c4936-ubuntu2204-x86_64-gcc11 + tag: v20240713003527-b6b07342fd-ubuntu2204-x86_64-gcc11 - os_type: ubuntu23.04 architecture: x86_64 compiler_type: gcc13 - tag: v20240620163558-31776c4936-ubuntu2304-x86_64-gcc13 + tag: v20240713003516-b6b07342fd-ubuntu2304-x86_64-gcc13 diff --git a/python/yugabyte/thirdparty_tool.py b/python/yugabyte/thirdparty_tool.py index 1f08b6e814ca..27f641cfceaa 100755 --- a/python/yugabyte/thirdparty_tool.py +++ b/python/yugabyte/thirdparty_tool.py @@ -69,7 +69,9 @@ # These were incorrectly used without the "clang" prefix to indicate various versions of Clang. NUMBER_ONLY_VERSIONS_OF_CLANG = [str(i) for i in [12, 13, 14]] -PREFERRED_OS_TYPE = 'centos7' +# Linux distribution with the oldest glibc available to us. Third-party archives built on this +# OS can be used on newer Linux distributions, unless we need ASAN/TSAN. +PREFERRED_OS_TYPE = 'amzn2' ThirdPartyArchivesYAML = Dict[str, Union[str, List[Dict[str, str]]]] @@ -272,13 +274,14 @@ def is_consistent_with_yb_version(self, yb_version: str) -> bool: def should_skip_as_too_os_specific(self) -> bool: """ - Certain build types of specific OSes could be skipped because we can use the CentOS 7 build - instead. We can do that in cases we know we don't need to run ASAN/TSAN. We know that we - don't use ASAN/TSAN on aarch64 or for LTO builds as of 11/07/2022. Also we don't skip - Linuxbrew builds or GCC builds. + Certain build types of specific OSes could be skipped because we can use our "preferred OS + type", the supported Linux distribution with the oldest glibc version, instead. We can do + that in cases we know we don't need to run ASAN/TSAN. We know that we don't use ASAN/TSAN + on aarch64 or for LTO builds as of 11/07/2022. Also we don't skip Linuxbrew builds or GCC + builds. """ return ( - self.os_type != 'centos7' and + self.os_type != PREFERRED_OS_TYPE and self.compiler_type.startswith('clang') and # We handle Linuxbrew builds in a special way, e.g. they could be built on AlmaLinux 8. not self.is_linuxbrew and From cd7410c878c68ff136d78c21878c73561a9e9ba6 Mon Sep 17 00:00:00 2001 From: Dwight Hodge <79169168+ddhodge@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:23:08 -0400 Subject: [PATCH 1183/1195] 2.18.1.1 release notes (#23241) --- .../preview/releases/yba-releases/v2.18.md | 25 +++++++++++ .../preview/releases/ybdb-releases/v2.18.md | 41 +++++++++++++++++++ docs/data/currentVersions.json | 4 +- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/docs/content/preview/releases/yba-releases/v2.18.md b/docs/content/preview/releases/yba-releases/v2.18.md index baa8f0faadcd..d8476c216a96 100644 --- a/docs/content/preview/releases/yba-releases/v2.18.md +++ b/docs/content/preview/releases/yba-releases/v2.18.md @@ -33,6 +33,31 @@ What follows are the release notes for all releases in the **YugabyteDB Anywhere For an RSS feed of all release series to track the latest product updates, point your feed reader to the [RSS feed for releases](../index.xml). +## v2.18.8.1 - July 18, 2024 {#v2.18.8.1} + +**Build:** `2.18.8.1-b3` + +**Third-party licenses:** [YugabyteDB](https://downloads.yugabyte.com/releases/2.18.8.1/yugabytedb-2.18.8.1-b3-third-party-licenses.html), [YugabyteDB Anywhere](https://downloads.yugabyte.com/releases/2.18.8.1/yugabytedb-anywhere-2.18.8.1-b3-third-party-licenses.html) + +### Download + + + +For instructions on installing YugabyteDB Anywhere, refer to [Install YugabyteDB Anywhere](../../../yugabyte-platform/install-yugabyte-platform/). + +### Bug fixes + +#### Other + +* Repairs build failure in CentOS 7 pex/yugabundle builder Docker image. PLAT-14543 + ## v2.18.8.0 - June 21, 2024 {#v2.18.8.0} **Build:** `2.18.8.0-b42` diff --git a/docs/content/preview/releases/ybdb-releases/v2.18.md b/docs/content/preview/releases/ybdb-releases/v2.18.md index 36f8aee3308c..cb8c97dfcbbc 100644 --- a/docs/content/preview/releases/ybdb-releases/v2.18.md +++ b/docs/content/preview/releases/ybdb-releases/v2.18.md @@ -33,6 +33,47 @@ What follows are the release notes for the YugabyteDB v2.18 release series. Cont For an RSS feed of all release series to track the latest product updates, point your feed reader to the [RSS feed for releases](../index.xml). +## v2.18.8.1 - July 18, 2024 {#v2.18.8.1} + +**Build:** `2.18.8.1-b3` + +**Third-party licenses:** [YugabyteDB](https://downloads.yugabyte.com/releases/2.18.8.1/yugabytedb-2.18.8.1-b3-third-party-licenses.html), [YugabyteDB Anywhere](https://downloads.yugabyte.com/releases/2.18.8.1/yugabytedb-anywhere-2.18.8.1-b3-third-party-licenses.html) + +### Downloads + + + +### Docker + +```sh +docker pull yugabytedb/yugabyte:2.18.8.1-b3 +``` + +### Improvements + +#### DocDB + +* Allows asynchronous DNS cache updating and resolution retry upon failure to reduce RPC call delays and prevent unexpected leadership changes. {{}},{{}} + ## v2.18.8.0 - June 21, 2024 {#v2.18.8.0} **Build:** `2.18.8.0-b42` diff --git a/docs/data/currentVersions.json b/docs/data/currentVersions.json index e2dafb06bb82..b9ac51386077 100644 --- a/docs/data/currentVersions.json +++ b/docs/data/currentVersions.json @@ -40,9 +40,9 @@ { "series": "v2.18", "display": "v2.18 (STS)", - "version": "2.18.8.0", + "version": "2.18.8.1", "versionShort": "2.18.8", - "appVersion": "2.18.8.0-b42", + "appVersion": "2.18.8.1-b3", "isStable": true, "isLTS": false, "isSTS": true, From 550458d8f874d9363a1ccd6ee326dd9228b75e36 Mon Sep 17 00:00:00 2001 From: qhu Date: Tue, 16 Jul 2024 21:12:23 +0000 Subject: [PATCH 1184/1195] [#23047] docdb: Fix cotable ids in flushed frontier at restore Summary: When restoring a snopshot of a colocated tablet to a new database/table group, all tables are re-created in the new database so that the cotable ids are different from those in the snapshot. At restore, the cotables in flushed frontiers should be updated to the ids of newly created tables, otherwise we will probably hit the following issue after restore: ``` 1. we have 3 sst files after restore 1.sst (smallest:old_id=0, largest:old_id=0) 2.sst (smallest:old_id=0, largest:old_id=0) 3.sst (smallest:old_id=0, largest:old_id=0) 2. compact 1.sst and 2.sst and generate 4.sst 3.sst (smallest:old_id=0, largest:old_id=0) 4.sst (smallest:new_id=1, largest:new_id=1) After compaction, schema packing with version 0 for new_id can be dropped because from frontier we can only find new_id=1 3. When compact 3.sst and 4.sst there are still rows with version 0 for old_id but schema version 0 has been GCed in step 2 ``` Jira: DB-11979 Test Plan: PackedRows/YBBackupTestWithPackedRowsAndColocation.*/1 CrossColocationTests/YBBackupCrossColocation.TestYSQLRestoreWithInvalidIndex/1 TableRewriteTests/YBBackupTestWithTableRewrite.TestYSQLBackupAndRestoreAfterRewrite/1 TableRewriteTests/YBBackupTestWithTableRewrite.TestYSQLBackupAndRestoreAfterFailedRewrite/1 Reviewers: sergei, zdrudi, mhaddad Reviewed By: zdrudi Subscribers: ybase, qhu Differential Revision: https://phorge.dev.yugabyte.com/D36041 --- src/yb/docdb/consensus_frontier.cc | 14 +++++ src/yb/docdb/consensus_frontier.h | 4 ++ src/yb/docdb/docdb_rocksdb_util.cc | 16 +++-- src/yb/docdb/docdb_rocksdb_util.h | 7 ++- src/yb/tablet/tablet_metadata.cc | 21 ++++++- src/yb/tablet/tablet_metadata.h | 2 + src/yb/tablet/tablet_snapshots.cc | 61 +++++++++++++++---- src/yb/tablet/tablet_snapshots.h | 5 +- src/yb/tools/data-patcher.cc | 2 +- .../yb-backup/yb-backup-cross-feature-test.cc | 4 +- 10 files changed, 115 insertions(+), 21 deletions(-) diff --git a/src/yb/docdb/consensus_frontier.cc b/src/yb/docdb/consensus_frontier.cc index 7e1b8a83bb83..28acb5c738de 100644 --- a/src/yb/docdb/consensus_frontier.cc +++ b/src/yb/docdb/consensus_frontier.cc @@ -349,6 +349,20 @@ void ConsensusFrontier::ResetSchemaVersion() { cotable_schema_versions_.clear(); } +bool ConsensusFrontier::UpdateCoTableId(const Uuid& cotable_id, const Uuid& new_cotable_id) { + if (cotable_id == new_cotable_id) { + return false; + } + auto it = cotable_schema_versions_.find(cotable_id); + if (it == cotable_schema_versions_.end()) { + return false; + } + auto schema_version = it->second; + cotable_schema_versions_.erase(it); + cotable_schema_versions_[new_cotable_id] = schema_version; + return true; +} + void ConsensusFrontier::MakeExternalSchemaVersionsAtMost( std::unordered_map* min_schema_versions) const { if (primary_schema_version_) { diff --git a/src/yb/docdb/consensus_frontier.h b/src/yb/docdb/consensus_frontier.h index 963b6f23030c..3eda9a81b29a 100644 --- a/src/yb/docdb/consensus_frontier.h +++ b/src/yb/docdb/consensus_frontier.h @@ -120,6 +120,10 @@ class ConsensusFrontier : public rocksdb::UserFrontier { void AddSchemaVersion(const Uuid& table_id, SchemaVersion version); void ResetSchemaVersion(); + // Update cotable_id to new_cotable_id in current frontier's cotable_schema_versions_ map. + // Return true if the map is modified, otherwise, return false. + bool UpdateCoTableId(const Uuid& cotable_id, const Uuid& new_cotable_id); + // Merge current frontier with provided map, preferring min values. void MakeExternalSchemaVersionsAtMost( std::unordered_map* min_schema_versions) const; diff --git a/src/yb/docdb/docdb_rocksdb_util.cc b/src/yb/docdb/docdb_rocksdb_util.cc index d8fb2c23bbd6..9020a009cdf1 100644 --- a/src/yb/docdb/docdb_rocksdb_util.cc +++ b/src/yb/docdb/docdb_rocksdb_util.cc @@ -932,7 +932,8 @@ class RocksDBPatcher::Impl { return helper.Apply(options_, imm_cf_options_); } - Status ModifyFlushedFrontier(const ConsensusFrontier& frontier) { + Status ModifyFlushedFrontier( + const ConsensusFrontier& frontier, const CotableIdsMap& cotable_ids_map) { RocksDBPatcherHelper helper(&version_set_); docdb::ConsensusFrontier final_frontier = frontier; @@ -952,7 +953,8 @@ class RocksDBPatcher::Impl { helper.Edit().ModifyFlushedFrontier( final_frontier.Clone(), rocksdb::FrontierModificationMode::kForce); - helper.IterateFiles([&helper, &frontier](int level, rocksdb::FileMetaData fmd) { + helper.IterateFiles([&helper, &frontier, &cotable_ids_map]( + int level, rocksdb::FileMetaData fmd) { bool modified = false; for (auto* user_frontier : {&fmd.smallest.user_frontier, &fmd.largest.user_frontier}) { if (!*user_frontier) { @@ -967,6 +969,11 @@ class RocksDBPatcher::Impl { consensus_frontier.set_history_cutoff_information(frontier.history_cutoff()); modified = true; } + for (const auto& [table_id, new_table_id] : cotable_ids_map) { + if (consensus_frontier.UpdateCoTableId(table_id, new_table_id)) { + modified = true; + } + } } if (modified) { helper.ModifyFile(level, fmd); @@ -1043,8 +1050,9 @@ Status RocksDBPatcher::SetHybridTimeFilter(std::optional db_oid, Hybri return impl_->SetHybridTimeFilter(db_oid, value); } -Status RocksDBPatcher::ModifyFlushedFrontier(const ConsensusFrontier& frontier) { - return impl_->ModifyFlushedFrontier(frontier); +Status RocksDBPatcher::ModifyFlushedFrontier( + const ConsensusFrontier& frontier, const CotableIdsMap& cotable_ids_map) { + return impl_->ModifyFlushedFrontier(frontier, cotable_ids_map); } Status RocksDBPatcher::UpdateFileSizes() { diff --git a/src/yb/docdb/docdb_rocksdb_util.h b/src/yb/docdb/docdb_rocksdb_util.h index 095155b2a8e8..bdc032ce3da8 100644 --- a/src/yb/docdb/docdb_rocksdb_util.h +++ b/src/yb/docdb/docdb_rocksdb_util.h @@ -32,6 +32,10 @@ namespace yb { namespace docdb { +// Map from old cotable id to new cotable id. +// Used to restore snapshot to a new database/tablegroup and update cotable ids in the frontiers. +using CotableIdsMap = std::unordered_map; + const int kDefaultGroupNo = 0; dockv::KeyBytes AppendDocHt(Slice key, const DocHybridTime& doc_ht); @@ -135,7 +139,8 @@ class RocksDBPatcher { Status SetHybridTimeFilter(std::optional db_oid, HybridTime value); // Modify flushed frontier and clean up smallest/largest op id in per-SST file metadata. - Status ModifyFlushedFrontier(const ConsensusFrontier& frontier); + Status ModifyFlushedFrontier( + const ConsensusFrontier& frontier, const CotableIdsMap& cotable_ids_map); // Update file sizes in manifest if actual file size was changed because of direct manipulation // with .sst files. diff --git a/src/yb/tablet/tablet_metadata.cc b/src/yb/tablet/tablet_metadata.cc index 2bc4fa755a41..75c9577d83e6 100644 --- a/src/yb/tablet/tablet_metadata.cc +++ b/src/yb/tablet/tablet_metadata.cc @@ -703,6 +703,13 @@ Result RaftGroupMetadata::Load( return ret; } +Result RaftGroupMetadata::LoadFromPath( + FsManager* fs_manager, const std::string& path) { + RaftGroupMetadataPtr ret(new RaftGroupMetadata(fs_manager, "")); + RETURN_NOT_OK(ret->LoadFromDisk(path)); + return ret; +} + Result RaftGroupMetadata::TEST_LoadOrCreate( const RaftGroupMetadataData& data) { if (data.fs_manager->LookupTablet(data.raft_group_id)) { @@ -772,6 +779,18 @@ Result RaftGroupMetadata::GetTableInfoUnlocked(const TableId& tabl return iter->second; } +std::vector RaftGroupMetadata::GetColocatedTableInfos() const { + std::vector table_infos; + { + std::lock_guard lock(data_mutex_); + for (const auto& [_, table_info] : kv_store_.colocation_to_table) { + DCHECK(table_info->schema().has_colocation_id()); + table_infos.push_back(table_info); + } + } + return table_infos; +} + Result RaftGroupMetadata::GetTableInfoUnlocked(ColocationId colocation_id) const { if (colocation_id == kColocationIdNotSet) { return GetTableInfoUnlocked(primary_table_id_); @@ -965,7 +984,7 @@ Status RaftGroupMetadata::LoadFromSuperBlock(const RaftGroupReplicaSuperBlockPB& std::lock_guard lock(data_mutex_); // Verify that the Raft group id matches with the one in the protobuf. - if (superblock.raft_group_id() != raft_group_id_) { + if (!raft_group_id_.empty() && superblock.raft_group_id() != raft_group_id_) { return STATUS(Corruption, "Expected id=" + raft_group_id_ + " found " + superblock.raft_group_id(), superblock.DebugString()); diff --git a/src/yb/tablet/tablet_metadata.h b/src/yb/tablet/tablet_metadata.h index 2fcf8e0aaedd..0d7f4b34fd7c 100644 --- a/src/yb/tablet/tablet_metadata.h +++ b/src/yb/tablet/tablet_metadata.h @@ -313,6 +313,8 @@ class RaftGroupMetadata : public RefCountedThreadSafe, Result GetTableInfo(ColocationId colocation_id) const; Result GetTableInfoUnlocked(ColocationId colocation_id) const REQUIRES(data_mutex_); + std::vector GetColocatedTableInfos() const; + const RaftGroupId& raft_group_id() const { DCHECK_NE(state_, kNotLoadedYet); return raft_group_id_; diff --git a/src/yb/tablet/tablet_snapshots.cc b/src/yb/tablet/tablet_snapshots.cc index 0c5e8d47a5c9..1a93436d24cf 100644 --- a/src/yb/tablet/tablet_snapshots.cc +++ b/src/yb/tablet/tablet_snapshots.cc @@ -223,6 +223,10 @@ Env& TabletSnapshots::env() { return *metadata().fs_manager()->env(); } +FsManager* TabletSnapshots::fs_manager() { + return metadata().fs_manager(); +} + Status TabletSnapshots::CleanupSnapshotDir(const std::string& dir) { auto& env = this->env(); if (!env.FileExists(dir)) { @@ -361,21 +365,55 @@ Result TabletSnapshots::GenerateRestoreWriteBatch( } } +// Get the map of snapshot cotable ids to the current cotable ids. +// The restored flushed frontiers can have cotable ids that are different from current cotable ids. +// This map is used to update the cotable ids in the restored flushed frontiers. +Result TabletSnapshots::GetCotableIdsMap(const std::string& snapshot_dir) { + docdb::CotableIdsMap cotable_ids_map; + if (snapshot_dir.empty() || !metadata().colocated()) { + return cotable_ids_map; + } + auto snapshot_metadata_file = TabletMetadataFile(snapshot_dir); + if (!env().FileExists(snapshot_metadata_file)) { + return cotable_ids_map; + } + auto snapshot_metadata = + VERIFY_RESULT(RaftGroupMetadata::LoadFromPath(fs_manager(), snapshot_metadata_file)); + for (const auto& snapshot_table_info : snapshot_metadata->GetColocatedTableInfos()) { + auto current_table_info = metadata().GetTableInfo( + snapshot_table_info->schema().colocation_id()); + if (!current_table_info.ok()) { + if (!current_table_info.status().IsNotFound()) { + return current_table_info.status(); + } + LOG_WITH_PREFIX(WARNING) << "Table " << snapshot_table_info->table_id + << " not found: " << current_table_info.status(); + } else if ((*current_table_info)->cotable_id != snapshot_table_info->cotable_id) { + cotable_ids_map[snapshot_table_info->cotable_id] = (*current_table_info)->cotable_id; + } + } + if (!cotable_ids_map.empty()) { + LOG_WITH_PREFIX(INFO) << "Cotable ids map: " << yb::ToString(cotable_ids_map); + } + return cotable_ids_map; +} + Status TabletSnapshots::RestoreCheckpoint( - const std::string& dir, HybridTime restore_at, const RestoreMetadata& restore_metadata, + const std::string& snapshot_dir, HybridTime restore_at, const RestoreMetadata& restore_metadata, const docdb::ConsensusFrontier& frontier, bool is_pitr_restore, const OpId& op_id) { LongOperationTracker long_operation_tracker("Restore checkpoint", 5s); // The following two lines can't just be changed to RETURN_NOT_OK(PauseReadWriteOperations()): // op_pause has to stay in scope until the end of the function. - auto op_pauses = StartShutdownRocksDBs(DisableFlushOnShutdown(!dir.empty()), AbortOps::kTrue); + auto op_pauses = StartShutdownRocksDBs( + DisableFlushOnShutdown(!snapshot_dir.empty()), AbortOps::kTrue); std::lock_guard lock(create_checkpoint_lock()); const string db_dir = regular_db().GetName(); const std::string intents_db_dir = has_intents_db() ? intents_db().GetName() : std::string(); - if (dir.empty()) { + if (snapshot_dir.empty()) { // Just change rocksdb hybrid time limit, because it should be in retention interval. // TODO(pitr) apply transactions and reset intents. CompleteShutdownRocksDBs(op_pauses); @@ -385,7 +423,7 @@ Status TabletSnapshots::RestoreCheckpoint( RETURN_NOT_OK(DeleteRocksDBs(CompleteShutdownRocksDBs(op_pauses))); auto s = CopyDirectory( - &rocksdb_env(), dir, db_dir, UseHardLinks::kTrue, CreateIfMissing::kTrue); + &rocksdb_env(), snapshot_dir, db_dir, UseHardLinks::kTrue, CreateIfMissing::kTrue); if (PREDICT_FALSE(!s.ok())) { LOG_WITH_PREFIX(WARNING) << "Copy checkpoint files status: " << s; return STATUS(IllegalState, "Unable to copy checkpoint files", s.ToString()); @@ -402,7 +440,8 @@ Status TabletSnapshots::RestoreCheckpoint( docdb::RocksDBPatcher patcher(db_dir, rocksdb_options); RETURN_NOT_OK(patcher.Load()); - RETURN_NOT_OK(patcher.ModifyFlushedFrontier(frontier)); + RETURN_NOT_OK(patcher.ModifyFlushedFrontier( + frontier, VERIFY_RESULT(GetCotableIdsMap(snapshot_dir)))); if (restore_at) { RETURN_NOT_OK(patcher.SetHybridTimeFilter(std::nullopt, restore_at)); } @@ -434,14 +473,14 @@ Status TabletSnapshots::RestoreCheckpoint( need_flush = true; } - if (!dir.empty()) { - auto tablet_metadata_file = TabletMetadataFile(dir); + if (!snapshot_dir.empty()) { + auto snapshot_metadata_file = TabletMetadataFile(snapshot_dir); // Old snapshots could lack tablet metadata, so just do nothing in this case. - if (env().FileExists(tablet_metadata_file)) { - LOG_WITH_PREFIX(INFO) << "Merging metadata with restored: " << tablet_metadata_file + if (env().FileExists(snapshot_metadata_file)) { + LOG_WITH_PREFIX(INFO) << "Merging metadata with restored: " << snapshot_metadata_file << " , force overwrite of schema packing " << !is_pitr_restore; RETURN_NOT_OK(tablet().metadata()->MergeWithRestored( - tablet_metadata_file, + snapshot_metadata_file, is_pitr_restore ? dockv::OverwriteSchemaPacking::kFalse : dockv::OverwriteSchemaPacking::kTrue)); need_flush = true; @@ -461,7 +500,7 @@ Status TabletSnapshots::RestoreCheckpoint( return s; } - LOG_WITH_PREFIX(INFO) << "Checkpoint restored from " << dir; + LOG_WITH_PREFIX(INFO) << "Checkpoint restored from " << snapshot_dir; LOG_WITH_PREFIX(INFO) << "Re-enabling compactions"; s = tablet().EnableCompactions(&op_pauses.blocking_rocksdb_shutdown_start); if (!s.ok()) { diff --git a/src/yb/tablet/tablet_snapshots.h b/src/yb/tablet/tablet_snapshots.h index f700da6147fb..2db0286b3145 100644 --- a/src/yb/tablet/tablet_snapshots.h +++ b/src/yb/tablet/tablet_snapshots.h @@ -101,7 +101,7 @@ class TabletSnapshots : public TabletComponent { // Restore the RocksDB checkpoint from the provided directory. // Only used when table_type_ == YQL_TABLE_TYPE. Status RestoreCheckpoint( - const std::string& dir, HybridTime restore_at, const RestoreMetadata& metadata, + const std::string& snapshot_dir, HybridTime restore_at, const RestoreMetadata& metadata, const docdb::ConsensusFrontier& frontier, bool is_pitr_restore, const OpId& op_id); // Applies specified snapshot operation. @@ -109,12 +109,15 @@ class TabletSnapshots : public TabletComponent { Status CleanupSnapshotDir(const std::string& dir); Env& env(); + FsManager* fs_manager(); Status RestorePartialRows(SnapshotOperation* operation); Result GenerateRestoreWriteBatch( const tserver::TabletSnapshotOpRequestPB& request, docdb::DocWriteBatch* write_batch); + Result GetCotableIdsMap(const std::string& snapshot_dir); + std::string TEST_last_rocksdb_checkpoint_dir_; }; diff --git a/src/yb/tools/data-patcher.cc b/src/yb/tools/data-patcher.cc index 9733aa29089b..f49ff1f58fff 100644 --- a/src/yb/tools/data-patcher.cc +++ b/src/yb/tools/data-patcher.cc @@ -950,7 +950,7 @@ class ApplyPatch { frontier.set_history_cutoff_information( { HybridTime::FromMicros(kYugaByteMicrosecondEpoch), HybridTime::FromMicros(kYugaByteMicrosecondEpoch) }); - RETURN_NOT_OK(patcher.ModifyFlushedFrontier(frontier)); + RETURN_NOT_OK(patcher.ModifyFlushedFrontier(frontier, docdb::CotableIdsMap())); } else { LOG(INFO) << "We did not see RocksDB CURRENT or MANIFEST-... files in " << dir << ", skipping applying " << patched_path; diff --git a/src/yb/tools/yb-backup/yb-backup-cross-feature-test.cc b/src/yb/tools/yb-backup/yb-backup-cross-feature-test.cc index 5992fd462716..0e858bba3158 100644 --- a/src/yb/tools/yb-backup/yb-backup-cross-feature-test.cc +++ b/src/yb/tools/yb-backup/yb-backup-cross-feature-test.cc @@ -1850,10 +1850,10 @@ TEST_P( tserver::FlushTabletsRequestPB::COMPACT)); ASSERT_OK(RunBackupCommand( - {"--backup_location", backup_dir, "--keyspace", Format("ysql.$0", backup_db_name), + {"--backup_location", backup_dir, "--keyspace", Format("ysql.$0", restore_db_name), "restore"})); - SetDbName(backup_db_name); + SetDbName(restore_db_name); ASSERT_NO_FATALS( InsertRows(Format("INSERT INTO $0 VALUES (9,9,9), (10,10,10), (11,11,11)", table_name), 3)); From 225ddfefede231d338ed633c0ce04b6f90395ab8 Mon Sep 17 00:00:00 2001 From: Naorem Khogendro Singh Date: Wed, 17 Jul 2024 11:07:17 -0700 Subject: [PATCH 1185/1195] [PLAT-14700] Make node-agent error message on installation to be more precise MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: This error happens on verification with existing certs after the existence of node-agent on YBA is verified with the api token first. Test Plan: Verified by manually running it. ``` * Select your Zone. 1. Zone ID: f5d02b20-aed4-469d-823a-4c8e5f9fe2c8, Zone Code: us-west-2a 2. Zone ID: b7ff22b9-2278-475d-971d-607587a589ed, Zone Code: us-west-2b 3. Zone ID: 02267e7e-59f5-4bbb-8308-3d4b53c8cc7e, Zone Code: us-west-2c * The current value is Zone ID: 02267e7e-59f5-4bbb-8308-3d4b53c8cc7e, Zone Code: us-west-2c. Enter new option number or enter to skip: • Completed Node Agent Configuration • Checking for existing Node Agent with IP 10.9.101.164 ⨯ Node agent already exists on YBA but local credentials may be invalid. It may need to be unregistered first - open /home/ec2-user/node-agent/cert/node_agent.key: no such file or directory ``` Reviewers: cwang, sanketh, nbhatia Reviewed By: cwang Subscribers: yugaware Differential Revision: https://phorge.dev.yugabyte.com/D36664 --- managed/node-agent/cli/node/configure.go | 4 +++- managed/node-agent/util/certs_util.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/managed/node-agent/cli/node/configure.go b/managed/node-agent/cli/node/configure.go index fc49b5dca826..98b99ced8060 100644 --- a/managed/node-agent/cli/node/configure.go +++ b/managed/node-agent/cli/node/configure.go @@ -470,7 +470,9 @@ func configureEnabledEgress(ctx context.Context, cmd *cobra.Command) { util.ConsoleLogger().Fatalf(ctx, "Unable to register node agent - %s", err.Error()) } } else { - util.ConsoleLogger().Fatalf(ctx, "Unable to check for existing node agent - %s", err.Error()) + // Node agent already exists on YBA. But, it cannot be verified with the local key. + util.ConsoleLogger().Fatalf(ctx, "Node agent already exists on YBA but local "+ + "credentials may be invalid. It may need to be unregistered first - %s", err.Error()) } util.ConsoleLogger().Info(ctx, "Node Agent Configuration Successful") } diff --git a/managed/node-agent/util/certs_util.go b/managed/node-agent/util/certs_util.go index e5ebcc75984c..ce89dd7b15e4 100644 --- a/managed/node-agent/util/certs_util.go +++ b/managed/node-agent/util/certs_util.go @@ -156,7 +156,7 @@ func ServerKeyPath(config *Config) string { // The JWT is signed using the key in the certs directory. func GenerateJWT(ctx context.Context, config *Config) (string, error) { keyFilepath := ServerKeyPath(config) - privateKey, err := ioutil.ReadFile(keyFilepath) + privateKey, err := os.ReadFile(keyFilepath) if err != nil { FileLogger().Errorf(ctx, "Error while reading the private key: %s", err.Error()) return "", err From 9c9a0594be42bcd59882509c96edcd24ca627984 Mon Sep 17 00:00:00 2001 From: Aman Nijhawan Date: Fri, 19 Jul 2024 02:14:53 +0000 Subject: [PATCH 1186/1195] [PLAT-14563] Fixing disk mounting logic to use mount points defined in the config Summary: [PLAT-14563] Fixing disk mounting logic to use mount points defined in the config Test Plan: checked bash script, will need to run on test node Reviewers: svarshney Reviewed By: svarshney Subscribers: yugaware Differential Revision: https://phorge.dev.yugabyte.com/D36351 --- .../resources/node-agent-provision.yaml | 3 +- .../ynp/commands/provision_command.py | 1 - .../resources/ynp/configs/config.j2 | 3 +- .../configure_os/templates/precheck.j2 | 18 +++++- .../provision/configure_os/templates/run.j2 | 62 ------------------- 5 files changed, 19 insertions(+), 68 deletions(-) diff --git a/managed/node-agent/resources/node-agent-provision.yaml b/managed/node-agent/resources/node-agent-provision.yaml index 9c0b67b584e4..c8c93a921607 100644 --- a/managed/node-agent/resources/node-agent-provision.yaml +++ b/managed/node-agent/resources/node-agent-provision.yaml @@ -12,7 +12,6 @@ ynp: use_system_level_systemd: false ip_address: "127.0.0.1" tmp_directory: /tmp - mount_points: /data # Comma separated values. yba: url: @@ -32,3 +31,5 @@ yba: cores: cores memory_size: size volume_size: size + mount_points: + - /mnt/d1 diff --git a/managed/node-agent/resources/ynp/commands/provision_command.py b/managed/node-agent/resources/ynp/commands/provision_command.py index 3c8ae873e4a8..8cd23192950f 100644 --- a/managed/node-agent/resources/ynp/commands/provision_command.py +++ b/managed/node-agent/resources/ynp/commands/provision_command.py @@ -123,7 +123,6 @@ def _generate_template(self): context = self.config[key] context["templatedir"] = os.path.join(os.path.dirname(module[1]), "templates") - logger.info(context) module_instance = module[0]() rendered_template = module_instance.render_templates(context) if rendered_template is not None: diff --git a/managed/node-agent/resources/ynp/configs/config.j2 b/managed/node-agent/resources/ynp/configs/config.j2 index b93148165671..b70ec256f02e 100644 --- a/managed/node-agent/resources/ynp/configs/config.j2 +++ b/managed/node-agent/resources/ynp/configs/config.j2 @@ -32,7 +32,7 @@ nproc_limit = 12000 vm_swappiness = 0 kernel_core_pattern = {{ ynp.yb_home_dir }}/cores/core_%%p_%%t_%%E vm_max_map_count = 262144 -mount_points = {{ ynp.mount_points }} +mount_points = {{ yba.instance_type.mount_points | join(' ') }} [ConfigureOs.limits] core = unlimited @@ -71,5 +71,4 @@ ports = 7000 7100 9000 9100 18018 22 5433 9042 9070 9300 12000 13000 yb_user = yugabyte yb_home_dir = {{ ynp.yb_home_dir }} tmp_directory = {{ ynp.tmp_directory }} -mount_points = {{ ynp.mount_points }} diff --git a/managed/node-agent/resources/ynp/modules/provision/configure_os/templates/precheck.j2 b/managed/node-agent/resources/ynp/modules/provision/configure_os/templates/precheck.j2 index 128e5d9c618e..8896e04aa772 100644 --- a/managed/node-agent/resources/ynp/modules/provision/configure_os/templates/precheck.j2 +++ b/managed/node-agent/resources/ynp/modules/provision/configure_os/templates/precheck.j2 @@ -87,11 +87,12 @@ else add_result "kernel.core_pattern" "FAIL" "kernel.core_pattern is set to $kernel_core_pattern_value (expected: {{ kernel_core_pattern }})" fi +threshold=49 #Gigabytes mount_point_array={{ mount_points }} # Verify each mount point for mount_point in "${mount_point_array[@]}"; do if [ -d "$mount_point" ]; then - if [ -w "$mount_point" ] && [ "$(stat -c %A "$mount_point" | cut -c 10)" == "w" ]; then + if [ -w "$mount_point" ] && [ $(( $(stat -c %a "$mount_point") % 10 & 2 )) -ne 0 ]; then result="PASS" message="Directory $mount_point exists and is world-writable." echo "[PASS] $message" @@ -107,6 +108,19 @@ for mount_point in "${mount_point_array[@]}"; do echo "[FAIL] $message" any_fail=1 fi - add_result "$mount_point Check" "$result" "$message" + + # Get the available disk space in gigabytes. + free_space_gb=$(df -BG --output=avail "$MOUNT_POINT" | tail -n 1 | tr -d 'G ') + if [ "$free_space_gb" -gt "$threshold" ]; then + result="PASS" + message="Sufficient disk space available: ${AVAILABLE}G" + echo "[PASS] $message" + else + result="FAIL" + message="Insufficient disk space: ${AVAILABLE}G available, ${THRESHOLD}G required" + echo "[FAIL] $message" + any_fail=1 + fi + add_result "$mount_point Free space check" "$result" "$message" done diff --git a/managed/node-agent/resources/ynp/modules/provision/configure_os/templates/run.j2 b/managed/node-agent/resources/ynp/modules/provision/configure_os/templates/run.j2 index 50e94dbeb573..7cb60c58272c 100644 --- a/managed/node-agent/resources/ynp/modules/provision/configure_os/templates/run.j2 +++ b/managed/node-agent/resources/ynp/modules/provision/configure_os/templates/run.j2 @@ -68,66 +68,4 @@ sysctl -w vm.max_map_count={{ vm_max_map_count }} echo "Kernel settings configured." - -echo "Mounting volumes." -# Excluding the known mounts that we have to avoid. -{% set excluded_mounts = ["/", "boot", "efi"] %} - -# Generate a unique backup filename for /etc/fstab -backup_file="/etc/fstab.bak.$(date +%s)" -cp /etc/fstab $backup_file -echo "Backup of /etc/fstab created at $backup_file." - -# Get list of available volumes excluding the root and specified excluded mounts -volumes=$(lsblk -lnpo NAME,MOUNTPOINT | awk '$2 == "" {print $1}' | grep -vE 'NAME|MOUNTPOINT') - -# Prepare and mount each volume -index=1 -for volume in $volumes; do - if [[ $volume =~ /dev/nvme[0-9]n[0-9]p[0-9]+ ]]; then - echo "Skipping volume $volume" - continue - fi - mount_point="/mnt/d${index}" - - mkdir -p ${mount_point} - - if mount | grep -qE "^${volume}|^${volume}[0-9]"; then - echo "${volume} or one of its partitions is currently mounted. Skipping..." - continue - fi - - # Check if filesystem already exists - if ! blkid ${volume} | grep -q "TYPE=\"xfs\""; then - mkfs -t xfs ${volume} - echo "Filesystem created on ${volume}." - else - echo "Filesystem already exists on ${volume}." - fi - - # Add entry to /etc/fstab if it doesn't already exist - if ! grep -q "^${volume}" /etc/fstab; then - echo "${volume} ${mount_point} xfs noatime 0 0" | tee -a /etc/fstab - else - echo "Entry for ${volume} already exists in /etc/fstab." - fi - - # Mount the volume if it's not already mounted - if ! mountpoint -q ${mount_point}; then - mount ${mount_point} || fail "Failed to mount ${volume} at ${mount_point}." - echo "Mounted ${volume} at ${mount_point}." - else - echo "${mount_point} is already mounted." - fi - - # Set ownership and permissions - chown yugabyte:yugabyte ${mount_point} || fail "Failed to set ownership for ${mount_point}." - chmod 755 ${mount_point} || fail "Failed to set permissions for ${mount_point}." - echo "Set ownership and permissions for ${mount_point}." - - index=$((index + 1)) -done - -echo "Volume mounting process completed." - echo "OS Configuration applied successfully." From 84eefbdb17828d0a501612f7adb1d4829770396f Mon Sep 17 00:00:00 2001 From: Aishwarya Chakravarthy Date: Thu, 18 Jul 2024 22:43:35 -0400 Subject: [PATCH 1187/1195] [DOC-368] Azure workload identity docs changes (#22881) * added info * minor edit * change from review * changes from review and copy to stable * review comment fix * refined the order * edits * edits * minor edits * review comments * copy to stable --------- Co-authored-by: Dwight Hodge --- .../configure-yugabyte-platform/aws.md | 2 +- .../configure-yugabyte-platform/azure.md | 34 +++++++++++++----- .../configure-yugabyte-platform/gcp.md | 2 +- .../cloud-permissions-nodes-aws.md | 6 ++-- .../cloud-permissions-nodes-azure.md | 34 +++++++++++++----- .../cloud-permissions-nodes-gcp.md | 4 +-- .../configure-yugabyte-platform/aws.md | 2 +- .../configure-yugabyte-platform/azure.md | 34 +++++++++++++----- .../configure-yugabyte-platform/gcp.md | 2 +- .../cloud-permissions-nodes-aws.md | 6 ++-- .../cloud-permissions-nodes-azure.md | 36 +++++++++++++------ .../cloud-permissions-nodes-gcp.md | 4 +-- docs/netlify.toml | 2 +- 13 files changed, 118 insertions(+), 50 deletions(-) diff --git a/docs/content/preview/yugabyte-platform/configure-yugabyte-platform/aws.md b/docs/content/preview/yugabyte-platform/configure-yugabyte-platform/aws.md index c652473698c7..8b6e9874224f 100644 --- a/docs/content/preview/yugabyte-platform/configure-yugabyte-platform/aws.md +++ b/docs/content/preview/yugabyte-platform/configure-yugabyte-platform/aws.md @@ -101,7 +101,7 @@ Enter a Provider name. The Provider name is an internal tag used for organizing **Credential Type**. YBA requires the ability to create VMs in AWS. To do this, you can do one of the following: -- **Specify Access ID and Secret Key** - Create an AWS Service Account with the required permissions (refer to [Cloud permissions](../../prepare/cloud-permissions/cloud-permissions-nodes/)), and provide your AWS Access Key ID and Secret Access Key. +- **Specify Access ID and Secret Key** - Create an AWS Service Account with the required permissions (refer to [Cloud permissions](../../prepare/cloud-permissions/cloud-permissions-nodes-aws/)), and provide your AWS Access Key ID and Secret Access Key. - **Use IAM Role from this YBA host's instance** - Provision the YBA VM instance with an IAM role that has sufficient permissions by attaching an [IAM role](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html) to the YBA VM in the **EC2** tab. This option is only available if YBA is installed on AWS. **Use AWS Route 53 DNS Server**. Choose whether to use the cloud DNS Server / load balancer for universes deployed using this provider. Generally, SQL clients should prefer to use [smart client drivers](../../../drivers-orms/smart-drivers/) to connect to cluster nodes, rather than load balancers. However, in some cases (for example, if no smart driver is available in the language), you may use a DNS Server or load-balancer. The DNS Server acts as a load-balancer that routes clients to various nodes in the database universe. YBA integrates with [Amazon Route53](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/Welcome.html) to provide managed Canonical Name (CNAME) entries for your YugabyteDB universes, and automatically updates the DNS entry as nodes get created, removed, or undergo maintenance. diff --git a/docs/content/preview/yugabyte-platform/configure-yugabyte-platform/azure.md b/docs/content/preview/yugabyte-platform/configure-yugabyte-platform/azure.md index 78f8ab20a5d3..edc806bd49a0 100644 --- a/docs/content/preview/yugabyte-platform/configure-yugabyte-platform/azure.md +++ b/docs/content/preview/yugabyte-platform/configure-yugabyte-platform/azure.md @@ -51,13 +51,13 @@ When deploying a universe, YBA uses the provider configuration settings to do th ## Prerequisites -You need to add the following Azure cloud provider credentials via YBA: +You need to add the following Azure cloud provider credentials: +- Application client ID and (if using credentials) client secret +- Resource group name - Subscription ID - Tenant ID - SSH port and user -- Application client ID and secret -- Resource group YBA uses the credentials to automatically provision and deprovision YugabyteDB instances. @@ -107,11 +107,29 @@ Enter a Provider name. The Provider name is an internal tag used for organizing ### Cloud Info -- **Client ID** represents the [ID of an application](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#option-2-create-a-new-application-secret) registered in your Azure Active Directory. -- **Client Secret** represents the secret of an application registered in your Azure Active Directory. You need to enter the `Value` of the secret (not the `Secret ID`). -- **Resource Group** represents the group in which YugabyteDB nodes compute and network resources are created. Your Azure Active Directory application (client ID and client secret) needs to have `Network Contributor` and `Virtual Machine Contributor` roles assigned for this resource group. -- **Subscription ID** is required for cost management. The virtual machine resources managed by YBA are tagged with this subscription. -- **Tenant ID** represents the Azure Active Directory tenant ID which belongs to an active subscription. To find your tenant ID, follow instructions provided in [How to find your Azure Active Directory tenant ID](https://learn.microsoft.com/en-us/azure/active-directory/fundamentals/how-to-find-tenant). +Enter the following details of your Azure cloud account, as described in [Azure cloud permissions](../../prepare/cloud-permissions/cloud-permissions-nodes-azure/). + +#### Client ID + +Provide the ID of the application you registered with the Microsoft Identity Platform. + +#### Credential type + +If you [added credentials](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app?tabs=client-secret#add-credentials) in the form of a client secret to your registered application: + +1. Select **Specify Client Secret**. +1. Enter the Client Secret of the application associated with the Client ID you provided. You need to enter the `Value` of the secret (not the `Secret ID`). + +If you are using the [managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm) of the Azure VM hosting YugabyteDB Anywhere to authenticate: + +- Select **Use Managed Identity from this YBA host's instance**. + +#### Additional fields + +- **Resource Group** is the name of the resource group you created for your application, and in which YugabyteDB node compute and network resources will be created. +- **Subscription ID** is required for cost management. The virtual machine resources managed by YBA are tagged with this subscription. To get the subscription ID, open Subscriptions in Azure portal and find your subscription. Then, copy the Subscription ID. +- Optionally, if you created a different resource group for your network interfaces, provide the **Network Resource Group** name and the associated **Network Subscription ID**. If you do not provide a Network Resource Group or Subscription ID, network resources will be created in the default resource group. +- **Tenant ID** represents the tenant ID which belongs to an active subscription. To find your tenant ID, follow instructions provided in [How to find your Microsoft Entra tenant ID](https://learn.microsoft.com/en-us/entra/fundamentals/how-to-find-tenant). - **Private DNS zone** lets you use a custom domain name for the nodes in your universe. For details and instructions, see [Define a private DNS zone](#define-a-private-dns-zone). ### Regions diff --git a/docs/content/preview/yugabyte-platform/configure-yugabyte-platform/gcp.md b/docs/content/preview/yugabyte-platform/configure-yugabyte-platform/gcp.md index 9b9fc45bf950..c421e72c24e0 100644 --- a/docs/content/preview/yugabyte-platform/configure-yugabyte-platform/gcp.md +++ b/docs/content/preview/yugabyte-platform/configure-yugabyte-platform/gcp.md @@ -110,7 +110,7 @@ Enter a Provider name. The Provider name is an internal tag used for organizing ### Cloud Info -If your YBA instance is not running inside GCP, you need to supply YBA with credentials to the desired GCP project by uploading a configuration file. To do this, set **Credential Type** to **Upload Service Account config** and proceed to upload the JSON file that you obtained when you created your service account, as described in [Cloud permissions](../../prepare/cloud-permissions/cloud-permissions-nodes/). +If your YBA instance is not running inside GCP, you need to supply YBA with credentials to the desired GCP project by uploading a configuration file. To do this, set **Credential Type** to **Upload Service Account config** and proceed to upload the JSON file that you obtained when you created your service account, as described in [Cloud permissions](../../prepare/cloud-permissions/cloud-permissions-nodes-gcp/). If your YBA instance is running inside GCP, the preferred method for authentication to the GCP APIs is to add a service account role to the GCP instance running YBA and then configure YBA to use the instance's service account. To do this, set **Credential Type** to **Use service account from this YBA host's instance**. diff --git a/docs/content/preview/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-aws.md b/docs/content/preview/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-aws.md index 972439ea0698..af8182a0bb25 100644 --- a/docs/content/preview/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-aws.md +++ b/docs/content/preview/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-aws.md @@ -123,8 +123,8 @@ If using a service account, record the following two pieces of information about | Save for later | To configure | | :--- | :--- | -| Access key ID | [AWS cloud provider](../../../configure-yugabyte-platform/aws/) | -| Secret Access Key | [AWS cloud provider](../../../configure-yugabyte-platform/aws/) | +| Access key ID | [AWS provider configuration](../../../configure-yugabyte-platform/aws/) | +| Secret Access Key | | ### IAM role @@ -178,4 +178,4 @@ If you will be using your own custom SSH keys, then ensure that you have them wh | Save for later | To configure | | :--- | :--- | -| Custom SSH keys | [AWS provider](../../../configure-yugabyte-platform/kubernetes/) | +| Custom SSH keys | [AWS provider configuration](../../../configure-yugabyte-platform/kubernetes/) | diff --git a/docs/content/preview/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-azure.md b/docs/content/preview/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-azure.md index 6ca1477ddcea..1bf892f14bc0 100644 --- a/docs/content/preview/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-azure.md +++ b/docs/content/preview/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-azure.md @@ -52,30 +52,46 @@ The more permissions that you can provide, the more YBA can automate. ## Azure -The following permissions are required for the Azure resource group where you will deploy. +### Application and resource group + +YugabyteDB Anywhere requires cloud permissions to create VMs. You grant YugabyteDB Anywhere access to manage Azure resources such as VMs by registering an application in the Azure portal so the Microsoft identity platform can provide authentication and authorization services for your application. Registering your application establishes a trust relationship between your application and the Microsoft identity platform. + +In addition, your Azure application needs to have a [resource group](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/overview#resource-groups) with the following permissions: ```sh Network Contributor Virtual Machine Contributor ``` -To grant the required access, you can do one of the following: +You can optionally create a resource group for network resources if you want network interfaces to be created separately. The network resource group must have the `Network Contributor` permission. + +For more information on registering applications, refer to [Register an application with the Microsoft identity platform](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app?tabs=certificate) in the Microsoft Entra documentation. + +For more information on roles, refer to [Assign Azure roles using the Azure portal](https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal?tabs=delegate-condition) in the Microsoft Azure documentation. + +### Credentials + +YugabyteDB Anywhere can authenticate with Azure using one of the following methods: + +- [Add credentials](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app?tabs=client-secret#add-credentials), in the form of a client secret, to your registered application. -- [Register an application](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) in the Azure portal so the Microsoft identity platform can provide authentication and authorization services for your application. Registering your application establishes a trust relationship between your application and the Microsoft identity platform. + For information on creating client secrets, see [Create a new client secret](https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-service-principal-portal#option-3-create-a-new-client-secret) in the Microsoft Entra documentation. -- [Assign a managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm) to the Azure VM hosting YugabyteDB Anywhere. +- [Assign a managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm) to the Azure VM hosting YugabyteDB Anywhere. Azure will use the managed identity assigned to your instance to authenticate. -For information on assigning roles to applications, see [Assign a role to an application](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#assign-a-role-to-the-application); and assigning roles for managed identities, see [Assign Azure roles using the Azure portal](https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal?tabs=delegate-condition) in the Microsoft Azure documentation. + For information on assigning roles for managed identities, see [Assign Azure roles using the Azure portal](https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal?tabs=delegate-condition) in the Microsoft Azure documentation. -If you are registering an application, record the following information about your service account. You will need to provide this information later to YBA. +Record the following information about your service account. You will need to provide this information later when creating an Azure provider configuration. | Save for later | To configure | | :--- | :--- | -| **Service account details** | [Azure cloud provider](../../../configure-yugabyte-platform/azure/) | +| **Service account details** | [Azure provider configuration](../../../configure-yugabyte-platform/azure/) | | Client ID: | | -| Client Secret: | | +| Client Secret:
    (not required when using managed identity) | | | Resource Group: | | | Subscription ID: | | +| (Optional) Network Resource Group: | | +| (Optional) Network Subscription ID: | | | Tenant ID: | | ## Managing SSH keys for VMs @@ -89,4 +105,4 @@ If you will be using your own custom SSH keys, then ensure that you have them wh | Save for later | To configure | | :--- | :--- | -| Custom SSH keys | [Azure provider](../../../configure-yugabyte-platform/azure/) | +| Custom SSH keys | [Azure provider configuration](../../../configure-yugabyte-platform/azure/) | diff --git a/docs/content/preview/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-gcp.md b/docs/content/preview/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-gcp.md index 502d178bc1d2..a54c68f08097 100644 --- a/docs/content/preview/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-gcp.md +++ b/docs/content/preview/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-gcp.md @@ -70,7 +70,7 @@ Then use one of the following methods: | Save for later | To configure | | :--- | :--- | -| Service account JSON | [GCP cloud provider](../../../configure-yugabyte-platform/gcp/) | +| Service account JSON | [GCP provider configuration](../../../configure-yugabyte-platform/gcp/) | ## Managing SSH keys for VMs @@ -83,4 +83,4 @@ If you will be using your own custom SSH keys, then ensure that you have them wh | Save for later | To configure | | :--- | :--- | -| Custom SSH keys | [GCP provider](../../../configure-yugabyte-platform/gcp/) | +| Custom SSH keys | [GCP provider configuration](../../../configure-yugabyte-platform/gcp/) | diff --git a/docs/content/stable/yugabyte-platform/configure-yugabyte-platform/aws.md b/docs/content/stable/yugabyte-platform/configure-yugabyte-platform/aws.md index 3a56d91f9a0f..ce3499b4cbf2 100644 --- a/docs/content/stable/yugabyte-platform/configure-yugabyte-platform/aws.md +++ b/docs/content/stable/yugabyte-platform/configure-yugabyte-platform/aws.md @@ -98,7 +98,7 @@ Enter a Provider name. The Provider name is an internal tag used for organizing **Credential Type**. YBA requires the ability to create VMs in AWS. To do this, you can do one of the following: -- **Specify Access ID and Secret Key** - Create an AWS Service Account with the required permissions (refer to [Cloud permissions](../../prepare/cloud-permissions/cloud-permissions-nodes/)), and provide your AWS Access Key ID and Secret Access Key. +- **Specify Access ID and Secret Key** - Create an AWS Service Account with the required permissions (refer to [Cloud permissions](../../prepare/cloud-permissions/cloud-permissions-nodes-aws/)), and provide your AWS Access Key ID and Secret Access Key. - **Use IAM Role from this YBA host's instance** - Provision the YBA VM instance with an IAM role that has sufficient permissions by attaching an [IAM role](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html) to the YBA VM in the **EC2** tab. This option is only available if YBA is installed on AWS. **Use AWS Route 53 DNS Server**. Choose whether to use the cloud DNS Server / load balancer for universes deployed using this provider. Generally, SQL clients should prefer to use [smart client drivers](../../../drivers-orms/smart-drivers/) to connect to cluster nodes, rather than load balancers. However, in some cases (for example, if no smart driver is available in the language), you may use a DNS Server or load-balancer. The DNS Server acts as a load-balancer that routes clients to various nodes in the database universe. YBA integrates with [Amazon Route53](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/Welcome.html) to provide managed Canonical Name (CNAME) entries for your YugabyteDB universes, and automatically updates the DNS entry as nodes get created, removed, or undergo maintenance. diff --git a/docs/content/stable/yugabyte-platform/configure-yugabyte-platform/azure.md b/docs/content/stable/yugabyte-platform/configure-yugabyte-platform/azure.md index 0e8b120ed1c9..4284d16e7781 100644 --- a/docs/content/stable/yugabyte-platform/configure-yugabyte-platform/azure.md +++ b/docs/content/stable/yugabyte-platform/configure-yugabyte-platform/azure.md @@ -49,13 +49,13 @@ When deploying a universe, YBA uses the provider configuration settings to do th ## Prerequisites -You need to add the following Azure cloud provider credentials via YBA: +You need to add the following Azure cloud provider credentials: +- Application client ID and (if using credentials) client secret +- Resource group name - Subscription ID - Tenant ID - SSH port and user -- Application client ID and secret -- Resource group YBA uses the credentials to automatically provision and deprovision YugabyteDB instances. @@ -105,11 +105,29 @@ Enter a Provider name. The Provider name is an internal tag used for organizing ### Cloud Info -- **Client ID** represents the [ID of an application](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#option-2-create-a-new-application-secret) registered in your Azure Active Directory. -- **Client Secret** represents the secret of an application registered in your Azure Active Directory. You need to enter the `Value` of the secret (not the `Secret ID`). -- **Resource Group** represents the group in which YugabyteDB nodes compute and network resources are created. Your Azure Active Directory application (client ID and client secret) needs to have `Network Contributor` and `Virtual Machine Contributor` roles assigned for this resource group. -- **Subscription ID** is required for cost management. The virtual machine resources managed by YBA are tagged with this subscription. -- **Tenant ID** represents the Azure Active Directory tenant ID which belongs to an active subscription. To find your tenant ID, follow instructions provided in [How to find your Azure Active Directory tenant ID](https://learn.microsoft.com/en-us/azure/active-directory/fundamentals/how-to-find-tenant). +Enter the following details of your Azure cloud account, as described in [Azure cloud permissions](../../prepare/cloud-permissions/cloud-permissions-nodes-azure/). + +#### Client ID + +Provide the ID of the application you registered with the Microsoft Identity Platform. + +#### Credential type + +If you [added credentials](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app?tabs=client-secret#add-credentials) in the form of a client secret to your registered application: + +1. Select **Specify Client Secret**. +1. Enter the Client Secret of the application associated with the Client ID you provided. You need to enter the `Value` of the secret (not the `Secret ID`). + +If you are using the [managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm) of the Azure VM hosting YugabyteDB Anywhere to authenticate: + +- Select **Use Managed Identity from this YBA host's instance**. + +#### Additional fields + +- **Resource Group** is the name of the resource group you created for your application, and in which YugabyteDB node compute and network resources will be created. +- **Subscription ID** is required for cost management. The virtual machine resources managed by YBA are tagged with this subscription. To get the subscription ID, open Subscriptions in Azure portal and find your subscription. Then, copy the Subscription ID. +- Optionally, if you created a different resource group for your network interfaces, provide the **Network Resource Group** name and the associated **Network Subscription ID**. If you do not provide a Network Resource Group or Subscription ID, network resources will be created in the default resource group. +- **Tenant ID** represents the tenant ID which belongs to an active subscription. To find your tenant ID, follow instructions provided in [How to find your Microsoft Entra tenant ID](https://learn.microsoft.com/en-us/entra/fundamentals/how-to-find-tenant). - **Private DNS zone** lets you use a custom domain name for the nodes in your universe. For details and instructions, see [Define a private DNS zone](#define-a-private-dns-zone). ### Regions diff --git a/docs/content/stable/yugabyte-platform/configure-yugabyte-platform/gcp.md b/docs/content/stable/yugabyte-platform/configure-yugabyte-platform/gcp.md index d282e002d077..8fae19a40c8d 100644 --- a/docs/content/stable/yugabyte-platform/configure-yugabyte-platform/gcp.md +++ b/docs/content/stable/yugabyte-platform/configure-yugabyte-platform/gcp.md @@ -108,7 +108,7 @@ Enter a Provider name. The Provider name is an internal tag used for organizing ### Cloud Info -If your YBA instance is not running inside GCP, you need to supply YBA with credentials to the desired GCP project by uploading a configuration file. To do this, set **Credential Type** to **Upload Service Account config** and proceed to upload the JSON file that you obtained when you created your service account, as described in [Cloud permissions](../../prepare/cloud-permissions/cloud-permissions-nodes/). +If your YBA instance is not running inside GCP, you need to supply YBA with credentials to the desired GCP project by uploading a configuration file. To do this, set **Credential Type** to **Upload Service Account config** and proceed to upload the JSON file that you obtained when you created your service account, as described in [Cloud permissions](../../prepare/cloud-permissions/cloud-permissions-nodes-gcp/). If your YBA instance is running inside GCP, the preferred method for authentication to the GCP APIs is to add a service account role to the GCP instance running YBA and then configure YBA to use the instance's service account. To do this, set **Credential Type** to **Use service account from this YBA host's instance**. diff --git a/docs/content/stable/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-aws.md b/docs/content/stable/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-aws.md index 3525642e82ac..c66935dc4710 100644 --- a/docs/content/stable/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-aws.md +++ b/docs/content/stable/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-aws.md @@ -123,8 +123,8 @@ If using a service account, record the following two pieces of information about | Save for later | To configure | | :--- | :--- | -| Access key ID | [AWS cloud provider](../../../configure-yugabyte-platform/aws/) | -| Secret Access Key | [AWS cloud provider](../../../configure-yugabyte-platform/aws/) | +| Access key ID | [AWS provider configuration](../../../configure-yugabyte-platform/aws/) | +| Secret Access Key | | ### IAM role @@ -178,4 +178,4 @@ If you will be using your own custom SSH keys, then ensure that you have them wh | Save for later | To configure | | :--- | :--- | -| Custom SSH keys | [AWS provider](../../../configure-yugabyte-platform/kubernetes/) | +| Custom SSH keys | [AWS provider configuration](../../../configure-yugabyte-platform/kubernetes/) | diff --git a/docs/content/stable/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-azure.md b/docs/content/stable/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-azure.md index dfc94e1b63bc..87b68c7f7b70 100644 --- a/docs/content/stable/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-azure.md +++ b/docs/content/stable/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-azure.md @@ -52,30 +52,46 @@ The more permissions that you can provide, the more YBA can automate. ## Azure -The following permissions are required for the Azure resource group where you will deploy. +### Application and resource group + +YugabyteDB Anywhere requires cloud permissions to create VMs. You grant YugabyteDB Anywhere access to manage Azure resources such as VMs by registering an application in the Azure portal so the Microsoft identity platform can provide authentication and authorization services for your application. Registering your application establishes a trust relationship between your application and the Microsoft identity platform. + +In addition, your Azure application needs to have a [resource group](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/overview#resource-groups) with the following permissions: ```sh Network Contributor -Virtual Machine Contributor +Virtual Machine Contributor ``` -To grant the required access, you can do one of the following: +You can optionally create a resource group for network resources if you want network interfaces to be created separately. The network resource group must have the `Network Contributor` permission. + +For more information on registering applications, refer to [Register an application with the Microsoft identity platform](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app?tabs=certificate) in the Microsoft Entra documentation. + +For more information on roles, refer to [Assign Azure roles using the Azure portal](https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal?tabs=delegate-condition) in the Microsoft Azure documentation. + +### Credentials + +YugabyteDB Anywhere can authenticate with Azure using one of the following methods: + +- [Add credentials](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app?tabs=client-secret#add-credentials), in the form of a client secret, to your registered application. -- [Register an application](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) in the Azure portal so the Microsoft identity platform can provide authentication and authorization services for your application. Registering your application establishes a trust relationship between your application and the Microsoft identity platform. + For information on creating client secrets, see [Create a new client secret](https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-service-principal-portal#option-3-create-a-new-client-secret) in the Microsoft Entra documentation. -- [Assign a managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm) to the Azure VM hosting YugabyteDB Anywhere. +- [Assign a managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm) to the Azure VM hosting YugabyteDB Anywhere. Azure will use the managed identity assigned to your instance to authenticate. -For information on assigning roles to applications, see [Assign a role to an application](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#assign-a-role-to-the-application); and assigning roles for managed identities, see [Assign Azure roles using the Azure portal](https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal?tabs=delegate-condition) in the Microsoft Azure documentation. + For information on assigning roles for managed identities, see [Assign Azure roles using the Azure portal](https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal?tabs=delegate-condition) in the Microsoft Azure documentation. -If you are registering an application, record the following information about your service account. You will need to provide this information later to YBA. +Record the following information about your service account. You will need to provide this information later when creating an Azure provider configuration. | Save for later | To configure | | :--- | :--- | -| **Service account details** | [Azure cloud provider](../../../configure-yugabyte-platform/azure/) | +| **Service account details** | [Azure provider configuration](../../../configure-yugabyte-platform/azure/) | | Client ID: | | -| Client Secret: | | +| Client Secret:
    (not required when using managed identity) | | | Resource Group: | | | Subscription ID: | | +| (Optional) Network Resource Group: | | +| (Optional) Network Subscription ID: | | | Tenant ID: | | ## Managing SSH keys for VMs @@ -89,4 +105,4 @@ If you will be using your own custom SSH keys, then ensure that you have them wh | Save for later | To configure | | :--- | :--- | -| Custom SSH keys | [Azure provider](../../../configure-yugabyte-platform/azure/) | +| Custom SSH keys | [Azure provider configuration](../../../configure-yugabyte-platform/azure/) | diff --git a/docs/content/stable/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-gcp.md b/docs/content/stable/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-gcp.md index 87f5929a0ec0..3ce2bbefaaec 100644 --- a/docs/content/stable/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-gcp.md +++ b/docs/content/stable/yugabyte-platform/prepare/cloud-permissions/cloud-permissions-nodes-gcp.md @@ -70,7 +70,7 @@ Then use one of the following methods: | Save for later | To configure | | :--- | :--- | -| Service account JSON | [GCP cloud provider](../../../configure-yugabyte-platform/gcp/) | +| Service account JSON | [GCP provider configuration](../../../configure-yugabyte-platform/gcp/) | ## Managing SSH keys for VMs @@ -83,4 +83,4 @@ If you will be using your own custom SSH keys, then ensure that you have them wh | Save for later | To configure | | :--- | :--- | -| Custom SSH keys | [GCP provider](../../../configure-yugabyte-platform/gcp/) | +| Custom SSH keys | [GCP provider configuration](../../../configure-yugabyte-platform/gcp/) | diff --git a/docs/netlify.toml b/docs/netlify.toml index 50c2bf78fada..34c83c4c29c4 100644 --- a/docs/netlify.toml +++ b/docs/netlify.toml @@ -747,7 +747,7 @@ [[redirects]] from = "/:version/reference/connectors/*" - to = "/preview/integrations/apache-kafka/" + to = "/preview/explore/change-data-capture/" # Redirect for troubleshoot From 781af0df5daebea827af7d8baa44fcaed59060e8 Mon Sep 17 00:00:00 2001 From: Hari Krishna Sunder Date: Thu, 18 Jul 2024 16:20:38 -0700 Subject: [PATCH 1188/1195] [#23183] xCluster: Move Setup, Bootstrap, Alter and Delete Target Replication functions out of CatalogManager Summary: Moving all functions related to Setup, Bootstrap, Alter and Delete Target Replication functions out of CatalogManager. Setup functions have been moved to `SetupUniverseReplicationHelper` class of `xcluster_universe_replication_setup_helper.cc` Bootstrap functions have been moved to `SetupUniverseReplicationWithBootstrapHelper` class of `xcluster_bootstrap_helper.cc` Alter functions have been moved to `AlterUniverseReplicationHelper` class of `xcluster_universe_replication_alter_helper.cc` Delete function has been moved to `xcluster_replication_group.cc` `SetupUniverseReplication`, `IsSetupUniverseReplicationDone`, `SetupNamespaceReplicationWithBootstrap`, `IsSetupNamespaceReplicationWithBootstrapDone`, `AlterUniverseReplication`, `DeleteUniverseReplication` RPC functions have been moved to `XClusterManager` and `XClusterTargetManager`. Moved `GetAutoFlagConfigVersionIfCompatible` from `xcluster_replication_group.cc` to `xcluster_universe_replication_setup_helper.cc` Created new helpers `CatalogManager::GetTableGroupAndColocationInfo`, `CatalogManager::InsertNewUniverseReplication`, `CatalogManager::GetUniverseReplicationBootstrap`, `CatalogManager::InsertNewUniverseReplicationBootstrap`, `CatalogManager::InsertReplicationBootstrapToClear`, `CatalogManager::ReplaceUniverseReplication` and `CatalogManager::RemoveUniverseReplicationFromMap`. These will later be moved to XClusterManager as part of #23187 Removed some gFlags that are no longer used. `ns_replication_sync_retry_secs` `ns_replication_sync_backoff_secs` `ns_replication_sync_error_backoff_secs` `xcluster_fail_create_consumer_snapshot` These are all either test flags or never used in production so they are safe to remove without deprecating them. Fixes #23183 Jira: DB-12126 Test Plan: Jenkins Reviewers: jhe, xCluster, slingam, mlillibridge Reviewed By: mlillibridge Subscribers: ybase Differential Revision: https://phorge.dev.yugabyte.com/D36361 --- .../xcluster/xcluster-test.cc | 2 - .../xcluster/xcluster_test_base.cc | 2 +- src/yb/master/CMakeLists.txt | 3 + src/yb/master/catalog_manager.cc | 17 + src/yb/master/catalog_manager.h | 266 +- src/yb/master/master_replication_service.cc | 12 +- .../add_table_to_xcluster_target_task.cc | 5 +- .../xcluster/xcluster_bootstrap_helper.cc | 461 +++ .../xcluster/xcluster_bootstrap_helper.h | 116 + src/yb/master/xcluster/xcluster_manager.cc | 99 + src/yb/master/xcluster/xcluster_manager.h | 27 +- src/yb/master/xcluster/xcluster_manager_if.h | 7 + .../xcluster/xcluster_replication_group.cc | 227 +- .../xcluster/xcluster_replication_group.h | 19 +- .../xcluster/xcluster_target_manager.cc | 90 +- .../master/xcluster/xcluster_target_manager.h | 25 + ...uster_universe_replication_alter_helper.cc | 318 ++ ...luster_universe_replication_alter_helper.h | 71 + ...uster_universe_replication_setup_helper.cc | 1380 ++++++++ ...luster_universe_replication_setup_helper.h | 189 ++ src/yb/master/xrepl_catalog_manager.cc | 2821 ++--------------- 21 files changed, 3262 insertions(+), 2895 deletions(-) create mode 100644 src/yb/master/xcluster/xcluster_bootstrap_helper.cc create mode 100644 src/yb/master/xcluster/xcluster_bootstrap_helper.h create mode 100644 src/yb/master/xcluster/xcluster_universe_replication_alter_helper.cc create mode 100644 src/yb/master/xcluster/xcluster_universe_replication_alter_helper.h create mode 100644 src/yb/master/xcluster/xcluster_universe_replication_setup_helper.cc create mode 100644 src/yb/master/xcluster/xcluster_universe_replication_setup_helper.h diff --git a/src/yb/integration-tests/xcluster/xcluster-test.cc b/src/yb/integration-tests/xcluster/xcluster-test.cc index bc96e352b8aa..a873c0bdfd8d 100644 --- a/src/yb/integration-tests/xcluster/xcluster-test.cc +++ b/src/yb/integration-tests/xcluster/xcluster-test.cc @@ -124,8 +124,6 @@ DECLARE_int32(log_min_seconds_to_retain); DECLARE_int32(log_min_segments_to_retain); DECLARE_uint64(log_segment_size_bytes); DECLARE_int64(log_stop_retaining_min_disk_mb); -DECLARE_int32(ns_replication_sync_backoff_secs); -DECLARE_int32(ns_replication_sync_retry_secs); DECLARE_uint32(replication_failure_delay_exponent); DECLARE_int64(rpc_throttle_threshold_bytes); DECLARE_int32(transaction_table_num_tablets); diff --git a/src/yb/integration-tests/xcluster/xcluster_test_base.cc b/src/yb/integration-tests/xcluster/xcluster_test_base.cc index ff24bd334894..239ae1934e04 100644 --- a/src/yb/integration-tests/xcluster/xcluster_test_base.cc +++ b/src/yb/integration-tests/xcluster/xcluster_test_base.cc @@ -1010,7 +1010,7 @@ Result XClusterTestBase::GetProducerMasterProxy( Status XClusterTestBase::ClearFailedUniverse(Cluster& cluster) { auto& catalog_manager = VERIFY_RESULT(cluster.mini_cluster_->GetLeaderMiniMaster())->catalog_manager_impl(); - return catalog_manager.ClearFailedUniverse(); + return catalog_manager.ClearFailedUniverse(catalog_manager.GetLeaderEpochInternal()); } } // namespace yb diff --git a/src/yb/master/CMakeLists.txt b/src/yb/master/CMakeLists.txt index b9faacdf2603..6f78ade89ea5 100644 --- a/src/yb/master/CMakeLists.txt +++ b/src/yb/master/CMakeLists.txt @@ -138,6 +138,7 @@ set(MASTER_SRCS xcluster/add_table_to_xcluster_source_task.cc xcluster/add_table_to_xcluster_target_task.cc xcluster/master_xcluster_util.cc + xcluster/xcluster_bootstrap_helper.cc xcluster/xcluster_catalog_entity.cc xcluster/xcluster_config.cc xcluster/xcluster_consumer_metrics.cc @@ -148,6 +149,8 @@ set(MASTER_SRCS xcluster/xcluster_safe_time_service.cc xcluster/xcluster_source_manager.cc xcluster/xcluster_target_manager.cc + xcluster/xcluster_universe_replication_alter_helper.cc + xcluster/xcluster_universe_replication_setup_helper.cc xrepl_catalog_manager.cc yql_aggregates_vtable.cc yql_auth_resource_role_permissions_index.cc diff --git a/src/yb/master/catalog_manager.cc b/src/yb/master/catalog_manager.cc index e0dfe59c79b7..f8a5cc343564 100644 --- a/src/yb/master/catalog_manager.cc +++ b/src/yb/master/catalog_manager.cc @@ -12896,5 +12896,22 @@ CatalogManager::GetStatefulServicesStatus() const { return result; } +Status CatalogManager::GetTableGroupAndColocationInfo( + const TableId& table_id, TablegroupId& out_tablegroup_id, bool& out_colocated_database) { + SharedLock lock(mutex_); + const auto* tablegroup = tablegroup_manager_->FindByTable(table_id); + SCHECK_FORMAT(tablegroup, NotFound, "No tablegroup found for table: $0", table_id); + + out_tablegroup_id = tablegroup->id(); + + auto ns = FindPtrOrNull(namespace_ids_map_, tablegroup->database_id()); + SCHECK( + ns, NotFound, + Format("Could not find namespace by namespace id $0", tablegroup->database_id())); + out_colocated_database = ns->colocated(); + + return Status::OK(); +} + } // namespace master } // namespace yb diff --git a/src/yb/master/catalog_manager.h b/src/yb/master/catalog_manager.h index fcd7a685b845..dce20e507953 100644 --- a/src/yb/master/catalog_manager.h +++ b/src/yb/master/catalog_manager.h @@ -160,8 +160,6 @@ typedef std::unordered_map> TableToTables typedef std::unordered_map> TableToTabletInfos; -typedef std::unordered_map TableBootstrapIdsMap; - constexpr int32_t kInvalidClusterConfigVersion = 0; YB_DEFINE_ENUM( @@ -1276,11 +1274,6 @@ class CatalogManager : public tserver::TabletPeerLookupIf, rpc::RpcContext* rpc, const LeaderEpoch& epoch); - Status InitXClusterConsumer( - const std::vector& consumer_info, const std::string& master_addrs, - UniverseReplicationInfo& replication_info, - std::shared_ptr xcluster_rpc_tasks); - void HandleCreateTabletSnapshotResponse(TabletInfo* tablet, bool error) override; void HandleRestoreTabletSnapshotResponse(TabletInfo* tablet, bool error) override; @@ -1372,44 +1365,6 @@ class CatalogManager : public tserver::TabletPeerLookupIf, const GetUDTypeMetadataRequestPB* req, GetUDTypeMetadataResponsePB* resp, rpc::RpcContext* rpc); - // Bootstrap namespace and setup replication to consume data from another YB universe. - Status SetupNamespaceReplicationWithBootstrap( - const SetupNamespaceReplicationWithBootstrapRequestPB* req, - SetupNamespaceReplicationWithBootstrapResponsePB* resp, rpc::RpcContext* rpc, - const LeaderEpoch& epoch); - - // Setup Universe Replication to consume data from another YB universe. - Status SetupUniverseReplication( - const SetupUniverseReplicationRequestPB* req, - SetupUniverseReplicationResponsePB* resp, - rpc::RpcContext* rpc); - - // Delete Universe Replication. - Status DeleteUniverseReplication( - const DeleteUniverseReplicationRequestPB* req, - DeleteUniverseReplicationResponsePB* resp, - rpc::RpcContext* rpc); - - // Alter Universe Replication. - Status AlterUniverseReplication( - const AlterUniverseReplicationRequestPB* req, AlterUniverseReplicationResponsePB* resp, - rpc::RpcContext* rpc, const LeaderEpoch& epoch); - - Status UpdateProducerAddress( - scoped_refptr universe, - const AlterUniverseReplicationRequestPB* req); - - Status AddTablesToReplication( - scoped_refptr universe, - const AlterUniverseReplicationRequestPB* req, - AlterUniverseReplicationResponsePB* resp, - rpc::RpcContext* rpc); - - // Rename an existing Universe Replication. - Status RenameUniverseReplication( - scoped_refptr universe, - const AlterUniverseReplicationRequestPB* req); - Status ChangeXClusterRole( const ChangeXClusterRoleRequestPB* req, ChangeXClusterRoleResponsePB* resp, @@ -1436,17 +1391,6 @@ class CatalogManager : public tserver::TabletPeerLookupIf, std::vector> GetAllUniverseReplications() const; - // Checks if the universe is in an active state or has failed during setup. - Status IsSetupUniverseReplicationDone( - const IsSetupUniverseReplicationDoneRequestPB* req, - IsSetupUniverseReplicationDoneResponsePB* resp, - rpc::RpcContext* rpc); - - // Checks if the replication bootstrap is done, or return its current state. - Status IsSetupNamespaceReplicationWithBootstrapDone( - const IsSetupNamespaceReplicationWithBootstrapDoneRequestPB* req, - IsSetupNamespaceReplicationWithBootstrapDoneResponsePB* resp, rpc::RpcContext* rpc); - // On a producer side split, creates new pollers on the consumer for the new tablet children. Status UpdateConsumerOnProducerSplit( const UpdateConsumerOnProducerSplitRequestPB* req, @@ -1541,7 +1485,7 @@ class CatalogManager : public tserver::TabletPeerLookupIf, Result GetNumLiveTServersForActiveCluster() override; - Status ClearFailedUniverse(); + Status ClearFailedUniverse(const LeaderEpoch& epoch); void SetCDCServiceEnabled(); @@ -1622,6 +1566,42 @@ class CatalogManager : public tserver::TabletPeerLookupIf, Result> GetTableKeyRanges(const TableId& table_id); + Result GetTableSchemaVersion(const TableId& table_id); + + Status GetTableGroupAndColocationInfo( + const TableId& table_id, TablegroupId& out_tablegroup_id, bool& out_colocated_database) + EXCLUDES(mutex_); + + void InsertNewUniverseReplication(UniverseReplicationInfo& replication_group) EXCLUDES(mutex_); + + void MarkReplicationBootstrapForCleanup(const xcluster::ReplicationGroupId& replication_group_id) + EXCLUDES(mutex_); + + // Will return nullptr if not found. + scoped_refptr GetUniverseReplicationBootstrap( + const xcluster::ReplicationGroupId& replication_group_id) EXCLUDES(mutex_); + + void InsertNewUniverseReplicationInfoBootstrapInfo( + UniverseReplicationBootstrapInfo& bootstrap_info) EXCLUDES(mutex_); + + // All entities must be write locked. + Status ReplaceUniverseReplication( + const UniverseReplicationInfo& old_replication_group, + UniverseReplicationInfo& new_replication_group, const ClusterConfigInfo& cluster_config, + const LeaderEpoch& epoch) EXCLUDES(mutex_); + + void RemoveUniverseReplicationFromMap(const xcluster::ReplicationGroupId& replication_group_id) + EXCLUDES(mutex_); + + Status DoImportSnapshotMeta( + const SnapshotInfoPB& snapshot_pb, const LeaderEpoch& epoch, + const std::optional& clone_target_namespace_name, NamespaceMap* namespace_map, + UDTypeMap* type_map, ExternalTableSnapshotDataMap* tables_data, + CoarseTimePoint deadline) override; + + Status CreateTransactionAwareSnapshot( + const CreateSnapshotRequestPB& req, CreateSnapshotResponsePB* resp, CoarseTimePoint deadline); + protected: // TODO Get rid of these friend classes and introduce formal interface. friend class TableLoader; @@ -2123,37 +2103,6 @@ class CatalogManager : public tserver::TabletPeerLookupIf, Result IsCreateTableDone(const TableInfoPtr& table); - // SetupReplicationWithBootstrap - Status ValidateReplicationBootstrapRequest( - const SetupNamespaceReplicationWithBootstrapRequestPB* req); - - void DoReplicationBootstrap( - const xcluster::ReplicationGroupId& replication_id, - const std::vector& tables, - Result bootstrap_producer_result); - - Result DoReplicationBootstrapCreateSnapshot( - const std::vector& tables, - scoped_refptr bootstrap_info); - - using TableMetaPB = ImportSnapshotMetaResponsePB::TableMetaPB; - Result> DoReplicationBootstrapImportSnapshot( - const SnapshotInfoPB& snapshot, - scoped_refptr bootstrap_info); - - Status DoReplicationBootstrapTransferAndRestoreSnapshot( - const std::vector& tables_meta, - scoped_refptr bootstrap_info); - - void MarkReplicationBootstrapFailed( - scoped_refptr bootstrap_info, const Status& failure_status); - // Sets the appropriate failure state and the error status on the replication bootstrap and - // commits the mutation to the sys catalog. - void MarkReplicationBootstrapFailed( - const Status& failure_status, - CowWriteLock* bootstrap_info_lock, - scoped_refptr bootstrap_info); - struct CleanupFailedReplicationBootstrapInfo { // State that the task failed on. SysUniverseReplicationBootstrapEntryPB::State state; @@ -2560,14 +2509,6 @@ class CatalogManager : public tserver::TabletPeerLookupIf, const SysRowEntry& entry, const SnapshotId& snapshot_id, const LeaderEpoch& epoch) REQUIRES(mutex_); - Status DoImportSnapshotMeta( - const SnapshotInfoPB& snapshot_pb, - const LeaderEpoch& epoch, - const std::optional& clone_target_namespace_name, - NamespaceMap* namespace_map, - UDTypeMap* type_map, - ExternalTableSnapshotDataMap* tables_data, - CoarseTimePoint deadline) override; Status ImportSnapshotPreprocess( const SnapshotInfoPB& snapshot_pb, const LeaderEpoch& epoch, @@ -2640,8 +2581,6 @@ class CatalogManager : public tserver::TabletPeerLookupIf, Status PreprocessTabletEntry(const SysRowEntry& entry, ExternalTableSnapshotDataMap* table_map); Status ImportTabletEntry(const SysRowEntry& entry, ExternalTableSnapshotDataMap* table_map); - Result GetTableSchemaVersion(const TableId& table_id); - Result CollectEntries( const google::protobuf::RepeatedPtrField& tables, CollectFlags flags); @@ -2776,133 +2715,18 @@ class CatalogManager : public tserver::TabletPeerLookupIf, const TSHeartbeatRequestPB* req, TSHeartbeatResponsePB* resp); - // Helper functions for GetTableSchemaCallback, GetTablegroupSchemaCallback - // and GetColocatedTabletSchemaCallback. - - // Helper container to track colocationId and the producer to consumer schema version mapping. - typedef std::vector> - ColocationSchemaVersions; - - struct SetupReplicationInfo { - std::unordered_map table_bootstrap_ids; - bool transactional; - }; - - Status ValidateMasterAddressesBelongToDifferentCluster( - const google::protobuf::RepeatedPtrField& master_addresses); - - // Validates a single table's schema with the corresponding table on the consumer side, and - // updates consumer_table_id with the new table id. Return the consumer table schema if the - // validation is successful. - Status ValidateTableSchemaForXCluster( - const client::YBTableInfo& info, const SetupReplicationInfo& setup_info, - GetTableSchemaResponsePB* resp); - - // Adds a validated table to the sys catalog table map for the given universe - Status AddValidatedTableToUniverseReplication( - scoped_refptr universe, - const TableId& producer_table, - const TableId& consumer_table, - const SchemaVersion& producer_schema_version, - const SchemaVersion& consumer_schema_version, - const ColocationSchemaVersions& colocated_schema_versions); - Status AddSchemaVersionMappingToUniverseReplication( scoped_refptr universe, ColocationId consumer_table, const SchemaVersion& producer_schema_version, const SchemaVersion& consumer_schema_version); - // If all tables have been validated, creates a CDC stream for each table. - Status CreateCdcStreamsIfReplicationValidated( - scoped_refptr universe, - const std::unordered_map& table_bootstrap_ids); - - Status AddValidatedTableAndCreateCdcStreams( - scoped_refptr universe, - const std::unordered_map& table_bootstrap_ids, - const TableId& producer_table, - const TableId& consumer_table, - const ColocationSchemaVersions& colocated_schema_versions); - - Status ValidateTableAndCreateCdcStreams( - scoped_refptr universe, - const std::shared_ptr& producer_info, - const SetupReplicationInfo& setup_info); - - void GetTableSchemaCallback( - const xcluster::ReplicationGroupId& replication_group_id, - const std::shared_ptr& producer_info, - const SetupReplicationInfo& setup_info, const Status& s); - void GetTablegroupSchemaCallback( - const xcluster::ReplicationGroupId& replication_group_id, - const std::shared_ptr>& infos, - const TablegroupId& producer_tablegroup_id, const SetupReplicationInfo& setup_info, - const Status& s); - Status GetTablegroupSchemaCallbackInternal( - scoped_refptr& universe, - const std::vector& infos, const TablegroupId& producer_tablegroup_id, - const SetupReplicationInfo& setup_info, const Status& s); - void GetColocatedTabletSchemaCallback( - const xcluster::ReplicationGroupId& replication_group_id, - const std::shared_ptr>& info, - const SetupReplicationInfo& setup_info, const Status& s); - - typedef std::vector< - std::tuple>> - StreamUpdateInfos; - - void GetCDCStreamCallback( - const xrepl::StreamId& bootstrap_id, std::shared_ptr table_id, - std::shared_ptr> options, - const xcluster::ReplicationGroupId& replication_group_id, const TableId& table, - std::shared_ptr xcluster_rpc, const Status& s, - std::shared_ptr stream_update_infos, - std::shared_ptr update_infos_lock); - - void AddCDCStreamToUniverseAndInitConsumer( - const xcluster::ReplicationGroupId& replication_group_id, const TableId& table, - const Result& stream_id, std::function on_success_cb = nullptr); - - Status AddCDCStreamToUniverseAndInitConsumerInternal( - scoped_refptr universe, const TableId& table, - const xrepl::StreamId& stream_id, std::function on_success_cb); - - Status MergeUniverseReplication( - scoped_refptr info, xcluster::ReplicationGroupId original_id, - std::function on_success_cb); - - Status DeleteUniverseReplicationUnlocked(scoped_refptr info); - Status DeleteUniverseReplication( - const xcluster::ReplicationGroupId& replication_group_id, bool ignore_errors, - bool skip_producer_stream_deletion, DeleteUniverseReplicationResponsePB* resp); - - void MarkUniverseReplicationFailed( - scoped_refptr universe, const Status& failure_status); - // Sets the appropriate failure state and the error status on the universe and commits the - // mutation to the sys catalog. - void MarkUniverseReplicationFailed( - const Status& failure_status, CowWriteLock* universe_lock, - scoped_refptr universe); - - // Sets the appropriate state and on the replication bootstrap and commits the - // mutation to the sys catalog. - void SetReplicationBootstrapState( - scoped_refptr bootstrap_info, - const SysUniverseReplicationBootstrapEntryPB::State& state); - std::shared_ptr GetCDCServiceProxy( client::internal::RemoteTabletServer* ts); Result GetLeaderTServer( client::internal::RemoteTabletPtr tablet); - // Consumer API: Find out if bootstrap is required for the Producer tables. - Status IsBootstrapRequiredOnProducer( - scoped_refptr universe, - const TableId& producer_table, - const std::unordered_map& table_bootstrap_ids); - // Check if bootstrapping is required for a table. Status IsTableBootstrapRequired( const TableId& table_id, @@ -2912,9 +2736,6 @@ class CatalogManager : public tserver::TabletPeerLookupIf, std::unordered_set GetCDCSDKStreamsForTable(const TableId& table_id) const; - Status CreateTransactionAwareSnapshot( - const CreateSnapshotRequestPB& req, CreateSnapshotResponsePB* resp, CoarseTimePoint deadline); - Status CreateNonTransactionAwareSnapshot( const CreateSnapshotRequestPB* req, CreateSnapshotResponsePB* resp, const LeaderEpoch& epoch); @@ -2942,19 +2763,6 @@ class CatalogManager : public tserver::TabletPeerLookupIf, Result CollectEntriesForSequencesDataTable(); - Result> CreateUniverseReplicationInfoForProducer( - const xcluster::ReplicationGroupId& replication_group_id, - const google::protobuf::RepeatedPtrField& master_addresses, - const std::vector& producer_namespace_ids, - const std::vector& consumer_namespace_ids, - const google::protobuf::RepeatedPtrField& table_ids, bool transactional); - - Result> - CreateUniverseReplicationBootstrapInfoForProducer( - const xcluster::ReplicationGroupId& replication_group_id, - const google::protobuf::RepeatedPtrField& master_addresses, - const LeaderEpoch& epoch, bool transactional); - void ProcessXReplParentTabletDeletionPeriodically(); Status DoProcessCDCSDKTabletDeletion(); diff --git a/src/yb/master/master_replication_service.cc b/src/yb/master/master_replication_service.cc index 201ba5cf2563..c6befa687b74 100644 --- a/src/yb/master/master_replication_service.cc +++ b/src/yb/master/master_replication_service.cc @@ -30,22 +30,16 @@ class MasterReplicationServiceImpl : public MasterServiceBase, public MasterRepl MASTER_SERVICE_IMPL_ON_LEADER_WITH_LOCK( CatalogManager, (ValidateReplicationInfo) - (AlterUniverseReplication) (CreateCDCStream) (DeleteCDCStream) - (DeleteUniverseReplication) (GetCDCStream) (GetUniverseReplication) (GetUDTypeMetadata) - (IsSetupUniverseReplicationDone) - (IsSetupNamespaceReplicationWithBootstrapDone) (UpdateConsumerOnProducerSplit) (UpdateConsumerOnProducerMetadata) (ListCDCStreams) (IsObjectPartOfXRepl) (SetUniverseReplicationEnabled) - (SetupNamespaceReplicationWithBootstrap) - (SetupUniverseReplication) (UpdateCDCStream) (GetCDCDBStreamInfo) (IsBootstrapRequired) @@ -82,6 +76,12 @@ class MasterReplicationServiceImpl : public MasterServiceBase, public MasterRepl (GetUniverseReplicationInfo) (GetReplicationStatus) (XClusterReportNewAutoFlagConfigVersion) + (SetupUniverseReplication) + (IsSetupUniverseReplicationDone) + (SetupNamespaceReplicationWithBootstrap) + (IsSetupNamespaceReplicationWithBootstrapDone) + (AlterUniverseReplication) + (DeleteUniverseReplication) ) }; diff --git a/src/yb/master/xcluster/add_table_to_xcluster_target_task.cc b/src/yb/master/xcluster/add_table_to_xcluster_target_task.cc index 194849e023f5..f1025a8a0d52 100644 --- a/src/yb/master/xcluster/add_table_to_xcluster_target_task.cc +++ b/src/yb/master/xcluster/add_table_to_xcluster_target_task.cc @@ -168,7 +168,7 @@ Status AddTableToXClusterTargetTask::AddTableToReplicationGroup( req.set_replication_group_id(replication_group_id.ToString()); req.add_producer_table_ids_to_add(producer_table_id); req.add_producer_bootstrap_ids_to_add(bootstrap_id); - RETURN_NOT_OK(catalog_manager_.AlterUniverseReplication(&req, &resp, nullptr /* rpc */, epoch_)); + RETURN_NOT_OK(xcluster_manager_.AlterUniverseReplication(&req, &resp, nullptr /* rpc */, epoch_)); if (resp.has_error()) { return StatusFromPB(resp.error().status()); @@ -268,8 +268,7 @@ Status AddTableToXClusterTargetTask::WaitForXClusterSafeTimeCaughtUp() { Status AddTableToXClusterTargetTask::CleanupAndComplete() { // Ensure that we clean up the xcluster_source_table_id field. - RETURN_NOT_OK( - catalog_manager_.GetXClusterManager()->ClearXClusterSourceTableId(table_info_, epoch_)); + RETURN_NOT_OK(xcluster_manager_.ClearXClusterSourceTableId(table_info_, epoch_)); Complete(); return Status::OK(); diff --git a/src/yb/master/xcluster/xcluster_bootstrap_helper.cc b/src/yb/master/xcluster/xcluster_bootstrap_helper.cc new file mode 100644 index 000000000000..6c24f0f4f801 --- /dev/null +++ b/src/yb/master/xcluster/xcluster_bootstrap_helper.cc @@ -0,0 +1,461 @@ +// Copyright (c) YugabyteDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the License +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. See the License for the specific language governing permissions and limitations +// under the License. +// + +#include "yb/master/xcluster/xcluster_bootstrap_helper.h" + +#include "yb/client/yb_table_name.h" +#include "yb/gutil/bind.h" +#include "yb/master/catalog_manager-internal.h" +#include "yb/master/catalog_manager.h" +#include "yb/master/snapshot_transfer_manager.h" +#include "yb/master/xcluster_rpc_tasks.h" +#include "yb/master/xcluster/xcluster_universe_replication_setup_helper.h" +#include "yb/util/backoff_waiter.h" + +using namespace std::literals; + +DEFINE_test_flag(bool, xcluster_fail_restore_consumer_snapshot, false, + "In the SetupReplicationWithBootstrap flow, test failure to restore snapshot on consumer."); + +// assumes the existence of a local variable bootstrap_info +#define MARK_BOOTSTRAP_FAILED_NOT_OK(s) \ + do { \ + auto&& _s = (s); \ + if (PREDICT_FALSE(!_s.ok())) { \ + MarkReplicationBootstrapFailed(bootstrap_info, _s); \ + return; \ + } \ + } while (false) + +#define VERIFY_RESULT_MARK_BOOTSTRAP_FAILED(expr) \ + RESULT_CHECKER_HELPER(expr, MARK_BOOTSTRAP_FAILED_NOT_OK(ResultToStatus(__result))) + +namespace yb::master { + +SetupUniverseReplicationWithBootstrapHelper::SetupUniverseReplicationWithBootstrapHelper( + Master& master, CatalogManager& catalog_manager, const LeaderEpoch& epoch) + : master_(master), + catalog_manager_(catalog_manager), + sys_catalog_(*catalog_manager.sys_catalog()), + xcluster_manager_(*catalog_manager.GetXClusterManagerImpl()), + epoch_(epoch) {} + +SetupUniverseReplicationWithBootstrapHelper::~SetupUniverseReplicationWithBootstrapHelper() {} + +Status SetupUniverseReplicationWithBootstrapHelper::SetupWithBootstrap( + Master& master, CatalogManager& catalog_manager, + const SetupNamespaceReplicationWithBootstrapRequestPB* req, + SetupNamespaceReplicationWithBootstrapResponsePB* resp, const LeaderEpoch& epoch) { + scoped_refptr helper = + new SetupUniverseReplicationWithBootstrapHelper(master, catalog_manager, epoch); + return helper->SetupWithBootstrap(req, resp); +} + +/* + * SetupNamespaceReplicationWithBootstrap is setup in 5 stages. + * 1. Validates user input & connect to producer. + * 2. Calls BootstrapProducer with all user tables in namespace. + * 3. Create snapshot on producer and import onto consumer. + * 4. Download snapshots from producer and restore on consumer. + * 5. SetupUniverseReplication. + */ +Status SetupUniverseReplicationWithBootstrapHelper::SetupWithBootstrap( + const SetupNamespaceReplicationWithBootstrapRequestPB* req, + SetupNamespaceReplicationWithBootstrapResponsePB* resp) { + // PHASE 1: Validating user input. + RETURN_NOT_OK(ValidateReplicationBootstrapRequest(req)); + + // Create entry in sys catalog. + auto replication_id = xcluster::ReplicationGroupId(req->replication_id()); + auto transactional = req->has_transactional() ? req->transactional() : false; + auto bootstrap_info = VERIFY_RESULT(CreateUniverseReplicationBootstrapInfoForProducer( + replication_id, req->producer_master_addresses(), transactional)); + + // Connect to producer. + auto xcluster_rpc_result = + bootstrap_info->GetOrCreateXClusterRpcTasks(req->producer_master_addresses()); + if (!xcluster_rpc_result.ok()) { + auto s = ResultToStatus(xcluster_rpc_result); + MarkReplicationBootstrapFailed(bootstrap_info, s); + return s; + } + auto xcluster_rpc_tasks = std::move(*xcluster_rpc_result); + + // Get user tables in producer namespace. + auto tables_result = xcluster_rpc_tasks->client()->ListUserTables(req->producer_namespace()); + if (!tables_result.ok()) { + auto s = ResultToStatus(tables_result); + MarkReplicationBootstrapFailed(bootstrap_info, s); + return s; + } + auto tables = std::move(*tables_result); + + // Bootstrap producer. + SetReplicationBootstrapState( + bootstrap_info, SysUniverseReplicationBootstrapEntryPB::BOOTSTRAP_PRODUCER); + auto s = xcluster_rpc_tasks->BootstrapProducer( + req->producer_namespace(), tables, + Bind( + &SetupUniverseReplicationWithBootstrapHelper::DoReplicationBootstrap, + scoped_refptr(this), replication_id, + tables)); + if (!s.ok()) { + MarkReplicationBootstrapFailed(bootstrap_info, s); + return s; + } + + return Status::OK(); +} +Result> +SetupUniverseReplicationWithBootstrapHelper::CreateUniverseReplicationBootstrapInfoForProducer( + const xcluster::ReplicationGroupId& replication_group_id, + const google::protobuf::RepeatedPtrField& master_addresses, bool transactional) { + if (catalog_manager_.GetUniverseReplicationBootstrap(replication_group_id) != nullptr) { + return STATUS( + InvalidArgument, Format("Bootstrap already present: $0", replication_group_id), + MasterError(MasterErrorPB::INVALID_REQUEST)); + } + + // Create an entry in the system catalog DocDB for this new universe replication. + scoped_refptr bootstrap_info = + new UniverseReplicationBootstrapInfo(replication_group_id); + bootstrap_info->mutable_metadata()->StartMutation(); + + SysUniverseReplicationBootstrapEntryPB* metadata = + &bootstrap_info->mutable_metadata()->mutable_dirty()->pb; + metadata->set_replication_group_id(replication_group_id.ToString()); + metadata->mutable_producer_master_addresses()->CopyFrom(master_addresses); + metadata->set_state(SysUniverseReplicationBootstrapEntryPB::INITIALIZING); + metadata->set_transactional(transactional); + metadata->set_leader_term(epoch_.leader_term); + metadata->set_pitr_count(epoch_.pitr_count); + + RETURN_NOT_OK(CheckLeaderStatus( + sys_catalog_.Upsert(epoch_, bootstrap_info), + "inserting universe replication bootstrap info into sys-catalog")); + + // Commit the in-memory state now that it's added to the persistent catalog. + bootstrap_info->mutable_metadata()->CommitMutation(); + LOG(INFO) << "Setup universe replication bootstrap from producer " << bootstrap_info->ToString(); + + catalog_manager_.InsertNewUniverseReplicationInfoBootstrapInfo(*bootstrap_info); + + return bootstrap_info; +} + +void SetupUniverseReplicationWithBootstrapHelper::MarkReplicationBootstrapFailed( + scoped_refptr bootstrap_info, const Status& failure_status) { + auto l = bootstrap_info->LockForWrite(); + MarkReplicationBootstrapFailed(failure_status, &l, bootstrap_info); +} + +void SetupUniverseReplicationWithBootstrapHelper::MarkReplicationBootstrapFailed( + const Status& failure_status, + CowWriteLock* bootstrap_info_lock, + scoped_refptr bootstrap_info) { + auto& l = *bootstrap_info_lock; + auto state = l->pb.state(); + if (state == SysUniverseReplicationBootstrapEntryPB::DELETED) { + l.mutable_data()->pb.set_state(SysUniverseReplicationBootstrapEntryPB::DELETED_ERROR); + } else { + l.mutable_data()->pb.set_state(SysUniverseReplicationBootstrapEntryPB::FAILED); + l.mutable_data()->pb.set_failed_on(state); + } + + LOG(WARNING) << Format( + "Replication bootstrap $0 failed: $1", bootstrap_info->ToString(), failure_status.ToString()); + + bootstrap_info->SetReplicationBootstrapErrorStatus(failure_status); + + // Update sys_catalog. + const Status s = sys_catalog_.Upsert(epoch_, bootstrap_info); + + l.CommitOrWarn(s, "updating universe replication bootstrap info in sys-catalog"); +} + +void SetupUniverseReplicationWithBootstrapHelper::SetReplicationBootstrapState( + scoped_refptr bootstrap_info, + const SysUniverseReplicationBootstrapEntryPB::State& state) { + auto l = bootstrap_info->LockForWrite(); + l.mutable_data()->set_state(state); + + // Update sys_catalog. + const Status s = sys_catalog_.Upsert(epoch_, bootstrap_info); + l.CommitOrWarn(s, "updating universe replication bootstrap info in sys-catalog"); +} + +Status SetupUniverseReplicationWithBootstrapHelper::ValidateReplicationBootstrapRequest( + const SetupNamespaceReplicationWithBootstrapRequestPB* req) { + SCHECK( + !req->replication_id().empty(), InvalidArgument, "Replication ID must be provided", + req->ShortDebugString()); + + SCHECK( + req->producer_master_addresses_size() > 0, InvalidArgument, + "Producer master address must be provided", req->ShortDebugString()); + + { + auto l = catalog_manager_.ClusterConfig()->LockForRead(); + SCHECK( + l->pb.cluster_uuid() != req->replication_id(), InvalidArgument, + "Replication name cannot be the target universe UUID", req->ShortDebugString()); + } + + RETURN_NOT_OK_PREPEND( + SetupUniverseReplicationHelper::ValidateMasterAddressesBelongToDifferentCluster( + master_, req->producer_master_addresses()), + req->ShortDebugString()); + + auto universe = + catalog_manager_.GetUniverseReplication(xcluster::ReplicationGroupId(req->replication_id())); + SCHECK( + universe == nullptr, InvalidArgument, + Format("Can't bootstrap replication that already exists")); + + return Status::OK(); +} + +void SetupUniverseReplicationWithBootstrapHelper::DoReplicationBootstrap( + const xcluster::ReplicationGroupId& replication_id, + const std::vector& tables, + Result bootstrap_producer_result) { + // First get the universe. + auto bootstrap_info = catalog_manager_.GetUniverseReplicationBootstrap(replication_id); + if (bootstrap_info == nullptr) { + LOG(ERROR) << "UniverseReplicationBootstrap not found: " << replication_id; + return; + } + + // Verify the result from BootstrapProducer & update values in PB if successful. + auto table_bootstrap_ids = + VERIFY_RESULT_MARK_BOOTSTRAP_FAILED(std::move(bootstrap_producer_result)); + { + auto l = bootstrap_info->LockForWrite(); + auto map = l.mutable_data()->pb.mutable_table_bootstrap_ids(); + for (const auto& [table_id, bootstrap_id] : table_bootstrap_ids) { + (*map)[table_id] = bootstrap_id.ToString(); + } + + // Update sys_catalog. + const Status s = sys_catalog_.Upsert(epoch_, bootstrap_info); + l.CommitOrWarn(s, "updating universe replication bootstrap info in sys-catalog"); + } + + // Create producer snapshot. + auto snapshot = VERIFY_RESULT_MARK_BOOTSTRAP_FAILED( + DoReplicationBootstrapCreateSnapshot(tables, bootstrap_info)); + + // Import snapshot and create consumer snapshot. + auto tables_meta = VERIFY_RESULT_MARK_BOOTSTRAP_FAILED( + DoReplicationBootstrapImportSnapshot(snapshot, bootstrap_info)); + + // Transfer and restore snapshot. + MARK_BOOTSTRAP_FAILED_NOT_OK( + DoReplicationBootstrapTransferAndRestoreSnapshot(tables_meta, bootstrap_info)); + + // Call SetupUniverseReplication + SetupUniverseReplicationRequestPB replication_req; + SetupUniverseReplicationResponsePB replication_resp; + { + auto l = bootstrap_info->LockForRead(); + replication_req.set_replication_group_id(l->pb.replication_group_id()); + replication_req.set_transactional(l->pb.transactional()); + replication_req.mutable_producer_master_addresses()->CopyFrom( + l->pb.producer_master_addresses()); + for (const auto& [table_id, bootstrap_id] : table_bootstrap_ids) { + replication_req.add_producer_table_ids(table_id); + replication_req.add_producer_bootstrap_ids(bootstrap_id.ToString()); + } + } + + SetReplicationBootstrapState( + bootstrap_info, SysUniverseReplicationBootstrapEntryPB::SETUP_REPLICATION); + MARK_BOOTSTRAP_FAILED_NOT_OK(SetupUniverseReplicationHelper::Setup( + master_, catalog_manager_, &replication_req, &replication_resp, epoch_)); + + LOG(INFO) << Format( + "Successfully completed replication bootstrap for $0", replication_id.ToString()); + SetReplicationBootstrapState(bootstrap_info, SysUniverseReplicationBootstrapEntryPB::DONE); +} + +Result +SetupUniverseReplicationWithBootstrapHelper::DoReplicationBootstrapCreateSnapshot( + const std::vector& tables, + scoped_refptr bootstrap_info) { + LOG(INFO) << Format( + "SetupReplicationWithBootstrap: create producer snapshot for replication $0", + bootstrap_info->id()); + SetReplicationBootstrapState( + bootstrap_info, SysUniverseReplicationBootstrapEntryPB::CREATE_PRODUCER_SNAPSHOT); + + auto xcluster_rpc_tasks = VERIFY_RESULT(bootstrap_info->GetOrCreateXClusterRpcTasks( + bootstrap_info->LockForRead()->pb.producer_master_addresses())); + + TxnSnapshotId old_snapshot_id = TxnSnapshotId::Nil(); + + // Send create request and wait for completion. + auto snapshot_result = xcluster_rpc_tasks->CreateSnapshot(tables, &old_snapshot_id); + + // If the producer failed to complete the snapshot, we still want to store the snapshot_id for + // cleanup purposes. + if (!old_snapshot_id.IsNil()) { + auto l = bootstrap_info->LockForWrite(); + l.mutable_data()->set_old_snapshot_id(old_snapshot_id); + + // Update sys_catalog. + const Status s = sys_catalog_.Upsert(epoch_, bootstrap_info); + l.CommitOrWarn(s, "updating universe replication bootstrap info in sys-catalog"); + } + + return snapshot_result; +} + +Result> +SetupUniverseReplicationWithBootstrapHelper::DoReplicationBootstrapImportSnapshot( + const SnapshotInfoPB& snapshot, + scoped_refptr bootstrap_info) { + /////////////////////////// + // ImportSnapshotMeta + /////////////////////////// + LOG(INFO) << Format( + "SetupReplicationWithBootstrap: import snapshot for replication $0", bootstrap_info->id()); + SetReplicationBootstrapState( + bootstrap_info, SysUniverseReplicationBootstrapEntryPB::IMPORT_SNAPSHOT); + + NamespaceMap namespace_map; + UDTypeMap type_map; + ExternalTableSnapshotDataMap tables_data; + + // ImportSnapshotMeta timeout should be a function of the table size. + auto deadline = CoarseMonoClock::Now() + MonoDelta::FromSeconds(10 + 1 * tables_data.size()); + auto epoch = bootstrap_info->LockForRead()->epoch(); + RETURN_NOT_OK(catalog_manager_.DoImportSnapshotMeta( + snapshot, epoch, std::nullopt /* clone_target_namespace_name */, &namespace_map, &type_map, + &tables_data, deadline)); + + // Update sys catalog with new information. + { + auto l = bootstrap_info->LockForWrite(); + l.mutable_data()->set_new_snapshot_objects(namespace_map, type_map, tables_data); + + // Update sys_catalog. + const Status s = sys_catalog_.Upsert(epoch_, bootstrap_info); + l.CommitOrWarn(s, "updating universe replication bootstrap info in sys-catalog"); + } + + /////////////////////////// + // CreateConsumerSnapshot + /////////////////////////// + LOG(INFO) << Format( + "SetupReplicationWithBootstrap: create consumer snapshot for replication $0", + bootstrap_info->id()); + SetReplicationBootstrapState( + bootstrap_info, SysUniverseReplicationBootstrapEntryPB::CREATE_CONSUMER_SNAPSHOT); + + CreateSnapshotRequestPB snapshot_req; + CreateSnapshotResponsePB snapshot_resp; + + std::vector tables_meta; + for (const auto& [table_id, table_data] : tables_data) { + if (table_data.table_meta) { + tables_meta.push_back(std::move(*table_data.table_meta)); + } + } + + for (const auto& table_meta : tables_meta) { + SCHECK( + ImportSnapshotMetaResponsePB_TableType_IsValid(table_meta.table_type()), InternalError, + Format("Found unknown table type: $0", table_meta.table_type())); + + const auto& new_table_id = table_meta.table_ids().new_id(); + RETURN_NOT_OK(catalog_manager_.WaitForCreateTableToFinish(new_table_id, deadline)); + + snapshot_req.mutable_tables()->Add()->set_table_id(new_table_id); + } + + snapshot_req.set_add_indexes(false); + snapshot_req.set_transaction_aware(true); + snapshot_req.set_imported(true); + RETURN_NOT_OK( + catalog_manager_.CreateTransactionAwareSnapshot(snapshot_req, &snapshot_resp, deadline)); + + // Update sys catalog with new information. + { + auto l = bootstrap_info->LockForWrite(); + l.mutable_data()->set_new_snapshot_id(TryFullyDecodeTxnSnapshotId(snapshot_resp.snapshot_id())); + + // Update sys_catalog. + const Status s = sys_catalog_.Upsert(epoch_, bootstrap_info); + l.CommitOrWarn(s, "updating universe replication bootstrap info in sys-catalog"); + } + + return std::vector(tables_meta.begin(), tables_meta.end()); +} + +Status +SetupUniverseReplicationWithBootstrapHelper::DoReplicationBootstrapTransferAndRestoreSnapshot( + const std::vector& tables_meta, + scoped_refptr bootstrap_info) { + // Retrieve required data from PB. + TxnSnapshotId old_snapshot_id = TxnSnapshotId::Nil(); + TxnSnapshotId new_snapshot_id = TxnSnapshotId::Nil(); + google::protobuf::RepeatedPtrField producer_masters; + auto epoch = bootstrap_info->epoch(); + { + auto l = bootstrap_info->LockForRead(); + old_snapshot_id = l->old_snapshot_id(); + new_snapshot_id = l->new_snapshot_id(); + producer_masters.CopyFrom(l->pb.producer_master_addresses()); + } + + auto xcluster_rpc_tasks = + VERIFY_RESULT(bootstrap_info->GetOrCreateXClusterRpcTasks(producer_masters)); + + // Transfer snapshot. + SetReplicationBootstrapState( + bootstrap_info, SysUniverseReplicationBootstrapEntryPB::TRANSFER_SNAPSHOT); + auto snapshot_transfer_manager = std::make_shared( + &master_, &catalog_manager_, xcluster_rpc_tasks->client()); + RETURN_NOT_OK_PREPEND( + snapshot_transfer_manager->TransferSnapshot( + old_snapshot_id, new_snapshot_id, tables_meta, epoch), + Format("Failed to transfer snapshot $0 from producer", old_snapshot_id.ToString())); + + // Restore snapshot. + SetReplicationBootstrapState( + bootstrap_info, SysUniverseReplicationBootstrapEntryPB::RESTORE_SNAPSHOT); + auto restoration_id = VERIFY_RESULT(catalog_manager_.snapshot_coordinator().Restore( + new_snapshot_id, HybridTime(), epoch.leader_term)); + + if (PREDICT_FALSE(FLAGS_TEST_xcluster_fail_restore_consumer_snapshot)) { + return STATUS(Aborted, "Test failure"); + } + + // Wait for restoration to complete. + return WaitFor( + [this, &new_snapshot_id, &restoration_id]() -> Result { + ListSnapshotRestorationsResponsePB resp; + RETURN_NOT_OK(catalog_manager_.snapshot_coordinator().ListRestorations( + restoration_id, new_snapshot_id, &resp)); + + SCHECK_EQ( + resp.restorations_size(), 1, IllegalState, + Format("Expected 1 restoration, got $0", resp.restorations_size())); + const auto& restoration = *resp.restorations().begin(); + const auto& state = restoration.entry().state(); + return state == SysSnapshotEntryPB::RESTORED; + }, + MonoDelta::kMax, "Waiting for restoration to finish", 100ms); +} + +} // namespace yb::master diff --git a/src/yb/master/xcluster/xcluster_bootstrap_helper.h b/src/yb/master/xcluster/xcluster_bootstrap_helper.h new file mode 100644 index 000000000000..116f77bf05b2 --- /dev/null +++ b/src/yb/master/xcluster/xcluster_bootstrap_helper.h @@ -0,0 +1,116 @@ +// Copyright (c) YugabyteDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the License +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. See the License for the specific language governing permissions and limitations +// under the License. +// + +#pragma once + +#include "yb/cdc/xcluster_types.h" +#include "yb/master/leader_epoch.h" +#include "yb/master/master_fwd.h" +#include "yb/util/cow_object.h" +#include "yb/util/status_fwd.h" + +namespace yb { + +namespace rpc { +class RpcContext; +} // namespace rpc + +namespace client { +class YBTableName; +} // namespace client + +namespace master { + +class SetupNamespaceReplicationWithBootstrapRequestPB; +class SetupNamespaceReplicationWithBootstrapResponsePB; +class UniverseReplicationBootstrapInfo; +struct PersistentUniverseReplicationBootstrapInfo; + +// NOTE: +// SetupUniverseReplicationWithBootstrap is currently not in use. There are no tests that cover +// this, so it should be assumed to have regressions. This code is just kept as a refences for +// future use. Use caution when copying and reusing part of this code. + +class SetupUniverseReplicationWithBootstrapHelper + : public RefCountedThreadSafe { + public: + ~SetupUniverseReplicationWithBootstrapHelper(); + + static Status SetupWithBootstrap( + Master& master, CatalogManager& catalog_manager, + const SetupNamespaceReplicationWithBootstrapRequestPB* req, + SetupNamespaceReplicationWithBootstrapResponsePB* resp, const LeaderEpoch& epoch); + + private: + SetupUniverseReplicationWithBootstrapHelper( + Master& master, CatalogManager& catalog_manager, const LeaderEpoch& epoch); + + Status SetupWithBootstrap( + const SetupNamespaceReplicationWithBootstrapRequestPB* req, + SetupNamespaceReplicationWithBootstrapResponsePB* resp); + + Result> + CreateUniverseReplicationBootstrapInfoForProducer( + const xcluster::ReplicationGroupId& replication_group_id, + const google::protobuf::RepeatedPtrField& master_addresses, bool transactional); + + void MarkReplicationBootstrapFailed( + scoped_refptr bootstrap_info, const Status& failure_status); + // Sets the appropriate failure state and the error status on the replication bootstrap and + // commits the mutation to the sys catalog. + void MarkReplicationBootstrapFailed( + const Status& failure_status, + CowWriteLock* bootstrap_info_lock, + scoped_refptr bootstrap_info); + + // Sets the appropriate state and on the replication bootstrap and commits the + // mutation to the sys catalog. + void SetReplicationBootstrapState( + scoped_refptr bootstrap_info, + const SysUniverseReplicationBootstrapEntryPB::State& state); + + // SetupReplicationWithBootstrap + Status ValidateReplicationBootstrapRequest( + const SetupNamespaceReplicationWithBootstrapRequestPB* req); + + typedef std::unordered_map TableBootstrapIdsMap; + void DoReplicationBootstrap( + const xcluster::ReplicationGroupId& replication_id, + const std::vector& tables, + Result bootstrap_producer_result); + + Result DoReplicationBootstrapCreateSnapshot( + const std::vector& tables, + scoped_refptr bootstrap_info); + + Result> + DoReplicationBootstrapImportSnapshot( + const SnapshotInfoPB& snapshot, + scoped_refptr bootstrap_info); + + Status DoReplicationBootstrapTransferAndRestoreSnapshot( + const std::vector& tables_meta, + scoped_refptr bootstrap_info); + + Master& master_; + CatalogManager& catalog_manager_; + SysCatalogTable& sys_catalog_; + XClusterManager& xcluster_manager_; + const LeaderEpoch epoch_; + + DISALLOW_COPY_AND_ASSIGN(SetupUniverseReplicationWithBootstrapHelper); +}; + +} // namespace master + +} // namespace yb diff --git a/src/yb/master/xcluster/xcluster_manager.cc b/src/yb/master/xcluster/xcluster_manager.cc index 74ca3fcdfbf8..e99aeb9d4734 100644 --- a/src/yb/master/xcluster/xcluster_manager.cc +++ b/src/yb/master/xcluster/xcluster_manager.cc @@ -46,6 +46,23 @@ DEFINE_RUNTIME_AUTO_bool(enable_tablet_split_of_xcluster_replicated_tables, kExt namespace yb::master { +namespace { + +template +Status ValidateUniverseUUID(const RequestType& req, CatalogManager& catalog_manager) { + if (req->has_universe_uuid() && !req->universe_uuid().empty()) { + auto universe_uuid = catalog_manager.GetUniverseUuidIfExists(); + SCHECK( + universe_uuid && universe_uuid->ToString() == req->universe_uuid(), InvalidArgument, + "Invalid Universe UUID $0. Expected $1", req->universe_uuid(), + (universe_uuid ? universe_uuid->ToString() : "empty")); + } + + return Status::OK(); +} + +} // namespace + XClusterManager::XClusterManager( Master& master, CatalogManager& catalog_manager, SysCatalogTable& sys_catalog) : XClusterSourceManager(master, catalog_manager, sys_catalog), @@ -677,4 +694,86 @@ Status XClusterManager::HandleTabletSchemaVersionReport( table_info, consumer_schema_version, epoch); } +Status XClusterManager::SetupUniverseReplication( + const SetupUniverseReplicationRequestPB* req, SetupUniverseReplicationResponsePB* resp, + rpc::RpcContext* rpc, const LeaderEpoch& epoch) { + LOG_FUNC_AND_RPC; + + return XClusterTargetManager::SetupUniverseReplication(req, resp, epoch); +} + +/* + * Checks if the universe replication setup has completed. + * Returns Status::OK() if this call succeeds, and uses resp->done() to determine if the setup has + * completed (either failed or succeeded). If the setup has failed, then resp->replication_error() + * is also set. If it succeeds, replication_error() gets set to OK. + */ +Status XClusterManager::IsSetupUniverseReplicationDone( + const IsSetupUniverseReplicationDoneRequestPB* req, + IsSetupUniverseReplicationDoneResponsePB* resp, rpc::RpcContext* rpc) { + LOG_FUNC_AND_RPC; + + SCHECK_PB_FIELDS_NOT_EMPTY(*req, replication_group_id); + + auto is_operation_done = VERIFY_RESULT(XClusterTargetManager::IsSetupUniverseReplicationDone( + xcluster::ReplicationGroupId(req->replication_group_id()))); + + resp->set_done(is_operation_done.done()); + StatusToPB(is_operation_done.status(), resp->mutable_replication_error()); + return Status::OK(); +} + +Status XClusterManager::SetupNamespaceReplicationWithBootstrap( + const SetupNamespaceReplicationWithBootstrapRequestPB* req, + SetupNamespaceReplicationWithBootstrapResponsePB* resp, rpc::RpcContext* rpc, + const LeaderEpoch& epoch) { + LOG_FUNC_AND_RPC; + + return XClusterTargetManager::SetupNamespaceReplicationWithBootstrap(req, resp, epoch); +} + +Status XClusterManager::IsSetupNamespaceReplicationWithBootstrapDone( + const IsSetupNamespaceReplicationWithBootstrapDoneRequestPB* req, + IsSetupNamespaceReplicationWithBootstrapDoneResponsePB* resp, rpc::RpcContext* rpc) { + LOG_FUNC_AND_RPC; + + SCHECK_PB_FIELDS_NOT_EMPTY(*req, replication_group_id); + + *resp = VERIFY_RESULT(XClusterTargetManager::IsSetupNamespaceReplicationWithBootstrapDone( + xcluster::ReplicationGroupId(req->replication_group_id()))); + + return Status::OK(); +} + +Status XClusterManager::AlterUniverseReplication( + const AlterUniverseReplicationRequestPB* req, AlterUniverseReplicationResponsePB* resp, + rpc::RpcContext* rpc, const LeaderEpoch& epoch) { + LOG_FUNC_AND_RPC; + + SCHECK_PB_FIELDS_NOT_EMPTY(*req, replication_group_id); + + RETURN_NOT_OK(ValidateUniverseUUID(req, catalog_manager_)); + + return XClusterTargetManager::AlterUniverseReplication(req, resp, epoch); +} + +Status XClusterManager::DeleteUniverseReplication( + const DeleteUniverseReplicationRequestPB* req, DeleteUniverseReplicationResponsePB* resp, + rpc::RpcContext* rpc, const LeaderEpoch& epoch) { + LOG_FUNC_AND_RPC; + + SCHECK_PB_FIELDS_NOT_EMPTY(*req, replication_group_id); + + RETURN_NOT_OK(ValidateUniverseUUID(req, catalog_manager_)); + + RETURN_NOT_OK(XClusterTargetManager::DeleteUniverseReplication( + xcluster::ReplicationGroupId(req->replication_group_id()), req->ignore_errors(), + req->skip_producer_stream_deletion(), resp, epoch)); + + LOG(INFO) << "Successfully completed DeleteUniverseReplication request from " + << RequestorString(rpc); + + return Status::OK(); +} + } // namespace yb::master diff --git a/src/yb/master/xcluster/xcluster_manager.h b/src/yb/master/xcluster/xcluster_manager.h index 665fc66e1c64..60c51867b373 100644 --- a/src/yb/master/xcluster/xcluster_manager.h +++ b/src/yb/master/xcluster/xcluster_manager.h @@ -101,6 +101,31 @@ class XClusterManager : public XClusterManagerIf, Status RefreshXClusterSafeTimeMap(const LeaderEpoch& epoch) override; + Status SetupUniverseReplication( + const SetupUniverseReplicationRequestPB* req, SetupUniverseReplicationResponsePB* resp, + rpc::RpcContext* rpc, const LeaderEpoch& epoch); + + Status IsSetupUniverseReplicationDone( + const IsSetupUniverseReplicationDoneRequestPB* req, + IsSetupUniverseReplicationDoneResponsePB* resp, rpc::RpcContext* rpc); + + Status SetupNamespaceReplicationWithBootstrap( + const SetupNamespaceReplicationWithBootstrapRequestPB* req, + SetupNamespaceReplicationWithBootstrapResponsePB* resp, rpc::RpcContext* rpc, + const LeaderEpoch& epoch); + + Status IsSetupNamespaceReplicationWithBootstrapDone( + const IsSetupNamespaceReplicationWithBootstrapDoneRequestPB* req, + IsSetupNamespaceReplicationWithBootstrapDoneResponsePB* resp, rpc::RpcContext* rpc); + + Status AlterUniverseReplication( + const AlterUniverseReplicationRequestPB* req, AlterUniverseReplicationResponsePB* resp, + rpc::RpcContext* rpc, const LeaderEpoch& epoch) override; + + Status DeleteUniverseReplication( + const DeleteUniverseReplicationRequestPB* req, DeleteUniverseReplicationResponsePB* resp, + rpc::RpcContext* rpc, const LeaderEpoch& epoch); + // OutboundReplicationGroup RPCs. Status XClusterCreateOutboundReplicationGroup( const XClusterCreateOutboundReplicationGroupRequestPB* req, @@ -198,7 +223,7 @@ class XClusterManager : public XClusterManagerIf, const xrepl::StreamId& stream_id); void RemoveTableConsumerStream( - const TableId& table_id, const xcluster::ReplicationGroupId& replication_group_id); + const TableId& table_id, const xcluster::ReplicationGroupId& replication_group_id) override; void RemoveTableConsumerStreams( const xcluster::ReplicationGroupId& replication_group_id, diff --git a/src/yb/master/xcluster/xcluster_manager_if.h b/src/yb/master/xcluster/xcluster_manager_if.h index 2dfdc81c836e..1d7d696ac8d7 100644 --- a/src/yb/master/xcluster/xcluster_manager_if.h +++ b/src/yb/master/xcluster/xcluster_manager_if.h @@ -86,6 +86,13 @@ class XClusterManagerIf { const TableId& consumer_table_id, const SplitTabletIds& split_tablet_ids, const LeaderEpoch& epoch) = 0; + virtual void RemoveTableConsumerStream( + const TableId& table_id, const xcluster::ReplicationGroupId& replication_group_id) = 0; + + virtual Status AlterUniverseReplication( + const AlterUniverseReplicationRequestPB* req, AlterUniverseReplicationResponsePB* resp, + rpc::RpcContext* rpc, const LeaderEpoch& epoch) = 0; + protected: virtual ~XClusterManagerIf() = default; }; diff --git a/src/yb/master/xcluster/xcluster_replication_group.cc b/src/yb/master/xcluster/xcluster_replication_group.cc index 2545bf823e50..2b47c7bd572e 100644 --- a/src/yb/master/xcluster/xcluster_replication_group.cc +++ b/src/yb/master/xcluster/xcluster_replication_group.cc @@ -18,45 +18,27 @@ #include "yb/client/xcluster_client.h" #include "yb/common/wire_protocol.pb.h" #include "yb/master/catalog_entity_info.h" +#include "yb/master/catalog_manager-internal.h" #include "yb/master/catalog_manager.h" #include "yb/master/catalog_manager_util.h" #include "yb/master/xcluster/master_xcluster_util.h" +#include "yb/util/flags/auto_flags_util.h" #include "yb/util/is_operation_done_result.h" #include "yb/master/xcluster_rpc_tasks.h" #include "yb/master/xcluster/xcluster_manager_if.h" #include "yb/common/xcluster_util.h" #include "yb/master/sys_catalog.h" -#include "yb/util/flags/auto_flags_util.h" #include "yb/util/result.h" DEFINE_RUNTIME_bool(xcluster_skip_health_check_on_replication_setup, false, "Skip health check on xCluster replication setup"); +DEFINE_test_flag(bool, exit_unfinished_deleting, false, + "Whether to exit part way through the deleting universe process."); + namespace yb::master { namespace { -// Returns nullopt when source universe does not support AutoFlags compatibility check. -// Returns a pair of bool which indicates if the configs are compatible and the source universe -// AutoFlags config version. -Result>> ValidateAutoFlagsConfig( - UniverseReplicationInfo& replication_info, const AutoFlagsConfigPB& local_config) { - auto master_addresses = replication_info.LockForRead()->pb.producer_master_addresses(); - auto xcluster_rpc = VERIFY_RESULT(replication_info.GetOrCreateXClusterRpcTasks(master_addresses)); - auto result = VERIFY_RESULT( - xcluster_rpc->client()->ValidateAutoFlagsConfig(local_config, AutoFlagClass::kExternal)); - - if (!result) { - return std::nullopt; - } - - auto& [is_valid, source_version] = *result; - VLOG(2) << "ValidateAutoFlagsConfig for replication group: " - << replication_info.ReplicationGroupId() << ", is_valid: " << is_valid - << ", source universe version: " << source_version - << ", target universe version: " << local_config.config_version(); - - return result; -} Result GetProducerEntry( SysClusterConfigEntryPB& cluster_config_pb, @@ -182,32 +164,40 @@ Result IsReplicationGroupReady( return IsSafeTimeReady(l->pb, xcluster_manager); } +Status ReturnErrorOrAddWarning( + const Status& s, bool ignore_errors, DeleteUniverseReplicationResponsePB* resp) { + if (!s.ok()) { + if (ignore_errors) { + // Continue executing, save the status as a warning. + AppStatusPB* warning = resp->add_warnings(); + StatusToPB(s, warning); + return Status::OK(); + } + return s.CloneAndAppend("\nUse 'ignore-errors' to ignore this error."); + } + return s; +} + } // namespace -Result GetAutoFlagConfigVersionIfCompatible( +Result>> ValidateAutoFlagsConfig( UniverseReplicationInfo& replication_info, const AutoFlagsConfigPB& local_config) { - const auto& replication_group_id = replication_info.ReplicationGroupId(); - - VLOG_WITH_FUNC(2) << "Validating AutoFlags config for replication group: " << replication_group_id - << " with target config version: " << local_config.config_version(); - - auto validate_result = VERIFY_RESULT(ValidateAutoFlagsConfig(replication_info, local_config)); + auto master_addresses = replication_info.LockForRead()->pb.producer_master_addresses(); + auto xcluster_rpc = VERIFY_RESULT(replication_info.GetOrCreateXClusterRpcTasks(master_addresses)); + auto result = VERIFY_RESULT( + xcluster_rpc->client()->ValidateAutoFlagsConfig(local_config, AutoFlagClass::kExternal)); - if (!validate_result) { - VLOG_WITH_FUNC(2) - << "Source universe of replication group " << replication_group_id - << " is running a version that does not support the AutoFlags compatibility check yet"; - return kInvalidAutoFlagsConfigVersion; + if (!result) { + return std::nullopt; } - auto& [is_valid, source_version] = *validate_result; - - SCHECK( - is_valid, IllegalState, - "AutoFlags between the universes are not compatible. Upgrade the target universe to a " - "version higher than or equal to the source universe"); + auto& [is_valid, source_version] = *result; + VLOG(2) << "ValidateAutoFlagsConfig for replication group: " + << replication_info.ReplicationGroupId() << ", is_valid: " << is_valid + << ", source universe version: " << source_version + << ", target universe version: " << local_config.config_version(); - return source_version; + return result; } Status RefreshAutoFlagConfigVersion( @@ -610,43 +600,6 @@ Status RemoveTablesFromReplicationGroupInternal( return Status::OK(); } -Status ValidateTableListForDbScopedReplication( - UniverseReplicationInfo& universe, const std::vector& namespace_ids, - const std::set& replicated_tables, const CatalogManager& catalog_manager) { - std::set validated_tables; - - for (const auto& namespace_id : namespace_ids) { - auto table_infos = - VERIFY_RESULT(GetTablesEligibleForXClusterReplication(catalog_manager, namespace_id)); - - std::vector missing_tables; - - for (const auto& table_info : table_infos) { - const auto& table_id = table_info->id(); - if (replicated_tables.contains(table_id)) { - validated_tables.insert(table_id); - } else { - missing_tables.push_back(table_id); - } - } - - SCHECK_FORMAT( - missing_tables.empty(), IllegalState, - "Namespace $0 has additional tables that were not added to xCluster DB Scoped replication " - "group $1: $2", - namespace_id, universe.id(), yb::ToString(missing_tables)); - } - - auto diff = STLSetSymmetricDifference(replicated_tables, validated_tables); - SCHECK_FORMAT( - diff.empty(), IllegalState, - "xCluster DB Scoped replication group $0 contains tables $1 that do not belong to replicated " - "namespaces $2", - universe.id(), yb::ToString(diff), yb::ToString(namespace_ids)); - - return Status::OK(); -} - bool HasNamespace(UniverseReplicationInfo& universe, const NamespaceId& consumer_namespace_id) { auto l = universe.LockForRead(); if (!l->IsDbScoped()) { @@ -661,4 +614,120 @@ bool HasNamespace(UniverseReplicationInfo& universe, const NamespaceId& consumer }); } +Status DeleteUniverseReplication( + UniverseReplicationInfo& universe, bool ignore_errors, bool skip_producer_stream_deletion, + DeleteUniverseReplicationResponsePB* resp, CatalogManager& catalog_manager, + const LeaderEpoch& epoch) { + const auto& replication_group_id = universe.ReplicationGroupId(); + auto xcluster_manager = catalog_manager.GetXClusterManager(); + auto sys_catalog = catalog_manager.sys_catalog(); + + { + auto l = universe.LockForWrite(); + l.mutable_data()->pb.set_state(SysUniverseReplicationEntryPB::DELETING); + Status s = sys_catalog->Upsert(epoch, &universe); + RETURN_NOT_OK( + CheckLeaderStatus(s, "Updating delete universe replication info into sys-catalog")); + l.Commit(); + } + + auto l = universe.LockForWrite(); + l.mutable_data()->pb.set_state(SysUniverseReplicationEntryPB::DELETED); + + // We can skip the deletion of individual streams for DB Scoped replication since deletion of the + // outbound replication group will clean it up. + if (l->IsDbScoped()) { + skip_producer_stream_deletion = true; + } + + // Delete subscribers on the Consumer Registry (removes from TServers). + LOG(INFO) << "Deleting subscribers for producer " << replication_group_id; + { + auto cluster_config = catalog_manager.ClusterConfig(); + auto cl = cluster_config->LockForWrite(); + auto* consumer_registry = cl.mutable_data()->pb.mutable_consumer_registry(); + auto replication_group_map = consumer_registry->mutable_producer_map(); + auto it = replication_group_map->find(replication_group_id.ToString()); + if (it != replication_group_map->end()) { + replication_group_map->erase(it); + cl.mutable_data()->pb.set_version(cl.mutable_data()->pb.version() + 1); + RETURN_NOT_OK(CheckStatus( + sys_catalog->Upsert(epoch, cluster_config.get()), + "updating cluster config in sys-catalog")); + + xcluster_manager->SyncConsumerReplicationStatusMap( + replication_group_id, *replication_group_map); + cl.Commit(); + } + } + + // Delete CDC stream config on the Producer. + if (!l->pb.table_streams().empty() && !skip_producer_stream_deletion) { + auto result = universe.GetOrCreateXClusterRpcTasks(l->pb.producer_master_addresses()); + if (!result.ok()) { + LOG(WARNING) << "Unable to create cdc rpc task. CDC streams won't be deleted: " << result; + } else { + auto xcluster_rpc = *result; + std::vector streams; + std::unordered_map stream_to_producer_table_id; + for (const auto& [table_id, stream_id_str] : l->pb.table_streams()) { + auto stream_id = VERIFY_RESULT(xrepl::StreamId::FromString(stream_id_str)); + streams.emplace_back(stream_id); + stream_to_producer_table_id.emplace(stream_id, table_id); + } + + DeleteCDCStreamResponsePB delete_cdc_stream_resp; + // Set force_delete=true since we are deleting active xCluster streams. + // Since we are deleting universe replication, we should be ok with + // streams not existing on the other side, so we pass in ignore_errors + bool ignore_missing_streams = false; + auto s = xcluster_rpc->client()->DeleteCDCStream( + streams, true, /* force_delete */ + true /* ignore_errors */, &delete_cdc_stream_resp); + + if (delete_cdc_stream_resp.not_found_stream_ids().size() > 0) { + std::vector missing_streams; + missing_streams.reserve(delete_cdc_stream_resp.not_found_stream_ids().size()); + for (const auto& stream_id : delete_cdc_stream_resp.not_found_stream_ids()) { + missing_streams.emplace_back(Format( + "$0 (table_id: $1)", stream_id, + stream_to_producer_table_id[VERIFY_RESULT(xrepl::StreamId::FromString(stream_id))])); + } + auto message = + Format("Could not find the following streams: $0.", AsString(missing_streams)); + + if (s.ok()) { + // Returned but did not find some streams, so still need to warn the user about those. + ignore_missing_streams = true; + s = STATUS(NotFound, message); + } else { + s = s.CloneAndPrepend(message); + } + } + RETURN_NOT_OK(ReturnErrorOrAddWarning(s, ignore_errors | ignore_missing_streams, resp)); + } + } + + if (PREDICT_FALSE(FLAGS_TEST_exit_unfinished_deleting)) { + // Exit for testing services + return Status::OK(); + } + + // Delete universe in the Universe Config. + RETURN_NOT_OK( + ReturnErrorOrAddWarning(sys_catalog->Delete(epoch, &universe), ignore_errors, resp)); + + // Also update the mapping of consumer tables. + for (const auto& table : universe.metadata().state().pb.validated_tables()) { + xcluster_manager->RemoveTableConsumerStream(table.second, universe.ReplicationGroupId()); + } + + catalog_manager.RemoveUniverseReplicationFromMap(universe.ReplicationGroupId()); + + l.Commit(); + LOG(INFO) << "Processed delete universe replication of " << universe.ToString(); + + return Status::OK(); +} + } // namespace yb::master diff --git a/src/yb/master/xcluster/xcluster_replication_group.h b/src/yb/master/xcluster/xcluster_replication_group.h index d37d9c93a53f..066056394e05 100644 --- a/src/yb/master/xcluster/xcluster_replication_group.h +++ b/src/yb/master/xcluster/xcluster_replication_group.h @@ -31,12 +31,10 @@ namespace master { // TODO: #19714 Create XClusterReplicationGroup, a wrapper over UniverseReplicationInfo, that will // manage the ReplicationGroup and its ProducerEntryPB in ClusterConfigInfo. -// Check if the local AutoFlags config is compatible with the source universe and returns the source -// universe AutoFlags config version if they are compatible. -// If they are not compatible, returns a bad status. -// If the source universe is running an older version which does not support AutoFlags compatiblity -// check, returns an invalid AutoFlags config version. -Result GetAutoFlagConfigVersionIfCompatible( +// Returns nullopt when source universe does not support AutoFlags compatibility check. +// Returns a pair with a bool which indicates if the configs are compatible and the source universe +// AutoFlags config version. +Result>> ValidateAutoFlagsConfig( UniverseReplicationInfo& replication_info, const AutoFlagsConfigPB& local_config); // Reruns the AutoFlags compatiblity validation when source universe AutoFlags config version has @@ -94,12 +92,13 @@ Status RemoveNamespaceFromReplicationGroup( scoped_refptr universe, const NamespaceId& producer_namespace_id, CatalogManager& catalog_manager, const LeaderEpoch& epoch); -Status ValidateTableListForDbScopedReplication( - UniverseReplicationInfo& universe, const std::vector& namespace_ids, - const std::set& replicated_table_ids, const CatalogManager& catalog_manager); - // Returns true if the namespace is part of the DB Scoped replication group. bool HasNamespace(UniverseReplicationInfo& universe, const NamespaceId& consumer_namespace_id); +Status DeleteUniverseReplication( + UniverseReplicationInfo& universe, bool ignore_errors, bool skip_producer_stream_deletion, + DeleteUniverseReplicationResponsePB* resp, CatalogManager& catalog_manager, + const LeaderEpoch& epoch); + } // namespace master } // namespace yb diff --git a/src/yb/master/xcluster/xcluster_target_manager.cc b/src/yb/master/xcluster/xcluster_target_manager.cc index b46b461eacc4..85b90b724c36 100644 --- a/src/yb/master/xcluster/xcluster_target_manager.cc +++ b/src/yb/master/xcluster/xcluster_target_manager.cc @@ -18,16 +18,20 @@ #include "yb/master/catalog_entity_info.h" #include "yb/master/catalog_manager.h" #include "yb/master/master.h" + +#include "yb/master/xcluster_consumer_registry_service.h" #include "yb/master/xcluster/add_table_to_xcluster_target_task.h" #include "yb/master/xcluster/master_xcluster_util.h" +#include "yb/master/xcluster/xcluster_bootstrap_helper.h" #include "yb/master/xcluster/xcluster_replication_group.h" #include "yb/master/xcluster/xcluster_safe_time_service.h" - #include "yb/master/xcluster/xcluster_status.h" -#include "yb/master/xcluster_consumer_registry_service.h" -#include "yb/util/jsonwriter.h" +#include "yb/master/xcluster/xcluster_universe_replication_alter_helper.h" +#include "yb/master/xcluster/xcluster_universe_replication_setup_helper.h" + #include "yb/util/backoff_waiter.h" #include "yb/util/is_operation_done_result.h" +#include "yb/util/jsonwriter.h" #include "yb/util/scope_exit.h" #include "yb/util/status.h" @@ -235,7 +239,7 @@ Status XClusterTargetManager::WaitForSetupUniverseReplicationToFinish( return Wait( [&]() -> Result { auto is_operation_done = - VERIFY_RESULT(IsSetupUniverseReplicationDone(replication_group_id, catalog_manager_)); + VERIFY_RESULT(IsSetupUniverseReplicationDone(replication_group_id)); if (is_operation_done.done()) { RETURN_NOT_OK(is_operation_done.status()); @@ -1101,4 +1105,82 @@ Status XClusterTargetManager::ProcessPendingSchemaChanges(const LeaderEpoch& epo return Status::OK(); } +Status XClusterTargetManager::SetupUniverseReplication( + const SetupUniverseReplicationRequestPB* req, SetupUniverseReplicationResponsePB* resp, + const LeaderEpoch& epoch) { + return SetupUniverseReplicationHelper::Setup(master_, catalog_manager_, req, resp, epoch); +} + +Result XClusterTargetManager::IsSetupUniverseReplicationDone( + const xcluster::ReplicationGroupId& replication_group_id) { + return master::IsSetupUniverseReplicationDone(replication_group_id, catalog_manager_); +} + +Status XClusterTargetManager::SetupNamespaceReplicationWithBootstrap( + const SetupNamespaceReplicationWithBootstrapRequestPB* req, + SetupNamespaceReplicationWithBootstrapResponsePB* resp, const LeaderEpoch& epoch) { + return SetupUniverseReplicationWithBootstrapHelper::SetupWithBootstrap( + master_, catalog_manager_, req, resp, epoch); +} + +Result +XClusterTargetManager::IsSetupNamespaceReplicationWithBootstrapDone( + const xcluster::ReplicationGroupId& replication_group_id) { + auto bootstrap_info = catalog_manager_.GetUniverseReplicationBootstrap(replication_group_id); + SCHECK( + bootstrap_info != nullptr, NotFound, + Format("Could not find universe replication bootstrap $0", replication_group_id)); + + IsSetupNamespaceReplicationWithBootstrapDoneResponsePB resp; + + // Terminal states are DONE or some failure state. + auto l = bootstrap_info->LockForRead(); + resp.set_state(l->state()); + + if (l->is_done()) { + resp.set_done(true); + StatusToPB(Status::OK(), resp.mutable_bootstrap_error()); + } else if (l->is_deleted_or_failed()) { + resp.set_done(true); + + if (!bootstrap_info->GetReplicationBootstrapErrorStatus().ok()) { + StatusToPB( + bootstrap_info->GetReplicationBootstrapErrorStatus(), resp.mutable_bootstrap_error()); + } else { + LOG(WARNING) << "Did not find setup universe replication bootstrap error status."; + StatusToPB(STATUS(InternalError, "unknown error"), resp.mutable_bootstrap_error()); + } + + // Add failed bootstrap to GC now that we've responded to the user. + catalog_manager_.MarkReplicationBootstrapForCleanup(bootstrap_info->ReplicationGroupId()); + } else { + // Not done yet. + resp.set_done(false); + } + + return resp; +} + +Status XClusterTargetManager::AlterUniverseReplication( + const AlterUniverseReplicationRequestPB* req, AlterUniverseReplicationResponsePB* resp, + const LeaderEpoch& epoch) { + return AlterUniverseReplicationHelper::Alter(master_, catalog_manager_, req, resp, epoch); +} + +Status XClusterTargetManager::DeleteUniverseReplication( + const xcluster::ReplicationGroupId& replication_group_id, bool ignore_errors, + bool skip_producer_stream_deletion, DeleteUniverseReplicationResponsePB* resp, + const LeaderEpoch& epoch) { + auto ri = catalog_manager_.GetUniverseReplication(replication_group_id); + SCHECK(ri != nullptr, NotFound, "Universe replication $0 does not exist", replication_group_id); + + RETURN_NOT_OK(master::DeleteUniverseReplication( + *ri, ignore_errors, skip_producer_stream_deletion, resp, catalog_manager_, epoch)); + + // Run the safe time task as it may need to perform cleanups of it own + CreateXClusterSafeTimeTableAndStartService(); + + return Status::OK(); +} + } // namespace yb::master diff --git a/src/yb/master/xcluster/xcluster_target_manager.h b/src/yb/master/xcluster/xcluster_target_manager.h index 22132effd1e6..93ce74705f9f 100644 --- a/src/yb/master/xcluster/xcluster_target_manager.h +++ b/src/yb/master/xcluster/xcluster_target_manager.h @@ -18,6 +18,7 @@ #include "yb/master/xcluster/master_xcluster_types.h" #include "yb/master/xcluster/xcluster_catalog_entity.h" +#include "yb/util/is_operation_done_result.h" #include "yb/util/status_fwd.h" namespace yb::master { @@ -166,6 +167,30 @@ class XClusterTargetManager { const TableInfo& table_info, SchemaVersion consumer_schema_version, const LeaderEpoch& epoch) EXCLUDES(table_stream_ids_map_mutex_); + Status SetupUniverseReplication( + const SetupUniverseReplicationRequestPB* req, SetupUniverseReplicationResponsePB* resp, + const LeaderEpoch& epoch); + + Result IsSetupUniverseReplicationDone( + const xcluster::ReplicationGroupId& replication_group_id); + + Status SetupNamespaceReplicationWithBootstrap( + const SetupNamespaceReplicationWithBootstrapRequestPB* req, + SetupNamespaceReplicationWithBootstrapResponsePB* resp, const LeaderEpoch& epoch); + + Result + IsSetupNamespaceReplicationWithBootstrapDone( + const xcluster::ReplicationGroupId& replication_group_id); + + Status AlterUniverseReplication( + const AlterUniverseReplicationRequestPB* req, AlterUniverseReplicationResponsePB* resp, + const LeaderEpoch& epoch); + + Status DeleteUniverseReplication( + const xcluster::ReplicationGroupId& replication_group_id, bool ignore_errors, + bool skip_producer_stream_deletion, DeleteUniverseReplicationResponsePB* resp, + const LeaderEpoch& epoch); + private: // Gets the replication group status for the given replication group id. Does not populate the // table statuses. diff --git a/src/yb/master/xcluster/xcluster_universe_replication_alter_helper.cc b/src/yb/master/xcluster/xcluster_universe_replication_alter_helper.cc new file mode 100644 index 000000000000..b5cb87b86a7a --- /dev/null +++ b/src/yb/master/xcluster/xcluster_universe_replication_alter_helper.cc @@ -0,0 +1,318 @@ +// Copyright (c) YugabyteDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the License +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. See the License for the specific language governing permissions and limitations +// under the License. +// + +#include "yb/master/xcluster/xcluster_universe_replication_alter_helper.h" + +#include "yb/common/xcluster_util.h" +#include "yb/master/catalog_manager-internal.h" +#include "yb/master/catalog_manager.h" +#include "yb/master/master_util.h" +#include "yb/master/xcluster/xcluster_manager.h" +#include "yb/master/xcluster/xcluster_replication_group.h" + +namespace yb::master { + +AlterUniverseReplicationHelper::AlterUniverseReplicationHelper( + Master& master, CatalogManager& catalog_manager, const LeaderEpoch& epoch) + : master_(master), + catalog_manager_(catalog_manager), + sys_catalog_(*catalog_manager.sys_catalog()), + xcluster_manager_(*catalog_manager.GetXClusterManagerImpl()), + epoch_(epoch) {} + +AlterUniverseReplicationHelper::~AlterUniverseReplicationHelper() {} + +Status AlterUniverseReplicationHelper::Alter( + Master& master, CatalogManager& catalog_manager, const AlterUniverseReplicationRequestPB* req, + AlterUniverseReplicationResponsePB* resp, const LeaderEpoch& epoch) { + AlterUniverseReplicationHelper helper(master, catalog_manager, epoch); + return helper.AlterUniverseReplication(req, resp); +} + +Status AlterUniverseReplicationHelper::AlterUniverseReplication( + const AlterUniverseReplicationRequestPB* req, AlterUniverseReplicationResponsePB* resp) { + auto replication_group_id = xcluster::ReplicationGroupId(req->replication_group_id()); + auto original_ri = catalog_manager_.GetUniverseReplication(replication_group_id); + SCHECK_EC_FORMAT( + original_ri, NotFound, MasterError(MasterErrorPB::OBJECT_NOT_FOUND), + "Could not find xCluster replication group $0", replication_group_id); + + // Currently, config options are mutually exclusive to simplify transactionality. + int config_count = (req->producer_master_addresses_size() > 0 ? 1 : 0) + + (req->producer_table_ids_to_remove_size() > 0 ? 1 : 0) + + (req->producer_table_ids_to_add_size() > 0 ? 1 : 0) + + (req->has_new_replication_group_id() ? 1 : 0) + + (!req->producer_namespace_id_to_remove().empty() ? 1 : 0); + SCHECK_EC_FORMAT( + config_count == 1, InvalidArgument, MasterError(MasterErrorPB::INVALID_REQUEST), + "Only 1 Alter operation per request currently supported: $0", req->ShortDebugString()); + + if (req->producer_master_addresses_size() > 0) { + return UpdateProducerAddress(original_ri, req); + } + + if (req->has_producer_namespace_id_to_remove()) { + return RemoveNamespaceFromReplicationGroup( + original_ri, req->producer_namespace_id_to_remove(), catalog_manager_, epoch_); + } + + if (req->producer_table_ids_to_remove_size() > 0) { + std::vector table_ids( + req->producer_table_ids_to_remove().begin(), req->producer_table_ids_to_remove().end()); + return master::RemoveTablesFromReplicationGroup( + original_ri, table_ids, catalog_manager_, epoch_); + } + + if (req->producer_table_ids_to_add_size() > 0) { + RETURN_NOT_OK(AddTablesToReplication(original_ri, req, resp)); + xcluster_manager_.CreateXClusterSafeTimeTableAndStartService(); + return Status::OK(); + } + + if (req->has_new_replication_group_id()) { + return RenameUniverseReplication( + original_ri, xcluster::ReplicationGroupId(req->new_replication_group_id())); + } + + return Status::OK(); +} + +Status AlterUniverseReplicationHelper::UpdateProducerAddress( + scoped_refptr universe, const AlterUniverseReplicationRequestPB* req) { + CHECK_GT(req->producer_master_addresses_size(), 0); + + // TODO: Verify the input. Setup an RPC Task, ListTables, ensure same. + + { + // 1a. Persistent Config: Update the Universe Config for Master. + auto l = universe->LockForWrite(); + l.mutable_data()->pb.mutable_producer_master_addresses()->CopyFrom( + req->producer_master_addresses()); + + // 1b. Persistent Config: Update the Consumer Registry (updates TServers) + auto cluster_config = catalog_manager_.ClusterConfig(); + auto cl = cluster_config->LockForWrite(); + auto replication_group_map = + cl.mutable_data()->pb.mutable_consumer_registry()->mutable_producer_map(); + auto it = replication_group_map->find(req->replication_group_id()); + if (it == replication_group_map->end()) { + LOG(WARNING) << "Valid Producer Universe not in Consumer Registry: " + << req->replication_group_id(); + return STATUS( + NotFound, "Could not find xCluster ReplicationGroup", req->ShortDebugString(), + MasterError(MasterErrorPB::OBJECT_NOT_FOUND)); + } + it->second.mutable_master_addrs()->CopyFrom(req->producer_master_addresses()); + cl.mutable_data()->pb.set_version(cl.mutable_data()->pb.version() + 1); + + { + RETURN_NOT_OK(CheckStatus( + sys_catalog_.Upsert(epoch_, universe.get(), cluster_config.get()), + "Updating universe replication info and cluster config in sys-catalog")); + } + l.Commit(); + cl.Commit(); + } + + // 2. Memory Update: Change xcluster_rpc_tasks (Master cache) + { + auto result = universe->GetOrCreateXClusterRpcTasks(req->producer_master_addresses()); + if (!result.ok()) { + return result.status(); + } + } + + return Status::OK(); +} + +Status AlterUniverseReplicationHelper::AddTablesToReplication( + scoped_refptr universe, const AlterUniverseReplicationRequestPB* req, + AlterUniverseReplicationResponsePB* resp) { + SCHECK_GT(req->producer_table_ids_to_add_size(), 0, InvalidArgument, "No tables specified"); + + if (universe->IsDbScoped()) { + // We either add the entire namespace at once, or one table at a time as they get created. + if (req->has_producer_namespace_to_add()) { + SCHECK( + !req->producer_namespace_to_add().id().empty(), InvalidArgument, "Invalid Namespace Id"); + SCHECK( + !req->producer_namespace_to_add().name().empty(), InvalidArgument, + "Invalid Namespace name"); + SCHECK_EQ( + req->producer_namespace_to_add().database_type(), YQLDatabase::YQL_DATABASE_PGSQL, + InvalidArgument, "Invalid Namespace database_type"); + } else { + SCHECK_EQ( + req->producer_table_ids_to_add_size(), 1, InvalidArgument, + "When adding more than table to a DB scoped replication the namespace info must also be " + "provided"); + } + } else { + SCHECK( + !req->has_producer_namespace_to_add(), InvalidArgument, + "Cannot add namespaces to non DB scoped replication"); + } + + xcluster::ReplicationGroupId alter_replication_group_id(xcluster::GetAlterReplicationGroupId( + xcluster::ReplicationGroupId(req->replication_group_id()))); + + // If user passed in bootstrap ids, check that there is a bootstrap id for every table. + SCHECK( + req->producer_bootstrap_ids_to_add_size() == 0 || + req->producer_table_ids_to_add_size() == req->producer_bootstrap_ids_to_add().size(), + InvalidArgument, "Number of bootstrap ids must be equal to number of tables", + req->ShortDebugString()); + + // Verify no 'alter' command running. + auto alter_ri = catalog_manager_.GetUniverseReplication(alter_replication_group_id); + + { + if (alter_ri != nullptr) { + LOG(INFO) << "Found " << alter_replication_group_id << "... Removing"; + if (alter_ri->LockForRead()->is_deleted_or_failed()) { + // Delete previous Alter if it's completed but failed. + master::DeleteUniverseReplicationRequestPB delete_req; + delete_req.set_replication_group_id(alter_ri->id()); + master::DeleteUniverseReplicationResponsePB delete_resp; + Status s = xcluster_manager_.DeleteUniverseReplication( + &delete_req, &delete_resp, /*rpc=*/nullptr, epoch_); + if (!s.ok()) { + if (delete_resp.has_error()) { + resp->mutable_error()->Swap(delete_resp.mutable_error()); + return s; + } + return SetupError(resp->mutable_error(), s); + } + } else { + return STATUS( + InvalidArgument, "Alter for xCluster ReplicationGroup is already running", + req->ShortDebugString(), MasterError(MasterErrorPB::INVALID_REQUEST)); + } + } + } + + // Map each table id to its corresponding bootstrap id. + std::unordered_map table_id_to_bootstrap_id; + if (req->producer_bootstrap_ids_to_add().size() > 0) { + for (int i = 0; i < req->producer_table_ids_to_add().size(); i++) { + table_id_to_bootstrap_id[req->producer_table_ids_to_add(i)] = + req->producer_bootstrap_ids_to_add(i); + } + + // Ensure that table ids are unique. We need to do this here even though + // the same check is performed by SetupUniverseReplication because + // duplicate table ids can cause a bootstrap id entry in table_id_to_bootstrap_id + // to be overwritten. + if (table_id_to_bootstrap_id.size() != + implicit_cast(req->producer_table_ids_to_add().size())) { + return STATUS( + InvalidArgument, + "When providing bootstrap ids, " + "the list of tables must be unique", + req->ShortDebugString(), MasterError(MasterErrorPB::INVALID_REQUEST)); + } + } + + // Only add new tables. Ignore tables that are currently being replicated. + auto tid_iter = req->producer_table_ids_to_add(); + std::unordered_set new_tables(tid_iter.begin(), tid_iter.end()); + auto original_universe_l = universe->LockForRead(); + auto& original_universe_pb = original_universe_l->pb; + + for (const auto& table_id : original_universe_pb.tables()) { + new_tables.erase(table_id); + } + if (new_tables.empty()) { + return STATUS( + InvalidArgument, "xCluster ReplicationGroup already contains all requested tables", + req->ShortDebugString(), MasterError(MasterErrorPB::INVALID_REQUEST)); + } + + // 1. create an ALTER table request that mirrors the original 'setup_replication'. + master::SetupUniverseReplicationRequestPB setup_req; + master::SetupUniverseReplicationResponsePB setup_resp; + setup_req.set_replication_group_id(alter_replication_group_id.ToString()); + setup_req.mutable_producer_master_addresses()->CopyFrom( + original_universe_pb.producer_master_addresses()); + setup_req.set_transactional(original_universe_pb.transactional()); + + if (req->has_producer_namespace_to_add()) { + *setup_req.add_producer_namespaces() = req->producer_namespace_to_add(); + } + + for (const auto& table_id : new_tables) { + setup_req.add_producer_table_ids(table_id); + + // Add bootstrap id to request if it exists. + auto bootstrap_id = FindOrNull(table_id_to_bootstrap_id, table_id); + if (bootstrap_id) { + setup_req.add_producer_bootstrap_ids(*bootstrap_id); + } + } + + // 2. run the 'setup_replication' pipeline on the ALTER Table + Status s = + xcluster_manager_.SetupUniverseReplication(&setup_req, &setup_resp, /*rpc=*/nullptr, epoch_); + if (!s.ok()) { + if (setup_resp.has_error()) { + resp->mutable_error()->Swap(setup_resp.mutable_error()); + return s; + } + return SetupError(resp->mutable_error(), s); + } + + return Status::OK(); +} + +Status AlterUniverseReplicationHelper::RenameUniverseReplication( + scoped_refptr universe, + const xcluster::ReplicationGroupId new_replication_group_id) { + const xcluster::ReplicationGroupId old_replication_group_id(universe->id()); + SCHECK_NE( + old_replication_group_id, new_replication_group_id, InvalidArgument, + "Old and new replication ids must be different"); + + SCHECK( + catalog_manager_.GetUniverseReplication(new_replication_group_id) == nullptr, InvalidArgument, + "New replication id is already in use"); + + auto l = universe->LockForWrite(); + + // Since the replication_group_id is used as the key, we need to create a new + // UniverseReplicationInfo. + scoped_refptr new_ri = + new UniverseReplicationInfo(new_replication_group_id); + new_ri->mutable_metadata()->StartMutation(); + SysUniverseReplicationEntryPB* metadata = &new_ri->mutable_metadata()->mutable_dirty()->pb; + metadata->CopyFrom(l->pb); + metadata->set_replication_group_id(new_replication_group_id.ToString()); + + // Also need to update internal maps. + auto cluster_config = catalog_manager_.ClusterConfig(); + auto cl = cluster_config->LockForWrite(); + auto replication_group_map = + cl.mutable_data()->pb.mutable_consumer_registry()->mutable_producer_map(); + (*replication_group_map)[new_replication_group_id.ToString()] = + std::move((*replication_group_map)[old_replication_group_id.ToString()]); + replication_group_map->erase(old_replication_group_id.ToString()); + + RETURN_NOT_OK( + catalog_manager_.ReplaceUniverseReplication(*universe, *new_ri, *cluster_config, epoch_)); + + new_ri->mutable_metadata()->CommitMutation(); + cl.Commit(); + + return Status::OK(); +} + +} // namespace yb::master diff --git a/src/yb/master/xcluster/xcluster_universe_replication_alter_helper.h b/src/yb/master/xcluster/xcluster_universe_replication_alter_helper.h new file mode 100644 index 000000000000..8ac5d76a03ce --- /dev/null +++ b/src/yb/master/xcluster/xcluster_universe_replication_alter_helper.h @@ -0,0 +1,71 @@ +// Copyright (c) YugabyteDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the License +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. See the License for the specific language governing permissions and limitations +// under the License. +// + +#pragma once + +#include "yb/cdc/xcluster_types.h" +#include "yb/master/leader_epoch.h" +#include "yb/master/master_fwd.h" + +namespace yb { + +namespace rpc { +class RpcContext; +} // namespace rpc + +namespace master { + +class UniverseReplicationInfo; + +// Helper class to handle AlterUniverseReplication RPC. +// This object will only live as long as the operation is in progress. +class AlterUniverseReplicationHelper { + public: + ~AlterUniverseReplicationHelper(); + + static Status Alter( + Master& master, CatalogManager& catalog_manager, const AlterUniverseReplicationRequestPB* req, + AlterUniverseReplicationResponsePB* resp, const LeaderEpoch& epoch); + + private: + AlterUniverseReplicationHelper( + Master& master, CatalogManager& catalog_manager, const LeaderEpoch& epoch); + + Status AlterUniverseReplication( + const AlterUniverseReplicationRequestPB* req, AlterUniverseReplicationResponsePB* resp); + + Status UpdateProducerAddress( + scoped_refptr universe, + const AlterUniverseReplicationRequestPB* req); + + Status AddTablesToReplication( + scoped_refptr universe, const AlterUniverseReplicationRequestPB* req, + AlterUniverseReplicationResponsePB* resp); + + // Rename an existing Universe Replication. + Status RenameUniverseReplication( + scoped_refptr universe, + const xcluster::ReplicationGroupId new_replication_group_id); + + Master& master_; + CatalogManager& catalog_manager_; + SysCatalogTable& sys_catalog_; + XClusterManager& xcluster_manager_; + const LeaderEpoch epoch_; + + DISALLOW_COPY_AND_ASSIGN(AlterUniverseReplicationHelper); +}; + +} // namespace master + +} // namespace yb diff --git a/src/yb/master/xcluster/xcluster_universe_replication_setup_helper.cc b/src/yb/master/xcluster/xcluster_universe_replication_setup_helper.cc new file mode 100644 index 000000000000..90b6886168cb --- /dev/null +++ b/src/yb/master/xcluster/xcluster_universe_replication_setup_helper.cc @@ -0,0 +1,1380 @@ +// Copyright (c) YugabyteDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the License +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. See the License for the specific language governing permissions and limitations +// under the License. +// + +#include "yb/master/xcluster/xcluster_universe_replication_setup_helper.h" + +#include "yb/client/table_info.h" +#include "yb/client/table.h" +#include "yb/client/xcluster_client.h" + +#include "yb/common/colocated_util.h" +#include "yb/common/common_net.pb.h" +#include "yb/common/xcluster_util.h" + +#include "yb/gutil/bind.h" + +#include "yb/master/catalog_manager-internal.h" +#include "yb/master/catalog_manager.h" +#include "yb/master/leader_epoch.h" +#include "yb/master/master_ddl.pb.h" +#include "yb/master/master_error.h" +#include "yb/master/master_replication.pb.h" +#include "yb/master/master_util.h" +#include "yb/master/master.h" +#include "yb/master/xcluster_consumer_registry_service.h" +#include "yb/master/xcluster_rpc_tasks.h" +#include "yb/master/xcluster/master_xcluster_util.h" +#include "yb/master/xcluster/xcluster_manager.h" +#include "yb/master/xcluster/xcluster_replication_group.h" + +#include "yb/rpc/rpc_context.h" + +#include "yb/util/flags/auto_flags_util.h" +#include "yb/util/status.h" + +DEFINE_RUNTIME_bool(check_bootstrap_required, false, + "Is it necessary to check whether bootstrap is required for Universe Replication."); + +DEFINE_test_flag(bool, allow_ycql_transactional_xcluster, false, + "Determines if xCluster transactional replication on YCQL tables is allowed."); + +DEFINE_test_flag( + bool, fail_universe_replication_merge, false, + "Causes MergeUniverseReplication to fail with an error."); + +DEFINE_test_flag(bool, exit_unfinished_merging, false, + "Whether to exit part way through the merging universe process."); + +DECLARE_bool(enable_xcluster_auto_flag_validation); + +#define RETURN_ACTION_NOT_OK(expr, action) \ + RETURN_NOT_OK_PREPEND((expr), Format("An error occurred while $0", action)) + +namespace yb::master { + +namespace { + +Status ValidateTableListForDbScopedReplication( + UniverseReplicationInfo& universe, const std::vector& namespace_ids, + const std::set& replicated_tables, const CatalogManager& catalog_manager) { + std::set validated_tables; + + for (const auto& namespace_id : namespace_ids) { + auto table_infos = + VERIFY_RESULT(GetTablesEligibleForXClusterReplication(catalog_manager, namespace_id)); + + std::vector missing_tables; + + for (const auto& table_info : table_infos) { + const auto& table_id = table_info->id(); + if (replicated_tables.contains(table_id)) { + validated_tables.insert(table_id); + } else { + missing_tables.push_back(table_id); + } + } + + SCHECK_FORMAT( + missing_tables.empty(), IllegalState, + "Namespace $0 has additional tables that were not added to xCluster DB Scoped replication " + "group $1: $2", + namespace_id, universe.id(), yb::ToString(missing_tables)); + } + + auto diff = STLSetSymmetricDifference(replicated_tables, validated_tables); + SCHECK_FORMAT( + diff.empty(), IllegalState, + "xCluster DB Scoped replication group $0 contains tables $1 that do not belong to replicated " + "namespaces $2", + universe.id(), yb::ToString(diff), yb::ToString(namespace_ids)); + + return Status::OK(); +} + +// Check if the local AutoFlags config is compatible with the source universe and returns the source +// universe AutoFlags config version if they are compatible. +// If they are not compatible, returns a bad status. +// If the source universe is running an older version which does not support AutoFlags compatiblity +// check, returns an invalid AutoFlags config version. +Result GetAutoFlagConfigVersionIfCompatible( + UniverseReplicationInfo& replication_info, const AutoFlagsConfigPB& local_config) { + const auto& replication_group_id = replication_info.ReplicationGroupId(); + + VLOG_WITH_FUNC(2) << "Validating AutoFlags config for replication group: " << replication_group_id + << " with target config version: " << local_config.config_version(); + + auto validate_result = VERIFY_RESULT(ValidateAutoFlagsConfig(replication_info, local_config)); + + if (!validate_result) { + VLOG_WITH_FUNC(2) + << "Source universe of replication group " << replication_group_id + << " is running a version that does not support the AutoFlags compatibility check yet"; + return kInvalidAutoFlagsConfigVersion; + } + + auto& [is_valid, source_version] = *validate_result; + + SCHECK( + is_valid, IllegalState, + "AutoFlags between the universes are not compatible. Upgrade the target universe to a " + "version higher than or equal to the source universe"); + + return source_version; +} + +} // namespace + +SetupUniverseReplicationHelper::SetupUniverseReplicationHelper( + Master& master, CatalogManager& catalog_manager, const LeaderEpoch& epoch) + : master_(master), + catalog_manager_(catalog_manager), + sys_catalog_(*catalog_manager.sys_catalog()), + xcluster_manager_(*catalog_manager.GetXClusterManagerImpl()), + epoch_(epoch) {} + +SetupUniverseReplicationHelper::~SetupUniverseReplicationHelper() {} + +Status SetupUniverseReplicationHelper::Setup( + Master& master, CatalogManager& catalog_manager, const SetupUniverseReplicationRequestPB* req, + SetupUniverseReplicationResponsePB* resp, const LeaderEpoch& epoch) { + auto helper = scoped_refptr( + new SetupUniverseReplicationHelper(master, catalog_manager, epoch)); + return helper->SetupUniverseReplication(req, resp); +} + +void SetupUniverseReplicationHelper::MarkUniverseReplicationFailed( + scoped_refptr universe, const Status& failure_status) { + auto l = universe->LockForWrite(); + MarkUniverseReplicationFailed(failure_status, &l, universe); +} + +void SetupUniverseReplicationHelper::MarkUniverseReplicationFailed( + const Status& failure_status, CowWriteLock* universe_lock, + scoped_refptr universe) { + auto& l = *universe_lock; + if (l->pb.state() == SysUniverseReplicationEntryPB::DELETED) { + l.mutable_data()->pb.set_state(SysUniverseReplicationEntryPB::DELETED_ERROR); + } else { + l.mutable_data()->pb.set_state(SysUniverseReplicationEntryPB::FAILED); + } + + LOG(WARNING) << "Universe replication " << universe->ToString() + << " failed: " << failure_status.ToString(); + + universe->SetSetupUniverseReplicationErrorStatus(failure_status); + + // Update sys_catalog. + const Status s = sys_catalog_.Upsert(epoch_, universe); + + l.CommitOrWarn(s, "updating universe replication info in sys-catalog"); +} + +/* + * UniverseReplication is setup in 4 stages within the Catalog Manager + * 1. SetupUniverseReplication: Validates user input & requests Producer schema. + * 2. GetTableSchemaCallback: Validates Schema compatibility & requests Producer xCluster init. + * 3. AddStreamToUniverseAndInitConsumer: Setup RPC connections for xCluster Streaming + * 4. InitXClusterConsumer: Initializes the Consumer settings to begin tailing data + */ +Status SetupUniverseReplicationHelper::SetupUniverseReplication( + const SetupUniverseReplicationRequestPB* req, SetupUniverseReplicationResponsePB* resp) { + // Sanity checking section. + if (!req->has_replication_group_id()) { + return STATUS( + InvalidArgument, "Producer universe ID must be provided", req->ShortDebugString(), + MasterError(MasterErrorPB::INVALID_REQUEST)); + } + + if (req->producer_master_addresses_size() <= 0) { + return STATUS( + InvalidArgument, "Producer master address must be provided", req->ShortDebugString(), + MasterError(MasterErrorPB::INVALID_REQUEST)); + } + + if (req->producer_bootstrap_ids().size() > 0 && + req->producer_bootstrap_ids().size() != req->producer_table_ids().size()) { + return STATUS( + InvalidArgument, "Number of bootstrap ids must be equal to number of tables", + req->ShortDebugString(), MasterError(MasterErrorPB::INVALID_REQUEST)); + } + + { + auto l = catalog_manager_.ClusterConfig()->LockForRead(); + if (l->pb.cluster_uuid() == req->replication_group_id()) { + return STATUS( + InvalidArgument, "The request UUID and cluster UUID are identical.", + req->ShortDebugString(), MasterError(MasterErrorPB::INVALID_REQUEST)); + } + } + + RETURN_NOT_OK_PREPEND( + ValidateMasterAddressesBelongToDifferentCluster(master_, req->producer_master_addresses()), + req->ShortDebugString()); + + SetupReplicationInfo setup_info; + setup_info.transactional = req->transactional(); + auto& table_id_to_bootstrap_id = setup_info.table_bootstrap_ids; + + if (!req->producer_bootstrap_ids().empty()) { + if (req->producer_table_ids().size() != req->producer_bootstrap_ids_size()) { + return STATUS( + InvalidArgument, "Bootstrap ids must be provided for all tables", req->ShortDebugString(), + MasterError(MasterErrorPB::INVALID_REQUEST)); + } + + table_id_to_bootstrap_id.reserve(req->producer_table_ids().size()); + for (int i = 0; i < req->producer_table_ids().size(); i++) { + table_id_to_bootstrap_id.insert_or_assign( + req->producer_table_ids(i), + VERIFY_RESULT(xrepl::StreamId::FromString(req->producer_bootstrap_ids(i)))); + } + } + + SCHECK( + req->producer_namespaces().empty() || req->transactional(), InvalidArgument, + "Transactional flag must be set for Db scoped replication groups"); + + std::vector producer_namespace_ids, consumer_namespace_ids; + for (const auto& producer_ns_id : req->producer_namespaces()) { + SCHECK(!producer_ns_id.id().empty(), InvalidArgument, "Invalid Namespace Id"); + SCHECK(!producer_ns_id.name().empty(), InvalidArgument, "Invalid Namespace name"); + SCHECK_EQ( + producer_ns_id.database_type(), YQLDatabase::YQL_DATABASE_PGSQL, InvalidArgument, + "Invalid Namespace database_type"); + + producer_namespace_ids.push_back(producer_ns_id.id()); + + NamespaceIdentifierPB consumer_ns_id; + consumer_ns_id.set_database_type(YQLDatabase::YQL_DATABASE_PGSQL); + consumer_ns_id.set_name(producer_ns_id.name()); + auto ns_info = VERIFY_RESULT(catalog_manager_.FindNamespace(consumer_ns_id)); + consumer_namespace_ids.push_back(ns_info->id()); + } + + // We should set the universe uuid even if we fail with AlreadyPresent error. + { + auto universe_uuid = catalog_manager_.GetUniverseUuidIfExists(); + if (universe_uuid) { + resp->set_universe_uuid(universe_uuid->ToString()); + } + } + + auto ri = VERIFY_RESULT(CreateUniverseReplicationInfo( + xcluster::ReplicationGroupId(req->replication_group_id()), req->producer_master_addresses(), + producer_namespace_ids, consumer_namespace_ids, req->producer_table_ids(), + setup_info.transactional)); + + // Initialize the xCluster Stream by querying the Producer server for RPC sanity checks. + auto result = ri->GetOrCreateXClusterRpcTasks(req->producer_master_addresses()); + if (!result.ok()) { + MarkUniverseReplicationFailed(ri, ResultToStatus(result)); + return SetupError(resp->mutable_error(), MasterErrorPB::INVALID_REQUEST, result.status()); + } + std::shared_ptr xcluster_rpc = *result; + + // For each table, run an async RPC task to verify a sufficient Producer:Consumer schema match. + for (int i = 0; i < req->producer_table_ids_size(); i++) { + scoped_refptr shared_this = this; + + // SETUP CONTINUES after this async call. + Status s; + if (IsColocatedDbParentTableId(req->producer_table_ids(i))) { + auto tables_info = std::make_shared>(); + s = xcluster_rpc->client()->GetColocatedTabletSchemaByParentTableId( + req->producer_table_ids(i), tables_info, + Bind( + &SetupUniverseReplicationHelper::GetColocatedTabletSchemaCallback, shared_this, + ri->ReplicationGroupId(), tables_info, setup_info)); + } else if (IsTablegroupParentTableId(req->producer_table_ids(i))) { + auto tablegroup_id = GetTablegroupIdFromParentTableId(req->producer_table_ids(i)); + auto tables_info = std::make_shared>(); + s = xcluster_rpc->client()->GetTablegroupSchemaById( + tablegroup_id, tables_info, + Bind( + &SetupUniverseReplicationHelper::GetTablegroupSchemaCallback, shared_this, + ri->ReplicationGroupId(), tables_info, tablegroup_id, setup_info)); + } else { + auto table_info = std::make_shared(); + s = xcluster_rpc->client()->GetTableSchemaById( + req->producer_table_ids(i), table_info, + Bind( + &SetupUniverseReplicationHelper::GetTableSchemaCallback, shared_this, + ri->ReplicationGroupId(), table_info, setup_info)); + } + + if (!s.ok()) { + MarkUniverseReplicationFailed(ri, s); + return SetupError(resp->mutable_error(), MasterErrorPB::INVALID_REQUEST, s); + } + } + + LOG(INFO) << "Started schema validation for universe replication " << ri->ToString(); + return Status::OK(); +} + +Status SetupUniverseReplicationHelper::ValidateMasterAddressesBelongToDifferentCluster( + Master& master, const google::protobuf::RepeatedPtrField& master_addresses) { + std::vector cluster_master_addresses; + RETURN_NOT_OK(master.ListMasters(&cluster_master_addresses)); + std::unordered_set cluster_master_hps; + + for (const auto& cluster_elem : cluster_master_addresses) { + if (cluster_elem.has_registration()) { + auto p_rpc_addresses = cluster_elem.registration().private_rpc_addresses(); + for (const auto& p_rpc_elem : p_rpc_addresses) { + cluster_master_hps.insert(HostPort::FromPB(p_rpc_elem)); + } + + auto broadcast_addresses = cluster_elem.registration().broadcast_addresses(); + for (const auto& bc_elem : broadcast_addresses) { + cluster_master_hps.insert(HostPort::FromPB(bc_elem)); + } + } + + for (const auto& master_address : master_addresses) { + auto master_hp = HostPort::FromPB(master_address); + SCHECK( + !cluster_master_hps.contains(master_hp), InvalidArgument, + "Master address $0 belongs to the target universe", master_hp); + } + } + return Status::OK(); +} + +void SetupUniverseReplicationHelper::GetTableSchemaCallback( + const xcluster::ReplicationGroupId& replication_group_id, + const std::shared_ptr& producer_info, + const SetupReplicationInfo& setup_info, const Status& s) { + // First get the universe. + auto universe = catalog_manager_.GetUniverseReplication(replication_group_id); + if (universe == nullptr) { + LOG(ERROR) << "Universe not found: " << replication_group_id; + return; + } + + std::string action = "getting schema for table"; + auto status = s; + if (status.ok()) { + action = "validating table schema and creating xCluster stream"; + status = ValidateTableAndCreateStreams(universe, producer_info, setup_info); + } + + if (!status.ok()) { + LOG(ERROR) << "Error " << action << ". Universe: " << replication_group_id + << ", Table: " << producer_info->table_id << ": " << status; + MarkUniverseReplicationFailed(universe, status); + } +} + +void SetupUniverseReplicationHelper::GetTablegroupSchemaCallback( + const xcluster::ReplicationGroupId& replication_group_id, + const std::shared_ptr>& infos, + const TablegroupId& producer_tablegroup_id, const SetupReplicationInfo& setup_info, + const Status& s) { + // First get the universe. + auto universe = catalog_manager_.GetUniverseReplication(replication_group_id); + if (universe == nullptr) { + LOG(ERROR) << "Universe not found: " << replication_group_id; + return; + } + + auto status = + GetTablegroupSchemaCallbackInternal(universe, *infos, producer_tablegroup_id, setup_info, s); + if (!status.ok()) { + std::ostringstream oss; + for (size_t i = 0; i < infos->size(); ++i) { + oss << ((i == 0) ? "" : ", ") << (*infos)[i].table_id; + } + LOG(ERROR) << "Error processing for tables: [ " << oss.str() + << " ] for xCluster replication group " << replication_group_id << ": " << status; + MarkUniverseReplicationFailed(universe, status); + } +} + +Status SetupUniverseReplicationHelper::GetTablegroupSchemaCallbackInternal( + scoped_refptr& universe, const std::vector& infos, + const TablegroupId& producer_tablegroup_id, const SetupReplicationInfo& setup_info, + const Status& s) { + RETURN_NOT_OK(s); + + SCHECK(!infos.empty(), IllegalState, Format("Tablegroup $0 is empty", producer_tablegroup_id)); + + // validated_consumer_tables contains the table IDs corresponding to that + // from the producer tables. + std::unordered_set validated_consumer_tables; + ColocationSchemaVersions colocated_schema_versions; + colocated_schema_versions.reserve(infos.size()); + for (const auto& info : infos) { + // Validate each of the member table in the tablegroup. + GetTableSchemaResponsePB resp; + RETURN_NOT_OK(ValidateTableSchemaForXCluster(info, setup_info, &resp)); + + colocated_schema_versions.emplace_back( + resp.schema().colocated_table_id().colocation_id(), info.schema.version(), resp.version()); + validated_consumer_tables.insert(resp.identifier().table_id()); + } + + // Get the consumer tablegroup ID. Since this call is expensive (one needs to reverse lookup + // the tablegroup ID from table ID), we only do this call once and do validation afterward. + TablegroupId consumer_tablegroup_id; + // Starting Colocation GA, colocated databases create implicit underlying tablegroups. + bool colocated_database; + RETURN_NOT_OK(catalog_manager_.GetTableGroupAndColocationInfo( + *validated_consumer_tables.begin(), consumer_tablegroup_id, colocated_database)); + + // tables_in_consumer_tablegroup are the tables listed within the consumer_tablegroup_id. + // We need validated_consumer_tables and tables_in_consumer_tablegroup to be identical. + std::unordered_set tables_in_consumer_tablegroup; + { + GetTablegroupSchemaRequestPB req; + GetTablegroupSchemaResponsePB resp; + req.mutable_tablegroup()->set_id(consumer_tablegroup_id); + auto status = catalog_manager_.GetTablegroupSchema(&req, &resp); + if (status.ok() && resp.has_error()) { + status = StatusFromPB(resp.error().status()); + } + RETURN_NOT_OK_PREPEND( + status, + Format("Error when getting consumer tablegroup schema: $0", consumer_tablegroup_id)); + + for (const auto& info : resp.get_table_schema_response_pbs()) { + tables_in_consumer_tablegroup.insert(info.identifier().table_id()); + } + } + + if (validated_consumer_tables != tables_in_consumer_tablegroup) { + return STATUS( + IllegalState, + Format( + "Mismatch between tables associated with producer tablegroup $0 and " + "tables in consumer tablegroup $1: ($2) vs ($3).", + producer_tablegroup_id, consumer_tablegroup_id, AsString(validated_consumer_tables), + AsString(tables_in_consumer_tablegroup))); + } + + RETURN_NOT_OK_PREPEND( + IsBootstrapRequiredOnProducer( + universe, producer_tablegroup_id, setup_info.table_bootstrap_ids), + Format( + "Found error while checking if bootstrap is required for table $0", + producer_tablegroup_id)); + + TableId producer_parent_table_id; + TableId consumer_parent_table_id; + if (colocated_database) { + producer_parent_table_id = GetColocationParentTableId(producer_tablegroup_id); + consumer_parent_table_id = GetColocationParentTableId(consumer_tablegroup_id); + } else { + producer_parent_table_id = GetTablegroupParentTableId(producer_tablegroup_id); + consumer_parent_table_id = GetTablegroupParentTableId(consumer_tablegroup_id); + } + + SCHECK( + !xcluster_manager_.IsTableReplicationConsumer(consumer_parent_table_id), IllegalState, + "N:1 replication topology not supported"); + + RETURN_NOT_OK(AddValidatedTableAndCreateStreams( + universe, setup_info.table_bootstrap_ids, producer_parent_table_id, consumer_parent_table_id, + colocated_schema_versions)); + return Status::OK(); +} + +void SetupUniverseReplicationHelper::GetColocatedTabletSchemaCallback( + const xcluster::ReplicationGroupId& replication_group_id, + const std::shared_ptr>& infos, + const SetupReplicationInfo& setup_info, const Status& s) { + // First get the universe. + auto universe = catalog_manager_.GetUniverseReplication(replication_group_id); + if (universe == nullptr) { + LOG(ERROR) << "Universe not found: " << replication_group_id; + return; + } + + if (!s.ok()) { + MarkUniverseReplicationFailed(universe, s); + std::ostringstream oss; + for (size_t i = 0; i < infos->size(); ++i) { + oss << ((i == 0) ? "" : ", ") << (*infos)[i].table_id; + } + LOG(ERROR) << "Error getting schema for tables: [ " << oss.str() << " ]: " << s; + return; + } + + if (infos->empty()) { + LOG(WARNING) << "Received empty list of tables to validate: " << s; + return; + } + + // Validate table schemas. + std::unordered_set producer_parent_table_ids; + std::unordered_set consumer_parent_table_ids; + ColocationSchemaVersions colocated_schema_versions; + colocated_schema_versions.reserve(infos->size()); + for (const auto& info : *infos) { + // Verify that we have a colocated table. + if (!info.colocated) { + MarkUniverseReplicationFailed( + universe, + STATUS(InvalidArgument, Format("Received non-colocated table: $0", info.table_id))); + LOG(ERROR) << "Received non-colocated table: " << info.table_id; + return; + } + // Validate each table, and get the parent colocated table id for the consumer. + GetTableSchemaResponsePB resp; + Status table_status = ValidateTableSchemaForXCluster(info, setup_info, &resp); + if (!table_status.ok()) { + MarkUniverseReplicationFailed(universe, table_status); + LOG(ERROR) << "Found error while validating table schema for table " << info.table_id << ": " + << table_status; + return; + } + // Store the parent table ids. + producer_parent_table_ids.insert(GetColocatedDbParentTableId(info.table_name.namespace_id())); + consumer_parent_table_ids.insert( + GetColocatedDbParentTableId(resp.identifier().namespace_().id())); + colocated_schema_versions.emplace_back( + resp.schema().colocated_table_id().colocation_id(), info.schema.version(), resp.version()); + } + + // Verify that we only found one producer and one consumer colocated parent table id. + if (producer_parent_table_ids.size() != 1) { + auto message = Format( + "Found incorrect number of producer colocated parent table ids. " + "Expected 1, but found: $0", + AsString(producer_parent_table_ids)); + MarkUniverseReplicationFailed(universe, STATUS(InvalidArgument, message)); + LOG(ERROR) << message; + return; + } + if (consumer_parent_table_ids.size() != 1) { + auto message = Format( + "Found incorrect number of consumer colocated parent table ids. " + "Expected 1, but found: $0", + AsString(consumer_parent_table_ids)); + MarkUniverseReplicationFailed(universe, STATUS(InvalidArgument, message)); + LOG(ERROR) << message; + return; + } + + if (xcluster_manager_.IsTableReplicationConsumer(*consumer_parent_table_ids.begin())) { + std::string message = "N:1 replication topology not supported"; + MarkUniverseReplicationFailed(universe, STATUS(IllegalState, message)); + LOG(ERROR) << message; + return; + } + + Status status = IsBootstrapRequiredOnProducer( + universe, *producer_parent_table_ids.begin(), setup_info.table_bootstrap_ids); + if (!status.ok()) { + MarkUniverseReplicationFailed(universe, status); + LOG(ERROR) << "Found error while checking if bootstrap is required for table " + << *producer_parent_table_ids.begin() << ": " << status; + } + + status = AddValidatedTableAndCreateStreams( + universe, setup_info.table_bootstrap_ids, *producer_parent_table_ids.begin(), + *consumer_parent_table_ids.begin(), colocated_schema_versions); + + if (!status.ok()) { + LOG(ERROR) << "Found error while adding validated table to system catalog: " + << *producer_parent_table_ids.begin() << ": " << status; + return; + } +} + +Status SetupUniverseReplicationHelper::ValidateTableAndCreateStreams( + scoped_refptr universe, + const std::shared_ptr& producer_info, + const SetupReplicationInfo& setup_info) { + auto l = universe->LockForWrite(); + if (producer_info->table_name.namespace_name() == master::kSystemNamespaceName) { + auto status = STATUS(IllegalState, "Cannot replicate system tables."); + MarkUniverseReplicationFailed(status, &l, universe); + return status; + } + RETURN_ACTION_NOT_OK( + sys_catalog_.Upsert(epoch_, universe), "updating system tables in universe replication"); + l.Commit(); + + GetTableSchemaResponsePB consumer_schema; + RETURN_NOT_OK(ValidateTableSchemaForXCluster(*producer_info, setup_info, &consumer_schema)); + + // If Bootstrap Id is passed in then it must be provided for all tables. + const auto& producer_bootstrap_ids = setup_info.table_bootstrap_ids; + SCHECK( + producer_bootstrap_ids.empty() || producer_bootstrap_ids.contains(producer_info->table_id), + NotFound, + Format("Bootstrap id not found for table $0", producer_info->table_name.ToString())); + + RETURN_NOT_OK( + IsBootstrapRequiredOnProducer(universe, producer_info->table_id, producer_bootstrap_ids)); + + SchemaVersion producer_schema_version = producer_info->schema.version(); + SchemaVersion consumer_schema_version = consumer_schema.version(); + ColocationSchemaVersions colocated_schema_versions; + RETURN_NOT_OK(AddValidatedTableToUniverseReplication( + universe, producer_info->table_id, consumer_schema.identifier().table_id(), + producer_schema_version, consumer_schema_version, colocated_schema_versions)); + + return CreateStreamsIfReplicationValidated(universe, producer_bootstrap_ids); +} + +Status SetupUniverseReplicationHelper::ValidateTableSchemaForXCluster( + const client::YBTableInfo& info, const SetupReplicationInfo& setup_info, + GetTableSchemaResponsePB* resp) { + bool is_ysql_table = info.table_type == client::YBTableType::PGSQL_TABLE_TYPE; + if (setup_info.transactional && !GetAtomicFlag(&FLAGS_TEST_allow_ycql_transactional_xcluster) && + !is_ysql_table) { + return STATUS_FORMAT( + NotSupported, "Transactional replication is not supported for non-YSQL tables: $0", + info.table_name.ToString()); + } + + // Get corresponding table schema on local universe. + GetTableSchemaRequestPB req; + + auto* table = req.mutable_table(); + table->set_table_name(info.table_name.table_name()); + table->mutable_namespace_()->set_name(info.table_name.namespace_name()); + table->mutable_namespace_()->set_database_type( + GetDatabaseTypeForTable(client::ClientToPBTableType(info.table_type))); + + // Since YSQL tables are not present in table map, we first need to list tables to get the table + // ID and then get table schema. + // Remove this once table maps are fixed for YSQL. + ListTablesRequestPB list_req; + ListTablesResponsePB list_resp; + + list_req.set_name_filter(info.table_name.table_name()); + Status status = catalog_manager_.ListTables(&list_req, &list_resp); + SCHECK( + status.ok() && !list_resp.has_error(), NotFound, + Format("Error while listing table: $0", status.ToString())); + + const auto& source_schema = client::internal::GetSchema(info.schema); + for (const auto& t : list_resp.tables()) { + // Check that table name and namespace both match. + if (t.name() != info.table_name.table_name() || + t.namespace_().name() != info.table_name.namespace_name()) { + continue; + } + + // Check that schema name matches for YSQL tables, if the field is empty, fill in that + // information during GetTableSchema call later. + bool has_valid_pgschema_name = !t.pgschema_name().empty(); + if (is_ysql_table && has_valid_pgschema_name && + t.pgschema_name() != source_schema.SchemaName()) { + continue; + } + + // Get the table schema. + table->set_table_id(t.id()); + status = catalog_manager_.GetTableSchema(&req, resp); + SCHECK( + status.ok() && !resp->has_error(), NotFound, + Format("Error while getting table schema: $0", status.ToString())); + + // Double-check schema name here if the previous check was skipped. + if (is_ysql_table && !has_valid_pgschema_name) { + std::string target_schema_name = resp->schema().pgschema_name(); + if (target_schema_name != source_schema.SchemaName()) { + table->clear_table_id(); + continue; + } + } + + // Verify that the table on the target side supports replication. + if (is_ysql_table && t.has_relation_type() && t.relation_type() == MATVIEW_TABLE_RELATION) { + return STATUS_FORMAT( + NotSupported, "Replication is not supported for materialized view: $0", + info.table_name.ToString()); + } + + Schema consumer_schema; + auto result = SchemaFromPB(resp->schema(), &consumer_schema); + + // We now have a table match. Validate the schema. + SCHECK( + result.ok() && consumer_schema.EquivalentForDataCopy(source_schema), IllegalState, + Format( + "Source and target schemas don't match: " + "Source: $0, Target: $1, Source schema: $2, Target schema: $3", + info.table_id, resp->identifier().table_id(), info.schema.ToString(), + resp->schema().DebugString())); + break; + } + + SCHECK( + table->has_table_id(), NotFound, + Format( + "Could not find matching table for $0$1", info.table_name.ToString(), + (is_ysql_table ? " pgschema_name: " + source_schema.SchemaName() : ""))); + + // Still need to make map of table id to resp table id (to add to validated map) + // For colocated tables, only add the parent table since we only added the parent table to the + // original pb (we use the number of tables in the pb to determine when validation is done). + if (info.colocated) { + // We require that colocated tables have the same colocation ID. + // + // Backward compatibility: tables created prior to #7378 use YSQL table OID as a colocation ID. + auto source_clc_id = info.schema.has_colocation_id() + ? info.schema.colocation_id() + : CHECK_RESULT(GetPgsqlTableOid(info.table_id)); + auto target_clc_id = (resp->schema().has_colocated_table_id() && + resp->schema().colocated_table_id().has_colocation_id()) + ? resp->schema().colocated_table_id().colocation_id() + : CHECK_RESULT(GetPgsqlTableOid(resp->identifier().table_id())); + SCHECK( + source_clc_id == target_clc_id, IllegalState, + Format( + "Source and target colocation IDs don't match for colocated table: " + "Source: $0, Target: $1, Source colocation ID: $2, Target colocation ID: $3", + info.table_id, resp->identifier().table_id(), source_clc_id, target_clc_id)); + } + + SCHECK( + !xcluster_manager_.IsTableReplicationConsumer(table->table_id()), IllegalState, + "N:1 replication topology not supported"); + + return Status::OK(); +} + +Status SetupUniverseReplicationHelper::IsBootstrapRequiredOnProducer( + scoped_refptr universe, const TableId& producer_table, + const std::unordered_map& table_bootstrap_ids) { + if (!FLAGS_check_bootstrap_required) { + return Status::OK(); + } + auto master_addresses = universe->LockForRead()->pb.producer_master_addresses(); + boost::optional bootstrap_id; + if (table_bootstrap_ids.count(producer_table) > 0) { + bootstrap_id = table_bootstrap_ids.at(producer_table); + } + + auto xcluster_rpc = VERIFY_RESULT(universe->GetOrCreateXClusterRpcTasks(master_addresses)); + if (VERIFY_RESULT(xcluster_rpc->client()->IsBootstrapRequired({producer_table}, bootstrap_id))) { + return STATUS( + IllegalState, + Format( + "Error Missing Data in Logs. Bootstrap is required for producer $0", universe->id())); + } + return Status::OK(); +} + +Status SetupUniverseReplicationHelper::AddValidatedTableToUniverseReplication( + scoped_refptr universe, const TableId& producer_table, + const TableId& consumer_table, const SchemaVersion& producer_schema_version, + const SchemaVersion& consumer_schema_version, + const ColocationSchemaVersions& colocated_schema_versions) { + auto l = universe->LockForWrite(); + + auto map = l.mutable_data()->pb.mutable_validated_tables(); + (*map)[producer_table] = consumer_table; + + SchemaVersionMappingEntryPB entry; + if (IsColocationParentTableId(consumer_table)) { + for (const auto& [colocation_id, producer_schema_version, consumer_schema_version] : + colocated_schema_versions) { + auto colocated_entry = entry.add_colocated_schema_versions(); + auto colocation_mapping = colocated_entry->mutable_schema_version_mapping(); + colocated_entry->set_colocation_id(colocation_id); + colocation_mapping->set_producer_schema_version(producer_schema_version); + colocation_mapping->set_consumer_schema_version(consumer_schema_version); + } + } else { + auto mapping = entry.mutable_schema_version_mapping(); + mapping->set_producer_schema_version(producer_schema_version); + mapping->set_consumer_schema_version(consumer_schema_version); + } + + auto schema_versions_map = l.mutable_data()->pb.mutable_schema_version_mappings(); + (*schema_versions_map)[producer_table] = std::move(entry); + + // TODO: end of config validation should be where SetupUniverseReplication exits back to user + LOG(INFO) << "UpdateItem in AddValidatedTable"; + + // Update sys_catalog. + RETURN_ACTION_NOT_OK( + sys_catalog_.Upsert(epoch_, universe), "updating universe replication info in sys-catalog"); + l.Commit(); + + return Status::OK(); +} + +Status SetupUniverseReplicationHelper::AddValidatedTableAndCreateStreams( + scoped_refptr universe, + const std::unordered_map& table_bootstrap_ids, + const TableId& producer_table, const TableId& consumer_table, + const ColocationSchemaVersions& colocated_schema_versions) { + RETURN_NOT_OK(AddValidatedTableToUniverseReplication( + universe, producer_table, consumer_table, cdc::kInvalidSchemaVersion, + cdc::kInvalidSchemaVersion, colocated_schema_versions)); + return CreateStreamsIfReplicationValidated(universe, table_bootstrap_ids); +} + +Status SetupUniverseReplicationHelper::CreateStreamsIfReplicationValidated( + scoped_refptr universe, + const std::unordered_map& table_bootstrap_ids) { + auto l = universe->LockForWrite(); + if (l->is_deleted_or_failed()) { + // Nothing to do since universe is being deleted. + return STATUS(Aborted, "Universe is being deleted"); + } + + auto* mutable_pb = &l.mutable_data()->pb; + + if (mutable_pb->state() != SysUniverseReplicationEntryPB::INITIALIZING) { + VLOG_WITH_FUNC(2) << "Universe replication is in invalid state " << l->pb.state(); + + // Replication stream has already been validated, or is in FAILED state which cannot be + // recovered. + return Status::OK(); + } + + if (mutable_pb->validated_tables_size() != mutable_pb->tables_size()) { + // Replication stream is not yet ready. All the tables have to be validated. + return Status::OK(); + } + + auto master_addresses = mutable_pb->producer_master_addresses(); + cdc::StreamModeTransactional transactional(mutable_pb->transactional()); + auto res = universe->GetOrCreateXClusterRpcTasks(master_addresses); + if (!res.ok()) { + MarkUniverseReplicationFailed(res.status(), &l, universe); + return STATUS( + InternalError, Format( + "Error while setting up client for producer $0: $1", universe->id(), + res.status().ToString())); + } + std::shared_ptr xcluster_rpc = *res; + + // Now, all tables are validated. + std::vector validated_tables; + auto& tbl_iter = mutable_pb->tables(); + validated_tables.insert(validated_tables.begin(), tbl_iter.begin(), tbl_iter.end()); + + mutable_pb->set_state(SysUniverseReplicationEntryPB::VALIDATED); + // Update sys_catalog. + RETURN_ACTION_NOT_OK( + sys_catalog_.Upsert(epoch_, universe), "updating universe replication info in sys-catalog"); + l.Commit(); + + // Create xCluster stream for each validated table, after persisting the replication state change. + if (!validated_tables.empty()) { + // Keep track of the bootstrap_id, table_id, and options of streams to update after + // the last GetStreamCallback finishes. Will be updated by multiple async + // GetStreamCallback. + auto stream_update_infos = std::make_shared(); + stream_update_infos->reserve(validated_tables.size()); + auto update_infos_lock = std::make_shared(); + + for (const auto& table : validated_tables) { + scoped_refptr shared_this = this; + + auto producer_bootstrap_id = FindOrNull(table_bootstrap_ids, table); + if (producer_bootstrap_id && *producer_bootstrap_id) { + auto table_id = std::make_shared(); + auto stream_options = std::make_shared>(); + xcluster_rpc->client()->GetCDCStream( + *producer_bootstrap_id, table_id, stream_options, + std::bind( + &SetupUniverseReplicationHelper::GetStreamCallback, shared_this, + *producer_bootstrap_id, table_id, stream_options, universe->ReplicationGroupId(), + table, xcluster_rpc, std::placeholders::_1, stream_update_infos, + update_infos_lock)); + } else { + // Streams are used as soon as they are created so set state to active. + client::XClusterClient(*xcluster_rpc->client()) + .CreateXClusterStreamAsync( + table, /*active=*/true, transactional, + std::bind( + &SetupUniverseReplicationHelper::AddStreamToUniverseAndInitConsumer, + shared_this, universe->ReplicationGroupId(), table, std::placeholders::_1, + nullptr /* on_success_cb */)); + } + } + } + return Status::OK(); +} + +void SetupUniverseReplicationHelper::GetStreamCallback( + const xrepl::StreamId& bootstrap_id, std::shared_ptr table_id, + std::shared_ptr> options, + const xcluster::ReplicationGroupId& replication_group_id, const TableId& table, + std::shared_ptr xcluster_rpc, const Status& s, + std::shared_ptr stream_update_infos, + std::shared_ptr update_infos_lock) { + if (!s.ok()) { + LOG(ERROR) << "Unable to find bootstrap id " << bootstrap_id; + AddStreamToUniverseAndInitConsumer(replication_group_id, table, s); + return; + } + + if (*table_id != table) { + const Status invalid_bootstrap_id_status = STATUS_FORMAT( + InvalidArgument, "Invalid bootstrap id for table $0. Bootstrap id $1 belongs to table $2", + table, bootstrap_id, *table_id); + LOG(ERROR) << invalid_bootstrap_id_status; + AddStreamToUniverseAndInitConsumer(replication_group_id, table, invalid_bootstrap_id_status); + return; + } + + auto original_universe = catalog_manager_.GetUniverseReplication(replication_group_id); + + if (original_universe == nullptr) { + LOG(ERROR) << "Universe not found: " << replication_group_id; + return; + } + + cdc::StreamModeTransactional transactional(original_universe->LockForRead()->pb.transactional()); + + // todo check options + { + std::lock_guard lock(*update_infos_lock); + stream_update_infos->push_back({bootstrap_id, *table_id, *options}); + } + + const auto update_xrepl_stream_func = [&]() -> Status { + // Extra callback on universe setup success - update the producer to let it know that + // the bootstrapping is complete. This callback will only be called once among all + // the GetStreamCallback calls, and we update all streams in batch at once. + + std::vector update_bootstrap_ids; + std::vector update_entries; + { + std::lock_guard lock(*update_infos_lock); + + for (const auto& [update_bootstrap_id, update_table_id, update_options] : + *stream_update_infos) { + SysCDCStreamEntryPB new_entry; + new_entry.add_table_id(update_table_id); + new_entry.mutable_options()->Reserve(narrow_cast(update_options.size())); + for (const auto& [key, value] : update_options) { + if (key == cdc::kStreamState) { + // We will set state explicitly. + continue; + } + auto new_option = new_entry.add_options(); + new_option->set_key(key); + new_option->set_value(value); + } + new_entry.set_state(master::SysCDCStreamEntryPB::ACTIVE); + new_entry.set_transactional(transactional); + + update_bootstrap_ids.push_back(update_bootstrap_id); + update_entries.push_back(new_entry); + } + } + + RETURN_NOT_OK_PREPEND( + xcluster_rpc->client()->UpdateCDCStream(update_bootstrap_ids, update_entries), + "Unable to update xrepl stream options on source universe"); + + { + std::lock_guard lock(*update_infos_lock); + stream_update_infos->clear(); + } + return Status::OK(); + }; + + AddStreamToUniverseAndInitConsumer( + replication_group_id, table, bootstrap_id, update_xrepl_stream_func); +} + +void SetupUniverseReplicationHelper::AddStreamToUniverseAndInitConsumer( + const xcluster::ReplicationGroupId& replication_group_id, const TableId& table_id, + const Result& stream_id, std::function on_success_cb) { + auto universe = catalog_manager_.GetUniverseReplication(replication_group_id); + if (universe == nullptr) { + LOG(ERROR) << "Universe not found: " << replication_group_id; + return; + } + + Status s; + if (!stream_id.ok()) { + s = std::move(stream_id).status(); + } else { + s = AddStreamToUniverseAndInitConsumerInternal( + universe, table_id, *stream_id, std::move(on_success_cb)); + } + + if (!s.ok()) { + MarkUniverseReplicationFailed(universe, s); + } +} + +Status SetupUniverseReplicationHelper::AddStreamToUniverseAndInitConsumerInternal( + scoped_refptr universe, const TableId& table_id, + const xrepl::StreamId& stream_id, std::function on_success_cb) { + bool merge_alter = false; + bool validated_all_tables = false; + std::vector consumer_info; + { + auto l = universe->LockForWrite(); + if (l->is_deleted_or_failed()) { + // Nothing to do if universe is being deleted. + return Status::OK(); + } + + auto map = l.mutable_data()->pb.mutable_table_streams(); + (*map)[table_id] = stream_id.ToString(); + + // This functions as a barrier: waiting for the last RPC call from GetTableSchemaCallback. + if (l.mutable_data()->pb.table_streams_size() == l->pb.tables_size()) { + // All tables successfully validated! Register xCluster consumers & start replication. + validated_all_tables = true; + LOG(INFO) << "Registering xCluster consumers for universe " << universe->id(); + + consumer_info.reserve(l->pb.tables_size()); + std::set consumer_table_ids; + for (const auto& [producer_table_id, consumer_table_id] : l->pb.validated_tables()) { + consumer_table_ids.insert(consumer_table_id); + + XClusterConsumerStreamInfo info; + info.producer_table_id = producer_table_id; + info.consumer_table_id = consumer_table_id; + info.stream_id = VERIFY_RESULT(xrepl::StreamId::FromString((*map)[producer_table_id])); + consumer_info.push_back(info); + } + + if (l->IsDbScoped()) { + std::vector consumer_namespace_ids; + for (const auto& ns_info : l->pb.db_scoped_info().namespace_infos()) { + consumer_namespace_ids.push_back(ns_info.consumer_namespace_id()); + } + RETURN_NOT_OK(ValidateTableListForDbScopedReplication( + *universe, consumer_namespace_ids, consumer_table_ids, catalog_manager_)); + } + + std::vector hp; + HostPortsFromPBs(l->pb.producer_master_addresses(), &hp); + auto xcluster_rpc_tasks = + VERIFY_RESULT(universe->GetOrCreateXClusterRpcTasks(l->pb.producer_master_addresses())); + RETURN_NOT_OK(InitXClusterConsumer( + consumer_info, HostPort::ToCommaSeparatedString(hp), *universe.get(), + xcluster_rpc_tasks)); + + if (xcluster::IsAlterReplicationGroupId(universe->ReplicationGroupId())) { + // Don't enable ALTER universes, merge them into the main universe instead. + // on_success_cb will be invoked in MergeUniverseReplication. + merge_alter = true; + } else { + l.mutable_data()->pb.set_state(SysUniverseReplicationEntryPB::ACTIVE); + if (on_success_cb) { + // Before updating, run any callbacks on success. + RETURN_NOT_OK(on_success_cb()); + } + } + } + + // Update sys_catalog with new producer table id info. + RETURN_NOT_OK(sys_catalog_.Upsert(epoch_, universe)); + + l.Commit(); + } + + if (!validated_all_tables) { + return Status::OK(); + } + + auto final_id = xcluster::GetOriginalReplicationGroupId(universe->ReplicationGroupId()); + // If this is an 'alter', merge back into primary command now that setup is a success. + if (merge_alter) { + RETURN_NOT_OK(MergeUniverseReplication(universe, final_id, std::move(on_success_cb))); + } + // Update the in-memory cache of consumer tables. + for (const auto& info : consumer_info) { + xcluster_manager_.RecordTableConsumerStream(info.consumer_table_id, final_id, info.stream_id); + } + + return Status::OK(); +} + +Status SetupUniverseReplicationHelper::InitXClusterConsumer( + const std::vector& consumer_info, const std::string& master_addrs, + UniverseReplicationInfo& replication_info, + std::shared_ptr xcluster_rpc_tasks) { + auto universe_l = replication_info.LockForRead(); + auto schema_version_mappings = universe_l->pb.schema_version_mappings(); + + // Get the tablets in the consumer table. + cdc::ProducerEntryPB producer_entry; + + if (FLAGS_enable_xcluster_auto_flag_validation) { + auto compatible_auto_flag_config_version = VERIFY_RESULT( + GetAutoFlagConfigVersionIfCompatible(replication_info, master_.GetAutoFlagsConfig())); + producer_entry.set_compatible_auto_flag_config_version(compatible_auto_flag_config_version); + producer_entry.set_validated_auto_flags_config_version(compatible_auto_flag_config_version); + } + + auto cluster_config = catalog_manager_.ClusterConfig(); + auto l = cluster_config->LockForWrite(); + auto* consumer_registry = l.mutable_data()->pb.mutable_consumer_registry(); + auto transactional = universe_l->pb.transactional(); + if (!xcluster::IsAlterReplicationGroupId(replication_info.ReplicationGroupId())) { + if (universe_l->IsDbScoped()) { + DCHECK(transactional); + } + } + + for (const auto& stream_info : consumer_info) { + auto consumer_tablet_keys = + VERIFY_RESULT(catalog_manager_.GetTableKeyRanges(stream_info.consumer_table_id)); + auto schema_version = + VERIFY_RESULT(catalog_manager_.GetTableSchemaVersion(stream_info.consumer_table_id)); + + cdc::StreamEntryPB stream_entry; + // Get producer tablets and map them to the consumer tablets + RETURN_NOT_OK(InitXClusterStream( + stream_info.producer_table_id, stream_info.consumer_table_id, consumer_tablet_keys, + &stream_entry, xcluster_rpc_tasks)); + // Set the validated consumer schema version + auto* producer_schema_pb = stream_entry.mutable_producer_schema(); + producer_schema_pb->set_last_compatible_consumer_schema_version(schema_version); + auto* schema_versions = stream_entry.mutable_schema_versions(); + auto mapping = FindOrNull(schema_version_mappings, stream_info.producer_table_id); + SCHECK(mapping, NotFound, Format("No schema mapping for $0", stream_info.producer_table_id)); + if (IsColocationParentTableId(stream_info.consumer_table_id)) { + // Get all the child tables and add their mappings + auto& colocated_schema_versions_pb = *stream_entry.mutable_colocated_schema_versions(); + for (const auto& colocated_entry : mapping->colocated_schema_versions()) { + auto colocation_id = colocated_entry.colocation_id(); + colocated_schema_versions_pb[colocation_id].set_current_producer_schema_version( + colocated_entry.schema_version_mapping().producer_schema_version()); + colocated_schema_versions_pb[colocation_id].set_current_consumer_schema_version( + colocated_entry.schema_version_mapping().consumer_schema_version()); + } + } else { + schema_versions->set_current_producer_schema_version( + mapping->schema_version_mapping().producer_schema_version()); + schema_versions->set_current_consumer_schema_version( + mapping->schema_version_mapping().consumer_schema_version()); + } + + // Mark this stream as special if it is for the ddl_queue table. + auto table_info = catalog_manager_.GetTableInfo(stream_info.consumer_table_id); + stream_entry.set_is_ddl_queue_table( + table_info->GetTableType() == PGSQL_TABLE_TYPE && + table_info->name() == xcluster::kDDLQueueTableName && + table_info->pgschema_name() == xcluster::kDDLQueuePgSchemaName); + + (*producer_entry.mutable_stream_map())[stream_info.stream_id.ToString()] = + std::move(stream_entry); + } + + // Log the Network topology of the Producer Cluster + auto master_addrs_list = StringSplit(master_addrs, ','); + producer_entry.mutable_master_addrs()->Reserve(narrow_cast(master_addrs_list.size())); + for (const auto& addr : master_addrs_list) { + auto hp = VERIFY_RESULT(HostPort::FromString(addr, 0)); + HostPortToPB(hp, producer_entry.add_master_addrs()); + } + + auto* replication_group_map = consumer_registry->mutable_producer_map(); + SCHECK_EQ( + replication_group_map->count(replication_info.id()), 0, InvalidArgument, + "Already created a consumer for this universe"); + + // TServers will use the ClusterConfig to create xCluster Consumers for applicable local tablets. + (*replication_group_map)[replication_info.id()] = std::move(producer_entry); + + l.mutable_data()->pb.set_version(l.mutable_data()->pb.version() + 1); + RETURN_NOT_OK(CheckStatus( + sys_catalog_.Upsert(epoch_, cluster_config.get()), "updating cluster config in sys-catalog")); + + xcluster_manager_.SyncConsumerReplicationStatusMap( + replication_info.ReplicationGroupId(), *replication_group_map); + l.Commit(); + + xcluster_manager_.CreateXClusterSafeTimeTableAndStartService(); + + return Status::OK(); +} + +Status SetupUniverseReplicationHelper::MergeUniverseReplication( + scoped_refptr universe, xcluster::ReplicationGroupId original_id, + std::function on_success_cb) { + // Merge back into primary command now that setup is a success. + LOG(INFO) << "Merging xCluster ReplicationGroup: " << universe->id() << " into " << original_id; + + SCHECK( + !FLAGS_TEST_fail_universe_replication_merge, IllegalState, + "TEST_fail_universe_replication_merge"); + + auto original_universe = catalog_manager_.GetUniverseReplication(original_id); + if (original_universe == nullptr) { + LOG(ERROR) << "Universe not found: " << original_id; + return Status::OK(); + } + + { + auto cluster_config = catalog_manager_.ClusterConfig(); + // Acquire Locks in order of Original Universe, Cluster Config, New Universe + auto original_lock = original_universe->LockForWrite(); + auto alter_lock = universe->LockForWrite(); + auto cl = cluster_config->LockForWrite(); + + // Merge Cluster Config for TServers. + auto* consumer_registry = cl.mutable_data()->pb.mutable_consumer_registry(); + auto pm = consumer_registry->mutable_producer_map(); + auto original_producer_entry = pm->find(original_universe->id()); + auto alter_producer_entry = pm->find(universe->id()); + if (original_producer_entry != pm->end() && alter_producer_entry != pm->end()) { + // Merge the Tables from the Alter into the original. + auto as = alter_producer_entry->second.stream_map(); + original_producer_entry->second.mutable_stream_map()->insert(as.begin(), as.end()); + // Delete the Alter + pm->erase(alter_producer_entry); + } else { + LOG(WARNING) << "Could not find both universes in Cluster Config: " << universe->id(); + } + cl.mutable_data()->pb.set_version(cl.mutable_data()->pb.version() + 1); + + // Merge Master Config on Consumer. (no need for Producer changes, since it uses stream_id) + // Merge Table->StreamID mapping. + auto& alter_pb = alter_lock.mutable_data()->pb; + auto& original_pb = original_lock.mutable_data()->pb; + + auto* alter_tables = alter_pb.mutable_tables(); + original_pb.mutable_tables()->MergeFrom(*alter_tables); + alter_tables->Clear(); + auto* alter_table_streams = alter_pb.mutable_table_streams(); + original_pb.mutable_table_streams()->insert( + alter_table_streams->begin(), alter_table_streams->end()); + alter_table_streams->clear(); + auto* alter_validated_tables = alter_pb.mutable_validated_tables(); + original_pb.mutable_validated_tables()->insert( + alter_validated_tables->begin(), alter_validated_tables->end()); + alter_validated_tables->clear(); + if (alter_lock.mutable_data()->IsDbScoped()) { + auto* alter_namespace_info = alter_pb.mutable_db_scoped_info()->mutable_namespace_infos(); + original_pb.mutable_db_scoped_info()->mutable_namespace_infos()->MergeFrom( + *alter_namespace_info); + alter_namespace_info->Clear(); + } + + alter_pb.set_state(SysUniverseReplicationEntryPB::DELETED); + + if (PREDICT_FALSE(FLAGS_TEST_exit_unfinished_merging)) { + return Status::OK(); + } + + if (on_success_cb) { + RETURN_NOT_OK(on_success_cb()); + } + + { + // Need all three updates to be atomic. + auto s = CheckStatus( + sys_catalog_.Upsert( + epoch_, original_universe.get(), universe.get(), cluster_config.get()), + "Updating universe replication entries and cluster config in sys-catalog"); + } + + xcluster_manager_.SyncConsumerReplicationStatusMap( + original_universe->ReplicationGroupId(), *pm); + xcluster_manager_.SyncConsumerReplicationStatusMap(universe->ReplicationGroupId(), *pm); + + alter_lock.Commit(); + cl.Commit(); + original_lock.Commit(); + } + + // Add alter temp universe to GC. + catalog_manager_.MarkUniverseForCleanup(universe->ReplicationGroupId()); + + LOG(INFO) << "Done with Merging " << universe->id() << " into " << original_universe->id(); + + xcluster_manager_.CreateXClusterSafeTimeTableAndStartService(); + + return Status::OK(); +} + +Result> +SetupUniverseReplicationHelper::CreateUniverseReplicationInfo( + const xcluster::ReplicationGroupId& replication_group_id, + const google::protobuf::RepeatedPtrField& master_addresses, + const std::vector& producer_namespace_ids, + const std::vector& consumer_namespace_ids, + const google::protobuf::RepeatedPtrField& table_ids, bool transactional) { + SCHECK_EQ( + producer_namespace_ids.size(), consumer_namespace_ids.size(), InvalidArgument, + "We should have the namespaceIds from both producer and consumer"); + + SCHECK( + catalog_manager_.GetUniverseReplication(replication_group_id) == nullptr, AlreadyPresent, + "Replication group $0 already present", replication_group_id.ToString()); + + for (const auto& universe : catalog_manager_.GetAllUniverseReplications()) { + for (const auto& consumer_namespace_id : consumer_namespace_ids) { + SCHECK_FORMAT( + !IncludesConsumerNamespace(*universe, consumer_namespace_id), AlreadyPresent, + "Namespace $0 already included in replication group $1", consumer_namespace_id, + universe->ReplicationGroupId()); + } + } + + // Create an entry in the system catalog DocDB for this new universe replication. + scoped_refptr ri = new UniverseReplicationInfo(replication_group_id); + ri->mutable_metadata()->StartMutation(); + SysUniverseReplicationEntryPB* metadata = &ri->mutable_metadata()->mutable_dirty()->pb; + metadata->set_replication_group_id(replication_group_id.ToString()); + metadata->mutable_producer_master_addresses()->CopyFrom(master_addresses); + + if (!producer_namespace_ids.empty()) { + auto* db_scoped_info = metadata->mutable_db_scoped_info(); + for (size_t i = 0; i < producer_namespace_ids.size(); i++) { + auto* ns_info = db_scoped_info->mutable_namespace_infos()->Add(); + ns_info->set_producer_namespace_id(producer_namespace_ids[i]); + ns_info->set_consumer_namespace_id(consumer_namespace_ids[i]); + } + } + metadata->mutable_tables()->CopyFrom(table_ids); + metadata->set_state(SysUniverseReplicationEntryPB::INITIALIZING); + metadata->set_transactional(transactional); + + RETURN_NOT_OK(CheckLeaderStatus( + sys_catalog_.Upsert(epoch_, ri), "inserting universe replication info into sys-catalog")); + + // Commit the in-memory state now that it's added to the persistent catalog. + ri->mutable_metadata()->CommitMutation(); + LOG(INFO) << "Setup universe replication from producer " << ri->ToString(); + + catalog_manager_.InsertNewUniverseReplication(*ri); + + // Make sure the AutoFlags are compatible. + // This is done after the replication info is persisted since it performs RPC calls to source + // universe and we can crash during this call. + // TODO: When new master starts it can retry this step or mark the replication group as failed. + if (FLAGS_enable_xcluster_auto_flag_validation) { + const auto auto_flags_config = master_.GetAutoFlagsConfig(); + auto status = ResultToStatus(GetAutoFlagConfigVersionIfCompatible(*ri, auto_flags_config)); + + if (!status.ok()) { + MarkUniverseReplicationFailed(ri, status); + return status.CloneAndAddErrorCode(MasterError(MasterErrorPB::INVALID_REQUEST)); + } + + auto l = ri->LockForWrite(); + l.mutable_data()->pb.set_validated_local_auto_flags_config_version( + auto_flags_config.config_version()); + + RETURN_NOT_OK(CheckLeaderStatus( + sys_catalog_.Upsert(epoch_, ri), "inserting universe replication info into sys-catalog")); + + l.Commit(); + } + return ri; +} + +} // namespace yb::master diff --git a/src/yb/master/xcluster/xcluster_universe_replication_setup_helper.h b/src/yb/master/xcluster/xcluster_universe_replication_setup_helper.h new file mode 100644 index 000000000000..3ec7f4a946ce --- /dev/null +++ b/src/yb/master/xcluster/xcluster_universe_replication_setup_helper.h @@ -0,0 +1,189 @@ +// Copyright (c) YugabyteDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the License +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. See the License for the specific language governing permissions and limitations +// under the License. +// + +#pragma once + +#include "yb/cdc/xcluster_types.h" +#include "yb/cdc/xrepl_types.h" +#include "yb/common/common_fwd.h" +#include "yb/common/entity_ids_types.h" +#include "yb/gutil/ref_counted.h" +#include "yb/master/leader_epoch.h" +#include "yb/master/master_fwd.h" +#include "yb/util/cow_object.h" +#include "yb/util/status_fwd.h" +#include + +namespace yb { + +class HostPortPB; + +namespace rpc { +class RpcContext; +} // namespace rpc + +namespace client { +struct YBTableInfo; +class YBTableName; +} // namespace client + +namespace master { + +class GetTableSchemaResponsePB; +struct PersistentUniverseReplicationInfo; +class SetupUniverseReplicationRequestPB; +class SetupUniverseReplicationResponsePB; +class SysCatalogTable; +class UniverseReplicationInfo; +class XClusterRpcTasks; + +// Helper class to handle SetupUniverseReplication RPC. +// This object will only live as long as the operation is in progress. +class SetupUniverseReplicationHelper : public RefCountedThreadSafe { + public: + ~SetupUniverseReplicationHelper(); + + static Status Setup( + Master& master, CatalogManager& catalog_manager, const SetupUniverseReplicationRequestPB* req, + SetupUniverseReplicationResponsePB* resp, const LeaderEpoch& epoch); + + static Status ValidateMasterAddressesBelongToDifferentCluster( + Master& master, const google::protobuf::RepeatedPtrField& master_addresses); + + private: + SetupUniverseReplicationHelper( + Master& master, CatalogManager& catalog_manager, const LeaderEpoch& epoch); + + struct SetupReplicationInfo { + std::unordered_map table_bootstrap_ids; + bool transactional; + }; + + // Helper container to track colocationId and the producer to consumer schema version mapping. + typedef std::vector> + ColocationSchemaVersions; + + typedef std::vector< + std::tuple>> + StreamUpdateInfos; + + Status SetupUniverseReplication( + const SetupUniverseReplicationRequestPB* req, SetupUniverseReplicationResponsePB* resp); + + void MarkUniverseReplicationFailed( + scoped_refptr universe, const Status& failure_status); + // Sets the appropriate failure state and the error status on the universe and commits the + // mutation to the sys catalog. + void MarkUniverseReplicationFailed( + const Status& failure_status, CowWriteLock* universe_lock, + scoped_refptr universe); + + void GetTableSchemaCallback( + const xcluster::ReplicationGroupId& replication_group_id, + const std::shared_ptr& producer_info, + const SetupReplicationInfo& setup_info, const Status& s); + + Status GetTablegroupSchemaCallbackInternal( + scoped_refptr& universe, + const std::vector& infos, const TablegroupId& producer_tablegroup_id, + const SetupReplicationInfo& setup_info, const Status& s); + + void GetTablegroupSchemaCallback( + const xcluster::ReplicationGroupId& replication_group_id, + const std::shared_ptr>& infos, + const TablegroupId& producer_tablegroup_id, const SetupReplicationInfo& setup_info, + const Status& s); + + void GetColocatedTabletSchemaCallback( + const xcluster::ReplicationGroupId& replication_group_id, + const std::shared_ptr>& info, + const SetupReplicationInfo& setup_info, const Status& s); + + Status ValidateTableAndCreateStreams( + scoped_refptr universe, + const std::shared_ptr& producer_info, + const SetupReplicationInfo& setup_info); + + // Validates a single table's schema with the corresponding table on the consumer side, and + // updates consumer_table_id with the new table id. Return the consumer table schema if the + // validation is successful. + Status ValidateTableSchemaForXCluster( + const client::YBTableInfo& info, const SetupReplicationInfo& setup_info, + GetTableSchemaResponsePB* resp); + + // Consumer API: Find out if bootstrap is required for the Producer tables. + Status IsBootstrapRequiredOnProducer( + scoped_refptr universe, const TableId& producer_table, + const std::unordered_map& table_bootstrap_ids); + + Status AddValidatedTableAndCreateStreams( + scoped_refptr universe, + const std::unordered_map& table_bootstrap_ids, + const TableId& producer_table, const TableId& consumer_table, + const ColocationSchemaVersions& colocated_schema_versions); + + // Adds a validated table to the sys catalog table map for the given universe + Status AddValidatedTableToUniverseReplication( + scoped_refptr universe, const TableId& producer_table, + const TableId& consumer_table, const SchemaVersion& producer_schema_version, + const SchemaVersion& consumer_schema_version, + const ColocationSchemaVersions& colocated_schema_versions); + + // If all tables have been validated, creates a CDC stream for each table. + Status CreateStreamsIfReplicationValidated( + scoped_refptr universe, + const std::unordered_map& table_bootstrap_ids); + + void GetStreamCallback( + const xrepl::StreamId& bootstrap_id, std::shared_ptr table_id, + std::shared_ptr> options, + const xcluster::ReplicationGroupId& replication_group_id, const TableId& table, + std::shared_ptr xcluster_rpc, const Status& s, + std::shared_ptr stream_update_infos, + std::shared_ptr update_infos_lock); + + void AddStreamToUniverseAndInitConsumer( + const xcluster::ReplicationGroupId& replication_group_id, const TableId& table, + const Result& stream_id, std::function on_success_cb = nullptr); + + Status AddStreamToUniverseAndInitConsumerInternal( + scoped_refptr universe, const TableId& table, + const xrepl::StreamId& stream_id, std::function on_success_cb); + + Status InitXClusterConsumer( + const std::vector& consumer_info, const std::string& master_addrs, + UniverseReplicationInfo& replication_info, + std::shared_ptr xcluster_rpc_tasks); + + Status MergeUniverseReplication( + scoped_refptr info, xcluster::ReplicationGroupId original_id, + std::function on_success_cb); + + Result> CreateUniverseReplicationInfo( + const xcluster::ReplicationGroupId& replication_group_id, + const google::protobuf::RepeatedPtrField& master_addresses, + const std::vector& producer_namespace_ids, + const std::vector& consumer_namespace_ids, + const google::protobuf::RepeatedPtrField& table_ids, bool transactional); + + Master& master_; + CatalogManager& catalog_manager_; + SysCatalogTable& sys_catalog_; + XClusterManager& xcluster_manager_; + const LeaderEpoch epoch_; + + DISALLOW_COPY_AND_ASSIGN(SetupUniverseReplicationHelper); +}; + +} // namespace master +} // namespace yb diff --git a/src/yb/master/xrepl_catalog_manager.cc b/src/yb/master/xrepl_catalog_manager.cc index fdf8e420a8b6..40f079ba43f3 100644 --- a/src/yb/master/xrepl_catalog_manager.cc +++ b/src/yb/master/xrepl_catalog_manager.cc @@ -28,9 +28,6 @@ #include "yb/docdb/docdb_pgapi.h" -#include "yb/gutil/bind.h" -#include "yb/gutil/bind_helpers.h" - #include "yb/master/catalog_entity_info.h" #include "yb/master/catalog_manager-internal.h" #include "yb/master/catalog_manager.h" @@ -45,12 +42,10 @@ #include "yb/master/master_replication.pb.h" #include "yb/master/master_util.h" #include "yb/master/snapshot_transfer_manager.h" -#include "yb/master/ysql_tablegroup_manager.h" #include "yb/util/backoff_waiter.h" #include "yb/util/debug-util.h" #include "yb/util/scope_exit.h" -#include "yb/util/string_util.h" #include "yb/util/sync_point.h" #include "yb/util/thread.h" #include "yb/util/trace.h" @@ -64,9 +59,6 @@ using std::vector; DEFINE_RUNTIME_uint32(cdc_wal_retention_time_secs, 4 * 3600, "WAL retention time in seconds to be used for tables for which a CDC stream was created."); -DEFINE_RUNTIME_bool(check_bootstrap_required, false, - "Is it necessary to check whether bootstrap is required for Universe Replication."); - DEFINE_test_flag(bool, disable_cdc_state_insert_on_setup, false, "Disable inserting new entries into cdc state as part of the setup flow."); @@ -82,18 +74,6 @@ DEFINE_RUNTIME_int32(wait_replication_drain_retry_timeout_ms, 2000, "Timeout in milliseconds in between CheckReplicationDrain calls to tservers " "in case of retries."); -DEFINE_RUNTIME_int32(ns_replication_sync_retry_secs, 5, - "Frequency at which the bg task will try to sync with producer and add tables to " - "the current NS-level replication, when there are non-replicated consumer tables."); - -DEFINE_RUNTIME_int32(ns_replication_sync_backoff_secs, 60, - "Frequency of the add table task for a NS-level replication, when there are no " - "non-replicated consumer tables."); - -DEFINE_RUNTIME_int32(ns_replication_sync_error_backoff_secs, 300, - "Frequency of the add table task for a NS-level replication, when there are too " - "many consecutive errors happening for the replication."); - DEFINE_RUNTIME_bool(disable_universe_gc, false, "Whether to run the GC on universes or not."); DEFINE_RUNTIME_int32(cdc_parent_tablet_deletion_task_retry_secs, 30, @@ -106,24 +86,6 @@ DEFINE_NON_RUNTIME_uint32(max_replication_slots, 10, DEFINE_test_flag(bool, hang_wait_replication_drain, false, "Used in tests to temporarily block WaitForReplicationDrain."); -DEFINE_test_flag(bool, exit_unfinished_deleting, false, - "Whether to exit part way through the deleting universe process."); - -DEFINE_test_flag(bool, exit_unfinished_merging, false, - "Whether to exit part way through the merging universe process."); - -DEFINE_test_flag( - bool, xcluster_fail_create_consumer_snapshot, false, - "In the SetupReplicationWithBootstrap flow, test failure to create snapshot on consumer."); - -DEFINE_test_flag( - bool, xcluster_fail_restore_consumer_snapshot, false, - "In the SetupReplicationWithBootstrap flow, test failure to restore snapshot on consumer."); - -DEFINE_test_flag( - bool, allow_ycql_transactional_xcluster, false, - "Determines if xCluster transactional replication on YCQL tables is allowed."); - DEFINE_RUNTIME_AUTO_bool(cdc_enable_postgres_replica_identity, kLocalPersisted, false, true, "Enable new record types in CDC streams"); @@ -133,9 +95,6 @@ DEFINE_RUNTIME_bool(enable_backfilling_cdc_stream_with_replication_slot, false, "Intended to be used for making CDC streams created before replication slot support work with" " the replication slot commands."); -DEFINE_test_flag(bool, fail_universe_replication_merge, false, "Causes MergeUniverseReplication to " - "fail with an error."); - DEFINE_test_flag(bool, xcluster_fail_setup_stream_update, false, "Fail UpdateCDCStream RPC call"); DEFINE_RUNTIME_AUTO_bool(cdcsdk_enable_dynamic_tables_disable_option, @@ -175,7 +134,6 @@ TAG_FLAG(cdcsdk_enable_identification_of_non_eligible_tables, hidden); DECLARE_int32(master_rpc_timeout_ms); DECLARE_bool(ysql_yb_enable_replication_commands); DECLARE_bool(yb_enable_cdc_consistent_snapshot_streams); -DECLARE_bool(enable_xcluster_auto_flag_validation); DECLARE_bool(ysql_yb_enable_replica_identity); @@ -183,51 +141,15 @@ DECLARE_bool(ysql_yb_enable_replica_identity); #define RETURN_ACTION_NOT_OK(expr, action) \ RETURN_NOT_OK_PREPEND((expr), Format("An error occurred while $0", action)) -// assumes the existence of a local variable bootstrap_info -#define MARK_BOOTSTRAP_FAILED_NOT_OK(s) \ - do { \ - auto&& _s = (s); \ - if (PREDICT_FALSE(!_s.ok())) { \ - MarkReplicationBootstrapFailed(bootstrap_info, _s); \ - return; \ - } \ - } while (false) - -#define VERIFY_RESULT_MARK_BOOTSTRAP_FAILED(expr) \ - RESULT_CHECKER_HELPER(expr, MARK_BOOTSTRAP_FAILED_NOT_OK(ResultToStatus(__result))) - #define RETURN_INVALID_REQUEST_STATUS(error_msg) \ return STATUS( \ InvalidArgument, error_msg, \ MasterError(MasterErrorPB::INVALID_REQUEST)) -#define SET_CDCSDK_STREAM_CREATION_STATE(value) \ - if (mode == CreateNewCDCStreamMode::kCdcsdkNamespaceAndTableIds) { \ - cdcsdk_stream_creation_state = value; \ - } \ - namespace yb { using client::internal::RemoteTabletServer; namespace master { -using TableMetaPB = ImportSnapshotMetaResponsePB::TableMetaPB; - -namespace { - -template -Status ValidateUniverseUUID(const RequestType& req, CatalogManager& catalog_manager) { - if (req->has_universe_uuid() && !req->universe_uuid().empty()) { - auto universe_uuid = catalog_manager.GetUniverseUuidIfExists(); - SCHECK( - universe_uuid && universe_uuid->ToString() == req->universe_uuid(), InvalidArgument, - "Invalid Universe UUID $0. Expected $1", req->universe_uuid(), - (universe_uuid ? universe_uuid->ToString() : "empty")); - } - - return Status::OK(); -} - -} // namespace //////////////////////////////////////////////////////////// // CDC Stream Loader @@ -593,19 +515,6 @@ std::string CDCStreamInfosAsString(const std::vector& cdc_ return AsString(cdc_stream_ids); } -Status ReturnErrorOrAddWarning( - const Status& s, bool ignore_errors, DeleteUniverseReplicationResponsePB* resp) { - if (!s.ok()) { - if (ignore_errors) { - // Continue executing, save the status as a warning. - AppStatusPB* warning = resp->add_warnings(); - StatusToPB(s, warning); - return Status::OK(); - } - return s.CloneAndAppend("\nUse 'ignore-errors' to ignore this error."); - } - return s; -} } // namespace Status CatalogManager::DropXClusterStreamsOfTables(const std::unordered_set& table_ids) { @@ -3040,1999 +2949,230 @@ Status CatalogManager::IsBootstrapRequired( return Status::OK(); } -Result> -CatalogManager::CreateUniverseReplicationInfoForProducer( - const xcluster::ReplicationGroupId& replication_group_id, - const google::protobuf::RepeatedPtrField& master_addresses, - const std::vector& producer_namespace_ids, - const std::vector& consumer_namespace_ids, - const google::protobuf::RepeatedPtrField& table_ids, bool transactional) { - SCHECK_EQ( - producer_namespace_ids.size(), consumer_namespace_ids.size(), InvalidArgument, - "We should have the namespaceIds from both producer and consumer"); - - scoped_refptr ri; - { - TRACE("Acquired catalog manager lock"); - SharedLock lock(mutex_); - - if (FindPtrOrNull(universe_replication_map_, replication_group_id) != nullptr) { - return STATUS( - AlreadyPresent, "Replication group already present", replication_group_id.ToString()); - } +Status CatalogManager::IsTableBootstrapRequired( + const TableId& table_id, + const xrepl::StreamId& stream_id, + CoarseTimePoint deadline, + bool* const bootstrap_required) { + scoped_refptr table = VERIFY_RESULT(FindTableById(table_id)); + RSTATUS_DCHECK(table != nullptr, NotFound, "Table ID not found: " + table_id); - for (const auto& [universe_rg_id, universe] : universe_replication_map_) { - for (const auto& consumer_namespace_id : consumer_namespace_ids) { - SCHECK_FORMAT( - !IncludesConsumerNamespace(*universe, consumer_namespace_id), AlreadyPresent, - "Namespace $0 already included in replication group $1", consumer_namespace_id, - universe_rg_id); - } - } + // Make a batch call for IsBootstrapRequired on every relevant TServer. + std::map, cdc::IsBootstrapRequiredRequestPB> + proxy_to_request; + for (const auto& tablet : VERIFY_RESULT(table->GetTablets())) { + auto ts = VERIFY_RESULT(tablet->GetLeader()); + std::shared_ptr proxy; + RETURN_NOT_OK(ts->GetProxy(&proxy)); + proxy_to_request[proxy].add_tablet_ids(tablet->id()); } - // Create an entry in the system catalog DocDB for this new universe replication. - ri = new UniverseReplicationInfo(replication_group_id); - ri->mutable_metadata()->StartMutation(); - SysUniverseReplicationEntryPB* metadata = &ri->mutable_metadata()->mutable_dirty()->pb; - metadata->set_replication_group_id(replication_group_id.ToString()); - metadata->mutable_producer_master_addresses()->CopyFrom(master_addresses); + // TODO: Make the RPCs async and parallel. + *bootstrap_required = false; + for (auto& proxy_request : proxy_to_request) { + auto& tablet_req = proxy_request.second; + cdc::IsBootstrapRequiredResponsePB tablet_resp; + rpc::RpcController rpc; + rpc.set_deadline(deadline); + if (stream_id) { + tablet_req.set_stream_id(stream_id.ToString()); + } + auto& cdc_service = proxy_request.first; - if (!producer_namespace_ids.empty()) { - auto* db_scoped_info = metadata->mutable_db_scoped_info(); - for (size_t i = 0; i < producer_namespace_ids.size(); i++) { - auto* ns_info = db_scoped_info->mutable_namespace_infos()->Add(); - ns_info->set_producer_namespace_id(producer_namespace_ids[i]); - ns_info->set_consumer_namespace_id(consumer_namespace_ids[i]); + RETURN_NOT_OK(cdc_service->IsBootstrapRequired(tablet_req, &tablet_resp, &rpc)); + if (tablet_resp.has_error()) { + RETURN_NOT_OK(StatusFromPB(tablet_resp.error().status())); + } else if (tablet_resp.has_bootstrap_required() && tablet_resp.bootstrap_required()) { + *bootstrap_required = true; + break; } } - metadata->mutable_tables()->CopyFrom(table_ids); - metadata->set_state(SysUniverseReplicationEntryPB::INITIALIZING); - metadata->set_transactional(transactional); - - RETURN_NOT_OK(CheckLeaderStatus( - sys_catalog_->Upsert(leader_ready_term(), ri), - "inserting universe replication info into sys-catalog")); - - TRACE("Wrote universe replication info to sys-catalog"); - // Commit the in-memory state now that it's added to the persistent catalog. - ri->mutable_metadata()->CommitMutation(); - LOG(INFO) << "Setup universe replication from producer " << ri->ToString(); - { - LockGuard lock(mutex_); - universe_replication_map_[ri->ReplicationGroupId()] = ri; - } + return Status::OK(); +} - // Make sure the AutoFlags are compatible. - // This is done after the replication info is persisted since it performs RPC calls to source - // universe and we can crash during this call. - // TODO: When new master starts it can retry this step or mark the replication group as failed. - if (FLAGS_enable_xcluster_auto_flag_validation) { - const auto auto_flags_config = master_->GetAutoFlagsConfig(); - auto status = ResultToStatus(GetAutoFlagConfigVersionIfCompatible(*ri, auto_flags_config)); +Status CatalogManager::UpdateCDCProducerOnTabletSplit( + const TableId& producer_table_id, const SplitTabletIds& split_tablet_ids) { + std::vector streams; + std::vector entries; + for (const auto stream_type : {cdc::XCLUSTER, cdc::CDCSDK}) { + if (stream_type == cdc::CDCSDK && + FLAGS_cdcsdk_enable_cleanup_of_non_eligible_tables_from_stream) { + const auto& table_info = GetTableInfo(producer_table_id); + // Skip adding children tablet entries in cdc state if the table is an index or a mat view. + // These tables, if present in CDC stream, are anyway going to be removed by a bg thread. This + // check ensures even if there is a race condition where a tablet of a non-eligible table + // splits and concurrently we are removing such tables from stream, the child tables do not + // get added. + { + SharedLock lock(mutex_); + if (!IsTableEligibleForCDCSDKStream(table_info, std::nullopt)) { + LOG(INFO) << "Skipping adding children tablets to cdc state for table " + << producer_table_id << " as it is not meant to part of a CDC stream"; + continue; + } + } + } - if (!status.ok()) { - MarkUniverseReplicationFailed(ri, status); - return status.CloneAndAddErrorCode(MasterError(MasterErrorPB::INVALID_REQUEST)); + { + SharedLock lock(mutex_); + streams = GetXReplStreamsForTable(producer_table_id, stream_type); } - auto l = ri->LockForWrite(); - l.mutable_data()->pb.set_validated_local_auto_flags_config_version( - auto_flags_config.config_version()); + for (const auto& stream : streams) { + auto last_active_time = GetCurrentTimeMicros(); - RETURN_NOT_OK(CheckLeaderStatus( - sys_catalog_->Upsert(leader_ready_term(), ri), - "inserting universe replication info into sys-catalog")); + std::optional parent_entry_opt; + if (stream_type == cdc::CDCSDK) { + parent_entry_opt = VERIFY_RESULT(cdc_state_table_->TryFetchEntry( + {split_tablet_ids.source, stream->StreamId()}, + cdc::CDCStateTableEntrySelector().IncludeActiveTime().IncludeCDCSDKSafeTime())); + DCHECK(parent_entry_opt); + } - l.Commit(); - } - return ri; -} + // In the case of a Consistent Snapshot Stream, set the active_time of the children tablets + // to the corresponding value in the parent tablet. + // This will allow to establish that a child tablet is of interest to a stream + // iff the parent tablet is also of interest to the stream. + // Thus, retention barriers, inherited from the parent tablet, can be released + // on the children tablets also if not of interest to the stream + if (stream->IsConsistentSnapshotStream()) { + LOG_WITH_FUNC(INFO) << "Copy active time from parent to child tablets" + << " Tablets involved: " << split_tablet_ids.ToString() + << " Consistent Snapshot StreamId: " << stream->StreamId(); + DCHECK(parent_entry_opt->active_time); + if (parent_entry_opt && parent_entry_opt->active_time) { + last_active_time = *parent_entry_opt->active_time; + } else { + LOG_WITH_FUNC(WARNING) + << Format("Did not find $0 value in the cdc state table", + parent_entry_opt ? "active_time" : "row") + << " for parent tablet: " << split_tablet_ids.source + << " and stream: " << stream->StreamId(); + } + } -Result> -CatalogManager::CreateUniverseReplicationBootstrapInfoForProducer( - const xcluster::ReplicationGroupId& replication_group_id, - const google::protobuf::RepeatedPtrField& master_addresses, - const LeaderEpoch& epoch, bool transactional) { - scoped_refptr bootstrap_info; - { - TRACE("Acquired catalog manager lock"); - SharedLock lock(mutex_); + // Insert children entries into cdc_state now, set the opid to 0.0 and the timestamp to + // NULL. When we process the parent's SPLIT_OP in GetChanges, we will update the opid to + // the SPLIT_OP so that the children pollers continue from the next records. When we process + // the first GetChanges for the children, then their timestamp value will be set. We use + // this information to know that the children has been polled for. Once both children have + // been polled for, then we can delete the parent tablet via the bg task + // DoProcessXClusterParentTabletDeletion. + for (const auto& child_tablet_id : + {split_tablet_ids.children.first, split_tablet_ids.children.second}) { + cdc::CDCStateTableEntry entry(child_tablet_id, stream->StreamId()); + entry.checkpoint = OpId().Min(); - if (FindPtrOrNull(universe_replication_bootstrap_map_, replication_group_id) != nullptr) { - return STATUS( - InvalidArgument, "Bootstrap already present", replication_group_id.ToString(), - MasterError(MasterErrorPB::INVALID_REQUEST)); + if (stream_type == cdc::CDCSDK) { + entry.active_time = last_active_time; + DCHECK(parent_entry_opt->cdc_sdk_safe_time); + if (parent_entry_opt && parent_entry_opt->cdc_sdk_safe_time) { + entry.cdc_sdk_safe_time = *parent_entry_opt->cdc_sdk_safe_time; + } else { + LOG_WITH_FUNC(WARNING) << Format( + "Did not find $0 value in the cdc state table", + parent_entry_opt ? "cdc_sdk_safe_time" : "row") + << " for parent tablet: " << split_tablet_ids.source + << " and stream: " << stream->StreamId(); + entry.cdc_sdk_safe_time = last_active_time; + } + } + + entries.push_back(std::move(entry)); + } } } - // Create an entry in the system catalog DocDB for this new universe replication. - bootstrap_info = new UniverseReplicationBootstrapInfo(replication_group_id); - bootstrap_info->mutable_metadata()->StartMutation(); - - SysUniverseReplicationBootstrapEntryPB* metadata = - &bootstrap_info->mutable_metadata()->mutable_dirty()->pb; - metadata->set_replication_group_id(replication_group_id.ToString()); - metadata->mutable_producer_master_addresses()->CopyFrom(master_addresses); - metadata->set_state(SysUniverseReplicationBootstrapEntryPB::INITIALIZING); - metadata->set_transactional(transactional); - metadata->set_leader_term(epoch.leader_term); - metadata->set_pitr_count(epoch.pitr_count); + return cdc_state_table_->InsertEntries(entries); +} - RETURN_NOT_OK(CheckLeaderStatus( - sys_catalog_->Upsert(leader_ready_term(), bootstrap_info), - "inserting universe replication bootstrap info into sys-catalog")); +Status CatalogManager::ChangeXClusterRole( + const ChangeXClusterRoleRequestPB* req, + ChangeXClusterRoleResponsePB* resp, + rpc::RpcContext* rpc) { + LOG(INFO) << "Servicing ChangeXClusterRole request from " << RequestorString(rpc) << ": " + << req->ShortDebugString(); + return Status::OK(); +} - TRACE("Wrote universe replication bootstrap info to sys-catalog"); - // Commit the in-memory state now that it's added to the persistent catalog. - bootstrap_info->mutable_metadata()->CommitMutation(); - LOG(INFO) << "Setup universe replication bootstrap from producer " << bootstrap_info->ToString(); +Status CatalogManager::BootstrapProducer( + const BootstrapProducerRequestPB* req, + BootstrapProducerResponsePB* resp, + rpc::RpcContext* rpc) { + LOG(INFO) << "Servicing BootstrapProducer request from " << RequestorString(rpc) << ": " + << req->ShortDebugString(); - { - LockGuard lock(mutex_); - universe_replication_bootstrap_map_[bootstrap_info->ReplicationGroupId()] = bootstrap_info; + const bool pg_database_type = req->db_type() == YQL_DATABASE_PGSQL; + SCHECK( + pg_database_type || req->db_type() == YQL_DATABASE_CQL, InvalidArgument, + "Invalid database type"); + SCHECK( + req->has_namespace_name() && !req->namespace_name().empty(), InvalidArgument, + "No namespace specified"); + SCHECK_GT(req->table_name_size(), 0, InvalidArgument, "No tables specified"); + if (pg_database_type) { + SCHECK_EQ( + req->pg_schema_name_size(), req->table_name_size(), InvalidArgument, + "Number of tables and number of pg schemas must match"); + } else { + SCHECK_EQ( + req->pg_schema_name_size(), 0, InvalidArgument, + "Pg Schema does not apply to CQL databases"); } - return bootstrap_info; -} -Status CatalogManager::ValidateMasterAddressesBelongToDifferentCluster( - const google::protobuf::RepeatedPtrField& master_addresses) { - std::vector cluster_master_addresses; - RETURN_NOT_OK(master_->ListMasters(&cluster_master_addresses)); - std::unordered_set cluster_master_hps; - - for (const auto& cluster_elem : cluster_master_addresses) { - if (cluster_elem.has_registration()) { - auto p_rpc_addresses = cluster_elem.registration().private_rpc_addresses(); - for (const auto& p_rpc_elem : p_rpc_addresses) { - cluster_master_hps.insert(HostPort::FromPB(p_rpc_elem)); - } + cdc::BootstrapProducerRequestPB bootstrap_req; + master::TSDescriptor* ts = nullptr; + for (int i = 0; i < req->table_name_size(); i++) { + string pg_schema_name = pg_database_type ? req->pg_schema_name(i) : ""; + auto table_info = GetTableInfoFromNamespaceNameAndTableName( + req->db_type(), req->namespace_name(), req->table_name(i), pg_schema_name); + SCHECK( + table_info, NotFound, Format("Table $0.$1$2 not found"), req->namespace_name(), + (pg_schema_name.empty() ? "" : pg_schema_name + "."), req->table_name(i)); - auto broadcast_addresses = cluster_elem.registration().broadcast_addresses(); - for (const auto& bc_elem : broadcast_addresses) { - cluster_master_hps.insert(HostPort::FromPB(bc_elem)); - } - } + bootstrap_req.add_table_ids(table_info->id()); + resp->add_table_ids(table_info->id()); - for (const auto& master_address : master_addresses) { - auto master_hp = HostPort::FromPB(master_address); - SCHECK( - !cluster_master_hps.contains(master_hp), InvalidArgument, - "Master address $0 belongs to the target universe", master_hp); + // Pick a valid tserver to bootstrap from. + if (!ts) { + ts = VERIFY_RESULT(VERIFY_RESULT(table_info->GetTablets()).front()->GetLeader()); } } - return Status::OK(); -} - -Result CatalogManager::DoReplicationBootstrapCreateSnapshot( - const std::vector& tables, - scoped_refptr bootstrap_info) { - LOG(INFO) << Format( - "SetupReplicationWithBootstrap: create producer snapshot for replication $0", - bootstrap_info->id()); - SetReplicationBootstrapState( - bootstrap_info, SysUniverseReplicationBootstrapEntryPB::CREATE_PRODUCER_SNAPSHOT); - - auto xcluster_rpc_tasks = VERIFY_RESULT(bootstrap_info->GetOrCreateXClusterRpcTasks( - bootstrap_info->LockForRead()->pb.producer_master_addresses())); + SCHECK(ts, IllegalState, "No valid tserver found to bootstrap from"); - TxnSnapshotId old_snapshot_id = TxnSnapshotId::Nil(); + std::shared_ptr proxy; + RETURN_NOT_OK(ts->GetProxy(&proxy)); - // Send create request and wait for completion. - auto snapshot_result = xcluster_rpc_tasks->CreateSnapshot(tables, &old_snapshot_id); + cdc::BootstrapProducerResponsePB bootstrap_resp; + rpc::RpcController bootstrap_rpc; + bootstrap_rpc.set_deadline(rpc->GetClientDeadline()); - // If the producer failed to complete the snapshot, we still want to store the snapshot_id for - // cleanup purposes. - if (!old_snapshot_id.IsNil()) { - auto l = bootstrap_info->LockForWrite(); - l.mutable_data()->set_old_snapshot_id(old_snapshot_id); + RETURN_NOT_OK(proxy->BootstrapProducer(bootstrap_req, &bootstrap_resp, &bootstrap_rpc)); + if (bootstrap_resp.has_error()) { + RETURN_NOT_OK(StatusFromPB(bootstrap_resp.error().status())); + } - // Update sys_catalog. - const Status s = sys_catalog_->Upsert(leader_ready_term(), bootstrap_info); - l.CommitOrWarn(s, "updating universe replication bootstrap info in sys-catalog"); + resp->mutable_bootstrap_ids()->Swap(bootstrap_resp.mutable_cdc_bootstrap_ids()); + if (bootstrap_resp.has_bootstrap_time()) { + resp->set_bootstrap_time(bootstrap_resp.bootstrap_time()); } - return snapshot_result; + return Status::OK(); } -Result> CatalogManager::DoReplicationBootstrapImportSnapshot( - const SnapshotInfoPB& snapshot, - scoped_refptr bootstrap_info) { - /////////////////////////// - // ImportSnapshotMeta - /////////////////////////// - LOG(INFO) << Format( - "SetupReplicationWithBootstrap: import snapshot for replication $0", bootstrap_info->id()); - SetReplicationBootstrapState( - bootstrap_info, SysUniverseReplicationBootstrapEntryPB::IMPORT_SNAPSHOT); - - NamespaceMap namespace_map; - UDTypeMap type_map; - ExternalTableSnapshotDataMap tables_data; - - // ImportSnapshotMeta timeout should be a function of the table size. - auto deadline = CoarseMonoClock::Now() + MonoDelta::FromSeconds(10 + 1 * tables_data.size()); - auto epoch = bootstrap_info->LockForRead()->epoch(); - RETURN_NOT_OK(DoImportSnapshotMeta( - snapshot, epoch, std::nullopt /* clone_target_namespace_name */, &namespace_map, - &type_map, &tables_data, deadline)); - - // Update sys catalog with new information. +Status CatalogManager::SetUniverseReplicationInfoEnabled( + const xcluster::ReplicationGroupId& replication_group_id, bool is_enabled) { + scoped_refptr universe; { - auto l = bootstrap_info->LockForWrite(); - l.mutable_data()->set_new_snapshot_objects(namespace_map, type_map, tables_data); + SharedLock lock(mutex_); - // Update sys_catalog. - const Status s = sys_catalog_->Upsert(leader_ready_term(), bootstrap_info); - l.CommitOrWarn(s, "updating universe replication bootstrap info in sys-catalog"); - } - - /////////////////////////// - // CreateConsumerSnapshot - /////////////////////////// - LOG(INFO) << Format( - "SetupReplicationWithBootstrap: create consumer snapshot for replication $0", - bootstrap_info->id()); - SetReplicationBootstrapState( - bootstrap_info, SysUniverseReplicationBootstrapEntryPB::CREATE_CONSUMER_SNAPSHOT); - - CreateSnapshotRequestPB snapshot_req; - CreateSnapshotResponsePB snapshot_resp; - - std::vector tables_meta; - for (const auto& [table_id, table_data] : tables_data) { - if (table_data.table_meta) { - tables_meta.push_back(std::move(*table_data.table_meta)); - } - } - - for (const auto& table_meta : tables_meta) { - SCHECK( - ImportSnapshotMetaResponsePB_TableType_IsValid(table_meta.table_type()), InternalError, - Format("Found unknown table type: $0", table_meta.table_type())); - - const string& new_table_id = table_meta.table_ids().new_id(); - RETURN_NOT_OK(WaitForCreateTableToFinish(new_table_id, deadline)); - - snapshot_req.mutable_tables()->Add()->set_table_id(new_table_id); - } - - snapshot_req.set_add_indexes(false); - snapshot_req.set_transaction_aware(true); - snapshot_req.set_imported(true); - RETURN_NOT_OK(CreateTransactionAwareSnapshot(snapshot_req, &snapshot_resp, deadline)); - - // Update sys catalog with new information. - { - auto l = bootstrap_info->LockForWrite(); - l.mutable_data()->set_new_snapshot_id(TryFullyDecodeTxnSnapshotId(snapshot_resp.snapshot_id())); - - // Update sys_catalog. - const Status s = sys_catalog_->Upsert(leader_ready_term(), bootstrap_info); - l.CommitOrWarn(s, "updating universe replication bootstrap info in sys-catalog"); - } - - return std::vector(tables_meta.begin(), tables_meta.end()); -} - -Status CatalogManager::DoReplicationBootstrapTransferAndRestoreSnapshot( - const std::vector& tables_meta, - scoped_refptr bootstrap_info) { - // Retrieve required data from PB. - TxnSnapshotId old_snapshot_id = TxnSnapshotId::Nil(); - TxnSnapshotId new_snapshot_id = TxnSnapshotId::Nil(); - google::protobuf::RepeatedPtrField producer_masters; - auto epoch = bootstrap_info->epoch(); - { - auto l = bootstrap_info->LockForRead(); - old_snapshot_id = l->old_snapshot_id(); - new_snapshot_id = l->new_snapshot_id(); - producer_masters.CopyFrom(l->pb.producer_master_addresses()); - } - - auto xcluster_rpc_tasks = - VERIFY_RESULT(bootstrap_info->GetOrCreateXClusterRpcTasks(producer_masters)); - - // Transfer snapshot. - SetReplicationBootstrapState( - bootstrap_info, SysUniverseReplicationBootstrapEntryPB::TRANSFER_SNAPSHOT); - auto snapshot_transfer_manager = - std::make_shared(master_, this, xcluster_rpc_tasks->client()); - RETURN_NOT_OK_PREPEND( - snapshot_transfer_manager->TransferSnapshot( - old_snapshot_id, new_snapshot_id, tables_meta, epoch), - Format("Failed to transfer snapshot $0 from producer", old_snapshot_id.ToString())); - - // Restore snapshot. - SetReplicationBootstrapState( - bootstrap_info, SysUniverseReplicationBootstrapEntryPB::RESTORE_SNAPSHOT); - auto restoration_id = VERIFY_RESULT( - snapshot_coordinator_.Restore(new_snapshot_id, HybridTime(), epoch.leader_term)); - - if (PREDICT_FALSE(FLAGS_TEST_xcluster_fail_restore_consumer_snapshot)) { - return STATUS(Aborted, "Test failure"); - } - - // Wait for restoration to complete. - return WaitFor( - [this, &new_snapshot_id, &restoration_id]() -> Result { - ListSnapshotRestorationsResponsePB resp; - RETURN_NOT_OK( - snapshot_coordinator_.ListRestorations(restoration_id, new_snapshot_id, &resp)); - - SCHECK_EQ( - resp.restorations_size(), 1, IllegalState, - Format("Expected 1 restoration, got $0", resp.restorations_size())); - const auto& restoration = *resp.restorations().begin(); - const auto& state = restoration.entry().state(); - return state == SysSnapshotEntryPB::RESTORED; - }, - MonoDelta::kMax, "Waiting for restoration to finish", 100ms); -} - -Status CatalogManager::ValidateReplicationBootstrapRequest( - const SetupNamespaceReplicationWithBootstrapRequestPB* req) { - SCHECK( - !req->replication_id().empty(), InvalidArgument, "Replication ID must be provided", - req->ShortDebugString()); - - SCHECK( - req->producer_master_addresses_size() > 0, InvalidArgument, - "Producer master address must be provided", req->ShortDebugString()); - - { - auto l = ClusterConfig()->LockForRead(); - SCHECK( - l->pb.cluster_uuid() != req->replication_id(), InvalidArgument, - "Replication name cannot be the target universe UUID", req->ShortDebugString()); - } - - RETURN_NOT_OK_PREPEND( - ValidateMasterAddressesBelongToDifferentCluster(req->producer_master_addresses()), - req->ShortDebugString()); - - GetUniverseReplicationRequestPB universe_req; - GetUniverseReplicationResponsePB universe_resp; - universe_req.set_replication_group_id(req->replication_id()); - SCHECK( - GetUniverseReplication(&universe_req, &universe_resp, /* RpcContext */ nullptr).IsNotFound(), - InvalidArgument, Format("Can't bootstrap replication that already exists")); - - return Status::OK(); -} - -void CatalogManager::DoReplicationBootstrap( - const xcluster::ReplicationGroupId& replication_id, - const std::vector& tables, - Result bootstrap_producer_result) { - // First get the universe. - scoped_refptr bootstrap_info; - { - SharedLock lock(mutex_); - TRACE("Acquired catalog manager lock"); - - bootstrap_info = FindPtrOrNull(universe_replication_bootstrap_map_, replication_id); - if (bootstrap_info == nullptr) { - LOG(ERROR) << "UniverseReplicationBootstrap not found: " << replication_id; - return; - } - } - - // Verify the result from BootstrapProducer & update values in PB if successful. - auto table_bootstrap_ids = - VERIFY_RESULT_MARK_BOOTSTRAP_FAILED(std::move(bootstrap_producer_result)); - { - auto l = bootstrap_info->LockForWrite(); - auto map = l.mutable_data()->pb.mutable_table_bootstrap_ids(); - for (const auto& [table_id, bootstrap_id] : table_bootstrap_ids) { - (*map)[table_id] = bootstrap_id.ToString(); - } - - // Update sys_catalog. - const Status s = sys_catalog_->Upsert(leader_ready_term(), bootstrap_info); - l.CommitOrWarn(s, "updating universe replication bootstrap info in sys-catalog"); - } - - // Create producer snapshot. - auto snapshot = VERIFY_RESULT_MARK_BOOTSTRAP_FAILED( - DoReplicationBootstrapCreateSnapshot(tables, bootstrap_info)); - - // Import snapshot and create consumer snapshot. - auto tables_meta = VERIFY_RESULT_MARK_BOOTSTRAP_FAILED( - DoReplicationBootstrapImportSnapshot(snapshot, bootstrap_info)); - - // Transfer and restore snapshot. - MARK_BOOTSTRAP_FAILED_NOT_OK( - DoReplicationBootstrapTransferAndRestoreSnapshot(tables_meta, bootstrap_info)); - - // Call SetupUniverseReplication - SetupUniverseReplicationRequestPB replication_req; - SetupUniverseReplicationResponsePB replication_resp; - { - auto l = bootstrap_info->LockForRead(); - replication_req.set_replication_group_id(l->pb.replication_group_id()); - replication_req.set_transactional(l->pb.transactional()); - replication_req.mutable_producer_master_addresses()->CopyFrom( - l->pb.producer_master_addresses()); - for (const auto& [table_id, bootstrap_id] : table_bootstrap_ids) { - replication_req.add_producer_table_ids(table_id); - replication_req.add_producer_bootstrap_ids(bootstrap_id.ToString()); - } - } - - SetReplicationBootstrapState( - bootstrap_info, SysUniverseReplicationBootstrapEntryPB::SETUP_REPLICATION); - MARK_BOOTSTRAP_FAILED_NOT_OK( - SetupUniverseReplication(&replication_req, &replication_resp, /* rpc = */ nullptr)); - - LOG(INFO) << Format( - "Successfully completed replication bootstrap for $0", replication_id.ToString()); - SetReplicationBootstrapState(bootstrap_info, SysUniverseReplicationBootstrapEntryPB::DONE); -} - -/* - * SetupNamespaceReplicationWithBootstrap is setup in 5 stages. - * 1. Validates user input & connect to producer. - * 2. Calls BootstrapProducer with all user tables in namespace. - * 3. Create snapshot on producer and import onto consumer. - * 4. Download snapshots from producer and restore on consumer. - * 5. SetupUniverseReplication. - */ -Status CatalogManager::SetupNamespaceReplicationWithBootstrap( - const SetupNamespaceReplicationWithBootstrapRequestPB* req, - SetupNamespaceReplicationWithBootstrapResponsePB* resp, - rpc::RpcContext* rpc, - const LeaderEpoch& epoch) { - LOG(INFO) << Format( - "SetupNamespaceReplicationWithBootstrap from $0: $1", RequestorString(rpc), - req->DebugString()); - - // PHASE 1: Validating user input. - RETURN_NOT_OK(ValidateReplicationBootstrapRequest(req)); - - // Create entry in sys catalog. - auto replication_id = xcluster::ReplicationGroupId(req->replication_id()); - auto transactional = req->has_transactional() ? req->transactional() : false; - auto bootstrap_info = VERIFY_RESULT(CreateUniverseReplicationBootstrapInfoForProducer( - replication_id, req->producer_master_addresses(), epoch, transactional)); - - // Connect to producer. - auto xcluster_rpc_result = - bootstrap_info->GetOrCreateXClusterRpcTasks(req->producer_master_addresses()); - if (!xcluster_rpc_result.ok()) { - auto s = ResultToStatus(xcluster_rpc_result); - MarkReplicationBootstrapFailed(bootstrap_info, s); - return s; - } - auto xcluster_rpc_tasks = std::move(*xcluster_rpc_result); - - // Get user tables in producer namespace. - auto tables_result = xcluster_rpc_tasks->client()->ListUserTables(req->producer_namespace()); - if (!tables_result.ok()) { - auto s = ResultToStatus(tables_result); - MarkReplicationBootstrapFailed(bootstrap_info, s); - return s; - } - auto tables = std::move(*tables_result); - - // Bootstrap producer. - SetReplicationBootstrapState( - bootstrap_info, SysUniverseReplicationBootstrapEntryPB::BOOTSTRAP_PRODUCER); - auto s = xcluster_rpc_tasks->BootstrapProducer( - req->producer_namespace(), tables, - Bind(&CatalogManager::DoReplicationBootstrap, Unretained(this), replication_id, tables)); - if (!s.ok()) { - MarkReplicationBootstrapFailed(bootstrap_info, s); - return s; - } - - return Status::OK(); -} - -/* - * UniverseReplication is setup in 4 stages within the Catalog Manager - * 1. SetupUniverseReplication: Validates user input & requests Producer schema. - * 2. GetTableSchemaCallback: Validates Schema compatibility & requests Producer CDC init. - * 3. AddCDCStreamToUniverseAndInitConsumer: Setup RPC connections for CDC Streaming - * 4. InitXClusterConsumer: Initializes the Consumer settings to begin tailing data - */ -Status CatalogManager::SetupUniverseReplication( - const SetupUniverseReplicationRequestPB* req, - SetupUniverseReplicationResponsePB* resp, - rpc::RpcContext* rpc) { - LOG(INFO) << "SetupUniverseReplication from " << RequestorString(rpc) << ": " - << req->DebugString(); - - // Sanity checking section. - if (!req->has_replication_group_id()) { - return STATUS( - InvalidArgument, "Producer universe ID must be provided", req->ShortDebugString(), - MasterError(MasterErrorPB::INVALID_REQUEST)); - } - - if (req->producer_master_addresses_size() <= 0) { - return STATUS( - InvalidArgument, "Producer master address must be provided", req->ShortDebugString(), - MasterError(MasterErrorPB::INVALID_REQUEST)); - } - - if (req->producer_bootstrap_ids().size() > 0 && - req->producer_bootstrap_ids().size() != req->producer_table_ids().size()) { - return STATUS( - InvalidArgument, "Number of bootstrap ids must be equal to number of tables", - req->ShortDebugString(), MasterError(MasterErrorPB::INVALID_REQUEST)); - } - - { - auto l = ClusterConfig()->LockForRead(); - if (l->pb.cluster_uuid() == req->replication_group_id()) { - return STATUS( - InvalidArgument, "The request UUID and cluster UUID are identical.", - req->ShortDebugString(), MasterError(MasterErrorPB::INVALID_REQUEST)); - } - } - - RETURN_NOT_OK_PREPEND( - ValidateMasterAddressesBelongToDifferentCluster(req->producer_master_addresses()), - req->ShortDebugString()); - - SetupReplicationInfo setup_info; - setup_info.transactional = req->transactional(); - auto& table_id_to_bootstrap_id = setup_info.table_bootstrap_ids; - - if (!req->producer_bootstrap_ids().empty()) { - if (req->producer_table_ids().size() != req->producer_bootstrap_ids_size()) { - return STATUS( - InvalidArgument, "Bootstrap ids must be provided for all tables", req->ShortDebugString(), - MasterError(MasterErrorPB::INVALID_REQUEST)); - } - - table_id_to_bootstrap_id.reserve(req->producer_table_ids().size()); - for (int i = 0; i < req->producer_table_ids().size(); i++) { - table_id_to_bootstrap_id.insert_or_assign( - req->producer_table_ids(i), - VERIFY_RESULT(xrepl::StreamId::FromString(req->producer_bootstrap_ids(i)))); - } - } - - SCHECK( - req->producer_namespaces().empty() || req->transactional(), InvalidArgument, - "Transactional flag must be set for Db scoped replication groups"); - - std::vector producer_namespace_ids, consumer_namespace_ids; - for (const auto& producer_ns_id : req->producer_namespaces()) { - SCHECK(!producer_ns_id.id().empty(), InvalidArgument, "Invalid Namespace Id"); - SCHECK(!producer_ns_id.name().empty(), InvalidArgument, "Invalid Namespace name"); - SCHECK_EQ( - producer_ns_id.database_type(), YQLDatabase::YQL_DATABASE_PGSQL, InvalidArgument, - "Invalid Namespace database_type"); - - producer_namespace_ids.push_back(producer_ns_id.id()); - - NamespaceIdentifierPB consumer_ns_id; - consumer_ns_id.set_database_type(YQLDatabase::YQL_DATABASE_PGSQL); - consumer_ns_id.set_name(producer_ns_id.name()); - auto ns_info = VERIFY_RESULT(FindNamespace(consumer_ns_id)); - consumer_namespace_ids.push_back(ns_info->id()); - } - - // We should set the universe uuid even if we fail with AlreadyPresent error. - { - auto universe_uuid = GetUniverseUuidIfExists(); - if (universe_uuid) { - resp->set_universe_uuid(universe_uuid->ToString()); - } - } - - auto ri = VERIFY_RESULT(CreateUniverseReplicationInfoForProducer( - xcluster::ReplicationGroupId(req->replication_group_id()), req->producer_master_addresses(), - producer_namespace_ids, consumer_namespace_ids, req->producer_table_ids(), - setup_info.transactional)); - - // Initialize the CDC Stream by querying the Producer server for RPC sanity checks. - auto result = ri->GetOrCreateXClusterRpcTasks(req->producer_master_addresses()); - if (!result.ok()) { - MarkUniverseReplicationFailed(ri, ResultToStatus(result)); - return SetupError(resp->mutable_error(), MasterErrorPB::INVALID_REQUEST, result.status()); - } - std::shared_ptr xcluster_rpc = *result; - - // For each table, run an async RPC task to verify a sufficient Producer:Consumer schema match. - for (int i = 0; i < req->producer_table_ids_size(); i++) { - // SETUP CONTINUES after this async call. - Status s; - if (IsColocatedDbParentTableId(req->producer_table_ids(i))) { - auto tables_info = std::make_shared>(); - s = xcluster_rpc->client()->GetColocatedTabletSchemaByParentTableId( - req->producer_table_ids(i), tables_info, - Bind( - &CatalogManager::GetColocatedTabletSchemaCallback, Unretained(this), - ri->ReplicationGroupId(), tables_info, setup_info)); - } else if (IsTablegroupParentTableId(req->producer_table_ids(i))) { - auto tablegroup_id = GetTablegroupIdFromParentTableId(req->producer_table_ids(i)); - auto tables_info = std::make_shared>(); - s = xcluster_rpc->client()->GetTablegroupSchemaById( - tablegroup_id, tables_info, - Bind( - &CatalogManager::GetTablegroupSchemaCallback, Unretained(this), - ri->ReplicationGroupId(), tables_info, tablegroup_id, setup_info)); - } else { - auto table_info = std::make_shared(); - s = xcluster_rpc->client()->GetTableSchemaById( - req->producer_table_ids(i), table_info, - Bind( - &CatalogManager::GetTableSchemaCallback, Unretained(this), ri->ReplicationGroupId(), - table_info, setup_info)); - } - - if (!s.ok()) { - MarkUniverseReplicationFailed(ri, s); - return SetupError(resp->mutable_error(), MasterErrorPB::INVALID_REQUEST, s); - } - } - - LOG(INFO) << "Started schema validation for universe replication " << ri->ToString(); - return Status::OK(); -} - -void CatalogManager::MarkUniverseReplicationFailed( - scoped_refptr universe, const Status& failure_status) { - auto l = universe->LockForWrite(); - MarkUniverseReplicationFailed(failure_status, &l, universe); -} - -void CatalogManager::MarkUniverseReplicationFailed( - const Status& failure_status, CowWriteLock* universe_lock, - scoped_refptr universe) { - auto& l = *universe_lock; - if (l->pb.state() == SysUniverseReplicationEntryPB::DELETED) { - l.mutable_data()->pb.set_state(SysUniverseReplicationEntryPB::DELETED_ERROR); - } else { - l.mutable_data()->pb.set_state(SysUniverseReplicationEntryPB::FAILED); - } - - LOG(WARNING) << "Universe replication " << universe->ToString() - << " failed: " << failure_status.ToString(); - - universe->SetSetupUniverseReplicationErrorStatus(failure_status); - - // Update sys_catalog. - const Status s = sys_catalog_->Upsert(leader_ready_term(), universe); - - l.CommitOrWarn(s, "updating universe replication info in sys-catalog"); -} - -void CatalogManager::MarkReplicationBootstrapFailed( - scoped_refptr bootstrap_info, - const Status& failure_status) { - auto l = bootstrap_info->LockForWrite(); - MarkReplicationBootstrapFailed(failure_status, &l, bootstrap_info); -} - -void CatalogManager::MarkReplicationBootstrapFailed( - const Status& failure_status, - CowWriteLock* bootstrap_info_lock, - scoped_refptr bootstrap_info) { - auto& l = *bootstrap_info_lock; - auto state = l->pb.state(); - if (state == SysUniverseReplicationBootstrapEntryPB::DELETED) { - l.mutable_data()->pb.set_state(SysUniverseReplicationBootstrapEntryPB::DELETED_ERROR); - } else { - l.mutable_data()->pb.set_state(SysUniverseReplicationBootstrapEntryPB::FAILED); - l.mutable_data()->pb.set_failed_on(state); - } - - LOG(WARNING) << Format( - "Replication bootstrap $0 failed: $1", bootstrap_info->ToString(), - failure_status.ToString()); - - bootstrap_info->SetReplicationBootstrapErrorStatus(failure_status); - - // Update sys_catalog. - const Status s = sys_catalog_->Upsert(leader_ready_term(), bootstrap_info); - - l.CommitOrWarn(s, "updating universe replication bootstrap info in sys-catalog"); -} - -void CatalogManager::SetReplicationBootstrapState( - scoped_refptr bootstrap_info, - const SysUniverseReplicationBootstrapEntryPB::State& state) { - auto l = bootstrap_info->LockForWrite(); - l.mutable_data()->set_state(state); - - // Update sys_catalog. - const Status s = sys_catalog_->Upsert(leader_ready_term(), bootstrap_info); - l.CommitOrWarn(s, "updating universe replication bootstrap info in sys-catalog"); -} - -Status CatalogManager::IsBootstrapRequiredOnProducer( - scoped_refptr universe, const TableId& producer_table, - const std::unordered_map& table_bootstrap_ids) { - if (!FLAGS_check_bootstrap_required) { - return Status::OK(); - } - auto master_addresses = universe->LockForRead()->pb.producer_master_addresses(); - boost::optional bootstrap_id; - if (table_bootstrap_ids.count(producer_table) > 0) { - bootstrap_id = table_bootstrap_ids.at(producer_table); - } - - auto xcluster_rpc = VERIFY_RESULT(universe->GetOrCreateXClusterRpcTasks(master_addresses)); - if (VERIFY_RESULT(xcluster_rpc->client()->IsBootstrapRequired({producer_table}, bootstrap_id))) { - return STATUS( - IllegalState, - Format( - "Error Missing Data in Logs. Bootstrap is required for producer $0", universe->id())); - } - return Status::OK(); -} - -Status CatalogManager::IsTableBootstrapRequired( - const TableId& table_id, - const xrepl::StreamId& stream_id, - CoarseTimePoint deadline, - bool* const bootstrap_required) { - scoped_refptr table = VERIFY_RESULT(FindTableById(table_id)); - RSTATUS_DCHECK(table != nullptr, NotFound, "Table ID not found: " + table_id); - - // Make a batch call for IsBootstrapRequired on every relevant TServer. - std::map, cdc::IsBootstrapRequiredRequestPB> - proxy_to_request; - for (const auto& tablet : VERIFY_RESULT(table->GetTablets())) { - auto ts = VERIFY_RESULT(tablet->GetLeader()); - std::shared_ptr proxy; - RETURN_NOT_OK(ts->GetProxy(&proxy)); - proxy_to_request[proxy].add_tablet_ids(tablet->id()); - } - - // TODO: Make the RPCs async and parallel. - *bootstrap_required = false; - for (auto& proxy_request : proxy_to_request) { - auto& tablet_req = proxy_request.second; - cdc::IsBootstrapRequiredResponsePB tablet_resp; - rpc::RpcController rpc; - rpc.set_deadline(deadline); - if (stream_id) { - tablet_req.set_stream_id(stream_id.ToString()); - } - auto& cdc_service = proxy_request.first; - - RETURN_NOT_OK(cdc_service->IsBootstrapRequired(tablet_req, &tablet_resp, &rpc)); - if (tablet_resp.has_error()) { - RETURN_NOT_OK(StatusFromPB(tablet_resp.error().status())); - } else if (tablet_resp.has_bootstrap_required() && tablet_resp.bootstrap_required()) { - *bootstrap_required = true; - break; - } - } - - return Status::OK(); -} - -Status CatalogManager::AddValidatedTableToUniverseReplication( - scoped_refptr universe, - const TableId& producer_table, - const TableId& consumer_table, - const SchemaVersion& producer_schema_version, - const SchemaVersion& consumer_schema_version, - const ColocationSchemaVersions& colocated_schema_versions) { - auto l = universe->LockForWrite(); - - auto map = l.mutable_data()->pb.mutable_validated_tables(); - (*map)[producer_table] = consumer_table; - - SchemaVersionMappingEntryPB entry; - if (IsColocationParentTableId(consumer_table)) { - for (const auto& [colocation_id, producer_schema_version, consumer_schema_version] : - colocated_schema_versions) { - auto colocated_entry = entry.add_colocated_schema_versions(); - auto colocation_mapping = colocated_entry->mutable_schema_version_mapping(); - colocated_entry->set_colocation_id(colocation_id); - colocation_mapping->set_producer_schema_version(producer_schema_version); - colocation_mapping->set_consumer_schema_version(consumer_schema_version); - } - } else { - auto mapping = entry.mutable_schema_version_mapping(); - mapping->set_producer_schema_version(producer_schema_version); - mapping->set_consumer_schema_version(consumer_schema_version); - } - - - auto schema_versions_map = l.mutable_data()->pb.mutable_schema_version_mappings(); - (*schema_versions_map)[producer_table] = std::move(entry); - - // TODO: end of config validation should be where SetupUniverseReplication exits back to user - LOG(INFO) << "UpdateItem in AddValidatedTable"; - - // Update sys_catalog. - RETURN_ACTION_NOT_OK( - sys_catalog_->Upsert(leader_ready_term(), universe), - "updating universe replication info in sys-catalog"); - l.Commit(); - - return Status::OK(); -} - -Status CatalogManager::CreateCdcStreamsIfReplicationValidated( - scoped_refptr universe, - const std::unordered_map& table_bootstrap_ids) { - auto l = universe->LockForWrite(); - if (l->is_deleted_or_failed()) { - // Nothing to do since universe is being deleted. - return STATUS(Aborted, "Universe is being deleted"); - } - - auto* mutable_pb = &l.mutable_data()->pb; - - if (mutable_pb->state() != SysUniverseReplicationEntryPB::INITIALIZING) { - VLOG_WITH_FUNC(2) << "Universe replication is in invalid state " << l->pb.state(); - - // Replication stream has already been validated, or is in FAILED state which cannot be - // recovered. - return Status::OK(); - } - - if (mutable_pb->validated_tables_size() != mutable_pb->tables_size()) { - // Replication stream is not yet ready. All the tables have to be validated. - return Status::OK(); - } - - auto master_addresses = mutable_pb->producer_master_addresses(); - cdc::StreamModeTransactional transactional(mutable_pb->transactional()); - auto res = universe->GetOrCreateXClusterRpcTasks(master_addresses); - if (!res.ok()) { - MarkUniverseReplicationFailed(res.status(), &l, universe); - return STATUS( - InternalError, - Format( - "Error while setting up client for producer $0: $1", universe->id(), - res.status().ToString())); - } - std::shared_ptr xcluster_rpc = *res; - - // Now, all tables are validated. - vector validated_tables; - auto& tbl_iter = mutable_pb->tables(); - validated_tables.insert(validated_tables.begin(), tbl_iter.begin(), tbl_iter.end()); - - mutable_pb->set_state(SysUniverseReplicationEntryPB::VALIDATED); - // Update sys_catalog. - RETURN_ACTION_NOT_OK( - sys_catalog_->Upsert(leader_ready_term(), universe), - "updating universe replication info in sys-catalog"); - l.Commit(); - - // Create CDC stream for each validated table, after persisting the replication state change. - if (!validated_tables.empty()) { - // Keep track of the bootstrap_id, table_id, and options of streams to update after - // the last GetCDCStreamCallback finishes. Will be updated by multiple async - // GetCDCStreamCallback. - auto stream_update_infos = std::make_shared(); - stream_update_infos->reserve(validated_tables.size()); - auto update_infos_lock = std::make_shared(); - - for (const auto& table : validated_tables) { - auto producer_bootstrap_id = FindOrNull(table_bootstrap_ids, table); - if (producer_bootstrap_id && *producer_bootstrap_id) { - auto table_id = std::make_shared(); - auto stream_options = std::make_shared>(); - xcluster_rpc->client()->GetCDCStream( - *producer_bootstrap_id, table_id, stream_options, - std::bind( - &CatalogManager::GetCDCStreamCallback, this, *producer_bootstrap_id, table_id, - stream_options, universe->ReplicationGroupId(), table, xcluster_rpc, - std::placeholders::_1, stream_update_infos, update_infos_lock)); - } else { - // Streams are used as soon as they are created so set state to active. - client::XClusterClient(*xcluster_rpc->client()) - .CreateXClusterStreamAsync( - table, /*active=*/true, transactional, - std::bind( - &CatalogManager::AddCDCStreamToUniverseAndInitConsumer, this, - universe->ReplicationGroupId(), table, std::placeholders::_1, - nullptr /* on_success_cb */)); - } - } - } - return Status::OK(); -} - -Status CatalogManager::AddValidatedTableAndCreateCdcStreams( - scoped_refptr universe, - const std::unordered_map& table_bootstrap_ids, - const TableId& producer_table, - const TableId& consumer_table, - const ColocationSchemaVersions& colocated_schema_versions) { - RETURN_NOT_OK(AddValidatedTableToUniverseReplication(universe, producer_table, consumer_table, - cdc::kInvalidSchemaVersion, - cdc::kInvalidSchemaVersion, - colocated_schema_versions)); - return CreateCdcStreamsIfReplicationValidated(universe, table_bootstrap_ids); -} - -void CatalogManager::GetTableSchemaCallback( - const xcluster::ReplicationGroupId& replication_group_id, - const std::shared_ptr& producer_info, - const SetupReplicationInfo& setup_info, const Status& s) { - // First get the universe. - scoped_refptr universe; - { - SharedLock lock(mutex_); - TRACE("Acquired catalog manager lock"); - - universe = FindPtrOrNull(universe_replication_map_, replication_group_id); - if (universe == nullptr) { - LOG(ERROR) << "Universe not found: " << replication_group_id; - return; - } - } - - string action = "getting schema for table"; - auto status = s; - if (status.ok()) { - action = "validating table schema and creating CDC stream"; - status = ValidateTableAndCreateCdcStreams(universe, producer_info, setup_info); - } - - if (!status.ok()) { - LOG(ERROR) << "Error " << action << ". Universe: " << replication_group_id - << ", Table: " << producer_info->table_id << ": " << status; - MarkUniverseReplicationFailed(universe, status); - } -} - -Status CatalogManager::ValidateTableAndCreateCdcStreams( - scoped_refptr universe, - const std::shared_ptr& producer_info, - const SetupReplicationInfo& setup_info) { - auto l = universe->LockForWrite(); - if (producer_info->table_name.namespace_name() == master::kSystemNamespaceName) { - auto status = STATUS(IllegalState, "Cannot replicate system tables."); - MarkUniverseReplicationFailed(status, &l, universe); - return status; - } - RETURN_ACTION_NOT_OK( - sys_catalog_->Upsert(leader_ready_term(), universe), - "updating system tables in universe replication"); - l.Commit(); - - GetTableSchemaResponsePB consumer_schema; - RETURN_NOT_OK(ValidateTableSchemaForXCluster(*producer_info, setup_info, &consumer_schema)); - - // If Bootstrap Id is passed in then it must be provided for all tables. - const auto& producer_bootstrap_ids = setup_info.table_bootstrap_ids; - SCHECK( - producer_bootstrap_ids.empty() || producer_bootstrap_ids.contains(producer_info->table_id), - NotFound, - Format("Bootstrap id not found for table $0", producer_info->table_name.ToString())); - - RETURN_NOT_OK( - IsBootstrapRequiredOnProducer(universe, producer_info->table_id, producer_bootstrap_ids)); - - SchemaVersion producer_schema_version = producer_info->schema.version(); - SchemaVersion consumer_schema_version = consumer_schema.version(); - ColocationSchemaVersions colocated_schema_versions; - RETURN_NOT_OK(AddValidatedTableToUniverseReplication( - universe, producer_info->table_id, consumer_schema.identifier().table_id(), - producer_schema_version, consumer_schema_version, colocated_schema_versions)); - - return CreateCdcStreamsIfReplicationValidated(universe, producer_bootstrap_ids); -} - -void CatalogManager::GetTablegroupSchemaCallback( - const xcluster::ReplicationGroupId& replication_group_id, - const std::shared_ptr>& infos, - const TablegroupId& producer_tablegroup_id, const SetupReplicationInfo& setup_info, - const Status& s) { - // First get the universe. - scoped_refptr universe; - { - SharedLock lock(mutex_); - TRACE("Acquired catalog manager lock"); - - universe = FindPtrOrNull(universe_replication_map_, replication_group_id); - if (universe == nullptr) { - LOG(ERROR) << "Universe not found: " << replication_group_id; - return; - } - } - - auto status = - GetTablegroupSchemaCallbackInternal(universe, *infos, producer_tablegroup_id, setup_info, s); - if (!status.ok()) { - std::ostringstream oss; - for (size_t i = 0; i < infos->size(); ++i) { - oss << ((i == 0) ? "" : ", ") << (*infos)[i].table_id; - } - LOG(ERROR) << "Error processing for tables: [ " << oss.str() - << " ] for xCluster replication group " << replication_group_id << ": " << status; - MarkUniverseReplicationFailed(universe, status); - } -} - -Status CatalogManager::GetTablegroupSchemaCallbackInternal( - scoped_refptr& universe, const std::vector& infos, - const TablegroupId& producer_tablegroup_id, const SetupReplicationInfo& setup_info, - const Status& s) { - RETURN_NOT_OK(s); - - SCHECK(!infos.empty(), IllegalState, Format("Tablegroup $0 is empty", producer_tablegroup_id)); - - // validated_consumer_tables contains the table IDs corresponding to that - // from the producer tables. - std::unordered_set validated_consumer_tables; - ColocationSchemaVersions colocated_schema_versions; - colocated_schema_versions.reserve(infos.size()); - for (const auto& info : infos) { - // Validate each of the member table in the tablegroup. - GetTableSchemaResponsePB resp; - RETURN_NOT_OK(ValidateTableSchemaForXCluster(info, setup_info, &resp)); - - colocated_schema_versions.emplace_back( - resp.schema().colocated_table_id().colocation_id(), info.schema.version(), resp.version()); - validated_consumer_tables.insert(resp.identifier().table_id()); - } - - // Get the consumer tablegroup ID. Since this call is expensive (one needs to reverse lookup - // the tablegroup ID from table ID), we only do this call once and do validation afterward. - TablegroupId consumer_tablegroup_id; - // Starting Colocation GA, colocated databases create implicit underlying tablegroups. - bool colocated_database; - { - SharedLock lock(mutex_); - const auto* tablegroup = tablegroup_manager_->FindByTable(*validated_consumer_tables.begin()); - SCHECK( - tablegroup, IllegalState, - Format("No consumer tablegroup found for producer tablegroup: $0", producer_tablegroup_id)); - - consumer_tablegroup_id = tablegroup->id(); - - auto ns = FindPtrOrNull(namespace_ids_map_, tablegroup->database_id()); - SCHECK( - ns, IllegalState, - Format("Could not find namespace by namespace id $0", tablegroup->database_id())); - colocated_database = ns->colocated(); - } - - // tables_in_consumer_tablegroup are the tables listed within the consumer_tablegroup_id. - // We need validated_consumer_tables and tables_in_consumer_tablegroup to be identical. - std::unordered_set tables_in_consumer_tablegroup; - { - GetTablegroupSchemaRequestPB req; - GetTablegroupSchemaResponsePB resp; - req.mutable_tablegroup()->set_id(consumer_tablegroup_id); - auto status = GetTablegroupSchema(&req, &resp); - if (status.ok() && resp.has_error()) { - status = StatusFromPB(resp.error().status()); - } - RETURN_NOT_OK_PREPEND( - status, - Format("Error when getting consumer tablegroup schema: $0", consumer_tablegroup_id)); - - for (const auto& info : resp.get_table_schema_response_pbs()) { - tables_in_consumer_tablegroup.insert(info.identifier().table_id()); - } - } - - if (validated_consumer_tables != tables_in_consumer_tablegroup) { - return STATUS( - IllegalState, - Format( - "Mismatch between tables associated with producer tablegroup $0 and " - "tables in consumer tablegroup $1: ($2) vs ($3).", - producer_tablegroup_id, consumer_tablegroup_id, AsString(validated_consumer_tables), - AsString(tables_in_consumer_tablegroup))); - } - - RETURN_NOT_OK_PREPEND( - IsBootstrapRequiredOnProducer( - universe, producer_tablegroup_id, setup_info.table_bootstrap_ids), - Format( - "Found error while checking if bootstrap is required for table $0", - producer_tablegroup_id)); - - TableId producer_parent_table_id; - TableId consumer_parent_table_id; - if (colocated_database) { - producer_parent_table_id = GetColocationParentTableId(producer_tablegroup_id); - consumer_parent_table_id = GetColocationParentTableId(consumer_tablegroup_id); - } else { - producer_parent_table_id = GetTablegroupParentTableId(producer_tablegroup_id); - consumer_parent_table_id = GetTablegroupParentTableId(consumer_tablegroup_id); - } - - { - SharedLock lock(mutex_); - SCHECK( - !xcluster_manager_->IsTableReplicationConsumer(consumer_parent_table_id), IllegalState, - "N:1 replication topology not supported"); - } - - RETURN_NOT_OK(AddValidatedTableAndCreateCdcStreams( - universe, setup_info.table_bootstrap_ids, producer_parent_table_id, consumer_parent_table_id, - colocated_schema_versions)); - return Status::OK(); -} - -void CatalogManager::GetColocatedTabletSchemaCallback( - const xcluster::ReplicationGroupId& replication_group_id, - const std::shared_ptr>& infos, - const SetupReplicationInfo& setup_info, const Status& s) { - // First get the universe. - scoped_refptr universe; - { - SharedLock lock(mutex_); - TRACE("Acquired catalog manager lock"); - - universe = FindPtrOrNull(universe_replication_map_, replication_group_id); - if (universe == nullptr) { - LOG(ERROR) << "Universe not found: " << replication_group_id; - return; - } - } - - if (!s.ok()) { - MarkUniverseReplicationFailed(universe, s); - std::ostringstream oss; - for (size_t i = 0; i < infos->size(); ++i) { - oss << ((i == 0) ? "" : ", ") << (*infos)[i].table_id; - } - LOG(ERROR) << "Error getting schema for tables: [ " << oss.str() << " ]: " << s; - return; - } - - if (infos->empty()) { - LOG(WARNING) << "Received empty list of tables to validate: " << s; - return; - } - - // Validate table schemas. - std::unordered_set producer_parent_table_ids; - std::unordered_set consumer_parent_table_ids; - ColocationSchemaVersions colocated_schema_versions; - colocated_schema_versions.reserve(infos->size()); - for (const auto& info : *infos) { - // Verify that we have a colocated table. - if (!info.colocated) { - MarkUniverseReplicationFailed( - universe, - STATUS(InvalidArgument, Format("Received non-colocated table: $0", info.table_id))); - LOG(ERROR) << "Received non-colocated table: " << info.table_id; - return; - } - // Validate each table, and get the parent colocated table id for the consumer. - GetTableSchemaResponsePB resp; - Status table_status = ValidateTableSchemaForXCluster(info, setup_info, &resp); - if (!table_status.ok()) { - MarkUniverseReplicationFailed(universe, table_status); - LOG(ERROR) << "Found error while validating table schema for table " << info.table_id << ": " - << table_status; - return; - } - // Store the parent table ids. - producer_parent_table_ids.insert(GetColocatedDbParentTableId(info.table_name.namespace_id())); - consumer_parent_table_ids.insert( - GetColocatedDbParentTableId(resp.identifier().namespace_().id())); - colocated_schema_versions.emplace_back( - resp.schema().colocated_table_id().colocation_id(), info.schema.version(), resp.version()); - } - - // Verify that we only found one producer and one consumer colocated parent table id. - if (producer_parent_table_ids.size() != 1) { - auto message = Format( - "Found incorrect number of producer colocated parent table ids. " - "Expected 1, but found: $0", - AsString(producer_parent_table_ids)); - MarkUniverseReplicationFailed(universe, STATUS(InvalidArgument, message)); - LOG(ERROR) << message; - return; - } - if (consumer_parent_table_ids.size() != 1) { - auto message = Format( - "Found incorrect number of consumer colocated parent table ids. " - "Expected 1, but found: $0", - AsString(consumer_parent_table_ids)); - MarkUniverseReplicationFailed(universe, STATUS(InvalidArgument, message)); - LOG(ERROR) << message; - return; - } - - { - SharedLock lock(mutex_); - if (xcluster_manager_->IsTableReplicationConsumer(*consumer_parent_table_ids.begin())) { - std::string message = "N:1 replication topology not supported"; - MarkUniverseReplicationFailed(universe, STATUS(IllegalState, message)); - LOG(ERROR) << message; - return; - } - } - - Status status = IsBootstrapRequiredOnProducer( - universe, *producer_parent_table_ids.begin(), setup_info.table_bootstrap_ids); - if (!status.ok()) { - MarkUniverseReplicationFailed(universe, status); - LOG(ERROR) << "Found error while checking if bootstrap is required for table " - << *producer_parent_table_ids.begin() << ": " << status; - } - - status = AddValidatedTableAndCreateCdcStreams( - universe, - setup_info.table_bootstrap_ids, - *producer_parent_table_ids.begin(), - *consumer_parent_table_ids.begin(), - colocated_schema_versions); - - if (!status.ok()) { - LOG(ERROR) << "Found error while adding validated table to system catalog: " - << *producer_parent_table_ids.begin() << ": " << status; - return; - } -} - -void CatalogManager::GetCDCStreamCallback( - const xrepl::StreamId& bootstrap_id, std::shared_ptr table_id, - std::shared_ptr> options, - const xcluster::ReplicationGroupId& replication_group_id, const TableId& table, - std::shared_ptr xcluster_rpc, const Status& s, - std::shared_ptr stream_update_infos, - std::shared_ptr update_infos_lock) { - if (!s.ok()) { - LOG(ERROR) << "Unable to find bootstrap id " << bootstrap_id; - AddCDCStreamToUniverseAndInitConsumer(replication_group_id, table, s); - return; - } - - if (*table_id != table) { - const Status invalid_bootstrap_id_status = STATUS_FORMAT( - InvalidArgument, "Invalid bootstrap id for table $0. Bootstrap id $1 belongs to table $2", - table, bootstrap_id, *table_id); - LOG(ERROR) << invalid_bootstrap_id_status; - AddCDCStreamToUniverseAndInitConsumer(replication_group_id, table, invalid_bootstrap_id_status); - return; - } - - scoped_refptr original_universe; - { - SharedLock lock(mutex_); - original_universe = FindPtrOrNull( - universe_replication_map_, xcluster::GetOriginalReplicationGroupId(replication_group_id)); - } - - if (original_universe == nullptr) { - LOG(ERROR) << "Universe not found: " << replication_group_id; - return; - } - - cdc::StreamModeTransactional transactional(original_universe->LockForRead()->pb.transactional()); - - // todo check options - { - std::lock_guard lock(*update_infos_lock); - stream_update_infos->push_back({bootstrap_id, *table_id, *options}); - } - - const auto update_xrepl_stream_func = [&]() -> Status { - // Extra callback on universe setup success - update the producer to let it know that - // the bootstrapping is complete. This callback will only be called once among all - // the GetCDCStreamCallback calls, and we update all streams in batch at once. - - std::vector update_bootstrap_ids; - std::vector update_entries; - { - std::lock_guard lock(*update_infos_lock); - - for (const auto& [update_bootstrap_id, update_table_id, update_options] : - *stream_update_infos) { - SysCDCStreamEntryPB new_entry; - new_entry.add_table_id(update_table_id); - new_entry.mutable_options()->Reserve(narrow_cast(update_options.size())); - for (const auto& [key, value] : update_options) { - if (key == cdc::kStreamState) { - // We will set state explicitly. - continue; - } - auto new_option = new_entry.add_options(); - new_option->set_key(key); - new_option->set_value(value); - } - new_entry.set_state(master::SysCDCStreamEntryPB::ACTIVE); - new_entry.set_transactional(transactional); - - update_bootstrap_ids.push_back(update_bootstrap_id); - update_entries.push_back(new_entry); - } - } - - RETURN_NOT_OK_PREPEND( - xcluster_rpc->client()->UpdateCDCStream(update_bootstrap_ids, update_entries), - "Unable to update xrepl stream options on source universe"); - - { - std::lock_guard lock(*update_infos_lock); - stream_update_infos->clear(); - } - return Status::OK(); - }; - - AddCDCStreamToUniverseAndInitConsumer( - replication_group_id, table, bootstrap_id, update_xrepl_stream_func); -} - -void CatalogManager::AddCDCStreamToUniverseAndInitConsumer( - const xcluster::ReplicationGroupId& replication_group_id, const TableId& table_id, - const Result& stream_id, std::function on_success_cb) { - scoped_refptr universe; - { - SharedLock lock(mutex_); - TRACE("Acquired catalog manager lock"); - - universe = FindPtrOrNull(universe_replication_map_, replication_group_id); - if (universe == nullptr) { - LOG(ERROR) << "Universe not found: " << replication_group_id; - return; - } - } - - Status s; - if (!stream_id.ok()) { - s = std::move(stream_id).status(); - } else { - s = AddCDCStreamToUniverseAndInitConsumerInternal( - universe, table_id, *stream_id, std::move(on_success_cb)); - } - - if (!s.ok()) { - MarkUniverseReplicationFailed(universe, s); - } -} - -Status CatalogManager::AddCDCStreamToUniverseAndInitConsumerInternal( - scoped_refptr universe, const TableId& table_id, - const xrepl::StreamId& stream_id, std::function on_success_cb) { - bool merge_alter = false; - bool validated_all_tables = false; - std::vector consumer_info; - { - auto l = universe->LockForWrite(); - if (l->is_deleted_or_failed()) { - // Nothing to do if universe is being deleted. - return Status::OK(); - } - - auto map = l.mutable_data()->pb.mutable_table_streams(); - (*map)[table_id] = stream_id.ToString(); - - // This functions as a barrier: waiting for the last RPC call from GetTableSchemaCallback. - if (l.mutable_data()->pb.table_streams_size() == l->pb.tables_size()) { - // All tables successfully validated! Register CDC consumers & start replication. - validated_all_tables = true; - LOG(INFO) << "Registering CDC consumers for universe " << universe->id(); - - consumer_info.reserve(l->pb.tables_size()); - std::set consumer_table_ids; - for (const auto& [producer_table_id, consumer_table_id] : l->pb.validated_tables()) { - consumer_table_ids.insert(consumer_table_id); - - XClusterConsumerStreamInfo info; - info.producer_table_id = producer_table_id; - info.consumer_table_id = consumer_table_id; - info.stream_id = VERIFY_RESULT(xrepl::StreamId::FromString((*map)[producer_table_id])); - consumer_info.push_back(info); - } - - if (l->IsDbScoped()) { - std::vector consumer_namespace_ids; - for (const auto& ns_info : l->pb.db_scoped_info().namespace_infos()) { - consumer_namespace_ids.push_back(ns_info.consumer_namespace_id()); - } - RETURN_NOT_OK(ValidateTableListForDbScopedReplication( - *universe, consumer_namespace_ids, consumer_table_ids, *this)); - } - - std::vector hp; - HostPortsFromPBs(l->pb.producer_master_addresses(), &hp); - auto xcluster_rpc_tasks = - VERIFY_RESULT(universe->GetOrCreateXClusterRpcTasks(l->pb.producer_master_addresses())); - RETURN_NOT_OK(InitXClusterConsumer( - consumer_info, HostPort::ToCommaSeparatedString(hp), *universe.get(), - xcluster_rpc_tasks)); - - if (xcluster::IsAlterReplicationGroupId(universe->ReplicationGroupId())) { - // Don't enable ALTER universes, merge them into the main universe instead. - // on_success_cb will be invoked in MergeUniverseReplication. - merge_alter = true; - } else { - l.mutable_data()->pb.set_state(SysUniverseReplicationEntryPB::ACTIVE); - if (on_success_cb) { - // Before updating, run any callbacks on success. - RETURN_NOT_OK(on_success_cb()); - } - } - } - - // Update sys_catalog with new producer table id info. - RETURN_NOT_OK(sys_catalog_->Upsert(leader_ready_term(), universe)); - - l.Commit(); - } - - if (!validated_all_tables) { - return Status::OK(); - } - - auto final_id = xcluster::GetOriginalReplicationGroupId(universe->ReplicationGroupId()); - // If this is an 'alter', merge back into primary command now that setup is a success. - if (merge_alter) { - RETURN_NOT_OK(MergeUniverseReplication(universe, final_id, std::move(on_success_cb))); - } - // Update the in-memory cache of consumer tables. - for (const auto& info : consumer_info) { - xcluster_manager_->RecordTableConsumerStream(info.consumer_table_id, final_id, info.stream_id); - } - - return Status::OK(); -} - - -Status CatalogManager::UpdateCDCProducerOnTabletSplit( - const TableId& producer_table_id, const SplitTabletIds& split_tablet_ids) { - std::vector streams; - std::vector entries; - for (const auto stream_type : {cdc::XCLUSTER, cdc::CDCSDK}) { - if (stream_type == cdc::CDCSDK && - FLAGS_cdcsdk_enable_cleanup_of_non_eligible_tables_from_stream) { - const auto& table_info = GetTableInfo(producer_table_id); - // Skip adding children tablet entries in cdc state if the table is an index or a mat view. - // These tables, if present in CDC stream, are anyway going to be removed by a bg thread. This - // check ensures even if there is a race condition where a tablet of a non-eligible table - // splits and concurrently we are removing such tables from stream, the child tables do not - // get added. - { - SharedLock lock(mutex_); - if (!IsTableEligibleForCDCSDKStream(table_info, std::nullopt)) { - LOG(INFO) << "Skipping adding children tablets to cdc state for table " - << producer_table_id << " as it is not meant to part of a CDC stream"; - continue; - } - } - } - - { - SharedLock lock(mutex_); - streams = GetXReplStreamsForTable(producer_table_id, stream_type); - } - - for (const auto& stream : streams) { - auto last_active_time = GetCurrentTimeMicros(); - - std::optional parent_entry_opt; - if (stream_type == cdc::CDCSDK) { - parent_entry_opt = VERIFY_RESULT(cdc_state_table_->TryFetchEntry( - {split_tablet_ids.source, stream->StreamId()}, - cdc::CDCStateTableEntrySelector().IncludeActiveTime().IncludeCDCSDKSafeTime())); - DCHECK(parent_entry_opt); - } - - // In the case of a Consistent Snapshot Stream, set the active_time of the children tablets - // to the corresponding value in the parent tablet. - // This will allow to establish that a child tablet is of interest to a stream - // iff the parent tablet is also of interest to the stream. - // Thus, retention barriers, inherited from the parent tablet, can be released - // on the children tablets also if not of interest to the stream - if (stream->IsConsistentSnapshotStream()) { - LOG_WITH_FUNC(INFO) << "Copy active time from parent to child tablets" - << " Tablets involved: " << split_tablet_ids.ToString() - << " Consistent Snapshot StreamId: " << stream->StreamId(); - DCHECK(parent_entry_opt->active_time); - if (parent_entry_opt && parent_entry_opt->active_time) { - last_active_time = *parent_entry_opt->active_time; - } else { - LOG_WITH_FUNC(WARNING) - << Format("Did not find $0 value in the cdc state table", - parent_entry_opt ? "active_time" : "row") - << " for parent tablet: " << split_tablet_ids.source - << " and stream: " << stream->StreamId(); - } - } - - // Insert children entries into cdc_state now, set the opid to 0.0 and the timestamp to - // NULL. When we process the parent's SPLIT_OP in GetChanges, we will update the opid to - // the SPLIT_OP so that the children pollers continue from the next records. When we process - // the first GetChanges for the children, then their timestamp value will be set. We use - // this information to know that the children has been polled for. Once both children have - // been polled for, then we can delete the parent tablet via the bg task - // DoProcessXClusterParentTabletDeletion. - for (const auto& child_tablet_id : - {split_tablet_ids.children.first, split_tablet_ids.children.second}) { - cdc::CDCStateTableEntry entry(child_tablet_id, stream->StreamId()); - entry.checkpoint = OpId().Min(); - - if (stream_type == cdc::CDCSDK) { - entry.active_time = last_active_time; - DCHECK(parent_entry_opt->cdc_sdk_safe_time); - if (parent_entry_opt && parent_entry_opt->cdc_sdk_safe_time) { - entry.cdc_sdk_safe_time = *parent_entry_opt->cdc_sdk_safe_time; - } else { - LOG_WITH_FUNC(WARNING) << Format( - "Did not find $0 value in the cdc state table", - parent_entry_opt ? "cdc_sdk_safe_time" : "row") - << " for parent tablet: " << split_tablet_ids.source - << " and stream: " << stream->StreamId(); - entry.cdc_sdk_safe_time = last_active_time; - } - } - - entries.push_back(std::move(entry)); - } - } - } - - return cdc_state_table_->InsertEntries(entries); -} - -Status CatalogManager::InitXClusterConsumer( - const std::vector& consumer_info, const std::string& master_addrs, - UniverseReplicationInfo& replication_info, - std::shared_ptr xcluster_rpc_tasks) { - auto universe_l = replication_info.LockForRead(); - auto schema_version_mappings = universe_l->pb.schema_version_mappings(); - - // Get the tablets in the consumer table. - cdc::ProducerEntryPB producer_entry; - - if (FLAGS_enable_xcluster_auto_flag_validation) { - auto compatible_auto_flag_config_version = VERIFY_RESULT( - GetAutoFlagConfigVersionIfCompatible(replication_info, master_->GetAutoFlagsConfig())); - producer_entry.set_compatible_auto_flag_config_version(compatible_auto_flag_config_version); - producer_entry.set_validated_auto_flags_config_version(compatible_auto_flag_config_version); - } - - auto cluster_config = ClusterConfig(); - auto l = cluster_config->LockForWrite(); - auto* consumer_registry = l.mutable_data()->pb.mutable_consumer_registry(); - auto transactional = universe_l->pb.transactional(); - if (!xcluster::IsAlterReplicationGroupId(replication_info.ReplicationGroupId())) { - if (universe_l->IsDbScoped()) { - DCHECK(transactional); - } - } - - for (const auto& stream_info : consumer_info) { - auto consumer_tablet_keys = VERIFY_RESULT(GetTableKeyRanges(stream_info.consumer_table_id)); - auto schema_version = VERIFY_RESULT(GetTableSchemaVersion(stream_info.consumer_table_id)); - - cdc::StreamEntryPB stream_entry; - // Get producer tablets and map them to the consumer tablets - RETURN_NOT_OK(InitXClusterStream( - stream_info.producer_table_id, stream_info.consumer_table_id, consumer_tablet_keys, - &stream_entry, xcluster_rpc_tasks)); - // Set the validated consumer schema version - auto* producer_schema_pb = stream_entry.mutable_producer_schema(); - producer_schema_pb->set_last_compatible_consumer_schema_version(schema_version); - auto* schema_versions = stream_entry.mutable_schema_versions(); - auto mapping = FindOrNull(schema_version_mappings, stream_info.producer_table_id); - SCHECK(mapping, NotFound, Format("No schema mapping for $0", stream_info.producer_table_id)); - if (IsColocationParentTableId(stream_info.consumer_table_id)) { - // Get all the child tables and add their mappings - auto& colocated_schema_versions_pb = *stream_entry.mutable_colocated_schema_versions(); - for (const auto& colocated_entry : mapping->colocated_schema_versions()) { - auto colocation_id = colocated_entry.colocation_id(); - colocated_schema_versions_pb[colocation_id].set_current_producer_schema_version( - colocated_entry.schema_version_mapping().producer_schema_version()); - colocated_schema_versions_pb[colocation_id].set_current_consumer_schema_version( - colocated_entry.schema_version_mapping().consumer_schema_version()); - } - } else { - schema_versions->set_current_producer_schema_version( - mapping->schema_version_mapping().producer_schema_version()); - schema_versions->set_current_consumer_schema_version( - mapping->schema_version_mapping().consumer_schema_version()); - } - - // Mark this stream as special if it is for the ddl_queue table. - auto table_info = GetTableInfo(stream_info.consumer_table_id); - stream_entry.set_is_ddl_queue_table( - table_info->GetTableType() == PGSQL_TABLE_TYPE && - table_info->name() == xcluster::kDDLQueueTableName && - table_info->pgschema_name() == xcluster::kDDLQueuePgSchemaName); - - (*producer_entry.mutable_stream_map())[stream_info.stream_id.ToString()] = - std::move(stream_entry); - } - - // Log the Network topology of the Producer Cluster - auto master_addrs_list = StringSplit(master_addrs, ','); - producer_entry.mutable_master_addrs()->Reserve(narrow_cast(master_addrs_list.size())); - for (const auto& addr : master_addrs_list) { - auto hp = VERIFY_RESULT(HostPort::FromString(addr, 0)); - HostPortToPB(hp, producer_entry.add_master_addrs()); - } - - auto* replication_group_map = consumer_registry->mutable_producer_map(); - SCHECK_EQ( - replication_group_map->count(replication_info.id()), 0, InvalidArgument, - "Already created a consumer for this universe"); - - // TServers will use the ClusterConfig to create CDC Consumers for applicable local tablets. - (*replication_group_map)[replication_info.id()] = std::move(producer_entry); - - l.mutable_data()->pb.set_version(l.mutable_data()->pb.version() + 1); - RETURN_NOT_OK(CheckStatus( - sys_catalog_->Upsert(leader_ready_term(), cluster_config.get()), - "updating cluster config in sys-catalog")); - - xcluster_manager_->SyncConsumerReplicationStatusMap( - replication_info.ReplicationGroupId(), *replication_group_map); - l.Commit(); - - xcluster_manager_->CreateXClusterSafeTimeTableAndStartService(); - - return Status::OK(); -} - -Status CatalogManager::MergeUniverseReplication( - scoped_refptr universe, xcluster::ReplicationGroupId original_id, - std::function on_success_cb) { - // Merge back into primary command now that setup is a success. - LOG(INFO) << "Merging CDC universe: " << universe->id() << " into " << original_id; - - SCHECK( - !FLAGS_TEST_fail_universe_replication_merge, IllegalState, - "TEST_fail_universe_replication_merge"); - - scoped_refptr original_universe; - { - SharedLock lock(mutex_); - TRACE("Acquired catalog manager lock"); - - original_universe = FindPtrOrNull(universe_replication_map_, original_id); - if (original_universe == nullptr) { - LOG(ERROR) << "Universe not found: " << original_id; - return Status::OK(); - } - } - - { - auto cluster_config = ClusterConfig(); - // Acquire Locks in order of Original Universe, Cluster Config, New Universe - auto original_lock = original_universe->LockForWrite(); - auto alter_lock = universe->LockForWrite(); - auto cl = cluster_config->LockForWrite(); - - // Merge Cluster Config for TServers. - auto* consumer_registry = cl.mutable_data()->pb.mutable_consumer_registry(); - auto pm = consumer_registry->mutable_producer_map(); - auto original_producer_entry = pm->find(original_universe->id()); - auto alter_producer_entry = pm->find(universe->id()); - if (original_producer_entry != pm->end() && alter_producer_entry != pm->end()) { - // Merge the Tables from the Alter into the original. - auto as = alter_producer_entry->second.stream_map(); - original_producer_entry->second.mutable_stream_map()->insert(as.begin(), as.end()); - // Delete the Alter - pm->erase(alter_producer_entry); - } else { - LOG(WARNING) << "Could not find both universes in Cluster Config: " << universe->id(); - } - cl.mutable_data()->pb.set_version(cl.mutable_data()->pb.version() + 1); - - // Merge Master Config on Consumer. (no need for Producer changes, since it uses stream_id) - // Merge Table->StreamID mapping. - auto& alter_pb = alter_lock.mutable_data()->pb; - auto& original_pb = original_lock.mutable_data()->pb; - - auto* alter_tables = alter_pb.mutable_tables(); - original_pb.mutable_tables()->MergeFrom(*alter_tables); - alter_tables->Clear(); - auto* alter_table_streams = alter_pb.mutable_table_streams(); - original_pb.mutable_table_streams()->insert( - alter_table_streams->begin(), alter_table_streams->end()); - alter_table_streams->clear(); - auto* alter_validated_tables = alter_pb.mutable_validated_tables(); - original_pb.mutable_validated_tables()->insert( - alter_validated_tables->begin(), alter_validated_tables->end()); - alter_validated_tables->clear(); - if (alter_lock.mutable_data()->IsDbScoped()) { - auto* alter_namespace_info = alter_pb.mutable_db_scoped_info()->mutable_namespace_infos(); - original_pb.mutable_db_scoped_info()->mutable_namespace_infos()->MergeFrom( - *alter_namespace_info); - alter_namespace_info->Clear(); - } - - alter_pb.set_state(SysUniverseReplicationEntryPB::DELETED); - - if (PREDICT_FALSE(FLAGS_TEST_exit_unfinished_merging)) { - return Status::OK(); - } - - if (on_success_cb) { - RETURN_NOT_OK(on_success_cb()); - } - - { - // Need both these updates to be atomic. - auto w = sys_catalog_->NewWriter(leader_ready_term()); - auto s = w->Mutate( - QLWriteRequestPB::QL_STMT_UPDATE, - original_universe.get(), - universe.get(), - cluster_config.get()); - s = CheckStatus( - sys_catalog_->SyncWrite(w.get()), - "Updating universe replication entries and cluster config in sys-catalog"); - } - - xcluster_manager_->SyncConsumerReplicationStatusMap( - original_universe->ReplicationGroupId(), *pm); - xcluster_manager_->SyncConsumerReplicationStatusMap(universe->ReplicationGroupId(), *pm); - - alter_lock.Commit(); - cl.Commit(); - original_lock.Commit(); - } - - // Add alter temp universe to GC. - MarkUniverseForCleanup(universe->ReplicationGroupId()); - - LOG(INFO) << "Done with Merging " << universe->id() << " into " << original_universe->id(); - - xcluster_manager_->CreateXClusterSafeTimeTableAndStartService(); - - return Status::OK(); -} - -Status CatalogManager::DeleteUniverseReplication( - const xcluster::ReplicationGroupId& replication_group_id, bool ignore_errors, - bool skip_producer_stream_deletion, DeleteUniverseReplicationResponsePB* resp) { - scoped_refptr ri; - { - SharedLock lock(mutex_); - TRACE("Acquired catalog manager lock"); - - ri = FindPtrOrNull(universe_replication_map_, replication_group_id); - if (ri == nullptr) { - return STATUS( - NotFound, "Universe replication info does not exist", replication_group_id, - MasterError(MasterErrorPB::OBJECT_NOT_FOUND)); - } - } - - { - auto l = ri->LockForWrite(); - l.mutable_data()->pb.set_state(SysUniverseReplicationEntryPB::DELETING); - Status s = sys_catalog_->Upsert(leader_ready_term(), ri); - RETURN_NOT_OK( - CheckLeaderStatus(s, "Updating delete universe replication info into sys-catalog")); - TRACE("Wrote universe replication info to sys-catalog"); - l.Commit(); - } - - auto l = ri->LockForWrite(); - l.mutable_data()->pb.set_state(SysUniverseReplicationEntryPB::DELETED); - - // We can skip the deletion of individual streams for DB Scoped replication since deletion of the - // outbound replication group will clean it up. - if (l->IsDbScoped()) { - skip_producer_stream_deletion = true; - } - - // Delete subscribers on the Consumer Registry (removes from TServers). - LOG(INFO) << "Deleting subscribers for producer " << replication_group_id; - { - auto cluster_config = ClusterConfig(); - auto cl = cluster_config->LockForWrite(); - auto* consumer_registry = cl.mutable_data()->pb.mutable_consumer_registry(); - auto replication_group_map = consumer_registry->mutable_producer_map(); - auto it = replication_group_map->find(replication_group_id.ToString()); - if (it != replication_group_map->end()) { - replication_group_map->erase(it); - cl.mutable_data()->pb.set_version(cl.mutable_data()->pb.version() + 1); - RETURN_NOT_OK(CheckStatus( - sys_catalog_->Upsert(leader_ready_term(), cluster_config.get()), - "updating cluster config in sys-catalog")); - - xcluster_manager_->SyncConsumerReplicationStatusMap( - replication_group_id, *replication_group_map); - cl.Commit(); - } - } - - // Delete CDC stream config on the Producer. - if (!l->pb.table_streams().empty() && !skip_producer_stream_deletion) { - auto result = ri->GetOrCreateXClusterRpcTasks(l->pb.producer_master_addresses()); - if (!result.ok()) { - LOG(WARNING) << "Unable to create cdc rpc task. CDC streams won't be deleted: " << result; - } else { - auto xcluster_rpc = *result; - vector streams; - std::unordered_map stream_to_producer_table_id; - for (const auto& [table_id, stream_id_str] : l->pb.table_streams()) { - auto stream_id = VERIFY_RESULT(xrepl::StreamId::FromString(stream_id_str)); - streams.emplace_back(stream_id); - stream_to_producer_table_id.emplace(stream_id, table_id); - } - - DeleteCDCStreamResponsePB delete_cdc_stream_resp; - // Set force_delete=true since we are deleting active xCluster streams. - // Since we are deleting universe replication, we should be ok with - // streams not existing on the other side, so we pass in ignore_errors - bool ignore_missing_streams = false; - auto s = xcluster_rpc->client()->DeleteCDCStream( - streams, - true, /* force_delete */ - true /* ignore_errors */, - &delete_cdc_stream_resp); - - if (delete_cdc_stream_resp.not_found_stream_ids().size() > 0) { - std::vector missing_streams; - missing_streams.reserve(delete_cdc_stream_resp.not_found_stream_ids().size()); - for (const auto& stream_id : delete_cdc_stream_resp.not_found_stream_ids()) { - missing_streams.emplace_back(Format( - "$0 (table_id: $1)", stream_id, - stream_to_producer_table_id[VERIFY_RESULT(xrepl::StreamId::FromString(stream_id))])); - } - auto message = - Format("Could not find the following streams: $0.", AsString(missing_streams)); - - if (s.ok()) { - // Returned but did not find some streams, so still need to warn the user about those. - ignore_missing_streams = true; - s = STATUS(NotFound, message); - } else { - s = s.CloneAndPrepend(message); - } - } - RETURN_NOT_OK(ReturnErrorOrAddWarning(s, ignore_errors | ignore_missing_streams, resp)); - } - } - - if (PREDICT_FALSE(FLAGS_TEST_exit_unfinished_deleting)) { - // Exit for texting services - return Status::OK(); - } - - // Delete universe in the Universe Config. - RETURN_NOT_OK( - ReturnErrorOrAddWarning(DeleteUniverseReplicationUnlocked(ri), ignore_errors, resp)); - l.Commit(); - LOG(INFO) << "Processed delete universe replication of " << ri->ToString(); - - // Run the safe time task as it may need to perform cleanups of it own - xcluster_manager_->CreateXClusterSafeTimeTableAndStartService(); - - return Status::OK(); -} - -Status CatalogManager::DeleteUniverseReplication( - const DeleteUniverseReplicationRequestPB* req, - DeleteUniverseReplicationResponsePB* resp, - rpc::RpcContext* rpc) { - LOG(INFO) << "Servicing DeleteUniverseReplication request from " << RequestorString(rpc) << ": " - << req->ShortDebugString(); - - if (!req->has_replication_group_id()) { - return STATUS( - InvalidArgument, "Producer universe ID required", req->ShortDebugString(), - MasterError(MasterErrorPB::INVALID_REQUEST)); - } - - RETURN_NOT_OK(ValidateUniverseUUID(req, *this)); - - RETURN_NOT_OK(DeleteUniverseReplication( - xcluster::ReplicationGroupId(req->replication_group_id()), req->ignore_errors(), - req->skip_producer_stream_deletion(), resp)); - LOG(INFO) << "Successfully completed DeleteUniverseReplication request from " - << RequestorString(rpc); - return Status::OK(); -} - -Status CatalogManager::DeleteUniverseReplicationUnlocked( - scoped_refptr universe) { - // Assumes that caller has locked universe. - RETURN_ACTION_NOT_OK( - sys_catalog_->Delete(leader_ready_term(), universe), - Format("updating sys-catalog, replication_group_id: $0", universe->id())); - - // Remove it from the map. - LockGuard lock(mutex_); - if (universe_replication_map_.erase(universe->ReplicationGroupId()) < 1) { - LOG(WARNING) << "Failed to remove replication info from map: replication_group_id: " - << universe->id(); - } - - // Also update the mapping of consumer tables. - for (const auto& table : universe->metadata().state().pb.validated_tables()) { - xcluster_manager_->RemoveTableConsumerStream(table.second, universe->ReplicationGroupId()); - } - return Status::OK(); -} - -Status CatalogManager::ChangeXClusterRole( - const ChangeXClusterRoleRequestPB* req, - ChangeXClusterRoleResponsePB* resp, - rpc::RpcContext* rpc) { - LOG(INFO) << "Servicing ChangeXClusterRole request from " << RequestorString(rpc) << ": " - << req->ShortDebugString(); - return Status::OK(); -} - -Status CatalogManager::BootstrapProducer( - const BootstrapProducerRequestPB* req, - BootstrapProducerResponsePB* resp, - rpc::RpcContext* rpc) { - LOG(INFO) << "Servicing BootstrapProducer request from " << RequestorString(rpc) << ": " - << req->ShortDebugString(); - - const bool pg_database_type = req->db_type() == YQL_DATABASE_PGSQL; - SCHECK( - pg_database_type || req->db_type() == YQL_DATABASE_CQL, InvalidArgument, - "Invalid database type"); - SCHECK( - req->has_namespace_name() && !req->namespace_name().empty(), InvalidArgument, - "No namespace specified"); - SCHECK_GT(req->table_name_size(), 0, InvalidArgument, "No tables specified"); - if (pg_database_type) { - SCHECK_EQ( - req->pg_schema_name_size(), req->table_name_size(), InvalidArgument, - "Number of tables and number of pg schemas must match"); - } else { - SCHECK_EQ( - req->pg_schema_name_size(), 0, InvalidArgument, - "Pg Schema does not apply to CQL databases"); - } - - cdc::BootstrapProducerRequestPB bootstrap_req; - master::TSDescriptor* ts = nullptr; - for (int i = 0; i < req->table_name_size(); i++) { - string pg_schema_name = pg_database_type ? req->pg_schema_name(i) : ""; - auto table_info = GetTableInfoFromNamespaceNameAndTableName( - req->db_type(), req->namespace_name(), req->table_name(i), pg_schema_name); - SCHECK( - table_info, NotFound, Format("Table $0.$1$2 not found"), req->namespace_name(), - (pg_schema_name.empty() ? "" : pg_schema_name + "."), req->table_name(i)); - - bootstrap_req.add_table_ids(table_info->id()); - resp->add_table_ids(table_info->id()); - - // Pick a valid tserver to bootstrap from. - if (!ts) { - ts = VERIFY_RESULT(VERIFY_RESULT(table_info->GetTablets()).front()->GetLeader()); - } - } - SCHECK(ts, IllegalState, "No valid tserver found to bootstrap from"); - - std::shared_ptr proxy; - RETURN_NOT_OK(ts->GetProxy(&proxy)); - - cdc::BootstrapProducerResponsePB bootstrap_resp; - rpc::RpcController bootstrap_rpc; - bootstrap_rpc.set_deadline(rpc->GetClientDeadline()); - - RETURN_NOT_OK(proxy->BootstrapProducer(bootstrap_req, &bootstrap_resp, &bootstrap_rpc)); - if (bootstrap_resp.has_error()) { - RETURN_NOT_OK(StatusFromPB(bootstrap_resp.error().status())); - } - - resp->mutable_bootstrap_ids()->Swap(bootstrap_resp.mutable_cdc_bootstrap_ids()); - if (bootstrap_resp.has_bootstrap_time()) { - resp->set_bootstrap_time(bootstrap_resp.bootstrap_time()); - } - - return Status::OK(); -} - -Status CatalogManager::SetUniverseReplicationInfoEnabled( - const xcluster::ReplicationGroupId& replication_group_id, bool is_enabled) { - scoped_refptr universe; - { - SharedLock lock(mutex_); - - universe = FindPtrOrNull(universe_replication_map_, replication_group_id); - if (universe == nullptr) { - return STATUS( - NotFound, "Could not find CDC producer universe", replication_group_id, - MasterError(MasterErrorPB::OBJECT_NOT_FOUND)); - } + universe = FindPtrOrNull(universe_replication_map_, replication_group_id); + if (universe == nullptr) { + return STATUS( + NotFound, "Could not find CDC producer universe", replication_group_id, + MasterError(MasterErrorPB::OBJECT_NOT_FOUND)); + } } // Update the Master's Universe Config with the new state. @@ -5121,314 +3261,6 @@ Status CatalogManager::SetUniverseReplicationEnabled( return Status::OK(); } -Status CatalogManager::AlterUniverseReplication( - const AlterUniverseReplicationRequestPB* req, AlterUniverseReplicationResponsePB* resp, - rpc::RpcContext* rpc, const LeaderEpoch& epoch) { - LOG(INFO) << "Servicing AlterUniverseReplication request from " << RequestorString(rpc) << ": " - << req->ShortDebugString(); - - SCHECK_PB_FIELDS_NOT_EMPTY(*req, replication_group_id); - - RETURN_NOT_OK(ValidateUniverseUUID(req, *this)); - - auto replication_group_id = xcluster::ReplicationGroupId(req->replication_group_id()); - auto original_ri = GetUniverseReplication(replication_group_id); - SCHECK_EC_FORMAT( - original_ri, NotFound, MasterError(MasterErrorPB::OBJECT_NOT_FOUND), - "Could not find xCluster replication group $0", replication_group_id); - - // Currently, config options are mutually exclusive to simplify transactionality. - int config_count = (req->producer_master_addresses_size() > 0 ? 1 : 0) + - (req->producer_table_ids_to_remove_size() > 0 ? 1 : 0) + - (req->producer_table_ids_to_add_size() > 0 ? 1 : 0) + - (req->has_new_replication_group_id() ? 1 : 0) + - (!req->producer_namespace_id_to_remove().empty() ? 1 : 0); - SCHECK_EC_FORMAT( - config_count == 1, InvalidArgument, MasterError(MasterErrorPB::INVALID_REQUEST), - "Only 1 Alter operation per request currently supported: $0", req->ShortDebugString()); - - if (req->producer_master_addresses_size() > 0) { - return UpdateProducerAddress(original_ri, req); - } - - if (req->has_producer_namespace_id_to_remove()) { - return RemoveNamespaceFromReplicationGroup( - original_ri, req->producer_namespace_id_to_remove(), *this, epoch); - } - - if (req->producer_table_ids_to_remove_size() > 0) { - std::vector table_ids( - req->producer_table_ids_to_remove().begin(), req->producer_table_ids_to_remove().end()); - return master::RemoveTablesFromReplicationGroup(original_ri, table_ids, *this, epoch); - } - - if (req->producer_table_ids_to_add_size() > 0) { - RETURN_NOT_OK(AddTablesToReplication(original_ri, req, resp, rpc)); - xcluster_manager_->CreateXClusterSafeTimeTableAndStartService(); - return Status::OK(); - } - - if (req->has_new_replication_group_id()) { - return RenameUniverseReplication(original_ri, req); - } - - return Status::OK(); -} - -Status CatalogManager::UpdateProducerAddress( - scoped_refptr universe, const AlterUniverseReplicationRequestPB* req) { - CHECK_GT(req->producer_master_addresses_size(), 0); - - // TODO: Verify the input. Setup an RPC Task, ListTables, ensure same. - - { - // 1a. Persistent Config: Update the Universe Config for Master. - auto l = universe->LockForWrite(); - l.mutable_data()->pb.mutable_producer_master_addresses()->CopyFrom( - req->producer_master_addresses()); - - // 1b. Persistent Config: Update the Consumer Registry (updates TServers) - auto cluster_config = ClusterConfig(); - auto cl = cluster_config->LockForWrite(); - auto replication_group_map = - cl.mutable_data()->pb.mutable_consumer_registry()->mutable_producer_map(); - auto it = replication_group_map->find(req->replication_group_id()); - if (it == replication_group_map->end()) { - LOG(WARNING) << "Valid Producer Universe not in Consumer Registry: " - << req->replication_group_id(); - return STATUS( - NotFound, "Could not find CDC producer universe", req->ShortDebugString(), - MasterError(MasterErrorPB::OBJECT_NOT_FOUND)); - } - it->second.mutable_master_addrs()->CopyFrom(req->producer_master_addresses()); - cl.mutable_data()->pb.set_version(cl.mutable_data()->pb.version() + 1); - - { - // Need both these updates to be atomic. - auto w = sys_catalog_->NewWriter(leader_ready_term()); - RETURN_NOT_OK(w->Mutate( - QLWriteRequestPB::QL_STMT_UPDATE, universe.get(), cluster_config.get())); - RETURN_NOT_OK(CheckStatus( - sys_catalog_->SyncWrite(w.get()), - "Updating universe replication info and cluster config in sys-catalog")); - } - l.Commit(); - cl.Commit(); - } - - // 2. Memory Update: Change xcluster_rpc_tasks (Master cache) - { - auto result = universe->GetOrCreateXClusterRpcTasks(req->producer_master_addresses()); - if (!result.ok()) { - return result.status(); - } - } - - return Status::OK(); -} - -Status CatalogManager::AddTablesToReplication( - scoped_refptr universe, const AlterUniverseReplicationRequestPB* req, - AlterUniverseReplicationResponsePB* resp, rpc::RpcContext* rpc) { - SCHECK_GT(req->producer_table_ids_to_add_size(), 0, InvalidArgument, "No tables specified"); - - if (universe->IsDbScoped()) { - // We either add the entire namespace at once, or one table at a time as they get created. - if (req->has_producer_namespace_to_add()) { - SCHECK( - !req->producer_namespace_to_add().id().empty(), InvalidArgument, "Invalid Namespace Id"); - SCHECK( - !req->producer_namespace_to_add().name().empty(), InvalidArgument, - "Invalid Namespace name"); - SCHECK_EQ( - req->producer_namespace_to_add().database_type(), YQLDatabase::YQL_DATABASE_PGSQL, - InvalidArgument, "Invalid Namespace database_type"); - } else { - SCHECK_EQ( - req->producer_table_ids_to_add_size(), 1, InvalidArgument, - "When adding more than table to a DB scoped replication the namespace info must also be " - "provided"); - } - } else { - SCHECK( - !req->has_producer_namespace_to_add(), InvalidArgument, - "Cannot add namespaces to non DB scoped replication"); - } - - xcluster::ReplicationGroupId alter_replication_group_id(xcluster::GetAlterReplicationGroupId( - xcluster::ReplicationGroupId(req->replication_group_id()))); - - // If user passed in bootstrap ids, check that there is a bootstrap id for every table. - SCHECK( - req->producer_bootstrap_ids_to_add_size() == 0 || - req->producer_table_ids_to_add_size() == req->producer_bootstrap_ids_to_add().size(), - InvalidArgument, "Number of bootstrap ids must be equal to number of tables", - req->ShortDebugString()); - - // Verify no 'alter' command running. - scoped_refptr alter_ri; - { - SharedLock lock(mutex_); - alter_ri = FindPtrOrNull(universe_replication_map_, alter_replication_group_id); - } - { - if (alter_ri != nullptr) { - LOG(INFO) << "Found " << alter_replication_group_id << "... Removing"; - if (alter_ri->LockForRead()->is_deleted_or_failed()) { - // Delete previous Alter if it's completed but failed. - master::DeleteUniverseReplicationRequestPB delete_req; - delete_req.set_replication_group_id(alter_ri->id()); - master::DeleteUniverseReplicationResponsePB delete_resp; - Status s = DeleteUniverseReplication(&delete_req, &delete_resp, rpc); - if (!s.ok()) { - if (delete_resp.has_error()) { - resp->mutable_error()->Swap(delete_resp.mutable_error()); - return s; - } - return SetupError(resp->mutable_error(), s); - } - } else { - return STATUS( - InvalidArgument, "Alter for CDC producer currently running", req->ShortDebugString(), - MasterError(MasterErrorPB::INVALID_REQUEST)); - } - } - } - - // Map each table id to its corresponding bootstrap id. - std::unordered_map table_id_to_bootstrap_id; - if (req->producer_bootstrap_ids_to_add().size() > 0) { - for (int i = 0; i < req->producer_table_ids_to_add().size(); i++) { - table_id_to_bootstrap_id[req->producer_table_ids_to_add(i)] = - req->producer_bootstrap_ids_to_add(i); - } - - // Ensure that table ids are unique. We need to do this here even though - // the same check is performed by SetupUniverseReplication because - // duplicate table ids can cause a bootstrap id entry in table_id_to_bootstrap_id - // to be overwritten. - if (table_id_to_bootstrap_id.size() != - implicit_cast(req->producer_table_ids_to_add().size())) { - return STATUS( - InvalidArgument, - "When providing bootstrap ids, " - "the list of tables must be unique", - req->ShortDebugString(), MasterError(MasterErrorPB::INVALID_REQUEST)); - } - } - - // Only add new tables. Ignore tables that are currently being replicated. - auto tid_iter = req->producer_table_ids_to_add(); - std::unordered_set new_tables(tid_iter.begin(), tid_iter.end()); - auto original_universe_l = universe->LockForRead(); - auto& original_universe_pb = original_universe_l->pb; - - for (const auto& table_id : original_universe_pb.tables()) { - new_tables.erase(table_id); - } - if (new_tables.empty()) { - return STATUS( - InvalidArgument, "CDC producer already contains all requested tables", - req->ShortDebugString(), MasterError(MasterErrorPB::INVALID_REQUEST)); - } - - // 1. create an ALTER table request that mirrors the original 'setup_replication'. - master::SetupUniverseReplicationRequestPB setup_req; - master::SetupUniverseReplicationResponsePB setup_resp; - setup_req.set_replication_group_id(alter_replication_group_id.ToString()); - setup_req.mutable_producer_master_addresses()->CopyFrom( - original_universe_pb.producer_master_addresses()); - setup_req.set_transactional(original_universe_pb.transactional()); - - if (req->has_producer_namespace_to_add()) { - *setup_req.add_producer_namespaces() = req->producer_namespace_to_add(); - } - - for (const auto& table_id : new_tables) { - setup_req.add_producer_table_ids(table_id); - - // Add bootstrap id to request if it exists. - auto bootstrap_id = FindOrNull(table_id_to_bootstrap_id, table_id); - if (bootstrap_id) { - setup_req.add_producer_bootstrap_ids(*bootstrap_id); - } - } - - // 2. run the 'setup_replication' pipeline on the ALTER Table - Status s = SetupUniverseReplication(&setup_req, &setup_resp, rpc); - if (!s.ok()) { - if (setup_resp.has_error()) { - resp->mutable_error()->Swap(setup_resp.mutable_error()); - return s; - } - return SetupError(resp->mutable_error(), s); - } - - return Status::OK(); -} - -Status CatalogManager::RenameUniverseReplication( - scoped_refptr universe, const AlterUniverseReplicationRequestPB* req) { - CHECK(req->has_new_replication_group_id()); - - const xcluster::ReplicationGroupId old_replication_group_id(universe->id()); - const xcluster::ReplicationGroupId new_replication_group_id(req->new_replication_group_id()); - if (old_replication_group_id == new_replication_group_id) { - return STATUS( - InvalidArgument, "Old and new replication ids must be different", req->ShortDebugString(), - MasterError(MasterErrorPB::INVALID_REQUEST)); - } - - { - LockGuard lock(mutex_); - auto l = universe->LockForWrite(); - scoped_refptr new_ri; - - // Assert that new_replication_name isn't already in use. - if (FindPtrOrNull(universe_replication_map_, new_replication_group_id) != nullptr) { - return STATUS( - InvalidArgument, "New replication id is already in use", req->ShortDebugString(), - MasterError(MasterErrorPB::INVALID_REQUEST)); - } - - // Since the replication_group_id is used as the key, we need to create a new - // UniverseReplicationInfo. - new_ri = new UniverseReplicationInfo(new_replication_group_id); - new_ri->mutable_metadata()->StartMutation(); - SysUniverseReplicationEntryPB* metadata = &new_ri->mutable_metadata()->mutable_dirty()->pb; - metadata->CopyFrom(l->pb); - metadata->set_replication_group_id(new_replication_group_id.ToString()); - - // Also need to update internal maps. - auto cluster_config = ClusterConfig(); - auto cl = cluster_config->LockForWrite(); - auto replication_group_map = - cl.mutable_data()->pb.mutable_consumer_registry()->mutable_producer_map(); - (*replication_group_map)[new_replication_group_id.ToString()] = - std::move((*replication_group_map)[old_replication_group_id.ToString()]); - replication_group_map->erase(old_replication_group_id.ToString()); - - { - // Need both these updates to be atomic. - auto w = sys_catalog_->NewWriter(leader_ready_term()); - RETURN_NOT_OK(w->Mutate(QLWriteRequestPB::QL_STMT_DELETE, universe.get())); - RETURN_NOT_OK( - w->Mutate(QLWriteRequestPB::QL_STMT_UPDATE, new_ri.get(), cluster_config.get())); - RETURN_NOT_OK(CheckStatus( - sys_catalog_->SyncWrite(w.get()), - "Updating universe replication info and cluster config in sys-catalog")); - } - new_ri->mutable_metadata()->CommitMutation(); - cl.Commit(); - - // Update universe_replication_map after persistent data is saved. - universe_replication_map_[new_replication_group_id] = new_ri; - universe_replication_map_.erase(old_replication_group_id); - } - - return Status::OK(); -} - Status CatalogManager::GetUniverseReplication( const GetUniverseReplicationRequestPB* req, GetUniverseReplicationResponsePB* resp, rpc::RpcContext* rpc) { @@ -5457,87 +3289,6 @@ Status CatalogManager::GetUniverseReplication( return Status::OK(); } -/* - * Checks if the universe replication setup has completed. - * Returns Status::OK() if this call succeeds, and uses resp->done() to determine if the setup has - * completed (either failed or succeeded). If the setup has failed, then resp->replication_error() - * is also set. If it succeeds, replication_error() gets set to OK. - */ -Status CatalogManager::IsSetupUniverseReplicationDone( - const IsSetupUniverseReplicationDoneRequestPB* req, - IsSetupUniverseReplicationDoneResponsePB* resp, - rpc::RpcContext* rpc) { - LOG(INFO) << "IsSetupUniverseReplicationDone from " << RequestorString(rpc) << ": " - << req->DebugString(); - - SCHECK_PB_FIELDS_NOT_EMPTY(*req, replication_group_id); - - auto is_operation_done = VERIFY_RESULT(master::IsSetupUniverseReplicationDone( - xcluster::ReplicationGroupId(req->replication_group_id()), *this)); - - resp->set_done(is_operation_done.done()); - StatusToPB(is_operation_done.status(), resp->mutable_replication_error()); - return Status::OK(); -} - -Status CatalogManager::IsSetupNamespaceReplicationWithBootstrapDone( - const IsSetupNamespaceReplicationWithBootstrapDoneRequestPB* req, - IsSetupNamespaceReplicationWithBootstrapDoneResponsePB* resp, rpc::RpcContext* rpc) { - LOG(INFO) << Format( - "IsSetupNamespaceReplicationWithBootstrapDone $0: $1", RequestorString(rpc), - req->DebugString()); - - SCHECK(req->has_replication_group_id(), InvalidArgument, "Replication group ID must be provided"); - const xcluster::ReplicationGroupId replication_group_id(req->replication_group_id()); - - scoped_refptr bootstrap_info; - { - SharedLock lock(mutex_); - - bootstrap_info = FindPtrOrNull(universe_replication_bootstrap_map_, replication_group_id); - SCHECK( - bootstrap_info != nullptr, NotFound, - Format( - "Could not find universe replication bootstrap $0", replication_group_id.ToString())); - } - - // Terminal states are DONE or some failure state. - { - auto l = bootstrap_info->LockForRead(); - resp->set_state(l->state()); - - if (l->is_done()) { - resp->set_done(true); - StatusToPB(Status::OK(), resp->mutable_bootstrap_error()); - return Status::OK(); - } - - if (l->is_deleted_or_failed()) { - resp->set_done(true); - - if (!bootstrap_info->GetReplicationBootstrapErrorStatus().ok()) { - StatusToPB( - bootstrap_info->GetReplicationBootstrapErrorStatus(), resp->mutable_bootstrap_error()); - } else { - LOG(WARNING) << "Did not find setup universe replication bootstrap error status."; - StatusToPB(STATUS(InternalError, "unknown error"), resp->mutable_bootstrap_error()); - } - - // Add failed bootstrap to GC now that we've responded to the user. - { - LockGuard lock(mutex_); - replication_bootstraps_to_clear_.push_back(bootstrap_info->ReplicationGroupId()); - } - - return Status::OK(); - } - } - - // Not done yet. - resp->set_done(false); - return Status::OK(); -} - Status CatalogManager::UpdateConsumerOnProducerSplit( const UpdateConsumerOnProducerSplitRequestPB* req, UpdateConsumerOnProducerSplitResponsePB* resp, @@ -6332,7 +4083,7 @@ void CatalogManager::RunXReplBgTasks(const LeaderEpoch& epoch) { WARN_NOT_OK(CleanUpDeletedXReplStreams(epoch), "Failed Cleaning Deleted XRepl Streams"); // Clean up Failed Universes on the Consumer. - WARN_NOT_OK(ClearFailedUniverse(), "Failed Clearing Failed Universe"); + WARN_NOT_OK(ClearFailedUniverse(epoch), "Failed Clearing Failed Universe"); // Clean up Failed Replication Bootstrap on the Consumer. WARN_NOT_OK(ClearFailedReplicationBootstrap(), "Failed Clearing Failed Replication Bootstrap"); @@ -6344,8 +4095,7 @@ void CatalogManager::RunXReplBgTasks(const LeaderEpoch& epoch) { StartXReplParentTabletDeletionTaskIfStopped(); } - -Status CatalogManager::ClearFailedUniverse() { +Status CatalogManager::ClearFailedUniverse(const LeaderEpoch& epoch) { // Delete a single failed universe from universes_to_clear_. if (PREDICT_FALSE(FLAGS_disable_universe_gc)) { return Status::OK(); @@ -6374,7 +4124,8 @@ Status CatalogManager::ClearFailedUniverse() { req.set_replication_group_id(replication_group_id.ToString()); req.set_ignore_errors(true); - RETURN_NOT_OK(DeleteUniverseReplication(&req, &resp, /* RpcContext */ nullptr)); + RETURN_NOT_OK( + xcluster_manager_->DeleteUniverseReplication(&req, &resp, /* RpcContext */ nullptr, epoch)); return Status::OK(); } @@ -6767,126 +4518,6 @@ Status CatalogManager::BumpVersionAndStoreClusterConfig( return Status::OK(); } -Status CatalogManager::ValidateTableSchemaForXCluster( - const client::YBTableInfo& info, const SetupReplicationInfo& setup_info, - GetTableSchemaResponsePB* resp) { - bool is_ysql_table = info.table_type == client::YBTableType::PGSQL_TABLE_TYPE; - if (setup_info.transactional && !GetAtomicFlag(&FLAGS_TEST_allow_ycql_transactional_xcluster) && - !is_ysql_table) { - return STATUS_FORMAT( - NotSupported, "Transactional replication is not supported for non-YSQL tables: $0", - info.table_name.ToString()); - } - - // Get corresponding table schema on local universe. - GetTableSchemaRequestPB req; - - auto* table = req.mutable_table(); - table->set_table_name(info.table_name.table_name()); - table->mutable_namespace_()->set_name(info.table_name.namespace_name()); - table->mutable_namespace_()->set_database_type( - GetDatabaseTypeForTable(client::ClientToPBTableType(info.table_type))); - - // Since YSQL tables are not present in table map, we first need to list tables to get the table - // ID and then get table schema. - // Remove this once table maps are fixed for YSQL. - ListTablesRequestPB list_req; - ListTablesResponsePB list_resp; - - list_req.set_name_filter(info.table_name.table_name()); - Status status = ListTables(&list_req, &list_resp); - SCHECK( - status.ok() && !list_resp.has_error(), NotFound, - Format("Error while listing table: $0", status.ToString())); - - const auto& source_schema = client::internal::GetSchema(info.schema); - for (const auto& t : list_resp.tables()) { - // Check that table name and namespace both match. - if (t.name() != info.table_name.table_name() || - t.namespace_().name() != info.table_name.namespace_name()) { - continue; - } - - // Check that schema name matches for YSQL tables, if the field is empty, fill in that - // information during GetTableSchema call later. - bool has_valid_pgschema_name = !t.pgschema_name().empty(); - if (is_ysql_table && has_valid_pgschema_name && - t.pgschema_name() != source_schema.SchemaName()) { - continue; - } - - // Get the table schema. - table->set_table_id(t.id()); - status = GetTableSchema(&req, resp); - SCHECK( - status.ok() && !resp->has_error(), NotFound, - Format("Error while getting table schema: $0", status.ToString())); - - // Double-check schema name here if the previous check was skipped. - if (is_ysql_table && !has_valid_pgschema_name) { - std::string target_schema_name = resp->schema().pgschema_name(); - if (target_schema_name != source_schema.SchemaName()) { - table->clear_table_id(); - continue; - } - } - - // Verify that the table on the target side supports replication. - if (is_ysql_table && t.has_relation_type() && t.relation_type() == MATVIEW_TABLE_RELATION) { - return STATUS_FORMAT( - NotSupported, "Replication is not supported for materialized view: $0", - info.table_name.ToString()); - } - - Schema consumer_schema; - auto result = SchemaFromPB(resp->schema(), &consumer_schema); - - // We now have a table match. Validate the schema. - SCHECK( - result.ok() && consumer_schema.EquivalentForDataCopy(source_schema), IllegalState, - Format( - "Source and target schemas don't match: " - "Source: $0, Target: $1, Source schema: $2, Target schema: $3", - info.table_id, resp->identifier().table_id(), info.schema.ToString(), - resp->schema().DebugString())); - break; - } - - SCHECK( - table->has_table_id(), NotFound, - Format( - "Could not find matching table for $0$1", info.table_name.ToString(), - (is_ysql_table ? " pgschema_name: " + source_schema.SchemaName() : ""))); - - // Still need to make map of table id to resp table id (to add to validated map) - // For colocated tables, only add the parent table since we only added the parent table to the - // original pb (we use the number of tables in the pb to determine when validation is done). - if (info.colocated) { - // We require that colocated tables have the same colocation ID. - // - // Backward compatibility: tables created prior to #7378 use YSQL table OID as a colocation ID. - auto source_clc_id = info.schema.has_colocation_id() - ? info.schema.colocation_id() - : CHECK_RESULT(GetPgsqlTableOid(info.table_id)); - auto target_clc_id = (resp->schema().has_colocated_table_id() && - resp->schema().colocated_table_id().has_colocation_id()) - ? resp->schema().colocated_table_id().colocation_id() - : CHECK_RESULT(GetPgsqlTableOid(resp->identifier().table_id())); - SCHECK( - source_clc_id == target_clc_id, IllegalState, - Format( - "Source and target colocation IDs don't match for colocated table: " - "Source: $0, Target: $1, Source colocation ID: $2, Target colocation ID: $3", - info.table_id, resp->identifier().table_id(), source_clc_id, target_clc_id)); - } - - SCHECK( - !xcluster_manager_->IsTableReplicationConsumer(table->table_id()), IllegalState, - "N:1 replication topology not supported"); - - return Status::OK(); -} - std::unordered_set CatalogManager::GetAllXReplStreamIds() const { SharedLock l(mutex_); std::unordered_set result; @@ -7112,5 +4743,75 @@ Status CatalogManager::RemoveTableFromCDCStreamMetadataAndMaps( return Status::OK(); } +void CatalogManager::InsertNewUniverseReplication(UniverseReplicationInfo& replication_group) { + LockGuard lock(mutex_); + universe_replication_map_[replication_group.ReplicationGroupId()] = + scoped_refptr(&replication_group); +} + +scoped_refptr CatalogManager::GetUniverseReplicationBootstrap( + const xcluster::ReplicationGroupId& replication_group_id) { + TRACE("Acquired catalog manager lock"); + SharedLock lock(mutex_); + + return FindPtrOrNull(universe_replication_bootstrap_map_, replication_group_id); +} + +void CatalogManager::InsertNewUniverseReplicationInfoBootstrapInfo( + UniverseReplicationBootstrapInfo& bootstrap_info) { + LockGuard lock(mutex_); + universe_replication_bootstrap_map_[bootstrap_info.ReplicationGroupId()] = + scoped_refptr(&bootstrap_info); +} + +void CatalogManager::MarkReplicationBootstrapForCleanup( + const xcluster::ReplicationGroupId& replication_group_id) { + LockGuard lock(mutex_); + replication_bootstraps_to_clear_.push_back(replication_group_id); +} + +Status CatalogManager::ReplaceUniverseReplication( + const UniverseReplicationInfo& old_replication_group, + UniverseReplicationInfo& new_replication_group, const ClusterConfigInfo& cluster_config, + const LeaderEpoch& epoch) { + DCHECK(old_replication_group.metadata().HasWriteLock()); + DCHECK(new_replication_group.metadata().HasWriteLock()); + DCHECK(cluster_config.metadata().HasWriteLock()); + + LockGuard lock(mutex_); + + SCHECK_FORMAT( + !universe_replication_map_.contains(new_replication_group.ReplicationGroupId()), + InvalidArgument, "New replication id $0 is already in use", + new_replication_group.ReplicationGroupId()); + + { + // Need all three updates to be atomic. + auto w = sys_catalog_->NewWriter(epoch.leader_term); + RETURN_NOT_OK(w->Mutate(QLWriteRequestPB::QL_STMT_DELETE, &old_replication_group)); + RETURN_NOT_OK( + w->Mutate(QLWriteRequestPB::QL_STMT_UPDATE, &new_replication_group, &cluster_config)); + RETURN_NOT_OK(CheckStatus( + sys_catalog_->SyncWrite(w.get()), + "Updating universe replication info and cluster config in sys-catalog")); + } + + // Update universe_replication_map after persistent data is saved. + universe_replication_map_[new_replication_group.ReplicationGroupId()] = + scoped_refptr(&new_replication_group); + universe_replication_map_.erase(old_replication_group.ReplicationGroupId()); + + return Status::OK(); +} + +void CatalogManager::RemoveUniverseReplicationFromMap( + const xcluster::ReplicationGroupId& replication_group_id) { + LockGuard lock(mutex_); + if (universe_replication_map_.erase(replication_group_id) < 1) { + LOG(DFATAL) << "Replication group " << replication_group_id + << " was already deleted from in-mem map"; + } +} + } // namespace master } // namespace yb From 1d646b1217e1e024d2b9b0c715b76361bef3c280 Mon Sep 17 00:00:00 2001 From: Bvsk Patnaik Date: Thu, 18 Jul 2024 20:38:18 -0700 Subject: [PATCH 1189/1195] [docs] Add ysql_output_buffer_size to yb-tserver config reference. (#23233) @netlify preview/reference/configuration/yb-tserver/#ysql-output-buffer-size --- .../content/preview/reference/configuration/yb-tserver.md | 8 ++++++++ docs/content/stable/reference/configuration/yb-tserver.md | 8 ++++++++ docs/content/v2.20/reference/configuration/yb-tserver.md | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/docs/content/preview/reference/configuration/yb-tserver.md b/docs/content/preview/reference/configuration/yb-tserver.md index fff0e4835bca..e1b2381d05cd 100644 --- a/docs/content/preview/reference/configuration/yb-tserver.md +++ b/docs/content/preview/reference/configuration/yb-tserver.md @@ -855,6 +855,14 @@ Default: `-1` (disables logging statement durations) Specifies the lowest YSQL message level to log. +##### --ysql_output_buffer_size + +Size of YSQL layer output buffer, in bytes. YSQL buffers query responses in this output buffer until either a buffer flush is requested by the client or the buffer overflows. + +As long as no data has been flushed from the buffer, the database can retry queries on retryable errors. For example, you can increase the size of the buffer so that YSQL can retry [read restart errors](../../../architecture/transactions/read-restart-error). + +Default: `262144` (256kB, type: int32) + ### YCQL The following flags support the use of the [YCQL API](../../../api/ycql/): diff --git a/docs/content/stable/reference/configuration/yb-tserver.md b/docs/content/stable/reference/configuration/yb-tserver.md index f4c285516396..15ad7966c670 100644 --- a/docs/content/stable/reference/configuration/yb-tserver.md +++ b/docs/content/stable/reference/configuration/yb-tserver.md @@ -855,6 +855,14 @@ Default: `-1` (disables logging statement durations) Specifies the lowest YSQL message level to log. +##### --ysql_output_buffer_size + +Size of YSQL layer output buffer, in bytes. YSQL buffers query responses in this output buffer until either a buffer flush is requested by the client or the buffer overflows. + +As long as no data has been flushed from the buffer, the database can retry queries on retryable errors. For example, you can increase the size of the buffer so that YSQL can retry [read restart errors](../../../architecture/transactions/read-restart-error). + +Default: `262144` (256kB, type: int32) + ### YCQL The following flags support the use of the [YCQL API](../../../api/ycql/): diff --git a/docs/content/v2.20/reference/configuration/yb-tserver.md b/docs/content/v2.20/reference/configuration/yb-tserver.md index ffd6cdf57e58..75073fcda92e 100644 --- a/docs/content/v2.20/reference/configuration/yb-tserver.md +++ b/docs/content/v2.20/reference/configuration/yb-tserver.md @@ -732,6 +732,14 @@ Default: `-1` (disables logging statement durations) Specifies the lowest YSQL message level to log. +##### --ysql_output_buffer_size + +Size of YSQL layer output buffer, in bytes. YSQL buffers query responses in this output buffer until either a buffer flush is requested by the client or the buffer overflows. + +As long as no data has been flushed from the buffer, the database can retry queries on retryable errors. For example, you can increase the size of the buffer so that YSQL can retry [read restart errors](../../../architecture/transactions/read-restart-error). + +Default: `262144` (256kB, type: int32) + ### YCQL The following flags support the use of the [YCQL API](../../../api/ycql/): From 2d95dd274a936a8d16463f240b3c3cfda3e6c92a Mon Sep 17 00:00:00 2001 From: Shubham Date: Fri, 19 Jul 2024 16:08:02 +0530 Subject: [PATCH 1190/1195] Fix the issue yaml parsing Summary: Fixes the issue yaml parsing. We changed the formatting for yaml list. This diff fixes the usage for the same. Test Plan: Prepared alma9 node using ynp. Verified universe creation. Reviewers: vbansal, asharma Reviewed By: asharma Subscribers: yugaware Differential Revision: https://phorge.dev.yugabyte.com/D36711 --- .../modules/provision/configure_os/templates/precheck.j2 | 5 +++-- .../ynp/modules/provision/node_agent/node_agent.py | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/managed/node-agent/resources/ynp/modules/provision/configure_os/templates/precheck.j2 b/managed/node-agent/resources/ynp/modules/provision/configure_os/templates/precheck.j2 index 8896e04aa772..9268a5888e91 100644 --- a/managed/node-agent/resources/ynp/modules/provision/configure_os/templates/precheck.j2 +++ b/managed/node-agent/resources/ynp/modules/provision/configure_os/templates/precheck.j2 @@ -88,7 +88,8 @@ else fi threshold=49 #Gigabytes -mount_point_array={{ mount_points }} +# Convert the space-separated string to an array in bash +IFS=' ' read -r -a mount_points_array <<< {{ mount_points }} # Verify each mount point for mount_point in "${mount_point_array[@]}"; do if [ -d "$mount_point" ]; then @@ -118,7 +119,7 @@ for mount_point in "${mount_point_array[@]}"; do echo "[PASS] $message" else result="FAIL" - message="Insufficient disk space: ${AVAILABLE}G available, ${THRESHOLD}G required" + message="Insufficient disk space: ${free_space_gb}G available, ${threshold}G required" echo "[FAIL] $message" any_fail=1 fi diff --git a/managed/node-agent/resources/ynp/modules/provision/node_agent/node_agent.py b/managed/node-agent/resources/ynp/modules/provision/node_agent/node_agent.py index 0ef27cd2b52c..4d78a807397c 100644 --- a/managed/node-agent/resources/ynp/modules/provision/node_agent/node_agent.py +++ b/managed/node-agent/resources/ynp/modules/provision/node_agent/node_agent.py @@ -104,7 +104,7 @@ def _generate_instance_type_payload(self, context): 'volumeDetailsList': [] } } - mount_points = context.get('instance_type_mount_points').split(',') + mount_points = context.get('instance_type_mount_points').strip("[]").replace("'", "").split(", ") for mp in mount_points: volume_detail = { 'volumeSizeGB': context.get('instance_type_volume_size'), @@ -167,7 +167,7 @@ def _create_instance_if_not_exists(self, context, provider): logging.error(f"Request error: {req_err}") except ValueError as json_err: logging.error(f"Error parsing JSON response: {json_err}") - + def render_templates(self, context): node_agent_enabled = False yba_url = context.get('url') @@ -219,7 +219,7 @@ def render_templates(self, context): except requests.exceptions.RequestException as req_err: logging.error(f"Request error: {req_err}") - + add_node_payload = self._generate_add_node_payload(context) add_node_payload_file = os.path.join(context.get('tmp_directory'), 'add_node_to_provider.json') with open(add_node_payload_file, 'w') as f: From dd9e85b79b6ee2291d12283d6997c6916fdc9703 Mon Sep 17 00:00:00 2001 From: asharma-yb Date: Fri, 12 Jul 2024 14:02:37 +0530 Subject: [PATCH 1191/1195] [PLAT-14534]Add regex match for GCP Instance template Summary: Added regex match for gcp instance template. Regex taken from gcp documentation [[https://cloud.google.com/compute/docs/reference/rest/v1/instanceTemplates | here]]. Test Plan: Tested manually that validation fails with invalid characters. Reviewers: #yba-api-review!, svarshney Reviewed By: svarshney Subscribers: yugaware Differential Revision: https://phorge.dev.yugabyte.com/D36543 --- .../models/configs/validators/GCPProviderValidator.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/managed/src/main/java/com/yugabyte/yw/models/configs/validators/GCPProviderValidator.java b/managed/src/main/java/com/yugabyte/yw/models/configs/validators/GCPProviderValidator.java index 033ebbe1fe60..dd4da17b8652 100644 --- a/managed/src/main/java/com/yugabyte/yw/models/configs/validators/GCPProviderValidator.java +++ b/managed/src/main/java/com/yugabyte/yw/models/configs/validators/GCPProviderValidator.java @@ -1,5 +1,7 @@ package com.yugabyte.yw.models.configs.validators; +import static play.mvc.Http.Status.BAD_REQUEST; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.google.api.services.compute.model.Firewall; @@ -39,6 +41,7 @@ public class GCPProviderValidator extends ProviderFieldsValidator { private final GCPCloudImpl gcpCloudImpl; private final RuntimeConfGetter runtimeConfGetter; + private final String INSTANCE_TEMPLATE_REGEX = "[a-z]([-a-z0-9]*[a-z0-9])?"; @Inject public GCPProviderValidator( @@ -229,6 +232,12 @@ private void validateInstanceTempl( .get("jsonPath") .asText(); try { + if (!instanceTemplate.matches(INSTANCE_TEMPLATE_REGEX)) { + throw new PlatformServiceException( + BAD_REQUEST, + "Instance template must start with a lowercase character and can only contain" + + " alphanumeric characters and '-'"); + } if (!apiClient.checkInstanceTempelate(instanceTemplate)) { String errorMsg = String.format( From d3bba1870a28f18451892d18a3a443b28ae8b0ec Mon Sep 17 00:00:00 2001 From: Dwight Hodge <79169168+ddhodge@users.noreply.github.com> Date: Fri, 19 Jul 2024 11:15:46 -0400 Subject: [PATCH 1192/1195] update diagram (#23245) --- .../cdc-logical-replication.md | 16 +++++++++------- .../cdc-logical-replication-architecture.png | Bin 0 -> 163836 bytes .../logical_replication_architecture.png | Bin 121939 -> 0 bytes 3 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 docs/static/images/architecture/cdc-logical-replication-architecture.png delete mode 100644 docs/static/images/architecture/logical_replication_architecture.png diff --git a/docs/content/preview/architecture/docdb-replication/cdc-logical-replication.md b/docs/content/preview/architecture/docdb-replication/cdc-logical-replication.md index a70e352c2d9f..c894824bdd26 100644 --- a/docs/content/preview/architecture/docdb-replication/cdc-logical-replication.md +++ b/docs/content/preview/architecture/docdb-replication/cdc-logical-replication.md @@ -19,9 +19,9 @@ CDC in YugabyteDB is based on the PostgreSQL Logical Replication model. The fund ## Architecture -![Logical-Replication-Architecture](/images/architecture/logical_replication_architecture.png) +![Logical replication architecture](/images/architecture/cdc-logical-replication-architecture.png) -The following are the main components of the Yugabyte CDC solution - +The following are the main components of the Yugabyte CDC solution: 1. Walsender - A special purpose PG backend responsible for streaming changes to the client and handling acknowledgments. @@ -41,7 +41,7 @@ The initial snapshot data for each table is consumed by executing a correspondin First, a `SET LOCAL yb_read_time TO ' ht'` command should be executed on the connection (session). The SELECT statement corresponding to the snapshot query should then be executed as part of the same transaction. -The HybridTime value to use in the `SET LOCAL yb_read_time `command is the value of the `snapshot_name` field that is returned by the `CREATE_REPLICATION_SLOT` command. Alternatively, it can be obtained by querying the `pg_replication_slots` view. +The HybridTime value to use in the `SET LOCAL yb_read_time` command is the value of the `snapshot_name` field that is returned by the `CREATE_REPLICATION_SLOT` command. Alternatively, it can be obtained by querying the `pg_replication_slots` view. During Snapshot consumption, the snapshot data from all tables will be from the same consistent state (`consistent_point`). At the end of Snapshot consumption, the state of the target system is at/based on the `consistent_point`. History of the tables as of the `consistent_point` is retained on the source until the snapshot is consumed. @@ -67,14 +67,16 @@ VWAL collects changes across multiple tablets, assembles the transactions, assig Walsender sends changes to the output plugin, which filters them according to the slot's publication and converts them into the client's desired format. These changes are then streamed to the client using the appropriate streaming replication protocols determined by the output plugin. Yugabyte follows the same streaming replication protocols as defined in PostgreSQL. + -Refer to [Replication Protocol](../../../explore/logical-replication/#Streaming-Protocol) for more details. +Refer to [Replication Protocol](../../../explore/change-data-capture/using-logical-replication/#streaming-protocol) for more details. {{< /note >}} {{< tip title="Explore" >}} - -See [Getting Started with Logical Replication](../../../explore/logical-replication/getting-started) to set up Logical Replication in YugabyteDB. + +See [Getting Started with Logical Replication](../../../explore/change-data-capture/using-logical-replication/getting-started/) to set up Logical Replication in YugabyteDB. {{< /tip >}} +--> diff --git a/docs/static/images/architecture/cdc-logical-replication-architecture.png b/docs/static/images/architecture/cdc-logical-replication-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..558d7967d17c77f1f8477a9a262d2914467b1986 GIT binary patch literal 163836 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.#so!(?iFF>0qL=y*+bf!|l9PIXZD7r}+p$^`LR( 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

    h3fo@T6YH=8YH66!Yt#F#PFX#oKchFiQvQ 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 literal 0 HcmV?d00001 diff --git a/docs/static/images/architecture/logical_replication_architecture.png b/docs/static/images/architecture/logical_replication_architecture.png deleted file mode 100644 index fff604a94aeeb798160c027052bb10037313dee8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121939 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 dCor Date: Thu, 18 Jul 2024 17:19:42 -0700 Subject: [PATCH 1193/1195] [/PLAT-14708] Fix JSON field name in TaskInfo query Summary: This was missed when task params were moved out from details field. Test Plan: Trivial - existing tests should succeed. Reviewers: vbansal, cwang Reviewed By: vbansal Subscribers: yugaware Differential Revision: https://phorge.dev.yugabyte.com/D36705 --- .../src/main/java/com/yugabyte/yw/models/TaskInfo.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/managed/src/main/java/com/yugabyte/yw/models/TaskInfo.java b/managed/src/main/java/com/yugabyte/yw/models/TaskInfo.java index f1fa83c35c8b..55c58ab495cf 100644 --- a/managed/src/main/java/com/yugabyte/yw/models/TaskInfo.java +++ b/managed/src/main/java/com/yugabyte/yw/models/TaskInfo.java @@ -398,8 +398,8 @@ public static List findDuplicateDeleteBackupTasks(UUID customerUUID, U .eq("task_type", TaskType.DeleteBackup) .ne("task_state", State.Failure) .ne("task_state", State.Aborted) - .eq("details->>'customerUUID'", customerUUID.toString()) - .eq("details->>'backupUUID'", backupUUID.toString()) + .eq("task_params->>'customerUUID'", customerUUID.toString()) + .eq("task_params->>'backupUUID'", backupUUID.toString()) .findList(); } @@ -409,8 +409,8 @@ public static List findIncompleteDeleteBackupTasks(UUID customerUUID, .where() .in("task_type", TaskType.DeleteBackup, TaskType.DeleteBackupYb) .in("task_state", INCOMPLETE_STATES) - .eq("details->>'customerUUID'", customerUUID.toString()) - .eq("details->>'backupUUID'", backupUUID.toString()) + .eq("task_params->>'customerUUID'", customerUUID.toString()) + .eq("task_params->>'backupUUID'", backupUUID.toString()) .findList(); } } From ac9164b6c047ff4535edfd498632c954da854215 Mon Sep 17 00:00:00 2001 From: Hari Krishna Sunder Date: Thu, 18 Jul 2024 19:54:37 -0700 Subject: [PATCH 1194/1195] [#23173] DocDB: Allow large bytes to be passed to RateLimiter Summary: RateLimiter has a debug assert that you cannot `Request` more than `GetSingleBurstBytes`. In release mode we do not perform this check and any call gets stuck forever. This change allows large bytes to be requested on RateLimiter. It does so by breaking requests larger than `GetSingleBurstBytes` into multiple smaller requests. This change is a temporary fix to allow xCluster to operate without any issues. RocksDB RateLimiter has multiple enhancements over the years that would help avoid this and more starvation issues. Ex: https://github.com/facebook/rocksdb/commit/cb2476a0cacbf53ca16aecda46f7b4fe8a2594f3. We should consider pulling in those changes. Fixes #23173 Jira: DB-12112 Test Plan: RateLimiterTest.LargeRequests Reviewers: slingam Reviewed By: slingam Subscribers: ybase Differential Revision: https://phorge.dev.yugabyte.com/D36703 --- src/yb/rocksdb/util/rate_limiter.cc | 12 +++++++++++ src/yb/rocksdb/util/rate_limiter.h | 5 +++-- src/yb/rocksdb/util/rate_limiter_test.cc | 27 +++++++++++++++++++++++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/yb/rocksdb/util/rate_limiter.cc b/src/yb/rocksdb/util/rate_limiter.cc index 026232faab17..168a1e417948 100644 --- a/src/yb/rocksdb/util/rate_limiter.cc +++ b/src/yb/rocksdb/util/rate_limiter.cc @@ -62,6 +62,9 @@ GenericRateLimiter::GenericRateLimiter(int64_t rate_bytes_per_sec, fairness_(fairness > 100 ? 100 : fairness), rnd_((uint32_t)time(nullptr)), leader_(nullptr) { + CHECK_GT(refill_bytes_per_period_, 0) + << "rate_bytes_per_sec * refill_period_us should be > 1000000"; + for (size_t q = 0; q < yb::kElementsInIOPriority; ++q) { total_requests_[q] = 0; total_bytes_through_[q] = 0; @@ -108,6 +111,15 @@ void GenericRateLimiter::SetBytesPerSecond(int64_t bytes_per_second) { } void GenericRateLimiter::Request(int64_t bytes, const yb::IOPriority priority) { + while (bytes > 0) { + int64_t bytes_to_request = std::min(GetSingleBurstBytes(), bytes); + assert(bytes_to_request > 0); + RequestInternal(bytes_to_request, priority); + bytes -= bytes_to_request; + } +} + +void GenericRateLimiter::RequestInternal(int64_t bytes, const yb::IOPriority priority) { assert(bytes <= refill_bytes_per_period_.load(std::memory_order_relaxed)); const auto pri = yb::to_underlying(priority); diff --git a/src/yb/rocksdb/util/rate_limiter.h b/src/yb/rocksdb/util/rate_limiter.h index 572a807f22bd..71606a94ed68 100644 --- a/src/yb/rocksdb/util/rate_limiter.h +++ b/src/yb/rocksdb/util/rate_limiter.h @@ -49,9 +49,10 @@ class GenericRateLimiter : public RateLimiter { void SetBytesPerSecond(int64_t bytes_per_second) override; // Request for token to write bytes. If this request can not be satisfied, - // the call is blocked. Caller is responsible to make sure - // bytes <= GetSingleBurstBytes() + // the call is blocked. If the request is bigger than GetSingleBurstBytes() then the call is + // broken up into multiple requests of the same priority. void Request(const int64_t bytes, const yb::IOPriority pri) override; + void RequestInternal(const int64_t bytes, const yb::IOPriority pri); int64_t GetSingleBurstBytes() const override { return refill_bytes_per_period_.load(std::memory_order_relaxed); diff --git a/src/yb/rocksdb/util/rate_limiter_test.cc b/src/yb/rocksdb/util/rate_limiter_test.cc index 01d27df35a29..95e29d927907 100644 --- a/src/yb/rocksdb/util/rate_limiter_test.cc +++ b/src/yb/rocksdb/util/rate_limiter_test.cc @@ -40,7 +40,32 @@ namespace rocksdb { class RateLimiterTest : public RocksDBTest {}; TEST_F(RateLimiterTest, StartStop) { - std::unique_ptr limiter(new GenericRateLimiter(100, 100, 10)); + ASSERT_DEATH( + std::unique_ptr limiter(new GenericRateLimiter(100, 100, 10)), + "Check failed: refill_bytes_per_period_ > 0"); + + std::unique_ptr limiter(new GenericRateLimiter(1000, 1000, 10)); +} + +TEST_F(RateLimiterTest, LargeRequests) { + // Allow 1000 bytes per second. This gives us 1 byte every micro second, and a request of 1000 + // should take 1s. + std::unique_ptr limiter(new GenericRateLimiter(1000, 1000, 10)); + + auto now = yb::CoarseMonoClock::Now(); + limiter->Request(1000, yb::IOPriority::kHigh); + auto duration_waited = yb::ToMilliseconds(yb::CoarseMonoClock::Now() - now); + ASSERT_GT(duration_waited, 500); + +#if defined(OS_MACOSX) + // MacOS tests are much slower, so use a larger timeout. + ASSERT_LT(duration_waited, 10000); +#else + ASSERT_LT(duration_waited, 1500); +#endif + + ASSERT_EQ(limiter->GetTotalBytesThrough(), 1000); + ASSERT_EQ(limiter->GetTotalRequests(), 1000); } #ifndef OS_MACOSX From 5a76f6a8ff902ad40651320d9b4156739da845ed Mon Sep 17 00:00:00 2001 From: Sumukh-Phalgaonkar Date: Fri, 19 Jul 2024 15:36:19 +0530 Subject: [PATCH 1195/1195] [#23179] CDCSDK: Support data types with dynamically alloted oids in CDC Summary: This diff adds support for data types with dynamically alloted oids in CDC (for ex: hstore, enum array, etc). Such types contain invalid pg_type_oid for the corresponding columns in docdb schema. In the current implemtation, in `ybc_pggate`, while decoding the cdc records we look at the `type_map_` to obtain YBCPgTypeEntity, which is then used for decoding. However the `type_map_` does not contain any entries for the data types with dynamically alloted oids. As a result, this causes segmentation fault. To prevent such crashes, CDC prevents addition of tables with such columns to the stream. This diff removes the filtering logic and adds the tables to the stream even if it has such a type column. A function pointer will now be passed to `YBCPgGetCDCConsistentChanges`, which takes attribute number and the table_oid and returns the appropriate type entity by querying the `pg_type` catalog table. While decoding if a column is encountered with invalid pg_type_oid then, the passed function is invoked and type entity is obtained for decoding. **Upgrade/Rollback safety:** This diff adds a field `optional int32 attr_num` to DatumMessagePB. These changes are protected by the autoflag `ysql_yb_enable_replication_slot_consumption` which already exists but has not yet been released. Jira: DB-12118 Test Plan: Jenkins: urgent All the existing cdc tests ./yb_build.sh --java-test 'org.yb.pgsql.TestPgReplicationSlot#replicationConnectionConsumptionAllDataTypesWithYbOutput' Reviewers: skumar, stiwary, asrinivasan, dmitry Reviewed By: stiwary, dmitry Subscribers: steve.varnau, skarri, yql, ybase, ycdcxcluster Tags: #jenkins-ready Differential Revision: https://phorge.dev.yugabyte.com/D36689 --- .../org/yb/pgsql/TestPgReplicationSlot.java | 30 ++++++++++++---- src/postgres/src/backend/commands/ybccmds.c | 5 +-- .../logical/yb_virtual_wal_client.c | 23 ++++++++++++- src/postgres/src/include/commands/ybccmds.h | 3 +- src/yb/cdc/cdcsdk_producer.cc | 1 + src/yb/common/common.proto | 2 ++ src/yb/integration-tests/cdcsdk_ysql-test.cc | 13 ------- src/yb/master/master_xrepl-test.cc | 19 ++++++----- src/yb/master/xrepl_catalog_manager.cc | 18 ++++------ src/yb/yql/pggate/ybc_pggate.cc | 34 +++++++++++++------ src/yb/yql/pggate/ybc_pggate.h | 4 ++- 11 files changed, 97 insertions(+), 55 deletions(-) diff --git a/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgReplicationSlot.java b/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgReplicationSlot.java index 2c73dd859e78..4fe714230623 100644 --- a/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgReplicationSlot.java +++ b/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgReplicationSlot.java @@ -919,9 +919,12 @@ void replicationConnectionConsumptionAllDataTypes(String pluginName) throws Exce + "col_tsrange TSRANGE, " + "col_tstzrange TSTZRANGE, " + "col_daterange DATERANGE, " - + "col_discount coupon_discount_type)"; + + "col_hstore HSTORE, " + + "col_discount coupon_discount_type, " + +" col_discount_array coupon_discount_type[])"; try (Statement stmt = connection.createStatement()) { + stmt.execute("CREATE EXTENSION IF NOT EXISTS hstore;"); stmt.execute("CREATE TYPE coupon_discount_type AS ENUM ('FIXED', 'PERCENTAGE');"); stmt.execute(create_stmt); if (pluginName.equals(PG_OUTPUT_PLUGIN_NAME)) { @@ -947,7 +950,8 @@ void replicationConnectionConsumptionAllDataTypes(String pluginName) throws Exce + "'550e8400-e29b-41d4-a716-446655440000', B'101010', '2024-02-01 12:34:56+00:00', " + "'[1,10)', '[100,1000)', '[2024-01-01, 2024-12-31)', " + "'[2024-01-01 00:00:00+00:00, 2024-12-31 15:59:59+00:00)', " - + "'[2024-01-01, 2024-12-31)', 'FIXED');"); + + "'[2024-01-01, 2024-12-31)','key1 => value1, key2 => value2'::hstore, 'FIXED', " + + "array['FIXED', 'PERCENTAGE']::coupon_discount_type[]);"); } PGReplicationStream stream = replConnection.replicationStream() @@ -960,12 +964,14 @@ void replicationConnectionConsumptionAllDataTypes(String pluginName) throws Exce List result = new ArrayList(); // 1 Relation, begin, type, insert and commit record. - result.addAll(receiveMessage(stream, 5)); + result.addAll(receiveMessage(stream, 7)); List expectedResult = new ArrayList() { { add(PgOutputBeginMessage.CreateForComparison(LogSequenceNumber.valueOf("0/4"), 2)); + add(PgOutputTypeMessage.CreateForComparison("public", "hstore")); add(PgOutputTypeMessage.CreateForComparison("public", "coupon_discount_type")); + add(PgOutputTypeMessage.CreateForComparison("public", "_coupon_discount_type")); if (pluginName.equals(YB_OUTPUT_PLUGIN_NAME)) { add(PgOutputRelationMessage.CreateForComparison("public", "test_table", 'c', Arrays.asList(PgOutputRelationMessageColumn.CreateForComparison("a", 23), @@ -1003,8 +1009,12 @@ void replicationConnectionConsumptionAllDataTypes(String pluginName) throws Exce PgOutputRelationMessageColumn.CreateForComparison("col_tsrange", 3908), PgOutputRelationMessageColumn.CreateForComparison("col_tstzrange", 3910), PgOutputRelationMessageColumn.CreateForComparison("col_daterange", 3912), + // The Oids for columns below are not fixed. Changing the order of creation of + // objects (extensions, tables etc.) in the test will these Oids. + PgOutputRelationMessageColumn.CreateForComparison("col_hstore", 16385), PgOutputRelationMessageColumn.CreateForComparison( - "col_discount", /* IGNORED */ 0, /* compareDataType */ false)))); + "col_discount", 16518, /* compareDataType */ false), + PgOutputRelationMessageColumn.CreateForComparison("col_discount_array",16517)))); } else { // The replica identity for test_table in case of pgoutput is DEFAULT. add(PgOutputRelationMessage.CreateForComparison("public", "test_table", 'd', @@ -1043,10 +1053,14 @@ void replicationConnectionConsumptionAllDataTypes(String pluginName) throws Exce PgOutputRelationMessageColumn.CreateForComparison("col_tsrange", 3908), PgOutputRelationMessageColumn.CreateForComparison("col_tstzrange", 3910), PgOutputRelationMessageColumn.CreateForComparison("col_daterange", 3912), + // The Oids for columns below are not fixed. Changing the order of creation of + // objects (extensions, tables etc.) in the test will these Oids. + PgOutputRelationMessageColumn.CreateForComparison("col_hstore", 16385), PgOutputRelationMessageColumn.CreateForComparison( - "col_discount", /* IGNORED */ 0, /* compareDataType */ false)))); + "col_discount", 16518, /* compareDataType */ false), + PgOutputRelationMessageColumn.CreateForComparison("col_discount_array",16517)))); } - add(PgOutputInsertMessage.CreateForComparison(new PgOutputMessageTuple((short) 36, + add(PgOutputInsertMessage.CreateForComparison(new PgOutputMessageTuple((short) 38, Arrays.asList(new PgOutputMessageTupleColumnValue("1"), new PgOutputMessageTupleColumnValue("110110"), new PgOutputMessageTupleColumnValue("t"), @@ -1086,7 +1100,9 @@ void replicationConnectionConsumptionAllDataTypes(String pluginName) throws Exce convertTimestampToSystemTimezone("2024-01-01T00:00:00.00Z"), convertTimestampToSystemTimezone("2024-12-31T15:59:59.00Z"))), new PgOutputMessageTupleColumnValue("[2024-01-01,2024-12-31)"), - new PgOutputMessageTupleColumnValue("FIXED"))))); + new PgOutputMessageTupleColumnValue("\"key1\"=>\"value1\", \"key2\"=>\"value2\""), + new PgOutputMessageTupleColumnValue("FIXED"), + new PgOutputMessageTupleColumnValue("{FIXED,PERCENTAGE}"))))); add(PgOutputCommitMessage.CreateForComparison( LogSequenceNumber.valueOf("0/4"), LogSequenceNumber.valueOf("0/5"))); } diff --git a/src/postgres/src/backend/commands/ybccmds.c b/src/postgres/src/backend/commands/ybccmds.c index aa7a2b1076a8..7828231df919 100644 --- a/src/postgres/src/backend/commands/ybccmds.c +++ b/src/postgres/src/backend/commands/ybccmds.c @@ -2030,9 +2030,10 @@ YBCDestroyVirtualWalForCDC() void YBCGetCDCConsistentChanges(const char *stream_id, - YBCPgChangeRecordBatch **record_batch) + YBCPgChangeRecordBatch **record_batch, + YBCTypeEntityProvider type_entity_provider) { - HandleYBStatus(YBCPgGetCDCConsistentChanges(stream_id, record_batch)); + HandleYBStatus(YBCPgGetCDCConsistentChanges(stream_id, record_batch, type_entity_provider)); } void diff --git a/src/postgres/src/backend/replication/logical/yb_virtual_wal_client.c b/src/postgres/src/backend/replication/logical/yb_virtual_wal_client.c index e05ee247f8c1..31cacf0e6f81 100644 --- a/src/postgres/src/backend/replication/logical/yb_virtual_wal_client.c +++ b/src/postgres/src/backend/replication/logical/yb_virtual_wal_client.c @@ -26,6 +26,7 @@ #include #include "access/xact.h" +#include "catalog/yb_type.h" #include "commands/ybccmds.h" #include "pg_yb_utils.h" #include "replication/slot.h" @@ -240,6 +241,26 @@ InitVirtualWal(List *publication_names) list_free(tables); } +static const YBCPgTypeEntity * +GetDynamicTypeEntity(int attr_num, Oid relid) +{ + bool is_in_txn = IsTransactionOrTransactionBlock(); + if (!is_in_txn) + StartTransactionCommand(); + + Relation rel = RelationIdGetRelation(relid); + if (!RelationIsValid(rel)) + elog(ERROR, "Could not open relation with OID %u", relid); + Oid type_oid = GetTypeId(attr_num, RelationGetDescr(rel)); + RelationClose(rel); + const YBCPgTypeEntity* type_entity = YbDataTypeFromOidMod(attr_num, type_oid); + + if (!is_in_txn) + AbortCurrentTransaction(); + + return type_entity; +} + YBCPgVirtualWalRecord * YBCReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, List *publication_names, char **errormsg) @@ -293,7 +314,7 @@ YBCReadRecord(XLogReaderState *state, XLogRecPtr RecPtr, } YBCGetCDCConsistentChanges(MyReplicationSlot->data.yb_stream_id, - &cached_records); + &cached_records, &GetDynamicTypeEntity); cached_records_last_sent_row_idx = 0; YbWalSndTotalTimeInYBDecodeMicros = 0; diff --git a/src/postgres/src/include/commands/ybccmds.h b/src/postgres/src/include/commands/ybccmds.h index d243c2c96e18..17f1c8104231 100644 --- a/src/postgres/src/include/commands/ybccmds.h +++ b/src/postgres/src/include/commands/ybccmds.h @@ -143,7 +143,8 @@ extern void YBCUpdatePublicationTableList(const char *stream_id, extern void YBCDestroyVirtualWalForCDC(); extern void YBCGetCDCConsistentChanges(const char *stream_id, - YBCPgChangeRecordBatch **record_batch); + YBCPgChangeRecordBatch **record_batch, + YBCTypeEntityProvider type_entity_provider); extern void YBCUpdateAndPersistLSN(const char *stream_id, XLogRecPtr restart_lsn_hint, diff --git a/src/yb/cdc/cdcsdk_producer.cc b/src/yb/cdc/cdcsdk_producer.cc index b361ebfc16eb..629a854125c1 100644 --- a/src/yb/cdc/cdcsdk_producer.cc +++ b/src/yb/cdc/cdcsdk_producer.cc @@ -168,6 +168,7 @@ Status AddColumnToMap( // send NULL values to the walsender. This is needed to be able to differentiate between NULL // and Omitted values. if (request_source == CDCSDKRequestSource::WALSENDER) { + cdc_datum_message->set_col_attr_num(col_schema.order()); cdc_datum_message->set_column_type(col_schema.pg_type_oid()); cdc_datum_message->mutable_pg_ql_value()->CopyFrom(ql_value); return Status::OK(); diff --git a/src/yb/common/common.proto b/src/yb/common/common.proto index ffb7e59a729d..ee604b8ba84a 100644 --- a/src/yb/common/common.proto +++ b/src/yb/common/common.proto @@ -639,6 +639,8 @@ message DatumMessagePB { // The Walsender does the conversion from QLValuePB to the PG datum. QLValuePB pg_ql_value = 15; } + // This is applicable only for YSQL table columns. + optional int32 col_attr_num = 16; } message PgDatumPB { diff --git a/src/yb/integration-tests/cdcsdk_ysql-test.cc b/src/yb/integration-tests/cdcsdk_ysql-test.cc index bf61a34c2b0e..efec69bf592c 100644 --- a/src/yb/integration-tests/cdcsdk_ysql-test.cc +++ b/src/yb/integration-tests/cdcsdk_ysql-test.cc @@ -8611,19 +8611,6 @@ TEST_F(CDCSDKYsqlTest, TestNonEligibleTableShouldNotGetAddedToConsistentSnapshot TestNonEligibleTableShouldNotGetAddedToCDCStream(/* create_consistent_snapshot_stream */ true); } -TEST_F(CDCSDKYsqlTest, TestTablesWithEnumArrayColumnShouldNotGetAddedToStream) { - ASSERT_OK(SetUpWithParams(1, 1, false, false)); - auto conn = ASSERT_RESULT(test_cluster_.ConnectToDB(kNamespaceName)); - - ASSERT_OK(conn.Execute("CREATE TYPE \"enum_type\" AS ENUM('a', 'b', 'c');")); - ASSERT_OK(conn.Execute("CREATE TABLE test_table (a int primary key, b \"enum_type\"[])")); - auto stream_id = ASSERT_RESULT(CreateDBStream()); - - auto stream = ASSERT_RESULT(GetCDCStream(stream_id)); - // The table with enum array column will not be added to the stream. - ASSERT_EQ(stream.stream().table_id_size(), 0); -} - void CDCSDKYsqlTest::TestDisableOfDynamicTableAdditionOnCDCStream( bool use_consistent_snapshot_stream) { ANNOTATE_UNPROTECTED_WRITE(FLAGS_yb_enable_cdc_consistent_snapshot_streams) = diff --git a/src/yb/master/master_xrepl-test.cc b/src/yb/master/master_xrepl-test.cc index c3fbefb7de64..0f6a1966a3d1 100644 --- a/src/yb/master/master_xrepl-test.cc +++ b/src/yb/master/master_xrepl-test.cc @@ -556,7 +556,6 @@ TEST_F(MasterTestXRepl, YB_DISABLE_TEST_IN_TSAN(TestCDCStreamCreationWithOldReco CreateCDCStreamRequestPB req; CreateCDCStreamResponsePB resp; req.set_namespace_id(ns_id); - req.set_cdcsdk_ysql_replication_slot_name(kPgReplicationSlotName); AddKeyValueToCreateCDCStreamRequestOption(&req, cdc::kIdType, cdc::kNamespaceId); AddKeyValueToCreateCDCStreamRequestOption( &req, cdc::kSourceType, CDCRequestSource_Name(cdc::CDCRequestSource::CDCSDK)); @@ -574,7 +573,8 @@ TEST_F(MasterTestXRepl, TestCreateCDCStreamForNamespaceInvalidDuplicationSlotNam auto ns_id = create_namespace_resp.id(); for (auto i = 0; i < num_tables; ++i) { - ASSERT_OK(CreatePgsqlTable(ns_id, Format("cdc_table_$0", i), kTableIds[i], kTableSchema)); + ASSERT_OK( + CreatePgsqlTable(ns_id, Format("cdc_table_$0", i), kTableIds[i], kTableSchemaWithTypeOids)); } ASSERT_RESULT( @@ -644,7 +644,8 @@ TEST_F(MasterTestXRepl, TestCreateCDCStreamForNamespaceLimitReached) { auto ns_id = create_namespace_resp.id(); for (auto i = 0; i < num_tables; ++i) { - ASSERT_OK(CreatePgsqlTable(ns_id, Format("cdc_table_$0", i), kTableIds[i], kTableSchema)); + ASSERT_OK( + CreatePgsqlTable(ns_id, Format("cdc_table_$0", i), kTableIds[i], kTableSchemaWithTypeOids)); } ASSERT_RESULT( @@ -791,7 +792,8 @@ TEST_F(MasterTestXRepl, TestCreateDropCDCStreamWithReplicationSlotName) { ASSERT_OK(CreatePgsqlNamespace(kNamespaceName, kPgsqlNamespaceId, &create_namespace_resp)); auto ns_id = create_namespace_resp.id(); for (auto i = 0; i < num_tables; ++i) { - ASSERT_OK(CreatePgsqlTable(ns_id, Format("cdc_table_$0", i), kTableIds[i], kTableSchema)); + ASSERT_OK( + CreatePgsqlTable(ns_id, Format("cdc_table_$0", i), kTableIds[i], kTableSchemaWithTypeOids)); } // Create and Delete CDC stream with replication slot name in quick succession. @@ -845,9 +847,9 @@ TEST_F(MasterTestXRepl, TestListCDCStreamsCDCSDKWithReplicationSlot) { auto ns_id2 = create_namespace_resp.id(); // 2 tables in cdc_namespace and 1 table in cdc_namespace2 - ASSERT_OK(CreatePgsqlTable(ns_id, "cdc_table_1", kTableIds[0], kTableSchema)); - ASSERT_OK(CreatePgsqlTable(ns_id, "cdc_table_2", kTableIds[1], kTableSchema)); - ASSERT_OK(CreatePgsqlTable(ns_id2, "cdc_table_3", kTableIds[2], kTableSchema)); + ASSERT_OK(CreatePgsqlTable(ns_id, "cdc_table_1", kTableIds[0], kTableSchemaWithTypeOids)); + ASSERT_OK(CreatePgsqlTable(ns_id, "cdc_table_2", kTableIds[1], kTableSchemaWithTypeOids)); + ASSERT_OK(CreatePgsqlTable(ns_id2, "cdc_table_3", kTableIds[2], kTableSchemaWithTypeOids)); auto stream_id = ASSERT_RESULT( CreateCDCStreamForNamespace(ns_id, kPgReplicationSlotName, kPgReplicationSlotPgOutput)); @@ -1134,7 +1136,8 @@ TEST_F(MasterTestXRepl, DropNamespaceWithLiveCDCStream) { auto ns_id = create_namespace_resp.id(); for (auto i = 0; i < num_tables; ++i) { - ASSERT_OK(CreatePgsqlTable(ns_id, Format("cdc_table_$0", i), kTableIds[i], kTableSchema)); + ASSERT_OK( + CreatePgsqlTable(ns_id, Format("cdc_table_$0", i), kTableIds[i], kTableSchemaWithTypeOids)); } ASSERT_RESULT( CreateCDCStreamForNamespace(ns_id, kPgReplicationSlotName, kPgReplicationSlotPgOutput)); diff --git a/src/yb/master/xrepl_catalog_manager.cc b/src/yb/master/xrepl_catalog_manager.cc index 40f079ba43f3..b23dd192fdd7 100644 --- a/src/yb/master/xrepl_catalog_manager.cc +++ b/src/yb/master/xrepl_catalog_manager.cc @@ -1930,7 +1930,6 @@ bool CatalogManager::IsTableEligibleForCDCSDKStream( const TableInfoPtr& table_info, const std::optional& schema) const { if (schema.has_value()) { bool has_pk = true; - bool has_invalid_pg_typeoid = false; for (const auto& col : schema->columns()) { if (col.order() == static_cast(PgSystemAttrNum::kYBRowId)) { // ybrowid column is added for tables that don't have user-specified primary key. @@ -1939,19 +1938,16 @@ bool CatalogManager::IsTableEligibleForCDCSDKStream( has_pk = false; break; } - if (col.pg_type_oid() == 0) { - has_invalid_pg_typeoid = true; - } } - if (!has_pk || has_invalid_pg_typeoid) { - if (FLAGS_TEST_cdcsdk_add_indexes_to_stream) { - // allow adding user created indexes to CDC stream. - if (IsUserIndexUnlocked(*table_info)) { - return true; - } - } + + if (!has_pk) { return false; } + + // Allow adding user created indexes to CDC stream. + if (FLAGS_TEST_cdcsdk_add_indexes_to_stream && IsUserIndexUnlocked(*table_info)) { + return true; + } } if (IsMatviewTable(*table_info)) { diff --git a/src/yb/yql/pggate/ybc_pggate.cc b/src/yb/yql/pggate/ybc_pggate.cc index 556df16c6cba..ea0432602194 100644 --- a/src/yb/yql/pggate/ybc_pggate.cc +++ b/src/yb/yql/pggate/ybc_pggate.cc @@ -400,6 +400,14 @@ std::string HumanReadableTableType(yb::TableType table_type) { FATAL_INVALID_ENUM_VALUE(yb::TableType, table_type); } +const YBCPgTypeEntity* GetTypeEntity( + int pg_type_oid, int attr_num, YBCPgOid table_oid, YBCTypeEntityProvider type_entity_provider) { + + // TODO(23239): Optimize the lookup of type entities for dynamic types. + return pg_type_oid == kPgInvalidOid ? (*type_entity_provider)(attr_num, table_oid) + : pgapi->FindTypeEntity(pg_type_oid); +} + } // namespace //-------------------------------------------------------------------------------------------------- @@ -2425,7 +2433,9 @@ YBCPgRowMessageAction GetRowMessageAction(yb::cdc::RowMessage row_message_pb) { } YBCStatus YBCPgGetCDCConsistentChanges( - const char *stream_id, YBCPgChangeRecordBatch **record_batch) { + const char* stream_id, + YBCPgChangeRecordBatch** record_batch, + YBCTypeEntityProvider type_entity_provider) { const auto result = pgapi->GetConsistentChangesForCDC(std::string(stream_id)); if (!result.ok()) { return ToYBCStatus(result.status()); @@ -2484,6 +2494,10 @@ YBCStatus YBCPgGetCDCConsistentChanges( old_tuple_idx++; } + const auto table_oid = row_message_pb.has_table_id() + ? PgObjectId(row_message_pb.table_id()).object_oid + : kPgInvalidOid; + auto col_count = narrow_cast(col_name_idx_map.size()); YBCPgDatumMessage *cols = nullptr; if (col_count > 0) { @@ -2501,8 +2515,10 @@ YBCStatus YBCPgGetCDCConsistentChanges( if (!before_op_is_omitted) { const auto old_datum_pb = &row_message_pb.old_tuple(static_cast(col_idxs.second.first)); - const auto *type_entity = - pgapi->FindTypeEntity(static_cast(old_datum_pb->column_type())); + DCHECK(table_oid != kPgInvalidOid); + const auto* type_entity = GetTypeEntity( + static_cast(old_datum_pb->column_type()), old_datum_pb->col_attr_num(), + table_oid, type_entity_provider); auto s = PBToDatum( type_entity, type_attrs, old_datum_pb->pg_ql_value(), &before_op_datum, &before_op_is_null); @@ -2518,8 +2534,10 @@ YBCStatus YBCPgGetCDCConsistentChanges( if (!after_op_is_omitted) { const auto new_datum_pb = &row_message_pb.new_tuple(static_cast(col_idxs.second.second)); - const auto *type_entity = - pgapi->FindTypeEntity(static_cast(new_datum_pb->column_type())); + DCHECK(table_oid != kPgInvalidOid); + const auto* type_entity = GetTypeEntity( + static_cast(new_datum_pb->column_type()), new_datum_pb->col_attr_num(), + table_oid, type_entity_provider); auto s = PBToDatum( type_entity, type_attrs, new_datum_pb->pg_ql_value(), &after_op_datum, &after_op_is_null); @@ -2539,12 +2557,6 @@ YBCStatus YBCPgGetCDCConsistentChanges( } } - // Only present for DML records. - YBCPgOid table_oid = kPgInvalidOid; - if (row_message_pb.has_table_id()) { - auto table_id = PgObjectId(row_message_pb.table_id()); - table_oid = table_id.object_oid; - } new (&resp_rows[row_idx]) YBCPgRowMessage{ .col_count = col_count, diff --git a/src/yb/yql/pggate/ybc_pggate.h b/src/yb/yql/pggate/ybc_pggate.h index 12cf3c7f837d..fd082df1f87d 100644 --- a/src/yb/yql/pggate/ybc_pggate.h +++ b/src/yb/yql/pggate/ybc_pggate.h @@ -25,6 +25,7 @@ extern "C" { typedef void (*YBCAshAcquireBufferLock)(bool); typedef YBCAshSample* (*YBCAshGetNextCircularBufferSlot)(); +typedef const YBCPgTypeEntity* (*YBCTypeEntityProvider)(int, YBCPgOid); typedef void * SliceVector; typedef const void * ConstSliceVector; @@ -877,7 +878,8 @@ YBCStatus YBCPgUpdatePublicationTableList( YBCStatus YBCPgDestroyVirtualWalForCDC(); YBCStatus YBCPgGetCDCConsistentChanges(const char *stream_id, - YBCPgChangeRecordBatch **record_batch); + YBCPgChangeRecordBatch **record_batch, + YBCTypeEntityProvider type_entity_provider); YBCStatus YBCPgUpdateAndPersistLSN( const char* stream_id, YBCPgXLogRecPtr restart_lsn_hint, YBCPgXLogRecPtr confirmed_flush,